Set-up

library(tidyverse)

Acknowledgements

This notebook is based on Chapters 10-12 of of R for Data Science.

1 Tibbles

A tibble is just a data frame.

Consider the famous iris data:

data("iris")
head(iris, n = 5) # view the first five rows

It’s a data.frame:

class(iris)
[1] "data.frame"

which is really just a bunch of vectors chained together.

You can do most tidyverse things with data.frames. Plotting:

ggplot(data = iris, aes(x = Sepal.Length, y = Sepal.Width, color = Species)) + 
  geom_point()

and summarization:

iris %>% 
  group_by(Species) %>% 
  count()

though notice how we got a tibble in return:

iris %>% 
  group_by(Species) %>% 
  count() %>% 
  class()
[1] "grouped_df" "tbl_df"     "tbl"        "data.frame"

The fact of the matter is that if you do anything with tidyverse you will be using tibbles. Like it or lump it.

1.1 tibble vs data.frame

Why re-invent the wheel? The book lists three main reasons. A tibble:

  1. never changes the type of input;
  2. never changes the names of variables, and;
  3. never creates row names

We leave it to the book and others to expound the virtues of the tibble. Here we will just treat it like a souped-up data.frame.

Most of the data we will use will be tibbles. If you want convert a data.frame to a tibble, just use as_tibble():

iris_tbl <- as_tibble(iris)
slice_head(iris_tbl,n = 5) # view the first ten rows

It is nice to see the object type of each column (dbl for double or numeric, fctr for factor, and so on). Though RNotebooks do this for data.frames, too.

1.1.1 tibble vs data.frame: indexing

In terms of coding there is one important difference between tibbles and data.frames: indexing columns with [].

Both data.frames and tibbles can be indexed with $:

print(mean(iris$Sepal.Length))
[1] 5.843333
print(mean(iris_tbl$Sepal.Length))
[1] 5.843333

But while data.frames are indexed wit [row, column]:

# df[,1] indexes the first column of data.frame df and all rows
mean(iris[,1])
[1] 5.843333

the same code will not work with tibbles:

mean(iris_tbl[,1])
argument is not numeric or logical: returning NA
[1] NA

Why? Because with data.frames [] returns a vector:

class(iris[,1])
[1] "numeric"

but with tibbles [] returns another tibble:

class(iris_tbl[,1])
[1] "tbl_df"     "tbl"        "data.frame"

To return a vector you have to index with [[columns]] (no row argument):

mean(iris_tbl[[1]])
[1] 5.843333

In general the logic of subsets returning tibbles is nice. This makes them more suitable for use in dplyr and other tidyverse packages. (That’s the point of the tidyverse! Like how buying one Apple product increases the probability you will buy another.)

Long story short, if you live in the tidyverse, get used to tibbles.

1.1.2 Checkpoint

It helps to know [] (though largely because it’s the main subsetting practice for standalone vectors.)

So, use [] to subset the 20th to 47th rows of iris_tbl, then filter out sepals wider than 3.3, then calculate the median petal length:

iris_tbl[20:47,] %>% 
  filter(Sepal.Width < 3.3) %>% 
  summarise(median(Petal.Length))

1.2 Slice

Even better than [] for subsetting tibbles are the slice() operators.

slice(a:b) gives you rows from integer a to b. It works the same as []. But it fits more with the “tidyerse” way of doing things.

slice() is part of a family of subsetting operators:

  • slice_head(n) gives you the first \(n\) rows
  • slice_tail(n) the last \(n\) rows
  • slice_max(column,n) gives you the max \(n\) rows based on column
  • the reverse for slice_min(column, n)
  • a personal favorite is slice_sample(n), which draws a random sample of \(n\) rows. Very useful when learning stats!

1.2.1 Checkpoint

Use slice() to subset the 20th to 47th rows of iris_tbl, then filter out sepals wider than 3.3, then calculate the median petal length. You should get exactly the same answer as before:

iris_tbl %>% 
  slice(20:47) %>% 
  filter(Sepal.Width < 3.3) %>% 
  summarise(median(Petal.Length))

1.2.2 Checkpoint

Draw a random sample of 100 rows, then subset the first 20 rows of that sample, then calculate average petal width by species, then return only the max petal width. Each step uses a different slice operator!

set.seed(123) # seed the random number generator so we all get the same random sample
iris_tbl %>% 
  slice_sample(n = 100) %>% 
  slice(1:20) %>% 
  group_by(Species) %>% 
  summarise(mean_ptl_width = mean(Petal.Width)) %>% 
  slice_max(mean_ptl_width, n = 1)

2 Importing data

Use read_csv() to load a .csv file. The main argument is the path. It can be local (e.g., “~/Desktop/my_data.csv”). Or it can be a file hosted somehwere.

For instance, here is the 11/23 update on Covid-19 around the world from John Hopkins’ GitHub:

covid <- read_csv("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/11-23-2020.csv")

── Column specification ────────────────────────────────────────────────
cols(
  FIPS = col_double(),
  Admin2 = col_character(),
  Province_State = col_character(),
  Country_Region = col_character(),
  Last_Update = col_datetime(format = ""),
  Lat = col_double(),
  Long_ = col_double(),
  Confirmed = col_double(),
  Deaths = col_double(),
  Recovered = col_double(),
  Active = col_double(),
  Combined_Key = col_character(),
  Incident_Rate = col_double(),
  Case_Fatality_Ratio = col_double()
)

read_csv() is loud: it prints a lot of output when run. That can be useful. It can also be annoying, especially in a notebook.

Fortunately you can use the knitr switches to suppress output. Here we can use message = FALSE:

us_covid_deaths <- read_csv("https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_US.csv")

read_csv always returns a tibble: Surprise!

tidyverse also has methods to read tab-delimited files and more. See here.

2.1 Writing a file

Check out the Covid-19 deaths:

slice_head(covid, n=10)

Let’s calculate the total deaths for Belgium and South Korea:

covid %>% 
  filter(Country_Region %in% c("Belgium", "Korea, South")) %>% 
  group_by(Country_Region) %>% 
  summarise(sum(Deaths))

Suppose your boss wants an Excel file of your work. You can write the output to a csv file with write_csv():

# make the summary table
summary_table = covid %>% 
  filter(Country_Region %in% c("Belgium", "Korea, South")) %>% 
  group_by(Country_Region) %>% 
  summarise(sum(Deaths)) 
# then write it to a file
write_csv(summary_table, " ") # fill this in with your path

2.2 Side note: factors

Factors are R’s way of handling categorical variables.

Basically a factor is a character vector where each entry belongs to a category – or as R calls them, a level.

What are categorical variables? The book puts it nicely: “variables that have a fixed and known set of possible values”.

Species is a factor:

is.factor(iris_tbl$Species)
[1] TRUE

and you can view the levels or unique categories with levels():

levels(iris_tbl$Species)
[1] "setosa"     "versicolor" "virginica" 

When do you have to care about factors? Usually for mundane things like re-ordering the order of categories (e.g., in a plot, a regression, etc.). Dealing with factors can be annoying, but it’s just a matter of using a few code snippets here and there, so dealing with factors is firmly in the “don’t bother remembering, just go to Stack Overflow” pile.

For more on factors see Chapter 15. The tidyverse also ships a package just for factors called forcats.

2.3 Side note: other variable types

We’re glossing over other variable types like dates, times and so on. For more see here and Chapters 14 and 16. Again this is mostly Stack Overflow stuff.

3 Tidy data

A lot of data science is really data wrangling. That means getting your data into shape so you can do science. That also means spending hours on Stack Overflow. Why? Because each data set is unique, and how you want it shaped depends on your objectives.

The idea of “tidy” data is to produce data sets that satisfy three desiderata. Quoting from the book:

  1. Each variable must have its own column
  2. Each observation must have its own row
  3. Each value must have its own cell

Visually:

Think of this way: can you run a regression on the data as-is? If not, it’s not tidy.

This data is tidy:

table1

Why? Because each column is a unique variable and each row is a unique observation. The covid data set is tidy for the same reasons.

3.1 Easy (easier?) data wrangling

Data wrangling can take many forms. Missing data, weird strings, and so on. Lots of these problems involve taking action on a single column.

For instance, separate will separate one column into columns of an otherwise tidy data set.

Consider table3:

table3

Everything looks good, but we want rate (cases divided by population) to be two columns: “cases” and population. Easy enough:

table3 %>% 
  separate(rate, into = c("cases", "population"))

Now you can easily create a rate column. We just need to pass convert = TRUE to separate() so the new columns are numeric (and thus amenable to math):

table3 %>% 
  separate(rate, into = c("cases", "population"), convert = TRUE) %>% 
  mutate(rate = cases / population)

3.2 Hard (harder?) data wrangling

What about this data?

table2

Not tidy. The same observation (e.g., “Afghanistan in 1999”) is spread across multiple rows.

Tidying this data is harder. In general the hard stuff is when you have to think about changing the entire shape of the data. (By hard stuff I mean harder to find a quick StackOverlflow solution.)

Two key functions here: pivot_wider and pivot_longer. We’ll just focus on these two.

3.3 pivot_wider

table2 is a case where we want to add columns and reduce rows. This is a job for pivot_wider. You want the type column to be two separate columns: one for “cases”, the other for “population”.

table2 %>% 
  pivot_wider(names_from = type, values_from = count)

Now it’s tidy: each row is a unique observation (“Afghanistan 1999”, “Brazil 2000”, etc.)

The first argument names_from takes the column you want to separate into multiple columns.

The second argument values_from maps the values into their new columns.

3.4 pivot_longer

The opposite of pivot_wider. Use this when you want to reduce columns and add rows.

For instance, this data is untidy:

table4a

We don’t want a “1999” and “2000” column, we want a “year” column.

With pivot_longer():

table4a %>% 
  pivot_longer(c(`1999`, `2000`), names_to = "year", values_to = "cases")

3.5 Checkpoint

Consider this data:

players <- tribble(
  ~player,             ~stat,       ~value,
  "Kevin De Bruyne",   "goals",      20,
  "Kevin De Bruyne",   "assists",    17,
  "Kevin De Bruyne",   "red_cards",  2,
  "Sergio Ramos",      "goals",      5,
  "Sergio Ramos",      "assists",    8,
  "Sergio Ramos",      "red_cards",  30,
)

players

“Widen” it to create a new tibble called “players_wide”:

players_wide = players %>% 
  pivot_wider(names_from = stat, values_from = value) 

players_wide

Now pivot players_wide back to “long” form and use geom_col() to plot a bar chart of each player’s statistics:

players_wide %>% 
  pivot_longer(c(goals, assists, red_cards), names_to = "stat", values_to = "values") %>% 
  ggplot(data = ., aes(x = stat, y = values, fill = player)) + 
  geom_col(position = "dodge")

Key point. To make a plot like this you need the data in “long” format (i.e. with pivot_longer().)

3.6 Checkpoint

Pick some countries from the covid data, calculate total deaths and total confirmed cases by country, pivot_longer the summary table, then make a bar chart (one facet for total deaths by country, the other for total confirmed cases by country).

# part 1: the summary table
covid %>% 
  filter(Country_Region %in% c("Belgium", "Korea, South", "US", "Canada", "Italy")) %>% 
  group_by(Country_Region) %>% 
  summarise(total_deaths = sum(Deaths), total_confirmed = sum(Confirmed)) %>% 
  # part 2: now pivot_long the summary table
  pivot_longer(c(total_deaths, total_confirmed), names_to = "measure", values_to="value") %>% 
  # part 3: now plot it! 
  ggplot(., aes(x = Country_Region, y = value)) + 
  geom_col() + 
  facet_wrap(~measure)

3.7 Checkpoint

Consider this data:

people <- tribble(
  ~name,             ~names,  ~values,
  "Phillip Woods",   "age",       45,
  "Phillip Woods",   "height",   186,
  "Phillip Woods",   "age",       50,
  "Jessica Cordero", "age",       37,
  "Jessica Cordero", "height",   156
)

people

It needs to be tidied. What happens if you try to pivot wider?

people %>% 
  pivot_wider(names_from = names, values_from = values)

Suppose you are told that there was a mistake in the data entry: “Phillip Woods” is aged 50, not 45. Use this information to write a code that filters out that row. Then tidy the data with pivot_wider():

people %>% 
  filter(!(name == "Phillip Woods" & names == "age" & values == 45)) %>% 
  pivot_wider(names_from = names, values_from = values)
LS0tCnRpdGxlOiAiVGliYmxlcyBhbmQgVGlkeWluZyAoQ29tcGxldGVkIE5vdGVib29rKSIKc3VidGl0bGU6ICJSIGZvciBEYXRhIFNjaWVuY2UiCmF1dGhvcjogIkxERyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0aGVtZTogcmVhZGFibGUKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiAKICAgICAgY29sbGFwc2VkOiB5ZXMgICAgICAKLS0tCgojIFNldC11cCB7LX0KIApgYGB7ciBsb2FkIHBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIyBBY2tub3dsZWRnZW1lbnRzIHstfQoKVGhpcyBub3RlYm9vayBpcyBiYXNlZCBvbiBDaGFwdGVycyAxMC0xMiBvZiBvZiBbKlIgZm9yIERhdGEgU2NpZW5jZSpdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovaW5kZXguaHRtbCkuCgojIFRpYmJsZXMKCkEgdGliYmxlIGlzIGp1c3QgYSBkYXRhIGZyYW1lLiAKCkNvbnNpZGVyIHRoZSBmYW1vdXMgYGlyaXNgIGRhdGE6CgpgYGB7ciBsb2FkIGlyaXN9CmRhdGEoImlyaXMiKQpoZWFkKGlyaXMsIG4gPSA1KSAjIHZpZXcgdGhlIGZpcnN0IGZpdmUgcm93cwpgYGAKCkl0J3MgYSBgZGF0YS5mcmFtZWA6CgpgYGB7ciBjbGFzcyBpcmlzfQpjbGFzcyhpcmlzKQpgYGAKCndoaWNoIGlzIHJlYWxseSBqdXN0IGEgYnVuY2ggb2YgdmVjdG9ycyBjaGFpbmVkIHRvZ2V0aGVyLiAKCllvdSBjYW4gZG8gbW9zdCBgdGlkeXZlcnNlYCB0aGluZ3Mgd2l0aCBkYXRhLmZyYW1lcy4gUGxvdHRpbmc6CgpgYGB7ciBwbG90IGlyaXN9CmdncGxvdChkYXRhID0gaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTZXBhbC5XaWR0aCwgY29sb3IgPSBTcGVjaWVzKSkgKyAKICBnZW9tX3BvaW50KCkKYGBgCgphbmQgc3VtbWFyaXphdGlvbjoKCmBgYHtyIHN1bW1hcml6ZSBpcmlzfQppcmlzICU+JSAKICBncm91cF9ieShTcGVjaWVzKSAlPiUgCiAgY291bnQoKQpgYGAKCnRob3VnaCBub3RpY2UgaG93IHdlIGdvdCBhIHRpYmJsZSBpbiByZXR1cm46CgpgYGB7ciBzdW1tYXJpemUgaXJpcyBjbGFzc30KaXJpcyAlPiUgCiAgZ3JvdXBfYnkoU3BlY2llcykgJT4lIAogIGNvdW50KCkgJT4lIAogIGNsYXNzKCkKYGBgCgpUaGUgZmFjdCBvZiB0aGUgbWF0dGVyIGlzIHRoYXQgaWYgeW91IGRvIGFueXRoaW5nIHdpdGggYHRpZHl2ZXJzZWAgeW91IHdpbGwgYmUgdXNpbmcgdGliYmxlcy4gTGlrZSBpdCBvciBsdW1wIGl0LgoKIyMgdGliYmxlIHZzIGRhdGEuZnJhbWUKCldoeSByZS1pbnZlbnQgdGhlIHdoZWVsPyBUaGUgYm9vayBsaXN0cyB0aHJlZSBtYWluIHJlYXNvbnMuIEEgdGliYmxlOgoKMS4gbmV2ZXIgY2hhbmdlcyB0aGUgdHlwZSBvZiBpbnB1dDsKMi4gbmV2ZXIgY2hhbmdlcyB0aGUgbmFtZXMgb2YgdmFyaWFibGVzLCBhbmQ7CjMuIG5ldmVyIGNyZWF0ZXMgcm93IG5hbWVzCgpXZSBsZWF2ZSBpdCB0byB0aGUgYm9vayBhbmQgb3RoZXJzIHRvIGV4cG91bmQgdGhlIHZpcnR1ZXMgb2YgdGhlIHRpYmJsZS4gSGVyZSB3ZSB3aWxsIGp1c3QgdHJlYXQgaXQgbGlrZSBhIHNvdXBlZC11cCBkYXRhLmZyYW1lLgoKTW9zdCBvZiB0aGUgZGF0YSB3ZSB3aWxsIHVzZSB3aWxsIGJlIHRpYmJsZXMuIElmIHlvdSB3YW50IGNvbnZlcnQgYSBkYXRhLmZyYW1lIHRvIGEgdGliYmxlLCBqdXN0IHVzZSBgYXNfdGliYmxlKClgOgoKYGBge3IgaXJpcyB0aWJibGV9CmlyaXNfdGJsIDwtIGFzX3RpYmJsZShpcmlzKQpzbGljZV9oZWFkKGlyaXNfdGJsLG4gPSA1KSAjIHZpZXcgdGhlIGZpcnN0IHRlbiByb3dzCmBgYAoKSXQgaXMgbmljZSB0byBzZWUgdGhlIG9iamVjdCB0eXBlIG9mIGVhY2ggY29sdW1uIChgZGJsYCBmb3IgZG91YmxlIG9yIG51bWVyaWMsIGBmY3RyYCBmb3IgZmFjdG9yLCBhbmQgc28gb24pLiBUaG91Z2ggUk5vdGVib29rcyBkbyB0aGlzIGZvciBkYXRhLmZyYW1lcywgdG9vLiAKCiMjIyB0aWJibGUgdnMgZGF0YS5mcmFtZTogaW5kZXhpbmcKCkluIHRlcm1zIG9mIGNvZGluZyB0aGVyZSBpcyBvbmUgaW1wb3J0YW50IGRpZmZlcmVuY2UgYmV0d2VlbiB0aWJibGVzIGFuZCBkYXRhLmZyYW1lczogaW5kZXhpbmcgY29sdW1ucyB3aXRoIGBbXWAuCgpCb3RoIGRhdGEuZnJhbWVzIGFuZCB0aWJibGVzIGNhbiBiZSBpbmRleGVkIHdpdGggYCRgOgoKYGBge3IgaW5kZXhpbmcgd2l0aCAkfQpwcmludChtZWFuKGlyaXMkU2VwYWwuTGVuZ3RoKSkKcHJpbnQobWVhbihpcmlzX3RibCRTZXBhbC5MZW5ndGgpKQpgYGAKCkJ1dCB3aGlsZSBkYXRhLmZyYW1lcyBhcmUgaW5kZXhlZCB3aXQgIGBbcm93LCBjb2x1bW5dYDoKCmBgYHtyIGRmIGluZGV4aW5nIHdpdGggW119CiMgZGZbLDFdIGluZGV4ZXMgdGhlIGZpcnN0IGNvbHVtbiBvZiBkYXRhLmZyYW1lIGRmIGFuZCBhbGwgcm93cwptZWFuKGlyaXNbLDFdKQpgYGAKCnRoZSBzYW1lIGNvZGUgd2lsbCBub3Qgd29yayB3aXRoIHRpYmJsZXM6CgpgYGB7ciB0YmwgaW5kZXhpbmcgd2l0aCBbXX0KbWVhbihpcmlzX3RibFssMV0pCmBgYAoKV2h5PyBCZWNhdXNlIHdpdGggZGF0YS5mcmFtZXMgYFtdYCByZXR1cm5zIGEgdmVjdG9yOgoKYGBge3IgY2xhc3MgZGYgW119CmNsYXNzKGlyaXNbLDFdKQpgYGAKCmJ1dCB3aXRoIHRpYmJsZXMgYFtdYCByZXR1cm5zIGFub3RoZXIgdGliYmxlOgoKYGBge3IgY2xhc3MgdGliYmxlIFtdfQpjbGFzcyhpcmlzX3RibFssMV0pCmBgYAoKVG8gcmV0dXJuIGEgdmVjdG9yIHlvdSBoYXZlIHRvIGluZGV4IHdpdGggYFtbY29sdW1uc11dYCAobm8gcm93IGFyZ3VtZW50KToKCmBgYHtyIGluZGV4IHRpYmJsZSB3aXRoIFtbXV19Cm1lYW4oaXJpc190YmxbWzFdXSkKYGBgCgpJbiBnZW5lcmFsIHRoZSBsb2dpYyBvZiBzdWJzZXRzIHJldHVybmluZyB0aWJibGVzIGlzIG5pY2UuIFRoaXMgbWFrZXMgdGhlbSBtb3JlIHN1aXRhYmxlIGZvciB1c2UgaW4gYGRwbHlyYCBhbmQgb3RoZXIgYHRpZHl2ZXJzZWAgcGFja2FnZXMuIChUaGF0J3MgdGhlIHBvaW50IG9mIHRoZSBgdGlkeXZlcnNlYCEgTGlrZSBob3cgYnV5aW5nIG9uZSBBcHBsZSBwcm9kdWN0IGluY3JlYXNlcyB0aGUgcHJvYmFiaWxpdHkgeW91IHdpbGwgYnV5IGFub3RoZXIuKSAKCkxvbmcgc3Rvcnkgc2hvcnQsIGlmIHlvdSBsaXZlIGluIHRoZSBgdGlkeXZlcnNlYCwgZ2V0IHVzZWQgdG8gdGliYmxlcy4gCgojIyMgQ2hlY2twb2ludAoKSXQgaGVscHMgdG8ga25vdyBgW11gICh0aG91Z2ggbGFyZ2VseSBiZWNhdXNlIGl0J3MgdGhlIG1haW4gc3Vic2V0dGluZyBwcmFjdGljZSBmb3IgKipzdGFuZGFsb25lIHZlY3RvcnMqKi4pCgpTbywgdXNlIGBbXWAgdG8gc3Vic2V0IHRoZSAyMHRoIHRvIDQ3dGggcm93cyBvZiBgaXJpc190YmxgLCB0aGVuIGZpbHRlciBvdXQgc2VwYWxzIHdpZGVyIHRoYW4gMy4zLCAgdGhlbiBjYWxjdWxhdGUgdGhlIG1lZGlhbiBwZXRhbCBsZW5ndGg6CgpgYGB7ciBjaGVja3BvaW50IHN1YnNldCwgbWVzc2FnZT1GQUxTRX0KaXJpc190YmxbMjA6NDcsXSAlPiUgCiAgZmlsdGVyKFNlcGFsLldpZHRoIDwgMy4zKSAlPiUgCiAgc3VtbWFyaXNlKG1lZGlhbihQZXRhbC5MZW5ndGgpKQpgYGAKCiMjIFNsaWNlCgpFdmVuIGJldHRlciB0aGFuIGBbXWAgZm9yIHN1YnNldHRpbmcgdGliYmxlcyBhcmUgdGhlIGBzbGljZSgpYCBvcGVyYXRvcnMuIAoKYHNsaWNlKGE6YilgIGdpdmVzIHlvdSByb3dzIGZyb20gaW50ZWdlciBgYWAgdG8gYGJgLiBJdCB3b3JrcyB0aGUgc2FtZSBhcyBgW11gLiBCdXQgaXQgZml0cyBtb3JlIHdpdGggdGhlICJ0aWR5ZXJzZSIgd2F5IG9mIGRvaW5nIHRoaW5ncy4KCmBzbGljZSgpYCBpcyBwYXJ0IG9mIGEgZmFtaWx5IG9mIHN1YnNldHRpbmcgb3BlcmF0b3JzOgoKKiBgc2xpY2VfaGVhZChuKWAgZ2l2ZXMgeW91IHRoZSBmaXJzdCAkbiQgcm93cwoqIGBzbGljZV90YWlsKG4pYCB0aGUgbGFzdCAkbiQgcm93cwoqIGBzbGljZV9tYXgoY29sdW1uLG4pYCBnaXZlcyB5b3UgdGhlIG1heCAkbiQgcm93cyBiYXNlZCBvbiBgY29sdW1uYAoqIHRoZSByZXZlcnNlIGZvciBgc2xpY2VfbWluKGNvbHVtbiwgbilgCiogYSBwZXJzb25hbCBmYXZvcml0ZSBpcyBgc2xpY2Vfc2FtcGxlKG4pYCwgd2hpY2ggZHJhd3MgYSByYW5kb20gc2FtcGxlIG9mICRuJCByb3dzLiBWZXJ5IHVzZWZ1bCB3aGVuIGxlYXJuaW5nIHN0YXRzIQoKIyMjIENoZWNrcG9pbnQKClVzZSBgc2xpY2UoKWAgdG8gc3Vic2V0IHRoZSAyMHRoIHRvIDQ3dGggcm93cyBvZiBgaXJpc190YmxgLCB0aGVuIGZpbHRlciBvdXQgc2VwYWxzIHdpZGVyIHRoYW4gMy4zLCAgdGhlbiBjYWxjdWxhdGUgdGhlIG1lZGlhbiBwZXRhbCBsZW5ndGguIFlvdSBzaG91bGQgZ2V0IGV4YWN0bHkgdGhlIHNhbWUgYW5zd2VyIGFzIGJlZm9yZToKCmBgYHtyIGNoZWNrcG9pbnQgc3Vic2V0IHdpdGggc2xpY2UsIG1lc3NhZ2U9RkFMU0V9CmlyaXNfdGJsICU+JSAKICBzbGljZSgyMDo0NykgJT4lIAogIGZpbHRlcihTZXBhbC5XaWR0aCA8IDMuMykgJT4lIAogIHN1bW1hcmlzZShtZWRpYW4oUGV0YWwuTGVuZ3RoKSkKYGBgCgojIyMgQ2hlY2twb2ludAoKRHJhdyBhIHJhbmRvbSBzYW1wbGUgb2YgMTAwIHJvd3MsIHRoZW4gc3Vic2V0IHRoZSBmaXJzdCAyMCByb3dzIG9mIHRoYXQgc2FtcGxlLCB0aGVuIGNhbGN1bGF0ZSBhdmVyYWdlIHBldGFsIHdpZHRoIGJ5IHNwZWNpZXMsIHRoZW4gcmV0dXJuIG9ubHkgdGhlIG1heCBwZXRhbCB3aWR0aC4gRWFjaCBzdGVwIHVzZXMgYSBkaWZmZXJlbnQgc2xpY2Ugb3BlcmF0b3IhCgpgYGB7ciBjaGVja3BvaW50IHNsaWNpbmcgZ2Fsb3JlLCBtZXNzYWdlID0gRkFMU0V9CnNldC5zZWVkKDEyMykgIyBzZWVkIHRoZSByYW5kb20gbnVtYmVyIGdlbmVyYXRvciBzbyB3ZSBhbGwgZ2V0IHRoZSBzYW1lIHJhbmRvbSBzYW1wbGUKaXJpc190YmwgJT4lIAogIHNsaWNlX3NhbXBsZShuID0gMTAwKSAlPiUgCiAgc2xpY2UoMToyMCkgJT4lIAogIGdyb3VwX2J5KFNwZWNpZXMpICU+JSAKICBzdW1tYXJpc2UobWVhbl9wdGxfd2lkdGggPSBtZWFuKFBldGFsLldpZHRoKSkgJT4lIAogIHNsaWNlX21heChtZWFuX3B0bF93aWR0aCwgbiA9IDEpCmBgYAoKIyBJbXBvcnRpbmcgZGF0YQoKVXNlIGByZWFkX2NzdigpYCB0byBsb2FkIGEgLmNzdiBmaWxlLiBUaGUgbWFpbiBhcmd1bWVudCBpcyB0aGUgcGF0aC4gSXQgY2FuIGJlIGxvY2FsIChlLmcuLCAifi9EZXNrdG9wL215X2RhdGEuY3N2IikuIE9yIGl0IGNhbiBiZSBhIGZpbGUgaG9zdGVkIHNvbWVod2VyZS4gCgpGb3IgaW5zdGFuY2UsIGhlcmUgaXMgdGhlIDExLzIzIHVwZGF0ZSBvbiBDb3ZpZC0xOSBhcm91bmQgdGhlIHdvcmxkIGZyb20gW0pvaG4gSG9wa2lucycgR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkpOgoKYGBge3IgcmVhZF9jc3Z9CmNvdmlkIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X2RhaWx5X3JlcG9ydHMvMTEtMjMtMjAyMC5jc3YiKQpgYGAKCmByZWFkX2NzdigpYCBpcyBsb3VkOiBpdCBwcmludHMgYSBsb3Qgb2Ygb3V0cHV0IHdoZW4gcnVuLiBUaGF0IGNhbiBiZSB1c2VmdWwuIEl0IGNhbiBhbHNvIGJlIGFubm95aW5nLCBlc3BlY2lhbGx5IGluIGEgbm90ZWJvb2suIAoKRm9ydHVuYXRlbHkgeW91IGNhbiB1c2UgdGhlIGBrbml0cmAgc3dpdGNoZXMgdG8gc3VwcHJlc3Mgb3V0cHV0LiBIZXJlIHdlIGNhbiB1c2UgYG1lc3NhZ2UgPSBGQUxTRWA6CgpgYGB7ciByZWFkX2NzdiBxdWlldCwgbWVzc2FnZT1GQUxTRX0KdXNfY292aWRfZGVhdGhzIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzL3RpbWVfc2VyaWVzX2NvdmlkMTlfZGVhdGhzX1VTLmNzdiIpCmBgYAoKYHJlYWRfY3N2YCBhbHdheXMgcmV0dXJucyBhIHRpYmJsZTogU3VycHJpc2UhCgpgdGlkeXZlcnNlYCBhbHNvIGhhcyBtZXRob2RzIHRvIHJlYWQgdGFiLWRlbGltaXRlZCBmaWxlcyBhbmQgbW9yZS4gU2VlIFtoZXJlXShodHRwczovL3JlYWRyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3JlYWRfZGVsaW0uaHRtbCkuCgojIyBXcml0aW5nIGEgZmlsZQoKQ2hlY2sgb3V0IHRoZSBDb3ZpZC0xOSBkZWF0aHM6CgpgYGB7ciBoZWFkIGNvdmlkfQpzbGljZV9oZWFkKGNvdmlkLCBuPTEwKQpgYGAKCkxldCdzIGNhbGN1bGF0ZSB0aGUgdG90YWwgZGVhdGhzIGZvciBCZWxnaXVtIGFuZCBTb3V0aCBLb3JlYToKCmBgYHtyIHN1bW1hcml6ZSBjb3ZpZCwgbWVzc2FnZT1GQUxTRX0KY292aWQgJT4lIAogIGZpbHRlcihDb3VudHJ5X1JlZ2lvbiAlaW4lIGMoIkJlbGdpdW0iLCAiS29yZWEsIFNvdXRoIikpICU+JSAKICBncm91cF9ieShDb3VudHJ5X1JlZ2lvbikgJT4lIAogIHN1bW1hcmlzZShzdW0oRGVhdGhzKSkKYGBgCgpTdXBwb3NlIHlvdXIgYm9zcyB3YW50cyBhbiBFeGNlbCBmaWxlIG9mIHlvdXIgd29yay4gWW91IGNhbiB3cml0ZSB0aGUgb3V0cHV0IHRvIGEgY3N2IGZpbGUgd2l0aCBgd3JpdGVfY3N2KClgOgoKYGBge3Igc3VtbWFyaXplIGNvdmlkIHRoZW4gd3JpdGVfY3N2LCBtZXNzYWdlPUZBTFNFfQojIG1ha2UgdGhlIHN1bW1hcnkgdGFibGUKc3VtbWFyeV90YWJsZSA9IGNvdmlkICU+JSAKICBmaWx0ZXIoQ291bnRyeV9SZWdpb24gJWluJSBjKCJCZWxnaXVtIiwgIktvcmVhLCBTb3V0aCIpKSAlPiUgCiAgZ3JvdXBfYnkoQ291bnRyeV9SZWdpb24pICU+JSAKICBzdW1tYXJpc2Uoc3VtKERlYXRocykpIAojIHRoZW4gd3JpdGUgaXQgdG8gYSBmaWxlCndyaXRlX2NzdihzdW1tYXJ5X3RhYmxlLCAiICIpICMgZmlsbCB0aGlzIGluIHdpdGggeW91ciBwYXRoCmBgYAoKIyMgU2lkZSBub3RlOiBmYWN0b3JzCgpGYWN0b3JzIGFyZSBSJ3Mgd2F5IG9mIGhhbmRsaW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gCgpCYXNpY2FsbHkgYSBmYWN0b3IgaXMgYSBjaGFyYWN0ZXIgdmVjdG9yIHdoZXJlIGVhY2ggZW50cnkgYmVsb25ncyB0byBhICoqY2F0ZWdvcnkqKiAtLSBvciBhcyBSIGNhbGxzIHRoZW0sIGEgKipsZXZlbCoqLiAKCldoYXQgYXJlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcz8gVGhlIGJvb2sgcHV0cyBpdCBuaWNlbHk6ICJ2YXJpYWJsZXMgdGhhdCBoYXZlIGEgZml4ZWQgYW5kIGtub3duIHNldCBvZiBwb3NzaWJsZSB2YWx1ZXMiLgoKYFNwZWNpZXNgIGlzIGEgZmFjdG9yOgoKYGBge3J9CmlzLmZhY3RvcihpcmlzX3RibCRTcGVjaWVzKQpgYGAKCmFuZCB5b3UgY2FuIHZpZXcgdGhlICoqbGV2ZWxzKiogb3IgKip1bmlxdWUgY2F0ZWdvcmllcyoqIHdpdGggYGxldmVscygpYDoKCmBgYHtyfQpsZXZlbHMoaXJpc190YmwkU3BlY2llcykKYGBgCldoZW4gZG8geW91IGhhdmUgdG8gY2FyZSBhYm91dCBmYWN0b3JzPyBVc3VhbGx5IGZvciBtdW5kYW5lIHRoaW5ncyBsaWtlIHJlLW9yZGVyaW5nIHRoZSBvcmRlciBvZiBjYXRlZ29yaWVzIChlLmcuLCBpbiBhIHBsb3QsIGEgcmVncmVzc2lvbiwgZXRjLikuIERlYWxpbmcgd2l0aCBmYWN0b3JzIGNhbiBiZSBhbm5veWluZywgYnV0IGl0J3MganVzdCBhIG1hdHRlciBvZiB1c2luZyBhIGZldyBjb2RlIHNuaXBwZXRzIGhlcmUgYW5kIHRoZXJlLCBzbyBkZWFsaW5nIHdpdGggZmFjdG9ycyBpcyBmaXJtbHkgaW4gdGhlICJkb24ndCBib3RoZXIgcmVtZW1iZXJpbmcsIGp1c3QgZ28gdG8gU3RhY2sgT3ZlcmZsb3ciIHBpbGUuCgpGb3IgbW9yZSBvbiBmYWN0b3JzIHNlZSBDaGFwdGVyIDE1LiBUaGUgYHRpZHl2ZXJzZWAgYWxzbyBzaGlwcyBhIHBhY2thZ2UganVzdCBmb3IgZmFjdG9ycyBjYWxsZWQgYGZvcmNhdHNgLiAKCiMjIFNpZGUgbm90ZTogb3RoZXIgdmFyaWFibGUgdHlwZXMKCldlJ3JlIGdsb3NzaW5nIG92ZXIgb3RoZXIgdmFyaWFibGUgdHlwZXMgbGlrZSBkYXRlcywgdGltZXMgYW5kIHNvIG9uLiBGb3IgbW9yZSBbc2VlIGhlcmVdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovZGF0YS1pbXBvcnQuaHRtbCNwYXJzaW5nLWEtdmVjdG9yKSBhbmQgQ2hhcHRlcnMgMTQgYW5kIDE2LiBBZ2FpbiB0aGlzIGlzIG1vc3RseSBTdGFjayBPdmVyZmxvdyBzdHVmZi4gCgojIFRpZHkgZGF0YQoKQSBsb3Qgb2YgZGF0YSBzY2llbmNlIGlzIHJlYWxseSBkYXRhIHdyYW5nbGluZy4gVGhhdCBtZWFucyBnZXR0aW5nIHlvdXIgZGF0YSBpbnRvIHNoYXBlIHNvIHlvdSBjYW4gZG8gc2NpZW5jZS4gVGhhdCBhbHNvIG1lYW5zIHNwZW5kaW5nIGhvdXJzIG9uIFN0YWNrIE92ZXJmbG93LiBXaHk/IEJlY2F1c2UgZWFjaCBkYXRhIHNldCBpcyB1bmlxdWUsIGFuZCBob3cgeW91IHdhbnQgaXQgc2hhcGVkIGRlcGVuZHMgb24geW91ciBvYmplY3RpdmVzLiAKClRoZSBpZGVhIG9mICJ0aWR5IiBkYXRhIGlzIHRvIHByb2R1Y2UgZGF0YSBzZXRzIHRoYXQgc2F0aXNmeSB0aHJlZSBkZXNpZGVyYXRhLiBRdW90aW5nIGZyb20gdGhlIGJvb2s6CgoxLiBFYWNoIHZhcmlhYmxlIG11c3QgaGF2ZSBpdHMgb3duIGNvbHVtbgoyLiBFYWNoIG9ic2VydmF0aW9uIG11c3QgaGF2ZSBpdHMgb3duIHJvdwozLiBFYWNoIHZhbHVlIG11c3QgaGF2ZSBpdHMgb3duIGNlbGwKClZpc3VhbGx5OgoKIVtdKGh0dHBzOi8vZDMzd3VicmZraTBsNjguY2xvdWRmcm9udC5uZXQvNmYxZGRiNTQ0ZmM1YzY5YTI0NzhlNDQ0YWI4MTEyZmIwZWVhMjNmOC85MWFkYy9pbWFnZXMvdGlkeS0xLnBuZykKClRoaW5rIG9mIHRoaXMgd2F5OiBjYW4geW91IHJ1biBhIHJlZ3Jlc3Npb24gb24gdGhlIGRhdGEgYXMtaXM/IElmIG5vdCwgaXQncyBub3QgdGlkeS4KClRoaXMgZGF0YSBpcyB0aWR5OgoKYGBge3IgdGFibGUxfQp0YWJsZTEKYGBgCgpXaHk/IEJlY2F1c2UgZWFjaCBjb2x1bW4gaXMgYSB1bmlxdWUgdmFyaWFibGUgYW5kIGVhY2ggcm93IGlzIGEgdW5pcXVlIG9ic2VydmF0aW9uLiBUaGUgYGNvdmlkYCBkYXRhIHNldCBpcyB0aWR5IGZvciB0aGUgc2FtZSByZWFzb25zLgoKIyMgRWFzeSAoZWFzaWVyPykgZGF0YSB3cmFuZ2xpbmcgCgpEYXRhIHdyYW5nbGluZyBjYW4gdGFrZSBtYW55IGZvcm1zLiBNaXNzaW5nIGRhdGEsIHdlaXJkIHN0cmluZ3MsIGFuZCBzbyBvbi4gTG90cyBvZiB0aGVzZSBwcm9ibGVtcyBpbnZvbHZlIHRha2luZyBhY3Rpb24gb24gYSBzaW5nbGUgY29sdW1uLiAKCkZvciBpbnN0YW5jZSwgYHNlcGFyYXRlYCB3aWxsIHNlcGFyYXRlIG9uZSBjb2x1bW4gaW50byBjb2x1bW5zIG9mIGFuIG90aGVyd2lzZSB0aWR5IGRhdGEgc2V0LgoKQ29uc2lkZXIgYHRhYmxlM2A6CgpgYGB7ciB0YWJsZTN9CnRhYmxlMwpgYGAKCkV2ZXJ5dGhpbmcgbG9va3MgZ29vZCwgYnV0IHdlIHdhbnQgYHJhdGVgIChjYXNlcyBkaXZpZGVkIGJ5IHBvcHVsYXRpb24pIHRvIGJlIHR3byBjb2x1bW5zOiAiY2FzZXMiIGFuZCBwb3B1bGF0aW9uLiBFYXN5IGVub3VnaDoKCmBgYHtyIHNlcGFyYXRlIGRlbW99CnRhYmxlMyAlPiUgCiAgc2VwYXJhdGUocmF0ZSwgaW50byA9IGMoImNhc2VzIiwgInBvcHVsYXRpb24iKSkKYGBgCgpOb3cgeW91IGNhbiBlYXNpbHkgY3JlYXRlIGEgYHJhdGVgIGNvbHVtbi4gV2UganVzdCBuZWVkIHRvIHBhc3MgYGNvbnZlcnQgPSBUUlVFYCB0byBgc2VwYXJhdGUoKWAgc28gdGhlIG5ldyBjb2x1bW5zIGFyZSBudW1lcmljIChhbmQgdGh1cyBhbWVuYWJsZSB0byBtYXRoKToKCmBgYHtyIHNlcGFyYXRlIHRoZW4gbXV0YXRlfQp0YWJsZTMgJT4lIAogIHNlcGFyYXRlKHJhdGUsIGludG8gPSBjKCJjYXNlcyIsICJwb3B1bGF0aW9uIiksIGNvbnZlcnQgPSBUUlVFKSAlPiUgCiAgbXV0YXRlKHJhdGUgPSBjYXNlcyAvIHBvcHVsYXRpb24pCmBgYAoKCiMjIEhhcmQgKGhhcmRlcj8pIGRhdGEgd3JhbmdsaW5nCgpXaGF0IGFib3V0IHRoaXMgZGF0YT8gCgpgYGB7ciB0YWJsZTJ9CnRhYmxlMgpgYGAKCk5vdCB0aWR5LiBUaGUgc2FtZSBvYnNlcnZhdGlvbiAoZS5nLiwgIkFmZ2hhbmlzdGFuIGluIDE5OTkiKSBpcyBzcHJlYWQgYWNyb3NzIG11bHRpcGxlIHJvd3MuCgpUaWR5aW5nIHRoaXMgZGF0YSBpcyBoYXJkZXIuIEluIGdlbmVyYWwgdGhlIGhhcmQgc3R1ZmYgaXMgd2hlbiB5b3UgaGF2ZSB0byB0aGluayBhYm91dCBjaGFuZ2luZyB0aGUgZW50aXJlICpzaGFwZSogb2YgdGhlIGRhdGEuIChCeSBoYXJkIHN0dWZmIEkgbWVhbiBoYXJkZXIgdG8gZmluZCBhIHF1aWNrIFN0YWNrT3ZlcmxmbG93IHNvbHV0aW9uLikKClR3byBrZXkgZnVuY3Rpb25zIGhlcmU6IGBwaXZvdF93aWRlcmAgYW5kIGBwaXZvdF9sb25nZXJgLiBXZSdsbCBqdXN0IGZvY3VzIG9uIHRoZXNlIHR3by4gCgojIyBgcGl2b3Rfd2lkZXJgCgpgdGFibGUyYCBpcyBhIGNhc2Ugd2hlcmUgd2Ugd2FudCB0byAqKmFkZCBjb2x1bW5zKiogYW5kICoqcmVkdWNlIHJvd3MqKi4gVGhpcyBpcyBhIGpvYiBmb3IgYHBpdm90X3dpZGVyYC4gWW91IHdhbnQgdGhlIGB0eXBlYCBjb2x1bW4gdG8gYmUgdHdvIHNlcGFyYXRlIGNvbHVtbnM6IG9uZSBmb3IgImNhc2VzIiwgdGhlIG90aGVyIGZvciAicG9wdWxhdGlvbiIuIAoKYGBge3IgcGl2b3Qgd2lkZXIgdGFibGUyfQp0YWJsZTIgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0eXBlLCB2YWx1ZXNfZnJvbSA9IGNvdW50KQpgYGAKCk5vdyBpdCdzIHRpZHk6IGVhY2ggcm93IGlzIGEgdW5pcXVlIG9ic2VydmF0aW9uICgiQWZnaGFuaXN0YW4gMTk5OSIsICJCcmF6aWwgMjAwMCIsIGV0Yy4pCgpUaGUgZmlyc3QgYXJndW1lbnQgYG5hbWVzX2Zyb21gIHRha2VzIHRoZSBjb2x1bW4geW91IHdhbnQgdG8gc2VwYXJhdGUgaW50byBtdWx0aXBsZSBjb2x1bW5zLiAKClRoZSBzZWNvbmQgYXJndW1lbnQgYHZhbHVlc19mcm9tYCBtYXBzIHRoZSB2YWx1ZXMgaW50byB0aGVpciBuZXcgY29sdW1ucy4gCgojIyBgcGl2b3RfbG9uZ2VyYAoKVGhlIG9wcG9zaXRlIG9mIGBwaXZvdF93aWRlcmAuIFVzZSB0aGlzIHdoZW4geW91IHdhbnQgdG8gKipyZWR1Y2UgY29sdW1ucyoqIGFuZCAqKmFkZCByb3dzKiouIAoKRm9yIGluc3RhbmNlLCB0aGlzIGRhdGEgaXMgdW50aWR5OgoKYGBge3IgdGFibGU0YX0KdGFibGU0YQpgYGAKCldlIGRvbid0IHdhbnQgYSAiMTk5OSIgYW5kICIyMDAwIiBjb2x1bW4sIHdlIHdhbnQgYSAieWVhciIgY29sdW1uLgoKV2l0aCBgcGl2b3RfbG9uZ2VyKClgOgoKYGBge3IgdGFibGU0YSBwaXZvdF9sb25nZXJ9CnRhYmxlNGEgJT4lIAogIHBpdm90X2xvbmdlcihjKGAxOTk5YCwgYDIwMDBgKSwgbmFtZXNfdG8gPSAieWVhciIsIHZhbHVlc190byA9ICJjYXNlcyIpCmBgYAoKIyMgQ2hlY2twb2ludAoKQ29uc2lkZXIgdGhpcyBkYXRhOgoKYGBge3IgcGxheWVycyBjaGVja3BvaW50IHRpYmxlfQpwbGF5ZXJzIDwtIHRyaWJibGUoCiAgfnBsYXllciwgICAgICAgICAgICAgfnN0YXQsICAgICAgIH52YWx1ZSwKICAiS2V2aW4gRGUgQnJ1eW5lIiwgICAiZ29hbHMiLCAgICAgIDIwLAogICJLZXZpbiBEZSBCcnV5bmUiLCAgICJhc3Npc3RzIiwgICAgMTcsCiAgIktldmluIERlIEJydXluZSIsICAgInJlZF9jYXJkcyIsICAyLAogICJTZXJnaW8gUmFtb3MiLCAgICAgICJnb2FscyIsICAgICAgNSwKICAiU2VyZ2lvIFJhbW9zIiwgICAgICAiYXNzaXN0cyIsICAgIDgsCiAgIlNlcmdpbyBSYW1vcyIsICAgICAgInJlZF9jYXJkcyIsICAzMCwKKQoKcGxheWVycwpgYGAKCiJXaWRlbiIgaXQgdG8gY3JlYXRlIGEgbmV3IHRpYmJsZSBjYWxsZWQgInBsYXllcnNfd2lkZSI6CgpgYGB7ciBwbGF5ZXJzIGNoZWNrcG9pbnQgc29sdXRpb259CnBsYXllcnNfd2lkZSA9IHBsYXllcnMgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzdGF0LCB2YWx1ZXNfZnJvbSA9IHZhbHVlKSAKCnBsYXllcnNfd2lkZQpgYGAKCk5vdyBwaXZvdCBgcGxheWVyc193aWRlYCBiYWNrIHRvICJsb25nIiBmb3JtIGFuZCB1c2UgYGdlb21fY29sKClgIHRvIHBsb3QgYSBiYXIgY2hhcnQgb2YgZWFjaCBwbGF5ZXIncyBzdGF0aXN0aWNzOgoKYGBge3IgcGxheWVycyBwaXZvdCBsb25nZXIgYW5kIHBsb3R9CnBsYXllcnNfd2lkZSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGMoZ29hbHMsIGFzc2lzdHMsIHJlZF9jYXJkcyksIG5hbWVzX3RvID0gInN0YXQiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikgJT4lIAogIGdncGxvdChkYXRhID0gLiwgYWVzKHggPSBzdGF0LCB5ID0gdmFsdWVzLCBmaWxsID0gcGxheWVyKSkgKyAKICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpCmBgYAoKKipLZXkgcG9pbnQuKiogVG8gbWFrZSBhIHBsb3QgbGlrZSB0aGlzIHlvdSBuZWVkIHRoZSBkYXRhIGluICJsb25nIiBmb3JtYXQgKGkuZS4gd2l0aCBgcGl2b3RfbG9uZ2VyKClgLikKCiMjIENoZWNrcG9pbnQKClBpY2sgc29tZSBjb3VudHJpZXMgZnJvbSB0aGUgYGNvdmlkYCBkYXRhLCBjYWxjdWxhdGUgdG90YWwgZGVhdGhzIGFuZCB0b3RhbCBjb25maXJtZWQgY2FzZXMgYnkgY291bnRyeSwgYHBpdm90X2xvbmdlcmAgdGhlIHN1bW1hcnkgdGFibGUsIHRoZW4gbWFrZSBhIGJhciBjaGFydCAob25lIGZhY2V0IGZvciB0b3RhbCBkZWF0aHMgYnkgY291bnRyeSwgdGhlIG90aGVyIGZvciB0b3RhbCBjb25maXJtZWQgY2FzZXMgYnkgY291bnRyeSkuCgpgYGB7ciBjaGVja3BvaW50IGNvdmlkIHBsb3QsIG1lc3NhZ2U9RkFMU0V9CiMgcGFydCAxOiB0aGUgc3VtbWFyeSB0YWJsZQpjb3ZpZCAlPiUgCiAgZmlsdGVyKENvdW50cnlfUmVnaW9uICVpbiUgYygiQmVsZ2l1bSIsICJLb3JlYSwgU291dGgiLCAiVVMiLCAiQ2FuYWRhIiwgIkl0YWx5IikpICU+JSAKICBncm91cF9ieShDb3VudHJ5X1JlZ2lvbikgJT4lIAogIHN1bW1hcmlzZSh0b3RhbF9kZWF0aHMgPSBzdW0oRGVhdGhzKSwgdG90YWxfY29uZmlybWVkID0gc3VtKENvbmZpcm1lZCkpICU+JSAKICAjIHBhcnQgMjogbm93IHBpdm90X2xvbmcgdGhlIHN1bW1hcnkgdGFibGUKICBwaXZvdF9sb25nZXIoYyh0b3RhbF9kZWF0aHMsIHRvdGFsX2NvbmZpcm1lZCksIG5hbWVzX3RvID0gIm1lYXN1cmUiLCB2YWx1ZXNfdG89InZhbHVlIikgJT4lIAogICMgcGFydCAzOiBub3cgcGxvdCBpdCEgCiAgZ2dwbG90KC4sIGFlcyh4ID0gQ291bnRyeV9SZWdpb24sIHkgPSB2YWx1ZSkpICsgCiAgZ2VvbV9jb2woKSArIAogIGZhY2V0X3dyYXAofm1lYXN1cmUpCmBgYAoKIyMgQ2hlY2twb2ludAoKQ29uc2lkZXIgdGhpcyBkYXRhOgoKYGBge3IgY2hlY2twb2ludCBwZW9wbGUgdGlibGV9CnBlb3BsZSA8LSB0cmliYmxlKAogIH5uYW1lLCAgICAgICAgICAgICB+bmFtZXMsICB+dmFsdWVzLAogICJQaGlsbGlwIFdvb2RzIiwgICAiYWdlIiwgICAgICAgNDUsCiAgIlBoaWxsaXAgV29vZHMiLCAgICJoZWlnaHQiLCAgIDE4NiwKICAiUGhpbGxpcCBXb29kcyIsICAgImFnZSIsICAgICAgIDUwLAogICJKZXNzaWNhIENvcmRlcm8iLCAiYWdlIiwgICAgICAgMzcsCiAgIkplc3NpY2EgQ29yZGVybyIsICJoZWlnaHQiLCAgIDE1NgopCgpwZW9wbGUKYGBgCgpJdCBuZWVkcyB0byBiZSB0aWRpZWQuIFdoYXQgaGFwcGVucyBpZiB5b3UgdHJ5IHRvIHBpdm90IHdpZGVyPyAKCmBgYHtyIHRyeSB0byBwaXZvdCBwZW9wbGUgd2lkZXIsIHdhcm5pbmc9RkFMU0V9CnBlb3BsZSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG5hbWVzLCB2YWx1ZXNfZnJvbSA9IHZhbHVlcykKYGBgCgpTdXBwb3NlIHlvdSBhcmUgdG9sZCB0aGF0IHRoZXJlIHdhcyBhIG1pc3Rha2UgaW4gdGhlIGRhdGEgZW50cnk6ICJQaGlsbGlwIFdvb2RzIiBpcyBhZ2VkIDUwLCBub3QgNDUuIFVzZSB0aGlzIGluZm9ybWF0aW9uIHRvIHdyaXRlIGEgY29kZSB0aGF0IGZpbHRlcnMgb3V0IHRoYXQgcm93LiBUaGVuIHRpZHkgdGhlIGRhdGEgd2l0aCBgcGl2b3Rfd2lkZXIoKWA6CgpgYGB7ciBwZW9wbGUgZmlsdGVyIHRoYW4gcGl2b3QgfQpwZW9wbGUgJT4lIAogIGZpbHRlcighKG5hbWUgPT0gIlBoaWxsaXAgV29vZHMiICYgbmFtZXMgPT0gImFnZSIgJiB2YWx1ZXMgPT0gNDUpKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG5hbWVzLCB2YWx1ZXNfZnJvbSA9IHZhbHVlcykKYGBgCgoKCgoK