Install Packages
install.packages("rvest","stringr","dplyr","plyr","ggplot2","tidyr","caret","scales","gridExtra")
Load Libraries
library(rvest)
library(stringr)
library(dplyr)
library(plyr)
library(ggplot2)
library(tidyr)
library(caret)
library(scales)
library(gridExtra)
A website with holiday data is: timeanddate.com
url <- "https://www.timeanddate.com/holidays/"
Great, so how do we get the holidays for each country?
Two options: Either we use the countries names to make the URLs or we know all the URLs for each countries
links <- url %>%
read_html %>%
html_nodes(".main-content-div .row a") %>%
html_attr("href")
Let’s inspect what we have gotten
head(links)
[1] "/"
[2] "/calendar/"
[3] "/holidays/anguilla/"
[4] "/holidays/antigua-and-barbuda/"
[5] "/holidays/aruba/"
[6] "/holidays/barbados/"
paste("")
[1] ""
tail(links)
[1] "/holidays/tanzania/" "/holidays/togo/"
[3] "/holidays/tunisia/" "/holidays/uganda/"
[5] "/holidays/zambia/" "/holidays/zimbabwe/"
Let’s append the base URL to each path
links <- paste0("https://www.timeanddate.com",links)
links[1:10]
[1] "https://www.timeanddate.com/holidays/anguilla/"
[2] "https://www.timeanddate.com/holidays/antigua-and-barbuda/"
[3] "https://www.timeanddate.com/holidays/aruba/"
[4] "https://www.timeanddate.com/holidays/barbados/"
[5] "https://www.timeanddate.com/holidays/belize/"
[6] "https://www.timeanddate.com/holidays/british-virgin-islands/"
[7] "https://www.timeanddate.com/holidays/canada/"
[8] "https://www.timeanddate.com/holidays/cayman-islands/"
[9] "https://www.timeanddate.com/holidays/costa-rica/"
[10] "https://www.timeanddate.com/holidays/cuba/"
Let’s extract the actual country names from the links using stringr and regex
country <- stringr::str_replace_all(links,"https://www.timeanddate.com/holidays/","") %>%
str_replace_all(.,"[-/]"," ") %>%
stringr::str_trim()
country[1:10]
[1] "anguilla" "antigua and barbuda"
[3] "aruba" "barbados"
[5] "belize" "british virgin islands"
[7] "canada" "cayman islands"
[9] "costa rica" "cuba"
Change from lower case to Title Case
country =stringr::str_to_title(country)
country[1:10]
[1] "Anguilla" "Antigua And Barbuda"
[3] "Aruba" "Barbados"
[5] "Belize" "British Virgin Islands"
[7] "Canada" "Cayman Islands"
[9] "Costa Rica" "Cuba"
Now, we will apply this function to each of links we have our a dataset
################DO NOT RUN#######################
holiday_df=plyr::ldply(1:length(links),holiday_per_country)
names(holiday_df) = c("Country","no_of_holidays")
################DO NOT RUN#######################
holiday_df = read.csv("holiday_df.csv")
holiday_df %>% dplyr::as_tibble()
We would use ggplot2 to inspect our data. For more info on how to use ggplot2, check out the ggplot2 Cheatseet
holiday_df$no_of_holidays = as.numeric(holiday_df$no_of_holidays)
ggplot(holiday_df,aes(no_of_holidays))+
geom_histogram(fill="seagreen3",color="black")+
theme_classic()

Let’s find out the exact countries that are the top outliers
holiday_df %>%
arrange(-no_of_holidays)
Interesting, let’s take a look at The US
You would see that a lot of their holidays are either State or County-observed. Do these types of holidays count if you are looking at the nation as a whole?
local_holidays = function(index) {
column_names=names(links[index] %>%
read_html %>%
html_node(".zebra") %>%
html_table) %>% str_to_lower()
if("where it is observed" %in%column_names == T){
country[index]
}
}
#We have two options of using this function
#Option 1: A for loop.
#Pros: Ability to save intermediary result & Ability to create custom progress trackers
#Cons: Significantly slower & Code is less concise
# local_countries=NULL
# for (i in 1:length(links)){
# ind=local_holidays(i)
# local_countries=append(local_countries,ind)
# print(i/length(links))
# }
#Option 2: Vectorization using the lapply function
#Pros: Fast & Concise code
#Cons: Intermediary results are not saved & No custom progress function although some progressbar functionalities can be used with it
local_countries=lapply(1:length(links),local_holidays) %>% unlist
closing unused connection 3 (https://www.timeanddate.com/holidays/anguilla/)
################DO NOT RUN#######################
local_countries=NULL
for (i in 1:length(links)){
ind=local_holidays(i)
local_countries=append(local_countries,ind)
print(i/length(links))
}
#Option 2: Vectorization using the lapply function
#Pros: Fast & Concise code
#Cons: Intermediary results are not saved & No custom progress function although some progressbar functionalities can be used with it
local_countries=lapply(1:length(links),local_holidays) %>% unlist
################DO NOT RUN#######################
local_countries=c("Canada","Us","Australia","Indonesia","Philippines","United Arab Emirates","Germany","Spain","Switzerland","Uk")
Let’s take a look at the countries that have these local holidays
paste("The Number of Countries with local holidays:",length(local_countries))
[1] "The Number of Countries with local holidays: 10"
local_countries
[1] "Canada" "Us"
[3] "Australia" "Indonesia"
[5] "Philippines" "United Arab Emirates"
[7] "Germany" "Spain"
[9] "Switzerland" "Uk"
Let’s get the number of national holidays for these countries
#Extract the index numbers for each of these countries
indices = which(country %in% local_countries)
updated_holidays= function (index){
table=links[index] %>%
read_html %>%
html_node(".zebra") %>%
html_table
names(table)=names(table) %>%
str_to_lower()
updated_table = table %>%
filter( (grepl('holiday',`holiday type`,ignore.case =T) &
`where it is observed` == "" & grepl('jewish|Hindu',`holiday type`,ignore.case =T)==F) | grepl('all',`where it is observed`,ignore.case =T))
return(c(country[index],nrow(updated_table)))
}
new_holiday = ldply(indices,updated_holidays)
names(new_holiday)=c("Country","no_of_holidays")
new_holiday=cbind(new_holiday,indices)
new_holiday
Now, let’s replace the old holiday values with the new ones
for(i in indices){
holiday_df$no_of_holidays[i]=new_holiday$no_of_holidays[new_holiday$indices==i]
}
holiday_df=holiday_df %>%
mutate(no_of_holidays=as.numeric(no_of_holidays))
holiday_df%>%
filter(Country == "Canada") %>%
as_tibble()
Let’s check what we have left as outliers
holiday_df %>%
arrange(-no_of_holidays)
Update actual holidays for the top 4 outliers
holiday_df$no_of_holidays[holiday_df$Country=="Bangladesh"]=21
holiday_df$no_of_holidays[holiday_df$Country=="India"]=18
holiday_df$no_of_holidays[holiday_df$Country=="Pakistan"]=20
holiday_df$no_of_holidays[holiday_df$Country=="Malaysia"]=20
Inspect again using our Histogram from before
ggplot(holiday_df,aes(no_of_holidays))+
geom_histogram(fill="seagreen3",color="black")+
theme_classic()

Let’s check out those zeros
holiday_df %>%
arrange(no_of_holidays)
Those are not countries so we will take them out
holiday_df=holiday_df %>%
filter(no_of_holidays>0)
Let’s also make sure that there no duplicates
holiday_df %>%
group_by(Country) %>%
dplyr::summarise(no_of_occurences=n()) %>%
arrange(-no_of_occurences)
Let’s investigate Russia
holiday_df %>%
filter(Country=="Russia")
We would remove them using the distinct() function from dplyr
holiday_df=holiday_df %>%
distinct(Country,no_of_holidays)
Let’s check the dataframe after we have removed the duplicates
holiday_df %>%
group_by(Country) %>%
dplyr::summarise(no_of_occurences=n()) %>%
arrange(-no_of_occurences)
Now, let’s look at Life Expectancy data from The World Bank
life_expectancy_df = read.csv("Life Expectancy.csv")
life_expectancy_df
Let’s tidy up these two columns
life_expectancy_df$Year=substr(life_expectancy_df$Year,2,5)
life_expectancy_df=life_expectancy_df %>%
mutate(Year=as.integer(Year)) %>%
mutate(life_expectancy=as.numeric(life_expectancy)) %>%
mutate(Country.Name=as.character(Country.Name))
NAs introduced by coercion
life_expectancy_df
Let’s check for missing values
table(is.na(life_expectancy_df))
FALSE TRUE
5097 135
Let’s remove all the NA’s with R’s complete.cases()
paste("Before NA removal:",nrow(life_expectancy_df))
[1] "Before NA removal: 1744"
life_expectancy_df=life_expectancy_df[complete.cases(life_expectancy_df),]
paste("After NA removal:",nrow(life_expectancy_df))
[1] "After NA removal: 1609"
Let’s compute the average life expectancy since 2008
life_expectancy_df=life_expectancy_df %>%
group_by(Country.Name) %>%
dplyr::summarise(avg_life_expectancy=mean(life_expectancy))
life_expectancy_df
We would like to join the two tables but first let’s see what the different types of joins mean
Check for duplicates
life_expectancy_df %>%
group_by(Country.Name) %>%
dplyr::summarise(count=n()) %>%
arrange(-count)
Some exploration of the distribution of Life Expectancy
ggplot(life_expectancy_df,aes(avg_life_expectancy))+
geom_histogram(fill="violetred3",color="black")+
theme_classic()

Let’s investigate what countries are the lower end of the life expectancy
life_expectancy_df %>%
arrange(avg_life_expectancy)
We want to join the holiday and the life expectancy data frames for countries that have both numbers of holidays and life expectancy. What Join will be most suitable for this?
life_expectancy_df=life_expectancy_df %>%
dplyr::rename(Country=Country.Name)
holiday_and_life = inner_join(holiday_df,life_expectancy_df,by="Country")
Column `Country` joining factor and character vector, coercing into character vector
holiday_and_life
We are ready for the fun part, Machine Learning!
Let’s do some preprocessing like splitting the data into a training and test set using caret
country_data = read.csv("country_data.csv")
set.seed(1)
idx=createDataPartition(country_data$avg_life_expectancy,p=0.7,list = F)
train=country_data[idx,]
test=country_data[-idx,]
country_data
paste("Number of Observations for train is:",nrow(train),"& Number of Observations for test is:",nrow(test))
[1] "Number of Observations for train is: 104 & Number of Observations for test is: 41"
Some inital exploration of our data
p1=ggplot(country_data,aes(no_of_holidays,avg_gdp))+
geom_point(color="violetred3",size=2)+
ggtitle("Holidays")+
theme_classic()
p2=ggplot(country_data,aes(Population,avg_gdp))+
geom_point(color="#e67e22",size=2)+
ggtitle("Population")+
theme_classic()
p3=ggplot(country_data,aes(Health.expenditure..private....of.GDP.,avg_gdp))+
geom_point(color="#3498db",size=2)+
ggtitle("Health Expenditure")+
theme_classic()
p4=ggplot(country_data,aes(Labor.force..total,avg_gdp))+
geom_point(color="#1abc9c",size=2)+
ggtitle("Labor Force")+
theme_classic()
p5=ggplot(country_data,aes(Unemployment_Rate,avg_gdp))+
geom_point(color="#34495e",size=2)+
ggtitle("Unemployment Rate")+
theme_classic()
p6=ggplot(country_data,aes(avg_life_expectancy,avg_gdp))+
geom_point(color="#e74c3c",size=2)+
ggtitle("Average Life Expectancy")+
theme_classic()
grid.arrange(p1,p2,p3,p4,p5,p6)

Change to logarithmic scale
p1=ggplot(country_data,aes(no_of_holidays,log(avg_gdp)))+
geom_point(color="violetred3",size=2)+
ggtitle("Holidays")+
theme_classic()
p2=ggplot(country_data,aes(log(Population),log(avg_gdp)))+
geom_point(color="#e67e22",size=2)+
ggtitle("Population")+
theme_classic()
p3=ggplot(country_data,aes(Health.expenditure..private....of.GDP.,log(avg_gdp)))+
geom_point(color="#3498db",size=2)+
ggtitle("Health Expenditure")+
theme_classic()
p4=ggplot(country_data,aes(log(Labor.force..total),log(avg_gdp)))+
geom_point(color="#1abc9c",size=2)+
ggtitle("Labor Force")+
theme_classic()
p5=ggplot(country_data,aes(Unemployment_Rate,log(avg_gdp)))+
geom_point(color="#34495e",size=2)+
ggtitle("Unemployment Rate")+
theme_classic()
p6=ggplot(country_data,aes(avg_life_expectancy,log(avg_gdp)))+
geom_point(color="#e74c3c",size=2)+
ggtitle("Average Life Expectancy")+
theme_classic()
grid.arrange(p1,p2,p3,p4,p5,p6)

Let’s carry out model selection
models = c("lm","rf","rpart","gbm")
set.seed(7)
model_selection = function(method_name){
if(method_name=="rf"){
garbage <- capture.output(
model <-train(x= train[,c(2,4:9)], y = train[,3], method = method_name,importance=T)
)
}
else{
garbage <- capture.output(
model <-train(x= train[,c(2,4:9)], y = train[,3], method = method_name)
)
}
return(model)
}
R2_metric = function(model){
predTest=predict(model,newdata = test)
R2 = R2(predTest,test$avg_gdp)
return(c(model[["method"]],R2))
}
model_results = lapply(models,model_selection)
Loading required package: randomForest
randomForest 4.6-12
Type rfNews() to see new features/changes/bug fixes.
Attaching package: 㤼㸱randomForest㤼㸲
The following object is masked from 㤼㸱package:gridExtra㤼㸲:
combine
The following object is masked from 㤼㸱package:ggplot2㤼㸲:
margin
The following object is masked from 㤼㸱package:dplyr㤼㸲:
combine
Loading required package: rpart
There were missing values in resampled performance measures.Loading required package: gbm
Loading required package: survival
Attaching package: 㤼㸱survival㤼㸲
The following object is masked from 㤼㸱package:caret㤼㸲:
cluster
Loading required package: splines
Loading required package: parallel
Loaded gbm 2.1.3
models_r2 = model_results%>%
ldply(.,R2_metric)
names(models_r2) = c("model","r2")
models_r2 %>%
mutate(r2=as.numeric(r2)) %>%
arrange(-r2)
RandomForest did it best, let’s take a look at the important variables
varImp(model_results[[2]])[["importance"]] %>%
as.data.frame() %>%
mutate(variables=row.names(.)) %>%
ggplot(.,aes(x=reorder(variables,Overall),y= Overall ))+
geom_bar(stat = "identity",fill="#16a085",color="#16a085")+
coord_flip()+
theme_classic()+
xlab("Variables")+
ylab("Weighted Importance")+
ggtitle("Random Forest Variable Importance")+
geom_text(aes(label=paste0(round(Overall,1),"%"),hjust=ifelse(Overall<90,-0.1,1)))

LS0tDQp0aXRsZTogIlRoZSBBQkMtWFlaIG9mIERhdGEgU2NpZW5jZSINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2sNCi0tLQ0KKioqIA0KDQpgYGB7ciByZXN1bHRzPSJhc2lzIiwgZWNobz1GQUxTRX0NCmNhdCgiDQo8c3R5bGU+DQpAaW1wb3J0IHVybCgnaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVJhbGV3YXk6NDAwLDYwMCcpOw0KDQpib2R5ew0KCWZvbnQtZmFtaWx5OiAnUmFsZXdheSc7DQp9DQoudGl0bGV7DQp0ZXh0LWFsaWduOmNlbnRlcjsNCnRleHQtdHJhbnNmb3JtOnVwcGVyY2FzZTsNCmZvbnQtd2VpZ2h0OjcwMA0KfQ0KLnJvd3sNCmRpc3BsYXk6bm9uZTsNCn0NCiN3ZS1hcmUtcmVhZHktZm9yLXRoZS1mdW4tcGFydC1tYWNoaW5lLWxlYXJuaW5new0KdGV4dC1hbGlnbjogY2VudGVyOw0KZm9udC13ZWlnaHQ6IDcwMDsNCnRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7DQpjb2xvcjogIzhlNDRhZDsNCn0NCjwvc3R5bGU+DQoiKQ0KYGBgDQojIyMgKipJbnN0YWxsIFBhY2thZ2VzKiogDQpgYGB7ciBJbnN0YWxsIFBhY2thZ2VzfQ0KaW5zdGFsbC5wYWNrYWdlcygicnZlc3QiLCJzdHJpbmdyIiwiZHBseXIiLCJwbHlyIiwiZ2dwbG90MiIsInRpZHlyIiwiY2FyZXQiLCJzY2FsZXMiLCJncmlkRXh0cmEiKQ0KYGBgDQoNCiMjIyAqKkxvYWQgTGlicmFyaWVzKioNCmBgYHtyIG1lc3NhZ2U9RkFMU0UgfQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShncmlkRXh0cmEpDQpgYGANCg0KIyMjICoqQSB3ZWJzaXRlIHdpdGggaG9saWRheSBkYXRhIGlzOiB0aW1lYW5kZGF0ZS5jb20qKg0KYGBge3J9DQp1cmwgPC0gImh0dHBzOi8vd3d3LnRpbWVhbmRkYXRlLmNvbS9ob2xpZGF5cy8iDQpgYGANCg0KIyMjICoqR3JlYXQsIHNvIGhvdyBkbyB3ZSBnZXQgdGhlIGhvbGlkYXlzIGZvciBlYWNoIGNvdW50cnk/KioNClR3byBvcHRpb25zOiBFaXRoZXIgd2UgdXNlIHRoZSBjb3VudHJpZXMgbmFtZXMgdG8gbWFrZSB0aGUgVVJMcyBvciB3ZSBrbm93IGFsbCB0aGUgVVJMcyBmb3IgZWFjaCBjb3VudHJpZXMNCg0KYGBge3J9DQpsaW5rcyA8LSB1cmwgJT4lDQogcmVhZF9odG1sICU+JSANCiBodG1sX25vZGVzKCIubWFpbi1jb250ZW50LWRpdiAucm93IGEiKSAlPiUgDQogaHRtbF9hdHRyKCJocmVmIikNCmBgYA0KDQojIyMgKipMZXQncyBpbnNwZWN0IHdoYXQgd2UgaGF2ZSBnb3R0ZW4qKg0KYGBge3J9DQpoZWFkKGxpbmtzKQ0KdGFpbChsaW5rcykNCmBgYA0KIyMjICoqV2UgZ290IHRoZSBwYXRocyBmcm9tIHRoZSBiYXNlIFVSTCBhbmQgaXQgYWxzbyBzZWVtcyB0aGF0IHRoZSBmaXJzdCB0d28gbGlua3MgYXJlIG5vdCBjb3VudHJ5IHJlbGF0ZWQgc28gbGV0J3MgdGFrZSBpdCBvdXQqKg0KYGBge3J9DQpsaW5rcyA8LSBsaW5rcyBbMzpsZW5ndGgobGlua3MpXQ0KbGlua3MNCmBgYA0KDQojIyMgKipMZXQncyBhcHBlbmQgdGhlIGJhc2UgVVJMIHRvIGVhY2ggcGF0aCoqDQpgYGB7cn0NCmxpbmtzIDwtIHBhc3RlMCgiaHR0cHM6Ly93d3cudGltZWFuZGRhdGUuY29tIixsaW5rcykNCmxpbmtzWzE6MTBdDQpgYGANCg0KIyMjICoqTGV0J3MgZXh0cmFjdCB0aGUgYWN0dWFsIGNvdW50cnkgbmFtZXMgZnJvbSB0aGUgbGlua3MgdXNpbmcgc3RyaW5nciBhbmQqKiBbcmVnZXhdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE2LzA5L1JlZ0V4Q2hlYXRzaGVldC5wZGYpDQpgYGB7cn0NCmNvdW50cnkgPC0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGxpbmtzLCJodHRwczovL3d3dy50aW1lYW5kZGF0ZS5jb20vaG9saWRheXMvIiwiIikgJT4lDQpzdHJfcmVwbGFjZV9hbGwoLiwiWy0vXSIsIiAiKSAlPiUNCiAgc3RyaW5ncjo6c3RyX3RyaW0oKQ0KY291bnRyeVsxOjEwXQ0KYGBgDQoNCiMjIyAqKkNoYW5nZSBmcm9tIGxvd2VyIGNhc2UgdG8gVGl0bGUgQ2FzZSoqDQpgYGB7cn0NCmNvdW50cnkgPXN0cmluZ3I6OnN0cl90b190aXRsZShjb3VudHJ5KQ0KY291bnRyeVsxOjEwXQ0KYGBgDQojIyMgKipXZSBhcmUgZ29pbmcgdG8gd3JpdGUgb3VyIG93biBmdW5jdGlvbiB0byBleHRyYWN0IHRoZSBudW1iZXIgb2YgaG9saWRheXMgZm9yIGVhY2ggY291bnRyeSB1c2luZyB0aGUgaG9saWRheV9wZXJfY291bnRyeSBmdW5jdGlvbiBiZWxvdyoqDQpgYGB7cn0NCmhvbGlkYXlfcGVyX2NvdW50cnkgPSBmdW5jdGlvbihpbmRleCl7DQogIHJlcXVpcmUocnZlc3QpDQogIHJlcXVpcmUoZHBseXIpDQpob2xpZGF5cz10cnkobGlua3NbaW5kZXhdICU+JSANCiAgICAgICAgICAgICAgICAgICAgICByZWFkX2h0bWwgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgIGh0bWxfbm9kZSgiLnplYnJhIikgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgIGh0bWxfdGFibGUgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKChncmVwbChwYXR0ZXJuID0gIkhvbGlkYXkiLGBIb2xpZGF5IFR5cGVgLGlnbm9yZS5jYXNlPSBUKT09VCkpDQogICAgICAgICAgICAgICAgICAgICAsc2lsZW50ID0gVCkNCiAgaWYoaXMubGlzdChob2xpZGF5cykpew0KICAgIHJldHVybihjKGNvdW50cnlbaW5kZXhdLG5yb3coaG9saWRheXMpLTEpKQ0KICB9DQogIGVsc2V7DQogICAgcmV0dXJuKGMoY291bnRyeVtpbmRleF0sMCkpDQogIH0NCn0NCmBgYA0KDQojIyMgKipOb3csIHdlIHdpbGwgYXBwbHkgdGhpcyBmdW5jdGlvbiB0byBlYWNoIG9mIGxpbmtzIHdlIGhhdmUgb3VyIGEgZGF0YXNldCoqDQpgYGB7ciBldmFsPUZBTFNFfQ0KIyMjIyMjIyMjIyMjIyMjI0RPIE5PVCBSVU4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KaG9saWRheV9kZj1wbHlyOjpsZHBseSgxOmxlbmd0aChsaW5rcyksaG9saWRheV9wZXJfY291bnRyeSkNCm5hbWVzKGhvbGlkYXlfZGYpID0gYygiQ291bnRyeSIsIm5vX29mX2hvbGlkYXlzIikNCiMjIyMjIyMjIyMjIyMjIyNETyBOT1QgUlVOIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KYGBgDQpgYGB7cn0NCmhvbGlkYXlfZGYgPSByZWFkLmNzdigiaG9saWRheV9kZi5jc3YiKQ0KaG9saWRheV9kZiAlPiUgZHBseXI6OmFzX3RpYmJsZSgpDQpgYGANCg0KIyMjICoqV2Ugd291bGQgdXNlIGdncGxvdDIgdG8gaW5zcGVjdCBvdXIgZGF0YS4gRm9yIG1vcmUgaW5mbyBvbiBob3cgdG8gdXNlIGdncGxvdDIsIGNoZWNrIG91dCB0aGUqKiBbZ2dwbG90MiBDaGVhdHNlZXRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAzL2dncGxvdDItY2hlYXRzaGVldC5wZGYpIA0KYGBge3J9DQpob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzID0gYXMubnVtZXJpYyhob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzKQ0KZ2dwbG90KGhvbGlkYXlfZGYsYWVzKG5vX29mX2hvbGlkYXlzKSkrDQogIGdlb21faGlzdG9ncmFtKGZpbGw9InNlYWdyZWVuMyIsY29sb3I9ImJsYWNrIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQojIyMgKipMZXQncyBmaW5kIG91dCB0aGUgZXhhY3QgY291bnRyaWVzIHRoYXQgYXJlIHRoZSB0b3Agb3V0bGllcnMqKg0KYGBge3J9DQpob2xpZGF5X2RmICU+JQ0KICBhcnJhbmdlKC1ub19vZl9ob2xpZGF5cykNCmBgYA0KDQojIyMqKkludGVyZXN0aW5nLCBsZXQncyB0YWtlIGEgbG9vayBhdCBUaGUqKiBbVVNdKGh0dHBzOi8vd3d3LnRpbWVhbmRkYXRlLmNvbS9ob2xpZGF5cy91cy8pDQoNCllvdSB3b3VsZCBzZWUgdGhhdCBhIGxvdCBvZiB0aGVpciBob2xpZGF5cyBhcmUgZWl0aGVyIFN0YXRlIG9yIENvdW50eS1vYnNlcnZlZC4gRG8gdGhlc2UgdHlwZXMgb2YgaG9saWRheXMgY291bnQgaWYgeW91IGFyZSBsb29raW5nIGF0IHRoZSBuYXRpb24gYXMgYSB3aG9sZT8NCiAgDQoNCmBgYHtyfQ0KIGxvY2FsX2hvbGlkYXlzID0gZnVuY3Rpb24oaW5kZXgpIHsgIA0KICAgY29sdW1uX25hbWVzPW5hbWVzKGxpbmtzW2luZGV4XSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWFkX2h0bWwgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF9ub2RlKCIuemVicmEiKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX3RhYmxlKSAlPiUgc3RyX3RvX2xvd2VyKCkNCiAgIGlmKCJ3aGVyZSBpdCBpcyBvYnNlcnZlZCIgJWluJWNvbHVtbl9uYW1lcyAgPT0gVCl7IA0KICAgICBjb3VudHJ5W2luZGV4XQ0KICAgfQ0KIA0KIH0NCg0KI1dlIGhhdmUgdHdvIG9wdGlvbnMgb2YgdXNpbmcgdGhpcyBmdW5jdGlvbg0KDQojT3B0aW9uIDE6IEEgZm9yIGxvb3AuIA0KI1Byb3M6IEFiaWxpdHkgdG8gc2F2ZSBpbnRlcm1lZGlhcnkgcmVzdWx0ICYgQWJpbGl0eSB0byBjcmVhdGUgY3VzdG9tIHByb2dyZXNzIHRyYWNrZXJzDQojQ29uczogU2lnbmlmaWNhbnRseSBzbG93ZXIgJiBDb2RlIGlzIGxlc3MgY29uY2lzZQ0KDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMjIyMjIyMjIyMjIyMjIyNETyBOT1QgUlVOIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmxvY2FsX2NvdW50cmllcz1OVUxMDQpmb3IgKGkgaW4gMTpsZW5ndGgobGlua3MpKXsNCiAgaW5kPWxvY2FsX2hvbGlkYXlzKGkpDQogIGxvY2FsX2NvdW50cmllcz1hcHBlbmQobG9jYWxfY291bnRyaWVzLGluZCkNCiAgcHJpbnQoaS9sZW5ndGgobGlua3MpKQ0KfQ0KDQojT3B0aW9uIDI6IFZlY3Rvcml6YXRpb24gdXNpbmcgdGhlIGxhcHBseSBmdW5jdGlvbg0KI1Byb3M6IEZhc3QgJiBDb25jaXNlIGNvZGUNCiNDb25zOiBJbnRlcm1lZGlhcnkgcmVzdWx0cyBhcmUgbm90IHNhdmVkICYgTm8gY3VzdG9tIHByb2dyZXNzIGZ1bmN0aW9uIGFsdGhvdWdoIHNvbWUgcHJvZ3Jlc3NiYXIgZnVuY3Rpb25hbGl0aWVzIGNhbiBiZSB1c2VkIHdpdGggaXQNCmxvY2FsX2NvdW50cmllcz1sYXBwbHkoMTpsZW5ndGgobGlua3MpLGxvY2FsX2hvbGlkYXlzKSAlPiUgdW5saXN0DQojIyMjIyMjIyMjIyMjIyMjRE8gTk9UIFJVTiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpgYGANCg0KYGBge3J9DQpsb2NhbF9jb3VudHJpZXM9YygiQ2FuYWRhIiwiVXMiLCJBdXN0cmFsaWEiLCJJbmRvbmVzaWEiLCJQaGlsaXBwaW5lcyIsIlVuaXRlZCBBcmFiIEVtaXJhdGVzIiwiR2VybWFueSIsIlNwYWluIiwiU3dpdHplcmxhbmQiLCJVayIpDQpgYGANCg0KIyMjICoqTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNvdW50cmllcyB0aGF0IGhhdmUgdGhlc2UgbG9jYWwgaG9saWRheXMqKg0KYGBge3J9DQpwYXN0ZSgiVGhlIE51bWJlciBvZiBDb3VudHJpZXMgd2l0aCBsb2NhbCANCiAgICAgIGhvbGlkYXlzOiIsbGVuZ3RoKGxvY2FsX2NvdW50cmllcykpDQpsb2NhbF9jb3VudHJpZXMNCmBgYA0KDQojIyMgKipMZXQncyBnZXQgdGhlIG51bWJlciBvZiBuYXRpb25hbCBob2xpZGF5cyBmb3IgdGhlc2UgY291bnRyaWVzKioNCmBgYHtyfQ0KI0V4dHJhY3QgdGhlIGluZGV4IG51bWJlcnMgZm9yIGVhY2ggb2YgdGhlc2UgY291bnRyaWVzDQppbmRpY2VzID0gd2hpY2goY291bnRyeSAlaW4lIGxvY2FsX2NvdW50cmllcykNCnVwZGF0ZWRfaG9saWRheXM9IGZ1bmN0aW9uIChpbmRleCl7DQogIA0KdGFibGU9bGlua3NbaW5kZXhdICU+JSANCiAgICAgICAgICAgICAgICByZWFkX2h0bWwgJT4lDQogICAgICAgICAgICAgICAgaHRtbF9ub2RlKCIuemVicmEiKSAlPiUNCiAgICAgICAgICAgICAgICBodG1sX3RhYmxlIA0KbmFtZXModGFibGUpPW5hbWVzKHRhYmxlKSAlPiUNCiAgc3RyX3RvX2xvd2VyKCkNCnVwZGF0ZWRfdGFibGUgPSB0YWJsZSAlPiUNCiAgZmlsdGVyKCAoZ3JlcGwoJ2hvbGlkYXknLGBob2xpZGF5IHR5cGVgLGlnbm9yZS5jYXNlID1UKSAmIA0KICAgICAgICAgICAgIGB3aGVyZSBpdCBpcyBvYnNlcnZlZGAgPT0gIiIgJiBncmVwbCgnamV3aXNofEhpbmR1JyxgaG9saWRheSB0eXBlYCxpZ25vcmUuY2FzZSA9VCk9PUYpIHwgZ3JlcGwoJ2FsbCcsYHdoZXJlIGl0IGlzIG9ic2VydmVkYCxpZ25vcmUuY2FzZSA9VCkpDQogIHJldHVybihjKGNvdW50cnlbaW5kZXhdLG5yb3codXBkYXRlZF90YWJsZSkpKQ0KfQ0KbmV3X2hvbGlkYXkgPSBsZHBseShpbmRpY2VzLHVwZGF0ZWRfaG9saWRheXMpDQpuYW1lcyhuZXdfaG9saWRheSk9YygiQ291bnRyeSIsIm5vX29mX2hvbGlkYXlzIikNCm5ld19ob2xpZGF5PWNiaW5kKG5ld19ob2xpZGF5LGluZGljZXMpDQpuZXdfaG9saWRheQ0KYGBgDQoNCiMjIyAqKk5vdywgbGV0J3MgcmVwbGFjZSB0aGUgb2xkIGhvbGlkYXkgdmFsdWVzIHdpdGggdGhlIG5ldyBvbmVzKioNCmBgYHtyfQ0KDQpmb3IoaSBpbiBpbmRpY2VzKXsNCmhvbGlkYXlfZGYkbm9fb2ZfaG9saWRheXNbaV09bmV3X2hvbGlkYXkkbm9fb2ZfaG9saWRheXNbbmV3X2hvbGlkYXkkaW5kaWNlcz09aV0NCiAgDQp9DQoNCmhvbGlkYXlfZGY9aG9saWRheV9kZiAlPiUNCiAgbXV0YXRlKG5vX29mX2hvbGlkYXlzPWFzLm51bWVyaWMobm9fb2ZfaG9saWRheXMpKSANCg0KaG9saWRheV9kZiU+JQ0KICBmaWx0ZXIoQ291bnRyeSA9PSAiQ2FuYWRhIikgJT4lDQogIGFzX3RpYmJsZSgpDQpgYGANCg0KIyMjICoqTGV0J3MgY2hlY2sgd2hhdCB3ZSBoYXZlIGxlZnQgYXMgb3V0bGllcnMqKg0KYGBge3J9DQpob2xpZGF5X2RmICU+JQ0KICBhcnJhbmdlKC1ub19vZl9ob2xpZGF5cykNCmBgYA0KDQojIyMgKipVcGRhdGUgYWN0dWFsIGhvbGlkYXlzIGZvciB0aGUgdG9wIDQgb3V0bGllcnMqKg0KYGBge3J9DQpob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzW2hvbGlkYXlfZGYkQ291bnRyeT09IkJhbmdsYWRlc2giXT0yMQ0KaG9saWRheV9kZiRub19vZl9ob2xpZGF5c1tob2xpZGF5X2RmJENvdW50cnk9PSJJbmRpYSJdPTE4DQpob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzW2hvbGlkYXlfZGYkQ291bnRyeT09IlBha2lzdGFuIl09MjANCmhvbGlkYXlfZGYkbm9fb2ZfaG9saWRheXNbaG9saWRheV9kZiRDb3VudHJ5PT0iTWFsYXlzaWEiXT0yMA0KYGBgDQoNCiMjIyAqKkluc3BlY3QgYWdhaW4gdXNpbmcgb3VyIEhpc3RvZ3JhbSBmcm9tIGJlZm9yZSoqDQpgYGB7cn0NCg0KZ2dwbG90KGhvbGlkYXlfZGYsYWVzKG5vX29mX2hvbGlkYXlzKSkrDQogIGdlb21faGlzdG9ncmFtKGZpbGw9InNlYWdyZWVuMyIsY29sb3I9ImJsYWNrIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIyAqKkxldCdzIGNoZWNrIG91dCB0aG9zZSB6ZXJvcyoqDQpgYGB7cn0NCmhvbGlkYXlfZGYgJT4lDQogIGFycmFuZ2Uobm9fb2ZfaG9saWRheXMpDQpgYGANCg0KIyMjICoqVGhvc2UgYXJlIG5vdCBjb3VudHJpZXMgc28gd2Ugd2lsbCB0YWtlIHRoZW0gb3V0KioNCmBgYHtyfQ0KaG9saWRheV9kZj1ob2xpZGF5X2RmICU+JQ0KICBmaWx0ZXIobm9fb2ZfaG9saWRheXM+MCkNCmBgYA0KDQojIyMgKipMZXQncyBhbHNvIG1ha2Ugc3VyZSB0aGF0IHRoZXJlIG5vIGR1cGxpY2F0ZXMqKg0KYGBge3J9DQpob2xpZGF5X2RmICU+JQ0KICBncm91cF9ieShDb3VudHJ5KSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShub19vZl9vY2N1cmVuY2VzPW4oKSkgJT4lDQogIGFycmFuZ2UoLW5vX29mX29jY3VyZW5jZXMpDQpgYGANCg0KIyMjICoqTGV0J3MgaW52ZXN0aWdhdGUgUnVzc2lhKioNCmBgYHtyfQ0KaG9saWRheV9kZiAlPiUNCiAgZmlsdGVyKENvdW50cnk9PSJSdXNzaWEiKQ0KYGBgDQoNCiMjIyoqV2Ugd291bGQgcmVtb3ZlIHRoZW0gdXNpbmcgdGhlIGRpc3RpbmN0KCkgZnVuY3Rpb24gZnJvbSBkcGx5cioqDQpgYGB7cn0NCmhvbGlkYXlfZGY9aG9saWRheV9kZiAlPiUNCiAgZGlzdGluY3QoQ291bnRyeSxub19vZl9ob2xpZGF5cykNCmBgYA0KDQojIyMgKipMZXQncyBjaGVjayB0aGUgZGF0YWZyYW1lIGFmdGVyIHdlIGhhdmUgcmVtb3ZlZCB0aGUgZHVwbGljYXRlcyoqDQpgYGB7cn0NCiBob2xpZGF5X2RmICU+JQ0KICBncm91cF9ieShDb3VudHJ5KSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShub19vZl9vY2N1cmVuY2VzPW4oKSkgJT4lDQogIGFycmFuZ2UoLW5vX29mX29jY3VyZW5jZXMpDQpgYGANCg0KDQojIyMgKipMZXQncyBsb29rIGF0IGEgdmlzdWFsIG1hcCBvZiBob2xpZGF5cyBpbiBlYWNoIGNvdW50cnkgb24gYSoqIFtUYWJsZWF1IERhc2hib2FyZF0oaHR0cHM6Ly9wdWJsaWMudGFibGVhdS5jb20vcHJvZmlsZS9yb3NlYnVkLmFud3VyaSMhL3ZpemhvbWUvVGhlQUJDLVhZWm9mRGF0YVNjaWVuY2UvU2hlZXQxP3B1Ymxpc2g9eWVzKQ0KIVtdKE1hcC5wbmcpIA0KDQojIyMgKipOb3csIGxldCdzIGxvb2sgYXQgTGlmZSBFeHBlY3RhbmN5IGRhdGEgZnJvbSBUaGUgV29ybGQgQmFuayoqDQpgYGB7cn0NCmxpZmVfZXhwZWN0YW5jeV9kZiA9IHJlYWQuY3N2KCJMaWZlIEV4cGVjdGFuY3kuY3N2IikNCmxpZmVfZXhwZWN0YW5jeV9kZg0KYGBgDQoNCiMjIyAqKlVzaW5nIHRpZHlyJ3MgZ2F0aGVyKCkgZnVuY3Rpb24gd2Ugd291bGQgY29sbGFwc2UgYWxsIHRoZXNlIGxpZmUgZXhwZWN0YW5jaWVzIGludG8gdHdvIGNvbHVtbnMgaS5lLiBBIGtleS12YWx1ZSBmb3JtYXQqKg0KYGBge3J9DQpsaWZlX2V4cGVjdGFuY3lfZGYgPSBsaWZlX2V4cGVjdGFuY3lfZGYgJT4lDQogIGdhdGhlcihZZWFyLGxpZmVfZXhwZWN0YW5jeSxYMjAwOC4uWVIyMDA4LjpYMjAxNS4uWVIyMDE1LikgJT4lDQogIHNlbGVjdCgtQ291bnRyeS5Db2RlKQ0KbGlmZV9leHBlY3RhbmN5X2RmDQpgYGANCg0KIyMjICoqTGV0J3MgdGlkeSB1cCB0aGVzZSB0d28gY29sdW1ucyoqDQpgYGB7cn0NCmxpZmVfZXhwZWN0YW5jeV9kZiRZZWFyPXN1YnN0cihsaWZlX2V4cGVjdGFuY3lfZGYkWWVhciwyLDUpDQpsaWZlX2V4cGVjdGFuY3lfZGY9bGlmZV9leHBlY3RhbmN5X2RmICU+JQ0KICBtdXRhdGUoWWVhcj1hcy5pbnRlZ2VyKFllYXIpKSAlPiUNCiAgbXV0YXRlKGxpZmVfZXhwZWN0YW5jeT1hcy5udW1lcmljKGxpZmVfZXhwZWN0YW5jeSkpICU+JQ0KICAgIG11dGF0ZShDb3VudHJ5Lk5hbWU9YXMuY2hhcmFjdGVyKENvdW50cnkuTmFtZSkpIA0KbGlmZV9leHBlY3RhbmN5X2RmDQpgYGANCg0KIyMjICoqTGV0J3MgY2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzKioNCmBgYHtyfQ0KdGFibGUoaXMubmEobGlmZV9leHBlY3RhbmN5X2RmKSkNCg0KYGBgDQojIyMgKipMZXQncyByZW1vdmUgYWxsIHRoZSBOQSdzIHdpdGggUidzIGNvbXBsZXRlLmNhc2VzKCkqKg0KYGBge3J9DQpwYXN0ZSgiQmVmb3JlIE5BIHJlbW92YWw6Iixucm93KGxpZmVfZXhwZWN0YW5jeV9kZikpDQpsaWZlX2V4cGVjdGFuY3lfZGY9bGlmZV9leHBlY3RhbmN5X2RmW2NvbXBsZXRlLmNhc2VzKGxpZmVfZXhwZWN0YW5jeV9kZiksXQ0KcGFzdGUoIkFmdGVyIE5BIHJlbW92YWw6Iixucm93KGxpZmVfZXhwZWN0YW5jeV9kZikpDQpgYGANCiMjIyAqKkxldCdzIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgbGlmZSBleHBlY3RhbmN5IHNpbmNlIDIwMDgqKg0KYGBge3J9DQpsaWZlX2V4cGVjdGFuY3lfZGY9bGlmZV9leHBlY3RhbmN5X2RmICU+JSANCiAgZ3JvdXBfYnkoQ291bnRyeS5OYW1lKSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShhdmdfbGlmZV9leHBlY3RhbmN5PW1lYW4obGlmZV9leHBlY3RhbmN5KSkNCmxpZmVfZXhwZWN0YW5jeV9kZg0KYGBgDQoNCiMjIyAqKldlIHdvdWxkIGxpa2UgdG8gam9pbiB0aGUgdHdvIHRhYmxlcyBidXQgZmlyc3QgbGV0J3Mgc2VlIHdoYXQgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBqb2lucyBtZWFuKioNCg0KIVtdKFR5cGVzIG9mIEpvaW5zLnBuZykgDQoNCiMjIyAqKkNoZWNrIGZvciBkdXBsaWNhdGVzKioNCmBgYHtyfQ0KbGlmZV9leHBlY3RhbmN5X2RmICU+JQ0KICBncm91cF9ieShDb3VudHJ5Lk5hbWUpICU+JQ0KICBkcGx5cjo6c3VtbWFyaXNlKGNvdW50PW4oKSkgJT4lDQogIGFycmFuZ2UoLWNvdW50KQ0KYGBgDQoNCiMjIyAqKlNvbWUgZXhwbG9yYXRpb24gb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBMaWZlIEV4cGVjdGFuY3kqKg0KYGBge3J9DQpnZ3Bsb3QobGlmZV9leHBlY3RhbmN5X2RmLGFlcyhhdmdfbGlmZV9leHBlY3RhbmN5KSkrDQogIGdlb21faGlzdG9ncmFtKGZpbGw9InZpb2xldHJlZDMiLGNvbG9yPSJibGFjayIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KIyMjKipMZXQncyBpbnZlc3RpZ2F0ZSB3aGF0IGNvdW50cmllcyBhcmUgdGhlIGxvd2VyIGVuZCBvZiB0aGUgbGlmZSBleHBlY3RhbmN5KioNCmBgYHtyfQ0KbGlmZV9leHBlY3RhbmN5X2RmICU+JQ0KICBhcnJhbmdlKGF2Z19saWZlX2V4cGVjdGFuY3kpDQoNCmBgYA0KDQojIyMqKkxldCdzIGFsc28gdmlzdWFsaXplIGEqKiBbc3BhdGlhbCBtYXBdKGh0dHBzOi8vcHVibGljLnRhYmxlYXUuY29tL3Byb2ZpbGUvcm9zZWJ1ZC5hbnd1cmkjIS92aXpob21lL1RoZUFCQy1YWVpvZkRhdGFTY2llbmNlL1NoZWV0Mj9wdWJsaXNoPXllcykgZm9yIExpZmUgRXhwZWN0YW5jeQ0KIVtdKE1hcDIucG5nKQ0KDQojIyMqKldlIHdhbnQgdG8gam9pbiB0aGUgaG9saWRheSBhbmQgdGhlIGxpZmUgZXhwZWN0YW5jeSBkYXRhIGZyYW1lcyBmb3IgY291bnRyaWVzIHRoYXQgaGF2ZSBib3RoIG51bWJlcnMgb2YgaG9saWRheXMgYW5kIGxpZmUgZXhwZWN0YW5jeS4gV2hhdCBKb2luIHdpbGwgYmUgbW9zdCBzdWl0YWJsZSBmb3IgdGhpcz8qKg0KYGBge3J9DQogbGlmZV9leHBlY3RhbmN5X2RmPWxpZmVfZXhwZWN0YW5jeV9kZiAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKENvdW50cnk9Q291bnRyeS5OYW1lKQ0KaG9saWRheV9hbmRfbGlmZSA9IGlubmVyX2pvaW4oaG9saWRheV9kZixsaWZlX2V4cGVjdGFuY3lfZGYsYnk9IkNvdW50cnkiKQ0KaG9saWRheV9hbmRfbGlmZQ0KYGBgDQoNCiMjI1dlIGFyZSByZWFkeSBmb3IgdGhlIGZ1biBwYXJ0LCAqKk1hY2hpbmUgTGVhcm5pbmcqKiENCioqKg0KDQojIyMqKkxldCdzIGRvIHNvbWUgcHJlcHJvY2Vzc2luZyBsaWtlIHNwbGl0dGluZyB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0IHVzaW5nIGNhcmV0KioNCmBgYHtyfQ0KY291bnRyeV9kYXRhID0gcmVhZC5jc3YoImNvdW50cnlfZGF0YS5jc3YiKQ0Kc2V0LnNlZWQoMSkNCmlkeD1jcmVhdGVEYXRhUGFydGl0aW9uKGNvdW50cnlfZGF0YSRhdmdfbGlmZV9leHBlY3RhbmN5LHA9MC43LGxpc3QgPSBGKQ0KdHJhaW49Y291bnRyeV9kYXRhW2lkeCxdDQp0ZXN0PWNvdW50cnlfZGF0YVstaWR4LF0NCmNvdW50cnlfZGF0YQ0KcGFzdGUoIk51bWJlciBvZiBPYnNlcnZhdGlvbnMgZm9yIHRyYWluIGlzOiIsbnJvdyh0cmFpbiksIiYgTnVtYmVyIG9mIE9ic2VydmF0aW9ucyBmb3IgdGVzdCBpczoiLG5yb3codGVzdCkpDQoNCmBgYA0KDQojIyMqKlNvbWUgaW5pdGFsIGV4cGxvcmF0aW9uIG9mIG91ciBkYXRhKioNCmBgYHtyfQ0KDQpwMT1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhub19vZl9ob2xpZGF5cyxhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9InZpb2xldHJlZDMiLHNpemU9MikrDQogIGdndGl0bGUoIkhvbGlkYXlzIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KcDI9Z2dwbG90KGNvdW50cnlfZGF0YSxhZXMoUG9wdWxhdGlvbixhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9IiNlNjdlMjIiLHNpemU9MikrDQogIGdndGl0bGUoIlBvcHVsYXRpb24iKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwMz1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhIZWFsdGguZXhwZW5kaXR1cmUuLnByaXZhdGUuLi4ub2YuR0RQLixhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9IiMzNDk4ZGIiLHNpemU9MikrDQogIGdndGl0bGUoIkhlYWx0aCBFeHBlbmRpdHVyZSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnA0PWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKExhYm9yLmZvcmNlLi50b3RhbCxhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9IiMxYWJjOWMiLHNpemU9MikrDQogIGdndGl0bGUoIkxhYm9yIEZvcmNlIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KcDU9Z2dwbG90KGNvdW50cnlfZGF0YSxhZXMoVW5lbXBsb3ltZW50X1JhdGUsYXZnX2dkcCkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjMzQ0OTVlIixzaXplPTIpKw0KICBnZ3RpdGxlKCJVbmVtcGxveW1lbnQgUmF0ZSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnA2PWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKGF2Z19saWZlX2V4cGVjdGFuY3ksYXZnX2dkcCkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjZTc0YzNjIixzaXplPTIpKw0KICBnZ3RpdGxlKCJBdmVyYWdlIExpZmUgRXhwZWN0YW5jeSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmdyaWQuYXJyYW5nZShwMSxwMixwMyxwNCxwNSxwNikNCmBgYA0KDQojIyMqKkNoYW5nZSB0byBsb2dhcml0aG1pYyBzY2FsZSoqDQpgYGB7cn0NCnAxPWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKG5vX29mX2hvbGlkYXlzLGxvZyhhdmdfZ2RwKSkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSJ2aW9sZXRyZWQzIixzaXplPTIpKw0KICBnZ3RpdGxlKCJIb2xpZGF5cyIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnAyPWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKGxvZyhQb3B1bGF0aW9uKSxsb2coYXZnX2dkcCkpKSsNCiAgZ2VvbV9wb2ludChjb2xvcj0iI2U2N2UyMiIsc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiUG9wdWxhdGlvbiIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnAzPWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKEhlYWx0aC5leHBlbmRpdHVyZS4ucHJpdmF0ZS4uLi5vZi5HRFAuLGxvZyhhdmdfZ2RwKSkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjMzQ5OGRiIixzaXplPTIpKw0KICBnZ3RpdGxlKCJIZWFsdGggRXhwZW5kaXR1cmUiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwND1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhsb2coTGFib3IuZm9yY2UuLnRvdGFsKSxsb2coYXZnX2dkcCkpKSsNCiAgZ2VvbV9wb2ludChjb2xvcj0iIzFhYmM5YyIsc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiTGFib3IgRm9yY2UiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwNT1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhVbmVtcGxveW1lbnRfUmF0ZSxsb2coYXZnX2dkcCkpKSsNCiAgZ2VvbV9wb2ludChjb2xvcj0iIzM0NDk1ZSIsc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiVW5lbXBsb3ltZW50IFJhdGUiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwNj1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhhdmdfbGlmZV9leHBlY3RhbmN5LGxvZyhhdmdfZ2RwKSkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjZTc0YzNjIixzaXplPTIpKw0KICBnZ3RpdGxlKCJBdmVyYWdlIExpZmUgRXhwZWN0YW5jeSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmdyaWQuYXJyYW5nZShwMSxwMixwMyxwNCxwNSxwNikNCmBgYA0KDQojIyMqKkxldCdzIGNhcnJ5IG91dCBtb2RlbCBzZWxlY3Rpb24qKg0KYGBge3J9DQptb2RlbHMgPSBjKCJsbSIsInJmIiwicnBhcnQiLCJnYm0iKQ0KDQpzZXQuc2VlZCg3KQ0KbW9kZWxfc2VsZWN0aW9uID0gZnVuY3Rpb24obWV0aG9kX25hbWUpew0KICBpZihtZXRob2RfbmFtZT09InJmIil7DQogZ2FyYmFnZSA8LSBjYXB0dXJlLm91dHB1dCgNCiAgIA0KICAgbW9kZWwgPC10cmFpbih4PSB0cmFpblssYygyLDQ6OSldLCB5ID0gdHJhaW5bLDNdLCBtZXRob2QgPSBtZXRob2RfbmFtZSxpbXBvcnRhbmNlPVQpDQogICApDQogIH0NCiAgZWxzZXsNCiBnYXJiYWdlIDwtIGNhcHR1cmUub3V0cHV0KA0KICAgDQogICBtb2RlbCA8LXRyYWluKHg9IHRyYWluWyxjKDIsNDo5KV0sIHkgPSB0cmFpblssM10sIG1ldGhvZCA9IG1ldGhvZF9uYW1lKQ0KICAgKQ0KICAgIA0KICB9DQpyZXR1cm4obW9kZWwpDQoNCn0NClIyX21ldHJpYyA9IGZ1bmN0aW9uKG1vZGVsKXsNCiAgcHJlZFRlc3Q9cHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVzdCkNClIyID0gUjIocHJlZFRlc3QsdGVzdCRhdmdfZ2RwKQ0KcmV0dXJuKGMobW9kZWxbWyJtZXRob2QiXV0sUjIpKQ0KfQ0KbW9kZWxfcmVzdWx0cyA9IGxhcHBseShtb2RlbHMsbW9kZWxfc2VsZWN0aW9uKSANCm1vZGVsc19yMiA9IG1vZGVsX3Jlc3VsdHMlPiUNCiAgICAgICAgICAgICAgICAgICAgbGRwbHkoLixSMl9tZXRyaWMpDQpuYW1lcyhtb2RlbHNfcjIpID0gYygibW9kZWwiLCJyMiIpDQptb2RlbHNfcjIgJT4lDQogIG11dGF0ZShyMj1hcy5udW1lcmljKHIyKSkgJT4lDQogIGFycmFuZ2UoLXIyKQ0KYGBgDQoNCiMjIyoqUmFuZG9tRm9yZXN0IGRpZCBpdCBiZXN0LCBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgaW1wb3J0YW50IHZhcmlhYmxlcyoqDQpgYGB7cn0NCnZhckltcChtb2RlbF9yZXN1bHRzW1syXV0pW1siaW1wb3J0YW5jZSJdXSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBtdXRhdGUodmFyaWFibGVzPXJvdy5uYW1lcyguKSkgJT4lDQogIGdncGxvdCguLGFlcyh4PXJlb3JkZXIodmFyaWFibGVzLE92ZXJhbGwpLHk9IE92ZXJhbGwgKSkrDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLGZpbGw9IiMxNmEwODUiLGNvbG9yPSIjMTZhMDg1IikrDQogIGNvb3JkX2ZsaXAoKSsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgeGxhYigiVmFyaWFibGVzIikrDQogIHlsYWIoIldlaWdodGVkIEltcG9ydGFuY2UiKSsNCiAgZ2d0aXRsZSgiUmFuZG9tIEZvcmVzdCBWYXJpYWJsZSBJbXBvcnRhbmNlIikrDQogIGdlb21fdGV4dChhZXMobGFiZWw9cGFzdGUwKHJvdW5kKE92ZXJhbGwsMSksIiUiKSxoanVzdD1pZmVsc2UoT3ZlcmFsbDw5MCwtMC4xLDEpKSkNCmBgYA0KDQoNCg0K