Data Science Skills

Team Triple J: Jered Ataky, Zhouxin Shi, Irene Jacob

Project Overview:

In this project, our goal is to be able to answer to the question:

“Which are the most valued data science skills?”

As it is a group work and each member living in different time zone, we have established a great way of communication, code sharing, and documentation to enable us to be successful and efficient while working virtually together. The tools used and data source explored are described below:

Tools:

We are using Github for code sharing, and Google Cloud Platform (GCP) for data base and storage. In the other, Slack and Microsoft Teams are used for communication as well project documentation.

Data Source:

The data set we are working can be found in the link below:

https://www.kaggle.com/elroyggj/indeed-dataset-data-scientistanalystengineer https://www.linkedin.com

We also have it loaded as csv in our Github repository for project development:

(https://github.com/szx868/Project3)

ER Diagram

Libraries

library(tidyverse)
library(kableExtra)
library(rvest)
library(stringr)
library(xtable)
library(tm)
library(RMySQL)
library(PGRdup)
library(broom)
library(googleCloudStorageR)
library(readr)
library(cloudml)
library(plotly)
library(SnowballC)

Work process & responsabilities

Process <- c('Data Collection', 'Database Storage & Structure ',
             'Data Transformation (Cleaning & Tidying data)',
             'Data Analysis', 'Visualization',
             'Review & Summary','Conclusion and Presentation')

Team <- c('Zhouxin', 'Zhouxin & Jered', 'Jered & Irene', 
          'Jered, Zhouxin, Irene', 'Irene', 'Jered',
          'Jered, Zhouxin, Irene')

df_team <- data.frame(Process, Team)

names(df_team) <- c('Process', 'Team Members')

df_team %>%
  kbl(caption = "Work Process & Responsabilities") %>%
  kable_material(c("striped", "hover")) %>%
  row_spec(0, color = "indigo")
Work Process & Responsabilities
Process Team Members
Data Collection Zhouxin
Database Storage & Structure Zhouxin & Jered
Data Transformation (Cleaning & Tidying data) Jered & Irene
Data Analysis Jered, Zhouxin, Irene
Visualization Irene
Review & Summary Jered
Conclusion and Presentation Jered, Zhouxin, Irene

Approach

Data Acquisition & Storage

We target a data set in kaggle and decided to work on, on top of that data set, We created a database in MySQL in a set of normalized tables and link it to our GCP data base to allow each one to access it locally and make any change if necessary. Thus, data reside in both the GCP data base and storage we created.

Then, we extracted more data in linkedin by scraping it. This time, we scanned key words (Keys words were defined in a csv file in Github). and created an automation to feed the data base from r studio.

Since we had everything connected: local MySQL, GCP data base and storage, we decide to load the data directly from the cloud to start data transformation.

See Appendix in the end for details and codes for the entire process.

Data Transformation (Cleaning & Tidying)

The automation we created help us a lot to get data ready for tyding (missing values were removed in advance, see Appendix).

For the rest, we used tidyverse library and stringr libraries.

Data Analysis

Tidying data in advance help this section to be pretty forward, and we did two different analyses.

Extra & APPENDIX

Extra section includes an extra analysis, and the “Appendix” has codes used in data acquisition and storage

Load the data

# Load data from the cloud storage linked to GCP data base

raw_data <-read.csv("https://storage.googleapis.com/triplej_project3/Linkedin_dataset.csv")
# data insight

glimpse(raw_data)
## Rows: 1,000
## Columns: 5
## $ job_id       <int> 2414, 2415, 2416, 2417, 2418, 2419, 2420, 2421, 2422, ...
## $ job_url      <chr> " 0", " 1", " 2", " 3", " 4", " 5", " 6", " 7", " 8", ...
## $ job_site     <chr> "Indeed", "Indeed", "Indeed", "Indeed", "Indeed", "Ind...
## $ job_skill    <chr> "Data Visualization,Data Analysis,,Communication,Busin...
## $ job_location <chr> "MO ", "TX ", "OR ", "DC ", "TX ", "MD ", "NY ", "GA "...

Data Transformation

Cleaning

# Check for missing values

sum(is.na(raw_data))
## [1] 0
data <- raw_data

Tidying & Cleaning

# Convert to string the variable job_skill & unlist


ds_skill3 <- gsub("\\s+","", data$job_skill ) # Clear the comma

ds_skill3 <-unlist(ds_skill3) # unlist the string 

ds_skill3 <- unlist(str_extract_all(ds_skill3, "\\w+[a-z]" )) # extract skills
# New data frame

ds_skill <- data.frame(ds_skill3)

# summarise data frame

ds_skills <- ds_skill %>%
  group_by(ds_skill3) %>%
  summarise(count_skills = n())
## `summarise()` ungrouping output (override with `.groups` argument)
# save df

ds_final <- ds_skills
# Rename columns and order desc per counts

names(ds_final) <- c('ds_top_skills', 'total_count')

ds_final <- ds_final %>%
  arrange(desc(total_count))

ds_final <- ds_final %>%
  mutate(percent_count= round((total_count / sum(total_count))*100, 1))

head(ds_final)
## # A tibble: 6 x 3
##   ds_top_skills   total_count percent_count
##   <chr>                 <int>         <dbl>
## 1 Python                  730          11.8
## 2 Businesssense           631          10.2
## 3 MachineLearning         596           9.6
## 4 Statistics              507           8.2
## 5 DataAnalysis            500           8.1
## 6 Modeling                500           8.1

Most valued data science skills table

df <- ds_final

df %>%
  kbl(caption = "Most data science value skills") %>%
  kable_material(c("striped", "hover")) %>%
  row_spec(0, color = "indigo")
Most data science value skills
ds_top_skills total_count percent_count
Python 730 11.8
Businesssense 631 10.2
MachineLearning 596 9.6
Statistics 507 8.2
DataAnalysis 500 8.1
Modeling 500 8.1
Communication 461 7.4
Mathematics 367 5.9
Programming 334 5.4
RProgramming 317 5.1
DataVisualization 219 3.5
ProblemSolving 206 3.3
QuantitativeAnalysis 167 2.7
TensorFlow 121 2.0
Collaboration 110 1.8
Hadoop 110 1.8
InterpersonalSkills 85 1.4
CriticalThinking 81 1.3
No 75 1.2
ActiveLearning 25 0.4
CreativeThinking 25 0.4
Probability 14 0.2
Cloudera 10 0.2
Matplotlib 9 0.1
Debugging 3 0.0
Judgement 1 0.0

Data Analysis

Most valued data science skills plot

ds_final %>%
  
  ggplot(aes(reorder(ds_top_skills, percent_count), percent_count)) +
  
  geom_col(aes(fill = percent_count)) +
  
  scale_fill_gradient2(low = "red",
                       high = "blue",
                       midpoint = median(ds_final$percent_count)) +
  
  
coord_flip() +

  
  labs(title = "Most data science value skills",
       x = "ds_top_skills")

10 top skills

top_n(ds_final, n = 10) %>%
  
  ggplot(aes(reorder(ds_top_skills, percent_count), percent_count)) +
  
  geom_col(aes(fill = percent_count)) +
  
  geom_text(aes(label = percent_count)) +
  
  scale_fill_gradient2(low = "yellow",
                       high = "magenta",
                       midpoint = median(ds_final$percent_count)) +
  
  
coord_polar() +

  
  labs(title = "Top 10 valued data science skills (in percent)", x = NULL, y = NULL)
## Selecting by percent_count

Extra

Data Science jobs by website

data_site <- data %>%
  group_by(job_site) %>%
  summarise( n_jobs = n())
## `summarise()` ungrouping output (override with `.groups` argument)
data_site
## # A tibble: 2 x 2
##   job_site n_jobs
##   <chr>     <int>
## 1 Indeed      500
## 2 Linkedin    500
pc <- ggplot(data_site, aes  (x = "", y = n_jobs, fill = job_site)) +
  
  geom_bar(width = 1, stat = "identity")

pie <- pc + coord_polar("y", start = 0)

pie

Data Science jobs by state

data_state <- data %>%
  group_by(job_location) %>%
  filter(job_location != "REMOTE", job_location != " ") %>%
  summarise( n_jobs = n())
## `summarise()` ungrouping output (override with `.groups` argument)
data_state <- data_state[order(-data_state$n_jobs), ]
data_state <- data_state[-c(10), ]


data_state %>%
  kbl(caption = "Data Science jobs by state") %>%
  kable_material(c("striped", "hover")) %>%
  row_spec(0, color = "indigo")
Data Science jobs by state
job_location n_jobs
NY 173
CA 159
MA 81
WA 80
IL 62
NJ 45
TX 43
VA 41
MI 40
GA 27
TN 22
CO 19
FL 16
PA 16
MD 15
MO 13
AZ 11
MN 11
OH 11
NC 10
DC 8
AR 7
IN 7
OR 7
NV 6
NE 5
CT 4
AL 3
KS 3
SC 3
WI 3
HI 2
IA 2
KY 2
LA 2
ME 2
UT 2
DE 1
ID 1
NM 1
RI 1

Plot 10 top state in data science jobs

# Top 10 plot

top_n(data_state, n = 10) %>%
  
  ggplot(aes(reorder(job_location, n_jobs), n_jobs)) +
  
  geom_col(aes(fill = n_jobs)) +
  
  scale_fill_gradient2(low = "yellow",
                       high = "purple",
                       midpoint = median(ds_final$percent_count)) +
  
  
coord_flip() +

  
  labs(title = "Data scientist jobs per state (top 10)",
       x = "State")
## Selecting by n_jobs

A bit more extra: Mapping data science job in US

# Add missing states
dss <- data_state

job_location <- c('AK', 'MS', 'MT', 'ND', 'NH', 'OK', 'SD', 'VT', 'WV', 'WY')
n_jobs  <- c(0,0,0,0,0,0,0,0,0,0)

ds <- data.frame(job_location, n_jobs)

dff <- rbind(dss, ds)

names(dff) <- c('State', 'count_job')


# Export to csv: save it to Github and export it later for plot 
# (personal reference)

write.csv(dff, "jobs.csv", row.names = FALSE )
# Import the file back

dd <- read.csv("https://raw.githubusercontent.com/szx868/Project3/master/jobs.csv")

# Plot the map

w <- list(color = toRGB("white"), width = 2)

g <- list(
  scope = 'usa',
  projection = list(type = 'albers usa'),
  showlakes = TRUE,
  lakecolor = toRGB('white')
)

p <- plot_geo(dd, locationmode = 'USA-states') %>%
  add_trace(
    z = ~count_job, locations = ~State,
    color = ~count_job, colors = 'Blues'
  ) %>%
  colorbar(title = "Number of jobs") %>%
  layout(
    title = 'Data science Jobs by State',
    geo = g
  )
## Warning: `arrange_()` is deprecated as of dplyr 0.7.0.
## Please use `arrange()` instead.
## See vignette('programming') for more help
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_warnings()` to see where this warning was generated.
p

Findings

Data science is a field that required many skills. Note that hard skills ar not enough to be a data scientist. As the “Top 10 valued data science skills” in data analysis section shows, the top 10 most value data science skills are not only hard skills, but soft skills as well such as communication and business sens.

Appendix

Below is the code for data collection and storage explained in “Approach”

We leave it purposely in not “r code” form to avoid running it.

library(RMariaDB)
# The connection method below uses a password stored in a variable.
# To use this set localuserpassword="The password of newspaper_search_results_user"

storiesDb <- dbConnect(RMariaDB::MariaDB(), user='root', password='Ja07071990', dbname='ds_skills', host='23.251.154.21')
# dbListTables(storiesDb)
query<- "SELECT * FROM indeed_job_dataset7;"
rsInsert <- dbSendQuery(storiesDb, query)
Indeed <- dbFetch(rsInsert) # extract data in chunks of 10 rows
# dbHasCompleted(rsInsert)
Indeed

for (i in 1:500){
 
  
  job_loc <- Indeed[i,4]
  print(job_loc)
  job_desc <- Indeed[i,1]
  job_url <- Indeed[i,3]
}
for (i in 1:500){
 
  
  job_loc <- Indeed[i,4]
  job_desc <- Indeed[i,1]
  job_url <- Indeed[i,3]
  job_skill <- '' 
  if(str_length(str_trim(job_loc)) > 2 ){
      job_loc <- 'NA'
    }
  for(x in 1:nrow(general.skill)){
    if(general.skill[x,1]!=''){
        if(str_detect(tolower(job_desc),tolower(general.skill[x,2])) == TRUE){
          job_skill <- paste(job_skill,general.skill[x,1],sep=", ")
        }
    }
  }
    for(x in 1:nrow(general.skill)){
      if(general.skill[x,3]!=''){
        if(str_detect(tolower(job_desc),tolower(general.skill[x,4])) == TRUE){
          job_skill <- paste(general.skill[x,3],job_skill, sep=", ")
        }
      }
  }
  query <- "INSERT INTO `Linkedin_dataset` (`job_url`,`job_site`,`job_skill`,`job_location`)VALUES('"
  value <- c(str_trim(job_url),'Indeed',str_trim(job_skill),str_trim(job_loc))
  value <- paste(value,collapse = "','")
  query <- paste(query,value )
  query <- paste(query, "');")
  rsInsert <- dbSendQuery(storiesDb, query)
  dbClearResult(rsInsert)
  #df <- rbind(df, data.frame(Skill =  job_skill , Loc = job_loc2[2] ))
  
}
dbClearResult(rsInsert)
dbDisconnect(storiesDb)
library(rvest)
library(stringr)
index <- 0
Linkedin<-matrix(nrow=0,ncol=1)
for(i in 1:30){
  start <- index*25 
  index <- index + 1
  strlink =paste("https://www.linkedin.com/jobs/search/?geoId=103644278&keywords=data%20scientist&location=United%20States&start=", toString(start),sep="")

  strhtml <- read_html(strlink)%>% html_nodes(".result-card__full-card-link") %>%html_attr('href')
  tempmatrix <- as.matrix(strhtml)
  Linkedin <- rbind(Linkedin,tempmatrix)
  
}
#Linkedin %>%
#html_nodes(".full-width") %>%
#html_text()

Linkedin
  temphtml <- read_html(Linkedin[2])
  job_desc <- temphtml %>%
  html_nodes(".description__text") %>%
  html_text()
  print(job_desc)
  temphtml <- read_html(Linkedin[1])
  job_desc <- temphtml %>%
  html_nodes(".sub-nav-cta__sub-text-container") %>%
  html_text()
  print(job_desc)
length(Linkedin)
general.skill <- read.csv("https://raw.githubusercontent.com/szx868/Project3/master/data_skills.csv",header=T)

names(general.skill) <- c('Soft', 'Reg.Soft','Hard','Reg.Hard')
general.skill
str(general.skill)
nrow(general.skill)
for(i in nrow(general.skill)){
  print(general.skill[2,1])
}
tempstr <- "asdfaCreative adfas Thinkingfsa "
str_detect(tempstr,pattern="creative.*Thinking")
nrow(Linkedin)
for (i in 1:nrow(Linkedin)){
  print(Linkedin[i])
}
library(RMariaDB)
# The connection method below uses a password stored in a variable.
# To use this set localuserpassword="The password of newspaper_search_results_user"

storiesDb <- dbConnect(RMariaDB::MariaDB(), user='root', password='Ja07071990', dbname='ds_skills', host='23.251.154.21')
# dbListTables(storiesDb)



# dbHasCompleted(rsInsert)


df = data.frame(Skill = character(), Loc = character())
for (i in 1:500){
 temphtml <- read_html(Linkedin[i])

  job_desc <- temphtml %>%
  html_nodes(".description__text") %>%
  html_text()
  print(job_desc)
  job_loc <- temphtml %>%
  html_nodes(".sub-nav-cta__sub-text-container") %>%
  html_text()
  
  job_loc2 <- unlist(strsplit(job_loc, ","))

  print(str_length(str_trim(job_loc2[2])))
  if(length(job_loc2)>=2){
    if(str_length(str_trim(job_loc2[2])) > 2 ){
      job_loc2[2] <- 'NA'
    }
  }
  print(job_loc2[2])
  
  job_skill <- '' 
  job_url <- Linkedin[i]
  for(x in 1:nrow(general.skill)){
    if(general.skill[x,1]!=''){
        if(str_detect(tolower(job_desc),tolower(general.skill[x,2])) == TRUE){
          job_skill <- paste(job_skill,general.skill[x,1],sep=", ")
        }
    }
  }
    for(x in 1:nrow(general.skill)){
      if(general.skill[x,3]!=''){
        if(str_detect(tolower(job_desc),tolower(general.skill[x,4])) == TRUE){
          job_skill <- paste(general.skill[x,3],job_skill, sep=", ")
        }
      }
  }
  query <- "INSERT INTO `Linkedin_dataset` (`job_url`,`job_site`,`job_skill`,`job_location`)VALUES('"
  value <- c(str_trim(job_url),'Linkedin',str_trim(job_skill),str_trim(job_loc2[2]))
  value <- paste(value,collapse = "','")
  query <- paste(query,value )
  query <- paste(query, "');")
  print(job_loc2[2])
  rsInsert <- dbSendQuery(storiesDb, query)
  dbClearResult(rsInsert)
  #df <- rbind(df, data.frame(Skill =  job_skill , Loc = job_loc2[2] ))
  
}
dbClearResult(rsInsert)
dbDisconnect(storiesDb)
docs <- df$x
ps_dtm <- VectorSource(docs) %>%
  VCorpus() %>%
  DocumentTermMatrix(control = list(removePunctuation = TRUE,
                                    removeNumbers = TRUE,
                                    stopwords = TRUE))
inspect(ps_dtm)
ps_tidy <- tidy(ps_dtm)
ps_tidy
LS0tDQp0aXRsZTogIkRBVEEgNjA2IFByb2plY3QgMyINCmF1dGhvcjogIiBUZWFtIFRyaXBsZSBKOiBKZXJlZCBBdGFreSwgWmhvdXhpbiBTaGksIElyZW5lIEphY29iIg0KZGF0ZTogMjAyMC0xMC0xNw0Kb3V0cHV0OiANCiAgb3BlbmludHJvOjpsYWJfcmVwb3J0OiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIERhdGEgU2NpZW5jZSBTa2lsbHMNCg0KKipUZWFtIFRyaXBsZSBKOioqIEplcmVkIEF0YWt5LCBaaG91eGluIFNoaSwgSXJlbmUgSmFjb2INCg0KIyMgUHJvamVjdCBPdmVydmlldzoNCg0KSW4gdGhpcyBwcm9qZWN0LCBvdXIgZ29hbCBpcyB0byBiZSBhYmxlIHRvIGFuc3dlciB0byB0aGUgcXVlc3Rpb246DQoNCuKAnFdoaWNoIGFyZSB0aGUgbW9zdCB2YWx1ZWQgZGF0YSBzY2llbmNlIHNraWxscz/igJ0gDQoNCkFzIGl0IGlzIGEgZ3JvdXAgd29yayBhbmQgZWFjaCBtZW1iZXIgbGl2aW5nIGluIGRpZmZlcmVudCB0aW1lIHpvbmUsIHdlIGhhdmUgDQplc3RhYmxpc2hlZCBhIGdyZWF0IHdheSBvZiBjb21tdW5pY2F0aW9uLCBjb2RlIHNoYXJpbmcsIGFuZCBkb2N1bWVudGF0aW9uIHRvDQplbmFibGUgdXMgdG8gYmUgc3VjY2Vzc2Z1bCBhbmQgZWZmaWNpZW50IHdoaWxlIHdvcmtpbmcgdmlydHVhbGx5IHRvZ2V0aGVyLg0KVGhlIHRvb2xzIHVzZWQgYW5kIGRhdGEgc291cmNlIGV4cGxvcmVkIGFyZSBkZXNjcmliZWQgYmVsb3c6IA0KDQojIyBUb29sczoNCg0KV2UgYXJlIHVzaW5nIEdpdGh1YiBmb3IgY29kZSBzaGFyaW5nLCBhbmQgR29vZ2xlIENsb3VkIFBsYXRmb3JtIChHQ1ApDQpmb3IgZGF0YSBiYXNlIGFuZCBzdG9yYWdlLg0KSW4gdGhlIG90aGVyLCBTbGFjayBhbmQgTWljcm9zb2Z0IFRlYW1zIGFyZSB1c2VkIGZvciBjb21tdW5pY2F0aW9uIGFzIHdlbGwNCnByb2plY3QgZG9jdW1lbnRhdGlvbi4NCg0KIyMgRGF0YSBTb3VyY2U6DQoNClRoZSBkYXRhIHNldCB3ZSBhcmUgd29ya2luZyBjYW4gYmUgZm91bmQgaW4gdGhlIGxpbmsgYmVsb3c6DQoNCmh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZWxyb3lnZ2ovaW5kZWVkLWRhdGFzZXQtZGF0YS1zY2llbnRpc3RhbmFseXN0ZW5naW5lZXINCmh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbQ0KDQoNCldlIGFsc28gaGF2ZSBpdCBsb2FkZWQgYXMgY3N2IGluIG91ciBHaXRodWIgcmVwb3NpdG9yeSBmb3IgcHJvamVjdCBkZXZlbG9wbWVudDoNCg0KKGh0dHBzOi8vZ2l0aHViLmNvbS9zeng4NjgvUHJvamVjdDMpDQoNCg0KIyMgRVIgRGlhZ3JhbQ0KIVtdKEVudGl0eV9SZWxhdGlvbnNoaXBfRGlhZ3JhbS5qcGcgIkltYWdlIFRpdGxlIikNCg0KDQojIyBMaWJyYXJpZXMNCg0KYGBge3IgbG9hZC1wYWNrYWdlcywgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoeHRhYmxlKQ0KbGlicmFyeSh0bSkNCmxpYnJhcnkoUk15U1FMKQ0KbGlicmFyeShQR1JkdXApDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShnb29nbGVDbG91ZFN0b3JhZ2VSKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoY2xvdWRtbCkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShTbm93YmFsbEMpDQoNCmBgYA0KDQoNCiMjIFdvcmsgcHJvY2VzcyAmIHJlc3BvbnNhYmlsaXRpZXMNCg0KYGBge3J9DQoNClByb2Nlc3MgPC0gYygnRGF0YSBDb2xsZWN0aW9uJywgJ0RhdGFiYXNlIFN0b3JhZ2UgJiBTdHJ1Y3R1cmUgJywNCiAgICAgICAgICAgICAnRGF0YSBUcmFuc2Zvcm1hdGlvbiAoQ2xlYW5pbmcgJiBUaWR5aW5nIGRhdGEpJywNCiAgICAgICAgICAgICAnRGF0YSBBbmFseXNpcycsICdWaXN1YWxpemF0aW9uJywNCiAgICAgICAgICAgICAnUmV2aWV3ICYgU3VtbWFyeScsJ0NvbmNsdXNpb24gYW5kIFByZXNlbnRhdGlvbicpDQoNClRlYW0gPC0gYygnWmhvdXhpbicsICdaaG91eGluICYgSmVyZWQnLCAnSmVyZWQgJiBJcmVuZScsIA0KICAgICAgICAgICdKZXJlZCwgWmhvdXhpbiwgSXJlbmUnLCAnSXJlbmUnLCAnSmVyZWQnLA0KICAgICAgICAgICdKZXJlZCwgWmhvdXhpbiwgSXJlbmUnKQ0KDQpkZl90ZWFtIDwtIGRhdGEuZnJhbWUoUHJvY2VzcywgVGVhbSkNCg0KbmFtZXMoZGZfdGVhbSkgPC0gYygnUHJvY2VzcycsICdUZWFtIE1lbWJlcnMnKQ0KDQpkZl90ZWFtICU+JQ0KICBrYmwoY2FwdGlvbiA9ICJXb3JrIFByb2Nlc3MgJiBSZXNwb25zYWJpbGl0aWVzIikgJT4lDQogIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiKSkgJT4lDQogIHJvd19zcGVjKDAsIGNvbG9yID0gImluZGlnbyIpDQoNCg0KYGBgDQoNCiMjIEFwcHJvYWNoDQoNCg0KDQo8c3R5bGU+DQpkaXYuYXF1YW1hcmluZSB7IGJhY2tncm91bmQtY29sb3I6IzdmZmZkNDsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogNXB4O30NCjwvc3R5bGU+DQo8ZGl2IGNsYXNzID0gImFxdWFtYXJpbmUiPg0KDQoqKkRhdGEgQWNxdWlzaXRpb24gJiBTdG9yYWdlKioNCg0KV2UgdGFyZ2V0IGEgZGF0YSBzZXQgaW4ga2FnZ2xlIGFuZCBkZWNpZGVkIHRvIHdvcmsgb24sIG9uIHRvcCBvZiB0aGF0IGRhdGEgc2V0LCANCldlIGNyZWF0ZWQgYSBkYXRhYmFzZSBpbiBNeVNRTCBpbiBhIHNldCBvZiBub3JtYWxpemVkIHRhYmxlcyBhbmQgbGluayANCml0IHRvIG91ciBHQ1AgZGF0YSBiYXNlIHRvIGFsbG93IGVhY2ggb25lIHRvIGFjY2VzcyBpdCBsb2NhbGx5IGFuZCBtYWtlIGFueSBjaGFuZ2UgaWYgbmVjZXNzYXJ5Lg0KVGh1cywgZGF0YSByZXNpZGUgaW4gYm90aCB0aGUgR0NQIGRhdGEgYmFzZSBhbmQgc3RvcmFnZSB3ZSBjcmVhdGVkLg0KDQpUaGVuLCB3ZSBleHRyYWN0ZWQgbW9yZSBkYXRhIGluIGxpbmtlZGluIGJ5IHNjcmFwaW5nIGl0LiBUaGlzIHRpbWUsIHdlIHNjYW5uZWQga2V5IHdvcmRzDQooS2V5cyB3b3JkcyB3ZXJlIGRlZmluZWQgaW4gYSBjc3YgZmlsZSBpbiBHaXRodWIpLg0KYW5kIGNyZWF0ZWQgYW4gYXV0b21hdGlvbiB0byBmZWVkIHRoZSBkYXRhIGJhc2UgZnJvbSByIHN0dWRpby4NCg0KDQpTaW5jZSB3ZSBoYWQgZXZlcnl0aGluZyBjb25uZWN0ZWQ6IGxvY2FsIE15U1FMLCBHQ1AgZGF0YSBiYXNlIGFuZCBzdG9yYWdlLA0Kd2UgZGVjaWRlIHRvIGxvYWQgdGhlIGRhdGEgZGlyZWN0bHkgZnJvbSB0aGUgY2xvdWQgdG8gc3RhcnQgZGF0YSB0cmFuc2Zvcm1hdGlvbi4NCg0KDQoNClNlZSBBcHBlbmRpeCBpbiB0aGUgZW5kIGZvciBkZXRhaWxzIGFuZCBjb2RlcyBmb3IgdGhlIGVudGlyZSBwcm9jZXNzLg0KDQoNCioqRGF0YSBUcmFuc2Zvcm1hdGlvbiAoQ2xlYW5pbmcgJiAgVGlkeWluZykqKg0KDQoNClRoZSBhdXRvbWF0aW9uIHdlIGNyZWF0ZWQgaGVscCB1cyBhIGxvdCB0byBnZXQgZGF0YSByZWFkeSBmb3IgdHlkaW5nDQoobWlzc2luZyB2YWx1ZXMgd2VyZSByZW1vdmVkIGluIGFkdmFuY2UsIHNlZSBBcHBlbmRpeCkuDQoNCkZvciB0aGUgcmVzdCwgd2UgdXNlZCB0aWR5dmVyc2UgbGlicmFyeSBhbmQgc3RyaW5nciBsaWJyYXJpZXMuDQoNCg0KKipEYXRhIEFuYWx5c2lzKioNCg0KVGlkeWluZyBkYXRhIGluIGFkdmFuY2UgaGVscCB0aGlzIHNlY3Rpb24gdG8gYmUgcHJldHR5IGZvcndhcmQsDQphbmQgd2UgZGlkIHR3byBkaWZmZXJlbnQgYW5hbHlzZXMuIA0KDQoqKkV4dHJhICYgQVBQRU5ESVgqKg0KDQoNCkV4dHJhIHNlY3Rpb24gaW5jbHVkZXMgYW4gZXh0cmEgYW5hbHlzaXMsDQphbmQgdGhlICJBcHBlbmRpeCIgaGFzIGNvZGVzIHVzZWQgaW4gZGF0YSBhY3F1aXNpdGlvbiBhbmQgc3RvcmFnZQ0KDQoNCjwvZGl2PiBcaGZpbGxcYnJlYWsNCg0KDQoNCiMjIExvYWQgdGhlIGRhdGENCg0KYGBge3J9DQojIExvYWQgZGF0YSBmcm9tIHRoZSBjbG91ZCBzdG9yYWdlIGxpbmtlZCB0byBHQ1AgZGF0YSBiYXNlDQoNCnJhd19kYXRhIDwtcmVhZC5jc3YoImh0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS90cmlwbGVqX3Byb2plY3QzL0xpbmtlZGluX2RhdGFzZXQuY3N2IikNCg0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KIyBkYXRhIGluc2lnaHQNCg0KZ2xpbXBzZShyYXdfZGF0YSkNCg0KDQpgYGANCg0KDQojIyBEYXRhIFRyYW5zZm9ybWF0aW9uDQoNCg0KIyMjIENsZWFuaW5nDQoNCmBgYHtyfQ0KDQojIENoZWNrIGZvciBtaXNzaW5nIHZhbHVlcw0KDQpzdW0oaXMubmEocmF3X2RhdGEpKQ0KDQpkYXRhIDwtIHJhd19kYXRhDQoNCmBgYA0KIyMjIFRpZHlpbmcgJiBDbGVhbmluZw0KDQoNCmBgYHtyfQ0KDQojIENvbnZlcnQgdG8gc3RyaW5nIHRoZSB2YXJpYWJsZSBqb2Jfc2tpbGwgJiB1bmxpc3QNCg0KDQpkc19za2lsbDMgPC0gZ3N1YigiXFxzKyIsIiIsIGRhdGEkam9iX3NraWxsICkgIyBDbGVhciB0aGUgY29tbWENCg0KZHNfc2tpbGwzIDwtdW5saXN0KGRzX3NraWxsMykgIyB1bmxpc3QgdGhlIHN0cmluZyANCg0KZHNfc2tpbGwzIDwtIHVubGlzdChzdHJfZXh0cmFjdF9hbGwoZHNfc2tpbGwzLCAiXFx3K1thLXpdIiApKSAjIGV4dHJhY3Qgc2tpbGxzDQoNCg0KYGBgDQoNCmBgYHtyfQ0KIyBOZXcgZGF0YSBmcmFtZQ0KDQpkc19za2lsbCA8LSBkYXRhLmZyYW1lKGRzX3NraWxsMykNCg0KIyBzdW1tYXJpc2UgZGF0YSBmcmFtZQ0KDQpkc19za2lsbHMgPC0gZHNfc2tpbGwgJT4lDQogIGdyb3VwX2J5KGRzX3NraWxsMykgJT4lDQogIHN1bW1hcmlzZShjb3VudF9za2lsbHMgPSBuKCkpDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBzYXZlIGRmDQoNCmRzX2ZpbmFsIDwtIGRzX3NraWxscw0KDQpgYGANCmBgYHtyfQ0KDQojIFJlbmFtZSBjb2x1bW5zIGFuZCBvcmRlciBkZXNjIHBlciBjb3VudHMNCg0KbmFtZXMoZHNfZmluYWwpIDwtIGMoJ2RzX3RvcF9za2lsbHMnLCAndG90YWxfY291bnQnKQ0KDQpkc19maW5hbCA8LSBkc19maW5hbCAlPiUNCiAgYXJyYW5nZShkZXNjKHRvdGFsX2NvdW50KSkNCg0KZHNfZmluYWwgPC0gZHNfZmluYWwgJT4lDQogIG11dGF0ZShwZXJjZW50X2NvdW50PSByb3VuZCgodG90YWxfY291bnQgLyBzdW0odG90YWxfY291bnQpKSoxMDAsIDEpKQ0KDQpoZWFkKGRzX2ZpbmFsKQ0KYGBgDQoNCg0KIyMjIE1vc3QgdmFsdWVkIGRhdGEgc2NpZW5jZSBza2lsbHMgdGFibGUNCg0KYGBge3J9DQoNCmRmIDwtIGRzX2ZpbmFsDQoNCmRmICU+JQ0KICBrYmwoY2FwdGlvbiA9ICJNb3N0IGRhdGEgc2NpZW5jZSB2YWx1ZSBza2lsbHMiKSAlPiUNCiAga2FibGVfbWF0ZXJpYWwoYygic3RyaXBlZCIsICJob3ZlciIpKSAlPiUNCiAgcm93X3NwZWMoMCwgY29sb3IgPSAiaW5kaWdvIikNCg0KYGBgDQoNCg0KIyMgRGF0YSBBbmFseXNpcw0KDQoNCiMjIyBNb3N0IHZhbHVlZCBkYXRhIHNjaWVuY2Ugc2tpbGxzIHBsb3QNCg0KDQpgYGB7cn0NCg0KZHNfZmluYWwgJT4lDQogIA0KICBnZ3Bsb3QoYWVzKHJlb3JkZXIoZHNfdG9wX3NraWxscywgcGVyY2VudF9jb3VudCksIHBlcmNlbnRfY291bnQpKSArDQogIA0KICBnZW9tX2NvbChhZXMoZmlsbCA9IHBlcmNlbnRfY291bnQpKSArDQogIA0KICBzY2FsZV9maWxsX2dyYWRpZW50Mihsb3cgPSAicmVkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgaGlnaCA9ICJibHVlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSBtZWRpYW4oZHNfZmluYWwkcGVyY2VudF9jb3VudCkpICsNCiAgDQogIA0KY29vcmRfZmxpcCgpICsNCg0KICANCiAgbGFicyh0aXRsZSA9ICJNb3N0IGRhdGEgc2NpZW5jZSB2YWx1ZSBza2lsbHMiLA0KICAgICAgIHggPSAiZHNfdG9wX3NraWxscyIpDQogIA0KDQoNCg0KYGBgDQoNCg0KDQojIyMgMTAgdG9wIHNraWxscw0KDQoNCmBgYHtyfQ0KdG9wX24oZHNfZmluYWwsIG4gPSAxMCkgJT4lDQogIA0KICBnZ3Bsb3QoYWVzKHJlb3JkZXIoZHNfdG9wX3NraWxscywgcGVyY2VudF9jb3VudCksIHBlcmNlbnRfY291bnQpKSArDQogIA0KICBnZW9tX2NvbChhZXMoZmlsbCA9IHBlcmNlbnRfY291bnQpKSArDQogIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcGVyY2VudF9jb3VudCkpICsNCiAgDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdyA9ICJ5ZWxsb3ciLA0KICAgICAgICAgICAgICAgICAgICAgICBoaWdoID0gIm1hZ2VudGEiLA0KICAgICAgICAgICAgICAgICAgICAgICBtaWRwb2ludCA9IG1lZGlhbihkc19maW5hbCRwZXJjZW50X2NvdW50KSkgKw0KICANCiAgDQpjb29yZF9wb2xhcigpICsNCg0KICANCiAgbGFicyh0aXRsZSA9ICJUb3AgMTAgdmFsdWVkIGRhdGEgc2NpZW5jZSBza2lsbHMgKGluIHBlcmNlbnQpIiwgeCA9IE5VTEwsIHkgPSBOVUxMKQ0KICANCg0KYGBgDQoNCiMjIEV4dHJhDQoNCg0KIyMjIERhdGEgU2NpZW5jZSBqb2JzIGJ5IHdlYnNpdGUNCg0KYGBge3J9DQoNCmRhdGFfc2l0ZSA8LSBkYXRhICU+JQ0KICBncm91cF9ieShqb2Jfc2l0ZSkgJT4lDQogIHN1bW1hcmlzZSggbl9qb2JzID0gbigpKQ0KZGF0YV9zaXRlDQoNCnBjIDwtIGdncGxvdChkYXRhX3NpdGUsIGFlcyAgKHggPSAiIiwgeSA9IG5fam9icywgZmlsbCA9IGpvYl9zaXRlKSkgKw0KICANCiAgZ2VvbV9iYXIod2lkdGggPSAxLCBzdGF0ID0gImlkZW50aXR5IikNCg0KcGllIDwtIHBjICsgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApDQoNCnBpZQ0KDQpgYGANCg0KIyMjIERhdGEgU2NpZW5jZSBqb2JzIGJ5IHN0YXRlDQoNCg0KYGBge3J9DQoNCmRhdGFfc3RhdGUgPC0gZGF0YSAlPiUNCiAgZ3JvdXBfYnkoam9iX2xvY2F0aW9uKSAlPiUNCiAgZmlsdGVyKGpvYl9sb2NhdGlvbiAhPSAiUkVNT1RFIiwgam9iX2xvY2F0aW9uICE9ICIgIikgJT4lDQogIHN1bW1hcmlzZSggbl9qb2JzID0gbigpKQ0KDQpkYXRhX3N0YXRlIDwtIGRhdGFfc3RhdGVbb3JkZXIoLWRhdGFfc3RhdGUkbl9qb2JzKSwgXQ0KZGF0YV9zdGF0ZSA8LSBkYXRhX3N0YXRlWy1jKDEwKSwgXQ0KDQoNCmRhdGFfc3RhdGUgJT4lDQogIGtibChjYXB0aW9uID0gIkRhdGEgU2NpZW5jZSBqb2JzIGJ5IHN0YXRlIikgJT4lDQogIGthYmxlX21hdGVyaWFsKGMoInN0cmlwZWQiLCAiaG92ZXIiKSkgJT4lDQogIHJvd19zcGVjKDAsIGNvbG9yID0gImluZGlnbyIpDQoNCmBgYA0KDQojIyMjIFBsb3QgMTAgdG9wIHN0YXRlIGluIGRhdGEgc2NpZW5jZSBqb2JzDQoNCg0KYGBge3J9DQoNCiMgVG9wIDEwIHBsb3QNCg0KdG9wX24oZGF0YV9zdGF0ZSwgbiA9IDEwKSAlPiUNCiAgDQogIGdncGxvdChhZXMocmVvcmRlcihqb2JfbG9jYXRpb24sIG5fam9icyksIG5fam9icykpICsNCiAgDQogIGdlb21fY29sKGFlcyhmaWxsID0gbl9qb2JzKSkgKw0KICANCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gInllbGxvdyIsDQogICAgICAgICAgICAgICAgICAgICAgIGhpZ2ggPSAicHVycGxlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgbWlkcG9pbnQgPSBtZWRpYW4oZHNfZmluYWwkcGVyY2VudF9jb3VudCkpICsNCiAgDQogIA0KY29vcmRfZmxpcCgpICsNCg0KICANCiAgbGFicyh0aXRsZSA9ICJEYXRhIHNjaWVudGlzdCBqb2JzIHBlciBzdGF0ZSAodG9wIDEwKSIsDQogICAgICAgeCA9ICJTdGF0ZSIpDQogIA0KDQoNCg0KYGBgDQoNCg0KDQojIyMjIEEgYml0IG1vcmUgZXh0cmE6IE1hcHBpbmcgZGF0YSBzY2llbmNlIGpvYiBpbiBVUw0KDQpgYGB7cn0NCg0KIyBBZGQgbWlzc2luZyBzdGF0ZXMNCmRzcyA8LSBkYXRhX3N0YXRlDQoNCmpvYl9sb2NhdGlvbiA8LSBjKCdBSycsICdNUycsICdNVCcsICdORCcsICdOSCcsICdPSycsICdTRCcsICdWVCcsICdXVicsICdXWScpDQpuX2pvYnMgIDwtIGMoMCwwLDAsMCwwLDAsMCwwLDAsMCkNCg0KZHMgPC0gZGF0YS5mcmFtZShqb2JfbG9jYXRpb24sIG5fam9icykNCg0KZGZmIDwtIHJiaW5kKGRzcywgZHMpDQoNCm5hbWVzKGRmZikgPC0gYygnU3RhdGUnLCAnY291bnRfam9iJykNCg0KDQojIEV4cG9ydCB0byBjc3Y6IHNhdmUgaXQgdG8gR2l0aHViIGFuZCBleHBvcnQgaXQgbGF0ZXIgZm9yIHBsb3QgDQojIChwZXJzb25hbCByZWZlcmVuY2UpDQoNCndyaXRlLmNzdihkZmYsICJqb2JzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFICkNCg0KYGBgDQoNCg0KDQpgYGB7cn0NCg0KIyBJbXBvcnQgdGhlIGZpbGUgYmFjaw0KDQpkZCA8LSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N6eDg2OC9Qcm9qZWN0My9tYXN0ZXIvam9icy5jc3YiKQ0KDQojIFBsb3QgdGhlIG1hcA0KDQp3IDwtIGxpc3QoY29sb3IgPSB0b1JHQigid2hpdGUiKSwgd2lkdGggPSAyKQ0KDQpnIDwtIGxpc3QoDQogIHNjb3BlID0gJ3VzYScsDQogIHByb2plY3Rpb24gPSBsaXN0KHR5cGUgPSAnYWxiZXJzIHVzYScpLA0KICBzaG93bGFrZXMgPSBUUlVFLA0KICBsYWtlY29sb3IgPSB0b1JHQignd2hpdGUnKQ0KKQ0KDQpwIDwtIHBsb3RfZ2VvKGRkLCBsb2NhdGlvbm1vZGUgPSAnVVNBLXN0YXRlcycpICU+JQ0KICBhZGRfdHJhY2UoDQogICAgeiA9IH5jb3VudF9qb2IsIGxvY2F0aW9ucyA9IH5TdGF0ZSwNCiAgICBjb2xvciA9IH5jb3VudF9qb2IsIGNvbG9ycyA9ICdCbHVlcycNCiAgKSAlPiUNCiAgY29sb3JiYXIodGl0bGUgPSAiTnVtYmVyIG9mIGpvYnMiKSAlPiUNCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gJ0RhdGEgc2NpZW5jZSBKb2JzIGJ5IFN0YXRlJywNCiAgICBnZW8gPSBnDQogICkNCnANCg0KYGBgDQoNCg0KIyMgRmluZGluZ3MNCg0KDQo8c3R5bGU+DQpkaXYuYXF1YW1hcmluZSB7IGJhY2tncm91bmQtY29sb3I6IzdmZmZkNDsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogNXB4O30NCjwvc3R5bGU+DQo8ZGl2IGNsYXNzID0gImFxdWFtYXJpbmUiPg0KDQpEYXRhIHNjaWVuY2UgaXMgYSBmaWVsZCB0aGF0IHJlcXVpcmVkIG1hbnkgc2tpbGxzLg0KTm90ZSB0aGF0IGhhcmQgc2tpbGxzIGFyIG5vdCBlbm91Z2ggdG8gYmUgYSBkYXRhIHNjaWVudGlzdC4NCkFzIHRoZSAiVG9wIDEwIHZhbHVlZCBkYXRhIHNjaWVuY2Ugc2tpbGxzIiBpbiBkYXRhIGFuYWx5c2lzIHNlY3Rpb24gc2hvd3MsDQp0aGUgdG9wIDEwIG1vc3QgdmFsdWUgZGF0YSBzY2llbmNlIHNraWxscyBhcmUgbm90IG9ubHkgaGFyZCBza2lsbHMsIGJ1dCANCnNvZnQgc2tpbGxzIGFzIHdlbGwgc3VjaCBhcyBjb21tdW5pY2F0aW9uIGFuZCBidXNpbmVzcyBzZW5zLg0KDQoNCg0KPC9kaXY+IFxoZmlsbFxicmVhaw0KDQojIyBBcHBlbmRpeCANCg0KDQo8c3R5bGU+DQpkaXYuYXF1YW1hcmluZSB7IGJhY2tncm91bmQtY29sb3I6IzdmZmZkNDsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogNXB4O30NCjwvc3R5bGU+DQo8ZGl2IGNsYXNzID0gImFxdWFtYXJpbmUiPg0KDQpCZWxvdyBpcyB0aGUgY29kZSBmb3IgZGF0YSBjb2xsZWN0aW9uIGFuZCBzdG9yYWdlIGV4cGxhaW5lZCBpbiAiQXBwcm9hY2giDQoNCldlIGxlYXZlIGl0IHB1cnBvc2VseSBpbiBub3QgInIgY29kZSIgZm9ybSB0byBhdm9pZCBydW5uaW5nIGl0Lg0KDQoNCjwvZGl2PiBcaGZpbGxcYnJlYWsNCg0KYGBge30NCmxpYnJhcnkoUk1hcmlhREIpDQojIFRoZSBjb25uZWN0aW9uIG1ldGhvZCBiZWxvdyB1c2VzIGEgcGFzc3dvcmQgc3RvcmVkIGluIGEgdmFyaWFibGUuDQojIFRvIHVzZSB0aGlzIHNldCBsb2NhbHVzZXJwYXNzd29yZD0iVGhlIHBhc3N3b3JkIG9mIG5ld3NwYXBlcl9zZWFyY2hfcmVzdWx0c191c2VyIg0KDQpzdG9yaWVzRGIgPC0gZGJDb25uZWN0KFJNYXJpYURCOjpNYXJpYURCKCksIHVzZXI9J3Jvb3QnLCBwYXNzd29yZD0nSmEwNzA3MTk5MCcsIGRibmFtZT0nZHNfc2tpbGxzJywgaG9zdD0nMjMuMjUxLjE1NC4yMScpDQojIGRiTGlzdFRhYmxlcyhzdG9yaWVzRGIpDQpxdWVyeTwtICJTRUxFQ1QgKiBGUk9NIGluZGVlZF9qb2JfZGF0YXNldDc7Ig0KcnNJbnNlcnQgPC0gZGJTZW5kUXVlcnkoc3Rvcmllc0RiLCBxdWVyeSkNCkluZGVlZCA8LSBkYkZldGNoKHJzSW5zZXJ0KSAjIGV4dHJhY3QgZGF0YSBpbiBjaHVua3Mgb2YgMTAgcm93cw0KIyBkYkhhc0NvbXBsZXRlZChyc0luc2VydCkNCkluZGVlZA0KDQoNCmBgYA0KDQpgYGB7fQ0KZm9yIChpIGluIDE6NTAwKXsNCiANCiAgDQogIGpvYl9sb2MgPC0gSW5kZWVkW2ksNF0NCiAgcHJpbnQoam9iX2xvYykNCiAgam9iX2Rlc2MgPC0gSW5kZWVkW2ksMV0NCiAgam9iX3VybCA8LSBJbmRlZWRbaSwzXQ0KfQ0KYGBgDQoNCmBgYHt9DQpmb3IgKGkgaW4gMTo1MDApew0KIA0KICANCiAgam9iX2xvYyA8LSBJbmRlZWRbaSw0XQ0KICBqb2JfZGVzYyA8LSBJbmRlZWRbaSwxXQ0KICBqb2JfdXJsIDwtIEluZGVlZFtpLDNdDQogIGpvYl9za2lsbCA8LSAnJyANCiAgaWYoc3RyX2xlbmd0aChzdHJfdHJpbShqb2JfbG9jKSkgPiAyICl7DQogICAgICBqb2JfbG9jIDwtICdOQScNCiAgICB9DQogIGZvcih4IGluIDE6bnJvdyhnZW5lcmFsLnNraWxsKSl7DQogICAgaWYoZ2VuZXJhbC5za2lsbFt4LDFdIT0nJyl7DQogICAgICAgIGlmKHN0cl9kZXRlY3QodG9sb3dlcihqb2JfZGVzYyksdG9sb3dlcihnZW5lcmFsLnNraWxsW3gsMl0pKSA9PSBUUlVFKXsNCiAgICAgICAgICBqb2Jfc2tpbGwgPC0gcGFzdGUoam9iX3NraWxsLGdlbmVyYWwuc2tpbGxbeCwxXSxzZXA9IiwgIikNCiAgICAgICAgfQ0KICAgIH0NCiAgfQ0KICAgIGZvcih4IGluIDE6bnJvdyhnZW5lcmFsLnNraWxsKSl7DQogICAgICBpZihnZW5lcmFsLnNraWxsW3gsM10hPScnKXsNCiAgICAgICAgaWYoc3RyX2RldGVjdCh0b2xvd2VyKGpvYl9kZXNjKSx0b2xvd2VyKGdlbmVyYWwuc2tpbGxbeCw0XSkpID09IFRSVUUpew0KICAgICAgICAgIGpvYl9za2lsbCA8LSBwYXN0ZShnZW5lcmFsLnNraWxsW3gsM10sam9iX3NraWxsLCBzZXA9IiwgIikNCiAgICAgICAgfQ0KICAgICAgfQ0KICB9DQogIHF1ZXJ5IDwtICJJTlNFUlQgSU5UTyBgTGlua2VkaW5fZGF0YXNldGAgKGBqb2JfdXJsYCxgam9iX3NpdGVgLGBqb2Jfc2tpbGxgLGBqb2JfbG9jYXRpb25gKVZBTFVFUygnIg0KICB2YWx1ZSA8LSBjKHN0cl90cmltKGpvYl91cmwpLCdJbmRlZWQnLHN0cl90cmltKGpvYl9za2lsbCksc3RyX3RyaW0oam9iX2xvYykpDQogIHZhbHVlIDwtIHBhc3RlKHZhbHVlLGNvbGxhcHNlID0gIicsJyIpDQogIHF1ZXJ5IDwtIHBhc3RlKHF1ZXJ5LHZhbHVlICkNCiAgcXVlcnkgPC0gcGFzdGUocXVlcnksICInKTsiKQ0KICByc0luc2VydCA8LSBkYlNlbmRRdWVyeShzdG9yaWVzRGIsIHF1ZXJ5KQ0KICBkYkNsZWFyUmVzdWx0KHJzSW5zZXJ0KQ0KICAjZGYgPC0gcmJpbmQoZGYsIGRhdGEuZnJhbWUoU2tpbGwgPSAgam9iX3NraWxsICwgTG9jID0gam9iX2xvYzJbMl0gKSkNCiAgDQp9DQpgYGANCg0KDQpgYGB7fQ0KZGJDbGVhclJlc3VsdChyc0luc2VydCkNCmRiRGlzY29ubmVjdChzdG9yaWVzRGIpDQoNCmBgYA0KDQpgYGB7fQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkoc3RyaW5ncikNCmluZGV4IDwtIDANCkxpbmtlZGluPC1tYXRyaXgobnJvdz0wLG5jb2w9MSkNCmZvcihpIGluIDE6MzApew0KICBzdGFydCA8LSBpbmRleCoyNSANCiAgaW5kZXggPC0gaW5kZXggKyAxDQogIHN0cmxpbmsgPXBhc3RlKCJodHRwczovL3d3dy5saW5rZWRpbi5jb20vam9icy9zZWFyY2gvP2dlb0lkPTEwMzY0NDI3OCZrZXl3b3Jkcz1kYXRhJTIwc2NpZW50aXN0JmxvY2F0aW9uPVVuaXRlZCUyMFN0YXRlcyZzdGFydD0iLCB0b1N0cmluZyhzdGFydCksc2VwPSIiKQ0KDQogIHN0cmh0bWwgPC0gcmVhZF9odG1sKHN0cmxpbmspJT4lIGh0bWxfbm9kZXMoIi5yZXN1bHQtY2FyZF9fZnVsbC1jYXJkLWxpbmsiKSAlPiVodG1sX2F0dHIoJ2hyZWYnKQ0KICB0ZW1wbWF0cml4IDwtIGFzLm1hdHJpeChzdHJodG1sKQ0KICBMaW5rZWRpbiA8LSByYmluZChMaW5rZWRpbix0ZW1wbWF0cml4KQ0KICANCn0NCiNMaW5rZWRpbiAlPiUNCiNodG1sX25vZGVzKCIuZnVsbC13aWR0aCIpICU+JQ0KI2h0bWxfdGV4dCgpDQoNCg0KYGBgDQoNCmBgYHt9DQpMaW5rZWRpbg0KYGBgDQoNCg0KYGBge30NCiAgdGVtcGh0bWwgPC0gcmVhZF9odG1sKExpbmtlZGluWzJdKQ0KICBqb2JfZGVzYyA8LSB0ZW1waHRtbCAlPiUNCiAgaHRtbF9ub2RlcygiLmRlc2NyaXB0aW9uX190ZXh0IikgJT4lDQogIGh0bWxfdGV4dCgpDQogIHByaW50KGpvYl9kZXNjKQ0KYGBgDQoNCmBgYHt9DQogIHRlbXBodG1sIDwtIHJlYWRfaHRtbChMaW5rZWRpblsxXSkNCiAgam9iX2Rlc2MgPC0gdGVtcGh0bWwgJT4lDQogIGh0bWxfbm9kZXMoIi5zdWItbmF2LWN0YV9fc3ViLXRleHQtY29udGFpbmVyIikgJT4lDQogIGh0bWxfdGV4dCgpDQogIHByaW50KGpvYl9kZXNjKQ0KYGBgDQpgYGB7fQ0KbGVuZ3RoKExpbmtlZGluKQ0KYGBgDQoNCmBgYHt9DQpnZW5lcmFsLnNraWxsIDwtIHJlYWQuY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3p4ODY4L1Byb2plY3QzL21hc3Rlci9kYXRhX3NraWxscy5jc3YiLGhlYWRlcj1UKQ0KDQpuYW1lcyhnZW5lcmFsLnNraWxsKSA8LSBjKCdTb2Z0JywgJ1JlZy5Tb2Z0JywnSGFyZCcsJ1JlZy5IYXJkJykNCmdlbmVyYWwuc2tpbGwNCmBgYA0KYGBge30NCnN0cihnZW5lcmFsLnNraWxsKQ0KYGBgDQpgYGB7fQ0KbnJvdyhnZW5lcmFsLnNraWxsKQ0KYGBgDQoNCmBgYHt9DQpmb3IoaSBpbiBucm93KGdlbmVyYWwuc2tpbGwpKXsNCiAgcHJpbnQoZ2VuZXJhbC5za2lsbFsyLDFdKQ0KfQ0KYGBgDQoNCmBgYHt9DQp0ZW1wc3RyIDwtICJhc2RmYUNyZWF0aXZlIGFkZmFzIFRoaW5raW5nZnNhICINCnN0cl9kZXRlY3QodGVtcHN0cixwYXR0ZXJuPSJjcmVhdGl2ZS4qVGhpbmtpbmciKQ0KYGBgDQpgYGB7fQ0KbnJvdyhMaW5rZWRpbikNCmBgYA0KDQpgYGB7fQ0KZm9yIChpIGluIDE6bnJvdyhMaW5rZWRpbikpew0KICBwcmludChMaW5rZWRpbltpXSkNCn0NCmBgYA0KDQpgYGB7fQ0KbGlicmFyeShSTWFyaWFEQikNCiMgVGhlIGNvbm5lY3Rpb24gbWV0aG9kIGJlbG93IHVzZXMgYSBwYXNzd29yZCBzdG9yZWQgaW4gYSB2YXJpYWJsZS4NCiMgVG8gdXNlIHRoaXMgc2V0IGxvY2FsdXNlcnBhc3N3b3JkPSJUaGUgcGFzc3dvcmQgb2YgbmV3c3BhcGVyX3NlYXJjaF9yZXN1bHRzX3VzZXIiDQoNCnN0b3JpZXNEYiA8LSBkYkNvbm5lY3QoUk1hcmlhREI6Ok1hcmlhREIoKSwgdXNlcj0ncm9vdCcsIHBhc3N3b3JkPSdKYTA3MDcxOTkwJywgZGJuYW1lPSdkc19za2lsbHMnLCBob3N0PScyMy4yNTEuMTU0LjIxJykNCiMgZGJMaXN0VGFibGVzKHN0b3JpZXNEYikNCg0KDQoNCiMgZGJIYXNDb21wbGV0ZWQocnNJbnNlcnQpDQoNCg0KZGYgPSBkYXRhLmZyYW1lKFNraWxsID0gY2hhcmFjdGVyKCksIExvYyA9IGNoYXJhY3RlcigpKQ0KZm9yIChpIGluIDE6NTAwKXsNCiB0ZW1waHRtbCA8LSByZWFkX2h0bWwoTGlua2VkaW5baV0pDQoNCiAgam9iX2Rlc2MgPC0gdGVtcGh0bWwgJT4lDQogIGh0bWxfbm9kZXMoIi5kZXNjcmlwdGlvbl9fdGV4dCIpICU+JQ0KICBodG1sX3RleHQoKQ0KICBwcmludChqb2JfZGVzYykNCiAgam9iX2xvYyA8LSB0ZW1waHRtbCAlPiUNCiAgaHRtbF9ub2RlcygiLnN1Yi1uYXYtY3RhX19zdWItdGV4dC1jb250YWluZXIiKSAlPiUNCiAgaHRtbF90ZXh0KCkNCiAgDQogIGpvYl9sb2MyIDwtIHVubGlzdChzdHJzcGxpdChqb2JfbG9jLCAiLCIpKQ0KDQogIHByaW50KHN0cl9sZW5ndGgoc3RyX3RyaW0oam9iX2xvYzJbMl0pKSkNCiAgaWYobGVuZ3RoKGpvYl9sb2MyKT49Mil7DQogICAgaWYoc3RyX2xlbmd0aChzdHJfdHJpbShqb2JfbG9jMlsyXSkpID4gMiApew0KICAgICAgam9iX2xvYzJbMl0gPC0gJ05BJw0KICAgIH0NCiAgfQ0KICBwcmludChqb2JfbG9jMlsyXSkNCiAgDQogIGpvYl9za2lsbCA8LSAnJyANCiAgam9iX3VybCA8LSBMaW5rZWRpbltpXQ0KICBmb3IoeCBpbiAxOm5yb3coZ2VuZXJhbC5za2lsbCkpew0KICAgIGlmKGdlbmVyYWwuc2tpbGxbeCwxXSE9Jycpew0KICAgICAgICBpZihzdHJfZGV0ZWN0KHRvbG93ZXIoam9iX2Rlc2MpLHRvbG93ZXIoZ2VuZXJhbC5za2lsbFt4LDJdKSkgPT0gVFJVRSl7DQogICAgICAgICAgam9iX3NraWxsIDwtIHBhc3RlKGpvYl9za2lsbCxnZW5lcmFsLnNraWxsW3gsMV0sc2VwPSIsICIpDQogICAgICAgIH0NCiAgICB9DQogIH0NCiAgICBmb3IoeCBpbiAxOm5yb3coZ2VuZXJhbC5za2lsbCkpew0KICAgICAgaWYoZ2VuZXJhbC5za2lsbFt4LDNdIT0nJyl7DQogICAgICAgIGlmKHN0cl9kZXRlY3QodG9sb3dlcihqb2JfZGVzYyksdG9sb3dlcihnZW5lcmFsLnNraWxsW3gsNF0pKSA9PSBUUlVFKXsNCiAgICAgICAgICBqb2Jfc2tpbGwgPC0gcGFzdGUoZ2VuZXJhbC5za2lsbFt4LDNdLGpvYl9za2lsbCwgc2VwPSIsICIpDQogICAgICAgIH0NCiAgICAgIH0NCiAgfQ0KICBxdWVyeSA8LSAiSU5TRVJUIElOVE8gYExpbmtlZGluX2RhdGFzZXRgIChgam9iX3VybGAsYGpvYl9zaXRlYCxgam9iX3NraWxsYCxgam9iX2xvY2F0aW9uYClWQUxVRVMoJyINCiAgdmFsdWUgPC0gYyhzdHJfdHJpbShqb2JfdXJsKSwnTGlua2VkaW4nLHN0cl90cmltKGpvYl9za2lsbCksc3RyX3RyaW0oam9iX2xvYzJbMl0pKQ0KICB2YWx1ZSA8LSBwYXN0ZSh2YWx1ZSxjb2xsYXBzZSA9ICInLCciKQ0KICBxdWVyeSA8LSBwYXN0ZShxdWVyeSx2YWx1ZSApDQogIHF1ZXJ5IDwtIHBhc3RlKHF1ZXJ5LCAiJyk7IikNCiAgcHJpbnQoam9iX2xvYzJbMl0pDQogIHJzSW5zZXJ0IDwtIGRiU2VuZFF1ZXJ5KHN0b3JpZXNEYiwgcXVlcnkpDQogIGRiQ2xlYXJSZXN1bHQocnNJbnNlcnQpDQogICNkZiA8LSByYmluZChkZiwgZGF0YS5mcmFtZShTa2lsbCA9ICBqb2Jfc2tpbGwgLCBMb2MgPSBqb2JfbG9jMlsyXSApKQ0KICANCn0NCg0KYGBgDQoNCg0KYGBge30NCmRiQ2xlYXJSZXN1bHQocnNJbnNlcnQpDQpkYkRpc2Nvbm5lY3Qoc3Rvcmllc0RiKQ0KDQpgYGANCg0KDQpgYGB7fQ0KZG9jcyA8LSBkZiR4DQoNCmBgYA0KYGBge30NCnBzX2R0bSA8LSBWZWN0b3JTb3VyY2UoZG9jcykgJT4lDQogIFZDb3JwdXMoKSAlPiUNCiAgRG9jdW1lbnRUZXJtTWF0cml4KGNvbnRyb2wgPSBsaXN0KHJlbW92ZVB1bmN0dWF0aW9uID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZU51bWJlcnMgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcHdvcmRzID0gVFJVRSkpDQpgYGANCg0KYGBge30NCmluc3BlY3QocHNfZHRtKQ0KDQpgYGANCg0KDQpgYGB7fQ0KcHNfdGlkeSA8LSB0aWR5KHBzX2R0bSkNCnBzX3RpZHkNCmBgYA0KDQoNCg0KDQoNCg==