ggplot2 tips and tricks
Creating ggplot2
plots for use in examples
plot1 <- mpg %>%
ggplot(mapping = aes(x = displ, y = hwy)) +
geom_point() +
geom_smooth()
plot1

plot3 <- ggplot(data=mpg, mapping=aes(x = class, y = hwy)) +
geom_boxplot()
plot3

plot4 <- ggplot(mpg) +
geom_bar(aes(class,fill=class))
plot4

ggplot2 - Add titles, subtitles, captions, axes labels
One of the great things about ggplot2
is the way that you can create graphs quickly for your own use (to visualize data), but then you can take that object and refine it ready for presentation or publication. Here we add labels and titles to an existing plot.
plot1 +
labs(title = "Highway mileage is inversely related to engine size",
subtitle = "Data: mpg",
x = "Engine size (L)",
y = "Highway mileage (mpg)",
caption = "Source: https://github.com/MikeKSmith/master_the_tidyverse_2019_4Q")

ggplot2 - Using %+%
to change the dataset
One often overlooked functionality in ggplot2
is the ability to change the dataset within the ggplot2
object and essentially replay the same graph for new data. This functionality is really useful if you have draft data which you can use to create early visualizations which you might want to refine later with the final data. The assumption here is that variable names won’t change, since the mapping
to aes
thetics is carried over.
In the case below, we have to slightly change variable names in the mtcars
dataset to get it to match the mpg
data. But once we’ve done that we can simply update plot1
with the new data.
mtcars2 <- mtcars %>%
rename(hwy = mpg,
displ = disp)
plot2 <- plot1 %+%
mtcars2
plot2

Use forcats library functions to sort factors by size of some variable
plot5 <- mpg %>%
group_by(class) %>%
summarise(n = n()) %>%
mutate(class = fct_reorder(class, n)) %>%
ggplot(mapping = aes(x = class,fill=class)) +
geom_col(mapping = aes(y = n)) +
coord_flip()
plot5

patchwork - arrange multiple plots
The patchwork
package (by Thomas Lin Pedersen - https://patchwork.data-imaginist.com/) allows the user to define how to combine graphs. The +
operator here combines plots into a single plot object. plot_annotation
then allows us to define overarching titles, labels, annotations. tag_levels
defines how you might want to identify the graphs and tag_suffix
pastes onto the end of tag_level
.
library(patchwork)
plot3 + plot4 + plot_annotation(title = "mpg data exploration",
tag_levels="a", tag_suffix = ")")

ggeasy - easy access to theme aspects
The x-axis class
variable names above look AWFUL when the plots are pulled into patchwork
. I know that in ggplot2
I can rotate the axes labels… but can I recall the specific theme settings?
The ggeasy
package (by Jonathan Carroll - https://jonocarroll.github.io/ggeasy/) provides quick access to tweak commonly used ggplot2
theme settings.
library(ggeasy)
plot3b <- plot3 +
easy_rotate_x_labels(side = "right")
plot4b <- plot4 +
easy_rotate_x_labels(side = "right")
plot3b + plot4b + plot_annotation(title = "mpg data exploration",
tag_levels="a", tag_suffix = ")")

patchwork continued - Combining many plots
Here we’re using patchwork to produce a slightly more complex layout, combining plot3
and plot4
in one row, then plot1
underneath. The -
signifies that the combined plot3 + plot4
are on the same level as plot1
, and plot_layout
defines that there is one column.
plot3b + plot4b - plot1 + plot_layout(ncol=1) + plot_annotation(title = "mpg data exploration",
tag_levels="i", tag_suffix = ")")

Use Data Visualization best practice
In the combined graph of plot3 + plot4
above, there is a lot of repeated information in the graphic (vehicle class, legends etc.). By flipping the plot to be horizontal, we can show the boxplot with the bar chart and eliminate much of the redundant information.
Here we’re also using the ggeasy
functions easy_remove_legend()
and easy_remove_y_axis()
to remove the extraneous information from plot4c
.
plot3c <- plot3 + coord_flip()
plot4c <- plot4 + coord_flip() +
easy_remove_legend() +
easy_remove_y_axis()
plot3c + plot4c

cowplot - templates for publication ready plots
The cowplot
package (by Claus O. Wilke - https://wilkelab.org/cowplot/index.html) implements many of the standards and recommendations from his “Fundamentals of Data Visualization” book. It extends ggplot2
with themes for publication, functions for plot alignment and additional annotations.
library(cowplot)
plot1 +
labs(title="Highway mileage is inversely related to engine size",
x = "Engine displacement (Litres)",
y = "Highway mileage (mpg)") +
theme_cowplot(font_size = 14)

plot1 +
theme_minimal_grid()

plot1 +
theme_minimal_hgrid()

Adding annotations and text to plots
The ggdraw
function with the cowplot
package takes an existing ggplot2
object and sets up a drawing layer for additional annotation on top. Here we’re adding a “DRAFT” watermark.
ggdraw(plot1) +
draw_label("Draft", color = "light grey", alpha=0.3, size = 100, angle = 45)

Using stamp
to label plots
The stamp
functionality allows you to label an attribute of a whole graphic. In the book, Wilke labels plots as “ugly” or “bad”, but here we’re marking the whole graph as “DRAFT”. This is perhaps less obtrusive than the watermark above.
stamp(plot1, label = "DRAFT", color = "red")

ggrepel to label points without overlap
The ggrepel
package (by Kamil Slowikowsi - https://slowkow.com/ggrepel) has been developed to ensure that text labels in plots do not overlap each other, do not cover data points, nor fall outside of the plotting area. The method of doing so is indistinguishable from magic (or so it seems to me).
library(ggrepel)
plot_ugly <- ggplot(mtcars) +
aes(
x = wt, y = mpg,
label = rownames(mtcars)
) +
geom_point(color = "red") +
geom_text()
stamp_ugly(plot_ugly)

ggplot(mtcars) +
aes(
x = wt, y = mpg,
label = rownames(mtcars)
) +
geom_point(color = "red") +
geom_text_repel()

gghighlight - highlighting visualization items based on predicates
gghighlight
(by Hiroaki Yutani - https://yutannihilation.github.io/gghighlight/index.html) is a ggplot2
extension which highlights ggplot2
geoms
that match a given predicate.
In many cases this leads to the recommendations in “Fundamentals of Data Visualization” because is allows the reader to focus straight in on the particular element of the plot that is key to the claims being made. It helps the “story” of our data visualization…
library(gghighlight)
plot4 +
gghighlight(class == "suv")

plot1 +
gghighlight(class == "suv")

plot5 +
gghighlight(class == "suv") +
theme(legend.position="none")

ggridges to compare densities neatly
ggridges
(by Claus O Wilke - https://cran.r-project.org/web/packages/ggridges/vignettes/introduction.html) is a useful extension for plotting many densities, or other ridgeline visualizations where you may wish to plot the ridges by a categorical variable. ggridges
stacks these plots so that the individual ridges or densities are easily visible, but compactly.
Ordering the categorical variable or factor by magnitude of the x-axis variable using the fct_reorder
function from the forcats
package, makes it easier for the viewer to see which category has the largest mean, as well as the spread within the density or ridgeline plot.
library(ggridges)
ggplot(data = mpg, mapping = aes(x = hwy, y = class)) +
geom_density_ridges()

## Using fct_reorder from the `forcats` package to sort the factor
## by the median of `hwy` within each class.
ggplot(data = mpg, mapping = aes(x = hwy, y = fct_reorder(class, hwy))) +
geom_density_ridges()

GGally - Quick exploratory data analysis
ggally
(by the GGobi Foundation - https://ggobi.github.io/ggally/) provides useful matrix plots of data and is really well suited to quick data visualization for exploratory data analysis.
ggscatmat
quickly produces a scatterplot matrix, density and correlation readout for continuous variables.
ggpairs
is a more complete visualization of both continuous and categorical variables, but is somewhat slower than ggscatmat
.
library(GGally)
## Continuous outcomes only can use the quicker `ggscatmat`
mpg %>%
select(cty, hwy, displ) %>%
ggscatmat()

## For more general use with both categorical and continuous variables
## use ggpairs. Although function this is slower.
mpg %>%
select(class, cty, hwy, displ) %>%
ggpairs()

ggforce - additional geoms and extensions to ggplot2
ggforce
(by Thomas Lin Pedersen - https://ggforce.data-imaginist.com/) provides additional geoms
and extensions to ggplot2
.
library(ggforce)
plot6 <- mpg %>%
ggplot(mapping = aes(x = displ, y = hwy, colour = class)) +
geom_point() +
geom_mark_ellipse(aes(fill=class))
plot6

## Alternatively label the overlapping ellipses
plot6 +
geom_mark_ellipse(aes(label=class))

## Perhaps better using gghighlight
plot6 +
gghighlight(class == "2seater", use_direct_label = FALSE)

In addition ggforce
provides the facet_zoom
functionality to provide one visualization which shows the overview of the data, but zooms in to a predicate region, or an x- or y-axis region.
## Zoom in on the 2seater class
ggplot(data = mpg, mapping = aes(x = displ, y = hwy, colour = class)) +
geom_point() +
facet_zoom(class=="2seater")

## Alternatively zoom by x-axis limits
ggplot(data = mpg, mapping = aes(x = displ, y = hwy, colour = class)) +
geom_point() +
facet_zoom(xlim=c(1.5, 2.5))

ggedit - manipulate plot objects
ggedit
(from Metrum Research Group - https://metrumresearchgroup.github.io/ggedit/) provides very useful functions for removing layers of a ggplot2
visualization object. This can be useful when we want to track how plots were manipulated through scripts. For example if a plot has been QCed and saved as an .Rdata
object we can subsequently load that object, use ggedit
to alter it and save back to a new output object, or version. The changes from one state to another can easily be tracked and traced through the script.
Remove plot layers
library(ggedit)
## remove loess smooth and replace with a linear regression line
## NB: Uses %>% to pass in the plot object
## Because resulting output is a ggplot2 object we can revert to "+".
plot1 %>%
ggedit::remove_geom(geom = "smooth") +
geom_smooth(method = "lm")

## UPDATE: Can use `-` to remove geoms.
plot1 -
geom_smooth()

The -
(minus) functionality to remove geoms
from ggplot2
objects works on a “last in, first out” model if there is more than one geom
with the same type.
plot1c <- ggplot(mpg, aes(x = displ, y = hwy)) + geom_point()
plot1d <- plot1c + geom_point(shape=8, colour = "red",alpha=1,stroke=4)
plot1d

plot1d - geom_point() #LIFO

plot1d - geom_point() - geom_point()

ggedit - interactive editing of plot attributes
Another useful part of ggedit
is the Shiny application which can be used to interactively change ggplot2
object layers and themes, then saving the resulting object back to the environment.
ggedit(plot1)
xgxr - Helper functions for plotting concentration data
xgxr
is a package developed by Novartis (http://opensource.nibr.com/xgx/) for exploratory data analysis, particularly in the field of pharmacokinetic and pharmacodynamic data. Here we are only scratching the surface of this package but in particular I liked the ability to show minor grid lines on the log scale.
library(xgxr)
ggplot(data = Theoph, mapping = aes(x = Time, y = conc, group = Subject)) +
geom_line() +
xgx_scale_y_log10()

xgx_stat_ci - stat_summary with additional options for CI type
The xgx_stat_ci
is very similar to functionality in the stat_summary
geom (https://ggplot2.tidyverse.org/reference/stat_summary.html) but it extends this to allow the user to specify the confidence region width and the distribution of the outcome e.g. normal
, lognormal
, binomial
.
In the code below we’re also using the snap
function from the metrumrg
package which snaps observed sample times to a nominal grid. This allows us to summarise observed values at their nominal times.
times <- c(0, 0.25, 0.5, 1, 1.5, 2, 3.5, 5, 7, 9, 12, 24)
myTheoph <- Theoph %>%
mutate(NTPD = metrumrg::snap(Time, times))
table(myTheoph$NTPD)
0 0.25 0.5 1 2 3.5 5 7 9 12 24
12 12 11 13 12 12 12 12 12 12 12
theoPlot1 <- ggplot(data = myTheoph, aes(x = NTPD,y = conc)) +
geom_line(mapping = aes(group = Subject), alpha=0.2) +
xgx_stat_ci(color = "red", conf_level = 0.9, distribution = "lognormal") +
labs(y = "Concentration (ng/ml)")
theoPlot1

theoPlot1 +
xgx_scale_y_log10()

survminer - Plotting survival curves
We’re now going to look at particular cases where the visualization of specialist data types produce ggplot2
objects. The first is for survival data.
The survminer
package (by Alboukadel Kassambara - https://rpkgs.datanovia.com/survminer/index.html) produces nice ggplot2
visualizations of survival data including the number of subjects at risk within strata.
library(survival)
library(survminer)
fit <- survfit(Surv(time, status) ~ sex, data = lung)
ggsurvplot(fit, data = lung)


ggsurvplot(
fit,
data = lung,
size = 1, # change line size
palette =
c("#E7B800", "#2E9FDF"),# custom color palettes
conf.int = TRUE, # Add confidence interval
pval = TRUE, # Add p-value
risk.table = TRUE, # Add risk table
risk.table.col = "strata",# Risk table color by groups
legend.labs =
c("Male", "Female"), # Change legend labels
risk.table.height = 0.25 # Useful to change when you have multiple groups
)

ggraph - Visualizing networks
ggraph
(by Thomas Lin Pedersen - https://ggraph.data-imaginist.com/) produces visualizations of networks. Here we visualize the relationships and dependencies associated with the tidyverse
package, based on the miniCRAN
package function makeDepGraph
and the tidygraph
and ggraph
packages. Note that ggraph
uses repel
functionality in geom_node_text
to present tidy labels in the visualization.
library(miniCRAN)
library(ggraph)
library(tidygraph)
tidyverseDependencies <- makeDepGraph(pkg = "tidyverse") %>%
as_tbl_graph()
ggraph(tidyverseDependencies, layout = "graphopt") +
geom_node_point() +
geom_edge_link(aes(colour = type)) +
geom_node_text(aes(label = name),
repel = TRUE)

Save all ggplot2 objects to working directory
Finally, here’s one I produced (by Mike K Smith!). This snippet of code picks out all objects in the environment that have type gg
in their object class i.e. likely ggplot2
objects or objects that are associated with ggplot2
object types. It then writes each object individually to an externalised .Rdata
object. This allows you to read each object in individually and edit or add to it as discussed above. So rather than running many lines of code to reproduce the graphic, only to change one line, you can read in the object, alter or add to it, then render as .png
file.
plotObjects <- Filter( function(x) 'gg' %in% class( get(x) ), ls() )
## Save plot objects individually
sapply(plotObjects, function(x)saveRDS(object = get(x), file = paste0(x,".Rdata",sep="")))
## reload individually
# plot1 <- readRDS("plot1.Rdata")
# Save all plots as one object
# save(list = plotObjects, file = "plots.Rdata")
## test reloading
# remove(list = ls())
# load("plots.Rdata")
LS0tDQp0aXRsZTogImdncGxvdDIgVGlwcyBhbmQgVHJpY2tzIGFuZCBFeHRlbnNpb25zIg0KYXV0aG9yOiAiTWlrZSBLIFNtaXRoIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3Igc2V0dXB9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSkNCmBgYA0KDQojIyBBaW1zDQpPbmUgb2YgdGhlIGdyZWF0IHN0cmVuZ3RocyBvZiB0aGUgUiBkZXZlbG9wZXIgY29tbXVuaXR5IGlzIHRoYXQgZGV2ZWxvcGVycw0KY29udHJpYnV0ZSB0byBhbmQgZXh0ZW5kIGZ1bmN0aW9uYWxpdHkgb2YgZXhpc3RpbmcgcGFja2FnZXMuIFRoZXJlIGlzIGEgDQpncm93aW5nIG51bWJlciBvZiBleHRlbnNpb24gcGFja2FnZXMgdGhhdCB1c2VzIGBnZ3Bsb3QyYCBwcmluY2lwbGVzLCBvYmplY3RzIGFuZA0KYnVpbGRzIG9uIHRoaXMsIGV4dGVuZGluZyBhbmQgZW5oYW5jaW5nIHRoZSBmdW5jdGlvbmFsaXR5IG9mIHRoYXQgcGFja2FnZS4NCg0KVGhpcyBkb2N1bWVudCBoaWdobGlnaHRzIHNvbWUgdGhhdCBJIHBlcnNvbmFsbHkgZmluZCB1c2VmdWwuIEknbSBub3QgcHJlc2VudGluZw0KYW4gZXhoYXVzdGl2ZSBsaXN0LCBub3IgQUxMIG9mIHRoZSBmdW5jdGlvbmFsaXR5IHdpdGhpbiBlYWNoLCBidXQgcmF0aGVyIA0KaGlnaGxpZ2h0aW5nIHBhcnRpY3VsYXIgZnVuY3Rpb25zIG9yIGZ1bmN0aW9uYWxpdHkgdGhhdCBpbGx1c3RyYXRlcyB3aGF0IHRoZQ0KcGFja2FnZSBjYW4gZG8gZm9yIHlvdS4NCg0KIyMgUmVzb3VyY2VzICYgYmFja2dyb3VuZCByZWFkaW5nDQpTdGFydCB3aXRoICJSIGZvciBEYXRhIFNjaWVuY2UiIHNlY3Rpb24gb24gRGF0YSBWaXN1YWxpemF0aW9uIHVzaW5nIGBnZ3Bsb3QyYC4NCmh0dHBzOi8vcjRkcy5oYWQuY28ubnovZGF0YS12aXN1YWxpc2F0aW9uLmh0bWwgDQoNClRoZW4gSSBjYW4gaGVhcnRpbHkgcmVjb21tZW5kIENsYXVzIE8uIFdpbGtlJ3MgIkZ1bmRhbWVudGFscyBvZiBEYXRhIA0KVmlzdWFsaXphdGlvbiIuIGh0dHBzOi8vc2VyaWFsbWVudG9yLmNvbS9kYXRhdml6LyBUaGlzIGJvb2sgZ2l2ZXMgZ29vZCANCmFkdmljZSBvbiBob3cgeW91IHNob3VsZCByZXByZXNlbnQgZGlmZmVyZW50IGRhdGEgdHlwZXMsIGJ1dCBhbHNvIGdpdmVzIHNvbWUNCmdvb2QgcHJhY3RpY2Ugb24gbWFraW5nIHlvdXIgdmlzdWFsaXphdGlvbnMgZWFzaWVyIGFuZCBxdWlja2VyIGZvciBvdGhlcnMNCnRvIHVuZGVyc3RhbmQgYW5kIGRpZ2VzdCB0aGUgcmVsZXZhbnQgaW5mb3JtYXRpb24uDQogDQpGaW5hbGx5IGNoZWNrIG91dCBodHRwczovL3d3dy5nZ3Bsb3QyLWV4dHMub3JnLyBmb3IgYSBnb29kIHJldmlldyBvZiBnZ3Bsb3QyIA0KZXh0ZW5zaW9ucy4NCg0KIyBnZ3Bsb3QyIHRpcHMgYW5kIHRyaWNrcw0KDQojIyBDcmVhdGluZyBgZ2dwbG90MmAgcGxvdHMgZm9yIHVzZSBpbiBleGFtcGxlcw0KDQpgYGB7ciBjcmVhdGVfZ2dwbG90Ml9wbG90c30NCnBsb3QxIDwtIG1wZyAlPiUNCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGdlb21fc21vb3RoKCkNCg0KcGxvdDENCg0KcGxvdDMgPC0gZ2dwbG90KGRhdGE9bXBnLCBtYXBwaW5nPWFlcyh4ID0gY2xhc3MsIHkgPSBod3kpKSArDQogIGdlb21fYm94cGxvdCgpDQpwbG90Mw0KDQpwbG90NCA8LSBnZ3Bsb3QobXBnKSArIA0KICBnZW9tX2JhcihhZXMoY2xhc3MsZmlsbD1jbGFzcykpIA0KcGxvdDQNCmBgYA0KDQojIyMgZ2dwbG90MiAtIEFkZCB0aXRsZXMsIHN1YnRpdGxlcywgY2FwdGlvbnMsIGF4ZXMgbGFiZWxzDQpPbmUgb2YgdGhlIGdyZWF0IHRoaW5ncyBhYm91dCBgZ2dwbG90MmAgaXMgdGhlIHdheSB0aGF0IHlvdSBjYW4gY3JlYXRlIA0KZ3JhcGhzIHF1aWNrbHkgZm9yIHlvdXIgb3duIHVzZSAodG8gdmlzdWFsaXplIGRhdGEpLCBidXQgdGhlbiB5b3UgY2FuIHRha2UgDQp0aGF0IG9iamVjdCBhbmQgcmVmaW5lIGl0IHJlYWR5IGZvciBwcmVzZW50YXRpb24gb3IgcHVibGljYXRpb24uIEhlcmUgd2UgYWRkDQpsYWJlbHMgYW5kIHRpdGxlcyB0byBhbiBleGlzdGluZyBwbG90Lg0KDQpgYGB7ciBhbm5vdGF0ZV9nZ3Bsb3QyX3dpdGhfbGFiZWxzfQ0KcGxvdDEgKw0KICBsYWJzKHRpdGxlID0gIkhpZ2h3YXkgbWlsZWFnZSBpcyBpbnZlcnNlbHkgcmVsYXRlZCB0byBlbmdpbmUgc2l6ZSIsDQogICAgICAgc3VidGl0bGUgPSAiRGF0YTogbXBnIiwNCiAgICAgICB4ID0gIkVuZ2luZSBzaXplIChMKSIsDQogICAgICAgeSA9ICJIaWdod2F5IG1pbGVhZ2UgKG1wZykiLA0KICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vTWlrZUtTbWl0aC9tYXN0ZXJfdGhlX3RpZHl2ZXJzZV8yMDE5XzRRIikNCmBgYA0KDQoNCiMjIyBnZ3Bsb3QyIC0gVXNpbmcgYCUrJWAgdG8gY2hhbmdlIHRoZSBkYXRhc2V0DQpPbmUgb2Z0ZW4gb3Zlcmxvb2tlZCBmdW5jdGlvbmFsaXR5IGluIGBnZ3Bsb3QyYCBpcyB0aGUgYWJpbGl0eSB0byBjaGFuZ2UgdGhlIA0KZGF0YXNldCB3aXRoaW4gdGhlIGBnZ3Bsb3QyYCBvYmplY3QgYW5kIGVzc2VudGlhbGx5IHJlcGxheSB0aGUgc2FtZSBncmFwaCBmb3INCm5ldyBkYXRhLiBUaGlzIGZ1bmN0aW9uYWxpdHkgaXMgcmVhbGx5IHVzZWZ1bCBpZiB5b3UgaGF2ZSBkcmFmdCBkYXRhIHdoaWNoIHlvdQ0KY2FuIHVzZSB0byBjcmVhdGUgZWFybHkgdmlzdWFsaXphdGlvbnMgd2hpY2ggeW91IG1pZ2h0IHdhbnQgdG8gcmVmaW5lIGxhdGVyIHdpdGgNCnRoZSBmaW5hbCBkYXRhLiBUaGUgYXNzdW1wdGlvbiBoZXJlIGlzIHRoYXQgdmFyaWFibGUgbmFtZXMgd29uJ3QgY2hhbmdlLCBzaW5jZSANCnRoZSBgbWFwcGluZ2AgdG8gYGFlc2B0aGV0aWNzIGlzIGNhcnJpZWQgb3Zlci4gDQoNCkluIHRoZSBjYXNlIGJlbG93LCB3ZSBoYXZlIHRvIHNsaWdodGx5IGNoYW5nZSB2YXJpYWJsZSBuYW1lcyBpbiB0aGUgYG10Y2Fyc2AgDQpkYXRhc2V0IHRvIGdldCBpdCB0byBtYXRjaCB0aGUgYG1wZ2AgZGF0YS4gQnV0IG9uY2Ugd2UndmUgZG9uZSB0aGF0IHdlIGNhbg0Kc2ltcGx5IHVwZGF0ZSBgcGxvdDFgIHdpdGggdGhlIG5ldyBkYXRhLg0KDQpgYGB7ciBjaGFuZ2VfZ2dwbG90Ml9kYXRhfQ0KbXRjYXJzMiA8LSBtdGNhcnMgJT4lDQogIHJlbmFtZShod3kgPSBtcGcsDQogICAgICAgICBkaXNwbCA9IGRpc3ApDQoNCnBsb3QyIDwtIHBsb3QxICUrJQ0KICBtdGNhcnMyDQpwbG90Mg0KYGBgDQoNCiMjIyBVc2UgZm9yY2F0cyBsaWJyYXJ5IGZ1bmN0aW9ucyB0byBzb3J0IGZhY3RvcnMgYnkgc2l6ZSBvZiBzb21lIHZhcmlhYmxlDQpgYGB7ciBzb3J0X2NhdGVnb3JpZXNfdXNpbmdfZm9yY2F0c30NCnBsb3Q1IDwtIG1wZyAlPiUNCiAgZ3JvdXBfYnkoY2xhc3MpICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSkgJT4lDQogIG11dGF0ZShjbGFzcyA9IGZjdF9yZW9yZGVyKGNsYXNzLCBuKSkgJT4lDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBjbGFzcyxmaWxsPWNsYXNzKSkgKyANCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh5ID0gbikpICsNCiAgY29vcmRfZmxpcCgpDQpwbG90NQ0KYGBgDQoNCg0KIyMgcGF0Y2h3b3JrIC0gYXJyYW5nZSBtdWx0aXBsZSBwbG90cw0KVGhlIGBwYXRjaHdvcmtgIHBhY2thZ2UgKGJ5IFRob21hcyBMaW4gUGVkZXJzZW4gLSBodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKQ0KYWxsb3dzIHRoZSB1c2VyIHRvIGRlZmluZSBob3cgdG8gY29tYmluZSBncmFwaHMuIFRoZSBgK2Agb3BlcmF0b3IgaGVyZSBjb21iaW5lcw0KcGxvdHMgaW50byBhIHNpbmdsZSBwbG90IG9iamVjdC4gYHBsb3RfYW5ub3RhdGlvbmAgdGhlbiBhbGxvd3MgdXMgdG8gZGVmaW5lDQpvdmVyYXJjaGluZyB0aXRsZXMsIGxhYmVscywgYW5ub3RhdGlvbnMuIGB0YWdfbGV2ZWxzYCBkZWZpbmVzIGhvdyB5b3UgbWlnaHQgDQp3YW50IHRvIGlkZW50aWZ5IHRoZSBncmFwaHMgYW5kIGB0YWdfc3VmZml4YCBwYXN0ZXMgb250byB0aGUgZW5kIG9mIGB0YWdfbGV2ZWxgLg0KDQpgYGB7ciBwYXRjaHdvcmtfbXVsdGlwbGVfZ3JhcGhzMX0NCmxpYnJhcnkocGF0Y2h3b3JrKQ0KcGxvdDMgKyBwbG90NCArIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICJtcGcgZGF0YSBleHBsb3JhdGlvbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWdfbGV2ZWxzPSJhIiwgdGFnX3N1ZmZpeCA9ICIpIikNCmBgYA0KDQojIyBnZ2Vhc3kgLSBlYXN5IGFjY2VzcyB0byB0aGVtZSBhc3BlY3RzDQoNClRoZSB4LWF4aXMgYGNsYXNzYCB2YXJpYWJsZSBuYW1lcyBhYm92ZSBsb29rIEFXRlVMIHdoZW4gdGhlIHBsb3RzIGFyZSBwdWxsZWQNCmludG8gYHBhdGNod29ya2AuIEkga25vdyB0aGF0IGluIGBnZ3Bsb3QyYCBJIGNhbiByb3RhdGUgdGhlIGF4ZXMgbGFiZWxzLi4uIGJ1dA0KY2FuIEkgcmVjYWxsIHRoZSBzcGVjaWZpYyB0aGVtZSBzZXR0aW5ncz8NCg0KVGhlIGBnZ2Vhc3lgIHBhY2thZ2UgKGJ5IEpvbmF0aGFuIENhcnJvbGwgLSBodHRwczovL2pvbm9jYXJyb2xsLmdpdGh1Yi5pby9nZ2Vhc3kvKQ0KcHJvdmlkZXMgcXVpY2sgYWNjZXNzIHRvIHR3ZWFrIGNvbW1vbmx5IHVzZWQgYGdncGxvdDJgIHRoZW1lIHNldHRpbmdzLg0KDQpgYGB7ciBnZ2Vhc3lfcGVhc3l9DQpsaWJyYXJ5KGdnZWFzeSkNCnBsb3QzYiA8LSBwbG90MyArDQogIGVhc3lfcm90YXRlX3hfbGFiZWxzKHNpZGUgPSAicmlnaHQiKQ0KcGxvdDRiIDwtIHBsb3Q0ICsNCiAgZWFzeV9yb3RhdGVfeF9sYWJlbHMoc2lkZSA9ICJyaWdodCIpDQoNCnBsb3QzYiArIHBsb3Q0YiArIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICJtcGcgZGF0YSBleHBsb3JhdGlvbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWdfbGV2ZWxzPSJhIiwgdGFnX3N1ZmZpeCA9ICIpIikNCmBgYA0KDQoNCiMjIHBhdGNod29yayBjb250aW51ZWQgLSBDb21iaW5pbmcgbWFueSBwbG90cw0KSGVyZSB3ZSdyZSB1c2luZyBwYXRjaHdvcmsgdG8gcHJvZHVjZSBhIHNsaWdodGx5IG1vcmUgY29tcGxleCBsYXlvdXQsDQpjb21iaW5pbmcgYHBsb3QzYCBhbmQgYHBsb3Q0YCBpbiBvbmUgcm93LCB0aGVuIGBwbG90MWAgdW5kZXJuZWF0aC4gVGhlIGAtYA0Kc2lnbmlmaWVzIHRoYXQgdGhlIGNvbWJpbmVkIGBwbG90MyArIHBsb3Q0YCBhcmUgb24gdGhlIHNhbWUgbGV2ZWwgYXMgYHBsb3QxYCwgDQphbmQgYHBsb3RfbGF5b3V0YCBkZWZpbmVzIHRoYXQgdGhlcmUgaXMgb25lIGNvbHVtbi4gDQoNCmBgYHtyIHBhdGNod29ya19tdWx0aXBsZV9ncmFwaHMyfQ0KcGxvdDNiICsgcGxvdDRiIC0gcGxvdDEgKyBwbG90X2xheW91dChuY29sPTEpICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gIm1wZyBkYXRhIGV4cGxvcmF0aW9uIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhZ19sZXZlbHM9ImkiLCB0YWdfc3VmZml4ID0gIikiKQ0KYGBgDQoNCiMjIyBVc2UgRGF0YSBWaXN1YWxpemF0aW9uIGJlc3QgcHJhY3RpY2UNCkluIHRoZSBjb21iaW5lZCBncmFwaCBvZiBgcGxvdDMgKyBwbG90NGAgYWJvdmUsIHRoZXJlIGlzIGEgbG90IG9mIHJlcGVhdGVkIA0KaW5mb3JtYXRpb24gaW4gdGhlIGdyYXBoaWMgKHZlaGljbGUgY2xhc3MsIGxlZ2VuZHMgZXRjLikuIEJ5IGZsaXBwaW5nIHRoZSANCnBsb3QgdG8gYmUgaG9yaXpvbnRhbCwgd2UgY2FuIHNob3cgdGhlIGJveHBsb3Qgd2l0aCB0aGUgYmFyIGNoYXJ0IGFuZA0KZWxpbWluYXRlIG11Y2ggb2YgdGhlIHJlZHVuZGFudCBpbmZvcm1hdGlvbi4NCg0KSGVyZSB3ZSdyZSBhbHNvIHVzaW5nIHRoZSBgZ2dlYXN5YCBmdW5jdGlvbnMgYGVhc3lfcmVtb3ZlX2xlZ2VuZCgpYCBhbmQgDQpgZWFzeV9yZW1vdmVfeV9heGlzKClgIHRvIHJlbW92ZSB0aGUgZXh0cmFuZW91cyBpbmZvcm1hdGlvbiBmcm9tIGBwbG90NGNgLg0KDQpgYGB7ciBwYXRjaHdvcmtfbXVsdGlwbGVfZ3JhcGhzM30NCnBsb3QzYyA8LSBwbG90MyArIGNvb3JkX2ZsaXAoKQ0KcGxvdDRjIDwtIHBsb3Q0ICsgY29vcmRfZmxpcCgpICsgDQogIGVhc3lfcmVtb3ZlX2xlZ2VuZCgpICsgDQogIGVhc3lfcmVtb3ZlX3lfYXhpcygpDQoNCnBsb3QzYyArIHBsb3Q0Yw0KYGBgDQoNCg0KIyMgY293cGxvdCAtIHRlbXBsYXRlcyBmb3IgcHVibGljYXRpb24gcmVhZHkgcGxvdHMNClRoZSBgY293cGxvdGAgcGFja2FnZSAoYnkgQ2xhdXMgTy4gV2lsa2UgLSBodHRwczovL3dpbGtlbGFiLm9yZy9jb3dwbG90L2luZGV4Lmh0bWwpDQppbXBsZW1lbnRzIG1hbnkgb2YgdGhlIHN0YW5kYXJkcyBhbmQgcmVjb21tZW5kYXRpb25zIGZyb20gaGlzICJGdW5kYW1lbnRhbHMgb2YNCkRhdGEgVmlzdWFsaXphdGlvbiIgYm9vay4gSXQgZXh0ZW5kcyBgZ2dwbG90MmAgd2l0aCB0aGVtZXMgZm9yIHB1YmxpY2F0aW9uLA0KZnVuY3Rpb25zIGZvciBwbG90IGFsaWdubWVudCBhbmQgYWRkaXRpb25hbCBhbm5vdGF0aW9ucy4NCg0KYGBge3IgY293cGxvdF90aGVtZXN9DQpsaWJyYXJ5KGNvd3Bsb3QpDQpwbG90MSArIA0KICBsYWJzKHRpdGxlPSJIaWdod2F5IG1pbGVhZ2UgaXMgaW52ZXJzZWx5IHJlbGF0ZWQgdG8gZW5naW5lIHNpemUiLCANCiAgICAgICB4ID0gIkVuZ2luZSBkaXNwbGFjZW1lbnQgKExpdHJlcykiLA0KICAgICAgIHkgPSAiSGlnaHdheSBtaWxlYWdlIChtcGcpIikgKw0KICAgdGhlbWVfY293cGxvdChmb250X3NpemUgPSAxNCkNCg0KcGxvdDEgKyANCiAgIHRoZW1lX21pbmltYWxfZ3JpZCgpDQoNCnBsb3QxICsgDQogICB0aGVtZV9taW5pbWFsX2hncmlkKCkNCmBgYA0KDQojIyMgQWRkaW5nIGFubm90YXRpb25zIGFuZCB0ZXh0IHRvIHBsb3RzDQpUaGUgYGdnZHJhd2AgZnVuY3Rpb24gd2l0aCB0aGUgYGNvd3Bsb3RgIHBhY2thZ2UgdGFrZXMgYW4gZXhpc3RpbmcgYGdncGxvdDJgIA0Kb2JqZWN0IGFuZCBzZXRzIHVwIGEgZHJhd2luZyBsYXllciBmb3IgYWRkaXRpb25hbCBhbm5vdGF0aW9uIG9uIHRvcC4gSGVyZSB3ZSdyZSANCmFkZGluZyBhICJEUkFGVCIgd2F0ZXJtYXJrLg0KDQpgYGB7ciBjb3dwbG90X2xhYmVsfQ0KZ2dkcmF3KHBsb3QxKSArIA0KICBkcmF3X2xhYmVsKCJEcmFmdCIsIGNvbG9yID0gImxpZ2h0IGdyZXkiLCBhbHBoYT0wLjMsIHNpemUgPSAxMDAsIGFuZ2xlID0gNDUpIA0KYGBgDQoNCiMjIyBVc2luZyBgc3RhbXBgIHRvIGxhYmVsIHBsb3RzDQpUaGUgYHN0YW1wYCBmdW5jdGlvbmFsaXR5IGFsbG93cyB5b3UgdG8gbGFiZWwgYW4gYXR0cmlidXRlIG9mIGEgd2hvbGUgZ3JhcGhpYy4NCkluIHRoZSBib29rLCBXaWxrZSBsYWJlbHMgcGxvdHMgYXMgInVnbHkiIG9yICJiYWQiLCBidXQgaGVyZSB3ZSdyZSBtYXJraW5nDQp0aGUgd2hvbGUgZ3JhcGggYXMgIkRSQUZUIi4gVGhpcyBpcyBwZXJoYXBzIGxlc3Mgb2J0cnVzaXZlIHRoYW4gdGhlIHdhdGVybWFyaw0KYWJvdmUuDQoNCmBgYHtyIGNvd3Bsb3Rfc3RhbXB9DQpzdGFtcChwbG90MSwgbGFiZWwgPSAiRFJBRlQiLCBjb2xvciA9ICJyZWQiKQ0KYGBgDQoNCiMjIGdncmVwZWwgdG8gbGFiZWwgcG9pbnRzIHdpdGhvdXQgb3ZlcmxhcA0KVGhlIGBnZ3JlcGVsYCBwYWNrYWdlIChieSBLYW1pbCBTbG93aWtvd3NpIC0gaHR0cHM6Ly9zbG93a293LmNvbS9nZ3JlcGVsKSBoYXMNCmJlZW4gZGV2ZWxvcGVkIHRvIGVuc3VyZSB0aGF0IHRleHQgbGFiZWxzIGluIHBsb3RzIGRvIG5vdCBvdmVybGFwIGVhY2ggb3RoZXIsDQpkbyBub3QgY292ZXIgZGF0YSBwb2ludHMsIG5vciBmYWxsIG91dHNpZGUgb2YgdGhlIHBsb3R0aW5nIGFyZWEuIFRoZSBtZXRob2Qgb2YgDQpkb2luZyBzbyBpcyBpbmRpc3Rpbmd1aXNoYWJsZSBmcm9tIG1hZ2ljIChvciBzbyBpdCBzZWVtcyB0byBtZSkuDQoNCmBgYHtyIGdncmVwZWx9DQpsaWJyYXJ5KGdncmVwZWwpDQoNCnBsb3RfdWdseSA8LSBnZ3Bsb3QobXRjYXJzKSArIA0KICBhZXMoDQogICAgeCA9IHd0LCB5ID0gbXBnLA0KICAgIGxhYmVsID0gcm93bmFtZXMobXRjYXJzKQ0KICApICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAicmVkIikgKw0KICBnZW9tX3RleHQoKQ0KDQpzdGFtcF91Z2x5KHBsb3RfdWdseSkNCg0KZ2dwbG90KG10Y2FycykgKyANCiAgYWVzKA0KICAgIHggPSB3dCwgeSA9IG1wZywNCiAgICBsYWJlbCA9IHJvd25hbWVzKG10Y2FycykNCiAgKSArIA0KICBnZW9tX3BvaW50KGNvbG9yID0gInJlZCIpICsNCiAgZ2VvbV90ZXh0X3JlcGVsKCkNCmBgYA0KDQoNCiMjIGdnaGlnaGxpZ2h0IC0gaGlnaGxpZ2h0aW5nIHZpc3VhbGl6YXRpb24gaXRlbXMgYmFzZWQgb24gcHJlZGljYXRlcw0KYGdnaGlnaGxpZ2h0YCAoYnkgSGlyb2FraSBZdXRhbmkgLSBodHRwczovL3l1dGFubmloaWxhdGlvbi5naXRodWIuaW8vZ2doaWdobGlnaHQvaW5kZXguaHRtbCkNCmlzIGEgYGdncGxvdDJgIGV4dGVuc2lvbiB3aGljaCBoaWdobGlnaHRzIGBnZ3Bsb3QyYCBgZ2VvbXNgIHRoYXQgbWF0Y2ggYSBnaXZlbiANCnByZWRpY2F0ZS4NCg0KSW4gbWFueSBjYXNlcyB0aGlzIGxlYWRzIHRvIHRoZSByZWNvbW1lbmRhdGlvbnMgaW4gIkZ1bmRhbWVudGFscyBvZiBEYXRhDQpWaXN1YWxpemF0aW9uIiBiZWNhdXNlIGlzIGFsbG93cyB0aGUgcmVhZGVyIHRvIGZvY3VzIHN0cmFpZ2h0IGluIG9uIHRoZSANCnBhcnRpY3VsYXIgZWxlbWVudCBvZiB0aGUgcGxvdCB0aGF0IGlzIGtleSB0byB0aGUgY2xhaW1zIGJlaW5nIG1hZGUuIEl0DQpoZWxwcyB0aGUgInN0b3J5IiBvZiBvdXIgZGF0YSB2aXN1YWxpemF0aW9uLi4uDQoNCmBgYHtyIGdnaGlnaGxpZ2h0XzF9DQpsaWJyYXJ5KGdnaGlnaGxpZ2h0KQ0KDQpwbG90NCArIA0KICBnZ2hpZ2hsaWdodChjbGFzcyA9PSAic3V2IikNCg0KcGxvdDEgKyANCiAgZ2doaWdobGlnaHQoY2xhc3MgPT0gInN1diIpIA0KDQpwbG90NSArIA0KICBnZ2hpZ2hsaWdodChjbGFzcyA9PSAic3V2IikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCiMjIGdncmlkZ2VzIHRvIGNvbXBhcmUgZGVuc2l0aWVzIG5lYXRseQ0KYGdncmlkZ2VzYCAoYnkgQ2xhdXMgTyBXaWxrZSAtIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3JpZGdlcy92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpIA0KaXMgYSB1c2VmdWwgZXh0ZW5zaW9uIGZvciBwbG90dGluZyBtYW55IGRlbnNpdGllcywgb3Igb3RoZXIgDQpyaWRnZWxpbmUgdmlzdWFsaXphdGlvbnMgd2hlcmUgeW91IG1heSB3aXNoIHRvIHBsb3QgdGhlIHJpZGdlcyBieSBhIGNhdGVnb3JpY2FsIA0KdmFyaWFibGUuIGBnZ3JpZGdlc2Agc3RhY2tzIHRoZXNlIHBsb3RzIHNvIHRoYXQgdGhlIGluZGl2aWR1YWwgcmlkZ2VzIG9yIGRlbnNpdGllcw0KYXJlIGVhc2lseSB2aXNpYmxlLCBidXQgY29tcGFjdGx5Lg0KDQpPcmRlcmluZyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgb3IgZmFjdG9yIGJ5IG1hZ25pdHVkZSBvZiB0aGUgeC1heGlzIHZhcmlhYmxlDQp1c2luZyB0aGUgYGZjdF9yZW9yZGVyYCBmdW5jdGlvbiBmcm9tIHRoZSBgZm9yY2F0c2AgcGFja2FnZSwgbWFrZXMgaXQgZWFzaWVyDQpmb3IgdGhlIHZpZXdlciB0byBzZWUgd2hpY2ggY2F0ZWdvcnkgaGFzIHRoZSBsYXJnZXN0IG1lYW4sIGFzIHdlbGwgYXMgdGhlIA0Kc3ByZWFkIHdpdGhpbiB0aGUgZGVuc2l0eSBvciByaWRnZWxpbmUgcGxvdC4NCg0KYGBge3IgZ2dyaWRnZXN9DQpsaWJyYXJ5KGdncmlkZ2VzKQ0KDQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gaHd5LCB5ID0gY2xhc3MpKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKQ0KDQojIyBVc2luZyBmY3RfcmVvcmRlciBmcm9tIHRoZSBgZm9yY2F0c2AgcGFja2FnZSB0byBzb3J0IHRoZSBmYWN0b3INCiMjIGJ5IHRoZSBtZWRpYW4gb2YgYGh3eWAgd2l0aGluIGVhY2ggY2xhc3MuDQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gaHd5LCB5ID0gZmN0X3Jlb3JkZXIoY2xhc3MsIGh3eSkpKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKQ0KYGBgDQoNCiMjIEdHYWxseSAtIFF1aWNrIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMNCmBnZ2FsbHlgIChieSB0aGUgR0dvYmkgRm91bmRhdGlvbiAtIGh0dHBzOi8vZ2dvYmkuZ2l0aHViLmlvL2dnYWxseS8pIHByb3ZpZGVzDQp1c2VmdWwgbWF0cml4IHBsb3RzIG9mIGRhdGEgYW5kIGlzIHJlYWxseSB3ZWxsIHN1aXRlZCB0byBxdWljayBkYXRhIHZpc3VhbGl6YXRpb24NCmZvciBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzLiANCg0KYGdnc2NhdG1hdGAgcXVpY2tseSBwcm9kdWNlcyBhIHNjYXR0ZXJwbG90IG1hdHJpeCwgZGVuc2l0eSBhbmQgY29ycmVsYXRpb24gcmVhZG91dCBmb3IgDQpjb250aW51b3VzIHZhcmlhYmxlcy4NCg0KYGdncGFpcnNgIGlzIGEgbW9yZSBjb21wbGV0ZSB2aXN1YWxpemF0aW9uIG9mIGJvdGggY29udGludW91cyBhbmQgY2F0ZWdvcmljYWwgDQp2YXJpYWJsZXMsIGJ1dCBpcyBzb21ld2hhdCBzbG93ZXIgdGhhbiBgZ2dzY2F0bWF0YC4NCg0KYGBge3IgR0dhbGx5fQ0KbGlicmFyeShHR2FsbHkpDQoNCiMjIENvbnRpbnVvdXMgb3V0Y29tZXMgb25seSBjYW4gdXNlIHRoZSBxdWlja2VyIGBnZ3NjYXRtYXRgDQptcGcgJT4lDQogIHNlbGVjdChjdHksIGh3eSwgZGlzcGwpICU+JQ0KICBnZ3NjYXRtYXQoKQ0KDQojIyBGb3IgbW9yZSBnZW5lcmFsIHVzZSB3aXRoIGJvdGggY2F0ZWdvcmljYWwgYW5kIGNvbnRpbnVvdXMgdmFyaWFibGVzDQojIyB1c2UgZ2dwYWlycy4gQWx0aG91Z2ggZnVuY3Rpb24gdGhpcyBpcyBzbG93ZXIuDQptcGcgJT4lDQogIHNlbGVjdChjbGFzcywgY3R5LCBod3ksIGRpc3BsKSAlPiUNCiAgZ2dwYWlycygpDQpgYGANCg0KIyMgZ2dmb3JjZSAtIGFkZGl0aW9uYWwgZ2VvbXMgYW5kIGV4dGVuc2lvbnMgdG8gZ2dwbG90Mg0KYGdnZm9yY2VgIChieSBUaG9tYXMgTGluIFBlZGVyc2VuIC0gaHR0cHM6Ly9nZ2ZvcmNlLmRhdGEtaW1hZ2luaXN0LmNvbS8pDQpwcm92aWRlcyBhZGRpdGlvbmFsIGBnZW9tc2AgYW5kIGV4dGVuc2lvbnMgdG8gYGdncGxvdDJgLg0KDQpgYGB7ciBnZ2ZvcmNlX2dlb21zfQ0KbGlicmFyeShnZ2ZvcmNlKQ0KDQpwbG90NiA8LSBtcGcgJT4lDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gY2xhc3MpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgZ2VvbV9tYXJrX2VsbGlwc2UoYWVzKGZpbGw9Y2xhc3MpKQ0KcGxvdDYNCg0KIyMgQWx0ZXJuYXRpdmVseSBsYWJlbCB0aGUgb3ZlcmxhcHBpbmcgZWxsaXBzZXMNCnBsb3Q2ICsgDQogIGdlb21fbWFya19lbGxpcHNlKGFlcyhsYWJlbD1jbGFzcykpDQoNCiMjIFBlcmhhcHMgYmV0dGVyIHVzaW5nIGdnaGlnaGxpZ2h0DQpwbG90NiArDQogIGdnaGlnaGxpZ2h0KGNsYXNzID09ICIyc2VhdGVyIiwgdXNlX2RpcmVjdF9sYWJlbCA9IEZBTFNFKQ0KYGBgDQoNCkluIGFkZGl0aW9uIGBnZ2ZvcmNlYCBwcm92aWRlcyB0aGUgYGZhY2V0X3pvb21gIGZ1bmN0aW9uYWxpdHkgdG8gcHJvdmlkZSANCm9uZSB2aXN1YWxpemF0aW9uIHdoaWNoIHNob3dzIHRoZSBvdmVydmlldyBvZiB0aGUgZGF0YSwgYnV0IHpvb21zIGluIHRvIGEgDQpwcmVkaWNhdGUgcmVnaW9uLCBvciBhbiB4LSBvciB5LWF4aXMgcmVnaW9uLg0KDQpgYGB7ciBnZ2ZvcmNlX2ZhY2V0X3pvb219DQojIyBab29tIGluIG9uIHRoZSAyc2VhdGVyIGNsYXNzDQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG91ciA9IGNsYXNzKSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGZhY2V0X3pvb20oY2xhc3M9PSIyc2VhdGVyIikNCg0KIyMgQWx0ZXJuYXRpdmVseSB6b29tIGJ5IHgtYXhpcyBsaW1pdHMNCmdncGxvdChkYXRhID0gbXBnLCBtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gY2xhc3MpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgZmFjZXRfem9vbSh4bGltPWMoMS41LCAyLjUpKSANCmBgYA0KDQojIyBnZ2VkaXQgLSBtYW5pcHVsYXRlIHBsb3Qgb2JqZWN0cw0KYGdnZWRpdGAgKGZyb20gTWV0cnVtIFJlc2VhcmNoIEdyb3VwIC0gaHR0cHM6Ly9tZXRydW1yZXNlYXJjaGdyb3VwLmdpdGh1Yi5pby9nZ2VkaXQvKQ0KcHJvdmlkZXMgdmVyeSB1c2VmdWwgZnVuY3Rpb25zIGZvciByZW1vdmluZyBsYXllcnMgb2YgYSBgZ2dwbG90MmAgdmlzdWFsaXphdGlvbg0Kb2JqZWN0LiBUaGlzIGNhbiBiZSB1c2VmdWwgd2hlbiB3ZSB3YW50IHRvIHRyYWNrIGhvdyBwbG90cyB3ZXJlIG1hbmlwdWxhdGVkIA0KdGhyb3VnaCBzY3JpcHRzLiBGb3IgZXhhbXBsZSBpZiBhIHBsb3QgaGFzIGJlZW4gUUNlZCBhbmQgc2F2ZWQgYXMgYW4gYC5SZGF0YWANCm9iamVjdCB3ZSBjYW4gc3Vic2VxdWVudGx5IGxvYWQgdGhhdCBvYmplY3QsIHVzZSBgZ2dlZGl0YCB0byBhbHRlciBpdCBhbmQgDQpzYXZlIGJhY2sgdG8gYSBuZXcgb3V0cHV0IG9iamVjdCwgb3IgdmVyc2lvbi4gVGhlIGNoYW5nZXMgZnJvbSBvbmUgc3RhdGUgdG8NCmFub3RoZXIgY2FuIGVhc2lseSBiZSB0cmFja2VkIGFuZCB0cmFjZWQgdGhyb3VnaCB0aGUgc2NyaXB0Lg0KDQojIyMgUmVtb3ZlIHBsb3QgbGF5ZXJzDQpgYGB7ciBnZ2VkaXRfcmVtb3ZlX2dlb21zfQ0KbGlicmFyeShnZ2VkaXQpDQoNCiMjIHJlbW92ZSBsb2VzcyBzbW9vdGggYW5kIHJlcGxhY2Ugd2l0aCBhIGxpbmVhciByZWdyZXNzaW9uIGxpbmUNCiMjIE5COiBVc2VzICU+JSB0byBwYXNzIGluIHRoZSBwbG90IG9iamVjdA0KIyMgQmVjYXVzZSByZXN1bHRpbmcgb3V0cHV0IGlzIGEgZ2dwbG90MiBvYmplY3Qgd2UgY2FuIHJldmVydCB0byAiKyIuDQpwbG90MSAlPiUNCiAgZ2dlZGl0OjpyZW1vdmVfZ2VvbShnZW9tID0gInNtb290aCIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCg0KIyMgVVBEQVRFOiBDYW4gdXNlIGAtYCB0byByZW1vdmUgZ2VvbXMuIA0KcGxvdDEgLSANCiAgZ2VvbV9zbW9vdGgoKQ0KYGBgDQoNClRoZSBgLWAgKG1pbnVzKSBmdW5jdGlvbmFsaXR5IHRvIHJlbW92ZSBgZ2VvbXNgIGZyb20gYGdncGxvdDJgIG9iamVjdHMgd29ya3MNCm9uIGEgImxhc3QgaW4sIGZpcnN0IG91dCIgbW9kZWwgaWYgdGhlcmUgaXMgbW9yZSB0aGFuIG9uZSBgZ2VvbWAgd2l0aCB0aGUgc2FtZQ0KdHlwZS4NCg0KYGBge3IgZ2dlZGl0X21pbnVzX21pbnVzfQ0KcGxvdDFjIDwtIGdncGxvdChtcGcsIGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKSArIGdlb21fcG9pbnQoKQ0KcGxvdDFkIDwtIHBsb3QxYyArIGdlb21fcG9pbnQoc2hhcGU9OCwgY29sb3VyID0gInJlZCIsYWxwaGE9MSxzdHJva2U9NCkNCnBsb3QxZCANCg0KcGxvdDFkIC0gZ2VvbV9wb2ludCgpICNMSUZPDQpwbG90MWQgLSBnZW9tX3BvaW50KCkgLSBnZW9tX3BvaW50KCkNCmBgYA0KDQojIyMgZ2dlZGl0IC0gaW50ZXJhY3RpdmUgZWRpdGluZyBvZiBwbG90IGF0dHJpYnV0ZXMNCkFub3RoZXIgdXNlZnVsIHBhcnQgb2YgYGdnZWRpdGAgaXMgdGhlIFNoaW55IGFwcGxpY2F0aW9uIHdoaWNoIGNhbiBiZSB1c2VkDQp0byBpbnRlcmFjdGl2ZWx5IGNoYW5nZSBgZ2dwbG90MmAgb2JqZWN0IGxheWVycyBhbmQgdGhlbWVzLCB0aGVuIHNhdmluZyB0aGUgDQpyZXN1bHRpbmcgb2JqZWN0IGJhY2sgdG8gdGhlIGVudmlyb25tZW50Lg0KDQpgYGB7ciBnZ2VkaXRfU2hpbnksIGV2YWwgPSBGQUxTRX0NCmdnZWRpdChwbG90MSkNCmBgYA0KDQojIyB4Z3hyIC0gSGVscGVyIGZ1bmN0aW9ucyBmb3IgcGxvdHRpbmcgY29uY2VudHJhdGlvbiBkYXRhDQpgeGd4cmAgaXMgYSBwYWNrYWdlIGRldmVsb3BlZCBieSBOb3ZhcnRpcyAoaHR0cDovL29wZW5zb3VyY2Uubmlici5jb20veGd4LykgZm9yDQpleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzLCBwYXJ0aWN1bGFybHkgaW4gdGhlIGZpZWxkIG9mIHBoYXJtYWNva2luZXRpYyBhbmQNCnBoYXJtYWNvZHluYW1pYyBkYXRhLiBIZXJlIHdlIGFyZSBvbmx5IHNjcmF0Y2hpbmcgdGhlIHN1cmZhY2Ugb2YgdGhpcyBwYWNrYWdlDQpidXQgaW4gcGFydGljdWxhciBJIGxpa2VkIHRoZSBhYmlsaXR5IHRvIHNob3cgbWlub3IgZ3JpZCBsaW5lcyBvbiB0aGUgbG9nIHNjYWxlLg0KDQpgYGB7ciB4Z3hyX2xvZ19zY2FsZX0NCmxpYnJhcnkoeGd4cikNCg0KZ2dwbG90KGRhdGEgPSBUaGVvcGgsIG1hcHBpbmcgPSBhZXMoeCA9IFRpbWUsIHkgPSBjb25jLCBncm91cCA9IFN1YmplY3QpKSArDQogIGdlb21fbGluZSgpICsgDQogIHhneF9zY2FsZV95X2xvZzEwKCkNCmBgYA0KDQojIyMgeGd4X3N0YXRfY2kgLSBzdGF0X3N1bW1hcnkgd2l0aCBhZGRpdGlvbmFsIG9wdGlvbnMgZm9yIENJIHR5cGUNClRoZSBgeGd4X3N0YXRfY2lgIGlzIHZlcnkgc2ltaWxhciB0byBmdW5jdGlvbmFsaXR5IGluIHRoZSBgc3RhdF9zdW1tYXJ5YCBnZW9tDQooaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N0YXRfc3VtbWFyeS5odG1sKSBidXQgaXQgZXh0ZW5kcyANCnRoaXMgdG8gYWxsb3cgdGhlIHVzZXIgdG8gc3BlY2lmeSB0aGUgY29uZmlkZW5jZSByZWdpb24gd2lkdGggYW5kIHRoZSBkaXN0cmlidXRpb24NCm9mIHRoZSBvdXRjb21lIGUuZy4gYG5vcm1hbGAsIGBsb2dub3JtYWxgLCBgYmlub21pYWxgLg0KDQpJbiB0aGUgY29kZSBiZWxvdyB3ZSdyZSBhbHNvIHVzaW5nIHRoZSBgc25hcGAgZnVuY3Rpb24gZnJvbSB0aGUgYG1ldHJ1bXJnYA0KcGFja2FnZSB3aGljaCBzbmFwcyBvYnNlcnZlZCBzYW1wbGUgdGltZXMgdG8gYSBub21pbmFsIGdyaWQuIFRoaXMgYWxsb3dzIHVzIA0KdG8gc3VtbWFyaXNlIG9ic2VydmVkIHZhbHVlcyBhdCB0aGVpciBub21pbmFsIHRpbWVzLg0KDQpgYGB7ciB4Z3hyX3N0YXRfY2l9DQp0aW1lcyA8LSBjKDAsIDAuMjUsIDAuNSwgMSwgMS41LCAyLCAzLjUsIDUsIDcsIDksIDEyLCAyNCkNCm15VGhlb3BoIDwtIFRoZW9waCAlPiUNCiAgbXV0YXRlKE5UUEQgPSBtZXRydW1yZzo6c25hcChUaW1lLCB0aW1lcykpDQp0YWJsZShteVRoZW9waCROVFBEKQ0KDQp0aGVvUGxvdDEgPC0gZ2dwbG90KGRhdGEgPSBteVRoZW9waCwgYWVzKHggPSBOVFBELHkgPSBjb25jKSkgKw0KICBnZW9tX2xpbmUobWFwcGluZyA9IGFlcyhncm91cCA9IFN1YmplY3QpLCBhbHBoYT0wLjIpICsgDQogIHhneF9zdGF0X2NpKGNvbG9yID0gInJlZCIsIGNvbmZfbGV2ZWwgPSAwLjksIGRpc3RyaWJ1dGlvbiA9ICJsb2dub3JtYWwiKSArIA0KICBsYWJzKHkgPSAiQ29uY2VudHJhdGlvbiAobmcvbWwpIikgDQp0aGVvUGxvdDENCg0KdGhlb1Bsb3QxICsNCiAgeGd4X3NjYWxlX3lfbG9nMTAoKQ0KYGBgDQoNCg0KIyMgc3Vydm1pbmVyIC0gUGxvdHRpbmcgc3Vydml2YWwgY3VydmVzDQpXZSdyZSBub3cgZ29pbmcgdG8gbG9vayBhdCBwYXJ0aWN1bGFyIGNhc2VzIHdoZXJlIHRoZSB2aXN1YWxpemF0aW9uIG9mIA0Kc3BlY2lhbGlzdCBkYXRhIHR5cGVzIHByb2R1Y2UgYGdncGxvdDJgIG9iamVjdHMuIFRoZSBmaXJzdCBpcyBmb3Igc3Vydml2YWwgZGF0YS4NCg0KVGhlIGBzdXJ2bWluZXJgIHBhY2thZ2UgKGJ5IEFsYm91a2FkZWwgS2Fzc2FtYmFyYSAtIGh0dHBzOi8vcnBrZ3MuZGF0YW5vdmlhLmNvbS9zdXJ2bWluZXIvaW5kZXguaHRtbCkNCnByb2R1Y2VzIG5pY2UgYGdncGxvdDJgIHZpc3VhbGl6YXRpb25zIG9mIHN1cnZpdmFsIGRhdGEgaW5jbHVkaW5nIHRoZSBudW1iZXIgb2YgDQpzdWJqZWN0cyBhdCByaXNrIHdpdGhpbiBzdHJhdGEuDQoNCmBgYHtyIHN1cnZpdmFsfQ0KbGlicmFyeShzdXJ2aXZhbCkNCmxpYnJhcnkoc3Vydm1pbmVyKQ0KZml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBzdGF0dXMpIH4gc2V4LCBkYXRhID0gbHVuZykNCmdnc3VydnBsb3QoZml0LCBkYXRhID0gbHVuZykNCg0KZ2dzdXJ2cGxvdCgNCiAgZml0LCANCiAgZGF0YSA9IGx1bmcsIA0KICBzaXplID0gMSwgICAgICAgICAgICAgICAgICMgY2hhbmdlIGxpbmUgc2l6ZQ0KICBwYWxldHRlID0gDQogICAgYygiI0U3QjgwMCIsICIjMkU5RkRGIiksIyBjdXN0b20gY29sb3IgcGFsZXR0ZXMNCiAgY29uZi5pbnQgPSBUUlVFLCAgICAgICAgICAjIEFkZCBjb25maWRlbmNlIGludGVydmFsDQogIHB2YWwgPSBUUlVFLCAgICAgICAgICAgICAgIyBBZGQgcC12YWx1ZQ0KICByaXNrLnRhYmxlID0gVFJVRSwgICAgICAgICMgQWRkIHJpc2sgdGFibGUNCiAgcmlzay50YWJsZS5jb2wgPSAic3RyYXRhIiwjIFJpc2sgdGFibGUgY29sb3IgYnkgZ3JvdXBzDQogIGxlZ2VuZC5sYWJzID0gDQogICAgYygiTWFsZSIsICJGZW1hbGUiKSwgICAgIyBDaGFuZ2UgbGVnZW5kIGxhYmVscw0KICByaXNrLnRhYmxlLmhlaWdodCA9IDAuMjUgIyBVc2VmdWwgdG8gY2hhbmdlIHdoZW4geW91IGhhdmUgbXVsdGlwbGUgZ3JvdXBzDQopDQpgYGANCg0KIyMgZ2dyYXBoIC0gVmlzdWFsaXppbmcgbmV0d29ya3MNCmBnZ3JhcGhgIChieSBUaG9tYXMgTGluIFBlZGVyc2VuIC0gaHR0cHM6Ly9nZ3JhcGguZGF0YS1pbWFnaW5pc3QuY29tLykgDQpwcm9kdWNlcyB2aXN1YWxpemF0aW9ucyBvZiBuZXR3b3Jrcy4gSGVyZSB3ZSB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcHMgYW5kIA0KZGVwZW5kZW5jaWVzIGFzc29jaWF0ZWQgd2l0aCB0aGUgYHRpZHl2ZXJzZWAgcGFja2FnZSwgYmFzZWQgb24gdGhlIGBtaW5pQ1JBTmANCnBhY2thZ2UgZnVuY3Rpb24gYG1ha2VEZXBHcmFwaGAgYW5kIHRoZSBgdGlkeWdyYXBoYCBhbmQgYGdncmFwaGAgcGFja2FnZXMuIE5vdGUNCnRoYXQgYGdncmFwaGAgdXNlcyBgcmVwZWxgIGZ1bmN0aW9uYWxpdHkgaW4gYGdlb21fbm9kZV90ZXh0YCB0byBwcmVzZW50DQp0aWR5IGxhYmVscyBpbiB0aGUgdmlzdWFsaXphdGlvbi4NCg0KYGBge3IgZ2dyYXBofQ0KbGlicmFyeShtaW5pQ1JBTikNCmxpYnJhcnkoZ2dyYXBoKQ0KbGlicmFyeSh0aWR5Z3JhcGgpDQoNCnRpZHl2ZXJzZURlcGVuZGVuY2llcyA8LSBtYWtlRGVwR3JhcGgocGtnID0gInRpZHl2ZXJzZSIpICU+JQ0KICBhc190YmxfZ3JhcGgoKQ0KDQpnZ3JhcGgodGlkeXZlcnNlRGVwZW5kZW5jaWVzLCBsYXlvdXQgPSAiZ3JhcGhvcHQiKSArDQogIGdlb21fbm9kZV9wb2ludCgpICsNCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGNvbG91ciA9IHR5cGUpKSArDQogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLA0KICAgICAgICAgICAgICAgICByZXBlbCA9IFRSVUUpDQpgYGANCg0KDQojIyBTYXZlIGFsbCBnZ3Bsb3QyIG9iamVjdHMgdG8gd29ya2luZyBkaXJlY3RvcnkNCkZpbmFsbHksIGhlcmUncyBvbmUgSSBwcm9kdWNlZCAoYnkgTWlrZSBLIFNtaXRoISkuIFRoaXMgc25pcHBldCBvZiBjb2RlDQpwaWNrcyBvdXQgYWxsIG9iamVjdHMgaW4gdGhlIGVudmlyb25tZW50IHRoYXQgaGF2ZSB0eXBlIGBnZ2AgaW4gdGhlaXIgb2JqZWN0IA0KY2xhc3MgaS5lLiBsaWtlbHkgYGdncGxvdDJgIG9iamVjdHMgb3Igb2JqZWN0cyB0aGF0IGFyZSBhc3NvY2lhdGVkIHdpdGggYGdncGxvdDJgDQpvYmplY3QgdHlwZXMuIEl0IHRoZW4gd3JpdGVzIGVhY2ggb2JqZWN0IGluZGl2aWR1YWxseSB0byBhbiBleHRlcm5hbGlzZWQgYC5SZGF0YWANCm9iamVjdC4gVGhpcyBhbGxvd3MgeW91IHRvIHJlYWQgZWFjaCBvYmplY3QgaW4gaW5kaXZpZHVhbGx5IGFuZCBlZGl0IG9yIGFkZCB0byANCml0IGFzIGRpc2N1c3NlZCBhYm92ZS4gU28gcmF0aGVyIHRoYW4gcnVubmluZyBtYW55IGxpbmVzIG9mIGNvZGUgdG8gcmVwcm9kdWNlIHRoZQ0KZ3JhcGhpYywgb25seSB0byBjaGFuZ2Ugb25lIGxpbmUsIHlvdSBjYW4gcmVhZCBpbiB0aGUgb2JqZWN0LCBhbHRlciBvciBhZGQgdG8gaXQsDQp0aGVuIHJlbmRlciBhcyBgLnBuZ2AgZmlsZS4NCg0KYGBge3Igc2F2ZV9nZ3Bsb3QyX29iamVjdHMsIGV2YWw9RkFMU0V9DQpwbG90T2JqZWN0cyA8LSBGaWx0ZXIoIGZ1bmN0aW9uKHgpICdnZycgJWluJSBjbGFzcyggZ2V0KHgpICksIGxzKCkgKQ0KDQojIyBTYXZlIHBsb3Qgb2JqZWN0cyBpbmRpdmlkdWFsbHkNCnNhcHBseShwbG90T2JqZWN0cywgZnVuY3Rpb24oeClzYXZlUkRTKG9iamVjdCA9IGdldCh4KSwgZmlsZSA9IHBhc3RlMCh4LCIuUmRhdGEiLHNlcD0iIikpKQ0KDQojIyByZWxvYWQgaW5kaXZpZHVhbGx5DQojIHBsb3QxIDwtIHJlYWRSRFMoInBsb3QxLlJkYXRhIikNCg0KIyBTYXZlIGFsbCBwbG90cyBhcyBvbmUgb2JqZWN0DQojIHNhdmUobGlzdCA9IHBsb3RPYmplY3RzLCBmaWxlID0gInBsb3RzLlJkYXRhIikNCg0KIyMgdGVzdCByZWxvYWRpbmcNCiMgcmVtb3ZlKGxpc3QgPSBscygpKQ0KIyBsb2FkKCJwbG90cy5SZGF0YSIpDQpgYGANCg0KIyBTZXNzaW9uIEluZm8NCmBgYHtyIHNlc3Npb25JbmZvfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCg==