For this analysis, we will start by getting the data from GEO, processing it into a more suitable object for a survival analysis.

Setting up the environment

These are packages you will need for this notebook. For exact versions used, please refer to the session info at the bottom of this notebook.

library('tidyverse')
library('GEOquery')
library('ggplot2')
library('RColorBrewer')
library('survival')
library('survminer')


I usually set a couple of directories of where to store things, or where I have located things we may need. Below, generalDatasets is just where we have stored more global data, such as a general use database. The baseWorkspace is where we will store my coding files that will usually end up getting pushed to GitHub. The last is baseRepository. This is set up the same as baseWorkspace but is not pushed to GitHub, so will store larger files (such as the data we download from GEO).

generalDatasets = 'C:/Users/chris/OneDrive/Documents/bccrc/projectsRepository/generalDatasets'
baseWorkspace = 'C:/Users/chris/OneDrive/Documents/bccrc/protocolsDryLab/relatedToPublishedData'
baseRepository = 'C:/Users/chris/OneDrive/Documents/bccrc/protocolsRepository'

Data processing

This data set is from Savola et al. from GSE17679 that was published in PMID 22084725. There appears to be two sets of replicates here, with a bunch of other cell line and other tissue samples analyzed as well that may need filtering downstream. If you wanted to analyze different data here, replace with your own GEO identifier. The destdir portion of the command here is telling getGEO where we want to save the data. Here we are using the ‘paste’ function to combine my baseRepository from above with the name of the folder where we want to save it. The sep = ’’ just tells it to leave no spaces between the individual elements (it is a file path, we don’t want spaces).

##########################################################################################
esArrayData = getGEO('GSE17679',
             destdir = paste(baseRepository, '/getGeoDataForSurvivalAnalysis/', sep = ''),
             GSEMatrix = TRUE)
Found 1 file(s)
GSE17679_series_matrix.txt.gz
Using locally cached version: C:/Users/chris/OneDrive/Documents/bccrc/protocolsRepository/getGeoDataForSurvivalAnalysis//GSE17679_series_matrix.txt.gz
Parsed with column specification:
cols(
  .default = col_double(),
  ID_REF = col_character()
)
See spec(...) for full column specifications.
Using locally cached version of GPL570 found here:
C:/Users/chris/OneDrive/Documents/bccrc/protocolsRepository/getGeoDataForSurvivalAnalysis//GPL570.soft 
62 parsing failures.
  row     col           expected    actual         file
54614 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
54615 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
54616 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
54617 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
54618 SPOT_ID 1/0/T/F/TRUE/FALSE --Control literal data
..... ....... .................. ......... ............
See problems(...) for more details.

This command returns a list like object, so we will extract it to be an expressionSet.

##########################################################################################
esData = esArrayData[[1]]

We now have an ‘expressionSet’ that contains different types of information. We can look at some of the data to see what we have using different commands. Below, pData gives us the ‘phenotype data’ - this is usually the sample details provided by the submitter and is often where the clinical information is found.

##########################################################################################
head(pData(esData))

The exprs gives us the expression data for each sample analyzed, and all probes.

##########################################################################################
head(exprs(esData)[,1:5])
          GSM439886 GSM439887 GSM439888 GSM439889 GSM439890
1007_s_at  9.151022  8.160573  9.016025  8.609008  9.199717
1053_at    6.085073  5.719322  6.775956  4.984402  6.582644
117_at     4.108801  4.166347  4.740777  4.149291  4.202501
121_at     7.121038  7.437739  6.235114  6.537358  6.519501
1255_g_at  2.735556  2.889045  3.078523  2.681159  4.497911
1294_at    6.426440  6.113979  5.224356  6.830285  5.732191

The fData will give us ‘feature data’, which is usually related to the array itself, so things like probe identifiers and annotation.

##########################################################################################
head(fData(esData))

First we just want to annotate the expression data to include gene symbols (right now it is just probe identifiers), as these are what we will use for the survival analysis downstream. We can use the feature data to get this annotation. Below, we extract the expression data into the ‘esExpression’ object.

##################################################################################
esExpression = as_tibble(exprs(esData)) %>%
  mutate(ID = row.names(exprs(esData)))

Now, we extract the feature data, keep the first gene symbol in the ‘Gene Symbol’ column, select only the columns we want to keep with the ‘select’ function, and then merge it with the esExpression object we just created.

##################################################################################
esExpressionAnnotated = as_tibble(fData(esData)) %>%
  mutate(symbol = sapply(strsplit(`Gene Symbol`, ' '), '[', 1)) %>%
  dplyr::select(ID, GB_ACC, symbol) %>%
  left_join(esExpression)
Joining, by = "ID"

So now we have an expression object with gene symbols and all of the associated data. Now I want to look at the phenotype data. There appears to be PNET cases in here, cell lines, and some other tissues. I will keep only things classified as Ewing. Most of the command below is just text parsing to extract the information we want. This is the purpose of the ‘sub’ function. I know it can look a bit confusing, but if you want to learn more about it, you should look into regular expressions or ask here and I can do my best to explain it.

##################################################################################
esPhenotype = as_tibble(pData(esData)) %>%
  dplyr::select(geo_accession, characteristics_ch1.1:characteristics_ch1.8) %>%
  filter(grepl('Ewing', characteristics_ch1.1)) %>%
  mutate(type = sub('state\\: (.*)$', '\\1', characteristics_ch1.3)) %>%
  mutate(age = sub('age\\: (.*)$', '\\1', characteristics_ch1.4)) %>%
  mutate(sex = sub('sex\\: (.*)$', '\\1', characteristics_ch1.5)) %>%
  mutate(efs = as.double(sub('efs \\(months\\)\\: (.*)$', '\\1', characteristics_ch1.6))) %>%
  mutate(ovs = as.double(sub('ovs \\(months\\)\\: (.*)$', '\\1', characteristics_ch1.7))) %>%
  mutate(status = sub('status\\: (.*)$', '\\1', characteristics_ch1.8)) %>%
  dplyr::select(geo_accession, type:status)

Now we assign a numerical value to alive and dead, where dead = 2.

##################################################################################
esPhenotype$status = ifelse(esPhenotype$status == 'Dead', 2, 1)

Now we can save the two data sets, just in case we want to use them later on for something else and don’t want to redo this processing.

##################################################################################
saveRDS(esExpressionAnnotated, paste(baseRepository, '/getGeoDataForSurvivalAnalysis/dataset_savolaExpression.rds', sep = ''))
saveRDS(esPhenotype, paste(baseRepository, '/getGeoDataForSurvivalAnalysis/dataset_savolaPhenotype.rds', sep = ''))

Now we are ready to move on and do the survival analysis. For simplicity and to avoid messing up my original files, I am first going to read in the data we just saved to and start fresh from here with new names.

##########################################################################################
esExpression = readRDS(paste(baseRepository, '/getGeoDataForSurvivalAnalysis/dataset_savolaExpression.rds', sep = ''))
esPhenotype = readRDS(paste(baseRepository, '/getGeoDataForSurvivalAnalysis/dataset_savolaPhenotype.rds', sep = ''))

Now everything is set to do a survival analysis. First, we can just do this simply for a single gene. I find the easiest way to do this is to write a function as we want it to be reproducible if we decide to do it for many genes. For this, I like to create a new .R file, write the function in it, and call it into this session. So, if you are in RStudio, click File>New File>R Script. In my case, I have provided this file on GitHub and it is called getGeoDataForSurvivalAnalysisFunctions.R. You can open this file with any text editor, or RStudio. So, create the file, and read it in to this R session.

##########################################################################################
source(paste(baseWorkspace, '/getGeoDataForSurvivalAnalysisFunctions.R', sep = ''))

In this file, you will find a function called ‘survivalAnalysis’. This function will take your gene of interest, your phenotype and expression data, as well as inputs as to whether you would like to generate a plot or not. The function expects the data to be in the specific format as we have created here, so be careful of that in your own use. Expression data:

##########################################################################################
head(esExpression)
NA

And the phenotype data:

##########################################################################################
head(esPhenotype)

Now we can call our function on a gene we are interested in. By default, this function will write plot and text files to our baseRepository directory. The ‘writeDirectory’ option only allows you to change folders within the baseRepository location. You do not have to specify ‘writeDirectory’ if the baseRepository is suitable for you.

##########################################################################################
geneOfInterest = 'YBX1'
singleSurvival = survivalAnalysis(geneOfInterest = geneOfInterest,
                                expressionData = esExpression, 
                                phenotypeData = esPhenotype, 
                                survivalPlot = TRUE, 
                                survivalData = TRUE,
                                writeDirectory = '/getGeoDataForSurvivalAnalysis')
[1] "Performing analysis for YBX1"

A couple of things of note about how this function operates:

If we want to do a list of a bunch of genes, we can do that as below. Keep in mind that if you feed it 1,000 genes, it will make 1,000 plots and text files if you tell it to, so make sure you write to a directory where you can easily make sense of this if you need to (perhaps a new folder holding just these output files).

##########################################################################################
geneOfInterest = as.list(c('YBX1','EIF4A1','PODXL'))
multiSurvival = lapply(geneOfInterest, survivalAnalysis, 
                       expressionData = esExpression, 
                       phenotypeData = esPhenotype, 
                       survivalPlot = TRUE, 
                       survivalData = TRUE, 
                       writeDirectory = '/getGeoDataForSurvivalAnalysis')
[1] "Performing analysis for YBX1"
[1] "Performing analysis for EIF4A1"
[1] "Performing analysis for PODXL"

This is the end of this document. As a final command, we will run sessionInfo to ensure others who look at our work know exactly how our environment was set up.

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 18363)

Matrix products: default

locale:
[1] LC_COLLATE=English_Canada.1252  LC_CTYPE=English_Canada.1252    LC_MONETARY=English_Canada.1252 LC_NUMERIC=C                   
[5] LC_TIME=English_Canada.1252    

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

other attached packages:
 [1] survminer_0.4.7     ggpubr_0.3.0        survival_3.1-12     RColorBrewer_1.1-2  GEOquery_2.54.1     Biobase_2.46.0      BiocGenerics_0.32.0
 [8] forcats_0.5.0       stringr_1.4.0       dplyr_0.8.5         purrr_0.3.4         readr_1.3.1         tidyr_1.0.3         tibble_3.0.1       
[15] ggplot2_3.3.1       tidyverse_1.3.0    

loaded via a namespace (and not attached):
 [1] httr_1.4.1        jsonlite_1.6.1    splines_3.6.3     carData_3.0-4     modelr_0.1.8      assertthat_0.2.1  blob_1.2.1        cellranger_1.1.0 
 [9] yaml_2.2.1        pillar_1.4.4      backports_1.1.7   lattice_0.20-38   glue_1.4.1        limma_3.42.2      digest_0.6.25     ggsignif_0.6.0   
[17] rvest_0.3.5       colorspace_1.4-1  Matrix_1.2-18     pkgconfig_2.0.3   broom_0.5.6       haven_2.2.0       xtable_1.8-4      scales_1.1.1     
[25] km.ci_0.5-2       openxlsx_4.1.5    rio_0.5.16        KMsurv_0.1-5      farver_2.0.3      generics_0.0.2    car_3.0-8         ellipsis_0.3.1   
[33] withr_2.2.0       cli_2.0.2         magrittr_1.5      crayon_1.3.4      readxl_1.3.1      fs_1.4.1          fansi_0.4.1       nlme_3.1-144     
[41] rstatix_0.5.0     xml2_1.3.2        foreign_0.8-75    tools_3.6.3       data.table_1.12.8 hms_0.5.3         lifecycle_0.2.0   munsell_0.5.0    
[49] reprex_0.3.0      zip_2.0.4         compiler_3.6.3    rlang_0.4.6       grid_3.6.3        rstudioapi_0.11   labeling_0.3      gtable_0.3.0     
[57] abind_1.4-5       DBI_1.1.0         curl_4.3          R6_2.4.1          gridExtra_2.3     zoo_1.8-8         lubridate_1.7.8   knitr_1.28       
[65] survMisc_0.5.5    stringi_1.4.6     Rcpp_1.0.4.6      vctrs_0.3.0       dbplyr_1.4.4      tidyselect_1.1.0  xfun_0.14        
LS0tDQp0aXRsZTogIkFuYWx5c2lzIG9mIHN1cnZpdmFsIGRhdGEgZm9yIEV3aW5nIHNhcmNvbWEiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgICBjb2RlX2ZvbGRpbmc6IG5vbmUNCi0tLQ0KDQpGb3IgdGhpcyBhbmFseXNpcywgd2Ugd2lsbCBzdGFydCBieSBnZXR0aW5nIHRoZSBkYXRhIGZyb20gR0VPLCBwcm9jZXNzaW5nIGl0IGludG8gYSBtb3JlIHN1aXRhYmxlIG9iamVjdCBmb3IgYSBzdXJ2aXZhbCBhbmFseXNpcy4NCg0KIyMgU2V0dGluZyB1cCB0aGUgZW52aXJvbm1lbnQNCg0KVGhlc2UgYXJlIHBhY2thZ2VzIHlvdSB3aWxsIG5lZWQgZm9yIHRoaXMgbm90ZWJvb2suIEZvciBleGFjdCB2ZXJzaW9ucyB1c2VkLCBwbGVhc2UgcmVmZXIgdG8gdGhlIHNlc3Npb24gaW5mbyBhdCB0aGUgYm90dG9tIG9mIHRoaXMgbm90ZWJvb2suDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQpsaWJyYXJ5KCd0aWR5dmVyc2UnKQ0KbGlicmFyeSgnR0VPcXVlcnknKQ0KbGlicmFyeSgnZ2dwbG90MicpDQpsaWJyYXJ5KCdSQ29sb3JCcmV3ZXInKQ0KbGlicmFyeSgnc3Vydml2YWwnKQ0KbGlicmFyeSgnc3Vydm1pbmVyJykNCmBgYA0KIA0KPGJyPg0KDQpJIHVzdWFsbHkgc2V0IGEgY291cGxlIG9mIGRpcmVjdG9yaWVzIG9mIHdoZXJlIHRvIHN0b3JlIHRoaW5ncywgb3Igd2hlcmUgSSBoYXZlIGxvY2F0ZWQgdGhpbmdzIHdlIG1heSBuZWVkLiBCZWxvdywgZ2VuZXJhbERhdGFzZXRzIGlzIGp1c3Qgd2hlcmUgd2UgaGF2ZSBzdG9yZWQgbW9yZSBnbG9iYWwgZGF0YSwgc3VjaCBhcyBhIGdlbmVyYWwgdXNlIGRhdGFiYXNlLiBUaGUgYmFzZVdvcmtzcGFjZSBpcyB3aGVyZSB3ZSB3aWxsIHN0b3JlIG15IGNvZGluZyBmaWxlcyB0aGF0IHdpbGwgdXN1YWxseSBlbmQgdXAgZ2V0dGluZyBwdXNoZWQgdG8gR2l0SHViLiBUaGUgbGFzdCBpcyBiYXNlUmVwb3NpdG9yeS4gVGhpcyBpcyBzZXQgdXAgdGhlIHNhbWUgYXMgYmFzZVdvcmtzcGFjZSBidXQgaXMgbm90IHB1c2hlZCB0byBHaXRIdWIsIHNvIHdpbGwgc3RvcmUgbGFyZ2VyIGZpbGVzIChzdWNoIGFzIHRoZSBkYXRhIHdlIGRvd25sb2FkIGZyb20gR0VPKS4NCg0KYGBge3J9DQpnZW5lcmFsRGF0YXNldHMgPSAnQzovVXNlcnMvY2hyaXMvT25lRHJpdmUvRG9jdW1lbnRzL2JjY3JjL3Byb2plY3RzUmVwb3NpdG9yeS9nZW5lcmFsRGF0YXNldHMnDQpiYXNlV29ya3NwYWNlID0gJ0M6L1VzZXJzL2NocmlzL09uZURyaXZlL0RvY3VtZW50cy9iY2NyYy9wcm90b2NvbHNEcnlMYWIvcmVsYXRlZFRvUHVibGlzaGVkRGF0YScNCmJhc2VSZXBvc2l0b3J5ID0gJ0M6L1VzZXJzL2NocmlzL09uZURyaXZlL0RvY3VtZW50cy9iY2NyYy9wcm90b2NvbHNSZXBvc2l0b3J5Jw0KYGBgDQoNCiMjIERhdGEgcHJvY2Vzc2luZw0KDQpUaGlzIGRhdGEgc2V0IGlzIGZyb20gU2F2b2xhIGV0IGFsLiBmcm9tIEdTRTE3Njc5IHRoYXQgd2FzIHB1Ymxpc2hlZCBpbiBQTUlEIDIyMDg0NzI1LiBUaGVyZSBhcHBlYXJzIHRvIGJlIHR3byBzZXRzIG9mIHJlcGxpY2F0ZXMgaGVyZSwgd2l0aCBhIGJ1bmNoIG9mIG90aGVyIGNlbGwgbGluZSBhbmQgb3RoZXIgdGlzc3VlIHNhbXBsZXMgYW5hbHl6ZWQgYXMgd2VsbCB0aGF0IG1heSBuZWVkIGZpbHRlcmluZyBkb3duc3RyZWFtLiBJZiB5b3Ugd2FudGVkIHRvIGFuYWx5emUgZGlmZmVyZW50IGRhdGEgaGVyZSwgcmVwbGFjZSB3aXRoIHlvdXIgb3duIEdFTyBpZGVudGlmaWVyLiBUaGUgZGVzdGRpciBwb3J0aW9uIG9mIHRoZSBjb21tYW5kIGhlcmUgaXMgdGVsbGluZyBnZXRHRU8gd2hlcmUgd2Ugd2FudCB0byBzYXZlIHRoZSBkYXRhLiBIZXJlIHdlIGFyZSB1c2luZyB0aGUgJ3Bhc3RlJyBmdW5jdGlvbiB0byBjb21iaW5lIG15IGJhc2VSZXBvc2l0b3J5IGZyb20gYWJvdmUgd2l0aCB0aGUgbmFtZSBvZiB0aGUgZm9sZGVyIHdoZXJlIHdlIHdhbnQgdG8gc2F2ZSBpdC4gVGhlIHNlcCA9ICcnIGp1c3QgdGVsbHMgaXQgdG8gbGVhdmUgbm8gc3BhY2VzIGJldHdlZW4gdGhlIGluZGl2aWR1YWwgZWxlbWVudHMgKGl0IGlzIGEgZmlsZSBwYXRoLCB3ZSBkb24ndCB3YW50IHNwYWNlcykuDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQplc0FycmF5RGF0YSA9IGdldEdFTygnR1NFMTc2NzknLA0KICAgICAgICAgICAgIGRlc3RkaXIgPSBwYXN0ZShiYXNlUmVwb3NpdG9yeSwgJy9nZXRHZW9EYXRhRm9yU3Vydml2YWxBbmFseXNpcy8nLCBzZXAgPSAnJyksDQogICAgICAgICAgICAgR1NFTWF0cml4ID0gVFJVRSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KVGhpcyBjb21tYW5kIHJldHVybnMgYSBsaXN0IGxpa2Ugb2JqZWN0LCBzbyB3ZSB3aWxsIGV4dHJhY3QgaXQgdG8gYmUgYW4gZXhwcmVzc2lvblNldC4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmVzRGF0YSA9IGVzQXJyYXlEYXRhW1sxXV0NCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KV2Ugbm93IGhhdmUgYW4gJ2V4cHJlc3Npb25TZXQnIHRoYXQgY29udGFpbnMgZGlmZmVyZW50IHR5cGVzIG9mIGluZm9ybWF0aW9uLiBXZSBjYW4gbG9vayBhdCBzb21lIG9mIHRoZSBkYXRhIHRvIHNlZSB3aGF0IHdlIGhhdmUgdXNpbmcgZGlmZmVyZW50IGNvbW1hbmRzLiBCZWxvdywgcERhdGEgZ2l2ZXMgdXMgdGhlICdwaGVub3R5cGUgZGF0YScgLSB0aGlzIGlzIHVzdWFsbHkgdGhlIHNhbXBsZSBkZXRhaWxzIHByb3ZpZGVkIGJ5IHRoZSBzdWJtaXR0ZXIgYW5kIGlzIG9mdGVuIHdoZXJlIHRoZSBjbGluaWNhbCBpbmZvcm1hdGlvbiBpcyBmb3VuZC4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmhlYWQocERhdGEoZXNEYXRhKSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KVGhlIGV4cHJzIGdpdmVzIHVzIHRoZSBleHByZXNzaW9uIGRhdGEgZm9yIGVhY2ggc2FtcGxlIGFuYWx5emVkLCBhbmQgYWxsIHByb2Jlcy4gDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpoZWFkKGV4cHJzKGVzRGF0YSlbLDE6NV0pDQpgYGANCg0KPGRpdiBzdHlsZT0ibWFyZ2luLWJvdHRvbTo1MHB4OyI+PC9kaXY+DQoNClRoZSBmRGF0YSB3aWxsIGdpdmUgdXMgJ2ZlYXR1cmUgZGF0YScsIHdoaWNoIGlzIHVzdWFsbHkgcmVsYXRlZCB0byB0aGUgYXJyYXkgaXRzZWxmLCBzbyB0aGluZ3MgbGlrZSBwcm9iZSBpZGVudGlmaWVycyBhbmQgYW5ub3RhdGlvbi4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmhlYWQoZkRhdGEoZXNEYXRhKSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KRmlyc3Qgd2UganVzdCB3YW50IHRvIGFubm90YXRlIHRoZSBleHByZXNzaW9uIGRhdGEgdG8gaW5jbHVkZSBnZW5lIHN5bWJvbHMgKHJpZ2h0IG5vdyBpdCBpcyBqdXN0IHByb2JlIGlkZW50aWZpZXJzKSwgYXMgdGhlc2UgYXJlIHdoYXQgd2Ugd2lsbCB1c2UgZm9yIHRoZSBzdXJ2aXZhbCBhbmFseXNpcyBkb3duc3RyZWFtLiBXZSBjYW4gdXNlIHRoZSBmZWF0dXJlIGRhdGEgdG8gZ2V0IHRoaXMgYW5ub3RhdGlvbi4gQmVsb3csIHdlIGV4dHJhY3QgdGhlIGV4cHJlc3Npb24gZGF0YSBpbnRvIHRoZSAnZXNFeHByZXNzaW9uJyBvYmplY3QuIA0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmVzRXhwcmVzc2lvbiA9IGFzX3RpYmJsZShleHBycyhlc0RhdGEpKSAlPiUNCiAgbXV0YXRlKElEID0gcm93Lm5hbWVzKGV4cHJzKGVzRGF0YSkpKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpOb3csIHdlIGV4dHJhY3QgdGhlIGZlYXR1cmUgZGF0YSwga2VlcCB0aGUgZmlyc3QgZ2VuZSBzeW1ib2wgaW4gdGhlICdHZW5lIFN5bWJvbCcgY29sdW1uLCBzZWxlY3Qgb25seSB0aGUgY29sdW1ucyB3ZSB3YW50IHRvIGtlZXAgd2l0aCB0aGUgJ3NlbGVjdCcgZnVuY3Rpb24sIGFuZCB0aGVuIG1lcmdlIGl0IHdpdGggdGhlIGVzRXhwcmVzc2lvbiBvYmplY3Qgd2UganVzdCBjcmVhdGVkLg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmVzRXhwcmVzc2lvbkFubm90YXRlZCA9IGFzX3RpYmJsZShmRGF0YShlc0RhdGEpKSAlPiUNCiAgbXV0YXRlKHN5bWJvbCA9IHNhcHBseShzdHJzcGxpdChgR2VuZSBTeW1ib2xgLCAnICcpLCAnWycsIDEpKSAlPiUNCiAgZHBseXI6OnNlbGVjdChJRCwgR0JfQUNDLCBzeW1ib2wpICU+JQ0KICBsZWZ0X2pvaW4oZXNFeHByZXNzaW9uKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpTbyBub3cgd2UgaGF2ZSBhbiBleHByZXNzaW9uIG9iamVjdCB3aXRoIGdlbmUgc3ltYm9scyBhbmQgYWxsIG9mIHRoZSBhc3NvY2lhdGVkIGRhdGEuIE5vdyBJIHdhbnQgdG8gbG9vayBhdCB0aGUgcGhlbm90eXBlIGRhdGEuIFRoZXJlIGFwcGVhcnMgdG8gYmUgUE5FVCBjYXNlcyBpbiBoZXJlLCBjZWxsIGxpbmVzLCBhbmQgc29tZSBvdGhlciB0aXNzdWVzLiBJIHdpbGwga2VlcCBvbmx5IHRoaW5ncyBjbGFzc2lmaWVkIGFzIEV3aW5nLiBNb3N0IG9mIHRoZSBjb21tYW5kIGJlbG93IGlzIGp1c3QgdGV4dCBwYXJzaW5nIHRvIGV4dHJhY3QgdGhlIGluZm9ybWF0aW9uIHdlIHdhbnQuIFRoaXMgaXMgdGhlIHB1cnBvc2Ugb2YgdGhlICdzdWInIGZ1bmN0aW9uLiBJIGtub3cgaXQgY2FuIGxvb2sgYSBiaXQgY29uZnVzaW5nLCBidXQgaWYgeW91IHdhbnQgdG8gbGVhcm4gbW9yZSBhYm91dCBpdCwgeW91IHNob3VsZCBsb29rIGludG8gcmVndWxhciBleHByZXNzaW9ucyBvciBhc2sgaGVyZSBhbmQgSSBjYW4gZG8gbXkgYmVzdCB0byBleHBsYWluIGl0Lg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmVzUGhlbm90eXBlID0gYXNfdGliYmxlKHBEYXRhKGVzRGF0YSkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGdlb19hY2Nlc3Npb24sIGNoYXJhY3RlcmlzdGljc19jaDEuMTpjaGFyYWN0ZXJpc3RpY3NfY2gxLjgpICU+JQ0KICBmaWx0ZXIoZ3JlcGwoJ0V3aW5nJywgY2hhcmFjdGVyaXN0aWNzX2NoMS4xKSkgJT4lDQogIG11dGF0ZSh0eXBlID0gc3ViKCdzdGF0ZVxcOiAoLiopJCcsICdcXDEnLCBjaGFyYWN0ZXJpc3RpY3NfY2gxLjMpKSAlPiUNCiAgbXV0YXRlKGFnZSA9IHN1YignYWdlXFw6ICguKikkJywgJ1xcMScsIGNoYXJhY3RlcmlzdGljc19jaDEuNCkpICU+JQ0KICBtdXRhdGUoc2V4ID0gc3ViKCdzZXhcXDogKC4qKSQnLCAnXFwxJywgY2hhcmFjdGVyaXN0aWNzX2NoMS41KSkgJT4lDQogIG11dGF0ZShlZnMgPSBhcy5kb3VibGUoc3ViKCdlZnMgXFwobW9udGhzXFwpXFw6ICguKikkJywgJ1xcMScsIGNoYXJhY3RlcmlzdGljc19jaDEuNikpKSAlPiUNCiAgbXV0YXRlKG92cyA9IGFzLmRvdWJsZShzdWIoJ292cyBcXChtb250aHNcXClcXDogKC4qKSQnLCAnXFwxJywgY2hhcmFjdGVyaXN0aWNzX2NoMS43KSkpICU+JQ0KICBtdXRhdGUoc3RhdHVzID0gc3ViKCdzdGF0dXNcXDogKC4qKSQnLCAnXFwxJywgY2hhcmFjdGVyaXN0aWNzX2NoMS44KSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoZ2VvX2FjY2Vzc2lvbiwgdHlwZTpzdGF0dXMpDQpgYGANCg0KPGRpdiBzdHlsZT0ibWFyZ2luLWJvdHRvbTo1MHB4OyI+PC9kaXY+DQoNCk5vdyB3ZSBhc3NpZ24gYSBudW1lcmljYWwgdmFsdWUgdG8gYWxpdmUgYW5kIGRlYWQsIHdoZXJlIGRlYWQgPSAyLg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmVzUGhlbm90eXBlJHN0YXR1cyA9IGlmZWxzZShlc1BoZW5vdHlwZSRzdGF0dXMgPT0gJ0RlYWQnLCAyLCAxKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpOb3cgd2UgY2FuIHNhdmUgdGhlIHR3byBkYXRhIHNldHMsIGp1c3QgaW4gY2FzZSB3ZSB3YW50IHRvIHVzZSB0aGVtIGxhdGVyIG9uIGZvciBzb21ldGhpbmcgZWxzZSBhbmQgZG9uJ3Qgd2FudCB0byByZWRvIHRoaXMgcHJvY2Vzc2luZy4NCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpzYXZlUkRTKGVzRXhwcmVzc2lvbkFubm90YXRlZCwgcGFzdGUoYmFzZVJlcG9zaXRvcnksICcvZ2V0R2VvRGF0YUZvclN1cnZpdmFsQW5hbHlzaXMvZGF0YXNldF9zYXZvbGFFeHByZXNzaW9uLnJkcycsIHNlcCA9ICcnKSkNCnNhdmVSRFMoZXNQaGVub3R5cGUsIHBhc3RlKGJhc2VSZXBvc2l0b3J5LCAnL2dldEdlb0RhdGFGb3JTdXJ2aXZhbEFuYWx5c2lzL2RhdGFzZXRfc2F2b2xhUGhlbm90eXBlLnJkcycsIHNlcCA9ICcnKSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KTm93IHdlIGFyZSByZWFkeSB0byBtb3ZlIG9uIGFuZCBkbyB0aGUgc3Vydml2YWwgYW5hbHlzaXMuIEZvciBzaW1wbGljaXR5IGFuZCB0byBhdm9pZCBtZXNzaW5nIHVwIG15IG9yaWdpbmFsIGZpbGVzLCBJIGFtIGZpcnN0IGdvaW5nIHRvIHJlYWQgaW4gdGhlIGRhdGEgd2UganVzdCBzYXZlZCB0byBhbmQgc3RhcnQgZnJlc2ggZnJvbSBoZXJlIHdpdGggbmV3IG5hbWVzLg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KZXNFeHByZXNzaW9uID0gcmVhZFJEUyhwYXN0ZShiYXNlUmVwb3NpdG9yeSwgJy9nZXRHZW9EYXRhRm9yU3Vydml2YWxBbmFseXNpcy9kYXRhc2V0X3Nhdm9sYUV4cHJlc3Npb24ucmRzJywgc2VwID0gJycpKQ0KZXNQaGVub3R5cGUgPSByZWFkUkRTKHBhc3RlKGJhc2VSZXBvc2l0b3J5LCAnL2dldEdlb0RhdGFGb3JTdXJ2aXZhbEFuYWx5c2lzL2RhdGFzZXRfc2F2b2xhUGhlbm90eXBlLnJkcycsIHNlcCA9ICcnKSkNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KTm93IGV2ZXJ5dGhpbmcgaXMgc2V0IHRvIGRvIGEgc3Vydml2YWwgYW5hbHlzaXMuIEZpcnN0LCB3ZSBjYW4ganVzdCBkbyB0aGlzIHNpbXBseSBmb3IgYSBzaW5nbGUgZ2VuZS4gSSBmaW5kIHRoZSBlYXNpZXN0IHdheSB0byBkbyB0aGlzIGlzIHRvIHdyaXRlIGEgZnVuY3Rpb24gYXMgd2Ugd2FudCBpdCB0byBiZSByZXByb2R1Y2libGUgaWYgd2UgZGVjaWRlIHRvIGRvIGl0IGZvciBtYW55IGdlbmVzLiBGb3IgdGhpcywgSSBsaWtlIHRvIGNyZWF0ZSBhIG5ldyAuUiBmaWxlLCB3cml0ZSB0aGUgZnVuY3Rpb24gaW4gaXQsIGFuZCBjYWxsIGl0IGludG8gdGhpcyBzZXNzaW9uLiBTbywgaWYgeW91IGFyZSBpbiBSU3R1ZGlvLCBjbGljayBGaWxlPk5ldyBGaWxlPlIgU2NyaXB0LiBJbiBteSBjYXNlLCBJIGhhdmUgcHJvdmlkZWQgdGhpcyBmaWxlIG9uIEdpdEh1YiBhbmQgaXQgaXMgY2FsbGVkIFtnZXRHZW9EYXRhRm9yU3Vydml2YWxBbmFseXNpc0Z1bmN0aW9ucy5SXShodHRwczovL2dpdGh1Yi5jb20vY2hyaXNodWdlcy9wcm90b2NvbHNEcnlMYWIvYmxvYi9tYXN0ZXIvcmVsYXRlZFRvUHVibGlzaGVkRGF0YS9nZXRHZW9EYXRhRm9yU3Vydml2YWxBbmFseXNpc0Z1bmN0aW9ucy5SKS4gWW91IGNhbiBvcGVuIHRoaXMgZmlsZSB3aXRoIGFueSB0ZXh0IGVkaXRvciwgb3IgUlN0dWRpby4gU28sIGNyZWF0ZSB0aGUgZmlsZSwgYW5kIHJlYWQgaXQgaW4gdG8gdGhpcyBSIHNlc3Npb24uDQoNCmBgYHtyfQ0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpzb3VyY2UocGFzdGUoYmFzZVdvcmtzcGFjZSwgJy9nZXRHZW9EYXRhRm9yU3Vydml2YWxBbmFseXNpc0Z1bmN0aW9ucy5SJywgc2VwID0gJycpKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpJbiB0aGlzIGZpbGUsIHlvdSB3aWxsIGZpbmQgYSBmdW5jdGlvbiBjYWxsZWQgJ3N1cnZpdmFsQW5hbHlzaXMnLiBUaGlzIGZ1bmN0aW9uIHdpbGwgdGFrZSB5b3VyIGdlbmUgb2YgaW50ZXJlc3QsIHlvdXIgcGhlbm90eXBlIGFuZCBleHByZXNzaW9uIGRhdGEsIGFzIHdlbGwgYXMgaW5wdXRzIGFzIHRvIHdoZXRoZXIgeW91IHdvdWxkIGxpa2UgdG8gZ2VuZXJhdGUgYSBwbG90IG9yIG5vdC4gVGhlIGZ1bmN0aW9uIGV4cGVjdHMgdGhlIGRhdGEgdG8gYmUgaW4gdGhlIHNwZWNpZmljIGZvcm1hdCBhcyB3ZSBoYXZlIGNyZWF0ZWQgaGVyZSwgc28gYmUgY2FyZWZ1bCBvZiB0aGF0IGluIHlvdXIgb3duIHVzZS4gRXhwcmVzc2lvbiBkYXRhOg0KDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KaGVhZChlc0V4cHJlc3Npb24pDQoNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KQW5kIHRoZSBwaGVub3R5cGUgZGF0YToNCg0KYGBge3J9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmhlYWQoZXNQaGVub3R5cGUpDQpgYGANCg0KPGRpdiBzdHlsZT0ibWFyZ2luLWJvdHRvbTo1MHB4OyI+PC9kaXY+DQoNCk5vdyB3ZSBjYW4gY2FsbCBvdXIgZnVuY3Rpb24gb24gYSBnZW5lIHdlIGFyZSBpbnRlcmVzdGVkIGluLiBCeSBkZWZhdWx0LCB0aGlzIGZ1bmN0aW9uIHdpbGwgd3JpdGUgcGxvdCBhbmQgdGV4dCBmaWxlcyB0byBvdXIgYmFzZVJlcG9zaXRvcnkgZGlyZWN0b3J5LiBUaGUgJ3dyaXRlRGlyZWN0b3J5JyBvcHRpb24gb25seSBhbGxvd3MgeW91IHRvIGNoYW5nZSBmb2xkZXJzIHdpdGhpbiB0aGUgYmFzZVJlcG9zaXRvcnkgbG9jYXRpb24uIFlvdSBkbyBub3QgaGF2ZSB0byBzcGVjaWZ5ICd3cml0ZURpcmVjdG9yeScgaWYgdGhlIGJhc2VSZXBvc2l0b3J5IGlzIHN1aXRhYmxlIGZvciB5b3UuDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmdlbmVPZkludGVyZXN0ID0gJ1lCWDEnDQpzaW5nbGVTdXJ2aXZhbCA9IHN1cnZpdmFsQW5hbHlzaXMoZ2VuZU9mSW50ZXJlc3QgPSBnZW5lT2ZJbnRlcmVzdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXhwcmVzc2lvbkRhdGEgPSBlc0V4cHJlc3Npb24sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwaGVub3R5cGVEYXRhID0gZXNQaGVub3R5cGUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJ2aXZhbFBsb3QgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vydml2YWxEYXRhID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd3JpdGVEaXJlY3RvcnkgPSAnL2dldEdlb0RhdGFGb3JTdXJ2aXZhbEFuYWx5c2lzJykNCmBgYA0KDQo8ZGl2IHN0eWxlPSJtYXJnaW4tYm90dG9tOjUwcHg7Ij48L2Rpdj4NCg0KQSBjb3VwbGUgb2YgdGhpbmdzIG9mIG5vdGUgYWJvdXQgaG93IHRoaXMgZnVuY3Rpb24gb3BlcmF0ZXM6DQoNCiogRm9yIGEgZ2l2ZW4gaW5wdXQgZ2VuZSwgaXQgd2lsbCBmaXJzdCBwYXJzZSB0aGUgZXhwcmVzc2lvbiBkYXRhIHRvIHJldGFpbiBvbmx5IHByb2JlcyBmb3IgdGhhdCBnZW5lLiBCZWNhdXNlIHRoZXJlIGFyZSB1c3VhbGx5IG1hbnkgcHJvYmVzIGZvciBhIGdlbmUsIGl0IHJhbmtzIHRoZW0gYW5kIGtlZXBzIG9ubHkgdGhlIGhpZ2hlc3QgZXhwcmVzc2luZyBwcm9iZS4NCiogQWZ0ZXIgdGhpcywgdGhlIGV4cHJlc3Npb24gdmFsdWVzIGFjcm9zcyBhbGwgcGF0aWVudHMgZm9yIHRoaXMgcHJvYmUgYXJlIHJhbmtlZCwgYW5kIHRoZSB0b3AgYW5kIGJvdHRvbSAyNSUgb2YgcGF0aWVudHMgYXJlIGFzc2lnbmVkIGFzICdoaWdoJyBhbmQgJ2xvdycgZXhwcmVzc2lvbiBjYXNlcy4gDQoqIFRoZW4sIGl0IHBlcmZvcm1zIHN1cnZpdmFsIGFuYWx5c2lzIGJhc2VkIG9uIG92ZXJhbGwgc3Vydml2YWwgdXNpbmcgb25seSB0aGVzZSBoaWdoIGFuZCBsb3cgZXhwcmVzc2lvbiBjYXNlcy4gDQoqIFRoZSBwbG90IGNvbW1hbmQgdGVsbHMgaXQgd2hldGhlciB0byBvdXRwdXQgYSBzdXJ2aXZhbCBwbG90LCBhbmQgdGhlIGRhdGEgY29tbWFuZCB3aGV0aGVyIHRvIG91dHB1dCB0aGUgdGV4dCBvZiB0aGUgcmVzdWx0cy4gDQoqIFRoZSB2YXJpYWJsZSBhc3NpZ25tZW50IGhlcmUgdG8gJ3Rlc3RTdXJ2aXZhbCcgcmV0dXJucyB0aGUgY294WnNjb3JlIGZvciB0aGF0IGdlbmUsIGFsbG93aW5nIHlvdSB0byBnZXQgcmVzdWx0cyBmb3IgbWFueSBnZW5lcyB0byBjb21wYXJlLiANCg0KSWYgd2Ugd2FudCB0byBkbyBhIGxpc3Qgb2YgYSBidW5jaCBvZiBnZW5lcywgd2UgY2FuIGRvIHRoYXQgYXMgYmVsb3cuIEtlZXAgaW4gbWluZCB0aGF0IGlmIHlvdSBmZWVkIGl0IDEsMDAwIGdlbmVzLCBpdCB3aWxsIG1ha2UgMSwwMDAgcGxvdHMgYW5kIHRleHQgZmlsZXMgaWYgeW91IHRlbGwgaXQgdG8sIHNvIG1ha2Ugc3VyZSB5b3Ugd3JpdGUgdG8gYSBkaXJlY3Rvcnkgd2hlcmUgeW91IGNhbiBlYXNpbHkgbWFrZSBzZW5zZSBvZiB0aGlzIGlmIHlvdSBuZWVkIHRvIChwZXJoYXBzIGEgbmV3IGZvbGRlciBob2xkaW5nIGp1c3QgdGhlc2Ugb3V0cHV0IGZpbGVzKS4NCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0NCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KZ2VuZU9mSW50ZXJlc3QgPSBhcy5saXN0KGMoJ1lCWDEnLCdFSUY0QTEnLCdQT0RYTCcpKQ0KbXVsdGlTdXJ2aXZhbCA9IGxhcHBseShnZW5lT2ZJbnRlcmVzdCwgc3Vydml2YWxBbmFseXNpcywgDQogICAgICAgICAgICAgICAgICAgICAgIGV4cHJlc3Npb25EYXRhID0gZXNFeHByZXNzaW9uLCANCiAgICAgICAgICAgICAgICAgICAgICAgcGhlbm90eXBlRGF0YSA9IGVzUGhlbm90eXBlLCANCiAgICAgICAgICAgICAgICAgICAgICAgc3Vydml2YWxQbG90ID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgIHN1cnZpdmFsRGF0YSA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICB3cml0ZURpcmVjdG9yeSA9ICcvZ2V0R2VvRGF0YUZvclN1cnZpdmFsQW5hbHlzaXMnKQ0KYGBgDQoNCjxkaXYgc3R5bGU9Im1hcmdpbi1ib3R0b206NTBweDsiPjwvZGl2Pg0KDQpUaGlzIGlzIHRoZSBlbmQgb2YgdGhpcyBkb2N1bWVudC4gQXMgYSBmaW5hbCBjb21tYW5kLCB3ZSB3aWxsIHJ1biBzZXNzaW9uSW5mbyB0byBlbnN1cmUgb3RoZXJzIHdobyBsb29rIGF0IG91ciB3b3JrIGtub3cgZXhhY3RseSBob3cgb3VyIGVudmlyb25tZW50IHdhcyBzZXQgdXAuDQoNCmBgYHtyfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQo=