• Load libraries
  • load the dataset
  • Extract the apical progenitors
  • Filter gene counts matrix
  • Topic modeling
    • Fit topic model
    • Explore the different topics
    • Cluster Progenitors
    • Rename clusters
    • Transfer identity to the full dataset
  • Differentiating neurons lineages
    • Split Pallial from Cajal-Retzius cells
    • Transfer identities to the full dataset
  • Plot representative marker genes
  • Save data
  • Session Info

Load libraries

library(Seurat)
library(fastTopics)
library(RcppParallel)
library(Matrix)
library(dplyr)
library(RColorBrewer)
library(ggplot2)
library(ggExtra)
library(cowplot)
library(wesanderson)

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

load the dataset

Hem.data <- readRDS("../QC.filtered.cells.RDS")
DimPlot(Hem.data,
        reduction = "spring",
        cols = c(wes_palette("FantasticFox1"),"grey60"),
        pt.size = 0.5) & NoAxes()

Extract the apical progenitors

# Extract apical progenitors 
Progenitors.data <-  subset(Hem.data, idents = c(0,1,3))

DimPlot(Progenitors.data,
        reduction = "spring",
        pt.size = 0.5,
        cols = c(wes_palette("FantasticFox1")[c(1,2,4)]),
        split.by = 'ident') + NoLegend() & NoAxes()

rm(Hem.data) ; gc()
##             used   (Mb) gc trigger   (Mb)   max used   (Mb)
## Ncells   3307939  176.7    6059512  323.7    4834983  258.3
## Vcells 338673567 2583.9 1045076561 7973.4 1016168883 7752.8

Filter gene counts matrix

For this analysis we will keep only genes detected in at least 20 over 12325 cells

progenitors.counts <- GetAssayData(object = Progenitors.data[["RNA"]], slot = "counts")
dim(progenitors.counts)
## [1] 18268 12325
num.cells <- Matrix::rowSums(progenitors.counts > 0)
genes.use <- names(x = num.cells[which(x = num.cells >= 20)])
progenitors.counts <- progenitors.counts[genes.use, ]

dim(progenitors.counts)
## [1] 15314 12325
gc()
##             used   (Mb) gc trigger   (Mb)   max used   (Mb)
## Ncells   3295244  176.0    6059512  323.7    4834983  258.3
## Vcells 414502290 3162.5 1045076561 7973.4 1016168883 7752.8

Topic modeling

Fit topic model

set.seed(1)

fit <- fit_topic_model(t(progenitors.counts),
                       k = 15,
                       numiter.main = 200,
                       numiter.refine = 200,
                       method.main = "em",
                       method.refine = "scd",
                       control.main = list(numiter = 4, nc= 6),
                       control.refine = list(numiter = 4, nc= 6, extrapolate = TRUE),
                       verbose = "progressbar")
## Initializing factors using Topic SCORE algorithm.
## Initializing loadings by running 10 SCD updates.
## Fitting rank-15 Poisson NMF to 12325 x 15314 sparse matrix.
## Running 200 EM updates, without extrapolation (fastTopics 0.5-59).
## Refining model fit.
## Fitting rank-15 Poisson NMF to 12325 x 15314 sparse matrix.
## Running 200 SCD updates, with extrapolation (fastTopics 0.5-59).

Explore the different topics

# Add cells' topics loading to the metadata
Progenitors.data@meta.data <- cbind(Progenitors.data@meta.data, fit$L)
FeaturePlot(object = Progenitors.data,
                    features = paste0("k", 1:15),
                    cols = rev(brewer.pal(10,"Spectral")),
                    reduction = "spring") & NoLegend() & NoAxes()

FeaturePlot(object = Progenitors.data,
                    features = paste0("k", c(15,12,9,8,14,6)),
                    cols = rev(brewer.pal(10,"Spectral")),
                    reduction = "spring",
                    order = T) & NoLegend() & NoAxes()

Cluster Progenitors

set.seed(1)
pca <- prcomp(fit$L[,c(15,12,9,8,14,6)])$x
clusters <- cluster::pam(pca, k = 6)$clustering
Progenitors.data@meta.data$TopicsKmeans <- as.numeric(clusters)

FeaturePlot(object = Progenitors.data,
            features = "TopicsKmeans",
            cols = c(wes_palette("FantasticFox1"),"grey90", "grey40"),
            reduction = "spring") & NoLegend() & NoAxes()

Idents(Progenitors.data) <- Progenitors.data$TopicsKmeans

DimPlot(Progenitors.data,
        reduction = "spring",
        pt.size = 0.5,
        cols =  c(wes_palette("FantasticFox1"),"grey90", "grey40"),
        split.by = 'ident') + NoLegend() & NoAxes()

Rename clusters

ident = c("Dorso-Medial_pallium", "ChP", "Medial_pallium", "Hem", "ChP_progenitors", "Thalamic_eminence")

Progenitors.data$progenitor_type <- sapply(Progenitors.data$TopicsKmeans,
                                           FUN = function(x) {x= ident[x]})

Idents(Progenitors.data) <- Progenitors.data$progenitor_type
DimPlot(Progenitors.data,
        reduction = "spring",
        pt.size = 0.5,
        cols =  c(wes_palette("FantasticFox1"),"grey90"),
        split.by = 'ident') + NoLegend() & NoAxes()

Transfer identity to the full dataset

Hem.data <- readRDS("../QC.filtered.cells.RDS")
Hem.data$Cell_ident <- sapply(Hem.data$Barcodes,
                              FUN = function(x) {
                                if (x %in% Progenitors.data$Barcodes) {
                                  x = Progenitors.data@meta.data[x, "progenitor_type"]
                                } else {
                                  x = paste0("seurat_clusters_", Hem.data@meta.data[x, "seurat_clusters"])
                                  }
                              })
DimPlot(object = Hem.data,
        group.by = "Cell_ident",
        reduction = "spring",
        cols = c("#83c3b8", #"ChP"
                 "#009fda", #"ChP_progenitors"
                 "#68b041", #"Dorso-Medial_pallium"
                 "#e46b6b", #"Hem"
                 "#e3c148", #"Medial_pallium"
                 "#b7d174", #2
                 "grey40", #4
                 "black", #5
                 "#3e69ac" #"Thalamic_eminence"
                 ))

Differentiating neurons lineages

Neurons.data <-  subset(Hem.data, idents = 2)

DimPlot(Neurons.data ,
        reduction = "spring",
        pt.size = 1,
        cols =  c("#b7d174")) + NoAxes()

Split Pallial from Cajal-Retzius cells

p1 <- FeaturePlot(object = Neurons.data ,
            features = c("BP_signature1","LN_signature1"),
            pt.size = 0.5,
            cols = rev(brewer.pal(10,"Spectral")),
            reduction = "spring",
            order = T) & NoAxes()

p2 <- FeaturePlot(object = Neurons.data ,
            features = c("Foxg1", "Trp73"),
            pt.size = 0.5,
            cols = c("grey90", brewer.pal(9,"YlGnBu")),
            reduction = "spring",
            order = T) & NoAxes()

p1 / p2

Separation between the 2 lineage seems straightforward. We use louvain clustering to split the two.

Neurons.data <- RunPCA(Neurons.data, verbose = FALSE)

Neurons.data <- FindNeighbors(Neurons.data,
                              dims = 1:10,
                              k.param = 8)

Neurons.data <- FindClusters(Neurons.data, resolution = 0.05)
## Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
## 
## Number of nodes: 2835
## Number of edges: 56608
## 
## Running Louvain algorithm...
## Maximum modularity in 10 random starts: 0.9640
## Number of communities: 2
## Elapsed time: 0 seconds
DimPlot(Neurons.data,
        reduction = "spring",
        cols = c("#cc391b","#026c9a"),
        pt.size = 0.5) & NoAxes()

Neurons.data$Lineage <- sapply(as.numeric(Neurons.data$SCT_snn_res.0.05),
                               FUN = function(x) {x= c("Cajal-Retzius_neurons","Pallial_neurons")[x]})
DimPlot(object = Neurons.data,
        group.by = "Lineage",
        reduction = "spring",
        cols = c("#cc391b","#026c9a"),
        pt.size = 0.5) & NoAxes()

Transfer identities to the full dataset

Hem.data$Cell_ident <- sapply(Hem.data$Barcodes,
                              FUN = function(x) {
                                if (x %in% Neurons.data$Barcodes) {
                                  x = Neurons.data@meta.data[x, "Lineage"]
                                } else {
                                  x = Hem.data@meta.data[x, "Cell_ident"]
                                  }
                              })

Plot representative marker genes

We excluded Meninges and Immune cell clusters

Idents(Hem.data) <- Hem.data$Cell_ident

Hem.data <-  subset(Hem.data, idents = unique(Hem.data$Cell_ident)[!unique(Hem.data$Cell_ident) %in% c("seurat_clusters_4", "seurat_clusters_5")])
Hem.data <- BuildClusterTree(Hem.data,
                             assay = "SCT",
                             slot = "data",
                             reorder = T,
                             verbose = TRUE)

data.tree <- Tool(object = Hem.data, slot = "BuildClusterTree")
tree.rotated <- ape::rotate(data.tree, node =c(12))

Idents(Hem.data) <- factor(x = Idents(Hem.data),
                           levels = c("ChP","Cajal-Retzius_neurons","Pallial_neurons",
                                      "Dorso-Medial_pallium","Medial_pallium","Hem",
                                      "Thalamic_eminence","ChP_progenitors"),
                           ordered = TRUE)
DimPlot(object = Hem.data,
        group.by = "Cell_ident",
        reduction = "spring",
        cols = c("#ebcb2e", # CR 
                 "#9ec22f", #"ChP" 
                 "#e7823a", #"ChP_progenitors"
                 "#cc3a1b", #"Dorso-Medial_pallium" 
                 "#d14c8d", #"Hem" 
                 "#4cabdc", #"Medial_pallium"
                 "#046c9a", # Pallial
                 "#4990c9" #"Thalamic_eminence"
                 )
        ) + NoAxes()

p1 <- ggdendro::ggdendrogram(ggdendro::dendro_data(as.hclust(tree.rotated)), labels = F, rotate = T) + scale_y_reverse()
Marker.genes <- c("Htr2c", "Cfap126",
                  "Trp73", "Lhx1", "Foxg1","Cbln4", "Tbr1", "Neurod2",
                  "Lmo2", "Sox9", "Lhx2", "Meis2", "Shisa2",
                  "Wif1", "Wnt5a", "Id3",
                  "Rassf4", "Dkk3","Rspo3",
                  "Dlk1", "Meg3",
                  "Mlf1","Sulf1", "Ttr")

data.to.plot <- data.frame(t(as.matrix(Hem.data@assays$SCT[Marker.genes,])))
  
data.to.plot$Cell <- rownames(data.to.plot)
data.to.plot$id <- Idents(Hem.data)
  
#Reshape the dataframe
data.to.plot <- data.to.plot %>% tidyr::gather(key = Marker.genes, value = expression, -c(Cell, id)) 
  
#For each genes in each cluster calculate mean expression and percent cell with norm expression > 0
data.to.plot <- data.to.plot %>%
          group_by(id, Marker.genes) %>% 
              summarize(avg.exp = mean(expm1(x = expression)), pct.exp = length(x = expression[expression > 0.7]) / length(x = expression)) 
  
data.to.plot <- data.to.plot %>% ungroup() %>%
    group_by(Marker.genes) %>% 
    mutate(avg.exp.scale = scale(x = avg.exp)) %>%
    mutate(avg.exp.scale = MinMax(data = avg.exp.scale, max = 2, min = -2)) # add column with scaled expression values from -2 to 2
  
data.to.plot$genes.plot <- factor(x = data.to.plot$Marker.genes, levels = rev(x = Marker.genes)) #Put gene names as factor 
  
data.to.plot$pct.exp[data.to.plot$pct.exp < 0.05] <- NA #Set to Na if less than percent.min of cells express the gene
data.to.plot$pct.exp <- data.to.plot$pct.exp * 100
  
p2 <- ggplot(data = data.to.plot, mapping = aes(x = genes.plot, y = id)) +
        geom_point(mapping = aes(size = pct.exp, color = avg.exp.scale)) + # modify the colors by if want to color by domains or cluster ident
        scale_size_area(max_size= 4) + # Scale the radius of the dot from 0 to 6 
        scale_x_discrete(position = "top") +
        theme(axis.text.x = element_text(angle = 90, vjust = 1), axis.title.y = element_blank()) +
        xlab("") + ylab("") +
        scale_colour_gradientn(colours = brewer.pal(11,"RdPu"))
plot_grid(plotlist = list(p1,p2), ncol=2, align='h', rel_widths = c(0.2, 1.5))

FeaturePlot(object = Hem.data,
            features = c("Tbr1", "Trp73", "Htr2c",
                         "Shisa2", "Wif1", "Rassf4","Dkk3",
                         "Dlk1", "Sulf1", "Ttr"),
            pt.size = 0.2,
            cols = c("grey90", brewer.pal(9,"YlGnBu")),
            reduction = "spring",
            order = T) & NoAxes() & NoLegend()

Save data

saveRDS(Hem.data, "../QC.filtered.clustered.cells.RDS")

Session Info

#date
format(Sys.time(), "%d %B, %Y, %H,%M")
## [1] "23 février, 2022, 12,24"
#Packages used
sessionInfo()
## R version 4.1.2 (2021-11-01)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
## 
## 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] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] wesanderson_0.3.6  cowplot_1.1.1      ggExtra_0.9        ggplot2_3.3.5     
##  [5] RColorBrewer_1.1-2 dplyr_1.0.7        Matrix_1.4-0       RcppParallel_5.1.4
##  [9] fastTopics_0.5-59  SeuratObject_4.0.4 Seurat_4.0.5      
## 
## loaded via a namespace (and not attached):
##   [1] plyr_1.8.6            igraph_1.2.11         lazyeval_0.2.2       
##   [4] splines_4.1.2         listenv_0.8.0         scattermore_0.7      
##   [7] digest_0.6.29         invgamma_1.1          foreach_1.5.1        
##  [10] htmltools_0.5.2       SQUAREM_2021.1        fansi_0.5.0          
##  [13] magrittr_2.0.2        tensor_1.5            cluster_2.1.2        
##  [16] ROCR_1.0-11           recipes_0.1.17        globals_0.14.0       
##  [19] gower_0.2.2           matrixStats_0.61.0    MCMCpack_1.6-0       
##  [22] spatstat.sparse_2.0-0 prettyunits_1.1.1     colorspace_2.0-2     
##  [25] ggrepel_0.9.1         xfun_0.28             crayon_1.4.2         
##  [28] jsonlite_1.7.2        spatstat.data_2.1-0   ape_5.5              
##  [31] survival_3.2-13       zoo_1.8-9             iterators_1.0.13     
##  [34] glue_1.5.1            polyclip_1.10-0       gtable_0.3.0         
##  [37] ipred_0.9-12          MatrixModels_0.5-0    leiden_0.3.9         
##  [40] future.apply_1.8.1    abind_1.4-5           SparseM_1.81         
##  [43] scales_1.1.1          DBI_1.1.1             miniUI_0.1.1.1       
##  [46] Rcpp_1.0.8            progress_1.2.2        viridisLite_0.4.0    
##  [49] xtable_1.8-4          reticulate_1.22       spatstat.core_2.3-1  
##  [52] stats4_4.1.2          lava_1.6.10           prodlim_2019.11.13   
##  [55] truncnorm_1.0-8       htmlwidgets_1.5.4     httr_1.4.2           
##  [58] ellipsis_0.3.2        ica_1.0-2             farver_2.1.0         
##  [61] pkgconfig_2.0.3       nnet_7.3-17           sass_0.4.0           
##  [64] uwot_0.1.10           deldir_1.0-6          utf8_1.2.2           
##  [67] caret_6.0-90          labeling_0.4.2        tidyselect_1.1.1     
##  [70] rlang_0.4.12          reshape2_1.4.4        later_1.3.0          
##  [73] munsell_0.5.0         tools_4.1.2           generics_0.1.1       
##  [76] ggridges_0.5.3        ggdendro_0.1.23       evaluate_0.14        
##  [79] stringr_1.4.0         fastmap_1.1.0         yaml_2.2.1           
##  [82] goftest_1.2-3         mcmc_0.9-7            ModelMetrics_1.2.2.2 
##  [85] knitr_1.36            fitdistrplus_1.1-6    purrr_0.3.4          
##  [88] RANN_2.6.1            pbapply_1.5-0         future_1.23.0        
##  [91] nlme_3.1-153          mime_0.12             quantreg_5.86        
##  [94] compiler_4.1.2        plotly_4.10.0         png_0.1-7            
##  [97] spatstat.utils_2.2-0  tibble_3.1.6          bslib_0.3.1          
## [100] stringi_1.7.6         highr_0.9             lattice_0.20-45      
## [103] vctrs_0.3.8           pillar_1.6.4          lifecycle_1.0.1      
## [106] spatstat.geom_2.3-0   lmtest_0.9-39         jquerylib_0.1.4      
## [109] RcppAnnoy_0.0.19      data.table_1.14.2     irlba_2.3.3          
## [112] conquer_1.2.1         httpuv_1.6.3          patchwork_1.1.1      
## [115] R6_2.5.1              promises_1.2.0.1      KernSmooth_2.23-20   
## [118] gridExtra_2.3         parallelly_1.29.0     codetools_0.2-18     
## [121] MASS_7.3-55           assertthat_0.2.1      withr_2.4.3          
## [124] sctransform_0.3.2     hms_1.1.1             mgcv_1.8-38          
## [127] parallel_4.1.2        quadprog_1.5-8        grid_4.1.2           
## [130] rpart_4.1.16          timeDate_3043.102     tidyr_1.1.4          
## [133] coda_0.19-4           class_7.3-20          rmarkdown_2.11       
## [136] ashr_2.2-47           Rtsne_0.15            mixsqp_0.3-43        
## [139] pROC_1.18.0           shiny_1.7.1           lubridate_1.8.0

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

LS0tCnRpdGxlOiAiUHJvZ2VuaXRvciBkb21haW5zIGFuZCBuZXVyb25hbCBsaW5lYWdlIGNoYXJhY3RlcmlzYXRpb24iCmF1dGhvcjoKICAgLSBNYXR0aGlldSBNb3JlYXVeW0luc3RpdHV0ZSBvZiBQc3ljaGlhdHJ5IGFuZCBOZXVyb3NjaWVuY2Ugb2YgUGFyaXMsIElOU0VSTSBVMTI2NiwgNzUwMTQsIFBhcmlzLCBGcmFuY2UsIG1hdHRoaWV1Lm1vcmVhdUBpbnNlcm0uZnJdIFshW10oaHR0cHM6Ly9vcmNpZC5vcmcvc2l0ZXMvZGVmYXVsdC9maWxlcy9pbWFnZXMvb3JjaWRfMTZ4MTYucG5nKV0oaHR0cHM6Ly9vcmNpZC5vcmcvMDAwMC0wMDAyLTI1OTItMjM3MykKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6IAogICAgY29kZV9kb3dubG9hZDogeWVzCiAgICBkZl9wcmludDogdGliYmxlCiAgICBoaWdobGlnaHQ6IGhhZGRvY2sKICAgIHRoZW1lOiBjb3NtbwogICAgY3NzOiAiLi4vc3R5bGUuY3NzIgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNQogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IHllcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFKQpgYGAKCiMgTG9hZCBsaWJyYXJpZXMKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KGZhc3RUb3BpY3MpCmxpYnJhcnkoUmNwcFBhcmFsbGVsKQpsaWJyYXJ5KE1hdHJpeCkKbGlicmFyeShkcGx5cikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ0V4dHJhKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkod2VzYW5kZXJzb24pCgojU2V0IGdncGxvdCB0aGVtZSBhcyBjbGFzc2ljCnRoZW1lX3NldCh0aGVtZV9jbGFzc2ljKCkpCmBgYAoKIyBsb2FkIHRoZSBkYXRhc2V0CgpgYGB7cn0KSGVtLmRhdGEgPC0gcmVhZFJEUygiLi4vUUMuZmlsdGVyZWQuY2VsbHMuUkRTIikKYGBgCgpgYGB7cn0KRGltUGxvdChIZW0uZGF0YSwKICAgICAgICByZWR1Y3Rpb24gPSAic3ByaW5nIiwKICAgICAgICBjb2xzID0gYyh3ZXNfcGFsZXR0ZSgiRmFudGFzdGljRm94MSIpLCJncmV5NjAiKSwKICAgICAgICBwdC5zaXplID0gMC41KSAmIE5vQXhlcygpCmBgYAoKIyBFeHRyYWN0IHRoZSBhcGljYWwgcHJvZ2VuaXRvcnMKCmBgYHtyfQojIEV4dHJhY3QgYXBpY2FsIHByb2dlbml0b3JzIApQcm9nZW5pdG9ycy5kYXRhIDwtICBzdWJzZXQoSGVtLmRhdGEsIGlkZW50cyA9IGMoMCwxLDMpKQoKRGltUGxvdChQcm9nZW5pdG9ycy5kYXRhLAogICAgICAgIHJlZHVjdGlvbiA9ICJzcHJpbmciLAogICAgICAgIHB0LnNpemUgPSAwLjUsCiAgICAgICAgY29scyA9IGMod2VzX3BhbGV0dGUoIkZhbnRhc3RpY0ZveDEiKVtjKDEsMiw0KV0pLAogICAgICAgIHNwbGl0LmJ5ID0gJ2lkZW50JykgKyBOb0xlZ2VuZCgpICYgTm9BeGVzKCkKCnJtKEhlbS5kYXRhKSA7IGdjKCkKYGBgCgojIEZpbHRlciBnZW5lIGNvdW50cyBtYXRyaXgKCkZvciB0aGlzIGFuYWx5c2lzIHdlIHdpbGwga2VlcCBvbmx5IGdlbmVzIGRldGVjdGVkIGluIGF0IGxlYXN0IDIwIG92ZXIgMTIzMjUgY2VsbHMKCmBgYHtyfQpwcm9nZW5pdG9ycy5jb3VudHMgPC0gR2V0QXNzYXlEYXRhKG9iamVjdCA9IFByb2dlbml0b3JzLmRhdGFbWyJSTkEiXV0sIHNsb3QgPSAiY291bnRzIikKZGltKHByb2dlbml0b3JzLmNvdW50cykKCm51bS5jZWxscyA8LSBNYXRyaXg6OnJvd1N1bXMocHJvZ2VuaXRvcnMuY291bnRzID4gMCkKZ2VuZXMudXNlIDwtIG5hbWVzKHggPSBudW0uY2VsbHNbd2hpY2goeCA9IG51bS5jZWxscyA+PSAyMCldKQpwcm9nZW5pdG9ycy5jb3VudHMgPC0gcHJvZ2VuaXRvcnMuY291bnRzW2dlbmVzLnVzZSwgXQoKZGltKHByb2dlbml0b3JzLmNvdW50cykKYGBgCgpgYGB7cn0KZ2MoKQpgYGAKCiMgVG9waWMgbW9kZWxpbmcKCiMjIEZpdCB0b3BpYyBtb2RlbAoKYGBge3IgZml0X3RvcGljX21vZGVsLCBjYWNoZT1UUlVFLCBjbGFzcy5vdXRwdXQ9InNjcm9sbC0xMDAifQpzZXQuc2VlZCgxKQoKZml0IDwtIGZpdF90b3BpY19tb2RlbCh0KHByb2dlbml0b3JzLmNvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgayA9IDE1LAogICAgICAgICAgICAgICAgICAgICAgIG51bWl0ZXIubWFpbiA9IDIwMCwKICAgICAgICAgICAgICAgICAgICAgICBudW1pdGVyLnJlZmluZSA9IDIwMCwKICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QubWFpbiA9ICJlbSIsCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kLnJlZmluZSA9ICJzY2QiLAogICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2wubWFpbiA9IGxpc3QobnVtaXRlciA9IDQsIG5jPSA2KSwKICAgICAgICAgICAgICAgICAgICAgICBjb250cm9sLnJlZmluZSA9IGxpc3QobnVtaXRlciA9IDQsIG5jPSA2LCBleHRyYXBvbGF0ZSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSAicHJvZ3Jlc3NiYXIiKQpgYGAKCiMjIEV4cGxvcmUgdGhlIGRpZmZlcmVudCB0b3BpY3MKCmBgYHtyfQojIEFkZCBjZWxscycgdG9waWNzIGxvYWRpbmcgdG8gdGhlIG1ldGFkYXRhClByb2dlbml0b3JzLmRhdGFAbWV0YS5kYXRhIDwtIGNiaW5kKFByb2dlbml0b3JzLmRhdGFAbWV0YS5kYXRhLCBmaXQkTCkKYGBgCgpgYGB7ciBmaWcuZGltPWMoNiwgOSl9CkZlYXR1cmVQbG90KG9iamVjdCA9IFByb2dlbml0b3JzLmRhdGEsCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBwYXN0ZTAoImsiLCAxOjE1KSwKICAgICAgICAgICAgICAgICAgICBjb2xzID0gcmV2KGJyZXdlci5wYWwoMTAsIlNwZWN0cmFsIikpLAogICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJzcHJpbmciKSAmIE5vTGVnZW5kKCkgJiBOb0F4ZXMoKQoKYGBgCgpgYGB7ciBmaWcuZGltPWMoNiwgOSl9CkZlYXR1cmVQbG90KG9iamVjdCA9IFByb2dlbml0b3JzLmRhdGEsCiAgICAgICAgICAgICAgICAgICAgZmVhdHVyZXMgPSBwYXN0ZTAoImsiLCBjKDE1LDEyLDksOCwxNCw2KSksCiAgICAgICAgICAgICAgICAgICAgY29scyA9IHJldihicmV3ZXIucGFsKDEwLCJTcGVjdHJhbCIpKSwKICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAic3ByaW5nIiwKICAgICAgICAgICAgICAgICAgICBvcmRlciA9IFQpICYgTm9MZWdlbmQoKSAmIE5vQXhlcygpCgpgYGAKCiMjIENsdXN0ZXIgUHJvZ2VuaXRvcnMKCmBgYHtyIEttZWFucyBjbHVzdGVyaW5nIG9uIHRvcGljcyBQQ3N9CnNldC5zZWVkKDEpCnBjYSA8LSBwcmNvbXAoZml0JExbLGMoMTUsMTIsOSw4LDE0LDYpXSkkeApjbHVzdGVycyA8LSBjbHVzdGVyOjpwYW0ocGNhLCBrID0gNikkY2x1c3RlcmluZwpgYGAKCmBgYHtyfQpQcm9nZW5pdG9ycy5kYXRhQG1ldGEuZGF0YSRUb3BpY3NLbWVhbnMgPC0gYXMubnVtZXJpYyhjbHVzdGVycykKCkZlYXR1cmVQbG90KG9iamVjdCA9IFByb2dlbml0b3JzLmRhdGEsCiAgICAgICAgICAgIGZlYXR1cmVzID0gIlRvcGljc0ttZWFucyIsCiAgICAgICAgICAgIGNvbHMgPSBjKHdlc19wYWxldHRlKCJGYW50YXN0aWNGb3gxIiksImdyZXk5MCIsICJncmV5NDAiKSwKICAgICAgICAgICAgcmVkdWN0aW9uID0gInNwcmluZyIpICYgTm9MZWdlbmQoKSAmIE5vQXhlcygpCmBgYAoKYGBge3J9CklkZW50cyhQcm9nZW5pdG9ycy5kYXRhKSA8LSBQcm9nZW5pdG9ycy5kYXRhJFRvcGljc0ttZWFucwoKRGltUGxvdChQcm9nZW5pdG9ycy5kYXRhLAogICAgICAgIHJlZHVjdGlvbiA9ICJzcHJpbmciLAogICAgICAgIHB0LnNpemUgPSAwLjUsCiAgICAgICAgY29scyA9ICBjKHdlc19wYWxldHRlKCJGYW50YXN0aWNGb3gxIiksImdyZXk5MCIsICJncmV5NDAiKSwKICAgICAgICBzcGxpdC5ieSA9ICdpZGVudCcpICsgTm9MZWdlbmQoKSAmIE5vQXhlcygpCmBgYAoKCiMjIFJlbmFtZSBjbHVzdGVycwoKYGBge3J9CmlkZW50ID0gYygiRG9yc28tTWVkaWFsX3BhbGxpdW0iLCAiQ2hQIiwgIk1lZGlhbF9wYWxsaXVtIiwgIkhlbSIsICJDaFBfcHJvZ2VuaXRvcnMiLCAiVGhhbGFtaWNfZW1pbmVuY2UiKQoKUHJvZ2VuaXRvcnMuZGF0YSRwcm9nZW5pdG9yX3R5cGUgPC0gc2FwcGx5KFByb2dlbml0b3JzLmRhdGEkVG9waWNzS21lYW5zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oeCkge3g9IGlkZW50W3hdfSkKCklkZW50cyhQcm9nZW5pdG9ycy5kYXRhKSA8LSBQcm9nZW5pdG9ycy5kYXRhJHByb2dlbml0b3JfdHlwZQpgYGAKCmBgYHtyfQpEaW1QbG90KFByb2dlbml0b3JzLmRhdGEsCiAgICAgICAgcmVkdWN0aW9uID0gInNwcmluZyIsCiAgICAgICAgcHQuc2l6ZSA9IDAuNSwKICAgICAgICBjb2xzID0gIGMod2VzX3BhbGV0dGUoIkZhbnRhc3RpY0ZveDEiKSwiZ3JleTkwIiksCiAgICAgICAgc3BsaXQuYnkgPSAnaWRlbnQnKSArIE5vTGVnZW5kKCkgJiBOb0F4ZXMoKQpgYGAKCiMjIFRyYW5zZmVyIGlkZW50aXR5IHRvIHRoZSBmdWxsIGRhdGFzZXQKCmBgYHtyfQpIZW0uZGF0YSA8LSByZWFkUkRTKCIuLi9RQy5maWx0ZXJlZC5jZWxscy5SRFMiKQpgYGAKCmBgYHtyIH0KSGVtLmRhdGEkQ2VsbF9pZGVudCA8LSBzYXBwbHkoSGVtLmRhdGEkQmFyY29kZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTiA9IGZ1bmN0aW9uKHgpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoeCAlaW4lIFByb2dlbml0b3JzLmRhdGEkQmFyY29kZXMpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBQcm9nZW5pdG9ycy5kYXRhQG1ldGEuZGF0YVt4LCAicHJvZ2VuaXRvcl90eXBlIl0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IHBhc3RlMCgic2V1cmF0X2NsdXN0ZXJzXyIsIEhlbS5kYXRhQG1ldGEuZGF0YVt4LCAic2V1cmF0X2NsdXN0ZXJzIl0pCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pCmBgYAoKYGBge3J9CkRpbVBsb3Qob2JqZWN0ID0gSGVtLmRhdGEsCiAgICAgICAgZ3JvdXAuYnkgPSAiQ2VsbF9pZGVudCIsCiAgICAgICAgcmVkdWN0aW9uID0gInNwcmluZyIsCiAgICAgICAgY29scyA9IGMoIiM4M2MzYjgiLCAjIkNoUCIKICAgICAgICAgICAgICAgICAiIzAwOWZkYSIsICMiQ2hQX3Byb2dlbml0b3JzIgogICAgICAgICAgICAgICAgICIjNjhiMDQxIiwgIyJEb3Jzby1NZWRpYWxfcGFsbGl1bSIKICAgICAgICAgICAgICAgICAiI2U0NmI2YiIsICMiSGVtIgogICAgICAgICAgICAgICAgICIjZTNjMTQ4IiwgIyJNZWRpYWxfcGFsbGl1bSIKICAgICAgICAgICAgICAgICAiI2I3ZDE3NCIsICMyCiAgICAgICAgICAgICAgICAgImdyZXk0MCIsICM0CiAgICAgICAgICAgICAgICAgImJsYWNrIiwgIzUKICAgICAgICAgICAgICAgICAiIzNlNjlhYyIgIyJUaGFsYW1pY19lbWluZW5jZSIKICAgICAgICAgICAgICAgICApKQpgYGAKCiMgRGlmZmVyZW50aWF0aW5nIG5ldXJvbnMgbGluZWFnZXMKCmBgYHtyfQpOZXVyb25zLmRhdGEgPC0gIHN1YnNldChIZW0uZGF0YSwgaWRlbnRzID0gMikKCkRpbVBsb3QoTmV1cm9ucy5kYXRhICwKICAgICAgICByZWR1Y3Rpb24gPSAic3ByaW5nIiwKICAgICAgICBwdC5zaXplID0gMSwKICAgICAgICBjb2xzID0gIGMoIiNiN2QxNzQiKSkgKyBOb0F4ZXMoKQpgYGAKCiMjIFNwbGl0IFBhbGxpYWwgZnJvbSBDYWphbC1SZXR6aXVzIGNlbGxzCgpgYGB7cn0KcDEgPC0gRmVhdHVyZVBsb3Qob2JqZWN0ID0gTmV1cm9ucy5kYXRhICwKICAgICAgICAgICAgZmVhdHVyZXMgPSBjKCJCUF9zaWduYXR1cmUxIiwiTE5fc2lnbmF0dXJlMSIpLAogICAgICAgICAgICBwdC5zaXplID0gMC41LAogICAgICAgICAgICBjb2xzID0gcmV2KGJyZXdlci5wYWwoMTAsIlNwZWN0cmFsIikpLAogICAgICAgICAgICByZWR1Y3Rpb24gPSAic3ByaW5nIiwKICAgICAgICAgICAgb3JkZXIgPSBUKSAmIE5vQXhlcygpCgpwMiA8LSBGZWF0dXJlUGxvdChvYmplY3QgPSBOZXVyb25zLmRhdGEgLAogICAgICAgICAgICBmZWF0dXJlcyA9IGMoIkZveGcxIiwgIlRycDczIiksCiAgICAgICAgICAgIHB0LnNpemUgPSAwLjUsCiAgICAgICAgICAgIGNvbHMgPSBjKCJncmV5OTAiLCBicmV3ZXIucGFsKDksIllsR25CdSIpKSwKICAgICAgICAgICAgcmVkdWN0aW9uID0gInNwcmluZyIsCiAgICAgICAgICAgIG9yZGVyID0gVCkgJiBOb0F4ZXMoKQoKcDEgLyBwMgpgYGAKClNlcGFyYXRpb24gYmV0d2VlbiB0aGUgMiBsaW5lYWdlIHNlZW1zIHN0cmFpZ2h0Zm9yd2FyZC4gV2UgdXNlIGxvdXZhaW4gY2x1c3RlcmluZyB0byBzcGxpdCB0aGUgdHdvLgoKYGBge3J9Ck5ldXJvbnMuZGF0YSA8LSBSdW5QQ0EoTmV1cm9ucy5kYXRhLCB2ZXJib3NlID0gRkFMU0UpCgpOZXVyb25zLmRhdGEgPC0gRmluZE5laWdoYm9ycyhOZXVyb25zLmRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbXMgPSAxOjEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrLnBhcmFtID0gOCkKCk5ldXJvbnMuZGF0YSA8LSBGaW5kQ2x1c3RlcnMoTmV1cm9ucy5kYXRhLCByZXNvbHV0aW9uID0gMC4wNSkKYGBgCgpgYGB7cn0KRGltUGxvdChOZXVyb25zLmRhdGEsCiAgICAgICAgcmVkdWN0aW9uID0gInNwcmluZyIsCiAgICAgICAgY29scyA9IGMoIiNjYzM5MWIiLCIjMDI2YzlhIiksCiAgICAgICAgcHQuc2l6ZSA9IDAuNSkgJiBOb0F4ZXMoKQpgYGAKCmBgYHtyfQpOZXVyb25zLmRhdGEkTGluZWFnZSA8LSBzYXBwbHkoYXMubnVtZXJpYyhOZXVyb25zLmRhdGEkU0NUX3Nubl9yZXMuMC4wNSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSBmdW5jdGlvbih4KSB7eD0gYygiQ2FqYWwtUmV0eml1c19uZXVyb25zIiwiUGFsbGlhbF9uZXVyb25zIilbeF19KQpgYGAKCmBgYHtyfQpEaW1QbG90KG9iamVjdCA9IE5ldXJvbnMuZGF0YSwKICAgICAgICBncm91cC5ieSA9ICJMaW5lYWdlIiwKICAgICAgICByZWR1Y3Rpb24gPSAic3ByaW5nIiwKICAgICAgICBjb2xzID0gYygiI2NjMzkxYiIsIiMwMjZjOWEiKSwKICAgICAgICBwdC5zaXplID0gMC41KSAmIE5vQXhlcygpCmBgYAoKIyMgVHJhbnNmZXIgaWRlbnRpdGllcyB0byB0aGUgZnVsbCBkYXRhc2V0CgpgYGB7ciBUcmFuc2ZlciBpZGVudGl0aWVzfQpIZW0uZGF0YSRDZWxsX2lkZW50IDwtIHNhcHBseShIZW0uZGF0YSRCYXJjb2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oeCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh4ICVpbiUgTmV1cm9ucy5kYXRhJEJhcmNvZGVzKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gTmV1cm9ucy5kYXRhQG1ldGEuZGF0YVt4LCAiTGluZWFnZSJdCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBIZW0uZGF0YUBtZXRhLmRhdGFbeCwgIkNlbGxfaWRlbnQiXQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KQpgYGAKCiMgUGxvdCByZXByZXNlbnRhdGl2ZSBtYXJrZXIgZ2VuZXMKCldlIGV4Y2x1ZGVkIE1lbmluZ2VzIGFuZCBJbW11bmUgY2VsbCBjbHVzdGVycwoKYGBge3J9CklkZW50cyhIZW0uZGF0YSkgPC0gSGVtLmRhdGEkQ2VsbF9pZGVudAoKSGVtLmRhdGEgPC0gIHN1YnNldChIZW0uZGF0YSwgaWRlbnRzID0gdW5pcXVlKEhlbS5kYXRhJENlbGxfaWRlbnQpWyF1bmlxdWUoSGVtLmRhdGEkQ2VsbF9pZGVudCkgJWluJSBjKCJzZXVyYXRfY2x1c3RlcnNfNCIsICJzZXVyYXRfY2x1c3RlcnNfNSIpXSkKYGBgCgpgYGB7cn0KSGVtLmRhdGEgPC0gQnVpbGRDbHVzdGVyVHJlZShIZW0uZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhc3NheSA9ICJTQ1QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNsb3QgPSAiZGF0YSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVvcmRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IFRSVUUpCgpkYXRhLnRyZWUgPC0gVG9vbChvYmplY3QgPSBIZW0uZGF0YSwgc2xvdCA9ICJCdWlsZENsdXN0ZXJUcmVlIikKdHJlZS5yb3RhdGVkIDwtIGFwZTo6cm90YXRlKGRhdGEudHJlZSwgbm9kZSA9YygxMikpCgpJZGVudHMoSGVtLmRhdGEpIDwtIGZhY3Rvcih4ID0gSWRlbnRzKEhlbS5kYXRhKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQ2hQIiwiQ2FqYWwtUmV0eml1c19uZXVyb25zIiwiUGFsbGlhbF9uZXVyb25zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRG9yc28tTWVkaWFsX3BhbGxpdW0iLCJNZWRpYWxfcGFsbGl1bSIsIkhlbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRoYWxhbWljX2VtaW5lbmNlIiwiQ2hQX3Byb2dlbml0b3JzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyZWQgPSBUUlVFKQpgYGAKCmBgYHtyfQpEaW1QbG90KG9iamVjdCA9IEhlbS5kYXRhLAogICAgICAgIGdyb3VwLmJ5ID0gIkNlbGxfaWRlbnQiLAogICAgICAgIHJlZHVjdGlvbiA9ICJzcHJpbmciLAogICAgICAgIGNvbHMgPSBjKCIjZWJjYjJlIiwgIyBDUiAKICAgICAgICAgICAgICAgICAiIzllYzIyZiIsICMiQ2hQIiAKICAgICAgICAgICAgICAgICAiI2U3ODIzYSIsICMiQ2hQX3Byb2dlbml0b3JzIgogICAgICAgICAgICAgICAgICIjY2MzYTFiIiwgIyJEb3Jzby1NZWRpYWxfcGFsbGl1bSIgCiAgICAgICAgICAgICAgICAgIiNkMTRjOGQiLCAjIkhlbSIgCiAgICAgICAgICAgICAgICAgIiM0Y2FiZGMiLCAjIk1lZGlhbF9wYWxsaXVtIgogICAgICAgICAgICAgICAgICIjMDQ2YzlhIiwgIyBQYWxsaWFsCiAgICAgICAgICAgICAgICAgIiM0OTkwYzkiICMiVGhhbGFtaWNfZW1pbmVuY2UiCiAgICAgICAgICAgICAgICAgKQogICAgICAgICkgKyBOb0F4ZXMoKQpgYGAKCmBgYHtyfQpwMSA8LSBnZ2RlbmRybzo6Z2dkZW5kcm9ncmFtKGdnZGVuZHJvOjpkZW5kcm9fZGF0YShhcy5oY2x1c3QodHJlZS5yb3RhdGVkKSksIGxhYmVscyA9IEYsIHJvdGF0ZSA9IFQpICsgc2NhbGVfeV9yZXZlcnNlKCkKYGBgCgoKYGBge3J9Ck1hcmtlci5nZW5lcyA8LSBjKCJIdHIyYyIsICJDZmFwMTI2IiwKICAgICAgICAgICAgICAgICAgIlRycDczIiwgIkxoeDEiLCAiRm94ZzEiLCJDYmxuNCIsICJUYnIxIiwgIk5ldXJvZDIiLAogICAgICAgICAgICAgICAgICAiTG1vMiIsICJTb3g5IiwgIkxoeDIiLCAiTWVpczIiLCAiU2hpc2EyIiwKICAgICAgICAgICAgICAgICAgIldpZjEiLCAiV250NWEiLCAiSWQzIiwKICAgICAgICAgICAgICAgICAgIlJhc3NmNCIsICJEa2szIiwiUnNwbzMiLAogICAgICAgICAgICAgICAgICAiRGxrMSIsICJNZWczIiwKICAgICAgICAgICAgICAgICAgIk1sZjEiLCJTdWxmMSIsICJUdHIiKQoKZGF0YS50by5wbG90IDwtIGRhdGEuZnJhbWUodChhcy5tYXRyaXgoSGVtLmRhdGFAYXNzYXlzJFNDVFtNYXJrZXIuZ2VuZXMsXSkpKQogIApkYXRhLnRvLnBsb3QkQ2VsbCA8LSByb3duYW1lcyhkYXRhLnRvLnBsb3QpCmRhdGEudG8ucGxvdCRpZCA8LSBJZGVudHMoSGVtLmRhdGEpCiAgCiNSZXNoYXBlIHRoZSBkYXRhZnJhbWUKZGF0YS50by5wbG90IDwtIGRhdGEudG8ucGxvdCAlPiUgdGlkeXI6OmdhdGhlcihrZXkgPSBNYXJrZXIuZ2VuZXMsIHZhbHVlID0gZXhwcmVzc2lvbiwgLWMoQ2VsbCwgaWQpKSAKICAKI0ZvciBlYWNoIGdlbmVzIGluIGVhY2ggY2x1c3RlciBjYWxjdWxhdGUgbWVhbiBleHByZXNzaW9uIGFuZCBwZXJjZW50IGNlbGwgd2l0aCBub3JtIGV4cHJlc3Npb24gPiAwCmRhdGEudG8ucGxvdCA8LSBkYXRhLnRvLnBsb3QgJT4lCgkJICBncm91cF9ieShpZCwgTWFya2VyLmdlbmVzKSAlPiUgCiAgICAJCSAgc3VtbWFyaXplKGF2Zy5leHAgPSBtZWFuKGV4cG0xKHggPSBleHByZXNzaW9uKSksIHBjdC5leHAgPSBsZW5ndGgoeCA9IGV4cHJlc3Npb25bZXhwcmVzc2lvbiA+IDAuN10pIC8gbGVuZ3RoKHggPSBleHByZXNzaW9uKSkgCiAgCmRhdGEudG8ucGxvdCA8LSBkYXRhLnRvLnBsb3QgJT4lIHVuZ3JvdXAoKSAlPiUKICAgIGdyb3VwX2J5KE1hcmtlci5nZW5lcykgJT4lIAogICAgbXV0YXRlKGF2Zy5leHAuc2NhbGUgPSBzY2FsZSh4ID0gYXZnLmV4cCkpICU+JQogICAgbXV0YXRlKGF2Zy5leHAuc2NhbGUgPSBNaW5NYXgoZGF0YSA9IGF2Zy5leHAuc2NhbGUsIG1heCA9IDIsIG1pbiA9IC0yKSkgIyBhZGQgY29sdW1uIHdpdGggc2NhbGVkIGV4cHJlc3Npb24gdmFsdWVzIGZyb20gLTIgdG8gMgogIApkYXRhLnRvLnBsb3QkZ2VuZXMucGxvdCA8LSBmYWN0b3IoeCA9IGRhdGEudG8ucGxvdCRNYXJrZXIuZ2VuZXMsIGxldmVscyA9IHJldih4ID0gTWFya2VyLmdlbmVzKSkgI1B1dCBnZW5lIG5hbWVzIGFzIGZhY3RvciAKICAKZGF0YS50by5wbG90JHBjdC5leHBbZGF0YS50by5wbG90JHBjdC5leHAgPCAwLjA1XSA8LSBOQSAjU2V0IHRvIE5hIGlmIGxlc3MgdGhhbiBwZXJjZW50Lm1pbiBvZiBjZWxscyBleHByZXNzIHRoZSBnZW5lCmRhdGEudG8ucGxvdCRwY3QuZXhwIDwtIGRhdGEudG8ucGxvdCRwY3QuZXhwICogMTAwCiAgCnAyIDwtIGdncGxvdChkYXRhID0gZGF0YS50by5wbG90LCBtYXBwaW5nID0gYWVzKHggPSBnZW5lcy5wbG90LCB5ID0gaWQpKSArCgkgICAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHNpemUgPSBwY3QuZXhwLCBjb2xvciA9IGF2Zy5leHAuc2NhbGUpKSArICMgbW9kaWZ5IHRoZSBjb2xvcnMgYnkgaWYgd2FudCB0byBjb2xvciBieSBkb21haW5zIG9yIGNsdXN0ZXIgaWRlbnQKCSAgICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemU9IDQpICsgIyBTY2FsZSB0aGUgcmFkaXVzIG9mIHRoZSBkb3QgZnJvbSAwIHRvIDYgCgkgICAgc2NhbGVfeF9kaXNjcmV0ZShwb3NpdGlvbiA9ICJ0b3AiKSArCgkgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAxKSwgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpKSArCgkgICAgeGxhYigiIikgKyB5bGFiKCIiKSArCgkgICAgc2NhbGVfY29sb3VyX2dyYWRpZW50bihjb2xvdXJzID0gYnJld2VyLnBhbCgxMSwiUmRQdSIpKQpgYGAKCmBgYHtyfQpwbG90X2dyaWQocGxvdGxpc3QgPSBsaXN0KHAxLHAyKSwgbmNvbD0yLCBhbGlnbj0naCcsIHJlbF93aWR0aHMgPSBjKDAuMiwgMS41KSkKYGBgCgpgYGB7cn0KRmVhdHVyZVBsb3Qob2JqZWN0ID0gSGVtLmRhdGEsCiAgICAgICAgICAgIGZlYXR1cmVzID0gYygiVGJyMSIsICJUcnA3MyIsICJIdHIyYyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiU2hpc2EyIiwgIldpZjEiLCAiUmFzc2Y0IiwiRGtrMyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiRGxrMSIsICJTdWxmMSIsICJUdHIiKSwKICAgICAgICAgICAgcHQuc2l6ZSA9IDAuMiwKICAgICAgICAgICAgY29scyA9IGMoImdyZXk5MCIsIGJyZXdlci5wYWwoOSwiWWxHbkJ1IikpLAogICAgICAgICAgICByZWR1Y3Rpb24gPSAic3ByaW5nIiwKICAgICAgICAgICAgb3JkZXIgPSBUKSAmIE5vQXhlcygpICYgTm9MZWdlbmQoKQpgYGAKCiMgU2F2ZSBkYXRhCgpgYGB7cn0Kc2F2ZVJEUyhIZW0uZGF0YSwgIi4uL1FDLmZpbHRlcmVkLmNsdXN0ZXJlZC5jZWxscy5SRFMiKQpgYGAKCiMgU2Vzc2lvbiBJbmZvCgpgYGB7cn0KI2RhdGUKZm9ybWF0KFN5cy50aW1lKCksICIlZCAlQiwgJVksICVILCVNIikKCiNQYWNrYWdlcyB1c2VkCnNlc3Npb25JbmZvKCkKYGBgCg==