library(tidyverse)
library(tximport)
library(DESeq2)
library(cowplot)
library(ggpubr)
library(ggvenn)
library(ComplexHeatmap)

wd <- "~/data/bulk-rnaseq/mouse/merged_by_sample/output_bulk-rnaseq_align-sort-assemble-count"
dir.inputs <- wd

transcript.cutoff = 700
N_nonzt_samples = 2
cpm.thresh = 0.5
cpm.N_samples = 2

Session info

R version 3.6.2 (2019-12-12)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Debian GNU/Linux 10 (buster)

Matrix products: default
BLAS/LAPACK: /usr/lib/x86_64-linux-gnu/libopenblasp-r0.3.5.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
 [4] LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=C             
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
[10] LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
 [1] grid      parallel  stats4    stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] ComplexHeatmap_2.2.0        ggvenn_0.1.10               ggpubr_0.6.0               
 [4] cowplot_1.1.1               DESeq2_1.26.0               tximport_1.14.2            
 [7] forcats_1.0.0               stringr_1.5.0               dplyr_1.1.3                
[10] purrr_1.0.2                 readr_2.1.4                 tidyr_1.3.0                
[13] tibble_3.2.1                tidyverse_1.3.0             muscat_1.0.1               
[16] scater_1.14.6               ggplot2_3.4.3               SingleCellExperiment_1.8.0 
[19] SummarizedExperiment_1.16.1 DelayedArray_0.12.3         BiocParallel_1.20.1        
[22] matrixStats_1.0.0           Biobase_2.46.0              GenomicRanges_1.38.0       
[25] GenomeInfoDb_1.22.1         IRanges_2.20.2              S4Vectors_0.24.4           
[28] BiocGenerics_0.32.0         Seurat_3.1.0               

loaded via a namespace (and not attached):
  [1] R.methodsS3_1.8.2        acepack_1.4.2            bit64_4.0.5              knitr_1.43              
  [5] irlba_2.3.5.1            multcomp_1.4-25          R.utils_2.12.2           data.table_1.14.8       
  [9] rpart_4.1-15             RCurl_1.98-1.12          doParallel_1.0.17        generics_0.1.3          
 [13] metap_1.8                TH.data_1.1-2            RSQLite_2.3.1            RANN_2.6.1              
 [17] future_1.33.0            bit_4.0.5                tzdb_0.4.0               mutoss_0.1-13           
 [21] xml2_1.3.5               lubridate_1.9.2          viridis_0.5.1            xfun_0.40               
 [25] jquerylib_0.1.4          hms_1.1.3                evaluate_0.21            fansi_1.0.4             
 [29] progress_1.2.2           readxl_1.4.3             caTools_1.18.2           dbplyr_2.3.3            
 [33] igraph_1.5.1             DBI_1.1.3                geneplotter_1.64.0       htmlwidgets_1.6.2       
 [37] backports_1.4.1          annotate_1.64.0          deldir_1.0-9             vctrs_0.6.3             
 [41] ROCR_1.0-11              abind_1.4-5              cachem_1.0.8             withr_2.5.0             
 [45] vroom_1.6.3              checkmate_2.2.0          sctransform_0.3.5        prettyunits_1.1.1       
 [49] mnormt_2.1.1             cluster_2.1.0            ape_5.7-1                lazyeval_0.2.2          
 [53] crayon_1.5.2             genefilter_1.68.0        labeling_0.4.3           edgeR_3.28.1            
 [57] pkgconfig_2.0.3          qqconf_1.1.1             nlme_3.1-142             vipor_0.4.5             
 [61] blme_1.0-5               nnet_7.3-12              rlang_1.1.1              globals_0.16.2          
 [65] lifecycle_1.0.3          sandwich_3.0-2           mathjaxr_1.6-0           modelr_0.1.11           
 [69] rsvd_1.0.3               cellranger_1.1.0         lmtest_0.9-40            Matrix_1.6-1            
 [73] carData_3.0-5            boot_1.3-23              zoo_1.8-12               reprex_2.0.2            
 [77] base64enc_0.1-3          beeswarm_0.4.0           ggridges_0.5.4           GlobalOptions_0.1.2     
 [81] png_0.1-8                viridisLite_0.4.2        rjson_0.2.9              bitops_1.0-7            
 [85] R.oo_1.25.0              KernSmooth_2.23-16       blob_1.2.4               DelayedMatrixStats_1.8.0
 [89] shape_1.4.6              parallelly_1.36.0        jpeg_0.1-10              rstatix_0.7.2           
 [93] ggsignif_0.6.4           scales_1.2.1             memoise_2.0.1            magrittr_2.0.3          
 [97] plyr_1.8.8               ica_1.0-3                gplots_3.1.3             zlibbioc_1.32.0         
[101] compiler_3.6.2           RColorBrewer_1.1-3       plotrix_3.8-2            clue_0.3-64             
[105] lme4_1.1-34              fitdistrplus_1.1-11      cli_3.6.1                XVector_0.26.0          
[109] lmerTest_3.1-3           listenv_0.9.0            pbapply_1.7-2            TMB_1.9.6               
[113] htmlTable_2.4.1          Formula_1.2-5            MASS_7.3-51.4            tidyselect_1.2.0        
[117] stringi_1.7.12           yaml_2.3.7               BiocSingular_1.2.2       locfit_1.5-9.4          
[121] latticeExtra_0.6-30      ggrepel_0.9.3            sass_0.4.7               tools_3.6.2             
[125] timechange_0.2.0         future.apply_1.11.0      circlize_0.4.8           rstudioapi_0.15.0       
[129] foreach_1.5.2            foreign_0.8-72           gridExtra_2.3            farver_2.1.1            
[133] Rtsne_0.16               digest_0.6.33            Rcpp_1.0.11              car_3.1-2               
[137] broom_1.0.5              SDMTools_1.1-221.2       RcppAnnoy_0.0.21         httr_1.4.7              
[141] AnnotationDbi_1.48.0     Rdpack_2.5               colorspace_2.1-0         rvest_1.0.3             
[145] fs_1.6.3                 XML_3.99-0.3             reticulate_1.31          splines_3.6.2           
[149] uwot_0.1.16              sn_2.1.1                 multtest_2.42.0          plotly_4.10.2           
[153] xtable_1.8-4             jsonlite_1.8.7           nloptr_2.0.3             R6_2.5.1                
[157] TFisher_0.2.0            Hmisc_4.4-0              pillar_1.9.0             htmltools_0.5.6         
[161] glue_1.6.2               fastmap_1.1.1            minqa_1.2.5              BiocNeighbors_1.4.2     
[165] codetools_0.2-16         tsne_0.1-3.1             mvtnorm_1.2-3            utf8_1.2.3              
[169] bslib_0.5.1              lattice_0.20-38          numDeriv_2016.8-1.1      pbkrtest_0.4-8.6        
[173] ggbeeswarm_0.7.2         leiden_0.4.3             colorRamps_2.3.1         gtools_3.9.4            
[177] interp_1.1-4             survival_3.1-8           limma_3.42.2             glmmTMB_1.1.7           
[181] rmarkdown_2.24           munsell_0.5.0            GetoptLong_1.0.5         GenomeInfoDbData_1.2.2  
[185] iterators_1.0.14         variancePartition_1.16.1 haven_2.5.3              reshape2_1.4.4          
[189] gtable_0.3.4             rbibutils_2.2.15        

Data processing

dds.list <- list()
fpkm.mat.list <- list()
DESeq.result.list <- list()

timepoints <- c(
  "3h" = "3h",
  "24h" = "24h",
  "72h" = "72h",
  "All" = "3h|24h|72h",
  "All+male" = "3h|24h|72h"
)
timepoint_name = "3h"
for (timepoint_name in names(timepoints)) {
  # Filter the metadata
  colData.subset <- colData %>%
    filter(str_detect(
      condition3_sex,
      ifelse(timepoint_name == "All+male", "Male|Female", "Female")
    ) &
      str_detect(condition5_time, timepoints[[timepoint_name]]))
  
  # Load the data into a DDS object via tximport
  t_data.ctabs <-
    list.files(
      file.path(dir.inputs, "stringtie_rerun"),
      recursive = T,
      pattern = "t_data.ctab",
      full.names = T
    )
  names(t_data.ctabs) <- word(t_data.ctabs, -2, sep = "/")
  
  t_data.ctabs <- t_data.ctabs[rownames(colData.subset)]
  
  tmp <- read_tsv(t_data.ctabs[1])
  tx2gene <- tmp[, c("t_name", "gene_name")]
  txi <-
    tximport(
      t_data.ctabs,
      type = "stringtie",
      tx2gene = tx2gene,
      ignoreTxVersion = F
    )
  
  dds <-
    DESeqDataSetFromTximport(txi,
                             colData = colData.subset,
                             design = ~ condition4_treatment)
  
  # Filtering based on the transcript count row sum
  keep <- rowSums(counts(dds)) >= transcript.cutoff
  dds <- dds[keep,]
  
  # Filtering based on counts > 0 in more than N samples in either of the groups
  mat.Saline <-
    counts(dds)[, rownames(filter(colData.subset, condition4_treatment == "Saline"))]
  mat.Saline.filtered <-
    mat.Saline[rowSums(mat.Saline != 0) >= N_nonzt_samples,]
  
  mat.LPS <-
    counts(dds)[, rownames(filter(colData.subset, condition4_treatment == "LPS"))]
  mat.LPS.filtered <-
    mat.LPS[rowSums(mat.LPS != 0) >= N_nonzt_samples,]
  
  genes.filtered <-
    c(rownames(mat.Saline.filtered), rownames(mat.LPS.filtered)) %>% unique()
  
  dds <- dds[genes.filtered, ]
  
  # Filtering by CPM > threshold
  mat.CPM <- fpm(dds)
  keep <- rowSums(mat.CPM > cpm.thresh) >= cpm.N_samples
  dds <- dds[keep, ]
  
  # DESeq2 analysis
  dds <- DESeq(dds)
  res <- results(dds)
  
  # FPKM matrix
  fpkm.mat <- fpkm(dds)
  
  DE.genes.ordered <- res %>%
    as.data.frame() %>%
    rownames()
  
  fpkm.mat.ordered <- fpkm.mat[DE.genes.ordered,]
  
  dds.list[[timepoint_name]] <- dds
  fpkm.mat.list[[timepoint_name]] <- fpkm.mat.ordered
  DESeq.result.list[[timepoint_name]] <- res %>% as.data.frame()
}

Fig. 1b - FPKM scatterplots

Female samples.

Filtering strategy:

Fig. 1c - PCA

All samples, same filtering strategy.

Fig. 1d - Venn diagram

Filtered transcripts expressed in LPS-treated female mice compared to saline, additionally filtered to show genes with log2(fold change) >= 1 & adjusted p value < 0.05.

Fig. 1e - lineplots

Log2(fold change) of selected genes.

top.genes.list <- list(
  "3h" = c("Csf3", "Serpina3g", "Oasl1", "Gvin1", "Il6", "Socs3", "Gpr84"),
  "24h" = c("Timp1", "C3", "Lrg1", "C1ra", "Serping1", "Serpina3m", "Fbln5"),
  "72h" = c("Cybb", "Cxcl13", "Ifi207", "Rac2", "Cd55"),
  "All" = c("Cp", "H2-D1", "Gfap", "Trim30a", "Zbp1", "H2-Q6", "Gbp6")
)

top.genes.lfc <- list()
for (timepoint in names(top.genes.list)) {
  gene.set <- top.genes.list[[timepoint]]
  
  top.genes.3h <- DESeq.result.list[["3h"]][gene.set,]
  top.genes.24h <- DESeq.result.list[["24h"]][gene.set,]
  top.genes.72h <- DESeq.result.list[["72h"]][gene.set,]
  
  rownames(top.genes.3h) <- gene.set
  rownames(top.genes.24h) <- gene.set
  rownames(top.genes.72h) <- gene.set
  
  top.genes.lfc[[timepoint]] <- rbind(
    rownames_to_column(top.genes.3h, "genename") %>% 
      mutate(timepoint = "Saline", log2FoldChange = 0),
    rownames_to_column(top.genes.3h, "genename") %>% mutate(timepoint = "3h"),
    rownames_to_column(top.genes.24h, "genename") %>% mutate(timepoint = "24h"),
    rownames_to_column(top.genes.72h, "genename") %>% mutate(timepoint = "72h")
  ) %>%
    select(genename, log2FoldChange, timepoint) %>%
    mutate(log2FoldChange = replace_na(log2FoldChange, 0))
}

plot.list <- list()
for(plot_name in names(top.genes.lfc)){
  plot.list[[plot_name]] <- ggline(top.genes.lfc[[plot_name]],
       x = "timepoint",
       y = "log2FoldChange",
       color = "genename", point.size  = 1) +
  theme(legend.position = "right") +
  labs(y = "log2 fold (compared to saline)", x = NULL, color = NULL)
}
ggarrange(plotlist = plot.list, labels = names(plot.list), label.x = 0.11)

Fig. 1f - Heatmap

Filtered transcripts expressed in LPS-treated female mice compared to saline, additionally filtered to show genes with log2(fold change) >= 2 & adjusted p value < 0.05.

# HT matrix
zscore<- function(x){
    z<- (x - mean(x)) / sd(x)
    return(z)
}
fpkm.mat.z <- zscore(fpkm.mat.list[["All+male"]])

significant.genes <- DESeq.result.list[["All+male"]] %>% 
  filter(log2FoldChange >= 2 & padj < 0.05) %>% 
  arrange(padj) %>% 
  rownames()
fpkm.mat.sig.z <- fpkm.mat.z[significant.genes, rownames(colData)]

# HT annotation
## Samples
sample.names <- colData[colnames(fpkm.mat.sig.z),] %>%
  mutate(
    sample_names = paste(condition4_treatment, condition3_sex, condition5_time, sep = "_"),
    sample_names = factor(sample_names, levels = unique(sample_names))
  ) %>%
  pull(sample_names) %>%
  str_replace_all(., "_", " ") %>%
  str_replace(., "\\s(\\d+h)", " (\\1)")
sample.colors <- unique(sample.names) %>% length() %>%
  RColorBrewer::brewer.pal(., "Set2")
names(sample.colors) <- unique(sample.names)

## Treatment
treatment.names <-
  colData[colnames(fpkm.mat.sig.z),]$condition4_treatment
treatment.colors <- c("gray20", "gray70")
names(treatment.colors) <- unique(treatment.names)

treatment.groups <- word(colnames(fpkm.mat.sig.z), 2, sep = "_")
treatment.groups <- str_replace(treatment.groups, "Saline", "gray20")
treatment.groups <- str_replace(treatment.groups, "LPS", "gray70")
names(treatment.groups) <- word(colnames(fpkm.mat.sig.z), 2, sep = "_")

treatment.anno <- HeatmapAnnotation(
    "Group" = treatment.names,
    "Sample" = factor(sample.names, levels = names(sample.colors)),
    col = list("Group" = treatment.colors, "Sample" = sample.colors),
    show_legend = T,
    show_annotation_name = F
  )

highlight.genes <- c("Cybb", "Cxcl13", "Timp1", "Igtp", "Ifitm3", "H2-D1", "Ccl2", "Il6")

# HT
ComplexHeatmap::Heatmap(
  fpkm.mat.sig.z,
  name = "zFPKM",
  col = circlize::colorRamp2(c(-2, 0, 2), c("blue", "white", "red")),
  show_row_names = T,
  show_column_names = T,
  cluster_columns = F,
  cluster_rows = T,
  show_row_dend = F,
  bottom_annotation = treatment.anno
) +
  rowAnnotation(link = anno_mark(
    at = which(rownames(fpkm.mat.sig.z) %in% highlight.genes),
    labels = highlight.genes,
    padding = unit(1, "mm")
  ))

[1] 246
library(zFPKM)
library(dplyr)
library(ComplexHeatmap)
library(stringr)
library(circlize)

fpkm.mat.list.plus.one <- lapply(fpkm.mat.list, function(mat) mat + 1)

# Convert the matrix to a data.frame
fpkm.df <- as.data.frame(fpkm.mat.list.plus.one[["All+male"]])

# Compute z-scores using zFPKM
fpkm.mat.z <- zFPKM(fpkm.df)

# Convert the result back to a matrix
fpkm.mat.z <- as.matrix(fpkm.mat.z)

significant.genes <- DESeq.result.list[["All+male"]] %>% 
  filter(log2FoldChange >= 2 & padj < 0.05) %>% 
  arrange(padj) %>% 
  rownames()
fpkm.mat.sig.z <- fpkm.mat.z[significant.genes, rownames(colData)]

# HT annotation
## Samples
sample.names <- colData[colnames(fpkm.mat.sig.z),] %>%
  mutate(
    sample_names = paste(condition4_treatment, condition3_sex, condition5_time, sep = "_"),
    sample_names = factor(sample_names, levels = unique(sample_names))
  ) %>%
  pull(sample_names) %>%
  str_replace_all(., "_", " ") %>%
  str_replace(., "\\s(\\d+h)", " (\\1)")
sample.colors <- unique(sample.names) %>% length() %>%
  RColorBrewer::brewer.pal(., "Set2")
names(sample.colors) <- unique(sample.names)

## Treatment
treatment.names <-
  colData[colnames(fpkm.mat.sig.z),]$condition4_treatment
treatment.colors <- c("gray20", "gray70")
names(treatment.colors) <- unique(treatment.names)

treatment.groups <- word(colnames(fpkm.mat.sig.z), 2, sep = "_")
treatment.groups <- str_replace(treatment.groups, "Saline", "gray20")
treatment.groups <- str_replace(treatment.groups, "LPS", "gray70")
names(treatment.groups) <- word(colnames(fpkm.mat.sig.z), 2, sep = "_")

treatment.anno <- HeatmapAnnotation(
    "Group" = treatment.names,
    "Sample" = factor(sample.names, levels = names(sample.colors)),
    col = list("Group" = treatment.colors, "Sample" = sample.colors),
    show_legend = T,
    show_annotation_name = F
  )

highlight.genes <- c("Cybb", "Cxcl13", "Timp1", "Igtp", "Ifitm3", "H2-D1", "Ccl2", "Il6")

# HT
ComplexHeatmap::Heatmap(
  fpkm.mat.sig.z,
  name = "zFPKM",
  col = circlize::colorRamp2(c(-2, 0, 2), c("blue", "white", "red")),
  show_row_names = T,
  show_column_names = T,
  cluster_columns = F,
  cluster_rows = T,
  show_row_dend = F,
  bottom_annotation = treatment.anno
) +
  rowAnnotation(link = anno_mark(
    at = which(rownames(fpkm.mat.sig.z) %in% highlight.genes),
    labels = highlight.genes,
    padding = unit(1, "mm")
  ))

[1] 246
LS0tCnRpdGxlOiAiTmV1cm9pbmZsYW1tYXRvcnkgYXN0cm9jeXRlIHN1YnR5cGVzIGluIHRoZSBtb3VzZSBicmFpbiAoSGFzZWwgZXQgYWwuLCAyMDIxKSBGaWd1cmUgMSByZXBsaWNhdGlvbiIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiBUUlVFCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGhpZ2hsaWdodDoga2F0ZQotLS0KCmBgYHtyIHNldHVwLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHR4aW1wb3J0KQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShnZ3Zlbm4pCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCgp3ZCA8LSAifi9kYXRhL2J1bGstcm5hc2VxL21vdXNlL21lcmdlZF9ieV9zYW1wbGUvb3V0cHV0X2J1bGstcm5hc2VxX2FsaWduLXNvcnQtYXNzZW1ibGUtY291bnQiCmRpci5pbnB1dHMgPC0gd2QKCnRyYW5zY3JpcHQuY3V0b2ZmID0gNzAwCk5fbm9uenRfc2FtcGxlcyA9IDIKY3BtLnRocmVzaCA9IDAuNQpjcG0uTl9zYW1wbGVzID0gMgpgYGAKCiMgU2Vzc2lvbiBpbmZvCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCiMgRGF0YSBwcm9jZXNzaW5nCgpgYGB7YmFzaCBldmFsID0gRkFMU0V9CiMgaGlzYXQyLXNhbXRvb2xzLXN0cmluZ3RpZSBwaXBlbGluZSBleGFtcGxlLCBub3QgcnVuIGhlcmUKCiMgU3RlcCAxOiBBbGlnbm1lbnQKaGlzYXQyIC1wIDM2IC14ICIkUkVGRVJFTkNFX0dFTk9NRSIgLTEgIiRyMV9tZXJnZWQiIC0yICIkcjJfbWVyZ2VkIiAtUyAiJE9VVFBVVF9ESVIvJHNhbV9maWxlIgoKIyBTdGVwIDI6IENvbnZlcnRpbmcgYW5kIHNvcnRpbmcKc2FtdG9vbHMgdmlldyAtYlMgIiRPVVRQVVRfRElSLyRzYW1fZmlsZSIgPiAiJE9VVFBVVF9ESVIvYmFtcy8kYmFtX2ZpbGUiCnNhbXRvb2xzIHNvcnQgLS10aHJlYWRzIDM2ICIkT1VUUFVUX0RJUi9iYW1zLyRiYW1fZmlsZSIgLW8gIiRPVVRQVVRfRElSL2JhbXMvJHNvcnRlZF9iYW1fZmlsZSIKCiMgU3RlcCAzOiBBc3NlbWJsZSBhbmQgcXVhbnRpZnkgd2l0aCBzdHJpbmd0aWUKc3RyaW5ndGllIC1wIDQgLWVCICIkT1VUUFVUX0RJUi9iYW1zLyRzb3J0ZWRfYmFtX2ZpbGUiIC1HICIkQU5OT1RBVElPTl9HVEYiIC1vICIkT1VUUFVUX0RJUi8kc2FtcGxlLyR0cmFuc2NyaXB0c19ndGYiIC1BICIkT1VUUFVUX0RJUi8kc2FtcGxlLyRnZW5lX2FidW5kYW5jZXNfdHN2IgpgYGAKCmBgYHtyfQpjb2xEYXRhIDwtIHJlYWQuY3N2KGZpbGUucGF0aChkaXIuaW5wdXRzLCAicGhlbm9fZGF0YV92MS5jc3YiKSwKICAgICAgICAgICAgICAgICAgICBzZXAgPSAiLCIsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSkgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbjNfc2V4ID0gZmFjdG9yKGNvbmRpdGlvbjNfc2V4LCBsZXZlbHMgPSBjKCJGZW1hbGUiLCAiTWFsZSIpKSkgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbjRfdHJlYXRtZW50ID0gZmFjdG9yKGNvbmRpdGlvbjRfdHJlYXRtZW50LCBsZXZlbHMgPSBjKCJTYWxpbmUiLCAiTFBTIikpKSAlPiUKICBtdXRhdGUoY29uZGl0aW9uNV90aW1lID0gZmFjdG9yKGNvbmRpdGlvbjVfdGltZSwgbGV2ZWxzID0gYygiM2giLCAiMjRoIiwgIjcyaCIpKSkgJT4lCiAgYXJyYW5nZShjb25kaXRpb240X3RyZWF0bWVudCwgY29uZGl0aW9uNV90aW1lLCBjb25kaXRpb24zX3NleCkgJT4lCiAgbXV0YXRlKGNvbmRpdGlvbjFfc2V4LnRyZWF0bWVudC50aW1lID0gZmFjdG9yKAogICAgY29uZGl0aW9uMV9zZXgudHJlYXRtZW50LnRpbWUsCiAgICBsZXZlbHMgPSB1bmlxdWUoY29uZGl0aW9uMV9zZXgudHJlYXRtZW50LnRpbWUpCiAgKSkKY29sRGF0YQpgYGAKCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmRkcy5saXN0IDwtIGxpc3QoKQpmcGttLm1hdC5saXN0IDwtIGxpc3QoKQpERVNlcS5yZXN1bHQubGlzdCA8LSBsaXN0KCkKCnRpbWVwb2ludHMgPC0gYygKICAiM2giID0gIjNoIiwKICAiMjRoIiA9ICIyNGgiLAogICI3MmgiID0gIjcyaCIsCiAgIkFsbCIgPSAiM2h8MjRofDcyaCIsCiAgIkFsbCttYWxlIiA9ICIzaHwyNGh8NzJoIgopCnRpbWVwb2ludF9uYW1lID0gIjNoIgpmb3IgKHRpbWVwb2ludF9uYW1lIGluIG5hbWVzKHRpbWVwb2ludHMpKSB7CiAgIyBGaWx0ZXIgdGhlIG1ldGFkYXRhCiAgY29sRGF0YS5zdWJzZXQgPC0gY29sRGF0YSAlPiUKICAgIGZpbHRlcihzdHJfZGV0ZWN0KAogICAgICBjb25kaXRpb24zX3NleCwKICAgICAgaWZlbHNlKHRpbWVwb2ludF9uYW1lID09ICJBbGwrbWFsZSIsICJNYWxlfEZlbWFsZSIsICJGZW1hbGUiKQogICAgKSAmCiAgICAgIHN0cl9kZXRlY3QoY29uZGl0aW9uNV90aW1lLCB0aW1lcG9pbnRzW1t0aW1lcG9pbnRfbmFtZV1dKSkKICAKICAjIExvYWQgdGhlIGRhdGEgaW50byBhIEREUyBvYmplY3QgdmlhIHR4aW1wb3J0CiAgdF9kYXRhLmN0YWJzIDwtCiAgICBsaXN0LmZpbGVzKAogICAgICBmaWxlLnBhdGgoZGlyLmlucHV0cywgInN0cmluZ3RpZV9yZXJ1biIpLAogICAgICByZWN1cnNpdmUgPSBULAogICAgICBwYXR0ZXJuID0gInRfZGF0YS5jdGFiIiwKICAgICAgZnVsbC5uYW1lcyA9IFQKICAgICkKICBuYW1lcyh0X2RhdGEuY3RhYnMpIDwtIHdvcmQodF9kYXRhLmN0YWJzLCAtMiwgc2VwID0gIi8iKQogIAogIHRfZGF0YS5jdGFicyA8LSB0X2RhdGEuY3RhYnNbcm93bmFtZXMoY29sRGF0YS5zdWJzZXQpXQogIAogIHRtcCA8LSByZWFkX3Rzdih0X2RhdGEuY3RhYnNbMV0pCiAgdHgyZ2VuZSA8LSB0bXBbLCBjKCJ0X25hbWUiLCAiZ2VuZV9uYW1lIildCiAgdHhpIDwtCiAgICB0eGltcG9ydCgKICAgICAgdF9kYXRhLmN0YWJzLAogICAgICB0eXBlID0gInN0cmluZ3RpZSIsCiAgICAgIHR4MmdlbmUgPSB0eDJnZW5lLAogICAgICBpZ25vcmVUeFZlcnNpb24gPSBGCiAgICApCiAgCiAgZGRzIDwtCiAgICBERVNlcURhdGFTZXRGcm9tVHhpbXBvcnQodHhpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbERhdGEgPSBjb2xEYXRhLnN1YnNldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+IGNvbmRpdGlvbjRfdHJlYXRtZW50KQogIAogICMgRmlsdGVyaW5nIGJhc2VkIG9uIHRoZSB0cmFuc2NyaXB0IGNvdW50IHJvdyBzdW0KICBrZWVwIDwtIHJvd1N1bXMoY291bnRzKGRkcykpID49IHRyYW5zY3JpcHQuY3V0b2ZmCiAgZGRzIDwtIGRkc1trZWVwLF0KICAKICAjIEZpbHRlcmluZyBiYXNlZCBvbiBjb3VudHMgPiAwIGluIG1vcmUgdGhhbiBOIHNhbXBsZXMgaW4gZWl0aGVyIG9mIHRoZSBncm91cHMKICBtYXQuU2FsaW5lIDwtCiAgICBjb3VudHMoZGRzKVssIHJvd25hbWVzKGZpbHRlcihjb2xEYXRhLnN1YnNldCwgY29uZGl0aW9uNF90cmVhdG1lbnQgPT0gIlNhbGluZSIpKV0KICBtYXQuU2FsaW5lLmZpbHRlcmVkIDwtCiAgICBtYXQuU2FsaW5lW3Jvd1N1bXMobWF0LlNhbGluZSAhPSAwKSA+PSBOX25vbnp0X3NhbXBsZXMsXQogIAogIG1hdC5MUFMgPC0KICAgIGNvdW50cyhkZHMpWywgcm93bmFtZXMoZmlsdGVyKGNvbERhdGEuc3Vic2V0LCBjb25kaXRpb240X3RyZWF0bWVudCA9PSAiTFBTIikpXQogIG1hdC5MUFMuZmlsdGVyZWQgPC0KICAgIG1hdC5MUFNbcm93U3VtcyhtYXQuTFBTICE9IDApID49IE5fbm9uenRfc2FtcGxlcyxdCiAgCiAgZ2VuZXMuZmlsdGVyZWQgPC0KICAgIGMocm93bmFtZXMobWF0LlNhbGluZS5maWx0ZXJlZCksIHJvd25hbWVzKG1hdC5MUFMuZmlsdGVyZWQpKSAlPiUgdW5pcXVlKCkKICAKICBkZHMgPC0gZGRzW2dlbmVzLmZpbHRlcmVkLCBdCiAgCiAgIyBGaWx0ZXJpbmcgYnkgQ1BNID4gdGhyZXNob2xkCiAgbWF0LkNQTSA8LSBmcG0oZGRzKQogIGtlZXAgPC0gcm93U3VtcyhtYXQuQ1BNID4gY3BtLnRocmVzaCkgPj0gY3BtLk5fc2FtcGxlcwogIGRkcyA8LSBkZHNba2VlcCwgXQogIAogICMgREVTZXEyIGFuYWx5c2lzCiAgZGRzIDwtIERFU2VxKGRkcykKICByZXMgPC0gcmVzdWx0cyhkZHMpCiAgCiAgIyBGUEtNIG1hdHJpeAogIGZwa20ubWF0IDwtIGZwa20oZGRzKQogIAogIERFLmdlbmVzLm9yZGVyZWQgPC0gcmVzICU+JQogICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgcm93bmFtZXMoKQogIAogIGZwa20ubWF0Lm9yZGVyZWQgPC0gZnBrbS5tYXRbREUuZ2VuZXMub3JkZXJlZCxdCiAgCiAgZGRzLmxpc3RbW3RpbWVwb2ludF9uYW1lXV0gPC0gZGRzCiAgZnBrbS5tYXQubGlzdFtbdGltZXBvaW50X25hbWVdXSA8LSBmcGttLm1hdC5vcmRlcmVkCiAgREVTZXEucmVzdWx0Lmxpc3RbW3RpbWVwb2ludF9uYW1lXV0gPC0gcmVzICU+JSBhcy5kYXRhLmZyYW1lKCkKfQpgYGAKCiMgRmlnLiAxYiAtIEZQS00gc2NhdHRlcnBsb3RzCgpGZW1hbGUgc2FtcGxlcy4gCgpGaWx0ZXJpbmcgc3RyYXRlZ3k6CgogICogR2VuZXMgd2l0aCBtb3JlIHRoYW4gKipgciB0cmFuc2NyaXB0LmN1dG9mZmAgdHJhbnNjcmlwdHMqKiBzdW1tZWQgYWNyb3NzIGFsbCBzYW1wbGVzIAogICogRXhwcmVzc2VkIChjb3VudCA+IDApIGluIGF0IGxlYXN0ICoqYHIgTl9ub256dF9zYW1wbGVzYCBzYW1wbGVzKiogaW4gZWl0aGVyIExQUyBvciBTYWxpbmUgZ3JvdXBzCiAgKiBDb3VudHMgcGVyIG1pbGxpb24gKGNwbSkgKiptb3JlIHRoYW4gYHIgY3BtLnRocmVzaGAqKiBpbiBtb3JlIHRoYW4gKipgciBjcG0uTl9zYW1wbGVzYCBzYW1wbGVzKiogZm9yIGEgZ2l2ZW4gZ2VuZQoKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgUGxvdHRpbmcKbG9nMi5heGlzLmJyZWFrcy5mcGttID0gYygyIF4gLTUsIDIgXiAwLCAyIF4gNSwgMiBeIDEwLCAyIF4gMTUpCmxvZzIuYXhpcy5sYWJlbHMuZnBrbSA9IGMoIjJeLTUiLCAiMl4wIiwgIjJeNSIsICIyXjEwIiwgIjJeMTUiKQoKcGxvdC5GUEtNLmxpc3QgPC0gbGlzdCgpCmZvciAodGltZXBvaW50IGluIG5hbWVzKGZwa20ubWF0Lmxpc3QpWzE6M10pIHsKICBmcGttLm1hdC5maWx0ZXJlZCA8LSBmcGttLm1hdC5saXN0W1t0aW1lcG9pbnRdXQogIHJlcyA8LSBERVNlcS5yZXN1bHQubGlzdFtbdGltZXBvaW50XV0KICAKICBwbG90LkZQS00ubGlzdFtbdGltZXBvaW50XV0gPC0gZGF0YS5mcmFtZSgKICAgIEZQS00ubWVhbi5TYWxpbmUgPSBmcGttLm1hdC5maWx0ZXJlZFssIHN0cl93aGljaChjb2xuYW1lcyhmcGttLm1hdC5maWx0ZXJlZCksICJTYWxpbmUiKV0gJT4lCiAgICAgIHJvd01lYW5zKCksCiAgICBGUEtNLm1lYW4uTFBTID0gZnBrbS5tYXQuZmlsdGVyZWRbLCBzdHJfd2hpY2goY29sbmFtZXMoZnBrbS5tYXQuZmlsdGVyZWQpLCAiTFBTIildICU+JQogICAgICByb3dNZWFucygpCiAgKSAlPiUKICAgIG11dGF0ZShoaWdobGlnaHQgPSBjYXNlX3doZW4ocmVzJHBhZGogPiAwLjA1IHwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShyZXMkcGFkaikgfiBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IFRSVUUpKSAlPiUKICAgIGZpbHRlcihGUEtNLm1lYW4uU2FsaW5lICE9IDAgJiBGUEtNLm1lYW4uTFBTICE9IDApICU+JQogICAgZ2dwbG90KGFlcyh4ID0gRlBLTS5tZWFuLlNhbGluZSwgeSA9IEZQS00ubWVhbi5MUFMpKSArCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGhpZ2hsaWdodCksIHNpemUgPSAxKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoCiAgICAgIHRyYW5zID0gImxvZzIiLAogICAgICBsaW1pdHMgPSBjKDIgXiAtNSwgMiBeIDE1KSwKICAgICAgYnJlYWtzID0gbG9nMi5heGlzLmJyZWFrcy5mcGttLAogICAgICBsYWJlbHMgPSBsb2cyLmF4aXMubGFiZWxzLmZwa20KICAgICkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKAogICAgICB0cmFucyA9ICJsb2cyIiwKICAgICAgbGltaXRzID0gYygyIF4gLTUsIDIgXiAxNSksCiAgICAgIGJyZWFrcyA9IGxvZzIuYXhpcy5icmVha3MuZnBrbSwKICAgICAgbGFiZWxzID0gbG9nMi5heGlzLmxhYmVscy5mcGttCiAgICApICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsICIjRkY0MEZDIikpICsKICAgIGxhYnMoY29sb3IgPSAicC5hZGogPCAwLjA1IikgKwogICAgbGFicyh4ID0gIlNhbGluZSAoRlBLTSkiLCB5ID0gIkxQUyAoRlBLTSkiLAogICAgICAgICB0aXRsZSA9IHRpbWVwb2ludCkgKwogICAgY293cGxvdDo6dGhlbWVfY293cGxvdCgpCn0KCmdncHVicjo6Z2dhcnJhbmdlKAogIHBsb3RsaXN0ID0gcGxvdC5GUEtNLmxpc3QsCiAgY29tbW9uLmxlZ2VuZCA9IFQsCiAgbnJvdyA9IDEsCiAgbGVnZW5kID0gImJvdHRvbSIKKQpgYGAKCiMgRmlnLiAxYyAtIFBDQQoKQWxsIHNhbXBsZXMsIHNhbWUgZmlsdGVyaW5nIHN0cmF0ZWd5LgoKYGBge3IsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9ClBDQS5kYXRhIDwtCiAgcGxvdFBDQSh2c3QoZGRzLmxpc3RbWzVdXSksIGludGdyb3VwID0gImNvbmRpdGlvbjFfc2V4LnRyZWF0bWVudC50aW1lIiwgcmV0dXJuRGF0YSA9IFQpClBDQS5kYXRhICU+JQogIG11dGF0ZSgKICAgIGdyb3VwX2xhYmVscyA9IHN0cl9yZXBsYWNlX2FsbChncm91cCwgIl8iLCAiICIpLAogICAgZ3JvdXBfbGFiZWxzID0gc3RyX3JlcGxhY2UoZ3JvdXBfbGFiZWxzLCAiXFxzKFxcZCtoKSIsICIgKFxcMSkiKSwKICAgIGdyb3VwX2xhYmVscyA9IGZhY3Rvcihncm91cF9sYWJlbHMsIGxldmVscyA9IHVuaXF1ZShncm91cF9sYWJlbHMpKQogICkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gUEMxLCB5ID0gLVBDMikpICsKICBnZW9tX3BvaW50KChhZXMoY29sb3IgPSBncm91cF9sYWJlbHMpKSwgc2l6ZSA9IDIpICsKICBsYWJzKHkgPSAiUEMyIiwgY29sb3IgPSBOVUxMKQpgYGAKCiMgRmlnLiAxZCAtIFZlbm4gZGlhZ3JhbQoKRmlsdGVyZWQgdHJhbnNjcmlwdHMgZXhwcmVzc2VkIGluIExQUy10cmVhdGVkIGZlbWFsZSBtaWNlIGNvbXBhcmVkIHRvIHNhbGluZSwgYWRkaXRpb25hbGx5IGZpbHRlcmVkIHRvIHNob3cgZ2VuZXMgd2l0aCBsb2cyKGZvbGQgY2hhbmdlKSA+PSAxICYgYWRqdXN0ZWQgcCB2YWx1ZSA8IDAuMDUuCgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KREVTZXEucmVzdWx0Lmxpc3QuZmlsdGVyZWQgPC0gbGlzdCgpCmZvcih0aW1lcG9pbnQgaW4gbmFtZXMoREVTZXEucmVzdWx0Lmxpc3QpWzE6M10pewogIERFU2VxLnJlc3VsdC5saXN0LmZpbHRlcmVkW1t0aW1lcG9pbnRdXSA8LSBERVNlcS5yZXN1bHQubGlzdFtbdGltZXBvaW50XV0gJT4lIAogICAgZmlsdGVyKGxvZzJGb2xkQ2hhbmdlID49IDEgJiBwYWRqIDwgMC4wNSkKfQoKZ2d2ZW5uOjpnZ3Zlbm4oCiAgZGF0YSA9IGxhcHBseShERVNlcS5yZXN1bHQubGlzdC5maWx0ZXJlZCwgcm93bmFtZXMpLAogIGZpbGxfY29sb3IgPSBjKCJyZWQiLCAiYmx1ZSIsICJncmVlbiIpLAogIHNob3dfcGVyY2VudGFnZSA9IEYsCiAgdGV4dF9zaXplID0gNQopCmBgYAoKIyBGaWcuIDFlIC0gbGluZXBsb3RzCgpMb2cyKGZvbGQgY2hhbmdlKSBvZiBzZWxlY3RlZCBnZW5lcy4KCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Nn0KdG9wLmdlbmVzLmxpc3QgPC0gbGlzdCgKICAiM2giID0gYygiQ3NmMyIsICJTZXJwaW5hM2ciLCAiT2FzbDEiLCAiR3ZpbjEiLCAiSWw2IiwgIlNvY3MzIiwgIkdwcjg0IiksCiAgIjI0aCIgPSBjKCJUaW1wMSIsICJDMyIsICJMcmcxIiwgIkMxcmEiLCAiU2VycGluZzEiLCAiU2VycGluYTNtIiwgIkZibG41IiksCiAgIjcyaCIgPSBjKCJDeWJiIiwgIkN4Y2wxMyIsICJJZmkyMDciLCAiUmFjMiIsICJDZDU1IiksCiAgIkFsbCIgPSBjKCJDcCIsICJIMi1EMSIsICJHZmFwIiwgIlRyaW0zMGEiLCAiWmJwMSIsICJIMi1RNiIsICJHYnA2IikKKQoKdG9wLmdlbmVzLmxmYyA8LSBsaXN0KCkKZm9yICh0aW1lcG9pbnQgaW4gbmFtZXModG9wLmdlbmVzLmxpc3QpKSB7CiAgZ2VuZS5zZXQgPC0gdG9wLmdlbmVzLmxpc3RbW3RpbWVwb2ludF1dCiAgCiAgdG9wLmdlbmVzLjNoIDwtIERFU2VxLnJlc3VsdC5saXN0W1siM2giXV1bZ2VuZS5zZXQsXQogIHRvcC5nZW5lcy4yNGggPC0gREVTZXEucmVzdWx0Lmxpc3RbWyIyNGgiXV1bZ2VuZS5zZXQsXQogIHRvcC5nZW5lcy43MmggPC0gREVTZXEucmVzdWx0Lmxpc3RbWyI3MmgiXV1bZ2VuZS5zZXQsXQogIAogIHJvd25hbWVzKHRvcC5nZW5lcy4zaCkgPC0gZ2VuZS5zZXQKICByb3duYW1lcyh0b3AuZ2VuZXMuMjRoKSA8LSBnZW5lLnNldAogIHJvd25hbWVzKHRvcC5nZW5lcy43MmgpIDwtIGdlbmUuc2V0CiAgCiAgdG9wLmdlbmVzLmxmY1tbdGltZXBvaW50XV0gPC0gcmJpbmQoCiAgICByb3duYW1lc190b19jb2x1bW4odG9wLmdlbmVzLjNoLCAiZ2VuZW5hbWUiKSAlPiUgCiAgICAgIG11dGF0ZSh0aW1lcG9pbnQgPSAiU2FsaW5lIiwgbG9nMkZvbGRDaGFuZ2UgPSAwKSwKICAgIHJvd25hbWVzX3RvX2NvbHVtbih0b3AuZ2VuZXMuM2gsICJnZW5lbmFtZSIpICU+JSBtdXRhdGUodGltZXBvaW50ID0gIjNoIiksCiAgICByb3duYW1lc190b19jb2x1bW4odG9wLmdlbmVzLjI0aCwgImdlbmVuYW1lIikgJT4lIG11dGF0ZSh0aW1lcG9pbnQgPSAiMjRoIiksCiAgICByb3duYW1lc190b19jb2x1bW4odG9wLmdlbmVzLjcyaCwgImdlbmVuYW1lIikgJT4lIG11dGF0ZSh0aW1lcG9pbnQgPSAiNzJoIikKICApICU+JQogICAgc2VsZWN0KGdlbmVuYW1lLCBsb2cyRm9sZENoYW5nZSwgdGltZXBvaW50KSAlPiUKICAgIG11dGF0ZShsb2cyRm9sZENoYW5nZSA9IHJlcGxhY2VfbmEobG9nMkZvbGRDaGFuZ2UsIDApKQp9CgpwbG90Lmxpc3QgPC0gbGlzdCgpCmZvcihwbG90X25hbWUgaW4gbmFtZXModG9wLmdlbmVzLmxmYykpewogIHBsb3QubGlzdFtbcGxvdF9uYW1lXV0gPC0gZ2dsaW5lKHRvcC5nZW5lcy5sZmNbW3Bsb3RfbmFtZV1dLAogICAgICAgeCA9ICJ0aW1lcG9pbnQiLAogICAgICAgeSA9ICJsb2cyRm9sZENoYW5nZSIsCiAgICAgICBjb2xvciA9ICJnZW5lbmFtZSIsIHBvaW50LnNpemUgID0gMSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsKICBsYWJzKHkgPSAibG9nMiBmb2xkIChjb21wYXJlZCB0byBzYWxpbmUpIiwgeCA9IE5VTEwsIGNvbG9yID0gTlVMTCkKfQpnZ2FycmFuZ2UocGxvdGxpc3QgPSBwbG90Lmxpc3QsIGxhYmVscyA9IG5hbWVzKHBsb3QubGlzdCksIGxhYmVsLnggPSAwLjExKQpgYGAKCiMgRmlnLiAxZiAtIEhlYXRtYXAKCkZpbHRlcmVkIHRyYW5zY3JpcHRzIGV4cHJlc3NlZCBpbiBMUFMtdHJlYXRlZCBmZW1hbGUgbWljZSBjb21wYXJlZCB0byBzYWxpbmUsIGFkZGl0aW9uYWxseSBmaWx0ZXJlZCB0byBzaG93IGdlbmVzIHdpdGggbG9nMihmb2xkIGNoYW5nZSkgPj0gMiAmIGFkanVzdGVkIHAgdmFsdWUgPCAwLjA1LgoKYGBge3IsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CiMgSFQgbWF0cml4CnpzY29yZTwtIGZ1bmN0aW9uKHgpewogICAgejwtICh4IC0gbWVhbih4KSkgLyBzZCh4KQogICAgcmV0dXJuKHopCn0KZnBrbS5tYXQueiA8LSB6c2NvcmUoZnBrbS5tYXQubGlzdFtbIkFsbCttYWxlIl1dKQoKc2lnbmlmaWNhbnQuZ2VuZXMgPC0gREVTZXEucmVzdWx0Lmxpc3RbWyJBbGwrbWFsZSJdXSAlPiUgCiAgZmlsdGVyKGxvZzJGb2xkQ2hhbmdlID49IDIgJiBwYWRqIDwgMC4wNSkgJT4lIAogIGFycmFuZ2UocGFkaikgJT4lIAogIHJvd25hbWVzKCkKZnBrbS5tYXQuc2lnLnogPC0gZnBrbS5tYXQueltzaWduaWZpY2FudC5nZW5lcywgcm93bmFtZXMoY29sRGF0YSldCgojIEhUIGFubm90YXRpb24KIyMgU2FtcGxlcwpzYW1wbGUubmFtZXMgPC0gY29sRGF0YVtjb2xuYW1lcyhmcGttLm1hdC5zaWcueiksXSAlPiUKICBtdXRhdGUoCiAgICBzYW1wbGVfbmFtZXMgPSBwYXN0ZShjb25kaXRpb240X3RyZWF0bWVudCwgY29uZGl0aW9uM19zZXgsIGNvbmRpdGlvbjVfdGltZSwgc2VwID0gIl8iKSwKICAgIHNhbXBsZV9uYW1lcyA9IGZhY3RvcihzYW1wbGVfbmFtZXMsIGxldmVscyA9IHVuaXF1ZShzYW1wbGVfbmFtZXMpKQogICkgJT4lCiAgcHVsbChzYW1wbGVfbmFtZXMpICU+JQogIHN0cl9yZXBsYWNlX2FsbCguLCAiXyIsICIgIikgJT4lCiAgc3RyX3JlcGxhY2UoLiwgIlxccyhcXGQraCkiLCAiIChcXDEpIikKc2FtcGxlLmNvbG9ycyA8LSB1bmlxdWUoc2FtcGxlLm5hbWVzKSAlPiUgbGVuZ3RoKCkgJT4lCiAgUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKC4sICJTZXQyIikKbmFtZXMoc2FtcGxlLmNvbG9ycykgPC0gdW5pcXVlKHNhbXBsZS5uYW1lcykKCiMjIFRyZWF0bWVudAp0cmVhdG1lbnQubmFtZXMgPC0KICBjb2xEYXRhW2NvbG5hbWVzKGZwa20ubWF0LnNpZy56KSxdJGNvbmRpdGlvbjRfdHJlYXRtZW50CnRyZWF0bWVudC5jb2xvcnMgPC0gYygiZ3JheTIwIiwgImdyYXk3MCIpCm5hbWVzKHRyZWF0bWVudC5jb2xvcnMpIDwtIHVuaXF1ZSh0cmVhdG1lbnQubmFtZXMpCgp0cmVhdG1lbnQuZ3JvdXBzIDwtIHdvcmQoY29sbmFtZXMoZnBrbS5tYXQuc2lnLnopLCAyLCBzZXAgPSAiXyIpCnRyZWF0bWVudC5ncm91cHMgPC0gc3RyX3JlcGxhY2UodHJlYXRtZW50Lmdyb3VwcywgIlNhbGluZSIsICJncmF5MjAiKQp0cmVhdG1lbnQuZ3JvdXBzIDwtIHN0cl9yZXBsYWNlKHRyZWF0bWVudC5ncm91cHMsICJMUFMiLCAiZ3JheTcwIikKbmFtZXModHJlYXRtZW50Lmdyb3VwcykgPC0gd29yZChjb2xuYW1lcyhmcGttLm1hdC5zaWcueiksIDIsIHNlcCA9ICJfIikKCnRyZWF0bWVudC5hbm5vIDwtIEhlYXRtYXBBbm5vdGF0aW9uKAogICAgIkdyb3VwIiA9IHRyZWF0bWVudC5uYW1lcywKICAgICJTYW1wbGUiID0gZmFjdG9yKHNhbXBsZS5uYW1lcywgbGV2ZWxzID0gbmFtZXMoc2FtcGxlLmNvbG9ycykpLAogICAgY29sID0gbGlzdCgiR3JvdXAiID0gdHJlYXRtZW50LmNvbG9ycywgIlNhbXBsZSIgPSBzYW1wbGUuY29sb3JzKSwKICAgIHNob3dfbGVnZW5kID0gVCwKICAgIHNob3dfYW5ub3RhdGlvbl9uYW1lID0gRgogICkKCmhpZ2hsaWdodC5nZW5lcyA8LSBjKCJDeWJiIiwgIkN4Y2wxMyIsICJUaW1wMSIsICJJZ3RwIiwgIklmaXRtMyIsICJIMi1EMSIsICJDY2wyIiwgIklsNiIpCgojIEhUCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKAogIGZwa20ubWF0LnNpZy56LAogIG5hbWUgPSAiekZQS00iLAogIGNvbCA9IGNpcmNsaXplOjpjb2xvclJhbXAyKGMoLTIsIDAsIDIpLCBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpKSwKICBzaG93X3Jvd19uYW1lcyA9IFQsCiAgc2hvd19jb2x1bW5fbmFtZXMgPSBULAogIGNsdXN0ZXJfY29sdW1ucyA9IEYsCiAgY2x1c3Rlcl9yb3dzID0gVCwKICBzaG93X3Jvd19kZW5kID0gRiwKICBib3R0b21fYW5ub3RhdGlvbiA9IHRyZWF0bWVudC5hbm5vCikgKwogIHJvd0Fubm90YXRpb24obGluayA9IGFubm9fbWFyaygKICAgIGF0ID0gd2hpY2gocm93bmFtZXMoZnBrbS5tYXQuc2lnLnopICVpbiUgaGlnaGxpZ2h0LmdlbmVzKSwKICAgIGxhYmVscyA9IGhpZ2hsaWdodC5nZW5lcywKICAgIHBhZGRpbmcgPSB1bml0KDEsICJtbSIpCiAgKSkKCiNwcmludCBudW1iZXIgb2YgZ2VuZXMgaW4gaGVhdG1hcApudW1iZXJfb2ZfZ2VuZXMgPC0gbnJvdyhmcGttLm1hdC5zaWcueikKcHJpbnQobnVtYmVyX29mX2dlbmVzKQpgYGAKCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04fQpsaWJyYXJ5KHpGUEtNKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoY2lyY2xpemUpCgpmcGttLm1hdC5saXN0LnBsdXMub25lIDwtIGxhcHBseShmcGttLm1hdC5saXN0LCBmdW5jdGlvbihtYXQpIG1hdCArIDEpCgojIENvbnZlcnQgdGhlIG1hdHJpeCB0byBhIGRhdGEuZnJhbWUKZnBrbS5kZiA8LSBhcy5kYXRhLmZyYW1lKGZwa20ubWF0Lmxpc3QucGx1cy5vbmVbWyJBbGwrbWFsZSJdXSkKCiMgQ29tcHV0ZSB6LXNjb3JlcyB1c2luZyB6RlBLTQpmcGttLm1hdC56IDwtIHpGUEtNKGZwa20uZGYpCgojIENvbnZlcnQgdGhlIHJlc3VsdCBiYWNrIHRvIGEgbWF0cml4CmZwa20ubWF0LnogPC0gYXMubWF0cml4KGZwa20ubWF0LnopCgpzaWduaWZpY2FudC5nZW5lcyA8LSBERVNlcS5yZXN1bHQubGlzdFtbIkFsbCttYWxlIl1dICU+JSAKICBmaWx0ZXIobG9nMkZvbGRDaGFuZ2UgPj0gMiAmIHBhZGogPCAwLjA1KSAlPiUgCiAgYXJyYW5nZShwYWRqKSAlPiUgCiAgcm93bmFtZXMoKQpmcGttLm1hdC5zaWcueiA8LSBmcGttLm1hdC56W3NpZ25pZmljYW50LmdlbmVzLCByb3duYW1lcyhjb2xEYXRhKV0KCiMgSFQgYW5ub3RhdGlvbgojIyBTYW1wbGVzCnNhbXBsZS5uYW1lcyA8LSBjb2xEYXRhW2NvbG5hbWVzKGZwa20ubWF0LnNpZy56KSxdICU+JQogIG11dGF0ZSgKICAgIHNhbXBsZV9uYW1lcyA9IHBhc3RlKGNvbmRpdGlvbjRfdHJlYXRtZW50LCBjb25kaXRpb24zX3NleCwgY29uZGl0aW9uNV90aW1lLCBzZXAgPSAiXyIpLAogICAgc2FtcGxlX25hbWVzID0gZmFjdG9yKHNhbXBsZV9uYW1lcywgbGV2ZWxzID0gdW5pcXVlKHNhbXBsZV9uYW1lcykpCiAgKSAlPiUKICBwdWxsKHNhbXBsZV9uYW1lcykgJT4lCiAgc3RyX3JlcGxhY2VfYWxsKC4sICJfIiwgIiAiKSAlPiUKICBzdHJfcmVwbGFjZSguLCAiXFxzKFxcZCtoKSIsICIgKFxcMSkiKQpzYW1wbGUuY29sb3JzIDwtIHVuaXF1ZShzYW1wbGUubmFtZXMpICU+JSBsZW5ndGgoKSAlPiUKICBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoLiwgIlNldDIiKQpuYW1lcyhzYW1wbGUuY29sb3JzKSA8LSB1bmlxdWUoc2FtcGxlLm5hbWVzKQoKIyMgVHJlYXRtZW50CnRyZWF0bWVudC5uYW1lcyA8LQogIGNvbERhdGFbY29sbmFtZXMoZnBrbS5tYXQuc2lnLnopLF0kY29uZGl0aW9uNF90cmVhdG1lbnQKdHJlYXRtZW50LmNvbG9ycyA8LSBjKCJncmF5MjAiLCAiZ3JheTcwIikKbmFtZXModHJlYXRtZW50LmNvbG9ycykgPC0gdW5pcXVlKHRyZWF0bWVudC5uYW1lcykKCnRyZWF0bWVudC5ncm91cHMgPC0gd29yZChjb2xuYW1lcyhmcGttLm1hdC5zaWcueiksIDIsIHNlcCA9ICJfIikKdHJlYXRtZW50Lmdyb3VwcyA8LSBzdHJfcmVwbGFjZSh0cmVhdG1lbnQuZ3JvdXBzLCAiU2FsaW5lIiwgImdyYXkyMCIpCnRyZWF0bWVudC5ncm91cHMgPC0gc3RyX3JlcGxhY2UodHJlYXRtZW50Lmdyb3VwcywgIkxQUyIsICJncmF5NzAiKQpuYW1lcyh0cmVhdG1lbnQuZ3JvdXBzKSA8LSB3b3JkKGNvbG5hbWVzKGZwa20ubWF0LnNpZy56KSwgMiwgc2VwID0gIl8iKQoKdHJlYXRtZW50LmFubm8gPC0gSGVhdG1hcEFubm90YXRpb24oCiAgICAiR3JvdXAiID0gdHJlYXRtZW50Lm5hbWVzLAogICAgIlNhbXBsZSIgPSBmYWN0b3Ioc2FtcGxlLm5hbWVzLCBsZXZlbHMgPSBuYW1lcyhzYW1wbGUuY29sb3JzKSksCiAgICBjb2wgPSBsaXN0KCJHcm91cCIgPSB0cmVhdG1lbnQuY29sb3JzLCAiU2FtcGxlIiA9IHNhbXBsZS5jb2xvcnMpLAogICAgc2hvd19sZWdlbmQgPSBULAogICAgc2hvd19hbm5vdGF0aW9uX25hbWUgPSBGCiAgKQoKaGlnaGxpZ2h0LmdlbmVzIDwtIGMoIkN5YmIiLCAiQ3hjbDEzIiwgIlRpbXAxIiwgIklndHAiLCAiSWZpdG0zIiwgIkgyLUQxIiwgIkNjbDIiLCAiSWw2IikKCiMgSFQKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoCiAgZnBrbS5tYXQuc2lnLnosCiAgbmFtZSA9ICJ6RlBLTSIsCiAgY29sID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoYygtMiwgMCwgMiksIGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpLAogIHNob3dfcm93X25hbWVzID0gVCwKICBzaG93X2NvbHVtbl9uYW1lcyA9IFQsCiAgY2x1c3Rlcl9jb2x1bW5zID0gRiwKICBjbHVzdGVyX3Jvd3MgPSBULAogIHNob3dfcm93X2RlbmQgPSBGLAogIGJvdHRvbV9hbm5vdGF0aW9uID0gdHJlYXRtZW50LmFubm8KKSArCiAgcm93QW5ub3RhdGlvbihsaW5rID0gYW5ub19tYXJrKAogICAgYXQgPSB3aGljaChyb3duYW1lcyhmcGttLm1hdC5zaWcueikgJWluJSBoaWdobGlnaHQuZ2VuZXMpLAogICAgbGFiZWxzID0gaGlnaGxpZ2h0LmdlbmVzLAogICAgcGFkZGluZyA9IHVuaXQoMSwgIm1tIikKICApKQoKI3ByaW50IG51bWJlciBvZiBnZW5lcyBpbiBoZWF0bWFwCm51bWJlcl9vZl9nZW5lcyA8LSBucm93KGZwa20ubWF0LnNpZy56KQpwcmludChudW1iZXJfb2ZfZ2VuZXMpCgoKCmBgYA==