Kable package
In a previous weeks, we saw R Markdown in action, where multiple
things can be created in one location: code, commentary, and output.
In this chapter we will explore package which will facilitate the
creation of presentation-worthy tables: “kableExtra”.
Let’s work with the cross-sectional data on the credit history for a
sample of applicants for a type of credit card.
data(CreditCard)
cardHead <- head(CreditCard)
cardHead
## card reports age income share expenditure owner selfemp dependents
## 1 yes 0 37.66667 4.5200 0.033269910 124.983300 yes no 3
## 2 yes 0 33.25000 2.4200 0.005216942 9.854167 no no 3
## 3 yes 0 33.66667 4.5000 0.004155556 15.000000 yes no 4
## 4 yes 0 30.50000 2.5400 0.065213780 137.869200 no no 0
## 5 yes 0 32.16667 9.7867 0.067050590 546.503300 yes no 2
## 6 yes 0 23.25000 2.5000 0.044438400 91.996670 no no 0
## months majorcards active
## 1 54 1 12
## 2 34 1 13
## 3 58 1 5
## 4 25 1 7
## 5 64 1 5
## 6 54 1 1
cardHead %>%
kbl()
|
card
|
reports
|
age
|
income
|
share
|
expenditure
|
owner
|
selfemp
|
dependents
|
months
|
majorcards
|
active
|
|
yes
|
0
|
37.66667
|
4.5200
|
0.0332699
|
124.983300
|
yes
|
no
|
3
|
54
|
1
|
12
|
|
yes
|
0
|
33.25000
|
2.4200
|
0.0052169
|
9.854167
|
no
|
no
|
3
|
34
|
1
|
13
|
|
yes
|
0
|
33.66667
|
4.5000
|
0.0041556
|
15.000000
|
yes
|
no
|
4
|
58
|
1
|
5
|
|
yes
|
0
|
30.50000
|
2.5400
|
0.0652138
|
137.869200
|
no
|
no
|
0
|
25
|
1
|
7
|
|
yes
|
0
|
32.16667
|
9.7867
|
0.0670506
|
546.503300
|
yes
|
no
|
2
|
64
|
1
|
5
|
|
yes
|
0
|
23.25000
|
2.5000
|
0.0444384
|
91.996670
|
no
|
no
|
0
|
54
|
1
|
1
|
Let’s tweak the appearance of this with the “align” and the “caption”
arguments.
The align argument takes a character vector with letters “l”, “c”, or
“r” - specifying where you want the columns to be aligned.
The caption argument gives a caption to the table.
base <- cardHead %>%
kbl(align = c(rep("c", 7), rep("r", 5)), caption = "kable example with card data")
base
kable example with card data
|
card
|
reports
|
age
|
income
|
share
|
expenditure
|
owner
|
selfemp
|
dependents
|
months
|
majorcards
|
active
|
|
yes
|
0
|
37.66667
|
4.5200
|
0.0332699
|
124.983300
|
yes
|
no
|
3
|
54
|
1
|
12
|
|
yes
|
0
|
33.25000
|
2.4200
|
0.0052169
|
9.854167
|
no
|
no
|
3
|
34
|
1
|
13
|
|
yes
|
0
|
33.66667
|
4.5000
|
0.0041556
|
15.000000
|
yes
|
no
|
4
|
58
|
1
|
5
|
|
yes
|
0
|
30.50000
|
2.5400
|
0.0652138
|
137.869200
|
no
|
no
|
0
|
25
|
1
|
7
|
|
yes
|
0
|
32.16667
|
9.7867
|
0.0670506
|
546.503300
|
yes
|
no
|
2
|
64
|
1
|
5
|
|
yes
|
0
|
23.25000
|
2.5000
|
0.0444384
|
91.996670
|
no
|
no
|
0
|
54
|
1
|
1
|
A key function, where we can enjoy much of the configuration for the
table, is via kable_styling().
We have options “bootstrap_options” or “latex_options”, where the
latter requires the use of the package “tinytex” and a local
installation of LaTeX.
Possible options for “bootstrap_options” include ‘basic’, ‘striped’,
‘bordered’, ‘hover’, ‘condensed’, ‘responsive’, and none.
Possible for “latex_options” include ‘basic’, ‘striped’,
‘hold_position’, ‘HOLD_position’, ‘scale_down’, and ‘repeat_header’.
base %>%
kable_styling(bootstrap_options = "striped")
kable example with card data
|
card
|
reports
|
age
|
income
|
share
|
expenditure
|
owner
|
selfemp
|
dependents
|
months
|
majorcards
|
active
|
|
yes
|
0
|
37.66667
|
4.5200
|
0.0332699
|
124.983300
|
yes
|
no
|
3
|
54
|
1
|
12
|
|
yes
|
0
|
33.25000
|
2.4200
|
0.0052169
|
9.854167
|
no
|
no
|
3
|
34
|
1
|
13
|
|
yes
|
0
|
33.66667
|
4.5000
|
0.0041556
|
15.000000
|
yes
|
no
|
4
|
58
|
1
|
5
|
|
yes
|
0
|
30.50000
|
2.5400
|
0.0652138
|
137.869200
|
no
|
no
|
0
|
25
|
1
|
7
|
|
yes
|
0
|
32.16667
|
9.7867
|
0.0670506
|
546.503300
|
yes
|
no
|
2
|
64
|
1
|
5
|
|
yes
|
0
|
23.25000
|
2.5000
|
0.0444384
|
91.996670
|
no
|
no
|
0
|
54
|
1
|
1
|
Next, we can customize the look and feel of particular rows and
columns.
Let’s see an example here, where we make the last three rows
blue.
base %>%
kable_styling(bootstrap_options = "bordered") %>%
column_spec(8:12, bold = T) %>%
row_spec(4:6, italic = T, color = "gold", background = "blue")
kable example with card data
|
card
|
reports
|
age
|
income
|
share
|
expenditure
|
owner
|
selfemp
|
dependents
|
months
|
majorcards
|
active
|
|
yes
|
0
|
37.66667
|
4.5200
|
0.0332699
|
124.983300
|
yes
|
no
|
3
|
54
|
1
|
12
|
|
yes
|
0
|
33.25000
|
2.4200
|
0.0052169
|
9.854167
|
no
|
no
|
3
|
34
|
1
|
13
|
|
yes
|
0
|
33.66667
|
4.5000
|
0.0041556
|
15.000000
|
yes
|
no
|
4
|
58
|
1
|
5
|
|
yes
|
0
|
30.50000
|
2.5400
|
0.0652138
|
137.869200
|
no
|
no
|
0
|
25
|
1
|
7
|
|
yes
|
0
|
32.16667
|
9.7867
|
0.0670506
|
546.503300
|
yes
|
no
|
2
|
64
|
1
|
5
|
|
yes
|
0
|
23.25000
|
2.5000
|
0.0444384
|
91.996670
|
no
|
no
|
0
|
54
|
1
|
1
|
We can also create groups for our columns.
base %>%
kable_styling(bootstrap_options = "bordered") %>%
add_header_above(c("Group 1" = 4, "Group 2" = 2, "Group 3" = 6))
kable example with card data
|
Group 1
|
Group 2
|
Group 3
|
|
card
|
reports
|
age
|
income
|
share
|
expenditure
|
owner
|
selfemp
|
dependents
|
months
|
majorcards
|
active
|
|
yes
|
0
|
37.66667
|
4.5200
|
0.0332699
|
124.983300
|
yes
|
no
|
3
|
54
|
1
|
12
|
|
yes
|
0
|
33.25000
|
2.4200
|
0.0052169
|
9.854167
|
no
|
no
|
3
|
34
|
1
|
13
|
|
yes
|
0
|
33.66667
|
4.5000
|
0.0041556
|
15.000000
|
yes
|
no
|
4
|
58
|
1
|
5
|
|
yes
|
0
|
30.50000
|
2.5400
|
0.0652138
|
137.869200
|
no
|
no
|
0
|
25
|
1
|
7
|
|
yes
|
0
|
32.16667
|
9.7867
|
0.0670506
|
546.503300
|
yes
|
no
|
2
|
64
|
1
|
5
|
|
yes
|
0
|
23.25000
|
2.5000
|
0.0444384
|
91.996670
|
no
|
no
|
0
|
54
|
1
|
1
|
Data Aggregation
In the first stage of our analysis we are going to group our data in
the form of the simple frequency table.
First, let’s take a look at the distribution of income in our sample
and verify the tabular accuracy using TAI measure:
options(scipen=999)
limits<- cut(CreditCard$income,seq(0,14,by=2))
tabelka <- freq(limits,type="html")
##
|
| | 0%
|
|======================================================================| 100%
tabelka
## $`x:`
## x label Freq Percent Valid Percent Cumulative Percent
## Valid (0,2] 236 17.9 17.9 17.9
## (2,4] 783 59.4 59.4 77.3
## (4,6] 205 15.5 15.5 92.8
## (6,8] 63 4.8 4.8 97.6
## (8,10] 23 1.7 1.7 99.3
## (10,12] 7 0.5 0.5 99.8
## (12,14] 2 0.2 0.2 100.0
## Total 1319 100.0 100.0
## Missing <blank> 0 0.0
## <NA> 0 0.0
## Total 1319 100.0
Without ‘kable’ styling it’s quite ugly right? ;-)
Tabular accuracy
An index of tabular accuracy TAI, described by Jenks and Casspal in
1971 is to optimize the class distribution used in a
cartograms/frequency tables etc.
The TAI indicator takes values in the range (0;1). The numerator of
the expression is the sum of the absolute deviations of the values
classified into classes, and the denominator is the sum of the absolute
deviations of the entire classified set.
The better the class division reflects the nature of the data, the
larger the indicator will be. As the number of classes increases, the
indicator will take on larger values.
Let’s calculate TAI index to check the properties of the tabulated
data:
tabelka2 <- classIntervals(CreditCard$income, n=7, style="fixed", fixedBreaks=seq(0,14,by=2))
jenks.tests(tabelka2)
## # classes Goodness of fit Tabular accuracy
## 7.0000000 0.9085328 0.6568085
As we can see - TAI index…
We can use different recipes… (styles):
tabelka3<-classIntervals(CreditCard$income, n=10, style="sd")
plot(tabelka3,pal=c(1:10))

jenks.tests(tabelka3)
## # classes Goodness of fit Tabular accuracy
## 8.0000000 0.9274792 0.6909392
Still, the TAI indicator is not satisfactory. What should we change
in the final frequency table design?
hist(CreditCard$income)

Continuous variables
We can calculate the absolute and relative frequencies of a vector x
with the function ‘Freq’ from the DescTools packages.
Continuous (numeric) variables will be cut using the same logic as used
by the function hist. Categorical variables will be aggregated by table.
The result will contain single and cumulative frequencies for both,
absolute values and percentages.
tabela4<-Freq(CreditCard$income,breaks=seq(0,14,by=2),useNA="ifany")
tabela4 %>%
kable(col.names = c("Incomes in kUSD","Frequency","Percentage %","Cumulative frequency","Cumulative percentage %")) %>%
kable_classic(full_width = F, html_font = "Cambria")
|
Incomes in kUSD
|
Frequency
|
Percentage %
|
Cumulative frequency
|
Cumulative percentage %
|
|
[0,2]
|
236
|
0.1789234
|
236
|
0.1789234
|
|
(2,4]
|
783
|
0.5936315
|
1019
|
0.7725550
|
|
(4,6]
|
205
|
0.1554208
|
1224
|
0.9279757
|
|
(6,8]
|
63
|
0.0477635
|
1287
|
0.9757392
|
|
(8,10]
|
23
|
0.0174375
|
1310
|
0.9931766
|
|
(10,12]
|
7
|
0.0053071
|
1317
|
0.9984837
|
|
(12,14]
|
2
|
0.0015163
|
1319
|
1.0000000
|
BTW: what about TAI of that table?…
Categorical variables
Now, let’s take a look at the categorical data and make some
tabulations. The xtabs function works like table except
it can produce tables from frequencies using the formula interface.
Let’s say we want to see the table with data on how many card
applications was accepted or not:
## card
## no yes
## 296 1023
We may easily produce cross-tabs (status vs. Does the
individual own their home?) as well:
crosstab<-xtabs(~ card + owner, data=CreditCard)
crosstab
## owner
## card no yes
## no 206 90
## yes 532 491
and transform it into pretty html table with the kable function:
crosstab %>%
kbl() %>%
kable_styling(full_width = F) %>%
column_spec(1, bold = T, border_right = T) %>%
column_spec(2, background = "yellow")
|
|
no
|
yes
|
|
no
|
206
|
90
|
|
yes
|
532
|
491
|
Data Visualization
We will explore the “ggplot2” package of the
tidyverse for data visualization purposes. The “ggplot2” packages
involve the the following three mandatory components:
- Data
- An aesthetic mapping
- Geoms (aka objects)
The following components can also optionally be added:
- Stats (aka transformations)
- Scales
- Facets
- Coordinate systems
- Position adjustments
- Themes
Please note that code in this tutorial was adapted from Chapters 3 of
the book “R for Data Science” by Hadley Wickham and Garrett
Grolemund.
The full book can be found at: https://r4ds.had.co.nz/
A good cheat sheet for ggplot2 functions can be found at: https://rstudio.com/wp-content/uploads/2015/03/ggplot2-cheatsheet.pdf
Scatterplots
Let’s create an extremely simple scatterplot.
We will use the function ggplot() to do this.
The format of any ggplot graph is this function, followed by another
function to add objects.
The objects on a graph in the case of a scatterplot are points. The
function we add to it is geom_point.
These functions rely on a function on the inside called
aes().
The data and aesthetic mapping components can be added to either the
ggplot() or geom functions.
ggplot(data = mpg) +
geom_point(aes(x = displ, y = hwy))

This is one of the most basic graphs that one can make using the
ggplot2 framework.
Next, let’s add color.
geom_point() understands the following aesthetics: x, y,
alpha, color, fill, group, shape, size, and stroke (see help
documentation).
Let’s map the color argument to the variable “class” from mpg.
ggplot(mpg) +
geom_point(aes(x = displ, y = hwy, color = class))

This is not the only way to color objects.
Including the color argument inside of the aes()
function can map colors to a choice of variable.
However, we can specify colors manually, by specifying color outside
of the aes() function. We will also illustrate the “size”
argument.
ggplot(mpg) +
geom_point(aes(x = displ, y = hwy, size = class), color = "blue")

Barplots
Lastly, let’s examine other objects that we can plot using
ggplot(). We will create a bar chart using the function
geom_bar().
ggplot(mpg) +
geom_bar(aes(x = class))

With the geom_bar() function, we have a great use-case
for a stat transformation.
The following code can be used to convert these counts to
proportions:
ggplot(mpg) +
geom_bar(aes(x = class, y = stat(prop), group = 1))

Histograms
Next, let’s create a histogram with the geom_histogram()
function.
ggplot(mpg) +
geom_histogram(aes(x = hwy))

The geom_histogram() function accepts the argument
“binwidth”, and has two key arguments for color: fill (this controls the
overall color), and color (this controls the border).
Let’s fill all these in:
ggplot(mpg) +
geom_histogram(aes(x = hwy), binwidth = 5, fill = "navy", color = "gold")

geom_histogram() provides a great example to modify the
scale.
Notice in this example that the axis is automatically broken up by
units of 10, and does not begin at 0.
We can modify this with the function
scale_x_continuous(), as well as the y-axis with the
function scale_y_continuous().
There are three key arguments we will feed this function: “breaks”,
“limits”, and “expand”.
“breaks” will define the breaks on the axis.
“limits” will define the beginning and end of the axis, and the
“expand” argument can be used to start the axes at 0 by using “expand =
c(0,0)”.
ggplot(mpg) +
geom_histogram(aes(x = hwy), binwidth = 5, fill = "navy", color = "gold") +
scale_x_continuous(breaks = seq(0, 45, 5), limits = c(0, 50), expand = c(0,0)) +
scale_y_continuous(breaks = seq(0, 90, 10), limits = c(0, 90), expand = c(0,0))

Boxplots
Next, we will create boxplots.
p <- ggplot(mpg) +
geom_boxplot(aes(x = class, y = cty, fill = class))
p

Facets
Faceting generates small multiples each showing a different subset of
the data. Small multiples are a powerful tool for exploratory data
analysis: you can rapidly compare patterns in different parts of the
data and see whether they are the same or different.
Read more about facets here.
Notice in this document the use of the fig.height and fig.width
options.
Key arguments to facet_wrap() are “facets”, “nrow”, and
“ncol”.
ggplot(mpg) +
geom_boxplot(aes(x = class, y = cty, fill = class)) +
facet_wrap(facets = ~cyl, nrow = 2, ncol = 2)

Coordinates
Other coordinate systems can be applied to graphs created from
ggplot2.
One example is coord_polar(), which uses polar
coordinates. Most of these are quite rare. Probably the most common one
is coord_flip(), which will flip the X and Y axes. Let’s
also illustrate the labs() function, which can be used to
change labels.
ggplot(mpg) +
geom_bar(aes(x = class, fill = factor(cyl))) +
labs(title = "Cylinders by Class", fill = "cylinders") +
coord_flip()

These bars are stacked on top of each of other, due to the “cyl”
variable being mapped to the “fill” argument. There are various position
adjustments that can be used. Again, most of these are not very common,
but a common one is the argument “position = ‘dodge’”, which will put
items side-by-side.
See this example:
ggplot(mpg) +
geom_bar(aes(x = class, fill = factor(cyl)), position = "dodge") +
labs(title = "Cylinders by Class", fill = "cylinders") +
coord_flip()

Themes
Lastly, we can alter the “theme”, or the overall appearance of our
plot.
I recommend using the ggThemeAssist package, because this will make
this incredibly easy, with an interface that will automatically generate
reproducible code.
This can be used by highlighting a ggplot2 object, and navigating to
Addins > ggplot Theme Assistant.
We’ll make the following changes: eliminating the panel grid lines,
eliminating axis ticks, adding a title called “Boxplot Example”, making
it bigger and putting it in bold, and adjusting it to the center.
# p
p + theme(axis.ticks = element_line(linetype = "blank"),
panel.grid.major = element_line(linetype = "blank"),
panel.grid.minor = element_line(linetype = "blank"),
plot.title = element_text(size = 14,
face = "bold", hjust = 0.5)) +labs(title = "Boxplot Example")

There are many more examples of things that can be done with
ggplot2.
It is an amazingly powerful and flexible package, and it is worth
getting acquainted with the cheat sheet.
LS0tDQp0aXRsZTogIkRhdGEgVmlzdWFsaXphdGlvbiINCmF1dGhvcjogIllvdXIgTmFtZSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIGZvbnRzaXplOiA4cHQNCiAgICB0b2M6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICBkZl9wcmludDogZGVmYXVsdA0KICAgIHRvY19kZXB0aDogNQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQoNCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQojIyBHbG9iYWwgb3B0aW9ucw0Kb3B0aW9ucyhxd3JhcHMyX21hcmt1cCA9ICJtYXJrZG93biIpDQpsaWJyYXJ5KHF3cmFwczIpDQpsaWJyYXJ5KGFyc2VuYWwpDQpsaWJyYXJ5KGUxMDcxKQ0KbGlicmFyeShoYXZlbikNCmxpYnJhcnkocGFwZVIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHN1bW1hcnl0b29scykNCmxpYnJhcnkoY2xhc3NJbnQpDQpsaWJyYXJ5KHBhc3RlY3MpDQpsaWJyYXJ5KHJlcG9ydHRvb2xzKQ0KbGlicmFyeShkZXNjdGFibGUpDQpsaWJyYXJ5KERlc2NUb29scykNCmxpYnJhcnkoZnJlcXVlbmN5KQ0KbGlicmFyeShnZ3B1YnIpDQpsaWJyYXJ5KGRsb29rcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodGlueXRleCkNCmxpYnJhcnkoQUVSKQ0KbGlicmFyeShnZ1RoZW1lQXNzaXN0KQ0KbGlicmFyeShTbWFydGVyUG9sYW5kKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCg0KIyMgS2FibGUgcGFja2FnZQ0KDQpJbiBhIHByZXZpb3VzIHdlZWtzLCB3ZSBzYXcgUiBNYXJrZG93biBpbiBhY3Rpb24sIHdoZXJlIG11bHRpcGxlIHRoaW5ncyBjYW4gYmUgY3JlYXRlZCBpbiBvbmUgbG9jYXRpb246IGNvZGUsIGNvbW1lbnRhcnksIGFuZCBvdXRwdXQuIA0KDQpJbiB0aGlzIGNoYXB0ZXIgd2Ugd2lsbCBleHBsb3JlIHBhY2thZ2Ugd2hpY2ggd2lsbCBmYWNpbGl0YXRlIHRoZSBjcmVhdGlvbiBvZiBwcmVzZW50YXRpb24td29ydGh5IHRhYmxlczogImthYmxlRXh0cmEiLg0KDQpMZXQncyB3b3JrIHdpdGggdGhlIGNyb3NzLXNlY3Rpb25hbCBkYXRhIG9uIHRoZSBjcmVkaXQgaGlzdG9yeSBmb3IgYSBzYW1wbGUgb2YgYXBwbGljYW50cyBmb3IgYSB0eXBlIG9mIGNyZWRpdCBjYXJkLg0KDQpgYGB7cn0NCmRhdGEoQ3JlZGl0Q2FyZCkNCmNhcmRIZWFkIDwtIGhlYWQoQ3JlZGl0Q2FyZCkNCmNhcmRIZWFkDQpgYGANCg0KPGJyPg0KDQpgYGB7ciBUaWJibGUgd2l0aCAia2FibGUifQ0KY2FyZEhlYWQgJT4lDQogIGtibCgpDQpgYGANCg0KPGJyPg0KDQpMZXQncyB0d2VhayB0aGUgYXBwZWFyYW5jZSBvZiB0aGlzIHdpdGggdGhlICJhbGlnbiIgYW5kIHRoZSAiY2FwdGlvbiIgYXJndW1lbnRzLiAgDQoNClRoZSBhbGlnbiBhcmd1bWVudCB0YWtlcyBhIGNoYXJhY3RlciB2ZWN0b3Igd2l0aCBsZXR0ZXJzICJsIiwgImMiLCBvciAiciIgLSBzcGVjaWZ5aW5nIHdoZXJlIHlvdSB3YW50IHRoZSBjb2x1bW5zIHRvIGJlIGFsaWduZWQuDQoNClRoZSBjYXB0aW9uIGFyZ3VtZW50IGdpdmVzIGEgY2FwdGlvbiB0byB0aGUgdGFibGUuDQoNCjxicj4NCg0KYGBge3IgQWxpZ25lZCBrYWJsZX0NCmJhc2UgPC0gY2FyZEhlYWQgJT4lDQogIGtibChhbGlnbiA9IGMocmVwKCJjIiwgNyksIHJlcCgiciIsIDUpKSwgY2FwdGlvbiA9ICJrYWJsZSBleGFtcGxlIHdpdGggY2FyZCBkYXRhIikNCmJhc2UNCmBgYA0KDQo8YnI+DQoNCkEga2V5IGZ1bmN0aW9uLCB3aGVyZSB3ZSBjYW4gZW5qb3kgbXVjaCBvZiB0aGUgY29uZmlndXJhdGlvbiBmb3IgdGhlIHRhYmxlLCBpcyB2aWEgYGthYmxlX3N0eWxpbmcoKWAuICAgDQoNCldlIGhhdmUgb3B0aW9ucyAiYm9vdHN0cmFwX29wdGlvbnMiIG9yICJsYXRleF9vcHRpb25zIiwgd2hlcmUgdGhlIGxhdHRlciByZXF1aXJlcyB0aGUgdXNlIG9mIHRoZSBwYWNrYWdlICJ0aW55dGV4IiBhbmQgYSBsb2NhbCBpbnN0YWxsYXRpb24gb2YgTGFUZVguICANCg0KUG9zc2libGUgb3B0aW9ucyBmb3IgImJvb3RzdHJhcF9vcHRpb25zIiBpbmNsdWRlICdiYXNpYycsICdzdHJpcGVkJywgJ2JvcmRlcmVkJywgJ2hvdmVyJywgJ2NvbmRlbnNlZCcsICdyZXNwb25zaXZlJywgYW5kIG5vbmUuICANCg0KUG9zc2libGUgZm9yICJsYXRleF9vcHRpb25zIiBpbmNsdWRlICdiYXNpYycsICdzdHJpcGVkJywgJ2hvbGRfcG9zaXRpb24nLCAnSE9MRF9wb3NpdGlvbicsICdzY2FsZV9kb3duJywgYW5kICdyZXBlYXRfaGVhZGVyJy4NCg0KPGJyPg0KDQoNCmBgYHtyIEJvb3RzdHJhcCAtIHN0cmlwZWR9DQpiYXNlICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiKQ0KYGBgDQoNCjxicj4NCg0KTmV4dCwgd2UgY2FuIGN1c3RvbWl6ZSB0aGUgbG9vayBhbmQgZmVlbCBvZiBwYXJ0aWN1bGFyIHJvd3MgYW5kIGNvbHVtbnMuICAgDQoNCkxldCdzIHNlZSBhbiBleGFtcGxlIGhlcmUsIHdoZXJlIHdlIG1ha2UgdGhlIGxhc3QgdGhyZWUgcm93cyBibHVlLg0KDQpgYGB7ciBDb2x1bW4gYW5kIHJvdyBjdXN0b21pemluZ30NCmJhc2UgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAiYm9yZGVyZWQiKSAlPiUNCiAgY29sdW1uX3NwZWMoODoxMiwgYm9sZCA9IFQpICU+JQ0KICByb3dfc3BlYyg0OjYsIGl0YWxpYyA9IFQsIGNvbG9yID0gImdvbGQiLCBiYWNrZ3JvdW5kID0gImJsdWUiKQ0KYGBgDQoNCjxicj4NCg0KDQpXZSBjYW4gYWxzbyBjcmVhdGUgZ3JvdXBzIGZvciBvdXIgY29sdW1ucy4NCg0KPGJyPg0KDQpgYGB7cn0NCmJhc2UgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAiYm9yZGVyZWQiKSAlPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCJHcm91cCAxIiA9IDQsICJHcm91cCAyIiA9IDIsICJHcm91cCAzIiA9IDYpKQ0KYGBgDQoNCjxicj4NCg0KDQojIyBEYXRhIEFnZ3JlZ2F0aW9uDQoNCkluIHRoZSBmaXJzdCBzdGFnZSBvZiBvdXIgYW5hbHlzaXMgd2UgYXJlIGdvaW5nIHRvIGdyb3VwIG91ciBkYXRhIGluIHRoZSBmb3JtIG9mIHRoZSBzaW1wbGUgZnJlcXVlbmN5IHRhYmxlLg0KDQpGaXJzdCwgbGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBpbmNvbWUgaW4gb3VyIHNhbXBsZSBhbmQgdmVyaWZ5IHRoZSB0YWJ1bGFyIGFjY3VyYWN5IHVzaW5nIFRBSSBtZWFzdXJlOg0KDQpgYGB7ciB0YWJsZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9DQpvcHRpb25zKHNjaXBlbj05OTkpDQoNCmxpbWl0czwtIGN1dChDcmVkaXRDYXJkJGluY29tZSxzZXEoMCwxNCxieT0yKSkNCnRhYmVsa2EgPC0gZnJlcShsaW1pdHMsdHlwZT0iaHRtbCIpDQp0YWJlbGthDQpgYGANCg0KV2l0aG91dCAna2FibGUnIHN0eWxpbmcgaXQncyBxdWl0ZSB1Z2x5IHJpZ2h0PyA7LSkNCg0KDQojIyMgVGFidWxhciBhY2N1cmFjeQ0KDQpBbiBpbmRleCBvZiB0YWJ1bGFyIGFjY3VyYWN5IFRBSSwgZGVzY3JpYmVkIGJ5IEplbmtzIGFuZCBDYXNzcGFsIGluIDE5NzEgaXMgdG8gb3B0aW1pemUgdGhlIGNsYXNzIGRpc3RyaWJ1dGlvbiB1c2VkIGluIGEgY2FydG9ncmFtcy9mcmVxdWVuY3kgdGFibGVzIGV0Yy4gDQoNClRoZSBUQUkgaW5kaWNhdG9yIHRha2VzIHZhbHVlcyBpbiB0aGUgcmFuZ2UgKDA7MSkuIFRoZSBudW1lcmF0b3Igb2YgdGhlIGV4cHJlc3Npb24gaXMgdGhlIHN1bSBvZiB0aGUgYWJzb2x1dGUgZGV2aWF0aW9ucyBvZiB0aGUgdmFsdWVzIGNsYXNzaWZpZWQgaW50byBjbGFzc2VzLCBhbmQgdGhlIGRlbm9taW5hdG9yIGlzIHRoZSBzdW0gb2YgdGhlIGFic29sdXRlIGRldmlhdGlvbnMgb2YgdGhlIGVudGlyZSBjbGFzc2lmaWVkIHNldC4gDQoNClRoZSBiZXR0ZXIgdGhlIGNsYXNzIGRpdmlzaW9uIHJlZmxlY3RzIHRoZSBuYXR1cmUgb2YgdGhlIGRhdGEsIHRoZSBsYXJnZXIgdGhlIGluZGljYXRvciB3aWxsIGJlLiBBcyB0aGUgbnVtYmVyIG9mIGNsYXNzZXMgaW5jcmVhc2VzLCB0aGUgaW5kaWNhdG9yIHdpbGwgdGFrZSBvbiBsYXJnZXIgdmFsdWVzLg0KDQpMZXQncyBjYWxjdWxhdGUgVEFJIGluZGV4IHRvIGNoZWNrIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSB0YWJ1bGF0ZWQgZGF0YToNCg0KYGBge3IgdGFpLCBlY2hvPVRSVUV9DQp0YWJlbGthMiA8LSBjbGFzc0ludGVydmFscyhDcmVkaXRDYXJkJGluY29tZSwgbj03LCBzdHlsZT0iZml4ZWQiLCBmaXhlZEJyZWFrcz1zZXEoMCwxNCxieT0yKSkNCmplbmtzLnRlc3RzKHRhYmVsa2EyKQ0KYGBgDQoNCkFzIHdlIGNhbiBzZWUgLSBUQUkgaW5kZXguLi4NCg0KDQpXZSBjYW4gdXNlIGRpZmZlcmVudCByZWNpcGVzLi4uIChzdHlsZXMpOg0KDQoNCmBgYCB7cn0NCnRhYmVsa2EzPC1jbGFzc0ludGVydmFscyhDcmVkaXRDYXJkJGluY29tZSwgbj0xMCwgc3R5bGU9InNkIikNCnBsb3QodGFiZWxrYTMscGFsPWMoMToxMCkpDQpqZW5rcy50ZXN0cyh0YWJlbGthMykNCmBgYA0KDQpTdGlsbCwgdGhlIFRBSSBpbmRpY2F0b3IgaXMgbm90IHNhdGlzZmFjdG9yeS4gV2hhdCBzaG91bGQgd2UgY2hhbmdlIGluIHRoZSBmaW5hbCBmcmVxdWVuY3kgdGFibGUgZGVzaWduPw0KDQpgYGB7cn0NCmhpc3QoQ3JlZGl0Q2FyZCRpbmNvbWUpDQpgYGANCg0KDQojIyMgQ29udGludW91cyB2YXJpYWJsZXMNCg0KV2UgY2FuIGNhbGN1bGF0ZSB0aGUgYWJzb2x1dGUgYW5kIHJlbGF0aXZlIGZyZXF1ZW5jaWVzIG9mIGEgdmVjdG9yIHggd2l0aCB0aGUgZnVuY3Rpb24gJ0ZyZXEnIGZyb20gdGhlICpEZXNjVG9vbHMqIHBhY2thZ2VzLiBDb250aW51b3VzIChudW1lcmljKSB2YXJpYWJsZXMgd2lsbCBiZSBjdXQgdXNpbmcgdGhlIHNhbWUgbG9naWMgYXMgdXNlZCBieSB0aGUgZnVuY3Rpb24gaGlzdC4gQ2F0ZWdvcmljYWwgdmFyaWFibGVzIHdpbGwgYmUgYWdncmVnYXRlZCBieSB0YWJsZS4gVGhlIHJlc3VsdCB3aWxsIGNvbnRhaW4gc2luZ2xlIGFuZCBjdW11bGF0aXZlIGZyZXF1ZW5jaWVzIGZvciBib3RoLCBhYnNvbHV0ZSB2YWx1ZXMgYW5kIHBlcmNlbnRhZ2VzLiANCg0KYGBge3J9DQp0YWJlbGE0PC1GcmVxKENyZWRpdENhcmQkaW5jb21lLGJyZWFrcz1zZXEoMCwxNCxieT0yKSx1c2VOQT0iaWZhbnkiKQ0KDQp0YWJlbGE0ICU+JQ0KICBrYWJsZShjb2wubmFtZXMgPSBjKCJJbmNvbWVzIGluIGtVU0QiLCJGcmVxdWVuY3kiLCJQZXJjZW50YWdlICUiLCJDdW11bGF0aXZlIGZyZXF1ZW5jeSIsIkN1bXVsYXRpdmUgcGVyY2VudGFnZSAlIikpICU+JQ0KICBrYWJsZV9jbGFzc2ljKGZ1bGxfd2lkdGggPSBGLCBodG1sX2ZvbnQgPSAiQ2FtYnJpYSIpDQpgYGANCg0KQlRXOiB3aGF0IGFib3V0IFRBSSBvZiB0aGF0IHRhYmxlPy4uLg0KDQojIyMgQ2F0ZWdvcmljYWwgdmFyaWFibGVzDQoNCk5vdywgbGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNhdGVnb3JpY2FsIGRhdGEgYW5kIG1ha2Ugc29tZSB0YWJ1bGF0aW9ucy4gVGhlICoqeHRhYnMqKiBmdW5jdGlvbiB3b3JrcyBsaWtlIHRhYmxlIGV4Y2VwdCBpdCBjYW4gcHJvZHVjZSB0YWJsZXMgZnJvbSBmcmVxdWVuY2llcyB1c2luZyB0aGUgZm9ybXVsYSBpbnRlcmZhY2UuIA0KDQpMZXQncyBzYXkgd2Ugd2FudCB0byBzZWUgdGhlIHRhYmxlIHdpdGggZGF0YSBvbiBob3cgbWFueSBjYXJkIGFwcGxpY2F0aW9ucyB3YXMgYWNjZXB0ZWQgb3Igbm90Og0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCnh0YWJzKH4gY2FyZCwgZGF0YT1DcmVkaXRDYXJkKQ0KYGBgDQoNCldlIG1heSBlYXNpbHkgcHJvZHVjZSAqY3Jvc3MtdGFicyogKHN0YXR1cyB2cy4gRG9lcyB0aGUgaW5kaXZpZHVhbCBvd24gdGhlaXIgaG9tZT8pIGFzIHdlbGw6DQoNCmBgYHtyfQ0KY3Jvc3N0YWI8LXh0YWJzKH4gY2FyZCArIG93bmVyLCBkYXRhPUNyZWRpdENhcmQpDQpjcm9zc3RhYg0KYGBgDQoNCmFuZCB0cmFuc2Zvcm0gaXQgaW50byBwcmV0dHkgaHRtbCB0YWJsZSB3aXRoIHRoZSBrYWJsZSBmdW5jdGlvbjoNCg0KYGBge3J9DQpjcm9zc3RhYiAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGKSAlPiUNCiAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQsIGJvcmRlcl9yaWdodCA9IFQpICU+JQ0KICBjb2x1bW5fc3BlYygyLCBiYWNrZ3JvdW5kID0gInllbGxvdyIpDQpgYGANCg0KDQojIyBEYXRhIFZpc3VhbGl6YXRpb24NCg0KV2Ugd2lsbCBleHBsb3JlIHRoZSAqKiJnZ3Bsb3QyIioqIHBhY2thZ2Ugb2YgdGhlIHRpZHl2ZXJzZSBmb3IgZGF0YSB2aXN1YWxpemF0aW9uIHB1cnBvc2VzLiBUaGUgImdncGxvdDIiIHBhY2thZ2VzIGludm9sdmUgdGhlIHRoZSBmb2xsb3dpbmcgdGhyZWUgbWFuZGF0b3J5IGNvbXBvbmVudHM6DQoNCjEpIERhdGENCjIpIEFuIGFlc3RoZXRpYyBtYXBwaW5nDQozKSBHZW9tcyAoYWthIG9iamVjdHMpDQoNClRoZSBmb2xsb3dpbmcgY29tcG9uZW50cyBjYW4gYWxzbyBvcHRpb25hbGx5IGJlIGFkZGVkOg0KDQo0KSBTdGF0cyAoYWthIHRyYW5zZm9ybWF0aW9ucykNCjUpIFNjYWxlcw0KNikgRmFjZXRzDQo3KSBDb29yZGluYXRlIHN5c3RlbXMNCjgpIFBvc2l0aW9uIGFkanVzdG1lbnRzDQo5KSBUaGVtZXMNCg0KUGxlYXNlIG5vdGUgdGhhdCBjb2RlIGluIHRoaXMgdHV0b3JpYWwgd2FzIGFkYXB0ZWQgZnJvbSBDaGFwdGVycyAzIG9mIHRoZSBib29rICJSIGZvciBEYXRhIFNjaWVuY2UiIGJ5IEhhZGxleSBXaWNraGFtIGFuZCBHYXJyZXR0IEdyb2xlbXVuZC4gIA0KDQpUaGUgZnVsbCBib29rIGNhbiBiZSBmb3VuZCBhdDogaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8NCg0KQSBnb29kIGNoZWF0IHNoZWV0IGZvciBnZ3Bsb3QyIGZ1bmN0aW9ucyBjYW4gYmUgZm91bmQgYXQ6IGh0dHBzOi8vcnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDMvZ2dwbG90Mi1jaGVhdHNoZWV0LnBkZg0KDQoNCiMjIyBTY2F0dGVycGxvdHMNCg0KDQpMZXQncyBjcmVhdGUgYW4gZXh0cmVtZWx5IHNpbXBsZSBzY2F0dGVycGxvdC4gIA0KDQpXZSB3aWxsIHVzZSB0aGUgZnVuY3Rpb24gYGdncGxvdCgpYCB0byBkbyB0aGlzLiANCg0KVGhlIGZvcm1hdCBvZiBhbnkgZ2dwbG90IGdyYXBoIGlzIHRoaXMgZnVuY3Rpb24sIGZvbGxvd2VkIGJ5IGFub3RoZXIgZnVuY3Rpb24gdG8gYWRkIG9iamVjdHMuDQoNClRoZSBvYmplY3RzIG9uIGEgZ3JhcGggaW4gdGhlIGNhc2Ugb2YgYSBzY2F0dGVycGxvdCBhcmUgcG9pbnRzLiBUaGUgZnVuY3Rpb24gd2UgYWRkIHRvIGl0IGlzIGBnZW9tX3BvaW50YC4NCg0KVGhlc2UgZnVuY3Rpb25zIHJlbHkgb24gYSBmdW5jdGlvbiBvbiB0aGUgaW5zaWRlIGNhbGxlZCBgYWVzKClgLg0KDQpUaGUgZGF0YSBhbmQgYWVzdGhldGljIG1hcHBpbmcgY29tcG9uZW50cyBjYW4gYmUgYWRkZWQgdG8gZWl0aGVyIHRoZSBgZ2dwbG90KClgIG9yIGdlb20gZnVuY3Rpb25zLiAgDQoNCmBgYHtyIEdyYXBoIDF9DQpnZ3Bsb3QoZGF0YSA9IG1wZykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKQ0KYGBgIA0KDQo8YnI+DQoNClRoaXMgaXMgb25lIG9mIHRoZSBtb3N0IGJhc2ljIGdyYXBocyB0aGF0IG9uZSBjYW4gbWFrZSB1c2luZyB0aGUgZ2dwbG90MiBmcmFtZXdvcmsuIA0KDQpOZXh0LCBsZXQncyBhZGQgY29sb3IuDQoNCmBnZW9tX3BvaW50KClgIHVuZGVyc3RhbmRzIHRoZSBmb2xsb3dpbmcgYWVzdGhldGljczogeCwgeSwgYWxwaGEsIGNvbG9yLCBmaWxsLCBncm91cCwgc2hhcGUsIHNpemUsIGFuZCBzdHJva2UgKHNlZSBoZWxwIGRvY3VtZW50YXRpb24pLg0KDQpMZXQncyBtYXAgdGhlIGNvbG9yIGFyZ3VtZW50IHRvIHRoZSB2YXJpYWJsZSAiY2xhc3MiIGZyb20gbXBnLg0KDQpgYGB7ciBHcmFwaCAyfQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG9yID0gY2xhc3MpKQ0KYGBgDQoNCg0KVGhpcyBpcyBub3QgdGhlIG9ubHkgd2F5IHRvIGNvbG9yIG9iamVjdHMuICANCg0KSW5jbHVkaW5nIHRoZSBjb2xvciBhcmd1bWVudCBpbnNpZGUgb2YgdGhlIGBhZXMoKWAgZnVuY3Rpb24gY2FuIG1hcCBjb2xvcnMgdG8gYSBjaG9pY2Ugb2YgdmFyaWFibGUuIA0KDQpIb3dldmVyLCB3ZSBjYW4gc3BlY2lmeSBjb2xvcnMgbWFudWFsbHksIGJ5IHNwZWNpZnlpbmcgY29sb3Igb3V0c2lkZSBvZiB0aGUgYGFlcygpYCBmdW5jdGlvbi4gV2Ugd2lsbCBhbHNvIGlsbHVzdHJhdGUgdGhlICJzaXplIiBhcmd1bWVudC4NCg0KYGBge3IgR3JhcGggM30NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBzaXplID0gY2xhc3MpLCBjb2xvciA9ICJibHVlIikNCmBgYA0KDQojIyMgQmFycGxvdHMNCg0KTGFzdGx5LCBsZXQncyBleGFtaW5lIG90aGVyIG9iamVjdHMgdGhhdCB3ZSBjYW4gcGxvdCB1c2luZyBgZ2dwbG90KClgLiBXZSB3aWxsIGNyZWF0ZSBhIGJhciBjaGFydCB1c2luZyB0aGUgZnVuY3Rpb24gYGdlb21fYmFyKClgLiAgDQoNCmBgYHtyIEdyYXBoIDR9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21fYmFyKGFlcyh4ID0gY2xhc3MpKQ0KYGBgDQoNCjxicj4NCg0KV2l0aCB0aGUgYGdlb21fYmFyKClgIGZ1bmN0aW9uLCB3ZSBoYXZlIGEgZ3JlYXQgdXNlLWNhc2UgZm9yIGEgc3RhdCB0cmFuc2Zvcm1hdGlvbi4NCg0KVGhlIGZvbGxvd2luZyBjb2RlIGNhbiBiZSB1c2VkIHRvIGNvbnZlcnQgdGhlc2UgY291bnRzIHRvIHByb3BvcnRpb25zOg0KDQpgYGB7ciBHcmFwaCA1fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2JhcihhZXMoeCA9IGNsYXNzLCB5ID0gc3RhdChwcm9wKSwgZ3JvdXAgPSAxKSkNCmBgYA0KDQojIyMgSGlzdG9ncmFtcw0KDQpOZXh0LCBsZXQncyBjcmVhdGUgYSBoaXN0b2dyYW0gd2l0aCB0aGUgYGdlb21faGlzdG9ncmFtKClgIGZ1bmN0aW9uLg0KDQpgYGB7ciBHcmFwaCA2fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGh3eSkpDQpgYGANCg0KVGhlIGBnZW9tX2hpc3RvZ3JhbSgpYCBmdW5jdGlvbiBhY2NlcHRzIHRoZSBhcmd1bWVudCAiYmlud2lkdGgiLCBhbmQgaGFzIHR3byBrZXkgYXJndW1lbnRzIGZvciBjb2xvcjogZmlsbCAodGhpcyBjb250cm9scyB0aGUgb3ZlcmFsbCBjb2xvciksIGFuZCBjb2xvciAodGhpcyBjb250cm9scyB0aGUgYm9yZGVyKS4gIA0KDQpMZXQncyBmaWxsIGFsbCB0aGVzZSBpbjoNCg0KYGBge3IgR3JhcGggN30NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBod3kpLCBiaW53aWR0aCA9IDUsIGZpbGwgPSAibmF2eSIsIGNvbG9yID0gImdvbGQiKQ0KYGBgDQoNCjxicj4NCg0KYGdlb21faGlzdG9ncmFtKClgIHByb3ZpZGVzIGEgZ3JlYXQgZXhhbXBsZSB0byBtb2RpZnkgdGhlIHNjYWxlLg0KDQpOb3RpY2UgaW4gdGhpcyBleGFtcGxlIHRoYXQgdGhlIGF4aXMgaXMgYXV0b21hdGljYWxseSBicm9rZW4gdXAgYnkgdW5pdHMgb2YgMTAsIGFuZCBkb2VzIG5vdCBiZWdpbiBhdCAwLg0KDQpXZSBjYW4gbW9kaWZ5IHRoaXMgd2l0aCB0aGUgZnVuY3Rpb24gYHNjYWxlX3hfY29udGludW91cygpYCwgYXMgd2VsbCBhcyB0aGUgeS1heGlzIHdpdGggdGhlIGZ1bmN0aW9uIGBzY2FsZV95X2NvbnRpbnVvdXMoKWAuICANCg0KVGhlcmUgYXJlIHRocmVlIGtleSBhcmd1bWVudHMgd2Ugd2lsbCBmZWVkIHRoaXMgZnVuY3Rpb246ICJicmVha3MiLCAibGltaXRzIiwgYW5kICJleHBhbmQiLg0KDQoiYnJlYWtzIiB3aWxsIGRlZmluZSB0aGUgYnJlYWtzIG9uIHRoZSBheGlzLiANCg0KImxpbWl0cyIgd2lsbCBkZWZpbmUgdGhlIGJlZ2lubmluZyBhbmQgZW5kIG9mIHRoZSBheGlzLCBhbmQgdGhlICJleHBhbmQiIGFyZ3VtZW50IGNhbiBiZSB1c2VkIHRvIHN0YXJ0IHRoZSBheGVzIGF0IDAgYnkgdXNpbmcgImV4cGFuZCA9IGMoMCwwKSIuIA0KDQpgYGB7ciBHcmFwaCA4fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGh3eSksIGJpbndpZHRoID0gNSwgZmlsbCA9ICJuYXZ5IiwgY29sb3IgPSAiZ29sZCIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA0NSwgNSksIGxpbWl0cyA9IGMoMCwgNTApLCBleHBhbmQgPSBjKDAsMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA5MCwgMTApLCBsaW1pdHMgPSBjKDAsIDkwKSwgZXhwYW5kID0gYygwLDApKQ0KYGBgDQoNCiMjIyBCb3hwbG90cw0KDQpOZXh0LCB3ZSB3aWxsIGNyZWF0ZSBib3hwbG90cy4NCg0KYGBge3IgR3JhcGggOX0NCnAgPC0gZ2dwbG90KG1wZykgKw0KICBnZW9tX2JveHBsb3QoYWVzKHggPSBjbGFzcywgeSA9IGN0eSwgZmlsbCA9IGNsYXNzKSkNCnANCmBgYA0KDQojIyMgRmFjZXRzDQoNCkZhY2V0aW5nIGdlbmVyYXRlcyBzbWFsbCBtdWx0aXBsZXMgZWFjaCBzaG93aW5nIGEgZGlmZmVyZW50IHN1YnNldCBvZiB0aGUgZGF0YS4gU21hbGwgbXVsdGlwbGVzIGFyZSBhIHBvd2VyZnVsIHRvb2wgZm9yIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXM6IHlvdSBjYW4gcmFwaWRseSBjb21wYXJlIHBhdHRlcm5zIGluIGRpZmZlcmVudCBwYXJ0cyBvZiB0aGUgZGF0YSBhbmQgc2VlIHdoZXRoZXIgdGhleSBhcmUgdGhlIHNhbWUgb3IgZGlmZmVyZW50LiANCg0KUmVhZCBtb3JlIGFib3V0IGZhY2V0cyBbaGVyZV0oaHR0cHM6Ly9nZ3Bsb3QyLWJvb2sub3JnL2ZhY2V0Lmh0bWwpLg0KDQpOb3RpY2UgaW4gdGhpcyBkb2N1bWVudCB0aGUgdXNlIG9mIHRoZSBmaWcuaGVpZ2h0IGFuZCBmaWcud2lkdGggb3B0aW9ucy4NCg0KS2V5IGFyZ3VtZW50cyB0byBgZmFjZXRfd3JhcCgpYCBhcmUgImZhY2V0cyIsICJucm93IiwgYW5kICJuY29sIi4NCg0KYGBge3IgR3JhcGggMTAsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMn0NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gY2xhc3MsIHkgPSBjdHksIGZpbGwgPSBjbGFzcykpICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB+Y3lsLCBucm93ID0gMiwgbmNvbCA9IDIpDQpgYGANCg0KIyMjIENvb3JkaW5hdGVzDQoNCk90aGVyIGNvb3JkaW5hdGUgc3lzdGVtcyBjYW4gYmUgYXBwbGllZCB0byBncmFwaHMgY3JlYXRlZCBmcm9tIGdncGxvdDIuICANCg0KT25lIGV4YW1wbGUgaXMgYGNvb3JkX3BvbGFyKClgLCB3aGljaCB1c2VzIHBvbGFyIGNvb3JkaW5hdGVzLiBNb3N0IG9mIHRoZXNlIGFyZSBxdWl0ZSByYXJlLiBQcm9iYWJseSB0aGUgbW9zdCBjb21tb24gb25lIGlzIGBjb29yZF9mbGlwKClgLCB3aGljaCB3aWxsIGZsaXAgdGhlIFggYW5kIFkgYXhlcy4gTGV0J3MgYWxzbyBpbGx1c3RyYXRlIHRoZSBgbGFicygpYCBmdW5jdGlvbiwgd2hpY2ggY2FuIGJlIHVzZWQgdG8gY2hhbmdlIGxhYmVscy4NCg0KYGBge3IgR3JhcGggMTF9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21fYmFyKGFlcyh4ID0gY2xhc3MsIGZpbGwgPSBmYWN0b3IoY3lsKSkpICsNCiAgbGFicyh0aXRsZSA9ICJDeWxpbmRlcnMgYnkgQ2xhc3MiLCBmaWxsID0gImN5bGluZGVycyIpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KPGJyPg0KDQpUaGVzZSBiYXJzIGFyZSBzdGFja2VkIG9uIHRvcCBvZiBlYWNoIG9mIG90aGVyLCBkdWUgdG8gdGhlICJjeWwiIHZhcmlhYmxlIGJlaW5nIG1hcHBlZCB0byB0aGUgImZpbGwiIGFyZ3VtZW50LiBUaGVyZSBhcmUgdmFyaW91cyBwb3NpdGlvbiBhZGp1c3RtZW50cyB0aGF0IGNhbiBiZSB1c2VkLiBBZ2FpbiwgbW9zdCBvZiB0aGVzZSBhcmUgbm90IHZlcnkgY29tbW9uLCBidXQgYSBjb21tb24gb25lIGlzIHRoZSBhcmd1bWVudCAicG9zaXRpb24gPSAnZG9kZ2UnIiwgd2hpY2ggd2lsbCBwdXQgaXRlbXMgc2lkZS1ieS1zaWRlLiAgIA0KDQpTZWUgdGhpcyBleGFtcGxlOg0KDQpgYGB7ciBHcmFwaCAxMn0NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9iYXIoYWVzKHggPSBjbGFzcywgZmlsbCA9IGZhY3RvcihjeWwpKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIGxhYnModGl0bGUgPSAiQ3lsaW5kZXJzIGJ5IENsYXNzIiwgZmlsbCA9ICJjeWxpbmRlcnMiKSArIA0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQojIyMgVGhlbWVzDQoNCkxhc3RseSwgd2UgY2FuIGFsdGVyIHRoZSAidGhlbWUiLCBvciB0aGUgb3ZlcmFsbCBhcHBlYXJhbmNlIG9mIG91ciBwbG90LiAgDQoNCkkgcmVjb21tZW5kIHVzaW5nIHRoZSBnZ1RoZW1lQXNzaXN0IHBhY2thZ2UsIGJlY2F1c2UgdGhpcyB3aWxsIG1ha2UgdGhpcyBpbmNyZWRpYmx5IGVhc3ksIHdpdGggYW4gaW50ZXJmYWNlIHRoYXQgd2lsbCBhdXRvbWF0aWNhbGx5IGdlbmVyYXRlIHJlcHJvZHVjaWJsZSBjb2RlLg0KDQpUaGlzIGNhbiBiZSB1c2VkIGJ5IGhpZ2hsaWdodGluZyBhIGdncGxvdDIgb2JqZWN0LCBhbmQgbmF2aWdhdGluZyB0byBBZGRpbnMgPiBnZ3Bsb3QgVGhlbWUgQXNzaXN0YW50Lg0KDQpXZSdsbCBtYWtlIHRoZSBmb2xsb3dpbmcgY2hhbmdlczogZWxpbWluYXRpbmcgdGhlIHBhbmVsIGdyaWQgbGluZXMsIGVsaW1pbmF0aW5nIGF4aXMgdGlja3MsIGFkZGluZyBhIHRpdGxlIGNhbGxlZCAiQm94cGxvdCBFeGFtcGxlIiwgbWFraW5nIGl0IGJpZ2dlciBhbmQgcHV0dGluZyBpdCBpbiBib2xkLCBhbmQgYWRqdXN0aW5nIGl0IHRvIHRoZSBjZW50ZXIuDQoNCmBgYHtyIEdyYXBoIDEzfQ0KIyBwDQpwICsgdGhlbWUoYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShsaW5ldHlwZSA9ICJibGFuayIpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUobGluZXR5cGUgPSAiYmxhbmsiKSwNCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKGxpbmV0eXBlID0gImJsYW5rIiksDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsDQogICAgICAgIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkgK2xhYnModGl0bGUgPSAiQm94cGxvdCBFeGFtcGxlIikNCmBgYA0KDQo8YnI+DQoNClRoZXJlIGFyZSBtYW55IG1vcmUgZXhhbXBsZXMgb2YgdGhpbmdzIHRoYXQgY2FuIGJlIGRvbmUgd2l0aCBnZ3Bsb3QyLiAgDQoNCkl0IGlzIGFuIGFtYXppbmdseSBwb3dlcmZ1bCBhbmQgZmxleGlibGUgcGFja2FnZSwgYW5kIGl0IGlzIHdvcnRoIGdldHRpbmcgYWNxdWFpbnRlZCB3aXRoIHRoZSBjaGVhdCBzaGVldC4NCg0KIyMgRXhlcmNpc2UgMS4gDQoNClVzaW5nIGRhdGEgb24gY3JlZGl0IGNhcmQgYXBwbGljYXRpb25zJyBzdGF0dXMgcGxlYXNlIHByZXNlbnQgdGhlIGZyZXF1ZW5jeSB0YWJsZSB3aXRoIHRoZSBuaWNlLCBrYWJsZSBmb3JtYXQgZm9yIGF2ZXJhZ2UgbW9udGhseSBjcmVkaXQgY2FyZCBleHBlbmRpdHVyZXMgb2YgYXBwbGljYW50cy4NCg0KYGBge3IgZXgxfQ0KDQpgYGANCg0KDQojIyBFeGVyY2lzZSAyLg0KDQpUaGUgZGF0YSBjb21lcyBmcm9tIFtodHRwczovL2ZsaXhnZW0uY29tL10oaHR0cHM6Ly9mbGl4Z2VtLmNvbS8pIChkYXRhc2V0IHZlcnNpb24gYXMgb2YgTWFyY2ggMTIsIDIwMjEpLiBUaGUgZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiBvbiA5NDI1IG1vdmllcyBhbmQgc2VyaWVzIGF2YWlsYWJsZSBvbiBOZXRsaXguDQoNCmBgYHtyIGV4MiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL25ldGZsaXgtZGF0YXNldC5jc3Y/cmF3PXRydWUiLCBkZXN0ZmlsZSA9ImRhbmUuY3N2Iixtb2RlPSJ3YiIpDQpteWRhdGE8LXJlYWQuY3N2KGZpbGU9ImRhbmUuY3N2IixlbmNvZGluZyA9IlVURi04IixoZWFkZXI9VFJVRSxzZXAgPSAiLCIpDQphdHRhY2gobXlkYXRhKQ0KYGBgDQoNCkFuc3dlciB3aXRoIHRoZSBtb3N0IGFwcHJvcHJpYXRlIGRhdGEgdmlzdWFsaXphdGlvbiBmb3IgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6DQoNCjEuIFdoYXQgaXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBJbWRiIHNjb3JlcyBmb3IgUG9saXNoIG1vdmllcyBhbmQgbW92aWUtc2VyaWVzPw0KDQpgYGB7ciBleDJfMX0NCg0KYGBgDQoNCjIuIFdoYXQgaXMgdGhlIGRlbnNpdHkgZnVuY3Rpb24gb2YgSW1kYiBzY29yZXMgZm9yIFBvbGlzaCBtb3ZpZXMgYW5kIG1vdmllLXNlcmllcz8NCg0KYGBge3IgZXgyXzJ9DQoNCmBgYA0KDQozLiBXaGF0IGFyZSB0aGUgbW9zdCBwb3B1bGFyIGxhbmd1YWdlcyBhdmFpbGFibGUgb24gTmV0ZmxpeD8NCg0KYGBge3IgZXgyXzN9DQoNCmBgYA0KDQpGb3IgZXh0cmEgY3JlZGl0czoNCg0KKkV4dHJhIGNoYWxsZW5nZSAxLio6IENyZWF0ZSBhIGNoYXJ0IHNob3dpbmcgYWN0b3JzIHN0YXJyaW5nIGluIHRoZSBtb3N0IHBvcHVsYXIgcHJvZHVjdGlvbnMuDQoNCmBgYHtyIGNoYWxsZW5nZTF9DQoNCmBgYA0KDQoNCipFeHRyYSBjaGFsbGVuZ2UgMi4qOiBGb3IgbW92aWVzIGFuZCBzZXJpZXMsIGNyZWF0ZSByYXRpbmcgY2hhcnRzIGZyb20gdGhlIHZhcmlvdXMgcG9ydGFscyAoSGlkZGVuIEdlbSwgSU1EYiwgUm90dGVuIFRvbWF0b2VzLCBNZXRhY3JpdGljKS4gSGludDogaXQncyBhIGdvb2QgaWRlYSB0byByZXNoYXBlIHRoZSBkYXRhIHRvICpsb25nKiBmb3JtYXQuDQoNCmBgYHtyIGNoYWxsZW5nZTJ9DQoNCmBgYA0KDQoqRXh0cmEgY2hhbGxlbmdlIDMuKjogV2hpY2ggZmlsbSBzdHVkaW9zIHByb2R1Y2UgdGhlIG1vc3QgYW5kIGhvdyBoYXMgdGhpcyBjaGFuZ2VkIG92ZXIgdGhlIHllYXJzPw0KDQpgYGB7ciBjaGFsbGVuZ2UzfQ0KDQpgYGANCg0KDQo=