The R markdown is available from the pulldown menu for Code at the upper-right, choose “Download Rmd”, or download the Rmd from GitHub.


This vignette describes how to use data from an affinity purification-mass spectrometry experiment to generate relevant interaction networks, enriching the networks with information from public resources, analyzing the networks and creating effective visualizations.

The result of this vignette will be a visualization of a human-HIV integrated network combining experimental data and publicly available interaction data. This approach was use to make Figure 3 in this Jäger 2011 Nature paper.


Installation

if(!"RCy3" %in% installed.packages()){
    install.packages("BiocManager")
    BiocManager::install("RCy3")
}
library(RCy3)

Prerequisites

In addition to this package (RCy3), you will need:

installApp('stringApp')
installApp('enhancedGraphics')

Background

The data used for this protocol represents interactions between human and HIV proteins by Jäger et al (https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3310911/). In this quantitative AP-MS experiment, a relatively small number of bait proteins are used to pull down a larger set of prey proteins.

Note that this tutorial does not describe how to pre-process the raw AP-MS data, the data used here is already scored and filtered.

Import Network and Data

Let’s start by reading in the example data file:

apms.data<-read.csv(file="https://raw.githubusercontent.com/cytoscape/cytoscape-automation/master/for-scripters/R/notebooks/AP-MS/ap-ms-demodata.csv", stringsAsFactors = FALSE)

Now we can create a data frame for the network edges (interactions) using the imported data. We will add an interaction “AP-MS” to each edge, which will be useful later, and we can also add the AP-MS score from the data as an edge attribute:

edges <- data.frame(source=apms.data[,"Bait"],target=apms.data[,"Prey"],interaction="AP-MS", AP.MS.Score=apms.data[,"AP.MS.Score"],stringsAsFactors=FALSE)

Finally, we use the edge data fram to create the network. Note that we don’t need to define a data frame for nodes, as all nodes in this case are represented in the edge data frame.

createNetworkFromDataFrames(edges=edges, title="apms network", collection = "apms collection")

The imported network consists of multiple smaller subnetworks, each representing a bait node and its associated prey nodes

Loading Data

There are three other columns of data and annotations for the “Prey” proteins that we want to load into this network.

In this data, the Prey nodes are repeated for each interactions with a Bait node, so the data contains different values for the same attribute (for example HEKScore), for each Prey node. During import, the last value imported will overwrite prior values and visualizations using this attribute thus only shows the last value.

loadTableData(apms.data[,2:5], data.key.column="Prey")

Augmenting Network with Existing Protein-protein Interaction Data

We are going to use existing protein-protein interaction data to enrich the network, using the STRING database with the human protein nodes as input.

Let’s collect all the UniProt identifiers from the data, and create a text string that we can use to query STRING. For this purpose, we are going to specify the type of network as “physical subnetwork”, since we are looking for protein complexes:

uniprot.str<-toString(apms.data[,"UniProt"])
string.cmd<-paste('string protein query',
                  'query="',uniprot.str,'"', 
                  'species="Homo sapiens"', 
                  'limit=0', 
                  'cutoff=0.999', 'networkType="physical subnetwork"',
                  sep= ' ')
commandsRun(string.cmd)

The resulting network contains known interactions between the human proteins, with an evidence score of 0.999 or greater.

Merge Networks

To incorporate the new information into our AP-MS network, we need merge the STRING and AP-MS networks. We can use the Uniprot IDs available in both networks as the matching attribute, “Uniprot” in the AP-MS network, and “query term in the String network. We will also specify how to merge the attribute containing the node name (symbol), which is contained in the”name" attribute for the AP-MS network and the “display name” for the String network.

merge.cmd<-paste('network merge',
                 'operation=union',
                 'sources="apms network,STRING network (physical)"',
                 'nodeKeys="Uniprot,query term"', 
                 'nodeMergeMap="{name,display name,display name, string}"')
commandsRun(merge.cmd)

Network Visualization

When the merged network first loads, it will have the STRING style applied. However, because the HIV nodes are not from STRING, they will be grey. The layout also makes the network hard to interpret. Let’s change the style of the network a bit.

First, let’s set our AP-MS network as the current network:

setCurrentNetwork('union: apms network,STRING network (physical)')
setCurrentView()

Next, we can define our style and apply it to the network:

style.name = "AP-MS"
createVisualStyle(style.name)
lockNodeDimensions(TRUE)
setNodeSizeDefault('50', style.name = style.name)
setNodeColorDefault("#CCCCFF", style.name=style.name)
setNodeLabelMapping('display name', style.name=style.name)
setVisualStyle(style.name=style.name)
layoutNetwork(paste('force-directed', 
              'defaultSpringCoefficient=0.00001',
              'defaultSpringLength=50',
              'defaultNodeMass=4',
              sep=' '))

STRING Enrichment

Now that we have a merged network, we can do enrichment analysis on it, using the STRING enrichment tool.

The STRING app has built-in enrichment analysis functionality, which includes enrichment for GO Process, GO Component, GO Function, InterPro, KEGG Pathways, and PFAM.

commandsRun('string make string network="current"')

string.cmd<-paste('string retrieve enrichment', 
                  'allNetSpecies="Homo sapiens"')
commandsRun(string.cmd)

The STRING enrichment results don’t open by default if run from the command interface.

commandsRun('string show enrichment')

The STRING app includes several options for filtering and displaying the enrichment results. We will filter the results to only show GO Process.

commandsRun(paste('string filter enrichment', 
                  'categories="GO Biological Process"',
                  'removeOverlapping="true"',
                  sep = ' '))

Next, we will add a split donut chart.

commandsRun('string show charts')

Visualizing Results - Jurkat Score

We can create a visualization based on the Jurkat Score, and the different interaction types (AP-MS and STRING):

style.name = "AP-MS Jurkat Score"
createVisualStyle(style.name)
setVisualStyle(style.name)
setNodeColorDefault("#FFCC00", style.name=style.name)
setNodeLabelMapping('display name', style.name=style.name)
setEdgeColorMapping("interaction", "AP-MS", "#55AA55", "d", style.name = style.name)
setEdgeLineWidthMapping('AP.MS.Score', table.column.values=c(0,1), widths=c(1,5), mapping.type="c", style.name=style.name)

Now, we define a color gradient based on the data values in the JurkatScore column:

setNodeColorMapping('JurkatScore', colors = paletteColorBrewerPurples, style.name=style.name)

We now have a visualization of the network highlighting the ap-ms experimental data (green edges), as well as additional known interactions (grey edges), with node color indicating the Jurkat Score from the data.

Visualizing Results - Combined

We could create a similar kind of style for the HEK score, but that only allows for viewing each style seperately. Instead, we can create a combined style, using the enhancedGraphics app.

For this, we will need a new column defining a new attribute that will be used for mapping to the Custom Graphics property via the enhancedGraphics app. This new attribute has to be in the form of mappings recognized by the enhancedGraphics app.

We can copy the previous style to retain some of the mappings we want to keep:

copyVisualStyle(from.style="AP-MS Jurkat Score", to.style="AP-MS CombinedScore")
setVisualStyle(style.name="AP-MS CombinedScore")

To begin adding the new column, we first define a data frame with the new attribute formatted for enhancedGraphics:

all.nodes<-getAllNodes()
combined.df<-data.frame(all.nodes, 'piechart: showlabels=false range="0,1" arcstart=90 valuelist=".5,.5" colorlist="up:blue,zero:white,down:white;up:purple,zero:white,down:white" attributelist="HEKScore,JurkatScore"')
colnames(combined.df)<-c("name","CombinedScore")

Next, we load this dataframe into the Node Table to create and fill a new column:

loadTableData(combined.df, data.key.column = "name", table.key.column = "name")

We now have a new column, CombinedScore, that we can use for the mapping. This mapping does not come with a custom helper function, se we are going to use two alternative functions to prepare the passthrough mapping property and then update our visual style with the new mapping:

piechart.map<-mapVisualProperty('node customgraphics 4','CombinedScore','p')
updateStyleMapping('AP-MS CombinedScore', piechart.map)

Remember that when we imported multiple values for a single node attribute, such as the scores for human nodes interacting with more than one HIV nodes, the last value imported will overwrite prior values and the visualization thus only shows the last value. For EIF3A, which interacts with both PR and POL, only the data relevant to the PR interaction is maintained in the Node Table because our source data was sorted alphabetically by Bait.

Saving, sharing and publishing

Saving a Cytoscape session file

Session files save everything. As with most project software, we recommend saving often!

saveSession('AP-MS_session') #.cys

Note: If you don’t specify a complete path, the files will be saved relative to your current working directory in R.

Saving high resolution image files

You can export extremely high resolution images, including vector graphic formats.

exportImage('AP-MS_image', type = 'PDF') #.pdf
?exportImage
LS0tCnRpdGxlOiAiQWZmaW5pdHkgcHVyaWZpY2F0aW9uLW1hc3Mgc3BlY3Ryb21ldHJ5IG5ldHdvcmsgYW5hbHlzaXMiCmF1dGhvcjogIktyaXN0aW5hIEhhbnNwZXJzLCBBbGV4YW5kZXIgUGljbyIKcGFja2FnZTogUkN5MwpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6ICJub25lIgojICBwZGZfZG9jdW1lbnQ6CiMgICAgdG9jOiB0cnVlICAgIAotLS0KYGBge3IgZWNobz1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGV2YWw9RkFMU0UKKQpgYGAKCipUaGUgUiBtYXJrZG93biBpcyBhdmFpbGFibGUgZnJvbSB0aGUgcHVsbGRvd24gbWVudSBmb3IqIENvZGUgKmF0IHRoZSB1cHBlci1yaWdodCwgY2hvb3NlICJEb3dubG9hZCBSbWQiLCBvciBbZG93bmxvYWQgdGhlIFJtZCBmcm9tIEdpdEh1Yl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtYXV0b21hdGlvbi9tYXN0ZXIvZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9BUC1NUy1uZXR3b3JrLWFuYWx5c2lzLlJtZCkuKgoKPGhyIC8+ClRoaXMgdmlnbmV0dGUgZGVzY3JpYmVzIGhvdyB0byB1c2UgZGF0YSBmcm9tIGFuIGFmZmluaXR5IHB1cmlmaWNhdGlvbi1tYXNzIHNwZWN0cm9tZXRyeSBleHBlcmltZW50IHRvIGdlbmVyYXRlIHJlbGV2YW50IGludGVyYWN0aW9uIG5ldHdvcmtzLCBlbnJpY2hpbmcgdGhlIG5ldHdvcmtzIHdpdGggaW5mb3JtYXRpb24gZnJvbSBwdWJsaWMgcmVzb3VyY2VzLCBhbmFseXppbmcgdGhlIG5ldHdvcmtzIGFuZCBjcmVhdGluZyBlZmZlY3RpdmUgdmlzdWFsaXphdGlvbnMuCgpUaGUgcmVzdWx0IG9mIHRoaXMgdmlnbmV0dGUgd2lsbCBiZSBhIHZpc3VhbGl6YXRpb24gb2YgYSBodW1hbi1ISVYgaW50ZWdyYXRlZCBuZXR3b3JrIGNvbWJpbmluZyBleHBlcmltZW50YWwgZGF0YSBhbmQgcHVibGljbHkgYXZhaWxhYmxlIGludGVyYWN0aW9uIGRhdGEuIFRoaXMgYXBwcm9hY2ggd2FzIHVzZSB0byBtYWtlIEZpZ3VyZSAzIGluIHRoaXMgW0rDpGdlciAyMDExIE5hdHVyZSBwYXBlcl0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMzMxMDkxMS8pLgoKPGNlbnRlcj4KIVtdKGh0dHBzOi8vY3l0b3NjYXBlLmdpdGh1Yi5pby9jeXRvc2NhcGUtdHV0b3JpYWxzL3Byb3RvY29scy9BUC1NUy1uZXR3b3JrLWFuYWx5c2lzL2ZpbmFsLW1vZHVsZS5wbmcpe3dpZHRoPTYwJX0KPC9jZW50ZXI+CjxociAvPgoKIyBJbnN0YWxsYXRpb24KYGBge3J9CmlmKCEiUkN5MyIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiUkN5MyIpCn0KbGlicmFyeShSQ3kzKQpgYGAKCiMgUHJlcmVxdWlzaXRlcwpJbiBhZGRpdGlvbiB0byB0aGlzIHBhY2thZ2UgKFJDeTMpLCB5b3Ugd2lsbCBuZWVkOgoKICAqICoqTGF0ZXN0IHZlcnNpb24gb2YgQ3l0b3NjYXBlKiosIHdoaWNoIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gaHR0cHM6Ly9jeXRvc2NhcGUub3JnL2Rvd25sb2FkLmh0bWwuIFNpbXBseSBmb2xsb3cgdGhlIGluc3RhbGxhdGlvbiBpbnN0cnVjdGlvbnMgb24gc2NyZWVuLgoqIENvbXBsZXRlIGluc3RhbGxhdGlvbiB3aXphcmQKKiBMYXVuY2ggQ3l0b3NjYXBlIApGb3IgdGhpcyB2aWduZXR0ZSwgeW914oCZbGwgYWxzbyBuZWVkIHRoZSBTVFJJTkcgYXBwIGFuZCB0aGUgZW5oYW5jZWRHcmFwaGljcyBhcHA6IAoKKiBJbnN0YWxsIHRoZSBTVFJJTkcgYXBwIGZyb20gaHR0cHM6Ly9hcHBzLmN5dG9zY2FwZS5vcmcvYXBwcy9zdHJpbmdhcHAKKiBJbnN0YWxsIHRoZSBlbmhhbmNlZEdyYXBoaWNzIGFwcCBmcm9tIGh0dHA6Ly9hcHBzLmN5dG9zY2FwZS5vcmcvYXBwcy9lbmhhbmNlZGdyYXBoaWNzCgpgYGB7cn0KaW5zdGFsbEFwcCgnc3RyaW5nQXBwJykKaW5zdGFsbEFwcCgnZW5oYW5jZWRHcmFwaGljcycpCmBgYAoKIyBCYWNrZ3JvdW5kClRoZSBkYXRhIHVzZWQgZm9yIHRoaXMgcHJvdG9jb2wgcmVwcmVzZW50cyBpbnRlcmFjdGlvbnMgYmV0d2VlbiBodW1hbiBhbmQgSElWIHByb3RlaW5zIGJ5IErDpGdlciBldCBhbCAoaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMzMxMDkxMS8pLiBJbiB0aGlzIHF1YW50aXRhdGl2ZSBBUC1NUyBleHBlcmltZW50LCBhIHJlbGF0aXZlbHkgc21hbGwgbnVtYmVyIG9mIGJhaXQgcHJvdGVpbnMgYXJlIHVzZWQgdG8gcHVsbCBkb3duIGEgbGFyZ2VyIHNldCBvZiBwcmV5IHByb3RlaW5zLgoKTm90ZSB0aGF0IHRoaXMgdHV0b3JpYWwgZG9lcyBub3QgZGVzY3JpYmUgaG93IHRvIHByZS1wcm9jZXNzIHRoZSByYXcgQVAtTVMgZGF0YSwgdGhlIGRhdGEgdXNlZCBoZXJlIGlzIGFscmVhZHkgc2NvcmVkIGFuZCBmaWx0ZXJlZC4KCiMgSW1wb3J0IE5ldHdvcmsgYW5kIERhdGEKTGV0J3Mgc3RhcnQgYnkgcmVhZGluZyBpbiB0aGUgZXhhbXBsZSBkYXRhIGZpbGU6CgpgYGB7cn0KYXBtcy5kYXRhPC1yZWFkLmNzdihmaWxlPSJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS1hdXRvbWF0aW9uL21hc3Rlci9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL0FQLU1TL2FwLW1zLWRlbW9kYXRhLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYGBgCgpOb3cgd2UgY2FuIGNyZWF0ZSBhIGRhdGEgZnJhbWUgZm9yIHRoZSBuZXR3b3JrIGVkZ2VzIChpbnRlcmFjdGlvbnMpIHVzaW5nIHRoZSBpbXBvcnRlZCBkYXRhLiBXZSB3aWxsIGFkZCBhbiBpbnRlcmFjdGlvbiAiQVAtTVMiIHRvIGVhY2ggZWRnZSwgd2hpY2ggd2lsbCBiZSB1c2VmdWwgbGF0ZXIsIGFuZCB3ZSBjYW4gYWxzbyBhZGQgdGhlIEFQLU1TIHNjb3JlIGZyb20gdGhlIGRhdGEgYXMgYW4gZWRnZSBhdHRyaWJ1dGU6IAoKYGBge3J9CmVkZ2VzIDwtIGRhdGEuZnJhbWUoc291cmNlPWFwbXMuZGF0YVssIkJhaXQiXSx0YXJnZXQ9YXBtcy5kYXRhWywiUHJleSJdLGludGVyYWN0aW9uPSJBUC1NUyIsIEFQLk1TLlNjb3JlPWFwbXMuZGF0YVssIkFQLk1TLlNjb3JlIl0sc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKYGBgCgpGaW5hbGx5LCB3ZSB1c2UgdGhlIGVkZ2UgZGF0YSBmcmFtIHRvIGNyZWF0ZSB0aGUgbmV0d29yay4gTm90ZSB0aGF0IHdlIGRvbid0IG5lZWQgdG8gZGVmaW5lIGEgZGF0YSBmcmFtZSBmb3Igbm9kZXMsIGFzIGFsbCBub2RlcyBpbiB0aGlzIGNhc2UgYXJlIHJlcHJlc2VudGVkIGluIHRoZSBlZGdlIGRhdGEgZnJhbWUuCmBgYHtyfQpjcmVhdGVOZXR3b3JrRnJvbURhdGFGcmFtZXMoZWRnZXM9ZWRnZXMsIHRpdGxlPSJhcG1zIG5ldHdvcmsiLCBjb2xsZWN0aW9uID0gImFwbXMgY29sbGVjdGlvbiIpCmBgYAoKVGhlIGltcG9ydGVkIG5ldHdvcmsgY29uc2lzdHMgb2YgbXVsdGlwbGUgc21hbGxlciBzdWJuZXR3b3JrcywgZWFjaCByZXByZXNlbnRpbmcgYSBiYWl0IG5vZGUgYW5kIGl0cyBhc3NvY2lhdGVkIHByZXkgbm9kZXMKCiMgTG9hZGluZyBEYXRhClRoZXJlIGFyZSB0aHJlZSBvdGhlciBjb2x1bW5zIG9mIGRhdGEgYW5kIGFubm90YXRpb25zIGZvciB0aGUgIlByZXkiIHByb3RlaW5zIHRoYXQgd2Ugd2FudCB0byBsb2FkIGludG8gdGhpcyBuZXR3b3JrLiAKCkluIHRoaXMgZGF0YSwgdGhlIFByZXkgbm9kZXMgYXJlIHJlcGVhdGVkIGZvciBlYWNoIGludGVyYWN0aW9ucyB3aXRoIGEgQmFpdCBub2RlLCBzbyB0aGUgZGF0YSBjb250YWlucyBkaWZmZXJlbnQgdmFsdWVzIGZvciB0aGUgc2FtZSBhdHRyaWJ1dGUgKGZvciBleGFtcGxlIEhFS1Njb3JlKSwgZm9yIGVhY2ggUHJleSBub2RlLiBEdXJpbmcgaW1wb3J0LCB0aGUgbGFzdCB2YWx1ZSBpbXBvcnRlZCB3aWxsIG92ZXJ3cml0ZSBwcmlvciB2YWx1ZXMgYW5kIHZpc3VhbGl6YXRpb25zIHVzaW5nIHRoaXMgYXR0cmlidXRlIHRodXMgb25seSBzaG93cyB0aGUgbGFzdCB2YWx1ZS4KCmBgYHtyfQpsb2FkVGFibGVEYXRhKGFwbXMuZGF0YVssMjo1XSwgZGF0YS5rZXkuY29sdW1uPSJQcmV5IikKYGBgCgojIEF1Z21lbnRpbmcgTmV0d29yayB3aXRoIEV4aXN0aW5nIFByb3RlaW4tcHJvdGVpbiBJbnRlcmFjdGlvbiBEYXRhIApXZSBhcmUgZ29pbmcgdG8gdXNlIGV4aXN0aW5nIHByb3RlaW4tcHJvdGVpbiBpbnRlcmFjdGlvbiBkYXRhIHRvIGVucmljaCB0aGUgbmV0d29yaywgdXNpbmcgdGhlIFNUUklORyBkYXRhYmFzZSB3aXRoIHRoZSBodW1hbiBwcm90ZWluIG5vZGVzIGFzIGlucHV0LgoKTGV0J3MgY29sbGVjdCBhbGwgdGhlIFVuaVByb3QgaWRlbnRpZmllcnMgZnJvbSB0aGUgZGF0YSwgYW5kIGNyZWF0ZSBhIHRleHQgc3RyaW5nIHRoYXQgd2UgY2FuIHVzZSB0byBxdWVyeSBTVFJJTkcuIEZvciB0aGlzIHB1cnBvc2UsIHdlIGFyZSBnb2luZyB0byBzcGVjaWZ5IHRoZSB0eXBlIG9mIG5ldHdvcmsgYXMgInBoeXNpY2FsIHN1Ym5ldHdvcmsiLCBzaW5jZSB3ZSBhcmUgbG9va2luZyBmb3IgcHJvdGVpbiBjb21wbGV4ZXM6CgpgYGB7cn0KdW5pcHJvdC5zdHI8LXRvU3RyaW5nKGFwbXMuZGF0YVssIlVuaVByb3QiXSkKc3RyaW5nLmNtZDwtcGFzdGUoJ3N0cmluZyBwcm90ZWluIHF1ZXJ5JywKICAgICAgICAgICAgICAgICAgJ3F1ZXJ5PSInLHVuaXByb3Quc3RyLCciJywgCiAgICAgICAgICAgICAgICAgICdzcGVjaWVzPSJIb21vIHNhcGllbnMiJywgCiAgICAgICAgICAgICAgICAgICdsaW1pdD0wJywgCiAgICAgICAgICAgICAgICAgICdjdXRvZmY9MC45OTknLCAnbmV0d29ya1R5cGU9InBoeXNpY2FsIHN1Ym5ldHdvcmsiJywKICAgICAgICAgICAgICAgICAgc2VwPSAnICcpCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKVGhlIHJlc3VsdGluZyBuZXR3b3JrIGNvbnRhaW5zIGtub3duIGludGVyYWN0aW9ucyBiZXR3ZWVuIHRoZSBodW1hbiBwcm90ZWlucywgd2l0aCBhbiBldmlkZW5jZSBzY29yZSBvZiAwLjk5OSBvciBncmVhdGVyLgoKIyBNZXJnZSBOZXR3b3JrcwpUbyBpbmNvcnBvcmF0ZSB0aGUgbmV3IGluZm9ybWF0aW9uIGludG8gb3VyIEFQLU1TIG5ldHdvcmssIHdlIG5lZWQgbWVyZ2UgdGhlIFNUUklORyBhbmQgQVAtTVMgbmV0d29ya3MuIFdlIGNhbiB1c2UgdGhlIFVuaXByb3QgSURzIGF2YWlsYWJsZSBpbiBib3RoIG5ldHdvcmtzIGFzIHRoZSBtYXRjaGluZyBhdHRyaWJ1dGUsICJVbmlwcm90IiBpbiB0aGUgQVAtTVMgbmV0d29yaywgYW5kICJxdWVyeSB0ZXJtIGluIHRoZSBTdHJpbmcgbmV0d29yay4KV2Ugd2lsbCBhbHNvIHNwZWNpZnkgaG93IHRvIG1lcmdlIHRoZSBhdHRyaWJ1dGUgY29udGFpbmluZyB0aGUgbm9kZSBuYW1lIChzeW1ib2wpLCB3aGljaCBpcyBjb250YWluZWQgaW4gdGhlICJuYW1lIiBhdHRyaWJ1dGUgZm9yIHRoZSBBUC1NUyBuZXR3b3JrIGFuZCB0aGUgImRpc3BsYXkgbmFtZSIgZm9yIHRoZSBTdHJpbmcgbmV0d29yay4gCgpgYGB7cn0KbWVyZ2UuY21kPC1wYXN0ZSgnbmV0d29yayBtZXJnZScsCiAgICAgICAgICAgICAgICAgJ29wZXJhdGlvbj11bmlvbicsCiAgICAgICAgICAgICAgICAgJ3NvdXJjZXM9ImFwbXMgbmV0d29yayxTVFJJTkcgbmV0d29yayAocGh5c2ljYWwpIicsCiAgICAgICAgICAgICAgICAgJ25vZGVLZXlzPSJVbmlwcm90LHF1ZXJ5IHRlcm0iJywgCiAgICAgICAgICAgICAgICAgJ25vZGVNZXJnZU1hcD0ie25hbWUsZGlzcGxheSBuYW1lLGRpc3BsYXkgbmFtZSwgc3RyaW5nfSInKQpjb21tYW5kc1J1bihtZXJnZS5jbWQpCmBgYAoKIyBOZXR3b3JrIFZpc3VhbGl6YXRpb24KV2hlbiB0aGUgbWVyZ2VkIG5ldHdvcmsgZmlyc3QgbG9hZHMsIGl0IHdpbGwgaGF2ZSB0aGUgU1RSSU5HIHN0eWxlIGFwcGxpZWQuIEhvd2V2ZXIsIGJlY2F1c2UgdGhlIEhJViBub2RlcyBhcmUgbm90IGZyb20gU1RSSU5HLCB0aGV5IHdpbGwgYmUgZ3JleS4gVGhlIGxheW91dCBhbHNvIG1ha2VzIHRoZSBuZXR3b3JrIGhhcmQgdG8gaW50ZXJwcmV0LiBMZXQncyBjaGFuZ2UgdGhlIHN0eWxlIG9mIHRoZSBuZXR3b3JrIGEgYml0LgoKRmlyc3QsIGxldCdzIHNldCBvdXIgQVAtTVMgbmV0d29yayBhcyB0aGUgY3VycmVudCBuZXR3b3JrOgpgYGB7cn0Kc2V0Q3VycmVudE5ldHdvcmsoJ3VuaW9uOiBhcG1zIG5ldHdvcmssU1RSSU5HIG5ldHdvcmsgKHBoeXNpY2FsKScpCnNldEN1cnJlbnRWaWV3KCkKYGBgCgpOZXh0LCB3ZSBjYW4gZGVmaW5lIG91ciBzdHlsZSBhbmQgYXBwbHkgaXQgdG8gdGhlIG5ldHdvcms6CmBgYHtyfQpzdHlsZS5uYW1lID0gIkFQLU1TIgpjcmVhdGVWaXN1YWxTdHlsZShzdHlsZS5uYW1lKQpsb2NrTm9kZURpbWVuc2lvbnMoVFJVRSkKc2V0Tm9kZVNpemVEZWZhdWx0KCc1MCcsIHN0eWxlLm5hbWUgPSBzdHlsZS5uYW1lKQpzZXROb2RlQ29sb3JEZWZhdWx0KCIjQ0NDQ0ZGIiwgc3R5bGUubmFtZT1zdHlsZS5uYW1lKQpzZXROb2RlTGFiZWxNYXBwaW5nKCdkaXNwbGF5IG5hbWUnLCBzdHlsZS5uYW1lPXN0eWxlLm5hbWUpCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWU9c3R5bGUubmFtZSkKYGBgCgpgYGB7cn0KbGF5b3V0TmV0d29yayhwYXN0ZSgnZm9yY2UtZGlyZWN0ZWQnLCAKICAgICAgICAgICAgICAnZGVmYXVsdFNwcmluZ0NvZWZmaWNpZW50PTAuMDAwMDEnLAogICAgICAgICAgICAgICdkZWZhdWx0U3ByaW5nTGVuZ3RoPTUwJywKICAgICAgICAgICAgICAnZGVmYXVsdE5vZGVNYXNzPTQnLAogICAgICAgICAgICAgIHNlcD0nICcpKQpgYGAKCiMgU1RSSU5HIEVucmljaG1lbnQKTm93IHRoYXQgd2UgaGF2ZSBhIG1lcmdlZCBuZXR3b3JrLCB3ZSBjYW4gZG8gZW5yaWNobWVudCBhbmFseXNpcyBvbiBpdCwgdXNpbmcgdGhlIFNUUklORyBlbnJpY2htZW50IHRvb2wuCgpUaGUgU1RSSU5HIGFwcCBoYXMgYnVpbHQtaW4gZW5yaWNobWVudCBhbmFseXNpcyBmdW5jdGlvbmFsaXR5LCB3aGljaCBpbmNsdWRlcyBlbnJpY2htZW50IGZvciBHTyBQcm9jZXNzLCBHTyBDb21wb25lbnQsIEdPIEZ1bmN0aW9uLCBJbnRlclBybywgS0VHRyBQYXRod2F5cywgYW5kIFBGQU0uCgpgYGB7cn0KY29tbWFuZHNSdW4oJ3N0cmluZyBtYWtlIHN0cmluZyBuZXR3b3JrPSJjdXJyZW50IicpCgpzdHJpbmcuY21kPC1wYXN0ZSgnc3RyaW5nIHJldHJpZXZlIGVucmljaG1lbnQnLCAKICAgICAgICAgICAgICAgICAgJ2FsbE5ldFNwZWNpZXM9IkhvbW8gc2FwaWVucyInKQpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKClRoZSBTVFJJTkcgZW5yaWNobWVudCByZXN1bHRzIGRvbid0IG9wZW4gYnkgZGVmYXVsdCBpZiBydW4gZnJvbSB0aGUgY29tbWFuZCBpbnRlcmZhY2UuIApgYGB7cn0KY29tbWFuZHNSdW4oJ3N0cmluZyBzaG93IGVucmljaG1lbnQnKQpgYGAKClRoZSBTVFJJTkcgYXBwIGluY2x1ZGVzIHNldmVyYWwgb3B0aW9ucyBmb3IgZmlsdGVyaW5nIGFuZCBkaXNwbGF5aW5nIHRoZSBlbnJpY2htZW50IHJlc3VsdHMuIFdlIHdpbGwgZmlsdGVyIHRoZSByZXN1bHRzIHRvIG9ubHkgc2hvdyBHTyBQcm9jZXNzLgoKYGBge3J9CmNvbW1hbmRzUnVuKHBhc3RlKCdzdHJpbmcgZmlsdGVyIGVucmljaG1lbnQnLCAKICAgICAgICAgICAgICAgICAgJ2NhdGVnb3JpZXM9IkdPIEJpb2xvZ2ljYWwgUHJvY2VzcyInLAogICAgICAgICAgICAgICAgICAncmVtb3ZlT3ZlcmxhcHBpbmc9InRydWUiJywKICAgICAgICAgICAgICAgICAgc2VwID0gJyAnKSkKYGBgCgpOZXh0LCB3ZSB3aWxsIGFkZCBhIHNwbGl0IGRvbnV0IGNoYXJ0LgoKYGBge3J9CmNvbW1hbmRzUnVuKCdzdHJpbmcgc2hvdyBjaGFydHMnKQpgYGAKCiMgVmlzdWFsaXppbmcgUmVzdWx0cyAtIEp1cmthdCBTY29yZQpXZSBjYW4gY3JlYXRlIGEgdmlzdWFsaXphdGlvbiBiYXNlZCBvbiB0aGUgSnVya2F0IFNjb3JlLCBhbmQgdGhlIGRpZmZlcmVudCBpbnRlcmFjdGlvbiB0eXBlcyAoQVAtTVMgYW5kIFNUUklORyk6CgpgYGB7cn0Kc3R5bGUubmFtZSA9ICJBUC1NUyBKdXJrYXQgU2NvcmUiCmNyZWF0ZVZpc3VhbFN0eWxlKHN0eWxlLm5hbWUpCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWUpCnNldE5vZGVDb2xvckRlZmF1bHQoIiNGRkNDMDAiLCBzdHlsZS5uYW1lPXN0eWxlLm5hbWUpCnNldE5vZGVMYWJlbE1hcHBpbmcoJ2Rpc3BsYXkgbmFtZScsIHN0eWxlLm5hbWU9c3R5bGUubmFtZSkKc2V0RWRnZUNvbG9yTWFwcGluZygiaW50ZXJhY3Rpb24iLCAiQVAtTVMiLCAiIzU1QUE1NSIsICJkIiwgc3R5bGUubmFtZSA9IHN0eWxlLm5hbWUpCnNldEVkZ2VMaW5lV2lkdGhNYXBwaW5nKCdBUC5NUy5TY29yZScsIHRhYmxlLmNvbHVtbi52YWx1ZXM9YygwLDEpLCB3aWR0aHM9YygxLDUpLCBtYXBwaW5nLnR5cGU9ImMiLCBzdHlsZS5uYW1lPXN0eWxlLm5hbWUpCmBgYAoKTm93LCB3ZSBkZWZpbmUgYSBjb2xvciBncmFkaWVudCBiYXNlZCBvbiB0aGUgZGF0YSB2YWx1ZXMgaW4gdGhlIGBKdXJrYXRTY29yZWAgY29sdW1uOgoKYGBge3J9CnNldE5vZGVDb2xvck1hcHBpbmcoJ0p1cmthdFNjb3JlJywgY29sb3JzID0gcGFsZXR0ZUNvbG9yQnJld2VyUHVycGxlcywgc3R5bGUubmFtZT1zdHlsZS5uYW1lKQpgYGAKCldlIG5vdyBoYXZlIGEgdmlzdWFsaXphdGlvbiBvZiB0aGUgbmV0d29yayBoaWdobGlnaHRpbmcgdGhlIGFwLW1zIGV4cGVyaW1lbnRhbCBkYXRhIChncmVlbiBlZGdlcyksIGFzIHdlbGwgYXMgYWRkaXRpb25hbCBrbm93biBpbnRlcmFjdGlvbnMgKGdyZXkgZWRnZXMpLCB3aXRoIG5vZGUgY29sb3IgaW5kaWNhdGluZyB0aGUgSnVya2F0IFNjb3JlIGZyb20gdGhlIGRhdGEuCgojIFZpc3VhbGl6aW5nIFJlc3VsdHMgLSBDb21iaW5lZApXZSBjb3VsZCBjcmVhdGUgYSBzaW1pbGFyIGtpbmQgb2Ygc3R5bGUgZm9yIHRoZSBIRUsgc2NvcmUsIGJ1dCB0aGF0IG9ubHkgYWxsb3dzIGZvciB2aWV3aW5nIGVhY2ggc3R5bGUgc2VwZXJhdGVseS4gSW5zdGVhZCwgd2UgY2FuIGNyZWF0ZSBhIGNvbWJpbmVkIHN0eWxlLCB1c2luZyB0aGUgZW5oYW5jZWRHcmFwaGljcyBhcHAuCgpGb3IgdGhpcywgd2Ugd2lsbCBuZWVkIGEgbmV3IGNvbHVtbiBkZWZpbmluZyBhIG5ldyBhdHRyaWJ1dGUgdGhhdCB3aWxsIGJlIHVzZWQgZm9yIG1hcHBpbmcgdG8gdGhlIEN1c3RvbSBHcmFwaGljcyBwcm9wZXJ0eSB2aWEgdGhlIGVuaGFuY2VkR3JhcGhpY3MgYXBwLiBUaGlzIG5ldyBhdHRyaWJ1dGUgaGFzIHRvIGJlIGluIHRoZSBmb3JtIG9mIG1hcHBpbmdzIHJlY29nbml6ZWQgYnkgdGhlIGVuaGFuY2VkR3JhcGhpY3MgYXBwLgoKV2UgY2FuIGNvcHkgdGhlIHByZXZpb3VzIHN0eWxlIHRvIHJldGFpbiBzb21lIG9mIHRoZSBtYXBwaW5ncyB3ZSB3YW50IHRvIGtlZXA6CgpgYGB7cn0KY29weVZpc3VhbFN0eWxlKGZyb20uc3R5bGU9IkFQLU1TIEp1cmthdCBTY29yZSIsIHRvLnN0eWxlPSJBUC1NUyBDb21iaW5lZFNjb3JlIikKc2V0VmlzdWFsU3R5bGUoc3R5bGUubmFtZT0iQVAtTVMgQ29tYmluZWRTY29yZSIpCmBgYAoKVG8gYmVnaW4gYWRkaW5nIHRoZSBuZXcgY29sdW1uLCB3ZSBmaXJzdCBkZWZpbmUgYSBkYXRhIGZyYW1lIHdpdGggdGhlIG5ldyBhdHRyaWJ1dGUgZm9ybWF0dGVkIGZvciBlbmhhbmNlZEdyYXBoaWNzOgoKYGBge3J9CmFsbC5ub2RlczwtZ2V0QWxsTm9kZXMoKQpjb21iaW5lZC5kZjwtZGF0YS5mcmFtZShhbGwubm9kZXMsICdwaWVjaGFydDogc2hvd2xhYmVscz1mYWxzZSByYW5nZT0iMCwxIiBhcmNzdGFydD05MCB2YWx1ZWxpc3Q9Ii41LC41IiBjb2xvcmxpc3Q9InVwOmJsdWUsemVybzp3aGl0ZSxkb3duOndoaXRlO3VwOnB1cnBsZSx6ZXJvOndoaXRlLGRvd246d2hpdGUiIGF0dHJpYnV0ZWxpc3Q9IkhFS1Njb3JlLEp1cmthdFNjb3JlIicpCmNvbG5hbWVzKGNvbWJpbmVkLmRmKTwtYygibmFtZSIsIkNvbWJpbmVkU2NvcmUiKQpgYGAKCk5leHQsIHdlIGxvYWQgdGhpcyBkYXRhZnJhbWUgaW50byB0aGUgTm9kZSBUYWJsZSB0byBjcmVhdGUgYW5kIGZpbGwgYSBuZXcgY29sdW1uOgoKYGBge3J9CmxvYWRUYWJsZURhdGEoY29tYmluZWQuZGYsIGRhdGEua2V5LmNvbHVtbiA9ICJuYW1lIiwgdGFibGUua2V5LmNvbHVtbiA9ICJuYW1lIikKYGBgCgpXZSBub3cgaGF2ZSBhIG5ldyBjb2x1bW4sICpDb21iaW5lZFNjb3JlKiwgdGhhdCB3ZSBjYW4gdXNlIGZvciB0aGUgbWFwcGluZy4gVGhpcyBtYXBwaW5nIGRvZXMgbm90IGNvbWUgd2l0aCBhIGN1c3RvbSBoZWxwZXIgZnVuY3Rpb24sIHNlIHdlIGFyZSBnb2luZyB0byB1c2UgdHdvIGFsdGVybmF0aXZlIGZ1bmN0aW9ucyB0byBwcmVwYXJlIHRoZSBwYXNzdGhyb3VnaCBtYXBwaW5nIHByb3BlcnR5IGFuZCB0aGVuIHVwZGF0ZSBvdXIgdmlzdWFsIHN0eWxlIHdpdGggdGhlIG5ldyBtYXBwaW5nOgoKYGBge3J9CnBpZWNoYXJ0Lm1hcDwtbWFwVmlzdWFsUHJvcGVydHkoJ25vZGUgY3VzdG9tZ3JhcGhpY3MgNCcsJ0NvbWJpbmVkU2NvcmUnLCdwJykKdXBkYXRlU3R5bGVNYXBwaW5nKCdBUC1NUyBDb21iaW5lZFNjb3JlJywgcGllY2hhcnQubWFwKQpgYGAKClJlbWVtYmVyIHRoYXQgd2hlbiB3ZSBpbXBvcnRlZCBtdWx0aXBsZSB2YWx1ZXMgZm9yIGEgc2luZ2xlIG5vZGUgYXR0cmlidXRlLCBzdWNoIGFzIHRoZSBzY29yZXMgZm9yIGh1bWFuIG5vZGVzIGludGVyYWN0aW5nIHdpdGggbW9yZSB0aGFuIG9uZSBISVYgbm9kZXMsIHRoZSBsYXN0IHZhbHVlIGltcG9ydGVkIHdpbGwgb3ZlcndyaXRlIHByaW9yIHZhbHVlcyBhbmQgdGhlIHZpc3VhbGl6YXRpb24gdGh1cyBvbmx5IHNob3dzIHRoZSBsYXN0IHZhbHVlLiBGb3IgPGI+RUlGM0E8L2I+LCB3aGljaCBpbnRlcmFjdHMgd2l0aCBib3RoIDxiPlBSPC9iPiBhbmQgPGI+UE9MPC9iPiwgb25seSB0aGUgZGF0YSByZWxldmFudCB0byB0aGUgPGI+UFI8L2I+IGludGVyYWN0aW9uIGlzIG1haW50YWluZWQgaW4gdGhlIE5vZGUgVGFibGUgYmVjYXVzZSBvdXIgc291cmNlIGRhdGEgd2FzIHNvcnRlZCBhbHBoYWJldGljYWxseSBieSBCYWl0LgoKIyBTYXZpbmcsIHNoYXJpbmcgYW5kIHB1Ymxpc2hpbmcKCiMjIFNhdmluZyBhIEN5dG9zY2FwZSBzZXNzaW9uIGZpbGUKU2Vzc2lvbiBmaWxlcyBzYXZlICpldmVyeXRoaW5nKi4gQXMgd2l0aCBtb3N0IHByb2plY3Qgc29mdHdhcmUsIHdlIHJlY29tbWVuZCBzYXZpbmcgb2Z0ZW4hCmBgYHtyfQpzYXZlU2Vzc2lvbignQVAtTVNfc2Vzc2lvbicpICMuY3lzCmBgYAoqKk5vdGU6KiogSWYgeW91IGRvbid0IHNwZWNpZnkgYSBjb21wbGV0ZSBwYXRoLCB0aGUgZmlsZXMgd2lsbCBiZSBzYXZlZCByZWxhdGl2ZSB0byB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkgaW4gUi4gCgojIyBTYXZpbmcgaGlnaCByZXNvbHV0aW9uIGltYWdlIGZpbGVzCllvdSBjYW4gZXhwb3J0IGV4dHJlbWVseSBoaWdoIHJlc29sdXRpb24gaW1hZ2VzLCBpbmNsdWRpbmcgdmVjdG9yIGdyYXBoaWMgZm9ybWF0cy4KYGBge3J9CmV4cG9ydEltYWdlKCdBUC1NU19pbWFnZScsIHR5cGUgPSAnUERGJykgIy5wZGYKP2V4cG9ydEltYWdlCmBgYAo=