00. Envirenment Settings

options(install.packages.compile.from.source = "always")
list.packages <- c(
  "readxl", "ggplot2", "ggpubr", "matrixStats", "ggrepel", "reshape2", "dplyr", "stringr",
  "grid", "tcltk", "parallel", "doParallel", "doSNOW", "data.table", "gplots",
  "randomcoloR", "factoextra", "RColorBrewer", "grDevices", "gmp", "xtable", "latex2exp",
  "httr", "jsonlite", "curl", "RCurl", "magrittr", "rlist", "pipeR", "plyr",
  "xml2", "rvest", "knn.covertree", "knitr", "rlang", "visNetwork", "hwriter", "htmltools"
)
bio.list.packages <- c(
  "limma", "ExperimentHub", "clusterProfiler", "GO.db",
  "org.Mm.eg.db", "pathview", "enrichplot", "org.Hs.eg.db"
)
new.packages <- list.packages[!list.packages %in% installed.packages()]
bio.new.packages <- bio.list.packages[!bio.list.packages %in% installed.packages()]
# Packages that does not install yet
if (!requireNamespace("BiocManager", quietly = TRUE)) {
  update.packages(ask = FALSE)
  install.packages("BiocManager", repos="https://cloud.r-project.org")
}
if (length(new.packages)) {
  install.packages(new.packages)
  devtools::install_github("grimbough/biomaRt", force = FALSE)
}
if (length(bio.new.packages)) {
  update.packages(ask = FALSE)
  BiocManager::install(bio.new.packages)
}
# Loading all packages & functions
invisible(lapply(c(list.packages, bio.list.packages, "biomaRt"), library, character.only = TRUE))

Attaching package: ‘dplyr’

The following object is masked from ‘package:matrixStats’:

    count

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union

Loading required package: foreach
Loading required package: iterators
Loading required package: snow

Attaching package: ‘snow’

The following objects are masked from ‘package:parallel’:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ, clusterExport, clusterMap, clusterSplit, makeCluster, parApply,
    parCapply, parLapply, parRapply, parSapply, splitIndices, stopCluster

data.table 1.14.8 using 64 threads (see ?getDTthreads).  Latest news: r-datatable.com

Attaching package: ‘data.table’

The following objects are masked from ‘package:dplyr’:

    between, first, last

The following objects are masked from ‘package:reshape2’:

    dcast, melt


Attaching package: ‘gplots’

The following object is masked from ‘package:stats’:

    lowess

Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa

Attaching package: ‘gmp’

The following objects are masked from ‘package:base’:

    %*%, apply, crossprod, matrix, tcrossprod

Using libcurl 7.81.0 with OpenSSL/3.0.2

Attaching package: ‘curl’

The following object is masked from ‘package:httr’:

    handle_reset

----------------------------------------------------------------------------------------------------------------------------------------------
You have loaded plyr after dplyr - this is likely to cause problems.
If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
library(plyr); library(dplyr)
----------------------------------------------------------------------------------------------------------------------------------------------

Attaching package: ‘plyr’

The following objects are masked from ‘package:dplyr’:

    arrange, count, desc, failwith, id, mutate, rename, summarise, summarize

The following object is masked from ‘package:matrixStats’:

    count

The following object is masked from ‘package:ggpubr’:

    mutate


Attaching package: ‘rlang’

The following object is masked from ‘package:xml2’:

    as_list

The following object is masked from ‘package:magrittr’:

    set_names

The following objects are masked from ‘package:jsonlite’:

    flatten, unbox

The following object is masked from ‘package:data.table’:

    :=

Loading required package: BiocGenerics

Attaching package: ‘BiocGenerics’

The following object is masked from ‘package:limma’:

    plotMA

The following objects are masked from ‘package:gmp’:

    which.max, which.min

The following objects are masked from ‘package:dplyr’:

    combine, intersect, setdiff, union

The following objects are masked from ‘package:stats’:

    IQR, mad, sd, var, xtabs

The following objects are masked from ‘package:base’:

    anyDuplicated, aperm, append, as.data.frame, basename, cbind, colnames, dirname, do.call, duplicated, eval, evalq, Filter,
    Find, get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply, match, mget, order, paste, pmax, pmax.int, pmin,
    pmin.int, Position, rank, rbind, Reduce, rownames, sapply, setdiff, sort, table, tapply, union, unique, unsplit, which.max,
    which.min

Loading required package: AnnotationHub
Loading required package: BiocFileCache
Loading required package: dbplyr
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      

Attaching package: ‘dbplyr’

The following objects are masked from ‘package:dplyr’:

    ident, sql


clusterProfiler v4.6.2  For help: https://yulab-smu.top/biomedical-knowledge-mining-book/

If you use clusterProfiler in published research, please cite:
T Wu, E Hu, S Xu, M Chen, P Guo, Z Dai, T Feng, L Zhou, W Tang, L Zhan, X Fu, S Liu, X Bo, and G Yu. clusterProfiler 4.0: A universal enrichment tool for interpreting omics data. The Innovation. 2021, 2(3):100141

Attaching package: ‘clusterProfiler’

The following objects are masked from ‘package:plyr’:

    arrange, mutate, rename, summarise

The following object is masked from ‘package:stats’:

    filter

Loading required package: AnnotationDbi
Loading required package: stats4
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with 'browseVignettes()'. To cite Bioconductor, see 'citation("Biobase")', and
    for packages 'citation("pkgname")'.


Attaching package: ‘Biobase’

The following object is masked from ‘package:ExperimentHub’:

    cache

The following object is masked from ‘package:AnnotationHub’:

    cache

The following object is masked from ‘package:rlang’:

    exprs

The following object is masked from ‘package:httr’:

    content

The following objects are masked from ‘package:matrixStats’:

    anyMissing, rowMedians

Loading required package: IRanges
Loading required package: S4Vectors

Attaching package: ‘S4Vectors’

The following object is masked from ‘package:clusterProfiler’:

    rename

The following object is masked from ‘package:plyr’:

    rename

The following object is masked from ‘package:rlist’:

    List

The following object is masked from ‘package:gplots’:

    space

The following objects are masked from ‘package:data.table’:

    first, second

The following objects are masked from ‘package:dplyr’:

    first, rename

The following objects are masked from ‘package:base’:

    expand.grid, I, unname


Attaching package: ‘IRanges’

The following object is masked from ‘package:clusterProfiler’:

    slice

The following object is masked from ‘package:plyr’:

    desc

The following object is masked from ‘package:data.table’:

    shift

The following objects are masked from ‘package:dplyr’:

    collapse, desc, slice


Attaching package: ‘AnnotationDbi’

The following object is masked from ‘package:clusterProfiler’:

    select

The following object is masked from ‘package:dplyr’:

    select



##############################################################################
Pathview is an open source software package distributed under GNU General
Public License version 3 (GPLv3). Details of GPLv3 is available at
http://www.gnu.org/licenses/gpl-3.0.html. Particullary, users are required to
formally cite the original Pathview paper (not just mention it) in publications
or products. For details, do citation("pathview") within R.

The pathview downloads and uses KEGG data. Non-academic uses may require a KEGG
license agreement (details at http://www.kegg.jp/kegg/legal.html).
##############################################################################

Attaching package: ‘enrichplot’

The following object is masked from ‘package:ggpubr’:

    color_palette
options(rgl.useNULL = F, ggrepel.max.overlaps = Inf)

01. Check Results from GSE138783

01-01. Functional Enrichment Analysis (FEA) Results from GSE138783

Load the functional enrichment analysis (FEA) results from our re-analyzed GSE138783 Bulk RNA-seq

load(file.path(".", "01.HEK293_TMG_OSMI2", "00.Results", "DESeq2_Results.Rdata"))


Select the comparison with the most different treatment which is 6h TMG vs 6h OSMI-1

ORA_list <- DEGs_ls[["HEK293_TMG_6hBvsHEK293_OSMI2_6hA"]][["Over_Represent"]]


Keep the GO_BP terms that contains either “mitochondria” or “cytoskeleton”

for(i in c("All", "Up_regulated", "Down_regulated")){
  temp <- ORA_list[[i]]$GO_BP$Raw@result$Description %>%
    grep("(mitochondria(l){0,1}|cytoskele(ton|tal))", ., ignore.case = TRUE)
  ORA_list[[i]]$GO_BP$Raw@result <- ORA_list[[i]]$GO_BP$Raw@result[temp,]
  ORA_list[[i]]$GO_BP$Sig <- ORA_list[[i]]$GO_BP$Raw@result
}
names <- "TMG 6h vs OSMI-1 6h"
k <- "GO_BP"
temp <- ORA_list
mydf<-data.frame()
geneClusters<-list()
for(j in c("All","Up_regulated","Down_regulated")){
  if(is.null(nrow(temp[[j]][[k]][["Sig"]]))){next()}
  mydf<-rbind(
    mydf,
    cbind(
      group=rep(j,nrow(temp[[j]][[k]][["Sig"]])),
      temp[[j]][[k]][["Sig"]][,1:9]
    )
  )
  geneClusters[[j]]<-(temp[[j]][[k]][["Sig"]]$geneID)%>%
    str_split(.,"/")%>%unlist()%>%unique()
}

rownames(mydf)<-1:nrow(mydf)
mydf$group<-factor(mydf$group,levels=c("All","Up_regulated","Down_regulated"))
colnames(mydf)[1]<-"Cluster"
res <- new("compareClusterResult",
           compareClusterResult = mydf,
           geneClusters = geneClusters
)
.call<-match.call(compareCluster,call("compareCluster",
                                      ENSEMBL~group, data=mydf, fun="enrichGO",
                                      OrgDb         = org.Hs.eg.db,
                                      keyType       = "ENSEMBL",
                                      ont           = "BP",
                                      pAdjustMethod = "BH",
                                      pvalueCutoff  = 1,
                                      qvalueCutoff  = 1,
                                      readable      = TRUE))
.call$geneClusters<-as.symbol("geneClusters")
.call$OrgDb<-as.symbol("org.Hs.eg.db")
.call$data<-NULL
res@.call<-.call
res@fun<-"enrichGO"
res@keytype<-"UNKNOWN"
res@readable<-FALSE
if(!dir.exists(file.path(".","01.PlotOut"))){
  dir.create(file.path(".","01.PlotOut"))
}

01-02. Check FEA Results

Overlap

gp <- dotplot(
  res, showCategory=1000, label_format=85, color = "pvalue",
  title=paste0("ORA Summary of ", names, " for ", k," Terms.")
)
svg(filename = file.path(".", "01.PlotOut", "FEA_Overlap.svg"), width = 12, height = 17, pointsize = 12)
print(gp)
dev.off()
null device 
          1 
graphics.off()
knitr::opts_knit$set(global.device = TRUE)
print(gp)

knitr::opts_knit$set(global.device = FALSE)

All DEGs

j <- "GO_BP"
k <- "All"
Par_name <- "TMG 6h vs OSMI-1 6h"
FEA_title<-paste0(k, " ", nrow(ORA_list[[k]][[j]]$Raw)," significant GO_", j, " terms (", Par_name, "; the most descend terms)")
p1<-dotplot(ORA_list[[k]][[j]]$Raw, showCategory=1000, label_format=100, color = "pvalue", font.size = 18)
p2<-treeplot(
  pairwise_termsim(ORA_list[[k]][[j]]$Raw, method="JC"), showCategory=1000, color = "pvalue",
  cluster.params=list(hclust_method = "ward.D2", label_words_n = 5, n = 5, font.size = 18)
)
gp <- gridExtra::grid.arrange(
  p1, p2, nrow = 1, widths = c(1, 1),
  top=textGrob(paste0("GSEA: ",FEA_title," with similarity summary"), gp=gpar(fontsize=18, col="black"))
)
svg(filename = file.path(".", "01.PlotOut", "FEA_All.svg"), width = 26, height = 16, pointsize = 12)
plot(gp)
dev.off()
png 
  2 
graphics.off()
knitr::opts_knit$set(global.device = TRUE)
plot(gp)

knitr::opts_knit$set(global.device = FALSE)

Up-regulated DEGs

j <- "GO_BP"
k <- "Up_regulated"
Par_name <- "TMG 6h vs OSMI-1 6h"
FEA_title<-paste0(k, " ", nrow(ORA_list[[k]][[j]]$Raw)," significant GO_", j, " terms (", Par_name, "; the most descend terms)")
p1<-dotplot(ORA_list[[k]][[j]]$Raw, showCategory=1000, label_format=100, color = "pvalue", font.size = 18)
p2<-treeplot(
  pairwise_termsim(ORA_list[[k]][[j]]$Raw, method="JC"), showCategory=1000, color = "pvalue",
  cluster.params=list(hclust_method = "ward.D2", label_words_n = 5, n = 5, font.size = 18)
)
gp <- gridExtra::grid.arrange(
  p1, p2, nrow = 1, widths = c(1, 1),
  top=textGrob(paste0("GSEA: ",FEA_title," with similarity summary"), gp=gpar(fontsize=18, col="black"))
)
svg(filename = file.path(".", "01.PlotOut", "FEA_Up.svg"), width = 16, height = 9, pointsize = 12)
plot(gp)
dev.off()
png 
  2 
graphics.off()
knitr::opts_knit$set(global.device = TRUE)
plot(gp)

knitr::opts_knit$set(global.device = FALSE)

Down-regulated DEGs

j <- "GO_BP"
k <- "Down_regulated"
Par_name <- "TMG 6h vs OSMI-1 6h"
FEA_title<-paste0(k, " ", nrow(ORA_list[[k]][[j]]$Raw)," significant GO_", j, " terms (", Par_name, "; the most descend terms)")
p1<-dotplot(ORA_list[[k]][[j]]$Raw, showCategory=1000, label_format=70, color = "pvalue", font.size = 18)
p2<-treeplot(
  pairwise_termsim(ORA_list[[k]][[j]]$Raw, method="JC"), showCategory=1000, color = "pvalue",
  cluster.params=list(hclust_method = "ward.D2", label_words_n = 5, n = 5, font.size = 18), label_format=25
)
gp <- gridExtra::grid.arrange(
  p1, p2, nrow = 1, widths = c(1, 1),
  top=textGrob(paste0("GSEA: ",FEA_title," with similarity summary"), gp=gpar(fontsize=18, col="black"))
)
svg(filename = file.path(".", "01.PlotOut", "FEA_Down.svg"), width = 26, height = 15, pointsize = 12)
plot(gp)
dev.off()
png 
  2 
graphics.off()
knitr::opts_knit$set(global.device = TRUE)
plot(gp)

knitr::opts_knit$set(global.device = FALSE)

02. Retrieve up-stream transcription factors of interested GO_BP terms

In this section we will check the up-stream transcription factors that regulates the genes inside the Top5 overlapped mitochondrial/cytoskeletal GO_BP terms by using TFLink database.

The Top5 overlapped mitochondrial/cytoskeletal GO_BP terms are: 1. GO:0030705, cytoskeleton-dependent intracellular transport 2. GO:0061640, cytoskeleton-dependent cytokinesis 3. GO:0140053, mitochondrial gene expression 4. GO:0070507, regulation of microtubule cytoskeleton organization 5. GO:0006839, mitochondrial transport

02-01. Get up-stream Transcription factors list

Download TFLink database

TFLink <- fread(
  "https://cdn.netbiol.org/tflink/download_files/TFLink_Homo_sapiens_interactions_All_simpleFormat_v1.0.tsv.gz"
)

|==================================================|


Check the tables

head(res@compareClusterResult)
head(TFLink)


Get annotation of genes related to Mitochondrial/Cytoskeletal GO_BP terms.

TOP5 <- res@compareClusterResult[res@compareClusterResult$ID %in% c("GO:0030705", "GO:0061640", "GO:0140053", "GO:0070507", "GO:0006839"),]
GeneLs <- TOP5$geneID %>%
  str_split(., "/") %>%
  unlist() %>%
  unique()
Ann_0mart<-NULL
attempt<-1
attempt_max<-12
while(is.null(Ann_0mart)&&attempt<13){
  if(attempt%in%seq(from=1,to=attempt_max,3)){
    url<-"https://useast.ensembl.org"
  }else if(attempt%in%seq(from=2,to=attempt_max,3)){
    url<-"https://www.ensembl.org"
  }else if(attempt%in%seq(from=3,to=attempt_max,3)){
    url<-"https://useast.ensembl.org/"
  }else if(attempt%in%seq(from=4,to=attempt_max,3)){
    url<-"https://asia.ensembl.org/"
  }
  try({
    Ann_0mart <- useDataset(
      "hsapiens_gene_ensembl",
      useMart("ENSEMBL_MART_ENSEMBL", host = url)
    )
  }, silent = TRUE)
  attempt<-attempt+1
  if(is.null(Ann_0mart)){Sys.sleep(10)}
}
Anno_GeneLs <- getBM(
  mart = Ann_0mart,
  attributes = c("hgnc_symbol", "ensembl_gene_id", "external_gene_name"),
  filter = "ensembl_gene_id",
  values = GeneLs,
  uniqueRows = TRUE
)
rownames(Anno_GeneLs)<-Anno_GeneLs$ensembl_gene_id


Make functions based on the Uniport API and prepare the gene list.

library(httr)
isJobReady <- function(jobId) {
  pollingInterval <- 5
  nTries <- 20
  for (i in 1:nTries) {
    url <- paste("https://rest.uniprot.org/idmapping/status/", jobId, sep = "")
    r <- GET(url = url, accept_json())
    status <- httr::content(r, as = "parsed")
    if (!is.null(status[["results"]]) || !is.null(status[["failedIds"]])) {
      return(TRUE)
    }
    if (!is.null(status[["messages"]])) {
      print(status[["messages"]])
      return(FALSE)
    }
    Sys.sleep(pollingInterval)
  }
  return(FALSE)
}
get_next_link <- function(headers) {
  if (!is.null(headers[["Link"]])) {
    match <- str_match(
      headers[["Link"]],
      '^\\<\\s*(.*?)\\s*\\>\\; rel\\=\\"next\\"$'
    )
    if (!isEmpty(match)) {
      return(match[2])
    }
  } else {
    NULL
  }
}


Fetch UniprotID for the Mitochondrial/Cytoskeletal genes using Ensembl ID

temp <- Anno_GeneLs$ensembl_gene_id
files <- list(
  from = "Ensembl",
  to = "UniProtKB-Swiss-Prot",
  ids = paste0(temp, collapse = ",")
)
r <- POST(url = "https://rest.uniprot.org/idmapping/run", body = files, encode = "multipart", accept_json())
r <- POST(url = "https://rest.uniprot.org/idmapping/run", body = files, encode = "multipart", accept_json())
while (r$status_code != 200) {
  r <- POST(url = "https://rest.uniprot.org/idmapping/run", body = files, encode = "multipart", accept_json())
  Sys.sleep(5)
}
submission <- httr::content(r, as = "parsed")
if (isJobReady(submission[["jobId"]])) {
  url <- paste("https://rest.uniprot.org/idmapping/details/", submission[["jobId"]], sep = "")
  response <- GET(url = url, accept_json())
  details <- httr::content(response, as = "parsed")
  res_url <- paste0(
    details[["redirectURL"]],
    "?fields=accession%2Creviewed%2Cid%2Cprotein_name%2Clength%2Cgene_primary%2Cgo%2Cft_zn_fing%2Cft_repeat%2Cft_region%2Cft_domain%2Cft_motif%2Ccc_domain%2Cft_compbias%2Cft_coiled%2Cannotation_score%2Ckeyword%2Ccomment_count%2Ccc_subcellular_location%2Ccc_function%2Ccc_pathway%2Cprotein_families%2Cft_carbohyd%2Cft_peptide%2Cft_transit%2Cft_signal%2Cft_propep%2Ccc_ptm%2Cft_init_met%2Cft_lipid%2Cft_mod_res%2Cft_disulfid%2Cft_crosslnk%2Cft_chain%2Cxref_kegg&format=tsv&size=500"
  )
  response <- GET(url = res_url, accept_json())
  total <- response$headers$`x-total-results`
  temp2 <- data.table::fread(
    res_url,
    verbose = TRUE, showProgress = TRUE
  ) %>% as.data.frame()
  while (!is.null(res_url)) {
    response <- GET(url = res_url, accept_json())
    res_url <- get_next_link(headers(response))
    if (!is.null(res_url)) {
      temp2 <- rbind(
        temp2,
        data.table::fread(
          res_url,
          verbose = FALSE, showProgress = FALSE
        ) %>% as.data.frame()
      )
      print(
        paste0(
          nrow(temp2), "/", total
        )
      )
    }
  }
}
  OpenMP version (_OPENMP)       201511
  omp_get_num_procs()            128
  R_DATATABLE_NUM_PROCS_PERCENT  unset (default 50)
  R_DATATABLE_NUM_THREADS        unset
  R_DATATABLE_THROTTLE           unset (default 1024)
  omp_get_thread_limit()         2147483647
  omp_get_max_threads()          128
  OMP_THREAD_LIMIT               unset
  OMP_NUM_THREADS                unset
  RestoreAfterFork               true
  data.table is using 64 threads with throttle==1024. See ?setDTthreads.

 Downloaded 299 bytes...
 Downloaded 5845 bytes...
 Downloaded 24541 bytes...
 Downloaded 41191 bytes...
 Downloaded 61268 bytes...
 Downloaded 77908 bytes...
 Downloaded 96363 bytes...
 Downloaded 105344 bytes...
 Downloaded 113424 bytes...
 Downloaded 135626 bytes...
 Downloaded 168344 bytes...
 Downloaded 184703 bytes...
 Downloaded 217421 bytes...
 Downloaded 233780 bytes...
 Downloaded 266498 bytes...
 Downloaded 282857 bytes...
 Downloaded 381011 bytes...
 Downloaded 397370 bytes...
 Downloaded 417425 bytes...
Input contains no \n. Taking this to be a filename to open
[01] Check arguments
  Using 64 threads (omp_get_max_threads()=128, nth=64)
  NAstrings = [<<NA>>]
  None of the NAstrings look like numbers.
  show progress = 1
  0/1 column will be read as integer
[02] Opening the file
  Opening file /tmp/RtmpXF8M4i/file14cae4651113d5.
  File opened, size = 1.736MB (1820314 bytes).
  Memory mapped ok
[03] Detect and skip BOM
[04] Arrange mmap to be \0 terminated
  \n has been found in the input and different lines can end with different line endings (e.g. mixed \n and \r\n in one file). This is common and ideal.
[05] Skipping initial rows if needed
  Positioned on line 1 starting: <<From Entry   Reviewed    Entry Name>>
[06] Detect separator, quoting rule, and ncolumns
  Detecting sep automatically ...
  sep=','  with 2 lines of 4 fields using quote rule 0
  sep='|'  with 2 lines of 18 fields using quote rule 0
  sep=0x9  with 100 lines of 36 fields using quote rule 0
  Detected 36 columns on line 1. This line is either column names or first data row. Line starts as: <<From Entry   Reviewed    Entry Name>>
  Quote rule picked = 0
  fill=false and the most number of columns found is 36
[07] Detect column types, good nrow estimate and whether first row is column names
  Number of sampling jump points = 1 because (1820313 bytes from row 1 to eof) / (2 * 478689 jump0size) == 1
  Type codes (jump 000)    : CCCCC5CCCCCCCCCC7CCCCCCC2CCCCCCCCCCC  Quote rule 0
  Type codes (jump 001)    : CCCCC5CCCCCCCCCC7CCCCCCC2CCCCCCCCCCC  Quote rule 0
  'header' determined to be true due to column 6 containing a string on row 1 and a lower type (int32) in the rest of the 170 sample rows
  =====
  Sampled 170 rows (handled \n inside quoted fields) at 2 jump points
  Bytes from first data row on line 2 to the end of last row: 1819858
  Line length: mean=4210.86 sd=3219.03 min=839 max=18007
  Estimated number of rows: 1819858 / 4210.86 = 433
  Initial alloc = 866 rows (433 + 100%) using bytes/max(mean-2*sd,min) clamped between [1.1*estn, 2.0*estn]
  =====
[08] Assign column names
[09] Apply user overrides on column types
  After 0 type and 0 drop user overrides : CCCCC5CCCCCCCCCC7CCCCCCC2CCCCCCCCCCC
[10] Allocate memory for the datatable
  Allocating 36 column slots (36 - 0 dropped) with 866 rows
[11] Read the data
  jumps=[0..1), chunk_size=4210858, total_size=1819858
  1 out-of-sample type bumps: CCCCC5CCCCCCCCCC7CCCCCCCCCCCCCCCCCCC
  jumps=[0..1), chunk_size=4210858, total_size=1819858
Read 428 rows x 36 columns from 1.736MB (1820314 bytes) file in 00:00.027 wall clock time
[12] Finalizing the datatable
  Type counts:
         1 : int32     '5'
         1 : float64   '7'
        34 : string    'C'
=============================
   0.001s (  4%) Memory map 0.002GB file
   0.016s ( 60%) sep='\t' ncol=36 and header detection
   0.000s (  1%) Column type detection using 170 sample rows
   0.000s (  1%) Allocation of 866 rows x 36 cols (0.000GB) of which 428 ( 49%) rows used
   0.009s ( 34%) Reading 1 chunks (0 swept) of 4.016MB (each chunk 428 rows) using 1 threads
   +    0.003s ( 11%) Parse to row-major thread buffers (grown 0 times)
   +    0.005s ( 21%) Transpose
   +    0.001s (  2%) Waiting
   0.001s (  5%) Rereading 1 columns due to out-of-sample type exceptions
   0.027s        Total
Column 25 ("Peptide") bumped from 'bool8' to 'string' due to <<PEPTIDE 688..713; /note="P3(42)"; /id="PRO_0000000095"; PEPTIDE 688..711; /note="P3(40)"; /id="PRO_0000000096">> on row 243
colnames(temp2)[1] <- "EnsemblID"
a <- str_split(temp2$EnsemblID, ",")
temp <- which((lengths(a) >= 2) == TRUE)
for (i in temp) {
  temp1 <- a[[i]]
  temp2$EnsemblID[i] <- temp1[1]
  for (j in 2:length(temp1)) {
    temp2 <- rbind(temp2, temp2[i, ])
    nrow <- nrow(temp2)
    temp2$EnsemblID[nrow] <- temp1[j]
  }
}
dup_ls <- temp2$EnsemblID[duplicated(temp2$EnsemblID)]
for(i in dup_ls){
  temp <- temp2[temp2$EnsemblID!=i,]
  dup_rows <- temp2[temp2$EnsemblID==i,]
  dup_rows <- dup_rows[dup_rows$Length == max(dup_rows$Length),]
  temp2 <-rbind(dup_rows, temp)
}
rownames(temp2) <- temp2$EnsemblID
Anno_GeneLs <- temp2


Get related Transcription Factors (TFs)

TFLink_related <- TFLink[TFLink$UniprotID.Target %in% Anno_GeneLs$Entry,]
#TF_ls <- unique(TFLink_related$UniprotID.TF)
#TFLink_related <- TFLink[(TFLink$UniprotID.TF %in% TF_ls | TFLink$UniprotID.Target %in% TF_ls),]
#TFLink_related <- TFLink_related[(TFLink_related$UniprotID.Target %in% c(TF_ls,Anno_GeneLs$Entry)),]
head(TFLink_related)


02-02. Get the O-GlcNAc info

Add the O-GlcNAc site information from the O-GlcNAcAtlas database.

First we have to retrieve the UniPort Accession number for each genes

library("httr") 
library("dplyr") 
library("jsonlite")
library("curl")
library("RCurl")
library("magrittr")
library("rlist")
library("pipeR")
library("plyr")
library("xml2")
library("rvest")
library("parallel")
library("doParallel")
library("doSNOW")
# Fetch O-GlcNAc records from https://www.oglcnac.org/atlas/search/
temp2 <- data.frame()
Uniportls <- unique(TFLink_related$UniprotID.TF)
Par_cl <- makeCluster(parallelly::freeCores()[1])
registerDoSNOW(Par_cl)
Par_pb <- txtProgressBar(min = 1, max = length(Uniportls), style = 3)
progress <- function(n) setTxtProgressBar(Par_pb, n)
Par_opts <- list(progress = progress)
temp2 <- foreach(
  i = 1:length(Uniportls), .combine = base::rbind, .errorhandling = "pass",
  .options.snow = Par_opts, .packages = c("tcltk", "dplyr", "utils")
) %dopar% {
  Query <- Uniportls[i]
  url <- c("https://www.oglcnac.org/atlas/search/")
  pg <- rvest::session(url)
  token <- xml2::xml_attrs(xml2::xml_find_all(xml2::read_html(pg), ".//input")[[1]])[["value"]]
  myheaders <- c(
  'accept' ='text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
  'accept-encoding' = 'gzip, deflate, br',
  'accept-language' = 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7,zh-TW;q=0.6,ja;q=0.5',
  'cache-control' = 'max-age=0',
  'content-length' = as.character(nchar(Query) + 120),
  'content-type' = 'application/x-www-form-urlencoded',
  'cookie' = stringr::str_split(pg[["response"]][["headers"]][["set-cookie"]],";")[[1]][1],
  'dnt' = '1',
  'origin' = 'https://oglcnac.org',
  'referer' = 'https://oglcnac.org/atlas/search/',
  'sec-ch-ua' = '"Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"',
  'sec-ch-ua-mobile' = '?0',
  'sec-ch-ua-platform' = 'Linux',
  'sec-fetch-dest' = 'document',
  'sec-fetch-mode' = 'navigate',
  'sec-fetch-site' = 'same-origin',
  'sec-fetch-user' = '?1',
  'upgrade-insecure-requests' = '1',
  'user-agent' = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
  )
  response <- httr::POST(
    url = url,
    httr::add_headers(.headers = myheaders),
    body = list(
      "csrfmiddlewaretoken" = token,
      "search_term" = Query,
      "search_field" = "Accession"
    ),
    encode = "form"
  )
  if (httr::status_code(response) != 200) {
    out <- data.frame(
      Query = Query,
      Accession = "Failure",
      Entry_Name = "Failure",
      Protein_Name = "Failure",
      Gene_Name = "Failure",
      Position_in_Protein = "Failure",
      Status = httr::status_code(response),
      "O-GlcNAc" = "Unknown"
    )
  } else if (rvest::html_table(xml2::read_html(response))[[2]] %>% as.data.frame() %>% nrow() == 0) {
    out <- data.frame(
      Query = Query,
      Accession = "No_records",
      Entry_Name = "No_records",
      Protein_Name = "No_records",
      Gene_Name = "No_records",
      Position_in_Protein = "No_records",
      Status = httr::status_code(response),
      "O-GlcNAc" = "Non"
    )
  } else if (rvest::html_table(xml2::read_html(response))[[2]] %>% as.data.frame() %>% nrow() != 0) {
    out <- rvest::html_table(xml2::read_html(response))[[2]] %>% as.data.frame()
    out <- data.frame(
      Query = Query,
      Accession = out$Accession,
      Entry_Name = out$`Entry Name`,
      Protein_Name = out$`Protein Name`,
      Gene_Name = out$`Gene Name`,
      Position_in_Protein = out$`Position in Protein`,
      Status = httr::status_code(response),
      "O-GlcNAc" = "Yes"
    )
  }
  out
}


  |===================================================================================================================================== | 100%
  |======================================================================================================================================| 100%
stopCluster(Par_cl)
OGlcNAc_Anno <- temp2


Add O-GlcNAc info to TFs.

OGlcNAc <- rep("No", nrow(TFLink_related))
TFLink_related <- cbind(OGlcNAc, TFLink_related)
TFLink_related$OGlcNAc[TFLink_related$UniprotID.TF %in% OGlcNAc_Anno$Accession]<-"Yes"
head(TFLink_related)

02-03. Get signifciantly changed TFs

Add DEGs info to TFs.

DEGs <- DEGs_ls[["HEK293_TMG_6hBvsHEK293_OSMI2_6hA"]][["DEGs_raw"]]
DEGs <- DEGs[DEGs$Is.Sig.=="Yes",]
is.DEGs <- rep("No", nrow(TFLink_related))
TFLink_related <- cbind(is.DEGs, TFLink_related)
TFLink_related$is.DEGs[TFLink_related$UniprotID.TF %in% DEGs$UniProtKBID]<-"Yes"
head(TFLink_related)

02-04. Filtering & Visualization

1. Filtering

Filter out the TFs wihout both significantly changes and O-GlcNAc sites.

TFLink_related <- TFLink_related[(TFLink_related$is.DEGs == "Yes" | TFLink_related$OGlcNAc == "Yes"),]
TFLink_related <- TFLink_related[(TFLink_related$OGlcNAc == "Yes"),]
dim(TFLink_related)
[1] 105492     17
head(TFLink_related)


Remove the records that not related to both of Mitochondrial/Cytoskeletal genes and remained TFs.

ChkLs <- unique(c(TFLink_related$UniprotID.TF, Anno_GeneLs$Entry))
TFLink_related <- TFLink_related[TFLink_related$UniprotID.Target %in% ChkLs,]
dim(TFLink_related)
[1] 105492     17
head(TFLink_related)

2. Literature mining

Make functions

Make literature mining functions

#Load functions#
RetrieveEntrezID <- function(
    Homologues=FALSE,
    Homo_species=Homo_species,
    Par_species=Par_species,
    Par_species.Uniprot=Par_species.Uniprot,
    GenesLs=GenesLs,
    updateProgress = NULL
){
  #|----Set up Biomart parameters----|####
  Ann_0mart<-NULL
  attempt<-1
  attempt_max<-12
  while(is.null(Ann_0mart)&&attempt<13){
    if(attempt%in%seq(from=1,to=attempt_max,3)){
      url<-"https://useast.ensembl.org"
    }else if(attempt%in%seq(from=2,to=attempt_max,3)){
      url<-"https://www.ensembl.org"
    }else if(attempt%in%seq(from=3,to=attempt_max,3)){
      url<-"https://useast.ensembl.org/"
    }else if(attempt%in%seq(from=4,to=attempt_max,3)){
      url<-"https://asia.ensembl.org/"
    }
    try({
      Ann_0mart <- useMart("ENSEMBL_MART_ENSEMBL", host=url) %>%
        useDataset(Par_species, .)
    }, silent = TRUE)
    attempt<-attempt+1
    if(is.null(Ann_0mart)){Sys.sleep(10)}
  }
  
  if(Homologues){
    if (is.function(updateProgress)) {
      text <- paste0("Get Homologoeus Gene in ", Homo_species) 
      updateProgress(detail = text)
    }
    #listAttributes(Ann_0mart)%>%View()
    temp <- NULL
    attempt<-1
    attempt_max<-12
    while(is.null(temp)&&attempt<13){
      try({
        temp <- getBM(
          mart=Ann_0mart,
          attributes=c("ensembl_gene_id",
                       Homo_species),
          filter="ensembl_gene_id",
          values=GenesLs,
          uniqueRows = T)
      }, silent = TRUE)
      attempt<-attempt+1
      if(is.null(temp)){Sys.sleep(10)}
    }
    temp<-temp[!temp[,2]%in%"",]
    temp<-temp[!duplicated(temp[,2]),]
    NaMapping<-temp[,1]
    names(NaMapping)<-temp[,2]
    print(Homo_species)
    if(is_empty(temp)|nrow(temp)<1){
      return(NA)
    }
    #print("Set up Biomart parameters")
    #|----Set up Biomart parameters----|####
    if(Homo_species == "hsapiens_homolog_ensembl_gene"){
      Par_species <- "hsapiens_gene_ensembl"
    }else if(Homo_species == "mmusculus_homolog_ensembl_gene"){
      Par_species <- "mmusculus_gene_ensembl"
    }else if(Homo_species == "rnorvegicus_homolog_ensembl_gene"){
      Par_species <- "rnorvegicus_gene_ensembl"
    }else if(Homo_species == "sscrofa_homolog_ensembl_gene"){
      Par_species <- "sscrofa_gene_ensembl"
    }
    Ann_0mart<-NULL
    attempt<-1
    attempt_max<-12
    while(is.null(Ann_0mart)&&attempt<13){
      if(attempt%in%seq(from=1,to=attempt_max,3)){
        url<-"https://useast.ensembl.org"
      }else if(attempt%in%seq(from=2,to=attempt_max,3)){
        url<-"https://www.ensembl.org"
      }else if(attempt%in%seq(from=3,to=attempt_max,3)){
        url<-"https://useast.ensembl.org/"
      }else if(attempt%in%seq(from=4,to=attempt_max,3)){
        url<-"https://asia.ensembl.org/"
      }
      try({
        Ann_0mart <- useMart("ENSEMBL_MART_ENSEMBL", host=url) %>%
          useDataset(Par_species, .)
      }, silent = TRUE)
      attempt<-attempt+1
      if(is.null(Ann_0mart)){Sys.sleep(10)}
    }
    Anno_gene <- NULL
    attempt<-1
    attempt_max<-12
    while(is.null(Anno_gene)&&attempt<13){
      try({
        Anno_gene <- getBM(
          mart=Ann_0mart,
          attributes=c("ensembl_gene_id","entrezgene_id","gene_biotype",
                       "external_gene_name","description"),
          filter="ensembl_gene_id",
          values=temp[,2],
          uniqueRows = T)
      }, silent = TRUE)
      attempt<-attempt+1
      if(is.null(Anno_gene)){Sys.sleep(10)}
    }
    rm(temp)
  }else{
    print("getBM")
    if (is.function(updateProgress)) {
      text <- "Get PubMed Gene ID"
      updateProgress(detail = text)
    }
    Anno_gene <- NULL
    attempt<-1
    attempt_max<-12
    while(is.null(Anno_gene)&&attempt<13){
      try({
        Anno_gene <- getBM(
          mart=Ann_0mart,
          attributes=c("ensembl_gene_id","entrezgene_id","gene_biotype",
                       "external_gene_name","description"),
          filter="ensembl_gene_id",
          values=GenesLs,
          uniqueRows = T)
      }, silent = TRUE)
      attempt<-attempt+1
      if(is.null(Anno_gene)){Sys.sleep(10)}
    }
  }
  
  Anno<-Anno_gene[!duplicated(Anno_gene$ensembl_gene_id),]
  rownames(Anno)<-Anno$ensembl_gene_id
  Anno<-data.frame(
    ensembl_gene_id=Anno$ensembl_gene_id,
    entrezgene_id=Anno$entrezgene_id,
    external_gene_name=Anno$external_gene_name,
    gene_biotype=Anno$gene_biotype,
    row.names = rownames(Anno)
  )
  Anno2<-Anno[!is.na(Anno$entrezgene_id),] #For EntrezGeneID
  Anno2<-Anno2[!duplicated(Anno2[,'ensembl_gene_id']),] #For EntrezGeneID
  rownames(Anno2)<-Anno2$ensembl_gene_id
  temp<-Anno[is.na(Anno$entrezgene_id),"ensembl_gene_id"]
  print("Check EntrezGeneID again")
  #|----Check EntrezGeneID again----|####
  if(!is_empty(temp)){
    if(length(temp)<2){
      NCBIcheck<-read_html(
        paste0(
          "https://www.ncbi.nlm.nih.gov/gene/?term=",
          temp
        )
      )%>%xml_find_all(., ".//span")%>%xml_text()%>%
        grep("Gene ID",.,value = TRUE)
      if(!isEmpty(NCBIcheck)){
        NCBIcheck<-NCBIcheck%>%unlist()%>%
          str_extract_all(.,"(\\d)+")%>%.[[1]]%>%.[1]
      }else{
        NCBIcheck<-NA
      }
      out<-data.frame(
        ensembl_gene_id=temp,
        entrezgene_id=NCBIcheck
      )%>%as.data.frame()
      temp2<-out
    }else{
      Par_cl<-makeCluster(parallelly::freeConnections()-1)
      registerDoSNOW(Par_cl)
      Par_pb <- txtProgressBar(min=1, max=length(temp), style = 3)
      progress <- function(n) setTxtProgressBar(Par_pb, n)
      Par_opts<-list(progress = progress)
      #Refer here for all column names for Entrez API (https://www.ncbi.nlm.nih.gov/books/NBK25501/)
      temp2<-foreach(i=1:length(temp), .combine=rbind, .errorhandling = "pass", .inorder=FALSE,
                     .options.snow=Par_opts, .packages = c("dplyr","utils","xml2","S4Vectors","stringr","BiocGenerics")) %dopar% {
                       NCBIcheck<-read_html(
                         paste0(
                           "https://www.ncbi.nlm.nih.gov/gene/?term=",
                           temp[i]
                         )
                       )%>%xml_find_all(., ".//span")%>%xml_text()%>%
                         grep("Gene ID",.,value = TRUE)
                       if(!isEmpty(NCBIcheck)){
                         NCBIcheck<-NCBIcheck%>%unlist()%>%
                           str_extract_all(.,"(\\d)+")%>%.[[1]]%>%.[1]
                       }else{
                         NCBIcheck<-NA
                       }
                       out<-data.frame(
                         ensembl_gene_id=temp[i],
                         entrezgene_id=NCBIcheck
                       )%>%as.data.frame()
                       return(out)
                     }
      stopCluster(Par_cl)
    }
    temp2<-temp2[!is.na(temp2$entrezgene_id),]
    temp2<-temp2[!duplicated(temp2$ensembl_gene_id),]
    rownames(temp2)<-temp2$ensembl_gene_id
    temp2<-cbind(
      temp2,
      Anno[temp2$ensembl_gene_id,c(3:4)]
    )
    #sum(Anno2$ensembl_gene_id%in%temp2$ensembl_gene_id)==0
    Anno2<-rbind(
      Anno2,
      temp2
    )
  }
  if(Homologues){
    Anno2<-cbind(mapping=NaMapping[Anno2$ensembl_gene_id],Anno2)
    Anno2<-Anno2[!duplicated(Anno2$mapping),]
    rownames(Anno2) <- NaMapping[Anno2$ensembl_gene_id]
    Anno2<-Anno2[,-1]
  }
  print("end")
  if (is.function(updateProgress)) {
    text <- paste0("ID Convert Done ") 
    updateProgress(detail = text)
  }
  return(Anno2)
}

pseudoLog10 <- function(x){ asinh(x/2)/log(10) }

WZY_GetSummaryText<-function(p){
  pdata <- data.frame(name = p$data$label, color2 = p$data$group)
  pdata <- pdata[!is.na(pdata$name), ]
  cluster_color <- unique(pdata$color2)
  
  cluster_label <- sapply(
    cluster_color, enrichplot:::get_wordcloud,
    ggData = pdata, nWords = 4
  ) %>% paste0(.,collapse = " ") %>% str_split(., " ") %>% unlist()
  return(cluster_label)
}

WZY_literatureMining<-function(GenesLs=GenesLs, Mesh_term4Interesting=Mesh_term4Interesting, Species=Species, updateProgress = NULL){
  out<-list()
  N<-paste0(#To search for the total number of PubMed citations, enter all[sb] in the search box.(20210506)
    "https://pubmed.ncbi.nlm.nih.gov/?term=",
    URLencode('all[sb]'),
    "&sort=date"
  )%>%read_html()%>%html_nodes(".value")%>%html_text()%>%.[1]%>%gsub(",","",.)%>%as.numeric()
  K<-paste0(#the amounts of literature related to osteoblast differentiation #20210506# PubMed Query: 
    "https://pubmed.ncbi.nlm.nih.gov/?term=",
    URLencode(Mesh_term4Interesting),
    "&sort=date"
  )%>%read_html()%>%html_nodes(".value")%>%html_text()%>%.[1]%>%gsub(",","",.)%>%as.numeric()
  Thresh<-K/N*10
  
  ####|prepare gene-related articles|####
  # hsapiens_gene_ensembl; mmusculus_gene_ensembl; rnorvegicus_gene_ensembl; sscrofa_gene_ensembl
  # Homo sapiens (Human) [9606]; Mus musculus (Mouse) [10090]; Rattus norvegicus (rat) [10116]; Sus scrofa (pig) [9823]
  # (human) Homo sapiens (human) [hsa]; (mouse) Mus musculus (mouse) [mmu]; (rat) Rattus norvegicus [rno]; (pig) Sus scrofa [ssc]
  # hsapiens_homolog_ensembl_gene; mmusculus_homolog_ensembl_gene; rnorvegicus_homolog_ensembl_gene; sscrofa_homolog_ensembl_gene
  print(GenesLs)
  print(Mesh_term4Interesting)
  print(Species)
  if (is.function(updateProgress)) {
    text <- "Get All Homologoeus Genes"
    updateProgress(detail = text)
  }
  if(Species == "Hsa"){
    Par_species <- "hsapiens_gene_ensembl"
    Par_species.Uniprot <- "9606"
    Homo_species <- c(
      "mmusculus_homolog_ensembl_gene",
      "rnorvegicus_homolog_ensembl_gene",
      "sscrofa_homolog_ensembl_gene"
    )
  }else if(Species == "Mus"){
    Par_species <- "mmusculus_gene_ensembl"
    Par_species.Uniprot <- "10090"
    Homo_species <- c(
      "hsapiens_homolog_ensembl_gene",
      "rnorvegicus_homolog_ensembl_gene",
      "sscrofa_homolog_ensembl_gene"
    )
  }else if(Species == "Rno"){
    Par_species <- "rnorvegicus_gene_ensembl"
    Par_species.Uniprot <- "10116"
    Homo_species <- c(
      "mmusculus_homolog_ensembl_gene",
      "hsapiens_homolog_ensembl_gene",
      "sscrofa_homolog_ensembl_gene"
    )
  }else if(Species == "Ssc"){
    Par_species <- "sscrofa_gene_ensembl"
    Par_species.Uniprot <- "9823"
    Homo_species <- c(
      "mmusculus_homolog_ensembl_gene",
      "rnorvegicus_homolog_ensembl_gene",
      "hsapiens_homolog_ensembl_gene"
    )
  }
  print(Par_species)
  print(Par_species.Uniprot)
  query.res<-RetrieveEntrezID(Par_species=Par_species, Par_species.Uniprot=Par_species.Uniprot, GenesLs=GenesLs)
  homologeus1 <- RetrieveEntrezID(
    Homologues=TRUE, Homo_species=Homo_species[1], Par_species=Par_species,
    Par_species.Uniprot=Par_species.Uniprot, GenesLs=GenesLs, updateProgress = updateProgress
  )
  homologeus2 <- RetrieveEntrezID(
    Homologues=TRUE, Homo_species=Homo_species[2], Par_species=Par_species,
    Par_species.Uniprot=Par_species.Uniprot, GenesLs=GenesLs, updateProgress = updateProgress
  )
  homologeus3 <- RetrieveEntrezID(
    Homologues=TRUE, Homo_species=Homo_species[3], Par_species=Par_species,
    Par_species.Uniprot=Par_species.Uniprot, GenesLs=GenesLs, updateProgress = updateProgress
  )
  
  #Calculate results and construct the literature mining table
  literature.mining<-data.frame(matrix(nrow = 0, ncol=14))
  colnames(literature.mining)<-c("ensembl_gene_id","entrezgene_id","GeneSymbol",
                                 "gene_biotype", "N", "K", "n", "k", "R", "10x Background of R","P.value",
                                 "Gene related PMID", "Overlapped PMID", "Overlapped Genes [Article No]")
  # Par_pb <- txtProgressBar(min=1, max=nrow(query.res), style = 3)
  print("StartLoop")
  list.packages <- c(
    "readxl", "ggplot2", "ggpubr", "matrixStats", "ggrepel", "reshape2", "dplyr", "stringr",
    "grid", "tcltk", "parallel", "doParallel", "doSNOW", "data.table", "gplots",
    "randomcoloR", "factoextra", "RColorBrewer", "grDevices", "gmp", "xtable", "latex2exp",
    "httr", "jsonlite", "curl", "RCurl", "magrittr", "rlist", "pipeR", "plyr",
    "xml2", "rvest", "knn.covertree", "knitr", "rlang", "visNetwork", "hwriter", "htmltools"
  )
  bio.list.packages <- c(
    "limma", "ExperimentHub", "clusterProfiler", "GO.db",
    "org.Mm.eg.db", "pathview", "enrichplot", "org.Hs.eg.db"
  )
  wzy_mainloop <- function(
    query.res = query.res, attempt_max = 3, Thresh = Thresh,
    K = K, N = N, i = i
  ){
    GeneID<-c(
      query.res$entrezgene_id[i],
      homologeus1[query.res$ensembl_gene_id[i],"entrezgene_id"],
      homologeus2[query.res$ensembl_gene_id[i],"entrezgene_id"],
      homologeus3[query.res$ensembl_gene_id[i],"entrezgene_id"]
    )
    #### Calculate n
    # Get UID number that within interested MeSH term
    uid.res<-c()
    for(a in GeneID){
      # Retrieve PMID of gene-related articles
      # https://www.ncbi.nlm.nih.gov/books/NBK25497/table/chapter2.T._entrez_unique_identifiers_ui/?report=objectonly
      # https://www.ncbi.nlm.nih.gov/books/NBK25499/
      # https://dataguide.nlm.nih.gov/eutilities/utilities.html
      # https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=gene&db=pubmed&id=68389&linkname=homologene_pubmed
      # https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=gene&db=pubmed&id=12393
      URL_temp<-NULL
      attempt<-1
      url <- paste0(
        "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=gene&db=pubmed&id=",
        a
      )
      while(is.null(URL_temp)&&attempt<attempt_max){
        try({
          url <- url(url, "rb")
          if(class(url)[1]=="url"){
            URL_temp <- read_xml(
              url
            )%>%xml_find_all(., ".//Id")%>%xml_text()
            close(url)
          }
        }, silent = TRUE)
        attempt<-attempt+1
        if(is.null(URL_temp)){Sys.sleep(round(runif(1,10,180),1))}
      }
      uid.res<-c(
        uid.res,
        URL_temp
      )
    }
    uid.res<-unique(uid.res)
    n<-length(uid.res)
    #### Calculate k
    maxlimit<-125
    if(n>maxlimit){
      step<-n%/%maxlimit
      mod<-n%%maxlimit
      start<-seq(from=1, to=maxlimit*step, by=maxlimit)
      end<-seq(from=maxlimit, to=maxlimit*step, by=maxlimit)
      if(mod>0){
        start<-c(start, tail(end, 1)+1)
        end<-c(end, n)
      }
      step<-length(start)
    }else{
      step<-1
      start<-1
      end<-n
    }
    k<-0
    overlapped.uid<-c()
    for(a in 1:step){
      URL_temp<-NULL
      attempt<-1
      url <- URLencode(
        paste0(
          "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=",
          paste(uid.res[start[a]:end[a]],collapse=","),"[UID]+AND+(",URLencode(Mesh_term4Interesting),")",
          "&rettype=count"
        )
      )
      while(is.null(URL_temp)&&attempt<attempt_max){
        try({
          url <- url(url, "rb")
          if(class(url)[1]=="url"){
            URL_temp <- read_xml(#the amounts of articles of each gene on interested MeSH term:
              url
            )%>%xml_find_all(., ".//Count")%>%xml_text()%>%.[1]%>%as.numeric()
            close(url)
          }
        }, silent = TRUE)
        attempt<-attempt+1
        if(is.null(URL_temp)){Sys.sleep(round(runif(1,10,180),1))}
      }
      if(is.null(URL_temp)){URL_temp <- 0}
      k<-k+URL_temp
      URL_temp<-NULL
      attempt<-1
      url <- URLencode(
        paste0(
          "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=",
          paste(uid.res[start[a]:end[a]],collapse=","),"[UID]+AND+(",URLencode(Mesh_term4Interesting),")",
          "&rettype=unlist"
        )
      )
      while(is.null(URL_temp)&&attempt<attempt_max){
        try({
          url <- url(url, "rb")
          if(class(url)[1]=="url"){
            URL_temp <- read_xml(#the amounts of articles of each gene on interested MeSH term:
              url
            )%>%xml_find_all(., ".//Id")%>%xml_text()
            close(url)
          }
        }, silent = TRUE)
        attempt<-attempt+1
        if(is.null(URL_temp)){Sys.sleep(round(runif(1,10,180),1))}
      }
      overlapped.uid<-c(
        overlapped.uid,
        URL_temp
      )
    }
    if(is.na(k)){k<-0}
    #### Calculate P
    R<-k/n
    P<-0
    if(k<1){
      P<-0
    }else{
      for (a in 0:(k-1)) {
        P<-P+((chooseZ(K,a)*chooseZ(N-K,n-a))/chooseZ(N,n))
      }
    }
    P<-asNumeric(1-P)
    if(P==0){P<-1*10^-300}
    temp<-data.frame(
      ensembl_gene_id=query.res$ensembl_gene_id[i],
      entrezgene_id=query.res$entrezgene_id[i],
      GeneSymbol=query.res$external_gene_name[i],
      gene_biotype=query.res$gene_biotype[i],
      N=N,K=K,n=n,k=k,R=R,Thresh=Thresh,P=P,
      GenePMID=uid.res%>%paste(.,collapse = ","),
      OverlapPMID=overlapped.uid%>%paste(.,collapse = ",")
    )
    colnames(temp)<-c("ensembl_gene_id","entrezgene_id","GeneSymbol",
                      "gene_biotype", "N", "K", "n", "k", "R", "10x Background of R","P.value",
                      "Gene related PMID", "Overlapped PMID")
    return(temp)
  }
  Par_cl<-makeCluster(parallelly::freeConnections()-2)
  registerDoSNOW(Par_cl)
  Par_pb <- txtProgressBar(min=1, max=nrow(query.res), style = 3)
  progress <- function(n) setTxtProgressBar(Par_pb, n)
  Par_opts<-list(progress = progress)
  #Refer here for all column names for Entrez API (https://www.ncbi.nlm.nih.gov/books/NBK25501/)
  temp<-foreach(
    i=1:nrow(query.res), .combine=rbind, .errorhandling = "remove", .inorder=FALSE, .verbose = FALSE,
    .options.snow=Par_opts, .packages = c(list.packages, bio.list.packages)
  ) %dopar% {
    temp3 <- NULL
    temp3 <- wzy_mainloop(query.res = query.res, attempt_max = 5, Thresh = Thresh, K = K, N = N, i = i)
    if(is.null(temp3)){
      temp3<-data.frame(
        ensembl_gene_id=query.res$ensembl_gene_id[i],
        entrezgene_id=query.res$entrezgene_id[i],
        GeneSymbol=query.res$external_gene_name[i],
        gene_biotype=query.res$gene_biotype[i],
        N=NA,K=NA,n=NA,k=NA,R=NA,Thresh=NA,P=NA,
        GenePMID=NA,
        OverlapPMID=NA
      )
      colnames(temp3)<-c("ensembl_gene_id","entrezgene_id","GeneSymbol",
                         "gene_biotype", "N", "K", "n", "k", "R", "10x Background of R","P.value",
                         "Gene related PMID", "Overlapped PMID")
    }
    return(temp3)
  }
  stopCluster(Par_cl)
  literature.mining<-rbind(literature.mining,temp)
  rm(temp)
  gc()
  print("EndLoop")
  #Correcting P-value for multiple testing
  literature.mining<-cbind(literature.mining[,1:11], 
                           p.adj=p.adjust(as.numeric(literature.mining$P.value),method = "BH"),
                           literature.mining[,12:13])
  out<-list(
    literature.mining=literature.mining,
    query.res=query.res,
    homologeus1=homologeus1,
    homologeus2=homologeus2,
    homologeus3=homologeus3
  )
  print("end")
  return(out)
}

literatureMiningPlot<-function(
    literature.mining=out$literature.mining, Thresh.R=NULL,
    use.Padj = FALSE, Thresh.P=0.05, label=TRUE, label_n=5
){
  # print("Plotting")
  # Plotting Results
  literature.mining$n<-as.numeric(literature.mining$n)
  literature.mining$k<-as.numeric(literature.mining$k)
  literature.mining$R<-as.numeric(literature.mining$R)
  if(is.null(Thresh.R)){
    Thresh<-literature.mining$`10x Background of R`[1]
  }else{
    Thresh<-Thresh.R
  }
  df<-literature.mining[,1:12] 
  df$R<-df$R*1000
  df$n<-log10(df$n)
  df$R<-pseudoLog10(df$R)
  df$p.adj<-pseudoLog10(log(1/df$p.adj,2))
  df$P.value<-pseudoLog10(log(1/df$P.value,2))
  df <- df[order(df$P.value), ]
  maxR<-(max(df$R, na.rm=T)+0.3)%>%round(., digits = 1)
  maxP<-(max(df$p.adj, na.rm=T)+0.3)%>%round(., digits = 1)
  RepL_R<-length(rep(0:maxR, each = 9))%/%9
  RepL_P<-length(rep(0:maxP, each = 9))%/%9
  if(use.Padj){
    gplot<-ggplot(df,aes(x=p.adj, y=R, colour=n, size=k ))
  }else{
    gplot<-ggplot(df,aes(x=P.value, y=R, colour=n, size=k ))
  }
  gplot<-gplot +
    geom_point() +
    expand_limits(x=0) +
    scale_y_continuous(
      limits = c(0,maxR),
      breaks = 0:maxR,
      guide = "prism_offset_minor",
      minor_breaks = pseudoLog10(rep(1:9, RepL_R)*(10^rep(0:maxR, each = 9))),
      labels =  function(lab){
        do.call(
          expression,
          lapply(paste(lab), function(x) {
            if(x==0){bquote(bold("0"))}else{bquote(bold("10"^.(x)))}
          })
        )
      }
    )+
    scale_x_continuous(
      limits = c(0,maxP),
      breaks = 0:maxP,
      guide = "prism_offset_minor",
      minor_breaks = pseudoLog10(rep(1:9, RepL_P)*(10^rep(0:maxP, each = 9))),
      labels = function(lab){
        do.call(
          expression,
          lapply(paste(lab), function(x) {
            if(x==0){bquote(bold("0"))}else{bquote(bold("10"^.(x)))}
          })
        )
      }
    )+
    scale_size(range = c(0,20))+
    theme(
      axis.text=element_text(size=12),
      axis.title=element_text(size=14,face="bold"),
      axis.ticks.length = unit(5,"pt"),
      axis.ticks = element_line(linewidth = 0.7),
    )+
    coord_cartesian(clip = "off")+
    geom_hline(yintercept=pseudoLog10(Thresh*1000), linetype="dashed", 
               color = "red", size=0.4)+
    geom_vline(xintercept=pseudoLog10(log(1/Thresh.P,2)), linetype="dashed", 
               color = "red", size=0.4)+
    labs(x="log2(1/P.value)", 
         y="R (‰)", 
         title="",
         colour="log10(Gene Related Articles No.)", size="Topics\nRelated Articles No.")+
    annotation_custom(
      grob = grid::linesGrob(gp=gpar(lwd=2)),
      xmin = -Inf,
      xmax = -Inf,
      ymin = 0,
      ymax = maxR
    )+
    annotation_custom(
      grob = grid::linesGrob(gp=gpar(lwd=2)),
      xmin = 0,
      xmax = maxP,
      ymin = -Inf,
      ymax = -Inf
    )
  #print("PlottingEnd")
  if(label_n == "All"){
    label_n <- nrow(df) 
  }
  if(label){
    if(use.Padj){
      df <- df[df$R>=pseudoLog10(Thresh*1000) & df$p.adj>=pseudoLog10(log(1/Thresh.P,2)), ]
    }else{
      df <- df[df$R>=pseudoLog10(Thresh*1000) & df$P.value>=pseudoLog10(log(1/Thresh.P,2)), ]
    }
    df <- df[order(df$P.value, decreasing = TRUE), ]
    gplot <- gplot + geom_text_repel(
      data = df[1:label_n, ],
      aes(label=GeneSymbol),hjust=0, vjust=0, size=8, color="darkred"
    )
  }
  return(gplot)
}

literatureMiningOverlappedGenes<-function(out, updateProgress = NULL){
  
  literature.mining<-out$literature.mining
  query.res<-out$query.res
  homologeus1<-out$homologeus1
  homologeus2<-out$homologeus2
  homologeus3<-out$homologeus3
  
  outls<-c()
  
  for(i in 1:nrow(query.res)){
    print(paste0(i,"/",nrow(query.res)))
    if (is.function(updateProgress)) {
      text <- paste0(i," in ",nrow(query.res))
      updateProgress(message = text, detail = text)
    }
    GeneID<-c(
      query.res$entrezgene_id[i],
      homologeus1[query.res$ensembl_gene_id[i],"entrezgene_id"],
      homologeus2[query.res$ensembl_gene_id[i],"entrezgene_id"],
      homologeus3[query.res$ensembl_gene_id[i],"entrezgene_id"]
    )%>%.[!is.na(.)]
    
    overlapped.uid<-literature.mining$`Overlapped PMID`[i]%>%str_split(.,",")%>%unlist()
    
    overlapped.genes<-data.frame(matrix(nrow = 0, ncol=3))
    colnames(overlapped.genes)<-c("GeneSymbol","entrezgene_id","Overlapped_PMID")
    if(overlapped.uid[1]!=""){
      
      for(uid in overlapped.uid){
        
        print(paste0("PMID:", uid, " (", which(overlapped.uid==uid)," of ",length(overlapped.uid), ")"))
        temp.gene<-read_xml(
          paste0(
            "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?dbfrom=pubmed&db=gene&id=",
            uid
          )
        )%>%xml_find_all(., ".//Id")%>%xml_text()%>%.[-1]%>%unique()%>%.[!.%in%GeneID]
        
        if(is_empty(temp.gene)){next}
        if(length(temp.gene)>500){next}
        
        a.n<-length(temp.gene)
        maxlimit<-50
        if(a.n>maxlimit){
          step<-a.n%/%maxlimit
          mod<-a.n%%maxlimit
          start<-seq(from=1, to=maxlimit*step, by=maxlimit)
          end<-seq(from=maxlimit, to=maxlimit*step, by=maxlimit)
          if(mod>0){
            start<-c(start, tail(end, 1)+1)
            end<-c(end, a.n)
          }
          step<-length(start)
        }else{
          step<-1
          start<-1
          end<-a.n
        }
        
        if(step>1){
          pb <- txtProgressBar(min=1, max=step, style = 3)
        }
        
        removeIdx<-c()
        GeneSymbol<-c()
        for(a in 1:step){
          #print(paste0("b:",which(temp.gene==b),";",length(temp.gene)))
          GSB<-read_html(
            paste0(
              "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=gene&id=",
              temp.gene[start[a]:end[a]]%>%paste(.,collapse = ","),
              "&rettype=unlist"
            )
          )
          lh<-(end[a]-start[a]+1)
          if(lh==1){
            test<-xml_child(xml_child(xml_child(xml_child(xml_child(GSB, 1), 1), 1), 1), 2)%>%xml_text()%>%grep("Official Symbol:",.)
            if(!is_empty(test)){
              GeneSymbol<-c(
                GeneSymbol,
                xml_child(xml_child(xml_child(xml_child(xml_child(xml_child(xml_child(GSB, 1), 1), 1), 1), 2), 2), 2)%>%xml_text()
              )
            }else{
              removeIdx<-c(
                removeIdx,1
              )
            }
          }else{
            for(b in 1:lh){
              test<-xml_child(xml_child(xml_child(xml_child(xml_child(GSB, 1), 1), 1), b), 2)%>%xml_text()%>%grep("Official Symbol:",.)
              if(!is_empty(test)){
                GeneSymbol<-c(
                  GeneSymbol,
                  xml_child(xml_child(xml_child(xml_child(xml_child(xml_child(xml_child(xml_child(GSB, 1), 1), 1), b), 2), 2), 2), 2)%>%xml_text()
                )
              }else{
                removeIdx<-c(
                  removeIdx,(b+start[a]-1)
                )
              }
            }
          }
          
          if(step>1){
            setTxtProgressBar(pb, a)
          }
          if(is.function(updateProgress)) {
            text <- paste0(a," in ",step)
            updateProgress(message = paste0(i," in ",nrow(query.res)), detail = text)
          }
        }
        if(step>1){close(pb)}
        if(!is_empty(removeIdx)){
          temp.gene<-temp.gene[-removeIdx]
        }
        
        if(!is_empty(temp.gene)){
          temp<-data.frame(
            GeneSymbol=GeneSymbol%>%str_to_title(),
            entrezgene_id=temp.gene,
            Overlapped_PMID=rep(uid,length(temp.gene))
          )
          overlapped.genes<-rbind(
            overlapped.genes,
            temp
          )
        }
      }
    }
    
    temp<-c()
    OrderID<-c()
    for(a in overlapped.genes$GeneSymbol%>%unique()){
      temp2<-overlapped.genes[overlapped.genes$GeneSymbol==a,]
      OrderID<-c(
        OrderID,
        length(temp2$Overlapped_PMID%>%unique())
      )
      temp<-c(
        temp,
        paste0(
          temp2$GeneSymbol[1],
          " (EntrezID:", temp2$entrezgene_id[1], ";",
          "Overlapped_PMID[", tail(OrderID,1), "]:", paste(temp2$Overlapped_PMID%>%unique(),collapse = ","),")" 
        )
      )
    }
    OrderID<-as.numeric(OrderID)
    OrderID<-order(x=OrderID, decreasing=TRUE, na.last=T)
    overlapped.genes<-temp%>%.[OrderID]%>%paste(.,collapse = " ")
    outls<-c(
      outls,overlapped.genes
    )
  }
  
  overlapped.genes<-cbind(
    literature.mining[,1:4],
    overlapped.genes=outls
  )
  
  return(overlapped.genes)
  
}
library(rlang)
library(ggprism)

Results

Calcuate relativity of the TFs to the Mesh term “Osteoblasts”[Mesh] using a literature mining method.

TFs <- select(org.Hs.eg.db, keys = unique(TFLink_related$Name.TF), columns = c('ENSEMBL'), keytype = "SYMBOL")
'select()' returned 1:many mapping between keys and columns
Mesh_term4Interesting <- '"Osteoblasts"[Mesh]'
GenesLs <- TFs$ENSEMBL[!is.na(TFs$ENSEMBL)]
Species <- "Hsa" # Hsa Mus Rno Ssc
TFs.Mesh<-WZY_literatureMining(GenesLs=GenesLs, Mesh_term4Interesting=Mesh_term4Interesting, Species=Species, updateProgress = NULL)

  [1] "ENSG00000136997" "ENSG00000118260" "ENSG00000177606" "ENSG00000141510" "ENSG00000141905" "ENSG00000173039" "ENSG00000126561"
  [8] "ENSG00000107485" "ENSG00000168610" "ENSG00000096717" "ENSG00000128573" "ENSG00000134323" "ENSG00000185591" "ENSG00000100811"
 [15] "ENSG00000187098" "ENSG00000105698" "ENSG00000115641" "ENSG00000109320" "ENSG00000185122" "ENSG00000284774" "ENSG00000118689"
 [22] "ENSG00000137203" "ENSG00000150907" "ENSG00000175592" "ENSG00000106459" "ENSG00000118058" "ENSG00000138385" "ENSG00000102878"
 [29] "ENSG00000102804" "ENSG00000001167" "ENSG00000134954" "ENSG00000102974" "ENSG00000143437" "ENSG00000123405" "ENSG00000275700"
 [36] "ENSG00000276072" "ENSG00000143162" "ENSG00000170345" "ENSG00000257923" "ENSG00000097007" "ENSG00000115415" "ENSG00000126767"
 [43] "ENSG00000091831" "ENSG00000066136" "ENSG00000129514" "ENSG00000112592" "ENSG00000130816" "ENSG00000130522" "ENSG00000071564"
 [50] "ENSG00000113580" "ENSG00000154727" "ENSG00000132170" "ENSG00000081189" "ENSG00000168214" "ENSG00000074047" "ENSG00000182979"
 [57] "ENSG00000077092" "ENSG00000065978" "ENSG00000136352" "ENSG00000007372" "ENSG00000134982" "ENSG00000120738" "ENSG00000149311"
 [64] "ENSG00000064393" "ENSG00000140464" "ENSG00000069667" "ENSG00000172845" "ENSG00000102554" "ENSG00000078403" "ENSG00000120837"
 [71] "ENSG00000132005" "ENSG00000288283" "ENSG00000141646" "ENSG00000162924" "ENSG00000144791" "ENSG00000143190" "ENSG00000020633"
 [78] "ENSG00000077150" "ENSG00000115966" "ENSG00000072310" "ENSG00000074319" "ENSG00000171843" "ENSG00000106462" "ENSG00000104964"
 [85] "ENSG00000178028" "ENSG00000121022" "ENSG00000121481" "ENSG00000074181" "ENSG00000125398" "ENSG00000172943" "ENSG00000072364"
 [92] "ENSG00000141867" "ENSG00000198026" "ENSG00000136807" "ENSG00000147140" "ENSG00000126457" "ENSG00000151702" "ENSG00000125686"
 [99] "ENSG00000111206" "ENSG00000099956" "ENSG00000275837" "ENSG00000183337" "ENSG00000108468" "ENSG00000075426" "ENSG00000164032"
[106] "ENSG00000072501" "ENSG00000117222" "ENSG00000136715" "ENSG00000164754" "ENSG00000185022" "ENSG00000108055" "ENSG00000185811"
[113] "ENSG00000066422" "ENSG00000100393" "ENSG00000272333" "ENSG00000108312" "ENSG00000067955" "ENSG00000136504" "ENSG00000173473"
[120] "ENSG00000177485" "ENSG00000088038" "ENSG00000276082" "ENSG00000273943" "ENSG00000275979" "ENSG00000274941" "ENSG00000274176"
[127] "ENSG00000274616" "ENSG00000277615" "ENSG00000277600" "ENSG00000277114" "ENSG00000113658" "ENSG00000159216" "ENSG00000029363"
[134] "ENSG00000180530" "ENSG00000101972" "ENSG00000188612" "ENSG00000170653" "ENSG00000100425" "ENSG00000121068" "ENSG00000130382"
[141] "ENSG00000141380" "ENSG00000133884" "ENSG00000100395" "ENSG00000030419" "ENSG00000204371" "ENSG00000238134" "ENSG00000232045"
[148] "ENSG00000224143" "ENSG00000236759" "ENSG00000227333" "ENSG00000206376" "ENSG00000160075" "ENSG00000111276" "ENSG00000196498"
[155] "ENSG00000108175" "ENSG00000171681" "ENSG00000116560" "ENSG00000084676" "ENSG00000129173" "ENSG00000100320" "ENSG00000277564"
[162] "ENSG00000185551" "ENSG00000011304" "ENSG00000169375" "ENSG00000147133" "ENSG00000136574" "ENSG00000285109" "ENSG00000143379"
[169] "ENSG00000142599" "ENSG00000067369" "ENSG00000167491" "ENSG00000147050" "ENSG00000157259" "ENSG00000276644" "ENSG00000105323"
[176] "ENSG00000028310" "ENSG00000103510" "ENSG00000167258" "ENSG00000120690" "ENSG00000198911" "ENSG00000149136" "ENSG00000055609"
[183] "ENSG00000140262" "ENSG00000179348" "ENSG00000126746" "ENSG00000143889" "ENSG00000124813" "ENSG00000134852" "ENSG00000111642"
[190] "ENSG00000137947" "ENSG00000100888" "ENSG00000196591" "ENSG00000089902" "ENSG00000171456" "ENSG00000122482" "ENSG00000164190"
[197] "ENSG00000004487" "ENSG00000173575" "ENSG00000143614" "ENSG00000261992" "ENSG00000033800" "ENSG00000270647" "ENSG00000276833"
[204] "ENSG00000140396" "ENSG00000107290" "ENSG00000196235" "ENSG00000149480" "ENSG00000123374" "ENSG00000137693" "ENSG00000160201"
[211] "ENSG00000068323" "ENSG00000073584" "ENSG00000187079" "ENSG00000123268" "ENSG00000170374" "ENSG00000184634" "ENSG00000104320"
[218] "ENSG00000064102" "ENSG00000129691" "ENSG00000138785" "ENSG00000168813" "ENSG00000272886" "ENSG00000163435" "ENSG00000163848"
[225] "ENSG00000166478" "ENSG00000116017" "ENSG00000169564" "ENSG00000168283" "ENSG00000163348" "ENSG00000137871" "ENSG00000285253"
[232] "ENSG00000178691" "ENSG00000119866" "ENSG00000122877" "ENSG00000172534" "ENSG00000114315" "ENSG00000175550" "ENSG00000100281"
[239] "ENSG00000197063" "ENSG00000171720" "ENSG00000063169" "ENSG00000204256" "ENSG00000234704" "ENSG00000235307" "ENSG00000230678"
[246] "ENSG00000234507" "ENSG00000236227" "ENSG00000215077" "ENSG00000165156" "ENSG00000118418" "ENSG00000086589" "ENSG00000103479"
[253] "ENSG00000133422" "ENSG00000105866" "ENSG00000102034" "ENSG00000111145" "ENSG00000111880" "ENSG00000104064" "ENSG00000095794"
[260] "ENSG00000128604" "ENSG00000132964" "ENSG00000198900" "ENSG00000141027" "ENSG00000177565" "ENSG00000168286" "ENSG00000158636"
[267] "ENSG00000177030" "ENSG00000282712" "ENSG00000101040" "ENSG00000025434" "ENSG00000171988" "ENSG00000137265" "ENSG00000186834"
[274] "ENSG00000253729" "ENSG00000160789" "ENSG00000164105" "ENSG00000204531" "ENSG00000206454" "ENSG00000230336" "ENSG00000237582"
[281] "ENSG00000229094" "ENSG00000235068" "ENSG00000233911" "ENSG00000132510" "ENSG00000136492" "ENSG00000156531" "ENSG00000187555"
[288] "ENSG00000102098" "ENSG00000117713" "ENSG00000164458" "ENSG00000198728" "ENSG00000125651" "ENSG00000121060" "ENSG00000171223"
[295] "ENSG00000119203" "ENSG00000101266" "ENSG00000134532" "ENSG00000196363" "ENSG00000112658" "ENSG00000007866" "ENSG00000169925"
[302] "ENSG00000122565" "ENSG00000204356" "ENSG00000206268" "ENSG00000233801" "ENSG00000231044" "ENSG00000229363" "ENSG00000206357"
[309] "ENSG00000198517" "ENSG00000169057" "ENSG00000171316" "ENSG00000118922" "ENSG00000197905" "ENSG00000168769" "ENSG00000169045"
[316] "ENSG00000284254" "ENSG00000099381" "ENSG00000101191" "ENSG00000119547" "ENSG00000130726" "ENSG00000104856" "ENSG00000148400"
[323] "ENSG00000114861" "ENSG00000183495" "ENSG00000127616" "ENSG00000181315" "ENSG00000120733" "ENSG00000171862" "ENSG00000284792"
[330] "ENSG00000122779" "ENSG00000105856" "ENSG00000283847" "ENSG00000165732" "ENSG00000096063" "ENSG00000121691" "ENSG00000147155"
[337] "ENSG00000167182" "ENSG00000139842" "ENSG00000239306" "ENSG00000088930" "ENSG00000005889" "ENSG00000102710" "ENSG00000145216"
[344] "ENSG00000141568" "ENSG00000166886" "ENSG00000120948" "ENSG00000112081" "ENSG00000083307" "ENSG00000197724" "ENSG00000129351"
[351] "ENSG00000079246" "ENSG00000131051" "ENSG00000189369" "ENSG00000204227" "ENSG00000228520" "ENSG00000235107" "ENSG00000226788"
[358] "ENSG00000206287" "ENSG00000231115" "ENSG00000070444" "ENSG00000166503" "ENSG00000063244" "ENSG00000123908" "ENSG00000153147"
[365] "ENSG00000124613" "ENSG00000115942" "ENSG00000178764" "ENSG00000136603" "ENSG00000138378" "ENSG00000173275" "ENSG00000165891"
[372] "ENSG00000174720" "ENSG00000170581" "ENSG00000165494" "ENSG00000104824" "ENSG00000282947" "ENSG00000118007" "ENSG00000157933"
[379] "ENSG00000100084" "ENSG00000136451" "ENSG00000120616" "ENSG00000125740" "ENSG00000166716" "ENSG00000136367" "ENSG00000010244"
[386] "ENSG00000101216" "ENSG00000140382" "ENSG00000164916" "ENSG00000064313" "ENSG00000162702" "ENSG00000143842" "ENSG00000204231"
[393] "ENSG00000231321" "ENSG00000235712" "ENSG00000227322" "ENSG00000228333" "ENSG00000206289" "ENSG00000049618" "ENSG00000159692"
[400] "ENSG00000168036" "ENSG00000005339" "ENSG00000186350" "ENSG00000159140" "ENSG00000124795" "ENSG00000058673" "ENSG00000089280"
[407] "ENSG00000109381" "ENSG00000110713" "ENSG00000166508" "ENSG00000108654" "ENSG00000135100" "ENSG00000119707" "ENSG00000137309"
[414] "ENSG00000186660" "ENSG00000163930" "ENSG00000101115" "ENSG00000073614" "ENSG00000181722" "ENSG00000057657" "ENSG00000124151"
[421] "ENSG00000170325" "ENSG00000101849" "ENSG00000143799" "ENSG00000105497" "ENSG00000204209" "ENSG00000231617" "ENSG00000229396"
[428] "ENSG00000227046" "ENSG00000206206" "ENSG00000206279" "ENSG00000181449" "ENSG00000140332" "ENSG00000180806" "ENSG00000130940"
[435] "ENSG00000060709" "ENSG00000090447" "ENSG00000139613" "ENSG00000175029" "ENSG00000185049" "ENSG00000104447" "ENSG00000128908"
[442] "ENSG00000143321" "ENSG00000171634" "ENSG00000125618" "ENSG00000106571" "ENSG00000162419" "ENSG00000135111" "ENSG00000189079"
[449] "ENSG00000187325" "ENSG00000197157" "ENSG00000182568" "ENSG00000077097" "ENSG00000116478" "ENSG00000174652" "ENSG00000170322"
[456] "ENSG00000147130" "ENSG00000174197" "ENSG00000189403" "ENSG00000160094" "ENSG00000136875" "ENSG00000144554" "ENSG00000149308"
[463] "ENSG00000175727" "ENSG00000281178" "ENSG00000060138" "ENSG00000173276" "ENSG00000100410" "ENSG00000118900" "ENSG00000112182"
[470] "ENSG00000165119" "ENSG00000141644" "ENSG00000102908" "ENSG00000131848" "ENSG00000173894" "ENSG00000160633" "ENSG00000180787"
[477] "ENSG00000158321" "ENSG00000171940" "ENSG00000134371" "ENSG00000143390" "ENSG00000166199" "ENSG00000187605" "ENSG00000162599"
[484] "ENSG00000080603" "ENSG00000113368" "ENSG00000113810" "ENSG00000143373" "ENSG00000204304" "ENSG00000236353" "ENSG00000225987"
[491] "ENSG00000232005" "ENSG00000224952" "ENSG00000206315" "ENSG00000237344" "ENSG00000106004" "ENSG00000118412" "ENSG00000288475"
[498] "ENSG00000110435" "ENSG00000085224" "ENSG00000157540" "ENSG00000159259" "ENSG00000165819" "ENSG00000118217" "ENSG00000068305"
[505] "ENSG00000140836" "ENSG00000138336" "ENSG00000182944" "ENSG00000186448" "ENSG00000281709" "ENSG00000129245" "ENSG00000054598"
[512] "ENSG00000124789" "ENSG00000166333" "ENSG00000083093" "ENSG00000093072" "ENSG00000071655" "ENSG00000101126" "ENSG00000120798"
[519] "ENSG00000171467" "ENSG00000112118" "ENSG00000243678" "ENSG00000124535" "ENSG00000147862" "ENSG00000124216" "ENSG00000116350"
[526] "ENSG00000142235" "ENSG00000117000" "ENSG00000143970" "ENSG00000277258" "ENSG00000278644" "ENSG00000116030" "ENSG00000198783"
[533] "ENSG00000168661" "ENSG00000197757" "ENSG00000163946" "ENSG00000100625" "ENSG00000140632" "ENSG00000145734" "ENSG00000274803"
[540] "ENSG00000273873" "ENSG00000087903" "ENSG00000179583" "ENSG00000109685" "ENSG00000198646" "ENSG00000178338" "ENSG00000167548"
[547] "ENSG00000069011" "ENSG00000181827" "ENSG00000163346" "ENSG00000079432" "ENSG00000116604" "ENSG00000126778" "ENSG00000115875"
[554] "ENSG00000095951" "ENSG00000114416" "ENSG00000094916" "ENSG00000172977" "ENSG00000104885" "ENSG00000176165" "ENSG00000133392"
[561] "ENSG00000276480" "ENSG00000245680" "ENSG00000104221" "ENSG00000266412" "ENSG00000164104" "ENSG00000169297" "ENSG00000197265"
[568] "ENSG00000092201" "ENSG00000100426" "ENSG00000197111" "ENSG00000147789" "ENSG00000117395" "ENSG00000096401" "ENSG00000121741"
[575] "ENSG00000068024" "ENSG00000182141" "ENSG00000170448" "ENSG00000158941" "ENSG00000198824" "ENSG00000135365" "ENSG00000243943"
[582] "ENSG00000104852" "ENSG00000164151" "ENSG00000177469" "ENSG00000126218" "ENSG00000152217" "ENSG00000130856" "ENSG00000101596"
[589] "ENSG00000162775" "ENSG00000108773" "ENSG00000198554" "ENSG00000092203" "ENSG00000164684" "ENSG00000196700" "ENSG00000175792"
[596] "ENSG00000284901" "ENSG00000161021" "ENSG00000283780" "ENSG00000215021" "ENSG00000132670" "ENSG00000146648" "ENSG00000183207"
[603] "ENSG00000135250" "ENSG00000111786" "ENSG00000187140" "ENSG00000052850" "ENSG00000176619" "ENSG00000179981" "ENSG00000169032"
[610] "ENSG00000100714" "ENSG00000132383" "ENSG00000071539" "ENSG00000073111" "ENSG00000165097" "ENSG00000149050" "ENSG00000011451"
[617] "ENSG00000074657" "ENSG00000188994"
[1] "\"Osteoblasts\"[Mesh]"
[1] "Hsa"
[1] "hsapiens_gene_ensembl"
[1] "9606"
[1] "getBM"
[1] "Check EntrezGeneID again"
[1] "end"
[1] "mmusculus_homolog_ensembl_gene"
[1] "Check EntrezGeneID again"
[1] "end"
[1] "rnorvegicus_homolog_ensembl_gene"
[1] "Check EntrezGeneID again"

  |======================================================================================================================================| 100%[1] "end"
[1] "sscrofa_homolog_ensembl_gene"
[1] "Check EntrezGeneID again"

  |======================================================================================================================================| 100%[1] "end"
[1] "StartLoop"

Calcuate relativity of the Mitochondrial/Cytoskeletal genes to the Mesh term “Osteoblasts”[Mesh] using a literature mining method.

Mesh_term4Interesting <- '"Osteoblasts"[Mesh]'
GenesLs <- Anno_GeneLs$EnsemblID[!is.na(Anno_GeneLs$EnsemblID)]
Species <- "Hsa" # Hsa Mus Rno Ssc
MitoCySk.Mesh<-WZY_literatureMining(GenesLs=GenesLs, Mesh_term4Interesting=Mesh_term4Interesting, Species=Species, updateProgress = NULL)
  [1] "ENSG00000105327" "ENSG00000001084" "ENSG00000002330" "ENSG00000004142" "ENSG00000005059" "ENSG00000006530" "ENSG00000007168"
  [8] "ENSG00000011295" "ENSG00000011426" "ENSG00000015475" "ENSG00000019144" "ENSG00000021574" "ENSG00000027001" "ENSG00000030110"
 [15] "ENSG00000032742" "ENSG00000041880" "ENSG00000043514" "ENSG00000044090" "ENSG00000044524" "ENSG00000047410" "ENSG00000055950"
 [22] "ENSG00000060762" "ENSG00000061794" "ENSG00000062582" "ENSG00000065000" "ENSG00000067606" "ENSG00000067704" "ENSG00000067900"
 [29] "ENSG00000068028" "ENSG00000069329" "ENSG00000070718" "ENSG00000070831" "ENSG00000072506" "ENSG00000072756" "ENSG00000072803"
 [36] "ENSG00000074071" "ENSG00000074695" "ENSG00000075336" "ENSG00000075945" "ENSG00000076826" "ENSG00000077380" "ENSG00000078674"
 [43] "ENSG00000080371" "ENSG00000080815" "ENSG00000080824" "ENSG00000083799" "ENSG00000083937" "ENSG00000084731" "ENSG00000084764"
 [50] "ENSG00000085491" "ENSG00000085760" "ENSG00000086065" "ENSG00000086758" "ENSG00000087088" "ENSG00000089060" "ENSG00000095066"
 [57] "ENSG00000096080" "ENSG00000096092" "ENSG00000096093" "ENSG00000096872" "ENSG00000097033" "ENSG00000099624" "ENSG00000099800"
 [64] "ENSG00000099821" "ENSG00000099849" "ENSG00000100075" "ENSG00000100242" "ENSG00000100300" "ENSG00000100360" "ENSG00000100503"
 [71] "ENSG00000100644" "ENSG00000100890" "ENSG00000101052" "ENSG00000101181" "ENSG00000101222" "ENSG00000101367" "ENSG00000101574"
 [78] "ENSG00000103254" "ENSG00000103723" "ENSG00000104164" "ENSG00000104381" "ENSG00000104765" "ENSG00000104833" "ENSG00000104892"
 [85] "ENSG00000105197" "ENSG00000105255" "ENSG00000105723" "ENSG00000105819" "ENSG00000105948" "ENSG00000105976" "ENSG00000106211"
 [92] "ENSG00000106299" "ENSG00000107186" "ENSG00000107282" "ENSG00000107643" "ENSG00000107816" "ENSG00000107819" "ENSG00000107863"
 [99] "ENSG00000108064" "ENSG00000108262" "ENSG00000108387" "ENSG00000108561" "ENSG00000108961" "ENSG00000109083" "ENSG00000109103"
[106] "ENSG00000109171" "ENSG00000109332" "ENSG00000109670" "ENSG00000109971" "ENSG00000110711" "ENSG00000111199" "ENSG00000111276"
[113] "ENSG00000111837" "ENSG00000111843" "ENSG00000112031" "ENSG00000112110" "ENSG00000112144" "ENSG00000112651" "ENSG00000112701"
[120] "ENSG00000114107" "ENSG00000114120" "ENSG00000114346" "ENSG00000114446" "ENSG00000114993" "ENSG00000115091" "ENSG00000115317"
[127] "ENSG00000115355" "ENSG00000115840" "ENSG00000115966" "ENSG00000115993" "ENSG00000116459" "ENSG00000116584" "ENSG00000116874"
[134] "ENSG00000117155" "ENSG00000117245" "ENSG00000117593" "ENSG00000118046" "ENSG00000118200" "ENSG00000118242" "ENSG00000118246"
[141] "ENSG00000118965" "ENSG00000119333" "ENSG00000119541" "ENSG00000120662" "ENSG00000120832" "ENSG00000121621" "ENSG00000121957"
[148] "ENSG00000122140" "ENSG00000122386" "ENSG00000122545" "ENSG00000122912" "ENSG00000122970" "ENSG00000123416" "ENSG00000123607"
[155] "ENSG00000124172" "ENSG00000124279" "ENSG00000124333" "ENSG00000124356" "ENSG00000124532" "ENSG00000125354" "ENSG00000125648"
[162] "ENSG00000125652" "ENSG00000125995" "ENSG00000126756" "ENSG00000126768" "ENSG00000126814" "ENSG00000126858" "ENSG00000127914"
[169] "ENSG00000127955" "ENSG00000127989" "ENSG00000128311" "ENSG00000128626" "ENSG00000128641" "ENSG00000128654" "ENSG00000128833"
[176] "ENSG00000128881" "ENSG00000129250" "ENSG00000129682" "ENSG00000130294" "ENSG00000130340" "ENSG00000130348" "ENSG00000130479"
[183] "ENSG00000130643" "ENSG00000130724" "ENSG00000130779" "ENSG00000131165" "ENSG00000131269" "ENSG00000131437" "ENSG00000131966"
[190] "ENSG00000132300" "ENSG00000132356" "ENSG00000132589" "ENSG00000132612" "ENSG00000132842" "ENSG00000132849" "ENSG00000134108"
[197] "ENSG00000134278" "ENSG00000134318" "ENSG00000134375" "ENSG00000134709" "ENSG00000134809" "ENSG00000134982" "ENSG00000135049"
[204] "ENSG00000135127" "ENSG00000135297" "ENSG00000135338" "ENSG00000135441" "ENSG00000135776" "ENSG00000135900" "ENSG00000136108"
[211] "ENSG00000136122" "ENSG00000136270" "ENSG00000136463" "ENSG00000136522" "ENSG00000137100" "ENSG00000137210" "ENSG00000137288"
[218] "ENSG00000137804" "ENSG00000137807" "ENSG00000137942" "ENSG00000138035" "ENSG00000138036" "ENSG00000138069" "ENSG00000138071"
[225] "ENSG00000138095" "ENSG00000138175" "ENSG00000138180" "ENSG00000138182" "ENSG00000138399" "ENSG00000138592" "ENSG00000138758"
[232] "ENSG00000138821" "ENSG00000139131" "ENSG00000139220" "ENSG00000139737" "ENSG00000140463" "ENSG00000140854" "ENSG00000141279"
[239] "ENSG00000141367" "ENSG00000141556" "ENSG00000141577" "ENSG00000141682" "ENSG00000142192" "ENSG00000142208" "ENSG00000142347"
[246] "ENSG00000142444" "ENSG00000143158" "ENSG00000143575" "ENSG00000143761" "ENSG00000143801" "ENSG00000143862" "ENSG00000144040"
[253] "ENSG00000144381" "ENSG00000144824" "ENSG00000144868" "ENSG00000145715" "ENSG00000146282" "ENSG00000146425" "ENSG00000147586"
[260] "ENSG00000149499" "ENSG00000149557" "ENSG00000150756" "ENSG00000150764" "ENSG00000151150" "ENSG00000151929" "ENSG00000152503"
[267] "ENSG00000154174" "ENSG00000154342" "ENSG00000154429" "ENSG00000154839" "ENSG00000155366" "ENSG00000155906" "ENSG00000155970"
[274] "ENSG00000156026" "ENSG00000156313" "ENSG00000156735" "ENSG00000156990" "ENSG00000157540" "ENSG00000157796" "ENSG00000158411"
[281] "ENSG00000158623" "ENSG00000158828" "ENSG00000158882" "ENSG00000160551" "ENSG00000160993" "ENSG00000161800" "ENSG00000162409"
[288] "ENSG00000162769" "ENSG00000162851" "ENSG00000162972" "ENSG00000163462" "ENSG00000163539" "ENSG00000163626" "ENSG00000163635"
[295] "ENSG00000163808" "ENSG00000163930" "ENSG00000164114" "ENSG00000164284" "ENSG00000164347" "ENSG00000164466" "ENSG00000164695"
[302] "ENSG00000164877" "ENSG00000164896" "ENSG00000164933" "ENSG00000164961" "ENSG00000165480" "ENSG00000165487" "ENSG00000165629"
[309] "ENSG00000165813" "ENSG00000166503" "ENSG00000166780" "ENSG00000166963" "ENSG00000167306" "ENSG00000167552" "ENSG00000167553"
[316] "ENSG00000167797" "ENSG00000167862" "ENSG00000168071" "ENSG00000168172" "ENSG00000168385" "ENSG00000168827" "ENSG00000169057"
[323] "ENSG00000169100" "ENSG00000170248" "ENSG00000170606" "ENSG00000170759" "ENSG00000170959" "ENSG00000171103" "ENSG00000171169"
[330] "ENSG00000171533" "ENSG00000171552" "ENSG00000171612" "ENSG00000172171" "ENSG00000172575" "ENSG00000172590" "ENSG00000173548"
[337] "ENSG00000173726" "ENSG00000173786" "ENSG00000173805" "ENSG00000174032" "ENSG00000174173" "ENSG00000174871" "ENSG00000175216"
[344] "ENSG00000175564" "ENSG00000175567" "ENSG00000175582" "ENSG00000175768" "ENSG00000176101" "ENSG00000176108" "ENSG00000176171"
[351] "ENSG00000176410" "ENSG00000176720" "ENSG00000177192" "ENSG00000177370" "ENSG00000177542" "ENSG00000177879" "ENSG00000178209"
[358] "ENSG00000178694" "ENSG00000178952" "ENSG00000178996" "ENSG00000180096" "ENSG00000180834" "ENSG00000181004" "ENSG00000181284"
[365] "ENSG00000181991" "ENSG00000182504" "ENSG00000182628" "ENSG00000183032" "ENSG00000183048" "ENSG00000183172" "ENSG00000183978"
[372] "ENSG00000184640" "ENSG00000184702" "ENSG00000185009" "ENSG00000185043" "ENSG00000185129" "ENSG00000185133" "ENSG00000185340"
[379] "ENSG00000185721" "ENSG00000185825" "ENSG00000185909" "ENSG00000186010" "ENSG00000186522" "ENSG00000187240" "ENSG00000188428"
[386] "ENSG00000188763" "ENSG00000188807" "ENSG00000188906" "ENSG00000189114" "ENSG00000196072" "ENSG00000196230" "ENSG00000196586"
[393] "ENSG00000196659" "ENSG00000197119" "ENSG00000197457" "ENSG00000197535" "ENSG00000197557" "ENSG00000197822" "ENSG00000197912"
[400] "ENSG00000198718" "ENSG00000198836" "ENSG00000198899" "ENSG00000198954" "ENSG00000204152" "ENSG00000204388" "ENSG00000204389"
[407] "ENSG00000204843" "ENSG00000205981" "ENSG00000213123" "ENSG00000213221" "ENSG00000213465" "ENSG00000213625" "ENSG00000214026"
[414] "ENSG00000214253" "ENSG00000215251" "ENSG00000217930" "ENSG00000230989" "ENSG00000238227" "ENSG00000241837" "ENSG00000250479"
[421] "ENSG00000254858" "ENSG00000255112" "ENSG00000262814" "ENSG00000267855" "ENSG00000274523" "ENSG00000288709" "ENSG00000288722"
[1] "\"Osteoblasts\"[Mesh]"
[1] "Hsa"
[1] "hsapiens_gene_ensembl"
[1] "9606"
[1] "getBM"
[1] "Check EntrezGeneID again"
[1] "end"

Plots

gp <- literatureMiningPlot(
  literature.mining=TFs.Mesh$literature.mining, Thresh.R=0.002,
  use.Padj = FALSE, Thresh.P=0.05, label=TRUE, label_n = 10
)
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.
svg(filename = file.path(".", "01.PlotOut", "LiteratureMiningRes_01.svg"), width = 12, height = 8, pointsize = 12)
print(gp)
dev.off()
null device 
          1 
graphics.off()
knitr::opts_knit$set(global.device = TRUE)
print(gp)

knitr::opts_knit$set(global.device = FALSE)
gp <- literatureMiningPlot(
  literature.mining=MitoCySk.Mesh$literature.mining, Thresh.R=0.001,
  use.Padj = FALSE, Thresh.P=0.15, label=TRUE, label_n = 10
)
svg(filename = file.path(".", "01.PlotOut", "LiteratureMiningRes_02.svg"), width = 12, height = 8, pointsize = 12)
print(gp)
dev.off()
null device 
          1 
graphics.off()
knitr::opts_knit$set(global.device = TRUE)
print(gp)

knitr::opts_knit$set(global.device = FALSE)

02-06. Retrieve SNP records of TFs

Retrieve SNP records on the O-GlcNAc sites of TFs. If the a O-GlcNAc site of a TF really have a profound influences on the function of this TF, the misssense SNP on this site should be associated with some clincial pathogenic phenotype.

# Setup BioMart for SNP
Ann_Mart.Loc<-NULL
attempt<-1
attempt_max<-12
while(is.null(Ann_Mart.Loc)&&attempt<13){
  if(attempt%in%seq(from=1,to=attempt_max,3)){
    url<-"https://useast.ensembl.org"
  }else if(attempt%in%seq(from=2,to=attempt_max,3)){
    url<-"https://www.ensembl.org"
  }else if(attempt%in%seq(from=3,to=attempt_max,3)){
    url<-"https://useast.ensembl.org/"
  }else if(attempt%in%seq(from=4,to=attempt_max,3)){
    url<-"https://asia.ensembl.org/"
  }
  try({
    Ann_Mart.Loc <- useDataset(
      "hsapiens_gene_ensembl",
      useMart("ENSEMBL_MART_ENSEMBL", host = url)
    )
  }, silent = TRUE)
  attempt<-attempt+1
  if(is.null(Ann_Mart.Loc)){Sys.sleep(10)}
}
# Setup BioMart for Gene ID
Ann_Mart.snp<-NULL
attempt<-1
attempt_max<-12
while(is.null(Ann_Mart.snp)&&attempt<13){
  if(attempt%in%seq(from=1,to=attempt_max,3)){
    url<-"https://useast.ensembl.org"
  }else if(attempt%in%seq(from=2,to=attempt_max,3)){
    url<-"https://www.ensembl.org"
  }else if(attempt%in%seq(from=3,to=attempt_max,3)){
    url<-"https://useast.ensembl.org/"
  }else if(attempt%in%seq(from=4,to=attempt_max,3)){
    url<-"https://asia.ensembl.org/"
  }
  try({
    Ann_Mart.snp <- useDataset(
      "hsapiens_snp",
      useMart("ENSEMBL_MART_SNP", host = url)
    )
  }, silent = TRUE)
  attempt<-attempt+1
  if(is.null(Ann_Mart.snp)){Sys.sleep(10)}
}
# Get SNP results
UniPort.rs.OGlcNAc <- data.frame()
TFs.UniPort <- nodes$title[nodes$group=="TF"]
Par_pb <- txtProgressBar(min = 1, max = length(TFs.UniPort), style = 3)
n <- 1
for(i in TFs.UniPort){
  n<-n+0.49
  setTxtProgressBar(Par_pb, n)
  UniPort.Loc <- getBM(
    mart=Ann_Mart.Loc,
    attributes=c(
      "uniprotswissprot", "ensembl_gene_id", "ensembl_transcript_id", "strand",
      "chromosome_name", "transcript_start", "transcript_end", "cds_length"
    ),
    filter=c(
      "uniprotswissprot"
    ),
    values=i,
    uniqueRows = T
  )
  n<-n+0.49
  setTxtProgressBar(Par_pb, n)
  UniPort.Loc <- UniPort.Loc[order(UniPort.Loc$cds_length, decreasing = TRUE),][1,]
  UniPort.rs<-NULL
  rm(UniPort.rs)
  try({
    UniPort.rs <- UniPort.Loc %>%
      {
        paste(
          .[]$chromosome_name,
          min(c(.[]$transcript_start, .[]$transcript_end)),
          max(c(.[]$transcript_start, .[]$transcript_end)),
          sep = ":"
        )
      } %>%
      getBM(
        mart=Ann_Mart.snp,
        attributes=c(
          "refsnp_id", "consequence_type_tv", "consequence_allele_string",
          "ensembl_peptide_allele", "translation_start",
          "translation_end",  "ensembl_transcript_stable_id",
          "chr_name",  "chrom_start",  "chrom_end"
        ),
        filter="chromosomal_region",
        values=.,
        uniqueRows = TRUE,
        verbose = FALSE
      )
  }, silent = TRUE)
  if(!exists("UniPort.rs")) next
  UniPort.rs <- UniPort.rs[UniPort.rs$ensembl_transcript_stable_id == UniPort.Loc$ensembl_transcript_id,]
  UniPort.rs <- UniPort.rs[UniPort.rs$consequence_type_tv == "missense_variant",]
  UniPort.rs <- UniPort.rs[UniPort.rs$translation_start == UniPort.rs$translation_end,]
  temp <- OGlcNAc_Anno[OGlcNAc_Anno$Accession==i,]$Position_in_Protein %>% 
    {UniPort.rs[UniPort.rs$translation_start%in%.[],]}
  if(nrow(temp)){
    temp <-  cbind(
      UniPort_ID = i, temp
    )
    UniPort.rs.OGlcNAc <- rbind(
      UniPort.rs.OGlcNAc, temp
    )
  }
  n<-n+0.02
  setTxtProgressBar(Par_pb, n)
}


  |======================================================================================================================================| 100%
cat("\n")
close(Par_pb)
SNP.Phenotype <- getBM(
  mart=Ann_Mart.snp,
  attributes=c(
    "refsnp_id", "clinical_significance", "p_value",
    "phenotype_name", "phenotype_description", "variation_names"
  ),
  filter="snp_filter",
  values=UniPort.rs.OGlcNAc$refsnp_id%>%unique(),
  uniqueRows = TRUE
)
SNP.Phenotype<-SNP.Phenotype[SNP.Phenotype$phenotype_description!="",]
UniPort.rs.OGlcNAc.Clinc <- UniPort.rs.OGlcNAc[UniPort.rs.OGlcNAc$refsnp_id%in%unique(SNP.Phenotype$refsnp_id),]
UniPort.rs.OGlcNAc.Clinc <- merge(UniPort.rs.OGlcNAc.Clinc, SNP.Phenotype, by="refsnp_id")

02-07. Visualization of the gene regulatory network

Code

Filter out the TFs that doesn’t have pathogenic SNP on O-GlcNAc sites.

temp <- nodes[nodes$group=="TF",]
temp <- temp[temp$title%in%unique(UniPort.rs.OGlcNAc.Clinc$UniPort_ID),]
nodes <- rbind(
  nodes[nodes$group!="TF",],
  temp
)
edges <- edges[edges$from%in%nodes$id & edges$to%in%nodes$id,]
nodes<-nodes[nodes$id%in%unique(c(edges$from,edges$to)),]


Add url link to nodes.

for(i in nodes$title[nodes$group=="TF"]){
  OGlcNAc.site <- OGlcNAc_Anno[OGlcNAc_Anno$Query==i,]$Position_in_Protein %>%
    unique() %>% paste(., collapse = ";")
  temp <- UniPort.rs.OGlcNAc.Clinc[UniPort.rs.OGlcNAc.Clinc$UniPort_ID==i, ]
  temp <- temp[!duplicated(temp$refsnp_id), ]
  SNP.list <- c()
  for (k in temp$refsnp_id) {
    SNP.list <- c(
      SNP.list,
      paste0(
        "<br><a href='https://www.ensembl.org/Homo_sapiens/Variation/Phenotype?db=core;r=",
        paste0(temp$chr_name[temp$refsnp_id==k],":",temp$chrom_start[temp$refsnp_id==k],"-",temp$chrom_end[temp$refsnp_id==k]),
        ";v=", k, ";vdb=variation' target='_blank'>SNP: ",
        k, "; on AA site: ", temp$translation_start[temp$refsnp_id==k],
        " of ", temp$ensembl_transcript_stable_id[temp$refsnp_id==k], "</a>"
      )
    )
  }
  nodes$title[nodes$title==i] <- HTML(
    paste0(
      '<a href="https://www.uniprot.org/uniprotkb/',i,'/entry" target="_blank">', i, '</a><br>',
      '<a href="https://oglcnac.org/atlas/detail/', i, '" target="_blank">O-GlcNAc Site: ', OGlcNAc.site, '</a><br>',
      'O-GlcNAc site with pathogenic SNP:'
    ), HTML(SNP.list)
  )
}
for(i in nodes$title[nodes$group!="TF"]){
  nodes$title[nodes$title==i] <- HTML(
    paste0(
      '<a href="https://www.uniprot.org/uniprotkb/',i,'/entry" target="_blank">', i, '</a>'
    )
  )
}
TFs.Mesh.keep <- TFs.Mesh$literature.mining[TFs.Mesh$literature.mining$GeneSymbol%in%nodes$label[nodes$group=="TF"],]
MitoCySk.Mesh.keep <- MitoCySk.Mesh$literature.mining[MitoCySk.Mesh$literature.mining$GeneSymbol%in%nodes$label[nodes$group!="TF"],]
Mesh.keep <- rbind(
  TFs.Mesh.keep, MitoCySk.Mesh.keep
)
R.max <- max(Mesh.keep$R)
R.min <- min(Mesh.keep$R)
for(i in nodes$label){
  nodes$value[nodes$label==i]<-1+(((Mesh.keep$R[Mesh.keep$GeneSymbol==i]-R.min)/R.max)*100)
}
TF_ls <- TFLink$Name.TF %>% unique()
for(i in nodes$label[nodes$group!="TF"]){
  if(i %in% TF_ls){
    nodes$shape[nodes$label==i] <- "dot"
  }
}


Add url link to edge.

edges <- cbind(
  edges, title = 1, width = 1, color="lightblue", highlight = "red"
)
for(i in 1:nrow(edges)){
  from <- edges[i,]$from
  to <- edges[i,]$to
  from <- nodes$label[nodes$id==from]
  to <- nodes$label[nodes$id==to]
  temp <- TFLink_related_filter[TFLink_related_filter$Name.TF==from & TFLink_related_filter$Name.Target==to,]
  PubMed.ls <- temp$PubmedID %>%
    str_split(.,";") %>% unlist() %>%
    paste0(
      "<a href='https://pubmed.ncbi.nlm.nih.gov/", ., "/' target='_blank'>",
      ., "</a><br>"
    )
  edges$title[i] <- paste0(
    "Has Small-scale Evidence: ", temp$`Small-scale.evidence`, "<br>", HTML(PubMed.ls)
  ) %>% HTML()
  edges$width[i] <- length(PubMed.ls)
  if(temp$`Small-scale.evidence`=="Yes"){
    edges$color[i] <- "#9999FF"
  }
}
e.Max <- max(edges$width)
e.Min <- min(edges$width)
edges$width <- round(log(edges$width - e.Min + 1, base = 2) + 1)


Add legends

# nodes data.frame for legend
lnodes <- data.frame(
  label = c(
    "TFs related to cytoskeleton\nwithout O-GlcNAc site records\nand is not DEGs in GSE138783",
    "TFs related to cytoskeleton\nwith O-GlcNAc site records\nand is DEGs in GSE138783",
    "TFs with O-GlcNAc site records\nand is not DEGs in GSE138783",
    "TFs with O-GlcNAc site records\nand is DEGs in GSE138783",
    "Targets related to cytoskeleton", "Targets related to mitochondria",
    "Targets related to\nboth of cytoskeleton and mitochondria",
    "Size of node indicates the amount\nof articles related to osteoblast",
    "Width of edge indicates\nthe amount of articles"
  ),
  shape = c(
    "dot", "star", "square", "star", "triangle", "triangle", "diamond", "text", "text"
  ),
  color = c(
    "red", "red", "blue", "blue", "red", "orange", "black", "#FFFFFF", "#FFFFFF"
  ),
  title = "Legends",
  id = 1:9
)
# edges data.frame for legend
ledges <- data.frame(
  color = c("#9999FF", "lightblue"),
  label = c("Has small-scale\nevidence", "No small-scale\nevidence"),
  arrows =c("to", "to"), width = 10
)


Make interactive network

network <- visNetwork(nodes, edges, height = "800px", width = "100%") %>%
  visNodes(physics = TRUE) %>%
  visPhysics(stabilization = TRUE, solver = "repulsion", maxVelocity = 1) %>%
  visEdges(
    smooth = TRUE, shadow = FALSE,
    arrows =list(to = list(enabled = TRUE, scaleFactor = 0.5))
  ) %>%
  visOptions(
    nodesIdSelection = TRUE, selectedBy = "group", manipulation = FALSE,
    highlightNearest = list(enabled = TRUE, degree = 9999, algorithm = "hierarchical")
  ) %>%
  visLegend(addEdges = ledges, addNodes = lnodes, useGroups = FALSE)
network %>% visSave(file = "network.html")

Result

print(network)
NULL

03. Supplementary

save.image("./CheckPoint.RData")
LS0tCnRpdGxlOiAiTy1HbGNOQWMgUmVndWxhdGVkIE1pdG9jaG9uZHJpYWwgR2VuZXMiCmF1dGhvcjogIldaWSIKZGF0ZTogIjIwMjMtMDQtMDEiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIGNzczogLi9IVE1MX3NvdXJjZS9zdHlsZS5jc3MKLS0tCgojIDAwLiBFbnZpcmVubWVudCBTZXR0aW5ncwoKYGBge3IgMDEtMDEuRW52aXJlbm1lbnQgU2V0dGluZ3N9Cm9wdGlvbnMoaW5zdGFsbC5wYWNrYWdlcy5jb21waWxlLmZyb20uc291cmNlID0gImFsd2F5cyIpCmxpc3QucGFja2FnZXMgPC0gYygKICAicmVhZHhsIiwgImdncGxvdDIiLCAiZ2dwdWJyIiwgIm1hdHJpeFN0YXRzIiwgImdncmVwZWwiLCAicmVzaGFwZTIiLCAiZHBseXIiLCAic3RyaW5nciIsCiAgImdyaWQiLCAidGNsdGsiLCAicGFyYWxsZWwiLCAiZG9QYXJhbGxlbCIsICJkb1NOT1ciLCAiZGF0YS50YWJsZSIsICJncGxvdHMiLAogICJyYW5kb21jb2xvUiIsICJmYWN0b2V4dHJhIiwgIlJDb2xvckJyZXdlciIsICJnckRldmljZXMiLCAiZ21wIiwgInh0YWJsZSIsICJsYXRleDJleHAiLAogICJodHRyIiwgImpzb25saXRlIiwgImN1cmwiLCAiUkN1cmwiLCAibWFncml0dHIiLCAicmxpc3QiLCAicGlwZVIiLCAicGx5ciIsCiAgInhtbDIiLCAicnZlc3QiLCAia25uLmNvdmVydHJlZSIsICJrbml0ciIsICJybGFuZyIsICJ2aXNOZXR3b3JrIiwgImh3cml0ZXIiLCAiaHRtbHRvb2xzIgopCmJpby5saXN0LnBhY2thZ2VzIDwtIGMoCiAgImxpbW1hIiwgIkV4cGVyaW1lbnRIdWIiLCAiY2x1c3RlclByb2ZpbGVyIiwgIkdPLmRiIiwKICAib3JnLk1tLmVnLmRiIiwgInBhdGh2aWV3IiwgImVucmljaHBsb3QiLCAib3JnLkhzLmVnLmRiIgopCm5ldy5wYWNrYWdlcyA8LSBsaXN0LnBhY2thZ2VzWyFsaXN0LnBhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCldCmJpby5uZXcucGFja2FnZXMgPC0gYmlvLmxpc3QucGFja2FnZXNbIWJpby5saXN0LnBhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCldCiMgUGFja2FnZXMgdGhhdCBkb2VzIG5vdCBpbnN0YWxsIHlldAppZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKSB7CiAgdXBkYXRlLnBhY2thZ2VzKGFzayA9IEZBTFNFKQogIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIiwgcmVwb3M9Imh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZyIpCn0KaWYgKGxlbmd0aChuZXcucGFja2FnZXMpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcyhuZXcucGFja2FnZXMpCiAgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJncmltYm91Z2gvYmlvbWFSdCIsIGZvcmNlID0gRkFMU0UpCn0KaWYgKGxlbmd0aChiaW8ubmV3LnBhY2thZ2VzKSkgewogIHVwZGF0ZS5wYWNrYWdlcyhhc2sgPSBGQUxTRSkKICBCaW9jTWFuYWdlcjo6aW5zdGFsbChiaW8ubmV3LnBhY2thZ2VzKQp9CiMgTG9hZGluZyBhbGwgcGFja2FnZXMgJiBmdW5jdGlvbnMKaW52aXNpYmxlKGxhcHBseShjKGxpc3QucGFja2FnZXMsIGJpby5saXN0LnBhY2thZ2VzLCAiYmlvbWFSdCIpLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQpvcHRpb25zKHJnbC51c2VOVUxMID0gRiwgZ2dyZXBlbC5tYXgub3ZlcmxhcHMgPSBJbmYpCmBgYAoKIyAwMS4gQ2hlY2sgUmVzdWx0cyBmcm9tIEdTRTEzODc4MwoKIyMgMDEtMDEuIEZ1bmN0aW9uYWwgRW5yaWNobWVudCBBbmFseXNpcyAoRkVBKSBSZXN1bHRzIGZyb20gR1NFMTM4NzgzCgpMb2FkIHRoZSBmdW5jdGlvbmFsIGVucmljaG1lbnQgYW5hbHlzaXMgKEZFQSkgcmVzdWx0cyBmcm9tIG91ciByZS1hbmFseXplZCBHU0UxMzg3ODMgQnVsayBSTkEtc2VxCmBgYHtyIDAxLTAxLjF9CmxvYWQoZmlsZS5wYXRoKCIuIiwgIjAxLkhFSzI5M19UTUdfT1NNSTIiLCAiMDAuUmVzdWx0cyIsICJERVNlcTJfUmVzdWx0cy5SZGF0YSIpKQpgYGAKPGJyPgoKU2VsZWN0IHRoZSBjb21wYXJpc29uIHdpdGggdGhlIG1vc3QgZGlmZmVyZW50IHRyZWF0bWVudCB3aGljaCBpcyA2aCBUTUcgdnMgNmggT1NNSS0xCmBgYHtyIDAxLTAxLjJ9Ck9SQV9saXN0IDwtIERFR3NfbHNbWyJIRUsyOTNfVE1HXzZoQnZzSEVLMjkzX09TTUkyXzZoQSJdXVtbIk92ZXJfUmVwcmVzZW50Il1dCmBgYAo8YnI+CgpLZWVwIHRoZSBHT19CUCB0ZXJtcyB0aGF0IGNvbnRhaW5zIGVpdGhlciAibWl0b2Nob25kcmlhIiBvciAiY3l0b3NrZWxldG9uIgpgYGB7ciAwMS0wMS4zfQpmb3IoaSBpbiBjKCJBbGwiLCAiVXBfcmVndWxhdGVkIiwgIkRvd25fcmVndWxhdGVkIikpewogIHRlbXAgPC0gT1JBX2xpc3RbW2ldXSRHT19CUCRSYXdAcmVzdWx0JERlc2NyaXB0aW9uICU+JQogICAgZ3JlcCgiKG1pdG9jaG9uZHJpYShsKXswLDF9fGN5dG9za2VsZSh0b258dGFsKSkiLCAuLCBpZ25vcmUuY2FzZSA9IFRSVUUpCiAgT1JBX2xpc3RbW2ldXSRHT19CUCRSYXdAcmVzdWx0IDwtIE9SQV9saXN0W1tpXV0kR09fQlAkUmF3QHJlc3VsdFt0ZW1wLF0KICBPUkFfbGlzdFtbaV1dJEdPX0JQJFNpZyA8LSBPUkFfbGlzdFtbaV1dJEdPX0JQJFJhd0ByZXN1bHQKfQpuYW1lcyA8LSAiVE1HIDZoIHZzIE9TTUktMSA2aCIKayA8LSAiR09fQlAiCnRlbXAgPC0gT1JBX2xpc3QKbXlkZjwtZGF0YS5mcmFtZSgpCmdlbmVDbHVzdGVyczwtbGlzdCgpCmZvcihqIGluIGMoIkFsbCIsIlVwX3JlZ3VsYXRlZCIsIkRvd25fcmVndWxhdGVkIikpewogIGlmKGlzLm51bGwobnJvdyh0ZW1wW1tqXV1bW2tdXVtbIlNpZyJdXSkpKXtuZXh0KCl9CiAgbXlkZjwtcmJpbmQoCiAgICBteWRmLAogICAgY2JpbmQoCiAgICAgIGdyb3VwPXJlcChqLG5yb3codGVtcFtbal1dW1trXV1bWyJTaWciXV0pKSwKICAgICAgdGVtcFtbal1dW1trXV1bWyJTaWciXV1bLDE6OV0KICAgICkKICApCiAgZ2VuZUNsdXN0ZXJzW1tqXV08LSh0ZW1wW1tqXV1bW2tdXVtbIlNpZyJdXSRnZW5lSUQpJT4lCiAgICBzdHJfc3BsaXQoLiwiLyIpJT4ldW5saXN0KCklPiV1bmlxdWUoKQp9Cgpyb3duYW1lcyhteWRmKTwtMTpucm93KG15ZGYpCm15ZGYkZ3JvdXA8LWZhY3RvcihteWRmJGdyb3VwLGxldmVscz1jKCJBbGwiLCJVcF9yZWd1bGF0ZWQiLCJEb3duX3JlZ3VsYXRlZCIpKQpjb2xuYW1lcyhteWRmKVsxXTwtIkNsdXN0ZXIiCnJlcyA8LSBuZXcoImNvbXBhcmVDbHVzdGVyUmVzdWx0IiwKICAgICAgICAgICBjb21wYXJlQ2x1c3RlclJlc3VsdCA9IG15ZGYsCiAgICAgICAgICAgZ2VuZUNsdXN0ZXJzID0gZ2VuZUNsdXN0ZXJzCikKLmNhbGw8LW1hdGNoLmNhbGwoY29tcGFyZUNsdXN0ZXIsY2FsbCgiY29tcGFyZUNsdXN0ZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVOU0VNQkx+Z3JvdXAsIGRhdGE9bXlkZiwgZnVuPSJlbnJpY2hHTyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgT3JnRGIgICAgICAgICA9IG9yZy5Icy5lZy5kYiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXlUeXBlICAgICAgID0gIkVOU0VNQkwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9udCAgICAgICAgICAgPSAiQlAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBBZGp1c3RNZXRob2QgPSAiQkgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB2YWx1ZUN1dG9mZiAgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF2YWx1ZUN1dG9mZiAgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlYWRhYmxlICAgICAgPSBUUlVFKSkKLmNhbGwkZ2VuZUNsdXN0ZXJzPC1hcy5zeW1ib2woImdlbmVDbHVzdGVycyIpCi5jYWxsJE9yZ0RiPC1hcy5zeW1ib2woIm9yZy5Icy5lZy5kYiIpCi5jYWxsJGRhdGE8LU5VTEwKcmVzQC5jYWxsPC0uY2FsbApyZXNAZnVuPC0iZW5yaWNoR08iCnJlc0BrZXl0eXBlPC0iVU5LTk9XTiIKcmVzQHJlYWRhYmxlPC1GQUxTRQppZighZGlyLmV4aXN0cyhmaWxlLnBhdGgoIi4iLCIwMS5QbG90T3V0IikpKXsKICBkaXIuY3JlYXRlKGZpbGUucGF0aCgiLiIsIjAxLlBsb3RPdXQiKSkKfQpgYGAKCiMjIDAxLTAyLiBDaGVjayBGRUEgUmVzdWx0cyB7LnRhYnNldCAudGFic2V0LWZhZGUgfQoKIyMjIE92ZXJsYXAgey50YWJzZXQgLnRhYnNldC1mYWRlfQoKYGBge3IgMDEtMDIuYSwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTE3LCBvdXQud2lkdGg9IjEwMCUifQpncCA8LSBkb3RwbG90KAogIHJlcywgc2hvd0NhdGVnb3J5PTEwMDAsIGxhYmVsX2Zvcm1hdD04NSwgY29sb3IgPSAicHZhbHVlIiwKICB0aXRsZT1wYXN0ZTAoIk9SQSBTdW1tYXJ5IG9mICIsIG5hbWVzLCAiIGZvciAiLCBrLCIgVGVybXMuIikKKQpzdmcoZmlsZW5hbWUgPSBmaWxlLnBhdGgoIi4iLCAiMDEuUGxvdE91dCIsICJGRUFfT3ZlcmxhcC5zdmciKSwgd2lkdGggPSAxMiwgaGVpZ2h0ID0gMTcsIHBvaW50c2l6ZSA9IDEyKQpwcmludChncCkKZGV2Lm9mZigpCmdyYXBoaWNzLm9mZigpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KGdsb2JhbC5kZXZpY2UgPSBUUlVFKQpwcmludChncCkKa25pdHI6Om9wdHNfa25pdCRzZXQoZ2xvYmFsLmRldmljZSA9IEZBTFNFKQpgYGAKCiMjIyBBbGwgREVHcyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgpgYGB7ciAwMS0wMi5iLCBmaWcud2lkdGg9MjYsIGZpZy5oZWlnaHQ9MTYsIG91dC53aWR0aD0iMTAwJSJ9CmogPC0gIkdPX0JQIgprIDwtICJBbGwiClBhcl9uYW1lIDwtICJUTUcgNmggdnMgT1NNSS0xIDZoIgpGRUFfdGl0bGU8LXBhc3RlMChrLCAiICIsIG5yb3coT1JBX2xpc3RbW2tdXVtbal1dJFJhdyksIiBzaWduaWZpY2FudCBHT18iLCBqLCAiIHRlcm1zICgiLCBQYXJfbmFtZSwgIjsgdGhlIG1vc3QgZGVzY2VuZCB0ZXJtcykiKQpwMTwtZG90cGxvdChPUkFfbGlzdFtba11dW1tqXV0kUmF3LCBzaG93Q2F0ZWdvcnk9MTAwMCwgbGFiZWxfZm9ybWF0PTEwMCwgY29sb3IgPSAicHZhbHVlIiwgZm9udC5zaXplID0gMTgpCnAyPC10cmVlcGxvdCgKICBwYWlyd2lzZV90ZXJtc2ltKE9SQV9saXN0W1trXV1bW2pdXSRSYXcsIG1ldGhvZD0iSkMiKSwgc2hvd0NhdGVnb3J5PTEwMDAsIGNvbG9yID0gInB2YWx1ZSIsCiAgY2x1c3Rlci5wYXJhbXM9bGlzdChoY2x1c3RfbWV0aG9kID0gIndhcmQuRDIiLCBsYWJlbF93b3Jkc19uID0gNSwgbiA9IDUsIGZvbnQuc2l6ZSA9IDE4KQopCmdwIDwtIGdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKAogIHAxLCBwMiwgbnJvdyA9IDEsIHdpZHRocyA9IGMoMSwgMSksCiAgdG9wPXRleHRHcm9iKHBhc3RlMCgiR1NFQTogIixGRUFfdGl0bGUsIiB3aXRoIHNpbWlsYXJpdHkgc3VtbWFyeSIpLCBncD1ncGFyKGZvbnRzaXplPTE4LCBjb2w9ImJsYWNrIikpCikKc3ZnKGZpbGVuYW1lID0gZmlsZS5wYXRoKCIuIiwgIjAxLlBsb3RPdXQiLCAiRkVBX0FsbC5zdmciKSwgd2lkdGggPSAyNiwgaGVpZ2h0ID0gMTYsIHBvaW50c2l6ZSA9IDEyKQpwbG90KGdwKQpkZXYub2ZmKCkKZ3JhcGhpY3Mub2ZmKCkKa25pdHI6Om9wdHNfa25pdCRzZXQoZ2xvYmFsLmRldmljZSA9IFRSVUUpCnBsb3QoZ3ApCmtuaXRyOjpvcHRzX2tuaXQkc2V0KGdsb2JhbC5kZXZpY2UgPSBGQUxTRSkKYGBgCgojIyMgVXAtcmVndWxhdGVkIERFR3Mgey50YWJzZXQgLnRhYnNldC1mYWRlfQoKYGBge3IgMDEtMDIuYywgZmlnLndpZHRoPTI2LCBmaWcuaGVpZ2h0PTksIG91dC53aWR0aD0iMTAwJSJ9CmogPC0gIkdPX0JQIgprIDwtICJVcF9yZWd1bGF0ZWQiClBhcl9uYW1lIDwtICJUTUcgNmggdnMgT1NNSS0xIDZoIgpGRUFfdGl0bGU8LXBhc3RlMChrLCAiICIsIG5yb3coT1JBX2xpc3RbW2tdXVtbal1dJFJhdyksIiBzaWduaWZpY2FudCBHT18iLCBqLCAiIHRlcm1zICgiLCBQYXJfbmFtZSwgIjsgdGhlIG1vc3QgZGVzY2VuZCB0ZXJtcykiKQpwMTwtZG90cGxvdChPUkFfbGlzdFtba11dW1tqXV0kUmF3LCBzaG93Q2F0ZWdvcnk9MTAwMCwgbGFiZWxfZm9ybWF0PTEwMCwgY29sb3IgPSAicHZhbHVlIiwgZm9udC5zaXplID0gMTgpCnAyPC10cmVlcGxvdCgKICBwYWlyd2lzZV90ZXJtc2ltKE9SQV9saXN0W1trXV1bW2pdXSRSYXcsIG1ldGhvZD0iSkMiKSwgc2hvd0NhdGVnb3J5PTEwMDAsIGNvbG9yID0gInB2YWx1ZSIsCiAgY2x1c3Rlci5wYXJhbXM9bGlzdChoY2x1c3RfbWV0aG9kID0gIndhcmQuRDIiLCBsYWJlbF93b3Jkc19uID0gNSwgbiA9IDUsIGZvbnQuc2l6ZSA9IDE4KQopCmdwIDwtIGdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKAogIHAxLCBwMiwgbnJvdyA9IDEsIHdpZHRocyA9IGMoMSwgMSksCiAgdG9wPXRleHRHcm9iKHBhc3RlMCgiR1NFQTogIixGRUFfdGl0bGUsIiB3aXRoIHNpbWlsYXJpdHkgc3VtbWFyeSIpLCBncD1ncGFyKGZvbnRzaXplPTE4LCBjb2w9ImJsYWNrIikpCikKc3ZnKGZpbGVuYW1lID0gZmlsZS5wYXRoKCIuIiwgIjAxLlBsb3RPdXQiLCAiRkVBX1VwLnN2ZyIpLCB3aWR0aCA9IDE2LCBoZWlnaHQgPSA5LCBwb2ludHNpemUgPSAxMikKcGxvdChncCkKZGV2Lm9mZigpCmdyYXBoaWNzLm9mZigpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KGdsb2JhbC5kZXZpY2UgPSBUUlVFKQpwbG90KGdwKQprbml0cjo6b3B0c19rbml0JHNldChnbG9iYWwuZGV2aWNlID0gRkFMU0UpCmBgYAoKIyMjIERvd24tcmVndWxhdGVkIERFR3Mgey50YWJzZXQgLnRhYnNldC1mYWRlfQoKYGBge3IgMDEtMDIuZCwgZmlnLndpZHRoPTI2LCBmaWcuaGVpZ2h0PTE1LCBvdXQud2lkdGg9IjEwMCUifQpqIDwtICJHT19CUCIKayA8LSAiRG93bl9yZWd1bGF0ZWQiClBhcl9uYW1lIDwtICJUTUcgNmggdnMgT1NNSS0xIDZoIgpGRUFfdGl0bGU8LXBhc3RlMChrLCAiICIsIG5yb3coT1JBX2xpc3RbW2tdXVtbal1dJFJhdyksIiBzaWduaWZpY2FudCBHT18iLCBqLCAiIHRlcm1zICgiLCBQYXJfbmFtZSwgIjsgdGhlIG1vc3QgZGVzY2VuZCB0ZXJtcykiKQpwMTwtZG90cGxvdChPUkFfbGlzdFtba11dW1tqXV0kUmF3LCBzaG93Q2F0ZWdvcnk9MTAwMCwgbGFiZWxfZm9ybWF0PTcwLCBjb2xvciA9ICJwdmFsdWUiLCBmb250LnNpemUgPSAxOCkKcDI8LXRyZWVwbG90KAogIHBhaXJ3aXNlX3Rlcm1zaW0oT1JBX2xpc3RbW2tdXVtbal1dJFJhdywgbWV0aG9kPSJKQyIpLCBzaG93Q2F0ZWdvcnk9MTAwMCwgY29sb3IgPSAicHZhbHVlIiwKICBjbHVzdGVyLnBhcmFtcz1saXN0KGhjbHVzdF9tZXRob2QgPSAid2FyZC5EMiIsIGxhYmVsX3dvcmRzX24gPSA1LCBuID0gNSwgZm9udC5zaXplID0gMTgpLCBsYWJlbF9mb3JtYXQ9MjUKKQpncCA8LSBncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSgKICBwMSwgcDIsIG5yb3cgPSAxLCB3aWR0aHMgPSBjKDEsIDEpLAogIHRvcD10ZXh0R3JvYihwYXN0ZTAoIkdTRUE6ICIsRkVBX3RpdGxlLCIgd2l0aCBzaW1pbGFyaXR5IHN1bW1hcnkiKSwgZ3A9Z3Bhcihmb250c2l6ZT0xOCwgY29sPSJibGFjayIpKQopCnN2ZyhmaWxlbmFtZSA9IGZpbGUucGF0aCgiLiIsICIwMS5QbG90T3V0IiwgIkZFQV9Eb3duLnN2ZyIpLCB3aWR0aCA9IDI2LCBoZWlnaHQgPSAxNSwgcG9pbnRzaXplID0gMTIpCnBsb3QoZ3ApCmRldi5vZmYoKQpncmFwaGljcy5vZmYoKQprbml0cjo6b3B0c19rbml0JHNldChnbG9iYWwuZGV2aWNlID0gVFJVRSkKcGxvdChncCkKa25pdHI6Om9wdHNfa25pdCRzZXQoZ2xvYmFsLmRldmljZSA9IEZBTFNFKQpgYGAKCiMgMDIuIFJldHJpZXZlIHVwLXN0cmVhbSB0cmFuc2NyaXB0aW9uIGZhY3RvcnMgb2YgaW50ZXJlc3RlZCBHT19CUCB0ZXJtcwoKSW4gdGhpcyBzZWN0aW9uIHdlIHdpbGwgY2hlY2sgdGhlIHVwLXN0cmVhbSB0cmFuc2NyaXB0aW9uIGZhY3RvcnMgdGhhdCByZWd1bGF0ZXMgdGhlIGdlbmVzIGluc2lkZSB0aGUgVG9wNSBvdmVybGFwcGVkIG1pdG9jaG9uZHJpYWwvY3l0b3NrZWxldGFsIEdPX0JQIHRlcm1zIGJ5IHVzaW5nIFtURkxpbmsgZGF0YWJhc2VdKGh0dHBzOi8vdGZsaW5rLm5ldC8pLgoKVGhlIFRvcDUgb3ZlcmxhcHBlZCBtaXRvY2hvbmRyaWFsL2N5dG9za2VsZXRhbCBHT19CUCB0ZXJtcyBhcmU6CjEuIEdPOjAwMzA3MDUsIGN5dG9za2VsZXRvbi1kZXBlbmRlbnQgaW50cmFjZWxsdWxhciB0cmFuc3BvcnQKMi4gR086MDA2MTY0MCwgY3l0b3NrZWxldG9uLWRlcGVuZGVudCBjeXRva2luZXNpcwozLiBHTzowMTQwMDUzLCBtaXRvY2hvbmRyaWFsIGdlbmUgZXhwcmVzc2lvbgo0LiBHTzowMDcwNTA3LCByZWd1bGF0aW9uIG9mIG1pY3JvdHVidWxlIGN5dG9za2VsZXRvbiBvcmdhbml6YXRpb24KNS4gR086MDAwNjgzOSwgbWl0b2Nob25kcmlhbCB0cmFuc3BvcnQKCiMjIDAyLTAxLiBHZXQgdXAtc3RyZWFtIFRyYW5zY3JpcHRpb24gZmFjdG9ycyBsaXN0CgpEb3dubG9hZCBURkxpbmsgZGF0YWJhc2UKYGBge3IgMDItMDEuYX0KVEZMaW5rIDwtIGZyZWFkKAogICJodHRwczovL2Nkbi5uZXRiaW9sLm9yZy90ZmxpbmsvZG93bmxvYWRfZmlsZXMvVEZMaW5rX0hvbW9fc2FwaWVuc19pbnRlcmFjdGlvbnNfQWxsX3NpbXBsZUZvcm1hdF92MS4wLnRzdi5neiIKKQpgYGAKPGJyPgoKQ2hlY2sgdGhlIHRhYmxlcwpgYGB7ciAwMi0wMS5ifQpoZWFkKHJlc0Bjb21wYXJlQ2x1c3RlclJlc3VsdCkKaGVhZChURkxpbmspCmBgYAo8YnI+CgpHZXQgYW5ub3RhdGlvbiBvZiBnZW5lcyByZWxhdGVkIHRvIE1pdG9jaG9uZHJpYWwvQ3l0b3NrZWxldGFsIEdPX0JQIHRlcm1zLgpgYGB7ciAwMi0wMS5jfQpUT1A1IDwtIHJlc0Bjb21wYXJlQ2x1c3RlclJlc3VsdFtyZXNAY29tcGFyZUNsdXN0ZXJSZXN1bHQkSUQgJWluJSBjKCJHTzowMDMwNzA1IiwgIkdPOjAwNjE2NDAiLCAiR086MDE0MDA1MyIsICJHTzowMDcwNTA3IiwgIkdPOjAwMDY4MzkiKSxdCkdlbmVMcyA8LSBUT1A1JGdlbmVJRCAlPiUKICBzdHJfc3BsaXQoLiwgIi8iKSAlPiUKICB1bmxpc3QoKSAlPiUKICB1bmlxdWUoKQpBbm5fMG1hcnQ8LU5VTEwKYXR0ZW1wdDwtMQphdHRlbXB0X21heDwtMTIKd2hpbGUoaXMubnVsbChBbm5fMG1hcnQpJiZhdHRlbXB0PDEzKXsKICBpZihhdHRlbXB0JWluJXNlcShmcm9tPTEsdG89YXR0ZW1wdF9tYXgsMykpewogICAgdXJsPC0iaHR0cHM6Ly91c2Vhc3QuZW5zZW1ibC5vcmciCiAgfWVsc2UgaWYoYXR0ZW1wdCVpbiVzZXEoZnJvbT0yLHRvPWF0dGVtcHRfbWF4LDMpKXsKICAgIHVybDwtImh0dHBzOi8vd3d3LmVuc2VtYmwub3JnIgogIH1lbHNlIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209Myx0bz1hdHRlbXB0X21heCwzKSl7CiAgICB1cmw8LSJodHRwczovL3VzZWFzdC5lbnNlbWJsLm9yZy8iCiAgfWVsc2UgaWYoYXR0ZW1wdCVpbiVzZXEoZnJvbT00LHRvPWF0dGVtcHRfbWF4LDMpKXsKICAgIHVybDwtImh0dHBzOi8vYXNpYS5lbnNlbWJsLm9yZy8iCiAgfQogIHRyeSh7CiAgICBBbm5fMG1hcnQgPC0gdXNlRGF0YXNldCgKICAgICAgImhzYXBpZW5zX2dlbmVfZW5zZW1ibCIsCiAgICAgIHVzZU1hcnQoIkVOU0VNQkxfTUFSVF9FTlNFTUJMIiwgaG9zdCA9IHVybCkKICAgICkKICB9LCBzaWxlbnQgPSBUUlVFKQogIGF0dGVtcHQ8LWF0dGVtcHQrMQogIGlmKGlzLm51bGwoQW5uXzBtYXJ0KSl7U3lzLnNsZWVwKDEwKX0KfQpBbm5vX0dlbmVMcyA8LSBnZXRCTSgKICBtYXJ0ID0gQW5uXzBtYXJ0LAogIGF0dHJpYnV0ZXMgPSBjKCJoZ25jX3N5bWJvbCIsICJlbnNlbWJsX2dlbmVfaWQiLCAiZXh0ZXJuYWxfZ2VuZV9uYW1lIiksCiAgZmlsdGVyID0gImVuc2VtYmxfZ2VuZV9pZCIsCiAgdmFsdWVzID0gR2VuZUxzLAogIHVuaXF1ZVJvd3MgPSBUUlVFCikKcm93bmFtZXMoQW5ub19HZW5lTHMpPC1Bbm5vX0dlbmVMcyRlbnNlbWJsX2dlbmVfaWQKYGBgCjxicj4KCk1ha2UgZnVuY3Rpb25zIGJhc2VkIG9uIHRoZSBbVW5pcG9ydCBBUEldKGh0dHBzOi8vd3d3LnVuaXByb3Qub3JnL2hlbHAvYXBpX3F1ZXJpZXMpIGFuZCBwcmVwYXJlIHRoZSBnZW5lIGxpc3QuCmBgYHtyIDAyLTAxLmR9CmxpYnJhcnkoaHR0cikKaXNKb2JSZWFkeSA8LSBmdW5jdGlvbihqb2JJZCkgewogIHBvbGxpbmdJbnRlcnZhbCA8LSA1CiAgblRyaWVzIDwtIDIwCiAgZm9yIChpIGluIDE6blRyaWVzKSB7CiAgICB1cmwgPC0gcGFzdGUoImh0dHBzOi8vcmVzdC51bmlwcm90Lm9yZy9pZG1hcHBpbmcvc3RhdHVzLyIsIGpvYklkLCBzZXAgPSAiIikKICAgIHIgPC0gR0VUKHVybCA9IHVybCwgYWNjZXB0X2pzb24oKSkKICAgIHN0YXR1cyA8LSBodHRyOjpjb250ZW50KHIsIGFzID0gInBhcnNlZCIpCiAgICBpZiAoIWlzLm51bGwoc3RhdHVzW1sicmVzdWx0cyJdXSkgfHwgIWlzLm51bGwoc3RhdHVzW1siZmFpbGVkSWRzIl1dKSkgewogICAgICByZXR1cm4oVFJVRSkKICAgIH0KICAgIGlmICghaXMubnVsbChzdGF0dXNbWyJtZXNzYWdlcyJdXSkpIHsKICAgICAgcHJpbnQoc3RhdHVzW1sibWVzc2FnZXMiXV0pCiAgICAgIHJldHVybihGQUxTRSkKICAgIH0KICAgIFN5cy5zbGVlcChwb2xsaW5nSW50ZXJ2YWwpCiAgfQogIHJldHVybihGQUxTRSkKfQpnZXRfbmV4dF9saW5rIDwtIGZ1bmN0aW9uKGhlYWRlcnMpIHsKICBpZiAoIWlzLm51bGwoaGVhZGVyc1tbIkxpbmsiXV0pKSB7CiAgICBtYXRjaCA8LSBzdHJfbWF0Y2goCiAgICAgIGhlYWRlcnNbWyJMaW5rIl1dLAogICAgICAnXlxcPFxccyooLio/KVxccypcXD5cXDsgcmVsXFw9XFwibmV4dFxcIiQnCiAgICApCiAgICBpZiAoIWlzRW1wdHkobWF0Y2gpKSB7CiAgICAgIHJldHVybihtYXRjaFsyXSkKICAgIH0KICB9IGVsc2UgewogICAgTlVMTAogIH0KfQpgYGAKPGJyPgoKRmV0Y2ggVW5pcHJvdElEIGZvciB0aGUgTWl0b2Nob25kcmlhbC9DeXRvc2tlbGV0YWwgZ2VuZXMgdXNpbmcgRW5zZW1ibCBJRApgYGB7ciAwMi0wMS5lfQp0ZW1wIDwtIEFubm9fR2VuZUxzJGVuc2VtYmxfZ2VuZV9pZApmaWxlcyA8LSBsaXN0KAogIGZyb20gPSAiRW5zZW1ibCIsCiAgdG8gPSAiVW5pUHJvdEtCLVN3aXNzLVByb3QiLAogIGlkcyA9IHBhc3RlMCh0ZW1wLCBjb2xsYXBzZSA9ICIsIikKKQpyIDwtIFBPU1QodXJsID0gImh0dHBzOi8vcmVzdC51bmlwcm90Lm9yZy9pZG1hcHBpbmcvcnVuIiwgYm9keSA9IGZpbGVzLCBlbmNvZGUgPSAibXVsdGlwYXJ0IiwgYWNjZXB0X2pzb24oKSkKciA8LSBQT1NUKHVybCA9ICJodHRwczovL3Jlc3QudW5pcHJvdC5vcmcvaWRtYXBwaW5nL3J1biIsIGJvZHkgPSBmaWxlcywgZW5jb2RlID0gIm11bHRpcGFydCIsIGFjY2VwdF9qc29uKCkpCndoaWxlIChyJHN0YXR1c19jb2RlICE9IDIwMCkgewogIHIgPC0gUE9TVCh1cmwgPSAiaHR0cHM6Ly9yZXN0LnVuaXByb3Qub3JnL2lkbWFwcGluZy9ydW4iLCBib2R5ID0gZmlsZXMsIGVuY29kZSA9ICJtdWx0aXBhcnQiLCBhY2NlcHRfanNvbigpKQogIFN5cy5zbGVlcCg1KQp9CnN1Ym1pc3Npb24gPC0gaHR0cjo6Y29udGVudChyLCBhcyA9ICJwYXJzZWQiKQppZiAoaXNKb2JSZWFkeShzdWJtaXNzaW9uW1siam9iSWQiXV0pKSB7CiAgdXJsIDwtIHBhc3RlKCJodHRwczovL3Jlc3QudW5pcHJvdC5vcmcvaWRtYXBwaW5nL2RldGFpbHMvIiwgc3VibWlzc2lvbltbImpvYklkIl1dLCBzZXAgPSAiIikKICByZXNwb25zZSA8LSBHRVQodXJsID0gdXJsLCBhY2NlcHRfanNvbigpKQogIGRldGFpbHMgPC0gaHR0cjo6Y29udGVudChyZXNwb25zZSwgYXMgPSAicGFyc2VkIikKICByZXNfdXJsIDwtIHBhc3RlMCgKICAgIGRldGFpbHNbWyJyZWRpcmVjdFVSTCJdXSwKICAgICI/ZmllbGRzPWFjY2Vzc2lvbiUyQ3Jldmlld2VkJTJDaWQlMkNwcm90ZWluX25hbWUlMkNsZW5ndGglMkNnZW5lX3ByaW1hcnklMkNnbyUyQ2Z0X3puX2ZpbmclMkNmdF9yZXBlYXQlMkNmdF9yZWdpb24lMkNmdF9kb21haW4lMkNmdF9tb3RpZiUyQ2NjX2RvbWFpbiUyQ2Z0X2NvbXBiaWFzJTJDZnRfY29pbGVkJTJDYW5ub3RhdGlvbl9zY29yZSUyQ2tleXdvcmQlMkNjb21tZW50X2NvdW50JTJDY2Nfc3ViY2VsbHVsYXJfbG9jYXRpb24lMkNjY19mdW5jdGlvbiUyQ2NjX3BhdGh3YXklMkNwcm90ZWluX2ZhbWlsaWVzJTJDZnRfY2FyYm9oeWQlMkNmdF9wZXB0aWRlJTJDZnRfdHJhbnNpdCUyQ2Z0X3NpZ25hbCUyQ2Z0X3Byb3BlcCUyQ2NjX3B0bSUyQ2Z0X2luaXRfbWV0JTJDZnRfbGlwaWQlMkNmdF9tb2RfcmVzJTJDZnRfZGlzdWxmaWQlMkNmdF9jcm9zc2xuayUyQ2Z0X2NoYWluJTJDeHJlZl9rZWdnJmZvcm1hdD10c3Ymc2l6ZT01MDAiCiAgKQogIHJlc3BvbnNlIDwtIEdFVCh1cmwgPSByZXNfdXJsLCBhY2NlcHRfanNvbigpKQogIHRvdGFsIDwtIHJlc3BvbnNlJGhlYWRlcnMkYHgtdG90YWwtcmVzdWx0c2AKICB0ZW1wMiA8LSBkYXRhLnRhYmxlOjpmcmVhZCgKICAgIHJlc191cmwsCiAgICB2ZXJib3NlID0gVFJVRSwgc2hvd1Byb2dyZXNzID0gVFJVRQogICkgJT4lIGFzLmRhdGEuZnJhbWUoKQogIHdoaWxlICghaXMubnVsbChyZXNfdXJsKSkgewogICAgcmVzcG9uc2UgPC0gR0VUKHVybCA9IHJlc191cmwsIGFjY2VwdF9qc29uKCkpCiAgICByZXNfdXJsIDwtIGdldF9uZXh0X2xpbmsoaGVhZGVycyhyZXNwb25zZSkpCiAgICBpZiAoIWlzLm51bGwocmVzX3VybCkpIHsKICAgICAgdGVtcDIgPC0gcmJpbmQoCiAgICAgICAgdGVtcDIsCiAgICAgICAgZGF0YS50YWJsZTo6ZnJlYWQoCiAgICAgICAgICByZXNfdXJsLAogICAgICAgICAgdmVyYm9zZSA9IEZBTFNFLCBzaG93UHJvZ3Jlc3MgPSBGQUxTRQogICAgICAgICkgJT4lIGFzLmRhdGEuZnJhbWUoKQogICAgICApCiAgICAgIHByaW50KAogICAgICAgIHBhc3RlMCgKICAgICAgICAgIG5yb3codGVtcDIpLCAiLyIsIHRvdGFsCiAgICAgICAgKQogICAgICApCiAgICB9CiAgfQp9CmNvbG5hbWVzKHRlbXAyKVsxXSA8LSAiRW5zZW1ibElEIgphIDwtIHN0cl9zcGxpdCh0ZW1wMiRFbnNlbWJsSUQsICIsIikKdGVtcCA8LSB3aGljaCgobGVuZ3RocyhhKSA+PSAyKSA9PSBUUlVFKQpmb3IgKGkgaW4gdGVtcCkgewogIHRlbXAxIDwtIGFbW2ldXQogIHRlbXAyJEVuc2VtYmxJRFtpXSA8LSB0ZW1wMVsxXQogIGZvciAoaiBpbiAyOmxlbmd0aCh0ZW1wMSkpIHsKICAgIHRlbXAyIDwtIHJiaW5kKHRlbXAyLCB0ZW1wMltpLCBdKQogICAgbnJvdyA8LSBucm93KHRlbXAyKQogICAgdGVtcDIkRW5zZW1ibElEW25yb3ddIDwtIHRlbXAxW2pdCiAgfQp9CmR1cF9scyA8LSB0ZW1wMiRFbnNlbWJsSURbZHVwbGljYXRlZCh0ZW1wMiRFbnNlbWJsSUQpXQpmb3IoaSBpbiBkdXBfbHMpewogIHRlbXAgPC0gdGVtcDJbdGVtcDIkRW5zZW1ibElEIT1pLF0KICBkdXBfcm93cyA8LSB0ZW1wMlt0ZW1wMiRFbnNlbWJsSUQ9PWksXQogIGR1cF9yb3dzIDwtIGR1cF9yb3dzW2R1cF9yb3dzJExlbmd0aCA9PSBtYXgoZHVwX3Jvd3MkTGVuZ3RoKSxdCiAgdGVtcDIgPC1yYmluZChkdXBfcm93cywgdGVtcCkKfQpyb3duYW1lcyh0ZW1wMikgPC0gdGVtcDIkRW5zZW1ibElECkFubm9fR2VuZUxzIDwtIHRlbXAyCmBgYAo8YnI+CgpHZXQgcmVsYXRlZCBUcmFuc2NyaXB0aW9uIEZhY3RvcnMgKFRGcykKYGBge3IgMDItMDEuZn0KVEZMaW5rX3JlbGF0ZWQgPC0gVEZMaW5rW1RGTGluayRVbmlwcm90SUQuVGFyZ2V0ICVpbiUgQW5ub19HZW5lTHMkRW50cnksXQojVEZfbHMgPC0gdW5pcXVlKFRGTGlua19yZWxhdGVkJFVuaXByb3RJRC5URikKI1RGTGlua19yZWxhdGVkIDwtIFRGTGlua1soVEZMaW5rJFVuaXByb3RJRC5URiAlaW4lIFRGX2xzIHwgVEZMaW5rJFVuaXByb3RJRC5UYXJnZXQgJWluJSBURl9scyksXQojVEZMaW5rX3JlbGF0ZWQgPC0gVEZMaW5rX3JlbGF0ZWRbKFRGTGlua19yZWxhdGVkJFVuaXByb3RJRC5UYXJnZXQgJWluJSBjKFRGX2xzLEFubm9fR2VuZUxzJEVudHJ5KSksXQpoZWFkKFRGTGlua19yZWxhdGVkKQpgYGAKPGJyPgoKIyMgMDItMDIuIEdldCB0aGUgTy1HbGNOQWMgaW5mbwoKQWRkIHRoZSBPLUdsY05BYyBzaXRlIGluZm9ybWF0aW9uIGZyb20gdGhlIFtPLUdsY05BY0F0bGFzXShodHRwczovL29nbGNuYWMub3JnL2F0bGFzL3NlYXJjaC8pIGRhdGFiYXNlLgoKRmlyc3Qgd2UgaGF2ZSB0byByZXRyaWV2ZSB0aGUgVW5pUG9ydCBBY2Nlc3Npb24gbnVtYmVyIGZvciBlYWNoIGdlbmVzCmBgYHtyIDAyLTAyLmF9CmxpYnJhcnkoImh0dHIiKSAKbGlicmFyeSgiZHBseXIiKSAKbGlicmFyeSgianNvbmxpdGUiKQpsaWJyYXJ5KCJjdXJsIikKbGlicmFyeSgiUkN1cmwiKQpsaWJyYXJ5KCJtYWdyaXR0ciIpCmxpYnJhcnkoInJsaXN0IikKbGlicmFyeSgicGlwZVIiKQpsaWJyYXJ5KCJwbHlyIikKbGlicmFyeSgieG1sMiIpCmxpYnJhcnkoInJ2ZXN0IikKbGlicmFyeSgicGFyYWxsZWwiKQpsaWJyYXJ5KCJkb1BhcmFsbGVsIikKbGlicmFyeSgiZG9TTk9XIikKIyBGZXRjaCBPLUdsY05BYyByZWNvcmRzIGZyb20gaHR0cHM6Ly93d3cub2dsY25hYy5vcmcvYXRsYXMvc2VhcmNoLwp0ZW1wMiA8LSBkYXRhLmZyYW1lKCkKVW5pcG9ydGxzIDwtIHVuaXF1ZShURkxpbmtfcmVsYXRlZCRVbmlwcm90SUQuVEYpClBhcl9jbCA8LSBtYWtlQ2x1c3RlcihwYXJhbGxlbGx5OjpmcmVlQ29yZXMoKVsxXSkKcmVnaXN0ZXJEb1NOT1coUGFyX2NsKQpQYXJfcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMSwgbWF4ID0gbGVuZ3RoKFVuaXBvcnRscyksIHN0eWxlID0gMykKcHJvZ3Jlc3MgPC0gZnVuY3Rpb24obikgc2V0VHh0UHJvZ3Jlc3NCYXIoUGFyX3BiLCBuKQpQYXJfb3B0cyA8LSBsaXN0KHByb2dyZXNzID0gcHJvZ3Jlc3MpCnRlbXAyIDwtIGZvcmVhY2goCiAgaSA9IDE6bGVuZ3RoKFVuaXBvcnRscyksIC5jb21iaW5lID0gYmFzZTo6cmJpbmQsIC5lcnJvcmhhbmRsaW5nID0gInBhc3MiLAogIC5vcHRpb25zLnNub3cgPSBQYXJfb3B0cywgLnBhY2thZ2VzID0gYygidGNsdGsiLCAiZHBseXIiLCAidXRpbHMiKQopICVkb3BhciUgewogIFF1ZXJ5IDwtIFVuaXBvcnRsc1tpXQogIHVybCA8LSBjKCJodHRwczovL3d3dy5vZ2xjbmFjLm9yZy9hdGxhcy9zZWFyY2gvIikKICBwZyA8LSBydmVzdDo6c2Vzc2lvbih1cmwpCiAgdG9rZW4gPC0geG1sMjo6eG1sX2F0dHJzKHhtbDI6OnhtbF9maW5kX2FsbCh4bWwyOjpyZWFkX2h0bWwocGcpLCAiLi8vaW5wdXQiKVtbMV1dKVtbInZhbHVlIl1dCiAgbXloZWFkZXJzIDwtIGMoCiAgJ2FjY2VwdCcgPSd0ZXh0L2h0bWwsYXBwbGljYXRpb24veGh0bWwreG1sLGFwcGxpY2F0aW9uL3htbDtxPTAuOSxpbWFnZS9hdmlmLGltYWdlL3dlYnAsaW1hZ2UvYXBuZywqLyo7cT0wLjgsYXBwbGljYXRpb24vc2lnbmVkLWV4Y2hhbmdlO3Y9YjM7cT0wLjcnLAogICdhY2NlcHQtZW5jb2RpbmcnID0gJ2d6aXAsIGRlZmxhdGUsIGJyJywKICAnYWNjZXB0LWxhbmd1YWdlJyA9ICdlbi1VUyxlbjtxPTAuOSx6aC1DTjtxPTAuOCx6aDtxPTAuNyx6aC1UVztxPTAuNixqYTtxPTAuNScsCiAgJ2NhY2hlLWNvbnRyb2wnID0gJ21heC1hZ2U9MCcsCiAgJ2NvbnRlbnQtbGVuZ3RoJyA9IGFzLmNoYXJhY3RlcihuY2hhcihRdWVyeSkgKyAxMjApLAogICdjb250ZW50LXR5cGUnID0gJ2FwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCcsCiAgJ2Nvb2tpZScgPSBzdHJpbmdyOjpzdHJfc3BsaXQocGdbWyJyZXNwb25zZSJdXVtbImhlYWRlcnMiXV1bWyJzZXQtY29va2llIl1dLCI7IilbWzFdXVsxXSwKICAnZG50JyA9ICcxJywKICAnb3JpZ2luJyA9ICdodHRwczovL29nbGNuYWMub3JnJywKICAncmVmZXJlcicgPSAnaHR0cHM6Ly9vZ2xjbmFjLm9yZy9hdGxhcy9zZWFyY2gvJywKICAnc2VjLWNoLXVhJyA9ICciR29vZ2xlIENocm9tZSI7dj0iMTExIiwgIk5vdChBOkJyYW5kIjt2PSI4IiwgIkNocm9taXVtIjt2PSIxMTEiJywKICAnc2VjLWNoLXVhLW1vYmlsZScgPSAnPzAnLAogICdzZWMtY2gtdWEtcGxhdGZvcm0nID0gJ0xpbnV4JywKICAnc2VjLWZldGNoLWRlc3QnID0gJ2RvY3VtZW50JywKICAnc2VjLWZldGNoLW1vZGUnID0gJ25hdmlnYXRlJywKICAnc2VjLWZldGNoLXNpdGUnID0gJ3NhbWUtb3JpZ2luJywKICAnc2VjLWZldGNoLXVzZXInID0gJz8xJywKICAndXBncmFkZS1pbnNlY3VyZS1yZXF1ZXN0cycgPSAnMScsCiAgJ3VzZXItYWdlbnQnID0gJ01vemlsbGEvNS4wIChYMTE7IExpbnV4IHg4Nl82NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzExMS4wLjAuMCBTYWZhcmkvNTM3LjM2JwogICkKICByZXNwb25zZSA8LSBodHRyOjpQT1NUKAogICAgdXJsID0gdXJsLAogICAgaHR0cjo6YWRkX2hlYWRlcnMoLmhlYWRlcnMgPSBteWhlYWRlcnMpLAogICAgYm9keSA9IGxpc3QoCiAgICAgICJjc3JmbWlkZGxld2FyZXRva2VuIiA9IHRva2VuLAogICAgICAic2VhcmNoX3Rlcm0iID0gUXVlcnksCiAgICAgICJzZWFyY2hfZmllbGQiID0gIkFjY2Vzc2lvbiIKICAgICksCiAgICBlbmNvZGUgPSAiZm9ybSIKICApCiAgaWYgKGh0dHI6OnN0YXR1c19jb2RlKHJlc3BvbnNlKSAhPSAyMDApIHsKICAgIG91dCA8LSBkYXRhLmZyYW1lKAogICAgICBRdWVyeSA9IFF1ZXJ5LAogICAgICBBY2Nlc3Npb24gPSAiRmFpbHVyZSIsCiAgICAgIEVudHJ5X05hbWUgPSAiRmFpbHVyZSIsCiAgICAgIFByb3RlaW5fTmFtZSA9ICJGYWlsdXJlIiwKICAgICAgR2VuZV9OYW1lID0gIkZhaWx1cmUiLAogICAgICBQb3NpdGlvbl9pbl9Qcm90ZWluID0gIkZhaWx1cmUiLAogICAgICBTdGF0dXMgPSBodHRyOjpzdGF0dXNfY29kZShyZXNwb25zZSksCiAgICAgICJPLUdsY05BYyIgPSAiVW5rbm93biIKICAgICkKICB9IGVsc2UgaWYgKHJ2ZXN0OjpodG1sX3RhYmxlKHhtbDI6OnJlYWRfaHRtbChyZXNwb25zZSkpW1syXV0gJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgbnJvdygpID09IDApIHsKICAgIG91dCA8LSBkYXRhLmZyYW1lKAogICAgICBRdWVyeSA9IFF1ZXJ5LAogICAgICBBY2Nlc3Npb24gPSAiTm9fcmVjb3JkcyIsCiAgICAgIEVudHJ5X05hbWUgPSAiTm9fcmVjb3JkcyIsCiAgICAgIFByb3RlaW5fTmFtZSA9ICJOb19yZWNvcmRzIiwKICAgICAgR2VuZV9OYW1lID0gIk5vX3JlY29yZHMiLAogICAgICBQb3NpdGlvbl9pbl9Qcm90ZWluID0gIk5vX3JlY29yZHMiLAogICAgICBTdGF0dXMgPSBodHRyOjpzdGF0dXNfY29kZShyZXNwb25zZSksCiAgICAgICJPLUdsY05BYyIgPSAiTm9uIgogICAgKQogIH0gZWxzZSBpZiAocnZlc3Q6Omh0bWxfdGFibGUoeG1sMjo6cmVhZF9odG1sKHJlc3BvbnNlKSlbWzJdXSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSBucm93KCkgIT0gMCkgewogICAgb3V0IDwtIHJ2ZXN0OjpodG1sX3RhYmxlKHhtbDI6OnJlYWRfaHRtbChyZXNwb25zZSkpW1syXV0gJT4lIGFzLmRhdGEuZnJhbWUoKQogICAgb3V0IDwtIGRhdGEuZnJhbWUoCiAgICAgIFF1ZXJ5ID0gUXVlcnksCiAgICAgIEFjY2Vzc2lvbiA9IG91dCRBY2Nlc3Npb24sCiAgICAgIEVudHJ5X05hbWUgPSBvdXQkYEVudHJ5IE5hbWVgLAogICAgICBQcm90ZWluX05hbWUgPSBvdXQkYFByb3RlaW4gTmFtZWAsCiAgICAgIEdlbmVfTmFtZSA9IG91dCRgR2VuZSBOYW1lYCwKICAgICAgUG9zaXRpb25faW5fUHJvdGVpbiA9IG91dCRgUG9zaXRpb24gaW4gUHJvdGVpbmAsCiAgICAgIFN0YXR1cyA9IGh0dHI6OnN0YXR1c19jb2RlKHJlc3BvbnNlKSwKICAgICAgIk8tR2xjTkFjIiA9ICJZZXMiCiAgICApCiAgfQogIG91dAp9CnN0b3BDbHVzdGVyKFBhcl9jbCkKT0dsY05BY19Bbm5vIDwtIHRlbXAyCmBgYAo8YnI+CgpBZGQgTy1HbGNOQWMgaW5mbyB0byBURnMuCmBgYHtyIDAyLTAyLmJ9Ck9HbGNOQWMgPC0gcmVwKCJObyIsIG5yb3coVEZMaW5rX3JlbGF0ZWQpKQpURkxpbmtfcmVsYXRlZCA8LSBjYmluZChPR2xjTkFjLCBURkxpbmtfcmVsYXRlZCkKVEZMaW5rX3JlbGF0ZWQkT0dsY05BY1tURkxpbmtfcmVsYXRlZCRVbmlwcm90SUQuVEYgJWluJSBPR2xjTkFjX0Fubm8kQWNjZXNzaW9uXTwtIlllcyIKaGVhZChURkxpbmtfcmVsYXRlZCkKYGBgCgojIyAwMi0wMy4gR2V0IHNpZ25pZmNpYW50bHkgY2hhbmdlZCBURnMKCkFkZCBERUdzIGluZm8gdG8gVEZzLgpgYGB7ciAwMi0wM30KREVHcyA8LSBERUdzX2xzW1siSEVLMjkzX1RNR182aEJ2c0hFSzI5M19PU01JMl82aEEiXV1bWyJERUdzX3JhdyJdXQpERUdzIDwtIERFR3NbREVHcyRJcy5TaWcuPT0iWWVzIixdCmlzLkRFR3MgPC0gcmVwKCJObyIsIG5yb3coVEZMaW5rX3JlbGF0ZWQpKQpURkxpbmtfcmVsYXRlZCA8LSBjYmluZChpcy5ERUdzLCBURkxpbmtfcmVsYXRlZCkKVEZMaW5rX3JlbGF0ZWQkaXMuREVHc1tURkxpbmtfcmVsYXRlZCRVbmlwcm90SUQuVEYgJWluJSBERUdzJFVuaVByb3RLQklEXTwtIlllcyIKaGVhZChURkxpbmtfcmVsYXRlZCkKYGBgCgojIyAwMi0wNC4gRmlsdGVyaW5nICYgVmlzdWFsaXphdGlvbgoKIyMjIDEuIEZpbHRlcmluZwoKRmlsdGVyIG91dCB0aGUgVEZzIHdpaG91dCBib3RoIHNpZ25pZmljYW50bHkgY2hhbmdlcyBhbmQgTy1HbGNOQWMgc2l0ZXMuCmBgYHtyIDAyLTA0LjEuYX0KVEZMaW5rX3JlbGF0ZWQgPC0gVEZMaW5rX3JlbGF0ZWRbKFRGTGlua19yZWxhdGVkJGlzLkRFR3MgPT0gIlllcyIgfCBURkxpbmtfcmVsYXRlZCRPR2xjTkFjID09ICJZZXMiKSxdClRGTGlua19yZWxhdGVkIDwtIFRGTGlua19yZWxhdGVkWyhURkxpbmtfcmVsYXRlZCRPR2xjTkFjID09ICJZZXMiKSxdCmRpbShURkxpbmtfcmVsYXRlZCkKaGVhZChURkxpbmtfcmVsYXRlZCkKYGBgCjxicj4KClJlbW92ZSB0aGUgcmVjb3JkcyB0aGF0IG5vdCByZWxhdGVkIHRvIGJvdGggb2YgTWl0b2Nob25kcmlhbC9DeXRvc2tlbGV0YWwgZ2VuZXMgYW5kIHJlbWFpbmVkIFRGcy4KYGBge3IgMDItMDQuMS5ifQpDaGtMcyA8LSB1bmlxdWUoYyhURkxpbmtfcmVsYXRlZCRVbmlwcm90SUQuVEYsIEFubm9fR2VuZUxzJEVudHJ5KSkKVEZMaW5rX3JlbGF0ZWQgPC0gVEZMaW5rX3JlbGF0ZWRbVEZMaW5rX3JlbGF0ZWQkVW5pcHJvdElELlRhcmdldCAlaW4lIENoa0xzLF0KZGltKFRGTGlua19yZWxhdGVkKQpoZWFkKFRGTGlua19yZWxhdGVkKQpgYGAKCiMjIyAyLiBMaXRlcmF0dXJlIG1pbmluZyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgojIyMjIE1ha2UgZnVuY3Rpb25zIHsudGFic2V0IC50YWJzZXQtZmFkZX0KCk1ha2UgbGl0ZXJhdHVyZSBtaW5pbmcgZnVuY3Rpb25zCmBgYHtyIDAyLTA0LjIuYX0KI0xvYWQgZnVuY3Rpb25zIwpSZXRyaWV2ZUVudHJleklEIDwtIGZ1bmN0aW9uKAogICAgSG9tb2xvZ3Vlcz1GQUxTRSwKICAgIEhvbW9fc3BlY2llcz1Ib21vX3NwZWNpZXMsCiAgICBQYXJfc3BlY2llcz1QYXJfc3BlY2llcywKICAgIFBhcl9zcGVjaWVzLlVuaXByb3Q9UGFyX3NwZWNpZXMuVW5pcHJvdCwKICAgIEdlbmVzTHM9R2VuZXNMcywKICAgIHVwZGF0ZVByb2dyZXNzID0gTlVMTAopewogICN8LS0tLVNldCB1cCBCaW9tYXJ0IHBhcmFtZXRlcnMtLS0tfCMjIyMKICBBbm5fMG1hcnQ8LU5VTEwKICBhdHRlbXB0PC0xCiAgYXR0ZW1wdF9tYXg8LTEyCiAgd2hpbGUoaXMubnVsbChBbm5fMG1hcnQpJiZhdHRlbXB0PDEzKXsKICAgIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209MSx0bz1hdHRlbXB0X21heCwzKSl7CiAgICAgIHVybDwtImh0dHBzOi8vdXNlYXN0LmVuc2VtYmwub3JnIgogICAgfWVsc2UgaWYoYXR0ZW1wdCVpbiVzZXEoZnJvbT0yLHRvPWF0dGVtcHRfbWF4LDMpKXsKICAgICAgdXJsPC0iaHR0cHM6Ly93d3cuZW5zZW1ibC5vcmciCiAgICB9ZWxzZSBpZihhdHRlbXB0JWluJXNlcShmcm9tPTMsdG89YXR0ZW1wdF9tYXgsMykpewogICAgICB1cmw8LSJodHRwczovL3VzZWFzdC5lbnNlbWJsLm9yZy8iCiAgICB9ZWxzZSBpZihhdHRlbXB0JWluJXNlcShmcm9tPTQsdG89YXR0ZW1wdF9tYXgsMykpewogICAgICB1cmw8LSJodHRwczovL2FzaWEuZW5zZW1ibC5vcmcvIgogICAgfQogICAgdHJ5KHsKICAgICAgQW5uXzBtYXJ0IDwtIHVzZU1hcnQoIkVOU0VNQkxfTUFSVF9FTlNFTUJMIiwgaG9zdD11cmwpICU+JQogICAgICAgIHVzZURhdGFzZXQoUGFyX3NwZWNpZXMsIC4pCiAgICB9LCBzaWxlbnQgPSBUUlVFKQogICAgYXR0ZW1wdDwtYXR0ZW1wdCsxCiAgICBpZihpcy5udWxsKEFubl8wbWFydCkpe1N5cy5zbGVlcCgxMCl9CiAgfQogIAogIGlmKEhvbW9sb2d1ZXMpewogICAgaWYgKGlzLmZ1bmN0aW9uKHVwZGF0ZVByb2dyZXNzKSkgewogICAgICB0ZXh0IDwtIHBhc3RlMCgiR2V0IEhvbW9sb2dvZXVzIEdlbmUgaW4gIiwgSG9tb19zcGVjaWVzKSAKICAgICAgdXBkYXRlUHJvZ3Jlc3MoZGV0YWlsID0gdGV4dCkKICAgIH0KICAgICNsaXN0QXR0cmlidXRlcyhBbm5fMG1hcnQpJT4lVmlldygpCiAgICB0ZW1wIDwtIE5VTEwKICAgIGF0dGVtcHQ8LTEKICAgIGF0dGVtcHRfbWF4PC0xMgogICAgd2hpbGUoaXMubnVsbCh0ZW1wKSYmYXR0ZW1wdDwxMyl7CiAgICAgIHRyeSh7CiAgICAgICAgdGVtcCA8LSBnZXRCTSgKICAgICAgICAgIG1hcnQ9QW5uXzBtYXJ0LAogICAgICAgICAgYXR0cmlidXRlcz1jKCJlbnNlbWJsX2dlbmVfaWQiLAogICAgICAgICAgICAgICAgICAgICAgIEhvbW9fc3BlY2llcyksCiAgICAgICAgICBmaWx0ZXI9ImVuc2VtYmxfZ2VuZV9pZCIsCiAgICAgICAgICB2YWx1ZXM9R2VuZXNMcywKICAgICAgICAgIHVuaXF1ZVJvd3MgPSBUKQogICAgICB9LCBzaWxlbnQgPSBUUlVFKQogICAgICBhdHRlbXB0PC1hdHRlbXB0KzEKICAgICAgaWYoaXMubnVsbCh0ZW1wKSl7U3lzLnNsZWVwKDEwKX0KICAgIH0KICAgIHRlbXA8LXRlbXBbIXRlbXBbLDJdJWluJSIiLF0KICAgIHRlbXA8LXRlbXBbIWR1cGxpY2F0ZWQodGVtcFssMl0pLF0KICAgIE5hTWFwcGluZzwtdGVtcFssMV0KICAgIG5hbWVzKE5hTWFwcGluZyk8LXRlbXBbLDJdCiAgICBwcmludChIb21vX3NwZWNpZXMpCiAgICBpZihpc19lbXB0eSh0ZW1wKXxucm93KHRlbXApPDEpewogICAgICByZXR1cm4oTkEpCiAgICB9CiAgICAjcHJpbnQoIlNldCB1cCBCaW9tYXJ0IHBhcmFtZXRlcnMiKQogICAgI3wtLS0tU2V0IHVwIEJpb21hcnQgcGFyYW1ldGVycy0tLS18IyMjIwogICAgaWYoSG9tb19zcGVjaWVzID09ICJoc2FwaWVuc19ob21vbG9nX2Vuc2VtYmxfZ2VuZSIpewogICAgICBQYXJfc3BlY2llcyA8LSAiaHNhcGllbnNfZ2VuZV9lbnNlbWJsIgogICAgfWVsc2UgaWYoSG9tb19zcGVjaWVzID09ICJtbXVzY3VsdXNfaG9tb2xvZ19lbnNlbWJsX2dlbmUiKXsKICAgICAgUGFyX3NwZWNpZXMgPC0gIm1tdXNjdWx1c19nZW5lX2Vuc2VtYmwiCiAgICB9ZWxzZSBpZihIb21vX3NwZWNpZXMgPT0gInJub3J2ZWdpY3VzX2hvbW9sb2dfZW5zZW1ibF9nZW5lIil7CiAgICAgIFBhcl9zcGVjaWVzIDwtICJybm9ydmVnaWN1c19nZW5lX2Vuc2VtYmwiCiAgICB9ZWxzZSBpZihIb21vX3NwZWNpZXMgPT0gInNzY3JvZmFfaG9tb2xvZ19lbnNlbWJsX2dlbmUiKXsKICAgICAgUGFyX3NwZWNpZXMgPC0gInNzY3JvZmFfZ2VuZV9lbnNlbWJsIgogICAgfQogICAgQW5uXzBtYXJ0PC1OVUxMCiAgICBhdHRlbXB0PC0xCiAgICBhdHRlbXB0X21heDwtMTIKICAgIHdoaWxlKGlzLm51bGwoQW5uXzBtYXJ0KSYmYXR0ZW1wdDwxMyl7CiAgICAgIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209MSx0bz1hdHRlbXB0X21heCwzKSl7CiAgICAgICAgdXJsPC0iaHR0cHM6Ly91c2Vhc3QuZW5zZW1ibC5vcmciCiAgICAgIH1lbHNlIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209Mix0bz1hdHRlbXB0X21heCwzKSl7CiAgICAgICAgdXJsPC0iaHR0cHM6Ly93d3cuZW5zZW1ibC5vcmciCiAgICAgIH1lbHNlIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209Myx0bz1hdHRlbXB0X21heCwzKSl7CiAgICAgICAgdXJsPC0iaHR0cHM6Ly91c2Vhc3QuZW5zZW1ibC5vcmcvIgogICAgICB9ZWxzZSBpZihhdHRlbXB0JWluJXNlcShmcm9tPTQsdG89YXR0ZW1wdF9tYXgsMykpewogICAgICAgIHVybDwtImh0dHBzOi8vYXNpYS5lbnNlbWJsLm9yZy8iCiAgICAgIH0KICAgICAgdHJ5KHsKICAgICAgICBBbm5fMG1hcnQgPC0gdXNlTWFydCgiRU5TRU1CTF9NQVJUX0VOU0VNQkwiLCBob3N0PXVybCkgJT4lCiAgICAgICAgICB1c2VEYXRhc2V0KFBhcl9zcGVjaWVzLCAuKQogICAgICB9LCBzaWxlbnQgPSBUUlVFKQogICAgICBhdHRlbXB0PC1hdHRlbXB0KzEKICAgICAgaWYoaXMubnVsbChBbm5fMG1hcnQpKXtTeXMuc2xlZXAoMTApfQogICAgfQogICAgQW5ub19nZW5lIDwtIE5VTEwKICAgIGF0dGVtcHQ8LTEKICAgIGF0dGVtcHRfbWF4PC0xMgogICAgd2hpbGUoaXMubnVsbChBbm5vX2dlbmUpJiZhdHRlbXB0PDEzKXsKICAgICAgdHJ5KHsKICAgICAgICBBbm5vX2dlbmUgPC0gZ2V0Qk0oCiAgICAgICAgICBtYXJ0PUFubl8wbWFydCwKICAgICAgICAgIGF0dHJpYnV0ZXM9YygiZW5zZW1ibF9nZW5lX2lkIiwiZW50cmV6Z2VuZV9pZCIsImdlbmVfYmlvdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgICAgImV4dGVybmFsX2dlbmVfbmFtZSIsImRlc2NyaXB0aW9uIiksCiAgICAgICAgICBmaWx0ZXI9ImVuc2VtYmxfZ2VuZV9pZCIsCiAgICAgICAgICB2YWx1ZXM9dGVtcFssMl0sCiAgICAgICAgICB1bmlxdWVSb3dzID0gVCkKICAgICAgfSwgc2lsZW50ID0gVFJVRSkKICAgICAgYXR0ZW1wdDwtYXR0ZW1wdCsxCiAgICAgIGlmKGlzLm51bGwoQW5ub19nZW5lKSl7U3lzLnNsZWVwKDEwKX0KICAgIH0KICAgIHJtKHRlbXApCiAgfWVsc2V7CiAgICBwcmludCgiZ2V0Qk0iKQogICAgaWYgKGlzLmZ1bmN0aW9uKHVwZGF0ZVByb2dyZXNzKSkgewogICAgICB0ZXh0IDwtICJHZXQgUHViTWVkIEdlbmUgSUQiCiAgICAgIHVwZGF0ZVByb2dyZXNzKGRldGFpbCA9IHRleHQpCiAgICB9CiAgICBBbm5vX2dlbmUgPC0gTlVMTAogICAgYXR0ZW1wdDwtMQogICAgYXR0ZW1wdF9tYXg8LTEyCiAgICB3aGlsZShpcy5udWxsKEFubm9fZ2VuZSkmJmF0dGVtcHQ8MTMpewogICAgICB0cnkoewogICAgICAgIEFubm9fZ2VuZSA8LSBnZXRCTSgKICAgICAgICAgIG1hcnQ9QW5uXzBtYXJ0LAogICAgICAgICAgYXR0cmlidXRlcz1jKCJlbnNlbWJsX2dlbmVfaWQiLCJlbnRyZXpnZW5lX2lkIiwiZ2VuZV9iaW90eXBlIiwKICAgICAgICAgICAgICAgICAgICAgICAiZXh0ZXJuYWxfZ2VuZV9uYW1lIiwiZGVzY3JpcHRpb24iKSwKICAgICAgICAgIGZpbHRlcj0iZW5zZW1ibF9nZW5lX2lkIiwKICAgICAgICAgIHZhbHVlcz1HZW5lc0xzLAogICAgICAgICAgdW5pcXVlUm93cyA9IFQpCiAgICAgIH0sIHNpbGVudCA9IFRSVUUpCiAgICAgIGF0dGVtcHQ8LWF0dGVtcHQrMQogICAgICBpZihpcy5udWxsKEFubm9fZ2VuZSkpe1N5cy5zbGVlcCgxMCl9CiAgICB9CiAgfQogIAogIEFubm88LUFubm9fZ2VuZVshZHVwbGljYXRlZChBbm5vX2dlbmUkZW5zZW1ibF9nZW5lX2lkKSxdCiAgcm93bmFtZXMoQW5ubyk8LUFubm8kZW5zZW1ibF9nZW5lX2lkCiAgQW5ubzwtZGF0YS5mcmFtZSgKICAgIGVuc2VtYmxfZ2VuZV9pZD1Bbm5vJGVuc2VtYmxfZ2VuZV9pZCwKICAgIGVudHJlemdlbmVfaWQ9QW5ubyRlbnRyZXpnZW5lX2lkLAogICAgZXh0ZXJuYWxfZ2VuZV9uYW1lPUFubm8kZXh0ZXJuYWxfZ2VuZV9uYW1lLAogICAgZ2VuZV9iaW90eXBlPUFubm8kZ2VuZV9iaW90eXBlLAogICAgcm93Lm5hbWVzID0gcm93bmFtZXMoQW5ubykKICApCiAgQW5ubzI8LUFubm9bIWlzLm5hKEFubm8kZW50cmV6Z2VuZV9pZCksXSAjRm9yIEVudHJlekdlbmVJRAogIEFubm8yPC1Bbm5vMlshZHVwbGljYXRlZChBbm5vMlssJ2Vuc2VtYmxfZ2VuZV9pZCddKSxdICNGb3IgRW50cmV6R2VuZUlECiAgcm93bmFtZXMoQW5ubzIpPC1Bbm5vMiRlbnNlbWJsX2dlbmVfaWQKICB0ZW1wPC1Bbm5vW2lzLm5hKEFubm8kZW50cmV6Z2VuZV9pZCksImVuc2VtYmxfZ2VuZV9pZCJdCiAgcHJpbnQoIkNoZWNrIEVudHJlekdlbmVJRCBhZ2FpbiIpCiAgI3wtLS0tQ2hlY2sgRW50cmV6R2VuZUlEIGFnYWluLS0tLXwjIyMjCiAgaWYoIWlzX2VtcHR5KHRlbXApKXsKICAgIGlmKGxlbmd0aCh0ZW1wKTwyKXsKICAgICAgTkNCSWNoZWNrPC1yZWFkX2h0bWwoCiAgICAgICAgcGFzdGUwKAogICAgICAgICAgImh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvZ2VuZS8/dGVybT0iLAogICAgICAgICAgdGVtcAogICAgICAgICkKICAgICAgKSU+JXhtbF9maW5kX2FsbCguLCAiLi8vc3BhbiIpJT4leG1sX3RleHQoKSU+JQogICAgICAgIGdyZXAoIkdlbmUgSUQiLC4sdmFsdWUgPSBUUlVFKQogICAgICBpZighaXNFbXB0eShOQ0JJY2hlY2spKXsKICAgICAgICBOQ0JJY2hlY2s8LU5DQkljaGVjayU+JXVubGlzdCgpJT4lCiAgICAgICAgICBzdHJfZXh0cmFjdF9hbGwoLiwiKFxcZCkrIiklPiUuW1sxXV0lPiUuWzFdCiAgICAgIH1lbHNlewogICAgICAgIE5DQkljaGVjazwtTkEKICAgICAgfQogICAgICBvdXQ8LWRhdGEuZnJhbWUoCiAgICAgICAgZW5zZW1ibF9nZW5lX2lkPXRlbXAsCiAgICAgICAgZW50cmV6Z2VuZV9pZD1OQ0JJY2hlY2sKICAgICAgKSU+JWFzLmRhdGEuZnJhbWUoKQogICAgICB0ZW1wMjwtb3V0CiAgICB9ZWxzZXsKICAgICAgUGFyX2NsPC1tYWtlQ2x1c3RlcihwYXJhbGxlbGx5OjpmcmVlQ29ubmVjdGlvbnMoKS0xKQogICAgICByZWdpc3RlckRvU05PVyhQYXJfY2wpCiAgICAgIFBhcl9wYiA8LSB0eHRQcm9ncmVzc0JhcihtaW49MSwgbWF4PWxlbmd0aCh0ZW1wKSwgc3R5bGUgPSAzKQogICAgICBwcm9ncmVzcyA8LSBmdW5jdGlvbihuKSBzZXRUeHRQcm9ncmVzc0JhcihQYXJfcGIsIG4pCiAgICAgIFBhcl9vcHRzPC1saXN0KHByb2dyZXNzID0gcHJvZ3Jlc3MpCiAgICAgICNSZWZlciBoZXJlIGZvciBhbGwgY29sdW1uIG5hbWVzIGZvciBFbnRyZXogQVBJIChodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2Jvb2tzL05CSzI1NTAxLykKICAgICAgdGVtcDI8LWZvcmVhY2goaT0xOmxlbmd0aCh0ZW1wKSwgLmNvbWJpbmU9cmJpbmQsIC5lcnJvcmhhbmRsaW5nID0gInBhc3MiLCAuaW5vcmRlcj1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgLm9wdGlvbnMuc25vdz1QYXJfb3B0cywgLnBhY2thZ2VzID0gYygiZHBseXIiLCJ1dGlscyIsInhtbDIiLCJTNFZlY3RvcnMiLCJzdHJpbmdyIiwiQmlvY0dlbmVyaWNzIikpICVkb3BhciUgewogICAgICAgICAgICAgICAgICAgICAgIE5DQkljaGVjazwtcmVhZF9odG1sKAogICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKAogICAgICAgICAgICAgICAgICAgICAgICAgICAiaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9nZW5lLz90ZXJtPSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlbXBbaV0KICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICApJT4leG1sX2ZpbmRfYWxsKC4sICIuLy9zcGFuIiklPiV4bWxfdGV4dCgpJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBncmVwKCJHZW5lIElEIiwuLHZhbHVlID0gVFJVRSkKICAgICAgICAgICAgICAgICAgICAgICBpZighaXNFbXB0eShOQ0JJY2hlY2spKXsKICAgICAgICAgICAgICAgICAgICAgICAgIE5DQkljaGVjazwtTkNCSWNoZWNrJT4ldW5saXN0KCklPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX2V4dHJhY3RfYWxsKC4sIihcXGQpKyIpJT4lLltbMV1dJT4lLlsxXQogICAgICAgICAgICAgICAgICAgICAgIH1lbHNlewogICAgICAgICAgICAgICAgICAgICAgICAgTkNCSWNoZWNrPC1OQQogICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICBvdXQ8LWRhdGEuZnJhbWUoCiAgICAgICAgICAgICAgICAgICAgICAgICBlbnNlbWJsX2dlbmVfaWQ9dGVtcFtpXSwKICAgICAgICAgICAgICAgICAgICAgICAgIGVudHJlemdlbmVfaWQ9TkNCSWNoZWNrCiAgICAgICAgICAgICAgICAgICAgICAgKSU+JWFzLmRhdGEuZnJhbWUoKQogICAgICAgICAgICAgICAgICAgICAgIHJldHVybihvdXQpCiAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgc3RvcENsdXN0ZXIoUGFyX2NsKQogICAgfQogICAgdGVtcDI8LXRlbXAyWyFpcy5uYSh0ZW1wMiRlbnRyZXpnZW5lX2lkKSxdCiAgICB0ZW1wMjwtdGVtcDJbIWR1cGxpY2F0ZWQodGVtcDIkZW5zZW1ibF9nZW5lX2lkKSxdCiAgICByb3duYW1lcyh0ZW1wMik8LXRlbXAyJGVuc2VtYmxfZ2VuZV9pZAogICAgdGVtcDI8LWNiaW5kKAogICAgICB0ZW1wMiwKICAgICAgQW5ub1t0ZW1wMiRlbnNlbWJsX2dlbmVfaWQsYygzOjQpXQogICAgKQogICAgI3N1bShBbm5vMiRlbnNlbWJsX2dlbmVfaWQlaW4ldGVtcDIkZW5zZW1ibF9nZW5lX2lkKT09MAogICAgQW5ubzI8LXJiaW5kKAogICAgICBBbm5vMiwKICAgICAgdGVtcDIKICAgICkKICB9CiAgaWYoSG9tb2xvZ3Vlcyl7CiAgICBBbm5vMjwtY2JpbmQobWFwcGluZz1OYU1hcHBpbmdbQW5ubzIkZW5zZW1ibF9nZW5lX2lkXSxBbm5vMikKICAgIEFubm8yPC1Bbm5vMlshZHVwbGljYXRlZChBbm5vMiRtYXBwaW5nKSxdCiAgICByb3duYW1lcyhBbm5vMikgPC0gTmFNYXBwaW5nW0Fubm8yJGVuc2VtYmxfZ2VuZV9pZF0KICAgIEFubm8yPC1Bbm5vMlssLTFdCiAgfQogIHByaW50KCJlbmQiKQogIGlmIChpcy5mdW5jdGlvbih1cGRhdGVQcm9ncmVzcykpIHsKICAgIHRleHQgPC0gcGFzdGUwKCJJRCBDb252ZXJ0IERvbmUgIikgCiAgICB1cGRhdGVQcm9ncmVzcyhkZXRhaWwgPSB0ZXh0KQogIH0KICByZXR1cm4oQW5ubzIpCn0KCnBzZXVkb0xvZzEwIDwtIGZ1bmN0aW9uKHgpeyBhc2luaCh4LzIpL2xvZygxMCkgfQoKV1pZX0dldFN1bW1hcnlUZXh0PC1mdW5jdGlvbihwKXsKICBwZGF0YSA8LSBkYXRhLmZyYW1lKG5hbWUgPSBwJGRhdGEkbGFiZWwsIGNvbG9yMiA9IHAkZGF0YSRncm91cCkKICBwZGF0YSA8LSBwZGF0YVshaXMubmEocGRhdGEkbmFtZSksIF0KICBjbHVzdGVyX2NvbG9yIDwtIHVuaXF1ZShwZGF0YSRjb2xvcjIpCiAgCiAgY2x1c3Rlcl9sYWJlbCA8LSBzYXBwbHkoCiAgICBjbHVzdGVyX2NvbG9yLCBlbnJpY2hwbG90Ojo6Z2V0X3dvcmRjbG91ZCwKICAgIGdnRGF0YSA9IHBkYXRhLCBuV29yZHMgPSA0CiAgKSAlPiUgcGFzdGUwKC4sY29sbGFwc2UgPSAiICIpICU+JSBzdHJfc3BsaXQoLiwgIiAiKSAlPiUgdW5saXN0KCkKICByZXR1cm4oY2x1c3Rlcl9sYWJlbCkKfQoKV1pZX2xpdGVyYXR1cmVNaW5pbmc8LWZ1bmN0aW9uKEdlbmVzTHM9R2VuZXNMcywgTWVzaF90ZXJtNEludGVyZXN0aW5nPU1lc2hfdGVybTRJbnRlcmVzdGluZywgU3BlY2llcz1TcGVjaWVzLCB1cGRhdGVQcm9ncmVzcyA9IE5VTEwpewogIG91dDwtbGlzdCgpCiAgTjwtcGFzdGUwKCNUbyBzZWFyY2ggZm9yIHRoZSB0b3RhbCBudW1iZXIgb2YgUHViTWVkIGNpdGF0aW9ucywgZW50ZXIgYWxsW3NiXSBpbiB0aGUgc2VhcmNoIGJveC4oMjAyMTA1MDYpCiAgICAiaHR0cHM6Ly9wdWJtZWQubmNiaS5ubG0ubmloLmdvdi8/dGVybT0iLAogICAgVVJMZW5jb2RlKCdhbGxbc2JdJyksCiAgICAiJnNvcnQ9ZGF0ZSIKICApJT4lcmVhZF9odG1sKCklPiVodG1sX25vZGVzKCIudmFsdWUiKSU+JWh0bWxfdGV4dCgpJT4lLlsxXSU+JWdzdWIoIiwiLCIiLC4pJT4lYXMubnVtZXJpYygpCiAgSzwtcGFzdGUwKCN0aGUgYW1vdW50cyBvZiBsaXRlcmF0dXJlIHJlbGF0ZWQgdG8gb3N0ZW9ibGFzdCBkaWZmZXJlbnRpYXRpb24gIzIwMjEwNTA2IyBQdWJNZWQgUXVlcnk6IAogICAgImh0dHBzOi8vcHVibWVkLm5jYmkubmxtLm5paC5nb3YvP3Rlcm09IiwKICAgIFVSTGVuY29kZShNZXNoX3Rlcm00SW50ZXJlc3RpbmcpLAogICAgIiZzb3J0PWRhdGUiCiAgKSU+JXJlYWRfaHRtbCgpJT4laHRtbF9ub2RlcygiLnZhbHVlIiklPiVodG1sX3RleHQoKSU+JS5bMV0lPiVnc3ViKCIsIiwiIiwuKSU+JWFzLm51bWVyaWMoKQogIFRocmVzaDwtSy9OKjEwCiAgCiAgIyMjI3xwcmVwYXJlIGdlbmUtcmVsYXRlZCBhcnRpY2xlc3wjIyMjCiAgIyBoc2FwaWVuc19nZW5lX2Vuc2VtYmw7IG1tdXNjdWx1c19nZW5lX2Vuc2VtYmw7IHJub3J2ZWdpY3VzX2dlbmVfZW5zZW1ibDsgc3Njcm9mYV9nZW5lX2Vuc2VtYmwKICAjIEhvbW8gc2FwaWVucyAoSHVtYW4pIFs5NjA2XTsgTXVzIG11c2N1bHVzIChNb3VzZSkgWzEwMDkwXTsgUmF0dHVzIG5vcnZlZ2ljdXMgKHJhdCkgWzEwMTE2XTsgU3VzIHNjcm9mYSAocGlnKSBbOTgyM10KICAjIChodW1hbikgSG9tbyBzYXBpZW5zIChodW1hbikgW2hzYV07IChtb3VzZSkgTXVzIG11c2N1bHVzIChtb3VzZSkgW21tdV07IChyYXQpIFJhdHR1cyBub3J2ZWdpY3VzIFtybm9dOyAocGlnKSBTdXMgc2Nyb2ZhIFtzc2NdCiAgIyBoc2FwaWVuc19ob21vbG9nX2Vuc2VtYmxfZ2VuZTsgbW11c2N1bHVzX2hvbW9sb2dfZW5zZW1ibF9nZW5lOyBybm9ydmVnaWN1c19ob21vbG9nX2Vuc2VtYmxfZ2VuZTsgc3Njcm9mYV9ob21vbG9nX2Vuc2VtYmxfZ2VuZQogIHByaW50KEdlbmVzTHMpCiAgcHJpbnQoTWVzaF90ZXJtNEludGVyZXN0aW5nKQogIHByaW50KFNwZWNpZXMpCiAgaWYgKGlzLmZ1bmN0aW9uKHVwZGF0ZVByb2dyZXNzKSkgewogICAgdGV4dCA8LSAiR2V0IEFsbCBIb21vbG9nb2V1cyBHZW5lcyIKICAgIHVwZGF0ZVByb2dyZXNzKGRldGFpbCA9IHRleHQpCiAgfQogIGlmKFNwZWNpZXMgPT0gIkhzYSIpewogICAgUGFyX3NwZWNpZXMgPC0gImhzYXBpZW5zX2dlbmVfZW5zZW1ibCIKICAgIFBhcl9zcGVjaWVzLlVuaXByb3QgPC0gIjk2MDYiCiAgICBIb21vX3NwZWNpZXMgPC0gYygKICAgICAgIm1tdXNjdWx1c19ob21vbG9nX2Vuc2VtYmxfZ2VuZSIsCiAgICAgICJybm9ydmVnaWN1c19ob21vbG9nX2Vuc2VtYmxfZ2VuZSIsCiAgICAgICJzc2Nyb2ZhX2hvbW9sb2dfZW5zZW1ibF9nZW5lIgogICAgKQogIH1lbHNlIGlmKFNwZWNpZXMgPT0gIk11cyIpewogICAgUGFyX3NwZWNpZXMgPC0gIm1tdXNjdWx1c19nZW5lX2Vuc2VtYmwiCiAgICBQYXJfc3BlY2llcy5Vbmlwcm90IDwtICIxMDA5MCIKICAgIEhvbW9fc3BlY2llcyA8LSBjKAogICAgICAiaHNhcGllbnNfaG9tb2xvZ19lbnNlbWJsX2dlbmUiLAogICAgICAicm5vcnZlZ2ljdXNfaG9tb2xvZ19lbnNlbWJsX2dlbmUiLAogICAgICAic3Njcm9mYV9ob21vbG9nX2Vuc2VtYmxfZ2VuZSIKICAgICkKICB9ZWxzZSBpZihTcGVjaWVzID09ICJSbm8iKXsKICAgIFBhcl9zcGVjaWVzIDwtICJybm9ydmVnaWN1c19nZW5lX2Vuc2VtYmwiCiAgICBQYXJfc3BlY2llcy5Vbmlwcm90IDwtICIxMDExNiIKICAgIEhvbW9fc3BlY2llcyA8LSBjKAogICAgICAibW11c2N1bHVzX2hvbW9sb2dfZW5zZW1ibF9nZW5lIiwKICAgICAgImhzYXBpZW5zX2hvbW9sb2dfZW5zZW1ibF9nZW5lIiwKICAgICAgInNzY3JvZmFfaG9tb2xvZ19lbnNlbWJsX2dlbmUiCiAgICApCiAgfWVsc2UgaWYoU3BlY2llcyA9PSAiU3NjIil7CiAgICBQYXJfc3BlY2llcyA8LSAic3Njcm9mYV9nZW5lX2Vuc2VtYmwiCiAgICBQYXJfc3BlY2llcy5Vbmlwcm90IDwtICI5ODIzIgogICAgSG9tb19zcGVjaWVzIDwtIGMoCiAgICAgICJtbXVzY3VsdXNfaG9tb2xvZ19lbnNlbWJsX2dlbmUiLAogICAgICAicm5vcnZlZ2ljdXNfaG9tb2xvZ19lbnNlbWJsX2dlbmUiLAogICAgICAiaHNhcGllbnNfaG9tb2xvZ19lbnNlbWJsX2dlbmUiCiAgICApCiAgfQogIHByaW50KFBhcl9zcGVjaWVzKQogIHByaW50KFBhcl9zcGVjaWVzLlVuaXByb3QpCiAgcXVlcnkucmVzPC1SZXRyaWV2ZUVudHJleklEKFBhcl9zcGVjaWVzPVBhcl9zcGVjaWVzLCBQYXJfc3BlY2llcy5Vbmlwcm90PVBhcl9zcGVjaWVzLlVuaXByb3QsIEdlbmVzTHM9R2VuZXNMcykKICBob21vbG9nZXVzMSA8LSBSZXRyaWV2ZUVudHJleklEKAogICAgSG9tb2xvZ3Vlcz1UUlVFLCBIb21vX3NwZWNpZXM9SG9tb19zcGVjaWVzWzFdLCBQYXJfc3BlY2llcz1QYXJfc3BlY2llcywKICAgIFBhcl9zcGVjaWVzLlVuaXByb3Q9UGFyX3NwZWNpZXMuVW5pcHJvdCwgR2VuZXNMcz1HZW5lc0xzLCB1cGRhdGVQcm9ncmVzcyA9IHVwZGF0ZVByb2dyZXNzCiAgKQogIGhvbW9sb2dldXMyIDwtIFJldHJpZXZlRW50cmV6SUQoCiAgICBIb21vbG9ndWVzPVRSVUUsIEhvbW9fc3BlY2llcz1Ib21vX3NwZWNpZXNbMl0sIFBhcl9zcGVjaWVzPVBhcl9zcGVjaWVzLAogICAgUGFyX3NwZWNpZXMuVW5pcHJvdD1QYXJfc3BlY2llcy5Vbmlwcm90LCBHZW5lc0xzPUdlbmVzTHMsIHVwZGF0ZVByb2dyZXNzID0gdXBkYXRlUHJvZ3Jlc3MKICApCiAgaG9tb2xvZ2V1czMgPC0gUmV0cmlldmVFbnRyZXpJRCgKICAgIEhvbW9sb2d1ZXM9VFJVRSwgSG9tb19zcGVjaWVzPUhvbW9fc3BlY2llc1szXSwgUGFyX3NwZWNpZXM9UGFyX3NwZWNpZXMsCiAgICBQYXJfc3BlY2llcy5Vbmlwcm90PVBhcl9zcGVjaWVzLlVuaXByb3QsIEdlbmVzTHM9R2VuZXNMcywgdXBkYXRlUHJvZ3Jlc3MgPSB1cGRhdGVQcm9ncmVzcwogICkKICAKICAjQ2FsY3VsYXRlIHJlc3VsdHMgYW5kIGNvbnN0cnVjdCB0aGUgbGl0ZXJhdHVyZSBtaW5pbmcgdGFibGUKICBsaXRlcmF0dXJlLm1pbmluZzwtZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IDAsIG5jb2w9MTQpKQogIGNvbG5hbWVzKGxpdGVyYXR1cmUubWluaW5nKTwtYygiZW5zZW1ibF9nZW5lX2lkIiwiZW50cmV6Z2VuZV9pZCIsIkdlbmVTeW1ib2wiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ2VuZV9iaW90eXBlIiwgIk4iLCAiSyIsICJuIiwgImsiLCAiUiIsICIxMHggQmFja2dyb3VuZCBvZiBSIiwiUC52YWx1ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHZW5lIHJlbGF0ZWQgUE1JRCIsICJPdmVybGFwcGVkIFBNSUQiLCAiT3ZlcmxhcHBlZCBHZW5lcyBbQXJ0aWNsZSBOb10iKQogICMgUGFyX3BiIDwtIHR4dFByb2dyZXNzQmFyKG1pbj0xLCBtYXg9bnJvdyhxdWVyeS5yZXMpLCBzdHlsZSA9IDMpCiAgcHJpbnQoIlN0YXJ0TG9vcCIpCiAgbGlzdC5wYWNrYWdlcyA8LSBjKAogICAgInJlYWR4bCIsICJnZ3Bsb3QyIiwgImdncHViciIsICJtYXRyaXhTdGF0cyIsICJnZ3JlcGVsIiwgInJlc2hhcGUyIiwgImRwbHlyIiwgInN0cmluZ3IiLAogICAgImdyaWQiLCAidGNsdGsiLCAicGFyYWxsZWwiLCAiZG9QYXJhbGxlbCIsICJkb1NOT1ciLCAiZGF0YS50YWJsZSIsICJncGxvdHMiLAogICAgInJhbmRvbWNvbG9SIiwgImZhY3RvZXh0cmEiLCAiUkNvbG9yQnJld2VyIiwgImdyRGV2aWNlcyIsICJnbXAiLCAieHRhYmxlIiwgImxhdGV4MmV4cCIsCiAgICAiaHR0ciIsICJqc29ubGl0ZSIsICJjdXJsIiwgIlJDdXJsIiwgIm1hZ3JpdHRyIiwgInJsaXN0IiwgInBpcGVSIiwgInBseXIiLAogICAgInhtbDIiLCAicnZlc3QiLCAia25uLmNvdmVydHJlZSIsICJrbml0ciIsICJybGFuZyIsICJ2aXNOZXR3b3JrIiwgImh3cml0ZXIiLCAiaHRtbHRvb2xzIgogICkKICBiaW8ubGlzdC5wYWNrYWdlcyA8LSBjKAogICAgImxpbW1hIiwgIkV4cGVyaW1lbnRIdWIiLCAiY2x1c3RlclByb2ZpbGVyIiwgIkdPLmRiIiwKICAgICJvcmcuTW0uZWcuZGIiLCAicGF0aHZpZXciLCAiZW5yaWNocGxvdCIsICJvcmcuSHMuZWcuZGIiCiAgKQogIHd6eV9tYWlubG9vcCA8LSBmdW5jdGlvbigKICAgIHF1ZXJ5LnJlcyA9IHF1ZXJ5LnJlcywgYXR0ZW1wdF9tYXggPSAzLCBUaHJlc2ggPSBUaHJlc2gsCiAgICBLID0gSywgTiA9IE4sIGkgPSBpCiAgKXsKICAgIEdlbmVJRDwtYygKICAgICAgcXVlcnkucmVzJGVudHJlemdlbmVfaWRbaV0sCiAgICAgIGhvbW9sb2dldXMxW3F1ZXJ5LnJlcyRlbnNlbWJsX2dlbmVfaWRbaV0sImVudHJlemdlbmVfaWQiXSwKICAgICAgaG9tb2xvZ2V1czJbcXVlcnkucmVzJGVuc2VtYmxfZ2VuZV9pZFtpXSwiZW50cmV6Z2VuZV9pZCJdLAogICAgICBob21vbG9nZXVzM1txdWVyeS5yZXMkZW5zZW1ibF9nZW5lX2lkW2ldLCJlbnRyZXpnZW5lX2lkIl0KICAgICkKICAgICMjIyMgQ2FsY3VsYXRlIG4KICAgICMgR2V0IFVJRCBudW1iZXIgdGhhdCB3aXRoaW4gaW50ZXJlc3RlZCBNZVNIIHRlcm0KICAgIHVpZC5yZXM8LWMoKQogICAgZm9yKGEgaW4gR2VuZUlEKXsKICAgICAgIyBSZXRyaWV2ZSBQTUlEIG9mIGdlbmUtcmVsYXRlZCBhcnRpY2xlcwogICAgICAjIGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvYm9va3MvTkJLMjU0OTcvdGFibGUvY2hhcHRlcjIuVC5fZW50cmV6X3VuaXF1ZV9pZGVudGlmaWVyc191aS8/cmVwb3J0PW9iamVjdG9ubHkKICAgICAgIyBodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2Jvb2tzL05CSzI1NDk5LwogICAgICAjIGh0dHBzOi8vZGF0YWd1aWRlLm5sbS5uaWguZ292L2V1dGlsaXRpZXMvdXRpbGl0aWVzLmh0bWwKICAgICAgIyBodHRwczovL2V1dGlscy5uY2JpLm5sbS5uaWguZ292L2VudHJlei9ldXRpbHMvZWxpbmsuZmNnaT9kYmZyb209Z2VuZSZkYj1wdWJtZWQmaWQ9NjgzODkmbGlua25hbWU9aG9tb2xvZ2VuZV9wdWJtZWQKICAgICAgIyBodHRwczovL2V1dGlscy5uY2JpLm5sbS5uaWguZ292L2VudHJlei9ldXRpbHMvZWxpbmsuZmNnaT9kYmZyb209Z2VuZSZkYj1wdWJtZWQmaWQ9MTIzOTMKICAgICAgVVJMX3RlbXA8LU5VTEwKICAgICAgYXR0ZW1wdDwtMQogICAgICB1cmwgPC0gcGFzdGUwKAogICAgICAgICJodHRwczovL2V1dGlscy5uY2JpLm5sbS5uaWguZ292L2VudHJlei9ldXRpbHMvZWxpbmsuZmNnaT9kYmZyb209Z2VuZSZkYj1wdWJtZWQmaWQ9IiwKICAgICAgICBhCiAgICAgICkKICAgICAgd2hpbGUoaXMubnVsbChVUkxfdGVtcCkmJmF0dGVtcHQ8YXR0ZW1wdF9tYXgpewogICAgICAgIHRyeSh7CiAgICAgICAgICB1cmwgPC0gdXJsKHVybCwgInJiIikKICAgICAgICAgIGlmKGNsYXNzKHVybClbMV09PSJ1cmwiKXsKICAgICAgICAgICAgVVJMX3RlbXAgPC0gcmVhZF94bWwoCiAgICAgICAgICAgICAgdXJsCiAgICAgICAgICAgICklPiV4bWxfZmluZF9hbGwoLiwgIi4vL0lkIiklPiV4bWxfdGV4dCgpCiAgICAgICAgICAgIGNsb3NlKHVybCkKICAgICAgICAgIH0KICAgICAgICB9LCBzaWxlbnQgPSBUUlVFKQogICAgICAgIGF0dGVtcHQ8LWF0dGVtcHQrMQogICAgICAgIGlmKGlzLm51bGwoVVJMX3RlbXApKXtTeXMuc2xlZXAocm91bmQocnVuaWYoMSwxMCwxODApLDEpKX0KICAgICAgfQogICAgICB1aWQucmVzPC1jKAogICAgICAgIHVpZC5yZXMsCiAgICAgICAgVVJMX3RlbXAKICAgICAgKQogICAgfQogICAgdWlkLnJlczwtdW5pcXVlKHVpZC5yZXMpCiAgICBuPC1sZW5ndGgodWlkLnJlcykKICAgICMjIyMgQ2FsY3VsYXRlIGsKICAgIG1heGxpbWl0PC0xMjUKICAgIGlmKG4+bWF4bGltaXQpewogICAgICBzdGVwPC1uJS8lbWF4bGltaXQKICAgICAgbW9kPC1uJSVtYXhsaW1pdAogICAgICBzdGFydDwtc2VxKGZyb209MSwgdG89bWF4bGltaXQqc3RlcCwgYnk9bWF4bGltaXQpCiAgICAgIGVuZDwtc2VxKGZyb209bWF4bGltaXQsIHRvPW1heGxpbWl0KnN0ZXAsIGJ5PW1heGxpbWl0KQogICAgICBpZihtb2Q+MCl7CiAgICAgICAgc3RhcnQ8LWMoc3RhcnQsIHRhaWwoZW5kLCAxKSsxKQogICAgICAgIGVuZDwtYyhlbmQsIG4pCiAgICAgIH0KICAgICAgc3RlcDwtbGVuZ3RoKHN0YXJ0KQogICAgfWVsc2V7CiAgICAgIHN0ZXA8LTEKICAgICAgc3RhcnQ8LTEKICAgICAgZW5kPC1uCiAgICB9CiAgICBrPC0wCiAgICBvdmVybGFwcGVkLnVpZDwtYygpCiAgICBmb3IoYSBpbiAxOnN0ZXApewogICAgICBVUkxfdGVtcDwtTlVMTAogICAgICBhdHRlbXB0PC0xCiAgICAgIHVybCA8LSBVUkxlbmNvZGUoCiAgICAgICAgcGFzdGUwKAogICAgICAgICAgImh0dHBzOi8vZXV0aWxzLm5jYmkubmxtLm5paC5nb3YvZW50cmV6L2V1dGlscy9lc2VhcmNoLmZjZ2k/ZGI9cHVibWVkJnRlcm09IiwKICAgICAgICAgIHBhc3RlKHVpZC5yZXNbc3RhcnRbYV06ZW5kW2FdXSxjb2xsYXBzZT0iLCIpLCJbVUlEXStBTkQrKCIsVVJMZW5jb2RlKE1lc2hfdGVybTRJbnRlcmVzdGluZyksIikiLAogICAgICAgICAgIiZyZXR0eXBlPWNvdW50IgogICAgICAgICkKICAgICAgKQogICAgICB3aGlsZShpcy5udWxsKFVSTF90ZW1wKSYmYXR0ZW1wdDxhdHRlbXB0X21heCl7CiAgICAgICAgdHJ5KHsKICAgICAgICAgIHVybCA8LSB1cmwodXJsLCAicmIiKQogICAgICAgICAgaWYoY2xhc3ModXJsKVsxXT09InVybCIpewogICAgICAgICAgICBVUkxfdGVtcCA8LSByZWFkX3htbCgjdGhlIGFtb3VudHMgb2YgYXJ0aWNsZXMgb2YgZWFjaCBnZW5lIG9uIGludGVyZXN0ZWQgTWVTSCB0ZXJtOgogICAgICAgICAgICAgIHVybAogICAgICAgICAgICApJT4leG1sX2ZpbmRfYWxsKC4sICIuLy9Db3VudCIpJT4leG1sX3RleHQoKSU+JS5bMV0lPiVhcy5udW1lcmljKCkKICAgICAgICAgICAgY2xvc2UodXJsKQogICAgICAgICAgfQogICAgICAgIH0sIHNpbGVudCA9IFRSVUUpCiAgICAgICAgYXR0ZW1wdDwtYXR0ZW1wdCsxCiAgICAgICAgaWYoaXMubnVsbChVUkxfdGVtcCkpe1N5cy5zbGVlcChyb3VuZChydW5pZigxLDEwLDE4MCksMSkpfQogICAgICB9CiAgICAgIGlmKGlzLm51bGwoVVJMX3RlbXApKXtVUkxfdGVtcCA8LSAwfQogICAgICBrPC1rK1VSTF90ZW1wCiAgICAgIFVSTF90ZW1wPC1OVUxMCiAgICAgIGF0dGVtcHQ8LTEKICAgICAgdXJsIDwtIFVSTGVuY29kZSgKICAgICAgICBwYXN0ZTAoCiAgICAgICAgICAiaHR0cHM6Ly9ldXRpbHMubmNiaS5ubG0ubmloLmdvdi9lbnRyZXovZXV0aWxzL2VzZWFyY2guZmNnaT9kYj1wdWJtZWQmdGVybT0iLAogICAgICAgICAgcGFzdGUodWlkLnJlc1tzdGFydFthXTplbmRbYV1dLGNvbGxhcHNlPSIsIiksIltVSURdK0FORCsoIixVUkxlbmNvZGUoTWVzaF90ZXJtNEludGVyZXN0aW5nKSwiKSIsCiAgICAgICAgICAiJnJldHR5cGU9dW5saXN0IgogICAgICAgICkKICAgICAgKQogICAgICB3aGlsZShpcy5udWxsKFVSTF90ZW1wKSYmYXR0ZW1wdDxhdHRlbXB0X21heCl7CiAgICAgICAgdHJ5KHsKICAgICAgICAgIHVybCA8LSB1cmwodXJsLCAicmIiKQogICAgICAgICAgaWYoY2xhc3ModXJsKVsxXT09InVybCIpewogICAgICAgICAgICBVUkxfdGVtcCA8LSByZWFkX3htbCgjdGhlIGFtb3VudHMgb2YgYXJ0aWNsZXMgb2YgZWFjaCBnZW5lIG9uIGludGVyZXN0ZWQgTWVTSCB0ZXJtOgogICAgICAgICAgICAgIHVybAogICAgICAgICAgICApJT4leG1sX2ZpbmRfYWxsKC4sICIuLy9JZCIpJT4leG1sX3RleHQoKQogICAgICAgICAgICBjbG9zZSh1cmwpCiAgICAgICAgICB9CiAgICAgICAgfSwgc2lsZW50ID0gVFJVRSkKICAgICAgICBhdHRlbXB0PC1hdHRlbXB0KzEKICAgICAgICBpZihpcy5udWxsKFVSTF90ZW1wKSl7U3lzLnNsZWVwKHJvdW5kKHJ1bmlmKDEsMTAsMTgwKSwxKSl9CiAgICAgIH0KICAgICAgb3ZlcmxhcHBlZC51aWQ8LWMoCiAgICAgICAgb3ZlcmxhcHBlZC51aWQsCiAgICAgICAgVVJMX3RlbXAKICAgICAgKQogICAgfQogICAgaWYoaXMubmEoaykpe2s8LTB9CiAgICAjIyMjIENhbGN1bGF0ZSBQCiAgICBSPC1rL24KICAgIFA8LTAKICAgIGlmKGs8MSl7CiAgICAgIFA8LTAKICAgIH1lbHNlewogICAgICBmb3IgKGEgaW4gMDooay0xKSkgewogICAgICAgIFA8LVArKChjaG9vc2VaKEssYSkqY2hvb3NlWihOLUssbi1hKSkvY2hvb3NlWihOLG4pKQogICAgICB9CiAgICB9CiAgICBQPC1hc051bWVyaWMoMS1QKQogICAgaWYoUD09MCl7UDwtMSoxMF4tMzAwfQogICAgdGVtcDwtZGF0YS5mcmFtZSgKICAgICAgZW5zZW1ibF9nZW5lX2lkPXF1ZXJ5LnJlcyRlbnNlbWJsX2dlbmVfaWRbaV0sCiAgICAgIGVudHJlemdlbmVfaWQ9cXVlcnkucmVzJGVudHJlemdlbmVfaWRbaV0sCiAgICAgIEdlbmVTeW1ib2w9cXVlcnkucmVzJGV4dGVybmFsX2dlbmVfbmFtZVtpXSwKICAgICAgZ2VuZV9iaW90eXBlPXF1ZXJ5LnJlcyRnZW5lX2Jpb3R5cGVbaV0sCiAgICAgIE49TixLPUssbj1uLGs9ayxSPVIsVGhyZXNoPVRocmVzaCxQPVAsCiAgICAgIEdlbmVQTUlEPXVpZC5yZXMlPiVwYXN0ZSguLGNvbGxhcHNlID0gIiwiKSwKICAgICAgT3ZlcmxhcFBNSUQ9b3ZlcmxhcHBlZC51aWQlPiVwYXN0ZSguLGNvbGxhcHNlID0gIiwiKQogICAgKQogICAgY29sbmFtZXModGVtcCk8LWMoImVuc2VtYmxfZ2VuZV9pZCIsImVudHJlemdlbmVfaWQiLCJHZW5lU3ltYm9sIiwKICAgICAgICAgICAgICAgICAgICAgICJnZW5lX2Jpb3R5cGUiLCAiTiIsICJLIiwgIm4iLCAiayIsICJSIiwgIjEweCBCYWNrZ3JvdW5kIG9mIFIiLCJQLnZhbHVlIiwKICAgICAgICAgICAgICAgICAgICAgICJHZW5lIHJlbGF0ZWQgUE1JRCIsICJPdmVybGFwcGVkIFBNSUQiKQogICAgcmV0dXJuKHRlbXApCiAgfQogIFBhcl9jbDwtbWFrZUNsdXN0ZXIocGFyYWxsZWxseTo6ZnJlZUNvbm5lY3Rpb25zKCktMikKICByZWdpc3RlckRvU05PVyhQYXJfY2wpCiAgUGFyX3BiIDwtIHR4dFByb2dyZXNzQmFyKG1pbj0xLCBtYXg9bnJvdyhxdWVyeS5yZXMpLCBzdHlsZSA9IDMpCiAgcHJvZ3Jlc3MgPC0gZnVuY3Rpb24obikgc2V0VHh0UHJvZ3Jlc3NCYXIoUGFyX3BiLCBuKQogIFBhcl9vcHRzPC1saXN0KHByb2dyZXNzID0gcHJvZ3Jlc3MpCiAgI1JlZmVyIGhlcmUgZm9yIGFsbCBjb2x1bW4gbmFtZXMgZm9yIEVudHJleiBBUEkgKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvYm9va3MvTkJLMjU1MDEvKQogIHRlbXA8LWZvcmVhY2goCiAgICBpPTE6bnJvdyhxdWVyeS5yZXMpLCAuY29tYmluZT1yYmluZCwgLmVycm9yaGFuZGxpbmcgPSAicmVtb3ZlIiwgLmlub3JkZXI9RkFMU0UsIC52ZXJib3NlID0gRkFMU0UsCiAgICAub3B0aW9ucy5zbm93PVBhcl9vcHRzLCAucGFja2FnZXMgPSBjKGxpc3QucGFja2FnZXMsIGJpby5saXN0LnBhY2thZ2VzKQogICkgJWRvcGFyJSB7CiAgICB0ZW1wMyA8LSBOVUxMCiAgICB0ZW1wMyA8LSB3enlfbWFpbmxvb3AocXVlcnkucmVzID0gcXVlcnkucmVzLCBhdHRlbXB0X21heCA9IDUsIFRocmVzaCA9IFRocmVzaCwgSyA9IEssIE4gPSBOLCBpID0gaSkKICAgIGlmKGlzLm51bGwodGVtcDMpKXsKICAgICAgdGVtcDM8LWRhdGEuZnJhbWUoCiAgICAgICAgZW5zZW1ibF9nZW5lX2lkPXF1ZXJ5LnJlcyRlbnNlbWJsX2dlbmVfaWRbaV0sCiAgICAgICAgZW50cmV6Z2VuZV9pZD1xdWVyeS5yZXMkZW50cmV6Z2VuZV9pZFtpXSwKICAgICAgICBHZW5lU3ltYm9sPXF1ZXJ5LnJlcyRleHRlcm5hbF9nZW5lX25hbWVbaV0sCiAgICAgICAgZ2VuZV9iaW90eXBlPXF1ZXJ5LnJlcyRnZW5lX2Jpb3R5cGVbaV0sCiAgICAgICAgTj1OQSxLPU5BLG49TkEsaz1OQSxSPU5BLFRocmVzaD1OQSxQPU5BLAogICAgICAgIEdlbmVQTUlEPU5BLAogICAgICAgIE92ZXJsYXBQTUlEPU5BCiAgICAgICkKICAgICAgY29sbmFtZXModGVtcDMpPC1jKCJlbnNlbWJsX2dlbmVfaWQiLCJlbnRyZXpnZW5lX2lkIiwiR2VuZVN5bWJvbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiZ2VuZV9iaW90eXBlIiwgIk4iLCAiSyIsICJuIiwgImsiLCAiUiIsICIxMHggQmFja2dyb3VuZCBvZiBSIiwiUC52YWx1ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAiR2VuZSByZWxhdGVkIFBNSUQiLCAiT3ZlcmxhcHBlZCBQTUlEIikKICAgIH0KICAgIHJldHVybih0ZW1wMykKICB9CiAgc3RvcENsdXN0ZXIoUGFyX2NsKQogIGxpdGVyYXR1cmUubWluaW5nPC1yYmluZChsaXRlcmF0dXJlLm1pbmluZyx0ZW1wKQogIHJtKHRlbXApCiAgZ2MoKQogIHByaW50KCJFbmRMb29wIikKICAjQ29ycmVjdGluZyBQLXZhbHVlIGZvciBtdWx0aXBsZSB0ZXN0aW5nCiAgbGl0ZXJhdHVyZS5taW5pbmc8LWNiaW5kKGxpdGVyYXR1cmUubWluaW5nWywxOjExXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHAuYWRqPXAuYWRqdXN0KGFzLm51bWVyaWMobGl0ZXJhdHVyZS5taW5pbmckUC52YWx1ZSksbWV0aG9kID0gIkJIIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpdGVyYXR1cmUubWluaW5nWywxMjoxM10pCiAgb3V0PC1saXN0KAogICAgbGl0ZXJhdHVyZS5taW5pbmc9bGl0ZXJhdHVyZS5taW5pbmcsCiAgICBxdWVyeS5yZXM9cXVlcnkucmVzLAogICAgaG9tb2xvZ2V1czE9aG9tb2xvZ2V1czEsCiAgICBob21vbG9nZXVzMj1ob21vbG9nZXVzMiwKICAgIGhvbW9sb2dldXMzPWhvbW9sb2dldXMzCiAgKQogIHByaW50KCJlbmQiKQogIHJldHVybihvdXQpCn0KCmxpdGVyYXR1cmVNaW5pbmdQbG90PC1mdW5jdGlvbigKICAgIGxpdGVyYXR1cmUubWluaW5nPW91dCRsaXRlcmF0dXJlLm1pbmluZywgVGhyZXNoLlI9TlVMTCwKICAgIHVzZS5QYWRqID0gRkFMU0UsIFRocmVzaC5QPTAuMDUsIGxhYmVsPVRSVUUsIGxhYmVsX249NQopewogICMgcHJpbnQoIlBsb3R0aW5nIikKICAjIFBsb3R0aW5nIFJlc3VsdHMKICBsaXRlcmF0dXJlLm1pbmluZyRuPC1hcy5udW1lcmljKGxpdGVyYXR1cmUubWluaW5nJG4pCiAgbGl0ZXJhdHVyZS5taW5pbmckazwtYXMubnVtZXJpYyhsaXRlcmF0dXJlLm1pbmluZyRrKQogIGxpdGVyYXR1cmUubWluaW5nJFI8LWFzLm51bWVyaWMobGl0ZXJhdHVyZS5taW5pbmckUikKICBpZihpcy5udWxsKFRocmVzaC5SKSl7CiAgICBUaHJlc2g8LWxpdGVyYXR1cmUubWluaW5nJGAxMHggQmFja2dyb3VuZCBvZiBSYFsxXQogIH1lbHNlewogICAgVGhyZXNoPC1UaHJlc2guUgogIH0KICBkZjwtbGl0ZXJhdHVyZS5taW5pbmdbLDE6MTJdIAogIGRmJFI8LWRmJFIqMTAwMAogIGRmJG48LWxvZzEwKGRmJG4pCiAgZGYkUjwtcHNldWRvTG9nMTAoZGYkUikKICBkZiRwLmFkajwtcHNldWRvTG9nMTAobG9nKDEvZGYkcC5hZGosMikpCiAgZGYkUC52YWx1ZTwtcHNldWRvTG9nMTAobG9nKDEvZGYkUC52YWx1ZSwyKSkKICBkZiA8LSBkZltvcmRlcihkZiRQLnZhbHVlKSwgXQogIG1heFI8LShtYXgoZGYkUiwgbmEucm09VCkrMC4zKSU+JXJvdW5kKC4sIGRpZ2l0cyA9IDEpCiAgbWF4UDwtKG1heChkZiRwLmFkaiwgbmEucm09VCkrMC4zKSU+JXJvdW5kKC4sIGRpZ2l0cyA9IDEpCiAgUmVwTF9SPC1sZW5ndGgocmVwKDA6bWF4UiwgZWFjaCA9IDkpKSUvJTkKICBSZXBMX1A8LWxlbmd0aChyZXAoMDptYXhQLCBlYWNoID0gOSkpJS8lOQogIGlmKHVzZS5QYWRqKXsKICAgIGdwbG90PC1nZ3Bsb3QoZGYsYWVzKHg9cC5hZGosIHk9UiwgY29sb3VyPW4sIHNpemU9ayApKQogIH1lbHNlewogICAgZ3Bsb3Q8LWdncGxvdChkZixhZXMoeD1QLnZhbHVlLCB5PVIsIGNvbG91cj1uLCBzaXplPWsgKSkKICB9CiAgZ3Bsb3Q8LWdwbG90ICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBleHBhbmRfbGltaXRzKHg9MCkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKAogICAgICBsaW1pdHMgPSBjKDAsbWF4UiksCiAgICAgIGJyZWFrcyA9IDA6bWF4UiwKICAgICAgZ3VpZGUgPSAicHJpc21fb2Zmc2V0X21pbm9yIiwKICAgICAgbWlub3JfYnJlYWtzID0gcHNldWRvTG9nMTAocmVwKDE6OSwgUmVwTF9SKSooMTBecmVwKDA6bWF4UiwgZWFjaCA9IDkpKSksCiAgICAgIGxhYmVscyA9ICBmdW5jdGlvbihsYWIpewogICAgICAgIGRvLmNhbGwoCiAgICAgICAgICBleHByZXNzaW9uLAogICAgICAgICAgbGFwcGx5KHBhc3RlKGxhYiksIGZ1bmN0aW9uKHgpIHsKICAgICAgICAgICAgaWYoeD09MCl7YnF1b3RlKGJvbGQoIjAiKSl9ZWxzZXticXVvdGUoYm9sZCgiMTAiXi4oeCkpKX0KICAgICAgICAgIH0pCiAgICAgICAgKQogICAgICB9CiAgICApKwogICAgc2NhbGVfeF9jb250aW51b3VzKAogICAgICBsaW1pdHMgPSBjKDAsbWF4UCksCiAgICAgIGJyZWFrcyA9IDA6bWF4UCwKICAgICAgZ3VpZGUgPSAicHJpc21fb2Zmc2V0X21pbm9yIiwKICAgICAgbWlub3JfYnJlYWtzID0gcHNldWRvTG9nMTAocmVwKDE6OSwgUmVwTF9QKSooMTBecmVwKDA6bWF4UCwgZWFjaCA9IDkpKSksCiAgICAgIGxhYmVscyA9IGZ1bmN0aW9uKGxhYil7CiAgICAgICAgZG8uY2FsbCgKICAgICAgICAgIGV4cHJlc3Npb24sCiAgICAgICAgICBsYXBwbHkocGFzdGUobGFiKSwgZnVuY3Rpb24oeCkgewogICAgICAgICAgICBpZih4PT0wKXticXVvdGUoYm9sZCgiMCIpKX1lbHNle2JxdW90ZShib2xkKCIxMCJeLih4KSkpfQogICAgICAgICAgfSkKICAgICAgICApCiAgICAgIH0KICAgICkrCiAgICBzY2FsZV9zaXplKHJhbmdlID0gYygwLDIwKSkrCiAgICB0aGVtZSgKICAgICAgYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwKICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNCxmYWNlPSJib2xkIiksCiAgICAgIGF4aXMudGlja3MubGVuZ3RoID0gdW5pdCg1LCJwdCIpLAogICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9saW5lKGxpbmV3aWR0aCA9IDAuNyksCiAgICApKwogICAgY29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikrCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9cHNldWRvTG9nMTAoVGhyZXNoKjEwMDApLCBsaW5ldHlwZT0iZGFzaGVkIiwgCiAgICAgICAgICAgICAgIGNvbG9yID0gInJlZCIsIHNpemU9MC40KSsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1wc2V1ZG9Mb2cxMChsb2coMS9UaHJlc2guUCwyKSksIGxpbmV0eXBlPSJkYXNoZWQiLCAKICAgICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgc2l6ZT0wLjQpKwogICAgbGFicyh4PSJsb2cyKDEvUC52YWx1ZSkiLCAKICAgICAgICAgeT0iUiAo4oCwKSIsIAogICAgICAgICB0aXRsZT0iIiwKICAgICAgICAgY29sb3VyPSJsb2cxMChHZW5lIFJlbGF0ZWQgQXJ0aWNsZXMgTm8uKSIsIHNpemU9IlRvcGljc1xuUmVsYXRlZCBBcnRpY2xlcyBOby4iKSsKICAgIGFubm90YXRpb25fY3VzdG9tKAogICAgICBncm9iID0gZ3JpZDo6bGluZXNHcm9iKGdwPWdwYXIobHdkPTIpKSwKICAgICAgeG1pbiA9IC1JbmYsCiAgICAgIHhtYXggPSAtSW5mLAogICAgICB5bWluID0gMCwKICAgICAgeW1heCA9IG1heFIKICAgICkrCiAgICBhbm5vdGF0aW9uX2N1c3RvbSgKICAgICAgZ3JvYiA9IGdyaWQ6OmxpbmVzR3JvYihncD1ncGFyKGx3ZD0yKSksCiAgICAgIHhtaW4gPSAwLAogICAgICB4bWF4ID0gbWF4UCwKICAgICAgeW1pbiA9IC1JbmYsCiAgICAgIHltYXggPSAtSW5mCiAgICApCiAgI3ByaW50KCJQbG90dGluZ0VuZCIpCiAgaWYobGFiZWxfbiA9PSAiQWxsIil7CiAgICBsYWJlbF9uIDwtIG5yb3coZGYpIAogIH0KICBpZihsYWJlbCl7CiAgICBpZih1c2UuUGFkail7CiAgICAgIGRmIDwtIGRmW2RmJFI+PXBzZXVkb0xvZzEwKFRocmVzaCoxMDAwKSAmIGRmJHAuYWRqPj1wc2V1ZG9Mb2cxMChsb2coMS9UaHJlc2guUCwyKSksIF0KICAgIH1lbHNlewogICAgICBkZiA8LSBkZltkZiRSPj1wc2V1ZG9Mb2cxMChUaHJlc2gqMTAwMCkgJiBkZiRQLnZhbHVlPj1wc2V1ZG9Mb2cxMChsb2coMS9UaHJlc2guUCwyKSksIF0KICAgIH0KICAgIGRmIDwtIGRmW29yZGVyKGRmJFAudmFsdWUsIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQogICAgZ3Bsb3QgPC0gZ3Bsb3QgKyBnZW9tX3RleHRfcmVwZWwoCiAgICAgIGRhdGEgPSBkZlsxOmxhYmVsX24sIF0sCiAgICAgIGFlcyhsYWJlbD1HZW5lU3ltYm9sKSxoanVzdD0wLCB2anVzdD0wLCBzaXplPTgsIGNvbG9yPSJkYXJrcmVkIgogICAgKQogIH0KICByZXR1cm4oZ3Bsb3QpCn0KCmxpdGVyYXR1cmVNaW5pbmdPdmVybGFwcGVkR2VuZXM8LWZ1bmN0aW9uKG91dCwgdXBkYXRlUHJvZ3Jlc3MgPSBOVUxMKXsKICAKICBsaXRlcmF0dXJlLm1pbmluZzwtb3V0JGxpdGVyYXR1cmUubWluaW5nCiAgcXVlcnkucmVzPC1vdXQkcXVlcnkucmVzCiAgaG9tb2xvZ2V1czE8LW91dCRob21vbG9nZXVzMQogIGhvbW9sb2dldXMyPC1vdXQkaG9tb2xvZ2V1czIKICBob21vbG9nZXVzMzwtb3V0JGhvbW9sb2dldXMzCiAgCiAgb3V0bHM8LWMoKQogIAogIGZvcihpIGluIDE6bnJvdyhxdWVyeS5yZXMpKXsKICAgIHByaW50KHBhc3RlMChpLCIvIixucm93KHF1ZXJ5LnJlcykpKQogICAgaWYgKGlzLmZ1bmN0aW9uKHVwZGF0ZVByb2dyZXNzKSkgewogICAgICB0ZXh0IDwtIHBhc3RlMChpLCIgaW4gIixucm93KHF1ZXJ5LnJlcykpCiAgICAgIHVwZGF0ZVByb2dyZXNzKG1lc3NhZ2UgPSB0ZXh0LCBkZXRhaWwgPSB0ZXh0KQogICAgfQogICAgR2VuZUlEPC1jKAogICAgICBxdWVyeS5yZXMkZW50cmV6Z2VuZV9pZFtpXSwKICAgICAgaG9tb2xvZ2V1czFbcXVlcnkucmVzJGVuc2VtYmxfZ2VuZV9pZFtpXSwiZW50cmV6Z2VuZV9pZCJdLAogICAgICBob21vbG9nZXVzMltxdWVyeS5yZXMkZW5zZW1ibF9nZW5lX2lkW2ldLCJlbnRyZXpnZW5lX2lkIl0sCiAgICAgIGhvbW9sb2dldXMzW3F1ZXJ5LnJlcyRlbnNlbWJsX2dlbmVfaWRbaV0sImVudHJlemdlbmVfaWQiXQogICAgKSU+JS5bIWlzLm5hKC4pXQogICAgCiAgICBvdmVybGFwcGVkLnVpZDwtbGl0ZXJhdHVyZS5taW5pbmckYE92ZXJsYXBwZWQgUE1JRGBbaV0lPiVzdHJfc3BsaXQoLiwiLCIpJT4ldW5saXN0KCkKICAgIAogICAgb3ZlcmxhcHBlZC5nZW5lczwtZGF0YS5mcmFtZShtYXRyaXgobnJvdyA9IDAsIG5jb2w9MykpCiAgICBjb2xuYW1lcyhvdmVybGFwcGVkLmdlbmVzKTwtYygiR2VuZVN5bWJvbCIsImVudHJlemdlbmVfaWQiLCJPdmVybGFwcGVkX1BNSUQiKQogICAgaWYob3ZlcmxhcHBlZC51aWRbMV0hPSIiKXsKICAgICAgCiAgICAgIGZvcih1aWQgaW4gb3ZlcmxhcHBlZC51aWQpewogICAgICAgIAogICAgICAgIHByaW50KHBhc3RlMCgiUE1JRDoiLCB1aWQsICIgKCIsIHdoaWNoKG92ZXJsYXBwZWQudWlkPT11aWQpLCIgb2YgIixsZW5ndGgob3ZlcmxhcHBlZC51aWQpLCAiKSIpKQogICAgICAgIHRlbXAuZ2VuZTwtcmVhZF94bWwoCiAgICAgICAgICBwYXN0ZTAoCiAgICAgICAgICAgICJodHRwczovL2V1dGlscy5uY2JpLm5sbS5uaWguZ292L2VudHJlei9ldXRpbHMvZWxpbmsuZmNnaT9kYmZyb209cHVibWVkJmRiPWdlbmUmaWQ9IiwKICAgICAgICAgICAgdWlkCiAgICAgICAgICApCiAgICAgICAgKSU+JXhtbF9maW5kX2FsbCguLCAiLi8vSWQiKSU+JXhtbF90ZXh0KCklPiUuWy0xXSU+JXVuaXF1ZSgpJT4lLlshLiVpbiVHZW5lSURdCiAgICAgICAgCiAgICAgICAgaWYoaXNfZW1wdHkodGVtcC5nZW5lKSl7bmV4dH0KICAgICAgICBpZihsZW5ndGgodGVtcC5nZW5lKT41MDApe25leHR9CiAgICAgICAgCiAgICAgICAgYS5uPC1sZW5ndGgodGVtcC5nZW5lKQogICAgICAgIG1heGxpbWl0PC01MAogICAgICAgIGlmKGEubj5tYXhsaW1pdCl7CiAgICAgICAgICBzdGVwPC1hLm4lLyVtYXhsaW1pdAogICAgICAgICAgbW9kPC1hLm4lJW1heGxpbWl0CiAgICAgICAgICBzdGFydDwtc2VxKGZyb209MSwgdG89bWF4bGltaXQqc3RlcCwgYnk9bWF4bGltaXQpCiAgICAgICAgICBlbmQ8LXNlcShmcm9tPW1heGxpbWl0LCB0bz1tYXhsaW1pdCpzdGVwLCBieT1tYXhsaW1pdCkKICAgICAgICAgIGlmKG1vZD4wKXsKICAgICAgICAgICAgc3RhcnQ8LWMoc3RhcnQsIHRhaWwoZW5kLCAxKSsxKQogICAgICAgICAgICBlbmQ8LWMoZW5kLCBhLm4pCiAgICAgICAgICB9CiAgICAgICAgICBzdGVwPC1sZW5ndGgoc3RhcnQpCiAgICAgICAgfWVsc2V7CiAgICAgICAgICBzdGVwPC0xCiAgICAgICAgICBzdGFydDwtMQogICAgICAgICAgZW5kPC1hLm4KICAgICAgICB9CiAgICAgICAgCiAgICAgICAgaWYoc3RlcD4xKXsKICAgICAgICAgIHBiIDwtIHR4dFByb2dyZXNzQmFyKG1pbj0xLCBtYXg9c3RlcCwgc3R5bGUgPSAzKQogICAgICAgIH0KICAgICAgICAKICAgICAgICByZW1vdmVJZHg8LWMoKQogICAgICAgIEdlbmVTeW1ib2w8LWMoKQogICAgICAgIGZvcihhIGluIDE6c3RlcCl7CiAgICAgICAgICAjcHJpbnQocGFzdGUwKCJiOiIsd2hpY2godGVtcC5nZW5lPT1iKSwiOyIsbGVuZ3RoKHRlbXAuZ2VuZSkpKQogICAgICAgICAgR1NCPC1yZWFkX2h0bWwoCiAgICAgICAgICAgIHBhc3RlMCgKICAgICAgICAgICAgICAiaHR0cHM6Ly9ldXRpbHMubmNiaS5ubG0ubmloLmdvdi9lbnRyZXovZXV0aWxzL2VmZXRjaC5mY2dpP2RiPWdlbmUmaWQ9IiwKICAgICAgICAgICAgICB0ZW1wLmdlbmVbc3RhcnRbYV06ZW5kW2FdXSU+JXBhc3RlKC4sY29sbGFwc2UgPSAiLCIpLAogICAgICAgICAgICAgICImcmV0dHlwZT11bmxpc3QiCiAgICAgICAgICAgICkKICAgICAgICAgICkKICAgICAgICAgIGxoPC0oZW5kW2FdLXN0YXJ0W2FdKzEpCiAgICAgICAgICBpZihsaD09MSl7CiAgICAgICAgICAgIHRlc3Q8LXhtbF9jaGlsZCh4bWxfY2hpbGQoeG1sX2NoaWxkKHhtbF9jaGlsZCh4bWxfY2hpbGQoR1NCLCAxKSwgMSksIDEpLCAxKSwgMiklPiV4bWxfdGV4dCgpJT4lZ3JlcCgiT2ZmaWNpYWwgU3ltYm9sOiIsLikKICAgICAgICAgICAgaWYoIWlzX2VtcHR5KHRlc3QpKXsKICAgICAgICAgICAgICBHZW5lU3ltYm9sPC1jKAogICAgICAgICAgICAgICAgR2VuZVN5bWJvbCwKICAgICAgICAgICAgICAgIHhtbF9jaGlsZCh4bWxfY2hpbGQoeG1sX2NoaWxkKHhtbF9jaGlsZCh4bWxfY2hpbGQoeG1sX2NoaWxkKHhtbF9jaGlsZChHU0IsIDEpLCAxKSwgMSksIDEpLCAyKSwgMiksIDIpJT4leG1sX3RleHQoKQogICAgICAgICAgICAgICkKICAgICAgICAgICAgfWVsc2V7CiAgICAgICAgICAgICAgcmVtb3ZlSWR4PC1jKAogICAgICAgICAgICAgICAgcmVtb3ZlSWR4LDEKICAgICAgICAgICAgICApCiAgICAgICAgICAgIH0KICAgICAgICAgIH1lbHNlewogICAgICAgICAgICBmb3IoYiBpbiAxOmxoKXsKICAgICAgICAgICAgICB0ZXN0PC14bWxfY2hpbGQoeG1sX2NoaWxkKHhtbF9jaGlsZCh4bWxfY2hpbGQoeG1sX2NoaWxkKEdTQiwgMSksIDEpLCAxKSwgYiksIDIpJT4leG1sX3RleHQoKSU+JWdyZXAoIk9mZmljaWFsIFN5bWJvbDoiLC4pCiAgICAgICAgICAgICAgaWYoIWlzX2VtcHR5KHRlc3QpKXsKICAgICAgICAgICAgICAgIEdlbmVTeW1ib2w8LWMoCiAgICAgICAgICAgICAgICAgIEdlbmVTeW1ib2wsCiAgICAgICAgICAgICAgICAgIHhtbF9jaGlsZCh4bWxfY2hpbGQoeG1sX2NoaWxkKHhtbF9jaGlsZCh4bWxfY2hpbGQoeG1sX2NoaWxkKHhtbF9jaGlsZCh4bWxfY2hpbGQoR1NCLCAxKSwgMSksIDEpLCBiKSwgMiksIDIpLCAyKSwgMiklPiV4bWxfdGV4dCgpCiAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgfWVsc2V7CiAgICAgICAgICAgICAgICByZW1vdmVJZHg8LWMoCiAgICAgICAgICAgICAgICAgIHJlbW92ZUlkeCwoYitzdGFydFthXS0xKQogICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgfQogICAgICAgICAgCiAgICAgICAgICBpZihzdGVwPjEpewogICAgICAgICAgICBzZXRUeHRQcm9ncmVzc0JhcihwYiwgYSkKICAgICAgICAgIH0KICAgICAgICAgIGlmKGlzLmZ1bmN0aW9uKHVwZGF0ZVByb2dyZXNzKSkgewogICAgICAgICAgICB0ZXh0IDwtIHBhc3RlMChhLCIgaW4gIixzdGVwKQogICAgICAgICAgICB1cGRhdGVQcm9ncmVzcyhtZXNzYWdlID0gcGFzdGUwKGksIiBpbiAiLG5yb3cocXVlcnkucmVzKSksIGRldGFpbCA9IHRleHQpCiAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIGlmKHN0ZXA+MSl7Y2xvc2UocGIpfQogICAgICAgIGlmKCFpc19lbXB0eShyZW1vdmVJZHgpKXsKICAgICAgICAgIHRlbXAuZ2VuZTwtdGVtcC5nZW5lWy1yZW1vdmVJZHhdCiAgICAgICAgfQogICAgICAgIAogICAgICAgIGlmKCFpc19lbXB0eSh0ZW1wLmdlbmUpKXsKICAgICAgICAgIHRlbXA8LWRhdGEuZnJhbWUoCiAgICAgICAgICAgIEdlbmVTeW1ib2w9R2VuZVN5bWJvbCU+JXN0cl90b190aXRsZSgpLAogICAgICAgICAgICBlbnRyZXpnZW5lX2lkPXRlbXAuZ2VuZSwKICAgICAgICAgICAgT3ZlcmxhcHBlZF9QTUlEPXJlcCh1aWQsbGVuZ3RoKHRlbXAuZ2VuZSkpCiAgICAgICAgICApCiAgICAgICAgICBvdmVybGFwcGVkLmdlbmVzPC1yYmluZCgKICAgICAgICAgICAgb3ZlcmxhcHBlZC5nZW5lcywKICAgICAgICAgICAgdGVtcAogICAgICAgICAgKQogICAgICAgIH0KICAgICAgfQogICAgfQogICAgCiAgICB0ZW1wPC1jKCkKICAgIE9yZGVySUQ8LWMoKQogICAgZm9yKGEgaW4gb3ZlcmxhcHBlZC5nZW5lcyRHZW5lU3ltYm9sJT4ldW5pcXVlKCkpewogICAgICB0ZW1wMjwtb3ZlcmxhcHBlZC5nZW5lc1tvdmVybGFwcGVkLmdlbmVzJEdlbmVTeW1ib2w9PWEsXQogICAgICBPcmRlcklEPC1jKAogICAgICAgIE9yZGVySUQsCiAgICAgICAgbGVuZ3RoKHRlbXAyJE92ZXJsYXBwZWRfUE1JRCU+JXVuaXF1ZSgpKQogICAgICApCiAgICAgIHRlbXA8LWMoCiAgICAgICAgdGVtcCwKICAgICAgICBwYXN0ZTAoCiAgICAgICAgICB0ZW1wMiRHZW5lU3ltYm9sWzFdLAogICAgICAgICAgIiAoRW50cmV6SUQ6IiwgdGVtcDIkZW50cmV6Z2VuZV9pZFsxXSwgIjsiLAogICAgICAgICAgIk92ZXJsYXBwZWRfUE1JRFsiLCB0YWlsKE9yZGVySUQsMSksICJdOiIsIHBhc3RlKHRlbXAyJE92ZXJsYXBwZWRfUE1JRCU+JXVuaXF1ZSgpLGNvbGxhcHNlID0gIiwiKSwiKSIgCiAgICAgICAgKQogICAgICApCiAgICB9CiAgICBPcmRlcklEPC1hcy5udW1lcmljKE9yZGVySUQpCiAgICBPcmRlcklEPC1vcmRlcih4PU9yZGVySUQsIGRlY3JlYXNpbmc9VFJVRSwgbmEubGFzdD1UKQogICAgb3ZlcmxhcHBlZC5nZW5lczwtdGVtcCU+JS5bT3JkZXJJRF0lPiVwYXN0ZSguLGNvbGxhcHNlID0gIiAiKQogICAgb3V0bHM8LWMoCiAgICAgIG91dGxzLG92ZXJsYXBwZWQuZ2VuZXMKICAgICkKICB9CiAgCiAgb3ZlcmxhcHBlZC5nZW5lczwtY2JpbmQoCiAgICBsaXRlcmF0dXJlLm1pbmluZ1ssMTo0XSwKICAgIG92ZXJsYXBwZWQuZ2VuZXM9b3V0bHMKICApCiAgCiAgcmV0dXJuKG92ZXJsYXBwZWQuZ2VuZXMpCiAgCn0KbGlicmFyeShybGFuZykKbGlicmFyeShnZ3ByaXNtKQpgYGAKCiMjIyMgUmVzdWx0cyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgpDYWxjdWF0ZSByZWxhdGl2aXR5IG9mIHRoZSBURnMgdG8gdGhlIE1lc2ggdGVybSAiT3N0ZW9ibGFzdHMiW01lc2hdIHVzaW5nIGEgbGl0ZXJhdHVyZSBtaW5pbmcgbWV0aG9kLgpgYGB7ciAwMi0wNC4yLmJ9ClRGcyA8LSBzZWxlY3Qob3JnLkhzLmVnLmRiLCBrZXlzID0gdW5pcXVlKFRGTGlua19yZWxhdGVkJE5hbWUuVEYpLCBjb2x1bW5zID0gYygnRU5TRU1CTCcpLCBrZXl0eXBlID0gIlNZTUJPTCIpCk1lc2hfdGVybTRJbnRlcmVzdGluZyA8LSAnIk9zdGVvYmxhc3RzIltNZXNoXScKR2VuZXNMcyA8LSBURnMkRU5TRU1CTFshaXMubmEoVEZzJEVOU0VNQkwpXQpTcGVjaWVzIDwtICJIc2EiICMgSHNhIE11cyBSbm8gU3NjClRGcy5NZXNoPC1XWllfbGl0ZXJhdHVyZU1pbmluZyhHZW5lc0xzPUdlbmVzTHMsIE1lc2hfdGVybTRJbnRlcmVzdGluZz1NZXNoX3Rlcm00SW50ZXJlc3RpbmcsIFNwZWNpZXM9U3BlY2llcywgdXBkYXRlUHJvZ3Jlc3MgPSBOVUxMKQpgYGAKCkNhbGN1YXRlIHJlbGF0aXZpdHkgb2YgdGhlIE1pdG9jaG9uZHJpYWwvQ3l0b3NrZWxldGFsIGdlbmVzIHRvIHRoZSBNZXNoIHRlcm0gIk9zdGVvYmxhc3RzIltNZXNoXSB1c2luZyBhIGxpdGVyYXR1cmUgbWluaW5nIG1ldGhvZC4KYGBge3IgMDItMDQuMi5jfQpNZXNoX3Rlcm00SW50ZXJlc3RpbmcgPC0gJyJPc3Rlb2JsYXN0cyJbTWVzaF0nCkdlbmVzTHMgPC0gQW5ub19HZW5lTHMkRW5zZW1ibElEWyFpcy5uYShBbm5vX0dlbmVMcyRFbnNlbWJsSUQpXQpTcGVjaWVzIDwtICJIc2EiICMgSHNhIE11cyBSbm8gU3NjCk1pdG9DeVNrLk1lc2g8LVdaWV9saXRlcmF0dXJlTWluaW5nKEdlbmVzTHM9R2VuZXNMcywgTWVzaF90ZXJtNEludGVyZXN0aW5nPU1lc2hfdGVybTRJbnRlcmVzdGluZywgU3BlY2llcz1TcGVjaWVzLCB1cGRhdGVQcm9ncmVzcyA9IE5VTEwpCmBgYAoKIyMjIyBQbG90cyB7LnRhYnNldCAudGFic2V0LWZhZGV9CgpgYGB7ciAwMi0wNC4yLmQsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04LCBvdXQud2lkdGg9IjEwMCUifQpncCA8LSBsaXRlcmF0dXJlTWluaW5nUGxvdCgKICBsaXRlcmF0dXJlLm1pbmluZz1URnMuTWVzaCRsaXRlcmF0dXJlLm1pbmluZywgVGhyZXNoLlI9MC4wMDIsCiAgdXNlLlBhZGogPSBGQUxTRSwgVGhyZXNoLlA9MC4wNSwgbGFiZWw9VFJVRSwgbGFiZWxfbiA9IDEwCikKc3ZnKGZpbGVuYW1lID0gZmlsZS5wYXRoKCIuIiwgIjAxLlBsb3RPdXQiLCAiTGl0ZXJhdHVyZU1pbmluZ1Jlc18wMS5zdmciKSwgd2lkdGggPSAxMiwgaGVpZ2h0ID0gOCwgcG9pbnRzaXplID0gMTIpCnByaW50KGdwKQpkZXYub2ZmKCkKZ3JhcGhpY3Mub2ZmKCkKa25pdHI6Om9wdHNfa25pdCRzZXQoZ2xvYmFsLmRldmljZSA9IFRSVUUpCnByaW50KGdwKQprbml0cjo6b3B0c19rbml0JHNldChnbG9iYWwuZGV2aWNlID0gRkFMU0UpCmBgYAoKYGBge3IgMDItMDQuMi5lLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OCwgb3V0LndpZHRoPSIxMDAlIn0KZ3AgPC0gbGl0ZXJhdHVyZU1pbmluZ1Bsb3QoCiAgbGl0ZXJhdHVyZS5taW5pbmc9TWl0b0N5U2suTWVzaCRsaXRlcmF0dXJlLm1pbmluZywgVGhyZXNoLlI9MC4wMDEsCiAgdXNlLlBhZGogPSBGQUxTRSwgVGhyZXNoLlA9MC4xNSwgbGFiZWw9VFJVRSwgbGFiZWxfbiA9IDEwCikKc3ZnKGZpbGVuYW1lID0gZmlsZS5wYXRoKCIuIiwgIjAxLlBsb3RPdXQiLCAiTGl0ZXJhdHVyZU1pbmluZ1Jlc18wMi5zdmciKSwgd2lkdGggPSAxMiwgaGVpZ2h0ID0gOCwgcG9pbnRzaXplID0gMTIpCnByaW50KGdwKQpkZXYub2ZmKCkKZ3JhcGhpY3Mub2ZmKCkKa25pdHI6Om9wdHNfa25pdCRzZXQoZ2xvYmFsLmRldmljZSA9IFRSVUUpCnByaW50KGdwKQprbml0cjo6b3B0c19rbml0JHNldChnbG9iYWwuZGV2aWNlID0gRkFMU0UpCmBgYAoKIyMgMDItMDUuIEZpbHRlciBmb3Igb3N0ZW9ibGFzdC1yZWxhdGVkIGdlbmVzCgpgYGB7ciAwMi0wNX0KVEZzLkNvcnJlbGF0ZWQgPC0gVEZzLk1lc2gkbGl0ZXJhdHVyZS5taW5pbmdbVEZzLk1lc2gkbGl0ZXJhdHVyZS5taW5pbmckUC52YWx1ZTwwLjA1LF0KVEZzLkNvcnJlbGF0ZWQgPC0gVEZzLkNvcnJlbGF0ZWRbVEZzLkNvcnJlbGF0ZWQkUiA+IDAuMDAyLF0KR2VuZUxzLkNvcnJlbGF0ZWQgPC0gTWl0b0N5U2suTWVzaCRsaXRlcmF0dXJlLm1pbmluZ1tNaXRvQ3lTay5NZXNoJGxpdGVyYXR1cmUubWluaW5nJFAudmFsdWU8MC4xNSxdCkdlbmVMcy5Db3JyZWxhdGVkIDwtIEdlbmVMcy5Db3JyZWxhdGVkW0dlbmVMcy5Db3JyZWxhdGVkJFIgPiAwLjAwMSxdCkdlbmUua2VlcCA8LSBURkxpbmtfcmVsYXRlZCROYW1lLlRGICU+JSB1bmlxdWUoKQpHZW5lLmtlZXAgPC0gR2VuZS5rZWVwW0dlbmUua2VlcCAlaW4lIFRGcy5Db3JyZWxhdGVkJEdlbmVTeW1ib2xdCkdlbmUua2VlcCA8LSBjKAogIEdlbmUua2VlcCwKICBHZW5lTHMuQ29ycmVsYXRlZCRHZW5lU3ltYm9sICU+JSB1bmlxdWUoKQopJT4ldW5pcXVlKCkKVEZMaW5rX3JlbGF0ZWRfZmlsdGVyIDwtIFRGTGlua19yZWxhdGVkW1RGTGlua19yZWxhdGVkJE5hbWUuVGFyZ2V0JWluJUdlbmUua2VlcCxdClRGTGlua19yZWxhdGVkX2ZpbHRlciA8LSBURkxpbmtfcmVsYXRlZF9maWx0ZXJbVEZMaW5rX3JlbGF0ZWRfZmlsdGVyJE5hbWUuVEYlaW4lR2VuZS5rZWVwLF0KZm9yIChpIGluIHVuaXF1ZShHZW5lTHMuQ29ycmVsYXRlZCRHZW5lU3ltYm9sKSkgewogIHRlbXA8LVRGTGlua19yZWxhdGVkX2ZpbHRlcltURkxpbmtfcmVsYXRlZF9maWx0ZXIkTmFtZS5UYXJnZXQ9PWksXQogIGlmKCFucm93KHRlbXApKSBuZXh0CiAgaWYoc3VtKHRlbXAkYFNtYWxsLXNjYWxlLmV2aWRlbmNlYD09IlllcyIpKXsKICAgIHRlbXAgPC0gdGVtcFt0ZW1wJGBTbWFsbC1zY2FsZS5ldmlkZW5jZWA9PSJZZXMiLF0KICB9ZWxzZXsKICAgIFB1Yk1lZC5ObyA8LSB0ZW1wJFB1Ym1lZElEJT4lc3RyX3NwbGl0KC4sIjsiKSU+JXNhcHBseSguLGxlbmd0aCkKICAgIHRlbXAgPC0gdGVtcFtQdWJNZWQuTm8+MSxdCiAgfQogIFRGTGlua19yZWxhdGVkX2ZpbHRlciA8LSByYmluZCgKICAgIHRlbXAsIFRGTGlua19yZWxhdGVkX2ZpbHRlcltURkxpbmtfcmVsYXRlZF9maWx0ZXIkTmFtZS5UYXJnZXQhPWksXQogICkKfQpmb3IgKGkgaW4gdW5pcXVlKFRGTGlua19yZWxhdGVkJE5hbWUuVEYpKSB7CiAgdGVtcDwtVEZMaW5rX3JlbGF0ZWRfZmlsdGVyW1RGTGlua19yZWxhdGVkX2ZpbHRlciROYW1lLlRhcmdldD09aSxdCiAgaWYoIW5yb3codGVtcCkpIG5leHQKICBpZihzdW0odGVtcCRgU21hbGwtc2NhbGUuZXZpZGVuY2VgPT0iWWVzIikpewogICAgdGVtcCA8LSB0ZW1wW3RlbXAkYFNtYWxsLXNjYWxlLmV2aWRlbmNlYD09IlllcyIsXQogIH1lbHNlewogICAgUHViTWVkLk5vIDwtIHRlbXAkUHVibWVkSUQlPiVzdHJfc3BsaXQoLiwiOyIpJT4lc2FwcGx5KC4sbGVuZ3RoKQogICAgdGVtcCA8LSB0ZW1wW1B1Yk1lZC5Obz4xLF0KICB9CiAgVEZMaW5rX3JlbGF0ZWRfZmlsdGVyIDwtIHJiaW5kKAogICAgdGVtcCwgVEZMaW5rX3JlbGF0ZWRfZmlsdGVyW1RGTGlua19yZWxhdGVkX2ZpbHRlciROYW1lLlRhcmdldCE9aSxdCiAgKQp9CiMgUHJlcGFyZSBub2RlcyB0YWJsZQojIEFkZCBURnMKVEZfbHMgPC0gVEZMaW5rX3JlbGF0ZWRfZmlsdGVyJFVuaXByb3RJRC5URiAlPiUgdW5pcXVlKCkKbm9kZXMgPC0gZGF0YS5mcmFtZSgKICBsYWJlbCA9IE5VTEwsCiAgZ3JvdXAgPSBOVUxMLAogIHZhbHVlID0gTlVMTCwgICAgICAgICAgCiAgc2hhcGUgPSBOVUxMLAogIHRpdGxlID0gTlVMTCwKICBjb2xvciA9IE5VTEwsCiAgc2hhZG93ID0gTlVMTAopCiMgTGVnZW5kOgojIFNpZyBURjogYmx1ZSBkb3QKIyBPLUdsY05BYyBURjogYmx1ZSBzcXVhcmUKIyBTaWcgYW5kIE8tR2xjTkFjIFRGOiBibHVlIHN0YXIKZm9yIChpIGluIFRGX2xzKSB7CiAgbGFiZWwgPC0gVEZMaW5rX3JlbGF0ZWQkTmFtZS5URltURkxpbmtfcmVsYXRlZCRVbmlwcm90SUQuVEY9PWldICU+JSB1bmlxdWUoKSAlPiUgYXMuY2hhcmFjdGVyKCkKICBncm91cCA8LSAiVEYiCiAgdmFsdWUgPC0gMwogIHRpdGxlIDwtIGkKICBzaGFkb3cgPC0gRkFMU0UKICBjb2xvciA8LSAiYmx1ZSIKICBpcy5ERUdzIDwtIFRGTGlua19yZWxhdGVkW1RGTGlua19yZWxhdGVkJFVuaXByb3RJRC5URj09aSwgImlzLkRFR3MiXSAlPiUgdW5pcXVlKCkgJT4lIGFzLmNoYXJhY3RlcigpCiAgT0dsY05BYyA8LSBURkxpbmtfcmVsYXRlZFtURkxpbmtfcmVsYXRlZCRVbmlwcm90SUQuVEY9PWksICJPR2xjTkFjIl0gJT4lIHVuaXF1ZSgpICU+JSBhcy5jaGFyYWN0ZXIoKQogIGlmKGlzLkRFR3MgPT0gIlllcyIgJiBPR2xjTkFjID09ICJZZXMiKXsKICAgIHNoYXBlIDwtICJzdGFyIgogIH1lbHNlIGlmIChpcy5ERUdzID09ICJZZXMiKSB7CiAgICBzaGFwZSA8LSAiZG90IgogIH1lbHNlIGlmIChPR2xjTkFjID09ICJZZXMiKSB7CiAgICBzaGFwZSA8LSAic3F1YXJlIgogIH0KICBFTlNFTUJMIDwtIEFubm9fR2VuZUxzJEVuc2VtYmxJRFtBbm5vX0dlbmVMcyRFbnRyeT09aV1bMV0KICBpZighaXMubmEoRU5TRU1CTCkpewogICAgRkVBX1JlbGF0ZWQgPC0gcmVzQGNvbXBhcmVDbHVzdGVyUmVzdWx0W2dyZXBsKEVOU0VNQkwsIHJlc0Bjb21wYXJlQ2x1c3RlclJlc3VsdCRnZW5lSUQpLF0KICAgIE1pdG8gPC0gZ3JlcGwoIm1pdG9jaG9uZHJpYShsKXswLDF9IiwgRkVBX1JlbGF0ZWQkRGVzY3JpcHRpb24sIGlnbm9yZS5jYXNlID0gVFJVRSkgJT4lIHN1bSgpICU+JSBhcy5sb2dpY2FsKCkKICAgIEN5U2sgPC0gZ3JlcGwoImN5dG9za2VsZSh0b258dGFsKSIsIEZFQV9SZWxhdGVkJERlc2NyaXB0aW9uLCBpZ25vcmUuY2FzZSA9IFRSVUUpICU+JSBzdW0oKSAlPiUgYXMubG9naWNhbCgpCiAgICBpZihNaXRvICYgQ3lTayl7CiAgICAgIGNvbG9yIDwtICJibGFjayIKICAgICAgdmFsdWUgPC0gMTAKICAgIH1lbHNlIGlmKE1pdG8pewogICAgICBjb2xvciA8LSAib3JhbmdlIgogICAgICB2YWx1ZSA8LSAxMAogICAgfWVsc2UgaWYoQ3lTayl7CiAgICAgIGNvbG9yIDwtICJyZWQiCiAgICAgIHZhbHVlIDwtIDEwCiAgICB9CiAgfQogIG5vZGVzIDwtIHJiaW5kKAogICAgbm9kZXMsCiAgICBkYXRhLmZyYW1lKAogICAgICBsYWJlbCA9IGxhYmVsLAogICAgICBncm91cCA9IGdyb3VwLAogICAgICB2YWx1ZSA9IHZhbHVlLCAgICAgICAgICAKICAgICAgc2hhcGUgPSBzaGFwZSwKICAgICAgdGl0bGUgPSB0aXRsZSwKICAgICAgY29sb3IgPSBjb2xvciwKICAgICAgc2hhZG93ID0gc2hhZG93CiAgICApCiAgKQp9CiMgQWRkIE1pdG9jaG9uZHJpYWwvQ3l0b3NrZWxldGFsIGdlbmVzCkdlbmVMcyA8LSBURkxpbmtfcmVsYXRlZF9maWx0ZXIkVW5pcHJvdElELlRhcmdldFshVEZMaW5rX3JlbGF0ZWRfZmlsdGVyJFVuaXByb3RJRC5UYXJnZXQgJWluJSBURl9sc10gJT4lIHVuaXF1ZSgpICU+JSBhcy5jaGFyYWN0ZXIoKQpHZW5lTHMgPC0gR2VuZUxzWyFHZW5lTHMlaW4lVEZMaW5rX3JlbGF0ZWRfZmlsdGVyJFVuaXByb3RJRC5URl0KIyBMZWdlbmQ6CiMgTWl0byBHZW5lOiBvcmFuZ2UgdHJpYW5nbGUKIyBDeXRvc2tlbGV0YWwgR2VuZTogcmVkIHRyaWFuZ2xlCiMgYm90aCBNaXRvIGFuZCBDeXRvc2tlbGV0YWwgZ2VuZTogYmxhY2sgZGlhbW9uZApmb3IgKGkgaW4gR2VuZUxzKSB7CiAgbGFiZWwgPC0gQW5ub19HZW5lTHMkYEdlbmUgTmFtZXMgKHByaW1hcnkpYFtBbm5vX0dlbmVMcyRFbnRyeT09aV1bMV0KICB0aXRsZSA8LSBpCiAgRU5TRU1CTCA8LSBBbm5vX0dlbmVMcyRFbnNlbWJsSURbQW5ub19HZW5lTHMkRW50cnk9PWldWzFdCiAgaWYoaXMubmEoRU5TRU1CTCkpe25leHR9CiAgaWYoaXMubmEobGFiZWwpKXsKICAgIGxhYmVsIDwtIEVOU0VNQkwKICB9CiAgRkVBX1JlbGF0ZWQgPC0gcmVzQGNvbXBhcmVDbHVzdGVyUmVzdWx0W2dyZXBsKEVOU0VNQkwscmVzQGNvbXBhcmVDbHVzdGVyUmVzdWx0JGdlbmVJRCksXQogIE1pdG8gPC0gZ3JlcGwoIm1pdG9jaG9uZHJpYShsKXswLDF9IiwgRkVBX1JlbGF0ZWQkRGVzY3JpcHRpb24sIGlnbm9yZS5jYXNlID0gVFJVRSkgJT4lIHN1bSgpICU+JSBhcy5sb2dpY2FsKCkKICBDeVNrIDwtIGdyZXBsKCJjeXRvc2tlbGUodG9ufHRhbCkiLCBGRUFfUmVsYXRlZCREZXNjcmlwdGlvbiwgaWdub3JlLmNhc2UgPSBUUlVFKSAlPiUgc3VtKCkgJT4lIGFzLmxvZ2ljYWwoKQogIGlmKE1pdG8gJiBDeVNrKXsKICAgIHNoYWRvdyA8LSBUUlVFCiAgICBncm91cCA8LSAiTWl0by9DeVNrIgogICAgdmFsdWUgPC0gMTAKICAgIGNvbG9yIDwtICJibGFjayIKICAgIHNoYXBlIDwtICJkaWFtb25kIgogIH1lbHNlIGlmKE1pdG8pewogICAgc2hhZG93IDwtIEZBTFNFCiAgICBncm91cCA8LSAiTWl0byIKICAgIHZhbHVlIDwtIDYKICAgIGNvbG9yIDwtICJvcmFuZ2UiCiAgICBzaGFwZSA8LSAidHJpYW5nbGUiCiAgfWVsc2UgaWYoQ3lTayl7CiAgICBzaGFkb3cgPC0gRkFMU0UKICAgIGdyb3VwIDwtICJDeVNrIgogICAgdmFsdWUgPC0gNgogICAgY29sb3IgPC0gInJlZCIKICAgIHNoYXBlIDwtICJ0cmlhbmdsZSIKICB9CiAgbm9kZXMgPC0gcmJpbmQoCiAgICBub2RlcywKICAgIGRhdGEuZnJhbWUoCiAgICAgIGxhYmVsID0gbGFiZWwsCiAgICAgIGdyb3VwID0gZ3JvdXAsCiAgICAgIHZhbHVlID0gdmFsdWUsICAgICAgICAgIAogICAgICBzaGFwZSA9IHNoYXBlLAogICAgICB0aXRsZSA9IHRpdGxlLAogICAgICBjb2xvciA9IGNvbG9yLAogICAgICBzaGFkb3cgPSBzaGFkb3cKICAgICkKICApCn0Kbm9kZXMgPC0gY2JpbmQoCiAgaWQgPSAxOm5yb3cobm9kZXMpLAogIG5vZGVzCikKIyBQcmVwYXJlIGVkZ2UgdGFibGUKZWRnZXMgPC0gZGF0YS5mcmFtZSgKICBmcm9tID0gTlVMTCwKICB0byA9IE5VTEwKKQpOb2Rlc0xzIDwtIG5vZGVzJHRpdGxlClBhcl9jbCA8LSBtYWtlQ2x1c3RlcihwYXJhbGxlbGx5OjpmcmVlQ29yZXMoKVsxXSkKcmVnaXN0ZXJEb1NOT1coUGFyX2NsKQpQYXJfcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMSwgbWF4ID0gbGVuZ3RoKE5vZGVzTHMpLCBzdHlsZSA9IDMpCnByb2dyZXNzIDwtIGZ1bmN0aW9uKG4pIHNldFR4dFByb2dyZXNzQmFyKFBhcl9wYiwgbikKUGFyX29wdHMgPC0gbGlzdChwcm9ncmVzcyA9IHByb2dyZXNzKQplZGdlcyA8LSBmb3JlYWNoKAogIGkgPSAxOmxlbmd0aChOb2Rlc0xzKSwgLmNvbWJpbmUgPSBiYXNlOjpyYmluZCwgLmVycm9yaGFuZGxpbmcgPSAicGFzcyIsCiAgLm9wdGlvbnMuc25vdyA9IFBhcl9vcHRzLCAucGFja2FnZXMgPSBjKCJ0Y2x0ayIsICJkcGx5ciIsICJ1dGlscyIpCikgJWRvcGFyJSB7CiAgdGVtcCA8LSBURkxpbmtfcmVsYXRlZF9maWx0ZXJbKFRGTGlua19yZWxhdGVkX2ZpbHRlciRVbmlwcm90SUQuVEYgJWluJSBOb2Rlc0xzW2ldIHwgVEZMaW5rX3JlbGF0ZWRfZmlsdGVyJFVuaXByb3RJRC5UYXJnZXQgJWluJSBOb2Rlc0xzW2ldKSxdCiAgdGVtcCA8LSB0ZW1wWyh0ZW1wJFVuaXByb3RJRC5URiAlaW4lIE5vZGVzTHMgfCB0ZW1wJFVuaXByb3RJRC5UYXJnZXQgJWluJSBOb2Rlc0xzKSxdCiAgZnJvbSA8LSB0ZW1wJFVuaXByb3RJRC5URgogIHRvIDwtIHRlbXAkVW5pcHJvdElELlRhcmdldAogIGZvciAoayBpbiAxOmxlbmd0aChmcm9tKSkgewogICAgZnJvbVtrXSA8LSB3aGljaChub2RlcyR0aXRsZSA9PSBmcm9tW2tdKQogICAgdG9ba10gPC0gd2hpY2gobm9kZXMkdGl0bGUgPT0gdG9ba10pCiAgfQogIG91dCA8LSBkYXRhLmZyYW1lKAogICAgZnJvbSA9IGZyb20sCiAgICB0byA9IHRvCiAgKQogIG91dAp9CnN0b3BDbHVzdGVyKFBhcl9jbCkKZHVwTHMgPC0gcGFzdGUwKGVkZ2VzJGZyb20sICItIiwgZWRnZXMkdG8pCmVkZ2VzIDwtIGVkZ2VzWyFkdXBsaWNhdGVkKGR1cExzKSxdCmVkZ2VzIDwtIGVkZ2VzW2VkZ2VzJGZyb20gIT0gZWRnZXMkdG8sXQpgYGAKCiMjIDAyLTA2LiBSZXRyaWV2ZSBTTlAgcmVjb3JkcyBvZiBURnMKClJldHJpZXZlIFNOUCByZWNvcmRzIG9uIHRoZSBPLUdsY05BYyBzaXRlcyBvZiBURnMuIElmIHRoZSBhIE8tR2xjTkFjIHNpdGUgb2YgYSBURiByZWFsbHkgaGF2ZSBhIHByb2ZvdW5kIGluZmx1ZW5jZXMgb24gdGhlIGZ1bmN0aW9uIG9mIHRoaXMgVEYsIHRoZSBtaXNzc2Vuc2UgU05QIG9uIHRoaXMgc2l0ZSBzaG91bGQgYmUgYXNzb2NpYXRlZCB3aXRoIHNvbWUgY2xpbmNpYWwgcGF0aG9nZW5pYyBwaGVub3R5cGUuCmBgYHtyIDAyLTA2fQojIFNldHVwIEJpb01hcnQgZm9yIFNOUApBbm5fTWFydC5Mb2M8LU5VTEwKYXR0ZW1wdDwtMQphdHRlbXB0X21heDwtMTIKd2hpbGUoaXMubnVsbChBbm5fTWFydC5Mb2MpJiZhdHRlbXB0PDEzKXsKICBpZihhdHRlbXB0JWluJXNlcShmcm9tPTEsdG89YXR0ZW1wdF9tYXgsMykpewogICAgdXJsPC0iaHR0cHM6Ly91c2Vhc3QuZW5zZW1ibC5vcmciCiAgfWVsc2UgaWYoYXR0ZW1wdCVpbiVzZXEoZnJvbT0yLHRvPWF0dGVtcHRfbWF4LDMpKXsKICAgIHVybDwtImh0dHBzOi8vd3d3LmVuc2VtYmwub3JnIgogIH1lbHNlIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209Myx0bz1hdHRlbXB0X21heCwzKSl7CiAgICB1cmw8LSJodHRwczovL3VzZWFzdC5lbnNlbWJsLm9yZy8iCiAgfWVsc2UgaWYoYXR0ZW1wdCVpbiVzZXEoZnJvbT00LHRvPWF0dGVtcHRfbWF4LDMpKXsKICAgIHVybDwtImh0dHBzOi8vYXNpYS5lbnNlbWJsLm9yZy8iCiAgfQogIHRyeSh7CiAgICBBbm5fTWFydC5Mb2MgPC0gdXNlRGF0YXNldCgKICAgICAgImhzYXBpZW5zX2dlbmVfZW5zZW1ibCIsCiAgICAgIHVzZU1hcnQoIkVOU0VNQkxfTUFSVF9FTlNFTUJMIiwgaG9zdCA9IHVybCkKICAgICkKICB9LCBzaWxlbnQgPSBUUlVFKQogIGF0dGVtcHQ8LWF0dGVtcHQrMQogIGlmKGlzLm51bGwoQW5uX01hcnQuTG9jKSl7U3lzLnNsZWVwKDEwKX0KfQojIFNldHVwIEJpb01hcnQgZm9yIEdlbmUgSUQKQW5uX01hcnQuc25wPC1OVUxMCmF0dGVtcHQ8LTEKYXR0ZW1wdF9tYXg8LTEyCndoaWxlKGlzLm51bGwoQW5uX01hcnQuc25wKSYmYXR0ZW1wdDwxMyl7CiAgaWYoYXR0ZW1wdCVpbiVzZXEoZnJvbT0xLHRvPWF0dGVtcHRfbWF4LDMpKXsKICAgIHVybDwtImh0dHBzOi8vdXNlYXN0LmVuc2VtYmwub3JnIgogIH1lbHNlIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209Mix0bz1hdHRlbXB0X21heCwzKSl7CiAgICB1cmw8LSJodHRwczovL3d3dy5lbnNlbWJsLm9yZyIKICB9ZWxzZSBpZihhdHRlbXB0JWluJXNlcShmcm9tPTMsdG89YXR0ZW1wdF9tYXgsMykpewogICAgdXJsPC0iaHR0cHM6Ly91c2Vhc3QuZW5zZW1ibC5vcmcvIgogIH1lbHNlIGlmKGF0dGVtcHQlaW4lc2VxKGZyb209NCx0bz1hdHRlbXB0X21heCwzKSl7CiAgICB1cmw8LSJodHRwczovL2FzaWEuZW5zZW1ibC5vcmcvIgogIH0KICB0cnkoewogICAgQW5uX01hcnQuc25wIDwtIHVzZURhdGFzZXQoCiAgICAgICJoc2FwaWVuc19zbnAiLAogICAgICB1c2VNYXJ0KCJFTlNFTUJMX01BUlRfU05QIiwgaG9zdCA9IHVybCkKICAgICkKICB9LCBzaWxlbnQgPSBUUlVFKQogIGF0dGVtcHQ8LWF0dGVtcHQrMQogIGlmKGlzLm51bGwoQW5uX01hcnQuc25wKSl7U3lzLnNsZWVwKDEwKX0KfQojIEdldCBTTlAgcmVzdWx0cwpVbmlQb3J0LnJzLk9HbGNOQWMgPC0gZGF0YS5mcmFtZSgpClRGcy5VbmlQb3J0IDwtIG5vZGVzJHRpdGxlW25vZGVzJGdyb3VwPT0iVEYiXQpQYXJfcGIgPC0gdHh0UHJvZ3Jlc3NCYXIobWluID0gMSwgbWF4ID0gbGVuZ3RoKFRGcy5VbmlQb3J0KSwgc3R5bGUgPSAzKQpuIDwtIDEKZm9yKGkgaW4gVEZzLlVuaVBvcnQpewogIG48LW4rMC40OQogIHNldFR4dFByb2dyZXNzQmFyKFBhcl9wYiwgbikKICBVbmlQb3J0LkxvYyA8LSBnZXRCTSgKICAgIG1hcnQ9QW5uX01hcnQuTG9jLAogICAgYXR0cmlidXRlcz1jKAogICAgICAidW5pcHJvdHN3aXNzcHJvdCIsICJlbnNlbWJsX2dlbmVfaWQiLCAiZW5zZW1ibF90cmFuc2NyaXB0X2lkIiwgInN0cmFuZCIsCiAgICAgICJjaHJvbW9zb21lX25hbWUiLCAidHJhbnNjcmlwdF9zdGFydCIsICJ0cmFuc2NyaXB0X2VuZCIsICJjZHNfbGVuZ3RoIgogICAgKSwKICAgIGZpbHRlcj1jKAogICAgICAidW5pcHJvdHN3aXNzcHJvdCIKICAgICksCiAgICB2YWx1ZXM9aSwKICAgIHVuaXF1ZVJvd3MgPSBUCiAgKQogIG48LW4rMC40OQogIHNldFR4dFByb2dyZXNzQmFyKFBhcl9wYiwgbikKICBVbmlQb3J0LkxvYyA8LSBVbmlQb3J0LkxvY1tvcmRlcihVbmlQb3J0LkxvYyRjZHNfbGVuZ3RoLCBkZWNyZWFzaW5nID0gVFJVRSksXVsxLF0KICBVbmlQb3J0LnJzPC1OVUxMCiAgcm0oVW5pUG9ydC5ycykKICB0cnkoewogICAgVW5pUG9ydC5ycyA8LSBVbmlQb3J0LkxvYyAlPiUKICAgICAgewogICAgICAgIHBhc3RlKAogICAgICAgICAgLltdJGNocm9tb3NvbWVfbmFtZSwKICAgICAgICAgIG1pbihjKC5bXSR0cmFuc2NyaXB0X3N0YXJ0LCAuW10kdHJhbnNjcmlwdF9lbmQpKSwKICAgICAgICAgIG1heChjKC5bXSR0cmFuc2NyaXB0X3N0YXJ0LCAuW10kdHJhbnNjcmlwdF9lbmQpKSwKICAgICAgICAgIHNlcCA9ICI6IgogICAgICAgICkKICAgICAgfSAlPiUKICAgICAgZ2V0Qk0oCiAgICAgICAgbWFydD1Bbm5fTWFydC5zbnAsCiAgICAgICAgYXR0cmlidXRlcz1jKAogICAgICAgICAgInJlZnNucF9pZCIsICJjb25zZXF1ZW5jZV90eXBlX3R2IiwgImNvbnNlcXVlbmNlX2FsbGVsZV9zdHJpbmciLAogICAgICAgICAgImVuc2VtYmxfcGVwdGlkZV9hbGxlbGUiLCAidHJhbnNsYXRpb25fc3RhcnQiLAogICAgICAgICAgInRyYW5zbGF0aW9uX2VuZCIsICAiZW5zZW1ibF90cmFuc2NyaXB0X3N0YWJsZV9pZCIsCiAgICAgICAgICAiY2hyX25hbWUiLCAgImNocm9tX3N0YXJ0IiwgICJjaHJvbV9lbmQiCiAgICAgICAgKSwKICAgICAgICBmaWx0ZXI9ImNocm9tb3NvbWFsX3JlZ2lvbiIsCiAgICAgICAgdmFsdWVzPS4sCiAgICAgICAgdW5pcXVlUm93cyA9IFRSVUUsCiAgICAgICAgdmVyYm9zZSA9IEZBTFNFCiAgICAgICkKICB9LCBzaWxlbnQgPSBUUlVFKQogIGlmKCFleGlzdHMoIlVuaVBvcnQucnMiKSkgbmV4dAogIFVuaVBvcnQucnMgPC0gVW5pUG9ydC5yc1tVbmlQb3J0LnJzJGVuc2VtYmxfdHJhbnNjcmlwdF9zdGFibGVfaWQgPT0gVW5pUG9ydC5Mb2MkZW5zZW1ibF90cmFuc2NyaXB0X2lkLF0KICBVbmlQb3J0LnJzIDwtIFVuaVBvcnQucnNbVW5pUG9ydC5ycyRjb25zZXF1ZW5jZV90eXBlX3R2ID09ICJtaXNzZW5zZV92YXJpYW50IixdCiAgVW5pUG9ydC5ycyA8LSBVbmlQb3J0LnJzW1VuaVBvcnQucnMkdHJhbnNsYXRpb25fc3RhcnQgPT0gVW5pUG9ydC5ycyR0cmFuc2xhdGlvbl9lbmQsXQogIHRlbXAgPC0gT0dsY05BY19Bbm5vW09HbGNOQWNfQW5ubyRBY2Nlc3Npb249PWksXSRQb3NpdGlvbl9pbl9Qcm90ZWluICU+JSAKICAgIHtVbmlQb3J0LnJzW1VuaVBvcnQucnMkdHJhbnNsYXRpb25fc3RhcnQlaW4lLltdLF19CiAgaWYobnJvdyh0ZW1wKSl7CiAgICB0ZW1wIDwtICBjYmluZCgKICAgICAgVW5pUG9ydF9JRCA9IGksIHRlbXAKICAgICkKICAgIFVuaVBvcnQucnMuT0dsY05BYyA8LSByYmluZCgKICAgICAgVW5pUG9ydC5ycy5PR2xjTkFjLCB0ZW1wCiAgICApCiAgfQogIG48LW4rMC4wMgogIHNldFR4dFByb2dyZXNzQmFyKFBhcl9wYiwgbikKfQpjYXQoIlxuIikKY2xvc2UoUGFyX3BiKQpTTlAuUGhlbm90eXBlIDwtIGdldEJNKAogIG1hcnQ9QW5uX01hcnQuc25wLAogIGF0dHJpYnV0ZXM9YygKICAgICJyZWZzbnBfaWQiLCAiY2xpbmljYWxfc2lnbmlmaWNhbmNlIiwgInBfdmFsdWUiLAogICAgInBoZW5vdHlwZV9uYW1lIiwgInBoZW5vdHlwZV9kZXNjcmlwdGlvbiIsICJ2YXJpYXRpb25fbmFtZXMiCiAgKSwKICBmaWx0ZXI9InNucF9maWx0ZXIiLAogIHZhbHVlcz1VbmlQb3J0LnJzLk9HbGNOQWMkcmVmc25wX2lkJT4ldW5pcXVlKCksCiAgdW5pcXVlUm93cyA9IFRSVUUKKQpTTlAuUGhlbm90eXBlPC1TTlAuUGhlbm90eXBlW1NOUC5QaGVub3R5cGUkcGhlbm90eXBlX2Rlc2NyaXB0aW9uIT0iIixdClVuaVBvcnQucnMuT0dsY05BYy5DbGluYyA8LSBVbmlQb3J0LnJzLk9HbGNOQWNbVW5pUG9ydC5ycy5PR2xjTkFjJHJlZnNucF9pZCVpbiV1bmlxdWUoU05QLlBoZW5vdHlwZSRyZWZzbnBfaWQpLF0KVW5pUG9ydC5ycy5PR2xjTkFjLkNsaW5jIDwtIG1lcmdlKFVuaVBvcnQucnMuT0dsY05BYy5DbGluYywgU05QLlBoZW5vdHlwZSwgYnk9InJlZnNucF9pZCIpCmBgYAoKIyMgMDItMDcuIFZpc3VhbGl6YXRpb24gb2YgdGhlIGdlbmUgcmVndWxhdG9yeSBuZXR3b3JrIHsudGFic2V0IC50YWJzZXQtZmFkZX0KCiMjIyBDb2RlIHsudGFic2V0IC50YWJzZXQtZmFkZX0KCkZpbHRlciBvdXQgdGhlIFRGcyB0aGF0IGRvZXNuJ3QgaGF2ZSBwYXRob2dlbmljIFNOUCBvbiBPLUdsY05BYyBzaXRlcy4KYGBge3IgMDItMDcuYX0KdGVtcCA8LSBub2Rlc1tub2RlcyRncm91cD09IlRGIixdCnRlbXAgPC0gdGVtcFt0ZW1wJHRpdGxlJWluJXVuaXF1ZShVbmlQb3J0LnJzLk9HbGNOQWMuQ2xpbmMkVW5pUG9ydF9JRCksXQpub2RlcyA8LSByYmluZCgKICBub2Rlc1tub2RlcyRncm91cCE9IlRGIixdLAogIHRlbXAKKQplZGdlcyA8LSBlZGdlc1tlZGdlcyRmcm9tJWluJW5vZGVzJGlkICYgZWRnZXMkdG8laW4lbm9kZXMkaWQsXQpub2Rlczwtbm9kZXNbbm9kZXMkaWQlaW4ldW5pcXVlKGMoZWRnZXMkZnJvbSxlZGdlcyR0bykpLF0KYGBgCjxicj4KCkFkZCB1cmwgbGluayB0byBub2Rlcy4KYGBge3IgMDItMDcuYn0KZm9yKGkgaW4gbm9kZXMkdGl0bGVbbm9kZXMkZ3JvdXA9PSJURiJdKXsKICBPR2xjTkFjLnNpdGUgPC0gT0dsY05BY19Bbm5vW09HbGNOQWNfQW5ubyRRdWVyeT09aSxdJFBvc2l0aW9uX2luX1Byb3RlaW4gJT4lCiAgICB1bmlxdWUoKSAlPiUgcGFzdGUoLiwgY29sbGFwc2UgPSAiOyIpCiAgdGVtcCA8LSBVbmlQb3J0LnJzLk9HbGNOQWMuQ2xpbmNbVW5pUG9ydC5ycy5PR2xjTkFjLkNsaW5jJFVuaVBvcnRfSUQ9PWksIF0KICB0ZW1wIDwtIHRlbXBbIWR1cGxpY2F0ZWQodGVtcCRyZWZzbnBfaWQpLCBdCiAgU05QLmxpc3QgPC0gYygpCiAgZm9yIChrIGluIHRlbXAkcmVmc25wX2lkKSB7CiAgICBTTlAubGlzdCA8LSBjKAogICAgICBTTlAubGlzdCwKICAgICAgcGFzdGUwKAogICAgICAgICI8YnI+PGEgaHJlZj0naHR0cHM6Ly93d3cuZW5zZW1ibC5vcmcvSG9tb19zYXBpZW5zL1ZhcmlhdGlvbi9QaGVub3R5cGU/ZGI9Y29yZTtyPSIsCiAgICAgICAgcGFzdGUwKHRlbXAkY2hyX25hbWVbdGVtcCRyZWZzbnBfaWQ9PWtdLCI6Iix0ZW1wJGNocm9tX3N0YXJ0W3RlbXAkcmVmc25wX2lkPT1rXSwiLSIsdGVtcCRjaHJvbV9lbmRbdGVtcCRyZWZzbnBfaWQ9PWtdKSwKICAgICAgICAiO3Y9IiwgaywgIjt2ZGI9dmFyaWF0aW9uJyB0YXJnZXQ9J19ibGFuayc+U05QOiAiLAogICAgICAgIGssICI7IG9uIEFBIHNpdGU6ICIsIHRlbXAkdHJhbnNsYXRpb25fc3RhcnRbdGVtcCRyZWZzbnBfaWQ9PWtdLAogICAgICAgICIgb2YgIiwgdGVtcCRlbnNlbWJsX3RyYW5zY3JpcHRfc3RhYmxlX2lkW3RlbXAkcmVmc25wX2lkPT1rXSwgIjwvYT4iCiAgICAgICkKICAgICkKICB9CiAgbm9kZXMkdGl0bGVbbm9kZXMkdGl0bGU9PWldIDwtIEhUTUwoCiAgICBwYXN0ZTAoCiAgICAgICc8YSBocmVmPSJodHRwczovL3d3dy51bmlwcm90Lm9yZy91bmlwcm90a2IvJyxpLCcvZW50cnkiIHRhcmdldD0iX2JsYW5rIj4nLCBpLCAnPC9hPjxicj4nLAogICAgICAnPGEgaHJlZj0iaHR0cHM6Ly9vZ2xjbmFjLm9yZy9hdGxhcy9kZXRhaWwvJywgaSwgJyIgdGFyZ2V0PSJfYmxhbmsiPk8tR2xjTkFjIFNpdGU6ICcsIE9HbGNOQWMuc2l0ZSwgJzwvYT48YnI+JywKICAgICAgJ08tR2xjTkFjIHNpdGUgd2l0aCBwYXRob2dlbmljIFNOUDonCiAgICApLCBIVE1MKFNOUC5saXN0KQogICkKfQpmb3IoaSBpbiBub2RlcyR0aXRsZVtub2RlcyRncm91cCE9IlRGIl0pewogIG5vZGVzJHRpdGxlW25vZGVzJHRpdGxlPT1pXSA8LSBIVE1MKAogICAgcGFzdGUwKAogICAgICAnPGEgaHJlZj0iaHR0cHM6Ly93d3cudW5pcHJvdC5vcmcvdW5pcHJvdGtiLycsaSwnL2VudHJ5IiB0YXJnZXQ9Il9ibGFuayI+JywgaSwgJzwvYT4nCiAgICApCiAgKQp9ClRGcy5NZXNoLmtlZXAgPC0gVEZzLk1lc2gkbGl0ZXJhdHVyZS5taW5pbmdbVEZzLk1lc2gkbGl0ZXJhdHVyZS5taW5pbmckR2VuZVN5bWJvbCVpbiVub2RlcyRsYWJlbFtub2RlcyRncm91cD09IlRGIl0sXQpNaXRvQ3lTay5NZXNoLmtlZXAgPC0gTWl0b0N5U2suTWVzaCRsaXRlcmF0dXJlLm1pbmluZ1tNaXRvQ3lTay5NZXNoJGxpdGVyYXR1cmUubWluaW5nJEdlbmVTeW1ib2wlaW4lbm9kZXMkbGFiZWxbbm9kZXMkZ3JvdXAhPSJURiJdLF0KTWVzaC5rZWVwIDwtIHJiaW5kKAogIFRGcy5NZXNoLmtlZXAsIE1pdG9DeVNrLk1lc2gua2VlcAopClIubWF4IDwtIG1heChNZXNoLmtlZXAkUikKUi5taW4gPC0gbWluKE1lc2gua2VlcCRSKQpmb3IoaSBpbiBub2RlcyRsYWJlbCl7CiAgbm9kZXMkdmFsdWVbbm9kZXMkbGFiZWw9PWldPC0xKygoKE1lc2gua2VlcCRSW01lc2gua2VlcCRHZW5lU3ltYm9sPT1pXS1SLm1pbikvUi5tYXgpKjEwMCkKfQpURl9scyA8LSBURkxpbmskTmFtZS5URiAlPiUgdW5pcXVlKCkKZm9yKGkgaW4gbm9kZXMkbGFiZWxbbm9kZXMkZ3JvdXAhPSJURiJdKXsKICBpZihpICVpbiUgVEZfbHMpewogICAgbm9kZXMkc2hhcGVbbm9kZXMkbGFiZWw9PWldIDwtICJkb3QiCiAgfQp9CmBgYAo8YnI+CgpBZGQgdXJsIGxpbmsgdG8gZWRnZS4KYGBge3IgMDItMDcuY30KZWRnZXMgPC0gY2JpbmQoCiAgZWRnZXMsIHRpdGxlID0gMSwgd2lkdGggPSAxLCBjb2xvcj0ibGlnaHRibHVlIiwgaGlnaGxpZ2h0ID0gInJlZCIKKQpmb3IoaSBpbiAxOm5yb3coZWRnZXMpKXsKICBmcm9tIDwtIGVkZ2VzW2ksXSRmcm9tCiAgdG8gPC0gZWRnZXNbaSxdJHRvCiAgZnJvbSA8LSBub2RlcyRsYWJlbFtub2RlcyRpZD09ZnJvbV0KICB0byA8LSBub2RlcyRsYWJlbFtub2RlcyRpZD09dG9dCiAgdGVtcCA8LSBURkxpbmtfcmVsYXRlZF9maWx0ZXJbVEZMaW5rX3JlbGF0ZWRfZmlsdGVyJE5hbWUuVEY9PWZyb20gJiBURkxpbmtfcmVsYXRlZF9maWx0ZXIkTmFtZS5UYXJnZXQ9PXRvLF0KICBQdWJNZWQubHMgPC0gdGVtcCRQdWJtZWRJRCAlPiUKICAgIHN0cl9zcGxpdCguLCI7IikgJT4lIHVubGlzdCgpICU+JQogICAgcGFzdGUwKAogICAgICAiPGEgaHJlZj0naHR0cHM6Ly9wdWJtZWQubmNiaS5ubG0ubmloLmdvdi8iLCAuLCAiLycgdGFyZ2V0PSdfYmxhbmsnPiIsCiAgICAgIC4sICI8L2E+PGJyPiIKICAgICkKICBlZGdlcyR0aXRsZVtpXSA8LSBwYXN0ZTAoCiAgICAiSGFzIFNtYWxsLXNjYWxlIEV2aWRlbmNlOiAiLCB0ZW1wJGBTbWFsbC1zY2FsZS5ldmlkZW5jZWAsICI8YnI+IiwgSFRNTChQdWJNZWQubHMpCiAgKSAlPiUgSFRNTCgpCiAgZWRnZXMkd2lkdGhbaV0gPC0gbGVuZ3RoKFB1Yk1lZC5scykKICBpZih0ZW1wJGBTbWFsbC1zY2FsZS5ldmlkZW5jZWA9PSJZZXMiKXsKICAgIGVkZ2VzJGNvbG9yW2ldIDwtICIjOTk5OUZGIgogIH0KfQplLk1heCA8LSBtYXgoZWRnZXMkd2lkdGgpCmUuTWluIDwtIG1pbihlZGdlcyR3aWR0aCkKZWRnZXMkd2lkdGggPC0gcm91bmQobG9nKGVkZ2VzJHdpZHRoIC0gZS5NaW4gKyAxLCBiYXNlID0gMikgKyAxKQpgYGAKPGJyPgoKQWRkIGxlZ2VuZHMKYGBge3IgMDItMDcuZH0KIyBub2RlcyBkYXRhLmZyYW1lIGZvciBsZWdlbmQKbG5vZGVzIDwtIGRhdGEuZnJhbWUoCiAgbGFiZWwgPSBjKAogICAgIlRGcyByZWxhdGVkIHRvIGN5dG9za2VsZXRvblxud2l0aG91dCBPLUdsY05BYyBzaXRlIHJlY29yZHNcbmFuZCBpcyBub3QgREVHcyBpbiBHU0UxMzg3ODMiLAogICAgIlRGcyByZWxhdGVkIHRvIGN5dG9za2VsZXRvblxud2l0aCBPLUdsY05BYyBzaXRlIHJlY29yZHNcbmFuZCBpcyBERUdzIGluIEdTRTEzODc4MyIsCiAgICAiVEZzIHdpdGggTy1HbGNOQWMgc2l0ZSByZWNvcmRzXG5hbmQgaXMgbm90IERFR3MgaW4gR1NFMTM4NzgzIiwKICAgICJURnMgd2l0aCBPLUdsY05BYyBzaXRlIHJlY29yZHNcbmFuZCBpcyBERUdzIGluIEdTRTEzODc4MyIsCiAgICAiVGFyZ2V0cyByZWxhdGVkIHRvIGN5dG9za2VsZXRvbiIsICJUYXJnZXRzIHJlbGF0ZWQgdG8gbWl0b2Nob25kcmlhIiwKICAgICJUYXJnZXRzIHJlbGF0ZWQgdG9cbmJvdGggb2YgY3l0b3NrZWxldG9uIGFuZCBtaXRvY2hvbmRyaWEiLAogICAgIlNpemUgb2Ygbm9kZSBpbmRpY2F0ZXMgdGhlIGFtb3VudFxub2YgYXJ0aWNsZXMgcmVsYXRlZCB0byBvc3Rlb2JsYXN0IiwKICAgICJXaWR0aCBvZiBlZGdlIGluZGljYXRlc1xudGhlIGFtb3VudCBvZiBhcnRpY2xlcyIKICApLAogIHNoYXBlID0gYygKICAgICJkb3QiLCAic3RhciIsICJzcXVhcmUiLCAic3RhciIsICJ0cmlhbmdsZSIsICJ0cmlhbmdsZSIsICJkaWFtb25kIiwgInRleHQiLCAidGV4dCIKICApLAogIGNvbG9yID0gYygKICAgICJyZWQiLCAicmVkIiwgImJsdWUiLCAiYmx1ZSIsICJyZWQiLCAib3JhbmdlIiwgImJsYWNrIiwgIiNGRkZGRkYiLCAiI0ZGRkZGRiIKICApLAogIHRpdGxlID0gIkxlZ2VuZHMiLAogIGlkID0gMTo5CikKIyBlZGdlcyBkYXRhLmZyYW1lIGZvciBsZWdlbmQKbGVkZ2VzIDwtIGRhdGEuZnJhbWUoCiAgY29sb3IgPSBjKCIjOTk5OUZGIiwgImxpZ2h0Ymx1ZSIpLAogIGxhYmVsID0gYygiSGFzIHNtYWxsLXNjYWxlXG5ldmlkZW5jZSIsICJObyBzbWFsbC1zY2FsZVxuZXZpZGVuY2UiKSwKICBhcnJvd3MgPWMoInRvIiwgInRvIiksIHdpZHRoID0gMTAKKQpgYGAKPGJyPgoKTWFrZSBpbnRlcmFjdGl2ZSBuZXR3b3JrCmBgYHtyIDAyLTA3LmV9Cm5ldHdvcmsgPC0gdmlzTmV0d29yayhub2RlcywgZWRnZXMsIGhlaWdodCA9ICI4MDBweCIsIHdpZHRoID0gIjEwMCUiKSAlPiUKICB2aXNOb2RlcyhwaHlzaWNzID0gVFJVRSkgJT4lCiAgdmlzUGh5c2ljcyhzdGFiaWxpemF0aW9uID0gVFJVRSwgc29sdmVyID0gInJlcHVsc2lvbiIsIG1heFZlbG9jaXR5ID0gMSkgJT4lCiAgdmlzRWRnZXMoCiAgICBzbW9vdGggPSBUUlVFLCBzaGFkb3cgPSBGQUxTRSwKICAgIGFycm93cyA9bGlzdCh0byA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIHNjYWxlRmFjdG9yID0gMC41KSkKICApICU+JQogIHZpc09wdGlvbnMoCiAgICBub2Rlc0lkU2VsZWN0aW9uID0gVFJVRSwgc2VsZWN0ZWRCeSA9ICJncm91cCIsIG1hbmlwdWxhdGlvbiA9IEZBTFNFLAogICAgaGlnaGxpZ2h0TmVhcmVzdCA9IGxpc3QoZW5hYmxlZCA9IFRSVUUsIGRlZ3JlZSA9IDk5OTksIGFsZ29yaXRobSA9ICJoaWVyYXJjaGljYWwiKQogICkgJT4lCiAgdmlzTGVnZW5kKGFkZEVkZ2VzID0gbGVkZ2VzLCBhZGROb2RlcyA9IGxub2RlcywgdXNlR3JvdXBzID0gRkFMU0UpCm5ldHdvcmsgJT4lIHZpc1NhdmUoZmlsZSA9ICJuZXR3b3JrLmh0bWwiKQpgYGAKCiMjIyBSZXN1bHQgey50YWJzZXQgLnRhYnNldC1mYWRlfQoKYGBge3IgMDItMDcuZn0KcHJpbnQobmV0d29yaykKYGBgCgoKIyAwMy4gU3VwcGxlbWVudGFyeQoKYGBge3IgMDMuU3VwcGxlbWVudGFyeX0Kc2F2ZS5pbWFnZSgiLi9DaGVja1BvaW50LlJEYXRhIikKc2Vzc2lvbkluZm8oKQpgYGA=