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
:
[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.
tibble vs data.frame
Why re-invent the wheel? The book lists three main reasons. A tibble:
- never changes the type of input;
- never changes the names of variables, and;
- 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.
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:
argument is not numeric or logical: returning NA
[1] NA
Why? Because with data.frames []
returns a vector:
[1] "numeric"
but with tibbles []
returns another tibble:
[1] "tbl_df" "tbl" "data.frame"
To return a vector you have to index with [[columns]]
(no row argument):
[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.
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))
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!
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))
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)
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")
[36m──[39m [1m[1mColumn specification[1m[22m [36m────────────────────────────────────────────────[39m
cols(
FIPS = [32mcol_double()[39m,
Admin2 = [31mcol_character()[39m,
Province_State = [31mcol_character()[39m,
Country_Region = [31mcol_character()[39m,
Last_Update = [34mcol_datetime(format = "")[39m,
Lat = [32mcol_double()[39m,
Long_ = [32mcol_double()[39m,
Confirmed = [32mcol_double()[39m,
Deaths = [32mcol_double()[39m,
Recovered = [32mcol_double()[39m,
Active = [32mcol_double()[39m,
Combined_Key = [31mcol_character()[39m,
Incident_Rate = [32mcol_double()[39m,
Case_Fatality_Ratio = [32mcol_double()[39m
)
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.
Writing a file
Check out the Covid-19 deaths:
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
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()
:
[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
.
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.
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:
- Each variable must have its own column
- Each observation must have its own row
- 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:
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.
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
:
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)
Hard (harder?) data wrangling
What about this data?
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.
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.
pivot_longer
The opposite of pivot_wider
. Use this when you want to reduce columns and add rows.
For instance, this data is untidy:
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")
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()
.)
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)
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