ChemmineR is a cheminformatics package for analyzing drug-like small molecule data in R. Its latest version contains functions for efficient processing of large numbers of small molecules, physicochemical/structural property predictions, structural similarity searching, classification and clustering of compound libraries with a wide spectrum of algorithms.

## Installation

The R software for running ChemmineR can be downloaded from CRAN (http://cran.at.r-project.org/). The ChemmineR package can be installed from R with:

 if (!requireNamespace("BiocManager", quietly=TRUE))
     install.packages("BiocManager")
 BiocManager::install("ChemmineR")

Loading the Package and Documentation

 library("ChemmineR") # Loads the package
 library(help="ChemmineR") # Lists all functions and classes
 vignette("ChemmineR") # Opens this PDF manual from R

## Five Minute Tutorial

The following code gives an overview of the most important functionalities provided by ChemmineR. Copy and paste of the commands into the R console will demonstrate their utilities.

Create Instances of SDFset class:

 data(sdfsample)
 sdfset <- sdfsample
 sdfset # Returns summary of SDFset
## An instance of "SDFset" with 100 molecules
 sdfset[1:4] # Subsetting of object
## An instance of "SDFset" with 4 molecules
 sdfset[[1]] # Returns summarized content of one SDF
## An instance of "SDF"
## 
## <<header>>
##                             Molecule_Name                                    Source 
##                                  "650001"                  "  -OEChem-07071010512D" 
##                                   Comment                               Counts_Line 
##                                        "" " 61 64  0     0  0  0  0  0  0999 V2000" 
## 
## <<atomblock>>
##           C1      C2  C3  C5  C6  C7  C8  C9 C10 C11 C12 C13 C14 C15 C16
## O_1   7.0468  0.0839   0   0   0   0   0   0   0   0   0   0   0   0   0
## O_2  12.2708  1.0492   0   0   0   0   0   0   0   0   0   0   0   0   0
## ...      ...     ... ... ... ... ... ... ... ... ... ... ... ... ... ...
## H_60  1.8411 -1.5985   0   0   0   0   0   0   0   0   0   0   0   0   0
## H_61  2.6597 -1.2843   0   0   0   0   0   0   0   0   0   0   0   0   0
## 
## <<bondblock>>
##      C1  C2  C3  C4  C5  C6  C7
## 1     1  16   2   0   0   0   0
## 2     2  23   1   0   0   0   0
## ... ... ... ... ... ... ... ...
## 63   33  60   1   0   0   0   0
## 64   33  61   1   0   0   0   0
## 
## <<datablock>> (33 data items)
##           PUBCHEM_COMPOUND_CID PUBCHEM_COMPOUND_CANONICALIZED      PUBCHEM_CACTVS_COMPLEXITY 
##                       "650001"                            "1"                          "700" 
##  PUBCHEM_CACTVS_HBOND_ACCEPTOR                                
##                            "7"                          "..."
 view(sdfset[1:4]) # Returns summarized content of many SDFs, not printed here
 as(sdfset[1:4], "list") # Returns complete content of many SDFs, not printed here

An SDFset is created during the import of an SD file:

 sdfset <- read.SDFset("http://faculty.ucr.edu/~tgirke/Documents/R_BioCond/Samples/sdfsample.sdf")

Miscellaneous accessor methods for SDFset container:

 header(sdfset[1:4]) # Not printed here
 header(sdfset[[1]])
##                             Molecule_Name                                    Source 
##                                  "650001"                  "  -OEChem-07071010512D" 
##                                   Comment                               Counts_Line 
##                                        "" " 61 64  0     0  0  0  0  0  0999 V2000"
 atomblock(sdfset[1:4]) # Not printed here
atomblock(sdfset[[1]])[1:4,]
##          C1     C2 C3 C5 C6 C7 C8 C9 C10 C11 C12 C13 C14 C15 C16
## O_1  7.0468 0.0839  0  0  0  0  0  0   0   0   0   0   0   0   0
## O_2 12.2708 1.0492  0  0  0  0  0  0   0   0   0   0   0   0   0
## O_3 12.2708 3.1186  0  0  0  0  0  0   0   0   0   0   0   0   0
## O_4  7.9128 2.5839  0  0  0  0  0  0   0   0   0   0   0   0   0
bondblock(sdfset[1:4]) # Not printed here
 bondblock(sdfset[[1]])[1:4,]
##   C1 C2 C3 C4 C5 C6 C7
## 1  1 16  2  0  0  0  0
## 2  2 23  1  0  0  0  0
## 3  2 27  1  0  0  0  0
## 4  3 25  1  0  0  0  0
 datablock(sdfset[1:4]) # Not printed here
 datablock(sdfset[[1]])[1:4]
##           PUBCHEM_COMPOUND_CID PUBCHEM_COMPOUND_CANONICALIZED      PUBCHEM_CACTVS_COMPLEXITY 
##                       "650001"                            "1"                          "700" 
##  PUBCHEM_CACTVS_HBOND_ACCEPTOR 
##                            "7"

Assigning compound IDs and keeping them unique:

 cid(sdfset)[1:4] # Returns IDs from SDFset object
## [1] "CMP1" "CMP2" "CMP3" "CMP4"
 sdfid(sdfset)[1:4] # Returns IDs from SD file header block
## [1] "650001" "650002" "650003" "650004"
 unique_ids <- makeUnique(sdfid(sdfset))
## [1] "No duplicates detected!"
 cid(sdfset) <- unique_ids

Converting the data blocks in an SDFset to a matrix:

 blockmatrix <- datablock2ma(datablocklist=datablock(sdfset)) # Converts data block to matrix
 numchar <- splitNumChar(blockmatrix=blockmatrix) # Splits to numeric and character matrix
 numchar[[1]][1:2,1:2] # Slice of numeric matrix
##        PUBCHEM_COMPOUND_CID PUBCHEM_COMPOUND_CANONICALIZED
## 650001               650001                              1
## 650002               650002                              1
 numchar[[2]][1:2,10:11] # Slice of character matrix
##        PUBCHEM_MOLECULAR_FORMULA PUBCHEM_OPENEYE_CAN_SMILES                                     
## 650001 "C23H28N4O6"              "CC1=CC(=NO1)NC(=O)CCC(=O)N(CC(=O)NC2CCCC2)C3=CC4=C(C=C3)OCCO4"
## 650002 "C18H23N5O3"              "CN1C2=C(C(=O)NC1=O)N(C(=N2)NCCCO)CCCC3=CC=CC=C3"

Compute atom frequency matrix, molecular weight and formula:

 propma <- data.frame(MF=MF(sdfset), MW=MW(sdfset), atomcountMA(sdfset))
 propma[1:4, ]
##                 MF       MW  C  H N O S F Cl
## 650001  C23H28N4O6 456.4916 23 28 4 6 0 0  0
## 650002  C18H23N5O3 357.4069 18 23 5 3 0 0  0
## 650003 C18H18N4O3S 370.4255 18 18 4 3 1 0  0
## 650004 C21H27N5O5S 461.5346 21 27 5 5 1 0  0

Assign matrix data to data block:

 datablock(sdfset) <- propma
 datablock(sdfset[1])
## $`650001`
##           MF           MW            C            H            N            O            S 
## "C23H28N4O6"   "456.4916"         "23"         "28"          "4"          "6"          "0" 
##            F           Cl 
##          "0"          "0"

String searching in SDFset:

 grepSDFset("650001", sdfset, field="datablock", mode="subset") # Returns summary view of matches. Not printed here.
 grepSDFset("650001", sdfset, field="datablock", mode="index")
## 1 1 1 1 1 1 1 1 1 
## 1 2 3 4 5 6 7 8 9

Export SDFset to SD file:

 write.SDF(sdfset[1:4], file="sub.sdf", sig=TRUE)

Plot molecule structure of one or many SDFs:

 plot(sdfset[1:4], print=FALSE) # Plots structures to R graphics device

 sdf.visualize(sdfset[1:4]) # Compound viewing in web browser

Figure: Visualization webpage created by calling sdf.visualize.

Structure similarity searching and clustering:

 apset <- sdf2ap(sdfset) # Generate atom pair descriptor database for searching
 data(apset) # Load sample apset data provided by library.
 cmp.search(apset, apset[1], type=3, cutoff = 0.3, quiet=TRUE) # Search apset database with single compound.
##   index    cid    scores
## 1     1 650001 1.0000000
## 2    96 650102 0.3516643
## 3    67 650072 0.3117569
## 4    88 650094 0.3094629
## 5    15 650015 0.3010753
 cmp.cluster(db=apset, cutoff = c(0.65, 0.5), quiet=TRUE)[1:4,] # Binning clustering using variable similarity cutoffs.
## 
## sorting result...
##       ids CLSZ_0.65 CLID_0.65 CLSZ_0.5 CLID_0.5
## 48 650049         2        48        2       48
## 49 650050         2        48        2       48
## 54 650059         2        54        2       54
## 55 650060         2        54        2       54

### Molecular Structure Data

Classes

Functions/Methods (mainly for SDFset container, SMIset should be coerced with smiles2sd to SDFset)

Back to Table of Contents

Structure Descriptor Data

Classes

Functions/Methods

Back to Table of Contents

Import of Compounds

SDF Import

The following gives an overview of the most important import/export functionalities for small molecules provided by ChemmineR. The given example creates an instance of the SDFset class using as sample data set the first 100 compounds from this PubChem SD file (SDF): Compound_00650001_00675000.sdf.gz (ftp://ftp.ncbi.nih.gov/pubchem/Compound/CURRENT-Full/SDF/).

SDFs can be imported with the read.SDFset function:

 sdfset <- read.SDFset("http://faculty.ucr.edu/~tgirke/Documents/R_BioCond/Samples/sdfsample.sdf")
 data(sdfsample) # Loads the same SDFset provided by the library
 sdfset <- sdfsample
 valid <- validSDF(sdfset) # Identifies invalid SDFs in SDFset objects
 sdfset <- sdfset[valid] # Removes invalid SDFs, if there are any

Import SD file into SDFstr container:

 sdfstr <- read.SDFstr("http://faculty.ucr.edu/~tgirke/Documents/R_BioCond/Samples/sdfsample.sdf")

Create SDFset from SDFstr class:

 sdfstr <- as(sdfset, "SDFstr")
 sdfstr
## An instance of "SDFstr" with 100 molecules
 as(sdfstr, "SDFset")
## An instance of "SDFset" with 100 molecules
Back to Table of Contents

SMILES Import

The read.SMIset function imports one or many molecules from a SMILES file and stores them in a SMIset container. The input file is expected to contain one SMILES string per row with tab-separated compound identifiers at the end of each line. The compound identifiers are optional.

Create sample SMILES file and then import it:

 data(smisample); smiset <- smisample
 write.SMI(smiset[1:4], file="sub.smi")
 smiset <- read.SMIset("sub.smi")

Inspect content of SMIset:

 data(smisample) # Loads the same SMIset provided by the library
 smiset <- smisample
 smiset
## An instance of "SMIset" with 100 molecules
 view(smiset[1:2])
## $`650001`
## An instance of "SMI"
## [1] "O=C(NC1CCCC1)CN(c1cc2OCCOc2cc1)C(=O)CCC(=O)Nc1noc(c1)C"
## 
## $`650002`
## An instance of "SMI"
## [1] "O=c1[nH]c(=O)n(c2nc(n(CCCc3ccccc3)c12)NCCCO)C"

Accessor functions:

 cid(smiset[1:4])
## [1] "650001" "650002" "650003" "650004"
 smi <- as.character(smiset[1:2])

Create SMIset from named character vector:

 as(smi, "SMIset")
## An instance of "SMIset" with 2 molecules
Back to Table of Contents

Export of Compounds

SDF Export

Write objects of classes SDFset/SDFstr/SDF to SD file:

 write.SDF(sdfset[1:4], file="sub.sdf")

Writing customized SDFset to file containing ChemmineR signature, IDs from SDFset and no data block:

 write.SDF(sdfset[1:4], file="sub.sdf", sig=TRUE, cid=TRUE, db=NULL)

Example for injecting a custom matrix/data frame into the data block of an SDFset and then writing it to an SD file:

 props <- data.frame(MF=MF(sdfset), MW=MW(sdfset), atomcountMA(sdfset))
 datablock(sdfset) <- props
 write.SDF(sdfset[1:4], file="sub.sdf", sig=TRUE, cid=TRUE)

Indirect export via SDFstr object:

 sdf2str(sdf=sdfset[[1]], sig=TRUE, cid=TRUE) # Uses default components
 sdf2str(sdf=sdfset[[1]], head=letters[1:4], db=NULL) # Uses custom components for header and data block

Write SDF, SDFset or SDFstr classes to file:

 write.SDF(sdfset[1:4], file="sub.sdf", sig=TRUE, cid=TRUE, db=NULL)
 write.SDF(sdfstr[1:4], file="sub.sdf")
 cat(unlist(as(sdfstr[1:4], "list")), file="sub.sdf", sep="")
Back to Table of Contents

SMILES Export

Write objects of class SMIset to SMILES file with and without compound identifiers:

 data(smisample); smiset <- smisample # Sample data set

 write.SMI(smiset[1:4], file="sub.smi", cid=TRUE) write.SMI(smiset[1:4], file="sub.smi", cid=FALSE)
Back to Table of Contents

Format Interconversions

The sdf2smiles and smiles2sdf functions provide format interconversion between SMILES strings (Simplified Molecular Input Line Entry Specification) and SDFset containers.

Convert an SDFset container to a SMILES character string:

 data(sdfsample);
 sdfset <- sdfsample[1]
 smiles <- sdf2smiles(sdfset)
 smiles

Convert a SMILES character string to an SDFset container:

 sdf <- smiles2sdf("CC(=O)OC1=CC=CC=C1C(=O)O")
 view(sdf)

When the ChemineOB package is installed these conversions are performed with the OpenBabel Open Source Chemistry Toolbox. Otherwise the functions will fall back to using the ChemMine Tools web service for this operation. The latter will require internet connectivity and is limited to only the first compound given. ChemmineOB provides access to the compound format conversion functions of OpenBabel. Currently, over 160 formats are supported by OpenBabel. The functions convertFormat and convertFormatFile can be used to convert files or strings between any two formats supported by OpenBabel. For example, to convert a SMILES string to an SDF string, one can use the convertFormat function.

 sdfStr <- convertFormat("SMI","SDF","CC(=O)OC1=CC=CC=C1C(=O)O_name")

This will return the given compound as an SDF formatted string. 2D coordinates are also computed and included in the resulting SDF string.

To convert a file with compounds encoded in one format to another format, the convertFormatFile function can be used instead.

 convertFormatFile("SMI","SDF","test.smiles","test.sdf")

To see the whole list of file formats supported by OpenBabel, one can run from the command-line “obabel -L formats”.

Back to Table of Contents

Splitting SD Files

The following write.SDFsplit function allows to split SD Files into any number of smaller SD Files. This can become important when working with very big SD Files. Users should note that this function can output many files, thus one should run it in a dedicated directory!

Create sample SD File with 100 molecules:

 write.SDF(sdfset, "test.sdf")

Read in sample SD File. Note: reading file into SDFstr is much faster than into SDFset:

 sdfstr <- read.SDFstr("test.sdf")

Run export on SDFstr object:

 write.SDFsplit(x=sdfstr, filetag="myfile", nmol=10) # 'nmol' defines the number of molecules to write to each file

Run export on SDFset object:

 write.SDFsplit(x=sdfset, filetag="myfile", nmol=10)

Searching PubChem

Get Compounds from PubChem by Id

The function getIds accepts one or more numeric PubChem compound ids and downloads the corresponding compounds from PubChem Power User Gateway (PUG) returning results in an SDFset container. The ChemMine Tools web service is used as an intermediate, to translate queries from plain HTTP POST to a PUG SOAP query.

Fetch 2 compounds from PubChem:

 compounds <- getIds(c(111,123))
 compounds
Back to Table of Contents

Search a SMILES Query in PubChem

The function searchString accepts one SMILES string (Simplified Molecular Input Line Entry Specification) and performs a >0.95 similarity PubChem fingerprint search, returning the hits in an SDFset container. The ChemMine Tools web service is used as an intermediate, to translate queries from plain HTTP POST to a PubChem Power User Gateway (PUG) query.

Search a SMILES string on PubChem:

 compounds <- searchString("CC(=O)OC1=CC=CC=C1C(=O)O") compounds
Back to Table of Contents

Search an SDF Query in PubChem

The function searchSim performs a PubChem similarity search just like searchString, but accepts a query in an SDFset container. If the query contains more than one compound, only the first is searched.

Search an SDFset container on PubChem:

 data(sdfsample);
 sdfset <- sdfsample[1]
 compounds <- searchSim(sdfset)
 compounds
LS0tCnRpdGxlOiAiTGVjdHVyZSAzOiBDaGVtbWluZVIiCi0tLQoKCmBgYHtyIHN0eWxlLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAnYXNpcyd9Cm9wdGlvbnMod2lkdGg9MTAwLCBtYXgucHJpbnQ9MTAwMCkKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogICAgZXZhbD1hcy5sb2dpY2FsKFN5cy5nZXRlbnYoIktOSVRSX0VWQUwiLCAiVFJVRSIpKSwKICAgIGNhY2hlPWFzLmxvZ2ljYWwoU3lzLmdldGVudigiS05JVFJfQ0FDSEUiLCAiVFJVRSIpKQogICAgKQpgYGAKCmBgYHtyIHNldHVwLCBlY2hvPUZBTFNFLCBtZXNzYWdlcz1GQUxTRSwgd2FybmluZ3M9RkFMU0V9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgICBsaWJyYXJ5KENoZW1taW5lUikKICAgIGxpYnJhcnkoZm1jc1IpCiAgICBsaWJyYXJ5KGdncGxvdDIpCgkgI2xpYnJhcnkoQ2hlbW1pbmVPQikKfSkKYGBgCgoKYENoZW1taW5lUmAgaXMgYSBjaGVtaW5mb3JtYXRpY3MgcGFja2FnZSBmb3IgYW5hbHl6aW5nCmRydWctbGlrZSBzbWFsbCBtb2xlY3VsZSBkYXRhIGluIFIuIEl0cyBsYXRlc3QgdmVyc2lvbiBjb250YWlucwpmdW5jdGlvbnMgZm9yIGVmZmljaWVudCBwcm9jZXNzaW5nIG9mIGxhcmdlIG51bWJlcnMgb2Ygc21hbGwgbW9sZWN1bGVzLApwaHlzaWNvY2hlbWljYWwvc3RydWN0dXJhbCBwcm9wZXJ0eSBwcmVkaWN0aW9ucywgc3RydWN0dXJhbCBzaW1pbGFyaXR5CnNlYXJjaGluZywgY2xhc3NpZmljYXRpb24gYW5kIGNsdXN0ZXJpbmcgb2YgY29tcG91bmQgbGlicmFyaWVzIHdpdGggYQp3aWRlIHNwZWN0cnVtIG9mIGFsZ29yaXRobXMuCgoKIyMgSW5zdGFsbGF0aW9uCi0tLS0tLS0tLS0tLQoKVGhlIFIgc29mdHdhcmUgZm9yIHJ1bm5pbmcgQ2hlbW1pbmVSIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gQ1JBTgooPGh0dHA6Ly9jcmFuLmF0LnItcHJvamVjdC5vcmcvPikuIFRoZSBDaGVtbWluZVIgcGFja2FnZSBjYW4gYmUKaW5zdGFsbGVkIGZyb20gUiB3aXRoOgoKYGBge3IgZXZhbD1GQUxTRX0KIGlmICghcmVxdWlyZU5hbWVzcGFjZSgiQmlvY01hbmFnZXIiLCBxdWlldGx5PVRSVUUpKQogICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJDaGVtbWluZVIiKQpgYGAKCkxvYWRpbmcgdGhlIFBhY2thZ2UgYW5kIERvY3VtZW50YXRpb24KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKCmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KIGxpYnJhcnkoIkNoZW1taW5lUiIpICMgTG9hZHMgdGhlIHBhY2thZ2UKYGBgCgpgYGB7ciBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogbGlicmFyeShoZWxwPSJDaGVtbWluZVIiKSAjIExpc3RzIGFsbCBmdW5jdGlvbnMgYW5kIGNsYXNzZXMKIHZpZ25ldHRlKCJDaGVtbWluZVIiKSAjIE9wZW5zIHRoaXMgUERGIG1hbnVhbCBmcm9tIFIKYGBgCgoKIyMgRml2ZSBNaW51dGUgVHV0b3JpYWwKLS0tLS0tLS0tLS0tLS0tLS0tLS0KClRoZSBmb2xsb3dpbmcgY29kZSBnaXZlcyBhbiBvdmVydmlldyBvZiB0aGUgbW9zdCBpbXBvcnRhbnQKZnVuY3Rpb25hbGl0aWVzIHByb3ZpZGVkIGJ5IGBDaGVtbWluZVJgLiBDb3B5IGFuZCBwYXN0ZQpvZiB0aGUgY29tbWFuZHMgaW50byB0aGUgUiBjb25zb2xlIHdpbGwgZGVtb25zdHJhdGUgdGhlaXIgdXRpbGl0aWVzLgoKQ3JlYXRlIEluc3RhbmNlcyBvZiBgU0RGc2V0YCBjbGFzczoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogZGF0YShzZGZzYW1wbGUpCiBzZGZzZXQgPC0gc2Rmc2FtcGxlCiBzZGZzZXQgIyBSZXR1cm5zIHN1bW1hcnkgb2YgU0RGc2V0CiBzZGZzZXRbMTo0XSAjIFN1YnNldHRpbmcgb2Ygb2JqZWN0Cgogc2Rmc2V0W1sxXV0gIyBSZXR1cm5zIHN1bW1hcml6ZWQgY29udGVudCBvZiBvbmUgU0RGCgoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHZpZXcoc2Rmc2V0WzE6NF0pICMgUmV0dXJucyBzdW1tYXJpemVkIGNvbnRlbnQgb2YgbWFueSBTREZzLCBub3QgcHJpbnRlZCBoZXJlCiBhcyhzZGZzZXRbMTo0XSwgImxpc3QiKSAjIFJldHVybnMgY29tcGxldGUgY29udGVudCBvZiBtYW55IFNERnMsIG5vdCBwcmludGVkIGhlcmUKCmBgYAoKCkFuIGBTREZzZXRgIGlzIGNyZWF0ZWQgZHVyaW5nIHRoZSBpbXBvcnQgb2YgYW4gU0QgZmlsZToKCmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBzZGZzZXQgPC0gcmVhZC5TREZzZXQoImh0dHA6Ly9mYWN1bHR5LnVjci5lZHUvfnRnaXJrZS9Eb2N1bWVudHMvUl9CaW9Db25kL1NhbXBsZXMvc2Rmc2FtcGxlLnNkZiIpCmBgYAoKCk1pc2NlbGxhbmVvdXMgYWNjZXNzb3IgbWV0aG9kcyBmb3IgYFNERnNldGAgY29udGFpbmVyOgoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIGhlYWRlcihzZGZzZXRbMTo0XSkgIyBOb3QgcHJpbnRlZCBoZXJlCmBgYAoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogaGVhZGVyKHNkZnNldFtbMV1dKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBhdG9tYmxvY2soc2Rmc2V0WzE6NF0pICMgTm90IHByaW50ZWQgaGVyZQpgYGAKCmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KYXRvbWJsb2NrKHNkZnNldFtbMV1dKVsxOjQsXQpgYGAKCmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CmJvbmRibG9jayhzZGZzZXRbMTo0XSkgIyBOb3QgcHJpbnRlZCBoZXJlCmBgYAoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogYm9uZGJsb2NrKHNkZnNldFtbMV1dKVsxOjQsXQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIGRhdGFibG9jayhzZGZzZXRbMTo0XSkgIyBOb3QgcHJpbnRlZCBoZXJlCmBgYAoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogZGF0YWJsb2NrKHNkZnNldFtbMV1dKVsxOjRdCmBgYAoKCkFzc2lnbmluZyBjb21wb3VuZCBJRHMgYW5kIGtlZXBpbmcgdGhlbSB1bmlxdWU6CmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KIGNpZChzZGZzZXQpWzE6NF0gIyBSZXR1cm5zIElEcyBmcm9tIFNERnNldCBvYmplY3QKIHNkZmlkKHNkZnNldClbMTo0XSAjIFJldHVybnMgSURzIGZyb20gU0QgZmlsZSBoZWFkZXIgYmxvY2sKIHVuaXF1ZV9pZHMgPC0gbWFrZVVuaXF1ZShzZGZpZChzZGZzZXQpKQogY2lkKHNkZnNldCkgPC0gdW5pcXVlX2lkcwpgYGAKCgpDb252ZXJ0aW5nIHRoZSBkYXRhIGJsb2NrcyBpbiBhbiBgU0RGc2V0YCB0byBhIG1hdHJpeDoKCmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KIGJsb2NrbWF0cml4IDwtIGRhdGFibG9jazJtYShkYXRhYmxvY2tsaXN0PWRhdGFibG9jayhzZGZzZXQpKSAjIENvbnZlcnRzIGRhdGEgYmxvY2sgdG8gbWF0cml4CiBudW1jaGFyIDwtIHNwbGl0TnVtQ2hhcihibG9ja21hdHJpeD1ibG9ja21hdHJpeCkgIyBTcGxpdHMgdG8gbnVtZXJpYyBhbmQgY2hhcmFjdGVyIG1hdHJpeAogbnVtY2hhcltbMV1dWzE6MiwxOjJdICMgU2xpY2Ugb2YgbnVtZXJpYyBtYXRyaXgKIG51bWNoYXJbWzJdXVsxOjIsMTA6MTFdICMgU2xpY2Ugb2YgY2hhcmFjdGVyIG1hdHJpeApgYGAKCgpDb21wdXRlIGF0b20gZnJlcXVlbmN5IG1hdHJpeCwgbW9sZWN1bGFyIHdlaWdodCBhbmQgZm9ybXVsYToKCmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KIHByb3BtYSA8LSBkYXRhLmZyYW1lKE1GPU1GKHNkZnNldCksIE1XPU1XKHNkZnNldCksIGF0b21jb3VudE1BKHNkZnNldCkpCiBwcm9wbWFbMTo0LCBdCmBgYAoKCkFzc2lnbiBtYXRyaXggZGF0YSB0byBkYXRhIGJsb2NrOgpgYGB7ciBldmFsPVRSVUUsIHRpZHk9RkFMU0V9CiBkYXRhYmxvY2soc2Rmc2V0KSA8LSBwcm9wbWEKIGRhdGFibG9jayhzZGZzZXRbMV0pCmBgYAoKClN0cmluZyBzZWFyY2hpbmcgaW4gYFNERnNldGA6CmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBncmVwU0RGc2V0KCI2NTAwMDEiLCBzZGZzZXQsIGZpZWxkPSJkYXRhYmxvY2siLCBtb2RlPSJzdWJzZXQiKSAjIFJldHVybnMgc3VtbWFyeSB2aWV3IG9mIG1hdGNoZXMuIE5vdCBwcmludGVkIGhlcmUuCmBgYAoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogZ3JlcFNERnNldCgiNjUwMDAxIiwgc2Rmc2V0LCBmaWVsZD0iZGF0YWJsb2NrIiwgbW9kZT0iaW5kZXgiKQpgYGAKCgpFeHBvcnQgU0RGc2V0IHRvIFNEIGZpbGU6CmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiB3cml0ZS5TREYoc2Rmc2V0WzE6NF0sIGZpbGU9InN1Yi5zZGYiLCBzaWc9VFJVRSkKYGBgCgoKUGxvdCBtb2xlY3VsZSBzdHJ1Y3R1cmUgb2Ygb25lIG9yIG1hbnkgU0RGczoKYGBge3IgcGxvdHN0cnVjdCwgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogcGxvdChzZGZzZXRbMTo0XSwgcHJpbnQ9RkFMU0UpICMgUGxvdHMgc3RydWN0dXJlcyB0byBSIGdyYXBoaWNzIGRldmljZQpgYGAKCgoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHNkZi52aXN1YWxpemUoc2Rmc2V0WzE6NF0pICMgQ29tcG91bmQgdmlld2luZyBpbiB3ZWIgYnJvd3NlcgpgYGAKCiFbRmlndXJlOiBWaXN1YWxpemF0aW9uIHdlYnBhZ2UgY3JlYXRlZCBieSBjYWxsaW5nCmBzZGYudmlzdWFsaXplYC5dKHZpc3VhbGl6ZXNjcmVlbnNob3Qtc21hbGwucG5nICkKClN0cnVjdHVyZSBzaW1pbGFyaXR5IHNlYXJjaGluZyBhbmQgY2x1c3RlcmluZzoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIGFwc2V0IDwtIHNkZjJhcChzZGZzZXQpICMgR2VuZXJhdGUgYXRvbSBwYWlyIGRlc2NyaXB0b3IgZGF0YWJhc2UgZm9yIHNlYXJjaGluZwpgYGAKCmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KIGRhdGEoYXBzZXQpICMgTG9hZCBzYW1wbGUgYXBzZXQgZGF0YSBwcm92aWRlZCBieSBsaWJyYXJ5LgogY21wLnNlYXJjaChhcHNldCwgYXBzZXRbMV0sIHR5cGU9MywgY3V0b2ZmID0gMC4zLCBxdWlldD1UUlVFKSAjIFNlYXJjaCBhcHNldCBkYXRhYmFzZSB3aXRoIHNpbmdsZSBjb21wb3VuZC4KCiBjbXAuY2x1c3RlcihkYj1hcHNldCwgY3V0b2ZmID0gYygwLjY1LCAwLjUpLCBxdWlldD1UUlVFKVsxOjQsXSAjIEJpbm5pbmcgY2x1c3RlcmluZyB1c2luZyB2YXJpYWJsZSBzaW1pbGFyaXR5IGN1dG9mZnMuCmBgYAoKCiMjIyBNb2xlY3VsYXIgU3RydWN0dXJlIERhdGEKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpDbGFzc2VzCgotICAgYFNERnN0cmA6IGludGVybWVkaWF0ZSBzdHJpbmcgY2xhc3MgdG8gZmFjaWxpdGF0ZSBTRAogICAgZmlsZSBpbXBvcnQ7IG5vdCBpbXBvcnRhbnQgZm9yIGVuZCB1c2VyCgotICAgYFNERmA6IGNvbnRhaW5lciBmb3Igc2luZ2xlIG1vbGVjdWxlIGltcG9ydGVkIGZyb20gYW4KICAgIFNEIGZpbGUKCi0gICBgU0RGc2V0YDogY29udGFpbmVyIGZvciBtYW55IFNERiBvYmplY3RzOyBtb3N0CiAgICBpbXBvcnRhbnQgc3RydWN0dXJlIGNvbnRhaW5lciBmb3IgZW5kIHVzZXIKCi0gICBgU01JYDogY29udGFpbmVyIGZvciBhIHNpbmdsZSBTTUlMRVMgc3RyaW5nCgotICAgYFNNSXNldGA6IGNvbnRhaW5lciBmb3IgbWFueSBTTUlMRVMgc3RyaW5ncwoKRnVuY3Rpb25zL01ldGhvZHMgKG1haW5seSBmb3IgYFNERnNldGAgY29udGFpbmVyLApgU01Jc2V0YCBzaG91bGQgYmUgY29lcmNlZCB3aXRoCmBzbWlsZXMyc2RgIHRvIGBTREZzZXRgKQoKLSAgIEFjY2Vzc29yIG1ldGhvZHMgZm9yIGBTREYvU0RGc2V0YAoKICAgIC0gICBPYmplY3Qgc2xvdHM6IGBjaWRgLCBgaGVhZGVyYCwgYGF0b21ibG9ja2AsIGBib25kYmxvY2tgLAogICAgICAgIGBkYXRhYmxvY2tgIChgc2RmaWRgLCBgZGF0YWJsb2NrdGFnYCkKCiAgICAtICAgU3VtbWFyeSBvZiBgU0RGc2V0YDogYHZpZXdgCgogICAgLSAgIE1hdHJpeCBjb252ZXJzaW9uIG9mIGRhdGEgYmxvY2s6IGBkYXRhYmxvY2sybWFgLAogICAgICAgIGBzcGxpdE51bUNoYXJgCgogICAgLSAgIFN0cmluZyBzZWFyY2ggaW4gU0RGc2V0OiBgZ3JlcFNERnNldGAKCi0gICBDb2VyY2Ugb25lIGNsYXNzIHRvIGFub3RoZXIKCiAgICAtICAgU3RhbmRhcmQgc3ludGF4IGBhcyguLi4sICIuLi4iKWAgd29ya3MgaW4gbW9zdAogICAgICAgIGNhc2VzLiBGb3IgZGV0YWlscyBzZWUgUiBoZWxwIHdpdGgKICAgICAgIGA/IlNERnNldC1jbGFzcyJgLgoKLSAgIFV0aWxpdGllcwoKICAgIC0gICBBdG9tIGZyZXF1ZW5jaWVzOiBgYXRvbWNvdW50TUFgLCBgYXRvbWNvdW50YAoKICAgIC0gICBNb2xlY3VsYXIgd2VpZ2h0OiBgTVdgCgogICAgLSAgIE1vbGVjdWxhciBmb3JtdWxhOiBgTUZgCgogICAgLSAgIC4uLgoKLSAgIENvbXBvdW5kIHN0cnVjdHVyZSBkZXBpY3Rpb25zCgogICAgLSAgIFIgZ3JhcGhpY3MgZGV2aWNlOiBgcGxvdGAsIGBwbG90U3RydWNgCgogICAgLSAgIE9ubGluZTogYGNtcC52aXN1YWxpemVgCgo8ZGl2IGFsaWduPSJyaWdodCI+W0JhY2sgdG8gVGFibGUgb2YgQ29udGVudHNdKCk8L2Rpdj4KClN0cnVjdHVyZSBEZXNjcmlwdG9yIERhdGEKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKQ2xhc3NlcwoKLSAgIGBBUGA6IGNvbnRhaW5lciBmb3IgYXRvbSBwYWlyIGRlc2NyaXB0b3JzIG9mIGEgc2luZ2xlCiAgICBtb2xlY3VsZQoKLSAgIGBBUHNldGA6IGNvbnRhaW5lciBmb3IgbWFueSBBUCBvYmplY3RzOyBtb3N0CiAgICBpbXBvcnRhbnQgc3RydWN0dXJlIGRlc2NyaXB0b3IgY29udGFpbmVyIGZvciBlbmQgdXNlcgoKLSAgIGBGUGA6IGNvbnRhaW5lciBmb3IgZmluZ2VycHJpbnQgb2YgYSBzaW5nbGUgbW9sZWN1bGUKCi0gICBgRlBzZXRgOiBjb250YWluZXIgZm9yIGZpbmdlcnByaW50cyBvZiBtYW55CiAgICBtb2xlY3VsZXMsIG1vc3QgaW1wb3J0YW50IHN0cnVjdHVyZSBkZXNjcmlwdG9yIGNvbnRhaW5lciBmb3IgZW5kCiAgICB1c2VyCgpGdW5jdGlvbnMvTWV0aG9kcwoKLSAgIENyZWF0ZSBgQVAvQVBzZXRgIGluc3RhbmNlcwoKICAgIC0gICBGcm9tIGBTREZzZXRgOiBgc2RmMmFwYAoKICAgIC0gICBGcm9tIFNEIGZpbGU6IGBjbXAucGFyc2VgCgogICAgLSAgIFN1bW1hcnkgb2YgYEFQL0FQc2V0YDogYHZpZXdgLAogICAgICAgIGBkYi5leHBsYWluYAoKLSAgIEFjY2Vzc29yIG1ldGhvZHMgZm9yIEFQL0FQc2V0CgogICAgLSAgIE9iamVjdCBzbG90czogYGFwYCwgYGNpZGAKCi0gICBDb2VyY2Ugb25lIGNsYXNzIHRvIGFub3RoZXIKCiAgICAtICAgU3RhbmRhcmQgc3ludGF4IGBhcyguLi4sICIuLi4iKWAgd29ya3MgaW4gbW9zdAogICAgICAgIGNhc2VzLiBGb3IgZGV0YWlscyBzZWUgUiBoZWxwIHdpdGgKICAgICAgICBgPyJBUHNldC1jbGFzcyJgLgoKLSAgIFN0cnVjdHVyZSBTaW1pbGFyaXR5IGNvbXBhcmlzb25zIGFuZCBTZWFyY2hpbmcKCiAgICAtICAgQ29tcHV0ZSBwYWlyd2lzZSBzaW1pbGFyaXRpZXMgOiBgY21wLnNpbWlsYXJpdHlgLAogICAgICAgIGBmcFNpbWAKCiAgICAtICAgU2VhcmNoIEFQc2V0IGRhdGFiYXNlOiBgY21wLnNlYXJjaGAsIGBmcFNpbWAKCi0gICBBUC1iYXNlZCBTdHJ1Y3R1cmUgU2ltaWxhcml0eSBDbHVzdGVyaW5nCgogICAgLSAgIFNpbmdsZS1saW5rYWdlIGJpbm5pbmcgY2x1c3RlcmluZzogYGNtcC5jbHVzdGVyYAoKICAgIC0gICBWaXN1YWxpemUgY2x1c3RlcmluZyByZXN1bHQgd2l0aCBNRFM6CiAgICAgICAgYGNsdXN0ZXIudmlzdWFsaXplYAoKICAgIC0gICBTaXplIGRpc3RyaWJ1dGlvbiBvZiBjbHVzdGVyczogYGNsdXN0ZXIuc2l6ZXN0YXRgCi0gICBGb2xkaW5nCiAgICAtIEZvbGQgYSBkZXNjcmlwdG9yIHdpdGggYGZvbGRgCgkgLSBRdWVyeSB0aGUgbnVtYmVyIG9mIHRpbWVzIGEgZGVzY3JpcHRvciBoYXMgYmVlbiBmb2xkZWQ6CgkJYGZvbGRDb3VudGAKCSAtIFF1ZXJ5IHRoZSBudW1iZXIgb2YgYml0cyBpbiBhIGRlc2NyaXB0b3I6IGBudW1CaXRzYAoKPGRpdiBhbGlnbj0icmlnaHQiPltCYWNrIHRvIFRhYmxlIG9mIENvbnRlbnRzXSgpPC9kaXY+CgpJbXBvcnQgb2YgQ29tcG91bmRzCj09PT09PT09PT09PT09PT09PT0KClNERiBJbXBvcnQKLS0tLS0tLS0tLQoKVGhlIGZvbGxvd2luZyBnaXZlcyBhbiBvdmVydmlldyBvZiB0aGUgbW9zdCBpbXBvcnRhbnQgaW1wb3J0L2V4cG9ydApmdW5jdGlvbmFsaXRpZXMgZm9yIHNtYWxsIG1vbGVjdWxlcyBwcm92aWRlZCBieQpgQ2hlbW1pbmVSYC4gVGhlIGdpdmVuIGV4YW1wbGUgY3JlYXRlcyBhbiBpbnN0YW5jZSBvZiB0aGUKYFNERnNldGAgY2xhc3MgdXNpbmcgYXMgc2FtcGxlIGRhdGEgc2V0IHRoZSBmaXJzdCAxMDAKY29tcG91bmRzIGZyb20gdGhpcyBQdWJDaGVtIFNEIGZpbGUgKFNERik6CkNvbXBvdW5kXF8wMDY1MDAwMVxfMDA2NzUwMDAuc2RmLmd6Cig8ZnRwOi8vZnRwLm5jYmkubmloLmdvdi9wdWJjaGVtL0NvbXBvdW5kL0NVUlJFTlQtRnVsbC9TREYvPikuCgpTREZzIGNhbiBiZSBpbXBvcnRlZCB3aXRoIHRoZSBgcmVhZC5TREZzZXRgIGZ1bmN0aW9uOgoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHNkZnNldCA8LSByZWFkLlNERnNldCgiaHR0cDovL2ZhY3VsdHkudWNyLmVkdS9+dGdpcmtlL0RvY3VtZW50cy9SX0Jpb0NvbmQvU2FtcGxlcy9zZGZzYW1wbGUuc2RmIikKYGBgCgpgYGB7ciBldmFsPVRSVUUsIHRpZHk9RkFMU0V9CiBkYXRhKHNkZnNhbXBsZSkgIyBMb2FkcyB0aGUgc2FtZSBTREZzZXQgcHJvdmlkZWQgYnkgdGhlIGxpYnJhcnkKIHNkZnNldCA8LSBzZGZzYW1wbGUKIHZhbGlkIDwtIHZhbGlkU0RGKHNkZnNldCkgIyBJZGVudGlmaWVzIGludmFsaWQgU0RGcyBpbiBTREZzZXQgb2JqZWN0cwogc2Rmc2V0IDwtIHNkZnNldFt2YWxpZF0gIyBSZW1vdmVzIGludmFsaWQgU0RGcywgaWYgdGhlcmUgYXJlIGFueQpgYGAKCgpJbXBvcnQgU0QgZmlsZSBpbnRvIGBTREZzdHJgIGNvbnRhaW5lcjoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHNkZnN0ciA8LSByZWFkLlNERnN0cigiaHR0cDovL2ZhY3VsdHkudWNyLmVkdS9+dGdpcmtlL0RvY3VtZW50cy9SX0Jpb0NvbmQvU2FtcGxlcy9zZGZzYW1wbGUuc2RmIikKYGBgCkNyZWF0ZQpgU0RGc2V0YCBmcm9tIGBTREZzdHJgIGNsYXNzOgoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogc2Rmc3RyIDwtIGFzKHNkZnNldCwgIlNERnN0ciIpCiBzZGZzdHIKIGFzKHNkZnN0ciwgIlNERnNldCIpCmBgYAoKPGRpdiBhbGlnbj0icmlnaHQiPltCYWNrIHRvIFRhYmxlIG9mIENvbnRlbnRzXSgpPC9kaXY+CgpTTUlMRVMgSW1wb3J0Ci0tLS0tLS0tLS0tLS0KClRoZSBgcmVhZC5TTUlzZXRgIGZ1bmN0aW9uIGltcG9ydHMgb25lIG9yIG1hbnkgbW9sZWN1bGVzCmZyb20gYSBTTUlMRVMgZmlsZSBhbmQgc3RvcmVzIHRoZW0gaW4gYSBgU01Jc2V0YApjb250YWluZXIuIFRoZSBpbnB1dCBmaWxlIGlzIGV4cGVjdGVkIHRvIGNvbnRhaW4gb25lIFNNSUxFUyBzdHJpbmcgcGVyCnJvdyB3aXRoIHRhYi1zZXBhcmF0ZWQgY29tcG91bmQgaWRlbnRpZmllcnMgYXQgdGhlIGVuZCBvZiBlYWNoIGxpbmUuIFRoZQpjb21wb3VuZCBpZGVudGlmaWVycyBhcmUgb3B0aW9uYWwuCgpDcmVhdGUgc2FtcGxlIFNNSUxFUyBmaWxlIGFuZCB0aGVuIGltcG9ydCBpdDoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIGRhdGEoc21pc2FtcGxlKTsgc21pc2V0IDwtIHNtaXNhbXBsZQogd3JpdGUuU01JKHNtaXNldFsxOjRdLCBmaWxlPSJzdWIuc21pIikKIHNtaXNldCA8LSByZWFkLlNNSXNldCgic3ViLnNtaSIpCmBgYAoKCkluc3BlY3QgY29udGVudCBvZiBgU01Jc2V0YDoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogZGF0YShzbWlzYW1wbGUpICMgTG9hZHMgdGhlIHNhbWUgU01Jc2V0IHByb3ZpZGVkIGJ5IHRoZSBsaWJyYXJ5CiBzbWlzZXQgPC0gc21pc2FtcGxlCiBzbWlzZXQKIHZpZXcoc21pc2V0WzE6Ml0pCmBgYAoKCkFjY2Vzc29yIGZ1bmN0aW9uczoKYGBge3IgZXZhbD1UUlVFLCB0aWR5PUZBTFNFfQogY2lkKHNtaXNldFsxOjRdKQogc21pIDwtIGFzLmNoYXJhY3RlcihzbWlzZXRbMToyXSkKYGBgCgoKQ3JlYXRlIGBTTUlzZXRgIGZyb20gbmFtZWQgY2hhcmFjdGVyIHZlY3RvcjoKCmBgYHtyIGV2YWw9VFJVRSwgdGlkeT1GQUxTRX0KIGFzKHNtaSwgIlNNSXNldCIpCmBgYAoKPGRpdiBhbGlnbj0icmlnaHQiPltCYWNrIHRvIFRhYmxlIG9mIENvbnRlbnRzXSgpPC9kaXY+CgpFeHBvcnQgb2YgQ29tcG91bmRzCj09PT09PT09PT09PT09PT09PT0KClNERiBFeHBvcnQKLS0tLS0tLS0tLQoKV3JpdGUgb2JqZWN0cyBvZiBjbGFzc2VzIGBTREZzZXQvU0RGc3RyL1NERmAgdG8gU0QgZmlsZToKCmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiB3cml0ZS5TREYoc2Rmc2V0WzE6NF0sIGZpbGU9InN1Yi5zZGYiKQpgYGAKCgpXcml0aW5nIGN1c3RvbWl6ZWQgYFNERnNldGAgdG8gZmlsZSBjb250YWluaW5nCmBDaGVtbWluZVJgIHNpZ25hdHVyZSwgSURzIGZyb20gYFNERnNldGAKYW5kIG5vIGRhdGEgYmxvY2s6CmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiB3cml0ZS5TREYoc2Rmc2V0WzE6NF0sIGZpbGU9InN1Yi5zZGYiLCBzaWc9VFJVRSwgY2lkPVRSVUUsIGRiPU5VTEwpCmBgYAoKCkV4YW1wbGUgZm9yIGluamVjdGluZyBhIGN1c3RvbSBtYXRyaXgvZGF0YSBmcmFtZSBpbnRvIHRoZSBkYXRhIGJsb2NrIG9mCmFuIGBTREZzZXRgIGFuZCB0aGVuIHdyaXRpbmcgaXQgdG8gYW4gU0QgZmlsZToKCmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBwcm9wcyA8LSBkYXRhLmZyYW1lKE1GPU1GKHNkZnNldCksIE1XPU1XKHNkZnNldCksIGF0b21jb3VudE1BKHNkZnNldCkpCiBkYXRhYmxvY2soc2Rmc2V0KSA8LSBwcm9wcwogd3JpdGUuU0RGKHNkZnNldFsxOjRdLCBmaWxlPSJzdWIuc2RmIiwgc2lnPVRSVUUsIGNpZD1UUlVFKQpgYGAKCgpJbmRpcmVjdCBleHBvcnQgdmlhIGBTREZzdHJgIG9iamVjdDoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHNkZjJzdHIoc2RmPXNkZnNldFtbMV1dLCBzaWc9VFJVRSwgY2lkPVRSVUUpICMgVXNlcyBkZWZhdWx0IGNvbXBvbmVudHMKIHNkZjJzdHIoc2RmPXNkZnNldFtbMV1dLCBoZWFkPWxldHRlcnNbMTo0XSwgZGI9TlVMTCkgIyBVc2VzIGN1c3RvbSBjb21wb25lbnRzIGZvciBoZWFkZXIgYW5kIGRhdGEgYmxvY2sKYGBgCgoKV3JpdGUgYFNERmAsIGBTREZzZXRgIG9yCmBTREZzdHJgIGNsYXNzZXMgdG8gZmlsZToKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHdyaXRlLlNERihzZGZzZXRbMTo0XSwgZmlsZT0ic3ViLnNkZiIsIHNpZz1UUlVFLCBjaWQ9VFJVRSwgZGI9TlVMTCkKIHdyaXRlLlNERihzZGZzdHJbMTo0XSwgZmlsZT0ic3ViLnNkZiIpCiBjYXQodW5saXN0KGFzKHNkZnN0clsxOjRdLCAibGlzdCIpKSwgZmlsZT0ic3ViLnNkZiIsIHNlcD0iIikKYGBgCgo8ZGl2IGFsaWduPSJyaWdodCI+W0JhY2sgdG8gVGFibGUgb2YgQ29udGVudHNdKCk8L2Rpdj4KClNNSUxFUyBFeHBvcnQKLS0tLS0tLS0tLS0tLQoKV3JpdGUgb2JqZWN0cyBvZiBjbGFzcyBgU01Jc2V0YCB0byBTTUlMRVMgZmlsZSB3aXRoIGFuZAp3aXRob3V0IGNvbXBvdW5kIGlkZW50aWZpZXJzOgpgYGB7ciBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogZGF0YShzbWlzYW1wbGUpOyBzbWlzZXQgPC0gc21pc2FtcGxlICMgU2FtcGxlIGRhdGEgc2V0Cgogd3JpdGUuU01JKHNtaXNldFsxOjRdLCBmaWxlPSJzdWIuc21pIiwgY2lkPVRSVUUpIHdyaXRlLlNNSShzbWlzZXRbMTo0XSwgZmlsZT0ic3ViLnNtaSIsIGNpZD1GQUxTRSkKYGBgCgo8ZGl2IGFsaWduPSJyaWdodCI+W0JhY2sgdG8gVGFibGUgb2YgQ29udGVudHNdKCk8L2Rpdj4KCkZvcm1hdCBJbnRlcmNvbnZlcnNpb25zCj09PT09PT09PT09PT09PT09PT09PT09CgpUaGUgYHNkZjJzbWlsZXNgIGFuZCBgc21pbGVzMnNkZmAKZnVuY3Rpb25zIHByb3ZpZGUgZm9ybWF0IGludGVyY29udmVyc2lvbiBiZXR3ZWVuIFNNSUxFUyBzdHJpbmdzCihTaW1wbGlmaWVkIE1vbGVjdWxhciBJbnB1dCBMaW5lIEVudHJ5IFNwZWNpZmljYXRpb24pIGFuZApgU0RGc2V0YCBjb250YWluZXJzLgoKQ29udmVydCBhbiBgU0RGc2V0YCBjb250YWluZXIgdG8gYSBTTUlMRVMKYGNoYXJhY3RlcmAgc3RyaW5nOgoKCmBgYHtyIHNkZjJzbWlsZXMsIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBkYXRhKHNkZnNhbXBsZSk7CiBzZGZzZXQgPC0gc2Rmc2FtcGxlWzFdCiBzbWlsZXMgPC0gc2RmMnNtaWxlcyhzZGZzZXQpCiBzbWlsZXMKYGBgCgoKQ29udmVydCBhIFNNSUxFUyBgY2hhcmFjdGVyYCBzdHJpbmcgdG8gYW4KYFNERnNldGAgY29udGFpbmVyOgoKCmBgYHtyIHNtaWxlczJzZGYsIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBzZGYgPC0gc21pbGVzMnNkZigiQ0MoPU8pT0MxPUNDPUNDPUMxQyg9TylPIikKIHZpZXcoc2RmKQpgYGAKCgpXaGVuIHRoZSBgQ2hlbWluZU9CYCBwYWNrYWdlIGlzIGluc3RhbGxlZCB0aGVzZQpjb252ZXJzaW9ucyBhcmUgcGVyZm9ybWVkIHdpdGggdGhlIE9wZW5CYWJlbCBPcGVuIFNvdXJjZSBDaGVtaXN0cnkKVG9vbGJveC4gT3RoZXJ3aXNlIHRoZSBmdW5jdGlvbnMgd2lsbCBmYWxsIGJhY2sgdG8gdXNpbmcgdGhlIENoZW1NaW5lClRvb2xzIHdlYiBzZXJ2aWNlIGZvciB0aGlzIG9wZXJhdGlvbi4gVGhlIGxhdHRlciB3aWxsIHJlcXVpcmUgaW50ZXJuZXQKY29ubmVjdGl2aXR5IGFuZCBpcyBsaW1pdGVkIHRvIG9ubHkgdGhlIGZpcnN0IGNvbXBvdW5kIGdpdmVuLgpgQ2hlbW1pbmVPQmAgcHJvdmlkZXMgYWNjZXNzIHRvIHRoZSBjb21wb3VuZCBmb3JtYXQKY29udmVyc2lvbiBmdW5jdGlvbnMgb2YgT3BlbkJhYmVsLiBDdXJyZW50bHksIG92ZXIgMTYwIGZvcm1hdHMgYXJlCnN1cHBvcnRlZCBieSBPcGVuQmFiZWwuIFRoZSBmdW5jdGlvbnMgYGNvbnZlcnRGb3JtYXRgIGFuZApgY29udmVydEZvcm1hdEZpbGVgIGNhbiBiZSB1c2VkIHRvIGNvbnZlcnQgZmlsZXMgb3IKc3RyaW5ncyBiZXR3ZWVuIGFueSB0d28gZm9ybWF0cyBzdXBwb3J0ZWQgYnkgT3BlbkJhYmVsLiBGb3IgZXhhbXBsZSwgdG8KY29udmVydCBhIFNNSUxFUyBzdHJpbmcgdG8gYW4gU0RGIHN0cmluZywgb25lIGNhbiB1c2UgdGhlCmBjb252ZXJ0Rm9ybWF0YCBmdW5jdGlvbi4KCgpgYGB7ciBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogc2RmU3RyIDwtIGNvbnZlcnRGb3JtYXQoIlNNSSIsIlNERiIsIkNDKD1PKU9DMT1DQz1DQz1DMUMoPU8pT19uYW1lIikKYGBgCgoKVGhpcyB3aWxsIHJldHVybiB0aGUgZ2l2ZW4gY29tcG91bmQgYXMgYW4gU0RGIGZvcm1hdHRlZCBzdHJpbmcuIDJECmNvb3JkaW5hdGVzIGFyZSBhbHNvIGNvbXB1dGVkIGFuZCBpbmNsdWRlZCBpbiB0aGUgcmVzdWx0aW5nIFNERiBzdHJpbmcuCgpUbyBjb252ZXJ0IGEgZmlsZSB3aXRoIGNvbXBvdW5kcyBlbmNvZGVkIGluIG9uZSBmb3JtYXQgdG8gYW5vdGhlcgpmb3JtYXQsIHRoZSBgY29udmVydEZvcm1hdEZpbGVgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkCmluc3RlYWQuCmBgYHtyIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBjb252ZXJ0Rm9ybWF0RmlsZSgiU01JIiwiU0RGIiwidGVzdC5zbWlsZXMiLCJ0ZXN0LnNkZiIpCmBgYAoKClRvIHNlZSB0aGUgd2hvbGUgbGlzdCBvZiBmaWxlIGZvcm1hdHMgc3VwcG9ydGVkIGJ5IE9wZW5CYWJlbCwgb25lIGNhbgpydW4gZnJvbSB0aGUgY29tbWFuZC1saW5lICJvYmFiZWwgLUwgZm9ybWF0cyIuCgo8ZGl2IGFsaWduPSJyaWdodCI+W0JhY2sgdG8gVGFibGUgb2YgQ29udGVudHNdKCk8L2Rpdj4KClNwbGl0dGluZyBTRCBGaWxlcwo9PT09PT09PT09PT09PT09PT0KClRoZSBmb2xsb3dpbmcgYHdyaXRlLlNERnNwbGl0YCBmdW5jdGlvbiBhbGxvd3MgdG8gc3BsaXQKU0QgRmlsZXMgaW50byBhbnkgbnVtYmVyIG9mIHNtYWxsZXIgU0QgRmlsZXMuIFRoaXMgY2FuIGJlY29tZSBpbXBvcnRhbnQKd2hlbiB3b3JraW5nIHdpdGggdmVyeSBiaWcgU0QgRmlsZXMuIFVzZXJzIHNob3VsZCBub3RlIHRoYXQgdGhpcwpmdW5jdGlvbiBjYW4gb3V0cHV0IG1hbnkgZmlsZXMsIHRodXMgb25lIHNob3VsZCBydW4gaXQgaW4gYSBkZWRpY2F0ZWQKZGlyZWN0b3J5IQoKQ3JlYXRlIHNhbXBsZSBTRCBGaWxlIHdpdGggMTAwIG1vbGVjdWxlczoKYGBge3IgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIHdyaXRlLlNERihzZGZzZXQsICJ0ZXN0LnNkZiIpCmBgYAoKClJlYWQgaW4gc2FtcGxlIFNEIEZpbGUuIE5vdGU6IHJlYWRpbmcgZmlsZSBpbnRvIFNERnN0ciBpcyBtdWNoIGZhc3Rlcgp0aGFuIGludG8gU0RGc2V0OgpgYGB7ciBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogc2Rmc3RyIDwtIHJlYWQuU0RGc3RyKCJ0ZXN0LnNkZiIpCmBgYAoKClJ1biBleHBvcnQgb24gYFNERnN0cmAgb2JqZWN0OgpgYGB7ciBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogd3JpdGUuU0RGc3BsaXQoeD1zZGZzdHIsIGZpbGV0YWc9Im15ZmlsZSIsIG5tb2w9MTApICMgJ25tb2wnIGRlZmluZXMgdGhlIG51bWJlciBvZiBtb2xlY3VsZXMgdG8gd3JpdGUgdG8gZWFjaCBmaWxlCmBgYAoKClJ1biBleHBvcnQgb24gYFNERnNldGAgb2JqZWN0OgpgYGB7ciBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogd3JpdGUuU0RGc3BsaXQoeD1zZGZzZXQsIGZpbGV0YWc9Im15ZmlsZSIsIG5tb2w9MTApCmBgYAoKClNlYXJjaGluZyBQdWJDaGVtCj09PT09PT09PT09PT09PT09CgpHZXQgQ29tcG91bmRzIGZyb20gUHViQ2hlbSBieSBJZAotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKVGhlIGZ1bmN0aW9uIGBnZXRJZHNgIGFjY2VwdHMgb25lIG9yIG1vcmUgbnVtZXJpYyBQdWJDaGVtCmNvbXBvdW5kIGlkcyBhbmQgZG93bmxvYWRzIHRoZSBjb3JyZXNwb25kaW5nIGNvbXBvdW5kcyBmcm9tIFB1YkNoZW0KUG93ZXIgVXNlciBHYXRld2F5IChQVUcpIHJldHVybmluZyByZXN1bHRzIGluIGFuIGBTREZzZXRgCmNvbnRhaW5lci4gVGhlIENoZW1NaW5lIFRvb2xzIHdlYiBzZXJ2aWNlIGlzIHVzZWQgYXMgYW4gaW50ZXJtZWRpYXRlLCB0bwp0cmFuc2xhdGUgcXVlcmllcyBmcm9tIHBsYWluIEhUVFAgUE9TVCB0byBhIFBVRyBTT0FQIHF1ZXJ5LgoKRmV0Y2ggMiBjb21wb3VuZHMgZnJvbSBQdWJDaGVtOgoKCmBgYHtyIGdldElkcywgZXZhbD1GQUxTRSwgdGlkeT1GQUxTRX0KIGNvbXBvdW5kcyA8LSBnZXRJZHMoYygxMTEsMTIzKSkKIGNvbXBvdW5kcwpgYGAKCjxkaXYgYWxpZ249InJpZ2h0Ij5bQmFjayB0byBUYWJsZSBvZiBDb250ZW50c10oKTwvZGl2PgoKU2VhcmNoIGEgU01JTEVTIFF1ZXJ5IGluIFB1YkNoZW0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KClRoZSBmdW5jdGlvbiBgc2VhcmNoU3RyaW5nYCBhY2NlcHRzIG9uZSBTTUlMRVMgc3RyaW5nCihTaW1wbGlmaWVkIE1vbGVjdWxhciBJbnB1dCBMaW5lIEVudHJ5IFNwZWNpZmljYXRpb24pIGFuZCBwZXJmb3JtcyBhClw+MC45NSBzaW1pbGFyaXR5IFB1YkNoZW0gZmluZ2VycHJpbnQgc2VhcmNoLCByZXR1cm5pbmcgdGhlIGhpdHMgaW4gYW4KYFNERnNldGAgY29udGFpbmVyLiBUaGUgQ2hlbU1pbmUgVG9vbHMgd2ViIHNlcnZpY2UgaXMKdXNlZCBhcyBhbiBpbnRlcm1lZGlhdGUsIHRvIHRyYW5zbGF0ZSBxdWVyaWVzIGZyb20gcGxhaW4gSFRUUCBQT1NUIHRvIGEKUHViQ2hlbSBQb3dlciBVc2VyIEdhdGV3YXkgKFBVRykgcXVlcnkuCgpTZWFyY2ggYSBTTUlMRVMgc3RyaW5nIG9uIFB1YkNoZW06CgoKYGBge3Igc2VhcmNoU3RyaW5nLCBldmFsPUZBTFNFLCB0aWR5PUZBTFNFfQogY29tcG91bmRzIDwtIHNlYXJjaFN0cmluZygiQ0MoPU8pT0MxPUNDPUNDPUMxQyg9TylPIikgY29tcG91bmRzCmBgYAoKPGRpdiBhbGlnbj0icmlnaHQiPltCYWNrIHRvIFRhYmxlIG9mIENvbnRlbnRzXSgpPC9kaXY+CgpTZWFyY2ggYW4gU0RGIFF1ZXJ5IGluIFB1YkNoZW0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpUaGUgZnVuY3Rpb24gYHNlYXJjaFNpbWAgcGVyZm9ybXMgYSBQdWJDaGVtIHNpbWlsYXJpdHkKc2VhcmNoIGp1c3QgbGlrZSBgc2VhcmNoU3RyaW5nYCwgYnV0IGFjY2VwdHMgYSBxdWVyeSBpbgphbiBgU0RGc2V0YCBjb250YWluZXIuIElmIHRoZSBxdWVyeSBjb250YWlucyBtb3JlIHRoYW4Kb25lIGNvbXBvdW5kLCBvbmx5IHRoZSBmaXJzdCBpcyBzZWFyY2hlZC4KClNlYXJjaCBhbiBgU0RGc2V0YCBjb250YWluZXIgb24gUHViQ2hlbToKCgpgYGB7ciBzZWFyY2hTaW0sIGV2YWw9RkFMU0UsIHRpZHk9RkFMU0V9CiBkYXRhKHNkZnNhbXBsZSk7CiBzZGZzZXQgPC0gc2Rmc2FtcGxlWzFdCiBjb21wb3VuZHMgPC0gc2VhcmNoU2ltKHNkZnNldCkKIGNvbXBvdW5kcwpgYGAKCmBgYHtyIGtuaXRfZXhpdCwgaW5jbHVkZT1GLCBlY2hvPUZ9CmtuaXRyOjprbml0X2V4aXQoKQpgYGAK