Introduction
Najib asked for an initial conversion of the data provided by Maria
Adelaida into the various formats expected by dbgap. He sent me two
sheets provided by her: “20210115_EXP_ESPECIAL_TMRC3.xlsx” and
“CODEBOOK_EXP_ESPECIAL_TMRC3_20210115.xlsx”. The first provides the data
and the second for formatting.
In addition, we will require some of the data in the shared sample
sheet.
With that in mind, here are our inputs: 1. The metadata sheet of
patients. 2. The meta metadata sheet, the ‘codebook’. 3. The tmrc3
sample sheet. I am likely to add a worksheet to it to contain the meta
metadata.
Here are the expected outputs: 1. SubjectConsent_DS.txt: Tab
separated, containing SUBJECT_ID, CONSENT, SEX, SUBJECT_SOURCE, and
SUBJECT_SOURCE_ID In my current setup, this is only the patient ID, 1,
sex (1,2,NA or whatever), TMRC3, and the patient ID repeated. 2.
SSM_DS.txt: Tab separated, containing SUBJECT_ID and SAMPLE_ID. This is
the cross reference between the sample sheet and patient IDs, so it will
require some sort of merge between the tmrc3 sample sheet and the
EXP_ESPECIAL worksheet. 3. SubjectPhenotypes_DS.txt: This is pretty much
everything else from the EXP_ESPECIAL sheet with at least the first
column needing to be renamed to ‘SUBJECT_ID’ and probably the birthday
removed. 4. SampleAttributes_DS.txt: This is pretty much everything else
from the sample sheet. A bunch of columns will need to be removed. 5.
SubjectConsent_DD.xlsx: xlsx file, the meta-metadata for #1 above. I
think we can just create this de-novo and leave it unmodified because it
is quite small. 6. SSM_DD.xlsx: Ditto. This just explains the columns
from #2 above and is therefore even smaller. 7.
SubjectPhenotypes_DD.xlsx: This is the CODEBOOK from above and will
likely need some changes, but I think they are pretty minor. 8.
SampleAttributes_DD.xlsx: I want to add this to the tmrc3 sample sheet
and use it to define which columns to pull from it.
Reading existing
data
rundate <- format(Sys.Date(), format = "%Y%m%d")
phenotype_file <- "inputs/202401/20220721_EXP_ESPECIAL_TMRC3_V3.xlsx"
phenotype_meta <- "inputs/202401/CODEBOOK_EXP_ESPECIAL_TMRC3_VERSION_3_20220512.xlsx"
sample_file <- "inputs/202401/tmrc3_samples_pruned.xlsx"
sample_meta <- "templates/SampleAttributes_DD.xlsx"
subject_phenotypes <- openxlsx::read.xlsx(phenotype_file)
subject_meta <- openxlsx::read.xlsx(phenotype_meta)
sample_attributes <- openxlsx::read.xlsx(sample_file)
sample_meta <- openxlsx::read.xlsx(sample_meta)
created <- dir.create(glue("outputs/{rundate}"))
## Warning in dir.create(glue("outputs/{rundate}")): 'outputs/20240208' already exists
Sanitize column names
slightly.
There are a few things which are required by dbGap which I can
trivially change here.
## Sanitize the column names a little.
colnames(subject_phenotypes) <- toupper(colnames(subject_phenotypes))
subject_meta[["VARNAME"]] <- toupper(subject_meta[["VARNAME"]])
colnames(sample_attributes) <- toupper(colnames(sample_attributes))
colnames(sample_attributes) <- gsub(pattern = "\\.+",
replacement = "_",
x = colnames(sample_attributes))
colnames(sample_attributes) <- gsub(pattern = "\\)|\\(|\\[|\\]|,|-|/|%|'|´|\\s+",
replacement = "",
x = colnames(sample_attributes),
perl = TRUE)
subject_colname_substitutions <- list(
"CODIGO_PACIENTE" = "SUBJECT_ID",
"EB_LC_SEXO" = "SEX",
"TUBE_LABEL_ORIGIN" = "SUBJECT_ID",
"SAMPLE_NAME" = "SAMPLE_ID",
"TMRC_IDENTIFIER" = "TMRC_ID"
)
for (i in 1:length(subject_colname_substitutions)) {
from <- names(subject_colname_substitutions)[i]
to <- subject_colname_substitutions[[i]]
colnames(subject_phenotypes) <- gsub(pattern = from, replacement = to,
x = colnames(subject_phenotypes))
colnames(sample_attributes) <- gsub(pattern = from, replacement = to,
x = colnames(sample_attributes))
subject_meta[["VARNAME"]] <- gsub(pattern = from, replacement = to,
x = subject_meta[["VARNAME"]])
}
Sanity check
Now that I have made some changes to the metadata, let us attempt to
make certain that they still match. This primarily refers to the column
names of the subject_phenotype file and the VARNAME column from the
subject metadata file.
misses <- subject_meta[["VARNAME"]] != colnames(subject_phenotypes)
summary(misses)
## Mode FALSE
## logical 63
if (sum(misses) > 0) {
stop("There is a mismatch between the metadata and column names.")
}
Filter out samples
which were not sequenced.
We only want those samples for which we have extant TMRC IDs. Thus,
filter the sample_attributes for the samples that have been given tmrc
IDs for now.
useful_idx <- !is.na(sample_attributes[["TMRC_ID"]])
sample_attributes <- sample_attributes[useful_idx, ]
Create the
SubjectConsent_DS.txt
Creating the SubjectConsent_DS.txt should be the easiest, just pull
the ID and SEX columns and fill in the rest.
SubjectConsent_DS <- subject_phenotypes[, c("SUBJECT_ID", "SEX")]
SubjectConsent_DS[["CONSENT"]] <- 1
SubjectConsent_DS[["SUBJECT_SOURCE"]] <- "TMRC3"
SubjectConsent_DS[["SUBJECT_SOURCE_ID"]] <- SubjectConsent_DS[["SUBJECT_ID"]]
## And reorder it
column_order <- c("SUBJECT_ID", "CONSENT", "SEX", "SUBJECT_SOURCE", "SUBJECT_SOURCE_ID")
SubjectConsent_DS <- SubjectConsent_DS[, column_order]
readr::write_tsv(x = SubjectConsent_DS,
file = glue("outputs/{rundate}/{rundate}-SubjectConsent_DS.txt"))
Create the
SubjectConsent_DD.xlsx
This file should be unchanged from the template, so we will read it
in and immediately write it back out with a blank line in between in
case we do in fact need to change something later.
SubjectConsent_DD <- openxlsx::read.xlsx("templates/SubjectConsent_DD.xlsx")
openxlsx::write.xlsx(x = SubjectConsent_DD,
file = glue("outputs/{rundate}/{rundate}-SubjectConsent_DD.xlsx"))
Create the
SSM_DS.txt
The SSM_DS.txt will be more difficult, it requires a merge between
sheets…
sample_attributes[["SUBJECT_ID"]] <- gsub(pattern = "^su", replacement = "SU",
x = sample_attributes[["SUBJECT_ID"]])
SSM_DS <- merge(SubjectConsent_DS, sample_attributes, by = "SUBJECT_ID")
wanted_columns <- c("SUBJECT_ID", "SAMPLE_ID")
SSM_DS <- SSM_DS[, wanted_columns]
readr::write_tsv(x = SSM_DS, file = glue("outputs/{rundate}/{rundate}-SSM_DS.txt"))
Create the
SSM_DD.xlsx
Once again, we will assume that copying this from the template will
prove sufficient; but will perform explicit read/write steps in case we
need to change anything.
SSM_DD <- openxlsx::read.xlsx("templates/SSM_DD.xlsx")
openxlsx::write.xlsx(x = SSM_DD, file = glue("outputs/{rundate}/{rundate}-SSM_DD.xlsx"))
Create the
SubjectPhenotypes_DD
Normally, I would move the english-translated columns to the
defaults; but we do not have a translated version of this at this
time.
## I do not think I need to do anything else to the metadata file at this time.
SubjectPhenotypes_DD <- subject_meta
Juggle the
columns
I cannot run the following block at this time because the current
file has not been translated.
## We will need to more VARNAME_ENG to VARNAME
SubjectPhenotypes_DD[["VARNAME"]] <- SubjectPhenotypes_DD[["VARNAME_ENG"]]
SubjectPhenotypes_DD[["VARNAME_ENG"]] <- NULL
## Ditto for VARDESC and TYPE
SubjectPhenotypes_DD[["VARDESC"]] <- SubjectPhenotypes_DD[["VARDESC_ENG"]]
SubjectPhenotypes_DD[["VARDESC_ENG"]] <- NULL
SubjectPhenotypes_DD[["TYPE"]] <- SubjectPhenotypes_DD[["TYPE_ENG"]]
SubjectPhenotypes_DD[["TYPE_ENG"]] <- NULL
## Having done that, we need to set the column names of the SubjectPhenotypes_DS to the new
## VARNAME column.
Write out the
SubjectPhenotypes_DD
starting_ds_colnames <- SubjectPhenotypes_DD[["VARNAME"]]
openxlsx::write.xlsx(x = SubjectPhenotypes_DD,
file = glue("outputs/{rundate}/{rundate}-SubjectPhenotypes_DD.xlsx"))
Create the
SubjectPhenotypes_DS
The SubjectPhenotypes_DS is just the original subject_phenotypes with
some columns blacklisted.
subject_phenotypes_blacklist <- c("EB_LC_FECHA_NACIMIENTO", "SEX")
SubjectPhenotypes_DS <- subject_phenotypes
colnames(SubjectPhenotypes_DS) <- starting_ds_colnames
for (i in subject_phenotypes_blacklist) {
SubjectPhenotypes_DS[[i]] <- NULL
meta_keepers <- subject_meta[["VARNAME"]] != i
subject_meta <- subject_meta[meta_keepers, ]
}
readr::write_tsv(x = SubjectPhenotypes_DS,
file = glue("outputs/{rundate}/{rundate}-SubjectPhenotypes_DS.txt"))
Create the
SampleAttributes_DS
The SampleAttributes_DS will require some more work: 1. Read in the
SampleAttributes_DD and keep only the columns defined in it. 2. Recast
the data to ensure they are pure text.
Currently that is all we are doing, so it doesn’t really require very
much.
I do need to change the column ‘FINAL_OUTCOME’ to
‘CLINICAL_OUTCOME’
colnames(sample_attributes) <- gsub(x = colnames(sample_attributes), pattern = "FINAL_OUTCOME",
replacement = "CLINICAL_OUTCOME")
SampleAttributes_DS <- sample_attributes
kept_columns <- sample_meta[["VARNAME"]]
kept_columns[! kept_columns %in% colnames(SampleAttributes_DS)]
## character(0)
SampleAttributes_DS <- SampleAttributes_DS[, kept_columns]
for (i in 1:ncol(SampleAttributes_DS)) {
SampleAttributes_DS[[i]] <- as.character(SampleAttributes_DS[[i]])
}
readr::write_tsv(x = SampleAttributes_DS,
file = glue("outputs/{rundate}/{rundate}-SampleAttributes_DS.txt"))
Write the
SampleAttributes_DD
Once again, the DD file is expected to be identical to our
template.
SampleAttributes_DD <- sample_meta
openxlsx::write.xlsx(x = SampleAttributes_DD,
file = glue("outputs/{rundate}/{rundate}-SampleAttributes_DD.xlsx"))
LS0tCnRpdGxlOiAiQ29udmVydGluZyBtZXRhZGF0YSBmb3IgZGJnYXAhIgphdXRob3I6ICJhdGIgYWJlbGV3QGdtYWlsLmNvbSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZmlnX2NhcHRpb246IHRydWUKICAgIGZpZ19oZWlnaHQ6IDcKICAgIGZpZ193aWR0aDogNwogICAgaGlnaGxpZ2h0OiB6ZW5idXJuCiAgICBrZWVwX21kOiBmYWxzZQogICAgbW9kZTogc2VsZmNvbnRhaW5lZAogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgdGhlbWU6IHJlYWRhYmxlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQogIHJtZGZvcm1hdHM6OnJlYWR0aGVkb3duOgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDogcGFnZWQKICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICBmaWdfaGVpZ2h0OiA3CiAgICBmaWdfd2lkdGg6IDcKICAgIGhpZ2hsaWdodDogemVuYnVybgogICAgd2lkdGg6IDMwMAogICAga2VlcF9tZDogZmFsc2UKICAgIG1vZGU6IHNlbGZjb250YWluZWQKICAgIHRvY19mbG9hdDogdHJ1ZQogIEJpb2NTdHlsZTo6aHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZmlnX2NhcHRpb246IHRydWUKICAgIGZpZ19oZWlnaHQ6IDcKICAgIGZpZ193aWR0aDogNwogICAgaGlnaGxpZ2h0OiB6ZW5idXJuCiAgICBrZWVwX21kOiBmYWxzZQogICAgbW9kZTogc2VsZmNvbnRhaW5lZAogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KYm9keSwgdGQgewogIGZvbnQtc2l6ZTogMTZweDsKfQpjb2RlLnJ7CiAgZm9udC1zaXplOiAxNnB4Owp9CnByZSB7CiBmb250LXNpemU6IDE2cHgKfQpib2R5IC5tYWluLWNvbnRhaW5lciB7CiAgbWF4LXdpZHRoOiAxNjAwcHg7Cn0KPC9zdHlsZT4KCmBgYHtyIG9wdGlvbnMsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoaHBnbHRvb2xzKQpsaWJyYXJ5KGdsdWUpCmxpYnJhcnkocmV0aWN1bGF0ZSkKdHQgPC0gdHJ5KGRldnRvb2xzOjpsb2FkX2FsbCgifi9ocGdsdG9vbHMiKSkKa25pdHI6Om9wdHNfa25pdCRzZXQoCiAgcHJvZ3Jlc3MgPSBUUlVFLCB2ZXJib3NlID0gVFJVRSwgd2lkdGggPSA5MCwgZWNobyA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlcnJvciA9IFRSVUUsIGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA4LCBmaWcucmV0aW5hID0gMiwKICBmaWcucG9zID0gInQiLCBmaWcuYWxpZ24gPSAiY2VudGVyIiwgZHBpID0gaWYgKGtuaXRyOjppc19sYXRleF9vdXRwdXQoKSkgNzIgZWxzZSAzMDAsCiAgb3V0LndpZHRoID0gIjEwMCUiLCBkZXYgPSAicG5nIiwKICBkZXYuYXJncyA9IGxpc3QocG5nID0gbGlzdCh0eXBlID0gImNhaXJvLXBuZyIpKSkKb2xkX29wdGlvbnMgPC0gb3B0aW9ucyhkaWdpdHMgPSA0LAogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICBrbml0ci5kdXBsaWNhdGUubGFiZWwgPSAiYWxsb3ciKQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfYncoYmFzZV9zaXplID0gMTIpKQp2ZXIgPC0gIjIwMjMwNSIKcHJldmlvdXNfZmlsZSA8LSAiIgp2ZXIgPC0gZm9ybWF0KFN5cy5EYXRlKCksICIlWSVtJWQiKQoKIyN0bXAgPC0gc20obG9hZG1lKGZpbGVuYW1lPXBhc3RlMChnc3ViKHBhdHRlcm49IlxcLlJtZCIsIHJlcGxhY2U9IiIsIHg9cHJldmlvdXNfZmlsZSksICItdiIsIHZlciwgIi5yZGEueHoiKSkpCnJtZF9maWxlIDwtICJpbmRleC5SbWQiCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCk5hamliIGFza2VkIGZvciBhbiBpbml0aWFsIGNvbnZlcnNpb24gb2YgdGhlIGRhdGEgcHJvdmlkZWQgYnkgTWFyaWEgQWRlbGFpZGEKaW50byB0aGUgdmFyaW91cyBmb3JtYXRzIGV4cGVjdGVkIGJ5IGRiZ2FwLiAgSGUgc2VudCBtZSB0d28gc2hlZXRzIHByb3ZpZGVkCmJ5IGhlcjogICIyMDIxMDExNV9FWFBfRVNQRUNJQUxfVE1SQzMueGxzeCIgYW5kCiJDT0RFQk9PS19FWFBfRVNQRUNJQUxfVE1SQzNfMjAyMTAxMTUueGxzeCIuICBUaGUgZmlyc3QgcHJvdmlkZXMgdGhlIGRhdGEgYW5kCnRoZSBzZWNvbmQgZm9yIGZvcm1hdHRpbmcuCgpJbiBhZGRpdGlvbiwgd2Ugd2lsbCByZXF1aXJlIHNvbWUgb2YgdGhlIGRhdGEgaW4gdGhlIHNoYXJlZCBzYW1wbGUgc2hlZXQuCgpXaXRoIHRoYXQgaW4gbWluZCwgaGVyZSBhcmUgb3VyIGlucHV0czoKMS4gIFRoZSBtZXRhZGF0YSBzaGVldCBvZiBwYXRpZW50cy4KMi4gIFRoZSBtZXRhIG1ldGFkYXRhIHNoZWV0LCB0aGUgJ2NvZGVib29rJy4KMy4gIFRoZSB0bXJjMyBzYW1wbGUgc2hlZXQuICBJIGFtIGxpa2VseSB0byBhZGQgYSB3b3Jrc2hlZXQgdG8gaXQgdG8gY29udGFpbgogICAgdGhlIG1ldGEgbWV0YWRhdGEuCgpIZXJlIGFyZSB0aGUgZXhwZWN0ZWQgb3V0cHV0czoKMS4gIFN1YmplY3RDb25zZW50X0RTLnR4dDogIFRhYiBzZXBhcmF0ZWQsIGNvbnRhaW5pbmcgU1VCSkVDVF9JRCwgQ09OU0VOVCwKICAgIFNFWCwgU1VCSkVDVF9TT1VSQ0UsIGFuZCBTVUJKRUNUX1NPVVJDRV9JRAogIEluIG15IGN1cnJlbnQgc2V0dXAsIHRoaXMgaXMgb25seSB0aGUgcGF0aWVudCBJRCwgMSwgc2V4ICgxLDIsTkEgb3IKICB3aGF0ZXZlciksIFRNUkMzLCBhbmQgdGhlIHBhdGllbnQgSUQgcmVwZWF0ZWQuCjIuICBTU01fRFMudHh0OiBUYWIgc2VwYXJhdGVkLCBjb250YWluaW5nIFNVQkpFQ1RfSUQgYW5kIFNBTVBMRV9JRC4gIFRoaXMgaXMKICAgIHRoZSBjcm9zcyByZWZlcmVuY2UgYmV0d2VlbiB0aGUgc2FtcGxlIHNoZWV0IGFuZCBwYXRpZW50IElEcywgc28gaXQgd2lsbAogICAgcmVxdWlyZSBzb21lIHNvcnQgb2YgbWVyZ2UgYmV0d2VlbiB0aGUgdG1yYzMgc2FtcGxlIHNoZWV0IGFuZCB0aGUKICAgIEVYUF9FU1BFQ0lBTCB3b3Jrc2hlZXQuCjMuICBTdWJqZWN0UGhlbm90eXBlc19EUy50eHQ6IFRoaXMgaXMgcHJldHR5IG11Y2ggZXZlcnl0aGluZyBlbHNlIGZyb20gdGhlCiAgICBFWFBfRVNQRUNJQUwgc2hlZXQgd2l0aCBhdCBsZWFzdCB0aGUgZmlyc3QgY29sdW1uIG5lZWRpbmcgdG8gYmUgcmVuYW1lZAogICAgdG8gJ1NVQkpFQ1RfSUQnIGFuZCBwcm9iYWJseSB0aGUgYmlydGhkYXkgcmVtb3ZlZC4KNC4gIFNhbXBsZUF0dHJpYnV0ZXNfRFMudHh0OiBUaGlzIGlzIHByZXR0eSBtdWNoIGV2ZXJ5dGhpbmcgZWxzZSBmcm9tIHRoZQogICAgc2FtcGxlIHNoZWV0LiAgQSBidW5jaCBvZiBjb2x1bW5zIHdpbGwgbmVlZCB0byBiZSByZW1vdmVkLgo1LiAgU3ViamVjdENvbnNlbnRfREQueGxzeDogeGxzeCBmaWxlLCB0aGUgbWV0YS1tZXRhZGF0YSBmb3IgIzEgYWJvdmUuICBJCiAgICB0aGluayB3ZSBjYW4ganVzdCBjcmVhdGUgdGhpcyBkZS1ub3ZvIGFuZCBsZWF2ZSBpdCB1bm1vZGlmaWVkIGJlY2F1c2UgaXQgaXMKICAgIHF1aXRlIHNtYWxsLgo2LiAgU1NNX0RELnhsc3g6IERpdHRvLiAgVGhpcyBqdXN0IGV4cGxhaW5zIHRoZSBjb2x1bW5zIGZyb20gIzIgYWJvdmUgYW5kIGlzCiAgICB0aGVyZWZvcmUgZXZlbiBzbWFsbGVyLgo3LiAgU3ViamVjdFBoZW5vdHlwZXNfREQueGxzeDogVGhpcyBpcyB0aGUgQ09ERUJPT0sgZnJvbSBhYm92ZSBhbmQgd2lsbAogICAgbGlrZWx5IG5lZWQgc29tZSBjaGFuZ2VzLCBidXQgSSB0aGluayB0aGV5IGFyZSBwcmV0dHkgbWlub3IuCjguICBTYW1wbGVBdHRyaWJ1dGVzX0RELnhsc3g6IEkgd2FudCB0byBhZGQgdGhpcyB0byB0aGUgdG1yYzMgc2FtcGxlIHNoZWV0CiAgICBhbmQgdXNlIGl0IHRvIGRlZmluZSB3aGljaCBjb2x1bW5zIHRvIHB1bGwgZnJvbSBpdC4KCiMgUmVhZGluZyBleGlzdGluZyBkYXRhCgpgYGB7cn0KcnVuZGF0ZSA8LSBmb3JtYXQoU3lzLkRhdGUoKSwgZm9ybWF0ID0gIiVZJW0lZCIpCnBoZW5vdHlwZV9maWxlIDwtICJpbnB1dHMvMjAyNDAxLzIwMjIwNzIxX0VYUF9FU1BFQ0lBTF9UTVJDM19WMy54bHN4IgpwaGVub3R5cGVfbWV0YSA8LSAiaW5wdXRzLzIwMjQwMS9DT0RFQk9PS19FWFBfRVNQRUNJQUxfVE1SQzNfVkVSU0lPTl8zXzIwMjIwNTEyLnhsc3giCnNhbXBsZV9maWxlIDwtICJpbnB1dHMvMjAyNDAxL3RtcmMzX3NhbXBsZXNfcHJ1bmVkLnhsc3giCnNhbXBsZV9tZXRhIDwtICJ0ZW1wbGF0ZXMvU2FtcGxlQXR0cmlidXRlc19ERC54bHN4IgoKc3ViamVjdF9waGVub3R5cGVzIDwtIG9wZW54bHN4OjpyZWFkLnhsc3gocGhlbm90eXBlX2ZpbGUpCnN1YmplY3RfbWV0YSA8LSBvcGVueGxzeDo6cmVhZC54bHN4KHBoZW5vdHlwZV9tZXRhKQpzYW1wbGVfYXR0cmlidXRlcyA8LSBvcGVueGxzeDo6cmVhZC54bHN4KHNhbXBsZV9maWxlKQpzYW1wbGVfbWV0YSA8LSBvcGVueGxzeDo6cmVhZC54bHN4KHNhbXBsZV9tZXRhKQoKY3JlYXRlZCA8LSBkaXIuY3JlYXRlKGdsdWUoIm91dHB1dHMve3J1bmRhdGV9IikpCmBgYAoKIyBTYW5pdGl6ZSBjb2x1bW4gbmFtZXMgc2xpZ2h0bHkuCgpUaGVyZSBhcmUgYSBmZXcgdGhpbmdzIHdoaWNoIGFyZSByZXF1aXJlZCBieSBkYkdhcCB3aGljaCBJIGNhbiB0cml2aWFsbHkgY2hhbmdlIGhlcmUuCgpgYGB7cn0KIyMgU2FuaXRpemUgdGhlIGNvbHVtbiBuYW1lcyBhIGxpdHRsZS4KY29sbmFtZXMoc3ViamVjdF9waGVub3R5cGVzKSA8LSB0b3VwcGVyKGNvbG5hbWVzKHN1YmplY3RfcGhlbm90eXBlcykpCgpzdWJqZWN0X21ldGFbWyJWQVJOQU1FIl1dIDwtIHRvdXBwZXIoc3ViamVjdF9tZXRhW1siVkFSTkFNRSJdXSkKCmNvbG5hbWVzKHNhbXBsZV9hdHRyaWJ1dGVzKSA8LSB0b3VwcGVyKGNvbG5hbWVzKHNhbXBsZV9hdHRyaWJ1dGVzKSkKY29sbmFtZXMoc2FtcGxlX2F0dHJpYnV0ZXMpIDwtIGdzdWIocGF0dGVybiA9ICJcXC4rIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZW1lbnQgPSAiXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBjb2xuYW1lcyhzYW1wbGVfYXR0cmlidXRlcykpCmNvbG5hbWVzKHNhbXBsZV9hdHRyaWJ1dGVzKSA8LSBnc3ViKHBhdHRlcm4gPSAiXFwpfFxcKHxcXFt8XFxdfCx8LXwvfCV8J3zCtHxcXHMrIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZW1lbnQgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IGNvbG5hbWVzKHNhbXBsZV9hdHRyaWJ1dGVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGVybCA9IFRSVUUpCnN1YmplY3RfY29sbmFtZV9zdWJzdGl0dXRpb25zIDwtIGxpc3QoCiAgICAiQ09ESUdPX1BBQ0lFTlRFIiA9ICJTVUJKRUNUX0lEIiwKICAgICJFQl9MQ19TRVhPIiA9ICJTRVgiLAogICAgIlRVQkVfTEFCRUxfT1JJR0lOIiA9ICJTVUJKRUNUX0lEIiwKICAgICJTQU1QTEVfTkFNRSIgPSAiU0FNUExFX0lEIiwKICAgICJUTVJDX0lERU5USUZJRVIiID0gIlRNUkNfSUQiCikKZm9yIChpIGluIDE6bGVuZ3RoKHN1YmplY3RfY29sbmFtZV9zdWJzdGl0dXRpb25zKSkgewogIGZyb20gPC0gbmFtZXMoc3ViamVjdF9jb2xuYW1lX3N1YnN0aXR1dGlvbnMpW2ldCiAgdG8gPC0gc3ViamVjdF9jb2xuYW1lX3N1YnN0aXR1dGlvbnNbW2ldXQogIGNvbG5hbWVzKHN1YmplY3RfcGhlbm90eXBlcykgPC0gZ3N1YihwYXR0ZXJuID0gZnJvbSwgcmVwbGFjZW1lbnQgPSB0bywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IGNvbG5hbWVzKHN1YmplY3RfcGhlbm90eXBlcykpCiAgY29sbmFtZXMoc2FtcGxlX2F0dHJpYnV0ZXMpIDwtIGdzdWIocGF0dGVybiA9IGZyb20sIHJlcGxhY2VtZW50ID0gdG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IGNvbG5hbWVzKHNhbXBsZV9hdHRyaWJ1dGVzKSkKICBzdWJqZWN0X21ldGFbWyJWQVJOQU1FIl1dIDwtIGdzdWIocGF0dGVybiA9IGZyb20sIHJlcGxhY2VtZW50ID0gdG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBzdWJqZWN0X21ldGFbWyJWQVJOQU1FIl1dKQp9CmBgYAoKIyBTYW5pdHkgY2hlY2sKCk5vdyB0aGF0IEkgaGF2ZSBtYWRlIHNvbWUgY2hhbmdlcyB0byB0aGUgbWV0YWRhdGEsIGxldCB1cyBhdHRlbXB0IHRvCm1ha2UgY2VydGFpbiB0aGF0IHRoZXkgc3RpbGwgbWF0Y2guICBUaGlzIHByaW1hcmlseSByZWZlcnMgdG8gdGhlCmNvbHVtbiBuYW1lcyBvZiB0aGUgc3ViamVjdF9waGVub3R5cGUgZmlsZSBhbmQgdGhlIFZBUk5BTUUgY29sdW1uIGZyb20KdGhlIHN1YmplY3QgbWV0YWRhdGEgZmlsZS4KCmBgYHtyfQptaXNzZXMgPC0gc3ViamVjdF9tZXRhW1siVkFSTkFNRSJdXSAhPSBjb2xuYW1lcyhzdWJqZWN0X3BoZW5vdHlwZXMpCnN1bW1hcnkobWlzc2VzKQppZiAoc3VtKG1pc3NlcykgPiAwKSB7CiAgc3RvcCgiVGhlcmUgaXMgYSBtaXNtYXRjaCBiZXR3ZWVuIHRoZSBtZXRhZGF0YSBhbmQgY29sdW1uIG5hbWVzLiIpCn0KYGBgCgojIEZpbHRlciBvdXQgc2FtcGxlcyB3aGljaCB3ZXJlIG5vdCBzZXF1ZW5jZWQuCgpXZSBvbmx5IHdhbnQgdGhvc2Ugc2FtcGxlcyBmb3Igd2hpY2ggd2UgaGF2ZSBleHRhbnQgVE1SQyBJRHMuICBUaHVzLApmaWx0ZXIgdGhlIHNhbXBsZV9hdHRyaWJ1dGVzIGZvciB0aGUgc2FtcGxlcyB0aGF0IGhhdmUgYmVlbiBnaXZlbiB0bXJjCklEcyBmb3Igbm93LgoKYGBge3J9CnVzZWZ1bF9pZHggPC0gIWlzLm5hKHNhbXBsZV9hdHRyaWJ1dGVzW1siVE1SQ19JRCJdXSkKc2FtcGxlX2F0dHJpYnV0ZXMgPC0gc2FtcGxlX2F0dHJpYnV0ZXNbdXNlZnVsX2lkeCwgXQpgYGAKCiMgQ3JlYXRlIHRoZSBTdWJqZWN0Q29uc2VudF9EUy50eHQKCkNyZWF0aW5nIHRoZSBTdWJqZWN0Q29uc2VudF9EUy50eHQgc2hvdWxkIGJlIHRoZSBlYXNpZXN0LCBqdXN0IHB1bGwgdGhlIElECmFuZCBTRVggY29sdW1ucyBhbmQgZmlsbCBpbiB0aGUgcmVzdC4KCmBgYHtyfQpTdWJqZWN0Q29uc2VudF9EUyA8LSBzdWJqZWN0X3BoZW5vdHlwZXNbLCBjKCJTVUJKRUNUX0lEIiwgIlNFWCIpXQpTdWJqZWN0Q29uc2VudF9EU1tbIkNPTlNFTlQiXV0gPC0gMQpTdWJqZWN0Q29uc2VudF9EU1tbIlNVQkpFQ1RfU09VUkNFIl1dIDwtICJUTVJDMyIKU3ViamVjdENvbnNlbnRfRFNbWyJTVUJKRUNUX1NPVVJDRV9JRCJdXSA8LSBTdWJqZWN0Q29uc2VudF9EU1tbIlNVQkpFQ1RfSUQiXV0KIyMgQW5kIHJlb3JkZXIgaXQKY29sdW1uX29yZGVyIDwtIGMoIlNVQkpFQ1RfSUQiLCAiQ09OU0VOVCIsICJTRVgiLCAiU1VCSkVDVF9TT1VSQ0UiLCAiU1VCSkVDVF9TT1VSQ0VfSUQiKQpTdWJqZWN0Q29uc2VudF9EUyA8LSBTdWJqZWN0Q29uc2VudF9EU1ssIGNvbHVtbl9vcmRlcl0KcmVhZHI6OndyaXRlX3Rzdih4ID0gU3ViamVjdENvbnNlbnRfRFMsCiAgICAgICAgICAgICAgICAgZmlsZSA9IGdsdWUoIm91dHB1dHMve3J1bmRhdGV9L3tydW5kYXRlfS1TdWJqZWN0Q29uc2VudF9EUy50eHQiKSkKYGBgCgojIENyZWF0ZSB0aGUgU3ViamVjdENvbnNlbnRfREQueGxzeAoKVGhpcyBmaWxlIHNob3VsZCBiZSB1bmNoYW5nZWQgZnJvbSB0aGUgdGVtcGxhdGUsIHNvIHdlIHdpbGwgcmVhZCBpdCBpbgphbmQgaW1tZWRpYXRlbHkgd3JpdGUgaXQgYmFjayBvdXQgd2l0aCBhIGJsYW5rIGxpbmUgaW4gYmV0d2VlbiBpbiBjYXNlCndlIGRvIGluIGZhY3QgbmVlZCB0byBjaGFuZ2Ugc29tZXRoaW5nIGxhdGVyLgoKYGBge3J9ClN1YmplY3RDb25zZW50X0REIDwtIG9wZW54bHN4OjpyZWFkLnhsc3goInRlbXBsYXRlcy9TdWJqZWN0Q29uc2VudF9ERC54bHN4IikKCgpvcGVueGxzeDo6d3JpdGUueGxzeCh4ID0gU3ViamVjdENvbnNlbnRfREQsCiAgICAgICAgICAgICAgICAgICAgIGZpbGUgPSBnbHVlKCJvdXRwdXRzL3tydW5kYXRlfS97cnVuZGF0ZX0tU3ViamVjdENvbnNlbnRfREQueGxzeCIpKQpgYGAKCiMgQ3JlYXRlIHRoZSBTU01fRFMudHh0CgpUaGUgU1NNX0RTLnR4dCB3aWxsIGJlIG1vcmUgZGlmZmljdWx0LCBpdCByZXF1aXJlcyBhIG1lcmdlIGJldHdlZW4gc2hlZXRzLi4uCgpgYGB7cn0Kc2FtcGxlX2F0dHJpYnV0ZXNbWyJTVUJKRUNUX0lEIl1dIDwtIGdzdWIocGF0dGVybiA9ICJec3UiLCByZXBsYWNlbWVudCA9ICJTVSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBzYW1wbGVfYXR0cmlidXRlc1tbIlNVQkpFQ1RfSUQiXV0pClNTTV9EUyA8LSBtZXJnZShTdWJqZWN0Q29uc2VudF9EUywgc2FtcGxlX2F0dHJpYnV0ZXMsIGJ5ID0gIlNVQkpFQ1RfSUQiKQp3YW50ZWRfY29sdW1ucyA8LSBjKCJTVUJKRUNUX0lEIiwgIlNBTVBMRV9JRCIpClNTTV9EUyA8LSBTU01fRFNbLCB3YW50ZWRfY29sdW1uc10KcmVhZHI6OndyaXRlX3Rzdih4ID0gU1NNX0RTLCBmaWxlID0gZ2x1ZSgib3V0cHV0cy97cnVuZGF0ZX0ve3J1bmRhdGV9LVNTTV9EUy50eHQiKSkKYGBgCgojIENyZWF0ZSB0aGUgU1NNX0RELnhsc3gKCk9uY2UgYWdhaW4sIHdlIHdpbGwgYXNzdW1lIHRoYXQgY29weWluZyB0aGlzIGZyb20gdGhlIHRlbXBsYXRlIHdpbGwKcHJvdmUgc3VmZmljaWVudDsgYnV0IHdpbGwgcGVyZm9ybSBleHBsaWNpdCByZWFkL3dyaXRlIHN0ZXBzIGluIGNhc2UKd2UgbmVlZCB0byBjaGFuZ2UgYW55dGhpbmcuCgpgYGB7cn0KU1NNX0REIDwtIG9wZW54bHN4OjpyZWFkLnhsc3goInRlbXBsYXRlcy9TU01fREQueGxzeCIpCgoKb3Blbnhsc3g6OndyaXRlLnhsc3goeCA9IFNTTV9ERCwgZmlsZSA9IGdsdWUoIm91dHB1dHMve3J1bmRhdGV9L3tydW5kYXRlfS1TU01fREQueGxzeCIpKQpgYGAKCiMgQ3JlYXRlIHRoZSBTdWJqZWN0UGhlbm90eXBlc19ERAoKTm9ybWFsbHksIEkgd291bGQgbW92ZSB0aGUgZW5nbGlzaC10cmFuc2xhdGVkIGNvbHVtbnMgdG8gdGhlIGRlZmF1bHRzOwpidXQgd2UgZG8gbm90IGhhdmUgYSB0cmFuc2xhdGVkIHZlcnNpb24gb2YgdGhpcyBhdCB0aGlzIHRpbWUuCgpgYGB7cn0KIyMgSSBkbyBub3QgdGhpbmsgSSBuZWVkIHRvIGRvIGFueXRoaW5nIGVsc2UgdG8gdGhlIG1ldGFkYXRhIGZpbGUgYXQgdGhpcyB0aW1lLgpTdWJqZWN0UGhlbm90eXBlc19ERCA8LSBzdWJqZWN0X21ldGEKYGBgCgojIyBKdWdnbGUgdGhlIGNvbHVtbnMKCkkgY2Fubm90IHJ1biB0aGUgZm9sbG93aW5nIGJsb2NrIGF0IHRoaXMgdGltZSBiZWNhdXNlIHRoZSBjdXJyZW50IGZpbGUKaGFzIG5vdCBiZWVuIHRyYW5zbGF0ZWQuCgpgYGB7ciBqdWdnbGUsIGV2YWw9RkFMU0V9CiMjIFdlIHdpbGwgbmVlZCB0byBtb3JlIFZBUk5BTUVfRU5HIHRvIFZBUk5BTUUKU3ViamVjdFBoZW5vdHlwZXNfRERbWyJWQVJOQU1FIl1dIDwtIFN1YmplY3RQaGVub3R5cGVzX0REW1siVkFSTkFNRV9FTkciXV0KU3ViamVjdFBoZW5vdHlwZXNfRERbWyJWQVJOQU1FX0VORyJdXSA8LSBOVUxMCiMjIERpdHRvIGZvciBWQVJERVNDIGFuZCBUWVBFClN1YmplY3RQaGVub3R5cGVzX0REW1siVkFSREVTQyJdXSA8LSBTdWJqZWN0UGhlbm90eXBlc19ERFtbIlZBUkRFU0NfRU5HIl1dClN1YmplY3RQaGVub3R5cGVzX0REW1siVkFSREVTQ19FTkciXV0gPC0gTlVMTApTdWJqZWN0UGhlbm90eXBlc19ERFtbIlRZUEUiXV0gPC0gU3ViamVjdFBoZW5vdHlwZXNfRERbWyJUWVBFX0VORyJdXQpTdWJqZWN0UGhlbm90eXBlc19ERFtbIlRZUEVfRU5HIl1dIDwtIE5VTEwKIyMgSGF2aW5nIGRvbmUgdGhhdCwgd2UgbmVlZCB0byBzZXQgdGhlIGNvbHVtbiBuYW1lcyBvZiB0aGUgU3ViamVjdFBoZW5vdHlwZXNfRFMgdG8gdGhlIG5ldwojIyBWQVJOQU1FIGNvbHVtbi4KYGBgCgojIFdyaXRlIG91dCB0aGUgU3ViamVjdFBoZW5vdHlwZXNfREQKCmBgYHtyfQpzdGFydGluZ19kc19jb2xuYW1lcyA8LSBTdWJqZWN0UGhlbm90eXBlc19ERFtbIlZBUk5BTUUiXV0Kb3Blbnhsc3g6OndyaXRlLnhsc3goeCA9IFN1YmplY3RQaGVub3R5cGVzX0RELAogICAgICAgICAgICAgICAgICAgICBmaWxlID0gZ2x1ZSgib3V0cHV0cy97cnVuZGF0ZX0ve3J1bmRhdGV9LVN1YmplY3RQaGVub3R5cGVzX0RELnhsc3giKSkKYGBgCgojIENyZWF0ZSB0aGUgU3ViamVjdFBoZW5vdHlwZXNfRFMKClRoZSBTdWJqZWN0UGhlbm90eXBlc19EUyBpcyBqdXN0IHRoZSBvcmlnaW5hbCBzdWJqZWN0X3BoZW5vdHlwZXMgd2l0aApzb21lIGNvbHVtbnMgYmxhY2tsaXN0ZWQuCgpgYGB7cn0Kc3ViamVjdF9waGVub3R5cGVzX2JsYWNrbGlzdCA8LSBjKCJFQl9MQ19GRUNIQV9OQUNJTUlFTlRPIiwgIlNFWCIpClN1YmplY3RQaGVub3R5cGVzX0RTIDwtIHN1YmplY3RfcGhlbm90eXBlcwpjb2xuYW1lcyhTdWJqZWN0UGhlbm90eXBlc19EUykgPC0gc3RhcnRpbmdfZHNfY29sbmFtZXMKZm9yIChpIGluIHN1YmplY3RfcGhlbm90eXBlc19ibGFja2xpc3QpIHsKICBTdWJqZWN0UGhlbm90eXBlc19EU1tbaV1dIDwtIE5VTEwKICBtZXRhX2tlZXBlcnMgPC0gc3ViamVjdF9tZXRhW1siVkFSTkFNRSJdXSAhPSBpCiAgc3ViamVjdF9tZXRhIDwtIHN1YmplY3RfbWV0YVttZXRhX2tlZXBlcnMsIF0KfQpyZWFkcjo6d3JpdGVfdHN2KHggPSBTdWJqZWN0UGhlbm90eXBlc19EUywKICAgICAgICAgICAgICAgICBmaWxlID0gZ2x1ZSgib3V0cHV0cy97cnVuZGF0ZX0ve3J1bmRhdGV9LVN1YmplY3RQaGVub3R5cGVzX0RTLnR4dCIpKQpgYGAKCiMgQ3JlYXRlIHRoZSBTYW1wbGVBdHRyaWJ1dGVzX0RTCgpUaGUgU2FtcGxlQXR0cmlidXRlc19EUyB3aWxsIHJlcXVpcmUgc29tZSBtb3JlIHdvcms6CjEuICBSZWFkIGluIHRoZSBTYW1wbGVBdHRyaWJ1dGVzX0REIGFuZCBrZWVwIG9ubHkgdGhlIGNvbHVtbnMgZGVmaW5lZAogICAgaW4gaXQuCjIuICBSZWNhc3QgdGhlIGRhdGEgdG8gZW5zdXJlIHRoZXkgYXJlIHB1cmUgdGV4dC4KCkN1cnJlbnRseSB0aGF0IGlzIGFsbCB3ZSBhcmUgZG9pbmcsIHNvIGl0IGRvZXNuJ3QgcmVhbGx5IHJlcXVpcmUgdmVyeQptdWNoLgoKSSBkbyBuZWVkIHRvIGNoYW5nZSB0aGUgY29sdW1uICdGSU5BTF9PVVRDT01FJyB0byAnQ0xJTklDQUxfT1VUQ09NRScKCmBgYHtyfQpjb2xuYW1lcyhzYW1wbGVfYXR0cmlidXRlcykgPC0gZ3N1Yih4ID0gY29sbmFtZXMoc2FtcGxlX2F0dHJpYnV0ZXMpLCBwYXR0ZXJuID0gIkZJTkFMX09VVENPTUUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlbWVudCA9ICJDTElOSUNBTF9PVVRDT01FIikKU2FtcGxlQXR0cmlidXRlc19EUyA8LSBzYW1wbGVfYXR0cmlidXRlcwprZXB0X2NvbHVtbnMgPC0gc2FtcGxlX21ldGFbWyJWQVJOQU1FIl1dCgprZXB0X2NvbHVtbnNbISBrZXB0X2NvbHVtbnMgJWluJSBjb2xuYW1lcyhTYW1wbGVBdHRyaWJ1dGVzX0RTKV0KClNhbXBsZUF0dHJpYnV0ZXNfRFMgPC0gU2FtcGxlQXR0cmlidXRlc19EU1ssIGtlcHRfY29sdW1uc10KZm9yIChpIGluIDE6bmNvbChTYW1wbGVBdHRyaWJ1dGVzX0RTKSkgewogIFNhbXBsZUF0dHJpYnV0ZXNfRFNbW2ldXSA8LSBhcy5jaGFyYWN0ZXIoU2FtcGxlQXR0cmlidXRlc19EU1tbaV1dKQp9CnJlYWRyOjp3cml0ZV90c3YoeCA9IFNhbXBsZUF0dHJpYnV0ZXNfRFMsCiAgICAgICAgICAgICAgICAgZmlsZSA9IGdsdWUoIm91dHB1dHMve3J1bmRhdGV9L3tydW5kYXRlfS1TYW1wbGVBdHRyaWJ1dGVzX0RTLnR4dCIpKQpgYGAKCiMgV3JpdGUgdGhlIFNhbXBsZUF0dHJpYnV0ZXNfREQKCk9uY2UgYWdhaW4sIHRoZSBERCBmaWxlIGlzIGV4cGVjdGVkIHRvIGJlIGlkZW50aWNhbCB0byBvdXIgdGVtcGxhdGUuCgpgYGB7cn0KU2FtcGxlQXR0cmlidXRlc19ERCA8LSBzYW1wbGVfbWV0YQpvcGVueGxzeDo6d3JpdGUueGxzeCh4ID0gU2FtcGxlQXR0cmlidXRlc19ERCwKICAgICAgICAgICAgICAgICAgICAgZmlsZSA9IGdsdWUoIm91dHB1dHMve3J1bmRhdGV9L3tydW5kYXRlfS1TYW1wbGVBdHRyaWJ1dGVzX0RELnhsc3giKSkKYGBgCg==