Objective

Perform meta analysis of TB microarray AND RNA-seq data using the Rank Sum approach.
Overview:

  1. Microarray data: load log(fold change) data that Diana generated with Geo2r
  2. RNA-seq data: load published tables
  3. Aggregate microarray data by gene symbol; where the same gene is represented by several probes, calculate the median log(fold change)
  4. Merge fold change data from all datasets into a single matrix (outer join)
  5. Filter out genes with missing values (NA): keep only genes with a maximum number of missing values
  6. In each dataset rank genes by fold change (convert fold changes to ranks; NAs stay NAs; maximum ranks will vary by NA count)
  7. Replace NAs with average ranks in the other datasets
  8. Now rank again so number of ranks in each dataset is the same
  9. Calculate rank sum statistics and p values using RankProd package
  10. Generate heatmaps of top 100 regulated genes
# CHECK R VERSION
# stopifnot(R.version.string == "R version 3.4.3 (2017-11-30)")

# CLEANUP
# Clear all variables:
rm(list=ls(all=TRUE))

# Unload current packages:
# if (!is.null(names(utils::sessionInfo()[["otherPkgs"]]))) pacman::p_unload("all")
# KNITR DEFAULTS
knitr::opts_chunk$set(eval = TRUE, fig.height = 10)
# LOAD PACKAGES:
if (!requireNamespace("BiocManager", quietly = TRUE)) install.packages("BiocManager")
if (!requireNamespace("ComplexHeatmap", quietly = TRUE)) BiocManager::install("ComplexHeatmap")
if (!require(stringr)) install.packages("stringr")
if (!require(rstudioapi)) install.packages("rstudioapi")
if (!require(tidyr)) install.packages("tidyr")
if (!require(tibble)) install.packages("tibble")
if (!require(readxl)) install.packages("readxl")
if (!require(purrr)) install.packages("purrr")
if (!require(RColorBrewer)) install.packages("RColorBrewer")
if (!require(devtools)) install.packages("devtools")
if (!require(magrittr)) install.packages("magrittr")
if (!require(dplyr)) install.packages("dplyr")
if (!require(scales)) install.packages("scales")
if (!require(knitr)) install.packages("knitr")
if (!require(kableExtra)) install.packages("kableExtra")
if (!requireNamespace("RankProd", quietly = TRUE)) BiocManager::install("RankProd")

library(magrittr)
library(dplyr)
library(scales)
library(knitr)
library(kableExtra)
library(RankProd)
library(stringr)
# GET CURRENT SCRIPT NAME
(this.script <- rstudioapi::getActiveDocumentContext() %>% .$path %>% basename)
getwd()
list.files()
stopifnot(this.script != "")

Data: microarray studies

Geo2r data files

geo2r.data.files <- list.files(path="../source_data", recursive = T, 
                               pattern="^GSE.*Geo2r.*txt$", full.names = T)
geo2r.data.files <- grep(pattern = "GSE108363", geo2r.data.files, invert = T, value = T)
cat(basename(geo2r.data.files), sep="<br>\n")
tailleux.data.files <- list.files(path="../source_data", recursive = T,
                                  pattern="*tailleux.*txt", full.names = T)
cat(basename(tailleux.data.files), sep="<br>\n")
muarray.data.files <- c(geo2r.data.files, tailleux.data.files)
muarray.data.files <- grep("GSE29731", muarray.data.files, invert = T, value=T)
cat(basename(muarray.data.files), sep="<br>\n")

GSE103092_Geo2r_symbols.txt
GSE34151_Geo2r.txt
02_tailleux_18hrs_a.Rmd.tt.DC.2020_05_22.txt
02_tailleux_18hrs_a.Rmd.tt.MP.2020_05_22.txt

Load Geo2r data

geo2r.data.list <- lapply(muarray.data.files, function(f) {
  a <- read.table(f, sep="\t", header=T, quote='"', stringsAsFactors = F)
  names(a)[grep("symbol", names(a), ignore.case = T)] <- "Gene.symbol"
  names(a) <- stringr::str_remove(names(a), "^X\\.")
  names(a) <- stringr::str_remove(names(a), "\\.$")
  a
}) %>% 
  set_names(stringr::str_remove_all(basename(muarray.data.files), "\\.txt"))


names(geo2r.data.list) <- stringr::str_remove(names(geo2r.data.list), "^X")
names(geo2r.data.list)[grep("DC", names(geo2r.data.list))] <- "A-BUGS-23_DC"
names(geo2r.data.list)[grep("MP", names(geo2r.data.list))] <- "A-BUGS-23_MP"

# class(geo2r.data.list) # "list"
# length(geo2r.data.list) # 4
# sapply(geo2r.data.list, class) # "data.frame"
# sapply(geo2r.data.list, nrow) %>% unname # 29102 47231 24501 24501
data.frame(List.item = names(geo2r.data.list),
           class = sapply(geo2r.data.list, class),
           Columns = sapply(geo2r.data.list, ncol),
           Rows = comma(sapply(geo2r.data.list, nrow)),
           Has.Gene.Symbols = sapply(geo2r.data.list, function(x) {any(grepl("Gene.symbol", names(x)))})
           # Unique.genes = sapply(geo2r.data.list, function(x) {length(unique(x$))})
           ) %>% 
  mutate(Has.Gene.Symbols = cell_spec(Has.Gene.Symbols, "html", color = ifelse(Has.Gene.Symbols == "TRUE", "black", "red"))) %>% 
  knitr::kable(., row.names = F, align=c("l", "l", "c", "c", "c"), format = "html", escape = F) %>% 
  kableExtra::kable_styling("striped", full_width = T)
List.item class Columns Rows Has.Gene.Symbols
GSE103092_Geo2r_symbols data.frame 7 29,102 TRUE
GSE34151_Geo2r data.frame 8 47,231 TRUE
A-BUGS-23_DC data.frame 9 24,501 TRUE
A-BUGS-23_MP data.frame 9 24,501 TRUE
# head(geo2r.data.list[[2]])

Aggregate logFC by gene symbol

geo.fold.change.list <- lapply(seq_along(geo2r.data.list), function(i, list.names) {
  df <- geo2r.data.list[[i]]
  x <- dplyr::select(df, Gene.symbol, logFC) %>% 
    dplyr::mutate(Gene.symbol = ifelse(Gene.symbol=="", NA, Gene.symbol)) %>% 
    tidyr::drop_na(.) %>% 
    dplyr::mutate(logFC = as.numeric(logFC)) %>% 
    dplyr::mutate(Gene.symbol = stringr::str_remove_all(Gene.symbol, '\\"')) %>% 
    tidyr::drop_na(.) %>% 
    dplyr::group_by(Gene.symbol) %>% 
    dplyr::summarise(logFC = median(logFC), .groups="drop") %>% 
    tidyr::drop_na(.) %>% 
    as.data.frame(., stringsAsFactors=FALSE)
  # names(x)[names(x) == "logFC"] <- list.names[i]
  x
}, list.names = names(geo2r.data.list)) %>% 
  set_names(names(geo2r.data.list))
names(geo.fold.change.list) <- stringr::str_remove(names(geo.fold.change.list), "_Geo2r.*$")
data.frame(
  List.item = names(geo.fold.change.list),
  Class = sapply(geo.fold.change.list, function(x) {paste(class(x), collapse=",")}),
  Columns = sapply(geo.fold.change.list, ncol),
  Rows = comma(sapply(geo.fold.change.list, nrow)),
  Duplicated.genes = sapply(geo.fold.change.list, function(x) {any(duplicated(x$Gene.symbol))})
) %>% 
  knitr::kable(., row.names = F, align=c("l", "l", "c", "c", "c"), format = "html", escape = F) %>% 
  kableExtra::kable_styling("striped", full_width = T)
List.item Class Columns Rows Duplicated.genes
GSE103092 data.frame 2 21,025 FALSE
GSE34151 data.frame 2 20,762 FALSE
A-BUGS-23_DC data.frame 2 13,768 FALSE
A-BUGS-23_MP data.frame 2 13,768 FALSE

Data: RNA-seq studies

# <a href="https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc=GSE64179">GSE64179</a>
# https://pubmed.ncbi.nlm.nih.gov/26392366/
add.geo.hyperlink <- function(id) {
  a <- "<a href='https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi?acc="
  b <- "'>"
  c <- "</a>"
  res <- paste0(a, id, b, id, c)
  res
}

add.pubmed.hyperlink <- function(id) {
  a <- "<a href='https://pubmed.ncbi.nlm.nih.gov/"
  b <- "/'>"
  c <- "</a>"
  res <- paste0(a, id, b, id, c)
  res
}
rs.dataset.table <- data.frame(
  c=c("GSE64179", "26392366", "6", "Dendritic cells"),
  b=c("GSE67427", "26586179", "8", "Monocyte derived macrophages"),
  a=c("GSE148731", "32341411", "6", "M1, M2 macrophages")
) %>%
  t %>% 
  as.data.frame(., stringsAsFactors=F) %>% 
  set_colnames(c("Accession", "Pubmed", "Replicates", "Cell.Type")) %>% 
  mutate(Accession=add.geo.hyperlink(Accession)) %>% 
  mutate(Pubmed = add.pubmed.hyperlink(Pubmed))
knitr::kable(rs.dataset.table, row.names = F, escape=F, format = "html") %>% 
  kableExtra::kable_styling("striped", full_width = T)
Accession Pubmed Replicates Cell.Type
GSE64179 26392366 6 Dendritic cells
GSE67427 26586179 8 Monocyte derived macrophages
GSE148731 32341411 6 M1, M2 macrophages

Load RNA-seq data: GSE64179

# list.files("../source_data/rna_seq/GSE64179")
rna.seq.data.files <- list()
rna.seq.data.files$GSE64179 <- "../source_data/rna_seq/GSE64179/Pacis_2015_TableS6_DEG.xlsx"
stopifnot(file.exists(rna.seq.data.files$GSE64179))
rna.seq.data.list <- list()
rna.seq.data.list$GSE64179 <- readxl::read_xlsx(path=rna.seq.data.files$GSE64179, range = "B3:C18818") %>%
set_colnames(c("Gene.symbol", "logFC")) %>% 
mutate(Gene.symbol = toupper(Gene.symbol)) %>% 
group_by(Gene.symbol) %>% 
summarise(logFC = median(logFC), .groups="drop") %>% 
as.data.frame(., stringsAsFactors=FALSE)
# WITHOUT GROUPING / AGGREGATING:
# dim(rna.seq.data$GSE64179) # 18815     2
# sapply(rna.seq.data$GSE64179, class) # "character"   "numeric"
# range(rna.seq.data$GSE64179$logFC) # -9.426613 11.612170
# sum(rna.seq.data$GSE64179$logFC == 0) # 0
# sum(duplicated(rna.seq.data$GSE64179$Gene.symbol)) # 37
# dups <- rna.seq.data$GSE64179$Gene.symbol[duplicated(rna.seq.data$GSE64179$Gene.symbol)]
# subset(rna.seq.data$GSE64179, Gene.symbol %in% dups) %>% 
#   arrange(Gene.symbol, logFC)
# head(rna.seq.data$GSE64179)
# WITH AGGREGATING: 
# dim(rna.seq.data$GSE64179) # 18776     2
# sapply(rna.seq.data$GSE64179, class) # "character"   "numeric"
# range(rna.seq.data$GSE64179$logFC) # -9.426613 11.612170
# sum(rna.seq.data$GSE64179$logFC == 0) # 0
# sum(duplicated(rna.seq.data$GSE64179$Gene.symbol)) # 0
head(rna.seq.data.list$GSE64179) %>% 
  knitr::kable(., row.names = F, format="html") %>% 
  kableExtra::kable_styling("striped", full_width = T)
Gene.symbol logFC
A1BG 0.0601550
A1CF -0.0382101
A2M -0.9667654
A2ML1 0.5481361
A3GALT2 0.6965051
A4GALT -0.0035594

Load RNA-seq data: GSE67427

rna.seq.data.files$GSE67427 <-"../source_data/rna_seq/GSE67427/GSE67427_table_s2.txt"
stopifnot(file.exists(rna.seq.data.files$GSE67427))
#rna.seq.data.list <-list()
rna.seq.data.list$GSE67427<- read.table(file = rna.seq.data.files$GSE67427, header = T, sep = "\t", stringsAsFactors = FALSE)  %>% 
#str(rna.seq.data.list$GSE67427) #data.frame':  12728 obs. of  140 variables:
  # head(rna.seq.data.list$GSE67427)
  
  # names(rna.seq.data.list$GSE67427)
  # select(matches(c("name", "Rv.18")))
 select(matches(c("name","Rv.18.logFC"), ignore.case = TRUE)) %>%
   # head(rna.seq.data.list$GSE67427)
  set_colnames(c("Gene.symbol", "logFC")) %>%
  mutate(Gene.symbol = toupper(Gene.symbol)) %>% 
  group_by(Gene.symbol) %>% 
  summarise(logFC = median(logFC), .groups="drop") %>% 
  as.data.frame(., stringsAsFactors=FALSE)
 head(rna.seq.data.list$GSE67427)
# WITHOUT GROUPING / AGGREGATING:
dim(rna.seq.data.list$GSE67427) #  12724     2
[1] 12724     2
sapply(rna.seq.data.list$GSE67427, class) # "character"   "numeric"
Gene.symbol       logFC 
"character"   "numeric" 
range(rna.seq.data.list$GSE67427$logFC) # -4.155479  8.205439
[1] -4.155479  8.205439
sum(rna.seq.data.list$GSE67427$logFC == 0) # 0
[1] 0
sum(duplicated(rna.seq.data.list$GSE67427$Gene.symbol)) # 0
[1] 0
# dups <- rna.seq.data$GSE64179$Gene.symbol[duplicated(rna.seq.data$GSE64179$Gene.symbol)]
# subset(rna.seq.data$GSE64179, Gene.symbol %in% dups) %>% 
#   arrange(Gene.symbol, logFC)
# head(rna.seq.data$GSE64179)
# WITH AGGREGATING: 
# dim(rna.seq.data$GSE64179) # 18776     2
# sapply(rna.seq.data$GSE64179, class) # "character"   "numeric"
# range(rna.seq.data$GSE64179$logFC) # -9.426613 11.612170
# sum(rna.seq.data$GSE64179$logFC == 0) # 0
# sum(duplicated(rna.seq.data$GSE64179$Gene.symbol)) # 0
head(rna.seq.data.list$GSE67427) %>% 
  knitr::kable(., row.names = F, format="html") %>% 
  kableExtra::kable_styling("striped", full_width = T)

Gene.symbol logFC
A1BG -0.1946990
A2M -0.4127574
A4GALT 3.1197860
AAAS -0.4116309
AACS -0.4685173
AAED1 0.0936937

NA

Load RNA-seq data: GSE48731.MF1

# list.files("../source_data/rna_seq/GSE148731")
# rna.seq.data.files <- list()
rna.seq.data.files$GSE148731.MF1 <- "../source_data/rna_seq/GSE148731/GSE148731_toptable_MF1_01.Rmd.2020_07_09.txt"
stopifnot(file.exists(rna.seq.data.files$GSE148731.MF1))
 # rna.seq.data.list <- list()
rna.seq.data.list$GSE148731.MF1 <- read.table(file = rna.seq.data.files$GSE148731.MF1, header = TRUE, sep = "\t", stringsAsFactors = FALSE) %>%
   select(c("Gene.symbol", "logFC")) %>% 
  mutate(Gene.symbol = toupper(Gene.symbol)) %>% 
  # summarise(logFC = median(logFC), .groups="drop") %>% 
   as.data.frame(., stringsAsFactors=FALSE)
head(rna.seq.data.list$GSE148731.MF1)
# WITH AGGREGATING:
dim(rna.seq.data.list$GSE148731.MF1) # 17802     2
[1] 17802     2
sapply(rna.seq.data.list$GSE148731.MF1, class) # "character"   "numeric"
Gene.symbol       logFC 
"character"   "numeric" 
range(rna.seq.data.list$GSE148731.MF1$logFC) # -2.386124  9.704865
[1] -2.386124  9.704865
sum(rna.seq.data.list$GSE148731.MF1$logFC == 0) #
[1] 0
sum(duplicated(rna.seq.data.list$GSE148731.MF1$Gene.symbol)) # 0
[1] 0
head(rna.seq.data.list$GSE148731.MF1) %>% 
  knitr::kable(., row.names = F, format="html") %>% 
  kableExtra::kable_styling("striped", full_width = T)
Gene.symbol logFC
RSAD2 9.704866
IFIT1 7.715897
CMPK2 6.811519
GBP4 6.700888
SIGLEC1 6.601206
IFIT2 6.441537

Load RNA-seq data: GSE48731.MF2

 # list.files("../source_data/rna_seq/GSE148731")
# rna.seq.data.files <- list()
rna.seq.data.files$GSE148731.MF2 <- "../source_data/rna_seq/GSE148731/GSE148731_toptable_MF2_01.Rmd.2020_07_09.txt"
stopifnot(file.exists(rna.seq.data.files$GSE148731.MF2))
 # rna.seq.data.list <- list()
rna.seq.data.list$GSE148731.MF2 <- read.table(file = rna.seq.data.files$GSE148731.MF2, header = TRUE, sep = "\t", stringsAsFactors = FALSE) %>%
   select(c("Gene.symbol", "logFC")) %>% 
  mutate(Gene.symbol = toupper(Gene.symbol)) %>% 
 # summarise(logFC = median(logFC), .groups="drop") %>% 
   as.data.frame(., stringsAsFactors=FALSE)
head(rna.seq.data.list$GSE148731.MF2)
# WITH AGGREGATING:
dim(rna.seq.data.list$GSE148731.MF2) #  17085     2
[1] 17085     2
sapply(rna.seq.data.list$GSE148731.MF2, class) # "character"   "numeric"
Gene.symbol       logFC 
"character"   "numeric" 
range(rna.seq.data.list$GSE148731.MF2$logFC) # -7.524136  9.795100
[1] -7.524136  9.795100
sum(rna.seq.data.list$GSE148731.MF2$logFC == 0) #
[1] 0
sum(duplicated(rna.seq.data.list$GSE148731.MF2$Gene.symbol)) # 0
[1] 0
head(rna.seq.data.list$GSE148731.MF2) %>% 
  knitr::kable(., row.names = F, format="html") %>% 
  kableExtra::kable_styling("striped", full_width = T)
Gene.symbol logFC
CXCL8 9.795100
IL1B 9.369129
INHBA 9.227757
AC005027.3 9.200438
CXCL1 8.877153
IL7R 7.901586

Combine all data frames

# length(rna.seq.data.list) #4
# length(geo.fold.change.list)# 4
all.data.list <- c(geo.fold.change.list, rna.seq.data.list)
all(sapply(all.data.list, class) == "data.frame") # TRUE
[1] TRUE
lapply(all.data.list, names)
$GSE103092
[1] "Gene.symbol" "logFC"      

$GSE34151
[1] "Gene.symbol" "logFC"      

$`A-BUGS-23_DC`
[1] "Gene.symbol" "logFC"      

$`A-BUGS-23_MP`
[1] "Gene.symbol" "logFC"      

$GSE64179
[1] "Gene.symbol" "logFC"      

$GSE67427
[1] "Gene.symbol" "logFC"      

$GSE148731.MF1
[1] "Gene.symbol" "logFC"      

$GSE148731.MF2
[1] "Gene.symbol" "logFC"      
length(all.data.list) # 8
[1] 8

Merge data frames

merged.df <- all.data.list %>% purrr::reduce(dplyr::full_join, by="Gene.symbol") %>% 
  tibble::column_to_rownames("Gene.symbol") %>% 
  set_colnames(paste0("dataset", seq_along(.)))

dim(merged.df) # 30687     8
[1] 30687     8
head(merged.df) %>% 
  knitr::kable(., row.names = T, format="html") %>% 
  kableExtra::kable_styling("striped", full_width = T)
dataset1 dataset2 dataset3 dataset4 dataset5 dataset6 dataset7 dataset8
A1BG -0.0991062 0.025601 NA NA 0.0601550 -0.1946990 -0.3256752 -0.2898109
A1BG-AS1 0.1676623 0.010700 NA NA NA NA -0.0930987 -0.0342790
A1CF 0.2107242 -0.020400 0.0479557 0.0408764 -0.0382101 NA NA NA
A2M -1.2083956 -0.382000 -0.3817807 0.0638831 -0.9667654 -0.4127574 -0.0881266 -1.2132680
A2M-AS1 0.4142154 NA NA NA NA NA -0.3492692 -1.5406447
A2ML1 0.1463684 0.003970 NA NA 0.5481361 NA NA NA

Summary table:

df.summary <- data.frame(
  Column = names(merged.df),
  Dataset=names(all.data.list),
  Min = round(sapply(merged.df, min, na.rm=T), 2),
  Max = round(sapply(merged.df, max, na.rm=T), 2),
  Count.zero = sapply(merged.df, function(x) {sum(x==0, na.rm=T)}),
  Count.NA = comma(sapply(merged.df, function(x) {sum(is.na(x))}))
) %>% 
  mutate(Count.zero = comma(as.integer(Count.zero), accuracy=1))
knitr::kable(df.summary, row.names = F, format="html", align=rep("l", 5)) %>% 
  kableExtra::kable_styling("striped", full_width = T)
Column Dataset Min Max Count.zero Count.NA
dataset1 GSE103092 -3.48 7.02 0 9,662
dataset2 GSE34151 -3.75 5.65 2 9,925
dataset3 A-BUGS-23_DC -8.78 9.84 1,670 16,919
dataset4 A-BUGS-23_MP -6.26 8.72 14 16,919
dataset5 GSE64179 -9.43 11.61 0 11,911
dataset6 GSE67427 -4.16 8.21 0 17,963
dataset7 GSE148731.MF1 -2.39 9.70 0 12,885
dataset8 GSE148731.MF2 -7.52 9.80 0 13,602
cat("Number of rows in merged data frame:<b>", comma(nrow(merged.df)), "</b><br>\n")

Number of rows in merged data frame: 30,687

cat("Number of gene symbols in merged data frame:<b>", comma(length(unique(row.names(merged.df)))), "</b><br>\n")

Number of gene symbols in merged data frame: 30,687

cat("Number of complete data rows: <b>", comma(sum(complete.cases(merged.df))), "</b> (",
    percent(sum(complete.cases(merged.df))/length(unique(row.names(merged.df)))),
    ")<br>\n", sep="")

Number of complete data rows: 7,729 (25%)

Filter rows: Three NA max

three.na.max.count <- as.matrix(merged.df) %>% 
  apply(., 1, function(x) {  # 1 = select rows
    n <- sum(is.na(x))
    n
  })
class(three.na.max.count) # integer
[1] "integer"
length(three.na.max.count) # 30687
[1] 30687
merged.df.filt.na <- merged.df[three.na.max.count <= 3,]
dim(merged.df.filt.na)# 14743 8 vs 11873     8  vs  8626    8 (one NA max)
[1] 14743     8
 dim(merged.df)    #  30687     8  
[1] 30687     8
cat("Number of rows with max three NA: <b>", comma(nrow(merged.df.filt.na)), "</b> (",
    percent(nrow(merged.df.filt.na)/length(unique(row.names(merged.df)))),
    ")<br>\n", sep="")
Number of rows with max three NA: <b>14,743</b> (48%)<br>

Print 10 rows:

head(merged.df.filt.na, 10) %>% 
  knitr::kable(., row.names = T, format="html", align=rep("l", 5)) %>% 
  kableExtra::kable_styling("striped", full_width = T)
dataset1 dataset2 dataset3 dataset4 dataset5 dataset6 dataset7 dataset8
A1BG -0.0991062 0.025601 NA NA 0.0601550 -0.1946990 -0.3256752 -0.2898109
A1CF 0.2107242 -0.020400 0.0479557 0.0408764 -0.0382101 NA NA NA
A2M -1.2083956 -0.382000 -0.3817807 0.0638831 -0.9667654 -0.4127574 -0.0881266 -1.2132680
A4GALT -0.3770078 0.288000 0.1716445 0.3668996 -0.0035594 3.1197860 0.3486892 2.8026054
A4GNT -0.5358127 0.002210 0.0000000 0.0000000 -2.9383732 NA NA NA
AAAS -0.0636266 -0.224000 0.1201606 -0.0527861 -0.6797045 -0.4116309 -0.3497130 -0.3449479
AACS -0.3830068 0.090400 -0.5272623 0.0146090 0.0995290 -0.4685173 -0.2095024 -0.2474777
AAGAB 0.1637777 -0.150000 -0.3421747 -0.6694862 -0.2844965 -0.2943462 0.0484454 -0.2547554
AAK1 0.0656871 -0.006945 0.0000000 0.0000000 -0.0675787 0.3417414 0.3352014 -0.1097963
AAMDC -0.1248942 -0.022400 -0.2027101 -0.1717276 -0.6259770 -0.0881718 0.0590204 0.0251869

Summary table after filtering NAs:

df.summary.2 <- data.frame(
  Column = names(merged.df.filt.na),
  Dataset=names(all.data.list),
  Min = round(sapply(merged.df.filt.na, min, na.rm=T), 2),
  Max = round(sapply(merged.df.filt.na, max, na.rm=T), 2),
  Count.zero = sapply(merged.df.filt.na, function(x) {sum(x==0, na.rm=T)}),
  Count.NA = comma(sapply(merged.df.filt.na, function(x) {sum(is.na(x))}))
) %>% 
  mutate(Count.zero = comma(as.integer(Count.zero), accuracy=1))
knitr::kable(df.summary.2, row.names = F, format="html", align=rep("l", 5)) %>% 
  kableExtra::kable_styling("striped", full_width = T)

Column Dataset Min Max Count.zero Count.NA
dataset1 GSE103092 -3.48 7.02 0 505
dataset2 GSE34151 -3.75 5.65 1 252
dataset3 A-BUGS-23_DC -8.78 9.84 1,300 2,705
dataset4 A-BUGS-23_MP -6.26 8.72 12 2,705
dataset5 GSE64179 -9.43 11.10 0 278
dataset6 GSE67427 -4.11 8.21 0 3,063
dataset7 GSE148731.MF1 -2.39 9.70 0 3,131
dataset8 GSE148731.MF2 -7.52 9.80 0 3,362

rm(df.summary.2)

Calculate ranks (1)

Key paramters:

  • NAs stay NAs
  • ties are averaged
  • keeping NAs results in different maximum ranks
  • (will have to rank again after filling NAs)

Ranks: down-regulated

ranks.down.mx <- apply(merged.df.filt.na, 2, rank, ties.method="average", na.last="keep")
# class(ranks.down.mx) # matrix
# dim(ranks.down.mx) # 12786     5
# ranks.down.mx[1:10,]

Print 6 rows (sorted):

ranks.down.mx[order(rowMeans(ranks.down.mx, na.rm=T)),] %>% head
       dataset1 dataset2 dataset3 dataset4 dataset5 dataset6
MS4A6A      302      2.0        4        6       17      295
TREM2       195     58.5       12       37       62       88
ADORA3      185      4.0      531       24        4       22
CD36        140    139.0       58        1      298      167
S100A4      284     68.5       75       86       54      896
FZD2        510     37.5      175       95      313      400
       dataset7 dataset8
MS4A6A        4        3
TREM2       337      236
ADORA3       NA       NA
CD36        297      413
S100A4       49      506
FZD2         NA      313

The maximum ranks will vary by dataset:

apply(ranks.down.mx, 2, max, na.rm=T) %>%
  as.data.frame(., stringsAsFactors=F) %>% 
  tibble::rownames_to_column() %>% 
  set_colnames(c("Dataset (down)", "Max.Rank")) %>% 
  mutate(Max.Rank = comma(Max.Rank)) %>% 
  knitr::kable(., row.names = F, align=c("l","r"), format="html") %>% 
  kableExtra::kable_styling("striped")
Dataset (down) Max.Rank
dataset1 14,238
dataset2 14,491
dataset3 12,038
dataset4 12,038
dataset5 14,465
dataset6 11,680
dataset7 11,612
dataset8 11,381

Ranks: up-regulated

ranks.up.mx <- apply((merged.df.filt.na * -1), 2, rank, ties.method="average", na.last="keep")

Print 6 rows (sorted):

ranks.up.mx[order(rowMeans(ranks.up.mx, na.rm=T)),] %>% head
        dataset1 dataset2 dataset3 dataset4 dataset5 dataset6
CCL20         53      8.0        5        2        7        3
CCR7           1      4.0       25       16       41        4
IL1B          48     11.0       18       59       36       37
IDO1         167      1.0       22       10       12       72
TNFAIP6       17    176.0       50       50       45       43
IL6          131    129.5       64        6       46       17
        dataset7 dataset8
CCL20         NA       NA
CCR7          NA       NA
IL1B          51        2
IDO1          NA       NA
TNFAIP6       NA       NA
IL6           NA       NA
apply(ranks.up.mx, 2, max, na.rm=T) %>%
  as.data.frame(., stringsAsFactors=F) %>% 
  tibble::rownames_to_column() %>% 
  set_colnames(c("Dataset (up)", "Max.Rank")) %>% 
  mutate(Max.Rank = comma(Max.Rank)) %>% 
  knitr::kable(., row.names = F, align=c("l","r"), format="html") %>% 
  kableExtra::kable_styling("striped")
Dataset (up) Max.Rank
dataset1 14,238
dataset2 14,491
dataset3 12,038
dataset4 12,038
dataset5 14,465
dataset6 11,680
dataset7 11,612
dataset8 11,381

Replace missing values with averages

na.to.average <- function(x) {
  any.na <- any(is.na(x))
  if (isTRUE(any.na)) {
    indx.na <- which(is.na(x))
    ave <- mean(x, na.rm=T)
    x[indx.na] <- ave
  }
  x
}

Replace NAs: down-regulation

ranks.down.nona.mx <- t(apply(ranks.down.mx, 1, na.to.average))
# dim(ranks.down.mx) # 12786     5
# dim(ranks.down.nona.mx) # 12786     5
# sum(is.na(ranks.down.mx)) # 1436
# sum(is.na(ranks.down.nona.mx)) # 0
# ranks.down.nona.mx[1:4,1:4]
ranks.down.nona.mx[order(rowMeans(ranks.down.nona.mx, na.rm=T)),] %>% 
  head
       dataset1 dataset2 dataset3 dataset4 dataset5 dataset6
MS4A6A      302      2.0        4        6       17      295
TREM2       195     58.5       12       37       62       88
ADORA3      185      4.0      531       24        4       22
CD36        140    139.0       58        1      298      167
S100A4      284     68.5       75       86       54      896
FZD2        510     37.5      175       95      313      400
       dataset7 dataset8
MS4A6A   4.0000   3.0000
TREM2  337.0000 236.0000
ADORA3 128.3333 128.3333
CD36   297.0000 413.0000
S100A4  49.0000 506.0000
FZD2   263.3571 313.0000

Replace NAs: up-regulation

ranks.up.nona.mx <- t(apply(ranks.up.mx, 1, na.to.average))
# dim(ranks.up.mx) # 12786     5
# dim(ranks.up.nona.mx) # 12786     5
# sum(is.na(ranks.up.mx)) # 1436
# sum(is.na(ranks.up.nona.mx)) # 0
# ranks.up.nona.mx[1:4,1:4]
ranks.up.nona.mx[order(rowMeans(ranks.up.nona.mx, na.rm=T)),] %>% 
  head
        dataset1 dataset2 dataset3 dataset4 dataset5 dataset6
CCL20         53      8.0        5        2        7        3
CCR7           1      4.0       25       16       41        4
IL1B          48     11.0       18       59       36       37
IDO1         167      1.0       22       10       12       72
TNFAIP6       17    176.0       50       50       45       43
IL6          131    129.5       64        6       46       17
        dataset7 dataset8
CCL20   13.00000 13.00000
CCR7    15.16667 15.16667
IL1B    51.00000  2.00000
IDO1    47.33333 47.33333
TNFAIP6 63.50000 63.50000
IL6     65.58333 65.58333

Calculate ranks (2)

Having replaced missing values with averages, repeat the ranking such that maximum ranks will be the same for each dataset.
Key paramters:

  • ties are averaged

Ranks: down-regulated (2)

ranks.down.mx.2 <- apply(ranks.down.nona.mx, 2, rank, ties.method="average", na.last="keep")
# class(ranks.down.mx.2) # matrix
# dim(ranks.down.mx.2) # 12786     5
# ranks.down.mx.2[1:10,]
ranks.down.mx.2[order(rowMeans(ranks.down.mx.2, na.rm=T)),] %>% 
  head
       dataset1 dataset2 dataset3 dataset4 dataset5 dataset6
MS4A6A      302      2.0        4        6       17      295
TREM2       195     58.5       12       37       62       88
ADORA3      185      4.0      532       24        4       22
CD36        140    139.0       58        1      298      167
S100A4      284     68.5       75       86       54      897
FZD2        511     37.5      175       95      313      400
       dataset7 dataset8
MS4A6A        4        3
TREM2       339      237
ADORA3      129      129
CD36        299      414
S100A4       49      507
FZD2        265      314
apply(ranks.down.mx.2, 2, max) %>%
  as.data.frame(., stringsAsFactors=F) %>% 
  tibble::rownames_to_column() %>% 
  set_colnames(c("Dataset (down)", "Max.Rank")) %>% 
  knitr::kable(., row.names = F, align=c("l","r"), format="html") %>% 
  kableExtra::kable_styling("striped")
Dataset (down) Max.Rank
dataset1 14743
dataset2 14743
dataset3 14743
dataset4 14743
dataset5 14743
dataset6 14743
dataset7 14743
dataset8 14743

Ranks: up-regulated (2)

ranks.up.mx.2 <- apply(ranks.up.nona.mx, 2, rank, ties.method="average", na.last="keep")
# ranks.up.mx.2[1:10,]
ranks.up.mx.2[order(rowMeans(ranks.up.mx.2, na.rm=T)),] %>% head
        dataset1 dataset2 dataset3 dataset4 dataset5 dataset6
CCL20         53      8.0        5        2        7        3
CCR7           1      4.0       25       16       41        4
IL1B          48     11.0       18       59       36       37
IDO1         167      1.0       22       10       12       72
TNFAIP6       17    176.0       50       50       45       43
IL6          131    129.5       64        6       46       17
        dataset7 dataset8
CCL20       13.5     13.5
CCR7        17.0     17.0
IL1B        54.0      2.0
IDO1        50.0     50.0
TNFAIP6     67.0     67.0
IL6         70.0     70.0
apply(ranks.up.mx.2, 2, max) %>%
  as.data.frame(., stringsAsFactors=F) %>% 
  tibble::rownames_to_column() %>% 
  set_colnames(c("Dataset (up)", "Max.Rank")) %>% 
  knitr::kable(., row.names = F, align=c("l","r"), format="html") %>% 
  kableExtra::kable_styling("striped")
Dataset (up) Max.Rank
dataset1 14743
dataset2 14743
dataset3 14743
dataset4 14743
dataset5 14743
dataset6 14743
dataset7 14743
dataset8 14743

Plot correlations

Plot fold changes

panel.cor <- function(x, y, digits = 3, prefix = "", cex.cor, ...)
{
     usr <- par("usr"); on.exit(par(usr))
     par(usr = c(0, 1, 0, 1))
     r <- cor(x, y, use="pairwise.complete.obs")
     txt <- format(c(r, 0.123456789), digits = digits)[1]
     txt <- paste0("r=", txt)
     # if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt)
     # text(0.5, 0.5, txt, cex = cex.cor * r)
     text(0.5, 0.5, txt, cex=1.5, col="blue", font = 3)
}

cor.df<- merged.df %>% 
  set_colnames(names(all.data.list)) 
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\_DC", " \nDC")
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\_MP", " \nMP")
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\.MF1", " \nMF1")
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\.MF2", " \nMF2") 
  colnames(cor.df) <- str_replace(colnames(cor.df), "GSE103092", "GSE103092\nMP")
  colnames(cor.df) <- str_replace(colnames(cor.df), "GSE64179", "GSE64179\nDC")              
  colnames(cor.df) <- str_replace(colnames(cor.df),"GSE34151", "GSE34151\nDC")
  colnames(cor.df) <- str_replace(colnames(cor.df),"GSE67427",
"GSE67427\nMP")
  
  pairs(cor.df, col = alpha("black", 0.3), pch=20, upper.panel = panel.cor,
        xaxt='n', yaxt='n',
        text.panel = function(x,y,lab,cex,font) {text(x,y,lab, cex=1.0, font=2)}) 

NA
NA

Save plot as a png

out.file <- paste("data_out", stringr::str_replace(this.script, "\\.[Rr][Mm][Dd]$", ".png"), sep="/")
out.file
[1] "data_out/rank_product_01g.3NA.png"
png(file=out.file, width = 680, height= 480, units = "px")
cor.df<- merged.df %>% 
  set_colnames(names(all.data.list)) 
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\_DC", " \nDC")
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\_MP", " \nMP")
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\.MF1", " \nMF1")
  colnames(cor.df) <- str_replace(colnames(cor.df), "\\.MF2", " \nMF2") 
    colnames(cor.df) <- str_replace(colnames(cor.df), "GSE103092", "GSE103092\nMP")
  colnames(cor.df) <- str_replace(colnames(cor.df), "GSE64179", "GSE64179\nDC")              
  colnames(cor.df) <- str_replace(colnames(cor.df),"GSE34151", "GSE34151\nDC")
  colnames(cor.df) <- str_replace(colnames(cor.df),"GSE67427",
"GSE67427\nMP")
  
  pairs(cor.df, col = alpha("black", 0.3), pch=20, upper.panel = panel.cor,
        xaxt='n', yaxt='n',
        text.panel = function(x,y,lab,cex,font) {text(x,y,lab, cex=1.4, font=2)}) 
  
dev.off()
null device 
          1 

Plot ranks

panel.cor.spearman <- function(x, y, digits = 3, prefix = "", cex.cor, ...)
{
     usr <- par("usr"); on.exit(par(usr))
     par(usr = c(0, 1, 0, 1))
     r <- cor(x, y, use="pairwise.complete.obs", method="spearman")
     txt <- format(c(r, 0.123456789), digits = digits)[1]
     txt <- paste0("r=", txt)
     text(0.5, 0.5, txt, cex=2.5, col="purple", font = 3)
}
ranks.down.mx.2 %>% 
  set_colnames(names(all.data.list)) %>% 
  pairs(., col = alpha("black", 0.3), pch=18, upper.panel = panel.cor.spearman,
        xaxt='n', yaxt='n',
      text.panel = function(x,y,lab,cex,font) {text(x,y,lab, cex=3, font=2)})

Calculate rank sum statistics

# browseVignettes("RankProd")
# help(package="RankProd")
# help(RankProducts, package="RankProd")
get.rank.sum <- function(mx) {
  cl.down <- rep(1, ncol(mx))
  rank.sum <- RankProd::RankProducts(
    data=mx,
    cl=cl.down,
    calculateProduct=FALSE,
    gene.names = row.names(ranks.down.mx.2)
    )
  rank.sum
}
get.rank.sum.stats <- function(rank.sum.result) {
  # Get rank sums:
  RS <- as.data.frame(rank.sum.result$RSs) %>% 
    tibble::rownames_to_column(.) %>% 
    dplyr::select(1:2) %>% 
    set_colnames(c("Gene", "Rank.Sum")) %>% 
    arrange(Rank.Sum)
  
  # Get p values:
  PVAL <- as.data.frame(rank.sum.result$pval) %>% 
  tibble::rownames_to_column(.) %>% 
  dplyr::select(1:2) %>% 
  set_colnames(c("Gene", "P.Val"))
  
  # Merge:
  RS.PVAL <- merge(x=RS, y=PVAL, by="Gene", all=T)
  
  # Adjust p values:
  RS.PVAL$P.Val.Adj <- p.adjust(RS.PVAL$P.Val, method = "fdr")
  
  RS.PVAL
}

Rank sum stats: down-regulation

rank.sum.down <- get.rank.sum(ranks.down.mx.2) %>% 
  get.rank.sum.stats(.)
Rank Product analysis for paired case 
 

 done  
# class(rank.sum.down) # "data.frame"
# dim(rank.sum.down) # 11684     4
top.10.down<- rank.sum.down %>% 
  arrange(Rank.Sum) %>% 
  head(., 10) 
  # knitr::kable(., row.names = F, align=rep("l", 4), format = "html", escape = F) %>% 
  # kableExtra::kable_styling("striped", full_width = T)
top.10.down
NA

Save top 10 down-regulated genes

out.table.down <- paste0("data_out/",this.script, ".top.down.txt")
out.table.down
[1] "data_out/rank_product_01g.3NA.Rmd.top.down.txt"
write.table(top.10.down, file = out.table.down, sep = "\t")

Rank sum stats: up-regulation

rank.sum.up <- get.rank.sum(ranks.up.mx.2) %>% 
  get.rank.sum.stats(.)
Rank Product analysis for paired case 
 

 done  
# class(rank.sum.up) # "data.frame"
# dim(rank.sum.up) # 12786     4
top.10.up <- rank.sum.up %>% 
  arrange(Rank.Sum) %>% 
  head(., 10) 
  # knitr::kable(., row.names = F, align=rep("l", 4), format = "html", escape = F) %>% 
  # kableExtra::kable_styling("striped", full_width = T)
top.10.up

` ### Save top 10 up-regulated genes

out.table.up <- paste0("data_out/",this.script, ".top.up.txt")
out.table.up
[1] "data_out/rank_product_01g.3NA.Rmd.top.up.txt"
write.table(top.10.up, file = out.table.up, sep = "\t")

Heatmaps for top regulated genes

(Datanovia Tutorial)

get.heatmap <- function(mx, heat.cols, ha) {
  ComplexHeatmap::Heatmap(
  mx, name = "Rank",
  col = heat.cols,
  top_annotation = ha,
  cluster_rows = FALSE,
  show_row_names = FALSE,
  show_column_names = TRUE
  )
}

Heatmap: 100 most down-regulated genes

# nrow(subset(rank.sum.up, P.Val.Adj <= 0.05)) # 594
# nrow(subset(rank.sum.up, P.Val.Adj <= 0.01)) # 317
down.100 <- dplyr::arrange(rank.sum.down, P.Val.Adj, Rank.Sum) %>% 
  head(.,100) %>% .$Gene
# down.100
mx.down.100 <- ranks.down.mx.2[down.100,] %>% 
  set_colnames(names(all.data.list))
 colnames(mx.down.100) <- str_replace(colnames(mx.down.100), "_DC", " (DC)")
 colnames(mx.down.100) <- str_replace(colnames(mx.down.100), "_MP", " (MP)")
colnames(mx.down.100) <- str_replace(colnames(mx.down.100), ".MF1", " (MF1)")
colnames(mx.down.100) <- str_replace(colnames(mx.down.100), ".MF2", " (MF2)")
# dim(mx.down.100) # 100   5
head(mx.down.100)
       GSE103092 GSE34151 A-BUGS-23 (DC) A-BUGS-23 (MP) GSE64179
MS4A6A       302      2.0              4              6       17
ADORA3       185      4.0            532             24        4
TREM2        195     58.5             12             37       62
CD36         140    139.0             58              1      298
S100A4       284     68.5             75             86       54
FZD2         511     37.5            175             95      313
       GSE67427 GSE148731 (MF1) GSE148731 (MF2)
MS4A6A      295               4               3
ADORA3       22             129             129
TREM2        88             339             237
CD36        167             299             414
S100A4      897              49             507
FZD2        400             265             314

Save top 100 up-regulated genes

out.file <- paste("data_out", stringr::str_replace(this.script,"\\.[Rr][Mm][Dd]$", ".down.100.txt"), sep="/")

out.file
[1] "data_out/rank_product_01g.3NA.down.100.txt"
cat(down.100, file = out.file, sep = "\n")
down.100.df <- as.data.frame(t(mx.down.100))
# dim(down.100.df) # 5 100

row.names(down.100.df) <- names(all.data.list)

down.100.df <- as.data.frame(t(mx.down.100)) %>% 
  set_rownames(names(all.data.list)) %>% 
  tibble::rownames_to_column("Dataset") %>% 
  dplyr::mutate(Technology = "Microarray") %>%
  dplyr::mutate(Cell.type = "Macrophages") %>%
  dplyr::select(Dataset, Technology, Cell.type, dplyr::everything())
down.100.df$Technology[down.100.df$Dataset %in% names(rna.seq.data.list)] <- "RNA-seq"
down.100.df$Cell.type[down.100.df$Dataset %in% c("GSE64179", "A-BUGS-23_DC", "GSE34151")] <- "Dendritic cells"
down.100.df[,1:6]
# help(HeatmapAnnotation, package="ComplexHeatmap")
col.down <- list(Technology = c("Microarray" = "Darkblue", "RNA-seq" = "lightblue"),
            Cell.type = c("Macrophages" = "grey", "Dendritic cells" = "black"))
ha.down <- ComplexHeatmap::HeatmapAnnotation(
  Technology = down.100.df$Technology,
  Cell.type = down.100.df$Cell.type,
  col=col.down
)
rm(col.down)
# "YlOrRd", "YlOrBr", "YlGnBu"*, "PuBuGn", "YlGn", "RdPu"-, "PuRd"-, "BuGn"
# "Blues", "Greens", "Purples", "Oranges"
heat.cols <- colorRampPalette(RColorBrewer::brewer.pal(7, "Oranges"))(256) %>% rev
get.heatmap(mx.down.100, heat.cols, ha.down)

Save heatmap for down-regulated genes

out.file.map.down <- paste("data_out",
                           str_replace(this.script,"\\.[Rr][Mm][Dd]$", ".map.down.pdf"),
                           sep="/")
out.file.map.down
[1] "data_out/rank_product_01g.3NA.map.down.pdf"
pdf(file = out.file.map.down, width=6, height=8.5)
get.heatmap(mx.down.100, heat.cols, ha.down)
dev.off()
null device 
          1 

Heatmap: 100 most up-regulated genes

up.100 <- dplyr::arrange(rank.sum.up, P.Val.Adj, Rank.Sum) %>% 
  head(.,100) %>% .$Gene
head(up.100)
[1] "CCL20"   "CCR7"    "IL1B"    "IDO1"    "TNFAIP6" "IL6"    
mx.up.100 <- ranks.up.mx.2[up.100,] %>% 
  set_colnames(names(all.data.list))
colnames(mx.down.100) <- str_replace(colnames(mx.down.100), "_DC", " (DC)")
colnames(mx.down.100) <- str_replace(colnames(mx.down.100), "_MP", " (MP)")
colnames(mx.down.100) <- str_replace(colnames(mx.down.100), ".MF1", " (MF1)")
colnames(mx.down.100) <- str_replace(colnames(mx.down.100), ".MF2", " (MF2)")
 dim(mx.up.100) # 100   6
[1] 100   8
head(mx.up.100)
        GSE103092 GSE34151 A-BUGS-23_DC A-BUGS-23_MP GSE64179
CCL20          53      8.0            5            2        7
CCR7            1      4.0           25           16       41
IL1B           48     11.0           18           59       36
IDO1          167      1.0           22           10       12
TNFAIP6        17    176.0           50           50       45
IL6           131    129.5           64            6       46
        GSE67427 GSE148731.MF1 GSE148731.MF2
CCL20          3          13.5          13.5
CCR7           4          17.0          17.0
IL1B          37          54.0           2.0
IDO1          72          50.0          50.0
TNFAIP6       43          67.0          67.0
IL6           17          70.0          70.0

Save top 100 up-regulated genes

out.file <- paste("data_out", stringr::str_replace(this.script,"\\.[Rr][Mm][Dd]$", ".up.100.txt"), sep="/")

out.file
[1] "data_out/rank_product_01g.3NA.up.100.txt"
cat(up.100, file = out.file, sep = "\n")
up.100.df <- as.data.frame(t(mx.up.100))
# dim(down.100.df) # 5 100
row.names(up.100.df) <- names(all.data.list)

up.100.df <- as.data.frame(t(mx.up.100)) %>% 
  set_rownames(names(all.data.list)) %>% 
  tibble::rownames_to_column("Dataset") %>% 
  dplyr::mutate(Technology = "Microarray") %>%
  dplyr::mutate(Cell.type = "Macrophages") %>%
  dplyr::select(Dataset, Technology, Cell.type, dplyr::everything())
up.100.df$Technology[up.100.df$Dataset %in% names(rna.seq.data.list)] <- "RNA-seq"
up.100.df$Cell.type[up.100.df$Dataset %in% c("GSE64179", "A-BUGS-23_DC","GSE34151")] <- "Dendritic cells"
col.up <- list(Technology = c("Microarray" = "Darkblue", "RNA-seq" = "lightblue"),
            Cell.type = c("Macrophages" = "grey", "Dendritic cells" = "black"))
ha.up <- ComplexHeatmap::HeatmapAnnotation(
  Technology = up.100.df$Technology,
  Cell.type = up.100.df$Cell.type,
  col=col.up
)
heat.cols <- colorRampPalette(RColorBrewer::brewer.pal(7, "Oranges"))(256) %>% rev
get.heatmap(mx.up.100, heat.cols, ha.up)

Save heatmap for up-regulated genes

out.file.map.up <- paste("data_out", stringr::str_replace(this.script,"\\.[Rr][Mm][Dd]$", ".map.up.pdf"), sep="/")
out.file.map.up
[1] "data_out/rank_product_01g.3NA.map.up.pdf"
pdf(file = out.file.map.up, width=6, height=8.5)
get.heatmap(mx.up.100, heat.cols, ha.up)
dev.off()
null device 
          1 

Save genes

Save top down-regulated genes

gs.down.01 <- rank.sum.down %>% 
  dplyr::filter(P.Val.Adj <= 0.01) %>% 
  use_series("Gene")
 length(gs.down.01) # 824

[1] 824

cat("Number of consistently <b><i>down</i></b>regulated genes (p <= 0.01):<b>", length(gs.down.01), "</b>")

Number of consistently downregulated genes (p <= 0.01): 824

out.file.down.01 <- paste0("data_out/", this.script, ".down.01.txt")
cat(gs.down.01, sep="\n", file=out.file.down.01)
cat("File saved:", out.file.down.01)
File saved: data_out/rank_product_01g.3NA.Rmd.down.01.txt

Save down-regulated genes with adjusted p value <= 0.001

gs.down.001 <- rank.sum.down %>% 
  dplyr::filter(P.Val.Adj <= 0.001) %>% 
  use_series("Gene")
 length(gs.down.001) # 299
[1] 299
cat("Number of consistently <b><i>down</i></b>regulated genes (p <= 0.001):<b>", length(gs.down.001), "</b>")
Number of consistently <b><i>down</i></b>regulated genes (p <= 0.001):<b> 299 </b>
out.file.down.001 <- paste0("data_out/", this.script, ".down.001.txt")
cat(gs.down.001, sep="\n", file=out.file.down.001)
cat("File saved:", out.file.down.001)
File saved: data_out/rank_product_01g.3NA.Rmd.down.001.txt

Save top up-regulated genes

gs.up.01 <- rank.sum.up %>% 
  dplyr::filter(P.Val.Adj <= 0.01) %>% 
  use_series("Gene")
length(gs.up.01) # 912

[1] 912

cat("Number of consistently <b><i>up</i></b>regulated genes (p <= 0.01):<b>", length(gs.up.01), "</b>")

Number of consistently upregulated genes (p <= 0.01): 912

out.file.up.01 <- paste0("data_out/", this.script, ".up.01.txt")
cat(gs.up.01, sep="\n", file=out.file.up.01)
cat("File saved:", out.file.up.01)
File saved: data_out/rank_product_01g.3NA.Rmd.up.01.txt

Save up-regulated genes with adjusted p value <= 0.001

gs.up.001 <- rank.sum.up %>% 
  dplyr::filter(P.Val.Adj <= 0.001) %>% 
  use_series("Gene")
length(gs.up.001) # 479
[1] 479
cat("Number of consistently <b><i>up</i></b>regulated genes (p <= 0.001):<b>", length(gs.up.001), "</b>")
Number of consistently <b><i>up</i></b>regulated genes (p <= 0.001):<b> 479 </b>
out.file.up.001 <- paste0("data_out/", this.script, ".up.001.txt")
cat(gs.up.001, sep="\n", file=out.file.up.001)
cat("File saved:", out.file.up.001)
File saved: data_out/rank_product_01g.3NA.Rmd.up.001.txt

Session info

Sys.time()
[1] "2020-09-09 12:00:33 BST"
# sessionInfo()
devtools::session_info()
─ Session info ──────────────────────────────────────────────────

─ Packages ──────────────────────────────────────────────────────
 package        * version date       lib source        
 assertthat       0.2.1   2019-03-21 [2] CRAN (R 3.5.3)
 backports        1.1.4   2019-04-10 [2] CRAN (R 3.5.3)
 BiocManager      1.30.10 2019-11-16 [1] CRAN (R 3.5.3)
 callr            3.4.3   2020-03-28 [1] CRAN (R 3.5.3)
 cellranger       1.1.0   2016-07-27 [1] CRAN (R 3.5.3)
 circlize         0.4.10  2020-06-15 [1] CRAN (R 3.5.3)
 cli              2.0.2   2020-02-28 [1] CRAN (R 3.5.3)
 colorspace       1.4-1   2019-03-18 [2] CRAN (R 3.5.3)
 ComplexHeatmap   1.20.0  2018-10-30 [1] Bioconductor  
 crayon           1.3.4   2017-09-16 [2] CRAN (R 3.5.3)
 desc             1.2.0   2018-05-01 [1] CRAN (R 3.5.3)
 devtools       * 2.3.0   2020-04-10 [1] CRAN (R 3.5.3)
 digest           0.6.25  2020-02-23 [1] CRAN (R 3.5.3)
 dplyr          * 1.0.0   2020-05-29 [1] CRAN (R 3.5.3)
 ellipsis         0.3.1   2020-05-15 [1] CRAN (R 3.5.3)
 evaluate         0.14    2019-05-28 [2] CRAN (R 3.5.3)
 fansi            0.4.0   2018-10-05 [2] CRAN (R 3.5.3)
 farver           2.0.3   2020-01-16 [1] CRAN (R 3.5.3)
 fs               1.4.2   2020-06-30 [1] CRAN (R 3.5.3)
 generics         0.0.2   2018-11-29 [1] CRAN (R 3.5.3)
 GetoptLong       1.0.2   2020-07-06 [1] CRAN (R 3.5.3)
 GlobalOptions    0.1.2   2020-06-10 [1] CRAN (R 3.5.3)
 glue             1.4.1   2020-05-13 [1] CRAN (R 3.5.3)
 gmp            * 0.6-0   2020-06-09 [1] CRAN (R 3.5.3)
 highr            0.8     2019-03-20 [2] CRAN (R 3.5.3)
 hms              0.5.3   2020-01-08 [1] CRAN (R 3.5.3)
 htmltools        0.5.0   2020-06-16 [1] CRAN (R 3.5.3)
 httr             1.4.1   2019-08-05 [1] CRAN (R 3.5.3)
 kableExtra     * 1.1.0   2019-03-16 [1] CRAN (R 3.5.3)
 knitr          * 1.25    2019-09-18 [2] CRAN (R 3.5.3)
 lifecycle        0.2.0   2020-03-06 [1] CRAN (R 3.5.3)
 magrittr       * 1.5     2014-11-22 [2] CRAN (R 3.5.3)
 memoise          1.1.0   2017-04-21 [1] CRAN (R 3.5.3)
 munsell          0.5.0   2018-06-12 [2] CRAN (R 3.5.3)
 packrat          0.5.0   2018-11-14 [1] CRAN (R 3.5.3)
 pillar           1.4.5   2020-07-09 [1] CRAN (R 3.5.3)
 pkgbuild         1.0.8   2020-05-07 [1] CRAN (R 3.5.3)
 pkgconfig        2.0.2   2018-08-16 [2] CRAN (R 3.5.3)
 pkgload          1.1.0   2020-05-29 [1] CRAN (R 3.5.3)
 prettyunits      1.0.2   2015-07-13 [2] CRAN (R 3.5.3)
 processx         3.4.1   2019-07-18 [2] CRAN (R 3.5.3)
 ps               1.3.0   2018-12-21 [2] CRAN (R 3.5.3)
 purrr          * 0.3.4   2020-04-17 [1] CRAN (R 3.5.3)
 R6               2.4.0   2019-02-14 [2] CRAN (R 3.5.3)
 RankProd       * 3.8.0   2018-10-30 [1] Bioconductor  
 RColorBrewer   * 1.1-2   2014-12-07 [2] CRAN (R 3.5.3)
 Rcpp             1.0.2   2019-07-25 [2] CRAN (R 3.5.3)
 readr            1.3.1   2018-12-21 [1] CRAN (R 3.5.3)
 readxl         * 1.3.1   2019-03-13 [1] CRAN (R 3.5.3)
 rematch          1.0.1   2016-04-21 [2] CRAN (R 3.5.3)
 remotes          2.1.1   2020-02-15 [1] CRAN (R 3.5.3)
 rjson            0.2.20  2018-06-08 [1] CRAN (R 3.5.3)
 rlang            0.4.7   2020-07-09 [1] CRAN (R 3.5.3)
 rmarkdown        2.3     2020-06-18 [1] CRAN (R 3.5.3)
 Rmpfr          * 0.8-1   2020-01-24 [1] CRAN (R 3.5.3)
 rprojroot        1.3-2   2018-01-03 [1] CRAN (R 3.5.3)
 rstudioapi     * 0.11    2020-02-07 [1] CRAN (R 3.5.3)
 rvest            0.3.5   2019-11-08 [1] CRAN (R 3.5.3)
 scales         * 1.1.1   2020-05-11 [1] CRAN (R 3.5.3)
 sessioninfo      1.1.1   2018-11-05 [1] CRAN (R 3.5.3)
 shape            1.4.4   2018-02-07 [1] CRAN (R 3.5.3)
 stringi          1.4.3   2019-03-12 [2] CRAN (R 3.5.3)
 stringr        * 1.4.0   2019-02-10 [2] CRAN (R 3.5.3)
 testthat         2.3.2   2020-03-02 [1] CRAN (R 3.5.3)
 tibble         * 3.0.2   2020-07-07 [1] CRAN (R 3.5.3)
 tidyr          * 1.1.0   2020-05-20 [1] CRAN (R 3.5.3)
 tidyselect       1.1.0   2020-05-11 [1] CRAN (R 3.5.3)
 usethis        * 1.6.1   2020-04-29 [1] CRAN (R 3.5.3)
 vctrs            0.3.1   2020-06-05 [1] CRAN (R 3.5.3)
 viridisLite      0.3.0   2018-02-01 [2] CRAN (R 3.5.3)
 webshot          0.5.1   2018-09-28 [2] CRAN (R 3.5.3)
 withr            2.1.2   2018-03-15 [2] CRAN (R 3.5.3)
 xfun             0.9     2019-08-21 [2] CRAN (R 3.5.3)
 xml2             1.3.2   2020-04-23 [1] CRAN (R 3.5.3)

[1] /homes/homedirs20/sghms/student/users/m1902840/R/x86_64-pc-linux-gnu-library/3.5
[2] /usr/local/lib64/R/library
knitr::purl(this.script, documentation = 0)


processing file: rank_product_01g.3NA.Rmd

  |                                                                       
  |                                                                 |   0%
  |                                                                       
  |                                                                 |   1%
  |                                                                       
  |.                                                                |   1%
  |                                                                       
  |.                                                                |   2%
  |                                                                       
  |..                                                               |   2%
  |                                                                       
  |..                                                               |   3%
  |                                                                       
  |...                                                              |   4%
  |                                                                       
  |...                                                              |   5%
  |                                                                       
  |....                                                             |   6%
  |                                                                       
  |.....                                                            |   7%
  |                                                                       
  |.....                                                            |   8%
  |                                                                       
  |......                                                           |   9%
  |                                                                       
  |......                                                           |  10%
  |                                                                       
  |.......                                                          |  10%
  |                                                                       
  |.......                                                          |  11%
  |                                                                       
  |........                                                         |  12%
  |                                                                       
  |........                                                         |  13%
  |                                                                       
  |.........                                                        |  13%
  |                                                                       
  |.........                                                        |  14%
  |                                                                       
  |..........                                                       |  15%
  |                                                                       
  |..........                                                       |  16%
  |                                                                       
  |...........                                                      |  16%
  |                                                                       
  |...........                                                      |  17%
  |                                                                       
  |............                                                     |  18%
  |                                                                       
  |............                                                     |  19%
  |                                                                       
  |.............                                                    |  20%
  |                                                                       
  |..............                                                   |  21%
  |                                                                       
  |..............                                                   |  22%
  |                                                                       
  |...............                                                  |  23%
  |                                                                       
  |...............                                                  |  24%
  |                                                                       
  |................                                                 |  24%
  |                                                                       
  |................                                                 |  25%
  |                                                                       
  |.................                                                |  25%
  |                                                                       
  |.................                                                |  26%
  |                                                                       
  |.................                                                |  27%
  |                                                                       
  |..................                                               |  27%
  |                                                                       
  |..................                                               |  28%
  |                                                                       
  |...................                                              |  29%
  |                                                                       
  |....................                                             |  30%
  |                                                                       
  |....................                                             |  31%
  |                                                                       
  |.....................                                            |  32%
  |                                                                       
  |.....................                                            |  33%
  |                                                                       
  |......................                                           |  34%
  |                                                                       
  |.......................                                          |  35%
  |                                                                       
  |.......................                                          |  36%
  |                                                                       
  |........................                                         |  36%
  |                                                                       
  |........................                                         |  37%
  |                                                                       
  |........................                                         |  38%
  |                                                                       
  |.........................                                        |  38%
  |                                                                       
  |.........................                                        |  39%
  |                                                                       
  |..........................                                       |  39%
  |                                                                       
  |..........................                                       |  40%
  |                                                                       
  |...........................                                      |  41%
  |                                                                       
  |...........................                                      |  42%
  |                                                                       
  |............................                                     |  43%
  |                                                                       
  |.............................                                    |  44%
  |                                                                       
  |.............................                                    |  45%
  |                                                                       
  |..............................                                   |  46%
  |                                                                       
  |..............................                                   |  47%
  |                                                                       
  |...............................                                  |  47%
  |                                                                       
  |...............................                                  |  48%
  |                                                                       
  |................................                                 |  49%
  |                                                                       
  |................................                                 |  50%
  |                                                                       
  |.................................                                |  50%
  |                                                                       
  |.................................                                |  51%
  |                                                                       
  |..................................                               |  52%
  |                                                                       
  |..................................                               |  53%
  |                                                                       
  |...................................                              |  53%
  |                                                                       
  |...................................                              |  54%
  |                                                                       
  |....................................                             |  55%
  |                                                                       
  |....................................                             |  56%
  |                                                                       
  |.....................................                            |  57%
  |                                                                       
  |......................................                           |  58%
  |                                                                       
  |......................................                           |  59%
  |                                                                       
  |.......................................                          |  60%
  |                                                                       
  |.......................................                          |  61%
  |                                                                       
  |........................................                         |  61%
  |                                                                       
  |........................................                         |  62%
  |                                                                       
  |.........................................                        |  62%
  |                                                                       
  |.........................................                        |  63%
  |                                                                       
  |.........................................                        |  64%
  |                                                                       
  |..........................................                       |  64%
  |                                                                       
  |..........................................                       |  65%
  |                                                                       
  |...........................................                      |  66%
  |                                                                       
  |............................................                     |  67%
  |                                                                       
  |............................................                     |  68%
  |                                                                       
  |.............................................                    |  69%
  |                                                                       
  |.............................................                    |  70%
  |                                                                       
  |..............................................                   |  71%
  |                                                                       
  |...............................................                  |  72%
  |                                                                       
  |...............................................                  |  73%
  |                                                                       
  |................................................                 |  73%
  |                                                                       
  |................................................                 |  74%
  |                                                                       
  |................................................                 |  75%
  |                                                                       
  |.................................................                |  75%
  |                                                                       
  |.................................................                |  76%
  |                                                                       
  |..................................................               |  76%
  |                                                                       
  |..................................................               |  77%
  |                                                                       
  |...................................................              |  78%
  |                                                                       
  |...................................................              |  79%
  |                                                                       
  |....................................................             |  80%
  |                                                                       
  |.....................................................            |  81%
  |                                                                       
  |.....................................................            |  82%
  |                                                                       
  |......................................................           |  83%
  |                                                                       
  |......................................................           |  84%
  |                                                                       
  |.......................................................          |  84%
  |                                                                       
  |.......................................................          |  85%
  |                                                                       
  |........................................................         |  86%
  |                                                                       
  |........................................................         |  87%
  |                                                                       
  |.........................................................        |  87%
  |                                                                       
  |.........................................................        |  88%
  |                                                                       
  |..........................................................       |  89%
  |                                                                       
  |..........................................................       |  90%
  |                                                                       
  |...........................................................      |  90%
  |                                                                       
  |...........................................................      |  91%
  |                                                                       
  |............................................................     |  92%
  |                                                                       
  |............................................................     |  93%
  |                                                                       
  |.............................................................    |  94%
  |                                                                       
  |..............................................................   |  95%
  |                                                                       
  |..............................................................   |  96%
  |                                                                       
  |...............................................................  |  97%
  |                                                                       
  |...............................................................  |  98%
  |                                                                       
  |................................................................ |  98%
  |                                                                       
  |................................................................ |  99%
  |                                                                       
  |.................................................................|  99%
  |                                                                       
  |.................................................................| 100%
output file: rank_product_01g.3NA.R
[1] "rank_product_01g.3NA.R"
LS0tCnRpdGxlOiAiTWV0YSBhbmFseXNpcyAtIHJhbmsgcHJvZHVjdCBtZXRob2QiCmF1dGhvcjogJzk0MDEnCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vCiAgICBmaWdfY2FwdGlvbjogeWVzCiAgICB0aGVtZTogc2FuZHN0b25lCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB5ZXMKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnMycKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogJzMnCiAgICBkZl9wcmludDogcGFnZWQKLS0tCiAgCjxzdHlsZT4KaDEge2JhY2tncm91bmQ6IGRhcmtibHVlO2NvbG9yOiB3aGl0ZTtwYWRkaW5nLWxlZnQ6IDdweDt9CmgyIHtjb2xvcjogZGFya2JsdWU7fQouY29kZS1mb2xkaW5nLWJ0biB7ZGlzcGxheTogbm9uZTt9Cjwvc3R5bGU+CiAgCjxzY3JpcHQ+CiAgZnVuY3Rpb24gc2hvd19zcGFuKGlkKSB7CiAgICB2YXIgeCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKTsKICAgIGlmICh4LnN0eWxlLmRpc3BsYXkgPT09ICdub25lJykgewogICAgICB4LnN0eWxlLmRpc3BsYXkgPSAnaW5saW5lJzsKICAgIH0gZWxzZSB7CiAgICAgIHguc3R5bGUuZGlzcGxheSA9ICdub25lJzsKICAgIH0KICB9CmZ1bmN0aW9uIG15RnVuY3Rpb24oaWQpIHsKICB2YXIgeCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKGlkKTsKICBpZiAoeC5zdHlsZS5kaXNwbGF5ID09PSAnbm9uZScpIHsKICAgIHguc3R5bGUuZGlzcGxheSA9ICdibG9jayc7CiAgfSBlbHNlIHsKICAgIHguc3R5bGUuZGlzcGxheSA9ICdub25lJzsKICB9Cn0KPC9zY3JpcHQ+CgojIyBPYmplY3RpdmUgIAoKUGVyZm9ybSBtZXRhIGFuYWx5c2lzIG9mIFRCIG1pY3JvYXJyYXkgQU5EIFJOQS1zZXEgZGF0YSB1c2luZyB0aGUgUmFuayBTdW0gYXBwcm9hY2guICAKT3ZlcnZpZXc6ICAKCjEuIE1pY3JvYXJyYXkgZGF0YTogbG9hZCBsb2coZm9sZCBjaGFuZ2UpIGRhdGEgdGhhdCBEaWFuYSBnZW5lcmF0ZWQgd2l0aCBHZW8yciAgCjIuIFJOQS1zZXEgZGF0YTogbG9hZCBwdWJsaXNoZWQgdGFibGVzICAKMy4gQWdncmVnYXRlIG1pY3JvYXJyYXkgZGF0YSBieSBnZW5lIHN5bWJvbDsgd2hlcmUgdGhlIHNhbWUgZ2VuZSBpcyByZXByZXNlbnRlZCBieSBzZXZlcmFsIHByb2JlcywgY2FsY3VsYXRlIHRoZSBfKiptZWRpYW4qKl8gbG9nKGZvbGQgY2hhbmdlKSAgCjQuIE1lcmdlIGZvbGQgY2hhbmdlIGRhdGEgZnJvbSBhbGwgZGF0YXNldHMgaW50byBhIHNpbmdsZSBtYXRyaXggKCoqb3V0ZXIgam9pbioqKSAgCjUuIEZpbHRlciBvdXQgZ2VuZXMgd2l0aCBtaXNzaW5nIHZhbHVlcyAoTkEpOiBrZWVwIG9ubHkgZ2VuZXMgd2l0aCBhIG1heGltdW0gbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzICAKNi4gSW4gZWFjaCBkYXRhc2V0IHJhbmsgZ2VuZXMgYnkgZm9sZCBjaGFuZ2UgKGNvbnZlcnQgZm9sZCBjaGFuZ2VzIHRvIHJhbmtzOyBOQXMgc3RheSBOQXM7IG1heGltdW0gcmFua3Mgd2lsbCB2YXJ5IGJ5IE5BIGNvdW50KSAgCjcuIFJlcGxhY2UgTkFzIHdpdGggYXZlcmFnZSByYW5rcyBpbiB0aGUgb3RoZXIgZGF0YXNldHMgIAo4LiBOb3cgcmFuayBhZ2FpbiBzbyBudW1iZXIgb2YgcmFua3MgaW4gZWFjaCBkYXRhc2V0IGlzIHRoZSBzYW1lICAKOS4gQ2FsY3VsYXRlICoqcmFuayBzdW0qKiBzdGF0aXN0aWNzIGFuZCBwIHZhbHVlcyB1c2luZyBSYW5rUHJvZCBwYWNrYWdlICAKMTAuIEdlbmVyYXRlIGhlYXRtYXBzIG9mIHRvcCAxMDAgcmVndWxhdGVkIGdlbmVzICAKICAKYGBge3IgaW5jbHVkZT1GQUxTRX0KIyBBYm91dCA8c3R5bGU+IGNodW5rIGFib3ZlOgojICogcHJvdmlkZXMgY3VzdG9tIGZvcm1hdHRpbmcgZm9yIGxldmVsLTEgYW5kIGxldmVsLTIgaGVhZGVycwojICogaGlkZXMgdGhlICdjb2RlJyBidXR0b25zIG5leHQgdG8gZXZlcnkgY29kZSBjaHVuayBpbiB0aGUgaHRtbCBvdXRwdXQ7IGxlYXZlcyBvbmx5IG9uZSAnY29kZScgYnV0dG9uIGF0IHRoZSB0b3Agb2YgdGhlIGh0bWwgbm90ZWJvb2suCgojIEFib3V0IHRoZSA8c2NyaXB0PiBjaHVuayBhdCB0aGUgdG9wIGFuZCBib3R0b20gb2YgdGhlIGZpbGU6CiMgKiBhbGxvd3MgdG8gaW5zZXJ0IDxkaXY+J3Mgd2hvc2UgY29udGVudCB2aXNiaWxpdHkgaXMgdG9nZ2xlZCB3aXRoIGEgYnV0dG9uLgojICogSSB1c2UgdGhpcyB0byBzaG93L2hpZGUgdGUgb3V0cHV0IG9mIHNlc3Npb25JbmZvKCkgYXQgdGhlIGVuZCBvZiB0aGUgc2NyaXB0IChzZWUgYmVsb3cpCmBgYAoKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CiMgUk1BUktET1dOIEhFTFAKIyBSbWFya2Rvd246CiMgaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLwoKIyBDaHVuayBvcHRpb25zOgojIGh0dHBzOi8veWlodWkubmFtZS9rbml0ci9vcHRpb25zCmBgYAoKYGBge3IgY2xlYW51cCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9CiMgQ0hFQ0sgUiBWRVJTSU9OCiMgc3RvcGlmbm90KFIudmVyc2lvbi5zdHJpbmcgPT0gIlIgdmVyc2lvbiAzLjQuMyAoMjAxNy0xMS0zMCkiKQoKIyBDTEVBTlVQCiMgQ2xlYXIgYWxsIHZhcmlhYmxlczoKcm0obGlzdD1scyhhbGw9VFJVRSkpCgojIFVubG9hZCBjdXJyZW50IHBhY2thZ2VzOgojIGlmICghaXMubnVsbChuYW1lcyh1dGlsczo6c2Vzc2lvbkluZm8oKVtbIm90aGVyUGtncyJdXSkpKSBwYWNtYW46OnBfdW5sb2FkKCJhbGwiKQpgYGAKCmBgYHtyIHNldHVwfQojIEtOSVRSIERFRkFVTFRTCmtuaXRyOjpvcHRzX2NodW5rJHNldChldmFsID0gVFJVRSwgZmlnLmhlaWdodCA9IDEwKQpgYGAKCmBgYHtyIHBhY2thZ2VzLCByZXN1bHRzPSJoaWRlIiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0V9CiMgTE9BRCBQQUNLQUdFUzoKaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkNvbXBsZXhIZWF0bWFwIiwgcXVpZXRseSA9IFRSVUUpKSBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiQ29tcGxleEhlYXRtYXAiKQppZiAoIXJlcXVpcmUoc3RyaW5ncikpIGluc3RhbGwucGFja2FnZXMoInN0cmluZ3IiKQppZiAoIXJlcXVpcmUocnN0dWRpb2FwaSkpIGluc3RhbGwucGFja2FnZXMoInJzdHVkaW9hcGkiKQppZiAoIXJlcXVpcmUodGlkeXIpKSBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5ciIpCmlmICghcmVxdWlyZSh0aWJibGUpKSBpbnN0YWxsLnBhY2thZ2VzKCJ0aWJibGUiKQppZiAoIXJlcXVpcmUocmVhZHhsKSkgaW5zdGFsbC5wYWNrYWdlcygicmVhZHhsIikKaWYgKCFyZXF1aXJlKHB1cnJyKSkgaW5zdGFsbC5wYWNrYWdlcygicHVycnIiKQppZiAoIXJlcXVpcmUoUkNvbG9yQnJld2VyKSkgaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikKaWYgKCFyZXF1aXJlKGRldnRvb2xzKSkgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQppZiAoIXJlcXVpcmUobWFncml0dHIpKSBpbnN0YWxsLnBhY2thZ2VzKCJtYWdyaXR0ciIpCmlmICghcmVxdWlyZShkcGx5cikpIGluc3RhbGwucGFja2FnZXMoImRwbHlyIikKaWYgKCFyZXF1aXJlKHNjYWxlcykpIGluc3RhbGwucGFja2FnZXMoInNjYWxlcyIpCmlmICghcmVxdWlyZShrbml0cikpIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikKaWYgKCFyZXF1aXJlKGthYmxlRXh0cmEpKSBpbnN0YWxsLnBhY2thZ2VzKCJrYWJsZUV4dHJhIikKaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJSYW5rUHJvZCIsIHF1aWV0bHkgPSBUUlVFKSkgQmlvY01hbmFnZXI6Omluc3RhbGwoIlJhbmtQcm9kIikKCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoUmFua1Byb2QpCmxpYnJhcnkoc3RyaW5ncikKYGBgCgoKYGBge3Igc2V0dXBfc2NyaXB0LCBldmFsPVRSVUUsIHJlc3VsdHM9J2hpZGUnfQojIEdFVCBDVVJSRU5UIFNDUklQVCBOQU1FCih0aGlzLnNjcmlwdCA8LSByc3R1ZGlvYXBpOjpnZXRBY3RpdmVEb2N1bWVudENvbnRleHQoKSAlPiUgLiRwYXRoICU+JSBiYXNlbmFtZSkKZ2V0d2QoKQpsaXN0LmZpbGVzKCkKc3RvcGlmbm90KHRoaXMuc2NyaXB0ICE9ICIiKQpgYGAKCiMjIERhdGE6IG1pY3JvYXJyYXkgc3R1ZGllcyAgICAKCiMjIyBHZW8yciBkYXRhIGZpbGVzICAKYGBge3IgcmVzdWx0cz0iaGlkZSJ9CmdlbzJyLmRhdGEuZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoPSIuLi9zb3VyY2VfZGF0YSIsIHJlY3Vyc2l2ZSA9IFQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybj0iXkdTRS4qR2VvMnIuKnR4dCQiLCBmdWxsLm5hbWVzID0gVCkKZ2VvMnIuZGF0YS5maWxlcyA8LSBncmVwKHBhdHRlcm4gPSAiR1NFMTA4MzYzIiwgZ2VvMnIuZGF0YS5maWxlcywgaW52ZXJ0ID0gVCwgdmFsdWUgPSBUKQpjYXQoYmFzZW5hbWUoZ2VvMnIuZGF0YS5maWxlcyksIHNlcD0iPGJyPlxuIikKYGBgCgpgYGB7ciByZXN1bHRzPSJoaWRlIn0KdGFpbGxldXguZGF0YS5maWxlcyA8LSBsaXN0LmZpbGVzKHBhdGg9Ii4uL3NvdXJjZV9kYXRhIiwgcmVjdXJzaXZlID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm49Iip0YWlsbGV1eC4qdHh0IiwgZnVsbC5uYW1lcyA9IFQpCmNhdChiYXNlbmFtZSh0YWlsbGV1eC5kYXRhLmZpbGVzKSwgc2VwPSI8YnI+XG4iKQoKYGBgCgpgYGB7ciByZXN1bHRzPSJhc2lzIn0KbXVhcnJheS5kYXRhLmZpbGVzIDwtIGMoZ2VvMnIuZGF0YS5maWxlcywgdGFpbGxldXguZGF0YS5maWxlcykKbXVhcnJheS5kYXRhLmZpbGVzIDwtIGdyZXAoIkdTRTI5NzMxIiwgbXVhcnJheS5kYXRhLmZpbGVzLCBpbnZlcnQgPSBULCB2YWx1ZT1UKQpjYXQoYmFzZW5hbWUobXVhcnJheS5kYXRhLmZpbGVzKSwgc2VwPSI8YnI+XG4iKQpgYGAKIyMjIExvYWQgR2VvMnIgZGF0YSAgCmBgYHtyIHJlc3VsdHM9ImhpZGUifQpnZW8yci5kYXRhLmxpc3QgPC0gbGFwcGx5KG11YXJyYXkuZGF0YS5maWxlcywgZnVuY3Rpb24oZikgewogIGEgPC0gcmVhZC50YWJsZShmLCBzZXA9Ilx0IiwgaGVhZGVyPVQsIHF1b3RlPSciJywgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiAgbmFtZXMoYSlbZ3JlcCgic3ltYm9sIiwgbmFtZXMoYSksIGlnbm9yZS5jYXNlID0gVCldIDwtICJHZW5lLnN5bWJvbCIKICBuYW1lcyhhKSA8LSBzdHJpbmdyOjpzdHJfcmVtb3ZlKG5hbWVzKGEpLCAiXlhcXC4iKQogIG5hbWVzKGEpIDwtIHN0cmluZ3I6OnN0cl9yZW1vdmUobmFtZXMoYSksICJcXC4kIikKICBhCn0pICU+JSAKICBzZXRfbmFtZXMoc3RyaW5ncjo6c3RyX3JlbW92ZV9hbGwoYmFzZW5hbWUobXVhcnJheS5kYXRhLmZpbGVzKSwgIlxcLnR4dCIpKQoKCm5hbWVzKGdlbzJyLmRhdGEubGlzdCkgPC0gc3RyaW5ncjo6c3RyX3JlbW92ZShuYW1lcyhnZW8yci5kYXRhLmxpc3QpLCAiXlgiKQpuYW1lcyhnZW8yci5kYXRhLmxpc3QpW2dyZXAoIkRDIiwgbmFtZXMoZ2VvMnIuZGF0YS5saXN0KSldIDwtICJBLUJVR1MtMjNfREMiCm5hbWVzKGdlbzJyLmRhdGEubGlzdClbZ3JlcCgiTVAiLCBuYW1lcyhnZW8yci5kYXRhLmxpc3QpKV0gPC0gIkEtQlVHUy0yM19NUCIKCiMgY2xhc3MoZ2VvMnIuZGF0YS5saXN0KSAjICJsaXN0IgojIGxlbmd0aChnZW8yci5kYXRhLmxpc3QpICMgNAojIHNhcHBseShnZW8yci5kYXRhLmxpc3QsIGNsYXNzKSAjICJkYXRhLmZyYW1lIgojIHNhcHBseShnZW8yci5kYXRhLmxpc3QsIG5yb3cpICU+JSB1bm5hbWUgIyAyOTEwMiA0NzIzMSAyNDUwMSAyNDUwMQpgYGAKCmBgYHtyfQpkYXRhLmZyYW1lKExpc3QuaXRlbSA9IG5hbWVzKGdlbzJyLmRhdGEubGlzdCksCiAgICAgICAgICAgY2xhc3MgPSBzYXBwbHkoZ2VvMnIuZGF0YS5saXN0LCBjbGFzcyksCiAgICAgICAgICAgQ29sdW1ucyA9IHNhcHBseShnZW8yci5kYXRhLmxpc3QsIG5jb2wpLAogICAgICAgICAgIFJvd3MgPSBjb21tYShzYXBwbHkoZ2VvMnIuZGF0YS5saXN0LCBucm93KSksCiAgICAgICAgICAgSGFzLkdlbmUuU3ltYm9scyA9IHNhcHBseShnZW8yci5kYXRhLmxpc3QsIGZ1bmN0aW9uKHgpIHthbnkoZ3JlcGwoIkdlbmUuc3ltYm9sIiwgbmFtZXMoeCkpKX0pCiAgICAgICAgICAgIyBVbmlxdWUuZ2VuZXMgPSBzYXBwbHkoZ2VvMnIuZGF0YS5saXN0LCBmdW5jdGlvbih4KSB7bGVuZ3RoKHVuaXF1ZSh4JCkpfSkKICAgICAgICAgICApICU+JSAKICBtdXRhdGUoSGFzLkdlbmUuU3ltYm9scyA9IGNlbGxfc3BlYyhIYXMuR2VuZS5TeW1ib2xzLCAiaHRtbCIsIGNvbG9yID0gaWZlbHNlKEhhcy5HZW5lLlN5bWJvbHMgPT0gIlRSVUUiLCAiYmxhY2siLCAicmVkIikpKSAlPiUgCiAga25pdHI6OmthYmxlKC4sIHJvdy5uYW1lcyA9IEYsIGFsaWduPWMoImwiLCAibCIsICJjIiwgImMiLCAiYyIpLCBmb3JtYXQgPSAiaHRtbCIsIGVzY2FwZSA9IEYpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpCmBgYAoKYGBge3IgcmVzdWx0cz0iaGlkZSJ9CiMgaGVhZChnZW8yci5kYXRhLmxpc3RbWzJdXSkKYGBgCgoKIyMjIEFnZ3JlZ2F0ZSBsb2dGQyBieSBnZW5lIHN5bWJvbCAgCmBgYHtyfQpnZW8uZm9sZC5jaGFuZ2UubGlzdCA8LSBsYXBwbHkoc2VxX2Fsb25nKGdlbzJyLmRhdGEubGlzdCksIGZ1bmN0aW9uKGksIGxpc3QubmFtZXMpIHsKICBkZiA8LSBnZW8yci5kYXRhLmxpc3RbW2ldXQogIHggPC0gZHBseXI6OnNlbGVjdChkZiwgR2VuZS5zeW1ib2wsIGxvZ0ZDKSAlPiUgCiAgICBkcGx5cjo6bXV0YXRlKEdlbmUuc3ltYm9sID0gaWZlbHNlKEdlbmUuc3ltYm9sPT0iIiwgTkEsIEdlbmUuc3ltYm9sKSkgJT4lIAogICAgdGlkeXI6OmRyb3BfbmEoLikgJT4lIAogICAgZHBseXI6Om11dGF0ZShsb2dGQyA9IGFzLm51bWVyaWMobG9nRkMpKSAlPiUgCiAgICBkcGx5cjo6bXV0YXRlKEdlbmUuc3ltYm9sID0gc3RyaW5ncjo6c3RyX3JlbW92ZV9hbGwoR2VuZS5zeW1ib2wsICdcXCInKSkgJT4lIAogICAgdGlkeXI6OmRyb3BfbmEoLikgJT4lIAogICAgZHBseXI6Omdyb3VwX2J5KEdlbmUuc3ltYm9sKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKGxvZ0ZDID0gbWVkaWFuKGxvZ0ZDKSwgLmdyb3Vwcz0iZHJvcCIpICU+JSAKICAgIHRpZHlyOjpkcm9wX25hKC4pICU+JSAKICAgIGFzLmRhdGEuZnJhbWUoLiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKICAjIG5hbWVzKHgpW25hbWVzKHgpID09ICJsb2dGQyJdIDwtIGxpc3QubmFtZXNbaV0KICB4Cn0sIGxpc3QubmFtZXMgPSBuYW1lcyhnZW8yci5kYXRhLmxpc3QpKSAlPiUgCiAgc2V0X25hbWVzKG5hbWVzKGdlbzJyLmRhdGEubGlzdCkpCm5hbWVzKGdlby5mb2xkLmNoYW5nZS5saXN0KSA8LSBzdHJpbmdyOjpzdHJfcmVtb3ZlKG5hbWVzKGdlby5mb2xkLmNoYW5nZS5saXN0KSwgIl9HZW8yci4qJCIpCmBgYAoKCmBgYHtyfQpkYXRhLmZyYW1lKAogIExpc3QuaXRlbSA9IG5hbWVzKGdlby5mb2xkLmNoYW5nZS5saXN0KSwKICBDbGFzcyA9IHNhcHBseShnZW8uZm9sZC5jaGFuZ2UubGlzdCwgZnVuY3Rpb24oeCkge3Bhc3RlKGNsYXNzKHgpLCBjb2xsYXBzZT0iLCIpfSksCiAgQ29sdW1ucyA9IHNhcHBseShnZW8uZm9sZC5jaGFuZ2UubGlzdCwgbmNvbCksCiAgUm93cyA9IGNvbW1hKHNhcHBseShnZW8uZm9sZC5jaGFuZ2UubGlzdCwgbnJvdykpLAogIER1cGxpY2F0ZWQuZ2VuZXMgPSBzYXBwbHkoZ2VvLmZvbGQuY2hhbmdlLmxpc3QsIGZ1bmN0aW9uKHgpIHthbnkoZHVwbGljYXRlZCh4JEdlbmUuc3ltYm9sKSl9KQopICU+JSAKICBrbml0cjo6a2FibGUoLiwgcm93Lm5hbWVzID0gRiwgYWxpZ249YygibCIsICJsIiwgImMiLCAiYyIsICJjIiksIGZvcm1hdCA9ICJodG1sIiwgZXNjYXBlID0gRikgJT4lIAogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVCkKYGBgCgojIyBEYXRhOiBSTkEtc2VxIHN0dWRpZXMgIApgYGB7cn0KIyA8YSBocmVmPSJodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlby9xdWVyeS9hY2MuY2dpP2FjYz1HU0U2NDE3OSI+R1NFNjQxNzk8L2E+CiMgaHR0cHM6Ly9wdWJtZWQubmNiaS5ubG0ubmloLmdvdi8yNjM5MjM2Ni8KYWRkLmdlby5oeXBlcmxpbmsgPC0gZnVuY3Rpb24oaWQpIHsKICBhIDwtICI8YSBocmVmPSdodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlby9xdWVyeS9hY2MuY2dpP2FjYz0iCiAgYiA8LSAiJz4iCiAgYyA8LSAiPC9hPiIKICByZXMgPC0gcGFzdGUwKGEsIGlkLCBiLCBpZCwgYykKICByZXMKfQoKYWRkLnB1Ym1lZC5oeXBlcmxpbmsgPC0gZnVuY3Rpb24oaWQpIHsKICBhIDwtICI8YSBocmVmPSdodHRwczovL3B1Ym1lZC5uY2JpLm5sbS5uaWguZ292LyIKICBiIDwtICIvJz4iCiAgYyA8LSAiPC9hPiIKICByZXMgPC0gcGFzdGUwKGEsIGlkLCBiLCBpZCwgYykKICByZXMKfQpgYGAKCgpgYGB7cn0KcnMuZGF0YXNldC50YWJsZSA8LSBkYXRhLmZyYW1lKAogIGM9YygiR1NFNjQxNzkiLCAiMjYzOTIzNjYiLCAiNiIsICJEZW5kcml0aWMgY2VsbHMiKSwKICBiPWMoIkdTRTY3NDI3IiwgIjI2NTg2MTc5IiwgIjgiLCAiTW9ub2N5dGUgZGVyaXZlZCBtYWNyb3BoYWdlcyIpLAogIGE9YygiR1NFMTQ4NzMxIiwgIjMyMzQxNDExIiwgIjYiLCAiTTEsIE0yIG1hY3JvcGhhZ2VzIikKKSAlPiUKICB0ICU+JSAKICBhcy5kYXRhLmZyYW1lKC4sIHN0cmluZ3NBc0ZhY3RvcnM9RikgJT4lIAogIHNldF9jb2xuYW1lcyhjKCJBY2Nlc3Npb24iLCAiUHVibWVkIiwgIlJlcGxpY2F0ZXMiLCAiQ2VsbC5UeXBlIikpICU+JSAKICBtdXRhdGUoQWNjZXNzaW9uPWFkZC5nZW8uaHlwZXJsaW5rKEFjY2Vzc2lvbikpICU+JSAKICBtdXRhdGUoUHVibWVkID0gYWRkLnB1Ym1lZC5oeXBlcmxpbmsoUHVibWVkKSkKa25pdHI6OmthYmxlKHJzLmRhdGFzZXQudGFibGUsIHJvdy5uYW1lcyA9IEYsIGVzY2FwZT1GLCBmb3JtYXQgPSAiaHRtbCIpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpCmBgYAoKCiMjIyBMb2FkIFJOQS1zZXEgZGF0YTogR1NFNjQxNzkgIApgYGB7ciBtZXNzYWdlPUZBTFNFfQojIGxpc3QuZmlsZXMoIi4uL3NvdXJjZV9kYXRhL3JuYV9zZXEvR1NFNjQxNzkiKQpybmEuc2VxLmRhdGEuZmlsZXMgPC0gbGlzdCgpCnJuYS5zZXEuZGF0YS5maWxlcyRHU0U2NDE3OSA8LSAiLi4vc291cmNlX2RhdGEvcm5hX3NlcS9HU0U2NDE3OS9QYWNpc18yMDE1X1RhYmxlUzZfREVHLnhsc3giCnN0b3BpZm5vdChmaWxlLmV4aXN0cyhybmEuc2VxLmRhdGEuZmlsZXMkR1NFNjQxNzkpKQpybmEuc2VxLmRhdGEubGlzdCA8LSBsaXN0KCkKcm5hLnNlcS5kYXRhLmxpc3QkR1NFNjQxNzkgPC0gcmVhZHhsOjpyZWFkX3hsc3gocGF0aD1ybmEuc2VxLmRhdGEuZmlsZXMkR1NFNjQxNzksIHJhbmdlID0gIkIzOkMxODgxOCIpICU+JQpzZXRfY29sbmFtZXMoYygiR2VuZS5zeW1ib2wiLCAibG9nRkMiKSkgJT4lIAptdXRhdGUoR2VuZS5zeW1ib2wgPSB0b3VwcGVyKEdlbmUuc3ltYm9sKSkgJT4lIApncm91cF9ieShHZW5lLnN5bWJvbCkgJT4lIApzdW1tYXJpc2UobG9nRkMgPSBtZWRpYW4obG9nRkMpLCAuZ3JvdXBzPSJkcm9wIikgJT4lIAphcy5kYXRhLmZyYW1lKC4sIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCmBgYAoKCmBgYHtyfQojIFdJVEhPVVQgR1JPVVBJTkcgLyBBR0dSRUdBVElORzoKIyBkaW0ocm5hLnNlcS5kYXRhJEdTRTY0MTc5KSAjIDE4ODE1ICAgICAyCiMgc2FwcGx5KHJuYS5zZXEuZGF0YSRHU0U2NDE3OSwgY2xhc3MpICMgImNoYXJhY3RlciIgICAibnVtZXJpYyIKIyByYW5nZShybmEuc2VxLmRhdGEkR1NFNjQxNzkkbG9nRkMpICMgLTkuNDI2NjEzIDExLjYxMjE3MAojIHN1bShybmEuc2VxLmRhdGEkR1NFNjQxNzkkbG9nRkMgPT0gMCkgIyAwCiMgc3VtKGR1cGxpY2F0ZWQocm5hLnNlcS5kYXRhJEdTRTY0MTc5JEdlbmUuc3ltYm9sKSkgIyAzNwojIGR1cHMgPC0gcm5hLnNlcS5kYXRhJEdTRTY0MTc5JEdlbmUuc3ltYm9sW2R1cGxpY2F0ZWQocm5hLnNlcS5kYXRhJEdTRTY0MTc5JEdlbmUuc3ltYm9sKV0KIyBzdWJzZXQocm5hLnNlcS5kYXRhJEdTRTY0MTc5LCBHZW5lLnN5bWJvbCAlaW4lIGR1cHMpICU+JSAKIyAgIGFycmFuZ2UoR2VuZS5zeW1ib2wsIGxvZ0ZDKQojIGhlYWQocm5hLnNlcS5kYXRhJEdTRTY0MTc5KQpgYGAKCmBgYHtyfQojIFdJVEggQUdHUkVHQVRJTkc6IAojIGRpbShybmEuc2VxLmRhdGEkR1NFNjQxNzkpICMgMTg3NzYgICAgIDIKIyBzYXBwbHkocm5hLnNlcS5kYXRhJEdTRTY0MTc5LCBjbGFzcykgIyAiY2hhcmFjdGVyIiAgICJudW1lcmljIgojIHJhbmdlKHJuYS5zZXEuZGF0YSRHU0U2NDE3OSRsb2dGQykgIyAtOS40MjY2MTMgMTEuNjEyMTcwCiMgc3VtKHJuYS5zZXEuZGF0YSRHU0U2NDE3OSRsb2dGQyA9PSAwKSAjIDAKIyBzdW0oZHVwbGljYXRlZChybmEuc2VxLmRhdGEkR1NFNjQxNzkkR2VuZS5zeW1ib2wpKSAjIDAKaGVhZChybmEuc2VxLmRhdGEubGlzdCRHU0U2NDE3OSkgJT4lIAogIGtuaXRyOjprYWJsZSguLCByb3cubmFtZXMgPSBGLCBmb3JtYXQ9Imh0bWwiKSAlPiUgCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKQpgYGAKCgoKCiMjIyBMb2FkIFJOQS1zZXEgZGF0YTogR1NFNjc0MjcKYGBge3J9CnJuYS5zZXEuZGF0YS5maWxlcyRHU0U2NzQyNyA8LSIuLi9zb3VyY2VfZGF0YS9ybmFfc2VxL0dTRTY3NDI3L0dTRTY3NDI3X3RhYmxlX3MyLnR4dCIKc3RvcGlmbm90KGZpbGUuZXhpc3RzKHJuYS5zZXEuZGF0YS5maWxlcyRHU0U2NzQyNykpCiNybmEuc2VxLmRhdGEubGlzdCA8LWxpc3QoKQpybmEuc2VxLmRhdGEubGlzdCRHU0U2NzQyNzwtIHJlYWQudGFibGUoZmlsZSA9IHJuYS5zZXEuZGF0YS5maWxlcyRHU0U2NzQyNywgaGVhZGVyID0gVCwgc2VwID0gIlx0Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSAgJT4lIAojc3RyKHJuYS5zZXEuZGF0YS5saXN0JEdTRTY3NDI3KSAjZGF0YS5mcmFtZSc6CTEyNzI4IG9icy4gb2YgIDE0MCB2YXJpYWJsZXM6CiAgIyBoZWFkKHJuYS5zZXEuZGF0YS5saXN0JEdTRTY3NDI3KQogIAogICMgbmFtZXMocm5hLnNlcS5kYXRhLmxpc3QkR1NFNjc0MjcpCiAgIyBzZWxlY3QobWF0Y2hlcyhjKCJuYW1lIiwgIlJ2LjE4IikpKQogc2VsZWN0KG1hdGNoZXMoYygibmFtZSIsIlJ2LjE4LmxvZ0ZDIiksIGlnbm9yZS5jYXNlID0gVFJVRSkpICU+JQogICAjIGhlYWQocm5hLnNlcS5kYXRhLmxpc3QkR1NFNjc0MjcpCiAgc2V0X2NvbG5hbWVzKGMoIkdlbmUuc3ltYm9sIiwgImxvZ0ZDIikpICU+JQogIG11dGF0ZShHZW5lLnN5bWJvbCA9IHRvdXBwZXIoR2VuZS5zeW1ib2wpKSAlPiUgCiAgZ3JvdXBfYnkoR2VuZS5zeW1ib2wpICU+JSAKICBzdW1tYXJpc2UobG9nRkMgPSBtZWRpYW4obG9nRkMpLCAuZ3JvdXBzPSJkcm9wIikgJT4lIAogIGFzLmRhdGEuZnJhbWUoLiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKIGhlYWQocm5hLnNlcS5kYXRhLmxpc3QkR1NFNjc0MjcpCmBgYAoKYGBge3J9CiMgV0lUSE9VVCBHUk9VUElORyAvIEFHR1JFR0FUSU5HOgpkaW0ocm5hLnNlcS5kYXRhLmxpc3QkR1NFNjc0MjcpICMgIDEyNzI0ICAgICAyCnNhcHBseShybmEuc2VxLmRhdGEubGlzdCRHU0U2NzQyNywgY2xhc3MpICMgImNoYXJhY3RlciIgICAibnVtZXJpYyIKcmFuZ2Uocm5hLnNlcS5kYXRhLmxpc3QkR1NFNjc0MjckbG9nRkMpICMgLTQuMTU1NDc5ICA4LjIwNTQzOQpzdW0ocm5hLnNlcS5kYXRhLmxpc3QkR1NFNjc0MjckbG9nRkMgPT0gMCkgIyAwCnN1bShkdXBsaWNhdGVkKHJuYS5zZXEuZGF0YS5saXN0JEdTRTY3NDI3JEdlbmUuc3ltYm9sKSkgIyAwCiMgZHVwcyA8LSBybmEuc2VxLmRhdGEkR1NFNjQxNzkkR2VuZS5zeW1ib2xbZHVwbGljYXRlZChybmEuc2VxLmRhdGEkR1NFNjQxNzkkR2VuZS5zeW1ib2wpXQojIHN1YnNldChybmEuc2VxLmRhdGEkR1NFNjQxNzksIEdlbmUuc3ltYm9sICVpbiUgZHVwcykgJT4lIAojICAgYXJyYW5nZShHZW5lLnN5bWJvbCwgbG9nRkMpCiMgaGVhZChybmEuc2VxLmRhdGEkR1NFNjQxNzkpCmBgYAoKYGBge3J9CiMgV0lUSCBBR0dSRUdBVElORzogCiMgZGltKHJuYS5zZXEuZGF0YSRHU0U2NDE3OSkgIyAxODc3NiAgICAgMgojIHNhcHBseShybmEuc2VxLmRhdGEkR1NFNjQxNzksIGNsYXNzKSAjICJjaGFyYWN0ZXIiICAgIm51bWVyaWMiCiMgcmFuZ2Uocm5hLnNlcS5kYXRhJEdTRTY0MTc5JGxvZ0ZDKSAjIC05LjQyNjYxMyAxMS42MTIxNzAKIyBzdW0ocm5hLnNlcS5kYXRhJEdTRTY0MTc5JGxvZ0ZDID09IDApICMgMAojIHN1bShkdXBsaWNhdGVkKHJuYS5zZXEuZGF0YSRHU0U2NDE3OSRHZW5lLnN5bWJvbCkpICMgMApoZWFkKHJuYS5zZXEuZGF0YS5saXN0JEdTRTY3NDI3KSAlPiUgCiAga25pdHI6OmthYmxlKC4sIHJvdy5uYW1lcyA9IEYsIGZvcm1hdD0iaHRtbCIpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpCgpgYGAKCiMjIyBMb2FkIFJOQS1zZXEgZGF0YTogR1NFNDg3MzEuTUYxCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CiMgbGlzdC5maWxlcygiLi4vc291cmNlX2RhdGEvcm5hX3NlcS9HU0UxNDg3MzEiKQojIHJuYS5zZXEuZGF0YS5maWxlcyA8LSBsaXN0KCkKcm5hLnNlcS5kYXRhLmZpbGVzJEdTRTE0ODczMS5NRjEgPC0gIi4uL3NvdXJjZV9kYXRhL3JuYV9zZXEvR1NFMTQ4NzMxL0dTRTE0ODczMV90b3B0YWJsZV9NRjFfMDEuUm1kLjIwMjBfMDdfMDkudHh0IgpzdG9waWZub3QoZmlsZS5leGlzdHMocm5hLnNlcS5kYXRhLmZpbGVzJEdTRTE0ODczMS5NRjEpKQogIyBybmEuc2VxLmRhdGEubGlzdCA8LSBsaXN0KCkKcm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMSA8LSByZWFkLnRhYmxlKGZpbGUgPSBybmEuc2VxLmRhdGEuZmlsZXMkR1NFMTQ4NzMxLk1GMSwgaGVhZGVyID0gVFJVRSwgc2VwID0gIlx0Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSAlPiUKICAgc2VsZWN0KGMoIkdlbmUuc3ltYm9sIiwgImxvZ0ZDIikpICU+JSAKICBtdXRhdGUoR2VuZS5zeW1ib2wgPSB0b3VwcGVyKEdlbmUuc3ltYm9sKSkgJT4lIAogICMgc3VtbWFyaXNlKGxvZ0ZDID0gbWVkaWFuKGxvZ0ZDKSwgLmdyb3Vwcz0iZHJvcCIpICU+JSAKICAgYXMuZGF0YS5mcmFtZSguLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQpoZWFkKHJuYS5zZXEuZGF0YS5saXN0JEdTRTE0ODczMS5NRjEpCmBgYApgYGB7cn0KIyBXSVRIIEFHR1JFR0FUSU5HOgpkaW0ocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMSkgIyAxNzgwMiAgICAgMgpzYXBwbHkocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMSwgY2xhc3MpICMgImNoYXJhY3RlciIgICAibnVtZXJpYyIKcmFuZ2Uocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMSRsb2dGQykgIyAtMi4zODYxMjQgIDkuNzA0ODY1CnN1bShybmEuc2VxLmRhdGEubGlzdCRHU0UxNDg3MzEuTUYxJGxvZ0ZDID09IDApICMKc3VtKGR1cGxpY2F0ZWQocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMSRHZW5lLnN5bWJvbCkpICMgMAoKYGBgCgpgYGB7cn0KaGVhZChybmEuc2VxLmRhdGEubGlzdCRHU0UxNDg3MzEuTUYxKSAlPiUgCiAga25pdHI6OmthYmxlKC4sIHJvdy5uYW1lcyA9IEYsIGZvcm1hdD0iaHRtbCIpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpCmBgYAoKCiMjIyBMb2FkIFJOQS1zZXEgZGF0YTogR1NFNDg3MzEuTUYyCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CiAjIGxpc3QuZmlsZXMoIi4uL3NvdXJjZV9kYXRhL3JuYV9zZXEvR1NFMTQ4NzMxIikKIyBybmEuc2VxLmRhdGEuZmlsZXMgPC0gbGlzdCgpCnJuYS5zZXEuZGF0YS5maWxlcyRHU0UxNDg3MzEuTUYyIDwtICIuLi9zb3VyY2VfZGF0YS9ybmFfc2VxL0dTRTE0ODczMS9HU0UxNDg3MzFfdG9wdGFibGVfTUYyXzAxLlJtZC4yMDIwXzA3XzA5LnR4dCIKc3RvcGlmbm90KGZpbGUuZXhpc3RzKHJuYS5zZXEuZGF0YS5maWxlcyRHU0UxNDg3MzEuTUYyKSkKICMgcm5hLnNlcS5kYXRhLmxpc3QgPC0gbGlzdCgpCnJuYS5zZXEuZGF0YS5saXN0JEdTRTE0ODczMS5NRjIgPC0gcmVhZC50YWJsZShmaWxlID0gcm5hLnNlcS5kYXRhLmZpbGVzJEdTRTE0ODczMS5NRjIsIGhlYWRlciA9IFRSVUUsIHNlcCA9ICJcdCIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgJT4lCiAgIHNlbGVjdChjKCJHZW5lLnN5bWJvbCIsICJsb2dGQyIpKSAlPiUgCiAgbXV0YXRlKEdlbmUuc3ltYm9sID0gdG91cHBlcihHZW5lLnN5bWJvbCkpICU+JSAKICMgc3VtbWFyaXNlKGxvZ0ZDID0gbWVkaWFuKGxvZ0ZDKSwgLmdyb3Vwcz0iZHJvcCIpICU+JSAKICAgYXMuZGF0YS5mcmFtZSguLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQpoZWFkKHJuYS5zZXEuZGF0YS5saXN0JEdTRTE0ODczMS5NRjIpCmBgYAoKCmBgYHtyfQojIFdJVEggQUdHUkVHQVRJTkc6CmRpbShybmEuc2VxLmRhdGEubGlzdCRHU0UxNDg3MzEuTUYyKSAjICAxNzA4NSAgICAgMgpzYXBwbHkocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMiwgY2xhc3MpICMgImNoYXJhY3RlciIgICAibnVtZXJpYyIKcmFuZ2Uocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMiRsb2dGQykgIyAtNy41MjQxMzYgIDkuNzk1MTAwCnN1bShybmEuc2VxLmRhdGEubGlzdCRHU0UxNDg3MzEuTUYyJGxvZ0ZDID09IDApICMKc3VtKGR1cGxpY2F0ZWQocm5hLnNlcS5kYXRhLmxpc3QkR1NFMTQ4NzMxLk1GMiRHZW5lLnN5bWJvbCkpICMgMAoKYGBgCgpgYGB7cn0KaGVhZChybmEuc2VxLmRhdGEubGlzdCRHU0UxNDg3MzEuTUYyKSAlPiUgCiAga25pdHI6OmthYmxlKC4sIHJvdy5uYW1lcyA9IEYsIGZvcm1hdD0iaHRtbCIpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpCmBgYAoKIyMgQ29tYmluZSBhbGwgZGF0YSBmcmFtZXMgIApgYGB7cn0KIyBsZW5ndGgocm5hLnNlcS5kYXRhLmxpc3QpICM0CiMgbGVuZ3RoKGdlby5mb2xkLmNoYW5nZS5saXN0KSMgNAphbGwuZGF0YS5saXN0IDwtIGMoZ2VvLmZvbGQuY2hhbmdlLmxpc3QsIHJuYS5zZXEuZGF0YS5saXN0KQphbGwoc2FwcGx5KGFsbC5kYXRhLmxpc3QsIGNsYXNzKSA9PSAiZGF0YS5mcmFtZSIpICMgVFJVRQpgYGAKCmBgYHtyfQpsYXBwbHkoYWxsLmRhdGEubGlzdCwgbmFtZXMpCmxlbmd0aChhbGwuZGF0YS5saXN0KSAjIDgKYGBgCgoKIyMjIE1lcmdlIGRhdGEgZnJhbWVzICAKYGBge3J9Cm1lcmdlZC5kZiA8LSBhbGwuZGF0YS5saXN0ICU+JSBwdXJycjo6cmVkdWNlKGRwbHlyOjpmdWxsX2pvaW4sIGJ5PSJHZW5lLnN5bWJvbCIpICU+JSAKICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygiR2VuZS5zeW1ib2wiKSAlPiUgCiAgc2V0X2NvbG5hbWVzKHBhc3RlMCgiZGF0YXNldCIsIHNlcV9hbG9uZyguKSkpCgpkaW0obWVyZ2VkLmRmKSAjIDMwNjg3ICAgICA4CmhlYWQobWVyZ2VkLmRmKSAlPiUgCiAga25pdHI6OmthYmxlKC4sIHJvdy5uYW1lcyA9IFQsIGZvcm1hdD0iaHRtbCIpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpCmBgYAoKU3VtbWFyeSB0YWJsZTogIApgYGB7cn0KZGYuc3VtbWFyeSA8LSBkYXRhLmZyYW1lKAogIENvbHVtbiA9IG5hbWVzKG1lcmdlZC5kZiksCiAgRGF0YXNldD1uYW1lcyhhbGwuZGF0YS5saXN0KSwKICBNaW4gPSByb3VuZChzYXBwbHkobWVyZ2VkLmRmLCBtaW4sIG5hLnJtPVQpLCAyKSwKICBNYXggPSByb3VuZChzYXBwbHkobWVyZ2VkLmRmLCBtYXgsIG5hLnJtPVQpLCAyKSwKICBDb3VudC56ZXJvID0gc2FwcGx5KG1lcmdlZC5kZiwgZnVuY3Rpb24oeCkge3N1bSh4PT0wLCBuYS5ybT1UKX0pLAogIENvdW50Lk5BID0gY29tbWEoc2FwcGx5KG1lcmdlZC5kZiwgZnVuY3Rpb24oeCkge3N1bShpcy5uYSh4KSl9KSkKKSAlPiUgCiAgbXV0YXRlKENvdW50Lnplcm8gPSBjb21tYShhcy5pbnRlZ2VyKENvdW50Lnplcm8pLCBhY2N1cmFjeT0xKSkKa25pdHI6OmthYmxlKGRmLnN1bW1hcnksIHJvdy5uYW1lcyA9IEYsIGZvcm1hdD0iaHRtbCIsIGFsaWduPXJlcCgibCIsIDUpKSAlPiUgCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKQpgYGAKCgpgYGB7ciByZXN1bHRzPSJhc2lzIn0KY2F0KCJOdW1iZXIgb2Ygcm93cyBpbiBtZXJnZWQgZGF0YSBmcmFtZTo8Yj4iLCBjb21tYShucm93KG1lcmdlZC5kZikpLCAiPC9iPjxicj5cbiIpCmNhdCgiTnVtYmVyIG9mIGdlbmUgc3ltYm9scyBpbiBtZXJnZWQgZGF0YSBmcmFtZTo8Yj4iLCBjb21tYShsZW5ndGgodW5pcXVlKHJvdy5uYW1lcyhtZXJnZWQuZGYpKSkpLCAiPC9iPjxicj5cbiIpCmBgYAoKYGBge3IgcmVzdWx0cz0iYXNpcyJ9CmNhdCgiTnVtYmVyIG9mIGNvbXBsZXRlIGRhdGEgcm93czogPGI+IiwgY29tbWEoc3VtKGNvbXBsZXRlLmNhc2VzKG1lcmdlZC5kZikpKSwgIjwvYj4gKCIsCiAgICBwZXJjZW50KHN1bShjb21wbGV0ZS5jYXNlcyhtZXJnZWQuZGYpKS9sZW5ndGgodW5pcXVlKHJvdy5uYW1lcyhtZXJnZWQuZGYpKSkpLAogICAgIik8YnI+XG4iLCBzZXA9IiIpCmBgYAoKIyMjIEZpbHRlciByb3dzOiBUaHJlZSBOQSBtYXggIApgYGB7cn0KdGhyZWUubmEubWF4LmNvdW50IDwtIGFzLm1hdHJpeChtZXJnZWQuZGYpICU+JSAKICBhcHBseSguLCAxLCBmdW5jdGlvbih4KSB7ICAjIDEgPSBzZWxlY3Qgcm93cwogICAgbiA8LSBzdW0oaXMubmEoeCkpCiAgICBuCiAgfSkKY2xhc3ModGhyZWUubmEubWF4LmNvdW50KSAjIGludGVnZXIKbGVuZ3RoKHRocmVlLm5hLm1heC5jb3VudCkgIyAzMDY4NwptZXJnZWQuZGYuZmlsdC5uYSA8LSBtZXJnZWQuZGZbdGhyZWUubmEubWF4LmNvdW50IDw9IDMsXQpkaW0obWVyZ2VkLmRmLmZpbHQubmEpIyAxNDc0MyA4IHZzIDExODczICAgICA4ICB2cyAgODYyNiAgICA4IChvbmUgTkEgbWF4KQogZGltKG1lcmdlZC5kZikgICAgIyAgMzA2ODcgICAgIDggIApgYGAKCgpgYGB7cn0KY2F0KCJOdW1iZXIgb2Ygcm93cyB3aXRoIG1heCB0aHJlZSBOQTogPGI+IiwgY29tbWEobnJvdyhtZXJnZWQuZGYuZmlsdC5uYSkpLCAiPC9iPiAoIiwKICAgIHBlcmNlbnQobnJvdyhtZXJnZWQuZGYuZmlsdC5uYSkvbGVuZ3RoKHVuaXF1ZShyb3cubmFtZXMobWVyZ2VkLmRmKSkpKSwKICAgICIpPGJyPlxuIiwgc2VwPSIiKQoKYGBgCgpQcmludCAxMCByb3dzOiAgCmBgYHtyfQpoZWFkKG1lcmdlZC5kZi5maWx0Lm5hLCAxMCkgJT4lIAogIGtuaXRyOjprYWJsZSguLCByb3cubmFtZXMgPSBULCBmb3JtYXQ9Imh0bWwiLCBhbGlnbj1yZXAoImwiLCA1KSkgJT4lIAogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVCkKYGBgCgojIyMgU3VtbWFyeSB0YWJsZSBhZnRlciBmaWx0ZXJpbmcgTkFzOiAgCmBgYHtyfQpkZi5zdW1tYXJ5LjIgPC0gZGF0YS5mcmFtZSgKICBDb2x1bW4gPSBuYW1lcyhtZXJnZWQuZGYuZmlsdC5uYSksCiAgRGF0YXNldD1uYW1lcyhhbGwuZGF0YS5saXN0KSwKICBNaW4gPSByb3VuZChzYXBwbHkobWVyZ2VkLmRmLmZpbHQubmEsIG1pbiwgbmEucm09VCksIDIpLAogIE1heCA9IHJvdW5kKHNhcHBseShtZXJnZWQuZGYuZmlsdC5uYSwgbWF4LCBuYS5ybT1UKSwgMiksCiAgQ291bnQuemVybyA9IHNhcHBseShtZXJnZWQuZGYuZmlsdC5uYSwgZnVuY3Rpb24oeCkge3N1bSh4PT0wLCBuYS5ybT1UKX0pLAogIENvdW50Lk5BID0gY29tbWEoc2FwcGx5KG1lcmdlZC5kZi5maWx0Lm5hLCBmdW5jdGlvbih4KSB7c3VtKGlzLm5hKHgpKX0pKQopICU+JSAKICBtdXRhdGUoQ291bnQuemVybyA9IGNvbW1hKGFzLmludGVnZXIoQ291bnQuemVybyksIGFjY3VyYWN5PTEpKQprbml0cjo6a2FibGUoZGYuc3VtbWFyeS4yLCByb3cubmFtZXMgPSBGLCBmb3JtYXQ9Imh0bWwiLCBhbGlnbj1yZXAoImwiLCA1KSkgJT4lIAogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVCkKcm0oZGYuc3VtbWFyeS4yKQpgYGAKCgoKIyMgQ2FsY3VsYXRlIHJhbmtzICgxKSAgCgpLZXkgcGFyYW10ZXJzOiAgCgoqIE5BcyBzdGF5IE5BcyAgCiogdGllcyBhcmUgYXZlcmFnZWQgIAoqIGtlZXBpbmcgTkFzIHJlc3VsdHMgaW4gZGlmZmVyZW50IG1heGltdW0gcmFua3MgIAoqICh3aWxsIGhhdmUgdG8gcmFuayBhZ2FpbiBhZnRlciBmaWxsaW5nIE5BcykgIAoKIyMjIFJhbmtzOiBkb3duLXJlZ3VsYXRlZCAgCmBgYHtyfQpyYW5rcy5kb3duLm14IDwtIGFwcGx5KG1lcmdlZC5kZi5maWx0Lm5hLCAyLCByYW5rLCB0aWVzLm1ldGhvZD0iYXZlcmFnZSIsIG5hLmxhc3Q9ImtlZXAiKQojIGNsYXNzKHJhbmtzLmRvd24ubXgpICMgbWF0cml4CiMgZGltKHJhbmtzLmRvd24ubXgpICMgMTI3ODYgICAgIDUKIyByYW5rcy5kb3duLm14WzE6MTAsXQpgYGAKClByaW50IDYgcm93cyAoc29ydGVkKTogIApgYGB7cn0KcmFua3MuZG93bi5teFtvcmRlcihyb3dNZWFucyhyYW5rcy5kb3duLm14LCBuYS5ybT1UKSksXSAlPiUgaGVhZApgYGAKClRoZSBtYXhpbXVtIHJhbmtzIHdpbGwgdmFyeSBieSBkYXRhc2V0OiAgCmBgYHtyfQphcHBseShyYW5rcy5kb3duLm14LCAyLCBtYXgsIG5hLnJtPVQpICU+JQogIGFzLmRhdGEuZnJhbWUoLiwgc3RyaW5nc0FzRmFjdG9ycz1GKSAlPiUgCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oKSAlPiUgCiAgc2V0X2NvbG5hbWVzKGMoIkRhdGFzZXQgKGRvd24pIiwgIk1heC5SYW5rIikpICU+JSAKICBtdXRhdGUoTWF4LlJhbmsgPSBjb21tYShNYXguUmFuaykpICU+JSAKICBrbml0cjo6a2FibGUoLiwgcm93Lm5hbWVzID0gRiwgYWxpZ249YygibCIsInIiKSwgZm9ybWF0PSJodG1sIikgJT4lIAogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoInN0cmlwZWQiKQpgYGAKCiMjIyBSYW5rczogdXAtcmVndWxhdGVkICAKYGBge3J9CnJhbmtzLnVwLm14IDwtIGFwcGx5KChtZXJnZWQuZGYuZmlsdC5uYSAqIC0xKSwgMiwgcmFuaywgdGllcy5tZXRob2Q9ImF2ZXJhZ2UiLCBuYS5sYXN0PSJrZWVwIikKYGBgCgpQcmludCA2IHJvd3MgKHNvcnRlZCk6ICAKYGBge3J9CnJhbmtzLnVwLm14W29yZGVyKHJvd01lYW5zKHJhbmtzLnVwLm14LCBuYS5ybT1UKSksXSAlPiUgaGVhZApgYGAKCgpgYGB7cn0KYXBwbHkocmFua3MudXAubXgsIDIsIG1heCwgbmEucm09VCkgJT4lCiAgYXMuZGF0YS5mcmFtZSguLCBzdHJpbmdzQXNGYWN0b3JzPUYpICU+JSAKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigpICU+JSAKICBzZXRfY29sbmFtZXMoYygiRGF0YXNldCAodXApIiwgIk1heC5SYW5rIikpICU+JSAKICBtdXRhdGUoTWF4LlJhbmsgPSBjb21tYShNYXguUmFuaykpICU+JSAKICBrbml0cjo6a2FibGUoLiwgcm93Lm5hbWVzID0gRiwgYWxpZ249YygibCIsInIiKSwgZm9ybWF0PSJodG1sIikgJT4lIAogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoInN0cmlwZWQiKQpgYGAKCiMjIFJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgd2l0aCBhdmVyYWdlcyAgCmBgYHtyfQpuYS50by5hdmVyYWdlIDwtIGZ1bmN0aW9uKHgpIHsKICBhbnkubmEgPC0gYW55KGlzLm5hKHgpKQogIGlmIChpc1RSVUUoYW55Lm5hKSkgewogICAgaW5keC5uYSA8LSB3aGljaChpcy5uYSh4KSkKICAgIGF2ZSA8LSBtZWFuKHgsIG5hLnJtPVQpCiAgICB4W2luZHgubmFdIDwtIGF2ZQogIH0KICB4Cn0KYGBgCgojIyMgUmVwbGFjZSBOQXM6IGRvd24tcmVndWxhdGlvbiAgCmBgYHtyfQpyYW5rcy5kb3duLm5vbmEubXggPC0gdChhcHBseShyYW5rcy5kb3duLm14LCAxLCBuYS50by5hdmVyYWdlKSkKIyBkaW0ocmFua3MuZG93bi5teCkgIyAxMjc4NiAgICAgNQojIGRpbShyYW5rcy5kb3duLm5vbmEubXgpICMgMTI3ODYgICAgIDUKIyBzdW0oaXMubmEocmFua3MuZG93bi5teCkpICMgMTQzNgojIHN1bShpcy5uYShyYW5rcy5kb3duLm5vbmEubXgpKSAjIDAKIyByYW5rcy5kb3duLm5vbmEubXhbMTo0LDE6NF0KcmFua3MuZG93bi5ub25hLm14W29yZGVyKHJvd01lYW5zKHJhbmtzLmRvd24ubm9uYS5teCwgbmEucm09VCkpLF0gJT4lIAogIGhlYWQKYGBgCgojIyMgUmVwbGFjZSBOQXM6IHVwLXJlZ3VsYXRpb24gIApgYGB7cn0KcmFua3MudXAubm9uYS5teCA8LSB0KGFwcGx5KHJhbmtzLnVwLm14LCAxLCBuYS50by5hdmVyYWdlKSkKIyBkaW0ocmFua3MudXAubXgpICMgMTI3ODYgICAgIDUKIyBkaW0ocmFua3MudXAubm9uYS5teCkgIyAxMjc4NiAgICAgNQojIHN1bShpcy5uYShyYW5rcy51cC5teCkpICMgMTQzNgojIHN1bShpcy5uYShyYW5rcy51cC5ub25hLm14KSkgIyAwCiMgcmFua3MudXAubm9uYS5teFsxOjQsMTo0XQpyYW5rcy51cC5ub25hLm14W29yZGVyKHJvd01lYW5zKHJhbmtzLnVwLm5vbmEubXgsIG5hLnJtPVQpKSxdICU+JSAKICBoZWFkCmBgYAoKCiMjIENhbGN1bGF0ZSByYW5rcyAoMikgIApIYXZpbmcgcmVwbGFjZWQgbWlzc2luZyB2YWx1ZXMgd2l0aCBhdmVyYWdlcywgcmVwZWF0IHRoZSByYW5raW5nIHN1Y2ggdGhhdCBtYXhpbXVtIHJhbmtzIHdpbGwgYmUgdGhlIHNhbWUgZm9yIGVhY2ggZGF0YXNldC4gIApLZXkgcGFyYW10ZXJzOiAgCgoqIHRpZXMgYXJlIGF2ZXJhZ2VkICAKCiMjIyBSYW5rczogZG93bi1yZWd1bGF0ZWQgKDIpICAKYGBge3J9CnJhbmtzLmRvd24ubXguMiA8LSBhcHBseShyYW5rcy5kb3duLm5vbmEubXgsIDIsIHJhbmssIHRpZXMubWV0aG9kPSJhdmVyYWdlIiwgbmEubGFzdD0ia2VlcCIpCiMgY2xhc3MocmFua3MuZG93bi5teC4yKSAjIG1hdHJpeAojIGRpbShyYW5rcy5kb3duLm14LjIpICMgMTI3ODYgICAgIDUKIyByYW5rcy5kb3duLm14LjJbMToxMCxdCnJhbmtzLmRvd24ubXguMltvcmRlcihyb3dNZWFucyhyYW5rcy5kb3duLm14LjIsIG5hLnJtPVQpKSxdICU+JSAKICBoZWFkCmBgYAoKYGBge3J9CmFwcGx5KHJhbmtzLmRvd24ubXguMiwgMiwgbWF4KSAlPiUKICBhcy5kYXRhLmZyYW1lKC4sIHN0cmluZ3NBc0ZhY3RvcnM9RikgJT4lIAogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCkgJT4lIAogIHNldF9jb2xuYW1lcyhjKCJEYXRhc2V0IChkb3duKSIsICJNYXguUmFuayIpKSAlPiUgCiAga25pdHI6OmthYmxlKC4sIHJvdy5uYW1lcyA9IEYsIGFsaWduPWMoImwiLCJyIiksIGZvcm1hdD0iaHRtbCIpICU+JSAKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIikKYGBgCgojIyMgUmFua3M6IHVwLXJlZ3VsYXRlZCAoMikgIApgYGB7cn0KcmFua3MudXAubXguMiA8LSBhcHBseShyYW5rcy51cC5ub25hLm14LCAyLCByYW5rLCB0aWVzLm1ldGhvZD0iYXZlcmFnZSIsIG5hLmxhc3Q9ImtlZXAiKQojIHJhbmtzLnVwLm14LjJbMToxMCxdCnJhbmtzLnVwLm14LjJbb3JkZXIocm93TWVhbnMocmFua3MudXAubXguMiwgbmEucm09VCkpLF0gJT4lIGhlYWQKYGBgCgpgYGB7cn0KYXBwbHkocmFua3MudXAubXguMiwgMiwgbWF4KSAlPiUKICBhcy5kYXRhLmZyYW1lKC4sIHN0cmluZ3NBc0ZhY3RvcnM9RikgJT4lIAogIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCkgJT4lIAogIHNldF9jb2xuYW1lcyhjKCJEYXRhc2V0ICh1cCkiLCAiTWF4LlJhbmsiKSkgJT4lIAogIGtuaXRyOjprYWJsZSguLCByb3cubmFtZXMgPSBGLCBhbGlnbj1jKCJsIiwiciIpLCBmb3JtYXQ9Imh0bWwiKSAlPiUgCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygic3RyaXBlZCIpCmBgYAoKCgojIyBQbG90IGNvcnJlbGF0aW9ucyAgCiMjIyBQbG90IGZvbGQgY2hhbmdlcyAgCmBgYHtyfQpwYW5lbC5jb3IgPC0gZnVuY3Rpb24oeCwgeSwgZGlnaXRzID0gMywgcHJlZml4ID0gIiIsIGNleC5jb3IsIC4uLikKewogICAgIHVzciA8LSBwYXIoInVzciIpOyBvbi5leGl0KHBhcih1c3IpKQogICAgIHBhcih1c3IgPSBjKDAsIDEsIDAsIDEpKQogICAgIHIgPC0gY29yKHgsIHksIHVzZT0icGFpcndpc2UuY29tcGxldGUub2JzIikKICAgICB0eHQgPC0gZm9ybWF0KGMociwgMC4xMjM0NTY3ODkpLCBkaWdpdHMgPSBkaWdpdHMpWzFdCiAgICAgdHh0IDwtIHBhc3RlMCgicj0iLCB0eHQpCiAgICAgIyBpZihtaXNzaW5nKGNleC5jb3IpKSBjZXguY29yIDwtIDAuOC9zdHJ3aWR0aCh0eHQpCiAgICAgIyB0ZXh0KDAuNSwgMC41LCB0eHQsIGNleCA9IGNleC5jb3IgKiByKQogICAgIHRleHQoMC41LCAwLjUsIHR4dCwgY2V4PTEuNSwgY29sPSJibHVlIiwgZm9udCA9IDMpCn0KCmNvci5kZjwtIG1lcmdlZC5kZiAlPiUgCiAgc2V0X2NvbG5hbWVzKG5hbWVzKGFsbC5kYXRhLmxpc3QpKSAKICBjb2xuYW1lcyhjb3IuZGYpIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKGNvci5kZiksICJcXF9EQyIsICIgXG5EQyIpCiAgY29sbmFtZXMoY29yLmRmKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhjb3IuZGYpLCAiXFxfTVAiLCAiIFxuTVAiKQogIGNvbG5hbWVzKGNvci5kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMoY29yLmRmKSwgIlxcLk1GMSIsICIgXG5NRjEiKQogIGNvbG5hbWVzKGNvci5kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMoY29yLmRmKSwgIlxcLk1GMiIsICIgXG5NRjIiKSAKICBjb2xuYW1lcyhjb3IuZGYpIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKGNvci5kZiksICJHU0UxMDMwOTIiLCAiR1NFMTAzMDkyXG5NUCIpCiAgY29sbmFtZXMoY29yLmRmKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhjb3IuZGYpLCAiR1NFNjQxNzkiLCAiR1NFNjQxNzlcbkRDIikgICAgICAgICAgICAgIAogIGNvbG5hbWVzKGNvci5kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMoY29yLmRmKSwiR1NFMzQxNTEiLCAiR1NFMzQxNTFcbkRDIikKICBjb2xuYW1lcyhjb3IuZGYpIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKGNvci5kZiksIkdTRTY3NDI3IiwKIkdTRTY3NDI3XG5NUCIpCiAgCiAgcGFpcnMoY29yLmRmLCBjb2wgPSBhbHBoYSgiYmxhY2siLCAwLjMpLCBwY2g9MjAsIHVwcGVyLnBhbmVsID0gcGFuZWwuY29yLAogICAgICAgIHhheHQ9J24nLCB5YXh0PSduJywKICAgICAgICB0ZXh0LnBhbmVsID0gZnVuY3Rpb24oeCx5LGxhYixjZXgsZm9udCkge3RleHQoeCx5LGxhYiwgY2V4PTEuMCwgZm9udD0yKX0pIAogIAogIApgYGAKCiMjIyBTYXZlIHBsb3QgYXMgYSBwbmcKYGBge3J9Cm91dC5maWxlIDwtIHBhc3RlKCJkYXRhX291dCIsIHN0cmluZ3I6OnN0cl9yZXBsYWNlKHRoaXMuc2NyaXB0LCAiXFwuW1JyXVtNbV1bRGRdJCIsICIucG5nIiksIHNlcD0iLyIpCm91dC5maWxlCmBgYAoKYGBge3J9CnBuZyhmaWxlPW91dC5maWxlLCB3aWR0aCA9IDY4MCwgaGVpZ2h0PSA0ODAsIHVuaXRzID0gInB4IikKY29yLmRmPC0gbWVyZ2VkLmRmICU+JSAKICBzZXRfY29sbmFtZXMobmFtZXMoYWxsLmRhdGEubGlzdCkpIAogIGNvbG5hbWVzKGNvci5kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMoY29yLmRmKSwgIlxcX0RDIiwgIiBcbkRDIikKICBjb2xuYW1lcyhjb3IuZGYpIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKGNvci5kZiksICJcXF9NUCIsICIgXG5NUCIpCiAgY29sbmFtZXMoY29yLmRmKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhjb3IuZGYpLCAiXFwuTUYxIiwgIiBcbk1GMSIpCiAgY29sbmFtZXMoY29yLmRmKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhjb3IuZGYpLCAiXFwuTUYyIiwgIiBcbk1GMiIpIAogICAgY29sbmFtZXMoY29yLmRmKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhjb3IuZGYpLCAiR1NFMTAzMDkyIiwgIkdTRTEwMzA5MlxuTVAiKQogIGNvbG5hbWVzKGNvci5kZikgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMoY29yLmRmKSwgIkdTRTY0MTc5IiwgIkdTRTY0MTc5XG5EQyIpICAgICAgICAgICAgICAKICBjb2xuYW1lcyhjb3IuZGYpIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKGNvci5kZiksIkdTRTM0MTUxIiwgIkdTRTM0MTUxXG5EQyIpCiAgY29sbmFtZXMoY29yLmRmKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhjb3IuZGYpLCJHU0U2NzQyNyIsCiJHU0U2NzQyN1xuTVAiKQogIAogIHBhaXJzKGNvci5kZiwgY29sID0gYWxwaGEoImJsYWNrIiwgMC4zKSwgcGNoPTIwLCB1cHBlci5wYW5lbCA9IHBhbmVsLmNvciwKICAgICAgICB4YXh0PSduJywgeWF4dD0nbicsCiAgICAgICAgdGV4dC5wYW5lbCA9IGZ1bmN0aW9uKHgseSxsYWIsY2V4LGZvbnQpIHt0ZXh0KHgseSxsYWIsIGNleD0xLjQsIGZvbnQ9Mil9KSAKICAKZGV2Lm9mZigpCmBgYAoKCiMjIyBQbG90IHJhbmtzICAKYGBge3J9CnBhbmVsLmNvci5zcGVhcm1hbiA8LSBmdW5jdGlvbih4LCB5LCBkaWdpdHMgPSAzLCBwcmVmaXggPSAiIiwgY2V4LmNvciwgLi4uKQp7CiAgICAgdXNyIDwtIHBhcigidXNyIik7IG9uLmV4aXQocGFyKHVzcikpCiAgICAgcGFyKHVzciA9IGMoMCwgMSwgMCwgMSkpCiAgICAgciA8LSBjb3IoeCwgeSwgdXNlPSJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiLCBtZXRob2Q9InNwZWFybWFuIikKICAgICB0eHQgPC0gZm9ybWF0KGMociwgMC4xMjM0NTY3ODkpLCBkaWdpdHMgPSBkaWdpdHMpWzFdCiAgICAgdHh0IDwtIHBhc3RlMCgicj0iLCB0eHQpCiAgICAgdGV4dCgwLjUsIDAuNSwgdHh0LCBjZXg9Mi41LCBjb2w9InB1cnBsZSIsIGZvbnQgPSAzKQp9CnJhbmtzLmRvd24ubXguMiAlPiUgCiAgc2V0X2NvbG5hbWVzKG5hbWVzKGFsbC5kYXRhLmxpc3QpKSAlPiUgCiAgcGFpcnMoLiwgY29sID0gYWxwaGEoImJsYWNrIiwgMC4zKSwgcGNoPTE4LCB1cHBlci5wYW5lbCA9IHBhbmVsLmNvci5zcGVhcm1hbiwKICAgICAgICB4YXh0PSduJywgeWF4dD0nbicsCiAgICAgIHRleHQucGFuZWwgPSBmdW5jdGlvbih4LHksbGFiLGNleCxmb250KSB7dGV4dCh4LHksbGFiLCBjZXg9MywgZm9udD0yKX0pCmBgYAoKIyMgQ2FsY3VsYXRlIHJhbmsgc3VtIHN0YXRpc3RpY3MgIApgYGB7cn0KIyBicm93c2VWaWduZXR0ZXMoIlJhbmtQcm9kIikKIyBoZWxwKHBhY2thZ2U9IlJhbmtQcm9kIikKIyBoZWxwKFJhbmtQcm9kdWN0cywgcGFja2FnZT0iUmFua1Byb2QiKQpgYGAKCmBgYHtyfQpnZXQucmFuay5zdW0gPC0gZnVuY3Rpb24obXgpIHsKICBjbC5kb3duIDwtIHJlcCgxLCBuY29sKG14KSkKICByYW5rLnN1bSA8LSBSYW5rUHJvZDo6UmFua1Byb2R1Y3RzKAogICAgZGF0YT1teCwKICAgIGNsPWNsLmRvd24sCiAgICBjYWxjdWxhdGVQcm9kdWN0PUZBTFNFLAogICAgZ2VuZS5uYW1lcyA9IHJvdy5uYW1lcyhyYW5rcy5kb3duLm14LjIpCiAgICApCiAgcmFuay5zdW0KfQpgYGAKCmBgYHtyfQpnZXQucmFuay5zdW0uc3RhdHMgPC0gZnVuY3Rpb24ocmFuay5zdW0ucmVzdWx0KSB7CiAgIyBHZXQgcmFuayBzdW1zOgogIFJTIDwtIGFzLmRhdGEuZnJhbWUocmFuay5zdW0ucmVzdWx0JFJTcykgJT4lIAogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oLikgJT4lIAogICAgZHBseXI6OnNlbGVjdCgxOjIpICU+JSAKICAgIHNldF9jb2xuYW1lcyhjKCJHZW5lIiwgIlJhbmsuU3VtIikpICU+JSAKICAgIGFycmFuZ2UoUmFuay5TdW0pCiAgCiAgIyBHZXQgcCB2YWx1ZXM6CiAgUFZBTCA8LSBhcy5kYXRhLmZyYW1lKHJhbmsuc3VtLnJlc3VsdCRwdmFsKSAlPiUgCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oLikgJT4lIAogIGRwbHlyOjpzZWxlY3QoMToyKSAlPiUgCiAgc2V0X2NvbG5hbWVzKGMoIkdlbmUiLCAiUC5WYWwiKSkKICAKICAjIE1lcmdlOgogIFJTLlBWQUwgPC0gbWVyZ2UoeD1SUywgeT1QVkFMLCBieT0iR2VuZSIsIGFsbD1UKQogIAogICMgQWRqdXN0IHAgdmFsdWVzOgogIFJTLlBWQUwkUC5WYWwuQWRqIDwtIHAuYWRqdXN0KFJTLlBWQUwkUC5WYWwsIG1ldGhvZCA9ICJmZHIiKQogIAogIFJTLlBWQUwKfQpgYGAKCgojIyMgUmFuayBzdW0gc3RhdHM6IGRvd24tcmVndWxhdGlvbiAgCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CnJhbmsuc3VtLmRvd24gPC0gZ2V0LnJhbmsuc3VtKHJhbmtzLmRvd24ubXguMikgJT4lIAogIGdldC5yYW5rLnN1bS5zdGF0cyguKQojIGNsYXNzKHJhbmsuc3VtLmRvd24pICMgImRhdGEuZnJhbWUiCiMgZGltKHJhbmsuc3VtLmRvd24pICMgMTE2ODQgICAgIDQKdG9wLjEwLmRvd248LSByYW5rLnN1bS5kb3duICU+JSAKICBhcnJhbmdlKFJhbmsuU3VtKSAlPiUgCiAgaGVhZCguLCAxMCkgCiAgIyBrbml0cjo6a2FibGUoLiwgcm93Lm5hbWVzID0gRiwgYWxpZ249cmVwKCJsIiwgNCksIGZvcm1hdCA9ICJodG1sIiwgZXNjYXBlID0gRikgJT4lIAogICMga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKQp0b3AuMTAuZG93bgoKYGBgCiMjIyBTYXZlIHRvcCAxMCBkb3duLXJlZ3VsYXRlZCBnZW5lcwpgYGB7cn0Kb3V0LnRhYmxlLmRvd24gPC0gcGFzdGUwKCJkYXRhX291dC8iLHRoaXMuc2NyaXB0LCAiLnRvcC5kb3duLnR4dCIpCm91dC50YWJsZS5kb3duCndyaXRlLnRhYmxlKHRvcC4xMC5kb3duLCBmaWxlID0gb3V0LnRhYmxlLmRvd24sIHNlcCA9ICJcdCIpCmBgYAoKIyMjIFJhbmsgc3VtIHN0YXRzOiB1cC1yZWd1bGF0aW9uICAKYGBge3IgbWVzc2FnZT1GQUxTRX0KcmFuay5zdW0udXAgPC0gZ2V0LnJhbmsuc3VtKHJhbmtzLnVwLm14LjIpICU+JSAKICBnZXQucmFuay5zdW0uc3RhdHMoLikKIyBjbGFzcyhyYW5rLnN1bS51cCkgIyAiZGF0YS5mcmFtZSIKIyBkaW0ocmFuay5zdW0udXApICMgMTI3ODYgICAgIDQKdG9wLjEwLnVwIDwtIHJhbmsuc3VtLnVwICU+JSAKICBhcnJhbmdlKFJhbmsuU3VtKSAlPiUgCiAgaGVhZCguLCAxMCkgCiAgIyBrbml0cjo6a2FibGUoLiwgcm93Lm5hbWVzID0gRiwgYWxpZ249cmVwKCJsIiwgNCksIGZvcm1hdCA9ICJodG1sIiwgZXNjYXBlID0gRikgJT4lIAogICMga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKQp0b3AuMTAudXAKYGBgCmAKIyMjIFNhdmUgdG9wIDEwIHVwLXJlZ3VsYXRlZCBnZW5lcwpgYGB7cn0Kb3V0LnRhYmxlLnVwIDwtIHBhc3RlMCgiZGF0YV9vdXQvIix0aGlzLnNjcmlwdCwgIi50b3AudXAudHh0IikKb3V0LnRhYmxlLnVwCndyaXRlLnRhYmxlKHRvcC4xMC51cCwgZmlsZSA9IG91dC50YWJsZS51cCwgc2VwID0gIlx0IikKYGBgCgoKIyMgSGVhdG1hcHMgZm9yIHRvcCByZWd1bGF0ZWQgZ2VuZXMgIAooW0RhdGFub3ZpYSBUdXRvcmlhbF0oaHR0cHM6Ly93d3cuZGF0YW5vdmlhLmNvbS9lbi9sZXNzb25zL2hlYXRtYXAtaW4tci1zdGF0aWMtYW5kLWludGVyYWN0aXZlLXZpc3VhbGl6YXRpb24vI3ItYmFzZS1oZWF0bWFwLWhlYXRtYXApKQoKYGBge3J9CmdldC5oZWF0bWFwIDwtIGZ1bmN0aW9uKG14LCBoZWF0LmNvbHMsIGhhKSB7CiAgQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoCiAgbXgsIG5hbWUgPSAiUmFuayIsCiAgY29sID0gaGVhdC5jb2xzLAogIHRvcF9hbm5vdGF0aW9uID0gaGEsCiAgY2x1c3Rlcl9yb3dzID0gRkFMU0UsCiAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSwKICBzaG93X2NvbHVtbl9uYW1lcyA9IFRSVUUKICApCn0KCmBgYAoKIyMjIEhlYXRtYXA6IDEwMCBtb3N0IGRvd24tcmVndWxhdGVkIGdlbmVzICAKYGBge3J9CiMgbnJvdyhzdWJzZXQocmFuay5zdW0udXAsIFAuVmFsLkFkaiA8PSAwLjA1KSkgIyA1OTQKIyBucm93KHN1YnNldChyYW5rLnN1bS51cCwgUC5WYWwuQWRqIDw9IDAuMDEpKSAjIDMxNwpkb3duLjEwMCA8LSBkcGx5cjo6YXJyYW5nZShyYW5rLnN1bS5kb3duLCBQLlZhbC5BZGosIFJhbmsuU3VtKSAlPiUgCiAgaGVhZCguLDEwMCkgJT4lIC4kR2VuZQojIGRvd24uMTAwCm14LmRvd24uMTAwIDwtIHJhbmtzLmRvd24ubXguMltkb3duLjEwMCxdICU+JSAKICBzZXRfY29sbmFtZXMobmFtZXMoYWxsLmRhdGEubGlzdCkpCiBjb2xuYW1lcyhteC5kb3duLjEwMCkgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMobXguZG93bi4xMDApLCAiX0RDIiwgIiAoREMpIikKIGNvbG5hbWVzKG14LmRvd24uMTAwKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhteC5kb3duLjEwMCksICJfTVAiLCAiIChNUCkiKQpjb2xuYW1lcyhteC5kb3duLjEwMCkgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMobXguZG93bi4xMDApLCAiLk1GMSIsICIgKE1GMSkiKQpjb2xuYW1lcyhteC5kb3duLjEwMCkgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMobXguZG93bi4xMDApLCAiLk1GMiIsICIgKE1GMikiKQojIGRpbShteC5kb3duLjEwMCkgIyAxMDAgICA1CmhlYWQobXguZG93bi4xMDApCmBgYAojIyMgU2F2ZSB0b3AgMTAwIHVwLXJlZ3VsYXRlZCBnZW5lcwpgYGB7cn0Kb3V0LmZpbGUgPC0gcGFzdGUoImRhdGFfb3V0Iiwgc3RyaW5ncjo6c3RyX3JlcGxhY2UodGhpcy5zY3JpcHQsIlxcLltScl1bTW1dW0RkXSQiLCAiLmRvd24uMTAwLnR4dCIpLCBzZXA9Ii8iKQoKb3V0LmZpbGUKY2F0KGRvd24uMTAwLCBmaWxlID0gb3V0LmZpbGUsIHNlcCA9ICJcbiIpCmBgYAoKCgpgYGB7cn0KZG93bi4xMDAuZGYgPC0gYXMuZGF0YS5mcmFtZSh0KG14LmRvd24uMTAwKSkKIyBkaW0oZG93bi4xMDAuZGYpICMgNSAxMDAKCnJvdy5uYW1lcyhkb3duLjEwMC5kZikgPC0gbmFtZXMoYWxsLmRhdGEubGlzdCkKCmRvd24uMTAwLmRmIDwtIGFzLmRhdGEuZnJhbWUodChteC5kb3duLjEwMCkpICU+JSAKICBzZXRfcm93bmFtZXMobmFtZXMoYWxsLmRhdGEubGlzdCkpICU+JSAKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigiRGF0YXNldCIpICU+JSAKICBkcGx5cjo6bXV0YXRlKFRlY2hub2xvZ3kgPSAiTWljcm9hcnJheSIpICU+JQogIGRwbHlyOjptdXRhdGUoQ2VsbC50eXBlID0gIk1hY3JvcGhhZ2VzIikgJT4lCiAgZHBseXI6OnNlbGVjdChEYXRhc2V0LCBUZWNobm9sb2d5LCBDZWxsLnR5cGUsIGRwbHlyOjpldmVyeXRoaW5nKCkpCmRvd24uMTAwLmRmJFRlY2hub2xvZ3lbZG93bi4xMDAuZGYkRGF0YXNldCAlaW4lIG5hbWVzKHJuYS5zZXEuZGF0YS5saXN0KV0gPC0gIlJOQS1zZXEiCmRvd24uMTAwLmRmJENlbGwudHlwZVtkb3duLjEwMC5kZiREYXRhc2V0ICVpbiUgYygiR1NFNjQxNzkiLCAiQS1CVUdTLTIzX0RDIiwgIkdTRTM0MTUxIildIDwtICJEZW5kcml0aWMgY2VsbHMiCmRvd24uMTAwLmRmWywxOjZdCmBgYAoKYGBge3J9CiMgaGVscChIZWF0bWFwQW5ub3RhdGlvbiwgcGFja2FnZT0iQ29tcGxleEhlYXRtYXAiKQpjb2wuZG93biA8LSBsaXN0KFRlY2hub2xvZ3kgPSBjKCJNaWNyb2FycmF5IiA9ICJEYXJrYmx1ZSIsICJSTkEtc2VxIiA9ICJsaWdodGJsdWUiKSwKICAgICAgICAgICAgQ2VsbC50eXBlID0gYygiTWFjcm9waGFnZXMiID0gImdyZXkiLCAiRGVuZHJpdGljIGNlbGxzIiA9ICJibGFjayIpKQpoYS5kb3duIDwtIENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwQW5ub3RhdGlvbigKICBUZWNobm9sb2d5ID0gZG93bi4xMDAuZGYkVGVjaG5vbG9neSwKICBDZWxsLnR5cGUgPSBkb3duLjEwMC5kZiRDZWxsLnR5cGUsCiAgY29sPWNvbC5kb3duCikKcm0oY29sLmRvd24pCmBgYAoKYGBge3J9CiMgIllsT3JSZCIsICJZbE9yQnIiLCAiWWxHbkJ1IiosICJQdUJ1R24iLCAiWWxHbiIsICJSZFB1Ii0sICJQdVJkIi0sICJCdUduIgojICJCbHVlcyIsICJHcmVlbnMiLCAiUHVycGxlcyIsICJPcmFuZ2VzIgpoZWF0LmNvbHMgPC0gY29sb3JSYW1wUGFsZXR0ZShSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNywgIk9yYW5nZXMiKSkoMjU2KSAlPiUgcmV2CmdldC5oZWF0bWFwKG14LmRvd24uMTAwLCBoZWF0LmNvbHMsIGhhLmRvd24pCmBgYAojIyMgU2F2ZSBoZWF0bWFwIGZvciBkb3duLXJlZ3VsYXRlZCBnZW5lcwpgYGB7cn0Kb3V0LmZpbGUubWFwLmRvd24gPC0gcGFzdGUoImRhdGFfb3V0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3JlcGxhY2UodGhpcy5zY3JpcHQsIlxcLltScl1bTW1dW0RkXSQiLCAiLm1hcC5kb3duLnBkZiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzZXA9Ii8iKQpvdXQuZmlsZS5tYXAuZG93bgpgYGAKCmBgYHtyfQpwZGYoZmlsZSA9IG91dC5maWxlLm1hcC5kb3duLCB3aWR0aD02LCBoZWlnaHQ9OC41KQpnZXQuaGVhdG1hcChteC5kb3duLjEwMCwgaGVhdC5jb2xzLCBoYS5kb3duKQpkZXYub2ZmKCkKYGBgCgoKIyMjIEhlYXRtYXA6IDEwMCBtb3N0IHVwLXJlZ3VsYXRlZCBnZW5lcyAgCmBgYHtyfQp1cC4xMDAgPC0gZHBseXI6OmFycmFuZ2UocmFuay5zdW0udXAsIFAuVmFsLkFkaiwgUmFuay5TdW0pICU+JSAKICBoZWFkKC4sMTAwKSAlPiUgLiRHZW5lCmhlYWQodXAuMTAwKQpteC51cC4xMDAgPC0gcmFua3MudXAubXguMlt1cC4xMDAsXSAlPiUgCiAgc2V0X2NvbG5hbWVzKG5hbWVzKGFsbC5kYXRhLmxpc3QpKQpjb2xuYW1lcyhteC5kb3duLjEwMCkgPC0gc3RyX3JlcGxhY2UoY29sbmFtZXMobXguZG93bi4xMDApLCAiX0RDIiwgIiAoREMpIikKY29sbmFtZXMobXguZG93bi4xMDApIDwtIHN0cl9yZXBsYWNlKGNvbG5hbWVzKG14LmRvd24uMTAwKSwgIl9NUCIsICIgKE1QKSIpCmNvbG5hbWVzKG14LmRvd24uMTAwKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhteC5kb3duLjEwMCksICIuTUYxIiwgIiAoTUYxKSIpCmNvbG5hbWVzKG14LmRvd24uMTAwKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhteC5kb3duLjEwMCksICIuTUYyIiwgIiAoTUYyKSIpCiBkaW0obXgudXAuMTAwKSAjIDEwMCAgIDYKaGVhZChteC51cC4xMDApCgpgYGAKIyMjIFNhdmUgdG9wIDEwMCB1cC1yZWd1bGF0ZWQgZ2VuZXMKYGBge3J9Cm91dC5maWxlIDwtIHBhc3RlKCJkYXRhX291dCIsIHN0cmluZ3I6OnN0cl9yZXBsYWNlKHRoaXMuc2NyaXB0LCJcXC5bUnJdW01tXVtEZF0kIiwgIi51cC4xMDAudHh0IiksIHNlcD0iLyIpCgpvdXQuZmlsZQpjYXQodXAuMTAwLCBmaWxlID0gb3V0LmZpbGUsIHNlcCA9ICJcbiIpCgpgYGAKCgpgYGB7cn0KdXAuMTAwLmRmIDwtIGFzLmRhdGEuZnJhbWUodChteC51cC4xMDApKQojIGRpbShkb3duLjEwMC5kZikgIyA1IDEwMApyb3cubmFtZXModXAuMTAwLmRmKSA8LSBuYW1lcyhhbGwuZGF0YS5saXN0KQoKdXAuMTAwLmRmIDwtIGFzLmRhdGEuZnJhbWUodChteC51cC4xMDApKSAlPiUgCiAgc2V0X3Jvd25hbWVzKG5hbWVzKGFsbC5kYXRhLmxpc3QpKSAlPiUgCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oIkRhdGFzZXQiKSAlPiUgCiAgZHBseXI6Om11dGF0ZShUZWNobm9sb2d5ID0gIk1pY3JvYXJyYXkiKSAlPiUKICBkcGx5cjo6bXV0YXRlKENlbGwudHlwZSA9ICJNYWNyb3BoYWdlcyIpICU+JQogIGRwbHlyOjpzZWxlY3QoRGF0YXNldCwgVGVjaG5vbG9neSwgQ2VsbC50eXBlLCBkcGx5cjo6ZXZlcnl0aGluZygpKQp1cC4xMDAuZGYkVGVjaG5vbG9neVt1cC4xMDAuZGYkRGF0YXNldCAlaW4lIG5hbWVzKHJuYS5zZXEuZGF0YS5saXN0KV0gPC0gIlJOQS1zZXEiCnVwLjEwMC5kZiRDZWxsLnR5cGVbdXAuMTAwLmRmJERhdGFzZXQgJWluJSBjKCJHU0U2NDE3OSIsICJBLUJVR1MtMjNfREMiLCJHU0UzNDE1MSIpXSA8LSAiRGVuZHJpdGljIGNlbGxzIgoKYGBgCgpgYGB7cn0KY29sLnVwIDwtIGxpc3QoVGVjaG5vbG9neSA9IGMoIk1pY3JvYXJyYXkiID0gIkRhcmtibHVlIiwgIlJOQS1zZXEiID0gImxpZ2h0Ymx1ZSIpLAogICAgICAgICAgICBDZWxsLnR5cGUgPSBjKCJNYWNyb3BoYWdlcyIgPSAiZ3JleSIsICJEZW5kcml0aWMgY2VsbHMiID0gImJsYWNrIikpCmhhLnVwIDwtIENvbXBsZXhIZWF0bWFwOjpIZWF0bWFwQW5ub3RhdGlvbigKICBUZWNobm9sb2d5ID0gdXAuMTAwLmRmJFRlY2hub2xvZ3ksCiAgQ2VsbC50eXBlID0gdXAuMTAwLmRmJENlbGwudHlwZSwKICBjb2w9Y29sLnVwCikKaGVhdC5jb2xzIDwtIGNvbG9yUmFtcFBhbGV0dGUoUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDcsICJPcmFuZ2VzIikpKDI1NikgJT4lIHJldgpnZXQuaGVhdG1hcChteC51cC4xMDAsIGhlYXQuY29scywgaGEudXApCmBgYAoKIyMjIFNhdmUgaGVhdG1hcCBmb3IgdXAtcmVndWxhdGVkIGdlbmVzCmBgYHtyfQpvdXQuZmlsZS5tYXAudXAgPC0gcGFzdGUoImRhdGFfb3V0Iiwgc3RyaW5ncjo6c3RyX3JlcGxhY2UodGhpcy5zY3JpcHQsIlxcLltScl1bTW1dW0RkXSQiLCAiLm1hcC51cC5wZGYiKSwgc2VwPSIvIikKb3V0LmZpbGUubWFwLnVwCmBgYAoKYGBge3J9CnBkZihmaWxlID0gb3V0LmZpbGUubWFwLnVwLCB3aWR0aD02LCBoZWlnaHQ9OC41KQpnZXQuaGVhdG1hcChteC51cC4xMDAsIGhlYXQuY29scywgaGEudXApCmRldi5vZmYoKQpgYGAKCgojIyBTYXZlIGdlbmVzICAKIyMjIFNhdmUgdG9wIGRvd24tcmVndWxhdGVkIGdlbmVzICAKYGBge3IgcmVzdWx0cz0iYXNpcyJ9CmdzLmRvd24uMDEgPC0gcmFuay5zdW0uZG93biAlPiUgCiAgZHBseXI6OmZpbHRlcihQLlZhbC5BZGogPD0gMC4wMSkgJT4lIAogIHVzZV9zZXJpZXMoIkdlbmUiKQogbGVuZ3RoKGdzLmRvd24uMDEpICMgODI0CmNhdCgiTnVtYmVyIG9mIGNvbnNpc3RlbnRseSA8Yj48aT5kb3duPC9pPjwvYj5yZWd1bGF0ZWQgZ2VuZXMgKHAgPD0gMC4wMSk6PGI+IiwgbGVuZ3RoKGdzLmRvd24uMDEpLCAiPC9iPiIpCgpgYGAKYGBge3J9Cm91dC5maWxlLmRvd24uMDEgPC0gcGFzdGUwKCJkYXRhX291dC8iLCB0aGlzLnNjcmlwdCwgIi5kb3duLjAxLnR4dCIpCmNhdChncy5kb3duLjAxLCBzZXA9IlxuIiwgZmlsZT1vdXQuZmlsZS5kb3duLjAxKQpjYXQoIkZpbGUgc2F2ZWQ6Iiwgb3V0LmZpbGUuZG93bi4wMSkKCmBgYAoKIyMjIFNhdmUgZG93bi1yZWd1bGF0ZWQgZ2VuZXMgd2l0aCBhZGp1c3RlZCBwIHZhbHVlIDw9IDAuMDAxCmBgYHtyfQpncy5kb3duLjAwMSA8LSByYW5rLnN1bS5kb3duICU+JSAKICBkcGx5cjo6ZmlsdGVyKFAuVmFsLkFkaiA8PSAwLjAwMSkgJT4lIAogIHVzZV9zZXJpZXMoIkdlbmUiKQogbGVuZ3RoKGdzLmRvd24uMDAxKSAjIDI5OQpjYXQoIk51bWJlciBvZiBjb25zaXN0ZW50bHkgPGI+PGk+ZG93bjwvaT48L2I+cmVndWxhdGVkIGdlbmVzIChwIDw9IDAuMDAxKTo8Yj4iLCBsZW5ndGgoZ3MuZG93bi4wMDEpLCAiPC9iPiIpCgoKYGBgCgpgYGB7cn0Kb3V0LmZpbGUuZG93bi4wMDEgPC0gcGFzdGUwKCJkYXRhX291dC8iLCB0aGlzLnNjcmlwdCwgIi5kb3duLjAwMS50eHQiKQpjYXQoZ3MuZG93bi4wMDEsIHNlcD0iXG4iLCBmaWxlPW91dC5maWxlLmRvd24uMDAxKQpjYXQoIkZpbGUgc2F2ZWQ6Iiwgb3V0LmZpbGUuZG93bi4wMDEpCgpgYGAKCgojIyMgU2F2ZSB0b3AgdXAtcmVndWxhdGVkIGdlbmVzICAKYGBge3IgcmVzdWx0cz0iYXNpcyJ9CmdzLnVwLjAxIDwtIHJhbmsuc3VtLnVwICU+JSAKICBkcGx5cjo6ZmlsdGVyKFAuVmFsLkFkaiA8PSAwLjAxKSAlPiUgCiAgdXNlX3NlcmllcygiR2VuZSIpCmxlbmd0aChncy51cC4wMSkgIyA5MTIKY2F0KCJOdW1iZXIgb2YgY29uc2lzdGVudGx5IDxiPjxpPnVwPC9pPjwvYj5yZWd1bGF0ZWQgZ2VuZXMgKHAgPD0gMC4wMSk6PGI+IiwgbGVuZ3RoKGdzLnVwLjAxKSwgIjwvYj4iKQpgYGAKCmBgYHtyfQpvdXQuZmlsZS51cC4wMSA8LSBwYXN0ZTAoImRhdGFfb3V0LyIsIHRoaXMuc2NyaXB0LCAiLnVwLjAxLnR4dCIpCmNhdChncy51cC4wMSwgc2VwPSJcbiIsIGZpbGU9b3V0LmZpbGUudXAuMDEpCmNhdCgiRmlsZSBzYXZlZDoiLCBvdXQuZmlsZS51cC4wMSkKYGBgCiMjIyBTYXZlIHVwLXJlZ3VsYXRlZCBnZW5lcyB3aXRoIGFkanVzdGVkIHAgdmFsdWUgPD0gMC4wMDEKYGBge3J9CmdzLnVwLjAwMSA8LSByYW5rLnN1bS51cCAlPiUgCiAgZHBseXI6OmZpbHRlcihQLlZhbC5BZGogPD0gMC4wMDEpICU+JSAKICB1c2Vfc2VyaWVzKCJHZW5lIikKbGVuZ3RoKGdzLnVwLjAwMSkgIyA0NzkKY2F0KCJOdW1iZXIgb2YgY29uc2lzdGVudGx5IDxiPjxpPnVwPC9pPjwvYj5yZWd1bGF0ZWQgZ2VuZXMgKHAgPD0gMC4wMDEpOjxiPiIsIGxlbmd0aChncy51cC4wMDEpLCAiPC9iPiIpCmBgYAoKYGBge3J9Cm91dC5maWxlLnVwLjAwMSA8LSBwYXN0ZTAoImRhdGFfb3V0LyIsIHRoaXMuc2NyaXB0LCAiLnVwLjAwMS50eHQiKQpjYXQoZ3MudXAuMDAxLCBzZXA9IlxuIiwgZmlsZT1vdXQuZmlsZS51cC4wMDEpCmNhdCgiRmlsZSBzYXZlZDoiLCBvdXQuZmlsZS51cC4wMDEpCmBgYAoKCiMjIFNlc3Npb24gaW5mbyAgCjxidXR0b24gY2xhc3M9ImJ1dHRvbiIgb25jbGljaz0ibXlGdW5jdGlvbignRElWXzUnKSI+U2hvdy9oaWRlIHNlc3Npb24gaW5mbzwvYnV0dG9uPgo8ZGl2IGlkPSJESVZfNSIgY2xhc3M9ImRpdl9kZWZhdWx0X2hpZGUiPgoKYGBge3IgcHJpbnRfZGF0ZV9hbmRfdGltZX0KU3lzLnRpbWUoKQpgYGAKCmBgYHtyIHByaW50X3Nlc3Npb25faW5mb30KIyBzZXNzaW9uSW5mbygpCmRldnRvb2xzOjpzZXNzaW9uX2luZm8oKQpgYGAKPC9kaXY+CiAgCjxzY3JpcHQ+CiAgdmFyIGRpdnNUb0hpZGUgPSBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCJkaXZfZGVmYXVsdF9oaWRlIik7CmZvcih2YXIgaSA9IDA7IGkgPCBkaXZzVG9IaWRlLmxlbmd0aDsgaSsrKQp7CiAgZGl2c1RvSGlkZVtpXS5zdHlsZS5kaXNwbGF5ID0gJ25vbmUnOwp9Cjwvc2NyaXB0PgoKYGBge3J9CmtuaXRyOjpwdXJsKHRoaXMuc2NyaXB0LCBkb2N1bWVudGF0aW9uID0gMCkKYGBgCgo=