This is a project to practice getting and cleaning data in R and Python!
There are three notebooks for this project in the table of contents.

The goal behind this project is to collect data on genome sizes from various organisms on NCBI, generating a table that can be used to analyze which genome sizes (or mitchondrial genome sizes) are the largest (Part 1). I also decided to write some python scripts (Part 2) using the BioEntrez package to query Entrez on NCBI and collect taxonomy information for each organism to add to the table. Finally, I generated a local SQLite database to hold the BioEntrez taxonomy data, and then queried the database in R to add the new data to the dataframe (Part 3).

Part 1: Getting and cleaning data from genome assemblies statistics files

Setup

library(dplyr)
library(ggplot2)

Downloaded multiple text files containing the genome sizes of multiple organisms. The goal is to extract just the organism names, total genome length, and maybe mitochondrial genome length just for fun!

Here’s what the files look like:
Each file has an upper section that contains organism information such as scientific name and common name….

And a lower section containing genome size data:

The organism name and genome length data (we want the "Primary Assembly, total-length and ungapped-length lines) are in seperate parts of the file. We’ll need to extract and then combine them to be able to compare between organisms.

The first thing I tried was to get a table with the genome size with just one of the txt files.

assembliesR<-read.delim("~/ncbi-genomes-2019-05-19/GCF_900747795.1_fErpCal1.1_assembly_stats.txt",sep="",header=F,fill=TRUE,comment.char="#")
print(assembliesR[1:15,]) 

Ok, we have 6 columns containing the data we need, but the organism name isn’t listed because it’s # commented in the txt file.

After some testing, I decided to extract the genome size data and the organism name from the text files with two separate scripts…

Section 1: Organism Names

First, the names:

fdir<-list.files("~/ncbi-genomes-2019-05-19", 
                 pattern=".txt",full.names=T)
#450 files total
fdir<-(c(fdir[1:448])) #remove 449 and 450, empty files

Now I have a list of all the text files.

#Read the txt files, including # header containing names
assem.n<-lapply(fdir, function(i) {
  read.delim(i,
      sep=" ",
      header=F,
      fill=T,
      comment.char="")})
#Get name based on row locations.... !doesn't work for all files
get_name<- function(x){
  x[3,4:6]
}
#apply function to all files
N<- lapply(assem.n, get_name)

This extracts the names for some of the files, but not all…

print(N[4:12])

Need to find a way to extract based on the string preceding the organism name…

(Starting over)

assem.n<-lapply(fdir, function(i) {
  read.delim(i,
             sep=" ",
             header=F,
             fill=T,
             comment.char="") })

top_only<- function(x) '[' (x[1:20,])
assem.n<-lapply(assem.n,top_only)
cut_columns<- function(x) x[,2:7]
assem.n<-lapply(assem.n,cut_columns)

get_name<- function (x) '[' (x[which(x[,]=="Organism"),])
nn<-lapply(assem.n,get_name)

This works a lot better! Now nn can be converted to a data frame of organism names:


paste_species<- function(name_) {
  paste(name_$V5, name_$V6, sep=" ")
}
lnames<- lapply(nn,paste_species)
dfnames <- data.frame(matrix(unlist(lnames), nrow=length(lnames),
            byrow=T),stringsAsFactors=FALSE)
colnames(dfnames)<-"Organism"
head(dfnames)

It would be better to assign an ID to each organism name, so it can be linked to the data later…

lnames<- lapply(nn,paste_species)

lna<-rbind.data.frame((cbind(lnames)), make.row.names = F)
print(head(lna))
colnames(lna)<-"Organism"
lna2<-cbind("ID"=rownames(lna), lna)
#print(head(lna2))
#printing in the chunk doesn't seem to work here... this code works in console

#Check if ID column value matches the organism's original position
#in the list of assemblies files:
print(lna[448,])
print(nn[[448]])
print(assem.n[[448]])

Section 2: Genome data

This is a bit easier, since I can use the same function but exclude all of the commented text at the top, leaving just the data table at the bottom. Here’s the example again:

example<-read.delim("~/ncbi-genomes-2019-05-19/GCF_900747795.1_fErpCal1.1_assembly_stats.txt", sep="\t", col.names=colnames, comment.char="#")
print(example[1:15,])

Reading the data into R the same way as the names script, this time using comment character ‘#’ and assigning column names.

colnames<-c("unit_name","mol_name","mol_type","seq_type","stat","value")

assem.data<-lapply(fdir, function(i){
    read.delim(i,
      sep="\t",
      header=F,
      fill=T,
      comment.char="#",
      col.names=colnames)})

print(assem.data[[3]][1:10,])

Now, test extracting just the “Primary Assembly” rows with mol_name = all. Just use assem.data[[3]] to test.

subset(assem.data[[3]], unit_name=="Primary Assembly" & mol_name=="all")

Now, for file 3, we’re down from 452 rows to 18 rows. Narrowing it down further…

subset(assem.data[[3]], unit_name=="Primary Assembly" & mol_name=="all" & mol_type=="all" &seq_type=="all")

This narrows it down from 18 rows to 6 rows… And now that “total-length” is unique in this vector, we can extract just that row. Same for “ungapped-length”. I’ll store these to variables.

total<-subset(assem.data[[3]], unit_name=="Primary Assembly" & mol_name=="all" & mol_type=="all" &seq_type=="all" & stat=="total-length")
ungapped<-subset(assem.data[[3]], unit_name=="Primary Assembly" & mol_name=="all" & mol_type=="all" &seq_type=="all" & stat=="ungapped-length")

(Starting over) Extract data based on column names:

get_total_length_allcols<- function (x) '[' (x[x$unit_name %in% "Primary Assembly"
                                       &x$mol_name %in% "all"
                                       &x$mol_type %in% "all"
                                       &x$seq_type %in% "all"
                                       &x$stat %in% "total-length" ,] )
get_ungapped_length_allcols<- function (x) '[' (x[x$unit_name %in% "Primary Assembly"
                                          &x$mol_name %in% "all"
                                          &x$mol_type %in% "all"
                                          &x$seq_type %in% "all"
                                          &x$stat %in% "ungapped-length" ,] )

get_mito_allcols<- function (x) '[' (x[x$unit_name %in% "non-nuclear"
                               &x$mol_name %in% "MT"
                               &x$mol_type %in% "Mitochondrion"
                               &x$seq_type %in% "all"
                               &x$stat %in% "total-length" ,] )

total_all<-lapply(assem.data,get_total_length_allcols)
ungapped_all<-lapply(assem.data,get_ungapped_length_allcols)
mito_all<-lapply(assem.data,get_mito_allcols)

Change all of the above functions to get only the “value” column (keep the functions in the previous chunk for testing)

get_total_length_val<- function (x) '[' (x[x$unit_name %in% "Primary Assembly"
                          &x$mol_name %in% "all"
                          &x$mol_type %in% "all"
                          &x$seq_type %in% "all"
                          &x$stat %in% "total-length" ,"value"] )

get_ungapped_length_val<- function (x) '[' (x[x$unit_name %in% "Primary Assembly"
                                   &x$mol_name %in% "all"
                                   &x$mol_type %in% "all"
                                   &x$seq_type %in% "all"
                                   &x$stat %in% "ungapped-length" ,"value"] )

get_mito_val<- function (x) '[' (x[x$unit_name %in% "non-nuclear"
                                      &x$mol_name %in% "MT"
                                      &x$mol_type %in% "Mitochondrion"
                                      &x$seq_type %in% "all"
                                      &x$stat %in% "total-length" ,"value"] )

total<-lapply(assem.data,get_total_length_val)
ungapped<-lapply(assem.data,get_ungapped_length_val)
mito<-lapply(assem.data,get_mito_val)

Bind total, ungapped and mito into lists

head(cbind(total))

Bind each new list again:

head(cbind(cbind(total),cbind(ungapped),cbind(mito)))

Can do this all in one step:

assem.data_vals<- rbind.data.frame(cbind(cbind(total),cbind(ungapped),cbind(mito)))
colnames(assem.data_vals)<-c("total","ungapped","mito")
head(assem.data_vals)
#printing chunk doesn't work, code works in console

head(df) total ungapped mito 1 143706478 142553500 19524 2 16299 3 2870167880 2729969191 16313 4 3196721236 3118525743 16866 5 1373454788 1368765506 16596 6 927296314 737783370 14117

Row 2 is missing values? Row 2 organism is Mus musculus In the raw data, not compatible with this line in the functions above: get_total_length_val<- function (x) ‘[’ (x[x$unit_name %in% “Primary Assembly” In this file, “Primary Assembly” is written as “Primary Assembly (C57BL/6J)”, which is not considered a match by %in%….

Try writing a new operator for partial matches?

#examples
#"%in%" <- function(x, table) match(x, table, nomatch = 0) > 0
#example `%allin%` <- function(x,table) {all(match(x,table,nomatch = 0L) > 0L)}
#str(startsWith) is function (x,prefix) 
`%starts%` <- function(x, string) {startsWith(as.character(x), string)}

Replace this operator into the functions for getting total-length, etc.

get_total_length_val<- function (x) '[' (x[x$unit_name %starts% "Primary Assembly"
                          &x$mol_name %in% "all"
                          &x$mol_type %in% "all"
                          &x$seq_type %in% "all"
                          &x$stat %in% "total-length" ,"value"] )

get_ungapped_length_val<- function (x) '[' (x[x$unit_name %starts% "Primary Assembly"
                                   &x$mol_name %in% "all"
                                   &x$mol_type %in% "all"
                                   &x$seq_type %in% "all"
                                   &x$stat %in% "ungapped-length" ,"value"] )
get_mito_val<- function (x) '[' (x[x$unit_name %in% "non-nuclear"
                                      &x$mol_name %in% "MT"
                                      &x$mol_type %in% "Mitochondrion"
                                      &x$seq_type %in% "all"
                                      &x$stat %in% "total-length" ,"value"] )

total<-lapply(assem.data,get_total_length_val)
ungapped<-lapply(assem.data,get_ungapped_length_val)
mito<-lapply(assem.data,get_mito_val)

assem.data_vals<- rbind.data.frame(cbind(cbind(total),cbind(ungapped),cbind(mito)))
colnames(assem.data_vals)<-c("total","ungapped","mito")

It works!!

head(df) total ungapped mito 1 143706478 142553500 19524 2 2730855475 2652767259 16299 3 2870167880 2729969191 16313 4 3196721236 3118525743 16866 5 1373454788 1368765506 16596 6 927296314 737783370 14117

Now cbind an ID value for each row:

assem.data_vals<-cbind("ID"=rownames(assem.data_vals), assem.data_vals)

Section 3: Combine names and data

Finally, combining organism names with their respective data:

assemblies<-cbind(lna,assem.data_vals)

Export data frame to csv

Write the Organism column to csv so we can use it to query in python: Extract just the Organism column into a new data frame:

assemblies.orgs<-data.frame("Organism"=cbind(assemblies[,"Organism"]))

Write that column to csv:

assemblies.orgs<-apply(assemblies.orgs,2,as.character)
write.csv(assemblies.orgs, "assembliesorgs.csv")

Part 2: Making a Python SQLite database

The next thing I decided to do was get the taxonomy information from each organism to add to the table. To do this, I used the BioEntrez package for Python. This enables queries from Entrez. (I’m using anaconda with Spyder)

Fetching organism xml files from Entrez

(read_csv.py)
Setup:

from Bio import Entrez
Entrez.email = ' '
import csv
import lxml
import pprint
from lxml import etree
import time
import numpy as np
import traceback

Get a list of organism names from the csv file made in Part 1:


csvfile=(r'\assembliesorgs.csv')

ids=[]
orglist=[]
with open(csvfile,'rt') as f:
  data = csv.reader(f)
  for row in data:
      ids.append(row[0])
      orglist.append(row[1])

Fetch xml files with taxonomy results from orglist, store in orgs

orgs=[]
def fetchorgs(x):
    try:
        for idx,org in enumerate(x):
            query= Entrez.esearch(db="taxonomy",term= org, retmax=1)
            result=Entrez.read(query)
            handle=Entrez.efetch(db="taxonomy",id=result['IdList'])
            result=handle.read()
            orgs.append(result)
            if idx % 10==0:
                print(len(orgs),"records fetched")
    except Exception as e:
        print(e,idx,org)

#DO NOT FETCH MORE THAN 100 AT A TIME
#DO THIS ON WEEKENDS OR US DOWNTIMES
fetchorgs(orglist[100:101]) #Total number was 450, I modified this line to 0:100, 100:200, 200:300 etc to avoid too many requests at once.

Check if any records from orglist are missing in orgs:

for c,i in enumerate(orgs):
    try:
        i=bytes(i,encoding='utf8')
        A=etree.fromstring(i).find('Taxon/ScientificName').text
        if A not in orglist:
            print(c,A)
    except Exception as e:
        print(c,e)

Extracting taxonomy data from xml files

(organism_data.py) Setup:

from Bio import Entrez
Entrez.email = 'mcglynnkell@gmail.com'
import csv
import lxml
import pprint
from lxml import etree
import pandas as pd
import traceback

Using lxml and etree to extract data from the xml files. There are 10 values to get for each organism name: TaxId, ScientificName, CommonName, Division, Kingdom, Phylum, Class, Order, Family and Genus.

#Set up empty lists
TaxId_list=[]
ScientificName_list=[]
CommonName_list=[]
Division_list=[]
Kingdom_list=[]
Phylum_list=[]
Class_list=[]
Order_list=[]
Family_list=[]
Genus_list=[]

#Function to extract data from orgsfetched (a list that was generated
#from the fetchorgs function)
def extract_data(orgsfetched):
    for idx, i in enumerate(orgsfetched):
        if len(i)>260:    #excludes results that failed to fetch
            i=bytes(i,encoding='utf8')
            TaxId=etree.fromstring(i).find('Taxon/TaxId')
                    
            ScientificName= etree.fromstring(i).find('Taxon/ScientificName')
                       
            CommonName=etree.fromstring(i).find('Taxon/OtherNames/GenbankCommonName')
                 
            Division=etree.fromstring(i).find('Taxon/Division')  
                    
            Kingdom=etree.fromstring(i).xpath(
                    '//Rank[text()="superkingdom"]/preceding-sibling::ScientificName/text()')
            
            Phylum=etree.fromstring(i).xpath(
                    '//Rank[text()="phylum"]/preceding-sibling::ScientificName/text()')
                
            Class=etree.fromstring(i).xpath(
                    '//Rank[text()="class"]/preceding-sibling::ScientificName/text()')
                    
            Order=etree.fromstring(i).xpath(
                    '//Rank[text()="order"]/preceding-sibling::ScientificName/text()')
                    
            Family=etree.fromstring(i).xpath(
                    '//Rank[text()="family"]/preceding-sibling::ScientificName/text()')
                    
            Genus=etree.fromstring(i).xpath(
                    '//Rank[text()="genus"]/preceding-sibling::ScientificName/text()')
                        
            data1=[TaxId,ScientificName,CommonName,Division,
                   Kingdom,Phylum,Class,Order,Family,Genus]
            for idxx,d in enumerate(data1):
                if d is None:
                   data1[idxx]=etree.Element("empty")
                   data1[idxx].text="NULL"  
                elif len(d)==0:
                    if not hasattr(d,'text'):
                        data1[idxx]=["NULL"]
                
            try:         
                TaxId_list.append(data1[0].text)
                ScientificName_list.append(data1[1].text)
                CommonName_list.append(data1[2].text)
                Division_list.append(data1[3].text)
                Kingdom_list.append(data1[4][0])
                Phylum_list.append(data1[5][0])
                Class_list.append(data1[6][0]) 
                Order_list.append(data1[7][0])
                Family_list.append(data1[8][0])
                Genus_list.append(data1[9][0])
            except AttributeError as Att:
                print(idx, "NoneType",Att, traceback.format_exc())
            except IndexError as Ind:
                print(idx,"index",Ind, traceback.format_exc())

Make a data frame from the extracted taxonomy data:

org_dataframe = pd.DataFrame(
        {'TaxId' : TaxId_list,
         'ScientificName' : ScientificName_list, 
         'CommonName' : CommonName_list,
         'Division' : Division_list,                     
         'Kingdom' : Kingdom_list,
         'Phylum' : Phylum_list,
         'Class' : Class_list,
         'Order' : Order_list,
         'Family': Family_list,
         'Genus' : Genus_list})
    

Add taxonomy data to an SQLite database

(database.py)
After putting this data frame into a database, I can then query the database back in R. (And this way, I learn about SQLite databases!)

Create a local database with sqlite:

import sqlite3, sqlalchemy
import pandas as pd

from sqlalchemy import create_engine
engine= create_engine('sqlite:/// <PATH> \\Organismdb.db',
                      echo=True)

from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()

from sqlalchemy import Table, Column, Integer, String
class Organism(Base):
    __tablename__ = 'Organisms'
    
    TaxId= Column(Integer, index = True, primary_key = True)
    Scientific_Name = Column(String)
    Common_Name= Column(String)
    Division= Column(String)
    Kingdom= Column(String)
    Phylum= Column(String)
    Class= Column(String)
    Order= Column(String)
    Family= Column(String)
    Genus= Column(String)
    
    def __init__(self, TaxId,ScientificName,CommonName,Division,
               Kingdom,Phylum,Class,Order,Family,Genus, **kw):
        self.TaxId = TaxId
        self.ScientificName = ScientificName
        self.CommonName = CommonName
        self.Division = Division
        self.Kingdom = Kingdom
        self.Phylum = Phylum
        self.Class = Class
        self.Order = Order
        self.Family = Family
        self.Genus = Genus
           
Base.metadata.create_all(engine)

Organisms.__table__ 

#Notes on the commands needed to add to the database:   
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()

session.commit()
session.close()

(input_dbdata.py)

from sqlalchemy import create_engine
engine= create_engine('sqlite:/// <PATH> \\Organismdb.db',
                      echo=True)
conn = sqlite3.connect('Organismdb.db')
cursor = conn.cursor()

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()




for col in org_dataframe.columns: 
    print(col) 

#Input all data in pandas dataframe into table 'Organisms'
org_dataframe.to_sql('Organisms', con=engine, if_exists='replace')

session.commit()
session.close()

The database is all set up!

Back to R!

Part 3: Querying the Organisms.db SQLite database in R

The last step is to query the data from Organisms.db in R and append the new data to the assemblies R dataframe.

Setup:

library(RSQLite)
library(dbplyr)
library(dplyr)
library(DBI)

##Connect to SQLite database in R

con <- DBI::dbConnect(RSQLite::SQLite(), dbname = "<PATH> \\Organismdb.db")

Check if the connection worked:

attributes(con)
dbListTables(con)
dbExistsTable(con,"Organisms")

dbListFields(con,"Organisms")

View table!

tbl(con,"Organisms")

Write a function to query the database from the assemblies dataframe:

query<- function(nx) {
x<-dbSendQuery(con, 
               'SELECT * 
             FROM Organisms WHERE ScientificName == ?')
dbBind(x,list(nx))
z<-(dbFetch(x,n=1)) #n=1 specifies results to return, doesn't work as well if multiple results are fetched
dbClearResult(x) #VERY important! VERY!
return(z)
dbClearResult(x) #VERY important! VERY!
}

Query append new columns from SQLite database

db_results<-data.frame()  #make a new data.frame

for (i in assemblies[,"Organism"]) {
  x<-query(i)
  if (nrow(x)==0) {
    x[nrow(x)+1,]<- c(rep("NULL",11))}
  db_results<-rbind(db_results,x)}

#Bind new columns to assemblies dataframe
assemblies_complete<-cbind(assemblies,db_results)

If there are any entries missing in db_results, db_results won’t cbind to assemblies(differing numbers of rows). Double check:

which(assemblies[,"Organism"] %starts% "Drosophila melanogaster")

Check which rows are missing:

'%!in%' <- function(x,y)!('%in%'(x,y))
which(assemblies[,"Organism"] %!in% db_results[,"ScientificName"])

which(db_results[,"ScientificName"] %!in% assemblies[,"Organism"])

Verify that Organism matches Scientific Name in all rows

which(assemblies_complete[,"Organism"] != assemblies_complete[,"ScientificName"])

Rows 100,200 and 400 weren’t in the database! Print out the missing rows so I can go back to the Python script and add the missing data to the db.

assemblies_complete[c(100,200,400),]

I then went back to the python scripts and added the missing values from rows 100, 200 and 400, running these final R scripts again afterwards.

All done!

Now this dataset can be used to do some interesting analysis and visualizations, such as comparing genome size and mitochondrial genome size among the organisms in this dataset, and relationships between genome size and a particular taxonomic class.

LS0tDQp0aXRsZTogIk5DQkkgR2Vub21lIEFzc2VtYmxpZXMgUHJvamVjdCAtIEdldHRpbmcgYW5kIENsZWFuaW5nIERhdGEgaW4gUiBhbmQgUHl0aG9uIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogDQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQ0KLS0tDQoNClRoaXMgaXMgYSBwcm9qZWN0IHRvIHByYWN0aWNlIGdldHRpbmcgYW5kIGNsZWFuaW5nIGRhdGEgaW4gUiBhbmQgUHl0aG9uISAgDQpUaGVyZSBhcmUgdGhyZWUgbm90ZWJvb2tzIGZvciB0aGlzIHByb2plY3QgaW4gdGhlIHRhYmxlIG9mIGNvbnRlbnRzLg0KDQpUaGUgZ29hbCBiZWhpbmQgdGhpcyBwcm9qZWN0IGlzIHRvIGNvbGxlY3QgZGF0YSBvbiBnZW5vbWUgc2l6ZXMgZnJvbSB2YXJpb3VzIG9yZ2FuaXNtcyBvbiBOQ0JJLCBnZW5lcmF0aW5nIGEgdGFibGUgdGhhdCBjYW4gYmUgdXNlZCB0byBhbmFseXplIHdoaWNoIGdlbm9tZSBzaXplcyAob3IgbWl0Y2hvbmRyaWFsIGdlbm9tZSBzaXplcykgYXJlIHRoZSBsYXJnZXN0IChQYXJ0IDEpLiAgSSBhbHNvIGRlY2lkZWQgdG8gd3JpdGUgc29tZSBweXRob24gc2NyaXB0cyAoUGFydCAyKSB1c2luZyB0aGUgQmlvRW50cmV6IHBhY2thZ2UgdG8gcXVlcnkgRW50cmV6IG9uIE5DQkkgYW5kIGNvbGxlY3QgdGF4b25vbXkgaW5mb3JtYXRpb24gZm9yIGVhY2ggb3JnYW5pc20gdG8gYWRkIHRvIHRoZSB0YWJsZS4gIEZpbmFsbHksIEkgZ2VuZXJhdGVkIGEgbG9jYWwgU1FMaXRlIGRhdGFiYXNlIHRvIGhvbGQgdGhlIEJpb0VudHJleiB0YXhvbm9teSBkYXRhLCBhbmQgdGhlbiBxdWVyaWVkIHRoZSBkYXRhYmFzZSBpbiBSIHRvIGFkZCB0aGUgbmV3IGRhdGEgdG8gdGhlIGRhdGFmcmFtZSAoUGFydCAzKS4NCg0KIyBQYXJ0IDE6IEdldHRpbmcgYW5kIGNsZWFuaW5nIGRhdGEgZnJvbSBnZW5vbWUgYXNzZW1ibGllcyBzdGF0aXN0aWNzIGZpbGVzDQoNClNldHVwDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KRG93bmxvYWRlZCBtdWx0aXBsZSB0ZXh0IGZpbGVzIGNvbnRhaW5pbmcgdGhlIGdlbm9tZSBzaXplcyBvZiBtdWx0aXBsZSBvcmdhbmlzbXMuICBUaGUgZ29hbCBpcyB0byBleHRyYWN0IGp1c3QgdGhlIG9yZ2FuaXNtIG5hbWVzLCB0b3RhbCBnZW5vbWUgbGVuZ3RoLCBhbmQgbWF5YmUgbWl0b2Nob25kcmlhbCBnZW5vbWUgbGVuZ3RoIGp1c3QgZm9yIGZ1biENCg0KSGVyZSdzIHdoYXQgdGhlIGZpbGVzIGxvb2sgbGlrZTogICAgICANCkVhY2ggZmlsZSBoYXMgYW4gdXBwZXIgc2VjdGlvbiB0aGF0IGNvbnRhaW5zIG9yZ2FuaXNtIGluZm9ybWF0aW9uIHN1Y2ggYXMgc2NpZW50aWZpYyBuYW1lIGFuZCBjb21tb24gbmFtZS4uLi4gICAgICAgDQogICAgICANCiAgICAgIA0KICAgICAgDQogICAgICANCiFbXShHQ0ZfOS0xLnBuZykgICAgIA0KDQoNCg0KQW5kIGEgbG93ZXIgc2VjdGlvbiBjb250YWluaW5nIGdlbm9tZSBzaXplIGRhdGE6ICAgICAgIA0KICAgICAgIA0KICAgICAgIA0KICAgICAgIA0KICAgICAgIA0KIVtdKEdDRl85LTIucG5nKSAgICAgIA0KDQoNCg0KVGhlIG9yZ2FuaXNtIG5hbWUgYW5kIGdlbm9tZSBsZW5ndGggZGF0YSAod2Ugd2FudCB0aGUgIlByaW1hcnkgQXNzZW1ibHksIHRvdGFsLWxlbmd0aCBhbmQgdW5nYXBwZWQtbGVuZ3RoIGxpbmVzKSBhcmUgaW4gc2VwZXJhdGUgcGFydHMgb2YgdGhlIGZpbGUuICBXZSdsbCBuZWVkIHRvIGV4dHJhY3QgYW5kIHRoZW4gY29tYmluZSB0aGVtIHRvIGJlIGFibGUgdG8gY29tcGFyZSBiZXR3ZWVuIG9yZ2FuaXNtcy4NCg0KDQpUaGUgZmlyc3QgdGhpbmcgSSB0cmllZCB3YXMgdG8gZ2V0IGEgdGFibGUgd2l0aCB0aGUgZ2Vub21lIHNpemUgd2l0aCBqdXN0IG9uZSBvZiB0aGUgdHh0IGZpbGVzLg0KYGBge3J9DQphc3NlbWJsaWVzUjwtcmVhZC5kZWxpbSgifi9uY2JpLWdlbm9tZXMtMjAxOS0wNS0xOS9HQ0ZfOTAwNzQ3Nzk1LjFfZkVycENhbDEuMV9hc3NlbWJseV9zdGF0cy50eHQiLHNlcD0iIixoZWFkZXI9RixmaWxsPVRSVUUsY29tbWVudC5jaGFyPSIjIikNCnByaW50KGFzc2VtYmxpZXNSWzE6MTUsXSkgDQpgYGANCg0KT2ssIHdlIGhhdmUgNiBjb2x1bW5zIGNvbnRhaW5pbmcgdGhlIGRhdGEgd2UgbmVlZCwgYnV0IHRoZSBvcmdhbmlzbSBuYW1lIGlzbid0IGxpc3RlZCBiZWNhdXNlIGl0J3MgIyBjb21tZW50ZWQgaW4gdGhlIHR4dCBmaWxlLg0KDQpBZnRlciBzb21lIHRlc3RpbmcsIEkgZGVjaWRlZCB0byBleHRyYWN0IHRoZSBnZW5vbWUgc2l6ZSBkYXRhIGFuZCB0aGUgb3JnYW5pc20gbmFtZSBmcm9tIHRoZSB0ZXh0IGZpbGVzIHdpdGggdHdvIHNlcGFyYXRlIHNjcmlwdHMuLi4NCg0KIyMgU2VjdGlvbiAxOiBPcmdhbmlzbSBOYW1lcw0KDQpGaXJzdCwgdGhlIG5hbWVzOg0KYGBge3J9DQpmZGlyPC1saXN0LmZpbGVzKCJ+L25jYmktZ2Vub21lcy0yMDE5LTA1LTE5IiwgDQogICAgICAgICAgICAgICAgIHBhdHRlcm49Ii50eHQiLGZ1bGwubmFtZXM9VCkNCiM0NTAgZmlsZXMgdG90YWwNCmZkaXI8LShjKGZkaXJbMTo0NDhdKSkgI3JlbW92ZSA0NDkgYW5kIDQ1MCwgZW1wdHkgZmlsZXMNCmBgYA0KTm93IEkgaGF2ZSBhIGxpc3Qgb2YgYWxsIHRoZSB0ZXh0IGZpbGVzLg0KDQpgYGB7cn0NCiNSZWFkIHRoZSB0eHQgZmlsZXMsIGluY2x1ZGluZyAjIGhlYWRlciBjb250YWluaW5nIG5hbWVzDQphc3NlbS5uPC1sYXBwbHkoZmRpciwgZnVuY3Rpb24oaSkgew0KICByZWFkLmRlbGltKGksDQogICAgICBzZXA9IiAiLA0KICAgICAgaGVhZGVyPUYsDQogICAgICBmaWxsPVQsDQogICAgICBjb21tZW50LmNoYXI9IiIpfSkNCmBgYA0KDQpgYGB7cn0NCiNHZXQgbmFtZSBiYXNlZCBvbiByb3cgbG9jYXRpb25zLi4uLiAhZG9lc24ndCB3b3JrIGZvciBhbGwgZmlsZXMNCmdldF9uYW1lPC0gZnVuY3Rpb24oeCl7DQogIHhbMyw0OjZdDQp9DQojYXBwbHkgZnVuY3Rpb24gdG8gYWxsIGZpbGVzDQpOPC0gbGFwcGx5KGFzc2VtLm4sIGdldF9uYW1lKQ0KDQpgYGANClRoaXMgZXh0cmFjdHMgdGhlIG5hbWVzIGZvciBzb21lIG9mIHRoZSBmaWxlcywgYnV0IG5vdCBhbGwuLi4NCmBgYHtyfQ0KcHJpbnQoTls0OjEyXSkNCmBgYA0KDQpOZWVkIHRvIGZpbmQgYSB3YXkgdG8gZXh0cmFjdCBiYXNlZCBvbiB0aGUgc3RyaW5nIHByZWNlZGluZyB0aGUgb3JnYW5pc20gbmFtZS4uLg0KDQooU3RhcnRpbmcgb3ZlcikNCmBgYHtyfQ0KYXNzZW0ubjwtbGFwcGx5KGZkaXIsIGZ1bmN0aW9uKGkpIHsNCiAgcmVhZC5kZWxpbShpLA0KICAgICAgICAgICAgIHNlcD0iICIsDQogICAgICAgICAgICAgaGVhZGVyPUYsDQogICAgICAgICAgICAgZmlsbD1ULA0KICAgICAgICAgICAgIGNvbW1lbnQuY2hhcj0iIikgfSkNCg0KdG9wX29ubHk8LSBmdW5jdGlvbih4KSAnWycgKHhbMToyMCxdKQ0KYXNzZW0ubjwtbGFwcGx5KGFzc2VtLm4sdG9wX29ubHkpDQpjdXRfY29sdW1uczwtIGZ1bmN0aW9uKHgpIHhbLDI6N10NCmFzc2VtLm48LWxhcHBseShhc3NlbS5uLGN1dF9jb2x1bW5zKQ0KDQpnZXRfbmFtZTwtIGZ1bmN0aW9uICh4KSAnWycgKHhbd2hpY2goeFssXT09Ik9yZ2FuaXNtIiksXSkNCm5uPC1sYXBwbHkoYXNzZW0ubixnZXRfbmFtZSkNCmBgYA0KVGhpcyB3b3JrcyBhIGxvdCBiZXR0ZXIhDQpOb3cgbm4gY2FuIGJlIGNvbnZlcnRlZCB0byBhIGRhdGEgZnJhbWUgb2Ygb3JnYW5pc20gbmFtZXM6DQpgYGB7cn0NCg0KcGFzdGVfc3BlY2llczwtIGZ1bmN0aW9uKG5hbWVfKSB7DQogIHBhc3RlKG5hbWVfJFY1LCBuYW1lXyRWNiwgc2VwPSIgIikNCn0NCmxuYW1lczwtIGxhcHBseShubixwYXN0ZV9zcGVjaWVzKQ0KZGZuYW1lcyA8LSBkYXRhLmZyYW1lKG1hdHJpeCh1bmxpc3QobG5hbWVzKSwgbnJvdz1sZW5ndGgobG5hbWVzKSwNCiAgICAgICAgICAgIGJ5cm93PVQpLHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpjb2xuYW1lcyhkZm5hbWVzKTwtIk9yZ2FuaXNtIg0KaGVhZChkZm5hbWVzKQ0KYGBgDQpJdCB3b3VsZCBiZSBiZXR0ZXIgdG8gYXNzaWduIGFuIElEIHRvIGVhY2ggb3JnYW5pc20gbmFtZSwgc28gaXQgY2FuIGJlIGxpbmtlZCB0byB0aGUgZGF0YSBsYXRlci4uLg0KDQpgYGB7cn0NCmxuYW1lczwtIGxhcHBseShubixwYXN0ZV9zcGVjaWVzKQ0KDQpsbmE8LXJiaW5kLmRhdGEuZnJhbWUoKGNiaW5kKGxuYW1lcykpLCBtYWtlLnJvdy5uYW1lcyA9IEYpDQpwcmludChoZWFkKGxuYSkpDQpjb2xuYW1lcyhsbmEpPC0iT3JnYW5pc20iDQpsbmEyPC1jYmluZCgiSUQiPXJvd25hbWVzKGxuYSksIGxuYSkNCiNwcmludChoZWFkKGxuYTIpKQ0KI3ByaW50aW5nIGluIHRoZSBjaHVuayBkb2Vzbid0IHNlZW0gdG8gd29yayBoZXJlLi4uIHRoaXMgY29kZSB3b3JrcyBpbiBjb25zb2xlDQoNCiNDaGVjayBpZiBJRCBjb2x1bW4gdmFsdWUgbWF0Y2hlcyB0aGUgb3JnYW5pc20ncyBvcmlnaW5hbCBwb3NpdGlvbg0KI2luIHRoZSBsaXN0IG9mIGFzc2VtYmxpZXMgZmlsZXM6DQpwcmludChsbmFbNDQ4LF0pDQpwcmludChubltbNDQ4XV0pDQpwcmludChhc3NlbS5uW1s0NDhdXSkNCg0KYGBgDQoNCg0KDQojIyBTZWN0aW9uIDI6IEdlbm9tZSBkYXRhDQoNClRoaXMgaXMgYSBiaXQgZWFzaWVyLCBzaW5jZSBJIGNhbiB1c2UgdGhlIHNhbWUgZnVuY3Rpb24gYnV0IGV4Y2x1ZGUgYWxsIG9mIHRoZSBjb21tZW50ZWQgdGV4dCBhdCB0aGUgdG9wLCBsZWF2aW5nIGp1c3QgdGhlIGRhdGEgdGFibGUgYXQgdGhlIGJvdHRvbS4NCkhlcmUncyB0aGUgZXhhbXBsZSBhZ2FpbjoNCmBgYHtyfQ0KZXhhbXBsZTwtcmVhZC5kZWxpbSgifi9uY2JpLWdlbm9tZXMtMjAxOS0wNS0xOS9HQ0ZfOTAwNzQ3Nzk1LjFfZkVycENhbDEuMV9hc3NlbWJseV9zdGF0cy50eHQiLCBzZXA9Ilx0IiwgY29sLm5hbWVzPWNvbG5hbWVzLCBjb21tZW50LmNoYXI9IiMiKQ0KcHJpbnQoZXhhbXBsZVsxOjE1LF0pDQpgYGANCg0KUmVhZGluZyB0aGUgZGF0YSBpbnRvIFIgdGhlIHNhbWUgd2F5IGFzIHRoZSBuYW1lcyBzY3JpcHQsIHRoaXMgdGltZSB1c2luZyBjb21tZW50IGNoYXJhY3RlciAnIycgYW5kIGFzc2lnbmluZyBjb2x1bW4gbmFtZXMuDQpgYGB7cn0NCmNvbG5hbWVzPC1jKCJ1bml0X25hbWUiLCJtb2xfbmFtZSIsIm1vbF90eXBlIiwic2VxX3R5cGUiLCJzdGF0IiwidmFsdWUiKQ0KDQphc3NlbS5kYXRhPC1sYXBwbHkoZmRpciwgZnVuY3Rpb24oaSl7DQogICAgcmVhZC5kZWxpbShpLA0KICAgICAgc2VwPSJcdCIsDQogICAgICBoZWFkZXI9RiwNCiAgICAgIGZpbGw9VCwNCiAgICAgIGNvbW1lbnQuY2hhcj0iIyIsDQogICAgICBjb2wubmFtZXM9Y29sbmFtZXMpfSkNCg0KcHJpbnQoYXNzZW0uZGF0YVtbM11dWzE6MTAsXSkNCmBgYA0KDQoNCk5vdywgdGVzdCBleHRyYWN0aW5nIGp1c3QgdGhlICJQcmltYXJ5IEFzc2VtYmx5IiByb3dzIHdpdGggbW9sX25hbWUgPSBhbGwuICBKdXN0IHVzZSBhc3NlbS5kYXRhW1szXV0gdG8gdGVzdC4NCmBgYHtyfQ0Kc3Vic2V0KGFzc2VtLmRhdGFbWzNdXSwgdW5pdF9uYW1lPT0iUHJpbWFyeSBBc3NlbWJseSIgJiBtb2xfbmFtZT09ImFsbCIpDQpgYGANCk5vdywgZm9yIGZpbGUgMywgd2UncmUgZG93biBmcm9tIDQ1MiByb3dzIHRvIDE4IHJvd3MuIE5hcnJvd2luZyBpdCBkb3duIGZ1cnRoZXIuLi4NCmBgYHtyfQ0Kc3Vic2V0KGFzc2VtLmRhdGFbWzNdXSwgdW5pdF9uYW1lPT0iUHJpbWFyeSBBc3NlbWJseSIgJiBtb2xfbmFtZT09ImFsbCIgJiBtb2xfdHlwZT09ImFsbCIgJnNlcV90eXBlPT0iYWxsIikNCmBgYA0KVGhpcyBuYXJyb3dzIGl0IGRvd24gZnJvbSAxOCByb3dzIHRvIDYgcm93cy4uLiBBbmQgbm93IHRoYXQgInRvdGFsLWxlbmd0aCIgaXMgdW5pcXVlIGluIHRoaXMgdmVjdG9yLCB3ZSBjYW4gZXh0cmFjdCBqdXN0IHRoYXQgcm93LiAgU2FtZSBmb3IgInVuZ2FwcGVkLWxlbmd0aCIuICBJJ2xsIHN0b3JlIHRoZXNlIHRvIHZhcmlhYmxlcy4NCmBgYHtyfQ0KdG90YWw8LXN1YnNldChhc3NlbS5kYXRhW1szXV0sIHVuaXRfbmFtZT09IlByaW1hcnkgQXNzZW1ibHkiICYgbW9sX25hbWU9PSJhbGwiICYgbW9sX3R5cGU9PSJhbGwiICZzZXFfdHlwZT09ImFsbCIgJiBzdGF0PT0idG90YWwtbGVuZ3RoIikNCnVuZ2FwcGVkPC1zdWJzZXQoYXNzZW0uZGF0YVtbM11dLCB1bml0X25hbWU9PSJQcmltYXJ5IEFzc2VtYmx5IiAmIG1vbF9uYW1lPT0iYWxsIiAmIG1vbF90eXBlPT0iYWxsIiAmc2VxX3R5cGU9PSJhbGwiICYgc3RhdD09InVuZ2FwcGVkLWxlbmd0aCIpDQpgYGANCg0KKFN0YXJ0aW5nIG92ZXIpDQpFeHRyYWN0IGRhdGEgYmFzZWQgb24gY29sdW1uIG5hbWVzOg0KYGBge3J9DQpnZXRfdG90YWxfbGVuZ3RoX2FsbGNvbHM8LSBmdW5jdGlvbiAoeCkgJ1snICh4W3gkdW5pdF9uYW1lICVpbiUgIlByaW1hcnkgQXNzZW1ibHkiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRtb2xfbmFtZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRtb2xfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzZXFfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzdGF0ICVpbiUgInRvdGFsLWxlbmd0aCIgLF0gKQ0KZ2V0X3VuZ2FwcGVkX2xlbmd0aF9hbGxjb2xzPC0gZnVuY3Rpb24gKHgpICdbJyAoeFt4JHVuaXRfbmFtZSAlaW4lICJQcmltYXJ5IEFzc2VtYmx5Ig0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX25hbWUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX3R5cGUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkc2VxX3R5cGUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkc3RhdCAlaW4lICJ1bmdhcHBlZC1sZW5ndGgiICxdICkNCg0KZ2V0X21pdG9fYWxsY29sczwtIGZ1bmN0aW9uICh4KSAnWycgKHhbeCR1bml0X25hbWUgJWluJSAibm9uLW51Y2xlYXIiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX25hbWUgJWluJSAiTVQiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX3R5cGUgJWluJSAiTWl0b2Nob25kcmlvbiINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzZXFfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkc3RhdCAlaW4lICJ0b3RhbC1sZW5ndGgiICxdICkNCg0KdG90YWxfYWxsPC1sYXBwbHkoYXNzZW0uZGF0YSxnZXRfdG90YWxfbGVuZ3RoX2FsbGNvbHMpDQp1bmdhcHBlZF9hbGw8LWxhcHBseShhc3NlbS5kYXRhLGdldF91bmdhcHBlZF9sZW5ndGhfYWxsY29scykNCm1pdG9fYWxsPC1sYXBwbHkoYXNzZW0uZGF0YSxnZXRfbWl0b19hbGxjb2xzKQ0KYGBgDQoNCkNoYW5nZSBhbGwgb2YgdGhlIGFib3ZlIGZ1bmN0aW9ucyB0byBnZXQgb25seSB0aGUgInZhbHVlIiBjb2x1bW4gKGtlZXAgdGhlIGZ1bmN0aW9ucyBpbiB0aGUgcHJldmlvdXMgY2h1bmsgZm9yIHRlc3RpbmcpDQoNCmBgYHtyfQ0KZ2V0X3RvdGFsX2xlbmd0aF92YWw8LSBmdW5jdGlvbiAoeCkgJ1snICh4W3gkdW5pdF9uYW1lICVpbiUgIlByaW1hcnkgQXNzZW1ibHkiDQogICAgICAgICAgICAgICAgICAgICAgICAgICZ4JG1vbF9uYW1lICVpbiUgImFsbCINCiAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX3R5cGUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzZXFfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICZ4JHN0YXQgJWluJSAidG90YWwtbGVuZ3RoIiAsInZhbHVlIl0gKQ0KDQpnZXRfdW5nYXBwZWRfbGVuZ3RoX3ZhbDwtIGZ1bmN0aW9uICh4KSAnWycgKHhbeCR1bml0X25hbWUgJWluJSAiUHJpbWFyeSBBc3NlbWJseSINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX25hbWUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRtb2xfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JHNlcV90eXBlICVpbiUgImFsbCINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkc3RhdCAlaW4lICJ1bmdhcHBlZC1sZW5ndGgiICwidmFsdWUiXSApDQoNCmdldF9taXRvX3ZhbDwtIGZ1bmN0aW9uICh4KSAnWycgKHhbeCR1bml0X25hbWUgJWluJSAibm9uLW51Y2xlYXIiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JG1vbF9uYW1lICVpbiUgIk1UIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRtb2xfdHlwZSAlaW4lICJNaXRvY2hvbmRyaW9uIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzZXFfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JHN0YXQgJWluJSAidG90YWwtbGVuZ3RoIiAsInZhbHVlIl0gKQ0KDQp0b3RhbDwtbGFwcGx5KGFzc2VtLmRhdGEsZ2V0X3RvdGFsX2xlbmd0aF92YWwpDQp1bmdhcHBlZDwtbGFwcGx5KGFzc2VtLmRhdGEsZ2V0X3VuZ2FwcGVkX2xlbmd0aF92YWwpDQptaXRvPC1sYXBwbHkoYXNzZW0uZGF0YSxnZXRfbWl0b192YWwpDQoNCmBgYA0KQmluZCB0b3RhbCwgdW5nYXBwZWQgYW5kIG1pdG8gaW50byBsaXN0cw0KYGBge3J9DQpoZWFkKGNiaW5kKHRvdGFsKSkNCmBgYA0KQmluZCBlYWNoIG5ldyBsaXN0IGFnYWluOg0KYGBge3J9DQpoZWFkKGNiaW5kKGNiaW5kKHRvdGFsKSxjYmluZCh1bmdhcHBlZCksY2JpbmQobWl0bykpKQ0KYGBgDQoNCkNhbiBkbyB0aGlzIGFsbCBpbiBvbmUgc3RlcDoNCmBgYHtyfQ0KYXNzZW0uZGF0YV92YWxzPC0gcmJpbmQuZGF0YS5mcmFtZShjYmluZChjYmluZCh0b3RhbCksY2JpbmQodW5nYXBwZWQpLGNiaW5kKG1pdG8pKSkNCmNvbG5hbWVzKGFzc2VtLmRhdGFfdmFscyk8LWMoInRvdGFsIiwidW5nYXBwZWQiLCJtaXRvIikNCmhlYWQoYXNzZW0uZGF0YV92YWxzKQ0KI3ByaW50aW5nIGNodW5rIGRvZXNuJ3Qgd29yaywgY29kZSB3b3JrcyBpbiBjb25zb2xlDQpgYGANCj4gaGVhZChkZikNCiAgICAgICB0b3RhbCAgIHVuZ2FwcGVkICBtaXRvDQoxICAxNDM3MDY0NzggIDE0MjU1MzUwMCAxOTUyNA0KMiAgICAgICAgICAgICAgICAgICAgICAgMTYyOTkNCjMgMjg3MDE2Nzg4MCAyNzI5OTY5MTkxIDE2MzEzDQo0IDMxOTY3MjEyMzYgMzExODUyNTc0MyAxNjg2Ng0KNSAxMzczNDU0Nzg4IDEzNjg3NjU1MDYgMTY1OTYNCjYgIDkyNzI5NjMxNCAgNzM3NzgzMzcwIDE0MTE3DQoNClJvdyAyIGlzIG1pc3NpbmcgdmFsdWVzPw0KUm93IDIgb3JnYW5pc20gaXMgTXVzIG11c2N1bHVzDQpJbiB0aGUgcmF3IGRhdGEsIG5vdCBjb21wYXRpYmxlIHdpdGggdGhpcyBsaW5lIGluIHRoZSBmdW5jdGlvbnMgYWJvdmU6DQpnZXRfdG90YWxfbGVuZ3RoX3ZhbDwtIGZ1bmN0aW9uICh4KSAnWycgKHhbeCR1bml0X25hbWUgJWluJSAiUHJpbWFyeSBBc3NlbWJseSINCkluIHRoaXMgZmlsZSwgIlByaW1hcnkgQXNzZW1ibHkiIGlzIHdyaXR0ZW4gYXMgIlByaW1hcnkgQXNzZW1ibHkgKEM1N0JMLzZKKSIsIHdoaWNoIGlzIG5vdCBjb25zaWRlcmVkIGEgIG1hdGNoIGJ5ICVpbiUuLi4uDQoNClRyeSB3cml0aW5nIGEgbmV3IG9wZXJhdG9yIGZvciBwYXJ0aWFsIG1hdGNoZXM/DQpgYGB7cn0NCiNleGFtcGxlcw0KIyIlaW4lIiA8LSBmdW5jdGlvbih4LCB0YWJsZSkgbWF0Y2goeCwgdGFibGUsIG5vbWF0Y2ggPSAwKSA+IDANCiNleGFtcGxlIGAlYWxsaW4lYCA8LSBmdW5jdGlvbih4LHRhYmxlKSB7YWxsKG1hdGNoKHgsdGFibGUsbm9tYXRjaCA9IDBMKSA+IDBMKX0NCiNzdHIoc3RhcnRzV2l0aCkgaXMgZnVuY3Rpb24gKHgscHJlZml4KSANCmBgYA0KYGBge3J9DQpgJXN0YXJ0cyVgIDwtIGZ1bmN0aW9uKHgsIHN0cmluZykge3N0YXJ0c1dpdGgoYXMuY2hhcmFjdGVyKHgpLCBzdHJpbmcpfQ0KYGBgDQpSZXBsYWNlIHRoaXMgb3BlcmF0b3IgaW50byB0aGUgZnVuY3Rpb25zIGZvciBnZXR0aW5nIHRvdGFsLWxlbmd0aCwgZXRjLg0KYGBge3J9DQpnZXRfdG90YWxfbGVuZ3RoX3ZhbDwtIGZ1bmN0aW9uICh4KSAnWycgKHhbeCR1bml0X25hbWUgJXN0YXJ0cyUgIlByaW1hcnkgQXNzZW1ibHkiDQogICAgICAgICAgICAgICAgICAgICAgICAgICZ4JG1vbF9uYW1lICVpbiUgImFsbCINCiAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX3R5cGUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzZXFfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICZ4JHN0YXQgJWluJSAidG90YWwtbGVuZ3RoIiAsInZhbHVlIl0gKQ0KDQpnZXRfdW5nYXBwZWRfbGVuZ3RoX3ZhbDwtIGZ1bmN0aW9uICh4KSAnWycgKHhbeCR1bml0X25hbWUgJXN0YXJ0cyUgIlByaW1hcnkgQXNzZW1ibHkiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JG1vbF9uYW1lICVpbiUgImFsbCINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX3R5cGUgJWluJSAiYWxsIg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmeCRzZXFfdHlwZSAlaW4lICJhbGwiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JHN0YXQgJWluJSAidW5nYXBwZWQtbGVuZ3RoIiAsInZhbHVlIl0gKQ0KZ2V0X21pdG9fdmFsPC0gZnVuY3Rpb24gKHgpICdbJyAoeFt4JHVuaXRfbmFtZSAlaW4lICJub24tbnVjbGVhciINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkbW9sX25hbWUgJWluJSAiTVQiDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JG1vbF90eXBlICVpbiUgIk1pdG9jaG9uZHJpb24iDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICZ4JHNlcV90eXBlICVpbiUgImFsbCINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJngkc3RhdCAlaW4lICJ0b3RhbC1sZW5ndGgiICwidmFsdWUiXSApDQoNCnRvdGFsPC1sYXBwbHkoYXNzZW0uZGF0YSxnZXRfdG90YWxfbGVuZ3RoX3ZhbCkNCnVuZ2FwcGVkPC1sYXBwbHkoYXNzZW0uZGF0YSxnZXRfdW5nYXBwZWRfbGVuZ3RoX3ZhbCkNCm1pdG88LWxhcHBseShhc3NlbS5kYXRhLGdldF9taXRvX3ZhbCkNCg0KYXNzZW0uZGF0YV92YWxzPC0gcmJpbmQuZGF0YS5mcmFtZShjYmluZChjYmluZCh0b3RhbCksY2JpbmQodW5nYXBwZWQpLGNiaW5kKG1pdG8pKSkNCmNvbG5hbWVzKGFzc2VtLmRhdGFfdmFscyk8LWMoInRvdGFsIiwidW5nYXBwZWQiLCJtaXRvIikNCmBgYA0KDQpJdCB3b3JrcyEhDQoNCj4gaGVhZChkZikNCiAgICAgICB0b3RhbCAgIHVuZ2FwcGVkICBtaXRvDQoxICAxNDM3MDY0NzggIDE0MjU1MzUwMCAxOTUyNA0KMiAyNzMwODU1NDc1IDI2NTI3NjcyNTkgMTYyOTkNCjMgMjg3MDE2Nzg4MCAyNzI5OTY5MTkxIDE2MzEzDQo0IDMxOTY3MjEyMzYgMzExODUyNTc0MyAxNjg2Ng0KNSAxMzczNDU0Nzg4IDEzNjg3NjU1MDYgMTY1OTYNCjYgIDkyNzI5NjMxNCAgNzM3NzgzMzcwIDE0MTE3DQoNCk5vdyBjYmluZCBhbiBJRCB2YWx1ZSBmb3IgZWFjaCByb3c6DQpgYGB7cn0NCmFzc2VtLmRhdGFfdmFsczwtY2JpbmQoIklEIj1yb3duYW1lcyhhc3NlbS5kYXRhX3ZhbHMpLCBhc3NlbS5kYXRhX3ZhbHMpDQpgYGANCg0KIyMgU2VjdGlvbiAzOiBDb21iaW5lIG5hbWVzIGFuZCBkYXRhDQpGaW5hbGx5LCBjb21iaW5pbmcgb3JnYW5pc20gbmFtZXMgd2l0aCB0aGVpciByZXNwZWN0aXZlIGRhdGE6DQoNCmBgYHtyfQ0KYXNzZW1ibGllczwtY2JpbmQobG5hLGFzc2VtLmRhdGFfdmFscykNCmBgYA0KDQoNCiMjIEV4cG9ydCBkYXRhIGZyYW1lIHRvIGNzdg0KV3JpdGUgdGhlIE9yZ2FuaXNtIGNvbHVtbiB0byBjc3Ygc28gd2UgY2FuIHVzZSBpdCB0byANCnF1ZXJ5IGluIHB5dGhvbjoNCkV4dHJhY3QganVzdCB0aGUgT3JnYW5pc20gY29sdW1uIGludG8gYSBuZXcgZGF0YSBmcmFtZToNCmBgYHtyfQ0KYXNzZW1ibGllcy5vcmdzPC1kYXRhLmZyYW1lKCJPcmdhbmlzbSI9Y2JpbmQoYXNzZW1ibGllc1ssIk9yZ2FuaXNtIl0pKQ0KYGBgDQoNCldyaXRlIHRoYXQgY29sdW1uIHRvIGNzdjoNCmBgYHtyfQ0KYXNzZW1ibGllcy5vcmdzPC1hcHBseShhc3NlbWJsaWVzLm9yZ3MsMixhcy5jaGFyYWN0ZXIpDQp3cml0ZS5jc3YoYXNzZW1ibGllcy5vcmdzLCAiYXNzZW1ibGllc29yZ3MuY3N2IikNCmBgYA0KDQojIFBhcnQgMjogTWFraW5nIGEgUHl0aG9uIFNRTGl0ZSBkYXRhYmFzZQ0KDQpUaGUgbmV4dCB0aGluZyBJIGRlY2lkZWQgdG8gZG8gd2FzIGdldCB0aGUgdGF4b25vbXkgaW5mb3JtYXRpb24gZnJvbSBlYWNoIG9yZ2FuaXNtIHRvIGFkZCB0byB0aGUgdGFibGUuICBUbyBkbyB0aGlzLCBJIHVzZWQgdGhlIEJpb0VudHJleiBwYWNrYWdlIGZvciBQeXRob24uICBUaGlzIGVuYWJsZXMgcXVlcmllcyBmcm9tIEVudHJlei4NCihJJ20gdXNpbmcgYW5hY29uZGEgd2l0aCBTcHlkZXIpDQoNCiMjIyBGZXRjaGluZyBvcmdhbmlzbSB4bWwgZmlsZXMgZnJvbSBFbnRyZXoNCioocmVhZF9jc3YucHkpKiAgICAgDQpTZXR1cDoNCmBgYHtweXRob259DQpmcm9tIEJpbyBpbXBvcnQgRW50cmV6DQpFbnRyZXouZW1haWwgPSAnICcNCmltcG9ydCBjc3YNCmltcG9ydCBseG1sDQppbXBvcnQgcHByaW50DQpmcm9tIGx4bWwgaW1wb3J0IGV0cmVlDQppbXBvcnQgdGltZQ0KaW1wb3J0IG51bXB5IGFzIG5wDQppbXBvcnQgdHJhY2ViYWNrDQpgYGANCg0KR2V0IGEgbGlzdCBvZiBvcmdhbmlzbSBuYW1lcyBmcm9tIHRoZSBjc3YgZmlsZSBtYWRlIGluIFBhcnQgMToNCmBgYHtweXRob259DQoNCmNzdmZpbGU9KHInXGFzc2VtYmxpZXNvcmdzLmNzdicpDQoNCmlkcz1bXQ0Kb3JnbGlzdD1bXQ0Kd2l0aCBvcGVuKGNzdmZpbGUsJ3J0JykgYXMgZjoNCiAgZGF0YSA9IGNzdi5yZWFkZXIoZikNCiAgZm9yIHJvdyBpbiBkYXRhOg0KICAgICAgaWRzLmFwcGVuZChyb3dbMF0pDQogICAgICBvcmdsaXN0LmFwcGVuZChyb3dbMV0pDQpgYGANCg0KRmV0Y2ggeG1sIGZpbGVzIHdpdGggdGF4b25vbXkgcmVzdWx0cyBmcm9tIG9yZ2xpc3QsIHN0b3JlIGluIG9yZ3MNCmBgYHtweXRob259DQpvcmdzPVtdDQpkZWYgZmV0Y2hvcmdzKHgpOg0KICAgIHRyeToNCiAgICAgICAgZm9yIGlkeCxvcmcgaW4gZW51bWVyYXRlKHgpOg0KICAgICAgICAgICAgcXVlcnk9IEVudHJlei5lc2VhcmNoKGRiPSJ0YXhvbm9teSIsdGVybT0gb3JnLCByZXRtYXg9MSkNCiAgICAgICAgICAgIHJlc3VsdD1FbnRyZXoucmVhZChxdWVyeSkNCiAgICAgICAgICAgIGhhbmRsZT1FbnRyZXouZWZldGNoKGRiPSJ0YXhvbm9teSIsaWQ9cmVzdWx0WydJZExpc3QnXSkNCiAgICAgICAgICAgIHJlc3VsdD1oYW5kbGUucmVhZCgpDQogICAgICAgICAgICBvcmdzLmFwcGVuZChyZXN1bHQpDQogICAgICAgICAgICBpZiBpZHggJSAxMD09MDoNCiAgICAgICAgICAgICAgICBwcmludChsZW4ob3JncyksInJlY29yZHMgZmV0Y2hlZCIpDQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOg0KICAgICAgICBwcmludChlLGlkeCxvcmcpDQoNCiNETyBOT1QgRkVUQ0ggTU9SRSBUSEFOIDEwMCBBVCBBIFRJTUUNCiNETyBUSElTIE9OIFdFRUtFTkRTIE9SIFVTIERPV05USU1FUw0KZmV0Y2hvcmdzKG9yZ2xpc3RbMTAwOjEwMV0pICNUb3RhbCBudW1iZXIgd2FzIDQ1MCwgSSBtb2RpZmllZCB0aGlzIGxpbmUgdG8gMDoxMDAsIDEwMDoyMDAsIDIwMDozMDAgZXRjIHRvIGF2b2lkIHRvbyBtYW55IHJlcXVlc3RzIGF0IG9uY2UuDQpgYGANCg0KQ2hlY2sgaWYgYW55IHJlY29yZHMgZnJvbSBvcmdsaXN0IGFyZSBtaXNzaW5nIGluIG9yZ3M6DQpgYGB7cHl0aG9ufQ0KZm9yIGMsaSBpbiBlbnVtZXJhdGUob3Jncyk6DQogICAgdHJ5Og0KICAgICAgICBpPWJ5dGVzKGksZW5jb2Rpbmc9J3V0ZjgnKQ0KICAgICAgICBBPWV0cmVlLmZyb21zdHJpbmcoaSkuZmluZCgnVGF4b24vU2NpZW50aWZpY05hbWUnKS50ZXh0DQogICAgICAgIGlmIEEgbm90IGluIG9yZ2xpc3Q6DQogICAgICAgICAgICBwcmludChjLEEpDQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOg0KICAgICAgICBwcmludChjLGUpDQpgYGANCg0KIyMgRXh0cmFjdGluZyB0YXhvbm9teSBkYXRhIGZyb20geG1sIGZpbGVzDQoqKG9yZ2FuaXNtX2RhdGEucHkpKg0KU2V0dXA6DQpgYGB7cHl0aG9ufQ0KZnJvbSBCaW8gaW1wb3J0IEVudHJleg0KRW50cmV6LmVtYWlsID0gJ21jZ2x5bm5rZWxsQGdtYWlsLmNvbScNCmltcG9ydCBjc3YNCmltcG9ydCBseG1sDQppbXBvcnQgcHByaW50DQpmcm9tIGx4bWwgaW1wb3J0IGV0cmVlDQppbXBvcnQgcGFuZGFzIGFzIHBkDQppbXBvcnQgdHJhY2ViYWNrDQpgYGANCg0KVXNpbmcgbHhtbCBhbmQgZXRyZWUgdG8gZXh0cmFjdCBkYXRhIGZyb20gdGhlIHhtbCBmaWxlcy4gIFRoZXJlIGFyZSAxMCB2YWx1ZXMgdG8gZ2V0IGZvciBlYWNoIG9yZ2FuaXNtIG5hbWU6IFRheElkLCBTY2llbnRpZmljTmFtZSwgQ29tbW9uTmFtZSwgRGl2aXNpb24sIEtpbmdkb20sIFBoeWx1bSwgQ2xhc3MsIE9yZGVyLCBGYW1pbHkgYW5kIEdlbnVzLg0KYGBge3B5dGhvbn0NCiNTZXQgdXAgZW1wdHkgbGlzdHMNClRheElkX2xpc3Q9W10NClNjaWVudGlmaWNOYW1lX2xpc3Q9W10NCkNvbW1vbk5hbWVfbGlzdD1bXQ0KRGl2aXNpb25fbGlzdD1bXQ0KS2luZ2RvbV9saXN0PVtdDQpQaHlsdW1fbGlzdD1bXQ0KQ2xhc3NfbGlzdD1bXQ0KT3JkZXJfbGlzdD1bXQ0KRmFtaWx5X2xpc3Q9W10NCkdlbnVzX2xpc3Q9W10NCg0KI0Z1bmN0aW9uIHRvIGV4dHJhY3QgZGF0YSBmcm9tIG9yZ3NmZXRjaGVkIChhIGxpc3QgdGhhdCB3YXMgZ2VuZXJhdGVkDQojZnJvbSB0aGUgZmV0Y2hvcmdzIGZ1bmN0aW9uKQ0KZGVmIGV4dHJhY3RfZGF0YShvcmdzZmV0Y2hlZCk6DQogICAgZm9yIGlkeCwgaSBpbiBlbnVtZXJhdGUob3Jnc2ZldGNoZWQpOg0KICAgICAgICBpZiBsZW4oaSk+MjYwOiAgICAjZXhjbHVkZXMgcmVzdWx0cyB0aGF0IGZhaWxlZCB0byBmZXRjaA0KICAgICAgICAgICAgaT1ieXRlcyhpLGVuY29kaW5nPSd1dGY4JykNCiAgICAgICAgICAgIFRheElkPWV0cmVlLmZyb21zdHJpbmcoaSkuZmluZCgnVGF4b24vVGF4SWQnKQ0KICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIFNjaWVudGlmaWNOYW1lPSBldHJlZS5mcm9tc3RyaW5nKGkpLmZpbmQoJ1RheG9uL1NjaWVudGlmaWNOYW1lJykNCiAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICBDb21tb25OYW1lPWV0cmVlLmZyb21zdHJpbmcoaSkuZmluZCgnVGF4b24vT3RoZXJOYW1lcy9HZW5iYW5rQ29tbW9uTmFtZScpDQogICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgRGl2aXNpb249ZXRyZWUuZnJvbXN0cmluZyhpKS5maW5kKCdUYXhvbi9EaXZpc2lvbicpICANCiAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICBLaW5nZG9tPWV0cmVlLmZyb21zdHJpbmcoaSkueHBhdGgoDQogICAgICAgICAgICAgICAgICAgICcvL1JhbmtbdGV4dCgpPSJzdXBlcmtpbmdkb20iXS9wcmVjZWRpbmctc2libGluZzo6U2NpZW50aWZpY05hbWUvdGV4dCgpJykNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgUGh5bHVtPWV0cmVlLmZyb21zdHJpbmcoaSkueHBhdGgoDQogICAgICAgICAgICAgICAgICAgICcvL1JhbmtbdGV4dCgpPSJwaHlsdW0iXS9wcmVjZWRpbmctc2libGluZzo6U2NpZW50aWZpY05hbWUvdGV4dCgpJykNCiAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIENsYXNzPWV0cmVlLmZyb21zdHJpbmcoaSkueHBhdGgoDQogICAgICAgICAgICAgICAgICAgICcvL1JhbmtbdGV4dCgpPSJjbGFzcyJdL3ByZWNlZGluZy1zaWJsaW5nOjpTY2llbnRpZmljTmFtZS90ZXh0KCknKQ0KICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIE9yZGVyPWV0cmVlLmZyb21zdHJpbmcoaSkueHBhdGgoDQogICAgICAgICAgICAgICAgICAgICcvL1JhbmtbdGV4dCgpPSJvcmRlciJdL3ByZWNlZGluZy1zaWJsaW5nOjpTY2llbnRpZmljTmFtZS90ZXh0KCknKQ0KICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIEZhbWlseT1ldHJlZS5mcm9tc3RyaW5nKGkpLnhwYXRoKA0KICAgICAgICAgICAgICAgICAgICAnLy9SYW5rW3RleHQoKT0iZmFtaWx5Il0vcHJlY2VkaW5nLXNpYmxpbmc6OlNjaWVudGlmaWNOYW1lL3RleHQoKScpDQogICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgR2VudXM9ZXRyZWUuZnJvbXN0cmluZyhpKS54cGF0aCgNCiAgICAgICAgICAgICAgICAgICAgJy8vUmFua1t0ZXh0KCk9ImdlbnVzIl0vcHJlY2VkaW5nLXNpYmxpbmc6OlNjaWVudGlmaWNOYW1lL3RleHQoKScpDQogICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIGRhdGExPVtUYXhJZCxTY2llbnRpZmljTmFtZSxDb21tb25OYW1lLERpdmlzaW9uLA0KICAgICAgICAgICAgICAgICAgIEtpbmdkb20sUGh5bHVtLENsYXNzLE9yZGVyLEZhbWlseSxHZW51c10NCiAgICAgICAgICAgIGZvciBpZHh4LGQgaW4gZW51bWVyYXRlKGRhdGExKToNCiAgICAgICAgICAgICAgICBpZiBkIGlzIE5vbmU6DQogICAgICAgICAgICAgICAgICAgZGF0YTFbaWR4eF09ZXRyZWUuRWxlbWVudCgiZW1wdHkiKQ0KICAgICAgICAgICAgICAgICAgIGRhdGExW2lkeHhdLnRleHQ9Ik5VTEwiICANCiAgICAgICAgICAgICAgICBlbGlmIGxlbihkKT09MDoNCiAgICAgICAgICAgICAgICAgICAgaWYgbm90IGhhc2F0dHIoZCwndGV4dCcpOg0KICAgICAgICAgICAgICAgICAgICAgICAgZGF0YTFbaWR4eF09WyJOVUxMIl0NCiAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIHRyeTogICAgICAgICANCiAgICAgICAgICAgICAgICBUYXhJZF9saXN0LmFwcGVuZChkYXRhMVswXS50ZXh0KQ0KICAgICAgICAgICAgICAgIFNjaWVudGlmaWNOYW1lX2xpc3QuYXBwZW5kKGRhdGExWzFdLnRleHQpDQogICAgICAgICAgICAgICAgQ29tbW9uTmFtZV9saXN0LmFwcGVuZChkYXRhMVsyXS50ZXh0KQ0KICAgICAgICAgICAgICAgIERpdmlzaW9uX2xpc3QuYXBwZW5kKGRhdGExWzNdLnRleHQpDQogICAgICAgICAgICAgICAgS2luZ2RvbV9saXN0LmFwcGVuZChkYXRhMVs0XVswXSkNCiAgICAgICAgICAgICAgICBQaHlsdW1fbGlzdC5hcHBlbmQoZGF0YTFbNV1bMF0pDQogICAgICAgICAgICAgICAgQ2xhc3NfbGlzdC5hcHBlbmQoZGF0YTFbNl1bMF0pIA0KICAgICAgICAgICAgICAgIE9yZGVyX2xpc3QuYXBwZW5kKGRhdGExWzddWzBdKQ0KICAgICAgICAgICAgICAgIEZhbWlseV9saXN0LmFwcGVuZChkYXRhMVs4XVswXSkNCiAgICAgICAgICAgICAgICBHZW51c19saXN0LmFwcGVuZChkYXRhMVs5XVswXSkNCiAgICAgICAgICAgIGV4Y2VwdCBBdHRyaWJ1dGVFcnJvciBhcyBBdHQ6DQogICAgICAgICAgICAgICAgcHJpbnQoaWR4LCAiTm9uZVR5cGUiLEF0dCwgdHJhY2ViYWNrLmZvcm1hdF9leGMoKSkNCiAgICAgICAgICAgIGV4Y2VwdCBJbmRleEVycm9yIGFzIEluZDoNCiAgICAgICAgICAgICAgICBwcmludChpZHgsImluZGV4IixJbmQsIHRyYWNlYmFjay5mb3JtYXRfZXhjKCkpDQpgYGANCiAgICANCk1ha2UgYSBkYXRhIGZyYW1lIGZyb20gdGhlIGV4dHJhY3RlZCB0YXhvbm9teSBkYXRhOg0KYGBge3B5dGhvbn0NCm9yZ19kYXRhZnJhbWUgPSBwZC5EYXRhRnJhbWUoDQogICAgICAgIHsnVGF4SWQnIDogVGF4SWRfbGlzdCwNCiAgICAgICAgICdTY2llbnRpZmljTmFtZScgOiBTY2llbnRpZmljTmFtZV9saXN0LCANCiAgICAgICAgICdDb21tb25OYW1lJyA6IENvbW1vbk5hbWVfbGlzdCwNCiAgICAgICAgICdEaXZpc2lvbicgOiBEaXZpc2lvbl9saXN0LCAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgJ0tpbmdkb20nIDogS2luZ2RvbV9saXN0LA0KICAgICAgICAgJ1BoeWx1bScgOiBQaHlsdW1fbGlzdCwNCiAgICAgICAgICdDbGFzcycgOiBDbGFzc19saXN0LA0KICAgICAgICAgJ09yZGVyJyA6IE9yZGVyX2xpc3QsDQogICAgICAgICAnRmFtaWx5JzogRmFtaWx5X2xpc3QsDQogICAgICAgICAnR2VudXMnIDogR2VudXNfbGlzdH0pDQogICAgDQpgYGANCg0KIyMjIEFkZCB0YXhvbm9teSBkYXRhIHRvIGFuIFNRTGl0ZSBkYXRhYmFzZQ0KKihkYXRhYmFzZS5weSkqICAgIA0KQWZ0ZXIgcHV0dGluZyB0aGlzIGRhdGEgZnJhbWUgaW50byBhIGRhdGFiYXNlLCBJIGNhbiB0aGVuIHF1ZXJ5IHRoZSBkYXRhYmFzZSBiYWNrIGluIFIuIChBbmQgdGhpcyB3YXksIEkgbGVhcm4gYWJvdXQgU1FMaXRlIGRhdGFiYXNlcyEpDQoNCkNyZWF0ZSBhIGxvY2FsIGRhdGFiYXNlIHdpdGggc3FsaXRlOg0KYGBge3B5dGhvbn0NCmltcG9ydCBzcWxpdGUzLCBzcWxhbGNoZW15DQppbXBvcnQgcGFuZGFzIGFzIHBkDQoNCmZyb20gc3FsYWxjaGVteSBpbXBvcnQgY3JlYXRlX2VuZ2luZQ0KZW5naW5lPSBjcmVhdGVfZW5naW5lKCdzcWxpdGU6Ly8vIDxQQVRIPiBcXE9yZ2FuaXNtZGIuZGInLA0KICAgICAgICAgICAgICAgICAgICAgIGVjaG89VHJ1ZSkNCg0KZnJvbSBzcWxhbGNoZW15LmV4dC5kZWNsYXJhdGl2ZSBpbXBvcnQgZGVjbGFyYXRpdmVfYmFzZQ0KQmFzZSA9IGRlY2xhcmF0aXZlX2Jhc2UoKQ0KDQpmcm9tIHNxbGFsY2hlbXkgaW1wb3J0IFRhYmxlLCBDb2x1bW4sIEludGVnZXIsIFN0cmluZw0KY2xhc3MgT3JnYW5pc20oQmFzZSk6DQogICAgX190YWJsZW5hbWVfXyA9ICdPcmdhbmlzbXMnDQogICAgDQogICAgVGF4SWQ9IENvbHVtbihJbnRlZ2VyLCBpbmRleCA9IFRydWUsIHByaW1hcnlfa2V5ID0gVHJ1ZSkNCiAgICBTY2llbnRpZmljX05hbWUgPSBDb2x1bW4oU3RyaW5nKQ0KICAgIENvbW1vbl9OYW1lPSBDb2x1bW4oU3RyaW5nKQ0KICAgIERpdmlzaW9uPSBDb2x1bW4oU3RyaW5nKQ0KICAgIEtpbmdkb209IENvbHVtbihTdHJpbmcpDQogICAgUGh5bHVtPSBDb2x1bW4oU3RyaW5nKQ0KICAgIENsYXNzPSBDb2x1bW4oU3RyaW5nKQ0KICAgIE9yZGVyPSBDb2x1bW4oU3RyaW5nKQ0KICAgIEZhbWlseT0gQ29sdW1uKFN0cmluZykNCiAgICBHZW51cz0gQ29sdW1uKFN0cmluZykNCiAgICANCiAgICBkZWYgX19pbml0X18oc2VsZiwgVGF4SWQsU2NpZW50aWZpY05hbWUsQ29tbW9uTmFtZSxEaXZpc2lvbiwNCiAgICAgICAgICAgICAgIEtpbmdkb20sUGh5bHVtLENsYXNzLE9yZGVyLEZhbWlseSxHZW51cywgKiprdyk6DQogICAgICAgIHNlbGYuVGF4SWQgPSBUYXhJZA0KICAgICAgICBzZWxmLlNjaWVudGlmaWNOYW1lID0gU2NpZW50aWZpY05hbWUNCiAgICAgICAgc2VsZi5Db21tb25OYW1lID0gQ29tbW9uTmFtZQ0KICAgICAgICBzZWxmLkRpdmlzaW9uID0gRGl2aXNpb24NCiAgICAgICAgc2VsZi5LaW5nZG9tID0gS2luZ2RvbQ0KICAgICAgICBzZWxmLlBoeWx1bSA9IFBoeWx1bQ0KICAgICAgICBzZWxmLkNsYXNzID0gQ2xhc3MNCiAgICAgICAgc2VsZi5PcmRlciA9IE9yZGVyDQogICAgICAgIHNlbGYuRmFtaWx5ID0gRmFtaWx5DQogICAgICAgIHNlbGYuR2VudXMgPSBHZW51cw0KICAgICAgICAgICANCkJhc2UubWV0YWRhdGEuY3JlYXRlX2FsbChlbmdpbmUpDQoNCk9yZ2FuaXNtcy5fX3RhYmxlX18gDQoNCiNOb3RlcyBvbiB0aGUgY29tbWFuZHMgbmVlZGVkIHRvIGFkZCB0byB0aGUgZGF0YWJhc2U6ICAgDQpmcm9tIHNxbGFsY2hlbXkub3JtIGltcG9ydCBzZXNzaW9ubWFrZXINClNlc3Npb24gPSBzZXNzaW9ubWFrZXIoYmluZD1lbmdpbmUpDQpzZXNzaW9uID0gU2Vzc2lvbigpDQoNCnNlc3Npb24uY29tbWl0KCkNCnNlc3Npb24uY2xvc2UoKQ0KYGBgDQoNCg0KKihpbnB1dF9kYmRhdGEucHkpKiANCmBgYHtweXRob259DQpmcm9tIHNxbGFsY2hlbXkgaW1wb3J0IGNyZWF0ZV9lbmdpbmUNCmVuZ2luZT0gY3JlYXRlX2VuZ2luZSgnc3FsaXRlOi8vLyA8UEFUSD4gXFxPcmdhbmlzbWRiLmRiJywNCiAgICAgICAgICAgICAgICAgICAgICBlY2hvPVRydWUpDQpjb25uID0gc3FsaXRlMy5jb25uZWN0KCdPcmdhbmlzbWRiLmRiJykNCmN1cnNvciA9IGNvbm4uY3Vyc29yKCkNCg0KZnJvbSBzcWxhbGNoZW15Lm9ybSBpbXBvcnQgc2Vzc2lvbm1ha2VyDQpTZXNzaW9uID0gc2Vzc2lvbm1ha2VyKGJpbmQ9ZW5naW5lKQ0Kc2Vzc2lvbiA9IFNlc3Npb24oKQ0KDQoNCg0KDQpmb3IgY29sIGluIG9yZ19kYXRhZnJhbWUuY29sdW1uczogDQogICAgcHJpbnQoY29sKSANCg0KI0lucHV0IGFsbCBkYXRhIGluIHBhbmRhcyBkYXRhZnJhbWUgaW50byB0YWJsZSAnT3JnYW5pc21zJw0Kb3JnX2RhdGFmcmFtZS50b19zcWwoJ09yZ2FuaXNtcycsIGNvbj1lbmdpbmUsIGlmX2V4aXN0cz0ncmVwbGFjZScpDQoNCnNlc3Npb24uY29tbWl0KCkNCnNlc3Npb24uY2xvc2UoKQ0KYGBgDQpUaGUgZGF0YWJhc2UgaXMgYWxsIHNldCB1cCENCg0KQmFjayB0byBSIQ0KDQojIFBhcnQgMzogUXVlcnlpbmcgdGhlIE9yZ2FuaXNtcy5kYiBTUUxpdGUgZGF0YWJhc2UgaW4gUg0KDQpUaGUgbGFzdCBzdGVwIGlzIHRvIHF1ZXJ5IHRoZSBkYXRhIGZyb20gT3JnYW5pc21zLmRiIGluIFIgYW5kIGFwcGVuZCB0aGUgbmV3IGRhdGEgdG8gdGhlIGFzc2VtYmxpZXMgUiBkYXRhZnJhbWUuDQoNClNldHVwOg0KYGBge3J9DQpsaWJyYXJ5KFJTUUxpdGUpDQpsaWJyYXJ5KGRicGx5cikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KERCSSkNCmBgYA0KDQojI0Nvbm5lY3QgdG8gU1FMaXRlIGRhdGFiYXNlIGluIFINCmBgYHtyfQ0KY29uIDwtIERCSTo6ZGJDb25uZWN0KFJTUUxpdGU6OlNRTGl0ZSgpLCBkYm5hbWUgPSAiPFBBVEg+IFxcT3JnYW5pc21kYi5kYiIpDQoNCmBgYA0KDQpDaGVjayBpZiB0aGUgY29ubmVjdGlvbiB3b3JrZWQ6DQpgYGB7cn0NCmF0dHJpYnV0ZXMoY29uKQ0KZGJMaXN0VGFibGVzKGNvbikNCmRiRXhpc3RzVGFibGUoY29uLCJPcmdhbmlzbXMiKQ0KDQpkYkxpc3RGaWVsZHMoY29uLCJPcmdhbmlzbXMiKQ0KYGBgDQoNClZpZXcgdGFibGUhDQpgYGB7cn0NCnRibChjb24sIk9yZ2FuaXNtcyIpDQpgYGANCg0KV3JpdGUgYSBmdW5jdGlvbiB0byBxdWVyeSB0aGUgZGF0YWJhc2UgZnJvbSB0aGUgYXNzZW1ibGllcyBkYXRhZnJhbWU6DQpgYGB7cn0NCnF1ZXJ5PC0gZnVuY3Rpb24obngpIHsNCng8LWRiU2VuZFF1ZXJ5KGNvbiwgDQogICAgICAgICAgICAgICAnU0VMRUNUICogDQogICAgICAgICAgICAgRlJPTSBPcmdhbmlzbXMgV0hFUkUgU2NpZW50aWZpY05hbWUgPT0gPycpDQpkYkJpbmQoeCxsaXN0KG54KSkNCno8LShkYkZldGNoKHgsbj0xKSkgI249MSBzcGVjaWZpZXMgcmVzdWx0cyB0byByZXR1cm4sIGRvZXNuJ3Qgd29yayBhcyB3ZWxsIGlmIG11bHRpcGxlIHJlc3VsdHMgYXJlIGZldGNoZWQNCmRiQ2xlYXJSZXN1bHQoeCkgI1ZFUlkgaW1wb3J0YW50ISBWRVJZIQ0KcmV0dXJuKHopDQpkYkNsZWFyUmVzdWx0KHgpICNWRVJZIGltcG9ydGFudCEgVkVSWSENCn0NCmBgYA0KDQojIyBRdWVyeSBhcHBlbmQgbmV3IGNvbHVtbnMgZnJvbSBTUUxpdGUgZGF0YWJhc2UNCmBgYHtyfQ0KZGJfcmVzdWx0czwtZGF0YS5mcmFtZSgpICAjbWFrZSBhIG5ldyBkYXRhLmZyYW1lDQoNCmZvciAoaSBpbiBhc3NlbWJsaWVzWywiT3JnYW5pc20iXSkgew0KICB4PC1xdWVyeShpKQ0KICBpZiAobnJvdyh4KT09MCkgew0KICAgIHhbbnJvdyh4KSsxLF08LSBjKHJlcCgiTlVMTCIsMTEpKX0NCiAgZGJfcmVzdWx0czwtcmJpbmQoZGJfcmVzdWx0cyx4KX0NCg0KI0JpbmQgbmV3IGNvbHVtbnMgdG8gYXNzZW1ibGllcyBkYXRhZnJhbWUNCmFzc2VtYmxpZXNfY29tcGxldGU8LWNiaW5kKGFzc2VtYmxpZXMsZGJfcmVzdWx0cykNCmBgYA0KDQpJZiB0aGVyZSBhcmUgYW55IGVudHJpZXMgbWlzc2luZyBpbiBkYl9yZXN1bHRzLCBkYl9yZXN1bHRzIHdvbid0IGNiaW5kIHRvIGFzc2VtYmxpZXMoZGlmZmVyaW5nIG51bWJlcnMgb2Ygcm93cykuIERvdWJsZSBjaGVjazoNCmBgYHtyfQ0Kd2hpY2goYXNzZW1ibGllc1ssIk9yZ2FuaXNtIl0gJXN0YXJ0cyUgIkRyb3NvcGhpbGEgbWVsYW5vZ2FzdGVyIikNCmBgYA0KDQpDaGVjayB3aGljaCByb3dzIGFyZSBtaXNzaW5nOg0KYGBge3J9DQonJSFpbiUnIDwtIGZ1bmN0aW9uKHgseSkhKCclaW4lJyh4LHkpKQ0Kd2hpY2goYXNzZW1ibGllc1ssIk9yZ2FuaXNtIl0gJSFpbiUgZGJfcmVzdWx0c1ssIlNjaWVudGlmaWNOYW1lIl0pDQoNCndoaWNoKGRiX3Jlc3VsdHNbLCJTY2llbnRpZmljTmFtZSJdICUhaW4lIGFzc2VtYmxpZXNbLCJPcmdhbmlzbSJdKQ0KYGBgDQoNClZlcmlmeSB0aGF0IE9yZ2FuaXNtIG1hdGNoZXMgU2NpZW50aWZpYyBOYW1lIGluIGFsbCByb3dzDQpgYGB7cn0NCndoaWNoKGFzc2VtYmxpZXNfY29tcGxldGVbLCJPcmdhbmlzbSJdICE9IGFzc2VtYmxpZXNfY29tcGxldGVbLCJTY2llbnRpZmljTmFtZSJdKQ0KYGBgDQoNClJvd3MgMTAwLDIwMCBhbmQgNDAwIHdlcmVuJ3QgaW4gdGhlIGRhdGFiYXNlIQ0KUHJpbnQgb3V0IHRoZSBtaXNzaW5nIHJvd3Mgc28gSSBjYW4gZ28gYmFjayB0byB0aGUgUHl0aG9uIHNjcmlwdCBhbmQgYWRkIHRoZSBtaXNzaW5nIGRhdGEgdG8gdGhlIGRiLg0KYGBge3J9DQphc3NlbWJsaWVzX2NvbXBsZXRlW2MoMTAwLDIwMCw0MDApLF0NCmBgYA0KDQpJIHRoZW4gd2VudCBiYWNrIHRvIHRoZSBweXRob24gc2NyaXB0cyBhbmQgYWRkZWQgdGhlIG1pc3NpbmcgdmFsdWVzIGZyb20gcm93cyAxMDAsIDIwMCBhbmQgNDAwLCBydW5uaW5nIHRoZXNlIGZpbmFsIFIgc2NyaXB0cyBhZ2FpbiBhZnRlcndhcmRzLg0KDQoNCkFsbCBkb25lIQ0KDQpOb3cgdGhpcyBkYXRhc2V0IGNhbiBiZSB1c2VkIHRvIGRvIHNvbWUgaW50ZXJlc3RpbmcgYW5hbHlzaXMgYW5kIHZpc3VhbGl6YXRpb25zLCBzdWNoIGFzIGNvbXBhcmluZyBnZW5vbWUgc2l6ZSBhbmQgbWl0b2Nob25kcmlhbCBnZW5vbWUgc2l6ZSBhbW9uZyB0aGUgb3JnYW5pc21zIGluIHRoaXMgZGF0YXNldCwgYW5kIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBnZW5vbWUgc2l6ZSBhbmQgYSBwYXJ0aWN1bGFyIHRheG9ub21pYyBjbGFzcy4gIA0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=