Setup

library(ithi.utils)
load_base_libs()

library(methods)
library(factoextra)
library(NbClust)
library(ComplexHeatmap)
library(cluster)
library(entropy)
library(mclust)

library(ithi.meta)
library(ithi.figures)
library(ithi.utils)
ihc_table_path <- snakemake@input$ihc_table
molecular_subtype_file <- snakemake@input$molsubtypes
ipynb_path <- snakemake@input$ipynb_path

## For python
til_clusters_output <- snakemake@params$til_clusters_output
ihc_features_output <- snakemake@params$ihc_features_output

tils_for_cluster <- snakemake@params$tils_for_cluster
db_path <- snakemake@params$db
annotation_colours <- ithi.figures::get_annotation_colours()
ihc_table <- fread(ihc_table_path)
molsubtypes <- fread(molecular_subtype_file)

ipynb_base_path <- basename(ipynb_path)

til_clusters <- ithi.figures:::get_til_clusters(ihc_table, molsubtypes, tils_for_cluster = tils_for_cluster, 
    nclusts = 3)

Analysis

This reviewer comment wants us to address the issue that – like HGSC molecular subtype assignments – TIL subtype assignments may overlap in some cases. In other words, how likely is it that a sample would be assigned to more than one TIL subtype?

In the case of transcriptional subtypes, this type of analysis was done in the cited papers (e.g. Verhaak et al.) with ssGSEA. This makes sense for expression data, where gene signatures can be defined for each molecular subtype. In the case of these TIL subtypes, however, this isn’t nearly as relevant – we aren’t looking at thousands of features (rather, we’re only looking at 8) – so defining signatures is tough.

There are a few approaches I can think of to address this:

  • Post-hoc cluster assessment, e.g. with the silhouette method
  • Unsupervised clustering
  • Supervised clustering

Post-hoc assessment of clusters: silhouette method

tilheat <- ithi.figures::plot_til_density_heatmap(ihc_table, tils_for_cluster, 
    3, molsubtypes, annotation_colours)
res.hc <- as.hclust(ComplexHeatmap::column_dend(tilheat))
fviz_dend(res.hc, palette = "jco", rect = TRUE, show_labels = FALSE)

ihc_data <- subset(ihc_table, select = c("condensed_id", tils_for_cluster))
valid_rows <- apply(ihc_data, 1, function(x) all(!is.na(x)))
ihc_data <- as.data.frame(ihc_data[valid_rows, ])

ihc_data <- ihc_data %>% plyr::rename(c(E_CD8_density = "Epithelial CD8+", E_CD4_density = "Epithelial CD4+", 
    E_CD20_density = "Epithelial CD20+", E_Plasma_density = "Epithelial Plasma", 
    S_CD8_density = "Stromal CD8+", S_CD4_density = "Stromal CD4+", S_CD20_density = "Stromal CD20+", 
    S_Plasma_density = "Stromal Plasma"))
ihc_mat <- ihc_data %>% tibble::column_to_rownames(var = "condensed_id")
ihc_mat_scaled <- scale(ihc_mat)

ihc_mat_scaled <- ithi.utils::clip_values(ihc_mat_scaled, hi = 2, lo = -2)

sil.hc <- cluster::silhouette(cutree(res.hc, 3), dist(ihc_mat_scaled, method = "euclidean"))
til_clusters_table <- table(til_clusters$cluster)

til_clusters_table

 N-TIL  S-TIL ES-TIL 
    47     30     34 
plot(sil.hc)

So all N-TILs are confidently clustered, whereas some S-TIL and ES-TIL are not as confidently clustered according to the silhouette.

sil.hc.mat <- sil.hc
class(sil.hc.mat) <- "matrix"
sil_confusion_mat <- sil.hc.mat %>% as.data.frame %>% subset(sil_width < 0)
sil_confusion_table <- table(sil_confusion_mat$cluster)

So 11 out of 30 are negative silhouette, and 7 out of NA are negative silhouette. The neighbour cluster in all but one case is N-TIL (in one case, it’s S-TIL).

In total, the fraction of negative silhouette samples is:

sum(sil_confusion_table)/sum(til_clusters_table)
[1] 0.1621622

which is pretty good. But this rate is quite high in S-TIL.

Unsupervised clustering

Fuzzy clustering

We’ll do fuzzy c-means and see how well that recapitulates the hierarchical clustering results.

res.fanny <- fanny(ihc_mat_scaled, k = 3, metric = "euclidean", stand = FALSE)
fanny_clusters <- data.frame(res.fanny$clustering) %>% rownames_to_column(var = "condensed_id") %>% 
    plyr::rename(c(res.fanny.clustering = "fanny_cluster"))
fanny_clusters$fanny_cluster <- fanny_clusters$fanny_cluster %>% plyr::mapvalues(from = c(1:3), 
    to = c("N-TIL", "S-TIL", "ES-TIL"))

hc_fanny_clusters <- til_clusters %>% plyr::join(fanny_clusters)
with(hc_fanny_clusters, table(cluster, fanny_cluster))
        fanny_cluster
cluster  ES-TIL N-TIL S-TIL
  N-TIL       0    47     0
  S-TIL       9     4    17
  ES-TIL     31     0     3
with(hc_fanny_clusters, adjustedRandIndex(cluster, fanny_cluster))
[1] 0.6893836
with(hc_fanny_clusters, mi.empirical(table(cluster, fanny_cluster)))
[1] 0.6852884

So generally we’re doing pretty well on the consistency of our clustering between hierarchical and fuzzy c-means clustering.

fviz_cluster(res.fanny, ellipse.type = "norm", repel = TRUE, palette = "jco", 
    ggtheme = theme_minimal(), legend = "right")

fviz_silhouette(res.fanny, palette = "jco", ggtheme = theme_minimal())
  cluster size ave.sil.width
1       1   51          0.59
2       2   20          0.09
3       3   40         -0.02

The silhouette plot looks similar, albeit quite a bit worse for ES-TIL.

Let’s now plot the clustering probabilities.

pheatmap(res.fanny$membership, show_rownames = FALSE)

What this means is that fuzzy c-means is doing a terrible job distinguishing S-TIL from ES-TIL.

I think the silhouette stuff is probably sufficient to address this comment, but what do you guys think? The fuzzy c-means clustering is bad. Alternatively I can look into using NMF, etc, since the data is non-negative.

NMF

When I have time? Worth doing?

Supervised clustering

We can alternatively look at supervised methods – if we work from the known TIL clusters, can we distinguish N/S/ES-TIL with methods that either assign cluster membership probabilities, or with methods that can designate multiple labels (multilabel classification)?

samples_intersect <- intersect(rownames(ihc_mat_scaled), til_clusters$condensed_id)

X <- ihc_mat_scaled[samples_intersect, ]
y <- (til_clusters %>% column_to_rownames(var = "condensed_id"))[samples_intersect, 
    ]

write.table(X, file = ihc_features_output, sep = "\t", quote = FALSE, row.names = FALSE, 
    col.names = TRUE)
write.table(data.frame(cluster = as.numeric(y)), file = til_clusters_output, 
    sep = "\t", quote = FALSE, row.names = FALSE, col.names = TRUE)

Now, look in the iPython notebook for the results of this stuff.

Supervised clustering analysis

The summary of this analysis was that, with a simple multinomial-objective logistic regression model and 10-fold cross-validation, we achieve 100% validation accuracy. Furthermore, 80% of samples are classified with >80% probabillity into the correct cluster. Consistent with our other results above, S-TIL is the most difficult to cluster to call, with only 63% of samples classified with >80% probability. However, 86% of S-TIL samples can be correctly classified with >50% probability.

Summary

16% have negative silhouette scores (some S-TIL that could also be assigned to N-TIL is the most common misclassification). This is comparable to the % of negative silhouette scores for TCGA molecular subtypes (~17% or so). The conclusion from Roel’s paper was:

“The overlap in gene signature scores suggests that HGS-OvCa does not consist of mutually exclusive expression subtypes, but that each tumor sample is represented by multiple signatures at different levels of activation.”

Unsupervised fuzzy clustering methods, like fuzzy c-means, do poorly and are unable to distinguish S-TIL and ES-TIL at all. This is not necessarily surprising, as these are entirely different clustering methods that do not know the assigned true labels.

However, we show that with supervised clustering methods, more specifically logistic regression, we can get very competitive accuracy and confidence rates for our classes.

LS0tCnRpdGxlOiAiVElMIGNsdXN0ZXJzOiBtdWx0aXBsZSBhc3NpZ25tZW50cyIKLS0tCiAgICAgICAgICAgICAgICAgICAgICAgIGBgYHtyLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyMjIyMjIyMgU25ha2VtYWtlIGhlYWRlciAjIyMjIyMjIwpsaWJyYXJ5KG1ldGhvZHMpClNuYWtlbWFrZSA8LSBzZXRDbGFzcygKICAgICJTbmFrZW1ha2UiLAogICAgc2xvdHMgPSBjKAogICAgICAgIGlucHV0ID0gImxpc3QiLAogICAgICAgIG91dHB1dCA9ICJsaXN0IiwKICAgICAgICBwYXJhbXMgPSAibGlzdCIsCiAgICAgICAgd2lsZGNhcmRzID0gImxpc3QiLAogICAgICAgIHRocmVhZHMgPSAibnVtZXJpYyIsCiAgICAgICAgbG9nID0gImxpc3QiLAogICAgICAgIHJlc291cmNlcyA9ICJsaXN0IiwKICAgICAgICBjb25maWcgPSAibGlzdCIsCiAgICAgICAgcnVsZSA9ICJjaGFyYWN0ZXIiCiAgICApCikKc25ha2VtYWtlIDwtIFNuYWtlbWFrZSgKICAgIGlucHV0ID0gbGlzdCgnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L25vdGVib29rcy9ydW4yL3RpbGNsdXN0ZXJfc3VwZXJ2aXNlZGFzc2lnbm1lbnRzLmh0bWwnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgJ25vdGVib29rcy90aWxjbHVzdGVyX211bHRpcGxlYXNzaWdubWVudHMuUm1kJywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2loY190YWJsZS50c3YnLCAiaXB5bmJfcGF0aCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L25vdGVib29rcy9ydW4yL3RpbGNsdXN0ZXJfc3VwZXJ2aXNlZGFzc2lnbm1lbnRzLmh0bWwnLCAibW9sc3VidHlwZXMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL21vbHN1YnR5cGVzLnRzdicsICJub3RlYm9vayIgPSAnbm90ZWJvb2tzL3RpbGNsdXN0ZXJfbXVsdGlwbGVhc3NpZ25tZW50cy5SbWQnLCAiaWhjX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9paGNfdGFibGUudHN2JyksCiAgICBvdXRwdXQgPSBsaXN0KCcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9yZXZpZXcvbm90ZWJvb2tzL3J1bjIvdGlsY2x1c3Rlcl9tdWx0aXBsZWFzc2lnbm1lbnRzLm5iLmh0bWwnKSwKICAgIHBhcmFtcyA9IGxpc3QoYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL2ludGVybWVkaWF0ZXMvcnVuMi90aWxfY2x1c3RlcnNfb3V0cHV0LnR4dCcsICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycsICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9pbnRlcm1lZGlhdGVzL3J1bjIvaWhjX2ZlYXR1cmVzX291dHB1dC50eHQnLCAndGlsY2x1c3Rlcl9tdWx0aXBsZWFzc2lnbm1lbnRzX2FuYWx5c2lzJywgIm5hbWUiID0gJ3RpbGNsdXN0ZXJfbXVsdGlwbGVhc3NpZ25tZW50c19hbmFseXNpcycsICJkYiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL21ldGFkYXRhL2RiL2ltbXVuZV9wcm9qZWN0LnNxbGl0ZTMnLCAidGlsX2NsdXN0ZXJzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL3RpbF9jbHVzdGVyc19vdXRwdXQudHh0JywgInRpbHNfZm9yX2NsdXN0ZXIiID0gYygnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgImloY19mZWF0dXJlc19vdXRwdXQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL2ludGVybWVkaWF0ZXMvcnVuMi9paGNfZmVhdHVyZXNfb3V0cHV0LnR4dCcpLAogICAgd2lsZGNhcmRzID0gbGlzdCgpLAogICAgdGhyZWFkcyA9IDEsCiAgICBsb2cgPSBsaXN0KCcvc2hhaGxhYi9hbHpoYW5nL2NsdXN0dG1wL3BhcGVycmV2aWV3Mi9ub3RlYm9va3MvdGlsY2x1c3Rlcl9tdWx0aXBsZWFzc2lnbm1lbnRzX2FuYWx5c2lzLmxvZycpLAogICAgcmVzb3VyY2VzID0gbGlzdCgpLAogICAgY29uZmlnID0gbGlzdCgibG9nZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2NsdXN0dG1wL3BhcGVycmV2aWV3MicsICJ0aWxjbHVzdGVyX3N1cGVydmlzZWRfaXB5bmIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXZpZXcvaXB5L3RpbGNsdXN0ZXJfc3VwZXJ2aXNlZG11bHRpY2xhc3MuaXB5bmInLCAicGF0aWVudHNfZm9yX2Nsb25hbCIgPSBjKDEsIDIsIDMsIDQsIDcsIDksIDEwLCAxMSwgMTIsIDEzLCAxNCwgMTUsIDE2LCAxNyksICJmaW5uaGVfcGlwZWxpbmVfcmVzdWx0c19kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Zpbm5oZS9ydW4xJywgInJlbWl4dF9jZWxsdWxhcml0eV9wbG9pZHkiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL3JlbWl4dF9jZWxsdWxhcml0eV9wbG9pZHkudHN2JywgImltYWdlX3N1bW1hcnkyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS95dWFuX2hlY3JfaW1hZ2VfcmVzdWx0c18yLmNzdicsICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgIml0aF9pY2djX2JjIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9pdGhfaWNnY19tZXJnZWRfYmMudHN2JywgIm5lb2VkaXRpbmdfb3V0ZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9uZW9lZGl0aW5nL3J1bjYnLCAiaXRoX3N0YXRfdHlwZXMiID0gYygnZW50cm9weScsICdwb3N0cHJvY2Vzc2VkX2RpdmVyZ2VuY2UnLCAnY29tYmluZWRfaXRoX25vcm1hbGl6ZWQnLCAncHJvcG9ydGlvbl9zdWJjbG9uYWwnKSwgImltYWdlX3N1bW1hcnkiID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9pdGhpL3l1YW5faGVjcl9pbWFnZV9yZXN1bHRzLmNzdicsICJ0aWxzX2Zvcl9jbHVzdGVyIiA9IGMoJ0VfQ0Q4X2RlbnNpdHknLCAnRV9DRDRfZGVuc2l0eScsICdFX0NEMjBfZGVuc2l0eScsICdFX1BsYXNtYV9kZW5zaXR5JywgJ1NfQ0Q4X2RlbnNpdHknLCAnU19DRDRfZGVuc2l0eScsICdTX0NEMjBfZGVuc2l0eScsICdTX1BsYXNtYV9kZW5zaXR5JyksICJub3RlYm9va19kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3Jldmlldy9ub3RlYm9va3MvcnVuMicsICJ0dW1vdXJfcHVyaXR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi90dW1vdXJfcHVyaXR5LnRzdicsICJzbnZfY2x1c3Rlcl9kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy9zbnZfY2x1c3RlcicsICJoZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkvZmlubl9yZXN1bHRzL2hlX291dHB1dF9Ob3YyOScsICJpY2djX3N1YnR5cGVzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvSUNHQy9pY2djX3ByaW1hcnlfdHVtb3VyX3N1YnR5cGVzLnRzdicsICJzbnZfdGFibGUiID0gJy9zaGFobGFiL2FtY3BoZXJzb24vcHJvamVjdHMvaXRoMy9pdGgzL25vdGVib29rcy9iZXNwb2tlL2l0aF9zbnZzLnRzdicsICJjbG9sYV9yZXN1bHRfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvY2xvbGEvcnVuNC9jbG9sYV9jb25kZW5zZWRfcmVzdWx0cy9iZXRhL2Nsb2xhX3Jlc3VsdHMudHN2JywgIm5hbm9zdHJpbmdfYW5ub3RhdGlvbnMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9kYXRhL2V4cHJlc3Npb24vbmFub3N0cmluZy9wYW5jYW5jZXJfYW5ub3RhdGlvbnMudHN2JywgImRpc3RhbmNlX21ldGhvZCIgPSAnaG9ybicsICJzb21hdGljX2NvZGluZ19yZXN1bHRfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9zb21hdGljX2NvZGluZ192YXJpYW50cycsICJtbWN0bV9maW5hbF9wYXRpZW50X2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3Jlc3VsdHMvbW1jdG1fcmVzdWx0cy9pdGhfYnktcGF0aWVudF93aXRoLW92JywgImtub3duX3N1YnR5cGVzX2FycmF5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZGF0YS9leHByZXNzaW9uL2FycmF5L3N1YnR5cGVzL2tub3duX3N1YnR5cGVzLnRzdicsICJpaGNfdGFibGUiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2loY190YWJsZS50c3YnLCAicmVmc2VxX2dlbmVfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2dlbm9tZS9oZzE5L3JlZnNlcV9nZW5lcy5iZWQnLCAidGlsc19mb3JfdmFyaWFiaWxpdHkiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknKSwgInRvdGFsX3RpbHR5cGVzIiA9IGMoJ1RfQ0Q4X2RlbnNpdHknLCAnVF9DRDRfZGVuc2l0eScsICdUX0NEMjBfZGVuc2l0eScsICdUX1BsYXNtYV9kZW5zaXR5JyksICJkYiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL21ldGFkYXRhL2RiL2ltbXVuZV9wcm9qZWN0LnNxbGl0ZTMnLCAidmFyaWFiaWxpdHlfdHlwZSIgPSAnc3RhYmlsaXplJywgImJjcl9kaXZlcnNpdHkiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL21peGNyL21peGNyX3J1bnMvaXRoXzFfMl8zL21peGNyNS9wb3N0cHJvY2Vzcy9JR0gvcG9zdGZpbHRlcl9kaXZlcnNpdHlfc3RhdHMvZGl2ZXJzaXR5LnN0cmljdC5yZXNhbXBsZWQudHh0JywgInRpbF9jbHVzdGVyc19vdXRwdXQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL2ludGVybWVkaWF0ZXMvcnVuMi90aWxfY2x1c3RlcnNfb3V0cHV0LnR4dCcsICJpdGhfc3RhdHMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2l0aF9zdGF0aXN0aWNzLnRzdicsICJjbG9uZV9icmFuY2hfbGVuZ3RocyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL2JyYW5jaF9kYXRhLnRzdicsICJ0YWJsZV9kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3Jldmlldy90YWJsZXMvcnVuMicsICJiZW5jaG1hcmtkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvYmVuY2htYXJrcy9wYXBlcnJldmlldzInLCAiY2xvbmVfcHJldmFsZW5jZXMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy9jbG9uZV9kYXRhLnRzdicsICJ0Y3JfZGl2ZXJzaXR5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9taXhjci9taXhjcl9ydW5zL2l0aF8xXzJfMy9taXhjcjUvcG9zdHByb2Nlc3MvVFJCL3Bvc3RmaWx0ZXJfZGl2ZXJzaXR5X3N0YXRzL2RpdmVyc2l0eS5zdHJpY3QucmVzYW1wbGVkLnR4dCcsICJjbG9uZV90cmVlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL3RyZWVfZGF0YS50c3YnLCAiaWdwYXJ0aXRpb25fb3V0ZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3BpcGVsaW5lX291dHB1dHMvaXRoX2ltbXVuZS9pZ3BhcnRpdGlvbi9ydW4yMicsICJyb29uZXlfbXV0c2lnY3ZfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2V4dGVybmFsL290aGVyX3BhcGVycy9tbWM2Lnhsc3gnLCAiZXBpdG9wZXNfdW5pcXVlX2ZpbHRlcmVkIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9lcGl0b3Blc191bmlxdWVfZmlsdGVyZWQudHN2JywgImFycmF5X2V4cHJlc3Npb25fZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9hcnJheS9nZW5lX2V4cHJzX3JtYV9iYXRjaF9jb3JyZWN0ZWQudHh0JywgInhjcl90YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIveGNyX3RhYmxlLnRzdicsICJhbGxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknLCAnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgImljZ2Nfc3BlY2ltZW4iID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9JQ0dDL3NwZWNpbWVuLnRzdicsICJjb3B5bnVtYmVyX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS9tYXN0ZXJfY29weW51bWJlcl9maWxlLnRzdicsICJwcmV2YWxlbmNlX3RocmVzaG9sZCIgPSAwLjAxLCAiaWhjX2ZlYXR1cmVzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL2loY19mZWF0dXJlc19vdXRwdXQudHh0JywgIm5hbm9zdHJpbmdfZGF0YSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3Jlc3VsdHMvbmFub3N0cmluZ19yZXN1bHRzL2l0aF9mdWxsL3FjL2xpbW1hX3F1YW50aWxlL25vcm1hbGl6ZWRfZXhwcmVzc2lvbl92b2FfbGFiZWxzX2ZpbHRlcmVkLnRzdicsICJicmVha3BvaW50X3RhYmxlIiA9ICcvc2hhaGxhYi9hbWNwaGVyc29uL3Byb2plY3RzL2l0aDMvaXRoMy9ub3RlYm9va3MvYmVzcG9rZS9pdGhfYnJlYWtwb2ludHMudHN2JyksCiAgICBydWxlID0gJ3RpbGNsdXN0ZXJfbXVsdGlwbGVhc3NpZ25tZW50c19hbmFseXNpcycKKQojIyMjIyMjIyBPcmlnaW5hbCBzY3JpcHQgIyMjIyMjIyMjCgogICAgICAgICAgICAgICAgICAgICAgICBgYGAKCgojIyBTZXR1cAoKYGBge3IgZ2xvYmFsX2NodW5rX29wdGlvbnMsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgdGlkeT1UUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFKSAjY2FjaGU9VFJVRQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGl0aGkudXRpbHMpCmxvYWRfYmFzZV9saWJzKCkKCmxpYnJhcnkobWV0aG9kcykKbGlicmFyeShmYWN0b2V4dHJhKQpsaWJyYXJ5KE5iQ2x1c3QpCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoY2x1c3RlcikKbGlicmFyeShlbnRyb3B5KQpsaWJyYXJ5KG1jbHVzdCkKCmxpYnJhcnkoaXRoaS5tZXRhKQpsaWJyYXJ5KGl0aGkuZmlndXJlcykKbGlicmFyeShpdGhpLnV0aWxzKQpgYGAKCmBgYHtyfQppaGNfdGFibGVfcGF0aCA8LSBzbmFrZW1ha2VAaW5wdXQkaWhjX3RhYmxlCm1vbGVjdWxhcl9zdWJ0eXBlX2ZpbGUgPC0gc25ha2VtYWtlQGlucHV0JG1vbHN1YnR5cGVzCmlweW5iX3BhdGggPC0gc25ha2VtYWtlQGlucHV0JGlweW5iX3BhdGgKCiMjIEZvciBweXRob24KdGlsX2NsdXN0ZXJzX291dHB1dCA8LSBzbmFrZW1ha2VAcGFyYW1zJHRpbF9jbHVzdGVyc19vdXRwdXQKaWhjX2ZlYXR1cmVzX291dHB1dCA8LSBzbmFrZW1ha2VAcGFyYW1zJGloY19mZWF0dXJlc19vdXRwdXQKCnRpbHNfZm9yX2NsdXN0ZXIgPC0gc25ha2VtYWtlQHBhcmFtcyR0aWxzX2Zvcl9jbHVzdGVyCmRiX3BhdGggPC0gc25ha2VtYWtlQHBhcmFtcyRkYgpgYGAKCmBgYHtyfQphbm5vdGF0aW9uX2NvbG91cnMgPC0gaXRoaS5maWd1cmVzOjpnZXRfYW5ub3RhdGlvbl9jb2xvdXJzKCkKaWhjX3RhYmxlIDwtIGZyZWFkKGloY190YWJsZV9wYXRoKQptb2xzdWJ0eXBlcyA8LSBmcmVhZChtb2xlY3VsYXJfc3VidHlwZV9maWxlKQoKaXB5bmJfYmFzZV9wYXRoIDwtIGJhc2VuYW1lKGlweW5iX3BhdGgpCgp0aWxfY2x1c3RlcnMgPC0gaXRoaS5maWd1cmVzOjo6Z2V0X3RpbF9jbHVzdGVycyhpaGNfdGFibGUsIG1vbHN1YnR5cGVzLCB0aWxzX2Zvcl9jbHVzdGVyID0gdGlsc19mb3JfY2x1c3RlciwgbmNsdXN0cyA9IDMpCmBgYAoKCiMjIEFuYWx5c2lzCgpUaGlzIHJldmlld2VyIGNvbW1lbnQgd2FudHMgdXMgdG8gYWRkcmVzcyB0aGUgaXNzdWUgdGhhdCAtLSBsaWtlIEhHU0MgbW9sZWN1bGFyIHN1YnR5cGUgYXNzaWdubWVudHMgLS0gVElMIHN1YnR5cGUgYXNzaWdubWVudHMgbWF5IG92ZXJsYXAgaW4gc29tZSBjYXNlcy4gSW4gb3RoZXIgd29yZHMsIGhvdyBsaWtlbHkgaXMgaXQgdGhhdCBhIHNhbXBsZSB3b3VsZCBiZSBhc3NpZ25lZCB0byBtb3JlIHRoYW4gb25lIFRJTCBzdWJ0eXBlPyAKCkluIHRoZSBjYXNlIG9mIHRyYW5zY3JpcHRpb25hbCBzdWJ0eXBlcywgdGhpcyB0eXBlIG9mIGFuYWx5c2lzIHdhcyBkb25lIGluIHRoZSBjaXRlZCBwYXBlcnMgKGUuZy4gVmVyaGFhayBldCBhbC4pIHdpdGggc3NHU0VBLiBUaGlzIG1ha2VzIHNlbnNlIGZvciBleHByZXNzaW9uIGRhdGEsIHdoZXJlIGdlbmUgc2lnbmF0dXJlcyBjYW4gYmUgZGVmaW5lZCBmb3IgZWFjaCBtb2xlY3VsYXIgc3VidHlwZS4gSW4gdGhlIGNhc2Ugb2YgdGhlc2UgVElMIHN1YnR5cGVzLCBob3dldmVyLCB0aGlzIGlzbid0IG5lYXJseSBhcyByZWxldmFudCAtLSB3ZSBhcmVuJ3QgbG9va2luZyBhdCB0aG91c2FuZHMgb2YgZmVhdHVyZXMgKHJhdGhlciwgd2UncmUgb25seSBsb29raW5nIGF0IDgpIC0tIHNvIGRlZmluaW5nIHNpZ25hdHVyZXMgaXMgdG91Z2guIAoKVGhlcmUgYXJlIGEgZmV3IGFwcHJvYWNoZXMgSSBjYW4gdGhpbmsgb2YgdG8gYWRkcmVzcyB0aGlzOgoKKiBQb3N0LWhvYyBjbHVzdGVyIGFzc2Vzc21lbnQsIGUuZy4gd2l0aCB0aGUgc2lsaG91ZXR0ZSBtZXRob2QKKiBVbnN1cGVydmlzZWQgY2x1c3RlcmluZwoqIFN1cGVydmlzZWQgY2x1c3RlcmluZwoKCiMjIyBQb3N0LWhvYyBhc3Nlc3NtZW50IG9mIGNsdXN0ZXJzOiBzaWxob3VldHRlIG1ldGhvZAoKYGBge3J9CnRpbGhlYXQgPC0gaXRoaS5maWd1cmVzOjpwbG90X3RpbF9kZW5zaXR5X2hlYXRtYXAoaWhjX3RhYmxlLCB0aWxzX2Zvcl9jbHVzdGVyLCAzLCBtb2xzdWJ0eXBlcywgYW5ub3RhdGlvbl9jb2xvdXJzKQpyZXMuaGMgPC0gYXMuaGNsdXN0KENvbXBsZXhIZWF0bWFwOjpjb2x1bW5fZGVuZCh0aWxoZWF0KSkKYGBgCgpgYGB7cn0KZnZpel9kZW5kKHJlcy5oYywgcGFsZXR0ZSA9ICJqY28iLAogICAgICAgICAgcmVjdCA9IFRSVUUsIHNob3dfbGFiZWxzID0gRkFMU0UpCmBgYAoKYGBge3J9CmloY19kYXRhIDwtIHN1YnNldChpaGNfdGFibGUsIHNlbGVjdCA9IGMoImNvbmRlbnNlZF9pZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbHNfZm9yX2NsdXN0ZXIpKQp2YWxpZF9yb3dzIDwtIGFwcGx5KGloY19kYXRhLCAxLCBmdW5jdGlvbih4KSBhbGwoIWlzLm5hKHgpKSkKaWhjX2RhdGEgPC0gYXMuZGF0YS5mcmFtZShpaGNfZGF0YVt2YWxpZF9yb3dzLCBdKQoKaWhjX2RhdGEgPC0gaWhjX2RhdGEgJT4lIHBseXI6OnJlbmFtZShjKEVfQ0Q4X2RlbnNpdHkgPSAiRXBpdGhlbGlhbCBDRDgrIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFX0NENF9kZW5zaXR5ID0gIkVwaXRoZWxpYWwgQ0Q0KyIsIEVfQ0QyMF9kZW5zaXR5ID0gIkVwaXRoZWxpYWwgQ0QyMCsiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVfUGxhc21hX2RlbnNpdHkgPSAiRXBpdGhlbGlhbCBQbGFzbWEiLCBTX0NEOF9kZW5zaXR5ID0gIlN0cm9tYWwgQ0Q4KyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU19DRDRfZGVuc2l0eSA9ICJTdHJvbWFsIENENCsiLCBTX0NEMjBfZGVuc2l0eSA9ICJTdHJvbWFsIENEMjArIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTX1BsYXNtYV9kZW5zaXR5ID0gIlN0cm9tYWwgUGxhc21hIikpCmloY19tYXQgPC0gaWhjX2RhdGEgJT4lIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKHZhciA9ICJjb25kZW5zZWRfaWQiKQppaGNfbWF0X3NjYWxlZCA8LSBzY2FsZShpaGNfbWF0KQoKaWhjX21hdF9zY2FsZWQgPC0gaXRoaS51dGlsczo6Y2xpcF92YWx1ZXMoaWhjX21hdF9zY2FsZWQsIGhpID0gMiwgbG8gPSAtMikKCnNpbC5oYyA8LSBjbHVzdGVyOjpzaWxob3VldHRlKGN1dHJlZShyZXMuaGMsIDMpLCBkaXN0KGloY19tYXRfc2NhbGVkLCBtZXRob2QgPSAiZXVjbGlkZWFuIikpCmBgYAoKYGBge3J9CnRpbF9jbHVzdGVyc190YWJsZSA8LSB0YWJsZSh0aWxfY2x1c3RlcnMkY2x1c3RlcikKCnRpbF9jbHVzdGVyc190YWJsZQpgYGAKCmBgYHtyfQpwbG90KHNpbC5oYykKYGBgCgpTbyBhbGwgTi1USUxzIGFyZSBjb25maWRlbnRseSBjbHVzdGVyZWQsIHdoZXJlYXMgc29tZSBTLVRJTCBhbmQgRVMtVElMIGFyZSBub3QgYXMgY29uZmlkZW50bHkgY2x1c3RlcmVkIGFjY29yZGluZyB0byB0aGUgc2lsaG91ZXR0ZS4gCgpgYGB7cn0Kc2lsLmhjLm1hdCA8LSBzaWwuaGMKY2xhc3Moc2lsLmhjLm1hdCkgPC0gIm1hdHJpeCIKc2lsX2NvbmZ1c2lvbl9tYXQgPC0gc2lsLmhjLm1hdCAlPiUgYXMuZGF0YS5mcmFtZSAlPiUgc3Vic2V0KHNpbF93aWR0aCA8IDApCmBgYAoKCmBgYHtyfQpzaWxfY29uZnVzaW9uX3RhYmxlIDwtIHRhYmxlKHNpbF9jb25mdXNpb25fbWF0JGNsdXN0ZXIpCmBgYAoKU28gYHIgc2lsX2NvbmZ1c2lvbl90YWJsZVsiMiJdYCBvdXQgb2YgYHIgdGlsX2NsdXN0ZXJzX3RhYmxlWydTLVRJTCddYCBhcmUgbmVnYXRpdmUgc2lsaG91ZXR0ZSwgYW5kIGByIHNpbF9jb25mdXNpb25fdGFibGVbIjMiXWAgb3V0IG9mIGByIHRpbF9jbHVzdGVyc190YWJsZVsiMyJdYCBhcmUgbmVnYXRpdmUgc2lsaG91ZXR0ZS4gVGhlIG5laWdoYm91ciBjbHVzdGVyIGluIGFsbCBidXQgb25lIGNhc2UgaXMgTi1USUwgKGluIG9uZSBjYXNlLCBpdCdzIFMtVElMKS4gCgpJbiB0b3RhbCwgdGhlIGZyYWN0aW9uIG9mIG5lZ2F0aXZlIHNpbGhvdWV0dGUgc2FtcGxlcyBpczoKCmBgYHtyfQpzdW0oc2lsX2NvbmZ1c2lvbl90YWJsZSkvc3VtKHRpbF9jbHVzdGVyc190YWJsZSkKYGBgCgp3aGljaCBpcyBwcmV0dHkgZ29vZC4gQnV0IHRoaXMgcmF0ZSBpcyBxdWl0ZSBoaWdoIGluIFMtVElMLiAKCiMjIyBVbnN1cGVydmlzZWQgY2x1c3RlcmluZwoKIyMjIyBGdXp6eSBjbHVzdGVyaW5nCgpXZSdsbCBkbyBmdXp6eSBjLW1lYW5zIGFuZCBzZWUgaG93IHdlbGwgdGhhdCByZWNhcGl0dWxhdGVzIHRoZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyByZXN1bHRzLiAKCmBgYHtyfQpyZXMuZmFubnkgPC0gZmFubnkoaWhjX21hdF9zY2FsZWQsIGsgPSAzLCBtZXRyaWMgPSAiZXVjbGlkZWFuIiwgc3RhbmQgPSBGQUxTRSkKYGBgCgpgYGB7cn0KZmFubnlfY2x1c3RlcnMgPC0gZGF0YS5mcmFtZShyZXMuZmFubnkkY2x1c3RlcmluZykgJT4lIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiY29uZGVuc2VkX2lkIikgJT4lIHBseXI6OnJlbmFtZShjKCdyZXMuZmFubnkuY2x1c3RlcmluZyc9J2Zhbm55X2NsdXN0ZXInKSkKZmFubnlfY2x1c3RlcnMkZmFubnlfY2x1c3RlciA8LSBmYW5ueV9jbHVzdGVycyRmYW5ueV9jbHVzdGVyICU+JSBwbHlyOjptYXB2YWx1ZXMoZnJvbSA9IGMoMTozKSwgdG8gPSBjKCJOLVRJTCIsICJTLVRJTCIsICJFUy1USUwiKSkKCmhjX2Zhbm55X2NsdXN0ZXJzIDwtIHRpbF9jbHVzdGVycyAlPiUgcGx5cjo6am9pbihmYW5ueV9jbHVzdGVycykKYGBgCgpgYGB7cn0Kd2l0aChoY19mYW5ueV9jbHVzdGVycywgdGFibGUoY2x1c3RlciwgZmFubnlfY2x1c3RlcikpCgp3aXRoKGhjX2Zhbm55X2NsdXN0ZXJzLCBhZGp1c3RlZFJhbmRJbmRleChjbHVzdGVyLCBmYW5ueV9jbHVzdGVyKSkKCndpdGgoaGNfZmFubnlfY2x1c3RlcnMsIG1pLmVtcGlyaWNhbCh0YWJsZShjbHVzdGVyLCBmYW5ueV9jbHVzdGVyKSkpCmBgYAoKU28gZ2VuZXJhbGx5IHdlJ3JlIGRvaW5nIHByZXR0eSB3ZWxsIG9uIHRoZSBjb25zaXN0ZW5jeSBvZiBvdXIgY2x1c3RlcmluZyBiZXR3ZWVuIGhpZXJhcmNoaWNhbCBhbmQgZnV6enkgYy1tZWFucyBjbHVzdGVyaW5nLiAKCmBgYHtyfQpmdml6X2NsdXN0ZXIocmVzLmZhbm55LCBlbGxpcHNlLnR5cGUgPSAibm9ybSIsIHJlcGVsID0gVFJVRSwKICAgICAgICAgICAgIHBhbGV0dGUgPSAiamNvIiwgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSwKICAgICAgICAgICAgIGxlZ2VuZCA9ICJyaWdodCIpCmBgYAoKYGBge3J9CmZ2aXpfc2lsaG91ZXR0ZShyZXMuZmFubnksIHBhbGV0dGUgPSAiamNvIiwKICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkpCmBgYAoKVGhlIHNpbGhvdWV0dGUgcGxvdCBsb29rcyBzaW1pbGFyLCBhbGJlaXQgcXVpdGUgYSBiaXQgd29yc2UgZm9yIEVTLVRJTC4gCgpMZXQncyBub3cgcGxvdCB0aGUgY2x1c3RlcmluZyBwcm9iYWJpbGl0aWVzLiAKCmBgYHtyfQpwaGVhdG1hcChyZXMuZmFubnkkbWVtYmVyc2hpcCwgc2hvd19yb3duYW1lcyA9IEZBTFNFKQpgYGAKCldoYXQgdGhpcyBtZWFucyBpcyB0aGF0IGZ1enp5IGMtbWVhbnMgaXMgZG9pbmcgYSB0ZXJyaWJsZSBqb2IgZGlzdGluZ3Vpc2hpbmcgUy1USUwgZnJvbSBFUy1USUwuIAoKSSB0aGluayB0aGUgc2lsaG91ZXR0ZSBzdHVmZiBpcyBwcm9iYWJseSBzdWZmaWNpZW50IHRvIGFkZHJlc3MgdGhpcyBjb21tZW50LCBidXQgd2hhdCBkbyB5b3UgZ3V5cyB0aGluaz8gVGhlIGZ1enp5IGMtbWVhbnMgY2x1c3RlcmluZyBpcyBiYWQuIEFsdGVybmF0aXZlbHkgSSBjYW4gbG9vayBpbnRvIHVzaW5nIE5NRiwgZXRjLCBzaW5jZSB0aGUgZGF0YSBpcyBub24tbmVnYXRpdmUuIAoKIyMjIyBOTUYKCldoZW4gSSBoYXZlIHRpbWU/IFdvcnRoIGRvaW5nPwoKIyMjIFN1cGVydmlzZWQgY2x1c3RlcmluZwoKV2UgY2FuIGFsdGVybmF0aXZlbHkgbG9vayBhdCBzdXBlcnZpc2VkIG1ldGhvZHMgLS0gaWYgd2Ugd29yayBmcm9tIHRoZSBrbm93biBUSUwgY2x1c3RlcnMsIGNhbiB3ZSBkaXN0aW5ndWlzaCBOL1MvRVMtVElMIHdpdGggbWV0aG9kcyB0aGF0IGVpdGhlciBhc3NpZ24gY2x1c3RlciBtZW1iZXJzaGlwIHByb2JhYmlsaXRpZXMsIG9yIHdpdGggbWV0aG9kcyB0aGF0IGNhbiBkZXNpZ25hdGUgbXVsdGlwbGUgbGFiZWxzIChtdWx0aWxhYmVsIGNsYXNzaWZpY2F0aW9uKT8gCgpgYGB7cn0Kc2FtcGxlc19pbnRlcnNlY3QgPC0gaW50ZXJzZWN0KHJvd25hbWVzKGloY19tYXRfc2NhbGVkKSwgdGlsX2NsdXN0ZXJzJGNvbmRlbnNlZF9pZCkKClggPC0gaWhjX21hdF9zY2FsZWRbc2FtcGxlc19pbnRlcnNlY3QsXQp5IDwtICh0aWxfY2x1c3RlcnMgJT4lIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiY29uZGVuc2VkX2lkIikpW3NhbXBsZXNfaW50ZXJzZWN0LF0KCndyaXRlLnRhYmxlKFgsIGZpbGUgPSBpaGNfZmVhdHVyZXNfb3V0cHV0LCBzZXA9Ilx0IiwgcXVvdGUgPSBGQUxTRSwgcm93Lm5hbWVzID0gRkFMU0UsIGNvbC5uYW1lcyA9IFRSVUUpCndyaXRlLnRhYmxlKGRhdGEuZnJhbWUoY2x1c3Rlcj1hcy5udW1lcmljKHkpKSwgZmlsZSA9IHRpbF9jbHVzdGVyc19vdXRwdXQsIHNlcD0iXHQiLCBxdW90ZSA9IEZBTFNFLCByb3cubmFtZXMgPSBGQUxTRSwgY29sLm5hbWVzID0gVFJVRSkKYGBgCgpOb3csIGxvb2sgaW4gdGhlIGlQeXRob24gbm90ZWJvb2sgZm9yIHRoZSByZXN1bHRzIG9mIHRoaXMgc3R1ZmYuIAoKW1N1cGVydmlzZWQgY2x1c3RlcmluZyBhbmFseXNpc10oYHIgaXB5bmJfYmFzZV9wYXRoYCkKClRoZSBzdW1tYXJ5IG9mIHRoaXMgYW5hbHlzaXMgd2FzIHRoYXQsIHdpdGggYSBzaW1wbGUgbXVsdGlub21pYWwtb2JqZWN0aXZlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgYW5kIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgd2UgYWNoaWV2ZSAxMDAlIHZhbGlkYXRpb24gYWNjdXJhY3kuIEZ1cnRoZXJtb3JlLCA4MCUgb2Ygc2FtcGxlcyBhcmUgY2xhc3NpZmllZCB3aXRoID44MCUgcHJvYmFiaWxsaXR5IGludG8gdGhlIGNvcnJlY3QgY2x1c3Rlci4gQ29uc2lzdGVudCB3aXRoIG91ciBvdGhlciByZXN1bHRzIGFib3ZlLCBTLVRJTCBpcyB0aGUgbW9zdCBkaWZmaWN1bHQgdG8gY2x1c3RlciB0byBjYWxsLCB3aXRoIG9ubHkgNjMlIG9mIHNhbXBsZXMgY2xhc3NpZmllZCB3aXRoID44MCUgcHJvYmFiaWxpdHkuIEhvd2V2ZXIsIDg2JSBvZiBTLVRJTCBzYW1wbGVzIGNhbiBiZSBjb3JyZWN0bHkgY2xhc3NpZmllZCB3aXRoID41MCUgcHJvYmFiaWxpdHkuIAoKIyMjIyBTdW1tYXJ5CgoxNlwlIGhhdmUgbmVnYXRpdmUgc2lsaG91ZXR0ZSBzY29yZXMgKHNvbWUgUy1USUwgdGhhdCBjb3VsZCBhbHNvIGJlIGFzc2lnbmVkIHRvIE4tVElMIGlzIHRoZSBtb3N0IGNvbW1vbiBtaXNjbGFzc2lmaWNhdGlvbikuIFRoaXMgaXMgY29tcGFyYWJsZSB0byB0aGUgJSBvZiBuZWdhdGl2ZSBzaWxob3VldHRlIHNjb3JlcyBmb3IgVENHQSBtb2xlY3VsYXIgc3VidHlwZXMgKH4xNyUgb3Igc28pLiBUaGUgY29uY2x1c2lvbiBmcm9tIFJvZWwncyBwYXBlciB3YXM6CgoiVGhlIG92ZXJsYXAgaW4gZ2VuZSBzaWduYXR1cmUgc2NvcmVzIHN1Z2dlc3RzIHRoYXQgSEdTLU92Q2EgZG9lcyBub3QgY29uc2lzdCBvZiBtdXR1YWxseSBleGNsdXNpdmUgZXhwcmVzc2lvbiBzdWJ0eXBlcywgYnV0IHRoYXQgZWFjaCB0dW1vciBzYW1wbGUgaXMgcmVwcmVzZW50ZWQgYnkgbXVsdGlwbGUgc2lnbmF0dXJlcyBhdCBkaWZmZXJlbnQgbGV2ZWxzIG9mIGFjdGl2YXRpb24uIgoKVW5zdXBlcnZpc2VkIGZ1enp5IGNsdXN0ZXJpbmcgbWV0aG9kcywgbGlrZSBmdXp6eSBjLW1lYW5zLCBkbyBwb29ybHkgYW5kIGFyZSB1bmFibGUgdG8gZGlzdGluZ3Vpc2ggUy1USUwgYW5kIEVTLVRJTCBhdCBhbGwuIFRoaXMgaXMgbm90IG5lY2Vzc2FyaWx5IHN1cnByaXNpbmcsIGFzIHRoZXNlIGFyZSBlbnRpcmVseSBkaWZmZXJlbnQgY2x1c3RlcmluZyBtZXRob2RzIHRoYXQgZG8gbm90IGtub3cgdGhlIGFzc2lnbmVkIHRydWUgbGFiZWxzLiAKCkhvd2V2ZXIsIHdlIHNob3cgdGhhdCB3aXRoIHN1cGVydmlzZWQgY2x1c3RlcmluZyBtZXRob2RzLCBtb3JlIHNwZWNpZmljYWxseSBsb2dpc3RpYyByZWdyZXNzaW9uLCB3ZSBjYW4gZ2V0IHZlcnkgY29tcGV0aXRpdmUgYWNjdXJhY3kgYW5kIGNvbmZpZGVuY2UgcmF0ZXMgZm9yIG91ciBjbGFzc2VzLiAKCg==