Background
The Grammar of Graphics is a language proposed by Leland
Wilkinson for describing statistical graphs.
Wilkinson, L. (2005), The Grammar of Graphics , 2nd ed.,
Springer.
The grammar of graphics has served as the foundation for the graphics
frameworks in SPSS , Vega-Lite and several other
systems.
ggplot2 represents an implementation and extension of
the grammar of graphics for R.
Wickham, H. (2016), ggplot2: Elegant Graphics for Data
Analysis , 2nd ed., Springer. 3rd ed. in progress .
On line documentation: https://ggplot2.tidyverse.org/reference/index.html .
Hadley Wickham, Mine Çetinkaya-Rundel, and Garrett Grolemund (2023),
R for Data Science (2nd
Edition) , O’Reilly.
Data
visualization cheatsheet
Winston Chang (2018), R
Graphics Cookbook , 2nd edition , O’Reilly. (Book source on GitHub )
The idea is that any basic plot can be built out of a combination
of
a data set;
one or more geometrical representation (geoms );
mappings of values to aesthetic features of the
geom;
a stat to produce values to be mapped;
position adjustments;
a coordinate system;
a scale specification;
a faceting scheme.
ggplot2 provides tools for specifying these components
and adjusting their features.
Many components and features are provided by default and do not need
to be specified explicitly unless the defaults are to be changed.
A Basic Template
The simplest graph needs a data set, a geom, and a mapping:
ggplot(data = <DATA>) + <GEOM>(mapping = aes(<MAPPINGS>))
The appearance of geom objects is controlled by aesthetic
features.
Each geom has some required and some optional aesthetics.
For geom_point the required aesthetics are
Optional aesthetics include
color (or colour)
fill
shape
size
geom_point is used to produce a scatter
plot .
Scatter Plots Using geom_point
The mpg data set included in the ggplot2
package includes EPA fuel economy data from 1999 to 2008 for 38 popular
models of cars.
mpg
## # A tibble: 234 × 11
## manufacturer model displ year cyl trans drv cty hwy fl class
## <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
## 1 audi a4 1.8 1999 4 auto… f 18 29 p comp…
## 2 audi a4 1.8 1999 4 manu… f 21 29 p comp…
## 3 audi a4 2 2008 4 manu… f 20 31 p comp…
## 4 audi a4 2 2008 4 auto… f 21 30 p comp…
## 5 audi a4 2.8 1999 6 auto… f 16 26 p comp…
## 6 audi a4 2.8 1999 6 manu… f 18 26 p comp…
## 7 audi a4 3.1 2008 6 auto… f 18 27 p comp…
## 8 audi a4 quattro 1.8 1999 4 manu… 4 18 26 p comp…
## 9 audi a4 quattro 1.8 1999 4 auto… 4 16 25 p comp…
## 10 audi a4 quattro 2 2008 4 manu… 4 20 28 p comp…
## # ℹ 224 more rows
A simple scatter plot:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy))
Map color to vehicle class:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy,
color = class))
And map shape to number of cylinders:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy,
color = class,
shape = factor(cyl)))
Perception:
Too many colors;
shapes are too small;
interference between shapes and colors.
Aesthetics can be mapped to a variable or set to a fixed common
value.
This can be used to override default settings:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy),
color = "blue",
shape = 1)
Changing the size aesthetic makes shapes easier to
recognize:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy,
color = class,
shape = factor(cyl)),
size = 3)
Perception: Still too many colors; still have interference.
Available point shapes are specified by number:
Shapes 1-20 have their color set by the color aesthetic
and ignore the fill aesthetic.
For shapes 21-25 the color aesthetic specifies the
border color and fill specifies the interior
color .
Using shape 21 with cyl mapped to the
fill aesthetic:
ggplot(mutate(mpg, cyl = factor(cyl))) +
geom_point(aes(x = displ,
y = hwy,
fill = cyl),
shape = 21,
size = 4)
Perception: Borders, larger symbols, fewer colors help.
Specifying a new default is very different from specifying a constant
value as an aesthetic.
Constant aesthetic: Rarely what you want:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy,
color = "blue"))
Default: Probably what you want:
ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy),
color = "blue")
Geometric Objects
ggplot2 provides a number of geoms:
geom_abline geom_area geom_bar geom_bin_2d
geom_bin2d geom_blank geom_boxplot geom_col
geom_contour geom_contour_filled geom_count geom_crossbar
geom_curve geom_density geom_density_2d geom_density_2d_filled
geom_density2d geom_density2d_filled geom_dotplot geom_errorbar
geom_errorbarh geom_freqpoly geom_function geom_hex
geom_histogram geom_hline geom_jitter geom_label
geom_line geom_linerange geom_map geom_path
geom_point geom_pointrange geom_polygon geom_qq
geom_qq_line geom_quantile geom_raster geom_rect
geom_ribbon geom_rug geom_segment geom_sf
geom_sf_label geom_sf_text geom_smooth geom_spoke
geom_step geom_text geom_tile geom_violin
geom_vline
Additional geoms are available in packages like ggforce,
ggridges, and others described on the ggplot2
extensions site .
Geoms can be added as layers to a plot.
Mappings common to all, or most, geoms can be specified in the
ggplot call:
ggplot(mpg,
aes(x = displ,
y = hwy)) +
geom_smooth() +
geom_point()
Geoms can also use different data sets.
One way to highlight Europe in a plot of life expectancy against log
income for 2007 is to start with a plot of the full data:
library(dplyr)
library(gapminder)
gm_2007 <- filter(gapminder, year == 2007)
(p <- ggplot(gm_2007, aes(x = gdpPercap,
y = lifeExp)) +
geom_point() +
scale_x_log10())
Then add a layer showing only Europe:
gm_2007_eu <- filter(gm_2007, continent == "Europe")
p + geom_point(data = gm_2007_eu,
color = "red",
size = 3)
Statistical Transformations
All geoms use a statistical transformation (stat ) to convert
raw data to the values to be mapped to the object’s features.
The available stats are
stat_align stat_bin stat_bin_2d
stat_bin_hex stat_bin2d stat_binhex
stat_boxplot stat_connect stat_contour
stat_contour_filled stat_count stat_density
stat_density_2d stat_density_2d_filled stat_density2d
stat_density2d_filled stat_ecdf stat_ellipse
stat_function stat_identity stat_manual
stat_qq stat_qq_line stat_quantile
stat_sf stat_sf_coordinates stat_smooth
stat_spoke stat_sum stat_summary
stat_summary_2d stat_summary_bin stat_summary_hex
stat_summary2d stat_unique stat_ydensity
Each geom has a default stat, and each stat has a default geom.
For geom_point the default stat is
stat_identity.
For geom_bar the default stat is
stat_count.
For geom_histogram the default stat is
stat_bin.
Stats can provide computed variables that can be mapped to
aesthetic features.
For stat_bin some of the computed variables are
count: number of points in bin
density: density of points in bin, scaled to integrate
to 1
The density variable can be accessed as
after_stat(density).
Older approaches that also work but are now discouraged:
stat(density)
..density..
By default, geom_histogram uses
y = after_stat(count).
ggplot(faithful) +
geom_histogram(aes(x = eruptions),
binwidth = 0.25,
fill = "grey",
color = "black")
Explicitly specifying y = after_stat(count) produces the
same plot:
ggplot(faithful) +
geom_histogram(aes(x = eruptions,
y = after_stat(count)),
binwidth = 0.25,
fill = "grey",
color = "black")
Using y = after_stat(density) produces a density scaled
axis.
(p <- ggplot(faithful) +
geom_histogram(aes(x = eruptions,
y = after_stat(density)),
binwidth = 0.25,
fill = "grey",
color = "black"))
stat_function can be used to add a density curve
specified as a mixture of two normal densities:
(ms <- mutate(faithful,
type = ifelse(eruptions < 3,
"short",
"long")) |>
group_by(type) |>
summarize(mean = mean(eruptions),
sd = sd(eruptions),
n = n()) |>
mutate(p = n / sum(n)))
## # A tibble: 2 × 5
## type mean sd n p
## <chr> <dbl> <dbl> <int> <dbl>
## 1 long 4.29 0.411 175 0.643
## 2 short 2.04 0.267 97 0.357
f <- function(x)
ms$p[1] * dnorm(x, ms$mean[1], ms$sd[1]) +
ms$p[2] * dnorm(x, ms$mean[2], ms$sd[2])
p + stat_function(fun = f, color = "red")
Position Adjustments
The available position adjustments:
position_dodge position_dodge2 position_fill
position_identity position_jitter position_jitterdodge
position_nudge position_stack
A bar chart showing the counts for the different cut
categories in the diamonds data:
ggplot(diamonds, aes(x = cut)) +
geom_bar()
Mapping clarity to fill shows the breakdown
by both cut and clarity in a stacked bar
chart :
ggplot(diamonds, aes(x = cut,
fill = clarity)) +
geom_bar()
The default position for bar charts is
position_stack:
ggplot(diamonds, aes(x = cut,
fill = clarity)) +
geom_bar(position = "stack")
position_dodge produces side-by-side bar
charts :
ggplot(diamonds, aes(x = cut,
fill = clarity)) +
geom_bar(position = "dodge")
position_fill rescales all bars to be equal height to
help compare proportions within bars.
ggplot(diamonds, aes(x = cut,
fill = clarity)) +
geom_bar(position = "fill")
Using the counts to scale the widths would produce a spine
plot , a variant of a mosaic plot .
This is easiest to do with the ggmosaic package.
position_jitter can be used with geom_point
to avoid overplotting or break up rounding artifacts.
Another version of the Old Faithful data available as
geyser in package MASS has some rounding in
the duration variable:
data(geyser, package = "MASS")
## Adjust for different meaning of `waiting` variable
geyser2 <- na.omit(mutate(geyser,
duration = lag(duration)))
p <- ggplot(geyser2, aes(x = duration, y = waiting))
p + geom_point()
Jittering can help break up the distracting heaping
of values on durations of 2 and 4 minutes.
The default amount of jittering isn’t quite enough in this case:
p + geom_point(position = "jitter")
To jitter only horizontally and by a larger amount you can use
p + geom_point(position =
position_jitter(height = 0,
width = 0.1))
Coordinate Systems
Coordinate system functions include
coord_cartesian coord_equal coord_fixed coord_flip
coord_map coord_munch coord_polar coord_quickmap
coord_radial coord_sf coord_trans coord_transform
The default coordinate system is coord_cartesian.
Cartesian Coordinates
coord_cartesian can be used to zoom in on a
particular regiion:
p + geom_point() +
coord_cartesian(xlim = c(3, 4))
coord_fixed and coord_equal fix the
aspect ratio for a cartesian coordinate system.
The aspect ratio is the ratio of the number physical display units
per y unit to the number of physical display units per
x unit.
The aspect ratio can be important for recognizing features and
patterns.
river <- scan("https://www.stat.uiowa.edu/~luke/data/river.dat")
r <- data.frame(flow = river, month = seq_along(river))
ggplot(r, aes(x = month, y = flow)) +
geom_point() +
coord_fixed(ratio = 4)
Polar Coordinates
A filled bar chart
(p <- ggplot(diamonds) +
geom_bar(aes(x = 1, fill = cut),
position = "fill"))
is turned into a pie chart by changing to polar coordinates:
p + coord_polar(theta = "y")
Coordinate Systems for Maps
Coordinate systems are particularly important for maps.
Polygons for many political and geographic boundaries are available
through the map_data function.
Boundaries for the lower 48 US states can be obtained as
usa <- map_data("state")
Polygon vertices are encoded by longitude and latitude.
Plotting these in the default cartesian coordinate system usually
does not work well:
usa <- map_data("state")
m <- ggplot(usa, aes(x = long,
y = lat,
group = group)) +
geom_polygon(fill = "white",
color = "black")
m
Using a fixed aspect ratio is better, but an aspect ratio of 1 does
not work well:
m + coord_equal()
The problem is that away from the equator a one degree change in
latitude corresponds to a larger distance than a one degree change in
longitude.
The ratio of one degree longitude separation to one degree latitude
separation for the latitude at the middle of Iowa of 41 degrees is
longlat <- cos(41 / 90 * pi / 2)
longlat
## [1] 0.7547096
A better map is obtained using the aspect ratio
1 / longlat:
m + coord_fixed(1 / longlat)
The best approach is to use a coordinate system designed specifically
for maps.
There are many projections used in map making.
The default projection used by coord_map is the Mercator
projection.
m + coord_map()
Proper map projections are non-linear; this is easier to see with an
Albers projection:
m + coord_map("albers", 20, 50)
Scales
Scales are used for controlling the mapping of values to physical
representations such as colors, shapes, and positions.
Scale functions are also responsible for producing guides
for translating physical representations back to values, such as
axis labels and marks;
color or shape legends.
There are currently 131 scale functions; some examples are
scale_color_gradient scale_shape_manual scale_x_log10
scale_color_manual scale_size_area scale_y_log10
scale_fill_gradient scale_x_sqrt
scale_fill_manual scale_y_sqrt
An experimental tool to
help choosing scales is available.
Start with a basic scatter plot:
(p <- ggplot(mpg, aes(x = displ,
y = hwy)) +
geom_point())
Remove the x tick marks and labels (this can also be
done with theme settings):
p + scale_x_continuous(labels = NULL,
breaks = NULL)
Change the tick locations and labels:
p + scale_x_continuous(labels =
paste(c(2, 4, 6), "ltr"),
breaks = c(2, 4, 6))
Use a logarithmic axis:
p + scale_x_log10(labels = paste(c(2, 4, 6), "ltr"),
breaks = c(2, 4, 6),
minor_breaks = c(3, 5, 7))
The Scales
section in R for Data Science
provides some more details.
Color assignment can also be controlled by scale functions.
For example, for some presidential approval ratings data
pr_appr
## pres appr party year
## 1 Obama 79 D 2009
## 2 Carter 78 D 1977
## 3 Clinton 68 D 1993
## 4 G.W. Bush 65 R 2001
## 5 Reagan 58 R 1981
## 6 G.H.W Bush 56 R 1989
## 7 Trump 40 R 2017
the default color scale is not ideal:
ggplot(pr_appr,
aes(x = appr, y = pres, fill = party)) +
geom_col()
The common assignment of red for Republican and blue for Democrat can
be obtained by
ggplot(pr_appr,
aes(x = appr, y = pres, fill = party)) +
geom_col() +
scale_fill_manual(values
= c(R = "red", D = "blue"))
A better choice is to use a well-designed color palette :
ggplot(pr_appr,
aes(x = appr, y = pres, fill = party)) +
geom_col() +
colorspace::scale_fill_discrete_diverging(
palette = "Blue-Red 2")
Facets
Faceting uses the small multiples approach to introduce
additional variables.
For a single variable facet_wrap is usually used:
p <- ggplot(mpg) +
geom_point(aes(x = displ,
y = hwy))
p + facet_wrap(~ class)
For two variables, each with a modest number of categories,
facet_grid can be effective:
p + facet_grid(factor(cyl) ~ drv)
To show common data in all facets make sure the data does not contain
the faceting variable.
This was used to show muted views of the full data in faceted
plots.
A faceted plot of the gapminder data:
library(gapminder)
years_to_keep <- c(1977, 1987, 1997, 2007)
gd <- filter(gapminder,
year %in% years_to_keep)
ggplot(gd,
aes(x = gdpPercap,
y = lifeExp,
color = continent)) +
geom_point(size = 2.5) +
scale_x_log10() +
facet_wrap(~ year)
Add a muted version of the full data in the background of each
panel:
library(gapminder)
years_to_keep <- c(1977, 1987, 1997, 2007)
gd <- filter(gapminder,
year %in% years_to_keep)
gd_no_year <- mutate(gd, year = NULL)
ggplot(gd,
aes(x = gdpPercap,
y = lifeExp,
color = continent)) +
geom_point(data = gd_no_year,
color = "grey80") +
geom_point(size = 2.5) +
scale_x_log10() +
facet_wrap(~ year)
Usually facets use common axis scales, but one or both can be allowed
to vary.
A useful approach for showing time series data with a good aspect
ratio can be to split the data into facets for non-overlapping portions
of the time axis.
pd <- rep(paste(seq(1, by = 32, length.out = 4),
seq(32, by = 32, length.out = 4),
sep = " - "),
each = 32)
rd <- data.frame(month = seq_along(river),
flow = river,
panel = pd)
ggplot(rd, aes(x = month,
y = flow)) +
geom_point() +
facet_wrap(~ panel,
scale = "free_x", #<<
ncol = 1)
Facet arrangement can also be used to convey other information, such
as geographic location.
The geofacet
package allows facets to be placed in approximate locations of
different geographic regions.
An example for data from US states:
library(geofacet)
ggplot(state_unemp, aes(year, rate)) +
geom_line() +
facet_geo(~ state,
grid = "us_state_grid2",
label = "code") +
scale_x_continuous(labels =
function(x) paste0("'", substr(x, 3, 4))) +
labs(title = "Seasonally Adjusted US Unemployment Rate 2000-2016",
caption = "Data Source: bls.gov",
x = "Year",
y = "Unemployment Rate (%)") +
theme(strip.text.x = element_text(size = 6),
axis.text = element_text(size = 5))
Arrangement according to a calendar can also be useful.
Themes
ggplot2 supports the notion of themes for
adjusting non-data appearance aspects of a plot, such as
Theme elements can be customized in several ways:
theme() can be used to adjust individual elements in
a plot.
theme_set() adjusts default settings for a
session;
pre-defined theme functions allow consistent style
changes.
The full
documentation of the theme function lists many
customizable elements.
One simple example:
ggplot(mutate(mpg, cyl = factor(cyl))) +
geom_point(aes(x = displ,
y = hwy,
fill = cyl),
shape = 21,
size = 3) +
theme(legend.position = "top",
axis.text = element_text(size = 12),
axis.title = element_text(size = 14,
face = "bold"))
Another example:
gthm <-
theme(plot.background =
element_rect(fill = "lightblue",
color = NA),
panel.background =
element_rect(fill = "pink"))
p + gthm
Some alternate complete themes provided by ggplot2
are
theme_bw theme_gray theme_minimal theme_void
theme_classic theme_grey theme_dark theme_light
Some examples:
p_bw <- p + theme_bw() + ggtitle("BW")
p_classic <- p + theme_classic() + ggtitle("Classic")
p_min <- p + theme_minimal() + ggtitle("Minimal")
p_void <- p + theme_void() + ggtitle("Void")
library(patchwork)
(p_bw + p_classic) / (p_min + p_void)
The ggthemes
package provides some additional themes.
Some examples:
library(ggthemes)
p_econ <- p + theme_economist() + ggtitle("Economist")
p_wsj <- p + theme_wsj() + ggtitle("WSJ")
p_tufte <- p + theme_tufte() + ggtitle("Tufte")
p_few <- p + theme_few() + ggtitle("Few")
(p_econ + p_wsj) / (p_tufte + p_few)
ggthemes also provides theme_map that
removes unnecessary elements from maps:
m + coord_map() + theme_map()
The Themes
section in R for Data Science
provides some more details.
A More Complete Template
ggplot(data = <DATA>) +
<GEOM>(mapping = aes(<MAPPINGS>),
stat = <STAT>,
position = <POSITION>) +
< ... MORE GEOMS ... > +
<COORDINATE_ADJUSTMENT> +
<SCALE_ADJUSTMENT> +
<FACETING> +
<THEME_ADJUSTMENT>
Labels and Annotations
A basic plot:
p <- ggplot(mpg, aes(x = displ,
y = hwy))
p1 <- p + geom_point(aes(color = factor(cyl)),
size = 2.5)
p1
Axis labels are based on the expressions given to
aes.
This is convenient for exploration but usually not ideal for a
report.
The labs() function can be used to change axis and
legend labels:
p1 + labs(x = "Displacement (Liters)",
y = "Highway Miles Per Gallon",
color = "Cylinders")
The labs() function can also add a title, subtitle, and
caption:
p2 <- p1 +
labs(x = "Displacement (Liters)",
y = "Highway Miles Per Gallon",
color = "Cylinders",
title = "Gas Mileage and Displacement",
subtitle = paste("For models which had a new release every year",
"between 1999 and 2008"),
caption = "Data Source: https://fueleconomy.gov/")
p2
Annotations can be used to provide popout that draws a viewer’s
attention to particular features.
The annotate() function is one option:
p2 +
annotate("label", x = 2.8, y = 43,
label = "Volkswagens") +
annotate("rect",
xmin = 1.7, xmax = 2.1,
ymin = 40, ymax = 45,
fill = NA, color = "black")
Often more convenient are some geom_mark objects
provided by the ggforce package:
library(ggforce)
p2 +
geom_mark_hull(aes(filter = class == "2seater"),
description =
paste("2-Seaters have high displacement",
"values, but also high fuel efficiency",
"for their displacement.")) +
geom_mark_rect(aes(filter = hwy > 40),
description =
"These are Volkswagens") +
geom_mark_circle(aes(filter = hwy == 12),
description =
"Three pickups and an SUV.")
These annotations can be customized in a number of ways.
Arranging Plots
There are several tools available for assembling ensemble plots.
The patchwork
package is a good choice.
A simple example:
p1 <- ggplot(mpg, aes(x = displ,
y = hwy)) +
geom_point()
p2 <- ggplot(mpg, aes(x = cyl,
y = hwy,
group = cyl)) +
geom_boxplot()
p3 <- ggplot(mpg, aes(x = cyl)) +
geom_bar()
library(patchwork)
(p1 + p2) / p3
Animation
The gganimate
package can be used to add animation to a ggplot graph.
Start with a plot p for all years in the
gapminder data, with year in the
background:
p <- gapminder |>
arrange(desc(pop)) |>
ggplot(aes(x = gdpPercap, y = lifeExp)) +
geom_text(aes(x = 5000, y = 55, label = as.character(year)),
size = 50, color = "grey",
hjust = "center", vjust = "center") +
geom_point(aes(size = pop, fill = continent), shape = 21) +
scale_x_log10(labels = scales::comma) +
ylim(c(20, 85)) +
scale_size_area(max_size = 20,
labels = scales::comma,
breaks = c(0.25 * 10 ^ 9, 0.5 * 10 ^ 9, 10 ^ 9)) +
scale_fill_manual(values = c(Africa = "deepskyblue",
Asia = "red",
Americas = "green",
Europe = "gold",
Oceania = "brown")) +
labs(x = "Income", y = "Life expectancy") +
theme(text = element_text(size = 16)) +
guides(fill = guide_legend(title = "Continent",
override.aes = list(size = 5),
order = 1),
size = guide_legend(title = "Population",
label.hjust = 1,
order = 2)) +
theme_minimal() +
theme(panel.border = element_rect(fill = NA, color = "grey20"))
A GIF
animation:
library(gganimate)
animate(p +
transition_states(
year,
transition_length = 2,
state_length = 0))
A movie:
animate(p +
transition_states(
year,
transition_length = 2,
state_length = 0,
wrap = FALSE),
renderer = ffmpeg_renderer())
Interaction
Plotly
The ggplotly function in the plotly package can be used
to add some interactive features to a plot created with
ggplot2.
In an R session a call to ggplotly() may open a
browser window with the interactive plot.
In an RStudio session the plot appears in the graphics
panel.
In an Rmarkdown document the interactive plot is embedded in the
html file.
Another interactive plotting approach that can be used from R is
described in an Infoworld
article .
A simple example using ggplotly():
library(ggplot2)
library(plotly)
p <- ggplot(mutate(mpg, cyl = factor(cyl))) +
geom_point(aes(x = displ,
y = hwy,
fill = cyl),
shape = 21,
size = 3)
ggplotly(p)
Adding a text aesthetic allows the tooltip display to be
customized:
p <- ggplot(mutate(mpg, cyl = factor(cyl))) +
geom_point(aes(x = displ,
y = hwy,
fill = cyl,
text = paste(year,
manufacturer,
model)),
shape = 21,
size = 3)
ggplotly(p, tooltip = "text") |>
style(hoverlabel = list(bgcolor = "white"))
Ggiraph
The ggiraph
package provides another approach.
library(ggplot2)
library(ggiraph)
p <- ggplot(mutate(mpg, cyl = factor(cyl))) +
geom_point_interactive(
aes(x = displ,
y = hwy,
fill = cyl,
tooltip = paste(year,
manufacturer,
model)),
shape = 21,
size = 3)
girafe(ggobj = p)
Grammar of Interactive Graphics
There have been several efforts to develop a grammar of interactive
graphics, including ggvis and animint ;
neither seems to be under active development at this time.
A promising approach is Vega-Lite , with a Python
interface Altair and an R
interface altair to
the Python interface.
An example using the altair package:
rub <- read.csv(here::here("rubber.csv"))
library(altair)
chartTH <- alt$Chart(rub)$
mark_point()$
encode(x = alt$X("H:Q", scale = alt$Scale(domain = range(rub$H))),
y = alt$Y("T:Q", scale = alt$Scale(domain = range(rub$T))))
brush <- alt$selection_interval()
chartTH_brush <- chartTH$add_selection(brush)
chartTH_selection <-
chartTH_brush$encode(color = alt$condition(brush,
"Origin:N",
alt$value("lightgray")))
chartAT <- chartTH_selection$
encode(x = alt$X("T:Q", scale = alt$Scale(domain = range(rub$T))),
y = alt$Y("A:Q", scale = alt$Scale(domain = range(rub$A))))
chartAT | chartTH_selection
The resulting linked plots:
## Error importing Altair python package:
##
## ModuleNotFoundError: No module named 'altair'
## Run `reticulate::py_last_error()` for details.
##
## Output from reticulate::py_config():
## python: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz/bin/python
## libpython: /home/luke/.cache/R/reticulate/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/libpython3.12.so
## pythonhome: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz:/home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz
## virtualenv: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz/bin/activate_this.py
## version: 3.12.12 (main, Dec 17 2025, 21:10:06) [Clang 21.1.4 ]
## numpy: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz/lib/python3.12/site-packages/numpy
## numpy_version: 2.4.2
## altair: [NOT FOUND]
##
## NOTE: Python version was forced by py_require()
## Error:
## ! Error loading Python module altair
## Error importing Altair python package:
##
## ModuleNotFoundError: No module named 'altair'
## Run `reticulate::py_last_error()` for details.
##
## Output from reticulate::py_config():
## python: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz/bin/python
## libpython: /home/luke/.cache/R/reticulate/uv/python/cpython-3.12.12-linux-x86_64-gnu/lib/libpython3.12.so
## pythonhome: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz:/home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz
## virtualenv: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz/bin/activate_this.py
## version: 3.12.12 (main, Dec 17 2025, 21:10:06) [Clang 21.1.4 ]
## numpy: /home/luke/.cache/R/reticulate/uv/cache/archive-v0/L55B6HpO160mlLsIjpHUz/lib/python3.12/site-packages/numpy
## numpy_version: 2.4.2
## altair: [NOT FOUND]
##
## NOTE: Python version was forced by py_require()
## Error:
## ! Error loading Python module altair
## Error:
## ! object 'chartTH' not found
## Error:
## ! object 'chartTH_brush' not found
## Error:
## ! object 'chartTH_selection' not found
## Error:
## ! object 'chartAT' not found
Interactive Tutorial
An interactive learnr
tutorial for these notes is available .
You can run the tutorial with
STAT4580::runTutorial("ggplot")
You can install the current version of the STAT4580
package with
remotes::install_gitlab("luke-tierney/STAT4580")
You may need to install the remotes package from CRAN
first.
Exercises
In the following expression, which value of the shape
aesthetic produces a plot with points represented as triangles outlined
in black colored according to the number of cylinders?
```r
library(ggplot2)
ggplot(mpg, aes(x = displ, y = hwy, fill = factor(cyl))) +
geom_point(size = 4, shape = ---)
```
a. 15
b. 17
c. 21
d. 24
It can sometimes be useful to plot text labels in a scatterplot
instead of points. Consider the plot set up as
library(ggplot2)
library(dplyr)
data(gapminder, package = "gapminder")
p <- filter(gapminder, year == 2007) |>
group_by(continent) |>
summarize(gdpPercap = mean(gdpPercap), lifeExp = mean(lifeExp)) |>
ggplot(aes(x = gdpPercap, y = lifeExp))
Which of the following produces a plot with continent names on white
rectangles?
p + geom_text(aes(label = continent))
p + geom_label(aes(label = continent))
p + geom_label(label = continent)
p + geom_text(text = continent)
The following code plots a kernel density estimate for
the eruptions variable in the faithful data
set:
library(ggplot2)
ggplot(faithful, aes(x = eruptions)) + geom_density(bw = 0.1)
Look at the help page for geom_density. Which of the
following best describes what specifying a value for bw
does:
Changes the kernel used to construct the estimate.
Changes the smoothing bandwidth to make the result more or
less smooth.
Changes the stat used to stat_bw.
Has no effect on the retult.
This code creates a map of Iowa counties.
library(ggplot2)
p <- ggplot(map_data("county", "iowa"),
aes(x = long, y = lat, group = group)) +
geom_polygon(, fill = "White", color = "black")
Which of these produces a plot with an aspect ratio that best matches
the map on this
page ?
p + coord_fixed(0.5)
p + coord_fixed(0.75)
p + coord_fixed(1.35)
p + coord_fixed(1.95)
Consider the two plots created by this code (print the values of
p1 and p2 to see the plots):
library(ggplot2)
data(gapminder, package = "gapminder")
p1 <- ggplot(gapminder, aes(x = log(gdpPercap), y = lifeExp)) +
geom_point() +
scale_x_continuous(name = "")
p2 <- ggplot(gapminder, aes(x = gdpPercap, y = lifeExp)) +
geom_point() +
scale_x_log10(labels = scales::comma, name = "")
Which of these statements is true?
The x axis labels are identical in both plots.
The x axis labels in p2 are in dollars;
the labels in p1 are in log dollars.
The x axis labels in p1 are in dollars;
the labels in p2 are in log dollars.
There are no labels on the x axis in
p2.
Consider the plot created by
library(ggplot2)
data(gapminder, package = "gapminder")
p <- ggplot(gapminder, aes(x = gdpPercap, y = lifeExp)) +
geom_point() +
scale_x_log10(labels = scales::comma)
Which of these expressions produces a plot with a white
background?
p
p + theme_grey()
p + theme_classic()
p + ggthemes::theme_economist()
There are many different ways to change the x axis
label in ggplot. Consider the plot created by
library(ggplot2)
p <- ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point()
Which of the following does not change the
x axis label to Displacement ?
p + labs(x = "Displacement")
p + scale_x_continuous("Displacement")
p + xlab("Displacement")
p + theme(axis.title.x = "Displacement")
LS0tCnRpdGxlOiAiVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCjxsaW5rIHJlbD0ic3R5bGVzaGVldCIgaHJlZj0ic3RhdDQ1ODAuY3NzIiB0eXBlPSJ0ZXh0L2NzcyIgLz4KCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9CnNvdXJjZShoZXJlOjpoZXJlKCJzZXR1cC5SIikpCmtuaXRyOjpvcHRzX2NodW5rJHNldChjb2xsYXBzZSA9IFRSVUUsIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSA1LCBmaWcud2lkdGggPSA2LCBmaWcuYWxpZ24gPSAiY2VudGVyIikKCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsYXR0aWNlKQpsaWJyYXJ5KGdyaWRFeHRyYSkKc2V0LnNlZWQoMTIzNDUpCmBgYAoKCiMjIEJhY2tncm91bmQKClRoZSBfR3JhbW1hciBvZiBHcmFwaGljc18gaXMgYSBsYW5ndWFnZSBwcm9wb3NlZCBieSBMZWxhbmQgV2lsa2luc29uCmZvciBkZXNjcmliaW5nIHN0YXRpc3RpY2FsIGdyYXBocy4KCj4gV2lsa2luc29uLCBMLiAoMjAwNSksIF9UaGUgR3JhbW1hciBvZiBHcmFwaGljc18sIDJuZCBlZC4sIFNwcmluZ2VyLgoKVGhlIGdyYW1tYXIgb2YgZ3JhcGhpY3MgaGFzIHNlcnZlZCBhcyB0aGUgZm91bmRhdGlvbiBmb3IgdGhlIGdyYXBoaWNzCmZyYW1ld29ya3MgaW4gW1NQU1NdKGh0dHBzOi8vd3d3LmlibS5jb20vcHJvZHVjdHMvc3Bzcy1zdGF0aXN0aWNzKSwKW1ZlZ2EtTGl0ZV0oaHR0cHM6Ly92ZWdhLmdpdGh1Yi5pby92ZWdhLWxpdGUvKSBhbmQgc2V2ZXJhbCBvdGhlcgpzeXN0ZW1zLgoKYGdncGxvdDJgIHJlcHJlc2VudHMgYW4gaW1wbGVtZW50YXRpb24gYW5kIGV4dGVuc2lvbiBvZiB0aGUgZ3JhbW1hcgpvZiBncmFwaGljcyBmb3IgUi4KCj4gV2lja2hhbSwgSC4gKDIwMTYpLCBfZ2dwbG90MjogRWxlZ2FudCBHcmFwaGljcyBmb3IgRGF0YSBBbmFseXNpc18sCj4gMm5kIGVkLiwgU3ByaW5nZXIuIFszcmQgZWQuIGluIHByb2dyZXNzXShodHRwczovL2dncGxvdDItYm9vay5vcmcvKS4KCj4gT24gbGluZSBkb2N1bWVudGF0aW9uOiA8aHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWw+LgoKPiBIYWRsZXkgV2lja2hhbSwgTWluZSDDh2V0aW5rYXlhLVJ1bmRlbCwgYW5kIEdhcnJldHQgR3JvbGVtdW5kICgyMDIzKSwKPiBbX1IgZm9yIERhdGEgU2NpZW5jZSAoMm5kIEVkaXRpb24pX10oaHR0cHM6Ly9yNGRzLmhhZGxleS5uei8pLAo+IE8nUmVpbGx5LgoKPiBbRGF0YSB2aXN1YWxpemF0aW9uIGNoZWF0c2hlZXRdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vZGF0YS12aXN1YWxpemF0aW9uLnBkZikKCj4gV2luc3RvbiBDaGFuZyAoMjAxOCksIFtfUiBHcmFwaGljcyBDb29rYm9va18sIDJuZAo+IGVkaXRpb25dKGh0dHBzOi8vci1ncmFwaGljcy5vcmcvKSwgT+KAmVJlaWxseS4gKFtCb29rIHNvdXJjZSBvbgo+IEdpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3djaC9yZ2Nvb2tib29rKSkKClRoZSBpZGVhIGlzIHRoYXQgYW55IGJhc2ljIHBsb3QgY2FuIGJlIGJ1aWx0IG91dCBvZiBhIGNvbWJpbmF0aW9uIG9mCgoqIGEgZGF0YSBzZXQ7CgoqIG9uZSBvciBtb3JlIGdlb21ldHJpY2FsIHJlcHJlc2VudGF0aW9uIChfZ2VvbXNfKTsKCiogbWFwcGluZ3Mgb2YgdmFsdWVzIHRvIF9hZXN0aGV0aWNfIGZlYXR1cmVzIG9mIHRoZSBnZW9tOwoKKiBhIF9zdGF0XyB0byBwcm9kdWNlIHZhbHVlcyB0byBiZSBtYXBwZWQ7CgoqIHBvc2l0aW9uIGFkanVzdG1lbnRzOwoKKiBhIGNvb3JkaW5hdGUgc3lzdGVtOwoKKiBhIHNjYWxlIHNwZWNpZmljYXRpb247CgoqIGEgZmFjZXRpbmcgc2NoZW1lLgoKYGdncGxvdDJgIHByb3ZpZGVzIHRvb2xzIGZvciBzcGVjaWZ5aW5nIHRoZXNlIGNvbXBvbmVudHMgYW5kIGFkanVzdGluZwp0aGVpciBmZWF0dXJlcy4KCk1hbnkgY29tcG9uZW50cyBhbmQgZmVhdHVyZXMgYXJlIHByb3ZpZGVkIGJ5IGRlZmF1bHQgYW5kIGRvIG5vdCBuZWVkCnRvIGJlIHNwZWNpZmllZCBleHBsaWNpdGx5IHVubGVzcyB0aGUgZGVmYXVsdHMgYXJlIHRvIGJlIGNoYW5nZWQuCgoKIyMgQSBCYXNpYyBUZW1wbGF0ZQoKVGhlIHNpbXBsZXN0IGdyYXBoIG5lZWRzIGEgZGF0YSBzZXQsIGEgZ2VvbSwgYW5kIGEgbWFwcGluZzoKCmBgYHIKZ2dwbG90KGRhdGEgPSA8REFUQT4pICsgPEdFT00+KG1hcHBpbmcgPSBhZXMoPE1BUFBJTkdTPikpCmBgYAoKVGhlIGFwcGVhcmFuY2Ugb2YgZ2VvbSBvYmplY3RzIGlzIGNvbnRyb2xsZWQgYnkgX2Flc3RoZXRpY18gZmVhdHVyZXMuCgpFYWNoIGdlb20gaGFzIHNvbWUgcmVxdWlyZWQgYW5kIHNvbWUgb3B0aW9uYWwgYWVzdGhldGljcy4KCkZvciBgZ2VvbV9wb2ludGAgdGhlIHJlcXVpcmVkIGFlc3RoZXRpY3MgYXJlCgoqIGB4YCBwb3NpdGlvbgoqIGB5YCBwb3NpdGlvbi4KCk9wdGlvbmFsIGFlc3RoZXRpY3MgaW5jbHVkZQoKKiBgY29sb3JgIChvciBgY29sb3VyYCkKKiBgZmlsbGAKKiBgc2hhcGVgCiogYHNpemVgCgpgZ2VvbV9wb2ludGAgaXMgdXNlZCB0byBwcm9kdWNlIGEgX3NjYXR0ZXIgcGxvdF8uCgoKIyMgU2NhdHRlciBQbG90cyBVc2luZyBgZ2VvbV9wb2ludGAKClRoZSBgbXBnYCBkYXRhIHNldCBpbmNsdWRlZCBpbiB0aGUgYGdncGxvdDJgIHBhY2thZ2UgaW5jbHVkZXMgRVBBCmZ1ZWwgZWNvbm9teSBkYXRhIGZyb20gMTk5OSB0byAyMDA4IGZvciAzOCBwb3B1bGFyIG1vZGVscyBvZiBjYXJzLgoKYGBge3J9Cm1wZwpgYGAKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9CmZpZ19hbGlnbiA8LSBpZiAodXNpbmdfeGFyaW5nYW4pICJsZWZ0IiBlbHNlICJjZW50ZXIiCmBgYAoKQSBzaW1wbGUgc2NhdHRlciBwbG90OgoKYGBge3IgbXBnLXBsYWluLCBldmFsID0gRkFMU0V9CmdncGxvdChtcGcpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSBkaXNwbCwKICAgICAgICAgICAgICAgICAgIHkgPSBod3kpKQpgYGAKCmBgYHtyIG1wZy1wbGFpbiwgZWNobyA9IEZBTFNFLCBmaWcud2lkdGggPSA1Ljc1LCBmaWcuYWxpZ24gPSBmaWdfYWxpZ259CmBgYAoKTWFwIGNvbG9yIHRvIHZlaGljbGUgY2xhc3M6CgpgYGB7ciBtcGctY29sb3IsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KG1wZykgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICAgICAgICAgeSA9IGh3eSwKICAgICAgICAgICAgICAgICAgIGNvbG9yID0gY2xhc3MpKQpgYGAKCmBgYHtyIG1wZy1jb2xvciwgZWNobyA9IEZBTFNFLCBmaWcud2lkdGggPSA3LCBmaWcuYWxpZ24gPSBmaWdfYWxpZ259CmBgYAoKQW5kIG1hcCBzaGFwZSB0byBudW1iZXIgb2YgY3lsaW5kZXJzOgoKYGBge3IgbXBnLWNvbG9yLXNoYXBlLCBldmFsID0gRkFMU0V9CmdncGxvdChtcGcpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSBkaXNwbCwKICAgICAgICAgICAgICAgICAgIHkgPSBod3ksCiAgICAgICAgICAgICAgICAgICBjb2xvciA9IGNsYXNzLAogICAgICAgICAgICAgICAgICAgc2hhcGUgPSBmYWN0b3IoY3lsKSkpCmBgYAoKYGBge3IgbXBnLWNvbG9yLXNoYXBlLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcsIGZpZy5hbGlnbiA9IGZpZ19hbGlnbn0KYGBgCgo8IS0tIC0tPiBQZXJjZXB0aW9uOgoKKiBUb28gbWFueSBjb2xvcnM7Ciogc2hhcGVzIGFyZSB0b28gc21hbGw7CiogaW50ZXJmZXJlbmNlIGJldHdlZW4gc2hhcGVzIGFuZCBjb2xvcnMuCgpBZXN0aGV0aWNzIGNhbiBiZSBtYXBwZWQgdG8gYSB2YXJpYWJsZSBvciBzZXQgdG8gYSBmaXhlZCBjb21tb24gdmFsdWUuCgpUaGlzIGNhbiBiZSB1c2VkIHRvIG92ZXJyaWRlIGRlZmF1bHQgc2V0dGluZ3M6CgpgYGB7ciBtcGctZml4ZWQsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KG1wZykgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICAgICAgICAgeSA9IGh3eSksCiAgICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLAogICAgICAgICAgICAgICBzaGFwZSA9IDEpCmBgYAoKYGBge3IgbXBnLWZpeGVkLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDcsIGZpZy5hbGlnbiA9IGZpZ19hbGlnbn0KYGBgCgpDaGFuZ2luZyB0aGUgYHNpemVgIGFlc3RoZXRpYyBtYWtlcyBzaGFwZXMgZWFzaWVyIHRvIHJlY29nbml6ZToKCmBgYHtyIG1wZy1jb2xvci1zaGFwZS1sYXJnZSwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QobXBnKSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgICAgICAgICB5ID0gaHd5LAogICAgICAgICAgICAgICAgICAgY29sb3IgPSBjbGFzcywKICAgICAgICAgICAgICAgICAgIHNoYXBlID0gZmFjdG9yKGN5bCkpLAogICAgICAgICAgICAgICBzaXplID0gMykKYGBgCgpgYGB7ciBtcGctY29sb3Itc2hhcGUtbGFyZ2UsIGVjaG8gPSBGQUxTRSwgZmlnLndpZHRoID0gNywgZmlnLmFsaWduID0gZmlnX2FsaWdufQpgYGAKCjwhLS0gLS0+IFBlcmNlcHRpb246IFN0aWxsIHRvbyBtYW55IGNvbG9yczsgc3RpbGwgaGF2ZSBpbnRlcmZlcmVuY2UuCgpBdmFpbGFibGUgcG9pbnQgc2hhcGVzIGFyZSBzcGVjaWZpZWQgYnkgbnVtYmVyOgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IEZBTFNFfQpnZW5lcmF0ZVJQb2ludFNoYXBlcyA8LSBmdW5jdGlvbigpIHsKICAgIG9sZFBhciA8LSBwYXIoKQogICAgcGFyKGZvbnQgPSAyLCBtYXIgPSBjKDAuNSwgMCwgMCwgMCkpCiAgICB5IDwtIHJldihjKHJlcCgxLCA2KSwgcmVwKDIsIDUpLCByZXAoMywgNSksIHJlcCg0LCA1KSwgcmVwKDUsIDUpKSkKICAgIHggPC0gYyhyZXAoMSA6IDUsIDUpLCA2KQogICAgcGxvdCh4LCB5LCBwY2ggPSAwIDogMjUsIGNleCA9IDEuNSwgeWxpbSA9IGMoMSwgNS41KSwgeGxpbSA9IGMoMSwgNi41KSwKICAgICAgICAgYXhlcyA9IEZBTFNFLCB4bGFiID0gIiIsIHlsYWIgPSAiIiwgYmcgPSAiYmx1ZSIpCiAgICB0ZXh0KHgsIHksIGxhYmVscyA9IDAgOiAyNSwgcG9zID0gMykKICAgIHBhcihtYXIgPSBvbGRQYXIkbWFyLCBmb250ID0gb2xkUGFyJGZvbnQpCn0KZ2VuZXJhdGVSUG9pbnRTaGFwZXMoKQpgYGAKYGBge3IsIGVjaG8gPSBGQUxTRX0KZ2dwbG90KE5VTEwsIGFlcyh4ID0gcmVwKDEgOiA1LCA1KSwgeSA9IHJldihyZXAoMSA6IDUsIGVhY2ggPSA1KSkpKSArCiAgICBnZW9tX3BvaW50KHNoYXBlID0gMSA6IDI1LCBzaXplID0gNSwgZmlsbCA9ICJibHVlIikgKwogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IDEgOiAyNSksIG51ZGdlX3kgPSAwLjI1LCBzaXplID0gNikgKwogICAgdGhlbWVfdm9pZCgpCmBgYAoKU2hhcGVzIDEtMjAgaGF2ZSB0aGVpciBjb2xvciBzZXQgYnkgdGhlIGBjb2xvcmAgYWVzdGhldGljIGFuZCBpZ25vcmUKdGhlIGBmaWxsYCBhZXN0aGV0aWMuCgpGb3Igc2hhcGVzIDIxLTI1IHRoZSBgY29sb3JgIGFlc3RoZXRpYyBzcGVjaWZpZXMgdGhlIF9ib3JkZXIgY29sb3JfIGFuZApgZmlsbGAgc3BlY2lmaWVzIHRoZSBfaW50ZXJpb3IgY29sb3JfLgoKVXNpbmcgYHNoYXBlYCAyMSB3aXRoIGBjeWxgIG1hcHBlZCB0byB0aGUgYGZpbGxgIGFlc3RoZXRpYzoKCmBgYHtyIG1wZy1maWxsLTIxLCBldmFsID0gRkFMU0V9CmdncGxvdChtdXRhdGUobXBnLCBjeWwgPSBmYWN0b3IoY3lsKSkpICsKICAgIGdlb21fcG9pbnQoYWVzKHggPSBkaXNwbCwKICAgICAgICAgICAgICAgICAgIHkgPSBod3ksCiAgICAgICAgICAgICAgICAgICBmaWxsID0gY3lsKSwKICAgICAgICAgICAgICAgc2hhcGUgPSAyMSwKICAgICAgICAgICAgICAgc2l6ZSA9IDQpCmBgYApgYGB7ciBtcGctZmlsbC0yMSwgZWNobyA9IEZBTFNFfQpgYGAKCjwhLS0gLS0+IFBlcmNlcHRpb246IEJvcmRlcnMsIGxhcmdlciBzeW1ib2xzLCBmZXdlciBjb2xvcnMgaGVscC4KClNwZWNpZnlpbmcgYSBuZXcgZGVmYXVsdCBpcyB2ZXJ5IGRpZmZlcmVudCBmcm9tIHNwZWNpZnlpbmcgYSBjb25zdGFudAp2YWx1ZSBhcyBhbiBhZXN0aGV0aWMuCgpDb25zdGFudCBhZXN0aGV0aWM6IFJhcmVseSB3aGF0IHlvdSB3YW50OgoKYGBge3IgbXBnLWJhZC1jb2xvciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QobXBnKSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgICAgICAgICB5ID0gaHd5LAogICAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIpKQpgYGAKCmBgYHtyIG1wZy1iYWQtY29sb3IsIGVjaG8gPSBGQUxTRSwgZmlnLmhlaWdodCA9IDQuMn0KYGBgCgpEZWZhdWx0OiBQcm9iYWJseSB3aGF0IHlvdSB3YW50OgpgYGB7ciBtcGctZ29vZC1jb2xvciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QobXBnKSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgICAgICAgICB5ID0gaHd5KSwKICAgICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIpCmBgYAoKYGBge3IgbXBnLWdvb2QtY29sb3IsIGVjaG8gPSBGQUxTRSwgZmlnLmhlaWdodCA9IDQuMn0KYGBgCgoKIyMgR2VvbWV0cmljIE9iamVjdHMKCmBnZ3Bsb3QyYCBwcm92aWRlcyBhIG51bWJlciBvZiBnZW9tczoKCmBgYHtyLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAiYXNpcyJ9CnNob3dMaXN0IDwtIGZ1bmN0aW9uKHYsIG5jb2wgPSA0LCBwYWQgPSAyKSB7CiAgICB3IDwtIG1heChuY2hhcih2KSkgKyBwYWQKICAgIG5yb3cgPC0gY2VpbGluZyhsZW5ndGgodikgLyBuY29sKQogICAgdiA8LSBjKHYsIGNoYXJhY3RlcihuY29sICogbnJvdyAtIGxlbmd0aCh2KSkpCgogICAgY2F0KCJgYGByXG4iKQogICAgZm9yIChpIGluIHNlcV9sZW4obnJvdykpIHsKICAgICAgICBsaW5lIDwtIHZbbmNvbCAqIChpIC0gMSkgKyAoMSA6IG5jb2wpXQogICAgICAgIGZvciAoaiBpbiAxIDogbmNvbCkKICAgICAgICAgICAgaWYgKGogPCBuY29sKQogICAgICAgICAgICAgICAgY2F0KHNwcmludGYoIiUtKnMiLCB3LCBsaW5lW2pdKSkKICAgICAgICAgICAgZWxzZQogICAgICAgICAgICAgICAgY2F0KHNwcmludGYoIiVzXG4iLCBsaW5lW2pdKSkKICAgICAgICAjIyBjYXQoc3ByaW50ZigiJS0qcyUtKnMlLSpzJXNcbiIsCiAgICAgICAgIyMgICAgICAgICAgICAgdywgbGluZVsxXSwgdywgbGluZVsyXSwgdywgbGluZVszXSwgbGluZVs0XSkpCiAgICB9CiAgICBjYXQoImBgYFxuIikKfQpzaG93TGlzdChscygicGFja2FnZTpnZ3Bsb3QyIiwgcGF0ID0gIl5nZW9tXyIpKQpgYGAKCkFkZGl0aW9uYWwgZ2VvbXMgYXJlIGF2YWlsYWJsZSBpbiBwYWNrYWdlcyBsaWtlIGBnZ2ZvcmNlYCwgYGdncmlkZ2VzYCwKYW5kIG90aGVycyBkZXNjcmliZWQgb24gdGhlIFtgZ2dwbG90MmAgZXh0ZW5zaW9ucwpzaXRlXShodHRwczovL2V4dHMuZ2dwbG90Mi50aWR5dmVyc2Uub3JnLykuCgpHZW9tcyBjYW4gYmUgYWRkZWQgYXMgX2xheWVyc18gdG8gYSBwbG90LgoKTWFwcGluZ3MgY29tbW9uIHRvIGFsbCwgb3IgbW9zdCwgZ2VvbXMgY2FuIGJlIHNwZWNpZmllZCBpbiB0aGUKYGdncGxvdGAgY2FsbDoKCmBgYHtyIG1wZy1zbW9vdGgsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KG1wZywKICAgICAgIGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgeSA9IGh3eSkpICsKICAgIGdlb21fc21vb3RoKCkgKwogICAgZ2VvbV9wb2ludCgpCmBgYAoKYGBge3IgbXBnLXNtb290aCwgZWNobyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmBgYAoKR2VvbXMgY2FuIGFsc28gdXNlIGRpZmZlcmVudCBkYXRhIHNldHMuCgpPbmUgd2F5IHRvIGhpZ2hsaWdodCBFdXJvcGUgaW4gYSBwbG90IG9mIGxpZmUgZXhwZWN0YW5jeSBhZ2FpbnN0IGxvZwppbmNvbWUgZm9yIDIwMDcgaXMgdG8gc3RhcnQgd2l0aCBhIHBsb3Qgb2YgdGhlIGZ1bGwgZGF0YToKCmBgYHtyIGdtXzIwMDcsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShkcGx5cikKbGlicmFyeShnYXBtaW5kZXIpCmdtXzIwMDcgPC0gZmlsdGVyKGdhcG1pbmRlciwgeWVhciA9PSAyMDA3KQoKKHAgPC0gZ2dwbG90KGdtXzIwMDcsIGFlcyh4ID0gZ2RwUGVyY2FwLAogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBsaWZlRXhwKSkgKwogICAgIGdlb21fcG9pbnQoKSArCiAgICAgc2NhbGVfeF9sb2cxMCgpKQpgYGAKCmBgYHtyIGdtXzIwMDcsIGVjaG8gPSBGQUxTRX0KYGBgCgpUaGVuIGFkZCBhIGxheWVyIHNob3dpbmcgb25seSBFdXJvcGU6CgpgYGB7ciBnbV8yMDA3X2V1LCBldmFsID0gRkFMU0V9CmdtXzIwMDdfZXUgPC0gZmlsdGVyKGdtXzIwMDcsIGNvbnRpbmVudCA9PSAiRXVyb3BlIikKCnAgKyBnZW9tX3BvaW50KGRhdGEgPSBnbV8yMDA3X2V1LAogICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLAogICAgICAgICAgICAgICBzaXplID0gMykKYGBgCgpgYGB7ciBnbV8yMDA3X2V1LCBlY2hvID0gRkFMU0V9CmBgYAoKCiMjIFN0YXRpc3RpY2FsIFRyYW5zZm9ybWF0aW9ucwoKQWxsIGdlb21zIHVzZSBhIHN0YXRpc3RpY2FsIHRyYW5zZm9ybWF0aW9uIChfc3RhdF8pIHRvIGNvbnZlcnQgcmF3CmRhdGEgdG8gdGhlIHZhbHVlcyB0byBiZSBtYXBwZWQgdG8gdGhlIG9iamVjdCdzIGZlYXR1cmVzLgoKVGhlIGF2YWlsYWJsZSBzdGF0cyBhcmUKCmBgYHtyLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAiYXNpcyJ9CnNob3dMaXN0KGxzKCJwYWNrYWdlOmdncGxvdDIiLCBwYXQgPSAiXnN0YXRfIiksIG5jb2wgPSAzKQpgYGAKCkVhY2ggZ2VvbSBoYXMgYSBkZWZhdWx0IHN0YXQsIGFuZCBlYWNoIHN0YXQgaGFzIGEgZGVmYXVsdCBnZW9tLgoKKiBGb3IgYGdlb21fcG9pbnRgIHRoZSBkZWZhdWx0IHN0YXQgaXMgYHN0YXRfaWRlbnRpdHlgLgoKKiBGb3IgYGdlb21fYmFyYCB0aGUgZGVmYXVsdCBzdGF0IGlzIGBzdGF0X2NvdW50YC4KCiogRm9yIGBnZW9tX2hpc3RvZ3JhbWAgdGhlIGRlZmF1bHQgc3RhdCBpcyBgc3RhdF9iaW5gLgoKU3RhdHMgY2FuIHByb3ZpZGUgX2NvbXB1dGVkIHZhcmlhYmxlc18gdGhhdCBjYW4gYmUgbWFwcGVkIHRvIGFlc3RoZXRpYwpmZWF0dXJlcy4KCkZvciBgc3RhdF9iaW5gIHNvbWUgb2YgdGhlIGNvbXB1dGVkIHZhcmlhYmxlcyBhcmUKCiogYGNvdW50YDogbnVtYmVyIG9mIHBvaW50cyBpbiBiaW4KKiBgZGVuc2l0eWA6IGRlbnNpdHkgb2YgcG9pbnRzIGluIGJpbiwgc2NhbGVkIHRvIGludGVncmF0ZSB0byAxCgpUaGUgYGRlbnNpdHlgIHZhcmlhYmxlIGNhbiBiZSBhY2Nlc3NlZCBhcyBgYWZ0ZXJfc3RhdChkZW5zaXR5KWAuCgpPbGRlciBhcHByb2FjaGVzIHRoYXQgYWxzbyB3b3JrIGJ1dCBhcmUgbm93IGRpc2NvdXJhZ2VkOgoKKiBgc3RhdChkZW5zaXR5KWAKKiBgLi5kZW5zaXR5Li5gCgpCeSBkZWZhdWx0LCBgZ2VvbV9oaXN0b2dyYW1gIHVzZXMgYHkgPSBhZnRlcl9zdGF0KGNvdW50KWAuCgpgYGB7ciBnZXlzZXItY291bnQsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGZhaXRoZnVsKSArCiAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGVydXB0aW9ucyksCiAgICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDAuMjUsCiAgICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLAogICAgICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKQpgYGAKYGBge3IgZ2V5c2VyLWNvdW50LCBlY2hvID0gRkFMU0V9CmBgYAoKRXhwbGljaXRseSBzcGVjaWZ5aW5nIGB5ID0gYWZ0ZXJfc3RhdChjb3VudClgIHByb2R1Y2VzIHRoZSBzYW1lIHBsb3Q6CgpgYGB7ciBnZXlzZXItY291bnQtZXhwLCBldmFsID0gRkFMU0V9CmdncGxvdChmYWl0aGZ1bCkgKwogICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBlcnVwdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgeSA9IGFmdGVyX3N0YXQoY291bnQpKSwKICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4yNSwKICAgICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleSIsCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpCmBgYApgYGB7ciBnZXlzZXItY291bnQtZXhwLCBlY2hvID0gRkFMU0V9CmBgYAoKVXNpbmcgYHkgPSBhZnRlcl9zdGF0KGRlbnNpdHkpYCBwcm9kdWNlcyBhIGRlbnNpdHkgc2NhbGVkIGF4aXMuCgpgYGB7ciBnZXlzZXItZGVuc2l0eSwgZXZhbCA9IEZBTFNFfQoocCA8LSBnZ3Bsb3QoZmFpdGhmdWwpICsKICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGVydXB0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGFmdGVyX3N0YXQoZGVuc2l0eSkpLAogICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4yNSwKICAgICAgICAgICAgICAgICAgICBmaWxsID0gImdyZXkiLAogICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikpCmBgYApgYGB7ciBnZXlzZXItZGVuc2l0eSwgZWNobyA9IEZBTFNFfQpgYGAKCmBzdGF0X2Z1bmN0aW9uYCBjYW4gYmUgdXNlZCB0byBhZGQgYSBkZW5zaXR5IGN1cnZlIHNwZWNpZmllZCBhcyBhCm1peHR1cmUgb2YgdHdvIG5vcm1hbCBkZW5zaXRpZXM6CgpgYGB7cn0KKG1zIDwtIG11dGF0ZShmYWl0aGZ1bCwKICAgICAgICAgICAgICB0eXBlID0gaWZlbHNlKGVydXB0aW9ucyA8IDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAic2hvcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgImxvbmciKSkgfD4KICAgICBncm91cF9ieSh0eXBlKSB8PgogICAgIHN1bW1hcml6ZShtZWFuID0gbWVhbihlcnVwdGlvbnMpLAogICAgICAgICAgICAgICBzZCA9IHNkKGVydXB0aW9ucyksCiAgICAgICAgICAgICAgIG4gPSBuKCkpIHw+CiAgICAgbXV0YXRlKHAgPSBuIC8gc3VtKG4pKSkKYGBgCgpgYGB7ciBnZXlzZXItaGlzdC1kZW5zLCBldmFsID0gRkFMU0V9CmYgPC0gZnVuY3Rpb24oeCkKICAgIG1zJHBbMV0gKiBkbm9ybSh4LCBtcyRtZWFuWzFdLCBtcyRzZFsxXSkgKwogICAgICAgIG1zJHBbMl0gKiBkbm9ybSh4LCBtcyRtZWFuWzJdLCBtcyRzZFsyXSkKCnAgKyBzdGF0X2Z1bmN0aW9uKGZ1biA9IGYsIGNvbG9yID0gInJlZCIpCmBgYApgYGB7ciBnZXlzZXItaGlzdC1kZW5zLCBlY2hvID0gRkFMU0V9CmBgYAoKCiMjIFBvc2l0aW9uIEFkanVzdG1lbnRzCgpUaGUgYXZhaWxhYmxlIHBvc2l0aW9uIGFkanVzdG1lbnRzOgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgcmVzdWx0cyA9ICJhc2lzIn0Kc2hvd0xpc3QobHMoInBhY2thZ2U6Z2dwbG90MiIsIHBhdCA9ICJecG9zaXRpb25fIiksIG5jb2wgPSAzKQpgYGAKCkEgYmFyIGNoYXJ0IHNob3dpbmcgdGhlIGNvdW50cyBmb3IgdGhlIGRpZmZlcmVudCBgY3V0YCBjYXRlZ29yaWVzIGluCnRoZSBgZGlhbW9uZHNgIGRhdGE6CgpgYGB7ciBkaWFtb25kcy1jdXQsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGN1dCkpICsKICAgIGdlb21fYmFyKCkKYGBgCmBgYHtyIGRpYW1vbmRzLWN1dCwgZWNobyA9IEZBTFNFfQpgYGAKCk1hcHBpbmcgYGNsYXJpdHlgIHRvIGBmaWxsYCBzaG93cyB0aGUgYnJlYWtkb3duIGJ5IGJvdGggYGN1dGAgYW5kCmBjbGFyaXR5YCBpbiBhIF9zdGFja2VkIGJhciBjaGFydF86CgpgYGB7ciBkaWFtb25kcy1zdGFjazEsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGN1dCwKICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGNsYXJpdHkpKSArCiAgICBnZW9tX2JhcigpCmBgYApgYGB7ciBkaWFtb25kcy1zdGFjazEsIGVjaG8gPSBGQUxTRX0KYGBgCgpUaGUgZGVmYXVsdCBgcG9zaXRpb25gIGZvciBiYXIgY2hhcnRzIGlzIGBwb3NpdGlvbl9zdGFja2A6CgpgYGB7ciBkaWFtb25kcy1zdGFjazIsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoeCA9IGN1dCwKICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGNsYXJpdHkpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJzdGFjayIpCmBgYApgYGB7ciBkaWFtb25kcy1zdGFjazIsIGVjaG8gPSBGQUxTRX0KYGBgCgpgcG9zaXRpb25fZG9kZ2VgIHByb2R1Y2VzIF9zaWRlLWJ5LXNpZGUgYmFyIGNoYXJ0c186CgpgYGB7ciBkaWFtb25kcy1kb2RnZSwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoZGlhbW9uZHMsIGFlcyh4ID0gY3V0LAogICAgICAgICAgICAgICAgICAgICBmaWxsID0gY2xhcml0eSkpICsKICAgIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikKYGBgCmBgYHtyIGRpYW1vbmRzLWRvZGdlLCBlY2hvID0gRkFMU0V9CmBgYAoKYHBvc2l0aW9uX2ZpbGxgIHJlc2NhbGVzIGFsbCBiYXJzIHRvIGJlIGVxdWFsIGhlaWdodCB0byBoZWxwIGNvbXBhcmUKcHJvcG9ydGlvbnMgd2l0aGluIGJhcnMuCgpgYGB7ciBkaWFtb25kcy1maWxsLCBldmFsID0gRkFMU0V9CmdncGxvdChkaWFtb25kcywgYWVzKHggPSBjdXQsCiAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBjbGFyaXR5KSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpCmBgYApgYGB7ciBkaWFtb25kcy1maWxsLCBlY2hvID0gRkFMU0V9CmBgYAoKVXNpbmcgdGhlIGNvdW50cyB0byBzY2FsZSB0aGUgd2lkdGhzIHdvdWxkIHByb2R1Y2UgYSBfc3BpbmUgcGxvdF8sIGEKdmFyaWFudCBvZiBhIF9tb3NhaWMgcGxvdF8uCgpUaGlzIGlzIGVhc2llc3QgdG8gZG8gd2l0aCB0aGUgYGdnbW9zYWljYCBwYWNrYWdlLgoKYHBvc2l0aW9uX2ppdHRlcmAgY2FuIGJlIHVzZWQgd2l0aCBgZ2VvbV9wb2ludGAgdG8gYXZvaWQgb3ZlcnBsb3R0aW5nCm9yIGJyZWFrIHVwIHJvdW5kaW5nIGFydGlmYWN0cy4KCkFub3RoZXIgdmVyc2lvbiBvZiB0aGUgT2xkIEZhaXRoZnVsIGRhdGEgYXZhaWxhYmxlIGFzIGBnZXlzZXJgIGluCnBhY2thZ2UgYE1BU1NgIGhhcyBzb21lIHJvdW5kaW5nIGluIHRoZSBgZHVyYXRpb25gIHZhcmlhYmxlOgoKYGBge3IgZ2V5c2VyMiwgZXZhbCA9IEZBTFNFfQpkYXRhKGdleXNlciwgcGFja2FnZSA9ICJNQVNTIikKCiMjIEFkanVzdCBmb3IgZGlmZmVyZW50IG1lYW5pbmcgb2YgYHdhaXRpbmdgIHZhcmlhYmxlCmdleXNlcjIgPC0gbmEub21pdChtdXRhdGUoZ2V5c2VyLAogICAgICAgICAgICAgICAgICAgICAgICAgIGR1cmF0aW9uID0gbGFnKGR1cmF0aW9uKSkpCgpwIDwtIGdncGxvdChnZXlzZXIyLCBhZXMoeCA9IGR1cmF0aW9uLCB5ID0gd2FpdGluZykpCnAgKyBnZW9tX3BvaW50KCkKYGBgCgpgYGB7ciBnZXlzZXIyLCBlY2hvID0gRkFMU0V9CmBgYAoKX0ppdHRlcmluZ18gY2FuIGhlbHAgYnJlYWsgdXAgdGhlIGRpc3RyYWN0aW5nIF9oZWFwaW5nXyBvZiB2YWx1ZXMgb24KZHVyYXRpb25zIG9mIDIgYW5kIDQgbWludXRlcy4KClRoZSBkZWZhdWx0IGFtb3VudCBvZiBqaXR0ZXJpbmcgaXNuJ3QgcXVpdGUgZW5vdWdoIGluIHRoaXMgY2FzZToKCmBgYHtyIGdleXNlcjItaml0LCBldmFsID0gRkFMU0V9CnAgKyBnZW9tX3BvaW50KHBvc2l0aW9uID0gImppdHRlciIpCmBgYApgYGB7ciBnZXlzZXIyLWppdCwgZWNobyA9IEZBTFNFfQpgYGAKClRvIGppdHRlciBvbmx5IGhvcml6b250YWxseSBhbmQgYnkgYSBsYXJnZXIgYW1vdW50IHlvdSBjYW4gdXNlCgpgYGB7ciBnZXlzZXIyLWppdDIsIGV2YWwgPSBGQUxTRX0KcCArIGdlb21fcG9pbnQocG9zaXRpb24gPQogICAgICAgICAgICAgICAgICAgcG9zaXRpb25faml0dGVyKGhlaWdodCA9IDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSAwLjEpKQpgYGAKYGBge3IgZ2V5c2VyMi1qaXQyLCBlY2hvID0gRkFMU0V9CmBgYAoKCiMjIENvb3JkaW5hdGUgU3lzdGVtcwoKQ29vcmRpbmF0ZSBzeXN0ZW0gZnVuY3Rpb25zIGluY2x1ZGUKCmBgYHtyLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAiYXNpcyJ9CnNob3dMaXN0KGxzKCJwYWNrYWdlOmdncGxvdDIiLCBwYXQgPSAiXmNvb3JkXyIpKQpgYGAKClRoZSBkZWZhdWx0IGNvb3JkaW5hdGUgc3lzdGVtIGlzIGBjb29yZF9jYXJ0ZXNpYW5gLgoKCiMjIyBDYXJ0ZXNpYW4gQ29vcmRpbmF0ZXMKCmBjb29yZF9jYXJ0ZXNpYW5gIGNhbiBiZSB1c2VkIHRvIF96b29tIGluXyBvbiBhIHBhcnRpY3VsYXIgcmVnaWlvbjoKCmBgYHtyIGdleXNlcjItem9vbSwgZXZhbCA9IEZBTFNFfQpwICsgZ2VvbV9wb2ludCgpICsKICAgIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygzLCA0KSkKYGBgCmBgYHtyIGdleXNlcjItem9vbSwgZWNobyA9IEZBTFNFfQpgYGAKCmBjb29yZF9maXhlZGAgYW5kIGBjb29yZF9lcXVhbGAgZml4IHRoZSBfYXNwZWN0IHJhdGlvXyBmb3IgYSBjYXJ0ZXNpYW4KY29vcmRpbmF0ZSBzeXN0ZW0uCgpUaGUgYXNwZWN0IHJhdGlvIGlzIHRoZSByYXRpbyBvZiB0aGUgbnVtYmVyIHBoeXNpY2FsIGRpc3BsYXkgdW5pdHMgcGVyCmB5YCB1bml0IHRvIHRoZSBudW1iZXIgb2YgcGh5c2ljYWwgZGlzcGxheSB1bml0cyBwZXIgYHhgIHVuaXQuCgpUaGUgYXNwZWN0IHJhdGlvIGNhbiBiZSBpbXBvcnRhbnQgZm9yIHJlY29nbml6aW5nIGZlYXR1cmVzIGFuZCBwYXR0ZXJucy4KCmBgYHtyfQpyaXZlciA8LSBzY2FuKCJodHRwczovL3d3dy5zdGF0LnVpb3dhLmVkdS9+bHVrZS9kYXRhL3JpdmVyLmRhdCIpCnIgPC0gZGF0YS5mcmFtZShmbG93ID0gcml2ZXIsIG1vbnRoID0gc2VxX2Fsb25nKHJpdmVyKSkKYGBgCgpgYGB7ciByaXZlci1mbGF0LCBldmFsID0gRkFMU0V9CmdncGxvdChyLCBhZXMoeCA9IG1vbnRoLCB5ID0gZmxvdykpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBjb29yZF9maXhlZChyYXRpbyA9IDQpCmBgYApgYGB7ciByaXZlci1mbGF0LCBlY2hvID0gRkFMU0UsIGZpZy5oZWlnaHQgPSAyLCBmaWcud2lkdGggPSA4fQpgYGAKCgojIyMgUG9sYXIgQ29vcmRpbmF0ZXMKCkEgZmlsbGVkIGJhciBjaGFydAoKYGBge3IgZGlhbW9uZHMtZmlsbC0xLCBldmFsID0gRkFMU0V9CihwIDwtIGdncGxvdChkaWFtb25kcykgKwogICAgIGdlb21fYmFyKGFlcyh4ID0gMSwgZmlsbCA9IGN1dCksCiAgICAgICAgICAgICAgcG9zaXRpb24gPSAiZmlsbCIpKQpgYGAKYGBge3IgZGlhbW9uZHMtZmlsbC0xLCBlY2hvID0gRkFMU0V9CmBgYAoKaXMgdHVybmVkIGludG8gYSBwaWUgY2hhcnQgYnkgY2hhbmdpbmcgdG8gcG9sYXIgY29vcmRpbmF0ZXM6CgpgYGB7ciBkaWFtb25kcy1waWUsIGV2YWwgPSBGQUxTRX0KcCArIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKQpgYGAKYGBge3IgZGlhbW9uZHMtcGllLCBlY2hvID0gRkFMU0V9CmBgYAoKCiMjIyBDb29yZGluYXRlIFN5c3RlbXMgZm9yIE1hcHMKCkNvb3JkaW5hdGUgc3lzdGVtcyBhcmUgcGFydGljdWxhcmx5IGltcG9ydGFudCBmb3IgbWFwcy4KClBvbHlnb25zIGZvciBtYW55IHBvbGl0aWNhbCBhbmQgZ2VvZ3JhcGhpYyBib3VuZGFyaWVzIGFyZSBhdmFpbGFibGUKdGhyb3VnaCB0aGUgYG1hcF9kYXRhYCBmdW5jdGlvbi4KCkJvdW5kYXJpZXMgZm9yIHRoZSBsb3dlciA0OCBVUyBzdGF0ZXMgY2FuIGJlIG9idGFpbmVkIGFzCgpgYGB7cn0KdXNhIDwtIG1hcF9kYXRhKCJzdGF0ZSIpCmBgYAoKUG9seWdvbiB2ZXJ0aWNlcyBhcmUgZW5jb2RlZCBieSBsb25naXR1ZGUgYW5kIGxhdGl0dWRlLgoKUGxvdHRpbmcgdGhlc2UgaW4gdGhlIGRlZmF1bHQgY2FydGVzaWFuIGNvb3JkaW5hdGUgc3lzdGVtIHVzdWFsbHkgZG9lcwpub3Qgd29yayB3ZWxsOgoKYGBge3IgdXNhLWNhcnQsIGV2YWwgPSBGQUxTRX0KdXNhIDwtIG1hcF9kYXRhKCJzdGF0ZSIpCm0gPC0gZ2dwbG90KHVzYSwgYWVzKHggPSBsb25nLAogICAgICAgICAgICAgICAgICAgICB5ID0gbGF0LAogICAgICAgICAgICAgICAgICAgICBncm91cCA9IGdyb3VwKSkgKwogICAgZ2VvbV9wb2x5Z29uKGZpbGwgPSAid2hpdGUiLAogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikKbQpgYGAKYGBge3IgdXNhLWNhcnQsIGVjaG8gPSBGQUxTRX0KYGBgCgpVc2luZyBhIGZpeGVkIGFzcGVjdCByYXRpbyBpcyBiZXR0ZXIsIGJ1dCBhbiBhc3BlY3QgcmF0aW8gb2YgMSBkb2VzCm5vdCB3b3JrIHdlbGw6CgpgYGB7cn0KbSArIGNvb3JkX2VxdWFsKCkKYGBgCgpUaGUgcHJvYmxlbSBpcyB0aGF0IGF3YXkgZnJvbSB0aGUgZXF1YXRvciBhIG9uZSBkZWdyZWUgY2hhbmdlIGluCmxhdGl0dWRlIGNvcnJlc3BvbmRzIHRvIGEgbGFyZ2VyIGRpc3RhbmNlIHRoYW4gYSBvbmUgZGVncmVlIGNoYW5nZSBpbgpsb25naXR1ZGUuCgpUaGUgcmF0aW8gb2Ygb25lIGRlZ3JlZSBsb25naXR1ZGUgc2VwYXJhdGlvbiB0byBvbmUgZGVncmVlIGxhdGl0dWRlCnNlcGFyYXRpb24gZm9yIHRoZSBsYXRpdHVkZSBhdCB0aGUgbWlkZGxlIG9mIElvd2Egb2YgNDEgZGVncmVlcyBpcwoKYGBge3J9CmxvbmdsYXQgPC0gY29zKDQxIC8gOTAgKiBwaSAvIDIpCmxvbmdsYXQKYGBgCgpBIGJldHRlciBtYXAgaXMgb2J0YWluZWQgdXNpbmcgdGhlIGFzcGVjdCByYXRpbyBgMSAvIGxvbmdsYXRgOgoKYGBge3IgdXNhLWZpeGVkLCBldmFsID0gRkFMU0V9Cm0gKyBjb29yZF9maXhlZCgxIC8gbG9uZ2xhdCkKYGBgCmBgYHtyIHVzYS1maXhlZCwgZWNobyA9IEZBTFNFfQpgYGAKClRoZSBiZXN0IGFwcHJvYWNoIGlzIHRvIHVzZSBhIGNvb3JkaW5hdGUgc3lzdGVtIGRlc2lnbmVkIHNwZWNpZmljYWxseSBmb3IKbWFwcy4KClRoZXJlIGFyZSBtYW55IF9wcm9qZWN0aW9uc18gdXNlZCBpbiBtYXAgbWFraW5nLgoKVGhlIGRlZmF1bHQgcHJvamVjdGlvbiB1c2VkIGJ5IGBjb29yZF9tYXBgIGlzIHRoZQpbTWVyY2F0b3JdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01lcmNhdG9yX3Byb2plY3Rpb24pCnByb2plY3Rpb24uCgpgYGB7ciB1c2EtbWVyY2F0b3IsIGV2YWwgPSBGQUxTRX0KbSArIGNvb3JkX21hcCgpCmBgYAoKYGBge3IgdXNhLW1lcmNhdG9yLCBlY2hvID0gRkFMU0V9CmBgYAoKUHJvcGVyIG1hcCBwcm9qZWN0aW9ucyBhcmUgbm9uLWxpbmVhcjsgdGhpcyBpcyBlYXNpZXIgdG8gc2VlIHdpdGggYW4KQWxiZXJzIHByb2plY3Rpb246CgpgYGB7ciB1c2EtYWxiZXJzLCBldmFsID0gRkFMU0V9Cm0gKyBjb29yZF9tYXAoImFsYmVycyIsIDIwLCA1MCkKYGBgCmBgYHtyIHVzYS1hbGJlcnMsIGVjaG8gPSBGQUxTRX0KYGBgCgoKIyMgU2NhbGVzCgpTY2FsZXMgYXJlIHVzZWQgZm9yIGNvbnRyb2xsaW5nIHRoZSBtYXBwaW5nIG9mIHZhbHVlcyB0byBwaHlzaWNhbApyZXByZXNlbnRhdGlvbnMgc3VjaCBhcyBjb2xvcnMsIHNoYXBlcywgYW5kIHBvc2l0aW9ucy4KClNjYWxlIGZ1bmN0aW9ucyBhcmUgYWxzbyByZXNwb25zaWJsZSBmb3IgcHJvZHVjaW5nIF9ndWlkZXNfIGZvcgp0cmFuc2xhdGluZyBwaHlzaWNhbCByZXByZXNlbnRhdGlvbnMgYmFjayB0byB2YWx1ZXMsIHN1Y2ggYXMKCiogYXhpcyBsYWJlbHMgYW5kIG1hcmtzOwoKKiBjb2xvciBvciBzaGFwZSBsZWdlbmRzLgoKVGhlcmUgYXJlIGN1cnJlbnRseSBgciBsZW5ndGgobHMoInBhY2thZ2U6Z2dwbG90MiIsIHBhdCA9ICJzY2FsZV8iKSlgCnNjYWxlIGZ1bmN0aW9uczsgc29tZSBleGFtcGxlcyBhcmUKCmBgYHIKc2NhbGVfY29sb3JfZ3JhZGllbnQgICAgICBzY2FsZV9zaGFwZV9tYW51YWwgICAgIHNjYWxlX3hfbG9nMTAKc2NhbGVfY29sb3JfbWFudWFsICAgICAgICBzY2FsZV9zaXplX2FyZWEgICAgICAgIHNjYWxlX3lfbG9nMTAKc2NhbGVfZmlsbF9ncmFkaWVudCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX3hfc3FydApzY2FsZV9maWxsX21hbnVhbCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfeV9zcXJ0CgpgYGAKCkFuIFtleHBlcmltZW50YWwgdG9vbF0oaHR0cHM6Ly9nZ3Bsb3QydG9yLmNvbS9zY2FsZXMvKSB0byBoZWxwCmNob29zaW5nIHNjYWxlcyBpcyBhdmFpbGFibGUuCgpTdGFydCB3aXRoIGEgYmFzaWMgc2NhdHRlciBwbG90OgoKYGBge3IgbXBnLWJhc2ljLCBldmFsID0gRkFMU0V9CihwIDwtIGdncGxvdChtcGcsIGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgICAgICAgICAgICB5ID0gaHd5KSkgKwogICAgIGdlb21fcG9pbnQoKSkKYGBgCmBgYHtyIG1wZy1iYXNpYywgZWNobyA9IEZBTFNFfQpgYGAKClJlbW92ZSB0aGUgYHhgIHRpY2sgbWFya3MgYW5kIGxhYmVscyAodGhpcyBjYW4gYWxzbyBiZSBkb25lIHdpdGggdGhlbWUKc2V0dGluZ3MpOgoKYGBge3IgbXBnLW5vLXRpY2tzLWxhYnMsIGV2YWwgPSBGQUxTRX0KcCArIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IE5VTEwpCmBgYApgYGB7ciBtcGctbm8tdGlja3MtbGFicywgZWNobyA9IEZBTFNFfQpgYGAKCkNoYW5nZSB0aGUgdGljayBsb2NhdGlvbnMgYW5kIGxhYmVsczoKCmBgYHtyIG1wZy1uZXctdGlja3MtbGFicywgZXZhbCA9IEZBTFNFfQpwICsgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9CiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKGMoMiwgNCwgNiksICJsdHIiKSwKICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBjKDIsIDQsIDYpKQpgYGAKYGBge3IgbXBnLW5ldy10aWNrcy1sYWJzLCBlY2hvID0gRkFMU0V9CmBgYAoKVXNlIGEgbG9nYXJpdGhtaWMgYXhpczoKCmBgYHtyIG1wZy1sb2cteCwgZXZhbCA9IEZBTFNFfQpwICsgc2NhbGVfeF9sb2cxMChsYWJlbHMgPSBwYXN0ZShjKDIsIDQsIDYpLCAibHRyIiksCiAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMiwgNCwgNiksCiAgICAgICAgICAgICAgICAgIG1pbm9yX2JyZWFrcyA9IGMoMywgNSwgNykpCmBgYApgYGB7ciBtcGctbG9nLXgsIGVjaG8gPSBGQUxTRX0KYGBgCgpUaGUKW1NjYWxlc10oaHR0cHM6Ly9yNGRzLmhhZGxleS5uei9jb21tdW5pY2F0aW9uLmh0bWwjc2NhbGVzKQpzZWN0aW9uIGluIFtSIGZvciBEYXRhIFNjaWVuY2VdKGh0dHBzOi8vcjRkcy5oYWRsZXkubnovKSBwcm92aWRlcyBzb21lCm1vcmUgZGV0YWlscy4KCkNvbG9yIGFzc2lnbm1lbnQgY2FuIGFsc28gYmUgY29udHJvbGxlZCBieSBzY2FsZSBmdW5jdGlvbnMuCgpGb3IgZXhhbXBsZSwgZm9yIHNvbWUgcHJlc2lkZW50aWFsIGFwcHJvdmFsIHJhdGluZ3MgZGF0YQoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KcHJfYXBwciA8LSBkYXRhLmZyYW1lKHByZXMgPSBjKCJPYmFtYSIsICJDYXJ0ZXIiLCAiQ2xpbnRvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRy5XLiBCdXNoIiwgIlJlYWdhbiIsICJHLkguVyBCdXNoIiwgIlRydW1wIiksCiAgICAgICAgICAgICAgICAgICAgICBhcHByID0gYyg3OSwgNzgsIDY4LCA2NSwgNTgsIDU2LCA0MCksCiAgICAgICAgICAgICAgICAgICAgICBwYXJ0eSA9IGMoIkQiLCAiRCIsICJEIiwgIlIiLCAiUiIsICJSIiwgIlIiKSwKICAgICAgICAgICAgICAgICAgICAgIHllYXIgPSBjKDIwMDksIDE5NzcsIDE5OTMsIDIwMDEsIDE5ODEsIDE5ODksIDIwMTcpKQpwcl9hcHByIDwtIG11dGF0ZShwcl9hcHByLCBwcmVzID0gcmVvcmRlcihwcmVzLCBhcHByKSkKYGBgCmBgYHtyfQpwcl9hcHByCmBgYAoKdGhlIGRlZmF1bHQgY29sb3Igc2NhbGUgaXMgbm90IGlkZWFsOgoKYGBge3IgcHItYXBwcjAsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KHByX2FwcHIsCiAgICAgICBhZXMoeCA9IGFwcHIsIHkgPSBwcmVzLCBmaWxsID0gcGFydHkpKSArCiAgICBnZW9tX2NvbCgpCmBgYAoKYGBge3IgcHItYXBwcjAsIGVjaG8gPSBGQUxTRX0KYGBgCgpUaGUgY29tbW9uIGFzc2lnbm1lbnQgb2YgcmVkIGZvciBSZXB1YmxpY2FuIGFuZCBibHVlIGZvciBEZW1vY3JhdCBjYW4KYmUgb2J0YWluZWQgYnkKCmBgYHtyIHByLWFwcHIsIGV2YWwgPSBGQUxTRX0KZ2dwbG90KHByX2FwcHIsCiAgICAgICBhZXMoeCA9IGFwcHIsIHkgPSBwcmVzLCBmaWxsID0gcGFydHkpKSArCiAgICBnZW9tX2NvbCgpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcwogICAgICAgICAgICAgICAgICAgICAgPSBjKFIgPSAicmVkIiwgRCA9ICJibHVlIikpCmBgYAoKYGBge3IgcHItYXBwciwgZWNobyA9IEZBTFNFfQpgYGAKCkEgYmV0dGVyIGNob2ljZSBpcyB0byB1c2UgYSB3ZWxsLWRlc2lnbmVkIFtjb2xvcgpwYWxldHRlXShodHRwczovL2hjbHdpemFyZC5vcmcvI2NvbG9yLXBhbGV0dGVzKToKCmBgYHtyIHByLWFwcHItMiwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QocHJfYXBwciwKICAgICAgIGFlcyh4ID0gYXBwciwgeSA9IHByZXMsIGZpbGwgPSBwYXJ0eSkpICsKICAgIGdlb21fY29sKCkgKwogICAgY29sb3JzcGFjZTo6c2NhbGVfZmlsbF9kaXNjcmV0ZV9kaXZlcmdpbmcoCiAgICAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9ICJCbHVlLVJlZCAyIikKYGBgCgpgYGB7ciBwci1hcHByLTIsIGVjaG8gPSBGQUxTRX0KYGBgCgoKIyMgRmFjZXRzCgpGYWNldGluZyB1c2VzIHRoZSBfc21hbGwgbXVsdGlwbGVzXyBhcHByb2FjaCB0byBpbnRyb2R1Y2UgYWRkaXRpb25hbAp2YXJpYWJsZXMuCgpGb3IgYSBzaW5nbGUgdmFyaWFibGUgYGZhY2V0X3dyYXBgIGlzIHVzdWFsbHkgdXNlZDoKCmBgYHtyIG1wZy1mYWNldC13cmFwLCBldmFsID0gRkFMU0V9CnAgPC0gZ2dwbG90KG1wZykgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICAgICAgICAgeSA9IGh3eSkpCnAgKyBmYWNldF93cmFwKH4gY2xhc3MpCmBgYApgYGB7ciBtcGctZmFjZXQtd3JhcCwgZWNobyA9IEZBTFNFfQpgYGAKCkZvciB0d28gdmFyaWFibGVzLCBlYWNoIHdpdGggYSBtb2Rlc3QgbnVtYmVyIG9mIGNhdGVnb3JpZXMsCmBmYWNldF9ncmlkYCBjYW4gYmUgZWZmZWN0aXZlOgoKYGBge3IgbXBnLWZhY2V0LWdyaWQsIGV2YWwgPSBGQUxTRX0KcCArIGZhY2V0X2dyaWQoZmFjdG9yKGN5bCkgfiBkcnYpCmBgYApgYGB7ciBtcGctZmFjZXQtZ3JpZCwgZWNobyA9IEZBTFNFfQpgYGAKCjwhLS0KVXNpbmcgdGhlIHByZXZpb3VzIG1wZyBmYWNldCBwbG90IHdvdWxkIGJlIGJldHRlciBidXQgdGhpcyBpcyBvbmUgb2YKdGhlIGhvbWV3b3JrIHByb2JsZW1zIGluIEhXMwotLT4KClRvIHNob3cgY29tbW9uIGRhdGEgaW4gYWxsIGZhY2V0cyBtYWtlIHN1cmUgdGhlIGRhdGEgZG9lcyBub3QgY29udGFpbiB0aGUKZmFjZXRpbmcgdmFyaWFibGUuCgpUaGlzIHdhcyB1c2VkIHRvIHNob3cgbXV0ZWQgdmlld3Mgb2YgdGhlIGZ1bGwgZGF0YSBpbiBmYWNldGVkIHBsb3RzLgoKQSBmYWNldGVkIHBsb3Qgb2YgdGhlIGBnYXBtaW5kZXJgIGRhdGE6CgpgYGB7ciBnYXBtaW5kZXItbm90LW11dGVkLCBldmFsID0gRkFMU0V9CmxpYnJhcnkoZ2FwbWluZGVyKQoKeWVhcnNfdG9fa2VlcCA8LSBjKDE5NzcsIDE5ODcsIDE5OTcsIDIwMDcpCmdkIDwtIGZpbHRlcihnYXBtaW5kZXIsCiAgICAgICAgICAgICB5ZWFyICVpbiUgeWVhcnNfdG9fa2VlcCkKCmdncGxvdChnZCwKICAgICAgIGFlcyh4ID0gZ2RwUGVyY2FwLAogICAgICAgICAgIHkgPSBsaWZlRXhwLAogICAgICAgICAgIGNvbG9yID0gY29udGluZW50KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMi41KSArCiAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgZmFjZXRfd3JhcCh+IHllYXIpCmBgYAoKYGBge3IgZ2FwbWluZGVyLW5vdC1tdXRlZCwgZWNobyA9IEZBTFNFLCBmaWcud2lkdGggPSA4fQpgYGAKCkFkZCBhIG11dGVkIHZlcnNpb24gb2YgdGhlIGZ1bGwgZGF0YSBpbiB0aGUgYmFja2dyb3VuZCBvZiBlYWNoIHBhbmVsOgoKPCEtLSB2YXJpYW50IG9mIGNvZGUgaW4gcHJlcmNlcC5SbWQgLS0+CmBgYHtyIGdhcG1pbmRlci1tdXRlZCwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGdhcG1pbmRlcikKCnllYXJzX3RvX2tlZXAgPC0gYygxOTc3LCAxOTg3LCAxOTk3LCAyMDA3KQpnZCA8LSBmaWx0ZXIoZ2FwbWluZGVyLAogICAgICAgICAgICAgeWVhciAlaW4lIHllYXJzX3RvX2tlZXApCmdkX25vX3llYXIgPC0gbXV0YXRlKGdkLCB5ZWFyID0gTlVMTCkKCmdncGxvdChnZCwKICAgICAgIGFlcyh4ID0gZ2RwUGVyY2FwLAogICAgICAgICAgIHkgPSBsaWZlRXhwLAogICAgICAgICAgIGNvbG9yID0gY29udGluZW50KSkgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZ2Rfbm9feWVhciwKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTgwIikgKwogICAgZ2VvbV9wb2ludChzaXplID0gMi41KSArCiAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgZmFjZXRfd3JhcCh+IHllYXIpCmBgYAoKYGBge3IgZ2FwbWluZGVyLW11dGVkLCBlY2hvID0gRkFMU0UsIGZpZy53aWR0aCA9IDh9CmBgYAoKVXN1YWxseSBmYWNldHMgdXNlIGNvbW1vbiBheGlzIHNjYWxlcywgYnV0IG9uZSBvciBib3RoIGNhbiBiZSBhbGxvd2VkCnRvIHZhcnkuCgpBIHVzZWZ1bCBhcHByb2FjaCBmb3Igc2hvd2luZyB0aW1lIHNlcmllcyBkYXRhIHdpdGggYSBnb29kIGFzcGVjdApyYXRpbyBjYW4gYmUgdG8gc3BsaXQgdGhlIGRhdGEgaW50byBmYWNldHMgZm9yIG5vbi1vdmVybGFwcGluZwpwb3J0aW9ucyBvZiB0aGUgdGltZSBheGlzLgoKYGBge3Igcml2ZXItZmFjZXQsIGV2YWwgPSBGQUxTRX0KcGQgPC0gcmVwKHBhc3RlKHNlcSgxLCBieSA9IDMyLCBsZW5ndGgub3V0ID0gNCksCiAgICAgICAgICAgICAgICBzZXEoMzIsIGJ5ID0gMzIsIGxlbmd0aC5vdXQgPSA0KSwKICAgICAgICAgICAgICAgIHNlcCA9ICIgLSAiKSwKICAgICAgICAgZWFjaCA9ICAzMikKcmQgPC0gZGF0YS5mcmFtZShtb250aCA9IHNlcV9hbG9uZyhyaXZlciksCiAgICAgICAgICAgICAgICAgZmxvdyA9IHJpdmVyLAogICAgICAgICAgICAgICAgIHBhbmVsID0gcGQpCmdncGxvdChyZCwgYWVzKHggPSBtb250aCwKICAgICAgICAgICAgICAgeSA9IGZsb3cpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgZmFjZXRfd3JhcCh+IHBhbmVsLAogICAgICAgICAgICAgICBzY2FsZSA9ICJmcmVlX3giLCAjPDwKICAgICAgICAgICAgICAgbmNvbCA9IDEpCmBgYApgYGB7ciByaXZlci1mYWNldCwgZWNobyA9IEZBTFNFfQpgYGAKCkZhY2V0IGFycmFuZ2VtZW50IGNhbiBhbHNvIGJlIHVzZWQgdG8gY29udmV5IG90aGVyIGluZm9ybWF0aW9uLCBzdWNoCmFzIGdlb2dyYXBoaWMgbG9jYXRpb24uCgpUaGUgW2BnZW9mYWNldGAgcGFja2FnZV0oaHR0cHM6Ly9oYWZlbi5naXRodWIuaW8vZ2VvZmFjZXQvKSBhbGxvd3MKZmFjZXRzIHRvIGJlIHBsYWNlZCBpbiBhcHByb3hpbWF0ZSBsb2NhdGlvbnMgb2YgZGlmZmVyZW50IGdlb2dyYXBoaWMKcmVnaW9ucy4KCkFuIGV4YW1wbGUgZm9yIGRhdGEgZnJvbSBVUyBzdGF0ZXM6CgpgYGB7ciBnZW9mYWNldCwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGdlb2ZhY2V0KQpnZ3Bsb3Qoc3RhdGVfdW5lbXAsIGFlcyh5ZWFyLCByYXRlKSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZmFjZXRfZ2VvKH4gc3RhdGUsCiAgICAgICAgICAgICAgZ3JpZCA9ICJ1c19zdGF0ZV9ncmlkMiIsCiAgICAgICAgICAgICAgbGFiZWwgPSAiY29kZSIpICsKICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPQogICAgICAgICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KSBwYXN0ZTAoIiciLCBzdWJzdHIoeCwgMywgNCkpKSArCiAgICBsYWJzKHRpdGxlID0gIlNlYXNvbmFsbHkgQWRqdXN0ZWQgVVMgVW5lbXBsb3ltZW50IFJhdGUgMjAwMC0yMDE2IiwKICAgICAgICAgY2FwdGlvbiA9ICJEYXRhIFNvdXJjZTogYmxzLmdvdiIsCiAgICAgICAgIHggPSAiWWVhciIsCiAgICAgICAgIHkgPSAiVW5lbXBsb3ltZW50IFJhdGUgKCUpIikgKwogICAgdGhlbWUoc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gNSkpCmBgYApgYGB7ciBnZW9mYWNldCwgZWNobyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmBgYAoKQXJyYW5nZW1lbnQgYWNjb3JkaW5nIHRvIGEgY2FsZW5kYXIgY2FuIGFsc28gYmUgdXNlZnVsLgoKCiMjIFRoZW1lcwoKYGdncGxvdDJgIHN1cHBvcnRzIHRoZSBub3Rpb24gb2YgX3RoZW1lc18gZm9yIGFkanVzdGluZyBub24tZGF0YQphcHBlYXJhbmNlIGFzcGVjdHMgb2YgYSBwbG90LCBzdWNoIGFzCgoqIHBsb3QgdGl0bGVzCgoqIGF4aXMgYW5kIGxlZ2VuZCBwbGFjZW1lbnQgYW5kIHRpdGxlcwoKKiBiYWNrZ3JvdW5kIGNvbG9ycwoKKiBndWlkZSBsaW5lIHBsYWNlbWVudAoKVGhlbWUgZWxlbWVudHMgY2FuIGJlIGN1c3RvbWl6ZWQgaW4gc2V2ZXJhbCB3YXlzOgoKKiBgdGhlbWUoKWAgY2FuIGJlIHVzZWQgdG8gYWRqdXN0IGluZGl2aWR1YWwgZWxlbWVudHMgaW4gYSBwbG90LgoKKiBgdGhlbWVfc2V0KClgIGFkanVzdHMgZGVmYXVsdCBzZXR0aW5ncyBmb3IgYSBzZXNzaW9uOwoKKiBwcmUtZGVmaW5lZCB0aGVtZSBmdW5jdGlvbnMgYWxsb3cgY29uc2lzdGVudCBzdHlsZSBjaGFuZ2VzLgoKVGhlCltmdWxsIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS90aGVtZS5odG1sKQpvZiB0aGUgYHRoZW1lYCBmdW5jdGlvbiBsaXN0cyBtYW55IGN1c3RvbWl6YWJsZSBlbGVtZW50cy4KCk9uZSBzaW1wbGUgZXhhbXBsZToKCmBgYHtyIHRoZW1lLXNpbXBsZSwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QobXV0YXRlKG1wZywgY3lsID0gZmFjdG9yKGN5bCkpKSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgICAgICAgICB5ID0gaHd5LAogICAgICAgICAgICAgICAgICAgZmlsbCA9IGN5bCksCiAgICAgICAgICAgICAgIHNoYXBlID0gMjEsCiAgICAgICAgICAgICAgIHNpemUgPSAzKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhY2UgPSAiYm9sZCIpKQpgYGAKYGBge3IgdGhlbWUtc2ltcGxlLCBlY2hvID0gRkFMU0V9CmBgYAoKQW5vdGhlciBleGFtcGxlOgoKYGBge3IgdGhlbWUtc2ltcGxlLTIsIGV2YWwgPSBGQUxTRX0KZ3RobSA8LQogICAgdGhlbWUocGxvdC5iYWNrZ3JvdW5kID0KICAgICAgICAgICAgICBlbGVtZW50X3JlY3QoZmlsbCA9ICJsaWdodGJsdWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IE5BKSwKICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPQogICAgICAgICAgICAgIGVsZW1lbnRfcmVjdChmaWxsID0gInBpbmsiKSkKcCArIGd0aG0KYGBgCmBgYHtyIHRoZW1lLXNpbXBsZS0yLCBlY2hvID0gRkFMU0V9CmBgYAoKU29tZSBhbHRlcm5hdGUgY29tcGxldGUgdGhlbWVzIHByb3ZpZGVkIGJ5IGBnZ3Bsb3QyYCBhcmUKCmBgYHIKdGhlbWVfYncgICAgICAgIHRoZW1lX2dyYXkgICAgICB0aGVtZV9taW5pbWFsICAgdGhlbWVfdm9pZAp0aGVtZV9jbGFzc2ljICAgdGhlbWVfZ3JleSAgICAgIHRoZW1lX2RhcmsgICAgICB0aGVtZV9saWdodApgYGAKClNvbWUgZXhhbXBsZXM6CgpgYGB7ciBhbHQtdGhlbWVzLCBldmFsID0gRkFMU0V9CnBfYncgPC0gcCArIHRoZW1lX2J3KCkgKyBnZ3RpdGxlKCJCVyIpCgpwX2NsYXNzaWMgPC0gcCArIHRoZW1lX2NsYXNzaWMoKSArIGdndGl0bGUoIkNsYXNzaWMiKQoKcF9taW4gPC0gcCArIHRoZW1lX21pbmltYWwoKSArIGdndGl0bGUoIk1pbmltYWwiKQoKcF92b2lkIDwtIHAgKyB0aGVtZV92b2lkKCkgKyBnZ3RpdGxlKCJWb2lkIikKCmxpYnJhcnkocGF0Y2h3b3JrKQoocF9idyArIHBfY2xhc3NpYykgLyAocF9taW4gKyBwX3ZvaWQpCmBgYApgYGB7ciBhbHQtdGhlbWVzLCBlY2hvID0gRkFMU0V9CmBgYAoKVGhlCltgZ2d0aGVtZXNgXShodHRwOi8vd3d3LnJwdWJzLmNvbS9NZW50b3JzX1ViaXF1bS9nZ3RoZW1lc18xKQpwYWNrYWdlIHByb3ZpZGVzIHNvbWUgYWRkaXRpb25hbCB0aGVtZXMuCgpTb21lIGV4YW1wbGVzOgoKYGBge3IgZ2d0aGVtZXMtZXhhbXBsZXMsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShnZ3RoZW1lcykKCnBfZWNvbiA8LSBwICsgdGhlbWVfZWNvbm9taXN0KCkgKyBnZ3RpdGxlKCJFY29ub21pc3QiKQoKcF93c2ogPC0gcCArIHRoZW1lX3dzaigpICsgZ2d0aXRsZSgiV1NKIikKCnBfdHVmdGUgPC0gcCArIHRoZW1lX3R1ZnRlKCkgKyBnZ3RpdGxlKCJUdWZ0ZSIpCgpwX2ZldyA8LSBwICsgdGhlbWVfZmV3KCkgKyBnZ3RpdGxlKCJGZXciKQoKKHBfZWNvbiArIHBfd3NqKSAvIChwX3R1ZnRlICsgcF9mZXcpCmBgYApgYGB7ciBnZ3RoZW1lcy1leGFtcGxlcywgZWNobyA9IEZBTFNFfQpgYGAKCmBnZ3RoZW1lc2AgYWxzbyBwcm92aWRlcyBgdGhlbWVfbWFwYCB0aGF0IHJlbW92ZXMgdW5uZWNlc3NhcnkgZWxlbWVudHMKZnJvbSBtYXBzOgoKYGBge3J9Cm0gKyBjb29yZF9tYXAoKSArIHRoZW1lX21hcCgpCmBgYAoKVGhlCltUaGVtZXNdKGh0dHBzOi8vcjRkcy5oYWRsZXkubnovY29tbXVuaWNhdGlvbi5odG1sI3NlYy10aGVtZXMpCnNlY3Rpb24gaW4gW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZGxleS5uei8pIHByb3ZpZGVzIHNvbWUKbW9yZSBkZXRhaWxzLgoKCiMjIEEgTW9yZSBDb21wbGV0ZSBUZW1wbGF0ZQoKYGBgcgpnZ3Bsb3QoZGF0YSA9IDxEQVRBPikgKwogICAgPEdFT00+KG1hcHBpbmcgPSBhZXMoPE1BUFBJTkdTPiksCiAgICAgICAgICAgc3RhdCA9IDxTVEFUPiwKICAgICAgICAgICBwb3NpdGlvbiA9IDxQT1NJVElPTj4pICsKICAgIDwgLi4uIE1PUkUgR0VPTVMgLi4uID4gKwogICAgPENPT1JESU5BVEVfQURKVVNUTUVOVD4gKwogICAgPFNDQUxFX0FESlVTVE1FTlQ+ICsKICAgIDxGQUNFVElORz4gKwogICAgPFRIRU1FX0FESlVTVE1FTlQ+CmBgYAoKCiMjIExhYmVscyBhbmQgQW5ub3RhdGlvbnMKCkEgYmFzaWMgcGxvdDoKCmBgYHtyIG1wZy1hbm4sIGV2YWwgPSBGQUxTRX0KcCA8LSBnZ3Bsb3QobXBnLCBhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICAgICAgICAgICB5ID0gaHd5KSkKcDEgPC0gcCArIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gZmFjdG9yKGN5bCkpLAogICAgICAgICAgICAgICAgICAgICBzaXplID0gMi41KQpwMQpgYGAKYGBge3IgbXBnLWFubiwgZWNobyA9IEZBTFNFfQpgYGAKCkF4aXMgbGFiZWxzIGFyZSBiYXNlZCBvbiB0aGUgZXhwcmVzc2lvbnMgZ2l2ZW4gdG8gYGFlc2AuCgpUaGlzIGlzIGNvbnZlbmllbnQgZm9yIGV4cGxvcmF0aW9uIGJ1dCB1c3VhbGx5IG5vdCBpZGVhbCBmb3IgYSByZXBvcnQuCgpUaGUgYGxhYnMoKWAgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gY2hhbmdlIGF4aXMgYW5kIGxlZ2VuZCBsYWJlbHM6CgpgYGB7ciBtcGctYW5uLWxhYnMsIGV2YWwgPSBGQUxTRX0KcDEgKyBsYWJzKHggPSAiRGlzcGxhY2VtZW50IChMaXRlcnMpIiwKICAgICAgICAgIHkgPSAiSGlnaHdheSBNaWxlcyBQZXIgR2FsbG9uIiwKICAgICAgICAgIGNvbG9yID0gIkN5bGluZGVycyIpCmBgYApgYGB7ciBtcGctYW5uLWxhYnMsIGVjaG8gPSBGQUxTRX0KYGBgCgpUaGUgYGxhYnMoKWAgZnVuY3Rpb24gY2FuIGFsc28gYWRkIGEgdGl0bGUsIHN1YnRpdGxlLCBhbmQgY2FwdGlvbjoKCmBgYHtyIG1wZy1hbm4tbGFicy0yLCBldmFsID0gRkFMU0V9CnAyIDwtIHAxICsKICAgIGxhYnMoeCA9ICJEaXNwbGFjZW1lbnQgKExpdGVycykiLAogICAgICAgICB5ID0gIkhpZ2h3YXkgTWlsZXMgUGVyIEdhbGxvbiIsCiAgICAgICAgIGNvbG9yID0gIkN5bGluZGVycyIsCiAgICAgICAgIHRpdGxlID0gIkdhcyBNaWxlYWdlIGFuZCBEaXNwbGFjZW1lbnQiLAogICAgICAgICBzdWJ0aXRsZSA9IHBhc3RlKCJGb3IgbW9kZWxzIHdoaWNoIGhhZCBhIG5ldyByZWxlYXNlIGV2ZXJ5IHllYXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiYmV0d2VlbiAxOTk5IGFuZCAyMDA4IiksCiAgICAgICAgIGNhcHRpb24gPSAiRGF0YSBTb3VyY2U6IGh0dHBzOi8vZnVlbGVjb25vbXkuZ292LyIpCnAyCmBgYApgYGB7ciBtcGctYW5uLWxhYnMtMiwgZWNobyA9IEZBTFNFfQpgYGAKCkFubm90YXRpb25zIGNhbiBiZSB1c2VkIHRvIHByb3ZpZGUgcG9wb3V0IHRoYXQgZHJhd3MgYSB2aWV3ZXIncwphdHRlbnRpb24gdG8gcGFydGljdWxhciBmZWF0dXJlcy4KClRoZSBgYW5ub3RhdGUoKWAgZnVuY3Rpb24gaXMgb25lIG9wdGlvbjoKCmBgYHtyIG1wZy1hbm4tcG9wb3V0LCBldmFsID0gRkFMU0V9CnAyICsKICAgIGFubm90YXRlKCJsYWJlbCIsIHggPSAyLjgsIHkgPSA0MywKICAgICAgICAgICAgIGxhYmVsID0gIlZvbGtzd2FnZW5zIikgKwogICAgYW5ub3RhdGUoInJlY3QiLAogICAgICAgICAgICAgeG1pbiA9IDEuNywgeG1heCA9IDIuMSwKICAgICAgICAgICAgIHltaW4gPSA0MCwgeW1heCA9IDQ1LAogICAgICAgICAgICAgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIpCmBgYApgYGB7ciBtcGctYW5uLXBvcG91dCwgZWNobyA9IEZBTFNFfQpgYGAKCk9mdGVuIG1vcmUgY29udmVuaWVudCBhcmUgc29tZSBgZ2VvbV9tYXJrYCBvYmplY3RzIHByb3ZpZGVkIGJ5IHRoZQpgZ2dmb3JjZWAgcGFja2FnZToKCmBgYHtyIG1wZy1hbm4tcG9wb3V0LTIsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShnZ2ZvcmNlKQpwMiArCiAgICBnZW9tX21hcmtfaHVsbChhZXMoZmlsdGVyID0gY2xhc3MgPT0gIjJzZWF0ZXIiKSwKICAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uID0KICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgiMi1TZWF0ZXJzIGhhdmUgaGlnaCBkaXNwbGFjZW1lbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZXMsIGJ1dCBhbHNvIGhpZ2ggZnVlbCBlZmZpY2llbmN5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZm9yIHRoZWlyIGRpc3BsYWNlbWVudC4iKSkgKwogICAgZ2VvbV9tYXJrX3JlY3QoYWVzKGZpbHRlciA9IGh3eSA+IDQwKSwKICAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uID0KICAgICAgICAgICAgICAgICAgICAgICAiVGhlc2UgYXJlIFZvbGtzd2FnZW5zIikgKwogICAgZ2VvbV9tYXJrX2NpcmNsZShhZXMoZmlsdGVyID0gaHd5ID09IDEyKSwKICAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb24gPQogICAgICAgICAgICAgICAgICAgICAgICAgIlRocmVlIHBpY2t1cHMgYW5kIGFuIFNVVi4iKQpgYGAKYGBge3IgbXBnLWFubi1wb3BvdXQtMiwgZWNobyA9IEZBTFNFLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNS41fQojfCB3YXJuaW5nOiBmYWxzZQpgYGAKClRoZXNlIGFubm90YXRpb25zIGNhbiBiZSBjdXN0b21pemVkIGluIGEgbnVtYmVyIG9mIHdheXMuCgoKIyMgQXJyYW5naW5nIFBsb3RzCgpUaGVyZSBhcmUgc2V2ZXJhbCB0b29scyBhdmFpbGFibGUgZm9yIGFzc2VtYmxpbmcgZW5zZW1ibGUgcGxvdHMuCgpUaGUgW2BwYXRjaHdvcmtgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKSBwYWNrYWdlIGlzIGEKZ29vZCBjaG9pY2UuCgpBIHNpbXBsZSBleGFtcGxlOgoKYGBge3IgbXBnLXBhdGNod29yaywgZXZhbCA9IEZBTFNFfQpwMSA8LSBnZ3Bsb3QobXBnLCBhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICAgICAgICAgICAgeSA9IGh3eSkpICsKICAgIGdlb21fcG9pbnQoKQpwMiA8LSBnZ3Bsb3QobXBnLCBhZXMoeCA9IGN5bCwKICAgICAgICAgICAgICAgICAgICAgIHkgPSBod3ksCiAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGN5bCkpICsKICAgIGdlb21fYm94cGxvdCgpCnAzIDwtIGdncGxvdChtcGcsIGFlcyh4ID0gY3lsKSkgKwogICAgZ2VvbV9iYXIoKQoKbGlicmFyeShwYXRjaHdvcmspCihwMSArIHAyKSAvIHAzCmBgYApgYGB7ciBtcGctcGF0Y2h3b3JrLCBlY2hvID0gRkFMU0V9CmBgYAoKCiMjIEFuaW1hdGlvbgoKVGhlIFtgZ2dhbmltYXRlYF0oaHR0cHM6Ly9naXRodWIuY29tL3Rob21hc3A4NS9nZ2FuaW1hdGUpIHBhY2thZ2UKY2FuIGJlIHVzZWQgdG8gYWRkIGFuaW1hdGlvbiB0byBhIGBnZ3Bsb3RgIGdyYXBoLgoKU3RhcnQgd2l0aCBhIHBsb3QgYHBgIGZvciBhbGwgeWVhcnMgaW4gdGhlIGBnYXBtaW5kZXJgIGRhdGEsIHdpdGgKYHllYXJgIGluIHRoZSBiYWNrZ3JvdW5kOgoKYGBge3J9CnAgPC0gZ2FwbWluZGVyIHw+CiAgICBhcnJhbmdlKGRlc2MocG9wKSkgfD4KICAgIGdncGxvdChhZXMoeCA9IGdkcFBlcmNhcCwgeSA9IGxpZmVFeHApKSArCiAgICBnZW9tX3RleHQoYWVzKHggPSA1MDAwLCB5ID0gNTUsIGxhYmVsID0gYXMuY2hhcmFjdGVyKHllYXIpKSwKICAgICAgICAgICAgICBzaXplID0gNTAsIGNvbG9yID0gImdyZXkiLAogICAgICAgICAgICAgIGhqdXN0ID0gImNlbnRlciIsIHZqdXN0ID0gImNlbnRlciIpICsKICAgIGdlb21fcG9pbnQoYWVzKHNpemUgPSBwb3AsIGZpbGwgPSBjb250aW5lbnQpLCBzaGFwZSA9IDIxKSArCiAgICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpICsKICAgIHlsaW0oYygyMCwgODUpKSArCiAgICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemUgPSAyMCwKICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OmNvbW1hLAogICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMC4yNSAqIDEwIF4gOSwgMC41ICogMTAgXiA5LCAxMCBeIDkpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKEFmcmljYSA9ICJkZWVwc2t5Ymx1ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFzaWEgPSAicmVkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQW1lcmljYXMgPSAiZ3JlZW4iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFdXJvcGUgPSAiZ29sZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9jZWFuaWEgPSAiYnJvd24iKSkgKwogICAgbGFicyh4ID0gIkluY29tZSIsIHkgPSAiTGlmZSBleHBlY3RhbmN5IikgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYpKSArCiAgICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJDb250aW5lbnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IDEpLAogICAgICAgICAgIHNpemUgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiUG9wdWxhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbC5oanVzdCA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IDIpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICAgIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEsIGNvbG9yID0gImdyZXkyMCIpKQpgYGAKCmBgYHtyIGdhcG1pbmRlci1mdWxsLCBlY2hvID0gRkFMU0UsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA4fQpwCmBgYAoKQSBbR0lGXShodHRwczovL3NpbXBsZS53aWtpcGVkaWEub3JnL3dpa2kvR3JhcGhpY3NfSW50ZXJjaGFuZ2VfRm9ybWF0KQphbmltYXRpb246CgpgYGB7ciBnYXBtaW5kZXItYW5pbSwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGdnYW5pbWF0ZSkKYW5pbWF0ZShwICsKICAgICAgICB0cmFuc2l0aW9uX3N0YXRlcygKICAgICAgICAgICAgeWVhciwKICAgICAgICAgICAgdHJhbnNpdGlvbl9sZW5ndGggPSAyLAogICAgICAgICAgICBzdGF0ZV9sZW5ndGggPSAwKSkKYGBgCmBgYHtyIGdhcG1pbmRlci1hbmltLCBlY2hvID0gRkFMU0UsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA4fQpgYGAKCkEgbW92aWU6CgpgYGB7ciBnYXBtaW5kZXItYW5pbS1tb3ZpZSwgZXZhbCA9IEZBTFNFfQphbmltYXRlKHAgKwogICAgICAgIHRyYW5zaXRpb25fc3RhdGVzKAogICAgICAgICAgICB5ZWFyLAogICAgICAgICAgICB0cmFuc2l0aW9uX2xlbmd0aCA9IDIsCiAgICAgICAgICAgIHN0YXRlX2xlbmd0aCA9IDAsCiAgICAgICAgICAgIHdyYXAgPSBGQUxTRSksCiAgICAgICAgcmVuZGVyZXIgPSBmZm1wZWdfcmVuZGVyZXIoKSkKYGBgCjxjZW50ZXI+IDwhLS0gdGhlcmUgc2hvdWxkL21heSBiZSBhIGJldHRlciB3YXkgLS0+CmBgYHtyIGdhcG1pbmRlci1hbmltLW1vdmllLCBlY2hvID0gRkFMU0UsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA4LCBvdXQud2lkdGggPSAiMTAwJSJ9CmBgYAo8L2NlbnRlcj4KCgojIyBJbnRlcmFjdGlvbgoKCiMjIyBQbG90bHkKClRoZSBgZ2dwbG90bHlgIGZ1bmN0aW9uIGluIHRoZSBbYHBsb3RseWAgcGFja2FnZV0oaHR0cHM6Ly9wbG90bHkuY29tL3IvKQpjYW4gYmUgdXNlZCB0byBhZGQgc29tZSBpbnRlcmFjdGl2ZSBmZWF0dXJlcyB0byBhIHBsb3QgY3JlYXRlZCB3aXRoCmBnZ3Bsb3QyYC4KCiogSW4gYW4gUiBzZXNzaW9uIGEgY2FsbCB0byBgZ2dwbG90bHkoKWAgbWF5IG9wZW4gYSBicm93c2VyCiAgd2luZG93IHdpdGggdGhlIGludGVyYWN0aXZlIHBsb3QuCgoqIEluIGFuIFJTdHVkaW8gc2Vzc2lvbiB0aGUgcGxvdCBhcHBlYXJzIGluIHRoZSBncmFwaGljcyBwYW5lbC4KCiogSW4gYW4gUm1hcmtkb3duIGRvY3VtZW50IHRoZSBpbnRlcmFjdGl2ZSBwbG90IGlzIGVtYmVkZGVkIGluIHRoZQogIGBodG1sYCBmaWxlLgoKQW5vdGhlciBpbnRlcmFjdGl2ZSBwbG90dGluZyBhcHByb2FjaCB0aGF0IGNhbiBiZSB1c2VkIGZyb20gUiBpcwpkZXNjcmliZWQgaW4gYW4gW0luZm93b3JsZAphcnRpY2xlXShodHRwczovL3d3dy5pbmZvd29ybGQuY29tL2FydGljbGUvMzYwNzA2OC9wbG90LWluLXItd2l0aC1lY2hhcnRzNHIuaHRtbCkuCgpBIHNpbXBsZSBleGFtcGxlIHVzaW5nIGBnZ3Bsb3RseSgpYDoKCmBgYHtyIG1wZy1wbG90bHksIGV2YWwgPSBGQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBsb3RseSkKcCA8LSBnZ3Bsb3QobXV0YXRlKG1wZywgY3lsID0gZmFjdG9yKGN5bCkpKSArCiAgICBnZW9tX3BvaW50KGFlcyh4ID0gZGlzcGwsCiAgICAgICAgICAgICAgICAgICB5ID0gaHd5LAogICAgICAgICAgICAgICAgICAgZmlsbCA9IGN5bCksCiAgICAgICAgICAgICAgIHNoYXBlID0gMjEsCiAgICAgICAgICAgICAgIHNpemUgPSAzKQpnZ3Bsb3RseShwKQpgYGAKYGBge3IgbXBnLXBsb3RseSwgZWNobyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmBgYAoKQWRkaW5nIGEgYHRleHRgIGFlc3RoZXRpYyBhbGxvd3MgdGhlIHRvb2x0aXAgZGlzcGxheSB0byBiZSBjdXN0b21pemVkOgoKYGBge3IgbXBnLXBsb3RseS0yLCBldmFsID0gRkFMU0V9CnAgPC0gZ2dwbG90KG11dGF0ZShtcGcsIGN5bCA9IGZhY3RvcihjeWwpKSkgKwogICAgZ2VvbV9wb2ludChhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICAgICAgICAgeSA9IGh3eSwKICAgICAgICAgICAgICAgICAgIGZpbGwgPSBjeWwsCiAgICAgICAgICAgICAgICAgICB0ZXh0ID0gcGFzdGUoeWVhciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYW51ZmFjdHVyZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwpKSwKICAgICAgICAgICAgICAgc2hhcGUgPSAyMSwKICAgICAgICAgICAgICAgc2l6ZSA9IDMpCmdncGxvdGx5KHAsIHRvb2x0aXAgPSAidGV4dCIpIHw+CiAgICBzdHlsZShob3ZlcmxhYmVsID0gbGlzdChiZ2NvbG9yID0gIndoaXRlIikpCmBgYApgYGB7ciBtcGctcGxvdGx5LTIsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmBgYAoKCiMjIyBHZ2lyYXBoCgpUaGUgW2BnZ2lyYXBoYCBwYWNrYWdlXShodHRwczovL2RhdmlkZ29oZWwuZ2l0aHViLmlvL2dnaXJhcGgvKQpwcm92aWRlcyBhbm90aGVyIGFwcHJvYWNoLgoKYGBge3IgbXBnLWdnaXJhcGgsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnaXJhcGgpCnAgPC0gZ2dwbG90KG11dGF0ZShtcGcsIGN5bCA9IGZhY3RvcihjeWwpKSkgKwogICAgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZSgKICAgICAgICBhZXMoeCA9IGRpc3BsLAogICAgICAgICAgICB5ID0gaHd5LAogICAgICAgICAgICBmaWxsID0gY3lsLAogICAgICAgICAgICB0b29sdGlwID0gcGFzdGUoeWVhciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hbnVmYWN0dXJlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsKSksCiAgICAgICAgc2hhcGUgPSAyMSwKICAgICAgICBzaXplID0gMykKZ2lyYWZlKGdnb2JqID0gcCkKYGBgCgpgYGB7ciBtcGctZ2dpcmFwaCwgZWNobyA9IEZBTFNFfQpgYGAKCgojIyMgR3JhbW1hciBvZiBJbnRlcmFjdGl2ZSBHcmFwaGljcwoKVGhlcmUgaGF2ZSBiZWVuIHNldmVyYWwgZWZmb3J0cyB0byBkZXZlbG9wIGEgZ3JhbW1hciBvZiBpbnRlcmFjdGl2ZQpncmFwaGljcywgaW5jbHVkaW5nIFtgZ2d2aXNgXShodHRwczovL2dndmlzLnJzdHVkaW8uY29tLykgYW5kCltgYW5pbWludGBdKGh0dHBzOi8vdGRob2NrLmdpdGh1Yi5pby9hbmltaW50Lyk7IG5laXRoZXIgc2VlbXMgdG8gYmUKdW5kZXIgYWN0aXZlIGRldmVsb3BtZW50IGF0IHRoaXMgdGltZS4KCkEgcHJvbWlzaW5nIGFwcHJvYWNoIGlzCltWZWdhLUxpdGVdKGh0dHBzOi8vdmVnYS5naXRodWIuaW8vdmVnYS1saXRlLyksIHdpdGggYSBQeXRob24KaW50ZXJmYWNlIFtBbHRhaXJdKGh0dHBzOi8vYWx0YWlyLXZpei5naXRodWIuaW8vKSBhbmQgYW4gUiBpbnRlcmZhY2UKW2FsdGFpcl0oaHR0cHM6Ly92ZWdhd2lkZ2V0LmdpdGh1Yi5pby9hbHRhaXIvKSB0byB0aGUgUHl0aG9uCmludGVyZmFjZS4KCkFuIGV4YW1wbGUgdXNpbmcgdGhlIGBhbHRhaXJgIHBhY2thZ2U6CgpgYGB7ciBydWJiZXItYWx0YWlyLCBldmFsID0gRkFMU0V9CnJ1YiA8LSByZWFkLmNzdihoZXJlOjpoZXJlKCJydWJiZXIuY3N2IikpCgpsaWJyYXJ5KGFsdGFpcikKCmNoYXJ0VEggPC0gYWx0JENoYXJ0KHJ1YikkCiAgICBtYXJrX3BvaW50KCkkCiAgICBlbmNvZGUoeCA9IGFsdCRYKCJIOlEiLCBzY2FsZSA9IGFsdCRTY2FsZShkb21haW4gPSByYW5nZShydWIkSCkpKSwKICAgICAgICAgICB5ID0gYWx0JFkoIlQ6USIsIHNjYWxlID0gYWx0JFNjYWxlKGRvbWFpbiA9IHJhbmdlKHJ1YiRUKSkpKQoKYnJ1c2ggPC0gYWx0JHNlbGVjdGlvbl9pbnRlcnZhbCgpCgpjaGFydFRIX2JydXNoIDwtIGNoYXJ0VEgkYWRkX3NlbGVjdGlvbihicnVzaCkKCmNoYXJ0VEhfc2VsZWN0aW9uIDwtCiAgICBjaGFydFRIX2JydXNoJGVuY29kZShjb2xvciA9IGFsdCRjb25kaXRpb24oYnJ1c2gsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk9yaWdpbjpOIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHQkdmFsdWUoImxpZ2h0Z3JheSIpKSkKCmNoYXJ0QVQgPC0gY2hhcnRUSF9zZWxlY3Rpb24kCiAgICBlbmNvZGUoeCA9IGFsdCRYKCJUOlEiLCBzY2FsZSA9IGFsdCRTY2FsZShkb21haW4gPSByYW5nZShydWIkVCkpKSwKICAgICAgICAgICB5ID0gYWx0JFkoIkE6USIsIHNjYWxlID0gYWx0JFNjYWxlKGRvbWFpbiA9IHJhbmdlKHJ1YiRBKSkpKQoKY2hhcnRBVCB8IGNoYXJ0VEhfc2VsZWN0aW9uCmBgYAoKVGhlIHJlc3VsdGluZyBsaW5rZWQgcGxvdHM6CgpgYGB7ciBydWJiZXItYWx0YWlyLCBlY2hvID0gRkFMU0UsIGVycm9yID0gVFJVRSwgd2FybmluZyA9IEZBTFNFfQpgYGAKCgojIyBOb3RlcwoKKiBBIG51bWJlciBvZiBvdGhlciBbYGdncGxvdGAKICBleHRlbnNpb25zXShodHRwczovL2V4dHMuZ2dwbG90Mi50aWR5dmVyc2Uub3JnLykgYXJlIGF2YWlsYWJsZS4KCiogQSBbYmxvZwogIHBvc3RdKGh0dHBzOi8vbWVkaXVtLmNvbS9iYmMtdmlzdWFsLWFuZC1kYXRhLWpvdXJuYWxpc20vaG93LXRoZS1iYmMtdmlzdWFsLWFuZC1kYXRhLWpvdXJuYWxpc20tdGVhbS13b3Jrcy13aXRoLWdyYXBoaWNzLWluLXItZWQwYjM1NjkzNTM1KQogIGV4cGxhaW5zIGhvdyB0aGUgW0JCQyBWaXN1YWwgYW5kIERhdGEKICBKb3VybmFsaXNtXShodHRwczovL21lZGl1bS5jb20vYmJjLXZpc3VhbC1hbmQtZGF0YS1qb3VybmFsaXNtKSB0ZWFtCiAgY3JlYXRlcyB0aGVpciBncmFwaGljcy4gTW9yZSBkZXRhaWxzIGFyZSBwcm92aWRlZCBpbiBhbiBbX1IgY29vawogIGJvb2tfXShodHRwczovL2JiYy5naXRodWIuaW8vcmNvb2tib29rLykuCgo8IS0tCiogQSBbYmxvZwogIHBvc3RdKGh0dHBzOi8vYmxvZy5yZXZvbHV0aW9uYW5hbHl0aWNzLmNvbS8yMDE2LzA3L2RhdGEtam91cm5hbGlzbS13aXRoLXItYXQtNTM4Lmh0bWwpCiAgZGVzY3JpYmVzIHRoZSB1c2Ugb2YgUiBhbmQgYGdncGxvdGAgYnkKICBbRml2ZVRoaXJ0eUVpZ2h0XShodHRwczovL2ZpdmV0aGlydHllaWdodC5jb20vKS4gIFRoZSBgZ2d0aGVtZXNgCiAgcGFja2FnZXMgaW5jbHVkZXMgYHRoZW1lX2ZpdmV0aGlydHllaWdodGAgdG8gZW11bGF0ZSB0aGVpciBzdHlsZS4KLS0+CgoKIyMgUmVhZGluZwoKQ2hhcHRlcnMgW19EYXRhCnZpc3VhbGl6YXRpb25fXShodHRwczovL3I0ZHMuaGFkbGV5Lm56L2RhdGEtdmlzdWFsaXplLmh0bWwpIGFuZApbX0dyYXBoaWNzIGZvcgpjb21tdW5pY2F0aW9uX10oaHR0cHM6Ly9yNGRzLmhhZGxleS5uei9jb21tdW5pY2F0aW9uLmh0bWwpCmluIFtfUiBmb3IgRGF0YSBTY2llbmNlX10oaHR0cHM6Ly9yNGRzLmhhZGxleS5uei8pLCBPJ1JlaWxseS4KCkNoYXB0ZXIgW19NYWtlIGEgcGxvdF9dKGh0dHBzOi8vc29jdml6LmNvL21ha2VwbG90Lmh0bWwpIGluIFtfRGF0YQpWaXN1YWxpemF0aW9uX10oaHR0cHM6Ly9zb2N2aXouY28vKS4KCkNoYXB0ZXIKW19nZ3Bsb3QyX10oaHR0cHM6Ly9yYWZhbGFiLmRmY2kuaGFydmFyZC5lZHUvZHNib29rLXBhcnQtMS9kYXRhdml6L2dncGxvdDIuaHRtbCkKaW4gW19JbnRyb2R1Y3Rpb24gdG8gRGF0YSBTY2llbmNlOiBEYXRhIEFuYWx5c2lzIGFuZCBQcmVkaWN0aW9uCkFsZ29yaXRobXMgd2l0aCBSX10oaHR0cHM6Ly9yYWZhbGFiLmRmY2kuaGFydmFyZC5lZHUvZHNib29rLXBhcnQtMS8pLgoKCiMjIEludGVyYWN0aXZlIFR1dG9yaWFsCgpBbiBpbnRlcmFjdGl2ZSBbYGxlYXJucmBdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhcm5yLykgdHV0b3JpYWwKZm9yIHRoZXNlIG5vdGVzIGlzIFthdmFpbGFibGVdKGByIFdMTksoInR1dG9yaWFscy9nZ3Bsb3QuUm1kIilgKS4KCllvdSBjYW4gcnVuIHRoZSB0dXRvcmlhbCB3aXRoCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpTVEFUNDU4MDo6cnVuVHV0b3JpYWwoImdncGxvdCIpCmBgYAoKWW91IGNhbiBpbnN0YWxsIHRoZSBjdXJyZW50IHZlcnNpb24gb2YgdGhlIGBTVEFUNDU4MGAgcGFja2FnZSB3aXRoCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpyZW1vdGVzOjppbnN0YWxsX2dpdGxhYigibHVrZS10aWVybmV5L1NUQVQ0NTgwIikKYGBgCgpZb3UgbWF5IG5lZWQgdG8gaW5zdGFsbCB0aGUgYHJlbW90ZXNgIHBhY2thZ2UgZnJvbSBDUkFOIGZpcnN0LgoKCiMjIEV4ZXJjaXNlcwoKMS4gSW4gdGhlIGZvbGxvd2luZyBleHByZXNzaW9uLCB3aGljaCB2YWx1ZSBvZiB0aGUgYHNoYXBlYCBhZXN0aGV0aWMKICAgcHJvZHVjZXMgYSBwbG90IHdpdGggcG9pbnRzIHJlcHJlc2VudGVkIGFzIHRyaWFuZ2xlcyBvdXRsaW5lZCBpbgogICBibGFjayBjb2xvcmVkIGFjY29yZGluZyB0byB0aGUgbnVtYmVyIG9mIGN5bGluZGVycz8KCjwhLS0gIyMgbm9saW50IHN0YXJ0IC0tPgogICAgYGBgcgogICAgbGlicmFyeShnZ3Bsb3QyKQogICAgZ2dwbG90KG1wZywgYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgZmlsbCA9IGZhY3RvcihjeWwpKSkgKwogICAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDQsIHNoYXBlID0gLS0tKQogICAgYGBgCjwhLS0gIyMgbm9saW50IGVuZCAtLT4KCiAgICBhLiAxNQogICAgYi4gMTcKICAgIGMuIDIxCiAgICBkLiAyNAoKMi4gSXQgY2FuIHNvbWV0aW1lcyBiZSB1c2VmdWwgdG8gcGxvdCB0ZXh0IGxhYmVscyBpbiBhIHNjYXR0ZXJwbG90CiAgIGluc3RlYWQgb2YgcG9pbnRzLiBDb25zaWRlciB0aGUgcGxvdCBzZXQgdXAgYXMKCiAgICBgYGByCiAgICBsaWJyYXJ5KGdncGxvdDIpCiAgICBsaWJyYXJ5KGRwbHlyKQogICAgZGF0YShnYXBtaW5kZXIsIHBhY2thZ2UgPSAiZ2FwbWluZGVyIikKICAgIHAgPC0gZmlsdGVyKGdhcG1pbmRlciwgeWVhciA9PSAyMDA3KSB8PgogICAgICAgIGdyb3VwX2J5KGNvbnRpbmVudCkgfD4KICAgICAgICBzdW1tYXJpemUoZ2RwUGVyY2FwID0gbWVhbihnZHBQZXJjYXApLCBsaWZlRXhwID0gbWVhbihsaWZlRXhwKSkgfD4KICAgICAgICBnZ3Bsb3QoYWVzKHggPSBnZHBQZXJjYXAsIHkgPSBsaWZlRXhwKSkKICAgIGBgYAogICAgV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBwcm9kdWNlcyBhIHBsb3Qgd2l0aCBjb250aW5lbnQKICAgIG5hbWVzIG9uIHdoaXRlIHJlY3RhbmdsZXM/CgogICAgYS4gYHAgKyBnZW9tX3RleHQoYWVzKGxhYmVsID0gY29udGluZW50KSlgCiAgICBiLiBgcCArIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gY29udGluZW50KSlgCiAgICBjLiBgcCArIGdlb21fbGFiZWwobGFiZWwgPSBjb250aW5lbnQpYAogICAgZC4gYHAgKyBnZW9tX3RleHQodGV4dCA9IGNvbnRpbmVudClgCgozLiBUaGUgZm9sbG93aW5nIGNvZGUgcGxvdHMgYSBfa2VybmVsIGRlbnNpdHkgZXN0aW1hdGVfIGZvciB0aGUKICAgYGVydXB0aW9uc2AgdmFyaWFibGUgaW4gdGhlIGBmYWl0aGZ1bGAgZGF0YSBzZXQ6CgogICAgYGBgcgogICAgbGlicmFyeShnZ3Bsb3QyKQogICAgZ2dwbG90KGZhaXRoZnVsLCBhZXMoeCA9IGVydXB0aW9ucykpICsgZ2VvbV9kZW5zaXR5KGJ3ID0gMC4xKQogICAgYGBgCiAgICBMb29rIGF0IHRoZSBoZWxwIHBhZ2UgZm9yIGBnZW9tX2RlbnNpdHlgLiBXaGljaCBvZiB0aGUgZm9sbG93aW5nIGJlc3QKICAgIGRlc2NyaWJlcyB3aGF0IHNwZWNpZnlpbmcgYSB2YWx1ZSBmb3IgYGJ3YCBkb2VzOgoKICAgIGEuIENoYW5nZXMgdGhlIF9rZXJuZWxfIHVzZWQgdG8gY29uc3RydWN0IHRoZSBlc3RpbWF0ZS4KICAgIGIuIENoYW5nZXMgdGhlIF9zbW9vdGhpbmcgYmFuZHdpZHRoXyB0byBtYWtlIHRoZSByZXN1bHQgbW9yZSBvciBsZXNzIHNtb290aC4KICAgIGMuIENoYW5nZXMgdGhlIGBzdGF0YCB1c2VkIHRvIGBzdGF0X2J3YC4KICAgIGQuIEhhcyBubyBlZmZlY3Qgb24gdGhlIHJldHVsdC4KCjQuIFRoaXMgY29kZSBjcmVhdGVzIGEgbWFwIG9mIElvd2EgY291bnRpZXMuCgogICAgYGBgcgogICAgbGlicmFyeShnZ3Bsb3QyKQogICAgcCA8LSBnZ3Bsb3QobWFwX2RhdGEoImNvdW50eSIsICJpb3dhIiksCiAgICAgICAgICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApKSArCiAgICAgICAgZ2VvbV9wb2x5Z29uKCwgZmlsbCA9ICJXaGl0ZSIsIGNvbG9yID0gImJsYWNrIikKICAgIGBgYAogICAKICAgIFdoaWNoIG9mIHRoZXNlIHByb2R1Y2VzIGEgcGxvdCB3aXRoIGFuIGFzcGVjdCByYXRpbyB0aGF0IGJlc3QKICAgIG1hdGNoZXMgdGhlIG1hcCBvbiBbdGhpcwogICAgcGFnZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3cvaW5kZXgucGhwP3RpdGxlPUxpc3Rfb2ZfY291bnRpZXNfaW5fSW93YSZvbGRpZD0xMDAxMTcxMDgyKT8KCiAgICBhLiBgcCArIGNvb3JkX2ZpeGVkKDAuNSlgIAogICAgYi4gYHAgKyBjb29yZF9maXhlZCgwLjc1KWAKICAgIGMuIGBwICsgY29vcmRfZml4ZWQoMS4zNSlgCiAgICBkLiBgcCArIGNvb3JkX2ZpeGVkKDEuOTUpYAoKNS4gQ29uc2lkZXIgdGhlIHR3byBwbG90cyBjcmVhdGVkIGJ5IHRoaXMgY29kZSAocHJpbnQgdGhlIHZhbHVlcyBvZgogICBgcDFgIGFuZCBgcDJgIHRvIHNlZSB0aGUgcGxvdHMpOgoKICAgIGBgYHIKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGRhdGEoZ2FwbWluZGVyLCBwYWNrYWdlID0gImdhcG1pbmRlciIpCiAgICBwMSA8LSBnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoeCA9IGxvZyhnZHBQZXJjYXApLCB5ID0gbGlmZUV4cCkpICsKICAgICAgICBnZW9tX3BvaW50KCkgKwogICAgICAgIHNjYWxlX3hfY29udGludW91cyhuYW1lID0gIiIpCiAgICBwMiA8LSBnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoeCA9IGdkcFBlcmNhcCwgeSA9IGxpZmVFeHApKSArCiAgICAgICAgZ2VvbV9wb2ludCgpICsKICAgICAgICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6Y29tbWEsIG5hbWUgPSAiIikgCiAgICBgYGAKCiAgICBXaGljaCBvZiB0aGVzZSBzdGF0ZW1lbnRzIGlzIHRydWU/CgogICAgYS4gVGhlIGB4YCBheGlzIGxhYmVscyBhcmUgaWRlbnRpY2FsIGluIGJvdGggcGxvdHMuCiAgICBiLiBUaGUgYHhgIGF4aXMgbGFiZWxzIGluIGBwMmAgYXJlIGluIGRvbGxhcnM7IHRoZSBsYWJlbHMgaW4gYHAxYAogICAgICAgYXJlIGluIGxvZyBkb2xsYXJzLiAKICAgIGMuIFRoZSBgeGAgYXhpcyBsYWJlbHMgaW4gYHAxYCBhcmUgaW4gZG9sbGFyczsgdGhlIGxhYmVscyBpbiBgcDJgCiAgICAgICBhcmUgaW4gbG9nIGRvbGxhcnMuCiAgICBkLiBUaGVyZSBhcmUgbm8gbGFiZWxzIG9uIHRoZSBgeGAgYXhpcyBpbiBgcDJgLgoKNi4gQ29uc2lkZXIgdGhlIHBsb3QgY3JlYXRlZCBieQoKICAgIGBgYHIKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIGRhdGEoZ2FwbWluZGVyLCBwYWNrYWdlID0gImdhcG1pbmRlciIpCiAgICBwIDwtIGdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0gZ2RwUGVyY2FwLCB5ID0gbGlmZUV4cCkpICsKICAgICAgICBnZW9tX3BvaW50KCkgKwogICAgICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgCiAgICBgYGAKCiAgICBXaGljaCBvZiB0aGVzZSBleHByZXNzaW9ucyBwcm9kdWNlcyBhIHBsb3Qgd2l0aCBhIHdoaXRlIGJhY2tncm91bmQ/CgogICAgYS4gYHBgCiAgICBiLiBgcCArIHRoZW1lX2dyZXkoKWAKICAgIGMuIGBwICsgdGhlbWVfY2xhc3NpYygpYAogICAgZC4gYHAgKyBnZ3RoZW1lczo6dGhlbWVfZWNvbm9taXN0KClgCgo3LiBUaGVyZSBhcmUgbWFueSBkaWZmZXJlbnQgd2F5cyB0byBjaGFuZ2UgdGhlIGB4YCBheGlzIGxhYmVsIGluCiAgIGBnZ3Bsb3RgLiAgQ29uc2lkZXIgdGhlIHBsb3QgY3JlYXRlZCBieQoKICAgIGBgYHIKICAgIGxpYnJhcnkoZ2dwbG90MikKICAgIHAgPC0gZ2dwbG90KG1wZywgYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsKICAgICAgICBnZW9tX3BvaW50KCkKICAgIGBgYAoKICAgIFdoaWNoIG9mIHRoZSBmb2xsb3dpbmcgZG9lcyAqKm5vdCoqIGNoYW5nZSB0aGUgYHhgIGF4aXMgbGFiZWwgdG8KICAgIF9EaXNwbGFjZW1lbnRfPwoKICAgIGEuIGBwICsgbGFicyh4ID0gIkRpc3BsYWNlbWVudCIpYAogICAgYi4gYHAgKyBzY2FsZV94X2NvbnRpbnVvdXMoIkRpc3BsYWNlbWVudCIpYAogICAgYy4gYHAgKyB4bGFiKCJEaXNwbGFjZW1lbnQiKWAKICAgIGQuIGBwICsgdGhlbWUoYXhpcy50aXRsZS54ID0gIkRpc3BsYWNlbWVudCIpYAoK