1 Evaluate Previous Result: version: 20170912

1.1 Reading in the existing result

As per Yan’s suggestion, I am using the existing results, found in the file: “2017_0811BrikenMultiConsensus.xlsx”

Unfortunately, this table is in a particularly strange format: it is a triply nested table of tables of tables in one worksheet in Excel. At one level, this is a really novel and interesting use of Excel, but on every other level it is sub-optimal. The primary reason it is sub-optimal is that the column headings end up being repeated once for every new sub-table (874 of the inner tables plus however many middle tables), which makes it nearly unparseable.

I am displeased I need to do this level of data wrangling.

The following function is my first attempt at handling this parsing puzzle.

For the interested, the structure is:

  • all_protein_groups is a list of two elements: group_id => (“summary”, “data”)
  • for every protein group discovered, one of these two element lists is created.
    • The summary slot gets the one-row summary for the protein group from the excel.
  • The data slot is a list of groups: protein => (“summary”, “data”)
    • Once again, the summary is the single row from excel.
  • Again, the data slot is a list of peptides: peptide => data.frame(peptides)
    • Each peptide data frame contains all of the inner-most rows describing the observed peptides.
parse_difficult_excel <- function(xlsx_file) {
  old_options <- options(java.parameters="-Xmx20G")
  message(paste0("Reading ", xlsx_file))
  result <- readxl::read_xlsx(path=xlsx_file, sheet=1)
  message("Starting parse.")
  group_data <- list()

  ## This is the toplevel heading for the protein groups.
  ## Dropping the first column because it is just used by excel to define the top-level table.
  outer_colnames <- colnames(result)
  outer_keepers <- !grepl(pattern="^X__", x=outer_colnames)
  outer_keepers[1] <- FALSE ## Get rid of the one that says 'Checked'
  outer_colnames <- outer_colnames[outer_keepers]

  for (r in 1:nrow(result)) {
    ## if ((1000 %% r) == 0) {
    ##   message(paste0("Completed ", r, " rows."))
    ## }
    row <- as.data.frame(result[r, ])
    row[, is.na(row)] <- ""

    ## When the first column is false, the next row will start a new protein group.
    if (row[, 1] == FALSE) {
      group_information <- row[outer_keepers]
      group_id <- group_information[, 1]
      group_data[[group_id]] <- list(
        "summary" = group_information,
        "data" = list())
      next
    }

    ## When the 2nd column is 'Checked', then this defines a new protein in the group.
    ## dropping the first 2 columns as they are used to define the middle-table.
    if (row[, 2] == "Checked") {
      group_keepers <- !grepl(pattern="X__", x=names(row))
      group_keepers <- names(row)[group_keepers]
      group_colnames <- as.character(row[, group_keepers])
      next
    }

    ## When the 2nd column is FALSE, then this defined a protein in the group.
    if (row[, 2] == FALSE) {
      group_row <- row[, group_keepers]
      colnames(group_row) <- group_colnames
      group_accession <- group_row[["Accession"]]
      protein_list <- list(
        "summary" = group_row,
        "data" = data.frame())
      group_data[[group_id]][["data"]][[group_accession]] <- protein_list
      next
    }

    ## When the 3rd column is 'Checked', then this starts a peptide definition
    if (row[, 3] == "Checked") {
      ## dropping the first 3 columns, they define the inner table.
      peptide_colnames <- row
      group_data[[group_id]][["data"]][[group_accession]][["data"]] <- data.frame()
      next
    }

    ## When the 3rd group is FALSE, then this adds a peptide.
    if (row[, 3] == FALSE) {
      peptide_data <- row
      colnames(peptide_data) <- peptide_colnames
      current <- group_data[[group_id]][["data"]][[group_accession]][["data"]]
      new <- rbind(current, peptide_data)
      group_data[[group_id]][["data"]][[group_accession]][["data"]] <- new
    }
  } ## End iterating over ever row of this unholy stupid data structure.

  return(group_data)
}

##xlsx_file <- "previous_results/2017_0811BrikenMultiConsensus.xlsx"
xlsx_file <- "previous_results/2017_1101BrikenMulti_PE_PPE_Complete.xlsx"
less_difficult <- parse_difficult_excel(xlsx_file)
## Reading previous_results/2017_1101BrikenMulti_PE_PPE_Complete.xlsx
## Starting parse.

The function above is not immediately helpful, as it only wrangles the crazy excel into a less-crazy data structure, but if we wish to graph the data or play with it, we need to extract the elements of interest. For the moment those are primarily the peptide fragments and whether they were found in the trypsin digestions, chymotrypsin digestions, or both.

1.2 Extract peptides

The following block of code handles that task. It just iterates over the new data structure and pulls out the peptides.

protein_df <- data.frame()
protein_names <- c()
for (group in names(less_difficult)) {
  group <- as.character(group)
  protein_group <- less_difficult[[group]][["data"]]
  protein_accessions <- names(protein_group)
  for (protein in protein_accessions) {
    protein_names <- c(protein_names, protein)
    protein_summary <- less_difficult[[group]][["data"]][[protein]][["summary"]]
    protein_df <- rbind(protein_df, protein_summary)
    peptide_df <- less_difficult[[group]][["data"]][[protein]][["data"]]
  }
}
head(protein_df)
##    Checked Protein FDR Confidence: Mascot Protein FDR Confidence: Sequest HT
## 1    FALSE                           High                               High
## 2    FALSE                           High                               High
## 3    FALSE                           High                               High
## 4    FALSE                           High                               High
## 5    FALSE                           High                               High
## 6    FALSE                           High                               High
##           Master Accession
## 1 Master Protein    P95029
## 2 Master Protein    I6X8D2
## 3 Master Protein    P9WGY7
## 4 Master Protein    P9WPE7
## 5 Master Protein    P9WGY9
## 6 Master Protein    P9WK07
##                                                                                                                                         Description
## 1                                                 3-oxoacyl-ACP synthase OS=Mycobacterium tuberculosis (strain ATCC 25618 / H37Rv) GN=fas PE=1 SV=3
## 2                                                  Polyketide synthase OS=Mycobacterium tuberculosis (strain ATCC 25618 / H37Rv) GN=pks13 PE=1 SV=1
## 3                             DNA-directed RNA polymerase subunit beta' OS=Mycobacterium tuberculosis (strain ATCC 25618 / H37Rv) GN=rpoC PE=1 SV=1
## 4                                                 60 kDa chaperonin 2 OS=Mycobacterium tuberculosis (strain ATCC 25618 / H37Rv) GN=groEL2 PE=1 SV=1
## 5                              DNA-directed RNA polymerase subunit beta OS=Mycobacterium tuberculosis (strain ATCC 25618 / H37Rv) GN=rpoB PE=1 SV=1
## 6 5-methyltetrahydropteroyltriglutamate--homocysteine methyltransferase OS=Mycobacterium tuberculosis (strain ATCC 25618 / H37Rv) GN=metE PE=1 SV=1
##   Exp. q-value: Mascot Exp. q-value: Sequest HT Coverage [%] Contaminant
## 1                0.000                    0.000           57       FALSE
## 2                0.000                    0.000           74       FALSE
## 3                0.000                    0.000           76       FALSE
## 4                0.000                    0.000           93       FALSE
## 5                0.000                    0.000           75       FALSE
## 6                0.000                    0.000           81       FALSE

This data frame now contains everything we are likely to want to visualize.

1.3 Cleaning the data frame

Unfortunately, this data structure also suffers from a couple weaknesses:

  1. The protein IDs are not unique (because of the groups)
  2. The column names from Excel are insane (punctuation, special characters, and really long)
  3. Excel left the numeric columns of data as characters, not numbers!

Therefore, I will take a moment to fix these problems before trying any plots.

## Set some reasonable row names
rownames(protein_df) <- make.names(protein_names, unique=TRUE)
## Set the column names to something legible.
## Each line denotes my mental organizational unit
new_colnames <- c("mascot_fdr", "sequest_fdr", "master", "accession", "description",
                  "mascot_q", "sequest_q", "coverage", "contaminant",
                  "num_peptides", "num_psms", "num_unique_pep", "num_prot_groups", "num_aas",
                  "mw", "pi", "mascot_score", "sequest_score",
                  "num_peptides_mascot", "num_peptides_sequest",
                  "chymo_found_1", "chymo_found_2", "tryp_found_1", "tryp_found_2",
                  "modifications")
colnames(protein_df) <- new_colnames
## Error in names(x) <- value: 'names' attribute [25] must be the same length as the vector [11]
## Now make sure the columns which should be numeric, are numeric.
numeric_cols <- c("mascot_q", "sequest_q", "coverage", "num_peptides", "num_psms",
                  "num_unique_pep", "num_prot_groups", "num_aas", "mw", "pi",
                  "mascot_score", "sequest_score",
                  "num_peptides_mascot", "num_peptides_sequest")
for (col in numeric_cols) {
  protein_df[[col]] <- as.numeric(protein_df[[col]])
}
## Error in `[[<-.data.frame`(`*tmp*`, col, value = numeric(0)): replacement has 0 rows, data has 2633
## Finally, set a 'state' column describing the 4 columns of chymotrypsin/trypsin digestions.
protein_df[["state"]] <- "ambiguous"

## If something is found in one chymo trypsin, but in neither trypsin, set it to chymo_only.
chymo_hits <- (protein_df[["chymo_found_1"]] == "High" | protein_df[["chymo_found_2"]] == "High") &
  (protein_df[["tryp_found_1"]] != "High" & protein_df[["tryp_found_2"]] != "High")
protein_df[chymo_hits, "state"] <- "chymo_only"
## Conversely, if found in trypsin but not chymo, set it to tryp_only.
tryp_hits <- (protein_df[["tryp_found_1"]] == "High" | protein_df[["tryp_found_2"]] == "High") &
  (protein_df[["chymo_found_1"]] != "High" & protein_df[["chymo_found_2"]] != "High")
protein_df[tryp_hits, "state"] <- "tryp_only"

chymo_df <- protein_df[chymo_hits, ]
tryp_df <- protein_df[tryp_hits, ]

1.4 Make a couple plots

Now that the data has been wrangled, we can use the various R plotters to look at the data relatively quickly and easily.

library(ggplot2)
coverage_wrt_pi <- ggplot(protein_df, aes_string(x="pi", y="coverage", colour="state")) +
  geom_point(alpha=0.6, size=2) +
  geom_density_2d()

coverage_wrt_mw <- ggplot(protein_df, aes_string(x="mw", y="coverage", colour="state")) +
  geom_point(alpha=0.6, size=2) +
  geom_density_2d()

protein_df$logmascot <- log2(protein_df$mascot_score)
## Error in log2(protein_df$mascot_score): non-numeric argument to mathematical function
scores_by_state <- ggplot(protein_df, aes_string(x="state", y="logmascot")) +
  geom_boxplot(aes_string(fill="state"))

1.4.1 Coverage vs. pI

Yan suggested that peptides with low-pIs would be less covered, especially in the chymotrypsin samples.

pp(file="images/coverage_wrt_pi.png")
## NULL
coverage_wrt_pi
## Error in FUN(X[[i]], ...): object 'coverage' not found
dev.off()
## png
##   2
## To me, it looks like there might be something to that idea.

1.4.2 Coverage vs. Molecular Weight

Maybe there is a trend of coverage and molecular weight by sample-type.

pp(file="images/coverage_wrt_mw.png")
## NULL
coverage_wrt_mw
## Error in FUN(X[[i]], ...): object 'mw' not found
dev.off()
## png
##   2
## It looks to me that the trypsin is slightly better than chymotrypsin.
## It is not clear in the excel, but I presume this is in kDa.

1.4.3 Mascot scores

A quick boxplot showing mascot scores by what was observed.

scores_by_state
## Error in FUN(X[[i]], ...): object 'logmascot' not found

1.5 Evaluate what proteins were found by mascot for the chymo1 digestion

keepers <- protein_df[["chymo_found_1"]] == "High"
chymo1_df <- protein_df[keepers, ]
dim(chymo1_df)
## [1]  0 12
## unfortunately, this does not tell me exactly which spectra were observed to
## support these 274 protein.  However, I should be able to cross reference the
## proteins I find against these.
chymo_accessions <- chymo1_df[["accession"]]

get_xref <- function(accession) {
  request_url <- paste0("http://www.uniprot.org/uniprot/", accession, ".xml")
  data <- xml2::read_xml(request_url)
  data_list <- xml2::as_list(data)
  gene_names <- data_list[["entry"]][["gene"]]
  names <- reshape2::melt(gene_names)
  names <- names[["value"]]
  return(names)
}

acc_df <- data.frame()
for (acc in chymo_accessions) {
  hits <- try(get_xref(acc), silent=TRUE)
  if (class(hits) != "try-error") {
    for (h in hits) {
      row <- c(acc, h)
      acc_df <- rbind(acc_df, row)
    }
  }
}
colnames(acc_df) <- c("uniprot", "alias")
## Error in names(x) <- value: 'names' attribute [2] must be the same length as the vector [0]
head(acc_df)
## data frame with 0 columns and 0 rows
## This now contains only those IDs which were in the chymo digestion.

dim(chymo1_df)
## [1]  0 12
## So now lets get the set of all RvIDs
mtb_ids <- read.table("reference/mtb_ids.txt")
## And extract from the accessions list those which match
mtb_acc_df <- merge(mtb_ids, acc_df, by.x="V1", by.y="alias")
## Error in fix.by(by.y, y): 'by' must specify a uniquely valid column
dim(mtb_acc_df)
## Error in eval(expr, envir, enclos): object 'mtb_acc_df' not found
## So we lost 7 (274 to 267), I can accept that.
colnames(mtb_acc_df) <- c("rv_id", "uniprot")
## Error in colnames(mtb_acc_df) <- c("rv_id", "uniprot"): object 'mtb_acc_df' not found
chymo_translated <- merge(chymo1_df, mtb_acc_df, by.x="accession", by.y="uniprot", all.x=TRUE)
## Error in as.data.frame(y): object 'mtb_acc_df' not found
## Ok, so now I can translate between the various IDs.

head(chymo1_df)
##  [1]                                    Checked
##  [3] Protein FDR Confidence: Mascot     Protein FDR Confidence: Sequest HT
##  [5] Master                             Accession
##  [7] Description                        Exp. q-value: Mascot
##  [9] Exp. q-value: Sequest HT           Coverage [%]
## [11] Contaminant                        state
## <0 rows> (or 0-length row.names)
head(mtb_annotations)
##    seqnames start  end width strand                 source type score phase       db_xref
## 1 NC_000962     1 1524  1524      + EMBL/GenBank/SwissProt gene    NA     1 GeneID:885041
## 2 NC_000962  2052 3260  1209      + EMBL/GenBank/SwissProt gene    NA     1 GeneID:887092
## 3 NC_000962  3280 4437  1158      + EMBL/GenBank/SwissProt gene    NA     1 GeneID:887089
## 4 NC_000962  4434 4997   564      + EMBL/GenBank/SwissProt gene    NA     1 GeneID:887088
## 5 NC_000962  5240 7267  2028      + EMBL/GenBank/SwissProt gene    NA     1 GeneID:887081
## 6 NC_000962  7302 9818  2517      + EMBL/GenBank/SwissProt gene    NA     1 GeneID:887105
##                                                       experiment gene locus_tag
## 1 DESCRIPTION:Mutation analysis, gene expression[PMID: 10375628] dnaA    Rv0001
## 2                                                           <NA> dnaN    Rv0002
## 3                                                           <NA> recF    Rv0003
## 4                                                           <NA> <NA>    Rv0004
## 5                    DESCRIPTION:Mutation analysis[PMID:8031045] gyrB    Rv0005
## 6                    DESCRIPTION:Mutation analysis[PMID:8031045] gyrA    Rv0006
##   gene_synonym pseudogene
## 1         <NA>       <NA>
## 2         <NA>       <NA>
## 3         <NA>       <NA>
## 4         <NA>       <NA>
## 5         <NA>       <NA>
## 6         <NA>       <NA>
chymo_t2 <- merge(chymo1_df, mtb_annotations, by.x=)


chymo2_df <- chymo1_df
chymo2_df[["gn"]] <- gsub(x=chymo2_df[["description"]], pattern="^.* GN=(.+) PE=.*$", replacement="\\1")
chymo2_translated <- merge(chymo2_df, mtb_annotations, by.x="gn", by.y="gene")
pander::pander(sessionInfo())

R version 3.4.3 (2017-11-30)

**Platform:** x86_64-pc-linux-gnu (64-bit)

locale: LC_CTYPE=en_US.UTF-8, LC_NUMERIC=C, LC_TIME=en_US.UTF-8, LC_COLLATE=en_US.UTF-8, LC_MONETARY=en_US.UTF-8, LC_MESSAGES=en_US.UTF-8, LC_PAPER=en_US.UTF-8, LC_NAME=C, LC_ADDRESS=C, LC_TELEPHONE=C, LC_MEASUREMENT=en_US.UTF-8 and LC_IDENTIFICATION=C

attached base packages: stats, graphics, grDevices, utils, datasets, methods and base

other attached packages: ggplot2(v.2.2.1) and hpgltools(v.2017.10)

loaded via a namespace (and not attached): Rcpp(v.0.12.15), cellranger(v.1.1.0), pillar(v.1.2.1), compiler(v.3.4.3), plyr(v.1.8.4), base64enc(v.0.1-3), iterators(v.1.0.9), tools(v.3.4.3), digest(v.0.6.15), memoise(v.1.1.0), evaluate(v.0.10.1), tibble(v.1.4.2), gtable(v.0.2.0), rlang(v.0.2.0), foreach(v.1.4.4), commonmark(v.1.4), yaml(v.2.1.16), parallel(v.3.4.3), withr(v.2.1.1), stringr(v.1.3.0), knitr(v.1.20), roxygen2(v.6.0.1), xml2(v.1.2.0), devtools(v.1.13.5), rprojroot(v.1.3-2), grid(v.3.4.3), data.table(v.1.10.4-3), Biobase(v.2.38.0), R6(v.2.2.2), readxl(v.1.0.0), rmarkdown(v.1.8), pander(v.0.6.1), magrittr(v.1.5), scales(v.0.5.0), backports(v.1.1.2), codetools(v.0.2-15), htmltools(v.0.3.6), BiocGenerics(v.0.24.0), colorspace(v.1.3-2), stringi(v.1.1.6), lazyeval(v.0.2.1) and munsell(v.0.4.3)

message(paste0("This is hpgltools commit: ", get_git_commit()))
## If you wish to reproduce this exact build of hpgltools, invoke the following:
## > git clone http://github.com/abelew/hpgltools.git
## > git reset 04051c4dfae6fbfe706f11b1ea8cb454e1cafa2c
## R> packrat::restore()
## This is hpgltools commit: Sun Feb 25 23:26:32 2018 -0500: 04051c4dfae6fbfe706f11b1ea8cb454e1cafa2c
this_save <- paste0(gsub(pattern="\\.Rmd", replace="", x=rmd_file), "-v", ver, ".rda.xz")
message(paste0("Saving to ", this_save))
## Saving to 02_eval_result-v20170912.rda.xz
tmp <- sm(saveme(filename=this_save))
LS0tCnRpdGxlOiAiRXZhbHVhdGluZyBzb21lIE1TL01TIHBlcHRpZGUgc2VhcmNoIG1ldHJpY3MuIgphdXRob3I6ICJhdGIgYWJlbGV3QGdtYWlsLmNvbSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiBodG1sX2RvY3VtZW50OgogIGNvZGVfZG93bmxvYWQ6IHRydWUKICBjb2RlX2ZvbGRpbmc6IHNob3cKICBmaWdfY2FwdGlvbjogdHJ1ZQogIGZpZ19oZWlnaHQ6IDcKICBmaWdfd2lkdGg6IDcKICBoaWdobGlnaHQ6IGRlZmF1bHQKICBrZWVwX21kOiBmYWxzZQogIG1vZGU6IHNlbGZjb250YWluZWQKICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogIHRoZW1lOiByZWFkYWJsZQogIHRvYzogdHJ1ZQogIHRvY19mbG9hdDoKICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIHNtb290aF9zY3JvbGw6IGZhbHNlCi0tLQoKPHN0eWxlPgogIGJvZHkgLm1haW4tY29udGFpbmVyIHsKICAgIG1heC13aWR0aDogMTYwMHB4OwogIH0KPC9zdHlsZT4KCmBgYHtyIG9wdGlvbnMsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoaHBnbHRvb2xzKQp0dCA8LSBkZXZ0b29sczo6bG9hZF9hbGwoIn4vaHBnbHRvb2xzIikKa25pdHI6Om9wdHNfa25pdCRzZXQocHJvZ3Jlc3M9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZT1UUlVFLAogICAgICAgICAgICAgICAgICAgICB3aWR0aD05MCwKICAgICAgICAgICAgICAgICAgICAgZWNobz1UUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3I9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy53aWR0aD04LAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodD04LAogICAgICAgICAgICAgICAgICAgICAgZHBpPTk2KQpvbGRfb3B0aW9ucyA8LSBvcHRpb25zKGRpZ2l0cz00LAogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAga25pdHIuZHVwbGljYXRlLmxhYmVsPSJhbGxvdyIpCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9idyhiYXNlX3NpemU9MTApKQpzZXQuc2VlZCgxKQp2ZXIgPC0gIjIwMTcwOTEyIgpwcmV2aW91c19maWxlIDwtICIwMV9hbm5vdGF0aW9uLlJtZCIKCnRtcCA8LSB0cnkoc20obG9hZG1lKGZpbGVuYW1lPXBhc3RlMChnc3ViKHBhdHRlcm49IlxcLlJtZCIsIHJlcGxhY2U9IiIsIHg9cHJldmlvdXNfZmlsZSksICItdiIsIHZlciwgIi5yZGEueHoiKSkpKQoKcm1kX2ZpbGUgPC0gIjAyX2V2YWxfcmVzdWx0LlJtZCIKYGBgCgojIEV2YWx1YXRlIFByZXZpb3VzIFJlc3VsdDogdmVyc2lvbjogYHIgdmVyYAoKIyMgUmVhZGluZyBpbiB0aGUgZXhpc3RpbmcgcmVzdWx0CgpBcyBwZXIgWWFuJ3Mgc3VnZ2VzdGlvbiwgSSBhbSB1c2luZyB0aGUgZXhpc3RpbmcgcmVzdWx0cywgZm91bmQgaW4gdGhlIGZpbGU6CiIyMDE3XzA4MTFCcmlrZW5NdWx0aUNvbnNlbnN1cy54bHN4IgoKVW5mb3J0dW5hdGVseSwgdGhpcyB0YWJsZSBpcyBpbiBhIHBhcnRpY3VsYXJseSBzdHJhbmdlIGZvcm1hdDogaXQgaXMgYSB0cmlwbHkKbmVzdGVkIHRhYmxlIG9mIHRhYmxlcyBvZiB0YWJsZXMgaW4gb25lIHdvcmtzaGVldCBpbiBFeGNlbC4gIEF0IG9uZSBsZXZlbCwgdGhpcwppcyBhIHJlYWxseSBub3ZlbCBhbmQgaW50ZXJlc3RpbmcgdXNlIG9mIEV4Y2VsLCBidXQgb24gZXZlcnkgb3RoZXIgbGV2ZWwgaXQgaXMKc3ViLW9wdGltYWwuICBUaGUgcHJpbWFyeSByZWFzb24gaXQgaXMgc3ViLW9wdGltYWwgaXMgdGhhdCB0aGUgY29sdW1uIGhlYWRpbmdzCmVuZCB1cCBiZWluZyByZXBlYXRlZCBvbmNlIGZvciBldmVyeSBuZXcgc3ViLXRhYmxlICg4NzQgb2YgdGhlIGlubmVyIHRhYmxlcyBwbHVzCmhvd2V2ZXIgbWFueSBtaWRkbGUgdGFibGVzKSwgd2hpY2ggbWFrZXMgaXQgbmVhcmx5IHVucGFyc2VhYmxlLgoKSSBhbSBkaXNwbGVhc2VkIEkgbmVlZCB0byBkbyB0aGlzIGxldmVsIG9mIGRhdGEgd3JhbmdsaW5nLgoKVGhlIGZvbGxvd2luZyBmdW5jdGlvbiBpcyBteSBmaXJzdCBhdHRlbXB0IGF0IGhhbmRsaW5nIHRoaXMgcGFyc2luZyBwdXp6bGUuCgpGb3IgdGhlIGludGVyZXN0ZWQsIHRoZSBzdHJ1Y3R1cmUgaXM6CgoqICBhbGxfcHJvdGVpbl9ncm91cHMgaXMgYSBsaXN0IG9mIHR3byBlbGVtZW50czogZ3JvdXBfaWQgPT4gKCJzdW1tYXJ5IiwgImRhdGEiKQoqICBmb3IgZXZlcnkgcHJvdGVpbiBncm91cCBkaXNjb3ZlcmVkLCBvbmUgb2YgdGhlc2UgdHdvIGVsZW1lbnQgbGlzdHMgaXMgY3JlYXRlZC4KICAgICogIFRoZSBzdW1tYXJ5IHNsb3QgZ2V0cyB0aGUgb25lLXJvdyBzdW1tYXJ5IGZvciB0aGUgcHJvdGVpbiBncm91cCBmcm9tIHRoZSBleGNlbC4KKiAgVGhlIGRhdGEgc2xvdCBpcyBhIGxpc3Qgb2YgZ3JvdXBzOiBwcm90ZWluID0+ICgic3VtbWFyeSIsICJkYXRhIikKICAgICogIE9uY2UgYWdhaW4sIHRoZSBzdW1tYXJ5IGlzIHRoZSBzaW5nbGUgcm93IGZyb20gZXhjZWwuCiogIEFnYWluLCB0aGUgZGF0YSBzbG90IGlzIGEgbGlzdCBvZiBwZXB0aWRlczogcGVwdGlkZSA9PiBkYXRhLmZyYW1lKHBlcHRpZGVzKQogICAgKiAgRWFjaCBwZXB0aWRlIGRhdGEgZnJhbWUgY29udGFpbnMgYWxsIG9mIHRoZSBpbm5lci1tb3N0IHJvd3MgZGVzY3JpYmluZyB0aGUKICAgICAgIG9ic2VydmVkIHBlcHRpZGVzLgoKYGBge3IgZXh0cmFjdF9wZXB0aWRlc30KcGFyc2VfZGlmZmljdWx0X2V4Y2VsIDwtIGZ1bmN0aW9uKHhsc3hfZmlsZSkgewogIG9sZF9vcHRpb25zIDwtIG9wdGlvbnMoamF2YS5wYXJhbWV0ZXJzPSItWG14MjBHIikKICBtZXNzYWdlKHBhc3RlMCgiUmVhZGluZyAiLCB4bHN4X2ZpbGUpKQogIHJlc3VsdCA8LSByZWFkeGw6OnJlYWRfeGxzeChwYXRoPXhsc3hfZmlsZSwgc2hlZXQ9MSkKICBtZXNzYWdlKCJTdGFydGluZyBwYXJzZS4iKQogIGdyb3VwX2RhdGEgPC0gbGlzdCgpCgogICMjIFRoaXMgaXMgdGhlIHRvcGxldmVsIGhlYWRpbmcgZm9yIHRoZSBwcm90ZWluIGdyb3Vwcy4KICAjIyBEcm9wcGluZyB0aGUgZmlyc3QgY29sdW1uIGJlY2F1c2UgaXQgaXMganVzdCB1c2VkIGJ5IGV4Y2VsIHRvIGRlZmluZSB0aGUgdG9wLWxldmVsIHRhYmxlLgogIG91dGVyX2NvbG5hbWVzIDwtIGNvbG5hbWVzKHJlc3VsdCkKICBvdXRlcl9rZWVwZXJzIDwtICFncmVwbChwYXR0ZXJuPSJeWF9fIiwgeD1vdXRlcl9jb2xuYW1lcykKICBvdXRlcl9rZWVwZXJzWzFdIDwtIEZBTFNFICMjIEdldCByaWQgb2YgdGhlIG9uZSB0aGF0IHNheXMgJ0NoZWNrZWQnCiAgb3V0ZXJfY29sbmFtZXMgPC0gb3V0ZXJfY29sbmFtZXNbb3V0ZXJfa2VlcGVyc10KCiAgZm9yIChyIGluIDE6bnJvdyhyZXN1bHQpKSB7CiAgICAjIyBpZiAoKDEwMDAgJSUgcikgPT0gMCkgewogICAgIyMgICBtZXNzYWdlKHBhc3RlMCgiQ29tcGxldGVkICIsIHIsICIgcm93cy4iKSkKICAgICMjIH0KICAgIHJvdyA8LSBhcy5kYXRhLmZyYW1lKHJlc3VsdFtyLCBdKQogICAgcm93WywgaXMubmEocm93KV0gPC0gIiIKCiAgICAjIyBXaGVuIHRoZSBmaXJzdCBjb2x1bW4gaXMgZmFsc2UsIHRoZSBuZXh0IHJvdyB3aWxsIHN0YXJ0IGEgbmV3IHByb3RlaW4gZ3JvdXAuCiAgICBpZiAocm93WywgMV0gPT0gRkFMU0UpIHsKICAgICAgZ3JvdXBfaW5mb3JtYXRpb24gPC0gcm93W291dGVyX2tlZXBlcnNdCiAgICAgIGdyb3VwX2lkIDwtIGdyb3VwX2luZm9ybWF0aW9uWywgMV0KICAgICAgZ3JvdXBfZGF0YVtbZ3JvdXBfaWRdXSA8LSBsaXN0KAogICAgICAgICJzdW1tYXJ5IiA9IGdyb3VwX2luZm9ybWF0aW9uLAogICAgICAgICJkYXRhIiA9IGxpc3QoKSkKICAgICAgbmV4dAogICAgfQoKICAgICMjIFdoZW4gdGhlIDJuZCBjb2x1bW4gaXMgJ0NoZWNrZWQnLCB0aGVuIHRoaXMgZGVmaW5lcyBhIG5ldyBwcm90ZWluIGluIHRoZSBncm91cC4KICAgICMjIGRyb3BwaW5nIHRoZSBmaXJzdCAyIGNvbHVtbnMgYXMgdGhleSBhcmUgdXNlZCB0byBkZWZpbmUgdGhlIG1pZGRsZS10YWJsZS4KICAgIGlmIChyb3dbLCAyXSA9PSAiQ2hlY2tlZCIpIHsKICAgICAgZ3JvdXBfa2VlcGVycyA8LSAhZ3JlcGwocGF0dGVybj0iWF9fIiwgeD1uYW1lcyhyb3cpKQogICAgICBncm91cF9rZWVwZXJzIDwtIG5hbWVzKHJvdylbZ3JvdXBfa2VlcGVyc10KICAgICAgZ3JvdXBfY29sbmFtZXMgPC0gYXMuY2hhcmFjdGVyKHJvd1ssIGdyb3VwX2tlZXBlcnNdKQogICAgICBuZXh0CiAgICB9CgogICAgIyMgV2hlbiB0aGUgMm5kIGNvbHVtbiBpcyBGQUxTRSwgdGhlbiB0aGlzIGRlZmluZWQgYSBwcm90ZWluIGluIHRoZSBncm91cC4KICAgIGlmIChyb3dbLCAyXSA9PSBGQUxTRSkgewogICAgICBncm91cF9yb3cgPC0gcm93WywgZ3JvdXBfa2VlcGVyc10KICAgICAgY29sbmFtZXMoZ3JvdXBfcm93KSA8LSBncm91cF9jb2xuYW1lcwogICAgICBncm91cF9hY2Nlc3Npb24gPC0gZ3JvdXBfcm93W1siQWNjZXNzaW9uIl1dCiAgICAgIHByb3RlaW5fbGlzdCA8LSBsaXN0KAogICAgICAgICJzdW1tYXJ5IiA9IGdyb3VwX3JvdywKICAgICAgICAiZGF0YSIgPSBkYXRhLmZyYW1lKCkpCiAgICAgIGdyb3VwX2RhdGFbW2dyb3VwX2lkXV1bWyJkYXRhIl1dW1tncm91cF9hY2Nlc3Npb25dXSA8LSBwcm90ZWluX2xpc3QKICAgICAgbmV4dAogICAgfQoKICAgICMjIFdoZW4gdGhlIDNyZCBjb2x1bW4gaXMgJ0NoZWNrZWQnLCB0aGVuIHRoaXMgc3RhcnRzIGEgcGVwdGlkZSBkZWZpbml0aW9uCiAgICBpZiAocm93WywgM10gPT0gIkNoZWNrZWQiKSB7CiAgICAgICMjIGRyb3BwaW5nIHRoZSBmaXJzdCAzIGNvbHVtbnMsIHRoZXkgZGVmaW5lIHRoZSBpbm5lciB0YWJsZS4KICAgICAgcGVwdGlkZV9jb2xuYW1lcyA8LSByb3cKICAgICAgZ3JvdXBfZGF0YVtbZ3JvdXBfaWRdXVtbImRhdGEiXV1bW2dyb3VwX2FjY2Vzc2lvbl1dW1siZGF0YSJdXSA8LSBkYXRhLmZyYW1lKCkKICAgICAgbmV4dAogICAgfQoKICAgICMjIFdoZW4gdGhlIDNyZCBncm91cCBpcyBGQUxTRSwgdGhlbiB0aGlzIGFkZHMgYSBwZXB0aWRlLgogICAgaWYgKHJvd1ssIDNdID09IEZBTFNFKSB7CiAgICAgIHBlcHRpZGVfZGF0YSA8LSByb3cKICAgICAgY29sbmFtZXMocGVwdGlkZV9kYXRhKSA8LSBwZXB0aWRlX2NvbG5hbWVzCiAgICAgIGN1cnJlbnQgPC0gZ3JvdXBfZGF0YVtbZ3JvdXBfaWRdXVtbImRhdGEiXV1bW2dyb3VwX2FjY2Vzc2lvbl1dW1siZGF0YSJdXQogICAgICBuZXcgPC0gcmJpbmQoY3VycmVudCwgcGVwdGlkZV9kYXRhKQogICAgICBncm91cF9kYXRhW1tncm91cF9pZF1dW1siZGF0YSJdXVtbZ3JvdXBfYWNjZXNzaW9uXV1bWyJkYXRhIl1dIDwtIG5ldwogICAgfQogIH0gIyMgRW5kIGl0ZXJhdGluZyBvdmVyIGV2ZXIgcm93IG9mIHRoaXMgdW5ob2x5IHN0dXBpZCBkYXRhIHN0cnVjdHVyZS4KCiAgcmV0dXJuKGdyb3VwX2RhdGEpCn0KCiMjeGxzeF9maWxlIDwtICJwcmV2aW91c19yZXN1bHRzLzIwMTdfMDgxMUJyaWtlbk11bHRpQ29uc2Vuc3VzLnhsc3giCnhsc3hfZmlsZSA8LSAicHJldmlvdXNfcmVzdWx0cy8yMDE3XzExMDFCcmlrZW5NdWx0aV9QRV9QUEVfQ29tcGxldGUueGxzeCIKbGVzc19kaWZmaWN1bHQgPC0gcGFyc2VfZGlmZmljdWx0X2V4Y2VsKHhsc3hfZmlsZSkKYGBgCgpUaGUgZnVuY3Rpb24gYWJvdmUgaXMgbm90IGltbWVkaWF0ZWx5IGhlbHBmdWwsIGFzIGl0IG9ubHkgd3JhbmdsZXMgdGhlIGNyYXp5CmV4Y2VsIGludG8gYSBsZXNzLWNyYXp5IGRhdGEgc3RydWN0dXJlLCBidXQgaWYgd2Ugd2lzaCB0byBncmFwaCB0aGUgZGF0YSBvciBwbGF5CndpdGggaXQsIHdlIG5lZWQgdG8gZXh0cmFjdCB0aGUgZWxlbWVudHMgb2YgaW50ZXJlc3QuICBGb3IgdGhlIG1vbWVudCB0aG9zZSBhcmUKcHJpbWFyaWx5IHRoZSBwZXB0aWRlIGZyYWdtZW50cyBhbmQgd2hldGhlciB0aGV5IHdlcmUgZm91bmQgaW4gdGhlIHRyeXBzaW4KZGlnZXN0aW9ucywgY2h5bW90cnlwc2luIGRpZ2VzdGlvbnMsIG9yIGJvdGguCgojIyBFeHRyYWN0IHBlcHRpZGVzCgpUaGUgZm9sbG93aW5nIGJsb2NrIG9mIGNvZGUgaGFuZGxlcyB0aGF0IHRhc2suICBJdCBqdXN0IGl0ZXJhdGVzIG92ZXIgdGhlIG5ldwpkYXRhIHN0cnVjdHVyZSBhbmQgcHVsbHMgb3V0IHRoZSBwZXB0aWRlcy4KCmBgYHtyIGV4dHJhY3RfZGZ9CnByb3RlaW5fZGYgPC0gZGF0YS5mcmFtZSgpCnByb3RlaW5fbmFtZXMgPC0gYygpCmZvciAoZ3JvdXAgaW4gbmFtZXMobGVzc19kaWZmaWN1bHQpKSB7CiAgZ3JvdXAgPC0gYXMuY2hhcmFjdGVyKGdyb3VwKQogIHByb3RlaW5fZ3JvdXAgPC0gbGVzc19kaWZmaWN1bHRbW2dyb3VwXV1bWyJkYXRhIl1dCiAgcHJvdGVpbl9hY2Nlc3Npb25zIDwtIG5hbWVzKHByb3RlaW5fZ3JvdXApCiAgZm9yIChwcm90ZWluIGluIHByb3RlaW5fYWNjZXNzaW9ucykgewogICAgcHJvdGVpbl9uYW1lcyA8LSBjKHByb3RlaW5fbmFtZXMsIHByb3RlaW4pCiAgICBwcm90ZWluX3N1bW1hcnkgPC0gbGVzc19kaWZmaWN1bHRbW2dyb3VwXV1bWyJkYXRhIl1dW1twcm90ZWluXV1bWyJzdW1tYXJ5Il1dCiAgICBwcm90ZWluX2RmIDwtIHJiaW5kKHByb3RlaW5fZGYsIHByb3RlaW5fc3VtbWFyeSkKICAgIHBlcHRpZGVfZGYgPC0gbGVzc19kaWZmaWN1bHRbW2dyb3VwXV1bWyJkYXRhIl1dW1twcm90ZWluXV1bWyJkYXRhIl1dCiAgfQp9CmhlYWQocHJvdGVpbl9kZikKYGBgCgpUaGlzIGRhdGEgZnJhbWUgbm93IGNvbnRhaW5zIGV2ZXJ5dGhpbmcgd2UgYXJlIGxpa2VseSB0byB3YW50IHRvIHZpc3VhbGl6ZS4KCiMjIENsZWFuaW5nIHRoZSBkYXRhIGZyYW1lCgpVbmZvcnR1bmF0ZWx5LCB0aGlzIGRhdGEgc3RydWN0dXJlIGFsc28gc3VmZmVycyBmcm9tIGEgY291cGxlIHdlYWtuZXNzZXM6CgoxLiAgVGhlIHByb3RlaW4gSURzIGFyZSBub3QgdW5pcXVlIChiZWNhdXNlIG9mIHRoZSBncm91cHMpCjIuICBUaGUgY29sdW1uIG5hbWVzIGZyb20gRXhjZWwgYXJlIGluc2FuZSAocHVuY3R1YXRpb24sIHNwZWNpYWwgY2hhcmFjdGVycywgYW5kIHJlYWxseSBsb25nKQozLiAgRXhjZWwgbGVmdCB0aGUgbnVtZXJpYyBjb2x1bW5zIG9mIGRhdGEgYXMgY2hhcmFjdGVycywgbm90IG51bWJlcnMhCgpUaGVyZWZvcmUsIEkgd2lsbCB0YWtlIGEgbW9tZW50IHRvIGZpeCB0aGVzZSBwcm9ibGVtcyBiZWZvcmUgdHJ5aW5nIGFueSBwbG90cy4KCmBgYHtyIGRmX2NsZWFuaW5nfQojIyBTZXQgc29tZSByZWFzb25hYmxlIHJvdyBuYW1lcwpyb3duYW1lcyhwcm90ZWluX2RmKSA8LSBtYWtlLm5hbWVzKHByb3RlaW5fbmFtZXMsIHVuaXF1ZT1UUlVFKQojIyBTZXQgdGhlIGNvbHVtbiBuYW1lcyB0byBzb21ldGhpbmcgbGVnaWJsZS4KIyMgRWFjaCBsaW5lIGRlbm90ZXMgbXkgbWVudGFsIG9yZ2FuaXphdGlvbmFsIHVuaXQKbmV3X2NvbG5hbWVzIDwtIGMoIm1hc2NvdF9mZHIiLCAic2VxdWVzdF9mZHIiLCAibWFzdGVyIiwgImFjY2Vzc2lvbiIsICJkZXNjcmlwdGlvbiIsCiAgICAgICAgICAgICAgICAgICJtYXNjb3RfcSIsICJzZXF1ZXN0X3EiLCAiY292ZXJhZ2UiLCAiY29udGFtaW5hbnQiLAogICAgICAgICAgICAgICAgICAibnVtX3BlcHRpZGVzIiwgIm51bV9wc21zIiwgIm51bV91bmlxdWVfcGVwIiwgIm51bV9wcm90X2dyb3VwcyIsICJudW1fYWFzIiwKICAgICAgICAgICAgICAgICAgIm13IiwgInBpIiwgIm1hc2NvdF9zY29yZSIsICJzZXF1ZXN0X3Njb3JlIiwKICAgICAgICAgICAgICAgICAgIm51bV9wZXB0aWRlc19tYXNjb3QiLCAibnVtX3BlcHRpZGVzX3NlcXVlc3QiLAogICAgICAgICAgICAgICAgICAiY2h5bW9fZm91bmRfMSIsICJjaHltb19mb3VuZF8yIiwgInRyeXBfZm91bmRfMSIsICJ0cnlwX2ZvdW5kXzIiLAogICAgICAgICAgICAgICAgICAibW9kaWZpY2F0aW9ucyIpCmNvbG5hbWVzKHByb3RlaW5fZGYpIDwtIG5ld19jb2xuYW1lcwojIyBOb3cgbWFrZSBzdXJlIHRoZSBjb2x1bW5zIHdoaWNoIHNob3VsZCBiZSBudW1lcmljLCBhcmUgbnVtZXJpYy4KbnVtZXJpY19jb2xzIDwtIGMoIm1hc2NvdF9xIiwgInNlcXVlc3RfcSIsICJjb3ZlcmFnZSIsICJudW1fcGVwdGlkZXMiLCAibnVtX3BzbXMiLAogICAgICAgICAgICAgICAgICAibnVtX3VuaXF1ZV9wZXAiLCAibnVtX3Byb3RfZ3JvdXBzIiwgIm51bV9hYXMiLCAibXciLCAicGkiLAogICAgICAgICAgICAgICAgICAibWFzY290X3Njb3JlIiwgInNlcXVlc3Rfc2NvcmUiLAogICAgICAgICAgICAgICAgICAibnVtX3BlcHRpZGVzX21hc2NvdCIsICJudW1fcGVwdGlkZXNfc2VxdWVzdCIpCmZvciAoY29sIGluIG51bWVyaWNfY29scykgewogIHByb3RlaW5fZGZbW2NvbF1dIDwtIGFzLm51bWVyaWMocHJvdGVpbl9kZltbY29sXV0pCn0KIyMgRmluYWxseSwgc2V0IGEgJ3N0YXRlJyBjb2x1bW4gZGVzY3JpYmluZyB0aGUgNCBjb2x1bW5zIG9mIGNoeW1vdHJ5cHNpbi90cnlwc2luIGRpZ2VzdGlvbnMuCnByb3RlaW5fZGZbWyJzdGF0ZSJdXSA8LSAiYW1iaWd1b3VzIgoKIyMgSWYgc29tZXRoaW5nIGlzIGZvdW5kIGluIG9uZSBjaHltbyB0cnlwc2luLCBidXQgaW4gbmVpdGhlciB0cnlwc2luLCBzZXQgaXQgdG8gY2h5bW9fb25seS4KY2h5bW9faGl0cyA8LSAocHJvdGVpbl9kZltbImNoeW1vX2ZvdW5kXzEiXV0gPT0gIkhpZ2giIHwgcHJvdGVpbl9kZltbImNoeW1vX2ZvdW5kXzIiXV0gPT0gIkhpZ2giKSAmCiAgKHByb3RlaW5fZGZbWyJ0cnlwX2ZvdW5kXzEiXV0gIT0gIkhpZ2giICYgcHJvdGVpbl9kZltbInRyeXBfZm91bmRfMiJdXSAhPSAiSGlnaCIpCnByb3RlaW5fZGZbY2h5bW9faGl0cywgInN0YXRlIl0gPC0gImNoeW1vX29ubHkiCiMjIENvbnZlcnNlbHksIGlmIGZvdW5kIGluIHRyeXBzaW4gYnV0IG5vdCBjaHltbywgc2V0IGl0IHRvIHRyeXBfb25seS4KdHJ5cF9oaXRzIDwtIChwcm90ZWluX2RmW1sidHJ5cF9mb3VuZF8xIl1dID09ICJIaWdoIiB8IHByb3RlaW5fZGZbWyJ0cnlwX2ZvdW5kXzIiXV0gPT0gIkhpZ2giKSAmCiAgKHByb3RlaW5fZGZbWyJjaHltb19mb3VuZF8xIl1dICE9ICJIaWdoIiAmIHByb3RlaW5fZGZbWyJjaHltb19mb3VuZF8yIl1dICE9ICJIaWdoIikKcHJvdGVpbl9kZlt0cnlwX2hpdHMsICJzdGF0ZSJdIDwtICJ0cnlwX29ubHkiCgpjaHltb19kZiA8LSBwcm90ZWluX2RmW2NoeW1vX2hpdHMsIF0KdHJ5cF9kZiA8LSBwcm90ZWluX2RmW3RyeXBfaGl0cywgXQpgYGAKCiMjIE1ha2UgYSBjb3VwbGUgcGxvdHMKCk5vdyB0aGF0IHRoZSBkYXRhIGhhcyBiZWVuIHdyYW5nbGVkLCB3ZSBjYW4gdXNlIHRoZSB2YXJpb3VzIFIgcGxvdHRlcnMgdG8gbG9vawphdCB0aGUgZGF0YSByZWxhdGl2ZWx5IHF1aWNrbHkgYW5kIGVhc2lseS4KCmBgYHtyIHBsb3RzfQpsaWJyYXJ5KGdncGxvdDIpCmNvdmVyYWdlX3dydF9waSA8LSBnZ3Bsb3QocHJvdGVpbl9kZiwgYWVzX3N0cmluZyh4PSJwaSIsIHk9ImNvdmVyYWdlIiwgY29sb3VyPSJzdGF0ZSIpKSArCiAgZ2VvbV9wb2ludChhbHBoYT0wLjYsIHNpemU9MikgKwogIGdlb21fZGVuc2l0eV8yZCgpCgpjb3ZlcmFnZV93cnRfbXcgPC0gZ2dwbG90KHByb3RlaW5fZGYsIGFlc19zdHJpbmcoeD0ibXciLCB5PSJjb3ZlcmFnZSIsIGNvbG91cj0ic3RhdGUiKSkgKwogIGdlb21fcG9pbnQoYWxwaGE9MC42LCBzaXplPTIpICsKICBnZW9tX2RlbnNpdHlfMmQoKQoKcHJvdGVpbl9kZiRsb2dtYXNjb3QgPC0gbG9nMihwcm90ZWluX2RmJG1hc2NvdF9zY29yZSkKc2NvcmVzX2J5X3N0YXRlIDwtIGdncGxvdChwcm90ZWluX2RmLCBhZXNfc3RyaW5nKHg9InN0YXRlIiwgeT0ibG9nbWFzY290IikpICsKICBnZW9tX2JveHBsb3QoYWVzX3N0cmluZyhmaWxsPSJzdGF0ZSIpKQpgYGAKCiMjIyBDb3ZlcmFnZSB2cy4gcEkKCllhbiBzdWdnZXN0ZWQgdGhhdCBwZXB0aWRlcyB3aXRoIGxvdy1wSXMgd291bGQgYmUgbGVzcyBjb3ZlcmVkLCBlc3BlY2lhbGx5IGluCnRoZSBjaHltb3RyeXBzaW4gc2FtcGxlcy4KCmBgYHtyIGNvdl9waX0KcHAoZmlsZT0iaW1hZ2VzL2NvdmVyYWdlX3dydF9waS5wbmciKQpjb3ZlcmFnZV93cnRfcGkKZGV2Lm9mZigpCiMjIFRvIG1lLCBpdCBsb29rcyBsaWtlIHRoZXJlIG1pZ2h0IGJlIHNvbWV0aGluZyB0byB0aGF0IGlkZWEuCmBgYAoKIyMjIENvdmVyYWdlIHZzLiBNb2xlY3VsYXIgV2VpZ2h0CgpNYXliZSB0aGVyZSBpcyBhIHRyZW5kIG9mIGNvdmVyYWdlIGFuZCBtb2xlY3VsYXIgd2VpZ2h0IGJ5IHNhbXBsZS10eXBlLgoKYGBge3IgY292X213fQpwcChmaWxlPSJpbWFnZXMvY292ZXJhZ2Vfd3J0X213LnBuZyIpCmNvdmVyYWdlX3dydF9tdwpkZXYub2ZmKCkKIyMgSXQgbG9va3MgdG8gbWUgdGhhdCB0aGUgdHJ5cHNpbiBpcyBzbGlnaHRseSBiZXR0ZXIgdGhhbiBjaHltb3RyeXBzaW4uCiMjIEl0IGlzIG5vdCBjbGVhciBpbiB0aGUgZXhjZWwsIGJ1dCBJIHByZXN1bWUgdGhpcyBpcyBpbiBrRGEuCmBgYAoKIyMjIE1hc2NvdCBzY29yZXMKCkEgcXVpY2sgYm94cGxvdCBzaG93aW5nIG1hc2NvdCBzY29yZXMgYnkgd2hhdCB3YXMgb2JzZXJ2ZWQuCgpgYGB7ciBtYXNjb3R9CnNjb3Jlc19ieV9zdGF0ZQpgYGAKCiMjIEV2YWx1YXRlIHdoYXQgcHJvdGVpbnMgd2VyZSBmb3VuZCBieSBtYXNjb3QgZm9yIHRoZSBjaHltbzEgZGlnZXN0aW9uCgpgYGB7ciBjaHltbzF9CmtlZXBlcnMgPC0gcHJvdGVpbl9kZltbImNoeW1vX2ZvdW5kXzEiXV0gPT0gIkhpZ2giCmNoeW1vMV9kZiA8LSBwcm90ZWluX2RmW2tlZXBlcnMsIF0KZGltKGNoeW1vMV9kZikKIyMgdW5mb3J0dW5hdGVseSwgdGhpcyBkb2VzIG5vdCB0ZWxsIG1lIGV4YWN0bHkgd2hpY2ggc3BlY3RyYSB3ZXJlIG9ic2VydmVkIHRvCiMjIHN1cHBvcnQgdGhlc2UgMjc0IHByb3RlaW4uICBIb3dldmVyLCBJIHNob3VsZCBiZSBhYmxlIHRvIGNyb3NzIHJlZmVyZW5jZSB0aGUKIyMgcHJvdGVpbnMgSSBmaW5kIGFnYWluc3QgdGhlc2UuCmNoeW1vX2FjY2Vzc2lvbnMgPC0gY2h5bW8xX2RmW1siYWNjZXNzaW9uIl1dCgpnZXRfeHJlZiA8LSBmdW5jdGlvbihhY2Nlc3Npb24pIHsKICByZXF1ZXN0X3VybCA8LSBwYXN0ZTAoImh0dHA6Ly93d3cudW5pcHJvdC5vcmcvdW5pcHJvdC8iLCBhY2Nlc3Npb24sICIueG1sIikKICBkYXRhIDwtIHhtbDI6OnJlYWRfeG1sKHJlcXVlc3RfdXJsKQogIGRhdGFfbGlzdCA8LSB4bWwyOjphc19saXN0KGRhdGEpCiAgZ2VuZV9uYW1lcyA8LSBkYXRhX2xpc3RbWyJlbnRyeSJdXVtbImdlbmUiXV0KICBuYW1lcyA8LSByZXNoYXBlMjo6bWVsdChnZW5lX25hbWVzKQogIG5hbWVzIDwtIG5hbWVzW1sidmFsdWUiXV0KICByZXR1cm4obmFtZXMpCn0KCmFjY19kZiA8LSBkYXRhLmZyYW1lKCkKZm9yIChhY2MgaW4gY2h5bW9fYWNjZXNzaW9ucykgewogIGhpdHMgPC0gdHJ5KGdldF94cmVmKGFjYyksIHNpbGVudD1UUlVFKQogIGlmIChjbGFzcyhoaXRzKSAhPSAidHJ5LWVycm9yIikgewogICAgZm9yIChoIGluIGhpdHMpIHsKICAgICAgcm93IDwtIGMoYWNjLCBoKQogICAgICBhY2NfZGYgPC0gcmJpbmQoYWNjX2RmLCByb3cpCiAgICB9CiAgfQp9CmNvbG5hbWVzKGFjY19kZikgPC0gYygidW5pcHJvdCIsICJhbGlhcyIpCmhlYWQoYWNjX2RmKQojIyBUaGlzIG5vdyBjb250YWlucyBvbmx5IHRob3NlIElEcyB3aGljaCB3ZXJlIGluIHRoZSBjaHltbyBkaWdlc3Rpb24uCgpkaW0oY2h5bW8xX2RmKQojIyBTbyBub3cgbGV0cyBnZXQgdGhlIHNldCBvZiBhbGwgUnZJRHMKbXRiX2lkcyA8LSByZWFkLnRhYmxlKCJyZWZlcmVuY2UvbXRiX2lkcy50eHQiKQojIyBBbmQgZXh0cmFjdCBmcm9tIHRoZSBhY2Nlc3Npb25zIGxpc3QgdGhvc2Ugd2hpY2ggbWF0Y2gKbXRiX2FjY19kZiA8LSBtZXJnZShtdGJfaWRzLCBhY2NfZGYsIGJ5Lng9IlYxIiwgYnkueT0iYWxpYXMiKQpkaW0obXRiX2FjY19kZikKIyMgU28gd2UgbG9zdCA3ICgyNzQgdG8gMjY3KSwgSSBjYW4gYWNjZXB0IHRoYXQuCmNvbG5hbWVzKG10Yl9hY2NfZGYpIDwtIGMoInJ2X2lkIiwgInVuaXByb3QiKQoKY2h5bW9fdHJhbnNsYXRlZCA8LSBtZXJnZShjaHltbzFfZGYsIG10Yl9hY2NfZGYsIGJ5Lng9ImFjY2Vzc2lvbiIsIGJ5Lnk9InVuaXByb3QiLCBhbGwueD1UUlVFKQojIyBPaywgc28gbm93IEkgY2FuIHRyYW5zbGF0ZSBiZXR3ZWVuIHRoZSB2YXJpb3VzIElEcy4KCmhlYWQoY2h5bW8xX2RmKQpoZWFkKG10Yl9hbm5vdGF0aW9ucykKY2h5bW9fdDIgPC0gbWVyZ2UoY2h5bW8xX2RmLCBtdGJfYW5ub3RhdGlvbnMsIGJ5Lng9KQoKCmNoeW1vMl9kZiA8LSBjaHltbzFfZGYKY2h5bW8yX2RmW1siZ24iXV0gPC0gZ3N1Yih4PWNoeW1vMl9kZltbImRlc2NyaXB0aW9uIl1dLCBwYXR0ZXJuPSJeLiogR049KC4rKSBQRT0uKiQiLCByZXBsYWNlbWVudD0iXFwxIikKY2h5bW8yX3RyYW5zbGF0ZWQgPC0gbWVyZ2UoY2h5bW8yX2RmLCBtdGJfYW5ub3RhdGlvbnMsIGJ5Lng9ImduIiwgYnkueT0iZ2VuZSIpCmBgYAoKYGBge3Igc2F2ZW1lfQpwYW5kZXI6OnBhbmRlcihzZXNzaW9uSW5mbygpKQptZXNzYWdlKHBhc3RlMCgiVGhpcyBpcyBocGdsdG9vbHMgY29tbWl0OiAiLCBnZXRfZ2l0X2NvbW1pdCgpKSkKdGhpc19zYXZlIDwtIHBhc3RlMChnc3ViKHBhdHRlcm49IlxcLlJtZCIsIHJlcGxhY2U9IiIsIHg9cm1kX2ZpbGUpLCAiLXYiLCB2ZXIsICIucmRhLnh6IikKbWVzc2FnZShwYXN0ZTAoIlNhdmluZyB0byAiLCB0aGlzX3NhdmUpKQp0bXAgPC0gc20oc2F2ZW1lKGZpbGVuYW1lPXRoaXNfc2F2ZSkpCmBgYAo=