This guide is directed at what I referred to as the “third audience” in an article I published in 2016 (https://www.linkedin.com/pulse/people-analytics-show-me-dont-tell-ben-teusch) - those who have a data background, but little HR expertise, and those in HR with a willingness to work with data, but not much of a quant background. There are already plenty of tutorials to show you how to run a regression or other kinds of predictive models, and I’m not sure that’s what this audience will find most helpful. Please give me feedback on that.

What I’m going to do is focus on showing you how to do things that don’t require too much of a data background, but would be really helpful for the HR folks I know. Those with a stronger data background can use this as a starting point to add in more complex analyses, if they want.

Making HR Reports

No matter how mature people analytics is at your organization, at some point you’re going to need to make some reports. This is especially true if you’re an HRBP with limited technical support – you might even be making these reports yourself! These reports, often done monthly, can take hours of VLOOKUPs and copy-pasting in Excel, then another while to put the right numbers into a PowerPoint template. Today I’m going to show you how to automate those reports using R, so you can save a bunch of time down the road. Andrew Marritt recently wrote about his process for automating quality reports (https://www.linkedin.com/pulse/manager-level-reporting-how-we-automate-process-andrew-marritt), and his advice is great. I’ll be showing you less strategy and more tactics about how to actually make this report (http://www.slideshare.net/BenTeusch/automated-attrition-report-using-r) in R.

Getting Started with R

If you’ve never used R before, check out Richard Rosenow’s Intro to R post (https://www.linkedin.com/pulse/hr-analytics-starter-kit-part-2-intro-r-richard-rosenow-pmp), and specifically you can download R (https://mirror.las.iastate.edu/CRAN/) and RStudio (https://www.rstudio.com/products/rstudio/download3/). There are lots of helpful tips online about getting started with R, and I’m writing this post with the assumption that you have RStudio installed and you’re able to run code that I provide.

Code for this article

The R notebook used to produce this article is found on my GitHub repository at https://github.com/teuschb/hr_data/blob/master/analyses/automating_report_slides.Rmd. If you just want the script with code only, that’s found at https://github.com/teuschb/hr_data/blob/master/analyses/automating_report_slides.R. There is also a simplified version of this code at https://github.com/teuschb/hr_data/blob/master/analyses/automating_report_slides_simple.Rmd which produces a PowerPoint report using much simpler code, but doesn’t look quite as nice.

Throughout this example, I’ve used mostly base R functions to manipulate data. However, I highly recommend dplyr, and the tidyverse in general. Check that out here: http://tidyverse.org/

Dataset

The example dataset is based on the HR Employee Attrition and Performance dataset from IBM Watson Analytics (https://www.ibm.com/communities/analytics/watson-analytics-blog/hr-employee-attrition/). I’ve modified it slightly and saved it to my GitHub repository (https://raw.githubusercontent.com/teuschb/hr_data/master/datasets/modified_watson_dataset.csv), which allows me to read in the .csv file directly from the Internet, without needing to download a file.

In practice, your dataset will be your standard HRIS report of headcount, attrition, performance, or whatever data you want to use for a report. That way, each month, quarter, or year, you’ll be able to run this same line of code, replacing the previous file with the name of the most recent dataset that you want to work with. If all the needed data fields have the same names each time, the rest of the code will run without you having to make changes each month. You can even use R to get several standard reports and merge them together quickly, again in an automated way, but here I use just one data file.

This code loads the data from GitHub into R.

mydata <- read.csv("https://raw.githubusercontent.com/teuschb/hr_data/master/datasets/modified_watson_dataset.csv", stringsAsFactors = FALSE)

Set Report Parameters

The one thing I do manually update each month is the date of the report. By setting it once at the top, I can reference it throughout the script without worrying about all the places I may have used it.

month = "May"
date = "May 2017"

The ReporteRs package

R isn’t able to write .pptx files by default. What makes this possible is the ReporteRs package. To learn a lot more about what the package can do, check out http://davidgohel.github.io/ReporteRs/. It allows you to use R to run your analysis, print plots, and export everything to a PowerPoint document in a repeatable, automated way.

ReporteRs requires rJava and for Java to be installed on your computer. This code will make sure it’s installed, and will show you the version of Java (it should be at least 1.6).

require(rJava)
system("java -version")
java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-468-11M4833)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-468, mixed mode)

The first time you use ReporteRs, you’ll need to install the package from CRAN, the R package repository. This code checks whether it’s already installed, and installs it if it isn’t.

if (!require('ReporteRs')) {
  install.packages("ReporteRs")
}

Libraries

Before we can get started, we have to load in a few other packages I like to use. You’ll see that ReporteRs is on the list - running install.packages('ReporteRs') gets the package onto our computer, and library(ReporteRs) tells R that we want to use that package right now.

If you see a package name you don’t recognize, make sure you run install.packages for it. For example, install.packages('ggplot2').

library(ReporteRs)
library(ggplot2)     # for plotting graphs
library(scales)      # for formatting numbers
library(magrittr)    # for the %>% operator

Analysis

What analysis needs to be done for the report? HR is often guilty of producing reports and dashboards on HR processes that don’t directly help business leaders make better decisions. Good reports should always be produced for a specific purpose. Since I don’t have a specific business problem I’m trying to solve in this example, and because HR is used to reporting on attrition, I’ll prepare some data for a basic attrition report with a few bar charts to visualize the data.

Just know that R is able to produce various types of charts and produce whatever analysis is necessary to help your business. If you have any specific examples in mind you’d like to see, let me know.

Attrition Overview Table

For this example, the first slide will be a table with an overview of attrition.

This code takes the data and uses aggregate to extract the total number of employees, the number of employees who left, and the attrition rate for this month (calculated as # of employees gone/# of employees at the start of the month), all by department.

overall_attrition <- as.data.frame(
  as.list(aggregate(Attrition ~ Department,
                    data = mydata,
                    FUN = function(x) c(
                      n = length(x),
                      s = sum(x, na.rm = T),
                      mn = mean(x, na.rm = T))
                    )
          ), stringsAsFactors = F)

Next, I add a row of totals using rbind.

overall_attrition <- rbind(overall_attrition, c(
                             0, # to be replaced with the word "Total"
                             sum(overall_attrition$Attrition.n),
                             sum(overall_attrition$Attrition.s),
                             mean(overall_attrition$Attrition.mn))
                           )
overall_attrition[nrow(overall_attrition),1] <- 'Total'

Before I put the data into a table, I want to add % symbols and to make the column names more presentable. I also change the font size before putting it into a special kind of ReporteRs table called a vanilla.table.

Once I make the table, I also format it. I use setZebraStyle to add some shading, and setFlexTableWidths to adjust the column widths to fit the text. The final block of code justifies the text to the left or center, and makes some text bold.

# format as percentages, rename columns
overall_attrition$Attrition.mn <- percent(as.numeric(overall_attrition$Attrition.mn))
names(overall_attrition) <- c('Department', 'Headcount', 'Attrition', 'Attrition %')
# Put data into a table
options("ReporteRs-fontsize"=16) #font size for tables
overall_attrition_table <- vanilla.table(overall_attrition) %>%
  setZebraStyle(odd = '#eeeeee', even = 'white' ) %>%
  setFlexTableWidths(widths = c(4.5, 2.5, 2, 2.5)) 
overall_attrition_table[,1] = parLeft()
overall_attrition_table[,2:4] = parCenter()
overall_attrition_table[,,to='header'] = parCenter()
overall_attrition_table[4,] = textProperties(font.weight = 'bold' )
overall_attrition_table

Department

Headcount

Attrition

Attrition %

Human Resources

63

12

19.05%

Research & Development

961

133

13.84%

Sales

446

92

20.63%

Total

1470

237

17.84%

Following that overview, I report attrition by job role, performance, and hire source (or recruiting channel). For each one I’m going to make a plot that shows the turnover, and then create a table that resembles the overall attrition table.

Attrition by job role

This code should look familiar – I’m extracting the total number of employees, the number of employees who left, and the attrition rate for this month by job role.

jobrole_attrition <- as.data.frame(
  as.list(aggregate(Attrition ~ JobRole,
                    data = mydata,
                    FUN = function(x) c(
                      n = length(x),
                      s = sum(x, na.rm = T),
                      mn = mean(x, na.rm = T))
                    )
          ))
names(jobrole_attrition) = c('JobRole', 'Headcount', 'Attrition', 'AttritionRate')

For making graphs, I use the popular ggplot2 package. It’s a really powerful package that allows you to visualize all kinds of data, and customize the resulting plots.

Here, I make a bar chart where each bar represents a JobRole and the height of the bar represents the attrition rate. I save the graph as jobrole_attrition_plot so I can use it later.

I do a bit of customization here. I use scale_fill_manual to make the bars blue. Theme_minimal removes a lot of extra chart lines that I prefer not be there. I also change the axis labels into percentages with labels=percent, flip the graph so it’s horizontal with coord_flip, and add custom labels with labs.

(jobrole_attrition_plot <- ggplot(jobrole_attrition,
                                  aes(reorder(JobRole, AttritionRate), AttritionRate)) +
  geom_col(aes(fill = '')) +
  scale_fill_manual(values = c("#1D243C"),guide=FALSE) +
  theme_minimal() +
  scale_y_continuous(labels=percent) +
  labs(list(y = paste("Attrition in ",month,sep = ""), x = "Job Role",
            title = paste("Attrition by Job Role, ", date, sep = ""))) +
  theme(panel.grid.minor = element_blank()) +
  coord_flip())

I want my table to be sorted from highest to lowest, so first I sort my dataset.

jobrole_attrition <- jobrole_attrition[order(jobrole_attrition$AttritionRate, decreasing = TRUE),]

Although I won’t include it in this table, I can use this information to get the impact of reducing attrition for the job role with the highest attrition. I’ll use this in the recommendations section, and it’s easier to calculate it now.

reduction = .05            # how much could we reduce attrition?
replacement_mult = 1.5       # how much does it cost to replace an employee, as a multiplier of their salary?
top_role <- as.character(jobrole_attrition$JobRole[1])   # job role with highest attrition
top_role_attrition <- jobrole_attrition$AttritionRate[1] # attrition for that role
left_from_top_role <- mydata$JobRole == top_role & mydata$Attrition == 1 
salary_lost = 
  sum(mydata$MonthlyIncome[left_from_top_role]) * 12 *     # total annual salary lost 
  replacement_mult                                         # times replacement cost
impact = salary_lost * reduction # amount we could save

Formatting the table should look familiar.

# format as percentages, rename columns
jobrole_attrition$AttritionRate <- percent(jobrole_attrition$AttritionRate)
names(jobrole_attrition) = c('Job Role', 'Headcount', 'Attrition', 'Attrition %')
# put data into a table
options("ReporteRs-fontsize"=16) #font size for tables
table_widths = c(2.8, 1.2, 1, 1.6) 
jobrole_attrition_table <- vanilla.table(jobrole_attrition) %>%
  setZebraStyle(odd = '#eeeeee', even = 'white' ) %>%
  setFlexTableWidths(widths = table_widths) 
jobrole_attrition_table[,2:4] = parCenter()
jobrole_attrition_table[,1] = parLeft()
jobrole_attrition_table[,,to='header'] = parCenter()
jobrole_attrition_table

Job Role

Headcount

Attrition

Attrition %

Sales Representative

83

33

39.8%

Laboratory Technician

259

62

23.9%

Human Resources

52

12

23.1%

Sales Executive

326

57

17.5%

Research Scientist

292

47

16.1%

Manufacturing Director

145

10

6.9%

Healthcare Representative

131

9

6.9%

Manager

102

5

4.9%

Research Director

80

2

2.5%

Now I’m going to make the same graph and table for performance and hire source. The only difference is I use scale_x_reverse on performance rating so that the ‘1’ rating is at the top of the graph. I’m also calculating the hire sources that have the highest and lowest attrition rates, for use in the recommendations section.

Attrition by Performance

performance_attrition <- as.data.frame(
  as.list(aggregate(Attrition ~ PerformanceRating,
                    data = mydata, FUN = function(x) c(
                      n = length(x),
                      s = sum(x, na.rm = T),
                      mn = mean(x, na.rm = T))
                    )
          ))
names(performance_attrition) = c('PerformanceRating', 'Headcount', 'Attrition', 'AttritionRate')
(performance_attrition_plot <- ggplot(performance_attrition, aes(PerformanceRating, AttritionRate)) +
  geom_bar(stat = "identity", aes(fill = '')) +
  scale_fill_manual(values = c("#1D243C"),guide=FALSE) +
  theme_minimal() +
  scale_y_continuous(labels=percent) +
  scale_x_reverse() +
  labs(list(y = paste("Attrition in ",month,sep = ""), x = "Performance Rating",
            title = paste("Attrition by Performance Rating, ", date, sep = ""))) +
  theme(panel.grid.minor = element_blank()) +
  coord_flip())

# format as percentages, rename columns
performance_attrition$AttritionRate <- percent(performance_attrition$AttritionRate)
names(performance_attrition) = c('Performance Rating', 'Headcount', 'Attrition', 'Attrition %')
# put data into a table
performance_attrition_table <- vanilla.table(performance_attrition) %>%
  setZebraStyle(odd = '#eeeeee', even = 'white' ) %>%
  setFlexTableWidths(widths = table_widths) 
performance_attrition_table[,1:4] = parCenter()
performance_attrition_table[,,to='header'] = parCenter()
performance_attrition_table

Performance Rating

Headcount

Attrition

Attrition %

1

16

5

31.2%

2

359

56

15.6%

3

841

137

16.3%

4

248

38

15.3%

5

6

1

16.7%

Attrition by Recruiting Channel

hiresource_attrition <- as.data.frame(
  as.list(aggregate(Attrition ~ HireSource, data = mydata,
                    FUN = function(x) c(
                      n = length(x),
                      s = sum(x, na.rm = T),
                      mn = mean(x, na.rm = T))
                    )
          ))
names(hiresource_attrition) = c('HireSource', 'Headcount', 'Attrition', 'AttritionRate')
(hiresource_attrition_plot <- ggplot(hiresource_attrition, aes(reorder(HireSource, AttritionRate), AttritionRate)) +
  geom_bar(stat = "identity", aes(fill = '')) +
  scale_fill_manual(values = c("#1D243C"),guide=FALSE) +
  theme_minimal() +
  scale_y_continuous(labels=percent) +
  labs(list(y = paste("Attrition in ",month,sep = ""), x = "Job Level",
            title = paste("Attrition by Recruiting Channel, ", date, sep = ""))) +
  theme(panel.grid.minor = element_blank()) +
  coord_flip())

# sort by attrition rate, format as percentages, rename columns
hiresource_attrition <- hiresource_attrition[order(hiresource_attrition$AttritionRate, decreasing = TRUE),]
hiresource_attrition$AttritionRate <- percent(hiresource_attrition$AttritionRate)
names(hiresource_attrition) = c('Recruiting Channel', 'Headcount', 'Attrition', 'Attrition %')
# put data into a table 
hiresource_attrition_table <- vanilla.table(hiresource_attrition) %>%
  setZebraStyle(odd = '#eeeeee', even = 'white' ) %>%
  setFlexTableWidths(widths = table_widths) 
hiresource_attrition_table[,1] = parLeft()
hiresource_attrition_table[,2:4] = parCenter()
hiresource_attrition_table[,,to='header'] = parCenter()
hiresource_attrition_table

Recruiting Channel

Headcount

Attrition

Attrition %

Search Firm

32

8

25.00%

Applied Online

385

81

21.04%

Campus

257

51

19.84%

Referral

178

31

17.42%

# save hire sources with highest and lowest levels of attrition
top_hire <- hiresource_attrition$`Recruiting Channel`[1]
bottom_hire <- hiresource_attrition$`Recruiting Channel`[nrow(hiresource_attrition)]

What’s great about using R to do the graphs is that whenever I need to update this report with new data, all I have to do is change the date, and the rest happens automatically. It takes a little more investment at first, but now all that dreaded Excel work will take almost no time at all!

Creating the Report

Explanation

Once the charts and tables are finished, I need to put them into PowerPoint. I’ve done this many times by copy-pasting the graphs from R into the report, but it’s tedious and hard to be consistent with their size and placement.

Again, for more details about how this works, the guide is on the author’s github page (http://davidgohel.github.io/ReporteRs/articles/powerpoint.html).

First I create a new pptx document and call it report. I set the font size to 24, and I like to use slide.layouts(report) to see which PowerPoint slide layouts are available. The basic idea is to add a slide (addSlide), choose a layout, and then fill that layout with a title (addTitle) and text (addParagraph), a table (addFlexTable), or a graph (addPlot). Finally, I use writeDoc to save the file. I’m saving it into the hr_data folder. You’ll need to make sure that path matches where you want to save it to.

You’ll also see an argument for template - I’m using a PowerPoint template for this report. This means you can build these reports directly on your corporate report template. I have the template saved in the hr_data folder on my machine. You’ll need to update that path with wherever your template is stored.

When you see code like this: par.properties = parProperties(list.style = "unordered", level = 1), That’s how ReporteRs can produce indented bullet point lists. Level = 2 would indent the bullets one level, level = 3 would be another level.

If you’ve never seen the %>% operator, you can read it like, “then”. So the following code:

report <- report %>%
  addSlide(slide.layout = "Title Slide") %>%
  addTitle(paste(date, "Acme Co. Attrition Report"))

can be read, “take report, then add a title slide, then add a title, and save it as report.”

Code to produce and publish the report

report <- pptx(title = paste(date, "Acme Co. \nAttrition Report"),
              template = '~/hr_data/example_corporate_template.pptx')
options("ReporteRs-fontsize"=24) #font size for text
slide.layouts(report)
 [1] "Picture with Caption"           "Alternate Section Header"       "Two Content"                   
 [4] "Alternate Content with Caption" "Comparison"                     "Title Slide"                   
 [7] "Title and Content"              "Title and Vertical Text"        "Section Header"                
[10] "Vertical Title and Text"        "Content with Caption"           "Title Only"                    
[13] "Blank"                         
report <- report %>%
  addSlide(slide.layout = "Title Slide") %>%
  addTitle(paste(date, "Acme Co. Attrition Report"))
report <- report %>%
  addSlide(slide.layout = "Title and Content") %>%
  addTitle("Table of Contents") %>%
  addParagraph( c("Overall Attrition Statistics"), 
                par.properties = parProperties(list.style = "unordered", level = 1)) %>%
  addParagraph( c("Attrition by Subgroups"), append = T,
                par.properties = parProperties(list.style = "unordered", level = 1)) %>%
  addParagraph( set_of_paragraphs("by Job Role",
                                  "by Performance Rating",
                                  "by Job Level"),
                append = T,
                par.properties = parProperties(list.style = "unordered", level = 2) )%>%
  addParagraph( c("Recommendations"), append = T,
                par.properties = parProperties(list.style = "unordered", level = 1))
report <- report %>%
  addSlide(slide.layout = "Section Header") %>%
  addTitle("Overall Attrition Statistics")
report <- report %>%
  addSlide(slide.layout = "Title and Content") %>%
  addTitle(paste0("Summary of Attrition in ", date)) %>%
  addFlexTable(overall_attrition_table)
report <- report %>%
  addSlide(slide.layout = "Section Header") %>%
  addTitle("Attrition by Subgroup")
report <- report %>%
  addSlide(slide.layout = "Two Content") %>%
  addTitle("Which Jobs have the Highest Attrition?") %>%
  addPlot(function() print(jobrole_attrition_plot)) %>%
  addFlexTable(jobrole_attrition_table)
System font `Calibri` not found. Closest match: `Verdana`
report <- report %>%
  addSlide(slide.layout = "Two Content") %>%
  addTitle("Are We Keeping Our Top Performers?") %>%
  addPlot(function() print(performance_attrition_plot)) %>%
  addFlexTable(performance_attrition_table) 
report <- report %>%
  addSlide(slide.layout = "Two Content") %>%
  addTitle("Which Hiring Channels have High Turnover?") %>%
  addPlot(function() print(hiresource_attrition_plot)) %>%
  addFlexTable(hiresource_attrition_table)
report <- report %>%
  addSlide(slide.layout = "Title and Content") %>%
  addTitle("Key Insights") %>%
  addParagraph( c(paste0("The job group with the highest attrition was ", top_role,
                        " with an attrition rate of ",as.character(jobrole_attrition$`Attrition %`[1]),".")), 
                par.properties = parProperties(list.style = "unordered", level = 1)) %>%
  addParagraph( c(paste0("Additional focus should be placed on retaining ", top_role,
                        "s.")),
                append = T,
                par.properties = parProperties(list.style = "unordered", level = 2)) %>%
  addParagraph( c(paste0("We estimate that replacing an employee costs ", replacement_mult,
                         "x their annual salary. Reducing attrition by ", percent(reduction),
                         " in ", month, " could have saved ", dollar(round(impact,0)), ".")),
                append = T,
                par.properties = parProperties(list.style = "unordered", level = 2)) %>%
  addParagraph( c(paste0("Our ", hiresource_attrition$`Recruiting Channel`[1],
                         " hires have the highest attrition rate. The channel with the lowest attrition rate in ",
                         month, " was the ",
                         hiresource_attrition$`Recruiting Channel`[nrow(hiresource_attrition)], " channel.")), 
                append = T,
                par.properties = parProperties(list.style = "unordered", level = 1)) 
writeDoc(report, paste0("~/hr_data/examples/",date," Attrition Report.pptx"))

And there you have it! The final report can be seen on SlideShare (http://www.slideshare.net/BenTeusch/automated-attrition-report-using-r). Again, if you’re interested in starting with a simple version of this code, check out http://rpubs.com/teuschb/making_ppt_slides_simple.

I hope this guide can be helpful for analytics teams, and especially people analytics teams or HR reporting teams, who would like to reduce the amount of time they spend on routine reports. Well-considered reporting is still a pillar of effective people analytics, but the best teams reduce the time they spend on it as much as possible, so they can work on more advanced analytics. If you find other helpful guides for people analytics, or would like to see something specific, please let me know in the comments!

LS0tCnRpdGxlOiAiTWFraW5nIFBvd2VyUG9pbnQgU2xpZGVzIHdpdGggUiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQpUaGlzIGd1aWRlIGlzIGRpcmVjdGVkIGF0IHdoYXQgSSByZWZlcnJlZCB0byBhcyB0aGUgInRoaXJkIGF1ZGllbmNlIiBpbiBhbiBhcnRpY2xlIEkgcHVibGlzaGVkIGluIDIwMTYgKGh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9wdWxzZS9wZW9wbGUtYW5hbHl0aWNzLXNob3ctbWUtZG9udC10ZWxsLWJlbi10ZXVzY2gpIC0gdGhvc2Ugd2hvIGhhdmUgYSBkYXRhIGJhY2tncm91bmQsIGJ1dCBsaXR0bGUgSFIgZXhwZXJ0aXNlLCBhbmQgdGhvc2UgaW4gSFIgd2l0aCBhIHdpbGxpbmduZXNzIHRvIHdvcmsgd2l0aCBkYXRhLCBidXQgbm90IG11Y2ggb2YgYSBxdWFudCBiYWNrZ3JvdW5kLiBUaGVyZSBhcmUgYWxyZWFkeSBwbGVudHkgb2YgdHV0b3JpYWxzIHRvIHNob3cgeW91IGhvdyB0byBydW4gYSByZWdyZXNzaW9uIG9yIG90aGVyIGtpbmRzIG9mIHByZWRpY3RpdmUgbW9kZWxzLCBhbmQgSSdtIG5vdCBzdXJlIHRoYXQncyB3aGF0IHRoaXMgYXVkaWVuY2Ugd2lsbCBmaW5kIG1vc3QgaGVscGZ1bC4gUGxlYXNlIGdpdmUgbWUgZmVlZGJhY2sgb24gdGhhdC4gCgpXaGF0IEknbSBnb2luZyB0byBkbyBpcyBmb2N1cyBvbiBzaG93aW5nIHlvdSBob3cgdG8gZG8gdGhpbmdzIHRoYXQgZG9uJ3QgcmVxdWlyZSB0b28gbXVjaCBvZiBhIGRhdGEgYmFja2dyb3VuZCwgYnV0IHdvdWxkIGJlIHJlYWxseSBoZWxwZnVsIGZvciB0aGUgSFIgZm9sa3MgSSBrbm93LiBUaG9zZSB3aXRoIGEgc3Ryb25nZXIgZGF0YSBiYWNrZ3JvdW5kIGNhbiB1c2UgdGhpcyBhcyBhIHN0YXJ0aW5nIHBvaW50IHRvIGFkZCBpbiBtb3JlIGNvbXBsZXggYW5hbHlzZXMsIGlmIHRoZXkgd2FudC4KCiMjIE1ha2luZyBIUiBSZXBvcnRzCk5vIG1hdHRlciBob3cgbWF0dXJlIHBlb3BsZSBhbmFseXRpY3MgaXMgYXQgeW91ciBvcmdhbml6YXRpb24sIGF0IHNvbWUgcG9pbnQgeW91J3JlIGdvaW5nIHRvIG5lZWQgdG8gbWFrZSBzb21lIHJlcG9ydHMuIFRoaXMgaXMgZXNwZWNpYWxseSB0cnVlIGlmIHlvdSdyZSBhbiBIUkJQIHdpdGggbGltaXRlZCB0ZWNobmljYWwgc3VwcG9ydCAtLSB5b3UgbWlnaHQgZXZlbiBiZSBtYWtpbmcgdGhlc2UgcmVwb3J0cyB5b3Vyc2VsZiEgVGhlc2UgcmVwb3J0cywgb2Z0ZW4gZG9uZSBtb250aGx5LCBjYW4gdGFrZSBob3VycyBvZiBWTE9PS1VQcyBhbmQgY29weS1wYXN0aW5nIGluIEV4Y2VsLCB0aGVuIGFub3RoZXIgd2hpbGUgdG8gcHV0IHRoZSByaWdodCBudW1iZXJzIGludG8gYSBQb3dlclBvaW50IHRlbXBsYXRlLiBUb2RheSBJJ20gZ29pbmcgdG8gc2hvdyB5b3UgaG93IHRvIGF1dG9tYXRlIHRob3NlIHJlcG9ydHMgdXNpbmcgUiwgc28geW91IGNhbiBzYXZlIGEgYnVuY2ggb2YgdGltZSBkb3duIHRoZSByb2FkLiBBbmRyZXcgTWFycml0dCByZWNlbnRseSB3cm90ZSBhYm91dCBoaXMgcHJvY2VzcyBmb3IgYXV0b21hdGluZyBxdWFsaXR5IHJlcG9ydHMgKGh0dHBzOi8vd3d3LmxpbmtlZGluLmNvbS9wdWxzZS9tYW5hZ2VyLWxldmVsLXJlcG9ydGluZy1ob3ctd2UtYXV0b21hdGUtcHJvY2Vzcy1hbmRyZXctbWFycml0dCksIGFuZCBoaXMgYWR2aWNlIGlzIGdyZWF0LiBJJ2xsIGJlIHNob3dpbmcgeW91IGxlc3Mgc3RyYXRlZ3kgYW5kIG1vcmUgdGFjdGljcyBhYm91dCBob3cgdG8gYWN0dWFsbHkgbWFrZSB0aGlzIHJlcG9ydCAoaHR0cDovL3d3dy5zbGlkZXNoYXJlLm5ldC9CZW5UZXVzY2gvYXV0b21hdGVkLWF0dHJpdGlvbi1yZXBvcnQtdXNpbmctcikgaW4gUi4gCgojIyBHZXR0aW5nIFN0YXJ0ZWQgd2l0aCBSCklmIHlvdSd2ZSBuZXZlciB1c2VkIFIgYmVmb3JlLCBjaGVjayBvdXQgUmljaGFyZCBSb3Nlbm93J3MgSW50cm8gdG8gUiBwb3N0IChodHRwczovL3d3dy5saW5rZWRpbi5jb20vcHVsc2UvaHItYW5hbHl0aWNzLXN0YXJ0ZXIta2l0LXBhcnQtMi1pbnRyby1yLXJpY2hhcmQtcm9zZW5vdy1wbXApLCBhbmQgc3BlY2lmaWNhbGx5IHlvdSBjYW4gZG93bmxvYWQgUiAoaHR0cHM6Ly9taXJyb3IubGFzLmlhc3RhdGUuZWR1L0NSQU4vKSBhbmQgUlN0dWRpbyAoaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcHJvZHVjdHMvcnN0dWRpby9kb3dubG9hZDMvKS4gVGhlcmUgYXJlIGxvdHMgb2YgaGVscGZ1bCB0aXBzIG9ubGluZSBhYm91dCBnZXR0aW5nIHN0YXJ0ZWQgd2l0aCBSLCBhbmQgSSdtIHdyaXRpbmcgdGhpcyBwb3N0IHdpdGggdGhlIGFzc3VtcHRpb24gdGhhdCB5b3UgaGF2ZSBSU3R1ZGlvIGluc3RhbGxlZCBhbmQgeW91J3JlIGFibGUgdG8gcnVuIGNvZGUgdGhhdCBJIHByb3ZpZGUuCgojIyBDb2RlIGZvciB0aGlzIGFydGljbGUKVGhlIFIgbm90ZWJvb2sgdXNlZCB0byBwcm9kdWNlIHRoaXMgYXJ0aWNsZSBpcyBmb3VuZCBvbiBteSBHaXRIdWIgcmVwb3NpdG9yeSBhdCAgaHR0cHM6Ly9naXRodWIuY29tL3RldXNjaGIvaHJfZGF0YS9ibG9iL21hc3Rlci9hbmFseXNlcy9hdXRvbWF0aW5nX3JlcG9ydF9zbGlkZXMuUm1kLiBJZiB5b3UganVzdCB3YW50IHRoZSBzY3JpcHQgd2l0aCBjb2RlIG9ubHksIHRoYXQncyBmb3VuZCBhdCBodHRwczovL2dpdGh1Yi5jb20vdGV1c2NoYi9ocl9kYXRhL2Jsb2IvbWFzdGVyL2FuYWx5c2VzL2F1dG9tYXRpbmdfcmVwb3J0X3NsaWRlcy5SLiBUaGVyZSBpcyBhbHNvIGEgc2ltcGxpZmllZCB2ZXJzaW9uIG9mIHRoaXMgY29kZSBhdCBodHRwczovL2dpdGh1Yi5jb20vdGV1c2NoYi9ocl9kYXRhL2Jsb2IvbWFzdGVyL2FuYWx5c2VzL2F1dG9tYXRpbmdfcmVwb3J0X3NsaWRlc19zaW1wbGUuUm1kIHdoaWNoIHByb2R1Y2VzIGEgUG93ZXJQb2ludCByZXBvcnQgdXNpbmcgbXVjaCBzaW1wbGVyIGNvZGUsIGJ1dCBkb2Vzbid0IGxvb2sgcXVpdGUgYXMgbmljZS4KClRocm91Z2hvdXQgdGhpcyBleGFtcGxlLCBJJ3ZlIHVzZWQgbW9zdGx5IGJhc2UgUiBmdW5jdGlvbnMgdG8gbWFuaXB1bGF0ZSBkYXRhLiBIb3dldmVyLCBJIGhpZ2hseSByZWNvbW1lbmQgZHBseXIsIGFuZCB0aGUgdGlkeXZlcnNlIGluIGdlbmVyYWwuIENoZWNrIHRoYXQgb3V0IGhlcmU6IGh0dHA6Ly90aWR5dmVyc2Uub3JnLwoKIyMgRGF0YXNldApUaGUgZXhhbXBsZSBkYXRhc2V0IGlzIGJhc2VkIG9uIHRoZSBIUiBFbXBsb3llZSBBdHRyaXRpb24gYW5kIFBlcmZvcm1hbmNlIGRhdGFzZXQgZnJvbSBJQk0gV2F0c29uIEFuYWx5dGljcyAoaHR0cHM6Ly93d3cuaWJtLmNvbS9jb21tdW5pdGllcy9hbmFseXRpY3Mvd2F0c29uLWFuYWx5dGljcy1ibG9nL2hyLWVtcGxveWVlLWF0dHJpdGlvbi8pLiBJJ3ZlIG1vZGlmaWVkIGl0IHNsaWdodGx5IGFuZCBzYXZlZCBpdCB0byBteSBHaXRIdWIgcmVwb3NpdG9yeSAoaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3RldXNjaGIvaHJfZGF0YS9tYXN0ZXIvZGF0YXNldHMvbW9kaWZpZWRfd2F0c29uX2RhdGFzZXQuY3N2KSwgd2hpY2ggYWxsb3dzIG1lIHRvIHJlYWQgaW4gdGhlIC5jc3YgZmlsZSBkaXJlY3RseSBmcm9tIHRoZSBJbnRlcm5ldCwgd2l0aG91dCBuZWVkaW5nIHRvIGRvd25sb2FkIGEgZmlsZS4KCkluIHByYWN0aWNlLCB5b3VyIGRhdGFzZXQgd2lsbCBiZSB5b3VyIHN0YW5kYXJkIEhSSVMgcmVwb3J0IG9mIGhlYWRjb3VudCwgYXR0cml0aW9uLCBwZXJmb3JtYW5jZSwgb3Igd2hhdGV2ZXIgZGF0YSB5b3Ugd2FudCB0byB1c2UgZm9yIGEgcmVwb3J0LiBUaGF0IHdheSwgZWFjaCBtb250aCwgcXVhcnRlciwgb3IgeWVhciwgeW91J2xsIGJlIGFibGUgdG8gcnVuIHRoaXMgc2FtZSBsaW5lIG9mIGNvZGUsIHJlcGxhY2luZyB0aGUgcHJldmlvdXMgZmlsZSB3aXRoIHRoZSBuYW1lIG9mIHRoZSBtb3N0IHJlY2VudCBkYXRhc2V0IHRoYXQgeW91IHdhbnQgdG8gd29yayB3aXRoLiBJZiBhbGwgdGhlIG5lZWRlZCBkYXRhIGZpZWxkcyBoYXZlIHRoZSBzYW1lIG5hbWVzIGVhY2ggdGltZSwgdGhlIHJlc3Qgb2YgdGhlIGNvZGUgd2lsbCBydW4gd2l0aG91dCB5b3UgaGF2aW5nIHRvIG1ha2UgY2hhbmdlcyBlYWNoIG1vbnRoLiBZb3UgY2FuIGV2ZW4gdXNlIFIgdG8gZ2V0IHNldmVyYWwgc3RhbmRhcmQgcmVwb3J0cyBhbmQgbWVyZ2UgdGhlbSB0b2dldGhlciBxdWlja2x5LCBhZ2FpbiBpbiBhbiBhdXRvbWF0ZWQgd2F5LCBidXQgaGVyZSBJIHVzZSBqdXN0IG9uZSBkYXRhIGZpbGUuCgpUaGlzIGNvZGUgbG9hZHMgdGhlIGRhdGEgZnJvbSBHaXRIdWIgaW50byBSLgoKYGBge3J9Cm15ZGF0YSA8LSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3RldXNjaGIvaHJfZGF0YS9tYXN0ZXIvZGF0YXNldHMvbW9kaWZpZWRfd2F0c29uX2RhdGFzZXQuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCiMjIFNldCBSZXBvcnQgUGFyYW1ldGVycwpUaGUgb25lIHRoaW5nIEkgZG8gbWFudWFsbHkgdXBkYXRlIGVhY2ggbW9udGggaXMgdGhlIGRhdGUgb2YgdGhlIHJlcG9ydC4gQnkgc2V0dGluZyBpdCBvbmNlIGF0IHRoZSB0b3AsIEkgY2FuIHJlZmVyZW5jZSBpdCB0aHJvdWdob3V0IHRoZSBzY3JpcHQgd2l0aG91dCB3b3JyeWluZyBhYm91dCBhbGwgdGhlIHBsYWNlcyBJIG1heSBoYXZlIHVzZWQgaXQuCgpgYGB7cn0KbW9udGggPSAiTWF5IgpkYXRlID0gIk1heSAyMDE3IgpgYGAKCiMjIFRoZSBSZXBvcnRlUnMgcGFja2FnZQpSIGlzbid0IGFibGUgdG8gd3JpdGUgLnBwdHggZmlsZXMgYnkgZGVmYXVsdC4gV2hhdCBtYWtlcyB0aGlzIHBvc3NpYmxlIGlzIHRoZSBgUmVwb3J0ZVJzYCBwYWNrYWdlLiBUbyBsZWFybiBhIGxvdCBtb3JlIGFib3V0IHdoYXQgdGhlIHBhY2thZ2UgY2FuIGRvLCBjaGVjayBvdXQgaHR0cDovL2RhdmlkZ29oZWwuZ2l0aHViLmlvL1JlcG9ydGVScy8uIEl0IGFsbG93cyB5b3UgdG8gdXNlIFIgdG8gcnVuIHlvdXIgYW5hbHlzaXMsIHByaW50IHBsb3RzLCBhbmQgZXhwb3J0IGV2ZXJ5dGhpbmcgdG8gYSBQb3dlclBvaW50IGRvY3VtZW50IGluIGEgcmVwZWF0YWJsZSwgYXV0b21hdGVkIHdheS4KCmBSZXBvcnRlUnNgIHJlcXVpcmVzIGBySmF2YWAgYW5kIGZvciBKYXZhIHRvIGJlIGluc3RhbGxlZCBvbiB5b3VyIGNvbXB1dGVyLiBUaGlzIGNvZGUgd2lsbCBtYWtlIHN1cmUgaXQncyBpbnN0YWxsZWQsIGFuZCB3aWxsIHNob3cgeW91IHRoZSB2ZXJzaW9uIG9mIEphdmEgKGl0IHNob3VsZCBiZSBhdCBsZWFzdCAxLjYpLgpgYGB7cn0KcmVxdWlyZShySmF2YSkKc3lzdGVtKCJqYXZhIC12ZXJzaW9uIikKYGBgClRoZSBmaXJzdCB0aW1lIHlvdSB1c2UgYFJlcG9ydGVSc2AsIHlvdSdsbCBuZWVkIHRvIGluc3RhbGwgdGhlIHBhY2thZ2UgZnJvbSBDUkFOLCB0aGUgUiBwYWNrYWdlIHJlcG9zaXRvcnkuIFRoaXMgY29kZSBjaGVja3Mgd2hldGhlciBpdCdzIGFscmVhZHkgaW5zdGFsbGVkLCBhbmQgaW5zdGFsbHMgaXQgaWYgaXQgaXNuJ3QuCmBgYHtyfQppZiAoIXJlcXVpcmUoJ1JlcG9ydGVScycpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygiUmVwb3J0ZVJzIikKfQpgYGAKCiMjIExpYnJhcmllcwpCZWZvcmUgd2UgY2FuIGdldCBzdGFydGVkLCB3ZSBoYXZlIHRvIGxvYWQgaW4gYSBmZXcgb3RoZXIgcGFja2FnZXMgSSBsaWtlIHRvIHVzZS4gWW91J2xsIHNlZSB0aGF0IGBSZXBvcnRlUnNgIGlzIG9uIHRoZSBsaXN0IC0gcnVubmluZyBgaW5zdGFsbC5wYWNrYWdlcygnUmVwb3J0ZVJzJylgIGdldHMgdGhlIHBhY2thZ2Ugb250byBvdXIgY29tcHV0ZXIsIGFuZCBgbGlicmFyeShSZXBvcnRlUnMpYCB0ZWxscyBSIHRoYXQgd2Ugd2FudCB0byB1c2UgdGhhdCBwYWNrYWdlIHJpZ2h0IG5vdy4gCgpJZiB5b3Ugc2VlIGEgcGFja2FnZSBuYW1lIHlvdSBkb24ndCByZWNvZ25pemUsIG1ha2Ugc3VyZSB5b3UgcnVuIGluc3RhbGwucGFja2FnZXMgZm9yIGl0LiBGb3IgZXhhbXBsZSwgYGluc3RhbGwucGFja2FnZXMoJ2dncGxvdDInKWAuCmBgYHtyfQpsaWJyYXJ5KFJlcG9ydGVScykKbGlicmFyeShnZ3Bsb3QyKSAgICAgIyBmb3IgcGxvdHRpbmcgZ3JhcGhzCmxpYnJhcnkoc2NhbGVzKSAgICAgICMgZm9yIGZvcm1hdHRpbmcgbnVtYmVycwpsaWJyYXJ5KG1hZ3JpdHRyKSAgICAjIGZvciB0aGUgJT4lIG9wZXJhdG9yCmBgYAoKIyMgQW5hbHlzaXMKV2hhdCBhbmFseXNpcyBuZWVkcyB0byBiZSBkb25lIGZvciB0aGUgcmVwb3J0PyBIUiBpcyBvZnRlbiBndWlsdHkgb2YgcHJvZHVjaW5nIHJlcG9ydHMgYW5kIGRhc2hib2FyZHMgb24gSFIgcHJvY2Vzc2VzIHRoYXQgZG9uJ3QgZGlyZWN0bHkgaGVscCBidXNpbmVzcyBsZWFkZXJzIG1ha2UgYmV0dGVyIGRlY2lzaW9ucy4gR29vZCByZXBvcnRzIHNob3VsZCBhbHdheXMgYmUgcHJvZHVjZWQgZm9yIGEgc3BlY2lmaWMgcHVycG9zZS4gU2luY2UgSSBkb24ndCBoYXZlIGEgc3BlY2lmaWMgYnVzaW5lc3MgcHJvYmxlbSBJJ20gdHJ5aW5nIHRvIHNvbHZlIGluIHRoaXMgZXhhbXBsZSwgYW5kIGJlY2F1c2UgSFIgaXMgdXNlZCB0byByZXBvcnRpbmcgb24gYXR0cml0aW9uLCBJJ2xsIHByZXBhcmUgc29tZSBkYXRhIGZvciBhIGJhc2ljIGF0dHJpdGlvbiByZXBvcnQgd2l0aCBhIGZldyBiYXIgY2hhcnRzIHRvIHZpc3VhbGl6ZSB0aGUgZGF0YS4gCgpKdXN0IGtub3cgdGhhdCBSIGlzIGFibGUgdG8gcHJvZHVjZSB2YXJpb3VzIHR5cGVzIG9mIGNoYXJ0cyBhbmQgcHJvZHVjZSB3aGF0ZXZlciBhbmFseXNpcyBpcyBuZWNlc3NhcnkgdG8gaGVscCB5b3VyIGJ1c2luZXNzLiBJZiB5b3UgaGF2ZSBhbnkgc3BlY2lmaWMgZXhhbXBsZXMgaW4gbWluZCB5b3UnZCBsaWtlIHRvIHNlZSwgbGV0IG1lIGtub3cuCgojIyMjIEF0dHJpdGlvbiBPdmVydmlldyBUYWJsZQpGb3IgdGhpcyBleGFtcGxlLCB0aGUgZmlyc3Qgc2xpZGUgd2lsbCBiZSBhIHRhYmxlIHdpdGggYW4gb3ZlcnZpZXcgb2YgYXR0cml0aW9uLgoKVGhpcyBjb2RlIHRha2VzIHRoZSBkYXRhIGFuZCB1c2VzIGBhZ2dyZWdhdGVgIHRvIGV4dHJhY3QgdGhlIHRvdGFsIG51bWJlciBvZiBlbXBsb3llZXMsIHRoZSBudW1iZXIgb2YgZW1wbG95ZWVzIHdobyBsZWZ0LCBhbmQgdGhlIGF0dHJpdGlvbiByYXRlIGZvciB0aGlzIG1vbnRoIChjYWxjdWxhdGVkIGFzIGAjIG9mIGVtcGxveWVlcyBnb25lLyMgb2YgZW1wbG95ZWVzIGF0IHRoZSBzdGFydCBvZiB0aGUgbW9udGhgKSwgYWxsIGJ5IGRlcGFydG1lbnQuCgpgYGB7cn0Kb3ZlcmFsbF9hdHRyaXRpb24gPC0gYXMuZGF0YS5mcmFtZSgKICBhcy5saXN0KGFnZ3JlZ2F0ZShBdHRyaXRpb24gfiBEZXBhcnRtZW50LAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBteWRhdGEsCiAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oeCkgYygKICAgICAgICAgICAgICAgICAgICAgIG4gPSBsZW5ndGgoeCksCiAgICAgICAgICAgICAgICAgICAgICBzID0gc3VtKHgsIG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICBtbiA9IG1lYW4oeCwgbmEucm0gPSBUKSkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICApLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKYGBgCgpOZXh0LCBJIGFkZCBhIHJvdyBvZiB0b3RhbHMgdXNpbmcgYHJiaW5kYC4KYGBge3J9Cm92ZXJhbGxfYXR0cml0aW9uIDwtIHJiaW5kKG92ZXJhbGxfYXR0cml0aW9uLCBjKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAsICMgdG8gYmUgcmVwbGFjZWQgd2l0aCB0aGUgd29yZCAiVG90YWwiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKG92ZXJhbGxfYXR0cml0aW9uJEF0dHJpdGlvbi5uKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdW0ob3ZlcmFsbF9hdHRyaXRpb24kQXR0cml0aW9uLnMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW4ob3ZlcmFsbF9hdHRyaXRpb24kQXR0cml0aW9uLm1uKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgKQpvdmVyYWxsX2F0dHJpdGlvbltucm93KG92ZXJhbGxfYXR0cml0aW9uKSwxXSA8LSAnVG90YWwnCmBgYAoKQmVmb3JlIEkgcHV0IHRoZSBkYXRhIGludG8gYSB0YWJsZSwgSSB3YW50IHRvIGFkZCAlIHN5bWJvbHMgYW5kIHRvIG1ha2UgdGhlIGNvbHVtbiBuYW1lcyBtb3JlIHByZXNlbnRhYmxlLiBJIGFsc28gY2hhbmdlIHRoZSBmb250IHNpemUgYmVmb3JlIHB1dHRpbmcgaXQgaW50byBhIHNwZWNpYWwga2luZCBvZiBgUmVwb3J0ZVJzYCB0YWJsZSBjYWxsZWQgYSBgdmFuaWxsYS50YWJsZWAuCgpPbmNlIEkgbWFrZSB0aGUgdGFibGUsIEkgYWxzbyBmb3JtYXQgaXQuIEkgdXNlIGBzZXRaZWJyYVN0eWxlYCB0byBhZGQgc29tZSBzaGFkaW5nLCBhbmQgYHNldEZsZXhUYWJsZVdpZHRoc2AgdG8gYWRqdXN0IHRoZSBjb2x1bW4gd2lkdGhzIHRvIGZpdCB0aGUgdGV4dC4gVGhlIGZpbmFsIGJsb2NrIG9mIGNvZGUganVzdGlmaWVzIHRoZSB0ZXh0IHRvIHRoZSBsZWZ0IG9yIGNlbnRlciwgYW5kIG1ha2VzIHNvbWUgdGV4dCBib2xkLgoKYGBge3J9CiMgZm9ybWF0IGFzIHBlcmNlbnRhZ2VzLCByZW5hbWUgY29sdW1ucwpvdmVyYWxsX2F0dHJpdGlvbiRBdHRyaXRpb24ubW4gPC0gcGVyY2VudChhcy5udW1lcmljKG92ZXJhbGxfYXR0cml0aW9uJEF0dHJpdGlvbi5tbikpCm5hbWVzKG92ZXJhbGxfYXR0cml0aW9uKSA8LSBjKCdEZXBhcnRtZW50JywgJ0hlYWRjb3VudCcsICdBdHRyaXRpb24nLCAnQXR0cml0aW9uICUnKQoKIyBQdXQgZGF0YSBpbnRvIGEgdGFibGUKb3B0aW9ucygiUmVwb3J0ZVJzLWZvbnRzaXplIj0xNikgI2ZvbnQgc2l6ZSBmb3IgdGFibGVzCm92ZXJhbGxfYXR0cml0aW9uX3RhYmxlIDwtIHZhbmlsbGEudGFibGUob3ZlcmFsbF9hdHRyaXRpb24pICU+JQogIHNldFplYnJhU3R5bGUob2RkID0gJyNlZWVlZWUnLCBldmVuID0gJ3doaXRlJyApICU+JQogIHNldEZsZXhUYWJsZVdpZHRocyh3aWR0aHMgPSBjKDQuNSwgMi41LCAyLCAyLjUpKSAKCm92ZXJhbGxfYXR0cml0aW9uX3RhYmxlWywxXSA9IHBhckxlZnQoKQpvdmVyYWxsX2F0dHJpdGlvbl90YWJsZVssMjo0XSA9IHBhckNlbnRlcigpCm92ZXJhbGxfYXR0cml0aW9uX3RhYmxlWywsdG89J2hlYWRlciddID0gcGFyQ2VudGVyKCkKb3ZlcmFsbF9hdHRyaXRpb25fdGFibGVbNCxdID0gdGV4dFByb3BlcnRpZXMoZm9udC53ZWlnaHQgPSAnYm9sZCcgKQpvdmVyYWxsX2F0dHJpdGlvbl90YWJsZQpgYGAKCkZvbGxvd2luZyB0aGF0IG92ZXJ2aWV3LCBJIHJlcG9ydCBhdHRyaXRpb24gYnkgam9iIHJvbGUsIHBlcmZvcm1hbmNlLCBhbmQgaGlyZSBzb3VyY2UgKG9yIHJlY3J1aXRpbmcgY2hhbm5lbCkuIEZvciBlYWNoIG9uZSBJJ20gZ29pbmcgdG8gbWFrZSBhIHBsb3QgdGhhdCBzaG93cyB0aGUgdHVybm92ZXIsIGFuZCB0aGVuIGNyZWF0ZSBhIHRhYmxlIHRoYXQgcmVzZW1ibGVzIHRoZSBvdmVyYWxsIGF0dHJpdGlvbiB0YWJsZS4KCiMjIyMgQXR0cml0aW9uIGJ5IGpvYiByb2xlClRoaXMgY29kZSBzaG91bGQgbG9vayBmYW1pbGlhciAtLSBJJ20gZXh0cmFjdGluZyB0aGUgdG90YWwgbnVtYmVyIG9mIGVtcGxveWVlcywgdGhlIG51bWJlciBvZiBlbXBsb3llZXMgd2hvIGxlZnQsIGFuZCB0aGUgYXR0cml0aW9uIHJhdGUgZm9yIHRoaXMgbW9udGggYnkgam9iIHJvbGUuCmBgYHtyfQpqb2Jyb2xlX2F0dHJpdGlvbiA8LSBhcy5kYXRhLmZyYW1lKAogIGFzLmxpc3QoYWdncmVnYXRlKEF0dHJpdGlvbiB+IEpvYlJvbGUsCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IG15ZGF0YSwKICAgICAgICAgICAgICAgICAgICBGVU4gPSBmdW5jdGlvbih4KSBjKAogICAgICAgICAgICAgICAgICAgICAgbiA9IGxlbmd0aCh4KSwKICAgICAgICAgICAgICAgICAgICAgIHMgPSBzdW0oeCwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICAgICAgICAgIG1uID0gbWVhbih4LCBuYS5ybSA9IFQpKQogICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICkpCgpuYW1lcyhqb2Jyb2xlX2F0dHJpdGlvbikgPSBjKCdKb2JSb2xlJywgJ0hlYWRjb3VudCcsICdBdHRyaXRpb24nLCAnQXR0cml0aW9uUmF0ZScpCmBgYAoKRm9yIG1ha2luZyBncmFwaHMsIEkgdXNlIHRoZSBwb3B1bGFyIGBnZ3Bsb3QyYCBwYWNrYWdlLiBJdCdzIGEgcmVhbGx5IHBvd2VyZnVsIHBhY2thZ2UgdGhhdCBhbGxvd3MgeW91IHRvIHZpc3VhbGl6ZSBhbGwga2luZHMgb2YgZGF0YSwgYW5kIGN1c3RvbWl6ZSB0aGUgcmVzdWx0aW5nIHBsb3RzLgoKSGVyZSwgSSBtYWtlIGEgYmFyIGNoYXJ0IHdoZXJlIGVhY2ggYmFyIHJlcHJlc2VudHMgYSBgSm9iUm9sZWAgYW5kIHRoZSBoZWlnaHQgb2YgdGhlIGJhciByZXByZXNlbnRzIHRoZSBhdHRyaXRpb24gcmF0ZS4gSSBzYXZlIHRoZSBncmFwaCBhcyBgam9icm9sZV9hdHRyaXRpb25fcGxvdGAgc28gSSBjYW4gdXNlIGl0IGxhdGVyLgoKSSBkbyBhIGJpdCBvZiBjdXN0b21pemF0aW9uIGhlcmUuIEkgdXNlIHNjYWxlX2ZpbGxfbWFudWFsIHRvIG1ha2UgdGhlIGJhcnMgYmx1ZS4gVGhlbWVfbWluaW1hbCByZW1vdmVzIGEgbG90IG9mIGV4dHJhIGNoYXJ0IGxpbmVzIHRoYXQgSSBwcmVmZXIgbm90IGJlIHRoZXJlLiBJIGFsc28gY2hhbmdlIHRoZSBheGlzIGxhYmVscyBpbnRvIHBlcmNlbnRhZ2VzIHdpdGggbGFiZWxzPXBlcmNlbnQsIGZsaXAgdGhlIGdyYXBoIHNvIGl04oCZcyBob3Jpem9udGFsIHdpdGggY29vcmRfZmxpcCwgYW5kIGFkZCBjdXN0b20gbGFiZWxzIHdpdGggbGFicy4KYGBge3J9Cihqb2Jyb2xlX2F0dHJpdGlvbl9wbG90IDwtIGdncGxvdChqb2Jyb2xlX2F0dHJpdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhyZW9yZGVyKEpvYlJvbGUsIEF0dHJpdGlvblJhdGUpLCBBdHRyaXRpb25SYXRlKSkgKwogIGdlb21fY29sKGFlcyhmaWxsID0gJycpKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiIzFEMjQzQyIpLGd1aWRlPUZBTFNFKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzPXBlcmNlbnQpICsKICBsYWJzKGxpc3QoeSA9IHBhc3RlKCJBdHRyaXRpb24gaW4gIixtb250aCxzZXAgPSAiIiksIHggPSAiSm9iIFJvbGUiLAogICAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJBdHRyaXRpb24gYnkgSm9iIFJvbGUsICIsIGRhdGUsIHNlcCA9ICIiKSkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSArCiAgY29vcmRfZmxpcCgpKQpgYGAKSSB3YW50IG15IHRhYmxlIHRvIGJlIHNvcnRlZCBmcm9tIGhpZ2hlc3QgdG8gbG93ZXN0LCBzbyBmaXJzdCBJIHNvcnQgbXkgZGF0YXNldC4KYGBge3J9CmpvYnJvbGVfYXR0cml0aW9uIDwtIGpvYnJvbGVfYXR0cml0aW9uW29yZGVyKGpvYnJvbGVfYXR0cml0aW9uJEF0dHJpdGlvblJhdGUsIGRlY3JlYXNpbmcgPSBUUlVFKSxdCmBgYAoKQWx0aG91Z2ggSSB3b24ndCBpbmNsdWRlIGl0IGluIHRoaXMgdGFibGUsIEkgY2FuIHVzZSB0aGlzIGluZm9ybWF0aW9uIHRvIGdldCB0aGUgaW1wYWN0IG9mIHJlZHVjaW5nIGF0dHJpdGlvbiBmb3IgdGhlIGpvYiByb2xlIHdpdGggdGhlIGhpZ2hlc3QgYXR0cml0aW9uLiBJJ2xsIHVzZSB0aGlzIGluIHRoZSByZWNvbW1lbmRhdGlvbnMgc2VjdGlvbiwgYW5kIGl0J3MgZWFzaWVyIHRvIGNhbGN1bGF0ZSBpdCBub3cuCgpgYGB7cn0KcmVkdWN0aW9uID0gLjA1ICAgICAgICAgICAgIyBob3cgbXVjaCBjb3VsZCB3ZSByZWR1Y2UgYXR0cml0aW9uPwpyZXBsYWNlbWVudF9tdWx0ID0gMS41ICAgICAgICMgaG93IG11Y2ggZG9lcyBpdCBjb3N0IHRvIHJlcGxhY2UgYW4gZW1wbG95ZWUsIGFzIGEgbXVsdGlwbGllciBvZiB0aGVpciBzYWxhcnk/CnRvcF9yb2xlIDwtIGFzLmNoYXJhY3Rlcihqb2Jyb2xlX2F0dHJpdGlvbiRKb2JSb2xlWzFdKSAgICMgam9iIHJvbGUgd2l0aCBoaWdoZXN0IGF0dHJpdGlvbgp0b3Bfcm9sZV9hdHRyaXRpb24gPC0gam9icm9sZV9hdHRyaXRpb24kQXR0cml0aW9uUmF0ZVsxXSAjIGF0dHJpdGlvbiBmb3IgdGhhdCByb2xlCmxlZnRfZnJvbV90b3Bfcm9sZSA8LSBteWRhdGEkSm9iUm9sZSA9PSB0b3Bfcm9sZSAmIG15ZGF0YSRBdHRyaXRpb24gPT0gMSAKc2FsYXJ5X2xvc3QgPSAKICBzdW0obXlkYXRhJE1vbnRobHlJbmNvbWVbbGVmdF9mcm9tX3RvcF9yb2xlXSkgKiAxMiAqICAgICAjIHRvdGFsIGFubnVhbCBzYWxhcnkgbG9zdCAKICByZXBsYWNlbWVudF9tdWx0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRpbWVzIHJlcGxhY2VtZW50IGNvc3QKaW1wYWN0ID0gc2FsYXJ5X2xvc3QgKiByZWR1Y3Rpb24gIyBhbW91bnQgd2UgY291bGQgc2F2ZQpgYGAKCkZvcm1hdHRpbmcgdGhlIHRhYmxlIHNob3VsZCBsb29rIGZhbWlsaWFyLgpgYGB7cn0gCgojIGZvcm1hdCBhcyBwZXJjZW50YWdlcywgcmVuYW1lIGNvbHVtbnMKam9icm9sZV9hdHRyaXRpb24kQXR0cml0aW9uUmF0ZSA8LSBwZXJjZW50KGpvYnJvbGVfYXR0cml0aW9uJEF0dHJpdGlvblJhdGUpCm5hbWVzKGpvYnJvbGVfYXR0cml0aW9uKSA9IGMoJ0pvYiBSb2xlJywgJ0hlYWRjb3VudCcsICdBdHRyaXRpb24nLCAnQXR0cml0aW9uICUnKQoKIyBwdXQgZGF0YSBpbnRvIGEgdGFibGUKb3B0aW9ucygiUmVwb3J0ZVJzLWZvbnRzaXplIj0xNikgI2ZvbnQgc2l6ZSBmb3IgdGFibGVzCnRhYmxlX3dpZHRocyA9IGMoMi44LCAxLjIsIDEsIDEuNikgCgpqb2Jyb2xlX2F0dHJpdGlvbl90YWJsZSA8LSB2YW5pbGxhLnRhYmxlKGpvYnJvbGVfYXR0cml0aW9uKSAlPiUKICBzZXRaZWJyYVN0eWxlKG9kZCA9ICcjZWVlZWVlJywgZXZlbiA9ICd3aGl0ZScgKSAlPiUKICBzZXRGbGV4VGFibGVXaWR0aHMod2lkdGhzID0gdGFibGVfd2lkdGhzKSAKam9icm9sZV9hdHRyaXRpb25fdGFibGVbLDI6NF0gPSBwYXJDZW50ZXIoKQpqb2Jyb2xlX2F0dHJpdGlvbl90YWJsZVssMV0gPSBwYXJMZWZ0KCkKam9icm9sZV9hdHRyaXRpb25fdGFibGVbLCx0bz0naGVhZGVyJ10gPSBwYXJDZW50ZXIoKQpqb2Jyb2xlX2F0dHJpdGlvbl90YWJsZQpgYGAKTm93IEknbSBnb2luZyB0byBtYWtlIHRoZSBzYW1lIGdyYXBoIGFuZCB0YWJsZSBmb3IgcGVyZm9ybWFuY2UgYW5kIGhpcmUgc291cmNlLiBUaGUgb25seSBkaWZmZXJlbmNlIGlzIEkgdXNlIGBzY2FsZV94X3JldmVyc2VgIG9uIHBlcmZvcm1hbmNlIHJhdGluZyBzbyB0aGF0IHRoZSAnMScgcmF0aW5nIGlzIGF0IHRoZSB0b3Agb2YgdGhlIGdyYXBoLiBJJ20gYWxzbyBjYWxjdWxhdGluZyB0aGUgaGlyZSBzb3VyY2VzIHRoYXQgaGF2ZSB0aGUgaGlnaGVzdCBhbmQgbG93ZXN0IGF0dHJpdGlvbiByYXRlcywgZm9yIHVzZSBpbiB0aGUgcmVjb21tZW5kYXRpb25zIHNlY3Rpb24uCgojIyMjIEF0dHJpdGlvbiBieSBQZXJmb3JtYW5jZQpgYGB7cn0KcGVyZm9ybWFuY2VfYXR0cml0aW9uIDwtIGFzLmRhdGEuZnJhbWUoCiAgYXMubGlzdChhZ2dyZWdhdGUoQXR0cml0aW9uIH4gUGVyZm9ybWFuY2VSYXRpbmcsCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IG15ZGF0YSwgRlVOID0gZnVuY3Rpb24oeCkgYygKICAgICAgICAgICAgICAgICAgICAgIG4gPSBsZW5ndGgoeCksCiAgICAgICAgICAgICAgICAgICAgICBzID0gc3VtKHgsIG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICBtbiA9IG1lYW4oeCwgbmEucm0gPSBUKSkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICApKQoKbmFtZXMocGVyZm9ybWFuY2VfYXR0cml0aW9uKSA9IGMoJ1BlcmZvcm1hbmNlUmF0aW5nJywgJ0hlYWRjb3VudCcsICdBdHRyaXRpb24nLCAnQXR0cml0aW9uUmF0ZScpCgoocGVyZm9ybWFuY2VfYXR0cml0aW9uX3Bsb3QgPC0gZ2dwbG90KHBlcmZvcm1hbmNlX2F0dHJpdGlvbiwgYWVzKFBlcmZvcm1hbmNlUmF0aW5nLCBBdHRyaXRpb25SYXRlKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBhZXMoZmlsbCA9ICcnKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMxRDI0M0MiKSxndWlkZT1GQUxTRSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscz1wZXJjZW50KSArCiAgc2NhbGVfeF9yZXZlcnNlKCkgKwogIGxhYnMobGlzdCh5ID0gcGFzdGUoIkF0dHJpdGlvbiBpbiAiLG1vbnRoLHNlcCA9ICIiKSwgeCA9ICJQZXJmb3JtYW5jZSBSYXRpbmciLAogICAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJBdHRyaXRpb24gYnkgUGVyZm9ybWFuY2UgUmF0aW5nLCAiLCBkYXRlLCBzZXAgPSAiIikpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIGNvb3JkX2ZsaXAoKSkKCiMgZm9ybWF0IGFzIHBlcmNlbnRhZ2VzLCByZW5hbWUgY29sdW1ucwpwZXJmb3JtYW5jZV9hdHRyaXRpb24kQXR0cml0aW9uUmF0ZSA8LSBwZXJjZW50KHBlcmZvcm1hbmNlX2F0dHJpdGlvbiRBdHRyaXRpb25SYXRlKQpuYW1lcyhwZXJmb3JtYW5jZV9hdHRyaXRpb24pID0gYygnUGVyZm9ybWFuY2UgUmF0aW5nJywgJ0hlYWRjb3VudCcsICdBdHRyaXRpb24nLCAnQXR0cml0aW9uICUnKQoKIyBwdXQgZGF0YSBpbnRvIGEgdGFibGUKcGVyZm9ybWFuY2VfYXR0cml0aW9uX3RhYmxlIDwtIHZhbmlsbGEudGFibGUocGVyZm9ybWFuY2VfYXR0cml0aW9uKSAlPiUKICBzZXRaZWJyYVN0eWxlKG9kZCA9ICcjZWVlZWVlJywgZXZlbiA9ICd3aGl0ZScgKSAlPiUKICBzZXRGbGV4VGFibGVXaWR0aHMod2lkdGhzID0gdGFibGVfd2lkdGhzKSAKcGVyZm9ybWFuY2VfYXR0cml0aW9uX3RhYmxlWywxOjRdID0gcGFyQ2VudGVyKCkKcGVyZm9ybWFuY2VfYXR0cml0aW9uX3RhYmxlWywsdG89J2hlYWRlciddID0gcGFyQ2VudGVyKCkKcGVyZm9ybWFuY2VfYXR0cml0aW9uX3RhYmxlCmBgYAoKIyMjIyBBdHRyaXRpb24gYnkgUmVjcnVpdGluZyBDaGFubmVsCmBgYHtyfQpoaXJlc291cmNlX2F0dHJpdGlvbiA8LSBhcy5kYXRhLmZyYW1lKAogIGFzLmxpc3QoYWdncmVnYXRlKEF0dHJpdGlvbiB+IEhpcmVTb3VyY2UsIGRhdGEgPSBteWRhdGEsCiAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oeCkgYygKICAgICAgICAgICAgICAgICAgICAgIG4gPSBsZW5ndGgoeCksCiAgICAgICAgICAgICAgICAgICAgICBzID0gc3VtKHgsIG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICBtbiA9IG1lYW4oeCwgbmEucm0gPSBUKSkKICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICApKQoKbmFtZXMoaGlyZXNvdXJjZV9hdHRyaXRpb24pID0gYygnSGlyZVNvdXJjZScsICdIZWFkY291bnQnLCAnQXR0cml0aW9uJywgJ0F0dHJpdGlvblJhdGUnKQoKKGhpcmVzb3VyY2VfYXR0cml0aW9uX3Bsb3QgPC0gZ2dwbG90KGhpcmVzb3VyY2VfYXR0cml0aW9uLCBhZXMocmVvcmRlcihIaXJlU291cmNlLCBBdHRyaXRpb25SYXRlKSwgQXR0cml0aW9uUmF0ZSkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgYWVzKGZpbGwgPSAnJykpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjMUQyNDNDIiksZ3VpZGU9RkFMU0UpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHM9cGVyY2VudCkgKwogIGxhYnMobGlzdCh5ID0gcGFzdGUoIkF0dHJpdGlvbiBpbiAiLG1vbnRoLHNlcCA9ICIiKSwgeCA9ICJKb2IgTGV2ZWwiLAogICAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJBdHRyaXRpb24gYnkgUmVjcnVpdGluZyBDaGFubmVsLCAiLCBkYXRlLCBzZXAgPSAiIikpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIGNvb3JkX2ZsaXAoKSkKCiMgc29ydCBieSBhdHRyaXRpb24gcmF0ZSwgZm9ybWF0IGFzIHBlcmNlbnRhZ2VzLCByZW5hbWUgY29sdW1ucwpoaXJlc291cmNlX2F0dHJpdGlvbiA8LSBoaXJlc291cmNlX2F0dHJpdGlvbltvcmRlcihoaXJlc291cmNlX2F0dHJpdGlvbiRBdHRyaXRpb25SYXRlLCBkZWNyZWFzaW5nID0gVFJVRSksXQpoaXJlc291cmNlX2F0dHJpdGlvbiRBdHRyaXRpb25SYXRlIDwtIHBlcmNlbnQoaGlyZXNvdXJjZV9hdHRyaXRpb24kQXR0cml0aW9uUmF0ZSkKbmFtZXMoaGlyZXNvdXJjZV9hdHRyaXRpb24pID0gYygnUmVjcnVpdGluZyBDaGFubmVsJywgJ0hlYWRjb3VudCcsICdBdHRyaXRpb24nLCAnQXR0cml0aW9uICUnKQoKIyBwdXQgZGF0YSBpbnRvIGEgdGFibGUgCmhpcmVzb3VyY2VfYXR0cml0aW9uX3RhYmxlIDwtIHZhbmlsbGEudGFibGUoaGlyZXNvdXJjZV9hdHRyaXRpb24pICU+JQogIHNldFplYnJhU3R5bGUob2RkID0gJyNlZWVlZWUnLCBldmVuID0gJ3doaXRlJyApICU+JQogIHNldEZsZXhUYWJsZVdpZHRocyh3aWR0aHMgPSB0YWJsZV93aWR0aHMpIApoaXJlc291cmNlX2F0dHJpdGlvbl90YWJsZVssMV0gPSBwYXJMZWZ0KCkKaGlyZXNvdXJjZV9hdHRyaXRpb25fdGFibGVbLDI6NF0gPSBwYXJDZW50ZXIoKQpoaXJlc291cmNlX2F0dHJpdGlvbl90YWJsZVssLHRvPSdoZWFkZXInXSA9IHBhckNlbnRlcigpCmhpcmVzb3VyY2VfYXR0cml0aW9uX3RhYmxlCgojIHNhdmUgaGlyZSBzb3VyY2VzIHdpdGggaGlnaGVzdCBhbmQgbG93ZXN0IGxldmVscyBvZiBhdHRyaXRpb24KdG9wX2hpcmUgPC0gaGlyZXNvdXJjZV9hdHRyaXRpb24kYFJlY3J1aXRpbmcgQ2hhbm5lbGBbMV0KYm90dG9tX2hpcmUgPC0gaGlyZXNvdXJjZV9hdHRyaXRpb24kYFJlY3J1aXRpbmcgQ2hhbm5lbGBbbnJvdyhoaXJlc291cmNlX2F0dHJpdGlvbildCmBgYApXaGF0J3MgZ3JlYXQgYWJvdXQgdXNpbmcgUiB0byBkbyB0aGUgZ3JhcGhzIGlzIHRoYXQgd2hlbmV2ZXIgSSBuZWVkIHRvIHVwZGF0ZSB0aGlzIHJlcG9ydCB3aXRoIG5ldyBkYXRhLCBhbGwgSSBoYXZlIHRvIGRvIGlzIGNoYW5nZSB0aGUgZGF0ZSwgYW5kIHRoZSByZXN0IGhhcHBlbnMgYXV0b21hdGljYWxseS4gSXQgdGFrZXMgYSBsaXR0bGUgbW9yZSBpbnZlc3RtZW50IGF0IGZpcnN0LCBidXQgbm93IGFsbCB0aGF0IGRyZWFkZWQgRXhjZWwgd29yayB3aWxsIHRha2UgYWxtb3N0IG5vIHRpbWUgYXQgYWxsISAKCiMjIENyZWF0aW5nIHRoZSBSZXBvcnQKIyMjIyBFeHBsYW5hdGlvbgpPbmNlIHRoZSBjaGFydHMgYW5kIHRhYmxlcyBhcmUgZmluaXNoZWQsIEkgbmVlZCB0byBwdXQgdGhlbSBpbnRvIFBvd2VyUG9pbnQuIEkndmUgZG9uZSB0aGlzIG1hbnkgdGltZXMgYnkgY29weS1wYXN0aW5nIHRoZSBncmFwaHMgZnJvbSBSIGludG8gdGhlIHJlcG9ydCwgYnV0IGl0J3MgdGVkaW91cyBhbmQgaGFyZCB0byBiZSBjb25zaXN0ZW50IHdpdGggdGhlaXIgc2l6ZSBhbmQgcGxhY2VtZW50LgoKQWdhaW4sIGZvciBtb3JlIGRldGFpbHMgYWJvdXQgaG93IHRoaXMgd29ya3MsIHRoZSBndWlkZSBpcyBvbiB0aGUgYXV0aG9yJ3MgZ2l0aHViIHBhZ2UgKGh0dHA6Ly9kYXZpZGdvaGVsLmdpdGh1Yi5pby9SZXBvcnRlUnMvYXJ0aWNsZXMvcG93ZXJwb2ludC5odG1sKS4gCgpGaXJzdCBJIGNyZWF0ZSBhIG5ldyBgcHB0eGAgZG9jdW1lbnQgYW5kIGNhbGwgaXQgYHJlcG9ydGAuIEkgc2V0IHRoZSBmb250IHNpemUgdG8gMjQsIGFuZCBJIGxpa2UgdG8gdXNlIGBzbGlkZS5sYXlvdXRzKHJlcG9ydClgIHRvIHNlZSB3aGljaCBQb3dlclBvaW50IHNsaWRlIGxheW91dHMgYXJlIGF2YWlsYWJsZS4gVGhlIGJhc2ljIGlkZWEgaXMgdG8gYWRkIGEgc2xpZGUgKGBhZGRTbGlkZWApLCBjaG9vc2UgYSBsYXlvdXQsIGFuZCB0aGVuIGZpbGwgdGhhdCBsYXlvdXQgd2l0aCBhIHRpdGxlIChgYWRkVGl0bGVgKSBhbmQgdGV4dCAoYGFkZFBhcmFncmFwaGApLCBhIHRhYmxlIChgYWRkRmxleFRhYmxlYCksIG9yIGEgZ3JhcGggKGBhZGRQbG90YCkuIEZpbmFsbHksIEkgdXNlIGB3cml0ZURvY2AgdG8gc2F2ZSB0aGUgZmlsZS4gSSdtIHNhdmluZyBpdCBpbnRvIHRoZSBocl9kYXRhIGZvbGRlci4gWW91J2xsIG5lZWQgdG8gbWFrZSBzdXJlIHRoYXQgcGF0aCBtYXRjaGVzIHdoZXJlIHlvdSB3YW50IHRvIHNhdmUgaXQgdG8uCgpZb3UnbGwgYWxzbyBzZWUgYW4gYXJndW1lbnQgZm9yIHRlbXBsYXRlIC0gSSdtIHVzaW5nIGEgUG93ZXJQb2ludCB0ZW1wbGF0ZSBmb3IgdGhpcyByZXBvcnQuIFRoaXMgbWVhbnMgeW91IGNhbiBidWlsZCB0aGVzZSByZXBvcnRzIGRpcmVjdGx5IG9uIHlvdXIgY29ycG9yYXRlIHJlcG9ydCB0ZW1wbGF0ZS4gSSBoYXZlIHRoZSB0ZW1wbGF0ZSBzYXZlZCBpbiB0aGUgaHJfZGF0YSBmb2xkZXIgb24gbXkgbWFjaGluZS4gWW91J2xsIG5lZWQgdG8gdXBkYXRlIHRoYXQgcGF0aCB3aXRoIHdoZXJldmVyIHlvdXIgdGVtcGxhdGUgaXMgc3RvcmVkLgoKV2hlbiB5b3Ugc2VlIGNvZGUgbGlrZSB0aGlzOiBgcGFyLnByb3BlcnRpZXMgPSBwYXJQcm9wZXJ0aWVzKGxpc3Quc3R5bGUgPSAidW5vcmRlcmVkIiwgbGV2ZWwgPSAxKWAsIFRoYXQncyBob3cgUmVwb3J0ZVJzIGNhbiBwcm9kdWNlIGluZGVudGVkIGJ1bGxldCBwb2ludCBsaXN0cy4gTGV2ZWwgPSAyIHdvdWxkIGluZGVudCB0aGUgYnVsbGV0cyBvbmUgbGV2ZWwsIGxldmVsID0gMyB3b3VsZCBiZSBhbm90aGVyIGxldmVsLgoKSWYgeW91J3ZlIG5ldmVyIHNlZW4gdGhlICU+JSBvcGVyYXRvciwgeW91IGNhbiByZWFkIGl0IGxpa2UsICJ0aGVuIi4gU28gdGhlIGZvbGxvd2luZyBjb2RlOgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpyZXBvcnQgPC0gcmVwb3J0ICU+JQogIGFkZFNsaWRlKHNsaWRlLmxheW91dCA9ICJUaXRsZSBTbGlkZSIpICU+JQogIGFkZFRpdGxlKHBhc3RlKGRhdGUsICJBY21lIENvLiBBdHRyaXRpb24gUmVwb3J0IikpCmBgYApjYW4gYmUgcmVhZCwgInRha2UgYHJlcG9ydGAsIHRoZW4gYWRkIGEgdGl0bGUgc2xpZGUsIHRoZW4gYWRkIGEgdGl0bGUsIGFuZCBzYXZlIGl0IGFzIGByZXBvcnRgLiIKCiMjIyMgQ29kZSB0byBwcm9kdWNlIGFuZCBwdWJsaXNoIHRoZSByZXBvcnQKYGBge3IgcHB0X3ByaW50fQpyZXBvcnQgPC0gcHB0eCh0aXRsZSA9IHBhc3RlKGRhdGUsICJBY21lIENvLiBcbkF0dHJpdGlvbiBSZXBvcnQiKSwKICAgICAgICAgICAgICB0ZW1wbGF0ZSA9ICd+L2hyX2RhdGEvZXhhbXBsZV9jb3Jwb3JhdGVfdGVtcGxhdGUucHB0eCcpCm9wdGlvbnMoIlJlcG9ydGVScy1mb250c2l6ZSI9MjQpICNmb250IHNpemUgZm9yIHRleHQKc2xpZGUubGF5b3V0cyhyZXBvcnQpCgpyZXBvcnQgPC0gcmVwb3J0ICU+JQogIGFkZFNsaWRlKHNsaWRlLmxheW91dCA9ICJUaXRsZSBTbGlkZSIpICU+JQogIGFkZFRpdGxlKHBhc3RlKGRhdGUsICJBY21lIENvLiBBdHRyaXRpb24gUmVwb3J0IikpCgoKCnJlcG9ydCA8LSByZXBvcnQgJT4lCiAgYWRkU2xpZGUoc2xpZGUubGF5b3V0ID0gIlRpdGxlIGFuZCBDb250ZW50IikgJT4lCiAgYWRkVGl0bGUoIlRhYmxlIG9mIENvbnRlbnRzIikgJT4lCiAgYWRkUGFyYWdyYXBoKCBjKCJPdmVyYWxsIEF0dHJpdGlvbiBTdGF0aXN0aWNzIiksIAogICAgICAgICAgICAgICAgcGFyLnByb3BlcnRpZXMgPSBwYXJQcm9wZXJ0aWVzKGxpc3Quc3R5bGUgPSAidW5vcmRlcmVkIiwgbGV2ZWwgPSAxKSkgJT4lCiAgYWRkUGFyYWdyYXBoKCBjKCJBdHRyaXRpb24gYnkgU3ViZ3JvdXBzIiksIGFwcGVuZCA9IFQsCiAgICAgICAgICAgICAgICBwYXIucHJvcGVydGllcyA9IHBhclByb3BlcnRpZXMobGlzdC5zdHlsZSA9ICJ1bm9yZGVyZWQiLCBsZXZlbCA9IDEpKSAlPiUKICBhZGRQYXJhZ3JhcGgoIHNldF9vZl9wYXJhZ3JhcGhzKCJieSBKb2IgUm9sZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYnkgUGVyZm9ybWFuY2UgUmF0aW5nIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJieSBKb2IgTGV2ZWwiKSwKICAgICAgICAgICAgICAgIGFwcGVuZCA9IFQsCiAgICAgICAgICAgICAgICBwYXIucHJvcGVydGllcyA9IHBhclByb3BlcnRpZXMobGlzdC5zdHlsZSA9ICJ1bm9yZGVyZWQiLCBsZXZlbCA9IDIpICklPiUKICBhZGRQYXJhZ3JhcGgoIGMoIlJlY29tbWVuZGF0aW9ucyIpLCBhcHBlbmQgPSBULAogICAgICAgICAgICAgICAgcGFyLnByb3BlcnRpZXMgPSBwYXJQcm9wZXJ0aWVzKGxpc3Quc3R5bGUgPSAidW5vcmRlcmVkIiwgbGV2ZWwgPSAxKSkKCnJlcG9ydCA8LSByZXBvcnQgJT4lCiAgYWRkU2xpZGUoc2xpZGUubGF5b3V0ID0gIlNlY3Rpb24gSGVhZGVyIikgJT4lCiAgYWRkVGl0bGUoIk92ZXJhbGwgQXR0cml0aW9uIFN0YXRpc3RpY3MiKQoKcmVwb3J0IDwtIHJlcG9ydCAlPiUKICBhZGRTbGlkZShzbGlkZS5sYXlvdXQgPSAiVGl0bGUgYW5kIENvbnRlbnQiKSAlPiUKICBhZGRUaXRsZShwYXN0ZTAoIlN1bW1hcnkgb2YgQXR0cml0aW9uIGluICIsIGRhdGUpKSAlPiUKICBhZGRGbGV4VGFibGUob3ZlcmFsbF9hdHRyaXRpb25fdGFibGUpCgoKcmVwb3J0IDwtIHJlcG9ydCAlPiUKICBhZGRTbGlkZShzbGlkZS5sYXlvdXQgPSAiU2VjdGlvbiBIZWFkZXIiKSAlPiUKICBhZGRUaXRsZSgiQXR0cml0aW9uIGJ5IFN1Ymdyb3VwIikKCnJlcG9ydCA8LSByZXBvcnQgJT4lCiAgYWRkU2xpZGUoc2xpZGUubGF5b3V0ID0gIlR3byBDb250ZW50IikgJT4lCiAgYWRkVGl0bGUoIldoaWNoIEpvYnMgaGF2ZSB0aGUgSGlnaGVzdCBBdHRyaXRpb24/IikgJT4lCiAgYWRkUGxvdChmdW5jdGlvbigpIHByaW50KGpvYnJvbGVfYXR0cml0aW9uX3Bsb3QpKSAlPiUKICBhZGRGbGV4VGFibGUoam9icm9sZV9hdHRyaXRpb25fdGFibGUpCgpyZXBvcnQgPC0gcmVwb3J0ICU+JQogIGFkZFNsaWRlKHNsaWRlLmxheW91dCA9ICJUd28gQ29udGVudCIpICU+JQogIGFkZFRpdGxlKCJBcmUgV2UgS2VlcGluZyBPdXIgVG9wIFBlcmZvcm1lcnM/IikgJT4lCiAgYWRkUGxvdChmdW5jdGlvbigpIHByaW50KHBlcmZvcm1hbmNlX2F0dHJpdGlvbl9wbG90KSkgJT4lCiAgYWRkRmxleFRhYmxlKHBlcmZvcm1hbmNlX2F0dHJpdGlvbl90YWJsZSkgCgpyZXBvcnQgPC0gcmVwb3J0ICU+JQogIGFkZFNsaWRlKHNsaWRlLmxheW91dCA9ICJUd28gQ29udGVudCIpICU+JQogIGFkZFRpdGxlKCJXaGljaCBIaXJpbmcgQ2hhbm5lbHMgaGF2ZSBIaWdoIFR1cm5vdmVyPyIpICU+JQogIGFkZFBsb3QoZnVuY3Rpb24oKSBwcmludChoaXJlc291cmNlX2F0dHJpdGlvbl9wbG90KSkgJT4lCiAgYWRkRmxleFRhYmxlKGhpcmVzb3VyY2VfYXR0cml0aW9uX3RhYmxlKQoKcmVwb3J0IDwtIHJlcG9ydCAlPiUKICBhZGRTbGlkZShzbGlkZS5sYXlvdXQgPSAiVGl0bGUgYW5kIENvbnRlbnQiKSAlPiUKICBhZGRUaXRsZSgiS2V5IEluc2lnaHRzIikgJT4lCiAgYWRkUGFyYWdyYXBoKCBjKHBhc3RlMCgiVGhlIGpvYiBncm91cCB3aXRoIHRoZSBoaWdoZXN0IGF0dHJpdGlvbiB3YXMgIiwgdG9wX3JvbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICIgd2l0aCBhbiBhdHRyaXRpb24gcmF0ZSBvZiAiLGFzLmNoYXJhY3Rlcihqb2Jyb2xlX2F0dHJpdGlvbiRgQXR0cml0aW9uICVgWzFdKSwiLiIpKSwgCiAgICAgICAgICAgICAgICBwYXIucHJvcGVydGllcyA9IHBhclByb3BlcnRpZXMobGlzdC5zdHlsZSA9ICJ1bm9yZGVyZWQiLCBsZXZlbCA9IDEpKSAlPiUKICBhZGRQYXJhZ3JhcGgoIGMocGFzdGUwKCJBZGRpdGlvbmFsIGZvY3VzIHNob3VsZCBiZSBwbGFjZWQgb24gcmV0YWluaW5nICIsIHRvcF9yb2xlLAogICAgICAgICAgICAgICAgICAgICAgICAicy4iKSksCiAgICAgICAgICAgICAgICBhcHBlbmQgPSBULAogICAgICAgICAgICAgICAgcGFyLnByb3BlcnRpZXMgPSBwYXJQcm9wZXJ0aWVzKGxpc3Quc3R5bGUgPSAidW5vcmRlcmVkIiwgbGV2ZWwgPSAyKSkgJT4lCiAgYWRkUGFyYWdyYXBoKCBjKHBhc3RlMCgiV2UgZXN0aW1hdGUgdGhhdCByZXBsYWNpbmcgYW4gZW1wbG95ZWUgY29zdHMgIiwgcmVwbGFjZW1lbnRfbXVsdCwKICAgICAgICAgICAgICAgICAgICAgICAgICJ4IHRoZWlyIGFubnVhbCBzYWxhcnkuIFJlZHVjaW5nIGF0dHJpdGlvbiBieSAiLCBwZXJjZW50KHJlZHVjdGlvbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAiIGluICIsIG1vbnRoLCAiIGNvdWxkIGhhdmUgc2F2ZWQgIiwgZG9sbGFyKHJvdW5kKGltcGFjdCwwKSksICIuIikpLAogICAgICAgICAgICAgICAgYXBwZW5kID0gVCwKICAgICAgICAgICAgICAgIHBhci5wcm9wZXJ0aWVzID0gcGFyUHJvcGVydGllcyhsaXN0LnN0eWxlID0gInVub3JkZXJlZCIsIGxldmVsID0gMikpICU+JQogIGFkZFBhcmFncmFwaCggYyhwYXN0ZTAoIk91ciAiLCBoaXJlc291cmNlX2F0dHJpdGlvbiRgUmVjcnVpdGluZyBDaGFubmVsYFsxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICIgaGlyZXMgaGF2ZSB0aGUgaGlnaGVzdCBhdHRyaXRpb24gcmF0ZS4gVGhlIGNoYW5uZWwgd2l0aCB0aGUgbG93ZXN0IGF0dHJpdGlvbiByYXRlIGluICIsCiAgICAgICAgICAgICAgICAgICAgICAgICBtb250aCwgIiB3YXMgdGhlICIsCiAgICAgICAgICAgICAgICAgICAgICAgICBoaXJlc291cmNlX2F0dHJpdGlvbiRgUmVjcnVpdGluZyBDaGFubmVsYFtucm93KGhpcmVzb3VyY2VfYXR0cml0aW9uKV0sICIgY2hhbm5lbC4iKSksIAogICAgICAgICAgICAgICAgYXBwZW5kID0gVCwKICAgICAgICAgICAgICAgIHBhci5wcm9wZXJ0aWVzID0gcGFyUHJvcGVydGllcyhsaXN0LnN0eWxlID0gInVub3JkZXJlZCIsIGxldmVsID0gMSkpIAoKd3JpdGVEb2MocmVwb3J0LCBwYXN0ZTAoIn4vaHJfZGF0YS9leGFtcGxlcy8iLGRhdGUsIiBBdHRyaXRpb24gUmVwb3J0LnBwdHgiKSkKYGBgCgoKQW5kIHRoZXJlIHlvdSBoYXZlIGl0ISBUaGUgZmluYWwgcmVwb3J0IGNhbiBiZSBzZWVuIG9uIFNsaWRlU2hhcmUgKGh0dHA6Ly93d3cuc2xpZGVzaGFyZS5uZXQvQmVuVGV1c2NoL2F1dG9tYXRlZC1hdHRyaXRpb24tcmVwb3J0LXVzaW5nLXIpLgpBZ2FpbiwgaWYgeW91J3JlIGludGVyZXN0ZWQgaW4gc3RhcnRpbmcgd2l0aCBhIHNpbXBsZSB2ZXJzaW9uIG9mIHRoaXMgY29kZSwgY2hlY2sgb3V0IGh0dHA6Ly9ycHVicy5jb20vdGV1c2NoYi9tYWtpbmdfcHB0X3NsaWRlc19zaW1wbGUuCgpJIGhvcGUgdGhpcyBndWlkZSBjYW4gYmUgaGVscGZ1bCBmb3IgYW5hbHl0aWNzIHRlYW1zLCBhbmQgZXNwZWNpYWxseSBwZW9wbGUgYW5hbHl0aWNzIHRlYW1zIG9yIEhSIHJlcG9ydGluZyB0ZWFtcywgd2hvIHdvdWxkIGxpa2UgdG8gcmVkdWNlIHRoZSBhbW91bnQgb2YgdGltZSB0aGV5IHNwZW5kIG9uIHJvdXRpbmUgcmVwb3J0cy4gV2VsbC1jb25zaWRlcmVkIHJlcG9ydGluZyBpcyBzdGlsbCBhIHBpbGxhciBvZiBlZmZlY3RpdmUgcGVvcGxlIGFuYWx5dGljcywgYnV0IHRoZSBiZXN0IHRlYW1zIHJlZHVjZSB0aGUgdGltZSB0aGV5IHNwZW5kIG9uIGl0IGFzIG11Y2ggYXMgcG9zc2libGUsIHNvIHRoZXkgY2FuIHdvcmsgb24gbW9yZSBhZHZhbmNlZCBhbmFseXRpY3MuIElmIHlvdSBmaW5kIG90aGVyIGhlbHBmdWwgZ3VpZGVzIGZvciBwZW9wbGUgYW5hbHl0aWNzLCBvciB3b3VsZCBsaWtlIHRvIHNlZSBzb21ldGhpbmcgc3BlY2lmaWMsIHBsZWFzZSBsZXQgbWUga25vdyBpbiB0aGUgY29tbWVudHMh