library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
-- Attaching packages --------------------------------------- tidyverse 1.2.1 --
v ggplot2 3.2.1     v purrr   0.3.2
v tibble  2.1.3     v dplyr   0.8.3
v tidyr   1.0.0     v stringr 1.4.0
v readr   1.3.1     v forcats 0.4.0
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
knitr::opts_chunk$set(message = FALSE, warning = FALSE)

Aims

One of the great strengths of the R developer community is that developers contribute to and extend functionality of existing packages. There is a growing number of extension packages that uses ggplot2 principles, objects and builds on this, extending and enhancing the functionality of that package.

This document highlights some that I personally find useful. I’m not presenting an exhaustive list, nor ALL of the functionality within each, but rather highlighting particular functions or functionality that illustrates what the package can do for you.

Resources & background reading

Start with “R for Data Science” section on Data Visualization using ggplot2. https://r4ds.had.co.nz/data-visualisation.html

Then I can heartily recommend Claus O. Wilke’s “Fundamentals of Data Visualization”. https://serialmentor.com/dataviz/ This book gives good advice on how you should represent different data types, but also gives some good practice on making your visualizations easier and quicker for others to understand and digest the relevant information.

Finally check out https://www.ggplot2-exts.org/ for a good review of ggplot2 extensions.

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 aesthetics 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")

Session Info

sessionInfo()
R version 3.6.1 (2019-07-05)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 16299)

Matrix products: default

locale:
[1] LC_COLLATE=English_United Kingdom.1252  LC_CTYPE=English_United Kingdom.1252    LC_MONETARY=English_United Kingdom.1252 LC_NUMERIC=C                            LC_TIME=English_United Kingdom.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] tidygraph_1.1.2   ggraph_2.0.0      miniCRAN_0.2.12   survminer_0.4.6   ggpubr_0.2.3      magrittr_1.5      survival_2.44-1.1 xgxr_1.0.2        ggedit_0.3.0      ggforce_0.3.1     GGally_1.4.0      ggridges_0.5.1    gghighlight_0.1.0
[14] ggrepel_0.8.1     cowplot_1.0.0     ggeasy_0.1.0      patchwork_0.0.1   forcats_0.4.0     stringr_1.4.0     dplyr_0.8.3       purrr_0.3.2       readr_1.3.1       tidyr_1.0.0       tibble_2.1.3      ggplot2_3.2.1     tidyverse_1.2.1  

loaded via a namespace (and not attached):
 [1] nlme_3.1-140       lubridate_1.7.4    RColorBrewer_1.1-2 progress_1.2.2     httr_1.4.1         tools_3.6.1        backports_1.1.4    R6_2.4.0           lazyeval_0.2.2     colorspace_1.4-1   withr_2.1.2        tidyselect_0.2.5  
[13] gridExtra_2.3      prettyunits_1.0.2  compiler_3.6.1     cli_1.1.0          rvest_0.3.4        binom_1.1-1        xml2_1.2.2         labeling_0.3       scales_1.0.0       survMisc_0.5.5     digest_0.6.20      shinyBS_0.61      
[25] pkgconfig_2.0.2    htmltools_0.3.6    rlang_0.4.0        readxl_1.3.1       rstudioapi_0.10    shiny_1.3.2        farver_1.1.0       generics_0.0.2     zoo_1.8-6          jsonlite_1.6       Matrix_1.2-17      Rcpp_1.0.2        
[37] munsell_0.5.0      viridis_0.5.1      lifecycle_0.1.0    stringi_1.4.3      MASS_7.3-51.4      plyr_1.8.4         grid_3.6.1         promises_1.0.1     crayon_1.3.4       miniUI_0.1.1.1     lattice_0.20-38    graphlayouts_0.5.0
[49] haven_2.1.1        splines_3.6.1      pander_0.6.3       hms_0.5.1          zeallot_0.1.0      knitr_1.24         pillar_1.4.2       igraph_1.2.4.1     ggsignif_0.6.0     reshape2_1.4.3     XML_3.98-1.20      glue_1.3.1        
[61] packrat_0.5.0      metrumrg_5.57      data.table_1.12.2  modelr_0.1.5       png_0.1-7          vctrs_0.2.0        tweenr_1.0.1       httpuv_1.5.2       cellranger_1.1.0   gtable_0.3.0       polyclip_1.10-0    km.ci_0.5-2       
[73] reshape_0.8.8      assertthat_0.2.1   xfun_0.9           mime_0.7           xtable_1.8-4       broom_0.5.2        later_0.8.0        viridisLite_0.3.0  KMsurv_0.1-5       ellipsis_0.2.0.1   shinyAce_0.4.0    
LS0tDQp0aXRsZTogImdncGxvdDIgVGlwcyBhbmQgVHJpY2tzIGFuZCBFeHRlbnNpb25zIg0KYXV0aG9yOiAiTWlrZSBLIFNtaXRoIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3Igc2V0dXB9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSkNCmBgYA0KDQojIyBBaW1zDQpPbmUgb2YgdGhlIGdyZWF0IHN0cmVuZ3RocyBvZiB0aGUgUiBkZXZlbG9wZXIgY29tbXVuaXR5IGlzIHRoYXQgZGV2ZWxvcGVycw0KY29udHJpYnV0ZSB0byBhbmQgZXh0ZW5kIGZ1bmN0aW9uYWxpdHkgb2YgZXhpc3RpbmcgcGFja2FnZXMuIFRoZXJlIGlzIGEgDQpncm93aW5nIG51bWJlciBvZiBleHRlbnNpb24gcGFja2FnZXMgdGhhdCB1c2VzIGBnZ3Bsb3QyYCBwcmluY2lwbGVzLCBvYmplY3RzIGFuZA0KYnVpbGRzIG9uIHRoaXMsIGV4dGVuZGluZyBhbmQgZW5oYW5jaW5nIHRoZSBmdW5jdGlvbmFsaXR5IG9mIHRoYXQgcGFja2FnZS4NCg0KVGhpcyBkb2N1bWVudCBoaWdobGlnaHRzIHNvbWUgdGhhdCBJIHBlcnNvbmFsbHkgZmluZCB1c2VmdWwuIEknbSBub3QgcHJlc2VudGluZw0KYW4gZXhoYXVzdGl2ZSBsaXN0LCBub3IgQUxMIG9mIHRoZSBmdW5jdGlvbmFsaXR5IHdpdGhpbiBlYWNoLCBidXQgcmF0aGVyIA0KaGlnaGxpZ2h0aW5nIHBhcnRpY3VsYXIgZnVuY3Rpb25zIG9yIGZ1bmN0aW9uYWxpdHkgdGhhdCBpbGx1c3RyYXRlcyB3aGF0IHRoZQ0KcGFja2FnZSBjYW4gZG8gZm9yIHlvdS4NCg0KIyMgUmVzb3VyY2VzICYgYmFja2dyb3VuZCByZWFkaW5nDQpTdGFydCB3aXRoICJSIGZvciBEYXRhIFNjaWVuY2UiIHNlY3Rpb24gb24gRGF0YSBWaXN1YWxpemF0aW9uIHVzaW5nIGBnZ3Bsb3QyYC4NCmh0dHBzOi8vcjRkcy5oYWQuY28ubnovZGF0YS12aXN1YWxpc2F0aW9uLmh0bWwgDQoNClRoZW4gSSBjYW4gaGVhcnRpbHkgcmVjb21tZW5kIENsYXVzIE8uIFdpbGtlJ3MgIkZ1bmRhbWVudGFscyBvZiBEYXRhIA0KVmlzdWFsaXphdGlvbiIuIGh0dHBzOi8vc2VyaWFsbWVudG9yLmNvbS9kYXRhdml6LyBUaGlzIGJvb2sgZ2l2ZXMgZ29vZCANCmFkdmljZSBvbiBob3cgeW91IHNob3VsZCByZXByZXNlbnQgZGlmZmVyZW50IGRhdGEgdHlwZXMsIGJ1dCBhbHNvIGdpdmVzIHNvbWUNCmdvb2QgcHJhY3RpY2Ugb24gbWFraW5nIHlvdXIgdmlzdWFsaXphdGlvbnMgZWFzaWVyIGFuZCBxdWlja2VyIGZvciBvdGhlcnMNCnRvIHVuZGVyc3RhbmQgYW5kIGRpZ2VzdCB0aGUgcmVsZXZhbnQgaW5mb3JtYXRpb24uDQogDQpGaW5hbGx5IGNoZWNrIG91dCBodHRwczovL3d3dy5nZ3Bsb3QyLWV4dHMub3JnLyBmb3IgYSBnb29kIHJldmlldyBvZiBnZ3Bsb3QyIA0KZXh0ZW5zaW9ucy4NCg0KIyBnZ3Bsb3QyIHRpcHMgYW5kIHRyaWNrcw0KDQojIyBDcmVhdGluZyBgZ2dwbG90MmAgcGxvdHMgZm9yIHVzZSBpbiBleGFtcGxlcw0KDQpgYGB7ciBjcmVhdGVfZ2dwbG90Ml9wbG90c30NCnBsb3QxIDwtIG1wZyAlPiUNCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5KSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGdlb21fc21vb3RoKCkNCg0KcGxvdDENCg0KcGxvdDMgPC0gZ2dwbG90KGRhdGE9bXBnLCBtYXBwaW5nPWFlcyh4ID0gY2xhc3MsIHkgPSBod3kpKSArDQogIGdlb21fYm94cGxvdCgpDQpwbG90Mw0KDQpwbG90NCA8LSBnZ3Bsb3QobXBnKSArIA0KICBnZW9tX2JhcihhZXMoY2xhc3MsZmlsbD1jbGFzcykpIA0KcGxvdDQNCmBgYA0KDQojIyMgZ2dwbG90MiAtIEFkZCB0aXRsZXMsIHN1YnRpdGxlcywgY2FwdGlvbnMsIGF4ZXMgbGFiZWxzDQpPbmUgb2YgdGhlIGdyZWF0IHRoaW5ncyBhYm91dCBgZ2dwbG90MmAgaXMgdGhlIHdheSB0aGF0IHlvdSBjYW4gY3JlYXRlIA0KZ3JhcGhzIHF1aWNrbHkgZm9yIHlvdXIgb3duIHVzZSAodG8gdmlzdWFsaXplIGRhdGEpLCBidXQgdGhlbiB5b3UgY2FuIHRha2UgDQp0aGF0IG9iamVjdCBhbmQgcmVmaW5lIGl0IHJlYWR5IGZvciBwcmVzZW50YXRpb24gb3IgcHVibGljYXRpb24uIEhlcmUgd2UgYWRkDQpsYWJlbHMgYW5kIHRpdGxlcyB0byBhbiBleGlzdGluZyBwbG90Lg0KDQpgYGB7ciBhbm5vdGF0ZV9nZ3Bsb3QyX3dpdGhfbGFiZWxzfQ0KcGxvdDEgKw0KICBsYWJzKHRpdGxlID0gIkhpZ2h3YXkgbWlsZWFnZSBpcyBpbnZlcnNlbHkgcmVsYXRlZCB0byBlbmdpbmUgc2l6ZSIsDQogICAgICAgc3VidGl0bGUgPSAiRGF0YTogbXBnIiwNCiAgICAgICB4ID0gIkVuZ2luZSBzaXplIChMKSIsDQogICAgICAgeSA9ICJIaWdod2F5IG1pbGVhZ2UgKG1wZykiLA0KICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBodHRwczovL2dpdGh1Yi5jb20vTWlrZUtTbWl0aC9tYXN0ZXJfdGhlX3RpZHl2ZXJzZV8yMDE5XzRRIikNCmBgYA0KDQoNCiMjIyBnZ3Bsb3QyIC0gVXNpbmcgYCUrJWAgdG8gY2hhbmdlIHRoZSBkYXRhc2V0DQpPbmUgb2Z0ZW4gb3Zlcmxvb2tlZCBmdW5jdGlvbmFsaXR5IGluIGBnZ3Bsb3QyYCBpcyB0aGUgYWJpbGl0eSB0byBjaGFuZ2UgdGhlIA0KZGF0YXNldCB3aXRoaW4gdGhlIGBnZ3Bsb3QyYCBvYmplY3QgYW5kIGVzc2VudGlhbGx5IHJlcGxheSB0aGUgc2FtZSBncmFwaCBmb3INCm5ldyBkYXRhLiBUaGlzIGZ1bmN0aW9uYWxpdHkgaXMgcmVhbGx5IHVzZWZ1bCBpZiB5b3UgaGF2ZSBkcmFmdCBkYXRhIHdoaWNoIHlvdQ0KY2FuIHVzZSB0byBjcmVhdGUgZWFybHkgdmlzdWFsaXphdGlvbnMgd2hpY2ggeW91IG1pZ2h0IHdhbnQgdG8gcmVmaW5lIGxhdGVyIHdpdGgNCnRoZSBmaW5hbCBkYXRhLiBUaGUgYXNzdW1wdGlvbiBoZXJlIGlzIHRoYXQgdmFyaWFibGUgbmFtZXMgd29uJ3QgY2hhbmdlLCBzaW5jZSANCnRoZSBgbWFwcGluZ2AgdG8gYGFlc2B0aGV0aWNzIGlzIGNhcnJpZWQgb3Zlci4gDQoNCkluIHRoZSBjYXNlIGJlbG93LCB3ZSBoYXZlIHRvIHNsaWdodGx5IGNoYW5nZSB2YXJpYWJsZSBuYW1lcyBpbiB0aGUgYG10Y2Fyc2AgDQpkYXRhc2V0IHRvIGdldCBpdCB0byBtYXRjaCB0aGUgYG1wZ2AgZGF0YS4gQnV0IG9uY2Ugd2UndmUgZG9uZSB0aGF0IHdlIGNhbg0Kc2ltcGx5IHVwZGF0ZSBgcGxvdDFgIHdpdGggdGhlIG5ldyBkYXRhLg0KDQpgYGB7ciBjaGFuZ2VfZ2dwbG90Ml9kYXRhfQ0KbXRjYXJzMiA8LSBtdGNhcnMgJT4lDQogIHJlbmFtZShod3kgPSBtcGcsDQogICAgICAgICBkaXNwbCA9IGRpc3ApDQoNCnBsb3QyIDwtIHBsb3QxICUrJQ0KICBtdGNhcnMyDQpwbG90Mg0KYGBgDQoNCiMjIyBVc2UgZm9yY2F0cyBsaWJyYXJ5IGZ1bmN0aW9ucyB0byBzb3J0IGZhY3RvcnMgYnkgc2l6ZSBvZiBzb21lIHZhcmlhYmxlDQpgYGB7ciBzb3J0X2NhdGVnb3JpZXNfdXNpbmdfZm9yY2F0c30NCnBsb3Q1IDwtIG1wZyAlPiUNCiAgZ3JvdXBfYnkoY2xhc3MpICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSkgJT4lDQogIG11dGF0ZShjbGFzcyA9IGZjdF9yZW9yZGVyKGNsYXNzLCBuKSkgJT4lDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBjbGFzcyxmaWxsPWNsYXNzKSkgKyANCiAgZ2VvbV9jb2wobWFwcGluZyA9IGFlcyh5ID0gbikpICsNCiAgY29vcmRfZmxpcCgpDQpwbG90NQ0KYGBgDQoNCg0KIyMgcGF0Y2h3b3JrIC0gYXJyYW5nZSBtdWx0aXBsZSBwbG90cw0KVGhlIGBwYXRjaHdvcmtgIHBhY2thZ2UgKGJ5IFRob21hcyBMaW4gUGVkZXJzZW4gLSBodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKQ0KYWxsb3dzIHRoZSB1c2VyIHRvIGRlZmluZSBob3cgdG8gY29tYmluZSBncmFwaHMuIFRoZSBgK2Agb3BlcmF0b3IgaGVyZSBjb21iaW5lcw0KcGxvdHMgaW50byBhIHNpbmdsZSBwbG90IG9iamVjdC4gYHBsb3RfYW5ub3RhdGlvbmAgdGhlbiBhbGxvd3MgdXMgdG8gZGVmaW5lDQpvdmVyYXJjaGluZyB0aXRsZXMsIGxhYmVscywgYW5ub3RhdGlvbnMuIGB0YWdfbGV2ZWxzYCBkZWZpbmVzIGhvdyB5b3UgbWlnaHQgDQp3YW50IHRvIGlkZW50aWZ5IHRoZSBncmFwaHMgYW5kIGB0YWdfc3VmZml4YCBwYXN0ZXMgb250byB0aGUgZW5kIG9mIGB0YWdfbGV2ZWxgLg0KDQpgYGB7ciBwYXRjaHdvcmtfbXVsdGlwbGVfZ3JhcGhzMX0NCmxpYnJhcnkocGF0Y2h3b3JrKQ0KcGxvdDMgKyBwbG90NCArIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICJtcGcgZGF0YSBleHBsb3JhdGlvbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWdfbGV2ZWxzPSJhIiwgdGFnX3N1ZmZpeCA9ICIpIikNCmBgYA0KDQojIyBnZ2Vhc3kgLSBlYXN5IGFjY2VzcyB0byB0aGVtZSBhc3BlY3RzDQoNClRoZSB4LWF4aXMgYGNsYXNzYCB2YXJpYWJsZSBuYW1lcyBhYm92ZSBsb29rIEFXRlVMIHdoZW4gdGhlIHBsb3RzIGFyZSBwdWxsZWQNCmludG8gYHBhdGNod29ya2AuIEkga25vdyB0aGF0IGluIGBnZ3Bsb3QyYCBJIGNhbiByb3RhdGUgdGhlIGF4ZXMgbGFiZWxzLi4uIGJ1dA0KY2FuIEkgcmVjYWxsIHRoZSBzcGVjaWZpYyB0aGVtZSBzZXR0aW5ncz8NCg0KVGhlIGBnZ2Vhc3lgIHBhY2thZ2UgKGJ5IEpvbmF0aGFuIENhcnJvbGwgLSBodHRwczovL2pvbm9jYXJyb2xsLmdpdGh1Yi5pby9nZ2Vhc3kvKQ0KcHJvdmlkZXMgcXVpY2sgYWNjZXNzIHRvIHR3ZWFrIGNvbW1vbmx5IHVzZWQgYGdncGxvdDJgIHRoZW1lIHNldHRpbmdzLg0KDQpgYGB7ciBnZ2Vhc3lfcGVhc3l9DQpsaWJyYXJ5KGdnZWFzeSkNCnBsb3QzYiA8LSBwbG90MyArDQogIGVhc3lfcm90YXRlX3hfbGFiZWxzKHNpZGUgPSAicmlnaHQiKQ0KcGxvdDRiIDwtIHBsb3Q0ICsNCiAgZWFzeV9yb3RhdGVfeF9sYWJlbHMoc2lkZSA9ICJyaWdodCIpDQoNCnBsb3QzYiArIHBsb3Q0YiArIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICJtcGcgZGF0YSBleHBsb3JhdGlvbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWdfbGV2ZWxzPSJhIiwgdGFnX3N1ZmZpeCA9ICIpIikNCmBgYA0KDQoNCiMjIHBhdGNod29yayBjb250aW51ZWQgLSBDb21iaW5pbmcgbWFueSBwbG90cw0KSGVyZSB3ZSdyZSB1c2luZyBwYXRjaHdvcmsgdG8gcHJvZHVjZSBhIHNsaWdodGx5IG1vcmUgY29tcGxleCBsYXlvdXQsDQpjb21iaW5pbmcgYHBsb3QzYCBhbmQgYHBsb3Q0YCBpbiBvbmUgcm93LCB0aGVuIGBwbG90MWAgdW5kZXJuZWF0aC4gVGhlIGAtYA0Kc2lnbmlmaWVzIHRoYXQgdGhlIGNvbWJpbmVkIGBwbG90MyArIHBsb3Q0YCBhcmUgb24gdGhlIHNhbWUgbGV2ZWwgYXMgYHBsb3QxYCwgDQphbmQgYHBsb3RfbGF5b3V0YCBkZWZpbmVzIHRoYXQgdGhlcmUgaXMgb25lIGNvbHVtbi4gDQoNCmBgYHtyIHBhdGNod29ya19tdWx0aXBsZV9ncmFwaHMyfQ0KcGxvdDNiICsgcGxvdDRiIC0gcGxvdDEgKyBwbG90X2xheW91dChuY29sPTEpICsgcGxvdF9hbm5vdGF0aW9uKHRpdGxlID0gIm1wZyBkYXRhIGV4cGxvcmF0aW9uIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhZ19sZXZlbHM9ImkiLCB0YWdfc3VmZml4ID0gIikiKQ0KYGBgDQoNCiMjIyBVc2UgRGF0YSBWaXN1YWxpemF0aW9uIGJlc3QgcHJhY3RpY2UNCkluIHRoZSBjb21iaW5lZCBncmFwaCBvZiBgcGxvdDMgKyBwbG90NGAgYWJvdmUsIHRoZXJlIGlzIGEgbG90IG9mIHJlcGVhdGVkIA0KaW5mb3JtYXRpb24gaW4gdGhlIGdyYXBoaWMgKHZlaGljbGUgY2xhc3MsIGxlZ2VuZHMgZXRjLikuIEJ5IGZsaXBwaW5nIHRoZSANCnBsb3QgdG8gYmUgaG9yaXpvbnRhbCwgd2UgY2FuIHNob3cgdGhlIGJveHBsb3Qgd2l0aCB0aGUgYmFyIGNoYXJ0IGFuZA0KZWxpbWluYXRlIG11Y2ggb2YgdGhlIHJlZHVuZGFudCBpbmZvcm1hdGlvbi4NCg0KSGVyZSB3ZSdyZSBhbHNvIHVzaW5nIHRoZSBgZ2dlYXN5YCBmdW5jdGlvbnMgYGVhc3lfcmVtb3ZlX2xlZ2VuZCgpYCBhbmQgDQpgZWFzeV9yZW1vdmVfeV9heGlzKClgIHRvIHJlbW92ZSB0aGUgZXh0cmFuZW91cyBpbmZvcm1hdGlvbiBmcm9tIGBwbG90NGNgLg0KDQpgYGB7ciBwYXRjaHdvcmtfbXVsdGlwbGVfZ3JhcGhzM30NCnBsb3QzYyA8LSBwbG90MyArIGNvb3JkX2ZsaXAoKQ0KcGxvdDRjIDwtIHBsb3Q0ICsgY29vcmRfZmxpcCgpICsgDQogIGVhc3lfcmVtb3ZlX2xlZ2VuZCgpICsgDQogIGVhc3lfcmVtb3ZlX3lfYXhpcygpDQoNCnBsb3QzYyArIHBsb3Q0Yw0KYGBgDQoNCg0KIyMgY293cGxvdCAtIHRlbXBsYXRlcyBmb3IgcHVibGljYXRpb24gcmVhZHkgcGxvdHMNClRoZSBgY293cGxvdGAgcGFja2FnZSAoYnkgQ2xhdXMgTy4gV2lsa2UgLSBodHRwczovL3dpbGtlbGFiLm9yZy9jb3dwbG90L2luZGV4Lmh0bWwpDQppbXBsZW1lbnRzIG1hbnkgb2YgdGhlIHN0YW5kYXJkcyBhbmQgcmVjb21tZW5kYXRpb25zIGZyb20gaGlzICJGdW5kYW1lbnRhbHMgb2YNCkRhdGEgVmlzdWFsaXphdGlvbiIgYm9vay4gSXQgZXh0ZW5kcyBgZ2dwbG90MmAgd2l0aCB0aGVtZXMgZm9yIHB1YmxpY2F0aW9uLA0KZnVuY3Rpb25zIGZvciBwbG90IGFsaWdubWVudCBhbmQgYWRkaXRpb25hbCBhbm5vdGF0aW9ucy4NCg0KYGBge3IgY293cGxvdF90aGVtZXN9DQpsaWJyYXJ5KGNvd3Bsb3QpDQpwbG90MSArIA0KICBsYWJzKHRpdGxlPSJIaWdod2F5IG1pbGVhZ2UgaXMgaW52ZXJzZWx5IHJlbGF0ZWQgdG8gZW5naW5lIHNpemUiLCANCiAgICAgICB4ID0gIkVuZ2luZSBkaXNwbGFjZW1lbnQgKExpdHJlcykiLA0KICAgICAgIHkgPSAiSGlnaHdheSBtaWxlYWdlIChtcGcpIikgKw0KICAgdGhlbWVfY293cGxvdChmb250X3NpemUgPSAxNCkNCg0KcGxvdDEgKyANCiAgIHRoZW1lX21pbmltYWxfZ3JpZCgpDQoNCnBsb3QxICsgDQogICB0aGVtZV9taW5pbWFsX2hncmlkKCkNCmBgYA0KDQojIyMgQWRkaW5nIGFubm90YXRpb25zIGFuZCB0ZXh0IHRvIHBsb3RzDQpUaGUgYGdnZHJhd2AgZnVuY3Rpb24gd2l0aCB0aGUgYGNvd3Bsb3RgIHBhY2thZ2UgdGFrZXMgYW4gZXhpc3RpbmcgYGdncGxvdDJgIA0Kb2JqZWN0IGFuZCBzZXRzIHVwIGEgZHJhd2luZyBsYXllciBmb3IgYWRkaXRpb25hbCBhbm5vdGF0aW9uIG9uIHRvcC4gSGVyZSB3ZSdyZSANCmFkZGluZyBhICJEUkFGVCIgd2F0ZXJtYXJrLg0KDQpgYGB7ciBjb3dwbG90X2xhYmVsfQ0KZ2dkcmF3KHBsb3QxKSArIA0KICBkcmF3X2xhYmVsKCJEcmFmdCIsIGNvbG9yID0gImxpZ2h0IGdyZXkiLCBhbHBoYT0wLjMsIHNpemUgPSAxMDAsIGFuZ2xlID0gNDUpIA0KYGBgDQoNCiMjIyBVc2luZyBgc3RhbXBgIHRvIGxhYmVsIHBsb3RzDQpUaGUgYHN0YW1wYCBmdW5jdGlvbmFsaXR5IGFsbG93cyB5b3UgdG8gbGFiZWwgYW4gYXR0cmlidXRlIG9mIGEgd2hvbGUgZ3JhcGhpYy4NCkluIHRoZSBib29rLCBXaWxrZSBsYWJlbHMgcGxvdHMgYXMgInVnbHkiIG9yICJiYWQiLCBidXQgaGVyZSB3ZSdyZSBtYXJraW5nDQp0aGUgd2hvbGUgZ3JhcGggYXMgIkRSQUZUIi4gVGhpcyBpcyBwZXJoYXBzIGxlc3Mgb2J0cnVzaXZlIHRoYW4gdGhlIHdhdGVybWFyaw0KYWJvdmUuDQoNCmBgYHtyIGNvd3Bsb3Rfc3RhbXB9DQpzdGFtcChwbG90MSwgbGFiZWwgPSAiRFJBRlQiLCBjb2xvciA9ICJyZWQiKQ0KYGBgDQoNCiMjIGdncmVwZWwgdG8gbGFiZWwgcG9pbnRzIHdpdGhvdXQgb3ZlcmxhcA0KVGhlIGBnZ3JlcGVsYCBwYWNrYWdlIChieSBLYW1pbCBTbG93aWtvd3NpIC0gaHR0cHM6Ly9zbG93a293LmNvbS9nZ3JlcGVsKSBoYXMNCmJlZW4gZGV2ZWxvcGVkIHRvIGVuc3VyZSB0aGF0IHRleHQgbGFiZWxzIGluIHBsb3RzIGRvIG5vdCBvdmVybGFwIGVhY2ggb3RoZXIsDQpkbyBub3QgY292ZXIgZGF0YSBwb2ludHMsIG5vciBmYWxsIG91dHNpZGUgb2YgdGhlIHBsb3R0aW5nIGFyZWEuIFRoZSBtZXRob2Qgb2YgDQpkb2luZyBzbyBpcyBpbmRpc3Rpbmd1aXNoYWJsZSBmcm9tIG1hZ2ljIChvciBzbyBpdCBzZWVtcyB0byBtZSkuDQoNCmBgYHtyIGdncmVwZWx9DQpsaWJyYXJ5KGdncmVwZWwpDQoNCnBsb3RfdWdseSA8LSBnZ3Bsb3QobXRjYXJzKSArIA0KICBhZXMoDQogICAgeCA9IHd0LCB5ID0gbXBnLA0KICAgIGxhYmVsID0gcm93bmFtZXMobXRjYXJzKQ0KICApICsgDQogIGdlb21fcG9pbnQoY29sb3IgPSAicmVkIikgKw0KICBnZW9tX3RleHQoKQ0KDQpzdGFtcF91Z2x5KHBsb3RfdWdseSkNCg0KZ2dwbG90KG10Y2FycykgKyANCiAgYWVzKA0KICAgIHggPSB3dCwgeSA9IG1wZywNCiAgICBsYWJlbCA9IHJvd25hbWVzKG10Y2FycykNCiAgKSArIA0KICBnZW9tX3BvaW50KGNvbG9yID0gInJlZCIpICsNCiAgZ2VvbV90ZXh0X3JlcGVsKCkNCmBgYA0KDQoNCiMjIGdnaGlnaGxpZ2h0IC0gaGlnaGxpZ2h0aW5nIHZpc3VhbGl6YXRpb24gaXRlbXMgYmFzZWQgb24gcHJlZGljYXRlcw0KYGdnaGlnaGxpZ2h0YCAoYnkgSGlyb2FraSBZdXRhbmkgLSBodHRwczovL3l1dGFubmloaWxhdGlvbi5naXRodWIuaW8vZ2doaWdobGlnaHQvaW5kZXguaHRtbCkNCmlzIGEgYGdncGxvdDJgIGV4dGVuc2lvbiB3aGljaCBoaWdobGlnaHRzIGBnZ3Bsb3QyYCBgZ2VvbXNgIHRoYXQgbWF0Y2ggYSBnaXZlbiANCnByZWRpY2F0ZS4NCg0KSW4gbWFueSBjYXNlcyB0aGlzIGxlYWRzIHRvIHRoZSByZWNvbW1lbmRhdGlvbnMgaW4gIkZ1bmRhbWVudGFscyBvZiBEYXRhDQpWaXN1YWxpemF0aW9uIiBiZWNhdXNlIGlzIGFsbG93cyB0aGUgcmVhZGVyIHRvIGZvY3VzIHN0cmFpZ2h0IGluIG9uIHRoZSANCnBhcnRpY3VsYXIgZWxlbWVudCBvZiB0aGUgcGxvdCB0aGF0IGlzIGtleSB0byB0aGUgY2xhaW1zIGJlaW5nIG1hZGUuIEl0DQpoZWxwcyB0aGUgInN0b3J5IiBvZiBvdXIgZGF0YSB2aXN1YWxpemF0aW9uLi4uDQoNCmBgYHtyIGdnaGlnaGxpZ2h0XzF9DQpsaWJyYXJ5KGdnaGlnaGxpZ2h0KQ0KDQpwbG90NCArIA0KICBnZ2hpZ2hsaWdodChjbGFzcyA9PSAic3V2IikNCg0KcGxvdDEgKyANCiAgZ2doaWdobGlnaHQoY2xhc3MgPT0gInN1diIpIA0KDQpwbG90NSArIA0KICBnZ2hpZ2hsaWdodChjbGFzcyA9PSAic3V2IikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKQ0KYGBgDQoNCiMjIGdncmlkZ2VzIHRvIGNvbXBhcmUgZGVuc2l0aWVzIG5lYXRseQ0KYGdncmlkZ2VzYCAoYnkgQ2xhdXMgTyBXaWxrZSAtIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3JpZGdlcy92aWduZXR0ZXMvaW50cm9kdWN0aW9uLmh0bWwpIA0KaXMgYSB1c2VmdWwgZXh0ZW5zaW9uIGZvciBwbG90dGluZyBtYW55IGRlbnNpdGllcywgb3Igb3RoZXIgDQpyaWRnZWxpbmUgdmlzdWFsaXphdGlvbnMgd2hlcmUgeW91IG1heSB3aXNoIHRvIHBsb3QgdGhlIHJpZGdlcyBieSBhIGNhdGVnb3JpY2FsIA0KdmFyaWFibGUuIGBnZ3JpZGdlc2Agc3RhY2tzIHRoZXNlIHBsb3RzIHNvIHRoYXQgdGhlIGluZGl2aWR1YWwgcmlkZ2VzIG9yIGRlbnNpdGllcw0KYXJlIGVhc2lseSB2aXNpYmxlLCBidXQgY29tcGFjdGx5Lg0KDQpPcmRlcmluZyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgb3IgZmFjdG9yIGJ5IG1hZ25pdHVkZSBvZiB0aGUgeC1heGlzIHZhcmlhYmxlDQp1c2luZyB0aGUgYGZjdF9yZW9yZGVyYCBmdW5jdGlvbiBmcm9tIHRoZSBgZm9yY2F0c2AgcGFja2FnZSwgbWFrZXMgaXQgZWFzaWVyDQpmb3IgdGhlIHZpZXdlciB0byBzZWUgd2hpY2ggY2F0ZWdvcnkgaGFzIHRoZSBsYXJnZXN0IG1lYW4sIGFzIHdlbGwgYXMgdGhlIA0Kc3ByZWFkIHdpdGhpbiB0aGUgZGVuc2l0eSBvciByaWRnZWxpbmUgcGxvdC4NCg0KYGBge3IgZ2dyaWRnZXN9DQpsaWJyYXJ5KGdncmlkZ2VzKQ0KDQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gaHd5LCB5ID0gY2xhc3MpKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKQ0KDQojIyBVc2luZyBmY3RfcmVvcmRlciBmcm9tIHRoZSBgZm9yY2F0c2AgcGFja2FnZSB0byBzb3J0IHRoZSBmYWN0b3INCiMjIGJ5IHRoZSBtZWRpYW4gb2YgYGh3eWAgd2l0aGluIGVhY2ggY2xhc3MuDQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gaHd5LCB5ID0gZmN0X3Jlb3JkZXIoY2xhc3MsIGh3eSkpKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKQ0KYGBgDQoNCiMjIEdHYWxseSAtIFF1aWNrIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMNCmBnZ2FsbHlgIChieSB0aGUgR0dvYmkgRm91bmRhdGlvbiAtIGh0dHBzOi8vZ2dvYmkuZ2l0aHViLmlvL2dnYWxseS8pIHByb3ZpZGVzDQp1c2VmdWwgbWF0cml4IHBsb3RzIG9mIGRhdGEgYW5kIGlzIHJlYWxseSB3ZWxsIHN1aXRlZCB0byBxdWljayBkYXRhIHZpc3VhbGl6YXRpb24NCmZvciBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzLiANCg0KYGdnc2NhdG1hdGAgcXVpY2tseSBwcm9kdWNlcyBhIHNjYXR0ZXJwbG90IG1hdHJpeCwgZGVuc2l0eSBhbmQgY29ycmVsYXRpb24gcmVhZG91dCBmb3IgDQpjb250aW51b3VzIHZhcmlhYmxlcy4NCg0KYGdncGFpcnNgIGlzIGEgbW9yZSBjb21wbGV0ZSB2aXN1YWxpemF0aW9uIG9mIGJvdGggY29udGludW91cyBhbmQgY2F0ZWdvcmljYWwgDQp2YXJpYWJsZXMsIGJ1dCBpcyBzb21ld2hhdCBzbG93ZXIgdGhhbiBgZ2dzY2F0bWF0YC4NCg0KYGBge3IgR0dhbGx5fQ0KbGlicmFyeShHR2FsbHkpDQoNCiMjIENvbnRpbnVvdXMgb3V0Y29tZXMgb25seSBjYW4gdXNlIHRoZSBxdWlja2VyIGBnZ3NjYXRtYXRgDQptcGcgJT4lDQogIHNlbGVjdChjdHksIGh3eSwgZGlzcGwpICU+JQ0KICBnZ3NjYXRtYXQoKQ0KDQojIyBGb3IgbW9yZSBnZW5lcmFsIHVzZSB3aXRoIGJvdGggY2F0ZWdvcmljYWwgYW5kIGNvbnRpbnVvdXMgdmFyaWFibGVzDQojIyB1c2UgZ2dwYWlycy4gQWx0aG91Z2ggZnVuY3Rpb24gdGhpcyBpcyBzbG93ZXIuDQptcGcgJT4lDQogIHNlbGVjdChjbGFzcywgY3R5LCBod3ksIGRpc3BsKSAlPiUNCiAgZ2dwYWlycygpDQpgYGANCg0KIyMgZ2dmb3JjZSAtIGFkZGl0aW9uYWwgZ2VvbXMgYW5kIGV4dGVuc2lvbnMgdG8gZ2dwbG90Mg0KYGdnZm9yY2VgIChieSBUaG9tYXMgTGluIFBlZGVyc2VuIC0gaHR0cHM6Ly9nZ2ZvcmNlLmRhdGEtaW1hZ2luaXN0LmNvbS8pDQpwcm92aWRlcyBhZGRpdGlvbmFsIGBnZW9tc2AgYW5kIGV4dGVuc2lvbnMgdG8gYGdncGxvdDJgLg0KDQpgYGB7ciBnZ2ZvcmNlX2dlb21zfQ0KbGlicmFyeShnZ2ZvcmNlKQ0KDQpwbG90NiA8LSBtcGcgJT4lDQogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gY2xhc3MpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgZ2VvbV9tYXJrX2VsbGlwc2UoYWVzKGZpbGw9Y2xhc3MpKQ0KcGxvdDYNCg0KIyMgQWx0ZXJuYXRpdmVseSBsYWJlbCB0aGUgb3ZlcmxhcHBpbmcgZWxsaXBzZXMNCnBsb3Q2ICsgDQogIGdlb21fbWFya19lbGxpcHNlKGFlcyhsYWJlbD1jbGFzcykpDQoNCiMjIFBlcmhhcHMgYmV0dGVyIHVzaW5nIGdnaGlnaGxpZ2h0DQpwbG90NiArDQogIGdnaGlnaGxpZ2h0KGNsYXNzID09ICIyc2VhdGVyIiwgdXNlX2RpcmVjdF9sYWJlbCA9IEZBTFNFKQ0KYGBgDQoNCkluIGFkZGl0aW9uIGBnZ2ZvcmNlYCBwcm92aWRlcyB0aGUgYGZhY2V0X3pvb21gIGZ1bmN0aW9uYWxpdHkgdG8gcHJvdmlkZSANCm9uZSB2aXN1YWxpemF0aW9uIHdoaWNoIHNob3dzIHRoZSBvdmVydmlldyBvZiB0aGUgZGF0YSwgYnV0IHpvb21zIGluIHRvIGEgDQpwcmVkaWNhdGUgcmVnaW9uLCBvciBhbiB4LSBvciB5LWF4aXMgcmVnaW9uLg0KDQpgYGB7ciBnZ2ZvcmNlX2ZhY2V0X3pvb219DQojIyBab29tIGluIG9uIHRoZSAyc2VhdGVyIGNsYXNzDQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG91ciA9IGNsYXNzKSkgKyANCiAgZ2VvbV9wb2ludCgpICsgDQogIGZhY2V0X3pvb20oY2xhc3M9PSIyc2VhdGVyIikNCg0KIyMgQWx0ZXJuYXRpdmVseSB6b29tIGJ5IHgtYXhpcyBsaW1pdHMNCmdncGxvdChkYXRhID0gbXBnLCBtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gY2xhc3MpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgZmFjZXRfem9vbSh4bGltPWMoMS41LCAyLjUpKSANCmBgYA0KDQojIyBnZ2VkaXQgLSBtYW5pcHVsYXRlIHBsb3Qgb2JqZWN0cw0KYGdnZWRpdGAgKGZyb20gTWV0cnVtIFJlc2VhcmNoIEdyb3VwIC0gaHR0cHM6Ly9tZXRydW1yZXNlYXJjaGdyb3VwLmdpdGh1Yi5pby9nZ2VkaXQvKQ0KcHJvdmlkZXMgdmVyeSB1c2VmdWwgZnVuY3Rpb25zIGZvciByZW1vdmluZyBsYXllcnMgb2YgYSBgZ2dwbG90MmAgdmlzdWFsaXphdGlvbg0Kb2JqZWN0LiBUaGlzIGNhbiBiZSB1c2VmdWwgd2hlbiB3ZSB3YW50IHRvIHRyYWNrIGhvdyBwbG90cyB3ZXJlIG1hbmlwdWxhdGVkIA0KdGhyb3VnaCBzY3JpcHRzLiBGb3IgZXhhbXBsZSBpZiBhIHBsb3QgaGFzIGJlZW4gUUNlZCBhbmQgc2F2ZWQgYXMgYW4gYC5SZGF0YWANCm9iamVjdCB3ZSBjYW4gc3Vic2VxdWVudGx5IGxvYWQgdGhhdCBvYmplY3QsIHVzZSBgZ2dlZGl0YCB0byBhbHRlciBpdCBhbmQgDQpzYXZlIGJhY2sgdG8gYSBuZXcgb3V0cHV0IG9iamVjdCwgb3IgdmVyc2lvbi4gVGhlIGNoYW5nZXMgZnJvbSBvbmUgc3RhdGUgdG8NCmFub3RoZXIgY2FuIGVhc2lseSBiZSB0cmFja2VkIGFuZCB0cmFjZWQgdGhyb3VnaCB0aGUgc2NyaXB0Lg0KDQojIyMgUmVtb3ZlIHBsb3QgbGF5ZXJzDQpgYGB7ciBnZ2VkaXRfcmVtb3ZlX2dlb21zfQ0KbGlicmFyeShnZ2VkaXQpDQoNCiMjIHJlbW92ZSBsb2VzcyBzbW9vdGggYW5kIHJlcGxhY2Ugd2l0aCBhIGxpbmVhciByZWdyZXNzaW9uIGxpbmUNCiMjIE5COiBVc2VzICU+JSB0byBwYXNzIGluIHRoZSBwbG90IG9iamVjdA0KIyMgQmVjYXVzZSByZXN1bHRpbmcgb3V0cHV0IGlzIGEgZ2dwbG90MiBvYmplY3Qgd2UgY2FuIHJldmVydCB0byAiKyIuDQpwbG90MSAlPiUNCiAgZ2dlZGl0OjpyZW1vdmVfZ2VvbShnZW9tID0gInNtb290aCIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCg0KIyMgVVBEQVRFOiBDYW4gdXNlIGAtYCB0byByZW1vdmUgZ2VvbXMuIA0KcGxvdDEgLSANCiAgZ2VvbV9zbW9vdGgoKQ0KYGBgDQoNClRoZSBgLWAgKG1pbnVzKSBmdW5jdGlvbmFsaXR5IHRvIHJlbW92ZSBgZ2VvbXNgIGZyb20gYGdncGxvdDJgIG9iamVjdHMgd29ya3MNCm9uIGEgImxhc3QgaW4sIGZpcnN0IG91dCIgbW9kZWwgaWYgdGhlcmUgaXMgbW9yZSB0aGFuIG9uZSBgZ2VvbWAgd2l0aCB0aGUgc2FtZQ0KdHlwZS4NCg0KYGBge3IgZ2dlZGl0X21pbnVzX21pbnVzfQ0KcGxvdDFjIDwtIGdncGxvdChtcGcsIGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKSArIGdlb21fcG9pbnQoKQ0KcGxvdDFkIDwtIHBsb3QxYyArIGdlb21fcG9pbnQoc2hhcGU9OCwgY29sb3VyID0gInJlZCIsYWxwaGE9MSxzdHJva2U9NCkNCnBsb3QxZCANCg0KcGxvdDFkIC0gZ2VvbV9wb2ludCgpICNMSUZPDQpwbG90MWQgLSBnZW9tX3BvaW50KCkgLSBnZW9tX3BvaW50KCkNCmBgYA0KDQojIyMgZ2dlZGl0IC0gaW50ZXJhY3RpdmUgZWRpdGluZyBvZiBwbG90IGF0dHJpYnV0ZXMNCkFub3RoZXIgdXNlZnVsIHBhcnQgb2YgYGdnZWRpdGAgaXMgdGhlIFNoaW55IGFwcGxpY2F0aW9uIHdoaWNoIGNhbiBiZSB1c2VkDQp0byBpbnRlcmFjdGl2ZWx5IGNoYW5nZSBgZ2dwbG90MmAgb2JqZWN0IGxheWVycyBhbmQgdGhlbWVzLCB0aGVuIHNhdmluZyB0aGUgDQpyZXN1bHRpbmcgb2JqZWN0IGJhY2sgdG8gdGhlIGVudmlyb25tZW50Lg0KDQpgYGB7ciBnZ2VkaXRfU2hpbnksIGV2YWwgPSBGQUxTRX0NCmdnZWRpdChwbG90MSkNCmBgYA0KDQojIyB4Z3hyIC0gSGVscGVyIGZ1bmN0aW9ucyBmb3IgcGxvdHRpbmcgY29uY2VudHJhdGlvbiBkYXRhDQpgeGd4cmAgaXMgYSBwYWNrYWdlIGRldmVsb3BlZCBieSBOb3ZhcnRpcyAoaHR0cDovL29wZW5zb3VyY2Uubmlici5jb20veGd4LykgZm9yDQpleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzLCBwYXJ0aWN1bGFybHkgaW4gdGhlIGZpZWxkIG9mIHBoYXJtYWNva2luZXRpYyBhbmQNCnBoYXJtYWNvZHluYW1pYyBkYXRhLiBIZXJlIHdlIGFyZSBvbmx5IHNjcmF0Y2hpbmcgdGhlIHN1cmZhY2Ugb2YgdGhpcyBwYWNrYWdlDQpidXQgaW4gcGFydGljdWxhciBJIGxpa2VkIHRoZSBhYmlsaXR5IHRvIHNob3cgbWlub3IgZ3JpZCBsaW5lcyBvbiB0aGUgbG9nIHNjYWxlLg0KDQpgYGB7ciB4Z3hyX2xvZ19zY2FsZX0NCmxpYnJhcnkoeGd4cikNCg0KZ2dwbG90KGRhdGEgPSBUaGVvcGgsIG1hcHBpbmcgPSBhZXMoeCA9IFRpbWUsIHkgPSBjb25jLCBncm91cCA9IFN1YmplY3QpKSArDQogIGdlb21fbGluZSgpICsgDQogIHhneF9zY2FsZV95X2xvZzEwKCkNCmBgYA0KDQojIyMgeGd4X3N0YXRfY2kgLSBzdGF0X3N1bW1hcnkgd2l0aCBhZGRpdGlvbmFsIG9wdGlvbnMgZm9yIENJIHR5cGUNClRoZSBgeGd4X3N0YXRfY2lgIGlzIHZlcnkgc2ltaWxhciB0byBmdW5jdGlvbmFsaXR5IGluIHRoZSBgc3RhdF9zdW1tYXJ5YCBnZW9tDQooaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N0YXRfc3VtbWFyeS5odG1sKSBidXQgaXQgZXh0ZW5kcyANCnRoaXMgdG8gYWxsb3cgdGhlIHVzZXIgdG8gc3BlY2lmeSB0aGUgY29uZmlkZW5jZSByZWdpb24gd2lkdGggYW5kIHRoZSBkaXN0cmlidXRpb24NCm9mIHRoZSBvdXRjb21lIGUuZy4gYG5vcm1hbGAsIGBsb2dub3JtYWxgLCBgYmlub21pYWxgLg0KDQpJbiB0aGUgY29kZSBiZWxvdyB3ZSdyZSBhbHNvIHVzaW5nIHRoZSBgc25hcGAgZnVuY3Rpb24gZnJvbSB0aGUgYG1ldHJ1bXJnYA0KcGFja2FnZSB3aGljaCBzbmFwcyBvYnNlcnZlZCBzYW1wbGUgdGltZXMgdG8gYSBub21pbmFsIGdyaWQuIFRoaXMgYWxsb3dzIHVzIA0KdG8gc3VtbWFyaXNlIG9ic2VydmVkIHZhbHVlcyBhdCB0aGVpciBub21pbmFsIHRpbWVzLg0KDQpgYGB7ciB4Z3hyX3N0YXRfY2l9DQp0aW1lcyA8LSBjKDAsIDAuMjUsIDAuNSwgMSwgMS41LCAyLCAzLjUsIDUsIDcsIDksIDEyLCAyNCkNCm15VGhlb3BoIDwtIFRoZW9waCAlPiUNCiAgbXV0YXRlKE5UUEQgPSBtZXRydW1yZzo6c25hcChUaW1lLCB0aW1lcykpDQp0YWJsZShteVRoZW9waCROVFBEKQ0KDQp0aGVvUGxvdDEgPC0gZ2dwbG90KGRhdGEgPSBteVRoZW9waCwgYWVzKHggPSBOVFBELHkgPSBjb25jKSkgKw0KICBnZW9tX2xpbmUobWFwcGluZyA9IGFlcyhncm91cCA9IFN1YmplY3QpLCBhbHBoYT0wLjIpICsgDQogIHhneF9zdGF0X2NpKGNvbG9yID0gInJlZCIsIGNvbmZfbGV2ZWwgPSAwLjksIGRpc3RyaWJ1dGlvbiA9ICJsb2dub3JtYWwiKSArIA0KICBsYWJzKHkgPSAiQ29uY2VudHJhdGlvbiAobmcvbWwpIikgDQp0aGVvUGxvdDENCg0KdGhlb1Bsb3QxICsNCiAgeGd4X3NjYWxlX3lfbG9nMTAoKQ0KYGBgDQoNCg0KIyMgc3Vydm1pbmVyIC0gUGxvdHRpbmcgc3Vydml2YWwgY3VydmVzDQpXZSdyZSBub3cgZ29pbmcgdG8gbG9vayBhdCBwYXJ0aWN1bGFyIGNhc2VzIHdoZXJlIHRoZSB2aXN1YWxpemF0aW9uIG9mIA0Kc3BlY2lhbGlzdCBkYXRhIHR5cGVzIHByb2R1Y2UgYGdncGxvdDJgIG9iamVjdHMuIFRoZSBmaXJzdCBpcyBmb3Igc3Vydml2YWwgZGF0YS4NCg0KVGhlIGBzdXJ2bWluZXJgIHBhY2thZ2UgKGJ5IEFsYm91a2FkZWwgS2Fzc2FtYmFyYSAtIGh0dHBzOi8vcnBrZ3MuZGF0YW5vdmlhLmNvbS9zdXJ2bWluZXIvaW5kZXguaHRtbCkNCnByb2R1Y2VzIG5pY2UgYGdncGxvdDJgIHZpc3VhbGl6YXRpb25zIG9mIHN1cnZpdmFsIGRhdGEgaW5jbHVkaW5nIHRoZSBudW1iZXIgb2YgDQpzdWJqZWN0cyBhdCByaXNrIHdpdGhpbiBzdHJhdGEuDQoNCmBgYHtyIHN1cnZpdmFsfQ0KbGlicmFyeShzdXJ2aXZhbCkNCmxpYnJhcnkoc3Vydm1pbmVyKQ0KZml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBzdGF0dXMpIH4gc2V4LCBkYXRhID0gbHVuZykNCmdnc3VydnBsb3QoZml0LCBkYXRhID0gbHVuZykNCg0KZ2dzdXJ2cGxvdCgNCiAgZml0LCANCiAgZGF0YSA9IGx1bmcsIA0KICBzaXplID0gMSwgICAgICAgICAgICAgICAgICMgY2hhbmdlIGxpbmUgc2l6ZQ0KICBwYWxldHRlID0gDQogICAgYygiI0U3QjgwMCIsICIjMkU5RkRGIiksIyBjdXN0b20gY29sb3IgcGFsZXR0ZXMNCiAgY29uZi5pbnQgPSBUUlVFLCAgICAgICAgICAjIEFkZCBjb25maWRlbmNlIGludGVydmFsDQogIHB2YWwgPSBUUlVFLCAgICAgICAgICAgICAgIyBBZGQgcC12YWx1ZQ0KICByaXNrLnRhYmxlID0gVFJVRSwgICAgICAgICMgQWRkIHJpc2sgdGFibGUNCiAgcmlzay50YWJsZS5jb2wgPSAic3RyYXRhIiwjIFJpc2sgdGFibGUgY29sb3IgYnkgZ3JvdXBzDQogIGxlZ2VuZC5sYWJzID0gDQogICAgYygiTWFsZSIsICJGZW1hbGUiKSwgICAgIyBDaGFuZ2UgbGVnZW5kIGxhYmVscw0KICByaXNrLnRhYmxlLmhlaWdodCA9IDAuMjUgIyBVc2VmdWwgdG8gY2hhbmdlIHdoZW4geW91IGhhdmUgbXVsdGlwbGUgZ3JvdXBzDQopDQpgYGANCg0KIyMgZ2dyYXBoIC0gVmlzdWFsaXppbmcgbmV0d29ya3MNCmBnZ3JhcGhgIChieSBUaG9tYXMgTGluIFBlZGVyc2VuIC0gaHR0cHM6Ly9nZ3JhcGguZGF0YS1pbWFnaW5pc3QuY29tLykgDQpwcm9kdWNlcyB2aXN1YWxpemF0aW9ucyBvZiBuZXR3b3Jrcy4gSGVyZSB3ZSB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcHMgYW5kIA0KZGVwZW5kZW5jaWVzIGFzc29jaWF0ZWQgd2l0aCB0aGUgYHRpZHl2ZXJzZWAgcGFja2FnZSwgYmFzZWQgb24gdGhlIGBtaW5pQ1JBTmANCnBhY2thZ2UgZnVuY3Rpb24gYG1ha2VEZXBHcmFwaGAgYW5kIHRoZSBgdGlkeWdyYXBoYCBhbmQgYGdncmFwaGAgcGFja2FnZXMuIE5vdGUNCnRoYXQgYGdncmFwaGAgdXNlcyBgcmVwZWxgIGZ1bmN0aW9uYWxpdHkgaW4gYGdlb21fbm9kZV90ZXh0YCB0byBwcmVzZW50DQp0aWR5IGxhYmVscyBpbiB0aGUgdmlzdWFsaXphdGlvbi4NCg0KYGBge3IgZ2dyYXBofQ0KbGlicmFyeShtaW5pQ1JBTikNCmxpYnJhcnkoZ2dyYXBoKQ0KbGlicmFyeSh0aWR5Z3JhcGgpDQoNCnRpZHl2ZXJzZURlcGVuZGVuY2llcyA8LSBtYWtlRGVwR3JhcGgocGtnID0gInRpZHl2ZXJzZSIpICU+JQ0KICBhc190YmxfZ3JhcGgoKQ0KDQpnZ3JhcGgodGlkeXZlcnNlRGVwZW5kZW5jaWVzLCBsYXlvdXQgPSAiZ3JhcGhvcHQiKSArDQogIGdlb21fbm9kZV9wb2ludCgpICsNCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGNvbG91ciA9IHR5cGUpKSArDQogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLA0KICAgICAgICAgICAgICAgICByZXBlbCA9IFRSVUUpDQpgYGANCg0KDQojIyBTYXZlIGFsbCBnZ3Bsb3QyIG9iamVjdHMgdG8gd29ya2luZyBkaXJlY3RvcnkNCkZpbmFsbHksIGhlcmUncyBvbmUgSSBwcm9kdWNlZCAoYnkgTWlrZSBLIFNtaXRoISkuIFRoaXMgc25pcHBldCBvZiBjb2RlDQpwaWNrcyBvdXQgYWxsIG9iamVjdHMgaW4gdGhlIGVudmlyb25tZW50IHRoYXQgaGF2ZSB0eXBlIGBnZ2AgaW4gdGhlaXIgb2JqZWN0IA0KY2xhc3MgaS5lLiBsaWtlbHkgYGdncGxvdDJgIG9iamVjdHMgb3Igb2JqZWN0cyB0aGF0IGFyZSBhc3NvY2lhdGVkIHdpdGggYGdncGxvdDJgDQpvYmplY3QgdHlwZXMuIEl0IHRoZW4gd3JpdGVzIGVhY2ggb2JqZWN0IGluZGl2aWR1YWxseSB0byBhbiBleHRlcm5hbGlzZWQgYC5SZGF0YWANCm9iamVjdC4gVGhpcyBhbGxvd3MgeW91IHRvIHJlYWQgZWFjaCBvYmplY3QgaW4gaW5kaXZpZHVhbGx5IGFuZCBlZGl0IG9yIGFkZCB0byANCml0IGFzIGRpc2N1c3NlZCBhYm92ZS4gU28gcmF0aGVyIHRoYW4gcnVubmluZyBtYW55IGxpbmVzIG9mIGNvZGUgdG8gcmVwcm9kdWNlIHRoZQ0KZ3JhcGhpYywgb25seSB0byBjaGFuZ2Ugb25lIGxpbmUsIHlvdSBjYW4gcmVhZCBpbiB0aGUgb2JqZWN0LCBhbHRlciBvciBhZGQgdG8gaXQsDQp0aGVuIHJlbmRlciBhcyBgLnBuZ2AgZmlsZS4NCg0KYGBge3Igc2F2ZV9nZ3Bsb3QyX29iamVjdHMsIGV2YWw9RkFMU0V9DQpwbG90T2JqZWN0cyA8LSBGaWx0ZXIoIGZ1bmN0aW9uKHgpICdnZycgJWluJSBjbGFzcyggZ2V0KHgpICksIGxzKCkgKQ0KDQojIyBTYXZlIHBsb3Qgb2JqZWN0cyBpbmRpdmlkdWFsbHkNCnNhcHBseShwbG90T2JqZWN0cywgZnVuY3Rpb24oeClzYXZlUkRTKG9iamVjdCA9IGdldCh4KSwgZmlsZSA9IHBhc3RlMCh4LCIuUmRhdGEiLHNlcD0iIikpKQ0KDQojIyByZWxvYWQgaW5kaXZpZHVhbGx5DQojIHBsb3QxIDwtIHJlYWRSRFMoInBsb3QxLlJkYXRhIikNCg0KIyBTYXZlIGFsbCBwbG90cyBhcyBvbmUgb2JqZWN0DQojIHNhdmUobGlzdCA9IHBsb3RPYmplY3RzLCBmaWxlID0gInBsb3RzLlJkYXRhIikNCg0KIyMgdGVzdCByZWxvYWRpbmcNCiMgcmVtb3ZlKGxpc3QgPSBscygpKQ0KIyBsb2FkKCJwbG90cy5SZGF0YSIpDQpgYGANCg0KIyBTZXNzaW9uIEluZm8NCmBgYHtyIHNlc3Npb25JbmZvfQ0Kc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCg==