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:

  1. Data
  2. An aesthetic mapping
  3. Geoms (aka objects)

The following components can also optionally be added:

  1. Stats (aka transformations)
  2. Scales
  3. Facets
  4. Coordinate systems
  5. Position adjustments
  6. 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.

Exercise 1.

Using data on credit card applications’ status please present the frequency table with the nice, kable format for average monthly credit card expenditures of applicants.

Exercise 2.

The data comes from https://flixgem.com/ (dataset version as of March 12, 2021). The data contains information on 9425 movies and series available on Netlix.

Answer with the most appropriate data visualization for the following questions:

  1. What is the distribution of Imdb scores for Polish movies and movie-series?

  2. What is the density function of Imdb scores for Polish movies and movie-series?

  3. What are the most popular languages available on Netflix?

For extra credits:

Extra challenge 1.: Create a chart showing actors starring in the most popular productions.

Extra challenge 2.: For movies and series, create rating charts from the various portals (Hidden Gem, IMDb, Rotten Tomatoes, Metacritic). Hint: it’s a good idea to reshape the data to long format.

Extra challenge 3.: Which film studios produce the most and how has this changed over the years?

LS0tDQp0aXRsZTogIkRhdGEgVmlzdWFsaXphdGlvbiINCmF1dGhvcjogIllvdXIgTmFtZSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIGZvbnRzaXplOiA4cHQNCiAgICB0b2M6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICBkZl9wcmludDogZGVmYXVsdA0KICAgIHRvY19kZXB0aDogNQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQoNCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQojIyBHbG9iYWwgb3B0aW9ucw0Kb3B0aW9ucyhxd3JhcHMyX21hcmt1cCA9ICJtYXJrZG93biIpDQpsaWJyYXJ5KHF3cmFwczIpDQpsaWJyYXJ5KGFyc2VuYWwpDQpsaWJyYXJ5KGUxMDcxKQ0KbGlicmFyeShoYXZlbikNCmxpYnJhcnkocGFwZVIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHN1bW1hcnl0b29scykNCmxpYnJhcnkoY2xhc3NJbnQpDQpsaWJyYXJ5KHBhc3RlY3MpDQpsaWJyYXJ5KHJlcG9ydHRvb2xzKQ0KbGlicmFyeShkZXNjdGFibGUpDQpsaWJyYXJ5KERlc2NUb29scykNCmxpYnJhcnkoZnJlcXVlbmN5KQ0KbGlicmFyeShnZ3B1YnIpDQpsaWJyYXJ5KGRsb29rcikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodGlueXRleCkNCmxpYnJhcnkoQUVSKQ0KbGlicmFyeShnZ1RoZW1lQXNzaXN0KQ0KbGlicmFyeShTbWFydGVyUG9sYW5kKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCg0KIyMgS2FibGUgcGFja2FnZQ0KDQpJbiBhIHByZXZpb3VzIHdlZWtzLCB3ZSBzYXcgUiBNYXJrZG93biBpbiBhY3Rpb24sIHdoZXJlIG11bHRpcGxlIHRoaW5ncyBjYW4gYmUgY3JlYXRlZCBpbiBvbmUgbG9jYXRpb246IGNvZGUsIGNvbW1lbnRhcnksIGFuZCBvdXRwdXQuIA0KDQpJbiB0aGlzIGNoYXB0ZXIgd2Ugd2lsbCBleHBsb3JlIHBhY2thZ2Ugd2hpY2ggd2lsbCBmYWNpbGl0YXRlIHRoZSBjcmVhdGlvbiBvZiBwcmVzZW50YXRpb24td29ydGh5IHRhYmxlczogImthYmxlRXh0cmEiLg0KDQpMZXQncyB3b3JrIHdpdGggdGhlIGNyb3NzLXNlY3Rpb25hbCBkYXRhIG9uIHRoZSBjcmVkaXQgaGlzdG9yeSBmb3IgYSBzYW1wbGUgb2YgYXBwbGljYW50cyBmb3IgYSB0eXBlIG9mIGNyZWRpdCBjYXJkLg0KDQpgYGB7cn0NCmRhdGEoQ3JlZGl0Q2FyZCkNCmNhcmRIZWFkIDwtIGhlYWQoQ3JlZGl0Q2FyZCkNCmNhcmRIZWFkDQpgYGANCg0KPGJyPg0KDQpgYGB7ciBUaWJibGUgd2l0aCAia2FibGUifQ0KY2FyZEhlYWQgJT4lDQogIGtibCgpDQpgYGANCg0KPGJyPg0KDQpMZXQncyB0d2VhayB0aGUgYXBwZWFyYW5jZSBvZiB0aGlzIHdpdGggdGhlICJhbGlnbiIgYW5kIHRoZSAiY2FwdGlvbiIgYXJndW1lbnRzLiAgDQoNClRoZSBhbGlnbiBhcmd1bWVudCB0YWtlcyBhIGNoYXJhY3RlciB2ZWN0b3Igd2l0aCBsZXR0ZXJzICJsIiwgImMiLCBvciAiciIgLSBzcGVjaWZ5aW5nIHdoZXJlIHlvdSB3YW50IHRoZSBjb2x1bW5zIHRvIGJlIGFsaWduZWQuDQoNClRoZSBjYXB0aW9uIGFyZ3VtZW50IGdpdmVzIGEgY2FwdGlvbiB0byB0aGUgdGFibGUuDQoNCjxicj4NCg0KYGBge3IgQWxpZ25lZCBrYWJsZX0NCmJhc2UgPC0gY2FyZEhlYWQgJT4lDQogIGtibChhbGlnbiA9IGMocmVwKCJjIiwgNyksIHJlcCgiciIsIDUpKSwgY2FwdGlvbiA9ICJrYWJsZSBleGFtcGxlIHdpdGggY2FyZCBkYXRhIikNCmJhc2UNCmBgYA0KDQo8YnI+DQoNCkEga2V5IGZ1bmN0aW9uLCB3aGVyZSB3ZSBjYW4gZW5qb3kgbXVjaCBvZiB0aGUgY29uZmlndXJhdGlvbiBmb3IgdGhlIHRhYmxlLCBpcyB2aWEgYGthYmxlX3N0eWxpbmcoKWAuICAgDQoNCldlIGhhdmUgb3B0aW9ucyAiYm9vdHN0cmFwX29wdGlvbnMiIG9yICJsYXRleF9vcHRpb25zIiwgd2hlcmUgdGhlIGxhdHRlciByZXF1aXJlcyB0aGUgdXNlIG9mIHRoZSBwYWNrYWdlICJ0aW55dGV4IiBhbmQgYSBsb2NhbCBpbnN0YWxsYXRpb24gb2YgTGFUZVguICANCg0KUG9zc2libGUgb3B0aW9ucyBmb3IgImJvb3RzdHJhcF9vcHRpb25zIiBpbmNsdWRlICdiYXNpYycsICdzdHJpcGVkJywgJ2JvcmRlcmVkJywgJ2hvdmVyJywgJ2NvbmRlbnNlZCcsICdyZXNwb25zaXZlJywgYW5kIG5vbmUuICANCg0KUG9zc2libGUgZm9yICJsYXRleF9vcHRpb25zIiBpbmNsdWRlICdiYXNpYycsICdzdHJpcGVkJywgJ2hvbGRfcG9zaXRpb24nLCAnSE9MRF9wb3NpdGlvbicsICdzY2FsZV9kb3duJywgYW5kICdyZXBlYXRfaGVhZGVyJy4NCg0KPGJyPg0KDQoNCmBgYHtyIEJvb3RzdHJhcCAtIHN0cmlwZWR9DQpiYXNlICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiKQ0KYGBgDQoNCjxicj4NCg0KTmV4dCwgd2UgY2FuIGN1c3RvbWl6ZSB0aGUgbG9vayBhbmQgZmVlbCBvZiBwYXJ0aWN1bGFyIHJvd3MgYW5kIGNvbHVtbnMuICAgDQoNCkxldCdzIHNlZSBhbiBleGFtcGxlIGhlcmUsIHdoZXJlIHdlIG1ha2UgdGhlIGxhc3QgdGhyZWUgcm93cyBibHVlLg0KDQpgYGB7ciBDb2x1bW4gYW5kIHJvdyBjdXN0b21pemluZ30NCmJhc2UgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAiYm9yZGVyZWQiKSAlPiUNCiAgY29sdW1uX3NwZWMoODoxMiwgYm9sZCA9IFQpICU+JQ0KICByb3dfc3BlYyg0OjYsIGl0YWxpYyA9IFQsIGNvbG9yID0gImdvbGQiLCBiYWNrZ3JvdW5kID0gImJsdWUiKQ0KYGBgDQoNCjxicj4NCg0KDQpXZSBjYW4gYWxzbyBjcmVhdGUgZ3JvdXBzIGZvciBvdXIgY29sdW1ucy4NCg0KPGJyPg0KDQpgYGB7cn0NCmJhc2UgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAiYm9yZGVyZWQiKSAlPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCJHcm91cCAxIiA9IDQsICJHcm91cCAyIiA9IDIsICJHcm91cCAzIiA9IDYpKQ0KYGBgDQoNCjxicj4NCg0KDQojIyBEYXRhIEFnZ3JlZ2F0aW9uDQoNCkluIHRoZSBmaXJzdCBzdGFnZSBvZiBvdXIgYW5hbHlzaXMgd2UgYXJlIGdvaW5nIHRvIGdyb3VwIG91ciBkYXRhIGluIHRoZSBmb3JtIG9mIHRoZSBzaW1wbGUgZnJlcXVlbmN5IHRhYmxlLg0KDQpGaXJzdCwgbGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBpbmNvbWUgaW4gb3VyIHNhbXBsZSBhbmQgdmVyaWZ5IHRoZSB0YWJ1bGFyIGFjY3VyYWN5IHVzaW5nIFRBSSBtZWFzdXJlOg0KDQpgYGB7ciB0YWJsZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9DQpvcHRpb25zKHNjaXBlbj05OTkpDQoNCmxpbWl0czwtIGN1dChDcmVkaXRDYXJkJGluY29tZSxzZXEoMCwxNCxieT0yKSkNCnRhYmVsa2EgPC0gZnJlcShsaW1pdHMsdHlwZT0iaHRtbCIpDQp0YWJlbGthDQpgYGANCg0KV2l0aG91dCAna2FibGUnIHN0eWxpbmcgaXQncyBxdWl0ZSB1Z2x5IHJpZ2h0PyA7LSkNCg0KDQojIyMgVGFidWxhciBhY2N1cmFjeQ0KDQpBbiBpbmRleCBvZiB0YWJ1bGFyIGFjY3VyYWN5IFRBSSwgZGVzY3JpYmVkIGJ5IEplbmtzIGFuZCBDYXNzcGFsIGluIDE5NzEgaXMgdG8gb3B0aW1pemUgdGhlIGNsYXNzIGRpc3RyaWJ1dGlvbiB1c2VkIGluIGEgY2FydG9ncmFtcy9mcmVxdWVuY3kgdGFibGVzIGV0Yy4gDQoNClRoZSBUQUkgaW5kaWNhdG9yIHRha2VzIHZhbHVlcyBpbiB0aGUgcmFuZ2UgKDA7MSkuIFRoZSBudW1lcmF0b3Igb2YgdGhlIGV4cHJlc3Npb24gaXMgdGhlIHN1bSBvZiB0aGUgYWJzb2x1dGUgZGV2aWF0aW9ucyBvZiB0aGUgdmFsdWVzIGNsYXNzaWZpZWQgaW50byBjbGFzc2VzLCBhbmQgdGhlIGRlbm9taW5hdG9yIGlzIHRoZSBzdW0gb2YgdGhlIGFic29sdXRlIGRldmlhdGlvbnMgb2YgdGhlIGVudGlyZSBjbGFzc2lmaWVkIHNldC4gDQoNClRoZSBiZXR0ZXIgdGhlIGNsYXNzIGRpdmlzaW9uIHJlZmxlY3RzIHRoZSBuYXR1cmUgb2YgdGhlIGRhdGEsIHRoZSBsYXJnZXIgdGhlIGluZGljYXRvciB3aWxsIGJlLiBBcyB0aGUgbnVtYmVyIG9mIGNsYXNzZXMgaW5jcmVhc2VzLCB0aGUgaW5kaWNhdG9yIHdpbGwgdGFrZSBvbiBsYXJnZXIgdmFsdWVzLg0KDQpMZXQncyBjYWxjdWxhdGUgVEFJIGluZGV4IHRvIGNoZWNrIHRoZSBwcm9wZXJ0aWVzIG9mIHRoZSB0YWJ1bGF0ZWQgZGF0YToNCg0KYGBge3IgdGFpLCBlY2hvPVRSVUV9DQp0YWJlbGthMiA8LSBjbGFzc0ludGVydmFscyhDcmVkaXRDYXJkJGluY29tZSwgbj03LCBzdHlsZT0iZml4ZWQiLCBmaXhlZEJyZWFrcz1zZXEoMCwxNCxieT0yKSkNCmplbmtzLnRlc3RzKHRhYmVsa2EyKQ0KYGBgDQoNCkFzIHdlIGNhbiBzZWUgLSBUQUkgaW5kZXguLi4NCg0KDQpXZSBjYW4gdXNlIGRpZmZlcmVudCByZWNpcGVzLi4uIChzdHlsZXMpOg0KDQoNCmBgYCB7cn0NCnRhYmVsa2EzPC1jbGFzc0ludGVydmFscyhDcmVkaXRDYXJkJGluY29tZSwgbj0xMCwgc3R5bGU9InNkIikNCnBsb3QodGFiZWxrYTMscGFsPWMoMToxMCkpDQpqZW5rcy50ZXN0cyh0YWJlbGthMykNCmBgYA0KDQpTdGlsbCwgdGhlIFRBSSBpbmRpY2F0b3IgaXMgbm90IHNhdGlzZmFjdG9yeS4gV2hhdCBzaG91bGQgd2UgY2hhbmdlIGluIHRoZSBmaW5hbCBmcmVxdWVuY3kgdGFibGUgZGVzaWduPw0KDQpgYGB7cn0NCmhpc3QoQ3JlZGl0Q2FyZCRpbmNvbWUpDQpgYGANCg0KDQojIyMgQ29udGludW91cyB2YXJpYWJsZXMNCg0KV2UgY2FuIGNhbGN1bGF0ZSB0aGUgYWJzb2x1dGUgYW5kIHJlbGF0aXZlIGZyZXF1ZW5jaWVzIG9mIGEgdmVjdG9yIHggd2l0aCB0aGUgZnVuY3Rpb24gJ0ZyZXEnIGZyb20gdGhlICpEZXNjVG9vbHMqIHBhY2thZ2VzLiBDb250aW51b3VzIChudW1lcmljKSB2YXJpYWJsZXMgd2lsbCBiZSBjdXQgdXNpbmcgdGhlIHNhbWUgbG9naWMgYXMgdXNlZCBieSB0aGUgZnVuY3Rpb24gaGlzdC4gQ2F0ZWdvcmljYWwgdmFyaWFibGVzIHdpbGwgYmUgYWdncmVnYXRlZCBieSB0YWJsZS4gVGhlIHJlc3VsdCB3aWxsIGNvbnRhaW4gc2luZ2xlIGFuZCBjdW11bGF0aXZlIGZyZXF1ZW5jaWVzIGZvciBib3RoLCBhYnNvbHV0ZSB2YWx1ZXMgYW5kIHBlcmNlbnRhZ2VzLiANCg0KYGBge3J9DQp0YWJlbGE0PC1GcmVxKENyZWRpdENhcmQkaW5jb21lLGJyZWFrcz1zZXEoMCwxNCxieT0yKSx1c2VOQT0iaWZhbnkiKQ0KDQp0YWJlbGE0ICU+JQ0KICBrYWJsZShjb2wubmFtZXMgPSBjKCJJbmNvbWVzIGluIGtVU0QiLCJGcmVxdWVuY3kiLCJQZXJjZW50YWdlICUiLCJDdW11bGF0aXZlIGZyZXF1ZW5jeSIsIkN1bXVsYXRpdmUgcGVyY2VudGFnZSAlIikpICU+JQ0KICBrYWJsZV9jbGFzc2ljKGZ1bGxfd2lkdGggPSBGLCBodG1sX2ZvbnQgPSAiQ2FtYnJpYSIpDQpgYGANCg0KQlRXOiB3aGF0IGFib3V0IFRBSSBvZiB0aGF0IHRhYmxlPy4uLg0KDQojIyMgQ2F0ZWdvcmljYWwgdmFyaWFibGVzDQoNCk5vdywgbGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNhdGVnb3JpY2FsIGRhdGEgYW5kIG1ha2Ugc29tZSB0YWJ1bGF0aW9ucy4gVGhlICoqeHRhYnMqKiBmdW5jdGlvbiB3b3JrcyBsaWtlIHRhYmxlIGV4Y2VwdCBpdCBjYW4gcHJvZHVjZSB0YWJsZXMgZnJvbSBmcmVxdWVuY2llcyB1c2luZyB0aGUgZm9ybXVsYSBpbnRlcmZhY2UuIA0KDQpMZXQncyBzYXkgd2Ugd2FudCB0byBzZWUgdGhlIHRhYmxlIHdpdGggZGF0YSBvbiBob3cgbWFueSBjYXJkIGFwcGxpY2F0aW9ucyB3YXMgYWNjZXB0ZWQgb3Igbm90Og0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCnh0YWJzKH4gY2FyZCwgZGF0YT1DcmVkaXRDYXJkKQ0KYGBgDQoNCldlIG1heSBlYXNpbHkgcHJvZHVjZSAqY3Jvc3MtdGFicyogKHN0YXR1cyB2cy4gRG9lcyB0aGUgaW5kaXZpZHVhbCBvd24gdGhlaXIgaG9tZT8pIGFzIHdlbGw6DQoNCmBgYHtyfQ0KY3Jvc3N0YWI8LXh0YWJzKH4gY2FyZCArIG93bmVyLCBkYXRhPUNyZWRpdENhcmQpDQpjcm9zc3RhYg0KYGBgDQoNCmFuZCB0cmFuc2Zvcm0gaXQgaW50byBwcmV0dHkgaHRtbCB0YWJsZSB3aXRoIHRoZSBrYWJsZSBmdW5jdGlvbjoNCg0KYGBge3J9DQpjcm9zc3RhYiAlPiUgDQogIGtibCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGKSAlPiUNCiAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQsIGJvcmRlcl9yaWdodCA9IFQpICU+JQ0KICBjb2x1bW5fc3BlYygyLCBiYWNrZ3JvdW5kID0gInllbGxvdyIpDQpgYGANCg0KDQojIyBEYXRhIFZpc3VhbGl6YXRpb24NCg0KV2Ugd2lsbCBleHBsb3JlIHRoZSAqKiJnZ3Bsb3QyIioqIHBhY2thZ2Ugb2YgdGhlIHRpZHl2ZXJzZSBmb3IgZGF0YSB2aXN1YWxpemF0aW9uIHB1cnBvc2VzLiBUaGUgImdncGxvdDIiIHBhY2thZ2VzIGludm9sdmUgdGhlIHRoZSBmb2xsb3dpbmcgdGhyZWUgbWFuZGF0b3J5IGNvbXBvbmVudHM6DQoNCjEpIERhdGENCjIpIEFuIGFlc3RoZXRpYyBtYXBwaW5nDQozKSBHZW9tcyAoYWthIG9iamVjdHMpDQoNClRoZSBmb2xsb3dpbmcgY29tcG9uZW50cyBjYW4gYWxzbyBvcHRpb25hbGx5IGJlIGFkZGVkOg0KDQo0KSBTdGF0cyAoYWthIHRyYW5zZm9ybWF0aW9ucykNCjUpIFNjYWxlcw0KNikgRmFjZXRzDQo3KSBDb29yZGluYXRlIHN5c3RlbXMNCjgpIFBvc2l0aW9uIGFkanVzdG1lbnRzDQo5KSBUaGVtZXMNCg0KUGxlYXNlIG5vdGUgdGhhdCBjb2RlIGluIHRoaXMgdHV0b3JpYWwgd2FzIGFkYXB0ZWQgZnJvbSBDaGFwdGVycyAzIG9mIHRoZSBib29rICJSIGZvciBEYXRhIFNjaWVuY2UiIGJ5IEhhZGxleSBXaWNraGFtIGFuZCBHYXJyZXR0IEdyb2xlbXVuZC4gIA0KDQpUaGUgZnVsbCBib29rIGNhbiBiZSBmb3VuZCBhdDogaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8NCg0KQSBnb29kIGNoZWF0IHNoZWV0IGZvciBnZ3Bsb3QyIGZ1bmN0aW9ucyBjYW4gYmUgZm91bmQgYXQ6IGh0dHBzOi8vcnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDMvZ2dwbG90Mi1jaGVhdHNoZWV0LnBkZg0KDQoNCiMjIyBTY2F0dGVycGxvdHMNCg0KDQpMZXQncyBjcmVhdGUgYW4gZXh0cmVtZWx5IHNpbXBsZSBzY2F0dGVycGxvdC4gIA0KDQpXZSB3aWxsIHVzZSB0aGUgZnVuY3Rpb24gYGdncGxvdCgpYCB0byBkbyB0aGlzLiANCg0KVGhlIGZvcm1hdCBvZiBhbnkgZ2dwbG90IGdyYXBoIGlzIHRoaXMgZnVuY3Rpb24sIGZvbGxvd2VkIGJ5IGFub3RoZXIgZnVuY3Rpb24gdG8gYWRkIG9iamVjdHMuDQoNClRoZSBvYmplY3RzIG9uIGEgZ3JhcGggaW4gdGhlIGNhc2Ugb2YgYSBzY2F0dGVycGxvdCBhcmUgcG9pbnRzLiBUaGUgZnVuY3Rpb24gd2UgYWRkIHRvIGl0IGlzIGBnZW9tX3BvaW50YC4NCg0KVGhlc2UgZnVuY3Rpb25zIHJlbHkgb24gYSBmdW5jdGlvbiBvbiB0aGUgaW5zaWRlIGNhbGxlZCBgYWVzKClgLg0KDQpUaGUgZGF0YSBhbmQgYWVzdGhldGljIG1hcHBpbmcgY29tcG9uZW50cyBjYW4gYmUgYWRkZWQgdG8gZWl0aGVyIHRoZSBgZ2dwbG90KClgIG9yIGdlb20gZnVuY3Rpb25zLiAgDQoNCmBgYHtyIEdyYXBoIDF9DQpnZ3Bsb3QoZGF0YSA9IG1wZykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKQ0KYGBgIA0KDQo8YnI+DQoNClRoaXMgaXMgb25lIG9mIHRoZSBtb3N0IGJhc2ljIGdyYXBocyB0aGF0IG9uZSBjYW4gbWFrZSB1c2luZyB0aGUgZ2dwbG90MiBmcmFtZXdvcmsuIA0KDQpOZXh0LCBsZXQncyBhZGQgY29sb3IuDQoNCmBnZW9tX3BvaW50KClgIHVuZGVyc3RhbmRzIHRoZSBmb2xsb3dpbmcgYWVzdGhldGljczogeCwgeSwgYWxwaGEsIGNvbG9yLCBmaWxsLCBncm91cCwgc2hhcGUsIHNpemUsIGFuZCBzdHJva2UgKHNlZSBoZWxwIGRvY3VtZW50YXRpb24pLg0KDQpMZXQncyBtYXAgdGhlIGNvbG9yIGFyZ3VtZW50IHRvIHRoZSB2YXJpYWJsZSAiY2xhc3MiIGZyb20gbXBnLg0KDQpgYGB7ciBHcmFwaCAyfQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG9yID0gY2xhc3MpKQ0KYGBgDQoNCg0KVGhpcyBpcyBub3QgdGhlIG9ubHkgd2F5IHRvIGNvbG9yIG9iamVjdHMuICANCg0KSW5jbHVkaW5nIHRoZSBjb2xvciBhcmd1bWVudCBpbnNpZGUgb2YgdGhlIGBhZXMoKWAgZnVuY3Rpb24gY2FuIG1hcCBjb2xvcnMgdG8gYSBjaG9pY2Ugb2YgdmFyaWFibGUuIA0KDQpIb3dldmVyLCB3ZSBjYW4gc3BlY2lmeSBjb2xvcnMgbWFudWFsbHksIGJ5IHNwZWNpZnlpbmcgY29sb3Igb3V0c2lkZSBvZiB0aGUgYGFlcygpYCBmdW5jdGlvbi4gV2Ugd2lsbCBhbHNvIGlsbHVzdHJhdGUgdGhlICJzaXplIiBhcmd1bWVudC4NCg0KYGBge3IgR3JhcGggM30NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBzaXplID0gY2xhc3MpLCBjb2xvciA9ICJibHVlIikNCmBgYA0KDQojIyMgQmFycGxvdHMNCg0KTGFzdGx5LCBsZXQncyBleGFtaW5lIG90aGVyIG9iamVjdHMgdGhhdCB3ZSBjYW4gcGxvdCB1c2luZyBgZ2dwbG90KClgLiBXZSB3aWxsIGNyZWF0ZSBhIGJhciBjaGFydCB1c2luZyB0aGUgZnVuY3Rpb24gYGdlb21fYmFyKClgLiAgDQoNCmBgYHtyIEdyYXBoIDR9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21fYmFyKGFlcyh4ID0gY2xhc3MpKQ0KYGBgDQoNCjxicj4NCg0KV2l0aCB0aGUgYGdlb21fYmFyKClgIGZ1bmN0aW9uLCB3ZSBoYXZlIGEgZ3JlYXQgdXNlLWNhc2UgZm9yIGEgc3RhdCB0cmFuc2Zvcm1hdGlvbi4NCg0KVGhlIGZvbGxvd2luZyBjb2RlIGNhbiBiZSB1c2VkIHRvIGNvbnZlcnQgdGhlc2UgY291bnRzIHRvIHByb3BvcnRpb25zOg0KDQpgYGB7ciBHcmFwaCA1fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2JhcihhZXMoeCA9IGNsYXNzLCB5ID0gc3RhdChwcm9wKSwgZ3JvdXAgPSAxKSkNCmBgYA0KDQojIyMgSGlzdG9ncmFtcw0KDQpOZXh0LCBsZXQncyBjcmVhdGUgYSBoaXN0b2dyYW0gd2l0aCB0aGUgYGdlb21faGlzdG9ncmFtKClgIGZ1bmN0aW9uLg0KDQpgYGB7ciBHcmFwaCA2fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGh3eSkpDQpgYGANCg0KVGhlIGBnZW9tX2hpc3RvZ3JhbSgpYCBmdW5jdGlvbiBhY2NlcHRzIHRoZSBhcmd1bWVudCAiYmlud2lkdGgiLCBhbmQgaGFzIHR3byBrZXkgYXJndW1lbnRzIGZvciBjb2xvcjogZmlsbCAodGhpcyBjb250cm9scyB0aGUgb3ZlcmFsbCBjb2xvciksIGFuZCBjb2xvciAodGhpcyBjb250cm9scyB0aGUgYm9yZGVyKS4gIA0KDQpMZXQncyBmaWxsIGFsbCB0aGVzZSBpbjoNCg0KYGBge3IgR3JhcGggN30NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBod3kpLCBiaW53aWR0aCA9IDUsIGZpbGwgPSAibmF2eSIsIGNvbG9yID0gImdvbGQiKQ0KYGBgDQoNCjxicj4NCg0KYGdlb21faGlzdG9ncmFtKClgIHByb3ZpZGVzIGEgZ3JlYXQgZXhhbXBsZSB0byBtb2RpZnkgdGhlIHNjYWxlLg0KDQpOb3RpY2UgaW4gdGhpcyBleGFtcGxlIHRoYXQgdGhlIGF4aXMgaXMgYXV0b21hdGljYWxseSBicm9rZW4gdXAgYnkgdW5pdHMgb2YgMTAsIGFuZCBkb2VzIG5vdCBiZWdpbiBhdCAwLg0KDQpXZSBjYW4gbW9kaWZ5IHRoaXMgd2l0aCB0aGUgZnVuY3Rpb24gYHNjYWxlX3hfY29udGludW91cygpYCwgYXMgd2VsbCBhcyB0aGUgeS1heGlzIHdpdGggdGhlIGZ1bmN0aW9uIGBzY2FsZV95X2NvbnRpbnVvdXMoKWAuICANCg0KVGhlcmUgYXJlIHRocmVlIGtleSBhcmd1bWVudHMgd2Ugd2lsbCBmZWVkIHRoaXMgZnVuY3Rpb246ICJicmVha3MiLCAibGltaXRzIiwgYW5kICJleHBhbmQiLg0KDQoiYnJlYWtzIiB3aWxsIGRlZmluZSB0aGUgYnJlYWtzIG9uIHRoZSBheGlzLiANCg0KImxpbWl0cyIgd2lsbCBkZWZpbmUgdGhlIGJlZ2lubmluZyBhbmQgZW5kIG9mIHRoZSBheGlzLCBhbmQgdGhlICJleHBhbmQiIGFyZ3VtZW50IGNhbiBiZSB1c2VkIHRvIHN0YXJ0IHRoZSBheGVzIGF0IDAgYnkgdXNpbmcgImV4cGFuZCA9IGMoMCwwKSIuIA0KDQpgYGB7ciBHcmFwaCA4fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGh3eSksIGJpbndpZHRoID0gNSwgZmlsbCA9ICJuYXZ5IiwgY29sb3IgPSAiZ29sZCIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA0NSwgNSksIGxpbWl0cyA9IGMoMCwgNTApLCBleHBhbmQgPSBjKDAsMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA5MCwgMTApLCBsaW1pdHMgPSBjKDAsIDkwKSwgZXhwYW5kID0gYygwLDApKQ0KYGBgDQoNCiMjIyBCb3hwbG90cw0KDQpOZXh0LCB3ZSB3aWxsIGNyZWF0ZSBib3hwbG90cy4NCg0KYGBge3IgR3JhcGggOX0NCnAgPC0gZ2dwbG90KG1wZykgKw0KICBnZW9tX2JveHBsb3QoYWVzKHggPSBjbGFzcywgeSA9IGN0eSwgZmlsbCA9IGNsYXNzKSkNCnANCmBgYA0KDQojIyMgRmFjZXRzDQoNCkZhY2V0aW5nIGdlbmVyYXRlcyBzbWFsbCBtdWx0aXBsZXMgZWFjaCBzaG93aW5nIGEgZGlmZmVyZW50IHN1YnNldCBvZiB0aGUgZGF0YS4gU21hbGwgbXVsdGlwbGVzIGFyZSBhIHBvd2VyZnVsIHRvb2wgZm9yIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXM6IHlvdSBjYW4gcmFwaWRseSBjb21wYXJlIHBhdHRlcm5zIGluIGRpZmZlcmVudCBwYXJ0cyBvZiB0aGUgZGF0YSBhbmQgc2VlIHdoZXRoZXIgdGhleSBhcmUgdGhlIHNhbWUgb3IgZGlmZmVyZW50LiANCg0KUmVhZCBtb3JlIGFib3V0IGZhY2V0cyBbaGVyZV0oaHR0cHM6Ly9nZ3Bsb3QyLWJvb2sub3JnL2ZhY2V0Lmh0bWwpLg0KDQpOb3RpY2UgaW4gdGhpcyBkb2N1bWVudCB0aGUgdXNlIG9mIHRoZSBmaWcuaGVpZ2h0IGFuZCBmaWcud2lkdGggb3B0aW9ucy4NCg0KS2V5IGFyZ3VtZW50cyB0byBgZmFjZXRfd3JhcCgpYCBhcmUgImZhY2V0cyIsICJucm93IiwgYW5kICJuY29sIi4NCg0KYGBge3IgR3JhcGggMTAsIGZpZy5oZWlnaHQgPSA4LCBmaWcud2lkdGggPSAxMn0NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9ib3hwbG90KGFlcyh4ID0gY2xhc3MsIHkgPSBjdHksIGZpbGwgPSBjbGFzcykpICsNCiAgZmFjZXRfd3JhcChmYWNldHMgPSB+Y3lsLCBucm93ID0gMiwgbmNvbCA9IDIpDQpgYGANCg0KIyMjIENvb3JkaW5hdGVzDQoNCk90aGVyIGNvb3JkaW5hdGUgc3lzdGVtcyBjYW4gYmUgYXBwbGllZCB0byBncmFwaHMgY3JlYXRlZCBmcm9tIGdncGxvdDIuICANCg0KT25lIGV4YW1wbGUgaXMgYGNvb3JkX3BvbGFyKClgLCB3aGljaCB1c2VzIHBvbGFyIGNvb3JkaW5hdGVzLiBNb3N0IG9mIHRoZXNlIGFyZSBxdWl0ZSByYXJlLiBQcm9iYWJseSB0aGUgbW9zdCBjb21tb24gb25lIGlzIGBjb29yZF9mbGlwKClgLCB3aGljaCB3aWxsIGZsaXAgdGhlIFggYW5kIFkgYXhlcy4gTGV0J3MgYWxzbyBpbGx1c3RyYXRlIHRoZSBgbGFicygpYCBmdW5jdGlvbiwgd2hpY2ggY2FuIGJlIHVzZWQgdG8gY2hhbmdlIGxhYmVscy4NCg0KYGBge3IgR3JhcGggMTF9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21fYmFyKGFlcyh4ID0gY2xhc3MsIGZpbGwgPSBmYWN0b3IoY3lsKSkpICsNCiAgbGFicyh0aXRsZSA9ICJDeWxpbmRlcnMgYnkgQ2xhc3MiLCBmaWxsID0gImN5bGluZGVycyIpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KPGJyPg0KDQpUaGVzZSBiYXJzIGFyZSBzdGFja2VkIG9uIHRvcCBvZiBlYWNoIG9mIG90aGVyLCBkdWUgdG8gdGhlICJjeWwiIHZhcmlhYmxlIGJlaW5nIG1hcHBlZCB0byB0aGUgImZpbGwiIGFyZ3VtZW50LiBUaGVyZSBhcmUgdmFyaW91cyBwb3NpdGlvbiBhZGp1c3RtZW50cyB0aGF0IGNhbiBiZSB1c2VkLiBBZ2FpbiwgbW9zdCBvZiB0aGVzZSBhcmUgbm90IHZlcnkgY29tbW9uLCBidXQgYSBjb21tb24gb25lIGlzIHRoZSBhcmd1bWVudCAicG9zaXRpb24gPSAnZG9kZ2UnIiwgd2hpY2ggd2lsbCBwdXQgaXRlbXMgc2lkZS1ieS1zaWRlLiAgIA0KDQpTZWUgdGhpcyBleGFtcGxlOg0KDQpgYGB7ciBHcmFwaCAxMn0NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9iYXIoYWVzKHggPSBjbGFzcywgZmlsbCA9IGZhY3RvcihjeWwpKSwgcG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIGxhYnModGl0bGUgPSAiQ3lsaW5kZXJzIGJ5IENsYXNzIiwgZmlsbCA9ICJjeWxpbmRlcnMiKSArIA0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQojIyMgVGhlbWVzDQoNCkxhc3RseSwgd2UgY2FuIGFsdGVyIHRoZSAidGhlbWUiLCBvciB0aGUgb3ZlcmFsbCBhcHBlYXJhbmNlIG9mIG91ciBwbG90LiAgDQoNCkkgcmVjb21tZW5kIHVzaW5nIHRoZSBnZ1RoZW1lQXNzaXN0IHBhY2thZ2UsIGJlY2F1c2UgdGhpcyB3aWxsIG1ha2UgdGhpcyBpbmNyZWRpYmx5IGVhc3ksIHdpdGggYW4gaW50ZXJmYWNlIHRoYXQgd2lsbCBhdXRvbWF0aWNhbGx5IGdlbmVyYXRlIHJlcHJvZHVjaWJsZSBjb2RlLg0KDQpUaGlzIGNhbiBiZSB1c2VkIGJ5IGhpZ2hsaWdodGluZyBhIGdncGxvdDIgb2JqZWN0LCBhbmQgbmF2aWdhdGluZyB0byBBZGRpbnMgPiBnZ3Bsb3QgVGhlbWUgQXNzaXN0YW50Lg0KDQpXZSdsbCBtYWtlIHRoZSBmb2xsb3dpbmcgY2hhbmdlczogZWxpbWluYXRpbmcgdGhlIHBhbmVsIGdyaWQgbGluZXMsIGVsaW1pbmF0aW5nIGF4aXMgdGlja3MsIGFkZGluZyBhIHRpdGxlIGNhbGxlZCAiQm94cGxvdCBFeGFtcGxlIiwgbWFraW5nIGl0IGJpZ2dlciBhbmQgcHV0dGluZyBpdCBpbiBib2xkLCBhbmQgYWRqdXN0aW5nIGl0IHRvIHRoZSBjZW50ZXIuDQoNCmBgYHtyIEdyYXBoIDEzfQ0KIyBwDQpwICsgdGhlbWUoYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShsaW5ldHlwZSA9ICJibGFuayIpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUobGluZXR5cGUgPSAiYmxhbmsiKSwNCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKGxpbmV0eXBlID0gImJsYW5rIiksDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsDQogICAgICAgIGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSkgK2xhYnModGl0bGUgPSAiQm94cGxvdCBFeGFtcGxlIikNCmBgYA0KDQo8YnI+DQoNClRoZXJlIGFyZSBtYW55IG1vcmUgZXhhbXBsZXMgb2YgdGhpbmdzIHRoYXQgY2FuIGJlIGRvbmUgd2l0aCBnZ3Bsb3QyLiAgDQoNCkl0IGlzIGFuIGFtYXppbmdseSBwb3dlcmZ1bCBhbmQgZmxleGlibGUgcGFja2FnZSwgYW5kIGl0IGlzIHdvcnRoIGdldHRpbmcgYWNxdWFpbnRlZCB3aXRoIHRoZSBjaGVhdCBzaGVldC4NCg0KIyMgRXhlcmNpc2UgMS4gDQoNClVzaW5nIGRhdGEgb24gY3JlZGl0IGNhcmQgYXBwbGljYXRpb25zJyBzdGF0dXMgcGxlYXNlIHByZXNlbnQgdGhlIGZyZXF1ZW5jeSB0YWJsZSB3aXRoIHRoZSBuaWNlLCBrYWJsZSBmb3JtYXQgZm9yIGF2ZXJhZ2UgbW9udGhseSBjcmVkaXQgY2FyZCBleHBlbmRpdHVyZXMgb2YgYXBwbGljYW50cy4NCg0KYGBge3IgZXgxfQ0KDQpgYGANCg0KDQojIyBFeGVyY2lzZSAyLg0KDQpUaGUgZGF0YSBjb21lcyBmcm9tIFtodHRwczovL2ZsaXhnZW0uY29tL10oaHR0cHM6Ly9mbGl4Z2VtLmNvbS8pIChkYXRhc2V0IHZlcnNpb24gYXMgb2YgTWFyY2ggMTIsIDIwMjEpLiBUaGUgZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiBvbiA5NDI1IG1vdmllcyBhbmQgc2VyaWVzIGF2YWlsYWJsZSBvbiBOZXRsaXguDQoNCmBgYHtyIGV4MiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL25ldGZsaXgtZGF0YXNldC5jc3Y/cmF3PXRydWUiLCBkZXN0ZmlsZSA9ImRhbmUuY3N2Iixtb2RlPSJ3YiIpDQpteWRhdGE8LXJlYWQuY3N2KGZpbGU9ImRhbmUuY3N2IixlbmNvZGluZyA9IlVURi04IixoZWFkZXI9VFJVRSxzZXAgPSAiLCIpDQphdHRhY2gobXlkYXRhKQ0KYGBgDQoNCkFuc3dlciB3aXRoIHRoZSBtb3N0IGFwcHJvcHJpYXRlIGRhdGEgdmlzdWFsaXphdGlvbiBmb3IgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6DQoNCjEuIFdoYXQgaXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBJbWRiIHNjb3JlcyBmb3IgUG9saXNoIG1vdmllcyBhbmQgbW92aWUtc2VyaWVzPw0KDQpgYGB7ciBleDJfMX0NCg0KYGBgDQoNCjIuIFdoYXQgaXMgdGhlIGRlbnNpdHkgZnVuY3Rpb24gb2YgSW1kYiBzY29yZXMgZm9yIFBvbGlzaCBtb3ZpZXMgYW5kIG1vdmllLXNlcmllcz8NCg0KYGBge3IgZXgyXzJ9DQoNCmBgYA0KDQozLiBXaGF0IGFyZSB0aGUgbW9zdCBwb3B1bGFyIGxhbmd1YWdlcyBhdmFpbGFibGUgb24gTmV0ZmxpeD8NCg0KYGBge3IgZXgyXzN9DQoNCmBgYA0KDQpGb3IgZXh0cmEgY3JlZGl0czoNCg0KKkV4dHJhIGNoYWxsZW5nZSAxLio6IENyZWF0ZSBhIGNoYXJ0IHNob3dpbmcgYWN0b3JzIHN0YXJyaW5nIGluIHRoZSBtb3N0IHBvcHVsYXIgcHJvZHVjdGlvbnMuDQoNCmBgYHtyIGNoYWxsZW5nZTF9DQoNCmBgYA0KDQoNCipFeHRyYSBjaGFsbGVuZ2UgMi4qOiBGb3IgbW92aWVzIGFuZCBzZXJpZXMsIGNyZWF0ZSByYXRpbmcgY2hhcnRzIGZyb20gdGhlIHZhcmlvdXMgcG9ydGFscyAoSGlkZGVuIEdlbSwgSU1EYiwgUm90dGVuIFRvbWF0b2VzLCBNZXRhY3JpdGljKS4gSGludDogaXQncyBhIGdvb2QgaWRlYSB0byByZXNoYXBlIHRoZSBkYXRhIHRvICpsb25nKiBmb3JtYXQuDQoNCmBgYHtyIGNoYWxsZW5nZTJ9DQoNCmBgYA0KDQoqRXh0cmEgY2hhbGxlbmdlIDMuKjogV2hpY2ggZmlsbSBzdHVkaW9zIHByb2R1Y2UgdGhlIG1vc3QgYW5kIGhvdyBoYXMgdGhpcyBjaGFuZ2VkIG92ZXIgdGhlIHllYXJzPw0KDQpgYGB7ciBjaGFsbGVuZ2UzfQ0KDQpgYGANCg0KDQo=