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.


Installation

R

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

if(!"RColorBrewer" %in% installed.packages()){
    install.packages("RColorBrewer")
}
library(RColorBrewer)

Python

import os
import sys
import pandas as pd
os.system('pip install py4cytoscape')
import py4cytoscape as p4c

Prerequisites

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

R

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

Python

p4c.install_app('stringApp')
p4c.install_app('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:

R

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)

Python

ap_ms_url = 'https://raw.githubusercontent.com/cytoscape/cytoscape-automation/master/for-scripters/R/notebooks/AP-MS/ap-ms-demodata.csv'
ap_ms_df = pd.read_csv(ap_ms_url)
ap_ms_df.head()

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:

R

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

Python

edge_data = {'source':ap_ms_df["Bait"],
             'target':ap_ms_df["Prey"],
             'interaction': "AP-MS",
             'AP-MS Score':ap_ms_df["AP-MS Score"]
            }
edges = pd.DataFrame(data=edge_data, columns=['source', 'target', 'interaction','AP-MS Score'])
edges.head(10)

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.

R

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

Python

p4c.networks.create_network_from_data_frames(None, 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.

R

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

Python

p4c.tables.load_table_data(ap_ms_df.iloc[:, 1: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:

R

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

Python

uniport_str = ','.join(ap_ms_df["UniProt"])
string_cmd_list = ['string protein query','query="',uniport_str,'"', 'species="Homo sapiens"', 'limit=0', 'cutoff=0.999']
string_cmd = " ".join(string_cmd_list)
p4c.commands.commands_run(string_cmd)

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

Merge Networks

To incorprate 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.

R

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

Python

p4c.tools.merge_networks(["apms network,String Network"], operation='union', node_keys=["Uniprot,query term"], node_merge_map=[["name","display name","display name", "string"]])

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:

R

setCurrentNetwork('union: apms network,String Network')
setCurrentView()

Python

p4c.networks.set_current_network('union: apms network,String Network')
p4c.network_views.set_current_view()

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

R

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)

Python

style_name = "AP-MS"
p4c.styles.create_visual_style(style_name)
p4c.style_dependencies.lock_node_dimensions(True)
p4c.style_defaults.set_node_size_default(50, style_name=style_name)
p4c.style_defaults.set_node_color_default("#CCCCFF", style_name=style_name)
p4c.style_mappings.set_node_label_mapping('display name', style_name=style_name)
p4c.styles.set_visual_style(style_name=style_name)

R

layoutNetwork(paste('force-directed', 
              'defaultSpringCoefficient=0.00001',
              'defaultSpringLength=50',
              'defaultNodeMass=4',
              sep=' '))

Python

p4c.layouts.layout_network(layout_name='force-directed defaultSpringCoefficient=0.00001 defaultSpringLength=50 defaultNodeMass=4')

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.

R

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

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

Python

p4c.commands.commands_run('string make string network="current"')

string_cmd_list = ['string retrieve enrichment','allNetSpecies="Homo sapiens"', 'background="genome"','selectedNodesOnly="false"']
string_cmd = " ".join(string_cmd_list)
p4c.commands.commands_run(string_cmd)

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

R

commandsRun('string show enrichment')

Python

p4c.commands.commands_run('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.

R

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

Python

p4c.commands.commands_run('string filter enrichment categories="GO Process" removeOverlapping="true"')

Next, we will add a split donut chart.

R

commandsRun('string show charts')

Python

p4c.commands.commands_run('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):

R

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)

Python

style_name = "AP-MS Jurkat Score"
p4c.styles.create_visual_style(style_name)
p4c.styles.set_visual_style(style_name=style_name)
p4c.style_defaults.set_node_color_default("#FFCC00", style_name=style_name)
p4c.style_mappings.set_node_label_mapping('display name', style_name=style_name)
p4c.style_mappings.set_edge_color_mapping("interaction", ["AP-MS"], ["#55AA55"], "d", style_name=style_name)
p4c.style_mappings.set_edge_line_width_mapping('AP-MS Score', table_column_values=[0, 1], widths=[1,5], mapping_type="c", style_name=style_name)

R

First, we define the range of data values for the mapping, and the Brewer palette to use:

data.values = c(0,0.5,1)
display.brewer.all(length(data.values), colorblindFriendly=TRUE, type="seq") # div,qual,seq,all
node.colors <- c(rev(brewer.pal(length(data.values), "Purples")))

Python

data_values = [0, 0.5, 1]
node_colors = ["#756BB1", "#BCBDDC", "#EFEDF5"]

Next, we apply the mapping to the network:

R

setNodeColorMapping('JurkatScore', data.values, node.colors, style.name=style.name)

Python

p4c.style_mappings.set_node_color_mapping('JurkatScore', data_values, node_colors, 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:

R

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

Python

p4c.styles.copy_visual_style("AP-MS Jurkat Score", "AP-MS CombinedScore")
p4c.styles.set_visual_style(style_name="AP-MS CombinedScore")

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

R

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")

Python

all_nodes = p4c.networks.get_all_nodes()
combined_df = pd.DataFrame(all_nodes, columns=['name'])
combined_df["CombinedScore"] = '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"'

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

R

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

Python

p4c.tables.load_table_data(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:

R

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

Python

piechart_map = p4c.style_mappings.map_visual_property('node customgraphics 4','CombinedScore','p')
p4c.style_mappings.update_style_mapping('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!

R

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.

Python

p4c.session.save_session('AP-MS_session')

Saving high resolution image files

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

R

exportImage('AP-MS_image', type = 'PDF') #.pdf
?exportImage

Python

p4c.network_views.export_image('AP-MS_image', type = 'PDF')
LS0tDQp0aXRsZTogIkFmZmluaXR5IHB1cmlmaWNhdGlvbi1tYXNzIHNwZWN0cm9tZXRyeSBuZXR3b3JrIGFuYWx5c2lzIg0KYXV0aG9yOiAiS3Jpc3RpbmEgSGFuc3BlcnMsIFlpaGFuZyBYaW4sIEtvem8gTmlzaGlkYSwgQmFycnkgRGVtY2hhaywgQWxleGFuZGVyIFBpY28iDQpwYWNrYWdlOiBSQ3kzDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiAibm9uZSINCiMgIHBkZl9kb2N1bWVudDoNCiMgICAgdG9jOiB0cnVlICAgIA0KLS0tDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBldmFsPUZBTFNFDQopDQpgYGANCg0KKlRoZSBSIG1hcmtkb3duIGlzIGF2YWlsYWJsZSBmcm9tIHRoZSBwdWxsZG93biBtZW51IGZvciogQ29kZSAqYXQgdGhlIHVwcGVyLXJpZ2h0LCBjaG9vc2UgIkRvd25sb2FkIFJtZCIsIG9yIFtkb3dubG9hZCB0aGUgUm1kIGZyb20gR2l0SHViXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS1hdXRvbWF0aW9uL21hc3Rlci9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL0FQLU1TLW5ldHdvcmstYW5hbHlzaXMuUm1kKS4qDQoNCjxociAvPg0KVGhpcyB2aWduZXR0ZSBkZXNjcmliZXMgaG93IHRvIHVzZSBkYXRhIGZyb20gYW4gYWZmaW5pdHkgcHVyaWZpY2F0aW9uLW1hc3Mgc3BlY3Ryb21ldHJ5IGV4cGVyaW1lbnQgdG8gZ2VuZXJhdGUgcmVsZXZhbnQgaW50ZXJhY3Rpb24gbmV0d29ya3MsIGVucmljaGluZyB0aGUgbmV0d29ya3Mgd2l0aCBpbmZvcm1hdGlvbiBmcm9tIHB1YmxpYyByZXNvdXJjZXMsIGFuYWx5emluZyB0aGUgbmV0d29ya3MgYW5kIGNyZWF0aW5nIGVmZmVjdGl2ZSB2aXN1YWxpemF0aW9ucy4NCg0KVGhlIHJlc3VsdCBvZiB0aGlzIHZpZ25ldHRlIHdpbGwgYmUgYSB2aXN1YWxpemF0aW9uIG9mIGEgaHVtYW4tSElWIGludGVncmF0ZWQgbmV0d29yayBjb21iaW5pbmcgZXhwZXJpbWVudGFsIGRhdGEgYW5kIHB1YmxpY2x5IGF2YWlsYWJsZSBpbnRlcmFjdGlvbiBkYXRhLiANCg0KPGNlbnRlcj4NCiFbXShodHRwczovL2N5dG9zY2FwZS5naXRodWIuaW8vY3l0b3NjYXBlLXR1dG9yaWFscy9wcm90b2NvbHMvQVAtTVMtbmV0d29yay1hbmFseXNpcy9maW5hbC1tb2R1bGUucG5nKXt3aWR0aD02MCV9DQo8L2NlbnRlcj4NCjxociAvPg0KDQojIEluc3RhbGxhdGlvbiB7LnRhYnNldH0NCg0KIyMgUg0KDQpgYGB7cn0NCmlmKCEiUkN5MyIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KICAgIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJSQ3kzIikNCn0NCmxpYnJhcnkoUkN5MykNCg0KaWYoISJSQ29sb3JCcmV3ZXIiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkpew0KICAgIGluc3RhbGwucGFja2FnZXMoIlJDb2xvckJyZXdlciIpDQp9DQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmBgYA0KDQojIyBQeXRob24NCmBgYHtweXRob259DQppbXBvcnQgb3MNCmltcG9ydCBzeXMNCmltcG9ydCBwYW5kYXMgYXMgcGQNCm9zLnN5c3RlbSgncGlwIGluc3RhbGwgcHk0Y3l0b3NjYXBlJykNCmltcG9ydCBweTRjeXRvc2NhcGUgYXMgcDRjDQpgYGANCg0KIyBQcmVyZXF1aXNpdGVzIHsudGFic2V0fQ0KSW4gYWRkaXRpb24gdG8gdGhpcyBwYWNrYWdlIChSQ3kzKSwgeW91IHdpbGwgbmVlZDoNCg0KICAqICoqTGF0ZXN0IHZlcnNpb24gb2YgQ3l0b3NjYXBlKiosIHdoaWNoIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gaHR0cHM6Ly9jeXRvc2NhcGUub3JnL2Rvd25sb2FkLmh0bWwuIFNpbXBseSBmb2xsb3cgdGhlIGluc3RhbGxhdGlvbiBpbnN0cnVjdGlvbnMgb24gc2NyZWVuLg0KKiBDb21wbGV0ZSBpbnN0YWxsYXRpb24gd2l6YXJkDQoqIExhdW5jaCBDeXRvc2NhcGUgDQpGb3IgdGhpcyB2aWduZXR0ZSwgeW914oCZbGwgYWxzbyBuZWVkIHRoZSBTVFJJTkcgYXBwIGFuZCB0aGUgZW5oYW5jZWRHcmFwaGljcyBhcHA6IA0KDQoqIEluc3RhbGwgdGhlIFNUUklORyBhcHAgZnJvbSBodHRwczovL2FwcHMuY3l0b3NjYXBlLm9yZy9hcHBzL3N0cmluZ2FwcA0KKiBJbnN0YWxsIHRoZSBlbmhhbmNlZEdyYXBoaWNzIGFwcCBmcm9tIGh0dHA6Ly9hcHBzLmN5dG9zY2FwZS5vcmcvYXBwcy9lbmhhbmNlZGdyYXBoaWNzDQoNCiMjIFINCg0KYGBge3J9DQppbnN0YWxsQXBwKCdzdHJpbmdBcHAnKQ0KaW5zdGFsbEFwcCgnZW5oYW5jZWRHcmFwaGljcycpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMuaW5zdGFsbF9hcHAoJ3N0cmluZ0FwcCcpDQpwNGMuaW5zdGFsbF9hcHAoJ2VuaGFuY2VkR3JhcGhpY3MnKQ0KYGBgDQoNCiMgQmFja2dyb3VuZA0KVGhlIGRhdGEgdXNlZCBmb3IgdGhpcyBwcm90b2NvbCByZXByZXNlbnRzIGludGVyYWN0aW9ucyBiZXR3ZWVuIGh1bWFuIGFuZCBISVYgcHJvdGVpbnMgYnkgSsOkZ2VyIGV0IGFsIChodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMzMzEwOTExLykuIEluIHRoaXMgcXVhbnRpdGF0aXZlIEFQLU1TIGV4cGVyaW1lbnQsIGEgcmVsYXRpdmVseSBzbWFsbCBudW1iZXIgb2YgYmFpdCBwcm90ZWlucyBhcmUgdXNlZCB0byBwdWxsIGRvd24gYSBsYXJnZXIgc2V0IG9mIHByZXkgcHJvdGVpbnMuDQoNCk5vdGUgdGhhdCB0aGlzIHR1dG9yaWFsIGRvZXMgbm90IGRlc2NyaWJlIGhvdyB0byBwcmUtcHJvY2VzcyB0aGUgcmF3IEFQLU1TIGRhdGEsIHRoZSBkYXRhIHVzZWQgaGVyZSBpcyBhbHJlYWR5IHNjb3JlZCBhbmQgZmlsdGVyZWQuDQoNCiMgSW1wb3J0IE5ldHdvcmsgYW5kIERhdGEgey50YWJzZXR9DQpMZXQncyBzdGFydCBieSByZWFkaW5nIGluIHRoZSBleGFtcGxlIGRhdGEgZmlsZToNCg0KIyMgUg0KDQpgYGB7cn0NCmFwbXMuZGF0YTwtcmVhZC5jc3YoZmlsZT0iaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtYXV0b21hdGlvbi9tYXN0ZXIvZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9BUC1NUy9hcC1tcy1kZW1vZGF0YS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQphcF9tc191cmwgPSAnaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtYXV0b21hdGlvbi9tYXN0ZXIvZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9BUC1NUy9hcC1tcy1kZW1vZGF0YS5jc3YnDQphcF9tc19kZiA9IHBkLnJlYWRfY3N2KGFwX21zX3VybCkNCmFwX21zX2RmLmhlYWQoKQ0KYGBgDQoNCiMgey0gLnRhYnNldH0NCg0KTm93IHdlIGNhbiBjcmVhdGUgYSBkYXRhIGZyYW1lIGZvciB0aGUgbmV0d29yayBlZGdlcyAoaW50ZXJhY3Rpb25zKSB1c2luZyB0aGUgaW1wb3J0ZWQgZGF0YS4gV2Ugd2lsbCBhZGQgYW4gaW50ZXJhY3Rpb24gIkFQLU1TIiB0byBlYWNoIGVkZ2UsIHdoaWNoIHdpbGwgYmUgdXNlZnVsIGxhdGVyLCBhbmQgd2UgY2FuIGFsc28gYWRkIHRoZSBBUC1NUyBzY29yZSBmcm9tIHRoZSBkYXRhIGFzIGFuIGVkZ2UgYXR0cmlidXRlOiANCg0KIyMgUg0KDQpgYGB7cn0NCmVkZ2VzIDwtIGRhdGEuZnJhbWUoc291cmNlPWFwbXMuZGF0YVssIkJhaXQiXSx0YXJnZXQ9YXBtcy5kYXRhWywiUHJleSJdLGludGVyYWN0aW9uPSJBUC1NUyIsIEFQLk1TLlNjb3JlPWFwbXMuZGF0YVssIkFQLk1TLlNjb3JlIl0sc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCmVkZ2VfZGF0YSA9IHsnc291cmNlJzphcF9tc19kZlsiQmFpdCJdLA0KICAgICAgICAgICAgICd0YXJnZXQnOmFwX21zX2RmWyJQcmV5Il0sDQogICAgICAgICAgICAgJ2ludGVyYWN0aW9uJzogIkFQLU1TIiwNCiAgICAgICAgICAgICAnQVAtTVMgU2NvcmUnOmFwX21zX2RmWyJBUC1NUyBTY29yZSJdDQogICAgICAgICAgICB9DQplZGdlcyA9IHBkLkRhdGFGcmFtZShkYXRhPWVkZ2VfZGF0YSwgY29sdW1ucz1bJ3NvdXJjZScsICd0YXJnZXQnLCAnaW50ZXJhY3Rpb24nLCdBUC1NUyBTY29yZSddKQ0KZWRnZXMuaGVhZCgxMCkNCmBgYA0KDQojIHstIC50YWJzZXR9DQoNCkZpbmFsbHksIHdlIHVzZSB0aGUgZWRnZSBkYXRhIGZyYW0gdG8gY3JlYXRlIHRoZSBuZXR3b3JrLiBOb3RlIHRoYXQgd2UgZG9uJ3QgbmVlZCB0byBkZWZpbmUgYSBkYXRhIGZyYW1lIGZvciBub2RlcywgYXMgYWxsIG5vZGVzIGluIHRoaXMgY2FzZSBhcmUgcmVwcmVzZW50ZWQgaW4gdGhlIGVkZ2UgZGF0YSBmcmFtZS4NCg0KIyMgUg0KDQpgYGB7cn0NCmNyZWF0ZU5ldHdvcmtGcm9tRGF0YUZyYW1lcyhlZGdlcz1lZGdlcywgdGl0bGU9ImFwbXMgbmV0d29yayIsIGNvbGxlY3Rpb24gPSAiYXBtcyBjb2xsZWN0aW9uIikNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCnA0Yy5uZXR3b3Jrcy5jcmVhdGVfbmV0d29ya19mcm9tX2RhdGFfZnJhbWVzKE5vbmUsIGVkZ2VzLCB0aXRsZT0nYXBtcyBuZXR3b3JrJywgY29sbGVjdGlvbiA9ICJhcG1zIGNvbGxlY3Rpb24iKQ0KYGBgDQoNCiMgey19DQoNClRoZSBpbXBvcnRlZCBuZXR3b3JrIGNvbnNpc3RzIG9mIG11bHRpcGxlIHNtYWxsZXIgc3VibmV0d29ya3MsIGVhY2ggcmVwcmVzZW50aW5nIGEgYmFpdCBub2RlIGFuZCBpdHMgYXNzb2NpYXRlZCBwcmV5IG5vZGVzDQoNCiMgTG9hZGluZyBEYXRhIHsudGFic2V0fQ0KVGhlcmUgYXJlIHRocmVlIG90aGVyIGNvbHVtbnMgb2YgZGF0YSBhbmQgYW5ub3RhdGlvbnMgZm9yIHRoZSAiUHJleSIgcHJvdGVpbnMgdGhhdCB3ZSB3YW50IHRvIGxvYWQgaW50byB0aGlzIG5ldHdvcmsuIA0KDQpJbiB0aGlzIGRhdGEsIHRoZSBQcmV5IG5vZGVzIGFyZSByZXBlYXRlZCBmb3IgZWFjaCBpbnRlcmFjdGlvbnMgd2l0aCBhIEJhaXQgbm9kZSwgc28gdGhlIGRhdGEgY29udGFpbnMgZGlmZmVyZW50IHZhbHVlcyBmb3IgdGhlIHNhbWUgYXR0cmlidXRlIChmb3IgZXhhbXBsZSBIRUtTY29yZSksIGZvciBlYWNoIFByZXkgbm9kZS4gRHVyaW5nIGltcG9ydCwgdGhlIGxhc3QgdmFsdWUgaW1wb3J0ZWQgd2lsbCBvdmVyd3JpdGUgcHJpb3IgdmFsdWVzIGFuZCB2aXN1YWxpemF0aW9ucyB1c2luZyB0aGlzIGF0dHJpYnV0ZSB0aHVzIG9ubHkgc2hvd3MgdGhlIGxhc3QgdmFsdWUuDQoNCiMjIFINCg0KYGBge3J9DQpsb2FkVGFibGVEYXRhKGFwbXMuZGF0YVssMjo1XSwgZGF0YS5rZXkuY29sdW1uPSJQcmV5IikNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCnA0Yy50YWJsZXMubG9hZF90YWJsZV9kYXRhKGFwX21zX2RmLmlsb2NbOiwgMTo1XSwgZGF0YV9rZXlfY29sdW1uPSJQcmV5IikNCmBgYA0KDQojIEF1Z21lbnRpbmcgTmV0d29yayB3aXRoIEV4aXN0aW5nIFByb3RlaW4tcHJvdGVpbiBJbnRlcmFjdGlvbiBEYXRhIHsudGFic2V0fQ0KV2UgYXJlIGdvaW5nIHRvIHVzZSBleGlzdGluZyBwcm90ZWluLXByb3RlaW4gaW50ZXJhY3Rpb24gZGF0YSB0byBlbnJpY2ggdGhlIG5ldHdvcmssIHVzaW5nIHRoZSBTVFJJTkcgZGF0YWJhc2Ugd2l0aCB0aGUgaHVtYW4gcHJvdGVpbiBub2RlcyBhcyBpbnB1dC4NCg0KTGV0J3MgY29sbGVjdCBhbGwgdGhlIFVuaVByb3QgaWRlbnRpZmllcnMgZnJvbSB0aGUgZGF0YSwgYW5kIGNyZWF0ZSBhIHRleHQgc3RyaW5nIHRoYXQgd2UgY2FuIHVzZSB0byBxdWVyeSBTVFJJTkc6DQoNCiMjIFINCg0KYGBge3J9DQp1bmlwcm90LnN0cjwtdG9TdHJpbmcoYXBtcy5kYXRhWywiVW5pUHJvdCJdKQ0Kc3RyaW5nLmNtZDwtcGFzdGUoJ3N0cmluZyBwcm90ZWluIHF1ZXJ5JywNCiAgICAgICAgICAgICAgICAgICdxdWVyeT0iJyx1bmlwcm90LnN0ciwnIicsIA0KICAgICAgICAgICAgICAgICAgJ3NwZWNpZXM9IkhvbW8gc2FwaWVucyInLCANCiAgICAgICAgICAgICAgICAgICdsaW1pdD0wJywgDQogICAgICAgICAgICAgICAgICAnY3V0b2ZmPTAuOTk5JywNCiAgICAgICAgICAgICAgICAgIHNlcD0gJyAnKQ0KY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCnVuaXBvcnRfc3RyID0gJywnLmpvaW4oYXBfbXNfZGZbIlVuaVByb3QiXSkNCnN0cmluZ19jbWRfbGlzdCA9IFsnc3RyaW5nIHByb3RlaW4gcXVlcnknLCdxdWVyeT0iJyx1bmlwb3J0X3N0ciwnIicsICdzcGVjaWVzPSJIb21vIHNhcGllbnMiJywgJ2xpbWl0PTAnLCAnY3V0b2ZmPTAuOTk5J10NCnN0cmluZ19jbWQgPSAiICIuam9pbihzdHJpbmdfY21kX2xpc3QpDQpwNGMuY29tbWFuZHMuY29tbWFuZHNfcnVuKHN0cmluZ19jbWQpDQpgYGANCg0KIyB7LX0NCg0KVGhlIHJlc3VsdGluZyBuZXR3b3JrIGNvbnRhaW5zIGtub3duIGludGVyYWN0aW9ucyBiZXR3ZWVuIHRoZSBodW1hbiBwcm90ZWlucywgd2l0aCBhbiBldmlkZW5jZSBzY29yZSBvZiAwLjk5OSBvciBncmVhdGVyLg0KDQojIE1lcmdlIE5ldHdvcmtzIHsudGFic2V0fQ0KVG8gaW5jb3JwcmF0ZSB0aGUgbmV3IGluZm9ybWF0aW9uIGludG8gb3VyIEFQLU1TIG5ldHdvcmssIHdlIG5lZWQgbWVyZ2UgdGhlIFNUUklORyBhbmQgQVAtTVMgbmV0d29ya3MuIFdlIGNhbiB1c2UgdGhlIFVuaXByb3QgSURzIGF2YWlsYWJsZSBpbiBib3RoIG5ldHdvcmtzIGFzIHRoZSBtYXRjaGluZyBhdHRyaWJ1dGUsICJVbmlwcm90IiBpbiB0aGUgQVAtTVMgbmV0d29yaywgYW5kICJxdWVyeSB0ZXJtIGluIHRoZSBTdHJpbmcgbmV0d29yay4NCldlIHdpbGwgYWxzbyBzcGVjaWZ5IGhvdyB0byBtZXJnZSB0aGUgYXR0cmlidXRlIGNvbnRhaW5pbmcgdGhlIG5vZGUgbmFtZSAoc3ltYm9sKSwgd2hpY2ggaXMgY29udGFpbmVkIGluIHRoZSAibmFtZSIgYXR0cmlidXRlIGZvciB0aGUgQVAtTVMgbmV0d29yayBhbmQgdGhlICJkaXNwbGF5IG5hbWUiIGZvciB0aGUgU3RyaW5nIG5ldHdvcmsuIA0KDQojIyBSDQoNCmBgYHtyfQ0KbWVyZ2UuY21kPC1wYXN0ZSgnbmV0d29yayBtZXJnZScsDQogICAgICAgICAgICAgICAgICdvcGVyYXRpb249dW5pb24nLA0KICAgICAgICAgICAgICAgICAnc291cmNlcz0iYXBtcyBuZXR3b3JrLFN0cmluZyBOZXR3b3JrIicsDQogICAgICAgICAgICAgICAgICdub2RlS2V5cz0iVW5pcHJvdCxxdWVyeSB0ZXJtIicsIA0KICAgICAgICAgICAgICAgICAnbm9kZU1lcmdlTWFwPSJ7bmFtZSxkaXNwbGF5IG5hbWUsZGlzcGxheSBuYW1lLCBzdHJpbmd9IicpDQpjb21tYW5kc1J1bihtZXJnZS5jbWQpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMudG9vbHMubWVyZ2VfbmV0d29ya3MoWyJhcG1zIG5ldHdvcmssU3RyaW5nIE5ldHdvcmsiXSwgb3BlcmF0aW9uPSd1bmlvbicsIG5vZGVfa2V5cz1bIlVuaXByb3QscXVlcnkgdGVybSJdLCBub2RlX21lcmdlX21hcD1bWyJuYW1lIiwiZGlzcGxheSBuYW1lIiwiZGlzcGxheSBuYW1lIiwgInN0cmluZyJdXSkNCmBgYA0KDQojIE5ldHdvcmsgVmlzdWFsaXphdGlvbiB7LnRhYnNldH0NCldoZW4gdGhlIG1lcmdlZCBuZXR3b3JrIGZpcnN0IGxvYWRzLCBpdCB3aWxsIGhhdmUgdGhlIFNUUklORyBzdHlsZSBhcHBsaWVkLiBIb3dldmVyLCBiZWNhdXNlIHRoZSBISVYgbm9kZXMgYXJlIG5vdCBmcm9tIFNUUklORywgdGhleSB3aWxsIGJlIGdyZXkuIFRoZSBsYXlvdXQgYWxzbyBtYWtlcyB0aGUgbmV0d29yayBoYXJkIHRvIGludGVycHJldC4gTGV0J3MgY2hhbmdlIHRoZSBzdHlsZSBvZiB0aGUgbmV0d29yayBhIGJpdC4NCg0KRmlyc3QsIGxldCdzIHNldCBvdXIgQVAtTVMgbmV0d29yayBhcyB0aGUgY3VycmVudCBuZXR3b3JrOg0KDQojIyBSDQoNCmBgYHtyfQ0Kc2V0Q3VycmVudE5ldHdvcmsoJ3VuaW9uOiBhcG1zIG5ldHdvcmssU3RyaW5nIE5ldHdvcmsnKQ0Kc2V0Q3VycmVudFZpZXcoKQ0KYGBgDQoNCiMjIFB5dGhvbg0KDQpgYGB7cHl0aG9ufQ0KcDRjLm5ldHdvcmtzLnNldF9jdXJyZW50X25ldHdvcmsoJ3VuaW9uOiBhcG1zIG5ldHdvcmssU3RyaW5nIE5ldHdvcmsnKQ0KcDRjLm5ldHdvcmtfdmlld3Muc2V0X2N1cnJlbnRfdmlldygpDQpgYGANCg0KIyB7LSAudGFic2V0fQ0KDQpOZXh0LCB3ZSBjYW4gZGVmaW5lIG91ciBzdHlsZSBhbmQgYXBwbHkgaXQgdG8gdGhlIG5ldHdvcms6DQoNCiMjIFINCg0KYGBge3J9DQpzdHlsZS5uYW1lID0gIkFQLU1TIg0KY3JlYXRlVmlzdWFsU3R5bGUoc3R5bGUubmFtZSkNCmxvY2tOb2RlRGltZW5zaW9ucyhUUlVFKQ0Kc2V0Tm9kZVNpemVEZWZhdWx0KCc1MCcsIHN0eWxlLm5hbWUgPSBzdHlsZS5uYW1lKQ0Kc2V0Tm9kZUNvbG9yRGVmYXVsdCgiI0NDQ0NGRiIsIHN0eWxlLm5hbWU9c3R5bGUubmFtZSkNCnNldE5vZGVMYWJlbE1hcHBpbmcoJ2Rpc3BsYXkgbmFtZScsIHN0eWxlLm5hbWU9c3R5bGUubmFtZSkNCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWU9c3R5bGUubmFtZSkNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCnN0eWxlX25hbWUgPSAiQVAtTVMiDQpwNGMuc3R5bGVzLmNyZWF0ZV92aXN1YWxfc3R5bGUoc3R5bGVfbmFtZSkNCnA0Yy5zdHlsZV9kZXBlbmRlbmNpZXMubG9ja19ub2RlX2RpbWVuc2lvbnMoVHJ1ZSkNCnA0Yy5zdHlsZV9kZWZhdWx0cy5zZXRfbm9kZV9zaXplX2RlZmF1bHQoNTAsIHN0eWxlX25hbWU9c3R5bGVfbmFtZSkNCnA0Yy5zdHlsZV9kZWZhdWx0cy5zZXRfbm9kZV9jb2xvcl9kZWZhdWx0KCIjQ0NDQ0ZGIiwgc3R5bGVfbmFtZT1zdHlsZV9uYW1lKQ0KcDRjLnN0eWxlX21hcHBpbmdzLnNldF9ub2RlX2xhYmVsX21hcHBpbmcoJ2Rpc3BsYXkgbmFtZScsIHN0eWxlX25hbWU9c3R5bGVfbmFtZSkNCnA0Yy5zdHlsZXMuc2V0X3Zpc3VhbF9zdHlsZShzdHlsZV9uYW1lPXN0eWxlX25hbWUpDQpgYGANCg0KIyB7LSAudGFic2V0fQ0KDQojIyBSDQoNCmBgYHtyfQ0KbGF5b3V0TmV0d29yayhwYXN0ZSgnZm9yY2UtZGlyZWN0ZWQnLCANCiAgICAgICAgICAgICAgJ2RlZmF1bHRTcHJpbmdDb2VmZmljaWVudD0wLjAwMDAxJywNCiAgICAgICAgICAgICAgJ2RlZmF1bHRTcHJpbmdMZW5ndGg9NTAnLA0KICAgICAgICAgICAgICAnZGVmYXVsdE5vZGVNYXNzPTQnLA0KICAgICAgICAgICAgICBzZXA9JyAnKSkNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCnA0Yy5sYXlvdXRzLmxheW91dF9uZXR3b3JrKGxheW91dF9uYW1lPSdmb3JjZS1kaXJlY3RlZCBkZWZhdWx0U3ByaW5nQ29lZmZpY2llbnQ9MC4wMDAwMSBkZWZhdWx0U3ByaW5nTGVuZ3RoPTUwIGRlZmF1bHROb2RlTWFzcz00JykNCmBgYA0KDQojIFNUUklORyBFbnJpY2htZW50IHsudGFic2V0fQ0KTm93IHRoYXQgd2UgaGF2ZSBhIG1lcmdlZCBuZXR3b3JrLCB3ZSBjYW4gZG8gZW5yaWNobWVudCBhbmFseXNpcyBvbiBpdCwgdXNpbmcgdGhlIFNUUklORyBlbnJpY2htZW50IHRvb2wuDQoNClRoZSBTVFJJTkcgYXBwIGhhcyBidWlsdC1pbiBlbnJpY2htZW50IGFuYWx5c2lzIGZ1bmN0aW9uYWxpdHksIHdoaWNoIGluY2x1ZGVzIGVucmljaG1lbnQgZm9yIEdPIFByb2Nlc3MsIEdPIENvbXBvbmVudCwgR08gRnVuY3Rpb24sIEludGVyUHJvLCBLRUdHIFBhdGh3YXlzLCBhbmQgUEZBTS4NCg0KIyMgUg0KDQpgYGB7cn0NCmNvbW1hbmRzUnVuKCdzdHJpbmcgbWFrZSBzdHJpbmcgbmV0d29yaz0iY3VycmVudCInKQ0KDQpzdHJpbmcuY21kPC1wYXN0ZSgnc3RyaW5nIHJldHJpZXZlIGVucmljaG1lbnQnLCANCiAgICAgICAgICAgICAgICAgICdhbGxOZXRTcGVjaWVzPSJIb21vIHNhcGllbnMiJywNCiAgICAgICAgICAgICAgICAgICdiYWNrZ3JvdW5kPSJnZW5vbWUiJywNCiAgICAgICAgICAgICAgICAgICdzZWxlY3RlZE5vZGVzT25seT0iZmFsc2UiJykNCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMuY29tbWFuZHMuY29tbWFuZHNfcnVuKCdzdHJpbmcgbWFrZSBzdHJpbmcgbmV0d29yaz0iY3VycmVudCInKQ0KDQpzdHJpbmdfY21kX2xpc3QgPSBbJ3N0cmluZyByZXRyaWV2ZSBlbnJpY2htZW50JywnYWxsTmV0U3BlY2llcz0iSG9tbyBzYXBpZW5zIicsICdiYWNrZ3JvdW5kPSJnZW5vbWUiJywnc2VsZWN0ZWROb2Rlc09ubHk9ImZhbHNlIiddDQpzdHJpbmdfY21kID0gIiAiLmpvaW4oc3RyaW5nX2NtZF9saXN0KQ0KcDRjLmNvbW1hbmRzLmNvbW1hbmRzX3J1bihzdHJpbmdfY21kKQ0KYGBgDQoNCiMgey0gLnRhYnNldH0NCg0KVGhlIFNUUklORyBlbnJpY2htZW50IHJlc3VsdHMgZG9uJ3Qgb3BlbiBieSBkZWZhdWx0IGlmIHJ1biBmcm9tIHRoZSBjb21tYW5kIGludGVyZmFjZS4NCg0KIyMgUg0KDQpgYGB7cn0NCmNvbW1hbmRzUnVuKCdzdHJpbmcgc2hvdyBlbnJpY2htZW50JykNCmBgYA0KDQojIyBQeXRob24NCg0KYGBge3B5dGhvbn0NCnA0Yy5jb21tYW5kcy5jb21tYW5kc19ydW4oJ3N0cmluZyBzaG93IGVucmljaG1lbnQnKQ0KYGBgDQoNCiMgey0gLnRhYnNldH0NCg0KVGhlIFNUUklORyBhcHAgaW5jbHVkZXMgc2V2ZXJhbCBvcHRpb25zIGZvciBmaWx0ZXJpbmcgYW5kIGRpc3BsYXlpbmcgdGhlIGVucmljaG1lbnQgcmVzdWx0cy4gV2Ugd2lsbCBmaWx0ZXIgdGhlIHJlc3VsdHMgdG8gb25seSBzaG93IEdPIFByb2Nlc3MuDQoNCiMjIFINCg0KYGBge3J9DQpjb21tYW5kc1J1bihwYXN0ZSgnc3RyaW5nIGZpbHRlciBlbnJpY2htZW50JywgDQogICAgICAgICAgICAgICAgICAnY2F0ZWdvcmllcz0iR08gUHJvY2VzcyInLA0KICAgICAgICAgICAgICAgICAgJ3JlbW92ZU92ZXJsYXBwaW5nPSJ0cnVlIicsDQogICAgICAgICAgICAgICAgICBzZXAgPSAnICcpKQ0KYGBgDQoNCiMjIFB5dGhvbg0KDQpgYGB7cHl0aG9ufQ0KcDRjLmNvbW1hbmRzLmNvbW1hbmRzX3J1bignc3RyaW5nIGZpbHRlciBlbnJpY2htZW50IGNhdGVnb3JpZXM9IkdPIFByb2Nlc3MiIHJlbW92ZU92ZXJsYXBwaW5nPSJ0cnVlIicpDQpgYGANCg0KIyB7LSAudGFic2V0fQ0KDQpOZXh0LCB3ZSB3aWxsIGFkZCBhIHNwbGl0IGRvbnV0IGNoYXJ0Lg0KDQojIyBSDQoNCmBgYHtyfQ0KY29tbWFuZHNSdW4oJ3N0cmluZyBzaG93IGNoYXJ0cycpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMuY29tbWFuZHMuY29tbWFuZHNfcnVuKCdzdHJpbmcgc2hvdyBjaGFydHMnKQ0KYGBgDQoNCiMgVmlzdWFsaXppbmcgUmVzdWx0cyAtIEp1cmthdCBTY29yZSB7LnRhYnNldH0NCldlIGNhbiBjcmVhdGUgYSB2aXN1YWxpemF0aW9uIGJhc2VkIG9uIHRoZSBKdXJrYXQgU2NvcmUsIGFuZCB0aGUgZGlmZmVyZW50IGludGVyYWN0aW9uIHR5cGVzIChBUC1NUyBhbmQgU1RSSU5HKToNCg0KIyMgUg0KDQpgYGB7cn0NCnN0eWxlLm5hbWUgPSAiQVAtTVMgSnVya2F0IFNjb3JlIg0KY3JlYXRlVmlzdWFsU3R5bGUoc3R5bGUubmFtZSkNCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWUpDQpzZXROb2RlQ29sb3JEZWZhdWx0KCIjRkZDQzAwIiwgc3R5bGUubmFtZT1zdHlsZS5uYW1lKQ0Kc2V0Tm9kZUxhYmVsTWFwcGluZygnZGlzcGxheSBuYW1lJywgc3R5bGUubmFtZT1zdHlsZS5uYW1lKQ0Kc2V0RWRnZUNvbG9yTWFwcGluZygiaW50ZXJhY3Rpb24iLCAiQVAtTVMiLCAiIzU1QUE1NSIsICJkIiwgc3R5bGUubmFtZSA9IHN0eWxlLm5hbWUpDQpzZXRFZGdlTGluZVdpZHRoTWFwcGluZygnQVAuTVMuU2NvcmUnLCB0YWJsZS5jb2x1bW4udmFsdWVzPWMoMCwxKSwgd2lkdGhzPWMoMSw1KSwgbWFwcGluZy50eXBlPSJjIiwgc3R5bGUubmFtZT1zdHlsZS5uYW1lKQ0KYGBgDQoNCiMjIFB5dGhvbg0KDQpgYGB7cHl0aG9ufQ0Kc3R5bGVfbmFtZSA9ICJBUC1NUyBKdXJrYXQgU2NvcmUiDQpwNGMuc3R5bGVzLmNyZWF0ZV92aXN1YWxfc3R5bGUoc3R5bGVfbmFtZSkNCnA0Yy5zdHlsZXMuc2V0X3Zpc3VhbF9zdHlsZShzdHlsZV9uYW1lPXN0eWxlX25hbWUpDQpwNGMuc3R5bGVfZGVmYXVsdHMuc2V0X25vZGVfY29sb3JfZGVmYXVsdCgiI0ZGQ0MwMCIsIHN0eWxlX25hbWU9c3R5bGVfbmFtZSkNCnA0Yy5zdHlsZV9tYXBwaW5ncy5zZXRfbm9kZV9sYWJlbF9tYXBwaW5nKCdkaXNwbGF5IG5hbWUnLCBzdHlsZV9uYW1lPXN0eWxlX25hbWUpDQpwNGMuc3R5bGVfbWFwcGluZ3Muc2V0X2VkZ2VfY29sb3JfbWFwcGluZygiaW50ZXJhY3Rpb24iLCBbIkFQLU1TIl0sIFsiIzU1QUE1NSJdLCAiZCIsIHN0eWxlX25hbWU9c3R5bGVfbmFtZSkNCnA0Yy5zdHlsZV9tYXBwaW5ncy5zZXRfZWRnZV9saW5lX3dpZHRoX21hcHBpbmcoJ0FQLU1TIFNjb3JlJywgdGFibGVfY29sdW1uX3ZhbHVlcz1bMCwgMV0sIHdpZHRocz1bMSw1XSwgbWFwcGluZ190eXBlPSJjIiwgc3R5bGVfbmFtZT1zdHlsZV9uYW1lKQ0KYGBgDQoNCiMgey0gLnRhYnNldH0NCg0KIyMgUg0KDQpGaXJzdCwgd2UgZGVmaW5lIHRoZSByYW5nZSBvZiBkYXRhIHZhbHVlcyBmb3IgdGhlIG1hcHBpbmcsIGFuZCB0aGUgQnJld2VyIHBhbGV0dGUgdG8gdXNlOg0KDQpgYGB7cn0NCmRhdGEudmFsdWVzID0gYygwLDAuNSwxKQ0KZGlzcGxheS5icmV3ZXIuYWxsKGxlbmd0aChkYXRhLnZhbHVlcyksIGNvbG9yYmxpbmRGcmllbmRseT1UUlVFLCB0eXBlPSJzZXEiKSAjIGRpdixxdWFsLHNlcSxhbGwNCm5vZGUuY29sb3JzIDwtIGMocmV2KGJyZXdlci5wYWwobGVuZ3RoKGRhdGEudmFsdWVzKSwgIlB1cnBsZXMiKSkpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpkYXRhX3ZhbHVlcyA9IFswLCAwLjUsIDFdDQpub2RlX2NvbG9ycyA9IFsiIzc1NkJCMSIsICIjQkNCRERDIiwgIiNFRkVERjUiXQ0KYGBgDQoNCiMgey0gLnRhYnNldH0NCg0KTmV4dCwgd2UgYXBwbHkgdGhlIG1hcHBpbmcgdG8gdGhlIG5ldHdvcms6DQoNCiMjIFINCg0KYGBge3J9DQpzZXROb2RlQ29sb3JNYXBwaW5nKCdKdXJrYXRTY29yZScsIGRhdGEudmFsdWVzLCBub2RlLmNvbG9ycywgc3R5bGUubmFtZT1zdHlsZS5uYW1lKQ0KYGBgDQoNCiMjIFB5dGhvbg0KDQpgYGB7cHl0aG9ufQ0KcDRjLnN0eWxlX21hcHBpbmdzLnNldF9ub2RlX2NvbG9yX21hcHBpbmcoJ0p1cmthdFNjb3JlJywgZGF0YV92YWx1ZXMsIG5vZGVfY29sb3JzLCBzdHlsZV9uYW1lPXN0eWxlX25hbWUpDQpgYGANCg0KIyB7LSAudGFic2V0fQ0KDQpXZSBub3cgaGF2ZSBhIHZpc3VhbGl6YXRpb24gb2YgdGhlIG5ldHdvcmsgaGlnaGxpZ2h0aW5nIHRoZSBhcC1tcyBleHBlcmltZW50YWwgZGF0YSAoZ3JlZW4gZWRnZXMpLCBhcyB3ZWxsIGFzIGFkZGl0aW9uYWwga25vd24gaW50ZXJhY3Rpb25zIChncmV5IGVkZ2VzKSwgd2l0aCBub2RlIGNvbG9yIGluZGljYXRpbmcgdGhlIEp1cmthdCBTY29yZSBmcm9tIHRoZSBkYXRhLg0KDQojIFZpc3VhbGl6aW5nIFJlc3VsdHMgLSBDb21iaW5lZCB7LnRhYnNldH0NCldlIGNvdWxkIGNyZWF0ZSBhIHNpbWlsYXIga2luZCBvZiBzdHlsZSBmb3IgdGhlIEhFSyBzY29yZSwgYnV0IHRoYXQgb25seSBhbGxvd3MgZm9yIHZpZXdpbmcgZWFjaCBzdHlsZSBzZXBlcmF0ZWx5LiBJbnN0ZWFkLCB3ZSBjYW4gY3JlYXRlIGEgY29tYmluZWQgc3R5bGUsIHVzaW5nIHRoZSBlbmhhbmNlZEdyYXBoaWNzIGFwcC4NCg0KRm9yIHRoaXMsIHdlIHdpbGwgbmVlZCBhIG5ldyBjb2x1bW4gZGVmaW5pbmcgYSBuZXcgYXR0cmlidXRlIHRoYXQgd2lsbCBiZSB1c2VkIGZvciBtYXBwaW5nIHRvIHRoZSBDdXN0b20gR3JhcGhpY3MgcHJvcGVydHkgdmlhIHRoZSBlbmhhbmNlZEdyYXBoaWNzIGFwcC4gVGhpcyBuZXcgYXR0cmlidXRlIGhhcyB0byBiZSBpbiB0aGUgZm9ybSBvZiBtYXBwaW5ncyByZWNvZ25pemVkIGJ5IHRoZSBlbmhhbmNlZEdyYXBoaWNzIGFwcC4NCg0KV2UgY2FuIGNvcHkgdGhlIHByZXZpb3VzIHN0eWxlIHRvIHJldGFpbiBzb21lIG9mIHRoZSBtYXBwaW5ncyB3ZSB3YW50IHRvIGtlZXA6DQoNCiMjIFINCg0KYGBge3J9DQpjb3B5VmlzdWFsU3R5bGUoZnJvbS5zdHlsZT0iQVAtTVMgSnVya2F0IFNjb3JlIiwgdG8uc3R5bGU9IkFQLU1TIENvbWJpbmVkU2NvcmUiKQ0Kc2V0VmlzdWFsU3R5bGUoc3R5bGUubmFtZT0iQVAtTVMgQ29tYmluZWRTY29yZSIpDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMuc3R5bGVzLmNvcHlfdmlzdWFsX3N0eWxlKCJBUC1NUyBKdXJrYXQgU2NvcmUiLCAiQVAtTVMgQ29tYmluZWRTY29yZSIpDQpwNGMuc3R5bGVzLnNldF92aXN1YWxfc3R5bGUoc3R5bGVfbmFtZT0iQVAtTVMgQ29tYmluZWRTY29yZSIpDQpgYGANCg0KIyB7LSAudGFic2V0fQ0KDQpUbyBiZWdpbiBhZGRpbmcgdGhlIG5ldyBjb2x1bW4sIHdlIGZpcnN0IGRlZmluZSBhIGRhdGEgZnJhbWUgd2l0aCB0aGUgbmV3IGF0dHJpYnV0ZSBmb3JtYXR0ZWQgZm9yIGVuaGFuY2VkR3JhcGhpY3M6DQoNCiMjIFINCg0KYGBge3J9DQphbGwubm9kZXM8LWdldEFsbE5vZGVzKCkNCmNvbWJpbmVkLmRmPC1kYXRhLmZyYW1lKGFsbC5ub2RlcywgJ3BpZWNoYXJ0OiBzaG93bGFiZWxzPWZhbHNlIHJhbmdlPSIwLDEiIGFyY3N0YXJ0PTkwIHZhbHVlbGlzdD0iLjUsLjUiIGNvbG9ybGlzdD0idXA6Ymx1ZSx6ZXJvOndoaXRlLGRvd246d2hpdGU7dXA6cHVycGxlLHplcm86d2hpdGUsZG93bjp3aGl0ZSIgYXR0cmlidXRlbGlzdD0iSEVLU2NvcmUsSnVya2F0U2NvcmUiJykNCmNvbG5hbWVzKGNvbWJpbmVkLmRmKTwtYygibmFtZSIsIkNvbWJpbmVkU2NvcmUiKQ0KYGBgDQoNCiMjIFB5dGhvbg0KDQpgYGB7cHl0aG9ufQ0KYWxsX25vZGVzID0gcDRjLm5ldHdvcmtzLmdldF9hbGxfbm9kZXMoKQ0KY29tYmluZWRfZGYgPSBwZC5EYXRhRnJhbWUoYWxsX25vZGVzLCBjb2x1bW5zPVsnbmFtZSddKQ0KY29tYmluZWRfZGZbIkNvbWJpbmVkU2NvcmUiXSA9ICdwaWVjaGFydDogc2hvd2xhYmVscz1mYWxzZSByYW5nZT0iMCwxIiBhcmNzdGFydD05MCB2YWx1ZWxpc3Q9Ii41LC41IiBjb2xvcmxpc3Q9InVwOmJsdWUsemVybzp3aGl0ZSxkb3duOndoaXRlO3VwOnB1cnBsZSx6ZXJvOndoaXRlLGRvd246d2hpdGUiIGF0dHJpYnV0ZWxpc3Q9IkhFS1Njb3JlLEp1cmthdFNjb3JlIicNCmBgYA0KDQojIHstIC50YWJzZXR9DQoNCk5leHQsIHdlIGxvYWQgdGhpcyBkYXRhZnJhbWUgaW50byB0aGUgTm9kZSBUYWJsZSB0byBjcmVhdGUgYW5kIGZpbGwgYSBuZXcgY29sdW1uOg0KDQojIyBSDQoNCmBgYHtyfQ0KbG9hZFRhYmxlRGF0YShjb21iaW5lZC5kZiwgZGF0YS5rZXkuY29sdW1uID0gIm5hbWUiLCB0YWJsZS5rZXkuY29sdW1uID0gIm5hbWUiKQ0KYGBgDQoNCiMjIFB5dGhvbg0KDQpgYGB7cHl0aG9ufQ0KcDRjLnRhYmxlcy5sb2FkX3RhYmxlX2RhdGEoY29tYmluZWRfZGYsIGRhdGFfa2V5X2NvbHVtbj0ibmFtZSIsIHRhYmxlX2tleV9jb2x1bW49J25hbWUnLCkNCmBgYA0KDQojIHstIC50YWJzZXR9DQoNCldlIG5vdyBoYXZlIGEgbmV3IGNvbHVtbiwgKkNvbWJpbmVkU2NvcmUqLCB0aGF0IHdlIGNhbiB1c2UgZm9yIHRoZSBtYXBwaW5nLiBUaGlzIG1hcHBpbmcgZG9lcyBub3QgY29tZSB3aXRoIGEgY3VzdG9tIGhlbHBlciBmdW5jdGlvbiwgc2Ugd2UgYXJlIGdvaW5nIHRvIHVzZSB0d28gYWx0ZXJuYXRpdmUgZnVuY3Rpb25zIHRvIHByZXBhcmUgdGhlIHBhc3N0aHJvdWdoIG1hcHBpbmcgcHJvcGVydHkgYW5kIHRoZW4gdXBkYXRlIG91ciB2aXN1YWwgc3R5bGUgd2l0aCB0aGUgbmV3IG1hcHBpbmc6DQoNCiMjIFINCg0KYGBge3J9DQpwaWVjaGFydC5tYXA8LW1hcFZpc3VhbFByb3BlcnR5KCdub2RlIGN1c3RvbWdyYXBoaWNzIDQnLCdDb21iaW5lZFNjb3JlJywncCcpDQp1cGRhdGVTdHlsZU1hcHBpbmcoJ0FQLU1TIENvbWJpbmVkU2NvcmUnLCBwaWVjaGFydC5tYXApDQpgYGANCg0KIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwaWVjaGFydF9tYXAgPSBwNGMuc3R5bGVfbWFwcGluZ3MubWFwX3Zpc3VhbF9wcm9wZXJ0eSgnbm9kZSBjdXN0b21ncmFwaGljcyA0JywnQ29tYmluZWRTY29yZScsJ3AnKQ0KcDRjLnN0eWxlX21hcHBpbmdzLnVwZGF0ZV9zdHlsZV9tYXBwaW5nKCdBUC1NUyBDb21iaW5lZFNjb3JlJywgcGllY2hhcnRfbWFwKQ0KYGBgDQoNCiMgey0gLnRhYnNldH0NCg0KUmVtZW1iZXIgdGhhdCB3aGVuIHdlIGltcG9ydGVkIG11bHRpcGxlIHZhbHVlcyBmb3IgYSBzaW5nbGUgbm9kZSBhdHRyaWJ1dGUsIHN1Y2ggYXMgdGhlIHNjb3JlcyBmb3IgaHVtYW4gbm9kZXMgaW50ZXJhY3Rpbmcgd2l0aCBtb3JlIHRoYW4gb25lIEhJViBub2RlcywgdGhlIGxhc3QgdmFsdWUgaW1wb3J0ZWQgd2lsbCBvdmVyd3JpdGUgcHJpb3IgdmFsdWVzIGFuZCB0aGUgdmlzdWFsaXphdGlvbiB0aHVzIG9ubHkgc2hvd3MgdGhlIGxhc3QgdmFsdWUuIEZvciA8Yj5FSUYzQTwvYj4sIHdoaWNoIGludGVyYWN0cyB3aXRoIGJvdGggPGI+UFI8L2I+IGFuZCA8Yj5QT0w8L2I+LCBvbmx5IHRoZSBkYXRhIHJlbGV2YW50IHRvIHRoZSA8Yj5QUjwvYj4gaW50ZXJhY3Rpb24gaXMgbWFpbnRhaW5lZCBpbiB0aGUgTm9kZSBUYWJsZSBiZWNhdXNlIG91ciBzb3VyY2UgZGF0YSB3YXMgc29ydGVkIGFscGhhYmV0aWNhbGx5IGJ5IEJhaXQuDQoNCiMgU2F2aW5nLCBzaGFyaW5nIGFuZCBwdWJsaXNoaW5nDQoNCiMjIFNhdmluZyBhIEN5dG9zY2FwZSBzZXNzaW9uIGZpbGUgey50YWJzZXR9DQpTZXNzaW9uIGZpbGVzIHNhdmUgKmV2ZXJ5dGhpbmcqLiBBcyB3aXRoIG1vc3QgcHJvamVjdCBzb2Z0d2FyZSwgd2UgcmVjb21tZW5kIHNhdmluZyBvZnRlbiENCg0KIyMjIFINCg0KYGBge3J9DQpzYXZlU2Vzc2lvbignQVAtTVNfc2Vzc2lvbicpICMuY3lzDQpgYGANCg0KKipOb3RlOioqIElmIHlvdSBkb24ndCBzcGVjaWZ5IGEgY29tcGxldGUgcGF0aCwgdGhlIGZpbGVzIHdpbGwgYmUgc2F2ZWQgcmVsYXRpdmUgdG8geW91ciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5IGluIFIuIA0KDQojIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMuc2Vzc2lvbi5zYXZlX3Nlc3Npb24oJ0FQLU1TX3Nlc3Npb24nKQ0KYGBgDQoNCiMjIFNhdmluZyBoaWdoIHJlc29sdXRpb24gaW1hZ2UgZmlsZXMgey50YWJzZXR9DQoNCllvdSBjYW4gZXhwb3J0IGV4dHJlbWVseSBoaWdoIHJlc29sdXRpb24gaW1hZ2VzLCBpbmNsdWRpbmcgdmVjdG9yIGdyYXBoaWMgZm9ybWF0cy4NCg0KIyMjIFINCg0KYGBge3J9DQpleHBvcnRJbWFnZSgnQVAtTVNfaW1hZ2UnLCB0eXBlID0gJ1BERicpICMucGRmDQo/ZXhwb3J0SW1hZ2UNCmBgYA0KDQojIyMgUHl0aG9uDQoNCmBgYHtweXRob259DQpwNGMubmV0d29ya192aWV3cy5leHBvcnRfaW1hZ2UoJ0FQLU1TX2ltYWdlJywgdHlwZSA9ICdQREYnKQ0KYGBgDQo=