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))
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.
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.
# In order to create monthly average we just divide the expenditure column by the months column. We also don't count the applicants which value in 'months' column equals to '0' in order to avoid dividing by '0'. We also create a frequency table showing the frequency of applicants in each interval of the Monthly Credit Card Expenditure.
frequency_table <- CreditCard %>%
filter(months != 0) %>%
mutate(AverageMonthlyExpenditure = expenditure / months) %>%
arrange(desc(AverageMonthlyExpenditure))
top_ten_rows <- head(frequency_table, 10)
kable_table <- top_ten_rows %>%
kable("html", caption = "Top 10 Average Monthly Credit Card Expenditures") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
kable_table
Top 10 Average Monthly Credit Card Expenditures
|
|
card
|
reports
|
age
|
income
|
share
|
expenditure
|
owner
|
selfemp
|
dependents
|
months
|
majorcards
|
active
|
AverageMonthlyExpenditure
|
|
472
|
yes
|
1
|
43.33333
|
4.80
|
0.3706127
|
1482.3680
|
yes
|
no
|
0
|
3
|
1
|
4
|
494.1227
|
|
765
|
yes
|
0
|
44.50000
|
9.00
|
0.2599927
|
1949.8620
|
no
|
no
|
2
|
4
|
1
|
7
|
487.4655
|
|
624
|
yes
|
0
|
27.16667
|
2.60
|
0.8778461
|
1902.0000
|
no
|
no
|
0
|
4
|
1
|
7
|
475.5000
|
|
1292
|
yes
|
0
|
22.00000
|
1.75
|
0.6214691
|
906.3092
|
no
|
no
|
0
|
2
|
1
|
0
|
453.1546
|
|
1181
|
yes
|
0
|
23.16667
|
2.60
|
0.8245508
|
1786.2770
|
no
|
no
|
0
|
4
|
1
|
0
|
446.5693
|
|
874
|
yes
|
0
|
25.58333
|
2.80
|
0.3252597
|
758.9391
|
no
|
no
|
0
|
2
|
1
|
7
|
379.4696
|
|
434
|
yes
|
0
|
28.91667
|
2.75
|
0.3822785
|
875.8884
|
yes
|
no
|
1
|
3
|
1
|
3
|
291.9628
|
|
487
|
yes
|
0
|
22.91667
|
2.50
|
0.2395248
|
498.9267
|
no
|
no
|
0
|
2
|
1
|
0
|
249.4633
|
|
763
|
yes
|
0
|
35.25000
|
6.00
|
0.4582348
|
2291.1740
|
no
|
no
|
2
|
10
|
1
|
0
|
229.1174
|
|
113
|
yes
|
0
|
28.16667
|
2.40
|
0.1137675
|
227.3683
|
no
|
no
|
0
|
1
|
1
|
29
|
227.3683
|
interval_data <- CreditCard %>%
filter(months != 0) %>%
mutate(AverageMonthlyExpenditure = expenditure / months) %>%
mutate(ExpendituresPerMonthIntervals = cut(AverageMonthlyExpenditure, breaks = seq(0, max(AverageMonthlyExpenditure, na.rm = TRUE) + 100, by = 100), right = FALSE)) %>%
group_by(ExpendituresPerMonthIntervals) %>%
summarise(Frequency = n(), .groups = 'drop') %>%
arrange(desc(Frequency))
kable_table_intervals <- interval_data %>%
kable("html", caption = "Frequency of Applicants by Monthly Credit Card Expenditure") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
kable_table_intervals
Frequency of Applicants by Monthly Credit Card Expenditure
|
ExpendituresPerMonthIntervals
|
Frequency
|
|
[0,100)
|
1275
|
|
[100,200)
|
26
|
|
[200,300)
|
9
|
|
[400,500)
|
5
|
|
[300,400)
|
1
|
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:
- What is the distribution of Imdb scores for Polish movies and
movie-series?
# To select only Polish Productions we grep the only one which have a "Polish" tag in their Tags column
polish_films <- mydata[grep("Polish", mydata$Tags), ]
ggplot(polish_films, aes(x = IMDb.Score)) +
geom_histogram(binwidth = 0.05, fill = "orange", color = "black") +
labs(title = "Distribution of IMDb Scores for Polish Productions",
x = "IMDb Score",
y = "Frequency")

- What is the density function of Imdb scores for Polish movies and
movie-series?
# The structure is really similar we just exchange the histogram to density chart
polish_films <- mydata[grep("Polish", mydata$Tags), ]
ggplot(polish_films, aes(x = IMDb.Score)) +
geom_density(fill = "steelblue", alpha = 0.9) +
labs(title = "Density of IMDb Scores for Polish Productions",
x = "IMDb Score",
y = "Density") +
theme_minimal()

- What are the most popular languages available on Netflix?
# Using separate_rows method we split the rows of films with more than language in Languages column and then we sum the occurrences. We only display 10 the most popular ones in order for the plot to look nice and clear
library(tidyr)
expanded_data <- mydata %>%
separate_rows(Languages, sep = ",\\s*")
language_counts <- expanded_data %>%
group_by(Languages) %>%
summarise(count = n()) %>%
arrange(desc(count)) %>%
top_n(10, count)
ggplot(language_counts, aes(x = reorder(Languages, -count), y = count, fill = Languages)) +
geom_bar(stat = "identity") +
labs(title = "Most Popular Languages",
x = "Languages",
y = "Frequency")

For extra credits:
Extra challenge 1.: Create a chart showing actors starring
in the most popular productions.
# In order to choose the actors who played in the most popular productions we used the Awards.Nominated.For column.
# Assuming that the production with huge number of nominations must be popular, we sum up the nominations for a film for each actor that played in it.
mydata_separated_actors <- mydata %>%
separate_rows(Actors, sep = ",\\s*")
actor_awards_counted <- mydata_separated_actors %>%
group_by(Actors) %>%
summarise(TotalNominations = sum(Awards.Nominated.For, na.rm = TRUE)) %>%
arrange(desc(TotalNominations))
top_actors <- head(actor_awards_counted, 10)
ggplot(top_actors, aes(x = reorder(Actors, -TotalNominations), y = TotalNominations, fill = Actors)) +
geom_bar(stat = "identity") +
labs(title = "Top 10 Actors by Award Nominations",
x = "Actors",
y = "Total Award Nominations") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

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.
# In order to get more clarity we created two charts, one with "Hidden.Gem.Score" and "IMDb.Score" and then second one with "Rotten.Tomatoes.Score", "Metacritic.Score". That's due to max score being 10 in the HiddenGem abd IMDb and 100 in RottenTomatoes and Metacritic.
movies_long <- mydata %>%
pivot_longer(
cols = c(Hidden.Gem.Score, IMDb.Score, Rotten.Tomatoes.Score, Metacritic.Score),
names_to = "RatingSource",
values_to = "Score"
) %>%
na.omit()
ratings_0_10 <- movies_long %>%
filter(RatingSource %in% c("Hidden.Gem.Score", "IMDb.Score"))
ratings_0_100 <- movies_long %>%
filter(RatingSource %in% c("Rotten.Tomatoes.Score", "Metacritic.Score"))
#First plot
ggplot(ratings_0_10, aes(x = RatingSource, y = Score, fill = RatingSource)) +
geom_boxplot() +
labs(title = "Ratings from 0 to 10 (Hidden Gem and IMDb)",
x = "Rating Source",
y = "Rating Score") +
theme_minimal() +
theme(legend.position = "none")

#Second plot
ggplot(ratings_0_100, aes(x = RatingSource, y = Score, fill = RatingSource)) +
geom_boxplot() +
labs(title = "Ratings from 0 to 100 (Rotten Tomatoes and Metacritic)",
x = "Rating Source",
y = "Rating Score") +
theme_minimal() +
theme(legend.position = "none")

Extra challenge 3.: Which film studios produce the most and
how has this changed over the years?
# Firstly we omit the productions that are not produced by any film studio. Then we group the result by release year and production house. Again for more clarity of the chart, we choose only 5 the most popular studios. Then we plot the result using geom_line.
# Also to show how much overall every Production House has created films and TV series we just group by the Production.House counting the appearances, of course beforehandly separating the Production.House column
movies <- mydata %>%
filter(!is.na(Production.House) & Production.House != "") %>%
mutate(Release.Date = mdy(Release.Date),
Release.Year = year(Release.Date)) %>%
separate_rows(Production.House, sep = ",\\s*")
production_counts <- movies %>%
group_by(Production.House, Release.Year) %>%
summarise(Films_Produced = n(), .groups = 'drop')
top_production_houses <- production_counts %>%
group_by(Production.House) %>%
summarise(Total_Films = sum(Films_Produced)) %>%
top_n(5, Total_Films) %>%
pull(Production.House)
filtered_production_counts <- production_counts %>%
filter(Production.House %in% top_production_houses)
ggplot(filtered_production_counts, aes(x = Release.Year, y = Films_Produced, color = Production.House)) +
geom_line() +
scale_x_continuous(limits = c(1960, 2024)) +
labs(title = "Film Production Over the Years by Top Studios",
x = "Year",
y = "Number of Films Produced") +
theme_minimal()

production_totals <- mydata %>%
filter(!is.na(Production.House) & Production.House != "") %>%
separate_rows(Production.House, sep = ",\\s*") %>%
group_by(Production.House) %>%
summarise(Total_Productions = n(), .groups = 'drop') %>%
arrange(desc(Total_Productions))
top_production_houses <- production_totals %>%
slice_max(order_by = Total_Productions, n = 10)
ggplot(top_production_houses, aes(x = reorder(Production.House, Total_Productions), y = Total_Productions, fill = Production.House)) +
geom_bar(stat = "identity") +
coord_flip() + # Flipping coordinates for better readability of production house names
labs(title = "Total Productions by Top 10 Production Houses",
x = "Production House",
y = "Total Productions") +
theme(legend.position = "none")

LS0tDQp0aXRsZTogIkRhdGEgVmlzdWFsaXphdGlvbiINCmF1dGhvcjogIlRvbWFzeiBCxYJhc3pjemFrIEp1bGlhIEtvemEgTWljaGHFgiBNYWxpbm93c2tpIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUNCiAgICBmb250c2l6ZTogOHB0DQogICAgdG9jOiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICBkZl9wcmludDogZGVmYXVsdA0KICAgIHRvY19kZXB0aDogNQ0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiAnNScNCmVkaXRvcl9vcHRpb25zOg0KICBtYXJrZG93bjoNCiAgICB3cmFwOiA3Mg0KLS0tDQoNCg0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCiMjIEdsb2JhbCBvcHRpb25zDQpvcHRpb25zKHF3cmFwczJfbWFya3VwID0gIm1hcmtkb3duIikNCmxpYnJhcnkocXdyYXBzMikNCmxpYnJhcnkoYXJzZW5hbCkNCmxpYnJhcnkoZTEwNzEpDQpsaWJyYXJ5KGhhdmVuKQ0KbGlicmFyeShwYXBlUikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoc3VtbWFyeXRvb2xzKQ0KbGlicmFyeShjbGFzc0ludCkNCmxpYnJhcnkocGFzdGVjcykNCmxpYnJhcnkocmVwb3J0dG9vbHMpDQpsaWJyYXJ5KGRlc2N0YWJsZSkNCmxpYnJhcnkoRGVzY1Rvb2xzKQ0KbGlicmFyeShmcmVxdWVuY3kpDQpsaWJyYXJ5KGdncHVicikNCmxpYnJhcnkoZGxvb2tyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeSh0aW55dGV4KQ0KbGlicmFyeShBRVIpDQpsaWJyYXJ5KGdnVGhlbWVBc3Npc3QpDQpsaWJyYXJ5KFNtYXJ0ZXJQb2xhbmQpDQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpDQpgYGANCg0KDQojIyBLYWJsZSBwYWNrYWdlDQoNCkluIGEgcHJldmlvdXMgd2Vla3MsIHdlIHNhdyBSIE1hcmtkb3duIGluIGFjdGlvbiwgd2hlcmUgbXVsdGlwbGUgdGhpbmdzIGNhbiBiZSBjcmVhdGVkIGluIG9uZSBsb2NhdGlvbjogY29kZSwgY29tbWVudGFyeSwgYW5kIG91dHB1dC4gDQoNCkluIHRoaXMgY2hhcHRlciB3ZSB3aWxsIGV4cGxvcmUgcGFja2FnZSB3aGljaCB3aWxsIGZhY2lsaXRhdGUgdGhlIGNyZWF0aW9uIG9mIHByZXNlbnRhdGlvbi13b3J0aHkgdGFibGVzOiAia2FibGVFeHRyYSIuDQoNCkxldCdzIHdvcmsgd2l0aCB0aGUgY3Jvc3Mtc2VjdGlvbmFsIGRhdGEgb24gdGhlIGNyZWRpdCBoaXN0b3J5IGZvciBhIHNhbXBsZSBvZiBhcHBsaWNhbnRzIGZvciBhIHR5cGUgb2YgY3JlZGl0IGNhcmQuDQoNCmBgYHtyfQ0KZGF0YShDcmVkaXRDYXJkKQ0KY2FyZEhlYWQgPC0gaGVhZChDcmVkaXRDYXJkKQ0KY2FyZEhlYWQNCmBgYA0KDQo8YnI+DQoNCmBgYHtyIFRpYmJsZSB3aXRoICJrYWJsZSJ9DQpjYXJkSGVhZCAlPiUNCiAga2JsKCkNCmBgYA0KDQo8YnI+DQoNCkxldCdzIHR3ZWFrIHRoZSBhcHBlYXJhbmNlIG9mIHRoaXMgd2l0aCB0aGUgImFsaWduIiBhbmQgdGhlICJjYXB0aW9uIiBhcmd1bWVudHMuICANCg0KVGhlIGFsaWduIGFyZ3VtZW50IHRha2VzIGEgY2hhcmFjdGVyIHZlY3RvciB3aXRoIGxldHRlcnMgImwiLCAiYyIsIG9yICJyIiAtIHNwZWNpZnlpbmcgd2hlcmUgeW91IHdhbnQgdGhlIGNvbHVtbnMgdG8gYmUgYWxpZ25lZC4NCg0KVGhlIGNhcHRpb24gYXJndW1lbnQgZ2l2ZXMgYSBjYXB0aW9uIHRvIHRoZSB0YWJsZS4NCg0KPGJyPg0KDQpgYGB7ciBBbGlnbmVkIGthYmxlfQ0KYmFzZSA8LSBjYXJkSGVhZCAlPiUNCiAga2JsKGFsaWduID0gYyhyZXAoImMiLCA3KSwgcmVwKCJyIiwgNSkpLCBjYXB0aW9uID0gImthYmxlIGV4YW1wbGUgd2l0aCBjYXJkIGRhdGEiKQ0KYmFzZQ0KYGBgDQoNCjxicj4NCg0KQSBrZXkgZnVuY3Rpb24sIHdoZXJlIHdlIGNhbiBlbmpveSBtdWNoIG9mIHRoZSBjb25maWd1cmF0aW9uIGZvciB0aGUgdGFibGUsIGlzIHZpYSBga2FibGVfc3R5bGluZygpYC4gICANCg0KV2UgaGF2ZSBvcHRpb25zICJib290c3RyYXBfb3B0aW9ucyIgb3IgImxhdGV4X29wdGlvbnMiLCB3aGVyZSB0aGUgbGF0dGVyIHJlcXVpcmVzIHRoZSB1c2Ugb2YgdGhlIHBhY2thZ2UgInRpbnl0ZXgiIGFuZCBhIGxvY2FsIGluc3RhbGxhdGlvbiBvZiBMYVRlWC4gIA0KDQpQb3NzaWJsZSBvcHRpb25zIGZvciAiYm9vdHN0cmFwX29wdGlvbnMiIGluY2x1ZGUgJ2Jhc2ljJywgJ3N0cmlwZWQnLCAnYm9yZGVyZWQnLCAnaG92ZXInLCAnY29uZGVuc2VkJywgJ3Jlc3BvbnNpdmUnLCBhbmQgbm9uZS4gIA0KDQpQb3NzaWJsZSBmb3IgImxhdGV4X29wdGlvbnMiIGluY2x1ZGUgJ2Jhc2ljJywgJ3N0cmlwZWQnLCAnaG9sZF9wb3NpdGlvbicsICdIT0xEX3Bvc2l0aW9uJywgJ3NjYWxlX2Rvd24nLCBhbmQgJ3JlcGVhdF9oZWFkZXInLg0KDQo8YnI+DQoNCg0KYGBge3IgQm9vdHN0cmFwIC0gc3RyaXBlZH0NCmJhc2UgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpDQpgYGANCg0KPGJyPg0KDQpOZXh0LCB3ZSBjYW4gY3VzdG9taXplIHRoZSBsb29rIGFuZCBmZWVsIG9mIHBhcnRpY3VsYXIgcm93cyBhbmQgY29sdW1ucy4gICANCg0KTGV0J3Mgc2VlIGFuIGV4YW1wbGUgaGVyZSwgd2hlcmUgd2UgbWFrZSB0aGUgbGFzdCB0aHJlZSByb3dzIGJsdWUuDQoNCmBgYHtyIENvbHVtbiBhbmQgcm93IGN1c3RvbWl6aW5nfQ0KYmFzZSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJib3JkZXJlZCIpICU+JQ0KICBjb2x1bW5fc3BlYyg4OjEyLCBib2xkID0gVCkgJT4lDQogIHJvd19zcGVjKDQ6NiwgaXRhbGljID0gVCwgY29sb3IgPSAiZ29sZCIsIGJhY2tncm91bmQgPSAiYmx1ZSIpDQpgYGANCg0KPGJyPg0KDQoNCldlIGNhbiBhbHNvIGNyZWF0ZSBncm91cHMgZm9yIG91ciBjb2x1bW5zLg0KDQo8YnI+DQoNCmBgYHtyfQ0KYmFzZSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJib3JkZXJlZCIpICU+JQ0KICBhZGRfaGVhZGVyX2Fib3ZlKGMoIkdyb3VwIDEiID0gNCwgIkdyb3VwIDIiID0gMiwgIkdyb3VwIDMiID0gNikpDQpgYGANCg0KPGJyPg0KDQoNCiMjIERhdGEgQWdncmVnYXRpb24NCg0KSW4gdGhlIGZpcnN0IHN0YWdlIG9mIG91ciBhbmFseXNpcyB3ZSBhcmUgZ29pbmcgdG8gZ3JvdXAgb3VyIGRhdGEgaW4gdGhlIGZvcm0gb2YgdGhlIHNpbXBsZSBmcmVxdWVuY3kgdGFibGUuDQoNCkZpcnN0LCBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGluY29tZSBpbiBvdXIgc2FtcGxlIGFuZCB2ZXJpZnkgdGhlIHRhYnVsYXIgYWNjdXJhY3kgdXNpbmcgVEFJIG1lYXN1cmU6DQoNCmBgYHtyIHRhYmxlLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0NCm9wdGlvbnMoc2NpcGVuPTk5OSkNCg0KbGltaXRzPC0gY3V0KENyZWRpdENhcmQkaW5jb21lLHNlcSgwLDE0LGJ5PTIpKQ0KDQoNCmBgYA0KDQpXaXRob3V0ICdrYWJsZScgc3R5bGluZyBpdCdzIHF1aXRlIHVnbHkgcmlnaHQ/IDstKQ0KDQoNCiMjIyBUYWJ1bGFyIGFjY3VyYWN5DQoNCkFuIGluZGV4IG9mIHRhYnVsYXIgYWNjdXJhY3kgVEFJLCBkZXNjcmliZWQgYnkgSmVua3MgYW5kIENhc3NwYWwgaW4gMTk3MSBpcyB0byBvcHRpbWl6ZSB0aGUgY2xhc3MgZGlzdHJpYnV0aW9uIHVzZWQgaW4gYSBjYXJ0b2dyYW1zL2ZyZXF1ZW5jeSB0YWJsZXMgZXRjLiANCg0KVGhlIFRBSSBpbmRpY2F0b3IgdGFrZXMgdmFsdWVzIGluIHRoZSByYW5nZSAoMDsxKS4gVGhlIG51bWVyYXRvciBvZiB0aGUgZXhwcmVzc2lvbiBpcyB0aGUgc3VtIG9mIHRoZSBhYnNvbHV0ZSBkZXZpYXRpb25zIG9mIHRoZSB2YWx1ZXMgY2xhc3NpZmllZCBpbnRvIGNsYXNzZXMsIGFuZCB0aGUgZGVub21pbmF0b3IgaXMgdGhlIHN1bSBvZiB0aGUgYWJzb2x1dGUgZGV2aWF0aW9ucyBvZiB0aGUgZW50aXJlIGNsYXNzaWZpZWQgc2V0LiANCg0KVGhlIGJldHRlciB0aGUgY2xhc3MgZGl2aXNpb24gcmVmbGVjdHMgdGhlIG5hdHVyZSBvZiB0aGUgZGF0YSwgdGhlIGxhcmdlciB0aGUgaW5kaWNhdG9yIHdpbGwgYmUuIEFzIHRoZSBudW1iZXIgb2YgY2xhc3NlcyBpbmNyZWFzZXMsIHRoZSBpbmRpY2F0b3Igd2lsbCB0YWtlIG9uIGxhcmdlciB2YWx1ZXMuDQoNCkxldCdzIGNhbGN1bGF0ZSBUQUkgaW5kZXggdG8gY2hlY2sgdGhlIHByb3BlcnRpZXMgb2YgdGhlIHRhYnVsYXRlZCBkYXRhOg0KDQpgYGB7ciB0YWksIGVjaG89VFJVRX0NCnRhYmVsa2EyIDwtIGNsYXNzSW50ZXJ2YWxzKENyZWRpdENhcmQkaW5jb21lLCBuPTcsIHN0eWxlPSJmaXhlZCIsIGZpeGVkQnJlYWtzPXNlcSgwLDE0LGJ5PTIpKQ0KamVua3MudGVzdHModGFiZWxrYTIpDQpgYGANCg0KQXMgd2UgY2FuIHNlZSAtIFRBSSBpbmRleC4uLg0KDQoNCldlIGNhbiB1c2UgZGlmZmVyZW50IHJlY2lwZXMuLi4gKHN0eWxlcyk6DQoNCg0KYGBgIHtyfQ0KdGFiZWxrYTM8LWNsYXNzSW50ZXJ2YWxzKENyZWRpdENhcmQkaW5jb21lLCBuPTEwLCBzdHlsZT0ic2QiKQ0KcGxvdCh0YWJlbGthMyxwYWw9YygxOjEwKSkNCmplbmtzLnRlc3RzKHRhYmVsa2EzKQ0KYGBgDQoNClN0aWxsLCB0aGUgVEFJIGluZGljYXRvciBpcyBub3Qgc2F0aXNmYWN0b3J5LiBXaGF0IHNob3VsZCB3ZSBjaGFuZ2UgaW4gdGhlIGZpbmFsIGZyZXF1ZW5jeSB0YWJsZSBkZXNpZ24/DQoNCmBgYHtyfQ0KaGlzdChDcmVkaXRDYXJkJGluY29tZSkNCmBgYA0KDQoNCiMjIyBDb250aW51b3VzIHZhcmlhYmxlcw0KDQpXZSBjYW4gY2FsY3VsYXRlIHRoZSBhYnNvbHV0ZSBhbmQgcmVsYXRpdmUgZnJlcXVlbmNpZXMgb2YgYSB2ZWN0b3IgeCB3aXRoIHRoZSBmdW5jdGlvbiAnRnJlcScgZnJvbSB0aGUgKkRlc2NUb29scyogcGFja2FnZXMuIENvbnRpbnVvdXMgKG51bWVyaWMpIHZhcmlhYmxlcyB3aWxsIGJlIGN1dCB1c2luZyB0aGUgc2FtZSBsb2dpYyBhcyB1c2VkIGJ5IHRoZSBmdW5jdGlvbiBoaXN0LiBDYXRlZ29yaWNhbCB2YXJpYWJsZXMgd2lsbCBiZSBhZ2dyZWdhdGVkIGJ5IHRhYmxlLiBUaGUgcmVzdWx0IHdpbGwgY29udGFpbiBzaW5nbGUgYW5kIGN1bXVsYXRpdmUgZnJlcXVlbmNpZXMgZm9yIGJvdGgsIGFic29sdXRlIHZhbHVlcyBhbmQgcGVyY2VudGFnZXMuIA0KDQpgYGB7cn0NCnRhYmVsYTQ8LUZyZXEoQ3JlZGl0Q2FyZCRpbmNvbWUsYnJlYWtzPXNlcSgwLDE0LGJ5PTIpLHVzZU5BPSJpZmFueSIpDQoNCnRhYmVsYTQgJT4lDQogIGthYmxlKGNvbC5uYW1lcyA9IGMoIkluY29tZXMgaW4ga1VTRCIsIkZyZXF1ZW5jeSIsIlBlcmNlbnRhZ2UgJSIsIkN1bXVsYXRpdmUgZnJlcXVlbmN5IiwiQ3VtdWxhdGl2ZSBwZXJjZW50YWdlICUiKSkgJT4lDQogIGthYmxlX2NsYXNzaWMoZnVsbF93aWR0aCA9IEYsIGh0bWxfZm9udCA9ICJDYW1icmlhIikNCmBgYA0KDQpCVFc6IHdoYXQgYWJvdXQgVEFJIG9mIHRoYXQgdGFibGU/Li4uDQoNCiMjIyBDYXRlZ29yaWNhbCB2YXJpYWJsZXMNCg0KTm93LCBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgY2F0ZWdvcmljYWwgZGF0YSBhbmQgbWFrZSBzb21lIHRhYnVsYXRpb25zLiBUaGUgKip4dGFicyoqIGZ1bmN0aW9uIHdvcmtzIGxpa2UgdGFibGUgZXhjZXB0IGl0IGNhbiBwcm9kdWNlIHRhYmxlcyBmcm9tIGZyZXF1ZW5jaWVzIHVzaW5nIHRoZSBmb3JtdWxhIGludGVyZmFjZS4gDQoNCkxldCdzIHNheSB3ZSB3YW50IHRvIHNlZSB0aGUgdGFibGUgd2l0aCBkYXRhIG9uIGhvdyBtYW55IGNhcmQgYXBwbGljYXRpb25zIHdhcyBhY2NlcHRlZCBvciBub3Q6DQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KeHRhYnMofiBjYXJkLCBkYXRhPUNyZWRpdENhcmQpDQpgYGANCg0KV2UgbWF5IGVhc2lseSBwcm9kdWNlICpjcm9zcy10YWJzKiAoc3RhdHVzIHZzLiBEb2VzIHRoZSBpbmRpdmlkdWFsIG93biB0aGVpciBob21lPykgYXMgd2VsbDoNCg0KYGBge3J9DQpjcm9zc3RhYjwteHRhYnMofiBjYXJkICsgb3duZXIsIGRhdGE9Q3JlZGl0Q2FyZCkNCmNyb3NzdGFiDQpgYGANCg0KYW5kIHRyYW5zZm9ybSBpdCBpbnRvIHByZXR0eSBodG1sIHRhYmxlIHdpdGggdGhlIGthYmxlIGZ1bmN0aW9uOg0KDQpgYGB7cn0NCmNyb3NzdGFiICU+JSANCiAga2JsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYpICU+JQ0KICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCwgYm9yZGVyX3JpZ2h0ID0gVCkgJT4lDQogIGNvbHVtbl9zcGVjKDIsIGJhY2tncm91bmQgPSAieWVsbG93IikNCmBgYA0KDQoNCiMjIERhdGEgVmlzdWFsaXphdGlvbg0KDQpXZSB3aWxsIGV4cGxvcmUgdGhlICoqImdncGxvdDIiKiogcGFja2FnZSBvZiB0aGUgdGlkeXZlcnNlIGZvciBkYXRhIHZpc3VhbGl6YXRpb24gcHVycG9zZXMuIFRoZSAiZ2dwbG90MiIgcGFja2FnZXMgaW52b2x2ZSB0aGUgdGhlIGZvbGxvd2luZyB0aHJlZSBtYW5kYXRvcnkgY29tcG9uZW50czoNCg0KMSkgRGF0YQ0KMikgQW4gYWVzdGhldGljIG1hcHBpbmcNCjMpIEdlb21zIChha2Egb2JqZWN0cykNCg0KVGhlIGZvbGxvd2luZyBjb21wb25lbnRzIGNhbiBhbHNvIG9wdGlvbmFsbHkgYmUgYWRkZWQ6DQoNCjQpIFN0YXRzIChha2EgdHJhbnNmb3JtYXRpb25zKQ0KNSkgU2NhbGVzDQo2KSBGYWNldHMNCjcpIENvb3JkaW5hdGUgc3lzdGVtcw0KOCkgUG9zaXRpb24gYWRqdXN0bWVudHMNCjkpIFRoZW1lcw0KDQpQbGVhc2Ugbm90ZSB0aGF0IGNvZGUgaW4gdGhpcyB0dXRvcmlhbCB3YXMgYWRhcHRlZCBmcm9tIENoYXB0ZXJzIDMgb2YgdGhlIGJvb2sgIlIgZm9yIERhdGEgU2NpZW5jZSIgYnkgSGFkbGV5IFdpY2toYW0gYW5kIEdhcnJldHQgR3JvbGVtdW5kLiAgDQoNClRoZSBmdWxsIGJvb2sgY2FuIGJlIGZvdW5kIGF0OiBodHRwczovL3I0ZHMuaGFkLmNvLm56Lw0KDQpBIGdvb2QgY2hlYXQgc2hlZXQgZm9yIGdncGxvdDIgZnVuY3Rpb25zIGNhbiBiZSBmb3VuZCBhdDogaHR0cHM6Ly9yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMy9nZ3Bsb3QyLWNoZWF0c2hlZXQucGRmDQoNCg0KIyMjIFNjYXR0ZXJwbG90cw0KDQoNCkxldCdzIGNyZWF0ZSBhbiBleHRyZW1lbHkgc2ltcGxlIHNjYXR0ZXJwbG90LiAgDQoNCldlIHdpbGwgdXNlIHRoZSBmdW5jdGlvbiBgZ2dwbG90KClgIHRvIGRvIHRoaXMuIA0KDQpUaGUgZm9ybWF0IG9mIGFueSBnZ3Bsb3QgZ3JhcGggaXMgdGhpcyBmdW5jdGlvbiwgZm9sbG93ZWQgYnkgYW5vdGhlciBmdW5jdGlvbiB0byBhZGQgb2JqZWN0cy4NCg0KVGhlIG9iamVjdHMgb24gYSBncmFwaCBpbiB0aGUgY2FzZSBvZiBhIHNjYXR0ZXJwbG90IGFyZSBwb2ludHMuIFRoZSBmdW5jdGlvbiB3ZSBhZGQgdG8gaXQgaXMgYGdlb21fcG9pbnRgLg0KDQpUaGVzZSBmdW5jdGlvbnMgcmVseSBvbiBhIGZ1bmN0aW9uIG9uIHRoZSBpbnNpZGUgY2FsbGVkIGBhZXMoKWAuDQoNClRoZSBkYXRhIGFuZCBhZXN0aGV0aWMgbWFwcGluZyBjb21wb25lbnRzIGNhbiBiZSBhZGRlZCB0byBlaXRoZXIgdGhlIGBnZ3Bsb3QoKWAgb3IgZ2VvbSBmdW5jdGlvbnMuICANCg0KYGBge3IgR3JhcGggMX0NCmdncGxvdChkYXRhID0gbXBnKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpDQpgYGAgDQoNCjxicj4NCg0KVGhpcyBpcyBvbmUgb2YgdGhlIG1vc3QgYmFzaWMgZ3JhcGhzIHRoYXQgb25lIGNhbiBtYWtlIHVzaW5nIHRoZSBnZ3Bsb3QyIGZyYW1ld29yay4gDQoNCk5leHQsIGxldCdzIGFkZCBjb2xvci4NCg0KYGdlb21fcG9pbnQoKWAgdW5kZXJzdGFuZHMgdGhlIGZvbGxvd2luZyBhZXN0aGV0aWNzOiB4LCB5LCBhbHBoYSwgY29sb3IsIGZpbGwsIGdyb3VwLCBzaGFwZSwgc2l6ZSwgYW5kIHN0cm9rZSAoc2VlIGhlbHAgZG9jdW1lbnRhdGlvbikuDQoNCkxldCdzIG1hcCB0aGUgY29sb3IgYXJndW1lbnQgdG8gdGhlIHZhcmlhYmxlICJjbGFzcyIgZnJvbSBtcGcuDQoNCmBgYHtyIEdyYXBoIDJ9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3IgPSBjbGFzcykpDQpgYGANCg0KDQpUaGlzIGlzIG5vdCB0aGUgb25seSB3YXkgdG8gY29sb3Igb2JqZWN0cy4gIA0KDQpJbmNsdWRpbmcgdGhlIGNvbG9yIGFyZ3VtZW50IGluc2lkZSBvZiB0aGUgYGFlcygpYCBmdW5jdGlvbiBjYW4gbWFwIGNvbG9ycyB0byBhIGNob2ljZSBvZiB2YXJpYWJsZS4gDQoNCkhvd2V2ZXIsIHdlIGNhbiBzcGVjaWZ5IGNvbG9ycyBtYW51YWxseSwgYnkgc3BlY2lmeWluZyBjb2xvciBvdXRzaWRlIG9mIHRoZSBgYWVzKClgIGZ1bmN0aW9uLiBXZSB3aWxsIGFsc28gaWxsdXN0cmF0ZSB0aGUgInNpemUiIGFyZ3VtZW50Lg0KDQpgYGB7ciBHcmFwaCAzfQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIHNpemUgPSBjbGFzcyksIGNvbG9yID0gImJsdWUiKQ0KYGBgDQoNCiMjIyBCYXJwbG90cw0KDQpMYXN0bHksIGxldCdzIGV4YW1pbmUgb3RoZXIgb2JqZWN0cyB0aGF0IHdlIGNhbiBwbG90IHVzaW5nIGBnZ3Bsb3QoKWAuIFdlIHdpbGwgY3JlYXRlIGEgYmFyIGNoYXJ0IHVzaW5nIHRoZSBmdW5jdGlvbiBgZ2VvbV9iYXIoKWAuICANCg0KYGBge3IgR3JhcGggNH0NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9iYXIoYWVzKHggPSBjbGFzcykpDQpgYGANCg0KPGJyPg0KDQpXaXRoIHRoZSBgZ2VvbV9iYXIoKWAgZnVuY3Rpb24sIHdlIGhhdmUgYSBncmVhdCB1c2UtY2FzZSBmb3IgYSBzdGF0IHRyYW5zZm9ybWF0aW9uLg0KDQpUaGUgZm9sbG93aW5nIGNvZGUgY2FuIGJlIHVzZWQgdG8gY29udmVydCB0aGVzZSBjb3VudHMgdG8gcHJvcG9ydGlvbnM6DQoNCmBgYHtyIEdyYXBoIDV9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21fYmFyKGFlcyh4ID0gY2xhc3MsIHkgPSBzdGF0KHByb3ApLCBncm91cCA9IDEpKQ0KYGBgDQoNCiMjIyBIaXN0b2dyYW1zDQoNCk5leHQsIGxldCdzIGNyZWF0ZSBhIGhpc3RvZ3JhbSB3aXRoIHRoZSBgZ2VvbV9oaXN0b2dyYW0oKWAgZnVuY3Rpb24uDQoNCmBgYHtyIEdyYXBoIDZ9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gaHd5KSkNCmBgYA0KDQpUaGUgYGdlb21faGlzdG9ncmFtKClgIGZ1bmN0aW9uIGFjY2VwdHMgdGhlIGFyZ3VtZW50ICJiaW53aWR0aCIsIGFuZCBoYXMgdHdvIGtleSBhcmd1bWVudHMgZm9yIGNvbG9yOiBmaWxsICh0aGlzIGNvbnRyb2xzIHRoZSBvdmVyYWxsIGNvbG9yKSwgYW5kIGNvbG9yICh0aGlzIGNvbnRyb2xzIHRoZSBib3JkZXIpLiAgDQoNCkxldCdzIGZpbGwgYWxsIHRoZXNlIGluOg0KDQpgYGB7ciBHcmFwaCA3fQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGh3eSksIGJpbndpZHRoID0gNSwgZmlsbCA9ICJuYXZ5IiwgY29sb3IgPSAiZ29sZCIpDQpgYGANCg0KPGJyPg0KDQpgZ2VvbV9oaXN0b2dyYW0oKWAgcHJvdmlkZXMgYSBncmVhdCBleGFtcGxlIHRvIG1vZGlmeSB0aGUgc2NhbGUuDQoNCk5vdGljZSBpbiB0aGlzIGV4YW1wbGUgdGhhdCB0aGUgYXhpcyBpcyBhdXRvbWF0aWNhbGx5IGJyb2tlbiB1cCBieSB1bml0cyBvZiAxMCwgYW5kIGRvZXMgbm90IGJlZ2luIGF0IDAuDQoNCldlIGNhbiBtb2RpZnkgdGhpcyB3aXRoIHRoZSBmdW5jdGlvbiBgc2NhbGVfeF9jb250aW51b3VzKClgLCBhcyB3ZWxsIGFzIHRoZSB5LWF4aXMgd2l0aCB0aGUgZnVuY3Rpb24gYHNjYWxlX3lfY29udGludW91cygpYC4gIA0KDQpUaGVyZSBhcmUgdGhyZWUga2V5IGFyZ3VtZW50cyB3ZSB3aWxsIGZlZWQgdGhpcyBmdW5jdGlvbjogImJyZWFrcyIsICJsaW1pdHMiLCBhbmQgImV4cGFuZCIuDQoNCiJicmVha3MiIHdpbGwgZGVmaW5lIHRoZSBicmVha3Mgb24gdGhlIGF4aXMuIA0KDQoibGltaXRzIiB3aWxsIGRlZmluZSB0aGUgYmVnaW5uaW5nIGFuZCBlbmQgb2YgdGhlIGF4aXMsIGFuZCB0aGUgImV4cGFuZCIgYXJndW1lbnQgY2FuIGJlIHVzZWQgdG8gc3RhcnQgdGhlIGF4ZXMgYXQgMCBieSB1c2luZyAiZXhwYW5kID0gYygwLDApIi4gDQoNCmBgYHtyIEdyYXBoIDh9DQpnZ3Bsb3QobXBnKSArDQogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gaHd5KSwgYmlud2lkdGggPSA1LCBmaWxsID0gIm5hdnkiLCBjb2xvciA9ICJnb2xkIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDQ1LCA1KSwgbGltaXRzID0gYygwLCA1MCksIGV4cGFuZCA9IGMoMCwwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDkwLCAxMCksIGxpbWl0cyA9IGMoMCwgOTApLCBleHBhbmQgPSBjKDAsMCkpDQpgYGANCg0KIyMjIEJveHBsb3RzDQoNCk5leHQsIHdlIHdpbGwgY3JlYXRlIGJveHBsb3RzLg0KDQpgYGB7ciBHcmFwaCA5fQ0KcCA8LSBnZ3Bsb3QobXBnKSArDQogIGdlb21fYm94cGxvdChhZXMoeCA9IGNsYXNzLCB5ID0gY3R5LCBmaWxsID0gY2xhc3MpKQ0KcA0KYGBgDQoNCiMjIyBGYWNldHMNCg0KRmFjZXRpbmcgZ2VuZXJhdGVzIHNtYWxsIG11bHRpcGxlcyBlYWNoIHNob3dpbmcgYSBkaWZmZXJlbnQgc3Vic2V0IG9mIHRoZSBkYXRhLiBTbWFsbCBtdWx0aXBsZXMgYXJlIGEgcG93ZXJmdWwgdG9vbCBmb3IgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpczogeW91IGNhbiByYXBpZGx5IGNvbXBhcmUgcGF0dGVybnMgaW4gZGlmZmVyZW50IHBhcnRzIG9mIHRoZSBkYXRhIGFuZCBzZWUgd2hldGhlciB0aGV5IGFyZSB0aGUgc2FtZSBvciBkaWZmZXJlbnQuIA0KDQpSZWFkIG1vcmUgYWJvdXQgZmFjZXRzIFtoZXJlXShodHRwczovL2dncGxvdDItYm9vay5vcmcvZmFjZXQuaHRtbCkuDQoNCk5vdGljZSBpbiB0aGlzIGRvY3VtZW50IHRoZSB1c2Ugb2YgdGhlIGZpZy5oZWlnaHQgYW5kIGZpZy53aWR0aCBvcHRpb25zLg0KDQpLZXkgYXJndW1lbnRzIHRvIGBmYWNldF93cmFwKClgIGFyZSAiZmFjZXRzIiwgIm5yb3ciLCBhbmQgIm5jb2wiLg0KDQpgYGB7ciBHcmFwaCAxMCwgZmlnLmhlaWdodCA9IDgsIGZpZy53aWR0aCA9IDEyfQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2JveHBsb3QoYWVzKHggPSBjbGFzcywgeSA9IGN0eSwgZmlsbCA9IGNsYXNzKSkgKw0KICBmYWNldF93cmFwKGZhY2V0cyA9IH5jeWwsIG5yb3cgPSAyLCBuY29sID0gMikNCmBgYA0KDQojIyMgQ29vcmRpbmF0ZXMNCg0KT3RoZXIgY29vcmRpbmF0ZSBzeXN0ZW1zIGNhbiBiZSBhcHBsaWVkIHRvIGdyYXBocyBjcmVhdGVkIGZyb20gZ2dwbG90Mi4gIA0KDQpPbmUgZXhhbXBsZSBpcyBgY29vcmRfcG9sYXIoKWAsIHdoaWNoIHVzZXMgcG9sYXIgY29vcmRpbmF0ZXMuIE1vc3Qgb2YgdGhlc2UgYXJlIHF1aXRlIHJhcmUuIFByb2JhYmx5IHRoZSBtb3N0IGNvbW1vbiBvbmUgaXMgYGNvb3JkX2ZsaXAoKWAsIHdoaWNoIHdpbGwgZmxpcCB0aGUgWCBhbmQgWSBheGVzLiBMZXQncyBhbHNvIGlsbHVzdHJhdGUgdGhlIGBsYWJzKClgIGZ1bmN0aW9uLCB3aGljaCBjYW4gYmUgdXNlZCB0byBjaGFuZ2UgbGFiZWxzLg0KDQpgYGB7ciBHcmFwaCAxMX0NCmdncGxvdChtcGcpICsNCiAgZ2VvbV9iYXIoYWVzKHggPSBjbGFzcywgZmlsbCA9IGZhY3RvcihjeWwpKSkgKw0KICBsYWJzKHRpdGxlID0gIkN5bGluZGVycyBieSBDbGFzcyIsIGZpbGwgPSAiY3lsaW5kZXJzIikgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQo8YnI+DQoNClRoZXNlIGJhcnMgYXJlIHN0YWNrZWQgb24gdG9wIG9mIGVhY2ggb2Ygb3RoZXIsIGR1ZSB0byB0aGUgImN5bCIgdmFyaWFibGUgYmVpbmcgbWFwcGVkIHRvIHRoZSAiZmlsbCIgYXJndW1lbnQuIFRoZXJlIGFyZSB2YXJpb3VzIHBvc2l0aW9uIGFkanVzdG1lbnRzIHRoYXQgY2FuIGJlIHVzZWQuIEFnYWluLCBtb3N0IG9mIHRoZXNlIGFyZSBub3QgdmVyeSBjb21tb24sIGJ1dCBhIGNvbW1vbiBvbmUgaXMgdGhlIGFyZ3VtZW50ICJwb3NpdGlvbiA9ICdkb2RnZSciLCB3aGljaCB3aWxsIHB1dCBpdGVtcyBzaWRlLWJ5LXNpZGUuICAgDQoNClNlZSB0aGlzIGV4YW1wbGU6DQoNCmBgYHtyIEdyYXBoIDEyfQ0KZ2dwbG90KG1wZykgKw0KICBnZW9tX2JhcihhZXMoeCA9IGNsYXNzLCBmaWxsID0gZmFjdG9yKGN5bCkpLCBwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgbGFicyh0aXRsZSA9ICJDeWxpbmRlcnMgYnkgQ2xhc3MiLCBmaWxsID0gImN5bGluZGVycyIpICsgDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMjIyBUaGVtZXMNCg0KTGFzdGx5LCB3ZSBjYW4gYWx0ZXIgdGhlICJ0aGVtZSIsIG9yIHRoZSBvdmVyYWxsIGFwcGVhcmFuY2Ugb2Ygb3VyIHBsb3QuICANCg0KSSByZWNvbW1lbmQgdXNpbmcgdGhlIGdnVGhlbWVBc3Npc3QgcGFja2FnZSwgYmVjYXVzZSB0aGlzIHdpbGwgbWFrZSB0aGlzIGluY3JlZGlibHkgZWFzeSwgd2l0aCBhbiBpbnRlcmZhY2UgdGhhdCB3aWxsIGF1dG9tYXRpY2FsbHkgZ2VuZXJhdGUgcmVwcm9kdWNpYmxlIGNvZGUuDQoNClRoaXMgY2FuIGJlIHVzZWQgYnkgaGlnaGxpZ2h0aW5nIGEgZ2dwbG90MiBvYmplY3QsIGFuZCBuYXZpZ2F0aW5nIHRvIEFkZGlucyA+IGdncGxvdCBUaGVtZSBBc3Npc3RhbnQuDQoNCldlJ2xsIG1ha2UgdGhlIGZvbGxvd2luZyBjaGFuZ2VzOiBlbGltaW5hdGluZyB0aGUgcGFuZWwgZ3JpZCBsaW5lcywgZWxpbWluYXRpbmcgYXhpcyB0aWNrcywgYWRkaW5nIGEgdGl0bGUgY2FsbGVkICJCb3hwbG90IEV4YW1wbGUiLCBtYWtpbmcgaXQgYmlnZ2VyIGFuZCBwdXR0aW5nIGl0IGluIGJvbGQsIGFuZCBhZGp1c3RpbmcgaXQgdG8gdGhlIGNlbnRlci4NCg0KYGBge3IgR3JhcGggMTN9DQojIHANCnAgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9saW5lKGxpbmV0eXBlID0gImJsYW5rIiksDQogICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShsaW5ldHlwZSA9ICJibGFuayIpLA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUobGluZXR5cGUgPSAiYmxhbmsiKSwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwNCiAgICAgICAgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpKSArbGFicyh0aXRsZSA9ICJCb3hwbG90IEV4YW1wbGUiKQ0KYGBgDQoNCjxicj4NCg0KVGhlcmUgYXJlIG1hbnkgbW9yZSBleGFtcGxlcyBvZiB0aGluZ3MgdGhhdCBjYW4gYmUgZG9uZSB3aXRoIGdncGxvdDIuICANCg0KSXQgaXMgYW4gYW1hemluZ2x5IHBvd2VyZnVsIGFuZCBmbGV4aWJsZSBwYWNrYWdlLCBhbmQgaXQgaXMgd29ydGggZ2V0dGluZyBhY3F1YWludGVkIHdpdGggdGhlIGNoZWF0IHNoZWV0Lg0KDQojIyBFeGVyY2lzZSAxLiANCg0KVXNpbmcgZGF0YSBvbiBjcmVkaXQgY2FyZCBhcHBsaWNhdGlvbnMnIHN0YXR1cyBwbGVhc2UgcHJlc2VudCB0aGUgZnJlcXVlbmN5IHRhYmxlIHdpdGggdGhlIG5pY2UsIGthYmxlIGZvcm1hdCBmb3IgYXZlcmFnZSBtb250aGx5IGNyZWRpdCBjYXJkIGV4cGVuZGl0dXJlcyBvZiBhcHBsaWNhbnRzLg0KDQpgYGB7ciBleDF9DQojIEluIG9yZGVyIHRvIGNyZWF0ZSBtb250aGx5IGF2ZXJhZ2Ugd2UganVzdCBkaXZpZGUgdGhlIGV4cGVuZGl0dXJlIGNvbHVtbiBieSB0aGUgbW9udGhzIGNvbHVtbi4gV2UgYWxzbyBkb24ndCBjb3VudCB0aGUgYXBwbGljYW50cyB3aGljaCB2YWx1ZSBpbiAnbW9udGhzJyBjb2x1bW4gZXF1YWxzIHRvICcwJyBpbiBvcmRlciB0byBhdm9pZCBkaXZpZGluZyBieSAnMCcuIFdlIGFsc28gY3JlYXRlIGEgZnJlcXVlbmN5IHRhYmxlIHNob3dpbmcgdGhlIGZyZXF1ZW5jeSBvZiBhcHBsaWNhbnRzIGluIGVhY2ggaW50ZXJ2YWwgb2YgdGhlIE1vbnRobHkgQ3JlZGl0IENhcmQgRXhwZW5kaXR1cmUuICANCg0KDQpmcmVxdWVuY3lfdGFibGUgPC0gQ3JlZGl0Q2FyZCAlPiUNCiAgIGZpbHRlcihtb250aHMgIT0gMCkgJT4lIA0KICAgbXV0YXRlKEF2ZXJhZ2VNb250aGx5RXhwZW5kaXR1cmUgPSBleHBlbmRpdHVyZSAvIG1vbnRocykgJT4lDQogICBhcnJhbmdlKGRlc2MoQXZlcmFnZU1vbnRobHlFeHBlbmRpdHVyZSkpDQoNCnRvcF90ZW5fcm93cyA8LSBoZWFkKGZyZXF1ZW5jeV90YWJsZSwgMTApDQoNCmthYmxlX3RhYmxlIDwtIHRvcF90ZW5fcm93cyAlPiUNCiAga2FibGUoImh0bWwiLCBjYXB0aW9uID0gIlRvcCAxMCBBdmVyYWdlIE1vbnRobHkgQ3JlZGl0IENhcmQgRXhwZW5kaXR1cmVzIikgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpDQoNCmthYmxlX3RhYmxlDQoNCmludGVydmFsX2RhdGEgPC0gQ3JlZGl0Q2FyZCAlPiUNCiAgIGZpbHRlcihtb250aHMgIT0gMCkgJT4lIA0KICAgbXV0YXRlKEF2ZXJhZ2VNb250aGx5RXhwZW5kaXR1cmUgPSBleHBlbmRpdHVyZSAvIG1vbnRocykgJT4lDQogICBtdXRhdGUoRXhwZW5kaXR1cmVzUGVyTW9udGhJbnRlcnZhbHMgPSBjdXQoQXZlcmFnZU1vbnRobHlFeHBlbmRpdHVyZSwgYnJlYWtzID0gc2VxKDAsIG1heChBdmVyYWdlTW9udGhseUV4cGVuZGl0dXJlLCAgICAgIG5hLnJtID0gVFJVRSkgKyAxMDAsIGJ5ID0gMTAwKSwgcmlnaHQgPSBGQUxTRSkpICU+JQ0KICAgZ3JvdXBfYnkoRXhwZW5kaXR1cmVzUGVyTW9udGhJbnRlcnZhbHMpICU+JQ0KICAgc3VtbWFyaXNlKEZyZXF1ZW5jeSA9IG4oKSwgLmdyb3VwcyA9ICdkcm9wJykgJT4lDQogICBhcnJhbmdlKGRlc2MoRnJlcXVlbmN5KSkNCg0Ka2FibGVfdGFibGVfaW50ZXJ2YWxzIDwtIGludGVydmFsX2RhdGEgJT4lDQogIGthYmxlKCJodG1sIiwgY2FwdGlvbiA9ICJGcmVxdWVuY3kgb2YgQXBwbGljYW50cyBieSBNb250aGx5IENyZWRpdCBDYXJkIEV4cGVuZGl0dXJlIikgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpDQoNCmthYmxlX3RhYmxlX2ludGVydmFscw0KDQpgYGANCg0KDQojIyBFeGVyY2lzZSAyLg0KDQpUaGUgZGF0YSBjb21lcyBmcm9tIFtodHRwczovL2ZsaXhnZW0uY29tL10oaHR0cHM6Ly9mbGl4Z2VtLmNvbS8pIChkYXRhc2V0IHZlcnNpb24gYXMgb2YgTWFyY2ggMTIsIDIwMjEpLiBUaGUgZGF0YSBjb250YWlucyBpbmZvcm1hdGlvbiBvbiA5NDI1IG1vdmllcyBhbmQgc2VyaWVzIGF2YWlsYWJsZSBvbiBOZXRsaXguDQoNCmBgYHtyIGV4MiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL25ldGZsaXgtZGF0YXNldC5jc3Y/cmF3PXRydWUiLCBkZXN0ZmlsZSA9ImRhbmUuY3N2Iixtb2RlPSJ3YiIpDQpteWRhdGE8LXJlYWQuY3N2KGZpbGU9ImRhbmUuY3N2IixlbmNvZGluZyA9IlVURi04IixoZWFkZXI9VFJVRSxzZXAgPSAiLCIpDQphdHRhY2gobXlkYXRhKQ0KYGBgDQoNCkFuc3dlciB3aXRoIHRoZSBtb3N0IGFwcHJvcHJpYXRlIGRhdGEgdmlzdWFsaXphdGlvbiBmb3IgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6DQoNCjEuIFdoYXQgaXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBJbWRiIHNjb3JlcyBmb3IgUG9saXNoIG1vdmllcyBhbmQgbW92aWUtc2VyaWVzPw0KYGBge3IgZXgyXzF9DQojIFRvIHNlbGVjdCBvbmx5IFBvbGlzaCBQcm9kdWN0aW9ucyB3ZSBncmVwIHRoZSBvbmx5IG9uZSB3aGljaCBoYXZlIGEgIlBvbGlzaCIgdGFnIGluIHRoZWlyIFRhZ3MgY29sdW1uDQoNCnBvbGlzaF9maWxtcyA8LSBteWRhdGFbZ3JlcCgiUG9saXNoIiwgbXlkYXRhJFRhZ3MpLCBdDQoNCmdncGxvdChwb2xpc2hfZmlsbXMsIGFlcyh4ID0gSU1EYi5TY29yZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjA1LCBmaWxsID0gIm9yYW5nZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBJTURiIFNjb3JlcyBmb3IgUG9saXNoIFByb2R1Y3Rpb25zIiwNCiAgICAgICB4ID0gIklNRGIgU2NvcmUiLA0KICAgICAgIHkgPSAiRnJlcXVlbmN5IikNCg0KDQpgYGANCg0KMi4gV2hhdCBpcyB0aGUgZGVuc2l0eSBmdW5jdGlvbiBvZiBJbWRiIHNjb3JlcyBmb3IgUG9saXNoIG1vdmllcyBhbmQgbW92aWUtc2VyaWVzPw0KDQpgYGB7ciBleDJfMn0NCiMgVGhlIHN0cnVjdHVyZSBpcyByZWFsbHkgc2ltaWxhciB3ZSBqdXN0IGV4Y2hhbmdlIHRoZSBoaXN0b2dyYW0gdG8gZGVuc2l0eSBjaGFydA0KcG9saXNoX2ZpbG1zIDwtIG15ZGF0YVtncmVwKCJQb2xpc2giLCBteWRhdGEkVGFncyksIF0NCg0KZ2dwbG90KHBvbGlzaF9maWxtcywgYWVzKHggPSBJTURiLlNjb3JlKSkgKw0KICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJzdGVlbGJsdWUiLCBhbHBoYSA9IDAuOSkgKw0KICBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgSU1EYiBTY29yZXMgZm9yIFBvbGlzaCBQcm9kdWN0aW9ucyIsDQogICAgICAgeCA9ICJJTURiIFNjb3JlIiwNCiAgICAgICB5ID0gIkRlbnNpdHkiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCjMuIFdoYXQgYXJlIHRoZSBtb3N0IHBvcHVsYXIgbGFuZ3VhZ2VzIGF2YWlsYWJsZSBvbiBOZXRmbGl4Pw0KDQpgYGB7ciBleDJfM30NCiMgVXNpbmcgc2VwYXJhdGVfcm93cyBtZXRob2Qgd2Ugc3BsaXQgdGhlIHJvd3Mgb2YgZmlsbXMgd2l0aCBtb3JlIHRoYW4gbGFuZ3VhZ2UgaW4gTGFuZ3VhZ2VzIGNvbHVtbiBhbmQgdGhlbiB3ZSBzdW0gdGhlIG9jY3VycmVuY2VzLiBXZSBvbmx5IGRpc3BsYXkgMTAgdGhlIG1vc3QgcG9wdWxhciBvbmVzIGluIG9yZGVyIGZvciB0aGUgcGxvdCB0byBsb29rIG5pY2UgYW5kIGNsZWFyIA0KDQpsaWJyYXJ5KHRpZHlyKQ0KDQpleHBhbmRlZF9kYXRhIDwtIG15ZGF0YSAlPiUNCiAgc2VwYXJhdGVfcm93cyhMYW5ndWFnZXMsIHNlcCA9ICIsXFxzKiIpDQoNCmxhbmd1YWdlX2NvdW50cyA8LSBleHBhbmRlZF9kYXRhICU+JQ0KICBncm91cF9ieShMYW5ndWFnZXMpICU+JQ0KICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoY291bnQpKSAlPiUNCiAgdG9wX24oMTAsIGNvdW50KQ0KDQpnZ3Bsb3QobGFuZ3VhZ2VfY291bnRzLCBhZXMoeCA9IHJlb3JkZXIoTGFuZ3VhZ2VzLCAtY291bnQpLCB5ID0gY291bnQsIGZpbGwgPSBMYW5ndWFnZXMpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGxhYnModGl0bGUgPSAiTW9zdCBQb3B1bGFyIExhbmd1YWdlcyIsDQogICAgICAgeCA9ICJMYW5ndWFnZXMiLA0KICAgICAgIHkgPSAiRnJlcXVlbmN5IikNCg0KYGBgDQoNCkZvciBleHRyYSBjcmVkaXRzOg0KDQoqRXh0cmEgY2hhbGxlbmdlIDEuKjogQ3JlYXRlIGEgY2hhcnQgc2hvd2luZyBhY3RvcnMgc3RhcnJpbmcgaW4gdGhlIG1vc3QgcG9wdWxhciBwcm9kdWN0aW9ucy4NCg0KYGBge3IgY2hhbGxlbmdlMX0NCiMgSW4gb3JkZXIgdG8gY2hvb3NlIHRoZSBhY3RvcnMgd2hvIHBsYXllZCBpbiB0aGUgbW9zdCBwb3B1bGFyIHByb2R1Y3Rpb25zIHdlIHVzZWQgdGhlIEF3YXJkcy5Ob21pbmF0ZWQuRm9yIGNvbHVtbi4gDQojIEFzc3VtaW5nIHRoYXQgdGhlIHByb2R1Y3Rpb24gd2l0aCBodWdlIG51bWJlciBvZiBub21pbmF0aW9ucyBtdXN0IGJlIHBvcHVsYXIsIHdlIHN1bSB1cCB0aGUgbm9taW5hdGlvbnMgZm9yIGEgZmlsbSBmb3IgZWFjaCBhY3RvciB0aGF0IHBsYXllZCBpbiBpdC4NCg0KbXlkYXRhX3NlcGFyYXRlZF9hY3RvcnMgPC0gbXlkYXRhICU+JQ0KICBzZXBhcmF0ZV9yb3dzKEFjdG9ycywgc2VwID0gIixcXHMqIikNCg0KYWN0b3JfYXdhcmRzX2NvdW50ZWQgPC0gbXlkYXRhX3NlcGFyYXRlZF9hY3RvcnMgJT4lDQogIGdyb3VwX2J5KEFjdG9ycykgJT4lDQogIHN1bW1hcmlzZShUb3RhbE5vbWluYXRpb25zID0gc3VtKEF3YXJkcy5Ob21pbmF0ZWQuRm9yLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShkZXNjKFRvdGFsTm9taW5hdGlvbnMpKQ0KDQp0b3BfYWN0b3JzIDwtIGhlYWQoYWN0b3JfYXdhcmRzX2NvdW50ZWQsIDEwKQ0KDQpnZ3Bsb3QodG9wX2FjdG9ycywgYWVzKHggPSByZW9yZGVyKEFjdG9ycywgLVRvdGFsTm9taW5hdGlvbnMpLCB5ID0gVG90YWxOb21pbmF0aW9ucywgZmlsbCA9IEFjdG9ycykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgbGFicyh0aXRsZSA9ICJUb3AgMTAgQWN0b3JzIGJ5IEF3YXJkIE5vbWluYXRpb25zIiwNCiAgICAgICB4ID0gIkFjdG9ycyIsDQogICAgICAgeSA9ICJUb3RhbCBBd2FyZCBOb21pbmF0aW9ucyIpICArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpDQpgYGANCg0KDQoqRXh0cmEgY2hhbGxlbmdlIDIuKjogRm9yIG1vdmllcyBhbmQgc2VyaWVzLCBjcmVhdGUgcmF0aW5nIGNoYXJ0cyBmcm9tIHRoZSB2YXJpb3VzIHBvcnRhbHMgKEhpZGRlbiBHZW0sIElNRGIsIFJvdHRlbiBUb21hdG9lcywgTWV0YWNyaXRpYykuIEhpbnQ6IGl0J3MgYSBnb29kIGlkZWEgdG8gcmVzaGFwZSB0aGUgZGF0YSB0byAqbG9uZyogZm9ybWF0Lg0KDQoNCg0KYGBge3IgY2hhbGxlbmdlMn0NCiMgSW4gb3JkZXIgdG8gZ2V0IG1vcmUgY2xhcml0eSB3ZSBjcmVhdGVkIHR3byBjaGFydHMsIG9uZSB3aXRoICJIaWRkZW4uR2VtLlNjb3JlIiBhbmQgIklNRGIuU2NvcmUiIGFuZCB0aGVuIHNlY29uZCBvbmUgd2l0aCAiUm90dGVuLlRvbWF0b2VzLlNjb3JlIiwgIk1ldGFjcml0aWMuU2NvcmUiLiBUaGF0J3MgZHVlIHRvIG1heCBzY29yZSBiZWluZyAxMCBpbiB0aGUgSGlkZGVuR2VtIGFiZCBJTURiIGFuZCAxMDAgaW4gUm90dGVuVG9tYXRvZXMgYW5kIE1ldGFjcml0aWMuIA0KDQptb3ZpZXNfbG9uZyA8LSBteWRhdGEgJT4lDQogIHBpdm90X2xvbmdlcigNCiAgICBjb2xzID0gYyhIaWRkZW4uR2VtLlNjb3JlLCBJTURiLlNjb3JlLCBSb3R0ZW4uVG9tYXRvZXMuU2NvcmUsIE1ldGFjcml0aWMuU2NvcmUpLCANCiAgICBuYW1lc190byA9ICJSYXRpbmdTb3VyY2UiLCANCiAgICB2YWx1ZXNfdG8gPSAiU2NvcmUiDQogICkgJT4lDQogIG5hLm9taXQoKQ0KDQpyYXRpbmdzXzBfMTAgPC0gbW92aWVzX2xvbmcgJT4lDQogIGZpbHRlcihSYXRpbmdTb3VyY2UgJWluJSBjKCJIaWRkZW4uR2VtLlNjb3JlIiwgIklNRGIuU2NvcmUiKSkNCg0KcmF0aW5nc18wXzEwMCA8LSBtb3ZpZXNfbG9uZyAlPiUNCiAgZmlsdGVyKFJhdGluZ1NvdXJjZSAlaW4lIGMoIlJvdHRlbi5Ub21hdG9lcy5TY29yZSIsICJNZXRhY3JpdGljLlNjb3JlIikpDQoNCiNGaXJzdCBwbG90DQpnZ3Bsb3QocmF0aW5nc18wXzEwLCBhZXMoeCA9IFJhdGluZ1NvdXJjZSwgeSA9IFNjb3JlLCBmaWxsID0gUmF0aW5nU291cmNlKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnModGl0bGUgPSAiUmF0aW5ncyBmcm9tIDAgdG8gMTAgKEhpZGRlbiBHZW0gYW5kIElNRGIpIiwNCiAgICAgICB4ID0gIlJhdGluZyBTb3VyY2UiLA0KICAgICAgIHkgPSAiUmF0aW5nIFNjb3JlIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICANCg0KI1NlY29uZCBwbG90DQpnZ3Bsb3QocmF0aW5nc18wXzEwMCwgYWVzKHggPSBSYXRpbmdTb3VyY2UsIHkgPSBTY29yZSwgZmlsbCA9IFJhdGluZ1NvdXJjZSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIlJhdGluZ3MgZnJvbSAwIHRvIDEwMCAoUm90dGVuIFRvbWF0b2VzIGFuZCBNZXRhY3JpdGljKSIsDQogICAgICAgeCA9ICJSYXRpbmcgU291cmNlIiwNCiAgICAgICB5ID0gIlJhdGluZyBTY29yZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNCipFeHRyYSBjaGFsbGVuZ2UgMy4qOiBXaGljaCBmaWxtIHN0dWRpb3MgcHJvZHVjZSB0aGUgbW9zdCBhbmQgaG93IGhhcyB0aGlzIGNoYW5nZWQgb3ZlciB0aGUgeWVhcnM/DQpgYGB7ciBjaGFsbGVuZ2UzfQ0KIyBGaXJzdGx5IHdlIG9taXQgdGhlIHByb2R1Y3Rpb25zIHRoYXQgYXJlIG5vdCBwcm9kdWNlZCBieSBhbnkgZmlsbSBzdHVkaW8uIFRoZW4gd2UgZ3JvdXAgdGhlIHJlc3VsdCBieSByZWxlYXNlIHllYXIgYW5kIHByb2R1Y3Rpb24gaG91c2UuIEFnYWluIGZvciBtb3JlIGNsYXJpdHkgb2YgdGhlIGNoYXJ0LCB3ZSBjaG9vc2Ugb25seSA1IHRoZSBtb3N0IHBvcHVsYXIgc3R1ZGlvcy4gVGhlbiB3ZSBwbG90IHRoZSByZXN1bHQgdXNpbmcgZ2VvbV9saW5lLg0KIyBBbHNvIHRvIHNob3cgaG93IG11Y2ggb3ZlcmFsbCBldmVyeSBQcm9kdWN0aW9uIEhvdXNlIGhhcyBjcmVhdGVkIGZpbG1zIGFuZCBUViBzZXJpZXMgd2UganVzdCBncm91cCBieSB0aGUgUHJvZHVjdGlvbi5Ib3VzZSBjb3VudGluZyB0aGUgYXBwZWFyYW5jZXMsIG9mIGNvdXJzZSBiZWZvcmVoYW5kbHkgc2VwYXJhdGluZyB0aGUgUHJvZHVjdGlvbi5Ib3VzZSBjb2x1bW4NCg0KbW92aWVzIDwtIG15ZGF0YSAlPiUNCiAgZmlsdGVyKCFpcy5uYShQcm9kdWN0aW9uLkhvdXNlKSAmIFByb2R1Y3Rpb24uSG91c2UgIT0gIiIpICAlPiUgIA0KICBtdXRhdGUoUmVsZWFzZS5EYXRlID0gbWR5KFJlbGVhc2UuRGF0ZSksICANCiAgICAgICAgIFJlbGVhc2UuWWVhciA9IHllYXIoUmVsZWFzZS5EYXRlKSkgJT4lDQogIHNlcGFyYXRlX3Jvd3MoUHJvZHVjdGlvbi5Ib3VzZSwgc2VwID0gIixcXHMqIikgIA0KDQpwcm9kdWN0aW9uX2NvdW50cyA8LSBtb3ZpZXMgJT4lDQogIGdyb3VwX2J5KFByb2R1Y3Rpb24uSG91c2UsIFJlbGVhc2UuWWVhcikgJT4lDQogIHN1bW1hcmlzZShGaWxtc19Qcm9kdWNlZCA9IG4oKSwgLmdyb3VwcyA9ICdkcm9wJykgIA0KDQp0b3BfcHJvZHVjdGlvbl9ob3VzZXMgPC0gcHJvZHVjdGlvbl9jb3VudHMgJT4lDQogIGdyb3VwX2J5KFByb2R1Y3Rpb24uSG91c2UpICU+JQ0KICBzdW1tYXJpc2UoVG90YWxfRmlsbXMgPSBzdW0oRmlsbXNfUHJvZHVjZWQpKSAlPiUNCiAgdG9wX24oNSwgVG90YWxfRmlsbXMpICU+JQ0KICBwdWxsKFByb2R1Y3Rpb24uSG91c2UpDQoNCmZpbHRlcmVkX3Byb2R1Y3Rpb25fY291bnRzIDwtIHByb2R1Y3Rpb25fY291bnRzICU+JQ0KICBmaWx0ZXIoUHJvZHVjdGlvbi5Ib3VzZSAlaW4lIHRvcF9wcm9kdWN0aW9uX2hvdXNlcykNCg0KZ2dwbG90KGZpbHRlcmVkX3Byb2R1Y3Rpb25fY291bnRzLCBhZXMoeCA9IFJlbGVhc2UuWWVhciwgeSA9IEZpbG1zX1Byb2R1Y2VkLCBjb2xvciA9IFByb2R1Y3Rpb24uSG91c2UpKSArDQogIGdlb21fbGluZSgpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMTk2MCwgMjAyNCkpICsNCiAgbGFicyh0aXRsZSA9ICJGaWxtIFByb2R1Y3Rpb24gT3ZlciB0aGUgWWVhcnMgYnkgVG9wIFN0dWRpb3MiLA0KICAgICAgIHggPSAiWWVhciIsDQogICAgICAgeSA9ICJOdW1iZXIgb2YgRmlsbXMgUHJvZHVjZWQiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQoNCg0KcHJvZHVjdGlvbl90b3RhbHMgPC0gbXlkYXRhICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFByb2R1Y3Rpb24uSG91c2UpICYgUHJvZHVjdGlvbi5Ib3VzZSAhPSAiIikgJT4lDQogIHNlcGFyYXRlX3Jvd3MoUHJvZHVjdGlvbi5Ib3VzZSwgc2VwID0gIixcXHMqIikgJT4lDQogIGdyb3VwX2J5KFByb2R1Y3Rpb24uSG91c2UpICU+JQ0KICBzdW1tYXJpc2UoVG90YWxfUHJvZHVjdGlvbnMgPSBuKCksIC5ncm91cHMgPSAnZHJvcCcpICU+JQ0KICBhcnJhbmdlKGRlc2MoVG90YWxfUHJvZHVjdGlvbnMpKQ0KDQp0b3BfcHJvZHVjdGlvbl9ob3VzZXMgPC0gcHJvZHVjdGlvbl90b3RhbHMgJT4lDQogIHNsaWNlX21heChvcmRlcl9ieSA9IFRvdGFsX1Byb2R1Y3Rpb25zLCBuID0gMTApDQoNCmdncGxvdCh0b3BfcHJvZHVjdGlvbl9ob3VzZXMsIGFlcyh4ID0gcmVvcmRlcihQcm9kdWN0aW9uLkhvdXNlLCBUb3RhbF9Qcm9kdWN0aW9ucyksIHkgPSBUb3RhbF9Qcm9kdWN0aW9ucywgZmlsbCA9IFByb2R1Y3Rpb24uSG91c2UpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGNvb3JkX2ZsaXAoKSArICAjIEZsaXBwaW5nIGNvb3JkaW5hdGVzIGZvciBiZXR0ZXIgcmVhZGFiaWxpdHkgb2YgcHJvZHVjdGlvbiBob3VzZSBuYW1lcw0KICBsYWJzKHRpdGxlID0gIlRvdGFsIFByb2R1Y3Rpb25zIGJ5IFRvcCAxMCBQcm9kdWN0aW9uIEhvdXNlcyIsDQogICAgICAgeCA9ICJQcm9kdWN0aW9uIEhvdXNlIiwNCiAgICAgICB5ID0gIlRvdGFsIFByb2R1Y3Rpb25zIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCmBgYA0KDQoNCg==