Functions
source_from_github(repositoy = "DEG_functions",version = "0.2.45")
ℹ SHA-1 hash of file is 645c7d8e9571eb9caed4b7baf4b058f6a2c051cc
Data
genesets <- msigdb_download("Homo sapiens",category="H") %>% append( msigdb_download("Homo sapiens",category="C2",subcategory = "CP:KEGG"))
genesets[["HIF_targets"]] = hif_targets
from cnmf import cNMF
import pickle
f = open('./Data/cnmf/cnmf_objects/models_2Kvargenes_all_K_cnmf_obj.pckl', 'rb')
cnmf_obj = pickle.load(f)
f.close()
K selection plot
plot_path = paste0("/sci/labs/yotamd/lab_share/avishai.wizel/R_projects/EGFR/Data/cNMF/cNMF_models_Varnorm_Harmony_2Kvargenes_all_K/cNMF_models_Varnorm_Harmony_2Kvargenes_all_K.k_selection.png")
knitr::include_graphics(plot_path)

k = 5
density_threshold = 0.1
cnmf_obj.consensus(k=k, density_threshold=density_threshold,show_clustering=True)
usage_norm, gep_scores, _, _ = cnmf_obj.load_results(K=k, density_threshold=density_threshold)
usage_norm5_xeno = py$usage_norm
gep_scores5_xeno = py$gep_scores
gep_scores = gep_scores5_xeno
usage_norm = usage_norm5_xeno
NMF usage

Programs
GSEA

for (col in seq_along(gep_scores)) {
ranked_vec = gep_scores[,col] %>% setNames(rownames(gep_scores)) %>% sort(decreasing = TRUE)
hyp_obj <- hypeR_fgsea(ranked_vec, genesets)
print_tab(hyp_dots(hyp_obj,title = paste("program",col))+ aes(size=nes),title = paste0("gep",col))
}
gep1

gep2

gep3

Warning in fgsea::fgseaMultilevel(stats = signature, pathways =
gsets.obj$genesets, : There were 1 pathways for which P-values were not
calculated properly due to unbalanced (positive and negative) gene-level
statistic values. For such pathways pval, padj, NES, log2err are set to
NA. You can try to increase the value of the argument nPermSimple (for
example set it nPermSimple = 10000) ## gep4 {.unnumbered }

Warning in fgsea::fgseaMultilevel(stats = signature, pathways =
gsets.obj$genesets, : There were 2 pathways for which P-values were not
calculated properly due to unbalanced (positive and negative) gene-level
statistic values. For such pathways pval, padj, NES, log2err are set to
NA. You can try to increase the value of the argument nPermSimple (for
example set it nPermSimple = 10000) ## gep5 {.unnumbered }

NA
programs_main_pathways = list(gep1 = "HALLMARK_INTERFERON_ALPHA_RESPONSE", gep2 = c("HALLMARK_TNFA_SIGNALING_VIA_NFKB","KEGG_ASTHMA","KEGG_TOLL_LIKE_RECEPTOR_SIGNALING_PATHWAY"),gep3 = c("HALLMARK_HYPOXIA","HIF_targets"),gep4 = "HALLMARK_E2F_TARGETS")
xeno = FindVariableFeatures(object = xeno,nfeatures = 2000)
xeno_vargenes = VariableFeatures(object = xeno)
xeno_expression = FetchData(object = xeno,vars = xeno_vargenes,slot='counts')
all_0_genes = colnames(xeno_expression)[colSums(xeno_expression==0, na.rm=TRUE)==nrow(xeno_expression)] #delete rows that have all 0
xeno_vargenes = xeno_vargenes[!xeno_vargenes %in% all_0_genes]
calculate score for
Xeno
import numpy as np
import scanpy as sc
xeno_expression = r.xeno_expression
xeno_vargenes = r.xeno_vargenes
tpm = compute_tpm(xeno_expression)
usage_by_calc = get_usage_from_score(counts=xeno_expression,tpm=tpm,genes=xeno_vargenes, cnmf_obj=cnmf_obj,k=5)
/sci/labs/yotamd/lab_share/avishai.wizel/python_envs/miniconda/envs/cnmf_env_6/bin/python3.7:7: FutureWarning: X.dtype being converted to np.float32 from float64. In the next version of anndata (0.9) conversion will not be automatic. Pass dtype explicitly to avoid this warning. Pass `AnnData(X, dtype=X.dtype, ...)` to get the future behavour.
/sci/labs/yotamd/lab_share/avishai.wizel/python_envs/miniconda/envs/cnmf_env_6/bin/python3.7:8: FutureWarning: X.dtype being converted to np.float32 from float64. In the next version of anndata (0.9) conversion will not be automatic. Pass dtype explicitly to avoid this warning. Pass `AnnData(X, dtype=X.dtype, ...)` to get the future behavour.
xeno_5_metagenes = py$usage_by_calc
all_metagenes = xeno_5_metagenes
colnames(all_metagenes) = c("IFNa","immune_response", "hypoxia","cell_cycle","unknown")
programs
expression
#add each metagene to metadata
for (i in 1:ncol(all_metagenes)) {
metagene_metadata = all_metagenes[,i,drop=F]
xeno = AddMetaData(object = xeno,metadata = metagene_metadata,col.name = names(all_metagenes)[i])
}
FeaturePlot(object = xeno,features = colnames(all_metagenes),ncol = 3)

NA
NA
Programs dotplot
DotPlot(object = xeno, features = colnames(all_metagenes),group.by = 'treatment',scale = T)+
guides(size = guide_legend(title = "Cells expressing (%)"),color = guide_colorbar(title = "Average Score"))

#rename unknown to resistant program
xeno$resistant_program <- xeno$unknown
xeno$unknown <- NULL
all_metagenes = all_metagenes %>% dplyr::rename(resistant_program=unknown)
NMF
programs regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = colnames(all_metagenes)[1:4],without_split = F)
IFNa per patient

immune_response per patient

hypoxia per patient

cell_cycle per patient

NA
LE
genes programs UMAP
le_genes
NULL
LE
genes programs regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = programs_main_pathways %>% unlist() %>% paste0("_le"),without_split = F)
HALLMARK_INTERFERON_ALPHA_RESPONSE_le per
patient

HALLMARK_TNFA_SIGNALING_VIA_NFKB_le per
patient

KEGG_ASTHMA_le per patient

KEGG_TOLL_LIKE_RECEPTOR_SIGNALING_PATHWAY_le per
patient

HALLMARK_HYPOXIA_le per patient

HIF_targets_le per patient

HALLMARK_E2F_TARGETS_le per patient

NA

Top program 2 genes
expression correlation
top_ot = gep_scores [order(gep_scores [,2],decreasing = T),2,drop = F]%>% head(200) %>% rownames()
num_of_clusters = 7
annotation = plot_genes_cor(dataset = xeno,num_of_clusters = num_of_clusters,geneIds = top_ot,height = 3)
program
2 all clusters expression
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
# print(chosen_genes)
hyp_obj <- hypeR(chosen_genes, genesets, test = "hypergeometric", fdr=1, plotting=F,background = rownames(xeno_5_gep_scores))
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,paste0("cluster",chosen_clusters))
print_tab(plt =
hyp_dots(hyp_obj,size_by = "none",title = paste0("cluster",chosen_clusters))+
FeaturePlot(object = xeno,features = paste0("cluster",chosen_clusters)),
title = chosen_clusters)
}
Correlation of
clusters
for (chosen_clusters in 1:num_of_clusters) {
cor_res = cor(xeno$TNFa,xeno[[paste0("cluster",chosen_clusters)]])
print(paste("correlation of TNFa program to", paste0("cluster",chosen_clusters),":", cor_res))
}
clusters_idents = c("cluster1", "KEGG_OXIDATIVE_PHOSPHORYLATION","HALLMARK_TNFA_SIGNALING_VIA_NFKB","KEGG_ANTIGEN_PROCESSING_AND_PRESENTATION","HALLMARK_P53_PATHWAY","cluster6","cluster7")
program 2 intersected
genes
programs_of_cluster = c()
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
pathway_name = clusters_idents[chosen_clusters]
if (!startsWith(x = pathway_name,prefix = "cluster")){
chosen_genes = (chosen_genes) %>% intersect(genesets[[pathway_name]])
pathway_name = paste0(pathway_name,"_cluster")
}
programs_of_cluster = c(programs_of_cluster,pathway_name)
print(pathway_name)
print(chosen_genes)
cat("\n")
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,pathway_name)
}
program
2 intersected pathway regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = programs_of_cluster,without_split = F)
program 2 significant
plot
signf_plot_pre_vs_on<- function(dataset,programs,patient.ident_var,prefix,pre_on,test,time.point_var) {
final_df = data.frame()
for (metegene in programs) {
genes_by_tp = FetchData(object = dataset,vars = metegene) %>% rowSums() %>% as.data.frame() #mean expression
names(genes_by_tp)[1] = metegene
genes_by_tp = cbind(genes_by_tp,FetchData(object = dataset,vars = c(patient.ident_var,time.point_var))) # add id and time points
genes_by_tp_forPlot = genes_by_tp %>% mutate(!!ensym(patient.ident_var) := paste(prefix,genes_by_tp[,patient.ident_var])) #add "model" before each model/patient
fm <- as.formula(paste(metegene, "~", time.point_var)) #make formula to plot
#plot and split by patient:
stat.test = compare_means(formula = fm ,data = genes_by_tp_forPlot,method = test,group.by = patient.ident_var)%>%
dplyr::filter(group1 == pre_on[1] & group2 == pre_on[2]) #filter for pre vs on treatment only
final_df = rbind(final_df,stat.test)
}
return(final_df)
}
undebug(signf_plot_pre_vs_on)
final_df = signf_plot_pre_vs_on(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = programs_of_cluster )
final_df = reshape2::dcast(final_df, orig.ident ~.y.,value.var = "p.adj") %>% column_to_rownames("orig.ident")
sig_heatmap(all_patients_result = final_df,title = "ad")
Top program 3 genes
expression correlation
top_hypoxia = gep_scores [order(gep_scores [,3],decreasing = T),2,drop = F]%>% head(200) %>% rownames()
num_of_clusters = 4
annotation = plot_genes_cor(dataset = xeno,hallmark_name = NULL,num_of_clusters = num_of_clusters,geneIds = top_hypoxia)
program
3 all clusters expression
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
# print(chosen_genes)
hyp_obj <- hypeR(chosen_genes, genesets_env, test = "hypergeometric", fdr=1, plotting=F,background = rownames(xeno_5_gep_scores))
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,paste0("cluster",chosen_clusters))
print_tab(plt =
hyp_dots(hyp_obj,size_by = "none",title = paste0("cluster",chosen_clusters))+
FeaturePlot(object = xeno,features = paste0("cluster",chosen_clusters)),
title = chosen_clusters)
}
Correlation of
clusters
for (chosen_clusters in 1:num_of_clusters) {
cor_res = cor(xeno$hypoxia,xeno[[paste0("cluster",chosen_clusters)]])
print(paste("correlation of hypoxia program to", paste0("cluster",chosen_clusters),":", cor_res))
}
clusters_idents = c("HALLMARK_HYPOXIA", "HIF_targets","cluster3","cluster4")
programs_of_cluster = c()
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
pathway_name = clusters_idents[chosen_clusters]
if (!startsWith(x = pathway_name,prefix = "cluster")){
chosen_genes = (chosen_genes) %>% intersect(genesets[[pathway_name]])
pathway_name = paste0(pathway_name,"_cluster")
}
programs_of_cluster = c(programs_of_cluster,pathway_name)
print(pathway_name)
print(chosen_genes)
cat("\n")
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,pathway_name)
}
program
3 intersected pathway regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = programs_of_cluster,without_split = F)
Top program 3 genes
expression correlation
top_cc = gep_scores [order(gep_scores [,4],decreasing = T),2,drop = F]%>% head(200) %>% rownames()
num_of_clusters = 4
annotation = plot_genes_cor(dataset = xeno,hallmark_name = NULL,num_of_clusters = num_of_clusters,geneIds = top_cc)
program
3 all clusters expression
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
# print(chosen_genes)
hyp_obj <- hypeR(chosen_genes, genesets_env, test = "hypergeometric", fdr=1, plotting=F,background = rownames(xeno_5_gep_scores))
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,paste0("cluster",chosen_clusters))
print_tab(plt =
hyp_dots(hyp_obj,size_by = "none",title = paste0("cluster",chosen_clusters))+
FeaturePlot(object = xeno,features = paste0("cluster",chosen_clusters)),
title = chosen_clusters)
}
LS0tCnRpdGxlOiAnYHIgcnN0dWRpb2FwaTo6Z2V0U291cmNlRWRpdG9yQ29udGV4dCgpJHBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLHJlcGxhY2VtZW50ID0gIiIpYCcgCmF1dGhvcjogIkF2aXNoYWkgV2l6ZWwiCmRhdGU6ICdgciBTeXMudGltZSgpYCcKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19jb2xsYXBzZTogeWVzCiAgICB0b2NfZmxvYXQ6IAogICAgICBjb2xsYXBzZWQ6IEZBTFNFCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvY19kZXB0aDogMQotLS0KCgoKIyBGdW5jdGlvbnMKCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQoKbGlicmFyeShzdHJpbmdpKQpsaWJyYXJ5KHJldGljdWxhdGUpCnNvdXJjZV9mcm9tX2dpdGh1YihyZXBvc2l0b3kgPSAiREVHX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjIuNDUiKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gImNOTUZfZnVuY3Rpb25zIix2ZXJzaW9uID0gIjAuNC4wIixzY3JpcHRfbmFtZSA9ICJjbm1mX2Z1bmN0aW9uc19WMy5SIikKc291cmNlX2Zyb21fZ2l0aHViKHJlcG9zaXRveSA9ICJzY19nZW5lcmFsX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjEuMCIsc2NyaXB0X25hbWUgPSAiZnVuY3Rpb25zLlIiKQoKYGBgCgojIERhdGEKCmBgYHtyfQpnZW5lc2V0cyA8LSBtc2lnZGJfZG93bmxvYWQoIkhvbW8gc2FwaWVucyIsY2F0ZWdvcnk9IkgiKSAlPiUgYXBwZW5kKCBtc2lnZGJfZG93bmxvYWQoIkhvbW8gc2FwaWVucyIsY2F0ZWdvcnk9IkMyIixzdWJjYXRlZ29yeSA9ICJDUDpLRUdHIikpCmdlbmVzZXRzW1siSElGX3RhcmdldHMiXV0gPSBoaWZfdGFyZ2V0cwpgYGAKCgpgYGB7cHl0aG9ufQpmcm9tIGNubWYgaW1wb3J0IGNOTUYKaW1wb3J0IHBpY2tsZQpmID0gb3BlbignLi9EYXRhL2NubWYvY25tZl9vYmplY3RzL21vZGVsc18yS3ZhcmdlbmVzX2FsbF9LX2NubWZfb2JqLnBja2wnLCAncmInKQpjbm1mX29iaiA9IHBpY2tsZS5sb2FkKGYpCmYuY2xvc2UoKQpgYGAKCiMgSyBzZWxlY3Rpb24gcGxvdApgYGB7ciBmaWcuaGVpZ2h0PTIsIGZpZy53aWR0aD0yfQpwbG90X3BhdGggPSBwYXN0ZTAoIi9zY2kvbGFicy95b3RhbWQvbGFiX3NoYXJlL2F2aXNoYWkud2l6ZWwvUl9wcm9qZWN0cy9FR0ZSL0RhdGEvY05NRi9jTk1GX21vZGVsc19WYXJub3JtX0hhcm1vbnlfMkt2YXJnZW5lc19hbGxfSy9jTk1GX21vZGVsc19WYXJub3JtX0hhcm1vbnlfMkt2YXJnZW5lc19hbGxfSy5rX3NlbGVjdGlvbi5wbmciKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwbG90X3BhdGgpCmBgYAoKYGBge3B5dGhvbn0KayA9IDUKZGVuc2l0eV90aHJlc2hvbGQgPSAwLjEgCmNubWZfb2JqLmNvbnNlbnN1cyhrPWssIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkLHNob3dfY2x1c3RlcmluZz1UcnVlKQp1c2FnZV9ub3JtLCBnZXBfc2NvcmVzLCBfLCBfID0gY25tZl9vYmoubG9hZF9yZXN1bHRzKEs9aywgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQpCmBgYAoKYGBge3J9CnVzYWdlX25vcm01X3hlbm8gID0gcHkkdXNhZ2Vfbm9ybQpnZXBfc2NvcmVzNV94ZW5vID0gcHkkZ2VwX3Njb3JlcwpgYGAKCmBgYHtyfQpnZXBfc2NvcmVzID0gZ2VwX3Njb3JlczVfeGVubwp1c2FnZV9ub3JtID0gdXNhZ2Vfbm9ybTVfeGVubyAKYGBgCgojIE5NRiB1c2FnZQpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMiwgcmVzdWx0cz0nYXNpcyd9CiAgZm9yIChpIGluIDE6bmNvbCh1c2FnZV9ub3JtKSkgewogICAgbWV0YWdlX21ldGFkYXRhID0gdXNhZ2Vfbm9ybSAlPiUgZHBseXI6OnNlbGVjdChpKQogICAgeGVubyA9IEFkZE1ldGFEYXRhKG9iamVjdCA9IHhlbm8sbWV0YWRhdGEgPSBtZXRhZ2VfbWV0YWRhdGEsY29sLm5hbWUgPSBwYXN0ZTAoImdlcCIsaSkpCiAgfQogIAogIEZlYXR1cmVQbG90KG9iamVjdCA9IHhlbm8sZmVhdHVyZXMgPSBwYXN0ZTAoImdlcCIsMTpuY29sKHVzYWdlX25vcm0pKSxuY29sID0gMykKYGBgCiMgUHJvZ3JhbXMgR1NFQSB7LnRhYnNldH0KCmBgYHtyIHJlc3VsdHM9J2FzaXMnfQogIGZvciAoY29sIGluIHNlcV9hbG9uZyhnZXBfc2NvcmVzKSkgewogICAgIHJhbmtlZF92ZWMgPSBnZXBfc2NvcmVzWyxjb2xdICU+JSBzZXROYW1lcyhyb3duYW1lcyhnZXBfc2NvcmVzKSkgJT4lIHNvcnQoZGVjcmVhc2luZyA9IFRSVUUpIAogICAgIGh5cF9vYmogPC0gaHlwZVJfZmdzZWEocmFua2VkX3ZlYywgZ2VuZXNldHMpCiAgICAgICBwcmludF90YWIoaHlwX2RvdHMoaHlwX29iaix0aXRsZSA9IHBhc3RlKCJwcm9ncmFtIixjb2wpKSsgYWVzKHNpemU9bmVzKSx0aXRsZSA9IHBhc3RlMCgiZ2VwIixjb2wpKQogIH0KYGBgCgpgYGB7cn0KcHJvZ3JhbXNfbWFpbl9wYXRod2F5cyA9IGxpc3QoZ2VwMSA9ICJIQUxMTUFSS19JTlRFUkZFUk9OX0FMUEhBX1JFU1BPTlNFIiwgZ2VwMiA9IGMoIkhBTExNQVJLX1RORkFfU0lHTkFMSU5HX1ZJQV9ORktCIiwiS0VHR19BU1RITUEiLCJLRUdHX1RPTExfTElLRV9SRUNFUFRPUl9TSUdOQUxJTkdfUEFUSFdBWSIpLGdlcDMgPSBjKCJIQUxMTUFSS19IWVBPWElBIiwiSElGX3RhcmdldHMiKSxnZXA0ID0gIkhBTExNQVJLX0UyRl9UQVJHRVRTIikKYGBgCgpgYGB7cn0KeGVubyA9IEZpbmRWYXJpYWJsZUZlYXR1cmVzKG9iamVjdCA9IHhlbm8sbmZlYXR1cmVzID0gMjAwMCkKeGVub192YXJnZW5lcyA9IFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0geGVubykKCnhlbm9fZXhwcmVzc2lvbiA9IEZldGNoRGF0YShvYmplY3QgPSB4ZW5vLHZhcnMgPSB4ZW5vX3ZhcmdlbmVzLHNsb3Q9J2NvdW50cycpCmFsbF8wX2dlbmVzID0gY29sbmFtZXMoeGVub19leHByZXNzaW9uKVtjb2xTdW1zKHhlbm9fZXhwcmVzc2lvbj09MCwgbmEucm09VFJVRSk9PW5yb3coeGVub19leHByZXNzaW9uKV0gI2RlbGV0ZSByb3dzIHRoYXQgaGF2ZSBhbGwgMAp4ZW5vX3ZhcmdlbmVzID0geGVub192YXJnZW5lc1sheGVub192YXJnZW5lcyAlaW4lIGFsbF8wX2dlbmVzXQoKYGBgCgoKIyBjYWxjdWxhdGUgc2NvcmUgZm9yIFhlbm8KYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBzY2FucHkgYXMgc2MKeGVub19leHByZXNzaW9uID0gci54ZW5vX2V4cHJlc3Npb24KeGVub192YXJnZW5lcyA9IHIueGVub192YXJnZW5lcwp0cG0gPSAgY29tcHV0ZV90cG0oeGVub19leHByZXNzaW9uKQp1c2FnZV9ieV9jYWxjID0gZ2V0X3VzYWdlX2Zyb21fc2NvcmUoY291bnRzPXhlbm9fZXhwcmVzc2lvbix0cG09dHBtLGdlbmVzPXhlbm9fdmFyZ2VuZXMsIGNubWZfb2JqPWNubWZfb2JqLGs9aykKYGBgCgpgYGB7cn0KeGVub181X21ldGFnZW5lcyA9IHB5JHVzYWdlX2J5X2NhbGMKYGBgCgpgYGB7cn0KYWxsX21ldGFnZW5lcyA9IHhlbm9fNV9tZXRhZ2VuZXMKY29sbmFtZXMoYWxsX21ldGFnZW5lcykgPSBjKCJJRk5hIiwiaW1tdW5lX3Jlc3BvbnNlIiwgImh5cG94aWEiLCJjZWxsX2N5Y2xlIiwidW5rbm93biIpCgpgYGAKCiMgcHJvZ3JhbXMgZXhwcmVzc2lvbgpgYGB7ciBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTEyLCByZXN1bHRzPSdhc2lzJ30KCiNhZGQgZWFjaCBtZXRhZ2VuZSB0byBtZXRhZGF0YQpmb3IgKGkgIGluIDE6bmNvbChhbGxfbWV0YWdlbmVzKSkgewogIG1ldGFnZW5lX21ldGFkYXRhID0gYWxsX21ldGFnZW5lc1ssaSxkcm9wPUZdCiAgeGVubyA9IEFkZE1ldGFEYXRhKG9iamVjdCA9IHhlbm8sbWV0YWRhdGEgPSBtZXRhZ2VuZV9tZXRhZGF0YSxjb2wubmFtZSA9IG5hbWVzKGFsbF9tZXRhZ2VuZXMpW2ldKQp9CgpGZWF0dXJlUGxvdChvYmplY3QgPSB4ZW5vLGZlYXR1cmVzID0gY29sbmFtZXMoYWxsX21ldGFnZW5lcyksbmNvbCA9IDMpCgoKYGBgCiMgUHJvZ3JhbXMgZG90cGxvdApgYGB7ciBmaWcud2lkdGg9OH0KRG90UGxvdChvYmplY3QgPSB4ZW5vLCBmZWF0dXJlcyA9ICBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKSxncm91cC5ieSAgPSAndHJlYXRtZW50JyxzY2FsZSA9IFQpKwogIGd1aWRlcyhzaXplID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIkNlbGxzIGV4cHJlc3NpbmcgKCUpIiksY29sb3IgPSBndWlkZV9jb2xvcmJhcih0aXRsZSA9ICJBdmVyYWdlIFNjb3JlIikpCmBgYAoKCgoKYGBge3J9CiNyZW5hbWUgdW5rbm93biB0byByZXNpc3RhbnQgcHJvZ3JhbQp4ZW5vJHJlc2lzdGFudF9wcm9ncmFtIDwtIHhlbm8kdW5rbm93bgp4ZW5vJHVua25vd24gPC0gTlVMTAphbGxfbWV0YWdlbmVzID0gYWxsX21ldGFnZW5lcyAlPiUgZHBseXI6OnJlbmFtZShyZXNpc3RhbnRfcHJvZ3JhbT11bmtub3duKQpgYGAKCiMgTk1GIHByb2dyYW1zIHJlZ3VsYXRpb24gIHsudGFic2V0fQoKYGBge3IgZWNobz1UUlVFLCAgcmVzdWx0cz0nYXNpcyd9Cm1ldGFnZW5lc19tZWFuX2NvbXBhcmUoZGF0YXNldCA9IHhlbm8sdGltZS5wb2ludF92YXIgPSAidHJlYXRtZW50IixwcmVmaXggPSAibW9kZWwiLHBhdGllbnQuaWRlbnRfdmFyID0gIm9yaWcuaWRlbnQiLHByZV9vbiA9IGMoIk5UIiwiT1NJIiksdGVzdCA9ICJ3aWxjb3gudGVzdCIscHJvZ3JhbXMgPSBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKVsxOjRdLHdpdGhvdXRfc3BsaXQgPSBGKQpgYGAKCgojIExFIGdlbmVzIHByb2dyYW1zIFVNQVAgIHsudGFic2V0fQoKCmBgYHtyIHJlc3VsdHM9J2FzaXMnfQogIGZvciAoY29sIGluIHNlcV9hbG9uZyhnZXBfc2NvcmVzWzE6NF0pKSB7CiAgICAgcmFua2VkX3ZlYyA9IGdlcF9zY29yZXNbLGNvbF0gJT4lIHNldE5hbWVzKHJvd25hbWVzKGdlcF9zY29yZXMpKSAlPiUgc29ydChkZWNyZWFzaW5nID0gVFJVRSkgCiAgICAgaHlwX29iaiA8LSBoeXBlUl9mZ3NlYShyYW5rZWRfdmVjLCBnZW5lc2V0cykKICAgICBmb3IgKHBhdGh3YXkgaW4gcHJvZ3JhbXNfbWFpbl9wYXRod2F5c1tbY29sXV0pIHsKICAgICAgICBsZV9nZW5lcyA9ICBoeXBfb2JqJGRhdGEgJT4lIGZpbHRlcihsYWJlbCA9PSBwYXRod2F5KSAlPiUgcHVsbCgibGUiKSAlPiUgc3Ryc3BsaXQoIiwiKSAlPiUgdW5saXN0KCkKICAgICAgICBzY29yZXNBbmRJbmRpY2VzID0gZ2V0UGF0aHdheVNjb3JlcyhkYXRhTWF0cml4ID0geGVub0Bhc3NheXMkUk5BQGRhdGEscGF0aHdheUdlbmVzID0gbGVfZ2VuZXMpCiAgICAgICAgcGF0aHdheV9uYW1lID0gcGFzdGUwKHBhdGh3YXksIl9sZSIpCiAgICAgICAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3Jlcyxjb2wubmFtZSA9IHBhdGh3YXlfbmFtZSkKICAgICAgICBwcmludF90YWIoRmVhdHVyZVBsb3Qob2JqZWN0ID0geGVubyxmZWF0dXJlcyA9IHBhdGh3YXlfbmFtZSksdGl0bGUgPSBwYXRod2F5X25hbWUpCiAgICAgfQogIH0KYGBgCgoKIyBMRSBnZW5lcyBwcm9ncmFtcyByZWd1bGF0aW9uICB7LnRhYnNldH0KCmBgYHtyICByZXN1bHRzPSdhc2lzJ30KbWV0YWdlbmVzX21lYW5fY29tcGFyZShkYXRhc2V0ID0geGVubyx0aW1lLnBvaW50X3ZhciA9ICJ0cmVhdG1lbnQiLHByZWZpeCA9ICJtb2RlbCIscGF0aWVudC5pZGVudF92YXIgPSAib3JpZy5pZGVudCIscHJlX29uID0gYygiTlQiLCJPU0kiKSx0ZXN0ID0gIndpbGNveC50ZXN0Iixwcm9ncmFtcyA9IHByb2dyYW1zX21haW5fcGF0aHdheXMgJT4lIHVubGlzdCgpICU+JSBwYXN0ZTAoIl9sZSIpLHdpdGhvdXRfc3BsaXQgPSBGKQpgYGAKYGBge3J9CmNvbD0yCnJhbmtlZF92ZWMgPSBnZXBfc2NvcmVzWywgY29sXSAlPiUgc2V0TmFtZXMocm93bmFtZXMoZ2VwX3Njb3JlcykpICU+JSBzb3J0KGRlY3JlYXNpbmcgPSBUUlVFKQpwcmludCAocGFzdGUoInJ1bm5pbmcgZ2VwIixjb2wpKQpoeXBfb2JqIDwtaHlwZVJfZmdzZWEocmFua2VkX3ZlYywgZ2VuZXNldHNfZ28sIHVwX29ubHkgPSBUKQoKcHJpbnQoaHlwX2RvdHMoaHlwX29iaiwgdGl0bGUgPSBwYXN0ZSgicHJvZ3JhbSIsIGNvbCksIGFicnYgPSA3MCkgKyBhZXMoc2l6ZSA9bmVzKSkKICAKYGBgCgojIFRvcCBwcm9ncmFtIDIgZ2VuZXMgZXhwcmVzc2lvbiBjb3JyZWxhdGlvbgpgYGB7cn0KdG9wX290ID0gZ2VwX3Njb3JlcyBbb3JkZXIoZ2VwX3Njb3JlcyBbLDJdLGRlY3JlYXNpbmcgPSBUKSwyLGRyb3AgPSBGXSU+JSBoZWFkKDIwMCkgJT4lIHJvd25hbWVzKCkKCm51bV9vZl9jbHVzdGVycyA9IDcKYW5ub3RhdGlvbiA9IHBsb3RfZ2VuZXNfY29yKGRhdGFzZXQgPSB4ZW5vLG51bV9vZl9jbHVzdGVycyA9IG51bV9vZl9jbHVzdGVycyxnZW5lSWRzID0gdG9wX290LGhlaWdodCA9IDMpCgpgYGAKCiMgIHByb2dyYW0gMiBhbGwgY2x1c3RlcnMgZXhwcmVzc2lvbiB7LnRhYnNldH0KYGBge3IgcmVzdWx0cz0nYXNpcycsZmlnLndpZHRoPTE0fQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb24gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgIyBwcmludChjaG9zZW5fZ2VuZXMpCiAgaHlwX29iaiA8LSBoeXBlUihjaG9zZW5fZ2VuZXMsIGdlbmVzZXRzLCB0ZXN0ID0gImh5cGVyZ2VvbWV0cmljIiwgZmRyPTEsIHBsb3R0aW5nPUYsYmFja2dyb3VuZCA9IHJvd25hbWVzKHhlbm9fNV9nZXBfc2NvcmVzKSkKCiAgIHNjb3Jlc0FuZEluZGljZXMgPC0gZ2V0UGF0aHdheVNjb3Jlcyh4ZW5vQGFzc2F5cyRSTkFAZGF0YSwgY2hvc2VuX2dlbmVzKQogIHhlbm89QWRkTWV0YURhdGEoeGVubyxzY29yZXNBbmRJbmRpY2VzJHBhdGh3YXlTY29yZXMscGFzdGUwKCJjbHVzdGVyIixjaG9zZW5fY2x1c3RlcnMpKQoKICAKICBwcmludF90YWIocGx0ID0gCiAgICAgICAgICAgICAgaHlwX2RvdHMoaHlwX29iaixzaXplX2J5ID0gIm5vbmUiLHRpdGxlID0gcGFzdGUwKCJjbHVzdGVyIixjaG9zZW5fY2x1c3RlcnMpKSsKICAgICAgICAgICAgICBGZWF0dXJlUGxvdChvYmplY3QgPSB4ZW5vLGZlYXR1cmVzID0gcGFzdGUwKCJjbHVzdGVyIixjaG9zZW5fY2x1c3RlcnMpKSwKICAgICAgICAgICAgdGl0bGUgPSBjaG9zZW5fY2x1c3RlcnMpCn0KCgpgYGAKCiMgQ29ycmVsYXRpb24gb2YgY2x1c3RlcnMKYGBge3J9CmZvciAoY2hvc2VuX2NsdXN0ZXJzIGluIDE6bnVtX29mX2NsdXN0ZXJzKSB7CiAgCiAgY29yX3JlcyA9IGNvcih4ZW5vJFRORmEseGVub1tbcGFzdGUwKCJjbHVzdGVyIixjaG9zZW5fY2x1c3RlcnMpXV0pCnByaW50KHBhc3RlKCJjb3JyZWxhdGlvbiBvZiBUTkZhIHByb2dyYW0gdG8iLCBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycyksIjoiLCBjb3JfcmVzKSkKCn0KYGBgCgoKYGBge3J9CmNsdXN0ZXJzX2lkZW50cyA9IGMoImNsdXN0ZXIxIiwgIktFR0dfT1hJREFUSVZFX1BIT1NQSE9SWUxBVElPTiIsIkhBTExNQVJLX1RORkFfU0lHTkFMSU5HX1ZJQV9ORktCIiwiS0VHR19BTlRJR0VOX1BST0NFU1NJTkdfQU5EX1BSRVNFTlRBVElPTiIsIkhBTExNQVJLX1A1M19QQVRIV0FZIiwiY2x1c3RlcjYiLCJjbHVzdGVyNyIpCmBgYAoKCiMgIHByb2dyYW0gMiBpbnRlcnNlY3RlZCBnZW5lcwoKCgpgYGB7cn0KcHJvZ3JhbXNfb2ZfY2x1c3RlciA9IGMoKQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgcGF0aHdheV9uYW1lID0gY2x1c3RlcnNfaWRlbnRzW2Nob3Nlbl9jbHVzdGVyc10KICBpZiAoIXN0YXJ0c1dpdGgoeCA9IHBhdGh3YXlfbmFtZSxwcmVmaXggPSAiY2x1c3RlciIpKXsKICAgICAgY2hvc2VuX2dlbmVzICA9IChjaG9zZW5fZ2VuZXMpICU+JSBpbnRlcnNlY3QoZ2VuZXNldHNbW3BhdGh3YXlfbmFtZV1dKQogICAgICBwYXRod2F5X25hbWUgPSBwYXN0ZTAocGF0aHdheV9uYW1lLCJfY2x1c3RlciIpCiAgfQogIHByb2dyYW1zX29mX2NsdXN0ZXIgPSBjKHByb2dyYW1zX29mX2NsdXN0ZXIscGF0aHdheV9uYW1lKQogIHByaW50KHBhdGh3YXlfbmFtZSkKICBwcmludChjaG9zZW5fZ2VuZXMpCiAgY2F0KCJcbiIpCiAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXRod2F5X25hbWUpCgp9CmBgYAojICBwcm9ncmFtIDIgaW50ZXJzZWN0ZWQgcGF0aHdheSByZWd1bGF0aW9uIHsudGFic2V0fSAgICAgCgpgYGB7ciAgcmVzdWx0cz0nYXNpcyd9Cm1ldGFnZW5lc19tZWFuX2NvbXBhcmUoZGF0YXNldCA9IHhlbm8sdGltZS5wb2ludF92YXIgPSAidHJlYXRtZW50IixwcmVmaXggPSAibW9kZWwiLHBhdGllbnQuaWRlbnRfdmFyID0gIm9yaWcuaWRlbnQiLHByZV9vbiA9IGMoIk5UIiwiT1NJIiksdGVzdCA9ICJ3aWxjb3gudGVzdCIscHJvZ3JhbXMgPSBwcm9ncmFtc19vZl9jbHVzdGVyLHdpdGhvdXRfc3BsaXQgPSBGKQpgYGAKIyAgcHJvZ3JhbSAyIHNpZ25pZmljYW50IHBsb3QgIAoKYGBge3IgZmlnLmhlaWdodD0xMn0Kc2lnbmZfcGxvdF9wcmVfdnNfb248LSBmdW5jdGlvbihkYXRhc2V0LHByb2dyYW1zLHBhdGllbnQuaWRlbnRfdmFyLHByZWZpeCxwcmVfb24sdGVzdCx0aW1lLnBvaW50X3ZhcikgewogICAgZmluYWxfZGYgPSBkYXRhLmZyYW1lKCkKICAgIGZvciAobWV0ZWdlbmUgaW4gcHJvZ3JhbXMpIHsKICAgICAgZ2VuZXNfYnlfdHAgPSBGZXRjaERhdGEob2JqZWN0ID0gZGF0YXNldCx2YXJzID0gbWV0ZWdlbmUpICU+JSByb3dTdW1zKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAjbWVhbiBleHByZXNzaW9uCiAgICAgIG5hbWVzKGdlbmVzX2J5X3RwKVsxXSA9IG1ldGVnZW5lCiAgICAgIGdlbmVzX2J5X3RwID0gY2JpbmQoZ2VuZXNfYnlfdHAsRmV0Y2hEYXRhKG9iamVjdCA9IGRhdGFzZXQsdmFycyA9IGMocGF0aWVudC5pZGVudF92YXIsdGltZS5wb2ludF92YXIpKSkgIyBhZGQgaWQgYW5kIHRpbWUgcG9pbnRzCiAgICAgIAogICAgICAKICAgICAgZ2VuZXNfYnlfdHBfZm9yUGxvdCA9ICBnZW5lc19ieV90cCAlPiUgbXV0YXRlKCEhZW5zeW0ocGF0aWVudC5pZGVudF92YXIpIDo9IHBhc3RlKHByZWZpeCxnZW5lc19ieV90cFsscGF0aWVudC5pZGVudF92YXJdKSkgI2FkZCAibW9kZWwiIGJlZm9yZSAgZWFjaCBtb2RlbC9wYXRpZW50CiAgICAgIGZtIDwtIGFzLmZvcm11bGEocGFzdGUobWV0ZWdlbmUsICJ+IiwgdGltZS5wb2ludF92YXIpKSAjbWFrZSBmb3JtdWxhIHRvIHBsb3QKICAgICAgCiAgICAgICNwbG90IGFuZCBzcGxpdCBieSBwYXRpZW50OiAgIAogICAgICBzdGF0LnRlc3QgPSBjb21wYXJlX21lYW5zKGZvcm11bGEgPSBmbSAsZGF0YSA9IGdlbmVzX2J5X3RwX2ZvclBsb3QsbWV0aG9kID0gdGVzdCxncm91cC5ieSA9IHBhdGllbnQuaWRlbnRfdmFyKSU+JSAKICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKGdyb3VwMSA9PSBwcmVfb25bMV0gJiBncm91cDIgPT0gcHJlX29uWzJdKSAgI2ZpbHRlciBmb3IgcHJlIHZzIG9uIHRyZWF0bWVudCBvbmx5CiAgICAgIGZpbmFsX2RmID0gcmJpbmQoZmluYWxfZGYsc3RhdC50ZXN0KQogICAgfQogICAgcmV0dXJuKGZpbmFsX2RmKQp9Cgp1bmRlYnVnKHNpZ25mX3Bsb3RfcHJlX3ZzX29uKQpmaW5hbF9kZiA9IHNpZ25mX3Bsb3RfcHJlX3ZzX29uKGRhdGFzZXQgPSB4ZW5vLHRpbWUucG9pbnRfdmFyID0gInRyZWF0bWVudCIscHJlZml4ID0gIm1vZGVsIixwYXRpZW50LmlkZW50X3ZhciA9ICJvcmlnLmlkZW50IixwcmVfb24gPSBjKCJOVCIsIk9TSSIpLHRlc3QgPSAid2lsY294LnRlc3QiLHByb2dyYW1zID0gcHJvZ3JhbXNfb2ZfY2x1c3RlciApCmZpbmFsX2RmID0gcmVzaGFwZTI6OmRjYXN0KGZpbmFsX2RmLCBvcmlnLmlkZW50ICB+LnkuLHZhbHVlLnZhciA9ICJwLmFkaiIpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoIm9yaWcuaWRlbnQiKQoKc2lnX2hlYXRtYXAoYWxsX3BhdGllbnRzX3Jlc3VsdCA9IGZpbmFsX2RmLHRpdGxlID0gImFkIikKYGBgCiMgVG9wIHByb2dyYW0gMyBnZW5lcyBleHByZXNzaW9uIGNvcnJlbGF0aW9uCmBgYHtyfQp0b3BfaHlwb3hpYSA9IGdlcF9zY29yZXMgW29yZGVyKGdlcF9zY29yZXMgWywzXSxkZWNyZWFzaW5nID0gVCksMixkcm9wID0gRl0lPiUgaGVhZCgyMDApICU+JSByb3duYW1lcygpCgpudW1fb2ZfY2x1c3RlcnMgPSA0CmFubm90YXRpb24gPSBwbG90X2dlbmVzX2NvcihkYXRhc2V0ID0geGVubyxoYWxsbWFya19uYW1lID0gTlVMTCxudW1fb2ZfY2x1c3RlcnMgPSBudW1fb2ZfY2x1c3RlcnMsZ2VuZUlkcyA9IHRvcF9oeXBveGlhKQoKYGBgCiMgIHByb2dyYW0gMyBhbGwgY2x1c3RlcnMgZXhwcmVzc2lvbiB7LnRhYnNldH0KCmBgYHtyIHJlc3VsdHM9J2FzaXMnLGZpZy53aWR0aD0xNH0KZm9yIChjaG9zZW5fY2x1c3RlcnMgaW4gMTpudW1fb2ZfY2x1c3RlcnMpIHsKICBjaG9zZW5fZ2VuZXMgPSBhbm5vdGF0aW9uW1sibXlhbm5vdGF0aW9uIl1dICU+JSBkcGx5cjo6ZmlsdGVyKGNsdXN0ZXIgPT0gY2hvc2VuX2NsdXN0ZXJzKSAlPiUgcm93bmFtZXMoKSAjdGFrZSByZWxldmFudCBnZW5lcwogICMgcHJpbnQoY2hvc2VuX2dlbmVzKQogIGh5cF9vYmogPC0gaHlwZVIoY2hvc2VuX2dlbmVzLCBnZW5lc2V0c19lbnYsIHRlc3QgPSAiaHlwZXJnZW9tZXRyaWMiLCBmZHI9MSwgcGxvdHRpbmc9RixiYWNrZ3JvdW5kID0gcm93bmFtZXMoeGVub181X2dlcF9zY29yZXMpKQoKICAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpCgogIAogIHByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgICBoeXBfZG90cyhoeXBfb2JqLHNpemVfYnkgPSAibm9uZSIsdGl0bGUgPSBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpKwogICAgICAgICAgICAgIEZlYXR1cmVQbG90KG9iamVjdCA9IHhlbm8sZmVhdHVyZXMgPSBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpLAogICAgICAgICAgICB0aXRsZSA9IGNob3Nlbl9jbHVzdGVycykKCgp9CgoKYGBgCgojIENvcnJlbGF0aW9uIG9mIGNsdXN0ZXJzCmBgYHtyfQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIAogIGNvcl9yZXMgPSBjb3IoeGVubyRoeXBveGlhLHhlbm9bW3Bhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKV1dKQpwcmludChwYXN0ZSgiY29ycmVsYXRpb24gb2YgaHlwb3hpYSBwcm9ncmFtIHRvIiwgcGFzdGUwKCJjbHVzdGVyIixjaG9zZW5fY2x1c3RlcnMpLCI6IiwgY29yX3JlcykpCgp9CmBgYAoKYGBge3J9CmNsdXN0ZXJzX2lkZW50cyA9IGMoIkhBTExNQVJLX0hZUE9YSUEiLCAiSElGX3RhcmdldHMiLCJjbHVzdGVyMyIsImNsdXN0ZXI0IikKYGBgCgpgYGB7cn0KcHJvZ3JhbXNfb2ZfY2x1c3RlciA9IGMoKQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgcGF0aHdheV9uYW1lID0gY2x1c3RlcnNfaWRlbnRzW2Nob3Nlbl9jbHVzdGVyc10KICBpZiAoIXN0YXJ0c1dpdGgoeCA9IHBhdGh3YXlfbmFtZSxwcmVmaXggPSAiY2x1c3RlciIpKXsKICAgICAgY2hvc2VuX2dlbmVzICA9IChjaG9zZW5fZ2VuZXMpICU+JSBpbnRlcnNlY3QoZ2VuZXNldHNbW3BhdGh3YXlfbmFtZV1dKQogICAgICBwYXRod2F5X25hbWUgPSBwYXN0ZTAocGF0aHdheV9uYW1lLCJfY2x1c3RlciIpCiAgfQogIHByb2dyYW1zX29mX2NsdXN0ZXIgPSBjKHByb2dyYW1zX29mX2NsdXN0ZXIscGF0aHdheV9uYW1lKQogIHByaW50KHBhdGh3YXlfbmFtZSkKICBwcmludChjaG9zZW5fZ2VuZXMpCiAgY2F0KCJcbiIpCiAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXRod2F5X25hbWUpCgp9CmBgYAojICBwcm9ncmFtIDMgaW50ZXJzZWN0ZWQgcGF0aHdheSByZWd1bGF0aW9uIHsudGFic2V0fSAgICAgCgpgYGB7ciByZXN1bHRzPSdhc2lzJ30KbWV0YWdlbmVzX21lYW5fY29tcGFyZShkYXRhc2V0ID0geGVubyx0aW1lLnBvaW50X3ZhciA9ICJ0cmVhdG1lbnQiLHByZWZpeCA9ICJtb2RlbCIscGF0aWVudC5pZGVudF92YXIgPSAib3JpZy5pZGVudCIscHJlX29uID0gYygiTlQiLCJPU0kiKSx0ZXN0ID0gIndpbGNveC50ZXN0Iixwcm9ncmFtcyA9IHByb2dyYW1zX29mX2NsdXN0ZXIsd2l0aG91dF9zcGxpdCA9IEYpCmBgYAoKIyBUb3AgcHJvZ3JhbSAzIGdlbmVzIGV4cHJlc3Npb24gY29ycmVsYXRpb24KCmBgYHtyfQp0b3BfY2MgPSBnZXBfc2NvcmVzIFtvcmRlcihnZXBfc2NvcmVzIFssNF0sZGVjcmVhc2luZyA9IFQpLDIsZHJvcCA9IEZdJT4lIGhlYWQoMjAwKSAlPiUgcm93bmFtZXMoKQoKbnVtX29mX2NsdXN0ZXJzID0gNAphbm5vdGF0aW9uID0gcGxvdF9nZW5lc19jb3IoZGF0YXNldCA9IHhlbm8saGFsbG1hcmtfbmFtZSA9IE5VTEwsbnVtX29mX2NsdXN0ZXJzID0gbnVtX29mX2NsdXN0ZXJzLGdlbmVJZHMgPSB0b3BfY2MpCgpgYGAKIyAgcHJvZ3JhbSAzIGFsbCBjbHVzdGVycyBleHByZXNzaW9uIHsudGFic2V0fQoKYGBge3IgcmVzdWx0cz0nYXNpcycsZmlnLndpZHRoPTE0fQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgIyBwcmludChjaG9zZW5fZ2VuZXMpCiAgaHlwX29iaiA8LSBoeXBlUihjaG9zZW5fZ2VuZXMsIGdlbmVzZXRzX2VudiwgdGVzdCA9ICJoeXBlcmdlb21ldHJpYyIsIGZkcj0xLCBwbG90dGluZz1GLGJhY2tncm91bmQgPSByb3duYW1lcyh4ZW5vXzVfZ2VwX3Njb3JlcykpCgogICBzY29yZXNBbmRJbmRpY2VzIDwtIGdldFBhdGh3YXlTY29yZXMoeGVub0Bhc3NheXMkUk5BQGRhdGEsIGNob3Nlbl9nZW5lcykKICB4ZW5vPUFkZE1ldGFEYXRhKHhlbm8sc2NvcmVzQW5kSW5kaWNlcyRwYXRod2F5U2NvcmVzLHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSkKCiAgCiAgcHJpbnRfdGFiKHBsdCA9IAogICAgICAgICAgICAgIGh5cF9kb3RzKGh5cF9vYmosc2l6ZV9ieSA9ICJub25lIix0aXRsZSA9IHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSkrCiAgICAgICAgICAgICAgRmVhdHVyZVBsb3Qob2JqZWN0ID0geGVubyxmZWF0dXJlcyA9IHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSksCiAgICAgICAgICAgIHRpdGxlID0gY2hvc2VuX2NsdXN0ZXJzKQogIAp9CgoKYGBgCgo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9oeXBvdGhlcy5pcy9lbWJlZC5qcyIgYXN5bmM+PC9zY3JpcHQ+CgoKCg==