ALL IMPORTED FILES MUST BE IN THE SAME DIRECTORY AS THIS SCRIPT
Load tidyverse package.
library("tidyverse")
Import individual tumour exome somatic variants
(“tumour_sample_germline_exome_somatic_caller_file.tsv”) and tumour-only
variants (“tumour_sample_germline_paired_exomes_HAP.tsv”- N/A for
unpaired somatic tumour data) annotated vcf files as ‘f’ and ‘g’ data
objects, alongside linked sample (“ViP Samples & Candidate
Genes.txt”) and tumour purity (“Tumour Purity.txt”) files. All files
available from the authors on reasonable request.
max.callers = 3
f <- read.table("tumour_sample_germline_exome_somatic_caller_file.tsv", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="", quote="") %>%
dplyr::rename("Tumour_Sample"="X.Tumour_Sample") %>%
mutate(n.callers = setNames(sapply(strsplit(unique(Identified), "-"),
function(callers){
ifelse("Intersection" %in% callers, max.callers, sum(!grepl("^filter", callers)))
}), unique(Identified))[Identified])
g <- read.delim("tumour_sample_germline_paired_exomes_HAP.tsv", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="") %>%
dplyr::rename("Tumour_Sample"="X.Tumour_Sample")
ViP_list <- read.delim("ViP Samples & Candidate Genes.txt", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="") %>%
arrange(Sample)
tumour_type <- select(ViP_list,Sample,Tumour.Type) %>% distinct() %>% rename("Normal_Sample"="Sample")
f$Normal_Sample <- as.character(f$Normal_Sample)
g$Normal_Sample <- as.character(g$Normal_Sample)
f <- left_join(f,tumour_type,by="Normal_Sample",copy=FALSE)
g <- left_join(g,tumour_type,by="Normal_Sample",copy=FALSE) %>% filter(CANONICAL%in%"YES")
gene_list <- select(ViP_list,Sample,SYMBOL) %>% filter(Sample%in%(f$Normal_Sample))
genes <- gene_list$SYMBOL
tumour_purity <- read.delim("Tumour Purity.txt", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="") %>%
select(Tumour_Sample,Purity,No_Mutations)
f <- left_join(f,tumour_purity,by="Tumour_Sample",copy=FALSE)
g <- left_join(g,tumour_purity,by="Tumour_Sample",copy=FALSE)
Import individual tumour genome somatic variants
(“tumour_germline_genome_somatic_caller_file.tsv”) annotated vcf file as
‘ff’ data object. All files available from the authors on reasonable
request.
ff <- read.table("tumour_germline_genome_somatic_caller_file.tsv", header=TRUE, stringsAsFactors=FALSE, sep="\t", comment.char="", quote="") %>%
dplyr::rename("Tumour_Sample"="X.Tumour_Sample")
ff$Normal_Sample <- as.character(ff$Normal_Sample)
ff <- left_join(ff,tumour_purity,by="Tumour_Sample",copy=FALSE)
Convert all relevant strings to integers/doubles, and (for somatic
exome data only) remove variants that fail QC in one or more somatic
pipeline callers or are Consequence 7 (i.e. IMPACT==MODIFIER).
f$QUAL <- as.numeric(f$QUAL) %>% replace_na(0)
f$TUMOUR.PMCAD <- as.numeric(f$TUMOUR.PMCAD) %>% replace_na(0)
f$TUMOUR.PMCDP <- as.numeric(f$TUMOUR.PMCDP) %>% replace_na(0)
f$TUMOUR.PMCFREQ <- as.numeric(f$TUMOUR.PMCFREQ) %>% replace_na(0)
f$NORMAL.PMCAD <- as.numeric(f$NORMAL.PMCAD) %>% replace_na(0)
f$NORMAL.PMCDP <- as.numeric(f$NORMAL.PMCDP) %>% replace_na(0)
f$NORMAL.PMCFREQ <- as.numeric(f$NORMAL.PMCFREQ) %>% replace_na(0)
f$GnomAD_v2.1_non_cancer_AF <- as.numeric(f$GnomAD_v2.1_non_cancer_AF) %>% replace_na(0)
f$GnomAD_v3_AF <- as.numeric(f$GnomAD_v3_AF) %>% replace_na(0)
f0 <- filter(f,!Identified%in%c("FilteredInAll")) %>%
filter(n.callers >= 2) %>% # n.callers >= 3 for unpaired somatic tumour data (see Supplementary Figure 1a)
filter(Consequence_Rank < 7)
g$QUAL <- as.numeric(g$QUAL) %>% replace_na(0)
g$TUMOUR.PMCAD <- as.numeric(g$TUMOUR.PMCAD) %>% replace_na(0)
g$TUMOUR.PMCDP <- as.numeric(g$TUMOUR.PMCDP) %>% replace_na(0)
g$TUMOUR.PMCFREQ <- as.numeric(g$TUMOUR.PMCFREQ) %>% replace_na(0)
g$NORMAL.PMCAD <- as.numeric(g$NORMAL.PMCAD) %>% replace_na(0)
g$NORMAL.PMCDP <- as.numeric(g$NORMAL.PMCDP) %>% replace_na(0)
g$NORMAL.PMCFREQ <- as.numeric(g$NORMAL.PMCFREQ) %>% replace_na(0)
g$GnomAD_v2.1_non_cancer_AF <- as.numeric(g$GnomAD_v2.1_non_cancer_AF) %>% replace_na(0)
g$GnomAD_v3_AF <- as.numeric(g$GnomAD_v3_AF) %>% replace_na(0)
ff$QUAL <- as.numeric(ff$QUAL) %>% replace_na(0)
ff$TUMOUR.PMCAD <- as.numeric(ff$TUMOUR.PMCAD) %>% replace_na(0)
ff$TUMOUR.PMCDP <- as.numeric(ff$TUMOUR.PMCDP) %>% replace_na(0)
ff$TUMOUR.PMCFREQ <- as.numeric(ff$TUMOUR.PMCFREQ) %>% replace_na(0)
ff$NORMAL.PMCAD <- as.numeric(ff$NORMAL.PMCAD) %>% replace_na(0)
ff$NORMAL.PMCDP <- as.numeric(ff$NORMAL.PMCDP) %>% replace_na(0)
ff$NORMAL.PMCFREQ <- as.numeric(ff$NORMAL.PMCFREQ) %>% replace_na(0)
ff$gnomAD_AF <- as.numeric(ff$gnomAD_AF) %>% replace_na(0)
For somatic caller files, list unique, sorted elements in Identified
(Somatic Caller QC results, exomes only), Consequence Rank (exomes
only), NORMAL.PMCAD (germline alt allele base depth, exomes only),
NORMAL.PMCADP (germline total base depth, exomes and genomes)
NORMAL.PMCFREQ (germline alt allele read freq, exomes and genomes),
GnomAD v2.1/3.0 AF (exomes and genomes) and QUAL score (exomes
only).
sort(unique(f0$Identified))
sort(unique(f0$Consequence_Rank))
sort(unique(f0$NORMAL.PMCAD))
sort(unique(f0$NORMAL.PMCDP))
sort(unique(f0$NORMAL.PMCFREQ))
sort(unique(f0$GnomAD_v2.1_non_cancer_AF))
sort(unique(f0$GnomAD_v3_AF))
sort(unique(f0$QUAL))
sort(unique(ff$NORMAL.PMCDP))
sort(unique(ff$NORMAL.PMCFREQ))
sort(unique(ff$gnomAD_AF))
Plot pre-filtering distribution of tumour alt allele read
number(TUMOUR.PMCAD, exomes only), depth (TUMOUR.PMCDP, exomes and
genomes) and frequency (TUMOUR.PMCFREQ, exomes and genomes).
sort(unique(f0$TUMOUR.PMCAD))
hist(f0$TUMOUR.PMCAD, breaks=2000, main="Distribution of TUMOUR.PMCAD Values", xlab="TUMOUR.PMCAD",xlim=c(0,20))
sort(unique(f0$TUMOUR.PMCDP))
hist(f0$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(f0$TUMOUR.PMCFREQ))
hist(f0$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))
sort(unique(ff$TUMOUR.PMCDP))
hist(ff$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(ff$TUMOUR.PMCFREQ))
hist(ff$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))
Filter out and remove elements/rows with high number alt allele reads
in germline (NORMAL.PMCAD, exomes only), high germline alt allele read
freq (NORMAL.PMCFREQ, exomes and genomes), low germline and tumour read
depths (NORMAL.PMCDP and TUMOUR.PMCDP, exomes and genomes), low number
alt allele reads in tumour (TUMOUR.PMCAD, exomes only), low tumour alt
allele read freq (TUMOUR.PMCFREQ, exomes and genomes) and common
variants (using GnomAD v2.1/3.0 AFs, exomes and genomes).
f1<-filter(f0, (NORMAL.PMCDP >= 10) & (NORMAL.PMCAD <= 2) & (NORMAL.PMCFREQ < 0.01)) # N/A for unpaired somatic tumour data (see Supplementary Figure 1a)
f2<-filter(f1,TUMOUR.PMCDP >= 20)
f3<-filter(f2,TUMOUR.PMCAD >= 5)
f4<-filter(f3,TUMOUR.PMCFREQ >= (f3$Purity*0.5*(2/3)))
f5<-filter(f4,GnomAD_v2.1_non_cancer_AF <= 0.0001) # GnomAD_v2.1_non_cancer_AF == 0 for unpaired somatic tumour data (see Supplementary Figure 1a)
f6<-filter(f5,GnomAD_v3_AF <= 0.0001) # GnomAD_v3_AF == 0 for unpaired somatic tumour data (see Supplementary Figure 1a)
ff1<-filter(ff,(NORMAL.PMCDP >= 10) & (NORMAL.PMCAD <= 2) & (NORMAL.PMCFREQ < 0.01))
ff2<-filter(ff1,TUMOUR.PMCDP >= 10)
ff3<-filter(ff2,TUMOUR.PMCAD >= 4)
ff4<-filter(ff3,TUMOUR.PMCFREQ >= (ff3$Purity*0.5*(3/4)))
ff5<-filter(ff4,gnomAD_AF == 0) # NB Tumour genome data annotated to GnomAD v3.0 only (see Supplementary Figure 1b)
List remaining unique elements from above fields, and save output
(exomes- for ENSEMBL CANONICAL transcripts only, genomes- for MANE
SELECT transcripts only).
sort(unique(f2$TUMOUR.PMCAD))
sort(unique(f3$TUMOUR.PMCDP))
sort(unique(f4$TUMOUR.PMCFREQ))
sort(unique(f5$GnomAD_v2.1_non_cancer_AF))
sort(unique(f6$GnomAD_v3_AF))
samples_WES <- unique(f$Tumour_Sample)
for (sample_WES in samples_WES){
dir.create(sample_WES)
setwd(sample_WES)
f6 %>%
filter(CANONICAL%in%"YES") %>%
write_excel_csv(path=paste(sample_WES,"tumour_germline_exome_somatic_caller_file_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
}
sort(unique(ff2$TUMOUR.PMCDP))
sort(unique(ff3$TUMOUR.PMCAD))
sort(unique(ff4$TUMOUR.PMCFREQ))
sort(unique(ff5$gnomAD_AF))
samples_WGS <- unique(ff$Tumour_Sample)
for (sample_WGS in samples_WGS){
dir.create(sample_WGS)
setwd(sample_WGS)
ff5 %>%
filter(MANE_Status!=".") %>%
write_excel_csv(path=paste(sample_WGS,"tumour_germline_genome_somatic_caller_file_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
}
Plot post-filtering distribution of tumour alt allele read number
(TUMOUR.PMCAD, exomes only), depth (TUMOUR.PMCDP/DP, exomes and genomes)
and frequency (TUMOUR.PMCFREQ/AF, exomes and genomes).
sort(unique(f6$TUMOUR.PMCAD))
hist(f6$TUMOUR.PMCAD, breaks=2000, main="Distribution of TUMOUR.PMCAD Values", xlab="TUMOUR.PMCAD",xlim=c(0,20))
sort(unique(f6$TUMOUR.PMCDP))
hist(f6$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(f6$TUMOUR.PMCFREQ))
hist(f6$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))
sort(unique(ff5$TUMOUR.PMCDP))
hist(ff5$TUMOUR.PMCDP, breaks=4000, main="Distribution of TUMOUR.PMCDP Values", xlab="TUMOUR.PMCDP",xlim=c(0,100))
sort(unique(ff4$TUMOUR.PMCFREQ))
hist(ff5$TUMOUR.PMCFREQ, breaks=200, main="Distribution of TUMOUR.PMCFREQ Values", xlab="TUMOUR.PMCFREQ",xlim=c(0,1))
Extract remaining variants and columns used for mutation signature
analysis, and rename sample column.
f7 <- select(f6,c(CHROM,POS,REF,ALT,Variant_Type))
unique(f7$Variant_Type)
f7$Sample <- f6$Tumour_Sample
ff6<-select(ff5,c(CHROM,POS,REF,ALT)) %>%
filter(!CHROM%in%"chrY")
ff6$Sample<-sample_WGS
Output mutations for SIGNAL mutation signature analysis, with count
of number of variants used and filtering metrics.
mut.ref_f <- f7
mut.ref_f$CHROM<-unlist(lapply(mut.ref_f$CHROM,function(x) paste("chr",x,sep="")))
mut.ref_edit_f <- mut.ref_f[,c("Sample","CHROM","POS","REF","ALT")]
for (sample_WES in samples_WES){
setwd(sample_WES)
mut.ref2_f<-mut.ref_edit_f[mut.ref_edit_f$Sample==sample_WES,]
mut.ref2_f<-unique(mut.ref2_f)
mut.Num_f <- nrow(mut.ref2_f)
s <- paste("The number of variants in sample", sample_WES, "used for mutation signature analysis is", mut.Num_f)
t <- paste("The minimum TUMOUR.PMCAD in sample", sample_WES, "used for filtering is", min(unique(f2$TUMOUR.PMCAD)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f7$TUMOUR.PMCAD)))
u <- paste("The minimum TUMOUR.PMCDP in sample", sample_WES, "used for filtering is", min(unique(f3$TUMOUR.PMCDP)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f7$TUMOUR.PMCDP)))
v <- paste("The minimum TUMOUR.PMCFREQ in sample", sample_WES, "used for filtering is", min(unique(f4$TUMOUR.PMCFREQ)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f7$TUMOUR.PMCFREQ)))
w <- paste("The maximuum GnomAD_v2.1_non_cancer_AF in sample", sample_WES, "used for filtering is", max(unique(f5$GnomAD_v2.1_non_cancer_AF)), "and the maximum figure for the variants used for mutation signature analysis is", max(unique(f7$GnomAD_v2.1_non_cancer_AF)))
x <- paste("The maximum GnomAD_v3_AF in sample", sample_WES, "used for filtering and for the variants used for mutation signature analysis is", max(unique(f7$GnomAD_v3_AF)))
parameters <- print(c(s,t,u,v,w,x))
mut.ref3_f = mut.ref2_f[,c(3:5)]
mut.ref_f.chr = tibble(gsub("[chr]","", mut.ref2_f$CHROM))
bind_cols(mut.ref_f.chr,mut.ref3_f) %>% write_tsv(path=paste(sample_WES,"unique_mutations_forSignal.tsv",sep="_"),col_names=FALSE)
write.table(parameters,file=paste(sample_WGS,"_parameters.txt",sep=""),quote=F,row.names = F,sep="\t")
}
mut.ref_ff <- ff6
mut.ref_edit_ff<-mut.ref_ff[,c("Sample","CHROM","POS","REF","ALT")]
for (sample_WGS in samples_WGS){
setwd(sample_WGS)
mut.ref2_ff<-mut.ref_edit_ff[mut.ref_edit_ff$Sample==sample_WGS,]
mut.ref2_ff<-unique(mut.ref2_ff)
mut.Num_ff <- nrow(mut.ref2_ff)
s <- paste("The number of variants in sample", sample_WGS, "used for mutation signature analysis is", mut.Num_ff)
t <- paste("The minimum TUMOUR.PMCAD in sample", sample_WGS, "used for filtering is", min(unique(f2$TUMOUR.PMCAD)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(f4$TUMOUR.PMCAD)))
u <- paste("The minimum TUMOUR.PMCDP in sample", sample_WGS, "used for filtering is", min(unique(ff2$TUMOUR.PMCDP)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(ff2$TUMOUR.PMCDP)))
v <- paste("The minimum TUMOUR.PMCFREQ in sample", sample_WGS, "used for filtering is", min(unique(ff3$TUMOUR.PMCFREQ)), "and the minimum figure for the variants used for mutation signature analysis is", min(unique(ff3$TUMOUR.PMCFREQ)))
w <- paste("The maximum GnomAD_v3_AF in sample", sample_WGS, "used for filtering and for the variants used for mutation signature analysis is", max(unique(ff4$gnomAD_AF)))
parameters <- print(c(s,t,u,v,w))
mut.ref3_ff = mut.ref2_ff[,c(3:5)]
mut.ref_ff.chr = tibble(gsub("[chr]","", mut.ref2_ff$CHROM))
bind_cols(mut.ref2_ff$Sample,mut.ref_ff.chr,mut.ref3_ff) %>% write_tsv(path=paste(sample_WGS,"unique_mutations_forSignal.tsv",sep="_"),col_names=FALSE)
write.table(parameters,file=paste(sample_WGS,"_parameters.txt",sep=""),quote=F,row.names = F,sep="\t")
}
Extract rows with variants in gene(s) of interest pre- and
post-filtering (e.g. TP53) from somatic variants file (exomes and
genomes), and save output.
HGS_tumour_genes <- c("TP53","BRCA1","BRCA2","PTEN","NF1","RB1","CDK12","CDKN2A")
setwd(sample_WES)
f0_genes <- filter(f0,(SYMBOL%in%HGS_tumour_genes)|
(SYMBOL%in%c(genes))) %>%
write_excel_csv(path=paste(samples_WES,"tumour_germline_exome_somatic_caller_genes_unfiltered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
f6_genes <- filter(f6,(SYMBOL%in%HGS_tumour_genes)|
(SYMBOL%in%c(genes))) %>%
write_excel_csv(sample,path=paste(samples_WES,"tumour_germline_exome_somatic_caller_genes_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
setwd(sample_WGS)
ff_genes <- filter(ff,(SYMBOL%in%HGS_tumour_genes)|
(SYMBOL%in%c(genes))) %>%
write_excel_csv(path=paste(samples_WGS,"tumour_germline_genome_somatic_caller_genes_unfiltered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
ff5_genes <- filter(ff5,(SYMBOL%in%HGS_tumour_genes)|
(SYMBOL%in%c(genes))) %>%
write_excel_csv(sample_WGS,path=paste(samples_WGS,"tumour_germline_genome_somatic_caller_genes_filtered.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
Extract equivalent rows with variants in gene(s) of interest (one
gene transcript per variant) from tumour-only variants file (exomes
only- N/A for unpaired somatic tumour data), and save output.
setwd(sample_WES)
g_genes <- filter(g,(SYMBOL%in%HGS_tumour_genes)|
(SYMBOL%in%c(genes))) %>%
arrange(Transcript_Index) %>%
distinct(CHROM,POS,REF,ALT,.keep_all=TRUE)
write_excel_csv(g_genes,path=paste(sample_WES,"tumour_germline_paired_exomes_HAP_genes.csv",sep="_"), na=".",append=FALSE,col_names=TRUE)
LS0tCnRpdGxlOiAiVHVtb3VyIHNlcXVlbmNpbmcgZGF0YSBmaWx0ZXJpbmcgUiBzY3JpcHQgZm9yICdBc3Nlc3NtZW50IG9mIGNhbmRpZGF0ZSBoaWdoLWdyYWRlIHNlcm91cyBvdmFyaWFuIGNhcmNpbm9tYSBwcmVkaXNwb3NpdGlvbiBnZW5lcyB0aHJvdWdoIGludGVncmF0ZWQgZ2VybWxpbmUgYW5kIHR1bW91ciBzZXF1ZW5jaW5nJyAoU3VicmFtYW5pYW4gZXQgYWwuLCBucGogR2Vub21pYyBNZWRpY2luZSwgMjAyNCkiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkFMTCBJTVBPUlRFRCBGSUxFUyBNVVNUIEJFIElOIFRIRSBTQU1FIERJUkVDVE9SWSBBUyBUSElTIFNDUklQVAoKTG9hZCB0aWR5dmVyc2UgcGFja2FnZS4KYGBge3J9CmxpYnJhcnkoInRpZHl2ZXJzZSIpCmBgYAoKSW1wb3J0IGluZGl2aWR1YWwgdHVtb3VyIGV4b21lIHNvbWF0aWMgdmFyaWFudHMgKCJ0dW1vdXJfc2FtcGxlX2dlcm1saW5lX2V4b21lX3NvbWF0aWNfY2FsbGVyX2ZpbGUudHN2IikgYW5kIHR1bW91ci1vbmx5IHZhcmlhbnRzICgidHVtb3VyX3NhbXBsZV9nZXJtbGluZV9wYWlyZWRfZXhvbWVzX0hBUC50c3YiLSBOL0EgZm9yIHVucGFpcmVkIHNvbWF0aWMgdHVtb3VyIGRhdGEpIGFubm90YXRlZCB2Y2YgZmlsZXMgYXMgJ2YnIGFuZCAnZycgZGF0YSBvYmplY3RzLCBhbG9uZ3NpZGUgbGlua2VkIHNhbXBsZSAoIlZpUCBTYW1wbGVzICYgQ2FuZGlkYXRlIEdlbmVzLnR4dCIpIGFuZCB0dW1vdXIgcHVyaXR5ICgiVHVtb3VyIFB1cml0eS50eHQiKSBmaWxlcy4gQWxsIGZpbGVzIGF2YWlsYWJsZSBmcm9tIHRoZSBhdXRob3JzIG9uIHJlYXNvbmFibGUgcmVxdWVzdC4KYGBge3J9Cm1heC5jYWxsZXJzID0gMwpmIDwtIHJlYWQudGFibGUoInR1bW91cl9zYW1wbGVfZ2VybWxpbmVfZXhvbWVfc29tYXRpY19jYWxsZXJfZmlsZS50c3YiLCBoZWFkZXI9VFJVRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSwgc2VwPSJcdCIsIGNvbW1lbnQuY2hhcj0iIiwgcXVvdGU9IiIpICU+JQogIGRwbHlyOjpyZW5hbWUoIlR1bW91cl9TYW1wbGUiPSJYLlR1bW91cl9TYW1wbGUiKSAlPiUgCiAgbXV0YXRlKG4uY2FsbGVycyA9IHNldE5hbWVzKHNhcHBseShzdHJzcGxpdCh1bmlxdWUoSWRlbnRpZmllZCksICItIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oY2FsbGVycyl7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZSgiSW50ZXJzZWN0aW9uIiAlaW4lIGNhbGxlcnMsIG1heC5jYWxsZXJzLCBzdW0oIWdyZXBsKCJeZmlsdGVyIiwgY2FsbGVycykpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KSwgdW5pcXVlKElkZW50aWZpZWQpKVtJZGVudGlmaWVkXSkKZyA8LSByZWFkLmRlbGltKCJ0dW1vdXJfc2FtcGxlX2dlcm1saW5lX3BhaXJlZF9leG9tZXNfSEFQLnRzdiIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLCBzZXA9Ilx0IiwgY29tbWVudC5jaGFyPSIiKSAlPiUgCiAgZHBseXI6OnJlbmFtZSgiVHVtb3VyX1NhbXBsZSI9IlguVHVtb3VyX1NhbXBsZSIpClZpUF9saXN0IDwtIHJlYWQuZGVsaW0oIlZpUCBTYW1wbGVzICYgQ2FuZGlkYXRlIEdlbmVzLnR4dCIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLCBzZXA9Ilx0IiwgY29tbWVudC5jaGFyPSIiKSAlPiUgCiAgYXJyYW5nZShTYW1wbGUpCnR1bW91cl90eXBlIDwtIHNlbGVjdChWaVBfbGlzdCxTYW1wbGUsVHVtb3VyLlR5cGUpICU+JSBkaXN0aW5jdCgpICU+JSByZW5hbWUoIk5vcm1hbF9TYW1wbGUiPSJTYW1wbGUiKQpmJE5vcm1hbF9TYW1wbGUgPC0gYXMuY2hhcmFjdGVyKGYkTm9ybWFsX1NhbXBsZSkKZyROb3JtYWxfU2FtcGxlIDwtIGFzLmNoYXJhY3RlcihnJE5vcm1hbF9TYW1wbGUpCmYgPC0gbGVmdF9qb2luKGYsdHVtb3VyX3R5cGUsYnk9Ik5vcm1hbF9TYW1wbGUiLGNvcHk9RkFMU0UpCmcgPC0gbGVmdF9qb2luKGcsdHVtb3VyX3R5cGUsYnk9Ik5vcm1hbF9TYW1wbGUiLGNvcHk9RkFMU0UpICU+JSBmaWx0ZXIoQ0FOT05JQ0FMJWluJSJZRVMiKQpnZW5lX2xpc3QgPC0gc2VsZWN0KFZpUF9saXN0LFNhbXBsZSxTWU1CT0wpICU+JSBmaWx0ZXIoU2FtcGxlJWluJShmJE5vcm1hbF9TYW1wbGUpKQpnZW5lcyA8LSBnZW5lX2xpc3QkU1lNQk9MCnR1bW91cl9wdXJpdHkgPC0gcmVhZC5kZWxpbSgiVHVtb3VyIFB1cml0eS50eHQiLCBoZWFkZXI9VFJVRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSwgc2VwPSJcdCIsIGNvbW1lbnQuY2hhcj0iIikgJT4lCiAgc2VsZWN0KFR1bW91cl9TYW1wbGUsUHVyaXR5LE5vX011dGF0aW9ucykKZiA8LSBsZWZ0X2pvaW4oZix0dW1vdXJfcHVyaXR5LGJ5PSJUdW1vdXJfU2FtcGxlIixjb3B5PUZBTFNFKQpnIDwtIGxlZnRfam9pbihnLHR1bW91cl9wdXJpdHksYnk9IlR1bW91cl9TYW1wbGUiLGNvcHk9RkFMU0UpCmBgYAoKSW1wb3J0IGluZGl2aWR1YWwgdHVtb3VyIGdlbm9tZSBzb21hdGljIHZhcmlhbnRzICgidHVtb3VyX2dlcm1saW5lX2dlbm9tZV9zb21hdGljX2NhbGxlcl9maWxlLnRzdiIpIGFubm90YXRlZCB2Y2YgZmlsZSBhcyAnZmYnIGRhdGEgb2JqZWN0LiBBbGwgZmlsZXMgYXZhaWxhYmxlIGZyb20gdGhlIGF1dGhvcnMgb24gcmVhc29uYWJsZSByZXF1ZXN0LgpgYGB7cn0KZmYgPC0gcmVhZC50YWJsZSgidHVtb3VyX2dlcm1saW5lX2dlbm9tZV9zb21hdGljX2NhbGxlcl9maWxlLnRzdiIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFLCBzZXA9Ilx0IiwgY29tbWVudC5jaGFyPSIiLCBxdW90ZT0iIikgJT4lCiAgZHBseXI6OnJlbmFtZSgiVHVtb3VyX1NhbXBsZSI9IlguVHVtb3VyX1NhbXBsZSIpCmZmJE5vcm1hbF9TYW1wbGUgPC0gYXMuY2hhcmFjdGVyKGZmJE5vcm1hbF9TYW1wbGUpCmZmIDwtIGxlZnRfam9pbihmZix0dW1vdXJfcHVyaXR5LGJ5PSJUdW1vdXJfU2FtcGxlIixjb3B5PUZBTFNFKQpgYGAKCkNvbnZlcnQgYWxsIHJlbGV2YW50IHN0cmluZ3MgdG8gaW50ZWdlcnMvZG91YmxlcywgYW5kIChmb3Igc29tYXRpYyBleG9tZSBkYXRhIG9ubHkpIHJlbW92ZSB2YXJpYW50cyB0aGF0IGZhaWwgUUMgaW4gb25lIG9yIG1vcmUgc29tYXRpYyBwaXBlbGluZSBjYWxsZXJzIG9yIGFyZSBDb25zZXF1ZW5jZSA3IChpLmUuIElNUEFDVD09TU9ESUZJRVIpLgpgYGB7cn0KZiRRVUFMIDwtIGFzLm51bWVyaWMoZiRRVUFMKSAlPiUgcmVwbGFjZV9uYSgwKQpmJFRVTU9VUi5QTUNBRCA8LSBhcy5udW1lcmljKGYkVFVNT1VSLlBNQ0FEKSAlPiUgcmVwbGFjZV9uYSgwKQpmJFRVTU9VUi5QTUNEUCA8LSBhcy5udW1lcmljKGYkVFVNT1VSLlBNQ0RQKSAlPiUgcmVwbGFjZV9uYSgwKQpmJFRVTU9VUi5QTUNGUkVRIDwtIGFzLm51bWVyaWMoZiRUVU1PVVIuUE1DRlJFUSkgJT4lIHJlcGxhY2VfbmEoMCkKZiROT1JNQUwuUE1DQUQgPC0gYXMubnVtZXJpYyhmJE5PUk1BTC5QTUNBRCkgJT4lIHJlcGxhY2VfbmEoMCkKZiROT1JNQUwuUE1DRFAgPC0gYXMubnVtZXJpYyhmJE5PUk1BTC5QTUNEUCkgJT4lIHJlcGxhY2VfbmEoMCkKZiROT1JNQUwuUE1DRlJFUSA8LSBhcy5udW1lcmljKGYkTk9STUFMLlBNQ0ZSRVEpICU+JSByZXBsYWNlX25hKDApCmYkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA8LSBhcy5udW1lcmljKGYkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikgJT4lIHJlcGxhY2VfbmEoMCkKZiRHbm9tQURfdjNfQUYgPC0gYXMubnVtZXJpYyhmJEdub21BRF92M19BRikgJT4lIHJlcGxhY2VfbmEoMCkKCmYwIDwtIGZpbHRlcihmLCFJZGVudGlmaWVkJWluJWMoIkZpbHRlcmVkSW5BbGwiKSkgJT4lIAogICAgZmlsdGVyKG4uY2FsbGVycyA+PSAyKSAlPiUgIyBuLmNhbGxlcnMgPj0gMyBmb3IgdW5wYWlyZWQgc29tYXRpYyB0dW1vdXIgZGF0YSAoc2VlIFN1cHBsZW1lbnRhcnkgRmlndXJlIDFhKQogICAgZmlsdGVyKENvbnNlcXVlbmNlX1JhbmsgPCA3KQoKZyRRVUFMIDwtIGFzLm51bWVyaWMoZyRRVUFMKSAlPiUgcmVwbGFjZV9uYSgwKQpnJFRVTU9VUi5QTUNBRCA8LSBhcy5udW1lcmljKGckVFVNT1VSLlBNQ0FEKSAlPiUgcmVwbGFjZV9uYSgwKQpnJFRVTU9VUi5QTUNEUCA8LSBhcy5udW1lcmljKGckVFVNT1VSLlBNQ0RQKSAlPiUgcmVwbGFjZV9uYSgwKQpnJFRVTU9VUi5QTUNGUkVRIDwtIGFzLm51bWVyaWMoZyRUVU1PVVIuUE1DRlJFUSkgJT4lIHJlcGxhY2VfbmEoMCkKZyROT1JNQUwuUE1DQUQgPC0gYXMubnVtZXJpYyhnJE5PUk1BTC5QTUNBRCkgJT4lIHJlcGxhY2VfbmEoMCkKZyROT1JNQUwuUE1DRFAgPC0gYXMubnVtZXJpYyhnJE5PUk1BTC5QTUNEUCkgJT4lIHJlcGxhY2VfbmEoMCkKZyROT1JNQUwuUE1DRlJFUSA8LSBhcy5udW1lcmljKGckTk9STUFMLlBNQ0ZSRVEpICU+JSByZXBsYWNlX25hKDApCmckR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA8LSBhcy5udW1lcmljKGckR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikgJT4lIHJlcGxhY2VfbmEoMCkKZyRHbm9tQURfdjNfQUYgPC0gYXMubnVtZXJpYyhnJEdub21BRF92M19BRikgJT4lIHJlcGxhY2VfbmEoMCkKCmZmJFFVQUwgPC0gYXMubnVtZXJpYyhmZiRRVUFMKSAlPiUgcmVwbGFjZV9uYSgwKQpmZiRUVU1PVVIuUE1DQUQgPC0gYXMubnVtZXJpYyhmZiRUVU1PVVIuUE1DQUQpICU+JSByZXBsYWNlX25hKDApCmZmJFRVTU9VUi5QTUNEUCA8LSBhcy5udW1lcmljKGZmJFRVTU9VUi5QTUNEUCkgJT4lIHJlcGxhY2VfbmEoMCkKZmYkVFVNT1VSLlBNQ0ZSRVEgPC0gYXMubnVtZXJpYyhmZiRUVU1PVVIuUE1DRlJFUSkgJT4lIHJlcGxhY2VfbmEoMCkKZmYkTk9STUFMLlBNQ0FEIDwtIGFzLm51bWVyaWMoZmYkTk9STUFMLlBNQ0FEKSAlPiUgcmVwbGFjZV9uYSgwKQpmZiROT1JNQUwuUE1DRFAgPC0gYXMubnVtZXJpYyhmZiROT1JNQUwuUE1DRFApICU+JSByZXBsYWNlX25hKDApCmZmJE5PUk1BTC5QTUNGUkVRIDwtIGFzLm51bWVyaWMoZmYkTk9STUFMLlBNQ0ZSRVEpICU+JSByZXBsYWNlX25hKDApCmZmJGdub21BRF9BRiA8LSBhcy5udW1lcmljKGZmJGdub21BRF9BRikgJT4lIHJlcGxhY2VfbmEoMCkKYGBgCgpGb3Igc29tYXRpYyBjYWxsZXIgZmlsZXMsIGxpc3QgdW5pcXVlLCBzb3J0ZWQgZWxlbWVudHMgaW4gSWRlbnRpZmllZCAoU29tYXRpYyBDYWxsZXIgUUMgcmVzdWx0cywgZXhvbWVzIG9ubHkpLCBDb25zZXF1ZW5jZSBSYW5rIChleG9tZXMgb25seSksIE5PUk1BTC5QTUNBRCAoZ2VybWxpbmUgYWx0IGFsbGVsZSBiYXNlIGRlcHRoLCBleG9tZXMgb25seSksIE5PUk1BTC5QTUNBRFAgKGdlcm1saW5lIHRvdGFsIGJhc2UgZGVwdGgsIGV4b21lcyBhbmQgZ2Vub21lcykgTk9STUFMLlBNQ0ZSRVEgKGdlcm1saW5lIGFsdCBhbGxlbGUgcmVhZCBmcmVxLCBleG9tZXMgYW5kIGdlbm9tZXMpLCBHbm9tQUQgdjIuMS8zLjAgQUYgKGV4b21lcyBhbmQgZ2Vub21lcykgYW5kIFFVQUwgc2NvcmUgKGV4b21lcyBvbmx5KS4KYGBge3J9CnNvcnQodW5pcXVlKGYwJElkZW50aWZpZWQpKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGYwJENvbnNlcXVlbmNlX1JhbmspKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGYwJE5PUk1BTC5QTUNBRCkpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZjAkTk9STUFMLlBNQ0RQKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmMCROT1JNQUwuUE1DRlJFUSkpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZjAkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZjAkR25vbUFEX3YzX0FGKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmMCRRVUFMKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmZiROT1JNQUwuUE1DRFApKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmJE5PUk1BTC5QTUNGUkVRKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmZiRnbm9tQURfQUYpKQpgYGAKClBsb3QgcHJlLWZpbHRlcmluZyBkaXN0cmlidXRpb24gb2YgdHVtb3VyIGFsdCBhbGxlbGUgcmVhZCBudW1iZXIoVFVNT1VSLlBNQ0FELCBleG9tZXMgb25seSksIGRlcHRoIChUVU1PVVIuUE1DRFAsIGV4b21lcyBhbmQgZ2Vub21lcykgYW5kIGZyZXF1ZW5jeSAoVFVNT1VSLlBNQ0ZSRVEsIGV4b21lcyBhbmQgZ2Vub21lcykuCmBgYHtyfQpzb3J0KHVuaXF1ZShmMCRUVU1PVVIuUE1DQUQpKQpoaXN0KGYwJFRVTU9VUi5QTUNBRCwgYnJlYWtzPTIwMDAsIG1haW49IkRpc3RyaWJ1dGlvbiBvZiBUVU1PVVIuUE1DQUQgVmFsdWVzIiwgeGxhYj0iVFVNT1VSLlBNQ0FEIix4bGltPWMoMCwyMCkpCnNvcnQodW5pcXVlKGYwJFRVTU9VUi5QTUNEUCkpCmhpc3QoZjAkVFVNT1VSLlBNQ0RQLCBicmVha3M9NDAwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNEUCBWYWx1ZXMiLCB4bGFiPSJUVU1PVVIuUE1DRFAiLHhsaW09YygwLDEwMCkpCnNvcnQodW5pcXVlKGYwJFRVTU9VUi5QTUNGUkVRKSkKaGlzdChmMCRUVU1PVVIuUE1DRlJFUSwgYnJlYWtzPTIwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNGUkVRIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNGUkVRIix4bGltPWMoMCwxKSkKCnNvcnQodW5pcXVlKGZmJFRVTU9VUi5QTUNEUCkpCmhpc3QoZmYkVFVNT1VSLlBNQ0RQLCBicmVha3M9NDAwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNEUCBWYWx1ZXMiLCB4bGFiPSJUVU1PVVIuUE1DRFAiLHhsaW09YygwLDEwMCkpCnNvcnQodW5pcXVlKGZmJFRVTU9VUi5QTUNGUkVRKSkKaGlzdChmZiRUVU1PVVIuUE1DRlJFUSwgYnJlYWtzPTIwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNGUkVRIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNGUkVRIix4bGltPWMoMCwxKSkKYGBgCgpGaWx0ZXIgb3V0IGFuZCByZW1vdmUgZWxlbWVudHMvcm93cyB3aXRoIGhpZ2ggbnVtYmVyIGFsdCBhbGxlbGUgcmVhZHMgaW4gZ2VybWxpbmUgKE5PUk1BTC5QTUNBRCwgZXhvbWVzIG9ubHkpLCBoaWdoIGdlcm1saW5lIGFsdCBhbGxlbGUgcmVhZCBmcmVxIChOT1JNQUwuUE1DRlJFUSwgZXhvbWVzIGFuZCBnZW5vbWVzKSwgbG93IGdlcm1saW5lIGFuZCB0dW1vdXIgcmVhZCBkZXB0aHMgKE5PUk1BTC5QTUNEUCBhbmQgVFVNT1VSLlBNQ0RQLCBleG9tZXMgYW5kIGdlbm9tZXMpLCBsb3cgbnVtYmVyIGFsdCBhbGxlbGUgcmVhZHMgaW4gdHVtb3VyIChUVU1PVVIuUE1DQUQsIGV4b21lcyBvbmx5KSwgbG93IHR1bW91ciBhbHQgYWxsZWxlIHJlYWQgZnJlcSAoVFVNT1VSLlBNQ0ZSRVEsIGV4b21lcyBhbmQgZ2Vub21lcykgYW5kIGNvbW1vbiB2YXJpYW50cyAodXNpbmcgR25vbUFEIHYyLjEvMy4wIEFGcywgZXhvbWVzIGFuZCBnZW5vbWVzKS4KYGBge3J9CmYxPC1maWx0ZXIoZjAsIChOT1JNQUwuUE1DRFAgPj0gMTApICYgKE5PUk1BTC5QTUNBRCA8PSAyKSAmIChOT1JNQUwuUE1DRlJFUSA8IDAuMDEpKSAjIE4vQSBmb3IgdW5wYWlyZWQgc29tYXRpYyB0dW1vdXIgZGF0YSAoc2VlIFN1cHBsZW1lbnRhcnkgRmlndXJlIDFhKQpmMjwtZmlsdGVyKGYxLFRVTU9VUi5QTUNEUCA+PSAyMCkKZjM8LWZpbHRlcihmMixUVU1PVVIuUE1DQUQgPj0gNSkKZjQ8LWZpbHRlcihmMyxUVU1PVVIuUE1DRlJFUSA+PSAoZjMkUHVyaXR5KjAuNSooMi8zKSkpCmY1PC1maWx0ZXIoZjQsR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA8PSAwLjAwMDEpICMgR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRiA9PSAwIGZvciB1bnBhaXJlZCBzb21hdGljIHR1bW91ciBkYXRhIChzZWUgU3VwcGxlbWVudGFyeSBGaWd1cmUgMWEpCmY2PC1maWx0ZXIoZjUsR25vbUFEX3YzX0FGIDw9IDAuMDAwMSkgIyBHbm9tQURfdjNfQUYgPT0gMCBmb3IgdW5wYWlyZWQgc29tYXRpYyB0dW1vdXIgZGF0YSAoc2VlIFN1cHBsZW1lbnRhcnkgRmlndXJlIDFhKQoKZmYxPC1maWx0ZXIoZmYsKE5PUk1BTC5QTUNEUCA+PSAxMCkgJiAoTk9STUFMLlBNQ0FEIDw9IDIpICYgKE5PUk1BTC5QTUNGUkVRIDwgMC4wMSkpCmZmMjwtZmlsdGVyKGZmMSxUVU1PVVIuUE1DRFAgPj0gMTApCmZmMzwtZmlsdGVyKGZmMixUVU1PVVIuUE1DQUQgPj0gNCkKZmY0PC1maWx0ZXIoZmYzLFRVTU9VUi5QTUNGUkVRID49IChmZjMkUHVyaXR5KjAuNSooMy80KSkpCmZmNTwtZmlsdGVyKGZmNCxnbm9tQURfQUYgPT0gMCkgIyBOQiBUdW1vdXIgZ2Vub21lIGRhdGEgYW5ub3RhdGVkIHRvIEdub21BRCB2My4wIG9ubHkgKHNlZSBTdXBwbGVtZW50YXJ5IEZpZ3VyZSAxYikKYGBgCgpMaXN0IHJlbWFpbmluZyB1bmlxdWUgZWxlbWVudHMgZnJvbSBhYm92ZSBmaWVsZHMsIGFuZCBzYXZlIG91dHB1dCAoZXhvbWVzLSBmb3IgRU5TRU1CTCBDQU5PTklDQUwgdHJhbnNjcmlwdHMgb25seSwgZ2Vub21lcy0gZm9yIE1BTkUgU0VMRUNUIHRyYW5zY3JpcHRzIG9ubHkpLgpgYGB7cn0Kc29ydCh1bmlxdWUoZjIkVFVNT1VSLlBNQ0FEKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmMyRUVU1PVVIuUE1DRFApKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGY0JFRVTU9VUi5QTUNGUkVRKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmNSRHbm9tQURfdjIuMV9ub25fY2FuY2VyX0FGKSkKYGBgCmBgYHtyfQpzb3J0KHVuaXF1ZShmNiRHbm9tQURfdjNfQUYpKQpgYGAKYGBge3J9CnNhbXBsZXNfV0VTIDwtIHVuaXF1ZShmJFR1bW91cl9TYW1wbGUpCmZvciAoc2FtcGxlX1dFUyBpbiBzYW1wbGVzX1dFUyl7CiAgZGlyLmNyZWF0ZShzYW1wbGVfV0VTKQogIHNldHdkKHNhbXBsZV9XRVMpCmY2ICU+JSAKICBmaWx0ZXIoQ0FOT05JQ0FMJWluJSJZRVMiKSAlPiUgCiAgd3JpdGVfZXhjZWxfY3N2KHBhdGg9cGFzdGUoc2FtcGxlX1dFUywidHVtb3VyX2dlcm1saW5lX2V4b21lX3NvbWF0aWNfY2FsbGVyX2ZpbGVfZmlsdGVyZWQuY3N2IixzZXA9Il8iKSwgbmE9Ii4iLGFwcGVuZD1GQUxTRSxjb2xfbmFtZXM9VFJVRSkKfQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmMiRUVU1PVVIuUE1DRFApKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmMyRUVU1PVVIuUE1DQUQpKQpgYGAKYGBge3J9CnNvcnQodW5pcXVlKGZmNCRUVU1PVVIuUE1DRlJFUSkpCmBgYApgYGB7cn0Kc29ydCh1bmlxdWUoZmY1JGdub21BRF9BRikpCmBgYApgYGB7cn0Kc2FtcGxlc19XR1MgPC0gdW5pcXVlKGZmJFR1bW91cl9TYW1wbGUpCmZvciAoc2FtcGxlX1dHUyBpbiBzYW1wbGVzX1dHUyl7CiAgZGlyLmNyZWF0ZShzYW1wbGVfV0dTKQogIHNldHdkKHNhbXBsZV9XR1MpCmZmNSAlPiUgCiAgZmlsdGVyKE1BTkVfU3RhdHVzIT0iLiIpICU+JSAKICB3cml0ZV9leGNlbF9jc3YocGF0aD1wYXN0ZShzYW1wbGVfV0dTLCJ0dW1vdXJfZ2VybWxpbmVfZ2Vub21lX3NvbWF0aWNfY2FsbGVyX2ZpbGVfZmlsdGVyZWQuY3N2IixzZXA9Il8iKSwgbmE9Ii4iLGFwcGVuZD1GQUxTRSxjb2xfbmFtZXM9VFJVRSkKfQpgYGAKClBsb3QgcG9zdC1maWx0ZXJpbmcgZGlzdHJpYnV0aW9uIG9mIHR1bW91ciBhbHQgYWxsZWxlIHJlYWQgbnVtYmVyIChUVU1PVVIuUE1DQUQsIGV4b21lcyBvbmx5KSwgZGVwdGggKFRVTU9VUi5QTUNEUC9EUCwgZXhvbWVzIGFuZCBnZW5vbWVzKSBhbmQgZnJlcXVlbmN5IChUVU1PVVIuUE1DRlJFUS9BRiwgZXhvbWVzIGFuZCBnZW5vbWVzKS4KYGBge3J9CnNvcnQodW5pcXVlKGY2JFRVTU9VUi5QTUNBRCkpCmhpc3QoZjYkVFVNT1VSLlBNQ0FELCBicmVha3M9MjAwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNBRCBWYWx1ZXMiLCB4bGFiPSJUVU1PVVIuUE1DQUQiLHhsaW09YygwLDIwKSkKc29ydCh1bmlxdWUoZjYkVFVNT1VSLlBNQ0RQKSkKaGlzdChmNiRUVU1PVVIuUE1DRFAsIGJyZWFrcz00MDAwLCBtYWluPSJEaXN0cmlidXRpb24gb2YgVFVNT1VSLlBNQ0RQIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNEUCIseGxpbT1jKDAsMTAwKSkKc29ydCh1bmlxdWUoZjYkVFVNT1VSLlBNQ0ZSRVEpKQpoaXN0KGY2JFRVTU9VUi5QTUNGUkVRLCBicmVha3M9MjAwLCBtYWluPSJEaXN0cmlidXRpb24gb2YgVFVNT1VSLlBNQ0ZSRVEgVmFsdWVzIiwgeGxhYj0iVFVNT1VSLlBNQ0ZSRVEiLHhsaW09YygwLDEpKQoKc29ydCh1bmlxdWUoZmY1JFRVTU9VUi5QTUNEUCkpCmhpc3QoZmY1JFRVTU9VUi5QTUNEUCwgYnJlYWtzPTQwMDAsIG1haW49IkRpc3RyaWJ1dGlvbiBvZiBUVU1PVVIuUE1DRFAgVmFsdWVzIiwgeGxhYj0iVFVNT1VSLlBNQ0RQIix4bGltPWMoMCwxMDApKQpzb3J0KHVuaXF1ZShmZjQkVFVNT1VSLlBNQ0ZSRVEpKQpoaXN0KGZmNSRUVU1PVVIuUE1DRlJFUSwgYnJlYWtzPTIwMCwgbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRVTU9VUi5QTUNGUkVRIFZhbHVlcyIsIHhsYWI9IlRVTU9VUi5QTUNGUkVRIix4bGltPWMoMCwxKSkKYGBgCgpFeHRyYWN0IHJlbWFpbmluZyB2YXJpYW50cyBhbmQgY29sdW1ucyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMsIGFuZCByZW5hbWUgc2FtcGxlIGNvbHVtbi4KYGBge3J9CmY3IDwtIHNlbGVjdChmNixjKENIUk9NLFBPUyxSRUYsQUxULFZhcmlhbnRfVHlwZSkpCnVuaXF1ZShmNyRWYXJpYW50X1R5cGUpCmY3JFNhbXBsZSA8LSBmNiRUdW1vdXJfU2FtcGxlCgpmZjY8LXNlbGVjdChmZjUsYyhDSFJPTSxQT1MsUkVGLEFMVCkpICU+JSAKICAgIGZpbHRlcighQ0hST00laW4lImNoclkiKQpmZjYkU2FtcGxlPC1zYW1wbGVfV0dTCmBgYAoKT3V0cHV0IG11dGF0aW9ucyBmb3IgU0lHTkFMIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcywgd2l0aCBjb3VudCBvZiBudW1iZXIgb2YgdmFyaWFudHMgdXNlZCBhbmQgZmlsdGVyaW5nIG1ldHJpY3MuCmBgYHtyfQptdXQucmVmX2YgPC0gZjcKbXV0LnJlZl9mJENIUk9NPC11bmxpc3QobGFwcGx5KG11dC5yZWZfZiRDSFJPTSxmdW5jdGlvbih4KSBwYXN0ZSgiY2hyIix4LHNlcD0iIikpKSAKbXV0LnJlZl9lZGl0X2YgPC0gbXV0LnJlZl9mWyxjKCJTYW1wbGUiLCJDSFJPTSIsIlBPUyIsIlJFRiIsIkFMVCIpXQoKZm9yIChzYW1wbGVfV0VTIGluIHNhbXBsZXNfV0VTKXsKICBzZXR3ZChzYW1wbGVfV0VTKQogIG11dC5yZWYyX2Y8LW11dC5yZWZfZWRpdF9mW211dC5yZWZfZWRpdF9mJFNhbXBsZT09c2FtcGxlX1dFUyxdCiAgbXV0LnJlZjJfZjwtdW5pcXVlKG11dC5yZWYyX2YpCiAgbXV0Lk51bV9mIDwtIG5yb3cobXV0LnJlZjJfZikKICBzIDwtIHBhc3RlKCJUaGUgbnVtYmVyIG9mIHZhcmlhbnRzIGluIHNhbXBsZSIsIHNhbXBsZV9XRVMsICJ1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtdXQuTnVtX2YpCiAgdCA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0FEIGluIHNhbXBsZSIsIHNhbXBsZV9XRVMsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGYyJFRVTU9VUi5QTUNBRCkpLCAiYW5kIHRoZSBtaW5pbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1pbih1bmlxdWUoZjckVFVNT1VSLlBNQ0FEKSkpCiAgdSA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0RQIGluIHNhbXBsZSIsIHNhbXBsZV9XRVMsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGYzJFRVTU9VUi5QTUNEUCkpLCAiYW5kIHRoZSBtaW5pbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1pbih1bmlxdWUoZjckVFVNT1VSLlBNQ0RQKSkpCiAgdiA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0ZSRVEgaW4gc2FtcGxlIiwgc2FtcGxlX1dFUywgInVzZWQgZm9yIGZpbHRlcmluZyBpcyIsIG1pbih1bmlxdWUoZjQkVFVNT1VSLlBNQ0ZSRVEpKSwgImFuZCB0aGUgbWluaW11bSBmaWd1cmUgZm9yIHRoZSB2YXJpYW50cyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtaW4odW5pcXVlKGY3JFRVTU9VUi5QTUNGUkVRKSkpCiAgdyA8LSBwYXN0ZSgiVGhlIG1heGltdXVtIEdub21BRF92Mi4xX25vbl9jYW5jZXJfQUYgaW4gc2FtcGxlIiwgc2FtcGxlX1dFUywgInVzZWQgZm9yIGZpbHRlcmluZyBpcyIsIG1heCh1bmlxdWUoZjUkR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikpLCAiYW5kIHRoZSBtYXhpbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1heCh1bmlxdWUoZjckR25vbUFEX3YyLjFfbm9uX2NhbmNlcl9BRikpKQogIHggPC0gcGFzdGUoIlRoZSBtYXhpbXVtIEdub21BRF92M19BRiBpbiBzYW1wbGUiLCBzYW1wbGVfV0VTLCAidXNlZCBmb3IgZmlsdGVyaW5nIGFuZCBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1heCh1bmlxdWUoZjckR25vbUFEX3YzX0FGKSkpCiAgcGFyYW1ldGVycyA8LSBwcmludChjKHMsdCx1LHYsdyx4KSkKICBtdXQucmVmM19mID0gbXV0LnJlZjJfZlssYygzOjUpXQogIG11dC5yZWZfZi5jaHIgPSB0aWJibGUoZ3N1YigiW2Nocl0iLCIiLCBtdXQucmVmMl9mJENIUk9NKSkKICBiaW5kX2NvbHMobXV0LnJlZl9mLmNocixtdXQucmVmM19mKSAlPiUgd3JpdGVfdHN2KHBhdGg9cGFzdGUoc2FtcGxlX1dFUywidW5pcXVlX211dGF0aW9uc19mb3JTaWduYWwudHN2IixzZXA9Il8iKSxjb2xfbmFtZXM9RkFMU0UpCiAgd3JpdGUudGFibGUocGFyYW1ldGVycyxmaWxlPXBhc3RlKHNhbXBsZV9XR1MsIl9wYXJhbWV0ZXJzLnR4dCIsc2VwPSIiKSxxdW90ZT1GLHJvdy5uYW1lcyA9IEYsc2VwPSJcdCIpCn0KYGBgCmBgYHtyfQptdXQucmVmX2ZmIDwtIGZmNgptdXQucmVmX2VkaXRfZmY8LW11dC5yZWZfZmZbLGMoIlNhbXBsZSIsIkNIUk9NIiwiUE9TIiwiUkVGIiwiQUxUIildCmZvciAoc2FtcGxlX1dHUyBpbiBzYW1wbGVzX1dHUyl7CiAgc2V0d2Qoc2FtcGxlX1dHUykKICBtdXQucmVmMl9mZjwtbXV0LnJlZl9lZGl0X2ZmW211dC5yZWZfZWRpdF9mZiRTYW1wbGU9PXNhbXBsZV9XR1MsXQogIG11dC5yZWYyX2ZmPC11bmlxdWUobXV0LnJlZjJfZmYpCiAgbXV0Lk51bV9mZiA8LSBucm93KG11dC5yZWYyX2ZmKQogIHMgPC0gcGFzdGUoIlRoZSBudW1iZXIgb2YgdmFyaWFudHMgaW4gc2FtcGxlIiwgc2FtcGxlX1dHUywgInVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG11dC5OdW1fZmYpCiAgdCA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0FEIGluIHNhbXBsZSIsIHNhbXBsZV9XR1MsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGYyJFRVTU9VUi5QTUNBRCkpLCAiYW5kIHRoZSBtaW5pbXVtIGZpZ3VyZSBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1pbih1bmlxdWUoZjQkVFVNT1VSLlBNQ0FEKSkpCiAgdSA8LSBwYXN0ZSgiVGhlIG1pbmltdW0gVFVNT1VSLlBNQ0RQIGluIHNhbXBsZSIsIHNhbXBsZV9XR1MsICJ1c2VkIGZvciBmaWx0ZXJpbmcgaXMiLCBtaW4odW5pcXVlKGZmMiRUVU1PVVIuUE1DRFApKSwgImFuZCB0aGUgbWluaW11bSBmaWd1cmUgZm9yIHRoZSB2YXJpYW50cyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtaW4odW5pcXVlKGZmMiRUVU1PVVIuUE1DRFApKSkKICB2IDwtIHBhc3RlKCJUaGUgbWluaW11bSBUVU1PVVIuUE1DRlJFUSBpbiBzYW1wbGUiLCBzYW1wbGVfV0dTLCAidXNlZCBmb3IgZmlsdGVyaW5nIGlzIiwgbWluKHVuaXF1ZShmZjMkVFVNT1VSLlBNQ0ZSRVEpKSwgImFuZCB0aGUgbWluaW11bSBmaWd1cmUgZm9yIHRoZSB2YXJpYW50cyB1c2VkIGZvciBtdXRhdGlvbiBzaWduYXR1cmUgYW5hbHlzaXMgaXMiLCBtaW4odW5pcXVlKGZmMyRUVU1PVVIuUE1DRlJFUSkpKQogIHcgPC0gcGFzdGUoIlRoZSBtYXhpbXVtIEdub21BRF92M19BRiBpbiBzYW1wbGUiLCBzYW1wbGVfV0dTLCAidXNlZCBmb3IgZmlsdGVyaW5nIGFuZCBmb3IgdGhlIHZhcmlhbnRzIHVzZWQgZm9yIG11dGF0aW9uIHNpZ25hdHVyZSBhbmFseXNpcyBpcyIsIG1heCh1bmlxdWUoZmY0JGdub21BRF9BRikpKQogIHBhcmFtZXRlcnMgPC0gcHJpbnQoYyhzLHQsdSx2LHcpKQogIG11dC5yZWYzX2ZmID0gbXV0LnJlZjJfZmZbLGMoMzo1KV0KICBtdXQucmVmX2ZmLmNociA9IHRpYmJsZShnc3ViKCJbY2hyXSIsIiIsIG11dC5yZWYyX2ZmJENIUk9NKSkKICBiaW5kX2NvbHMobXV0LnJlZjJfZmYkU2FtcGxlLG11dC5yZWZfZmYuY2hyLG11dC5yZWYzX2ZmKSAlPiUgd3JpdGVfdHN2KHBhdGg9cGFzdGUoc2FtcGxlX1dHUywidW5pcXVlX211dGF0aW9uc19mb3JTaWduYWwudHN2IixzZXA9Il8iKSxjb2xfbmFtZXM9RkFMU0UpCiAgd3JpdGUudGFibGUocGFyYW1ldGVycyxmaWxlPXBhc3RlKHNhbXBsZV9XR1MsIl9wYXJhbWV0ZXJzLnR4dCIsc2VwPSIiKSxxdW90ZT1GLHJvdy5uYW1lcyA9IEYsc2VwPSJcdCIpCn0KYGBgCgpFeHRyYWN0IHJvd3Mgd2l0aCB2YXJpYW50cyBpbiBnZW5lKHMpIG9mIGludGVyZXN0IHByZS0gYW5kIHBvc3QtZmlsdGVyaW5nIChlLmcuIFRQNTMpIGZyb20gc29tYXRpYyB2YXJpYW50cyBmaWxlIChleG9tZXMgYW5kIGdlbm9tZXMpLCBhbmQgc2F2ZSBvdXRwdXQuCmBgYHtyfQpIR1NfdHVtb3VyX2dlbmVzIDwtIGMoIlRQNTMiLCJCUkNBMSIsIkJSQ0EyIiwiUFRFTiIsIk5GMSIsIlJCMSIsIkNESzEyIiwiQ0RLTjJBIikKCnNldHdkKHNhbXBsZV9XRVMpCmYwX2dlbmVzIDwtIGZpbHRlcihmMCwoU1lNQk9MJWluJUhHU190dW1vdXJfZ2VuZXMpfAogICAgICAgICAgICAgICAgICAgICAoU1lNQk9MJWluJWMoZ2VuZXMpKSkgJT4lIAogIHdyaXRlX2V4Y2VsX2NzdihwYXRoPXBhc3RlKHNhbXBsZXNfV0VTLCJ0dW1vdXJfZ2VybWxpbmVfZXhvbWVfc29tYXRpY19jYWxsZXJfZ2VuZXNfdW5maWx0ZXJlZC5jc3YiLHNlcD0iXyIpLCBuYT0iLiIsYXBwZW5kPUZBTFNFLGNvbF9uYW1lcz1UUlVFKQpmNl9nZW5lcyA8LSBmaWx0ZXIoZjYsKFNZTUJPTCVpbiVIR1NfdHVtb3VyX2dlbmVzKXwKICAgICAgICAgICAgICAgICAgICAgKFNZTUJPTCVpbiVjKGdlbmVzKSkpICU+JSAKICB3cml0ZV9leGNlbF9jc3Yoc2FtcGxlLHBhdGg9cGFzdGUoc2FtcGxlc19XRVMsInR1bW91cl9nZXJtbGluZV9leG9tZV9zb21hdGljX2NhbGxlcl9nZW5lc19maWx0ZXJlZC5jc3YiLHNlcD0iXyIpLCBuYT0iLiIsYXBwZW5kPUZBTFNFLGNvbF9uYW1lcz1UUlVFKQpgYGAKYGBge3J9CnNldHdkKHNhbXBsZV9XR1MpCmZmX2dlbmVzIDwtIGZpbHRlcihmZiwoU1lNQk9MJWluJUhHU190dW1vdXJfZ2VuZXMpfAogICAgICAgICAgICAgICAgICAgICAoU1lNQk9MJWluJWMoZ2VuZXMpKSkgJT4lIAogIHdyaXRlX2V4Y2VsX2NzdihwYXRoPXBhc3RlKHNhbXBsZXNfV0dTLCJ0dW1vdXJfZ2VybWxpbmVfZ2Vub21lX3NvbWF0aWNfY2FsbGVyX2dlbmVzX3VuZmlsdGVyZWQuY3N2IixzZXA9Il8iKSwgbmE9Ii4iLGFwcGVuZD1GQUxTRSxjb2xfbmFtZXM9VFJVRSkKZmY1X2dlbmVzIDwtIGZpbHRlcihmZjUsKFNZTUJPTCVpbiVIR1NfdHVtb3VyX2dlbmVzKXwKICAgICAgICAgICAgICAgICAgICAgKFNZTUJPTCVpbiVjKGdlbmVzKSkpICU+JSAKICB3cml0ZV9leGNlbF9jc3Yoc2FtcGxlX1dHUyxwYXRoPXBhc3RlKHNhbXBsZXNfV0dTLCJ0dW1vdXJfZ2VybWxpbmVfZ2Vub21lX3NvbWF0aWNfY2FsbGVyX2dlbmVzX2ZpbHRlcmVkLmNzdiIsc2VwPSJfIiksIG5hPSIuIixhcHBlbmQ9RkFMU0UsY29sX25hbWVzPVRSVUUpCmBgYAoKRXh0cmFjdCBlcXVpdmFsZW50IHJvd3Mgd2l0aCB2YXJpYW50cyBpbiBnZW5lKHMpIG9mIGludGVyZXN0IChvbmUgZ2VuZSB0cmFuc2NyaXB0IHBlciB2YXJpYW50KSBmcm9tIHR1bW91ci1vbmx5IHZhcmlhbnRzIGZpbGUgKGV4b21lcyBvbmx5LSBOL0EgZm9yIHVucGFpcmVkIHNvbWF0aWMgdHVtb3VyIGRhdGEpLCBhbmQgc2F2ZSBvdXRwdXQuCmBgYHtyfQpzZXR3ZChzYW1wbGVfV0VTKQpnX2dlbmVzIDwtIGZpbHRlcihnLChTWU1CT0wlaW4lSEdTX3R1bW91cl9nZW5lcyl8CiAgICAgICAgICAgICAgICAgICAgIChTWU1CT0wlaW4lYyhnZW5lcykpKSAlPiUgCiAgYXJyYW5nZShUcmFuc2NyaXB0X0luZGV4KSAlPiUgCiAgZGlzdGluY3QoQ0hST00sUE9TLFJFRixBTFQsLmtlZXBfYWxsPVRSVUUpCndyaXRlX2V4Y2VsX2NzdihnX2dlbmVzLHBhdGg9cGFzdGUoc2FtcGxlX1dFUywidHVtb3VyX2dlcm1saW5lX3BhaXJlZF9leG9tZXNfSEFQX2dlbmVzLmNzdiIsc2VwPSJfIiksIG5hPSIuIixhcHBlbmQ9RkFMU0UsY29sX25hbWVzPVRSVUUpCmBgYA==