Load libraries

# Load library
library(Seurat)
library(reticulate)
library(princurve)
library(mgcv)
library(Rtsne)

library(tidyr)
library(dplyr)

library(ggplot2)
library(ggplotify)
library(pheatmap)
library(gridExtra)
library(ggExtra)
library(cowplot)
library(patchwork)
library(RColorBrewer)

library(monocle)

#Set ggplot theme as classic
theme_set(theme_classic())

Load E12 dataset

AP.data <- readRDS("./AP.data.RDS")

Extract pallial progenitors

# Filter out subpallial progenitors
AP.ident <- unique(AP.data@ident)
AP.data <-  SubsetData(AP.data,
                          ident.use = grep("Sub", AP.ident, value = T, invert = T),
                          subset.raw = T,
                          do.clean = F)

# Reset the pseudo-DV score to [0,1]
score <- AP.data@meta.data$DorsoVentral.Score
AP.data@meta.data$DorsoVentral.Score <- (score - min(score)) / (max(score) - min(score))
p1 <- DimPlot(AP.data,
               group.by = "Domaine",
               reduction.use = "spring",
               dim.1 = 1,
               dim.2 = 2,
               do.label=F,
               label.size = 4,
               no.legend = F,
               do.return = T,
               cols.use = tolower(c("#68B041", "#E3C148", "#B7D174", "#E46B6B")))


data <- data.frame(spring1 = AP.data@dr$spring@cell.embeddings[,1],
                   spring2 = AP.data@dr$spring@cell.embeddings[,2],
                   DorsoVentral.Score = AP.data@meta.data$DorsoVentral.Score)

cols <- colorRampPalette(brewer.pal(n =11, name = "Spectral"))(100)
p2 <- ggplot(data, aes(spring1, spring2)) + 
      geom_point(aes(color=DorsoVentral.Score), size=2, shape=16)  + 
      scale_color_gradientn(colours=rev(cols), name='Speudotime score')

p1 + p2

Filter, normalize and scale the expression matrix

# Remove non expressed genes
num.cells <- Matrix::rowSums(AP.data@data > 0)
genes.use <- names(x = num.cells[which(x = num.cells >= 20)])
AP.data@raw.data <- AP.data@raw.data[genes.use, ]
AP.data@data <- AP.data@data[genes.use, ]

# Normalize the counts matrix
AP.data <- NormalizeData(object = AP.data,
                         normalization.method = "LogNormalize", 
                         scale.factor = round(median(AP.data@meta.data$nUMI)), display.progress = F)

# Scale expression matrix
AP.data <- ScaleData(object = AP.data,
                         vars.to.regress = c("nUMI", "nGene", "percent.ribo", "percent.mito"),
                         display.progress = F)


# Find most variable genes
AP.data <- FindVariableGenes(object = AP.data,
                                 mean.function = ExpMean,
                                 dispersion.function = LogVMR,
                                 x.low.cutoff = 0.0125,
                                 x.high.cutoff = 3, 
                                 y.cutoff = 1, 
                                 do.plot = F,
                                 display.progress = F)
rm(list=ls()[!ls() %in% c("AP.data")])

Compute PCA and exclude PCs correlating with unwanted sources of variation

# PCA
AP.data <- RunPCA(object = AP.data,
                      pcs.compute = 10,
                      do.print = F,
                      pcs.print = 1,
                      genes.print = 1,
                      weight.by.var=T)
Varaxis <- data.frame(Ccycle = AP.data@meta.data$CC.Difference,
                      Sphase = AP.data@meta.data$S.Score,
                      Dvaxis = AP.data@meta.data$DorsoVentral.Score)

PCcor <- abs(cor(AP.data@dr$pca@cell.embeddings, Varaxis))

pheatmap(t(PCcor),
         color = colorRampPalette(brewer.pal(n = 9, name = "RdPu"))(100),
         cluster_rows = F,
         cluster_cols = F,
         main = "Pearson's correlation between PCs and characterized axis of variation")

# Exclude PCs having a correlation with characterized axis of variation > 0.25
cor.th <- 0.25
Selected_PCs <- seq(1:nrow(PCcor))[colSums(t(PCcor) >= cor.th) == 0]
Selected_PCs
## [1]  4  5  6  7  8  9 10

Find principal curve over the six remaining PCs

data <- as.data.frame(AP.data@dr$pca@cell.embeddings[,Selected_PCs])

fit <- principal_curve(as.matrix(data),
                       smoother='lowess',
                       trace=TRUE,
                       f = .7,
                       stretch=0)
## Starting curve---distance^2: 33027138
## Iteration 1---distance^2: 36209.57
## Iteration 2---distance^2: 35442.71
## Iteration 3---distance^2: 35089.61
## Iteration 4---distance^2: 34762.58
## Iteration 5---distance^2: 34502.34
## Iteration 6---distance^2: 34333.51
## Iteration 7---distance^2: 34266.7
## Iteration 8---distance^2: 34206.63
## Iteration 9---distance^2: 34180.74
RostroCaudal.score <- fit$lambda/max(fit$lambda)
AP.data@meta.data$RostroCaudal.score <- RostroCaudal.score
data$Domaine <- AP.data@meta.data$Domaine

ggplot(data, aes(PC4, PC5)) + 
      geom_point(aes(color=Domaine), size=3, shape=16) +
      geom_line(data=as.data.frame(fit$s[order(fit$lambda),]), color='red', size=0.77) +
      scale_color_manual(values=c("#68b041", "#e3c148", "#b7d174", "#e46b6b"))

Varaxis <- data.frame(Ccycle = AP.data@meta.data$CC.Difference,
                      Sphase = AP.data@meta.data$S.Score,
                      Dvaxis = AP.data@meta.data$DorsoVentral.Score,
                      RCaxis = AP.data@meta.data$RostroCaudal.score)

PCcor <- abs(cor(AP.data@dr$pca@cell.embeddings, Varaxis))

pheatmap(t(PCcor),
         color = colorRampPalette(brewer.pal(n = 9, name = "RdPu"))(100),
         cluster_rows = F,
         cluster_cols = F,
         main = "Pearson's correlation between PCs and characterized axis of variation")

Set new coordinates as Pseudo-DV and Pseudo-RC axis scores

Coordinates <- data.frame(RostroCaudal.Axis = -AP.data@meta.data$RostroCaudal.score,
                          DorsoVentral.Axis = AP.data@meta.data$DorsoVentral.Score)

colnames(Coordinates) <- c("RC.DV.axis1","RC.DV.axis2")
rownames(Coordinates) <- rownames(AP.data@meta.data)

AP.data <- SetDimReduction(AP.data,
                           reduction.type = "RC.DV.axis",
                           slot = "cell.embeddings",
                           new.data = as.matrix(Coordinates))

AP.data@dr$RC.DV.axis@key <- "RC.DV.axis"
colnames(AP.data@dr$RC.DV.axis@cell.embeddings) <- paste0(GetDimReduction(object = AP.data, reduction.type = "RC.DV.axis",slot = "key"), c(1,2))
DimPlot(AP.data,
        group.by = "Domaine",
        reduction.use = "RC.DV.axis",
        do.label=F,
        pt.size = 3,
        label.size = 4,
        no.legend = F,
        do.return = T,
        cols.use =c("#68b041", "#e3c148", "#b7d174", "#e46b6b"))

Some genes with known RC gradient

Along the new RC axis

source("./functions/GenesTrendPlots.R")

Plot.Genes.trend(AP.data,
                 genes = c("Etv5","Pou3f2", "Nr2f2", "Igfbp5"),
                 Axis = "RostroCaudal.score",
                 Use.scale.data = F)

On both RC and DV axis

plot <- FeaturePlot(object = AP.data,
                    features.plot = c("Etv5","Pou3f2", "Nr2f2", "Igfbp5"),
                    cols.use = c("grey90", brewer.pal(9,"YlGnBu")),
                    reduction.use = "RC.DV.axis",
                    no.legend = T,
                    pt.size = 2,
                    overlay = F,
                    dark.theme = F,
                    do.return =T,
                    no.axes = T)
for (i in 1:length(plot)){
  plot[[i]]$data <- plot[[i]]$data[order(plot[[i]]$data$gene),]
}
cowplot::plot_grid(plotlist = plot[1:4], ncol =2)

Find differentially expressed genes along the pseudo RC axis

Initialize a monocle object

# Transfer metadata 
meta.data <- data.frame(barcode = rownames(AP.data@meta.data),
                        Cluster = AP.data@meta.data$old.ident,
                        RostroCaudal.score =  AP.data@meta.data$RostroCaudal.score,
                        DorsoVentral.Score = AP.data@meta.data$DorsoVentral.Score,
                        Domaine = AP.data@meta.data$Domaine,
                        CellcyclePhase = AP.data@meta.data$Phase,
                        row.names = rownames(AP.data@meta.data))
                   
Annot.data  <- new('AnnotatedDataFrame', data = meta.data)

# Transfer count data
count.data = data.frame(gene_short_name = rownames(AP.data@raw.data),
                  row.names = rownames(AP.data@raw.data))

feature.data <- new('AnnotatedDataFrame', data = count.data)

# Create the CellDataSet object
gbm_cds <- newCellDataSet(as.matrix(AP.data@raw.data),
                          phenoData = Annot.data,
                          featureData = feature.data,
                          lowerDetectionLimit = 1,
                          expressionFamily = negbinomial())
gbm_cds <- estimateSizeFactors(gbm_cds)
gbm_cds <- estimateDispersions(gbm_cds)
gbm_cds <- detectGenes(gbm_cds, min_expr = 0.1)
rm(list = ls()[!ls() %in% c("AP.data", "gbm_cds")])

Cluster significant genes based on their RC-DV expression landscape

Fit the smoothed expression landscape over DV and RC axis

source("./functions/FunctionsLandscapeSmoothing.R")

genes <- as.character(RC.Axis.genes.FDR.filtered$gene_short_name)

Sig.gene.expr.data <- AP.data@dr$RC.DV.axis@cell.embeddings
Sig.gene.expr.data <- as.data.frame(cbind(Sig.gene.expr.data, t(as.matrix(AP.data@data[genes,]))))

Sig.gene.landscape <- Gene.smooth.landscape.gam(Sig.gene.expr.data, scale.exp = T)

Perform PCA

pcs <- prcomp(t(Sig.gene.landscape[,3:ncol(Sig.gene.landscape)]))

eigs <- (pcs$sdev[1:10])^2
PercentVAr <- data.frame(PercentVAr = eigs*100 / sum(eigs),
                         PCs = 1:length(eigs))

ggplot(PercentVAr,aes(x= as.factor(PCs),y=PercentVAr)) +
  geom_point() +
  geom_hline(yintercept = 5,linetype = 2, colour="red")

Cluster genes on the first 3 PCs

set.seed(1234)

GenePCs <- as.data.frame(pcs$x[,1:3])
GenePCs$Gene <- rownames(GenePCs)

# Perform Kmeans clustering
kmeans <- kmeans(x = GenePCs[,1:3], centers = 5)
GenePCs$Cluster <- factor(kmeans$cluster)

ggplot(GenePCs, aes(PC1, -PC2)) + 
      geom_point(aes(color=Cluster),size=3, shape=16) +
      scale_color_manual(values=c("#d14c8d","#9ec22f", "#a9961b", "#cc3a1b", "#4cabdc", "#5ab793")) +
      ggrepel::geom_text_repel((aes(label=ifelse(Gene %in% c("Mpped2","Lhx2", "Etv5","Pax6", "Celf4", "Fgf15","Nr2f1", "Lypd6"), as.character(Gene),''))), 
                        box.padding   = 0.2, 
                        point.padding = 0.2,
                        max.overlaps = 50,
                        segment.color = 'grey50',
                        colour = "black")

RC.Axis.genes.FDR.filtered$PC1 <- GenePCs$PC1
RC.Axis.genes.FDR.filtered$PC2 <- GenePCs$PC2
RC.Axis.genes.FDR.filtered$Cluster <- GenePCs$Cluster

Average cluster’s landscape

Genes.landscape <- Gene.smooth.landscape.gam(Sig.gene.expr.data, scale.exp = F)
Cluster.means <- Genes.landscape[,1:2]

Cluster.means$Clust.1 <- scale(rowMeans(Genes.landscape[,GenePCs$Gene[GenePCs$Cluster == 1]]))
Cluster.means$Clust.2 <- scale(rowMeans(Genes.landscape[,GenePCs$Gene[GenePCs$Cluster == 2]]))
Cluster.means$Clust.3 <- scale(rowMeans(Genes.landscape[,GenePCs$Gene[GenePCs$Cluster == 3]]))
Cluster.means$Clust.4 <- scale(rowMeans(Genes.landscape[,GenePCs$Gene[GenePCs$Cluster == 4]]))
Cluster.means$Clust.5 <- scale(rowMeans(Genes.landscape[,GenePCs$Gene[GenePCs$Cluster == 5]]))

table(GenePCs$Cluster)
## 
##  1  2  3  4  5 
## 33 29 30 46 21
Plot.landscape(data = Cluster.means,
               genes = paste0("Clust.", 1:5),
               col.pal =  "BrBG",
               ncols = 3)

Plot some selected genes landscape

Selected.gene <- c("Mpped2","Lhx2", "Etv5",
                   "Pax6", "Nr2f1", "Lypd6")

Selected.gene.expr.data <- AP.data@dr$RC.DV.axis@cell.embeddings
Selected.gene.expr.data <- as.data.frame(cbind(Selected.gene.expr.data, t(as.matrix(AP.data@data[Selected.gene,]))))


Selected.gene.landscape <- Gene.smooth.landscape.gam(Selected.gene.expr.data, scale.exp = T)

Plot.landscape(data = Selected.gene.landscape ,
               genes = Selected.gene,
               col.pal =  "RdBu",
               ncols = 3)

Intersect RC variable with DV variable genes

DVgenes <- read.table("./Progenitors/Gene.dynamique.csv", sep = ";", header = T)

RC.genes <- as.character(RC.Axis.genes.FDR.filtered$gene_short_name)
DV.genes <- as.character(DVgenes$Gene)

gene.list <- list(RC.genes = RC.genes, DV.genes = DV.genes)
ggvenn::ggvenn(gene.list)

RC.Axis.genes.FDR.filtered$DV_axis_variable <- RC.genes %in% DV.genes

write.table(RC.Axis.genes.FDR.filtered, "./Progenitors/Rostro_Caudal_genes.csv", sep = ";", quote = F)

Session Info

#date
format(Sys.time(), "%d %B, %Y, %H,%M")
## [1] "03 mai, 2021, 11,49"
#Packages used
sessionInfo()
## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 18.04.5 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/atlas/libblas.so.3.10.3
## LAPACK: /usr/lib/x86_64-linux-gnu/atlas/liblapack.so.3.10.3
## 
## locale:
##  [1] LC_CTYPE=fr_FR.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=fr_FR.UTF-8        LC_COLLATE=fr_FR.UTF-8    
##  [5] LC_MONETARY=fr_FR.UTF-8    LC_MESSAGES=fr_FR.UTF-8   
##  [7] LC_PAPER=fr_FR.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=fr_FR.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
##  [1] splines   stats4    parallel  stats     graphics  grDevices utils    
##  [8] datasets  methods   base     
## 
## other attached packages:
##  [1] monocle_2.14.0      DDRTree_0.1.5       irlba_2.3.3        
##  [4] VGAM_1.1-2          Biobase_2.46.0      BiocGenerics_0.32.0
##  [7] RColorBrewer_1.1-2  patchwork_0.0.1     ggExtra_0.9        
## [10] gridExtra_2.3       pheatmap_1.0.12     ggplotify_0.0.5    
## [13] dplyr_0.8.3         tidyr_1.0.0         Rtsne_0.15         
## [16] mgcv_1.8-33         nlme_3.1-141        princurve_2.1.4    
## [19] reticulate_1.18     Seurat_2.3.4        Matrix_1.2-17      
## [22] cowplot_1.0.0       ggplot2_3.2.1      
## 
## loaded via a namespace (and not attached):
##   [1] snow_0.4-3           backports_1.1.5      Hmisc_4.3-0         
##   [4] plyr_1.8.4           igraph_1.2.5         lazyeval_0.2.2      
##   [7] densityClust_0.3     fastICA_1.2-2        digest_0.6.25       
##  [10] foreach_1.4.7        htmltools_0.5.0      viridis_0.5.1       
##  [13] lars_1.2             gdata_2.18.0         magrittr_1.5        
##  [16] checkmate_1.9.4      cluster_2.1.0        mixtools_1.1.0      
##  [19] ROCR_1.0-7           limma_3.42.0         matrixStats_0.55.0  
##  [22] docopt_0.6.1         R.utils_2.9.0        colorspace_1.4-1    
##  [25] ggrepel_0.8.1        xfun_0.18            sparsesvd_0.2       
##  [28] crayon_1.4.0         jsonlite_1.7.0       zeallot_0.1.0       
##  [31] survival_2.44-1.1    zoo_1.8-6            iterators_1.0.12    
##  [34] ape_5.3              glue_1.4.1           gtable_0.3.0        
##  [37] kernlab_0.9-29       prabclus_2.3-1       DEoptimR_1.0-8      
##  [40] scales_1.1.0         bibtex_0.4.2         miniUI_0.1.1.1      
##  [43] Rcpp_1.0.5           metap_1.1            dtw_1.21-3          
##  [46] viridisLite_0.3.0    xtable_1.8-4         htmlTable_1.13.2    
##  [49] gridGraphics_0.5-1   foreign_0.8-72       bit_4.0.4           
##  [52] proxy_0.4-23         mclust_5.4.5         SDMTools_1.1-221.1  
##  [55] Formula_1.2-3        tsne_0.1-3           htmlwidgets_1.5.1   
##  [58] httr_1.4.1           FNN_1.1.3            gplots_3.0.1.1      
##  [61] fpc_2.2-3            acepack_1.4.1        modeltools_0.2-22   
##  [64] ica_1.0-2            farver_2.0.1         pkgconfig_2.0.3     
##  [67] R.methodsS3_1.7.1    flexmix_2.3-15       nnet_7.3-14         
##  [70] labeling_0.3         tidyselect_0.2.5     rlang_0.4.7         
##  [73] reshape2_1.4.3       later_1.0.0          munsell_0.5.0       
##  [76] tools_3.6.3          ggridges_0.5.1       evaluate_0.14       
##  [79] stringr_1.4.0        fastmap_1.0.1        yaml_2.2.1          
##  [82] npsurv_0.4-0         knitr_1.26           bit64_4.0.2         
##  [85] fitdistrplus_1.0-14  robustbase_0.93-5    caTools_1.17.1.2    
##  [88] purrr_0.3.3          RANN_2.6.1           pbapply_1.4-2       
##  [91] mime_0.7             slam_0.1-46          R.oo_1.23.0         
##  [94] ggvenn_0.1.8         hdf5r_1.3.2.9000     compiler_3.6.3      
##  [97] rstudioapi_0.11      png_0.1-7            lsei_1.2-0          
## [100] tibble_2.1.3         stringi_1.4.6        lattice_0.20-41     
## [103] HSMMSingleCell_1.6.0 vctrs_0.2.0          pillar_1.4.2        
## [106] lifecycle_0.1.0      BiocManager_1.30.10  combinat_0.0-8      
## [109] Rdpack_0.11-0        lmtest_0.9-37        data.table_1.12.6   
## [112] bitops_1.0-6         gbRd_0.4-11          httpuv_1.5.2        
## [115] R6_2.4.1             latticeExtra_0.6-28  promises_1.1.0      
## [118] KernSmooth_2.23-15   codetools_0.2-16     MASS_7.3-53         
## [121] gtools_3.8.1         assertthat_0.2.1     withr_2.1.2         
## [124] qlcMatrix_0.9.7      diptest_0.75-7       doSNOW_1.0.18       
## [127] grid_3.6.3           rpart_4.1-15         class_7.3-17        
## [130] rmarkdown_2.5        rvcheck_0.1.7        segmented_1.0-0     
## [133] shiny_1.4.0          base64enc_0.1-3

  1. Institute of Psychiatry and Neuroscience of Paris, INSERM U1266, 75014, Paris, France, ↩︎

LS0tCnRpdGxlOiAiUm9zdHJvLWNhdWRhbCBheGlzIG9mIHZhcmlhdGlvbiBhbW9uZyBwYWxsaWFsIHByb2dlbml0b3JzIgphdXRob3I6CiAgIC0gTWF0dGhpZXUgTW9yZWF1XltJbnN0aXR1dGUgb2YgUHN5Y2hpYXRyeSBhbmQgTmV1cm9zY2llbmNlIG9mIFBhcmlzLCBJTlNFUk0gVTEyNjYsIDc1MDE0LCBQYXJpcywgRnJhbmNlLCBtYXR0aGlldS5tb3JlYXVAaW5zZXJtLmZyXSBbIVtdKGh0dHBzOi8vb3JjaWQub3JnL3NpdGVzL2RlZmF1bHQvZmlsZXMvaW1hZ2VzL29yY2lkXzE2eDE2LnBuZyldKGh0dHBzOi8vb3JjaWQub3JnLzAwMDAtMDAwMi0yNTkyLTIzNzMpCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OiAKICAgIGNvZGVfZG93bmxvYWQ6IHllcwogICAgZGZfcHJpbnQ6IHRpYmJsZQogICAgaGlnaGxpZ2h0OiBoYWRkb2NrCiAgICBpbmNsdWRlczoKICAgICAgaW5faGVhZGVyOiBoZWFkZXIuaHRtbAogICAgdGhlbWU6IGNvc21vCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogeWVzCi0tLQoKYGBge2NzcywgZWNobz1GQUxTRX0KaDEgewogIGZvbnQtc2l6ZTogMzRweDsKICBtYXJnaW4tdG9wOiAycmVtOwogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgY29sb3I6ICNlNjRkMDA7CiAgdGV4dC1kZWNvcmF0aW9uOiBub25lOwp9CmgxLnRpdGxlIHsKICBmb250LXNpemU6IDQwcHg7CiAgbWFyZ2luLXRvcDogMnJlbTsKICBtYXJnaW4tYm90dG9tOiAxcmVtOwogIHRleHQtYWxpZ246IGNlbnRlcjsKICB0ZXh0LWRlY29yYXRpb246IG5vbmU7CiAgY29sb3I6ICMwMDAwMDA7Cn0KaDIgewogIGZvbnQtc2l6ZTogMzBweDsKICBtYXJnaW4tdG9wOiAycmVtOwogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgY29sb3I6ICMwMDAwMDA7Cn0KaDMgewogIGZvbnQtc2l6ZTogMjRweDsKICBtYXJnaW4tdG9wOiAycmVtOwogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgY29sb3I6ICMwMDAwMDA7Cn0KaDQgewogIGZvbnQtc2l6ZTogMjBweDsKICBtYXJnaW4tdG9wOiAycmVtOwogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgY29sb3I6ICMwMDAwMDA7Cn0KaDUgewogIGZvbnQtc2l6ZTogMThweDsKICBtYXJnaW4tdG9wOiAycmVtOwogIG1hcmdpbi1ib3R0b206IDFyZW07CiAgY29sb3I6ICMwMDAwMDA7Cn0KCi5zY3JvbGwtMTAwIHsKICBtYXgtaGVpZ2h0OiAyMDBweDsKICBvdmVyZmxvdy15OiBhdXRvOwogIGJhY2tncm91bmQtY29sb3I6IGluaGVyaXQ7Cn0KCnAgewogIGZvbnQtc2l6ZTogMTZweDsKfQpgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpzZXQuc2VlZCgxMjM0KQpgYGAKCiMgTG9hZCBsaWJyYXJpZXMKCmBgYHtyIH0KIyBMb2FkIGxpYnJhcnkKbGlicmFyeShTZXVyYXQpCmxpYnJhcnkocmV0aWN1bGF0ZSkKbGlicmFyeShwcmluY3VydmUpCmxpYnJhcnkobWdjdikKbGlicmFyeShSdHNuZSkKCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCgpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dwbG90aWZ5KQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShnZ0V4dHJhKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKCmxpYnJhcnkobW9ub2NsZSkKCiNTZXQgZ2dwbG90IHRoZW1lIGFzIGNsYXNzaWMKdGhlbWVfc2V0KHRoZW1lX2NsYXNzaWMoKSkKCgpgYGAKCiMgTG9hZCBFMTIgZGF0YXNldAoKYGBge3J9CkFQLmRhdGEgPC0gcmVhZFJEUygiLi9BUC5kYXRhLlJEUyIpCmBgYAoKIyMgRXh0cmFjdCBwYWxsaWFsIHByb2dlbml0b3JzCgpgYGB7cn0KIyBGaWx0ZXIgb3V0IHN1YnBhbGxpYWwgcHJvZ2VuaXRvcnMKQVAuaWRlbnQgPC0gdW5pcXVlKEFQLmRhdGFAaWRlbnQpCkFQLmRhdGEgPC0gIFN1YnNldERhdGEoQVAuZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICBpZGVudC51c2UgPSBncmVwKCJTdWIiLCBBUC5pZGVudCwgdmFsdWUgPSBULCBpbnZlcnQgPSBUKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJzZXQucmF3ID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICBkby5jbGVhbiA9IEYpCgojIFJlc2V0IHRoZSBwc2V1ZG8tRFYgc2NvcmUgdG8gWzAsMV0Kc2NvcmUgPC0gQVAuZGF0YUBtZXRhLmRhdGEkRG9yc29WZW50cmFsLlNjb3JlCkFQLmRhdGFAbWV0YS5kYXRhJERvcnNvVmVudHJhbC5TY29yZSA8LSAoc2NvcmUgLSBtaW4oc2NvcmUpKSAvIChtYXgoc2NvcmUpIC0gbWluKHNjb3JlKSkKYGBgCgpgYGB7ciBmaWcuZGltPWMoMTIsIDQpfQpwMSA8LSBEaW1QbG90KEFQLmRhdGEsCiAgICAgICAgICAgICAgIGdyb3VwLmJ5ID0gIkRvbWFpbmUiLAogICAgICAgICAgICAgICByZWR1Y3Rpb24udXNlID0gInNwcmluZyIsCiAgICAgICAgICAgICAgIGRpbS4xID0gMSwKICAgICAgICAgICAgICAgZGltLjIgPSAyLAogICAgICAgICAgICAgICBkby5sYWJlbD1GLAogICAgICAgICAgICAgICBsYWJlbC5zaXplID0gNCwKICAgICAgICAgICAgICAgbm8ubGVnZW5kID0gRiwKICAgICAgICAgICAgICAgZG8ucmV0dXJuID0gVCwKICAgICAgICAgICAgICAgY29scy51c2UgPSB0b2xvd2VyKGMoIiM2OEIwNDEiLCAiI0UzQzE0OCIsICIjQjdEMTc0IiwgIiNFNDZCNkIiKSkpCgoKZGF0YSA8LSBkYXRhLmZyYW1lKHNwcmluZzEgPSBBUC5kYXRhQGRyJHNwcmluZ0BjZWxsLmVtYmVkZGluZ3NbLDFdLAogICAgICAgICAgICAgICAgICAgc3ByaW5nMiA9IEFQLmRhdGFAZHIkc3ByaW5nQGNlbGwuZW1iZWRkaW5nc1ssMl0sCiAgICAgICAgICAgICAgICAgICBEb3Jzb1ZlbnRyYWwuU2NvcmUgPSBBUC5kYXRhQG1ldGEuZGF0YSREb3Jzb1ZlbnRyYWwuU2NvcmUpCgpjb2xzIDwtIGNvbG9yUmFtcFBhbGV0dGUoYnJld2VyLnBhbChuID0xMSwgbmFtZSA9ICJTcGVjdHJhbCIpKSgxMDApCnAyIDwtIGdncGxvdChkYXRhLCBhZXMoc3ByaW5nMSwgc3ByaW5nMikpICsgCiAgICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yPURvcnNvVmVudHJhbC5TY29yZSksIHNpemU9Miwgc2hhcGU9MTYpICArIAogICAgICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oY29sb3Vycz1yZXYoY29scyksIG5hbWU9J1NwZXVkb3RpbWUgc2NvcmUnKQoKcDEgKyBwMgpgYGAKCiMjIEZpbHRlciwgbm9ybWFsaXplIGFuZCBzY2FsZSB0aGUgZXhwcmVzc2lvbiBtYXRyaXgKCmBgYHtyfQojIFJlbW92ZSBub24gZXhwcmVzc2VkIGdlbmVzCm51bS5jZWxscyA8LSBNYXRyaXg6OnJvd1N1bXMoQVAuZGF0YUBkYXRhID4gMCkKZ2VuZXMudXNlIDwtIG5hbWVzKHggPSBudW0uY2VsbHNbd2hpY2goeCA9IG51bS5jZWxscyA+PSAyMCldKQpBUC5kYXRhQHJhdy5kYXRhIDwtIEFQLmRhdGFAcmF3LmRhdGFbZ2VuZXMudXNlLCBdCkFQLmRhdGFAZGF0YSA8LSBBUC5kYXRhQGRhdGFbZ2VuZXMudXNlLCBdCgojIE5vcm1hbGl6ZSB0aGUgY291bnRzIG1hdHJpeApBUC5kYXRhIDwtIE5vcm1hbGl6ZURhdGEob2JqZWN0ID0gQVAuZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIkxvZ05vcm1hbGl6ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUuZmFjdG9yID0gcm91bmQobWVkaWFuKEFQLmRhdGFAbWV0YS5kYXRhJG5VTUkpKSwgZGlzcGxheS5wcm9ncmVzcyA9IEYpCgojIFNjYWxlIGV4cHJlc3Npb24gbWF0cml4CkFQLmRhdGEgPC0gU2NhbGVEYXRhKG9iamVjdCA9IEFQLmRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICB2YXJzLnRvLnJlZ3Jlc3MgPSBjKCJuVU1JIiwgIm5HZW5lIiwgInBlcmNlbnQucmlibyIsICJwZXJjZW50Lm1pdG8iKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3BsYXkucHJvZ3Jlc3MgPSBGKQoKCiMgRmluZCBtb3N0IHZhcmlhYmxlIGdlbmVzCkFQLmRhdGEgPC0gRmluZFZhcmlhYmxlR2VuZXMob2JqZWN0ID0gQVAuZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbi5mdW5jdGlvbiA9IEV4cE1lYW4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3BlcnNpb24uZnVuY3Rpb24gPSBMb2dWTVIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHgubG93LmN1dG9mZiA9IDAuMDEyNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeC5oaWdoLmN1dG9mZiA9IDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5LmN1dG9mZiA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkby5wbG90ID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzcGxheS5wcm9ncmVzcyA9IEYpCmBgYAoKYGBge3J9CnJtKGxpc3Q9bHMoKVshbHMoKSAlaW4lIGMoIkFQLmRhdGEiKV0pCmBgYAoKIyBDb21wdXRlIFBDQSBhbmQgZXhjbHVkZSBQQ3MgY29ycmVsYXRpbmcgd2l0aCB1bndhbnRlZCBzb3VyY2VzIG9mIHZhcmlhdGlvbgoKYGBge3J9CiMgUENBCkFQLmRhdGEgPC0gUnVuUENBKG9iamVjdCA9IEFQLmRhdGEsCiAgICAgICAgICAgICAgICAgICAgICBwY3MuY29tcHV0ZSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgZG8ucHJpbnQgPSBGLAogICAgICAgICAgICAgICAgICAgICAgcGNzLnByaW50ID0gMSwKICAgICAgICAgICAgICAgICAgICAgIGdlbmVzLnByaW50ID0gMSwKICAgICAgICAgICAgICAgICAgICAgIHdlaWdodC5ieS52YXI9VCkKYGBgCgpgYGB7ciBmaWcuZGltPWMoNS4zLCA0KX0KVmFyYXhpcyA8LSBkYXRhLmZyYW1lKENjeWNsZSA9IEFQLmRhdGFAbWV0YS5kYXRhJENDLkRpZmZlcmVuY2UsCiAgICAgICAgICAgICAgICAgICAgICBTcGhhc2UgPSBBUC5kYXRhQG1ldGEuZGF0YSRTLlNjb3JlLAogICAgICAgICAgICAgICAgICAgICAgRHZheGlzID0gQVAuZGF0YUBtZXRhLmRhdGEkRG9yc29WZW50cmFsLlNjb3JlKQoKUENjb3IgPC0gYWJzKGNvcihBUC5kYXRhQGRyJHBjYUBjZWxsLmVtYmVkZGluZ3MsIFZhcmF4aXMpKQoKcGhlYXRtYXAodChQQ2NvciksCiAgICAgICAgIGNvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKG4gPSA5LCBuYW1lID0gIlJkUHUiKSkoMTAwKSwKICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRiwKICAgICAgICAgY2x1c3Rlcl9jb2xzID0gRiwKICAgICAgICAgbWFpbiA9ICJQZWFyc29uJ3MgY29ycmVsYXRpb24gYmV0d2VlbiBQQ3MgYW5kIGNoYXJhY3Rlcml6ZWQgYXhpcyBvZiB2YXJpYXRpb24iKQoKIyBFeGNsdWRlIFBDcyBoYXZpbmcgYSBjb3JyZWxhdGlvbiB3aXRoIGNoYXJhY3Rlcml6ZWQgYXhpcyBvZiB2YXJpYXRpb24gPiAwLjI1CmNvci50aCA8LSAwLjI1ClNlbGVjdGVkX1BDcyA8LSBzZXEoMTpucm93KFBDY29yKSlbY29sU3Vtcyh0KFBDY29yKSA+PSBjb3IudGgpID09IDBdClNlbGVjdGVkX1BDcwpgYGAKCiMgRmluZCBwcmluY2lwYWwgY3VydmUgb3ZlciB0aGUgc2l4IHJlbWFpbmluZyBQQ3MKCmBgYHtyfQpkYXRhIDwtIGFzLmRhdGEuZnJhbWUoQVAuZGF0YUBkciRwY2FAY2VsbC5lbWJlZGRpbmdzWyxTZWxlY3RlZF9QQ3NdKQoKZml0IDwtIHByaW5jaXBhbF9jdXJ2ZShhcy5tYXRyaXgoZGF0YSksCiAgICAgICAgICAgICAgICAgICAgICAgc21vb3RoZXI9J2xvd2VzcycsCiAgICAgICAgICAgICAgICAgICAgICAgdHJhY2U9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgICBmID0gLjcsCiAgICAgICAgICAgICAgICAgICAgICAgc3RyZXRjaD0wKQoKUm9zdHJvQ2F1ZGFsLnNjb3JlIDwtIGZpdCRsYW1iZGEvbWF4KGZpdCRsYW1iZGEpCkFQLmRhdGFAbWV0YS5kYXRhJFJvc3Ryb0NhdWRhbC5zY29yZSA8LSBSb3N0cm9DYXVkYWwuc2NvcmUKYGBgCgpgYGB7ciBmaWcuZGltPWMoNiwgNCl9CmRhdGEkRG9tYWluZSA8LSBBUC5kYXRhQG1ldGEuZGF0YSREb21haW5lCgpnZ3Bsb3QoZGF0YSwgYWVzKFBDNCwgUEM1KSkgKyAKICAgICAgZ2VvbV9wb2ludChhZXMoY29sb3I9RG9tYWluZSksIHNpemU9Mywgc2hhcGU9MTYpICsKICAgICAgZ2VvbV9saW5lKGRhdGE9YXMuZGF0YS5mcmFtZShmaXQkc1tvcmRlcihmaXQkbGFtYmRhKSxdKSwgY29sb3I9J3JlZCcsIHNpemU9MC43NykgKwogICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoIiM2OGIwNDEiLCAiI2UzYzE0OCIsICIjYjdkMTc0IiwgIiNlNDZiNmIiKSkKYGBgCgpgYGB7ciBmaWcuZGltPWMoNS4zLCA0KX0KVmFyYXhpcyA8LSBkYXRhLmZyYW1lKENjeWNsZSA9IEFQLmRhdGFAbWV0YS5kYXRhJENDLkRpZmZlcmVuY2UsCiAgICAgICAgICAgICAgICAgICAgICBTcGhhc2UgPSBBUC5kYXRhQG1ldGEuZGF0YSRTLlNjb3JlLAogICAgICAgICAgICAgICAgICAgICAgRHZheGlzID0gQVAuZGF0YUBtZXRhLmRhdGEkRG9yc29WZW50cmFsLlNjb3JlLAogICAgICAgICAgICAgICAgICAgICAgUkNheGlzID0gQVAuZGF0YUBtZXRhLmRhdGEkUm9zdHJvQ2F1ZGFsLnNjb3JlKQoKUENjb3IgPC0gYWJzKGNvcihBUC5kYXRhQGRyJHBjYUBjZWxsLmVtYmVkZGluZ3MsIFZhcmF4aXMpKQoKcGhlYXRtYXAodChQQ2NvciksCiAgICAgICAgIGNvbG9yID0gY29sb3JSYW1wUGFsZXR0ZShicmV3ZXIucGFsKG4gPSA5LCBuYW1lID0gIlJkUHUiKSkoMTAwKSwKICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRiwKICAgICAgICAgY2x1c3Rlcl9jb2xzID0gRiwKICAgICAgICAgbWFpbiA9ICJQZWFyc29uJ3MgY29ycmVsYXRpb24gYmV0d2VlbiBQQ3MgYW5kIGNoYXJhY3Rlcml6ZWQgYXhpcyBvZiB2YXJpYXRpb24iKQpgYGAKCgojIFNldCBuZXcgY29vcmRpbmF0ZXMgYXMgUHNldWRvLURWIGFuZCBQc2V1ZG8tUkMgYXhpcyBzY29yZXMKCmBgYHtyfQpDb29yZGluYXRlcyA8LSBkYXRhLmZyYW1lKFJvc3Ryb0NhdWRhbC5BeGlzID0gLUFQLmRhdGFAbWV0YS5kYXRhJFJvc3Ryb0NhdWRhbC5zY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgICBEb3Jzb1ZlbnRyYWwuQXhpcyA9IEFQLmRhdGFAbWV0YS5kYXRhJERvcnNvVmVudHJhbC5TY29yZSkKCmNvbG5hbWVzKENvb3JkaW5hdGVzKSA8LSBjKCJSQy5EVi5heGlzMSIsIlJDLkRWLmF4aXMyIikKcm93bmFtZXMoQ29vcmRpbmF0ZXMpIDwtIHJvd25hbWVzKEFQLmRhdGFAbWV0YS5kYXRhKQoKQVAuZGF0YSA8LSBTZXREaW1SZWR1Y3Rpb24oQVAuZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uLnR5cGUgPSAiUkMuRFYuYXhpcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAiY2VsbC5lbWJlZGRpbmdzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3LmRhdGEgPSBhcy5tYXRyaXgoQ29vcmRpbmF0ZXMpKQoKQVAuZGF0YUBkciRSQy5EVi5heGlzQGtleSA8LSAiUkMuRFYuYXhpcyIKY29sbmFtZXMoQVAuZGF0YUBkciRSQy5EVi5heGlzQGNlbGwuZW1iZWRkaW5ncykgPC0gcGFzdGUwKEdldERpbVJlZHVjdGlvbihvYmplY3QgPSBBUC5kYXRhLCByZWR1Y3Rpb24udHlwZSA9ICJSQy5EVi5heGlzIixzbG90ID0gImtleSIpLCBjKDEsMikpCmBgYAoKYGBge3IgZmlnLmRpbT1jKDUuMywgNCl9CkRpbVBsb3QoQVAuZGF0YSwKICAgICAgICBncm91cC5ieSA9ICJEb21haW5lIiwKICAgICAgICByZWR1Y3Rpb24udXNlID0gIlJDLkRWLmF4aXMiLAogICAgICAgIGRvLmxhYmVsPUYsCiAgICAgICAgcHQuc2l6ZSA9IDMsCiAgICAgICAgbGFiZWwuc2l6ZSA9IDQsCiAgICAgICAgbm8ubGVnZW5kID0gRiwKICAgICAgICBkby5yZXR1cm4gPSBULAogICAgICAgIGNvbHMudXNlID1jKCIjNjhiMDQxIiwgIiNlM2MxNDgiLCAiI2I3ZDE3NCIsICIjZTQ2YjZiIikpCmBgYAoKIyMgU29tZSBnZW5lcyB3aXRoIGtub3duIFJDIGdyYWRpZW50CgojIyMgQWxvbmcgdGhlIG5ldyBSQyBheGlzCgpgYGB7ciBmaWcuZGltPWMoNSwgMyl9CnNvdXJjZSgiLi9mdW5jdGlvbnMvR2VuZXNUcmVuZFBsb3RzLlIiKQoKUGxvdC5HZW5lcy50cmVuZChBUC5kYXRhLAogICAgICAgICAgICAgICAgIGdlbmVzID0gYygiRXR2NSIsIlBvdTNmMiIsICJOcjJmMiIsICJJZ2ZicDUiKSwKICAgICAgICAgICAgICAgICBBeGlzID0gIlJvc3Ryb0NhdWRhbC5zY29yZSIsCiAgICAgICAgICAgICAgICAgVXNlLnNjYWxlLmRhdGEgPSBGKQpgYGAKCgojIyMgT24gYm90aCBSQyBhbmQgRFYgYXhpcwoKYGBge3IgZmlnLnNob3c9J2hpZGUnfQpwbG90IDwtIEZlYXR1cmVQbG90KG9iamVjdCA9IEFQLmRhdGEsCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMucGxvdCA9IGMoIkV0djUiLCJQb3UzZjIiLCAiTnIyZjIiLCAiSWdmYnA1IiksCiAgICAgICAgICAgICAgICAgICAgY29scy51c2UgPSBjKCJncmV5OTAiLCBicmV3ZXIucGFsKDksIllsR25CdSIpKSwKICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24udXNlID0gIlJDLkRWLmF4aXMiLAogICAgICAgICAgICAgICAgICAgIG5vLmxlZ2VuZCA9IFQsCiAgICAgICAgICAgICAgICAgICAgcHQuc2l6ZSA9IDIsCiAgICAgICAgICAgICAgICAgICAgb3ZlcmxheSA9IEYsCiAgICAgICAgICAgICAgICAgICAgZGFyay50aGVtZSA9IEYsCiAgICAgICAgICAgICAgICAgICAgZG8ucmV0dXJuID1ULAogICAgICAgICAgICAgICAgICAgIG5vLmF4ZXMgPSBUKQoKCmZvciAoaSBpbiAxOmxlbmd0aChwbG90KSl7CiAgcGxvdFtbaV1dJGRhdGEgPC0gcGxvdFtbaV1dJGRhdGFbb3JkZXIocGxvdFtbaV1dJGRhdGEkZ2VuZSksXQp9CmBgYAoKYGBge3IgZmlnLmRpbT1jKDUsIDQpfQpjb3dwbG90OjpwbG90X2dyaWQocGxvdGxpc3QgPSBwbG90WzE6NF0sIG5jb2wgPTIpCmBgYAoKCgojIEZpbmQgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIGFsb25nIHRoZSBwc2V1ZG8gUkMgYXhpcwoKIyMgSW5pdGlhbGl6ZSBhIG1vbm9jbGUgb2JqZWN0CgpgYGB7cn0KIyBUcmFuc2ZlciBtZXRhZGF0YSAKbWV0YS5kYXRhIDwtIGRhdGEuZnJhbWUoYmFyY29kZSA9IHJvd25hbWVzKEFQLmRhdGFAbWV0YS5kYXRhKSwKICAgICAgICAgICAgICAgICAgICAgICAgQ2x1c3RlciA9IEFQLmRhdGFAbWV0YS5kYXRhJG9sZC5pZGVudCwKICAgICAgICAgICAgICAgICAgICAgICAgUm9zdHJvQ2F1ZGFsLnNjb3JlID0gIEFQLmRhdGFAbWV0YS5kYXRhJFJvc3Ryb0NhdWRhbC5zY29yZSwKICAgICAgICAgICAgICAgICAgICAgICAgRG9yc29WZW50cmFsLlNjb3JlID0gQVAuZGF0YUBtZXRhLmRhdGEkRG9yc29WZW50cmFsLlNjb3JlLAogICAgICAgICAgICAgICAgICAgICAgICBEb21haW5lID0gQVAuZGF0YUBtZXRhLmRhdGEkRG9tYWluZSwKICAgICAgICAgICAgICAgICAgICAgICAgQ2VsbGN5Y2xlUGhhc2UgPSBBUC5kYXRhQG1ldGEuZGF0YSRQaGFzZSwKICAgICAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gcm93bmFtZXMoQVAuZGF0YUBtZXRhLmRhdGEpKQogICAgICAgICAgICAgICAgICAgCkFubm90LmRhdGEgIDwtIG5ldygnQW5ub3RhdGVkRGF0YUZyYW1lJywgZGF0YSA9IG1ldGEuZGF0YSkKCiMgVHJhbnNmZXIgY291bnQgZGF0YQpjb3VudC5kYXRhID0gZGF0YS5mcmFtZShnZW5lX3Nob3J0X25hbWUgPSByb3duYW1lcyhBUC5kYXRhQHJhdy5kYXRhKSwKICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gcm93bmFtZXMoQVAuZGF0YUByYXcuZGF0YSkpCgpmZWF0dXJlLmRhdGEgPC0gbmV3KCdBbm5vdGF0ZWREYXRhRnJhbWUnLCBkYXRhID0gY291bnQuZGF0YSkKCiMgQ3JlYXRlIHRoZSBDZWxsRGF0YVNldCBvYmplY3QKZ2JtX2NkcyA8LSBuZXdDZWxsRGF0YVNldChhcy5tYXRyaXgoQVAuZGF0YUByYXcuZGF0YSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGhlbm9EYXRhID0gQW5ub3QuZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICBmZWF0dXJlRGF0YSA9IGZlYXR1cmUuZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICBsb3dlckRldGVjdGlvbkxpbWl0ID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICBleHByZXNzaW9uRmFtaWx5ID0gbmVnYmlub21pYWwoKSkKYGBgCgpgYGB7cn0KZ2JtX2NkcyA8LSBlc3RpbWF0ZVNpemVGYWN0b3JzKGdibV9jZHMpCmdibV9jZHMgPC0gZXN0aW1hdGVEaXNwZXJzaW9ucyhnYm1fY2RzKQpnYm1fY2RzIDwtIGRldGVjdEdlbmVzKGdibV9jZHMsIG1pbl9leHByID0gMC4xKQpgYGAKCmBgYHtyfQpybShsaXN0ID0gbHMoKVshbHMoKSAlaW4lIGMoIkFQLmRhdGEiLCAiZ2JtX2NkcyIpXSkKYGBgCgojIyBUZXN0IGVhY2ggZ2VuZSB0cmVuZHMgb3ZlciBwc2V1ZG8tUkMgc2NvcmUKCmBgYHtyfQojIEV4Y2x1ZGUgY2VsbCBjeWNsZSBhc3NvY2lhdGVkIGdlbmVzCkNDZ2VuZXMgPC0gYXMuY2hhcmFjdGVyKHJlYWQudGFibGUoIi4vUHJvZ2VuaXRvcnMvQ2VsbEN5Y2xlR2VuZXMuY3N2Iiwgc2VwID0gIlx0IiwgaGVhZGVyID0gRilbLDFdKQpJbnB1dC5nZW5lcyA8LSBBUC5kYXRhQHZhci5nZW5lc1shQVAuZGF0YUB2YXIuZ2VuZXMgJWluJSBjKENDZ2VuZXMsIlhpc3QiKV0KYGBgCgpgYGB7cn0KUkMuQXhpcy5nZW5lcyA8LSBkaWZmZXJlbnRpYWxHZW5lVGVzdChnYm1fY2RzW0lucHV0LmdlbmVzLF0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxNb2RlbEZvcm11bGFTdHIgPSAifnNtLm5zKFJvc3Ryb0NhdWRhbC5zY29yZSwgZGYgPSAzKSpzbS5ucyhEb3Jzb1ZlbnRyYWwuU2NvcmUsIGRmID0gMykiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y2VkTW9kZWxGb3JtdWxhU3RyID0gIn5zbS5ucyhEb3Jzb1ZlbnRyYWwuU2NvcmUsIGRmID0gMykiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3JlcyA9IGRldGVjdENvcmVzKCkgLTIpCgojIEZpbHRlciBnZW5lcyB3aXRoIGEgRkRSIDwgMC4wMDAxClJDLkF4aXMuZ2VuZXMuRkRSLmZpbHRlcmVkIDwtIFJDLkF4aXMuZ2VuZXMgJT4lIGZpbHRlcihxdmFsIDwgMWUtNCkKYGBgCgojIENsdXN0ZXIgc2lnbmlmaWNhbnQgZ2VuZXMgYmFzZWQgb24gdGhlaXIgUkMtRFYgZXhwcmVzc2lvbiBsYW5kc2NhcGUKCiMjIEZpdCB0aGUgc21vb3RoZWQgZXhwcmVzc2lvbiBsYW5kc2NhcGUgb3ZlciBEViBhbmQgUkMgYXhpcwoKYGBge3IgfQpzb3VyY2UoIi4vZnVuY3Rpb25zL0Z1bmN0aW9uc0xhbmRzY2FwZVNtb290aGluZy5SIikKCmdlbmVzIDwtIGFzLmNoYXJhY3RlcihSQy5BeGlzLmdlbmVzLkZEUi5maWx0ZXJlZCRnZW5lX3Nob3J0X25hbWUpCgpTaWcuZ2VuZS5leHByLmRhdGEgPC0gQVAuZGF0YUBkciRSQy5EVi5heGlzQGNlbGwuZW1iZWRkaW5ncwpTaWcuZ2VuZS5leHByLmRhdGEgPC0gYXMuZGF0YS5mcmFtZShjYmluZChTaWcuZ2VuZS5leHByLmRhdGEsIHQoYXMubWF0cml4KEFQLmRhdGFAZGF0YVtnZW5lcyxdKSkpKQoKU2lnLmdlbmUubGFuZHNjYXBlIDwtIEdlbmUuc21vb3RoLmxhbmRzY2FwZS5nYW0oU2lnLmdlbmUuZXhwci5kYXRhLCBzY2FsZS5leHAgPSBUKQpgYGAKCiMjIFBlcmZvcm0gUENBCgpgYGB7ciBmaWcuZGltPWMoNS4zLCA0KX0KcGNzIDwtIHByY29tcCh0KFNpZy5nZW5lLmxhbmRzY2FwZVssMzpuY29sKFNpZy5nZW5lLmxhbmRzY2FwZSldKSkKCmVpZ3MgPC0gKHBjcyRzZGV2WzE6MTBdKV4yClBlcmNlbnRWQXIgPC0gZGF0YS5mcmFtZShQZXJjZW50VkFyID0gZWlncyoxMDAgLyBzdW0oZWlncyksCiAgICAgICAgICAgICAgICAgICAgICAgICBQQ3MgPSAxOmxlbmd0aChlaWdzKSkKCmdncGxvdChQZXJjZW50VkFyLGFlcyh4PSBhcy5mYWN0b3IoUENzKSx5PVBlcmNlbnRWQXIpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LGxpbmV0eXBlID0gMiwgY29sb3VyPSJyZWQiKQpgYGAKCiMjIENsdXN0ZXIgZ2VuZXMgb24gdGhlIGZpcnN0IDMgUENzCgpgYGB7cn0Kc2V0LnNlZWQoMTIzNCkKCkdlbmVQQ3MgPC0gYXMuZGF0YS5mcmFtZShwY3MkeFssMTozXSkKR2VuZVBDcyRHZW5lIDwtIHJvd25hbWVzKEdlbmVQQ3MpCgojIFBlcmZvcm0gS21lYW5zIGNsdXN0ZXJpbmcKa21lYW5zIDwtIGttZWFucyh4ID0gR2VuZVBDc1ssMTozXSwgY2VudGVycyA9IDUpCkdlbmVQQ3MkQ2x1c3RlciA8LSBmYWN0b3Ioa21lYW5zJGNsdXN0ZXIpCgpnZ3Bsb3QoR2VuZVBDcywgYWVzKFBDMSwgLVBDMikpICsgCiAgICAgIGdlb21fcG9pbnQoYWVzKGNvbG9yPUNsdXN0ZXIpLHNpemU9Mywgc2hhcGU9MTYpICsKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjZDE0YzhkIiwiIzllYzIyZiIsICIjYTk5NjFiIiwgIiNjYzNhMWIiLCAiIzRjYWJkYyIsICIjNWFiNzkzIikpICsKICAgICAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKChhZXMobGFiZWw9aWZlbHNlKEdlbmUgJWluJSBjKCJNcHBlZDIiLCJMaHgyIiwgIkV0djUiLCJQYXg2IiwgIkNlbGY0IiwgIkZnZjE1IiwiTnIyZjEiLCAiTHlwZDYiKSwgYXMuY2hhcmFjdGVyKEdlbmUpLCcnKSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgYm94LnBhZGRpbmcgICA9IDAuMiwgCiAgICAgICAgICAgICAgICAgICAgICAgIHBvaW50LnBhZGRpbmcgPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgIG1heC5vdmVybGFwcyA9IDUwLAogICAgICAgICAgICAgICAgICAgICAgICBzZWdtZW50LmNvbG9yID0gJ2dyZXk1MCcsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIpCmBgYAoKCmBgYHtyfQpSQy5BeGlzLmdlbmVzLkZEUi5maWx0ZXJlZCRQQzEgPC0gR2VuZVBDcyRQQzEKUkMuQXhpcy5nZW5lcy5GRFIuZmlsdGVyZWQkUEMyIDwtIEdlbmVQQ3MkUEMyClJDLkF4aXMuZ2VuZXMuRkRSLmZpbHRlcmVkJENsdXN0ZXIgPC0gR2VuZVBDcyRDbHVzdGVyCmBgYAoKIyMgQXZlcmFnZSBjbHVzdGVyJ3MgbGFuZHNjYXBlCgpgYGB7ciBmaWcuZGltPWMoNiwgNCl9CkdlbmVzLmxhbmRzY2FwZSA8LSBHZW5lLnNtb290aC5sYW5kc2NhcGUuZ2FtKFNpZy5nZW5lLmV4cHIuZGF0YSwgc2NhbGUuZXhwID0gRikKQ2x1c3Rlci5tZWFucyA8LSBHZW5lcy5sYW5kc2NhcGVbLDE6Ml0KCkNsdXN0ZXIubWVhbnMkQ2x1c3QuMSA8LSBzY2FsZShyb3dNZWFucyhHZW5lcy5sYW5kc2NhcGVbLEdlbmVQQ3MkR2VuZVtHZW5lUENzJENsdXN0ZXIgPT0gMV1dKSkKQ2x1c3Rlci5tZWFucyRDbHVzdC4yIDwtIHNjYWxlKHJvd01lYW5zKEdlbmVzLmxhbmRzY2FwZVssR2VuZVBDcyRHZW5lW0dlbmVQQ3MkQ2x1c3RlciA9PSAyXV0pKQpDbHVzdGVyLm1lYW5zJENsdXN0LjMgPC0gc2NhbGUocm93TWVhbnMoR2VuZXMubGFuZHNjYXBlWyxHZW5lUENzJEdlbmVbR2VuZVBDcyRDbHVzdGVyID09IDNdXSkpCkNsdXN0ZXIubWVhbnMkQ2x1c3QuNCA8LSBzY2FsZShyb3dNZWFucyhHZW5lcy5sYW5kc2NhcGVbLEdlbmVQQ3MkR2VuZVtHZW5lUENzJENsdXN0ZXIgPT0gNF1dKSkKQ2x1c3Rlci5tZWFucyRDbHVzdC41IDwtIHNjYWxlKHJvd01lYW5zKEdlbmVzLmxhbmRzY2FwZVssR2VuZVBDcyRHZW5lW0dlbmVQQ3MkQ2x1c3RlciA9PSA1XV0pKQoKdGFibGUoR2VuZVBDcyRDbHVzdGVyKQoKUGxvdC5sYW5kc2NhcGUoZGF0YSA9IENsdXN0ZXIubWVhbnMsCiAgICAgICAgICAgICAgIGdlbmVzID0gcGFzdGUwKCJDbHVzdC4iLCAxOjUpLAogICAgICAgICAgICAgICBjb2wucGFsID0gICJCckJHIiwKICAgICAgICAgICAgICAgbmNvbHMgPSAzKQpgYGAKCiMjIFBsb3Qgc29tZSBzZWxlY3RlZCBnZW5lcyBsYW5kc2NhcGUKCmBgYHtyIGZpZy5kaW09Yyg2LCA0KX0KU2VsZWN0ZWQuZ2VuZSA8LSBjKCJNcHBlZDIiLCJMaHgyIiwgIkV0djUiLAogICAgICAgICAgICAgICAgICAgIlBheDYiLCAiTnIyZjEiLCAiTHlwZDYiKQoKU2VsZWN0ZWQuZ2VuZS5leHByLmRhdGEgPC0gQVAuZGF0YUBkciRSQy5EVi5heGlzQGNlbGwuZW1iZWRkaW5ncwpTZWxlY3RlZC5nZW5lLmV4cHIuZGF0YSA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKFNlbGVjdGVkLmdlbmUuZXhwci5kYXRhLCB0KGFzLm1hdHJpeChBUC5kYXRhQGRhdGFbU2VsZWN0ZWQuZ2VuZSxdKSkpKQoKClNlbGVjdGVkLmdlbmUubGFuZHNjYXBlIDwtIEdlbmUuc21vb3RoLmxhbmRzY2FwZS5nYW0oU2VsZWN0ZWQuZ2VuZS5leHByLmRhdGEsIHNjYWxlLmV4cCA9IFQpCgpQbG90LmxhbmRzY2FwZShkYXRhID0gU2VsZWN0ZWQuZ2VuZS5sYW5kc2NhcGUgLAogICAgICAgICAgICAgICBnZW5lcyA9IFNlbGVjdGVkLmdlbmUsCiAgICAgICAgICAgICAgIGNvbC5wYWwgPSAgIlJkQnUiLAogICAgICAgICAgICAgICBuY29scyA9IDMpCmBgYAoKIyMgSW50ZXJzZWN0IFJDIHZhcmlhYmxlIHdpdGggRFYgdmFyaWFibGUgZ2VuZXMKCmBgYHtyIGZpZy5kaW09YygzLCAzKX0KRFZnZW5lcyA8LSByZWFkLnRhYmxlKCIuL1Byb2dlbml0b3JzL0dlbmUuZHluYW1pcXVlLmNzdiIsIHNlcCA9ICI7IiwgaGVhZGVyID0gVCkKClJDLmdlbmVzIDwtIGFzLmNoYXJhY3RlcihSQy5BeGlzLmdlbmVzLkZEUi5maWx0ZXJlZCRnZW5lX3Nob3J0X25hbWUpCkRWLmdlbmVzIDwtIGFzLmNoYXJhY3RlcihEVmdlbmVzJEdlbmUpCgpnZW5lLmxpc3QgPC0gbGlzdChSQy5nZW5lcyA9IFJDLmdlbmVzLCBEVi5nZW5lcyA9IERWLmdlbmVzKQpnZ3Zlbm46OmdndmVubihnZW5lLmxpc3QpCgpgYGAKCmBgYHtyfQpSQy5BeGlzLmdlbmVzLkZEUi5maWx0ZXJlZCREVl9heGlzX3ZhcmlhYmxlIDwtIFJDLmdlbmVzICVpbiUgRFYuZ2VuZXMKCndyaXRlLnRhYmxlKFJDLkF4aXMuZ2VuZXMuRkRSLmZpbHRlcmVkLCAiLi9Qcm9nZW5pdG9ycy9Sb3N0cm9fQ2F1ZGFsX2dlbmVzLmNzdiIsIHNlcCA9ICI7IiwgcXVvdGUgPSBGKQpgYGAKCiMgU2Vzc2lvbiBJbmZvCgpgYGB7cn0KI2RhdGUKZm9ybWF0KFN5cy50aW1lKCksICIlZCAlQiwgJVksICVILCVNIikKCiNQYWNrYWdlcyB1c2VkCnNlc3Npb25JbmZvKCkKYGBgCg==