1 Functions

2 Data

library(GSEABase)
GBMSeurat_cancer = readRDS("./Data/GSM3828673_10X_GBM_seurat_cancer.RDS")
metaModuleGenes <- read.table(file = "./Data/MetaModuleGenes_CellPaper.tsv", header = TRUE)
# Creating genesets for the meta module markers
MesLike2GeneSet <- GeneSet(metaModuleGenes[,1])
MesLike1GeneSet <- GeneSet(metaModuleGenes[,2])
ACLikeGeneSet <- GeneSet(metaModuleGenes[!is.na(metaModuleGenes[,3]), 3])
OPCLikeGeneSet <- GeneSet(metaModuleGenes[,4])
NPCLike1GeneSet <- GeneSet(metaModuleGenes[,5])
NPCLike2GeneSet <- GeneSet(metaModuleGenes[,6])

MesLike1GeneSet@setName <- "MesLike1"
MesLike2GeneSet@setName <- "MesLike2"
ACLikeGeneSet@setName <- "ACLike"
OPCLikeGeneSet@setName <- "OPCLike"
NPCLike1GeneSet@setName <- "NPCLike1"
NPCLike2GeneSet@setName <- "NPCLike2"
library(harmony)
GBMSeurat_cancer <- GBMSeurat_cancer %>% 
    RunHarmony("orig.ident", plot_convergence = TRUE)
Harmony 1/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony 2/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony 3/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony 4/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony 5/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony 6/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony 7/10
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Harmony converged after 7 iterations
Warning: Invalid name supplied, making object name syntactically valid. New object name is Seurat..ProjectDim.RNA.harmony; see ?make.names for more details on syntax validity

GBMSeurat_cancer_harmony <- GBMSeurat_cancer %>% 
    RunUMAP(reduction = "harmony", dims = 1:15) %>% 
    FindNeighbors(reduction = "harmony", dims = 1:15) %>% 
    FindClusters(resolution = 0.5) %>% 
    identity()
17:59:38 UMAP embedding parameters a = 0.9922 b = 1.112
17:59:38 Read 10000 rows and found 15 numeric columns
17:59:38 Using Annoy for neighbor search, n_neighbors = 30
17:59:38 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
17:59:39 Writing NN index file to temp file /tmp/RtmpbskuF3/file186136a8df094
17:59:39 Searching Annoy index using 1 thread, search_k = 3000
17:59:42 Annoy recall = 100%
17:59:43 Commencing smooth kNN distance calibration using 1 thread
17:59:45 Initializing from normalized Laplacian + noise
17:59:45 Commencing optimization for 500 epochs, with 429308 positive edges
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
17:59:56 Optimization finished
Computing nearest neighbor graph
Computing SNN
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 10000
Number of edges: 375295

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.9160
Number of communities: 14
Elapsed time: 1 seconds

3 UMAP after integration

 DimPlot(object = GBMSeurat_cancer_harmony, reduction = "umap", pt.size = .1, group.by = "orig.ident")

 DimPlot(object = GBMSeurat_cancer_harmony, reduction = "umap", pt.size = .1, group.by = "seurat_clusters",label = T)

cancer_markers = list(
  MesLike1 = MesLike1GeneSet@geneIds,
  MesLike2 = MesLike2GeneSet@geneIds,
  ACLike = ACLikeGeneSet@geneIds,
  OPCLike = OPCLikeGeneSet@geneIds,
  NPCLike1 = NPCLike1GeneSet@geneIds,
  NPCLike2 = NPCLike2GeneSet@geneIds
)

GBMSeurat_cancer <- AddModuleScore(
  object = GBMSeurat_cancer,
  features = cancer_markers,ctrl = 50,name = "cancer_markers")
Warning: The following features are not present in the object: ERO1L, not searching for symbol synonyms
Warning: The following features are not present in the object: PPAP2B, not searching for symbol synonyms
Warning: The following features are not present in the object: SOX2-OT, LPPR1, not searching for symbol synonyms
Warning: The following features are not present in the object: CD24, GPR56, not searching for symbol synonyms
Warning: The following features are not present in the object: CD24, HMP19, LOC150568, not searching for symbol synonyms
GBMSeurat_cancer_harmony[["MesLike1"]] = GBMSeurat_cancer$cancer_markers1
GBMSeurat_cancer_harmony[["MesLike2"]] = GBMSeurat_cancer$cancer_markers2
GBMSeurat_cancer_harmony[["ACLike"]] = GBMSeurat_cancer$cancer_markers3
GBMSeurat_cancer_harmony[["OPCLike"]] = GBMSeurat_cancer$cancer_markers4
GBMSeurat_cancer_harmony[["NPCLike1"]] = GBMSeurat_cancer$cancer_markers5
GBMSeurat_cancer_harmony[["NPCLike2"]] = GBMSeurat_cancer$cancer_markers6


cancer_scores  = FetchData(object = GBMSeurat_cancer_harmony,vars = c("MesLike1","MesLike2","ACLike","OPCLike","NPCLike1","NPCLike2"))
cancer_scores$assignment<- colnames(cancer_scores)[max.col(cancer_scores, ties.method = "first")]

GBMSeurat_cancer_harmony %<>% AddMetaData(metadata = cancer_scores$assignment,col.name = "cancer_type")
DimPlot(GBMSeurat_cancer_harmony,group.by = "cancer_type",label = T)

4 Feature plots


# Testing for clusters enriched in markers of the different malignant meta-modules
print_tab(FeaturePlot(GBMSeurat_cancer_harmony, features = "MesLike1",reduction = "umap"),title = "MesLike1")

MesLike1

print_tab(FeaturePlot(GBMSeurat_cancer_harmony, features = "MesLike2",reduction = "umap"),title = "MesLike2")

MesLike2

print_tab(FeaturePlot(GBMSeurat_cancer_harmony, features = "ACLike",reduction = "umap"),title = "ACLike")

ACLike

print_tab(FeaturePlot(GBMSeurat_cancer_harmony, features = "OPCLike",reduction = "umap"),title = "OPCLike")

OPCLike

print_tab(FeaturePlot(GBMSeurat_cancer_harmony, features = "NPCLike1",reduction = "umap"),title = "NPCLike1")

NPCLike1

print_tab(FeaturePlot(GBMSeurat_cancer_harmony, features = "NPCLike2",reduction = "umap"),title = "NPCLike2")

NPCLike2

NA

5 Analysis

clusters_and_scores = FetchData(object = GBMSeurat_cancer_harmony,vars= c("cancer_type","seurat_clusters")) %>%  group_by(seurat_clusters,cancer_type) %>%  summarise(n_cells = n())%>% mutate(per =  100 *n_cells/sum(n_cells))
`summarise()` has grouped output by 'seurat_clusters'. You can override using the `.groups` argument.
integration_score = clusters_and_scores %>%  group_by(seurat_clusters) %>% filter(n_cells == max(n_cells)) %>% pull(per) %>% mean() %>% round(digits = 2)

v_factor_levels <-c( "MesLike1", "MesLike2", "NPCLike1", "NPCLike2", "OPCLike","ACLike")
colors = RColorBrewer::brewer.pal(6, "Paired"); colors[5] = "orange"
p2 = ggplot(data=clusters_and_scores, aes(x=seurat_clusters, y=per, fill=factor(cancer_type, levels = v_factor_levels))) +
  geom_bar(stat="identity")+theme_minimal() + scale_fill_manual(values = colors,name  = "Cancer type")+ labs(title = "Seurat",subtitle = "integration score=" %s+% integration_score %s+% "%")+ylab("% from cluster")
p2

6 silhouette score

library(cluster, quietly = TRUE)
dist.matrix <- dist(x = Embeddings(object = GBMSeurat_cancer_harmony[["umap"]]))
clusters <- GBMSeurat_cancer_harmony$cancer_type
sil <- silhouette(x = as.numeric(x = as.factor(x = clusters)), dist = dist.matrix)
summary(sil)
Silhouette of 10000 units in 6 clusters from silhouette.default(x = as.numeric(x = as.factor(x = clusters)),  from     dist = dist.matrix) :
 Cluster sizes and average silhouette widths:
       2842        2968        1479        1984         537         190 
 0.03627807  0.13422832 -0.16423981 -0.21607186  0.42678272 -0.12909305 
Individual silhouette widths:
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
-0.881398 -0.315680  0.040535  0.003455  0.354209  0.573049 
GBMSeurat_cancer_harmony$sil = sil[,3]
VlnPlot(object = GBMSeurat_cancer_harmony,features = "sil",group.by = "cancer_type")

7 Combine cancer subtypes

GBMSeurat_cancer_harmony$cancer_type = GBMSeurat_cancer_harmony$cancer_type %>% gsub(pattern = "MesLike1|MesLike2",replacement = "MesLike")%>% gsub(pattern = "NPCLike1|NPCLike2",replacement = "NPCLike")
clusters_and_scores = FetchData(object = GBMSeurat_cancer_harmony,vars= c("cancer_type","seurat_clusters")) %>%  group_by(seurat_clusters,cancer_type) %>%  summarise(n_cells = n())%>% mutate(per =  100 *n_cells/sum(n_cells))
`summarise()` has grouped output by 'seurat_clusters'. You can override using the `.groups` argument.
integration_score = clusters_and_scores %>%  group_by(seurat_clusters) %>% filter(n_cells == max(n_cells)) %>% pull(per) %>% mean() %>% round(digits = 2)

v_factor_levels <-c( "MesLike", "NPCLike", "OPCLike","ACLike")
colors = RColorBrewer::brewer.pal(6, "Paired")[c(2,4,5,6)]; colors[3] = "orange"
p4 = ggplot(data=clusters_and_scores, aes(x=seurat_clusters, y=per, fill=factor(cancer_type, levels = v_factor_levels))) +
  geom_bar(stat="identity")+theme_minimal() + scale_fill_manual(values = colors,name  = "Cancer type")+ labs(title = "SiPSiC",subtitle = "integration score=" %s+% integration_score %s+% "%")+ylab("% from cluster")
p4

library(cluster, quietly = TRUE)
dist.matrix <- dist(x = Embeddings(object = GBMSeurat_cancer_harmony[["umap"]]))
clusters <- GBMSeurat_cancer_harmony$cancer_type
sil <- silhouette(x = as.numeric(x = as.factor(x = clusters)), dist = dist.matrix)
summary(sil)
Silhouette of 10000 units in 4 clusters from silhouette.default(x = as.numeric(x = as.factor(x = clusters)),  from     dist = dist.matrix) :
 Cluster sizes and average silhouette widths:
       2842        4447        2521         190 
 0.08042854  0.04773834  0.31742084 -0.09328014 
Individual silhouette widths:
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-0.7576 -0.1140  0.2318  0.1223  0.3516  0.5490 
GBMSeurat_cancer_harmony$sil = sil[,3]
VlnPlot(object = GBMSeurat_cancer_harmony,features = "sil",group.by = "cancer_type")

pdf(file = "./Figures/Harmony/UMAP_orig.ident.pdf", onefile = FALSE)
DimPlot(GBMSeurat_cancer_harmony, reduction = "umap", group.by = "orig.ident")
dev.off()


pdf(file = "./Figures/Harmony/UMAP_clustering.pdf", onefile = FALSE)
DimPlot(GBMSeurat_cancer_harmony, reduction = "umap")
dev.off()


pdf(file = "./Figures/Harmony/macrophageScore.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "macrophageScore"))

dev.off()

pdf(file = "./Figures/Harmony/TCellScores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "TCellScores"))

dev.off()
pdf(file = "./Figures/Harmony/oligodendrocyteScores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "oligodendrocyteScores"))

dev.off()
pdf(file = "./Figures/Harmony/oligodendrocyteScores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "oligodendrocyteScores"))

dev.off()
pdf(file = "./Figures/Harmony/MESLike1Scores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "MESLike1Scores"))

dev.off()
pdf(file = "./Figures/Harmony/MESLike2Scores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "MESLike2Scores"))

dev.off()
pdf(file = "./Figures/Harmony/ACLikeScores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "ACLikeScores"))

dev.off()
pdf(file = "./Figures/Harmony/OPCLikeScores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "OPCLikeScores"))

dev.off()


pdf(file = "./Figures/Harmony/NPCLike1Scores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "NPCLike1Scores"))

dev.off()

pdf(file = "./Figures/Harmony/NPCLike2Scores.pdf", onefile = FALSE)
print(FeaturePlot(GBMSeurat_cancer_harmony,reduction = "umap", features = "NPCLike2Scores"))

dev.off()

# Measuring the percentage of cells of each malignant meta module that are in a single cluster
clusterAssignments <- Idents(GBMSeurat)
ScoresAndClusters <- cbind(clusterAssignments, MacrophageScores = macrophageScores$pathwayScores, OligodecdrocytesScores = oligodendrocyteScores$pathwayScores,
                           MESLike1Scores = MesLike1Scores$pathwayScores, MESLike2Scores = MesLike2Scores$pathwayScores, 
                           ACLikeScores = ACLikeScores$pathwayScores, OPCLikeScores = OPCLikeScores$pathwayScores, 
                           NPCLike1Scores = NPCLike1Scores$pathwayScores, NPCLike2Scores = NPCLike2Scores$pathwayScores, 
                           TCellScores = TCellScores$pathwayScores, as.data.frame(GBMSeurat@meta.data$orig.ident))
colnames(ScoresAndClusters)[colnames(ScoresAndClusters) == "GBMSeurat@meta.data$orig.ident"] <- "Patient"

# Checking the proportions of the different patients in each cluster
clusterByPatientIDs <- ScoresAndClusters[,c("clusterAssignments", "Patient")]
clusterByPatientIDs <- clusterByPatientIDs[order(clusterByPatientIDs$clusterAssignments),]
for(currCluster in 0:max(as.numeric(as.character(clusterByPatientIDs[,"clusterAssignments"]))))
{
  currClusterCells <- clusterByPatientIDs[clusterByPatientIDs[,"clusterAssignments"] == currCluster,"Patient"]
  cat("\n", "Cluster number: ", currCluster, " Total Cells: ", sum(clusterByPatientIDs[,"clusterAssignments"] == currCluster), "\n")
  print(table(currClusterCells))
}


NPCThreshold <- ACLikeThreshold <- OPCLikeThreshold <- MESLikesThreshold <- 0.13
OligodendrocyteThreshold <- 1.5
MacrophageThreshold <- TCellThreshold <- 0.05

macrophageOverThresh <- ScoresAndClusters[ScoresAndClusters[,"MacrophageScores"] > MacrophageThreshold,c("clusterAssignments", "MacrophageScores")]
print("Macrophage cells: Total over threshold = ")
print(nrow(macrophageOverThresh))
print(table(macrophageOverThresh[,"clusterAssignments"]))
cat("\n\n")

TCellsOverThresh <- ScoresAndClusters[ScoresAndClusters[,"TCellScores"] > TCellThreshold,c("clusterAssignments", "TCellScores")]
print("T cells: Total over threshold = ")
print(nrow(TCellsOverThresh))
print(table(TCellsOverThresh[,"clusterAssignments"]))
cat("\n\n")

OligodendrocytesOverThresh <- ScoresAndClusters[ScoresAndClusters[,"OligodecdrocytesScores"] > OligodendrocyteThreshold,c("clusterAssignments", "OligodecdrocytesScores")]
print("Oligodendrocyte cells: Total over threshold = ")
print(nrow(OligodendrocytesOverThresh))
print(table(OligodendrocytesOverThresh[,"clusterAssignments"]))
cat("\n\n")

NPCs1OverThresh <- ScoresAndClusters[ScoresAndClusters[,"NPCLike1Scores"] > NPCThreshold,c("clusterAssignments", "NPCLike1Scores")]
print("NPC1 cells: Total over threshold = ")
print(nrow(NPCs1OverThresh))
print(table(NPCs1OverThresh[,"clusterAssignments"]))
cat("\n\n")

NPCs2OverThresh <- ScoresAndClusters[ScoresAndClusters[,"NPCLike2Scores"] > NPCThreshold,c("clusterAssignments", "NPCLike2Scores")]
print("NPC2 cells: Total over threshold = ")
print(nrow(NPCs2OverThresh))
print(table(NPCs2OverThresh[,"clusterAssignments"]))
cat("\n\n")

ACLikesOverThresh <- ScoresAndClusters[ScoresAndClusters[,"ACLikeScores"] > ACLikeThreshold,c("clusterAssignments", "ACLikeScores")]
print("ACLike cells: Total over threshold = ")
print(nrow(ACLikesOverThresh))
print(table(ACLikesOverThresh[,"clusterAssignments"]))
cat("\n\n")

OPCLikesOverThresh <- ScoresAndClusters[ScoresAndClusters[,"OPCLikeScores"] > OPCLikeThreshold,c("clusterAssignments", "OPCLikeScores")]
print("OPCLike cells: Total over threshold = ")
print(nrow(OPCLikesOverThresh))
print(table(OPCLikesOverThresh[,"clusterAssignments"]))
cat("\n\n")

MES1OverThresh <- ScoresAndClusters[ScoresAndClusters[,"MESLike1Scores"] > MESLikesThreshold,c("clusterAssignments", "MESLike1Scores")]
print("MES1 cells: Total over threshold = ")
print(nrow(MES1OverThresh))
print(table(MES1OverThresh[,"clusterAssignments"]))
cat("\n\n")

MES2OverThresh <- ScoresAndClusters[ScoresAndClusters[,"MESLike2Scores"] > MESLikesThreshold,c("clusterAssignments", "MESLike2Scores")]
print("MES2 cells: Total over threshold = ")
print(nrow(MES2OverThresh))
print(table(MES2OverThresh[,"clusterAssignments"]))
cat("\n\n")

# Checking the distribution of each patient's malignant cells across clusters

isCellMalignant <- ScoresAndClusters[,"NPCLike1Scores"] > NPCThreshold |
                   ScoresAndClusters[,"NPCLike2Scores"] > NPCThreshold |
                   ScoresAndClusters[,"ACLikeScores"] > ACLikeThreshold |
                   ScoresAndClusters[,"OPCLikeScores"] > OPCLikeThreshold |
                   ScoresAndClusters[,"MESLike1Scores"] > MESLikesThreshold |
                   ScoresAndClusters[,"MESLike2Scores"] > MESLikesThreshold
malignantCellsOnly <- ScoresAndClusters[isCellMalignant,]

for (currPatient in levels(malignantCellsOnly$Patient))
{
  currPatientMaligCells <- malignantCellsOnly[malignantCellsOnly[,"Patient"] == currPatient,]
  cat("\n", "Patient number: ", currPatient, "\nTotal number of cells: ", nrow(currPatientMaligCells), "\n")
  print(table(currPatientMaligCells[,"clusterAssignments"]))
  
  # Finding the NPC-Like cells of this patient only
  currPatientNPCLike1 <- currPatientMaligCells[currPatientMaligCells[,"NPCLike1Scores"] > NPCThreshold,]
  currPatientNPCLike2 <- currPatientMaligCells[currPatientMaligCells[,"NPCLike2Scores"] > NPCThreshold,]
  
  cat("\n", "NPC-Like1 cells in this cluster:\n")
  cat("Total number of NPC-Like1: ", nrow(currPatientNPCLike1))
  print(table(currPatientNPCLike1[, "clusterAssignments"]))
  
  cat("\n\n", "NPC-Like2 cells in this cluster:\n")
  cat("Total number of NPC-Like2: ", nrow(currPatientNPCLike2))
  print(table(currPatientNPCLike2[, "clusterAssignments"]))
  
  cat("\n\n")
}
clusters_assignment_results = data.frame(cluster = 0:15)
clusters_assignment_results[,"Macrophages"] = table(macrophageOverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)
clusters_assignment_results[,"TCellS"] = table(TCellsOverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)
clusters_assignment_results[,"Oligodecdrocytes"] = table(OligodendrocytesOverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)
clusters_assignment_results[,"NPC1"] = table(NPCs1OverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)

clusters_assignment_results[,"NPC2"] = table(NPCs2OverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)
clusters_assignment_results[,"ACLike"] = table(ACLikesOverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)
clusters_assignment_results[,"OPCLike"] = table(OPCLikesOverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)

clusters_assignment_results[,"MES1"] = table(MES1OverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)
clusters_assignment_results[,"MES2"] = table(MES2OverThresh[,"clusterAssignments"]) %>% as.data.frame() %>% pull(Freq)

8 Session

session_info()
LS0tCnRpdGxlOiAnYHIgcnN0dWRpb2FwaTo6Z2V0U291cmNlRWRpdG9yQ29udGV4dCgpJHBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLHJlcGxhY2VtZW50ID0gIiIpYCcgCmF1dGhvcjogIkF2aXNoYWkgV2l6ZWwiCmRhdGU6ICdgciBTeXMudGltZSgpYCcKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19jb2xsYXBzZTogeWVzCiAgICB0b2NfZmxvYXQ6IAogICAgICBjb2xsYXBzZWQ6IEZBTFNFCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvY19kZXB0aDogMQotLS0KCgoKIyBGdW5jdGlvbnMKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmBgYAoKIyBEYXRhCgpgYGB7cn0KbGlicmFyeShHU0VBQmFzZSkKR0JNU2V1cmF0X2NhbmNlciA9IHJlYWRSRFMoIi4vRGF0YS9HU00zODI4NjczXzEwWF9HQk1fc2V1cmF0X2NhbmNlci5SRFMiKQptZXRhTW9kdWxlR2VuZXMgPC0gcmVhZC50YWJsZShmaWxlID0gIi4vRGF0YS9NZXRhTW9kdWxlR2VuZXNfQ2VsbFBhcGVyLnRzdiIsIGhlYWRlciA9IFRSVUUpCiMgQ3JlYXRpbmcgZ2VuZXNldHMgZm9yIHRoZSBtZXRhIG1vZHVsZSBtYXJrZXJzCk1lc0xpa2UyR2VuZVNldCA8LSBHZW5lU2V0KG1ldGFNb2R1bGVHZW5lc1ssMV0pCk1lc0xpa2UxR2VuZVNldCA8LSBHZW5lU2V0KG1ldGFNb2R1bGVHZW5lc1ssMl0pCkFDTGlrZUdlbmVTZXQgPC0gR2VuZVNldChtZXRhTW9kdWxlR2VuZXNbIWlzLm5hKG1ldGFNb2R1bGVHZW5lc1ssM10pLCAzXSkKT1BDTGlrZUdlbmVTZXQgPC0gR2VuZVNldChtZXRhTW9kdWxlR2VuZXNbLDRdKQpOUENMaWtlMUdlbmVTZXQgPC0gR2VuZVNldChtZXRhTW9kdWxlR2VuZXNbLDVdKQpOUENMaWtlMkdlbmVTZXQgPC0gR2VuZVNldChtZXRhTW9kdWxlR2VuZXNbLDZdKQoKTWVzTGlrZTFHZW5lU2V0QHNldE5hbWUgPC0gIk1lc0xpa2UxIgpNZXNMaWtlMkdlbmVTZXRAc2V0TmFtZSA8LSAiTWVzTGlrZTIiCkFDTGlrZUdlbmVTZXRAc2V0TmFtZSA8LSAiQUNMaWtlIgpPUENMaWtlR2VuZVNldEBzZXROYW1lIDwtICJPUENMaWtlIgpOUENMaWtlMUdlbmVTZXRAc2V0TmFtZSA8LSAiTlBDTGlrZTEiCk5QQ0xpa2UyR2VuZVNldEBzZXROYW1lIDwtICJOUENMaWtlMiIKYGBgCgoKYGBge3J9CmxpYnJhcnkoaGFybW9ueSkKR0JNU2V1cmF0X2NhbmNlciA8LSBHQk1TZXVyYXRfY2FuY2VyICU+JSAKICAgIFJ1bkhhcm1vbnkoIm9yaWcuaWRlbnQiLCBwbG90X2NvbnZlcmdlbmNlID0gVFJVRSkKYGBgCgoKYGBge3J9CkdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSA8LSBHQk1TZXVyYXRfY2FuY2VyICU+JSAKICAgIFJ1blVNQVAocmVkdWN0aW9uID0gImhhcm1vbnkiLCBkaW1zID0gMToxNSkgJT4lIAogICAgRmluZE5laWdoYm9ycyhyZWR1Y3Rpb24gPSAiaGFybW9ueSIsIGRpbXMgPSAxOjE1KSAlPiUgCiAgICBGaW5kQ2x1c3RlcnMocmVzb2x1dGlvbiA9IDAuNSkgJT4lIAogICAgaWRlbnRpdHkoKQpgYGAKCgoKCgoKIyBVTUFQIGFmdGVyIGludGVncmF0aW9uIHsudGFic2V0fQoKCgpgYGB7cn0KIERpbVBsb3Qob2JqZWN0ID0gR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LCByZWR1Y3Rpb24gPSAidW1hcCIsIHB0LnNpemUgPSAuMSwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIpCiBEaW1QbG90KG9iamVjdCA9IEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSwgcmVkdWN0aW9uID0gInVtYXAiLCBwdC5zaXplID0gLjEsIGdyb3VwLmJ5ID0gInNldXJhdF9jbHVzdGVycyIsbGFiZWwgPSBUKQoKYGBgCmBgYHtyfQpjYW5jZXJfbWFya2VycyA9IGxpc3QoCiAgTWVzTGlrZTEgPSBNZXNMaWtlMUdlbmVTZXRAZ2VuZUlkcywKICBNZXNMaWtlMiA9IE1lc0xpa2UyR2VuZVNldEBnZW5lSWRzLAogIEFDTGlrZSA9IEFDTGlrZUdlbmVTZXRAZ2VuZUlkcywKICBPUENMaWtlID0gT1BDTGlrZUdlbmVTZXRAZ2VuZUlkcywKICBOUENMaWtlMSA9IE5QQ0xpa2UxR2VuZVNldEBnZW5lSWRzLAogIE5QQ0xpa2UyID0gTlBDTGlrZTJHZW5lU2V0QGdlbmVJZHMKKQoKR0JNU2V1cmF0X2NhbmNlciA8LSBBZGRNb2R1bGVTY29yZSgKICBvYmplY3QgPSBHQk1TZXVyYXRfY2FuY2VyLAogIGZlYXR1cmVzID0gY2FuY2VyX21hcmtlcnMsY3RybCA9IDUwLG5hbWUgPSAiY2FuY2VyX21hcmtlcnMiKQoKR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255W1siTWVzTGlrZTEiXV0gPSBHQk1TZXVyYXRfY2FuY2VyJGNhbmNlcl9tYXJrZXJzMQpHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnlbWyJNZXNMaWtlMiJdXSA9IEdCTVNldXJhdF9jYW5jZXIkY2FuY2VyX21hcmtlcnMyCkdCTVNldXJhdF9jYW5jZXJfaGFybW9ueVtbIkFDTGlrZSJdXSA9IEdCTVNldXJhdF9jYW5jZXIkY2FuY2VyX21hcmtlcnMzCkdCTVNldXJhdF9jYW5jZXJfaGFybW9ueVtbIk9QQ0xpa2UiXV0gPSBHQk1TZXVyYXRfY2FuY2VyJGNhbmNlcl9tYXJrZXJzNApHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnlbWyJOUENMaWtlMSJdXSA9IEdCTVNldXJhdF9jYW5jZXIkY2FuY2VyX21hcmtlcnM1CkdCTVNldXJhdF9jYW5jZXJfaGFybW9ueVtbIk5QQ0xpa2UyIl1dID0gR0JNU2V1cmF0X2NhbmNlciRjYW5jZXJfbWFya2VyczYKCgpjYW5jZXJfc2NvcmVzICA9IEZldGNoRGF0YShvYmplY3QgPSBHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksdmFycyA9IGMoIk1lc0xpa2UxIiwiTWVzTGlrZTIiLCJBQ0xpa2UiLCJPUENMaWtlIiwiTlBDTGlrZTEiLCJOUENMaWtlMiIpKQpjYW5jZXJfc2NvcmVzJGFzc2lnbm1lbnQ8LSBjb2xuYW1lcyhjYW5jZXJfc2NvcmVzKVttYXguY29sKGNhbmNlcl9zY29yZXMsIHRpZXMubWV0aG9kID0gImZpcnN0IildCgpHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnkgJTw+JSBBZGRNZXRhRGF0YShtZXRhZGF0YSA9IGNhbmNlcl9zY29yZXMkYXNzaWdubWVudCxjb2wubmFtZSA9ICJjYW5jZXJfdHlwZSIpCmBgYApgYGB7cn0KRGltUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksZ3JvdXAuYnkgPSAiY2FuY2VyX3R5cGUiLGxhYmVsID0gVCkKYGBgCgoKCgoKCiMgRmVhdHVyZSBwbG90cyB7LnRhYnNldH0KCmBgYHtyIHJlc3VsdHM9J2FzaXMnfQoKIyBUZXN0aW5nIGZvciBjbHVzdGVycyBlbnJpY2hlZCBpbiBtYXJrZXJzIG9mIHRoZSBkaWZmZXJlbnQgbWFsaWduYW50IG1ldGEtbW9kdWxlcwpwcmludF90YWIoRmVhdHVyZVBsb3QoR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LCBmZWF0dXJlcyA9ICJNZXNMaWtlMSIscmVkdWN0aW9uID0gInVtYXAiKSx0aXRsZSA9ICJNZXNMaWtlMSIpCnByaW50X3RhYihGZWF0dXJlUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksIGZlYXR1cmVzID0gIk1lc0xpa2UyIixyZWR1Y3Rpb24gPSAidW1hcCIpLHRpdGxlID0gIk1lc0xpa2UyIikKcHJpbnRfdGFiKEZlYXR1cmVQbG90KEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSwgZmVhdHVyZXMgPSAiQUNMaWtlIixyZWR1Y3Rpb24gPSAidW1hcCIpLHRpdGxlID0gIkFDTGlrZSIpCnByaW50X3RhYihGZWF0dXJlUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksIGZlYXR1cmVzID0gIk9QQ0xpa2UiLHJlZHVjdGlvbiA9ICJ1bWFwIiksdGl0bGUgPSAiT1BDTGlrZSIpCnByaW50X3RhYihGZWF0dXJlUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksIGZlYXR1cmVzID0gIk5QQ0xpa2UxIixyZWR1Y3Rpb24gPSAidW1hcCIpLHRpdGxlID0gIk5QQ0xpa2UxIikKcHJpbnRfdGFiKEZlYXR1cmVQbG90KEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSwgZmVhdHVyZXMgPSAiTlBDTGlrZTIiLHJlZHVjdGlvbiA9ICJ1bWFwIiksdGl0bGUgPSAiTlBDTGlrZTIiKQoKCmBgYAojIEFuYWx5c2lzCgoKYGBge3J9CmNsdXN0ZXJzX2FuZF9zY29yZXMgPSBGZXRjaERhdGEob2JqZWN0ID0gR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LHZhcnM9IGMoImNhbmNlcl90eXBlIiwic2V1cmF0X2NsdXN0ZXJzIikpICU+JSAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzLGNhbmNlcl90eXBlKSAlPiUgIHN1bW1hcmlzZShuX2NlbGxzID0gbigpKSU+JSBtdXRhdGUocGVyID0gIDEwMCAqbl9jZWxscy9zdW0obl9jZWxscykpCgppbnRlZ3JhdGlvbl9zY29yZSA9IGNsdXN0ZXJzX2FuZF9zY29yZXMgJT4lICBncm91cF9ieShzZXVyYXRfY2x1c3RlcnMpICU+JSBmaWx0ZXIobl9jZWxscyA9PSBtYXgobl9jZWxscykpICU+JSBwdWxsKHBlcikgJT4lIG1lYW4oKSAlPiUgcm91bmQoZGlnaXRzID0gMikKCnZfZmFjdG9yX2xldmVscyA8LWMoICJNZXNMaWtlMSIsICJNZXNMaWtlMiIsICJOUENMaWtlMSIsICJOUENMaWtlMiIsICJPUENMaWtlIiwiQUNMaWtlIikKY29sb3JzID0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDYsICJQYWlyZWQiKTsgY29sb3JzWzVdID0gIm9yYW5nZSIKcDIgPSBnZ3Bsb3QoZGF0YT1jbHVzdGVyc19hbmRfc2NvcmVzLCBhZXMoeD1zZXVyYXRfY2x1c3RlcnMsIHk9cGVyLCBmaWxsPWZhY3RvcihjYW5jZXJfdHlwZSwgbGV2ZWxzID0gdl9mYWN0b3JfbGV2ZWxzKSkpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpK3RoZW1lX21pbmltYWwoKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9ycyxuYW1lICA9ICJDYW5jZXIgdHlwZSIpKyBsYWJzKHRpdGxlID0gIlNldXJhdCIsc3VidGl0bGUgPSAiaW50ZWdyYXRpb24gc2NvcmU9IiAlcyslIGludGVncmF0aW9uX3Njb3JlICVzKyUgIiUiKSt5bGFiKCIlIGZyb20gY2x1c3RlciIpCnAyCmBgYAoKCgoKIyBzaWxob3VldHRlIHNjb3JlCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXIsIHF1aWV0bHkgPSBUUlVFKQpkaXN0Lm1hdHJpeCA8LSBkaXN0KHggPSBFbWJlZGRpbmdzKG9iamVjdCA9IEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueVtbInVtYXAiXV0pKQpjbHVzdGVycyA8LSBHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnkkY2FuY2VyX3R5cGUKc2lsIDwtIHNpbGhvdWV0dGUoeCA9IGFzLm51bWVyaWMoeCA9IGFzLmZhY3Rvcih4ID0gY2x1c3RlcnMpKSwgZGlzdCA9IGRpc3QubWF0cml4KQpzdW1tYXJ5KHNpbCkKR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255JHNpbCA9IHNpbFssM10KVmxuUGxvdChvYmplY3QgPSBHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksZmVhdHVyZXMgPSAic2lsIixncm91cC5ieSA9ICJjYW5jZXJfdHlwZSIpCmBgYAoKIyBDb21iaW5lIGNhbmNlciBzdWJ0eXBlcwpgYGB7cn0KR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255JGNhbmNlcl90eXBlID0gR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255JGNhbmNlcl90eXBlICU+JSBnc3ViKHBhdHRlcm4gPSAiTWVzTGlrZTF8TWVzTGlrZTIiLHJlcGxhY2VtZW50ID0gIk1lc0xpa2UiKSU+JSBnc3ViKHBhdHRlcm4gPSAiTlBDTGlrZTF8TlBDTGlrZTIiLHJlcGxhY2VtZW50ID0gIk5QQ0xpa2UiKQpgYGAKCmBgYHtyfQpjbHVzdGVyc19hbmRfc2NvcmVzID0gRmV0Y2hEYXRhKG9iamVjdCA9IEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSx2YXJzPSBjKCJjYW5jZXJfdHlwZSIsInNldXJhdF9jbHVzdGVycyIpKSAlPiUgIGdyb3VwX2J5KHNldXJhdF9jbHVzdGVycyxjYW5jZXJfdHlwZSkgJT4lICBzdW1tYXJpc2Uobl9jZWxscyA9IG4oKSklPiUgbXV0YXRlKHBlciA9ICAxMDAgKm5fY2VsbHMvc3VtKG5fY2VsbHMpKQoKaW50ZWdyYXRpb25fc2NvcmUgPSBjbHVzdGVyc19hbmRfc2NvcmVzICU+JSAgZ3JvdXBfYnkoc2V1cmF0X2NsdXN0ZXJzKSAlPiUgZmlsdGVyKG5fY2VsbHMgPT0gbWF4KG5fY2VsbHMpKSAlPiUgcHVsbChwZXIpICU+JSBtZWFuKCkgJT4lIHJvdW5kKGRpZ2l0cyA9IDIpCgp2X2ZhY3Rvcl9sZXZlbHMgPC1jKCAiTWVzTGlrZSIsICJOUENMaWtlIiwgIk9QQ0xpa2UiLCJBQ0xpa2UiKQpjb2xvcnMgPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNiwgIlBhaXJlZCIpW2MoMiw0LDUsNildOyBjb2xvcnNbM10gPSAib3JhbmdlIgpwNCA9IGdncGxvdChkYXRhPWNsdXN0ZXJzX2FuZF9zY29yZXMsIGFlcyh4PXNldXJhdF9jbHVzdGVycywgeT1wZXIsIGZpbGw9ZmFjdG9yKGNhbmNlcl90eXBlLCBsZXZlbHMgPSB2X2ZhY3Rvcl9sZXZlbHMpKSkgKwogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikrdGhlbWVfbWluaW1hbCgpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3JzLG5hbWUgID0gIkNhbmNlciB0eXBlIikrIGxhYnModGl0bGUgPSAiU2lQU2lDIixzdWJ0aXRsZSA9ICJpbnRlZ3JhdGlvbiBzY29yZT0iICVzKyUgaW50ZWdyYXRpb25fc2NvcmUgJXMrJSAiJSIpK3lsYWIoIiUgZnJvbSBjbHVzdGVyIikKcDQKYGBgCgpgYGB7cn0KbGlicmFyeShjbHVzdGVyLCBxdWlldGx5ID0gVFJVRSkKZGlzdC5tYXRyaXggPC0gZGlzdCh4ID0gRW1iZWRkaW5ncyhvYmplY3QgPSBHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnlbWyJ1bWFwIl1dKSkKY2x1c3RlcnMgPC0gR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255JGNhbmNlcl90eXBlCnNpbCA8LSBzaWxob3VldHRlKHggPSBhcy5udW1lcmljKHggPSBhcy5mYWN0b3IoeCA9IGNsdXN0ZXJzKSksIGRpc3QgPSBkaXN0Lm1hdHJpeCkKc3VtbWFyeShzaWwpCkdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSRzaWwgPSBzaWxbLDNdClZsblBsb3Qob2JqZWN0ID0gR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LGZlYXR1cmVzID0gInNpbCIsZ3JvdXAuYnkgPSAiY2FuY2VyX3R5cGUiKQpgYGAKCgoKCgpgYGB7cn0KcGRmKGZpbGUgPSAiLi9GaWd1cmVzL0hhcm1vbnkvVU1BUF9vcmlnLmlkZW50LnBkZiIsIG9uZWZpbGUgPSBGQUxTRSkKRGltUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnksIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIpCmRldi5vZmYoKQoKCnBkZihmaWxlID0gIi4vRmlndXJlcy9IYXJtb255L1VNQVBfY2x1c3RlcmluZy5wZGYiLCBvbmVmaWxlID0gRkFMU0UpCkRpbVBsb3QoR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LCByZWR1Y3Rpb24gPSAidW1hcCIpCmRldi5vZmYoKQoKCnBkZihmaWxlID0gIi4vRmlndXJlcy9IYXJtb255L21hY3JvcGhhZ2VTY29yZS5wZGYiLCBvbmVmaWxlID0gRkFMU0UpCnByaW50KEZlYXR1cmVQbG90KEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSxyZWR1Y3Rpb24gPSAidW1hcCIsIGZlYXR1cmVzID0gIm1hY3JvcGhhZ2VTY29yZSIpKQoKZGV2Lm9mZigpCgpwZGYoZmlsZSA9ICIuL0ZpZ3VyZXMvSGFybW9ueS9UQ2VsbFNjb3Jlcy5wZGYiLCBvbmVmaWxlID0gRkFMU0UpCnByaW50KEZlYXR1cmVQbG90KEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSxyZWR1Y3Rpb24gPSAidW1hcCIsIGZlYXR1cmVzID0gIlRDZWxsU2NvcmVzIikpCgpkZXYub2ZmKCkKcGRmKGZpbGUgPSAiLi9GaWd1cmVzL0hhcm1vbnkvb2xpZ29kZW5kcm9jeXRlU2NvcmVzLnBkZiIsIG9uZWZpbGUgPSBGQUxTRSkKcHJpbnQoRmVhdHVyZVBsb3QoR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LHJlZHVjdGlvbiA9ICJ1bWFwIiwgZmVhdHVyZXMgPSAib2xpZ29kZW5kcm9jeXRlU2NvcmVzIikpCgpkZXYub2ZmKCkKcGRmKGZpbGUgPSAiLi9GaWd1cmVzL0hhcm1vbnkvb2xpZ29kZW5kcm9jeXRlU2NvcmVzLnBkZiIsIG9uZWZpbGUgPSBGQUxTRSkKcHJpbnQoRmVhdHVyZVBsb3QoR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LHJlZHVjdGlvbiA9ICJ1bWFwIiwgZmVhdHVyZXMgPSAib2xpZ29kZW5kcm9jeXRlU2NvcmVzIikpCgpkZXYub2ZmKCkKcGRmKGZpbGUgPSAiLi9GaWd1cmVzL0hhcm1vbnkvTUVTTGlrZTFTY29yZXMucGRmIiwgb25lZmlsZSA9IEZBTFNFKQpwcmludChGZWF0dXJlUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnkscmVkdWN0aW9uID0gInVtYXAiLCBmZWF0dXJlcyA9ICJNRVNMaWtlMVNjb3JlcyIpKQoKZGV2Lm9mZigpCnBkZihmaWxlID0gIi4vRmlndXJlcy9IYXJtb255L01FU0xpa2UyU2NvcmVzLnBkZiIsIG9uZWZpbGUgPSBGQUxTRSkKcHJpbnQoRmVhdHVyZVBsb3QoR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LHJlZHVjdGlvbiA9ICJ1bWFwIiwgZmVhdHVyZXMgPSAiTUVTTGlrZTJTY29yZXMiKSkKCmRldi5vZmYoKQpwZGYoZmlsZSA9ICIuL0ZpZ3VyZXMvSGFybW9ueS9BQ0xpa2VTY29yZXMucGRmIiwgb25lZmlsZSA9IEZBTFNFKQpwcmludChGZWF0dXJlUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnkscmVkdWN0aW9uID0gInVtYXAiLCBmZWF0dXJlcyA9ICJBQ0xpa2VTY29yZXMiKSkKCmRldi5vZmYoKQpwZGYoZmlsZSA9ICIuL0ZpZ3VyZXMvSGFybW9ueS9PUENMaWtlU2NvcmVzLnBkZiIsIG9uZWZpbGUgPSBGQUxTRSkKcHJpbnQoRmVhdHVyZVBsb3QoR0JNU2V1cmF0X2NhbmNlcl9oYXJtb255LHJlZHVjdGlvbiA9ICJ1bWFwIiwgZmVhdHVyZXMgPSAiT1BDTGlrZVNjb3JlcyIpKQoKZGV2Lm9mZigpCgoKcGRmKGZpbGUgPSAiLi9GaWd1cmVzL0hhcm1vbnkvTlBDTGlrZTFTY29yZXMucGRmIiwgb25lZmlsZSA9IEZBTFNFKQpwcmludChGZWF0dXJlUGxvdChHQk1TZXVyYXRfY2FuY2VyX2hhcm1vbnkscmVkdWN0aW9uID0gInVtYXAiLCBmZWF0dXJlcyA9ICJOUENMaWtlMVNjb3JlcyIpKQoKZGV2Lm9mZigpCgpwZGYoZmlsZSA9ICIuL0ZpZ3VyZXMvSGFybW9ueS9OUENMaWtlMlNjb3Jlcy5wZGYiLCBvbmVmaWxlID0gRkFMU0UpCnByaW50KEZlYXR1cmVQbG90KEdCTVNldXJhdF9jYW5jZXJfaGFybW9ueSxyZWR1Y3Rpb24gPSAidW1hcCIsIGZlYXR1cmVzID0gIk5QQ0xpa2UyU2NvcmVzIikpCgpkZXYub2ZmKCkKCgoKYGBgCgoKYGBge3J9CgojIE1lYXN1cmluZyB0aGUgcGVyY2VudGFnZSBvZiBjZWxscyBvZiBlYWNoIG1hbGlnbmFudCBtZXRhIG1vZHVsZSB0aGF0IGFyZSBpbiBhIHNpbmdsZSBjbHVzdGVyCmNsdXN0ZXJBc3NpZ25tZW50cyA8LSBJZGVudHMoR0JNU2V1cmF0KQpTY29yZXNBbmRDbHVzdGVycyA8LSBjYmluZChjbHVzdGVyQXNzaWdubWVudHMsIE1hY3JvcGhhZ2VTY29yZXMgPSBtYWNyb3BoYWdlU2NvcmVzJHBhdGh3YXlTY29yZXMsIE9saWdvZGVjZHJvY3l0ZXNTY29yZXMgPSBvbGlnb2RlbmRyb2N5dGVTY29yZXMkcGF0aHdheVNjb3JlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgTUVTTGlrZTFTY29yZXMgPSBNZXNMaWtlMVNjb3JlcyRwYXRod2F5U2NvcmVzLCBNRVNMaWtlMlNjb3JlcyA9IE1lc0xpa2UyU2NvcmVzJHBhdGh3YXlTY29yZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBBQ0xpa2VTY29yZXMgPSBBQ0xpa2VTY29yZXMkcGF0aHdheVNjb3JlcywgT1BDTGlrZVNjb3JlcyA9IE9QQ0xpa2VTY29yZXMkcGF0aHdheVNjb3JlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIE5QQ0xpa2UxU2NvcmVzID0gTlBDTGlrZTFTY29yZXMkcGF0aHdheVNjb3JlcywgTlBDTGlrZTJTY29yZXMgPSBOUENMaWtlMlNjb3JlcyRwYXRod2F5U2NvcmVzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgVENlbGxTY29yZXMgPSBUQ2VsbFNjb3JlcyRwYXRod2F5U2NvcmVzLCBhcy5kYXRhLmZyYW1lKEdCTVNldXJhdEBtZXRhLmRhdGEkb3JpZy5pZGVudCkpCmNvbG5hbWVzKFNjb3Jlc0FuZENsdXN0ZXJzKVtjb2xuYW1lcyhTY29yZXNBbmRDbHVzdGVycykgPT0gIkdCTVNldXJhdEBtZXRhLmRhdGEkb3JpZy5pZGVudCJdIDwtICJQYXRpZW50IgoKIyBDaGVja2luZyB0aGUgcHJvcG9ydGlvbnMgb2YgdGhlIGRpZmZlcmVudCBwYXRpZW50cyBpbiBlYWNoIGNsdXN0ZXIKY2x1c3RlckJ5UGF0aWVudElEcyA8LSBTY29yZXNBbmRDbHVzdGVyc1ssYygiY2x1c3RlckFzc2lnbm1lbnRzIiwgIlBhdGllbnQiKV0KY2x1c3RlckJ5UGF0aWVudElEcyA8LSBjbHVzdGVyQnlQYXRpZW50SURzW29yZGVyKGNsdXN0ZXJCeVBhdGllbnRJRHMkY2x1c3RlckFzc2lnbm1lbnRzKSxdCmZvcihjdXJyQ2x1c3RlciBpbiAwOm1heChhcy5udW1lcmljKGFzLmNoYXJhY3RlcihjbHVzdGVyQnlQYXRpZW50SURzWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pKSkpCnsKICBjdXJyQ2x1c3RlckNlbGxzIDwtIGNsdXN0ZXJCeVBhdGllbnRJRHNbY2x1c3RlckJ5UGF0aWVudElEc1ssImNsdXN0ZXJBc3NpZ25tZW50cyJdID09IGN1cnJDbHVzdGVyLCJQYXRpZW50Il0KICBjYXQoIlxuIiwgIkNsdXN0ZXIgbnVtYmVyOiAiLCBjdXJyQ2x1c3RlciwgIiBUb3RhbCBDZWxsczogIiwgc3VtKGNsdXN0ZXJCeVBhdGllbnRJRHNbLCJjbHVzdGVyQXNzaWdubWVudHMiXSA9PSBjdXJyQ2x1c3RlciksICJcbiIpCiAgcHJpbnQodGFibGUoY3VyckNsdXN0ZXJDZWxscykpCn0KCgpOUENUaHJlc2hvbGQgPC0gQUNMaWtlVGhyZXNob2xkIDwtIE9QQ0xpa2VUaHJlc2hvbGQgPC0gTUVTTGlrZXNUaHJlc2hvbGQgPC0gMC4xMwpPbGlnb2RlbmRyb2N5dGVUaHJlc2hvbGQgPC0gMS41Ck1hY3JvcGhhZ2VUaHJlc2hvbGQgPC0gVENlbGxUaHJlc2hvbGQgPC0gMC4wNQoKbWFjcm9waGFnZU92ZXJUaHJlc2ggPC0gU2NvcmVzQW5kQ2x1c3RlcnNbU2NvcmVzQW5kQ2x1c3RlcnNbLCJNYWNyb3BoYWdlU2NvcmVzIl0gPiBNYWNyb3BoYWdlVGhyZXNob2xkLGMoImNsdXN0ZXJBc3NpZ25tZW50cyIsICJNYWNyb3BoYWdlU2NvcmVzIildCnByaW50KCJNYWNyb3BoYWdlIGNlbGxzOiBUb3RhbCBvdmVyIHRocmVzaG9sZCA9ICIpCnByaW50KG5yb3cobWFjcm9waGFnZU92ZXJUaHJlc2gpKQpwcmludCh0YWJsZShtYWNyb3BoYWdlT3ZlclRocmVzaFssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSkKY2F0KCJcblxuIikKClRDZWxsc092ZXJUaHJlc2ggPC0gU2NvcmVzQW5kQ2x1c3RlcnNbU2NvcmVzQW5kQ2x1c3RlcnNbLCJUQ2VsbFNjb3JlcyJdID4gVENlbGxUaHJlc2hvbGQsYygiY2x1c3RlckFzc2lnbm1lbnRzIiwgIlRDZWxsU2NvcmVzIildCnByaW50KCJUIGNlbGxzOiBUb3RhbCBvdmVyIHRocmVzaG9sZCA9ICIpCnByaW50KG5yb3coVENlbGxzT3ZlclRocmVzaCkpCnByaW50KHRhYmxlKFRDZWxsc092ZXJUaHJlc2hbLCJjbHVzdGVyQXNzaWdubWVudHMiXSkpCmNhdCgiXG5cbiIpCgpPbGlnb2RlbmRyb2N5dGVzT3ZlclRocmVzaCA8LSBTY29yZXNBbmRDbHVzdGVyc1tTY29yZXNBbmRDbHVzdGVyc1ssIk9saWdvZGVjZHJvY3l0ZXNTY29yZXMiXSA+IE9saWdvZGVuZHJvY3l0ZVRocmVzaG9sZCxjKCJjbHVzdGVyQXNzaWdubWVudHMiLCAiT2xpZ29kZWNkcm9jeXRlc1Njb3JlcyIpXQpwcmludCgiT2xpZ29kZW5kcm9jeXRlIGNlbGxzOiBUb3RhbCBvdmVyIHRocmVzaG9sZCA9ICIpCnByaW50KG5yb3coT2xpZ29kZW5kcm9jeXRlc092ZXJUaHJlc2gpKQpwcmludCh0YWJsZShPbGlnb2RlbmRyb2N5dGVzT3ZlclRocmVzaFssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSkKY2F0KCJcblxuIikKCk5QQ3MxT3ZlclRocmVzaCA8LSBTY29yZXNBbmRDbHVzdGVyc1tTY29yZXNBbmRDbHVzdGVyc1ssIk5QQ0xpa2UxU2NvcmVzIl0gPiBOUENUaHJlc2hvbGQsYygiY2x1c3RlckFzc2lnbm1lbnRzIiwgIk5QQ0xpa2UxU2NvcmVzIildCnByaW50KCJOUEMxIGNlbGxzOiBUb3RhbCBvdmVyIHRocmVzaG9sZCA9ICIpCnByaW50KG5yb3coTlBDczFPdmVyVGhyZXNoKSkKcHJpbnQodGFibGUoTlBDczFPdmVyVGhyZXNoWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pKQpjYXQoIlxuXG4iKQoKTlBDczJPdmVyVGhyZXNoIDwtIFNjb3Jlc0FuZENsdXN0ZXJzW1Njb3Jlc0FuZENsdXN0ZXJzWywiTlBDTGlrZTJTY29yZXMiXSA+IE5QQ1RocmVzaG9sZCxjKCJjbHVzdGVyQXNzaWdubWVudHMiLCAiTlBDTGlrZTJTY29yZXMiKV0KcHJpbnQoIk5QQzIgY2VsbHM6IFRvdGFsIG92ZXIgdGhyZXNob2xkID0gIikKcHJpbnQobnJvdyhOUENzMk92ZXJUaHJlc2gpKQpwcmludCh0YWJsZShOUENzMk92ZXJUaHJlc2hbLCJjbHVzdGVyQXNzaWdubWVudHMiXSkpCmNhdCgiXG5cbiIpCgpBQ0xpa2VzT3ZlclRocmVzaCA8LSBTY29yZXNBbmRDbHVzdGVyc1tTY29yZXNBbmRDbHVzdGVyc1ssIkFDTGlrZVNjb3JlcyJdID4gQUNMaWtlVGhyZXNob2xkLGMoImNsdXN0ZXJBc3NpZ25tZW50cyIsICJBQ0xpa2VTY29yZXMiKV0KcHJpbnQoIkFDTGlrZSBjZWxsczogVG90YWwgb3ZlciB0aHJlc2hvbGQgPSAiKQpwcmludChucm93KEFDTGlrZXNPdmVyVGhyZXNoKSkKcHJpbnQodGFibGUoQUNMaWtlc092ZXJUaHJlc2hbLCJjbHVzdGVyQXNzaWdubWVudHMiXSkpCmNhdCgiXG5cbiIpCgpPUENMaWtlc092ZXJUaHJlc2ggPC0gU2NvcmVzQW5kQ2x1c3RlcnNbU2NvcmVzQW5kQ2x1c3RlcnNbLCJPUENMaWtlU2NvcmVzIl0gPiBPUENMaWtlVGhyZXNob2xkLGMoImNsdXN0ZXJBc3NpZ25tZW50cyIsICJPUENMaWtlU2NvcmVzIildCnByaW50KCJPUENMaWtlIGNlbGxzOiBUb3RhbCBvdmVyIHRocmVzaG9sZCA9ICIpCnByaW50KG5yb3coT1BDTGlrZXNPdmVyVGhyZXNoKSkKcHJpbnQodGFibGUoT1BDTGlrZXNPdmVyVGhyZXNoWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pKQpjYXQoIlxuXG4iKQoKTUVTMU92ZXJUaHJlc2ggPC0gU2NvcmVzQW5kQ2x1c3RlcnNbU2NvcmVzQW5kQ2x1c3RlcnNbLCJNRVNMaWtlMVNjb3JlcyJdID4gTUVTTGlrZXNUaHJlc2hvbGQsYygiY2x1c3RlckFzc2lnbm1lbnRzIiwgIk1FU0xpa2UxU2NvcmVzIildCnByaW50KCJNRVMxIGNlbGxzOiBUb3RhbCBvdmVyIHRocmVzaG9sZCA9ICIpCnByaW50KG5yb3coTUVTMU92ZXJUaHJlc2gpKQpwcmludCh0YWJsZShNRVMxT3ZlclRocmVzaFssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSkKY2F0KCJcblxuIikKCk1FUzJPdmVyVGhyZXNoIDwtIFNjb3Jlc0FuZENsdXN0ZXJzW1Njb3Jlc0FuZENsdXN0ZXJzWywiTUVTTGlrZTJTY29yZXMiXSA+IE1FU0xpa2VzVGhyZXNob2xkLGMoImNsdXN0ZXJBc3NpZ25tZW50cyIsICJNRVNMaWtlMlNjb3JlcyIpXQpwcmludCgiTUVTMiBjZWxsczogVG90YWwgb3ZlciB0aHJlc2hvbGQgPSAiKQpwcmludChucm93KE1FUzJPdmVyVGhyZXNoKSkKcHJpbnQodGFibGUoTUVTMk92ZXJUaHJlc2hbLCJjbHVzdGVyQXNzaWdubWVudHMiXSkpCmNhdCgiXG5cbiIpCgojIENoZWNraW5nIHRoZSBkaXN0cmlidXRpb24gb2YgZWFjaCBwYXRpZW50J3MgbWFsaWduYW50IGNlbGxzIGFjcm9zcyBjbHVzdGVycwoKaXNDZWxsTWFsaWduYW50IDwtIFNjb3Jlc0FuZENsdXN0ZXJzWywiTlBDTGlrZTFTY29yZXMiXSA+IE5QQ1RocmVzaG9sZCB8CiAgICAgICAgICAgICAgICAgICBTY29yZXNBbmRDbHVzdGVyc1ssIk5QQ0xpa2UyU2NvcmVzIl0gPiBOUENUaHJlc2hvbGQgfAogICAgICAgICAgICAgICAgICAgU2NvcmVzQW5kQ2x1c3RlcnNbLCJBQ0xpa2VTY29yZXMiXSA+IEFDTGlrZVRocmVzaG9sZCB8CiAgICAgICAgICAgICAgICAgICBTY29yZXNBbmRDbHVzdGVyc1ssIk9QQ0xpa2VTY29yZXMiXSA+IE9QQ0xpa2VUaHJlc2hvbGQgfAogICAgICAgICAgICAgICAgICAgU2NvcmVzQW5kQ2x1c3RlcnNbLCJNRVNMaWtlMVNjb3JlcyJdID4gTUVTTGlrZXNUaHJlc2hvbGQgfAogICAgICAgICAgICAgICAgICAgU2NvcmVzQW5kQ2x1c3RlcnNbLCJNRVNMaWtlMlNjb3JlcyJdID4gTUVTTGlrZXNUaHJlc2hvbGQKbWFsaWduYW50Q2VsbHNPbmx5IDwtIFNjb3Jlc0FuZENsdXN0ZXJzW2lzQ2VsbE1hbGlnbmFudCxdCgpmb3IgKGN1cnJQYXRpZW50IGluIGxldmVscyhtYWxpZ25hbnRDZWxsc09ubHkkUGF0aWVudCkpCnsKICBjdXJyUGF0aWVudE1hbGlnQ2VsbHMgPC0gbWFsaWduYW50Q2VsbHNPbmx5W21hbGlnbmFudENlbGxzT25seVssIlBhdGllbnQiXSA9PSBjdXJyUGF0aWVudCxdCiAgY2F0KCJcbiIsICJQYXRpZW50IG51bWJlcjogIiwgY3VyclBhdGllbnQsICJcblRvdGFsIG51bWJlciBvZiBjZWxsczogIiwgbnJvdyhjdXJyUGF0aWVudE1hbGlnQ2VsbHMpLCAiXG4iKQogIHByaW50KHRhYmxlKGN1cnJQYXRpZW50TWFsaWdDZWxsc1ssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSkKICAKICAjIEZpbmRpbmcgdGhlIE5QQy1MaWtlIGNlbGxzIG9mIHRoaXMgcGF0aWVudCBvbmx5CiAgY3VyclBhdGllbnROUENMaWtlMSA8LSBjdXJyUGF0aWVudE1hbGlnQ2VsbHNbY3VyclBhdGllbnRNYWxpZ0NlbGxzWywiTlBDTGlrZTFTY29yZXMiXSA+IE5QQ1RocmVzaG9sZCxdCiAgY3VyclBhdGllbnROUENMaWtlMiA8LSBjdXJyUGF0aWVudE1hbGlnQ2VsbHNbY3VyclBhdGllbnRNYWxpZ0NlbGxzWywiTlBDTGlrZTJTY29yZXMiXSA+IE5QQ1RocmVzaG9sZCxdCiAgCiAgY2F0KCJcbiIsICJOUEMtTGlrZTEgY2VsbHMgaW4gdGhpcyBjbHVzdGVyOlxuIikKICBjYXQoIlRvdGFsIG51bWJlciBvZiBOUEMtTGlrZTE6ICIsIG5yb3coY3VyclBhdGllbnROUENMaWtlMSkpCiAgcHJpbnQodGFibGUoY3VyclBhdGllbnROUENMaWtlMVssICJjbHVzdGVyQXNzaWdubWVudHMiXSkpCiAgCiAgY2F0KCJcblxuIiwgIk5QQy1MaWtlMiBjZWxscyBpbiB0aGlzIGNsdXN0ZXI6XG4iKQogIGNhdCgiVG90YWwgbnVtYmVyIG9mIE5QQy1MaWtlMjogIiwgbnJvdyhjdXJyUGF0aWVudE5QQ0xpa2UyKSkKICBwcmludCh0YWJsZShjdXJyUGF0aWVudE5QQ0xpa2UyWywgImNsdXN0ZXJBc3NpZ25tZW50cyJdKSkKICAKICBjYXQoIlxuXG4iKQp9CmBgYApgYGB7cn0KY2x1c3RlcnNfYXNzaWdubWVudF9yZXN1bHRzID0gZGF0YS5mcmFtZShjbHVzdGVyID0gMDoxNSkKY2x1c3RlcnNfYXNzaWdubWVudF9yZXN1bHRzWywiTWFjcm9waGFnZXMiXSA9IHRhYmxlKG1hY3JvcGhhZ2VPdmVyVGhyZXNoWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoRnJlcSkKY2x1c3RlcnNfYXNzaWdubWVudF9yZXN1bHRzWywiVENlbGxTIl0gPSB0YWJsZShUQ2VsbHNPdmVyVGhyZXNoWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoRnJlcSkKY2x1c3RlcnNfYXNzaWdubWVudF9yZXN1bHRzWywiT2xpZ29kZWNkcm9jeXRlcyJdID0gdGFibGUoT2xpZ29kZW5kcm9jeXRlc092ZXJUaHJlc2hbLCJjbHVzdGVyQXNzaWdubWVudHMiXSkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcHVsbChGcmVxKQpjbHVzdGVyc19hc3NpZ25tZW50X3Jlc3VsdHNbLCJOUEMxIl0gPSB0YWJsZShOUENzMU92ZXJUaHJlc2hbLCJjbHVzdGVyQXNzaWdubWVudHMiXSkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgcHVsbChGcmVxKQoKY2x1c3RlcnNfYXNzaWdubWVudF9yZXN1bHRzWywiTlBDMiJdID0gdGFibGUoTlBDczJPdmVyVGhyZXNoWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoRnJlcSkKY2x1c3RlcnNfYXNzaWdubWVudF9yZXN1bHRzWywiQUNMaWtlIl0gPSB0YWJsZShBQ0xpa2VzT3ZlclRocmVzaFssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwdWxsKEZyZXEpCmNsdXN0ZXJzX2Fzc2lnbm1lbnRfcmVzdWx0c1ssIk9QQ0xpa2UiXSA9IHRhYmxlKE9QQ0xpa2VzT3ZlclRocmVzaFssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwdWxsKEZyZXEpCgpjbHVzdGVyc19hc3NpZ25tZW50X3Jlc3VsdHNbLCJNRVMxIl0gPSB0YWJsZShNRVMxT3ZlclRocmVzaFssImNsdXN0ZXJBc3NpZ25tZW50cyJdKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBwdWxsKEZyZXEpCmNsdXN0ZXJzX2Fzc2lnbm1lbnRfcmVzdWx0c1ssIk1FUzIiXSA9IHRhYmxlKE1FUzJPdmVyVGhyZXNoWywiY2x1c3RlckFzc2lnbm1lbnRzIl0pICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHB1bGwoRnJlcSkKCmBgYAoKYGBge3J9CmRmMiA9IHJlc2hhcGUyOjptZWx0KGNsdXN0ZXJzX2Fzc2lnbm1lbnRfcmVzdWx0cywgaWQudmFycyA9IGMoImNsdXN0ZXIiKSwgdmFyaWFibGUubmFtZSA9ICJjZWxsX3R5cGUiLCB2YWx1ZS5uYW1lID0gIm5fY2VsbHMiKQpnZ3Bsb3QoZGF0YT1kZjIsIGFlcyh4PWNsdXN0ZXIsIHk9bl9jZWxscywgZmlsbD1jZWxsX3R5cGUpKSArCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKSt0aGVtZV9taW5pbWFsKCkgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPTA6MTUpKyBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJQYWlyZWQiKQpgYGAKCiMgU2Vzc2lvbgpgYGB7cn0Kc2Vzc2lvbl9pbmZvKCkKYGBgCgoKPHNjcmlwdCBzcmM9Imh0dHBzOi8vaHlwb3RoZXMuaXMvZW1iZWQuanMiIGFzeW5jPjwvc2NyaXB0PgoK