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=