Introduction
In their book R for Data Science, Wickham and Grolemund introduce the various stages of ‘data wrangling’ that go into analysis. A graphical portrayal of those stages, taken from their book, is below.
Amongst those stages is visualisation. Visualisation is used for communication – to communicate important features of the data such as their distribution or the relationship between variables. Often we think of that communication as being to an audience – the readers of an academic paper or a policy report, for example. But equally important is communication to ourselves as analysts, as we use visual tools (as well as other methods such as descriptive statistics) to help us to explore and to understand the data.
We already have learned that there is often more than one way of doing things in R and that is true of producing graphics too. R comes with the core graphics package. Open the index for that package by running the snippet of code below and have a look at some of the plots available:
help(package = "graphics")
Now modify (add to) the following block of code to produce a box plot of the variable \(y\). See if you can get the box plot to span horizontally instead of vertically across the screen. you might find the help file, help(boxplot), useful.
y <- c(1:10, 12, 15, 20)
# Replace this comment with your own code
ggplot2
A different approach – initially harder to use but good for producing publishable graphics - is to the use the ggplot2 package:
# If package is not installed then install it
if(!"ggplot2" %in% installed.packages()) install.packages("ggplot2")
# Load / require the package
require(ggplot2)
The most comprehensive book for learning ggplot2 is (again) Hadley Wickham’s. However, a number of shorter tutorials can be found online, including this one.
Another notes that there are basically three steps to producing a plot using ggplot2, that last of which is optional. These are:
- specify the aesthetic, which is what you want to graph
- choose one of more geoms, which is how you want to graph it
- add some options, such as labelling or the theme (style), and so forth.
So, for a box plot of \(y\), \(y\) is the aesthetic, boxplot is the geom. The only complication is that ggplot2 will want \(y\) to be part of a data frame:
mydata <- data.frame(y = y)
ggplot(mydata, aes(x = "", y = y)) +
geom_boxplot()
Actually, if you look again at the code, you will find there are two aesthetics, \(x\) and \(y\). This is because boxplots are often used to compare groups (categories) of data and \(x\) is the grouping variables. We can demonstrate this will some ‘in-built’ data that comes with ggplot2.
data(mpg)
ggplot(mpg, aes(x = class, y = hwy)) +
geom_boxplot() +
ylab("Miles per gallon") +
coord_flip() +
theme_minimal()
Note that in the example above, some options have been added: specifically, changing the axis title on one axis, flipping the coordinates (so the boxplots spread horizontally not vertically) and using a more minimal theme. A useful cheatsheet for ggplot2 is available. Use it to modify the following code chunk to produce a scatterplot with hwy as the \(Y\) variable and cty as the \(X\) variable, and add a regression line of best fit to the plot. See if you can get the regression line to be plotted without a confidence interval around it.
# Replace this comment with your own code
A useful feature of ggplot2 is its ability to facet. Say, for example, we wanted a dotplot showing the distribution of the fuel efficiency (hwy) statistics for each class of vehicle:
ggplot(data = mpg, aes(hwy)) +
geom_dotplot() +
facet_wrap(~ class)
If we wanted, we could also colour each dot by the year of manufacture. Trying running the following code block first as a whole and then without the bottom line - what changes?
ggplot(data = mpg, aes(hwy)) +
geom_dotplot(aes(fill = factor(year))) +
facet_wrap(~ class) +
scale_fill_manual(name = "year", values = c("white", "black"))
The function ggsave can be used to save the last plot, as in the example below. Take a look at the help menu (type ?ggsave in the Console) to learn how to change the file format, size of the graphic, resolution, and other options.
ggsave("myplot.pdf")
Spatial visualisation
From a geographical perspective, what often is of interest is geographical (spatial) variation - i.e. ‘what is where?’. This might focus on geographical clustering (similar things being found in close proximity to each other) and/or geographical heterogeneity (different things being found in different sub-regions). The principle tool for visualising these spatial patterns and variations is, of course, a map.
Introducing tmap
One way to draw maps in R is to use the tmap package where the t is short for thematic - i.e. it is a package for producing thematic maps.
# If package is not installed then install it
if(!"tmap" %in% installed.packages()) install.packages("tmap")
The following code chunk is from the vignette tmap: get started! and maps the Human Poverty Index (HPI) for countries around the World:
require(tmap)
data("World")
tm_shape(World) +
tm_polygons("HPI")
Looking at the code chunk, the first line of code (data(“World”)) simply loads a data set that comes bundled with the package (the World data). The data set is of class,
class(World)
The “data.frame” part of it is obvious enough (it includes a table of data) but what of the sf? It stands for simple feature but you might like to think of it as spatial feature - World is a mappable object, which is why we can use it to produce the choropleth map, above.
More specifically, we need two things to produce the choropleth map - the geometry of the countries (i.e. a digital representation of their boundaries) and something about the attributes of those countries (something measured about them). The attributes contained in the World map are,
require(sf)
st_drop_geometry(World)
In the following code chunk, enter the code required to produce a map of the estimated population of each country (using the variable pop_est).
# Replace this comment with your own code
Look again at the map you have just produced of the population estimates. You will probably find it is dominated by the largest countries, which we can identify in the data:
require(tidyverse)
World %>%
st_drop_geometry() %>%
dplyr::select(name, pop_est) %>%
arrange(desc(pop_est)) %>%
slice(1:5)
Looking at the distribution of the data, they have a very strong positive skew, which means that many of the map classes have few or no countries contained within them under an equal interval classification.
require(classInt)
brks <- classIntervals(World$pop_est, n = 7, style = "equal")$brks
World %>%
ggplot(aes(pop_est)) +
geom_histogram() +
geom_rug() +
geom_vline(xintercept = brks, linetype = "dotted")
We do better using a ‘natural breaks’ classification that tries to identify break points that reflect the distribution of the data.
brks <- classIntervals(World$pop_est, n = 7, style = "jenks")$brks
World %>%
ggplot(aes(pop_est)) +
geom_histogram() +
geom_rug() +
geom_vline(xintercept = brks, linetype = "dotted")
Applying those to the map is straightforward,
tm_shape(World) +
tm_polygons("pop_est", style = "jenks", n = 7)
Use the following code chunk to produce a map wherein the population estimates are grouped into quartiles.
# Replace this comment with your own code
Adding annotation
Once we have decided upon the basic design of the map, there are various ways to add to or modify it. For example,
data(metro)
tm_shape(World) +
tm_polygons("pop_est", style = "jenks", n = 7, palette = "Blues",
border.alpha = 0, title = "population estimate") +
tm_compass(type = "radar", position = c("left", "top"), size = 4) +
tm_scale_bar(position = c("right", "bottom")) +
tm_text("iso_a3", size = "AREA") +
tm_shape(metro) +
tm_symbols(col = "red", size = "pop2020", scale = .5, alpha = 1)
Or,
tmap_mode("plot")
tm_shape(World) +
tm_polygons("HPI") +
tm_layout(bg.color = "skyblue", inner.margins = c(0, .02, .02, .02))
The map can also be changed to an ‘interactive’ mode, as in the following example …
tmap_mode("view")
tm_basemap("Stamen.Watercolor") +
tm_shape(metro) +
tm_symbols(col = "red", size = "pop2020", scale = 2, alpha = 1)
… and in this,
tmap_mode("view")
World %>%
filter(grepl("America", continent)) %>%
tm_shape(.) +
tm_polygons("HPI")
Use the code block below to produce a map of the HPI scores for Africa to which you should add a scale bar and a north arrow. The first part of the code is completed for you.
World %>%
filter(continent == "Africa") %>%
# Replace this comment with your own code
Faceting in tmap
There are various ways to create facets in tmap. One is to combine the variables you wish to map using the c(…) [combine] function:
tmap_mode("plot")
World %>%
filter(continent == "Asia") %>%
tm_shape(.) +
tm_polygons(c("well_being", "inequality"), style = "jenks") +
tm_facets(ncol = 2) +
tm_legend(position = c("left", "bottom"))
… although, mapping it this way implies that well-being rises with inequality. Is that true?
World %>%
filter(continent == "Asia") %>%
ggplot(aes(x = inequality, y = well_being)) +
geom_point() +
geom_smooth(method = "lm")
… no, it isn’t! It would therefore be better to create a new equality variable and map that with well-being:
tmap_mode("plot")
World %>%
filter(continent == "Asia") %>%
mutate(equality = 1 - inequality) %>%
tm_shape(.) +
tm_polygons(c("well_being", "equality"), style = "jenks") +
tm_facets(ncol = 2) +
tm_legend(position = c("left", "bottom"))
Or, in ‘interactive’ mode:
tmap_mode("view")
World %>%
filter(continent == "Asia") %>%
mutate(equality = 1 - inequality) %>%
tm_shape(.) +
tm_polygons(c("well_being", "equality"), style = "jenks") +
tm_facets(sync = TRUE, ncol = 2) +
tm_legend(position = c("left", "bottom"))
(try setting sync to FALSE in the code above and see what happens)
In the examples above, the geographical region is held constant whilst the attribute variable changes. This can be reversed, as in the following example, which is modified from tmap: get started!
data(NLD_muni)
tmap_mode("plot")
NLD_muni %>%
mutate(perc_men = pop_men / population * 100) %>%
tm_shape(.) +
tm_polygons("perc_men", palette = "RdYlBu") +
tm_facets(by = "province")
Maps can also be arranged side-by-side using tmap’s arrange function:
NLD_muni %>%
tm_shape(.) +
tm_polygons("population", convert2density = TRUE) -> tm1
NLD_muni %>%
tm_shape(.) +
tm_bubbles(size = "population") -> tm2
tmap_arrange(tm1, tm2)
Modify the following code block so that the HPI scores for North America are mapped alongside the HPI scores for South America. The beginning of the code is included for you.
tmap_mode("plot")
World %>%
filter(grepl("America", continent)) %>%
# Replace this comment with your own code
Exporting the plots
Plots can be saved using the tmap_save(…) function - see ?tmap_save for a list of the possible file formats. For example,
World %>%
filter(grepl("America", continent)) %>%
tm_shape(.) +
tm_polygons("HPI") -> mymap
tmap_mode("plot")
tmap_save(mymap, "mymap.pdf", height = 29.7, width = 21.0, units = "cm")
tmap_mode("view")
tmap_save(mymap, filename = "mymap.html")
browseURL("mymap.pdf")
browseURL("mymap.html")
To Do
To end this session, your task is to produce a map of Noise Pollution from roads in London, using data from the London Datastore
The first task if to download the data from the website where they are stored in a zipped format and then unzip them:
download.file("https://data.london.gov.uk/download/noise-pollution-in-london/1154a491-c68f-4451-9111-09bac975be99/Road_LAeq_16h_London.zip", "noise.zip")
unzip("noise.zip")
Next, we need to read in the data:
require(sf)
road_noise <- st_read("Road_LAeq_16h_London.shp")
Taking a quick look at the data, we can see it is of class
class(road_noise)
and contains the following attribute variable
names(st_drop_geometry(road_noise))
Because it is also a large data set,
nrow(road_noise)
we will just map the locations of the noisiest roads,
road_noise %>%
filter(NoiseClass == ">=75.0") %>%
filter(st_is_valid(.)) -> noise_subset
(The additional filter using st_is_valid is to avoid some geometric errors in the data)
Now produce a map of the noisiest roads to which you could add a North arrow and a scale bar, and save it in a suitable file format. The code block may take a little longer to run than before so please be patient.
noise_subset %>%
# Replace this comment with your own code
If you still have time…
If you have finished the exercises above and have some time left over, work through one of these (you can choose which):
- (recommended, if you have a copy of the book)
Chapter 3 of An Introduction to R for Spatial Analysis & Mapping by Chris Brunsdon and Lex Comber. The code (but not text) for the chapter is here
Wickham H & Grolemund G, 2017, R for Data Science, Chapter 28: Graphics for Communication. https://r4ds.had.co.nz/graphics-for-communication.html
which is about ways to better communicate graphics.
browseURL("https://r4ds.had.co.nz/graphics-for-communication.html")
Leaflet for R. https://rstudio.github.io/leaflet/
browseURL("https://rstudio.github.io/leaflet/")
which offers another way to generate interactive maps.
LS0tCnRpdGxlOiAiTWFwcGluZyBhbmQgTW9kZWxsaW5nIEdlb2dyYXBoaWMgRGF0YSBpbiBSIgpzdWJ0aXRsZTogIlNlc3Npb24gMjogVmlzdWFsaXNhdGlvbiBpbiBSIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkluIHRoZWlyIGJvb2sgW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pLCBXaWNraGFtIGFuZCBHcm9sZW11bmQgaW50cm9kdWNlIHRoZSB2YXJpb3VzIHN0YWdlcyBvZiAnZGF0YSB3cmFuZ2xpbmcnIHRoYXQgZ28gaW50byBhbmFseXNpcy4gQSBncmFwaGljYWwgcG9ydHJheWFsIG9mIHRob3NlIHN0YWdlcywgdGFrZW4gZnJvbSB0aGVpciBib29rLCBpcyBiZWxvdy4KCiFbc291cmNlOiBXaWNraGFtICYgR3JvbGVtdW5kXShodHRwczovL2QzM3d1YnJma2kwbDY4LmNsb3VkZnJvbnQubmV0Lzc5NWMwMzliYTI1MjA0NTVkODMzYjQwMzRiZWZjOGNmMzYwYTcwYmEvNTU4YTUvZGlhZ3JhbXMvZGF0YS1zY2llbmNlLWV4cGxvcmUucG5nKQoKCkFtb25nc3QgdGhvc2Ugc3RhZ2VzIGlzICoqdmlzdWFsaXNhdGlvbioqLiBWaXN1YWxpc2F0aW9uIGlzIHVzZWQgZm9yIGNvbW11bmljYXRpb24gLS0gdG8gY29tbXVuaWNhdGUgaW1wb3J0YW50IGZlYXR1cmVzIG9mIHRoZSBkYXRhIHN1Y2ggYXMgdGhlaXIgZGlzdHJpYnV0aW9uIG9yIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB2YXJpYWJsZXMuIE9mdGVuIHdlIHRoaW5rIG9mIHRoYXQgY29tbXVuaWNhdGlvbiBhcyBiZWluZyB0byBhbiBhdWRpZW5jZSAtLSB0aGUgcmVhZGVycyBvZiBhbiBhY2FkZW1pYyBwYXBlciBvciBhIHBvbGljeSByZXBvcnQsIGZvciBleGFtcGxlLiBCdXQgZXF1YWxseSBpbXBvcnRhbnQgaXMgY29tbXVuaWNhdGlvbiB0byBvdXJzZWx2ZXMgYXMgYW5hbHlzdHMsIGFzIHdlIHVzZSB2aXN1YWwgdG9vbHMgKGFzIHdlbGwgYXMgb3RoZXIgbWV0aG9kcyBzdWNoIGFzIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MpIHRvIGhlbHAgdXMgdG8gZXhwbG9yZSBhbmQgdG8gdW5kZXJzdGFuZCB0aGUgZGF0YS4KCldlIGFscmVhZHkgaGF2ZSBsZWFybmVkIHRoYXQgdGhlcmUgaXMgb2Z0ZW4gbW9yZSB0aGFuIG9uZSB3YXkgb2YgZG9pbmcgdGhpbmdzIGluIFIgYW5kIHRoYXQgaXMgdHJ1ZSBvZiBwcm9kdWNpbmcgZ3JhcGhpY3MgdG9vLiBSIGNvbWVzIHdpdGggdGhlIGNvcmUgZ3JhcGhpY3MgcGFja2FnZS4gT3BlbiB0aGUgaW5kZXggZm9yIHRoYXQgcGFja2FnZSBieSBydW5uaW5nIHRoZSBzbmlwcGV0IG9mIGNvZGUgYmVsb3cgYW5kIGhhdmUgYSBsb29rIGF0IHNvbWUgb2YgdGhlIHBsb3RzIGF2YWlsYWJsZToKCmBgYHtyfQpoZWxwKHBhY2thZ2UgPSAiZ3JhcGhpY3MiKQpgYGAKCk5vdyBtb2RpZnkgKGFkZCB0bykgdGhlIGZvbGxvd2luZyBibG9jayBvZiBjb2RlIHRvIHByb2R1Y2UgYSBib3ggcGxvdCBvZiB0aGUgdmFyaWFibGUgJHkkLiBTZWUgaWYgeW91IGNhbiBnZXQgdGhlIGJveCBwbG90IHRvIHNwYW4gaG9yaXpvbnRhbGx5IGluc3RlYWQgb2YgdmVydGljYWxseSBhY3Jvc3MgdGhlIHNjcmVlbi4geW91IG1pZ2h0IGZpbmQgdGhlIGhlbHAgZmlsZSwgaGVscChib3hwbG90KSwgdXNlZnVsLgoKYGBge3J9CnkgPC0gYygxOjEwLCAxMiwgMTUsIDIwKQojIFJlcGxhY2UgdGhpcyBjb21tZW50IHdpdGggeW91ciBvd24gY29kZQpgYGAKCiMjIGdncGxvdDIKCkEgZGlmZmVyZW50IGFwcHJvYWNoIC0tIGluaXRpYWxseSBoYXJkZXIgdG8gdXNlIGJ1dCBnb29kIGZvciBwcm9kdWNpbmcgcHVibGlzaGFibGUgZ3JhcGhpY3MgLSBpcyB0byB0aGUgdXNlIHRoZSBnZ3Bsb3QyIHBhY2thZ2U6CgpgYGB7cn0KIyBJZiBwYWNrYWdlIGlzIG5vdCBpbnN0YWxsZWQgdGhlbiBpbnN0YWxsIGl0CmlmKCEiZ2dwbG90MiIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpCiMgTG9hZCAvIHJlcXVpcmUgdGhlIHBhY2thZ2UKcmVxdWlyZShnZ3Bsb3QyKQpgYGAKClRoZSBtb3N0IGNvbXByZWhlbnNpdmUgYm9vayBmb3IgbGVhcm5pbmcgZ2dwbG90MiBpcyAoYWdhaW4pIFtIYWRsZXkgV2lja2hhbSdzXShodHRwczovL3d3dy5hbWF6b24uY28udWsvZ2dwbG90Mi1FbGVnYW50LUdyYXBoaWNzLURhdGEtQW5hbHlzaXMvZHAvMDM4Nzk4MTQwMykuIEhvd2V2ZXIsIGEgbnVtYmVyIG9mIHNob3J0ZXIgdHV0b3JpYWxzIGNhbiBiZSBmb3VuZCBvbmxpbmUsIGluY2x1ZGluZyBbdGhpcyBvbmVdKGh0dHA6Ly9yLXN0YXRpc3RpY3MuY28vQ29tcGxldGUtR2dwbG90Mi1UdXRvcmlhbC1QYXJ0MS1XaXRoLVItQ29kZS5odG1sKS4KCltBbm90aGVyXShodHRwczovL2Jvb2tkb3duLm9yZy9hZ3JvZ2Fua2F5bG9yL3F1aWNrLWludHJvLXRvLWdncGxvdDIvcXVpY2staW50cm8tdG8tZ2dwbG90Mi5odG1sKSBub3RlcyB0aGF0IHRoZXJlIGFyZSBiYXNpY2FsbHkgdGhyZWUgc3RlcHMgdG8gcHJvZHVjaW5nIGEgcGxvdCB1c2luZyBnZ3Bsb3QyLCB0aGF0IGxhc3Qgb2Ygd2hpY2ggaXMgb3B0aW9uYWwuIFRoZXNlIGFyZToKCi0gc3BlY2lmeSB0aGUgKiphZXN0aGV0aWMqKiwgd2hpY2ggaXMgKndoYXQqIHlvdSB3YW50IHRvIGdyYXBoCi0gY2hvb3NlIG9uZSBvZiBtb3JlICoqZ2VvbXMqKiwgd2hpY2ggaXMgKmhvdyogeW91IHdhbnQgdG8gZ3JhcGggaXQKLSBhZGQgc29tZSBvcHRpb25zLCBzdWNoIGFzIGxhYmVsbGluZyBvciB0aGUgdGhlbWUgKHN0eWxlKSwgYW5kIHNvIGZvcnRoLgoKU28sIGZvciBhIGJveCBwbG90IG9mICR5JCwgJHkkIGlzIHRoZSBhZXN0aGV0aWMsIGJveHBsb3QgaXMgdGhlIGdlb20uIFRoZSBvbmx5IGNvbXBsaWNhdGlvbiBpcyB0aGF0IGdncGxvdDIgd2lsbCB3YW50ICR5JCB0byBiZSBwYXJ0IG9mIGEgZGF0YSBmcmFtZToKCmBgYHtyfQpteWRhdGEgPC0gZGF0YS5mcmFtZSh5ID0geSkKZ2dwbG90KG15ZGF0YSwgYWVzKHggPSAiIiwgeSA9IHkpKSArCiAgZ2VvbV9ib3hwbG90KCkKYGBgCgpBY3R1YWxseSwgaWYgeW91IGxvb2sgYWdhaW4gYXQgdGhlIGNvZGUsIHlvdSB3aWxsIGZpbmQgdGhlcmUgYXJlIHR3byBhZXN0aGV0aWNzLCAkeCQgYW5kICR5JC4gVGhpcyBpcyBiZWNhdXNlIGJveHBsb3RzIGFyZSBvZnRlbiB1c2VkIHRvIGNvbXBhcmUgZ3JvdXBzIChjYXRlZ29yaWVzKSBvZiBkYXRhIGFuZCAkeCQgaXMgdGhlIGdyb3VwaW5nIHZhcmlhYmxlcy4gV2UgY2FuIGRlbW9uc3RyYXRlIHRoaXMgd2lsbCBzb21lICdpbi1idWlsdCcgZGF0YSB0aGF0IGNvbWVzIHdpdGggZ2dwbG90Mi4KCmBgYHtyfQpkYXRhKG1wZykKZ2dwbG90KG1wZywgYWVzKHggPSBjbGFzcywgeSA9IGh3eSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgeWxhYigiTWlsZXMgcGVyIGdhbGxvbiIpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCk5vdGUgdGhhdCBpbiB0aGUgZXhhbXBsZSBhYm92ZSwgc29tZSBvcHRpb25zIGhhdmUgYmVlbiBhZGRlZDogc3BlY2lmaWNhbGx5LCBjaGFuZ2luZyB0aGUgYXhpcyB0aXRsZSBvbiBvbmUgYXhpcywgZmxpcHBpbmcgdGhlIGNvb3JkaW5hdGVzIChzbyB0aGUgYm94cGxvdHMgc3ByZWFkIGhvcml6b250YWxseSBub3QgdmVydGljYWxseSkgYW5kIHVzaW5nIGEgbW9yZSBtaW5pbWFsIHRoZW1lLiBBIHVzZWZ1bCBbY2hlYXRzaGVldF0oaHR0cHM6Ly9yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMy9nZ3Bsb3QyLWNoZWF0c2hlZXQucGRmKSBmb3IgZ2dwbG90MiBpcyBhdmFpbGFibGUuIFVzZSBpdCB0byBtb2RpZnkgdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHRvIHByb2R1Y2UgYSBzY2F0dGVycGxvdCB3aXRoIGh3eSBhcyB0aGUgJFkkIHZhcmlhYmxlIGFuZCBjdHkgYXMgdGhlICRYJCB2YXJpYWJsZSwgYW5kIGFkZCBhIHJlZ3Jlc3Npb24gbGluZSBvZiBiZXN0IGZpdCB0byB0aGUgcGxvdC4gU2VlIGlmIHlvdSBjYW4gZ2V0IHRoZSByZWdyZXNzaW9uIGxpbmUgdG8gYmUgcGxvdHRlZCAqd2l0aG91dCogYSBjb25maWRlbmNlIGludGVydmFsIGFyb3VuZCBpdC4KCmBgYHtyfQojIFJlcGxhY2UgdGhpcyBjb21tZW50IHdpdGggeW91ciBvd24gY29kZQpgYGAKCkEgdXNlZnVsIGZlYXR1cmUgb2YgZ2dwbG90MiBpcyBpdHMgYWJpbGl0eSB0byBmYWNldC4gU2F5LCBmb3IgZXhhbXBsZSwgd2Ugd2FudGVkIGEgZG90cGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGZ1ZWwgZWZmaWNpZW5jeSAoaHd5KSBzdGF0aXN0aWNzIGZvciBlYWNoIGNsYXNzIG9mIHZlaGljbGU6CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcsIGFlcyhod3kpKSArCiAgZ2VvbV9kb3RwbG90KCkgKwogIGZhY2V0X3dyYXAofiBjbGFzcykgCmBgYAoKSWYgd2Ugd2FudGVkLCB3ZSBjb3VsZCBhbHNvIGNvbG91ciBlYWNoIGRvdCBieSB0aGUgeWVhciBvZiBtYW51ZmFjdHVyZS4gVHJ5aW5nIHJ1bm5pbmcgdGhlIGZvbGxvd2luZyBjb2RlIGJsb2NrIGZpcnN0IGFzIGEgd2hvbGUgYW5kIHRoZW4gd2l0aG91dCB0aGUgYm90dG9tIGxpbmUgLSB3aGF0IGNoYW5nZXM/CgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBtcGcsIGFlcyhod3kpKSArCiAgZ2VvbV9kb3RwbG90KGFlcyhmaWxsID0gZmFjdG9yKHllYXIpKSkgKwogIGZhY2V0X3dyYXAofiBjbGFzcykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWUgPSAieWVhciIsIHZhbHVlcyA9IGMoIndoaXRlIiwgImJsYWNrIikpCmBgYAoKVGhlIGZ1bmN0aW9uIGdnc2F2ZSBjYW4gYmUgdXNlZCB0byBzYXZlIHRoZSBsYXN0IHBsb3QsIGFzIGluIHRoZSBleGFtcGxlIGJlbG93LiBUYWtlIGEgbG9vayBhdCB0aGUgaGVscCBtZW51ICh0eXBlID9nZ3NhdmUgaW4gdGhlIENvbnNvbGUpIHRvIGxlYXJuIGhvdyB0byBjaGFuZ2UgdGhlIGZpbGUgZm9ybWF0LCBzaXplIG9mIHRoZSBncmFwaGljLCByZXNvbHV0aW9uLCBhbmQgb3RoZXIgb3B0aW9ucy4KCmBgYHtyfQpnZ3NhdmUoIm15cGxvdC5wZGYiKQpgYGAKCiMjIFNwYXRpYWwgdmlzdWFsaXNhdGlvbgoKRnJvbSBhIGdlb2dyYXBoaWNhbCBwZXJzcGVjdGl2ZSwgd2hhdCBvZnRlbiBpcyBvZiBpbnRlcmVzdCBpcyBnZW9ncmFwaGljYWwgKHNwYXRpYWwpIHZhcmlhdGlvbiAtIGkuZS4gJ3doYXQgaXMgd2hlcmU/Jy4gVGhpcyBtaWdodCBmb2N1cyBvbiAqZ2VvZ3JhcGhpY2FsIGNsdXN0ZXJpbmcqIChzaW1pbGFyIHRoaW5ncyBiZWluZyBmb3VuZCBpbiBjbG9zZSBwcm94aW1pdHkgdG8gZWFjaCBvdGhlcikgYW5kL29yICpnZW9ncmFwaGljYWwgaGV0ZXJvZ2VuZWl0eSogKGRpZmZlcmVudCB0aGluZ3MgYmVpbmcgZm91bmQgaW4gZGlmZmVyZW50IHN1Yi1yZWdpb25zKS4gVGhlIHByaW5jaXBsZSB0b29sIGZvciB2aXN1YWxpc2luZyB0aGVzZSBzcGF0aWFsIHBhdHRlcm5zIGFuZCB2YXJpYXRpb25zIGlzLCBvZiBjb3Vyc2UsIGEgbWFwLgoKIyMjIEludHJvZHVjaW5nIHRtYXAKCk9uZSB3YXkgdG8gZHJhdyBtYXBzIGluIFIgaXMgdG8gdXNlIHRoZSBbdG1hcCBwYWNrYWdlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdG1hcC9pbmRleC5odG1sKSB3aGVyZSB0aGUgdCBpcyBzaG9ydCBmb3IgdGhlbWF0aWMgLSBpLmUuIGl0IGlzIGEgcGFja2FnZSBmb3IgcHJvZHVjaW5nIHRoZW1hdGljIG1hcHMuCgpgYGB7cn0KIyBJZiBwYWNrYWdlIGlzIG5vdCBpbnN0YWxsZWQgdGhlbiBpbnN0YWxsIGl0CmlmKCEidG1hcCIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSkgaW5zdGFsbC5wYWNrYWdlcygidG1hcCIpCmBgYAoKVGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIGlzIGZyb20gdGhlIHZpZ25ldHRlIFt0bWFwOiBnZXQgc3RhcnRlZCFdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90bWFwL3ZpZ25ldHRlcy90bWFwLWdldHN0YXJ0ZWQuaHRtbCkgYW5kIG1hcHMgdGhlIEh1bWFuIFBvdmVydHkgSW5kZXggKEhQSSkgZm9yIGNvdW50cmllcyBhcm91bmQgdGhlIFdvcmxkOgoKYGBge3J9CnJlcXVpcmUodG1hcCkKZGF0YSgiV29ybGQiKQp0bV9zaGFwZShXb3JsZCkgKwogICAgdG1fcG9seWdvbnMoIkhQSSIpCmBgYAoKTG9va2luZyBhdCB0aGUgY29kZSBjaHVuaywgdGhlIGZpcnN0IGxpbmUgb2YgY29kZSAoZGF0YSgiV29ybGQiKSkgc2ltcGx5IGxvYWRzIGEgZGF0YSBzZXQgdGhhdCBjb21lcyBidW5kbGVkIHdpdGggdGhlIHBhY2thZ2UgKHRoZSBXb3JsZCBkYXRhKS4gVGhlIGRhdGEgc2V0IGlzIG9mIGNsYXNzLAoKYGBge3J9CmNsYXNzKFdvcmxkKQpgYGAKClRoZSAiZGF0YS5mcmFtZSIgcGFydCBvZiBpdCBpcyBvYnZpb3VzIGVub3VnaCAoaXQgaW5jbHVkZXMgYSB0YWJsZSBvZiBkYXRhKSBidXQgd2hhdCBvZiB0aGUgc2Y/IEl0IHN0YW5kcyBmb3Igc2ltcGxlIGZlYXR1cmUgYnV0IHlvdSBtaWdodCBsaWtlIHRvIHRoaW5rIG9mIGl0IGFzIHNwYXRpYWwgZmVhdHVyZSAtIFdvcmxkIGlzIGEgbWFwcGFibGUgb2JqZWN0LCB3aGljaCBpcyB3aHkgd2UgY2FuIHVzZSBpdCB0byBwcm9kdWNlIHRoZSBjaG9yb3BsZXRoIG1hcCwgYWJvdmUuCgpNb3JlIHNwZWNpZmljYWxseSwgd2UgbmVlZCB0d28gdGhpbmdzIHRvIHByb2R1Y2UgdGhlIGNob3JvcGxldGggbWFwIC0gdGhlIGdlb21ldHJ5IG9mIHRoZSBjb3VudHJpZXMgKGkuZS4gYSBkaWdpdGFsIHJlcHJlc2VudGF0aW9uIG9mIHRoZWlyIGJvdW5kYXJpZXMpIGFuZCBzb21ldGhpbmcgYWJvdXQgdGhlICphdHRyaWJ1dGVzKiBvZiB0aG9zZSBjb3VudHJpZXMgKHNvbWV0aGluZyBtZWFzdXJlZCBhYm91dCB0aGVtKS4gVGhlIGF0dHJpYnV0ZXMgY29udGFpbmVkIGluIHRoZSBXb3JsZCBtYXAgYXJlLCAKCmBgYHtyfQpyZXF1aXJlKHNmKQpzdF9kcm9wX2dlb21ldHJ5KFdvcmxkKQpgYGAKCkluIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuaywgZW50ZXIgdGhlIGNvZGUgcmVxdWlyZWQgdG8gcHJvZHVjZSBhIG1hcCBvZiB0aGUgZXN0aW1hdGVkIHBvcHVsYXRpb24gb2YgZWFjaCBjb3VudHJ5ICh1c2luZyB0aGUgdmFyaWFibGUgcG9wX2VzdCkuCgpgYGB7cn0KIyBSZXBsYWNlIHRoaXMgY29tbWVudCB3aXRoIHlvdXIgb3duIGNvZGUKYGBgCgpMb29rIGFnYWluIGF0IHRoZSBtYXAgeW91IGhhdmUganVzdCBwcm9kdWNlZCBvZiB0aGUgcG9wdWxhdGlvbiBlc3RpbWF0ZXMuIFlvdSB3aWxsIHByb2JhYmx5IGZpbmQgaXQgaXMgZG9taW5hdGVkIGJ5IHRoZSBsYXJnZXN0IGNvdW50cmllcywgd2hpY2ggd2UgY2FuIGlkZW50aWZ5IGluIHRoZSBkYXRhOgoKYGBge3J9CnJlcXVpcmUodGlkeXZlcnNlKQpXb3JsZCAlPiUKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lCiAgZHBseXI6OnNlbGVjdChuYW1lLCBwb3BfZXN0KSAlPiUKICBhcnJhbmdlKGRlc2MocG9wX2VzdCkpICU+JQogIHNsaWNlKDE6NSkKYGBgCgpMb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGRhdGEsIHRoZXkgaGF2ZSBhIHZlcnkgc3Ryb25nIHBvc2l0aXZlIHNrZXcsIHdoaWNoIG1lYW5zIHRoYXQgbWFueSBvZiB0aGUgbWFwIGNsYXNzZXMgaGF2ZSBmZXcgb3Igbm8gY291bnRyaWVzIGNvbnRhaW5lZCB3aXRoaW4gdGhlbSB1bmRlciBhbiBlcXVhbCBpbnRlcnZhbCBjbGFzc2lmaWNhdGlvbi4KCmBgYHtyfQpyZXF1aXJlKGNsYXNzSW50KQpicmtzIDwtIGNsYXNzSW50ZXJ2YWxzKFdvcmxkJHBvcF9lc3QsIG4gPSA3LCBzdHlsZSA9ICJlcXVhbCIpJGJya3MKV29ybGQgJT4lCiAgZ2dwbG90KGFlcyhwb3BfZXN0KSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGdlb21fcnVnKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGJya3MsIGxpbmV0eXBlID0gImRvdHRlZCIpCmBgYAoKV2UgZG8gYmV0dGVyIHVzaW5nIGEgJ25hdHVyYWwgYnJlYWtzJyBjbGFzc2lmaWNhdGlvbiB0aGF0IHRyaWVzIHRvIGlkZW50aWZ5IGJyZWFrIHBvaW50cyB0aGF0IHJlZmxlY3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGF0YS4KCmBgYHtyfQpicmtzIDwtIGNsYXNzSW50ZXJ2YWxzKFdvcmxkJHBvcF9lc3QsIG4gPSA3LCBzdHlsZSA9ICJqZW5rcyIpJGJya3MKV29ybGQgJT4lCiAgZ2dwbG90KGFlcyhwb3BfZXN0KSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGdlb21fcnVnKCkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGJya3MsIGxpbmV0eXBlID0gImRvdHRlZCIpCmBgYAoKQXBwbHlpbmcgdGhvc2UgdG8gdGhlIG1hcCBpcyBzdHJhaWdodGZvcndhcmQsCgpgYGB7cn0KdG1fc2hhcGUoV29ybGQpICsKICAgIHRtX3BvbHlnb25zKCJwb3BfZXN0Iiwgc3R5bGUgPSAiamVua3MiLCBuID0gNykKYGBgCgpVc2UgdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIHRvIHByb2R1Y2UgYSBtYXAgd2hlcmVpbiB0aGUgcG9wdWxhdGlvbiBlc3RpbWF0ZXMgYXJlIGdyb3VwZWQgaW50byAqcXVhcnRpbGVzKi4KCmBgYHtyfQojIFJlcGxhY2UgdGhpcyBjb21tZW50IHdpdGggeW91ciBvd24gY29kZQpgYGAKCiMjIyBBZGRpbmcgYW5ub3RhdGlvbgoKT25jZSB3ZSBoYXZlIGRlY2lkZWQgdXBvbiB0aGUgYmFzaWMgZGVzaWduIG9mIHRoZSBtYXAsIHRoZXJlIGFyZSB2YXJpb3VzIHdheXMgdG8gYWRkIHRvIG9yIG1vZGlmeSBpdC4gRm9yIGV4YW1wbGUsCgpgYGB7cn0KZGF0YShtZXRybykKdG1fc2hhcGUoV29ybGQpICsKICAgIHRtX3BvbHlnb25zKCJwb3BfZXN0Iiwgc3R5bGUgPSAiamVua3MiLCBuID0gNywgcGFsZXR0ZSA9ICJCbHVlcyIsCiAgICAgICAgICAgICAgICBib3JkZXIuYWxwaGEgPSAwLCB0aXRsZSA9ICJwb3B1bGF0aW9uIGVzdGltYXRlIikgKwogICAgdG1fY29tcGFzcyh0eXBlID0gInJhZGFyIiwgcG9zaXRpb24gPSBjKCJsZWZ0IiwgInRvcCIpLCBzaXplID0gNCkgKwogICAgdG1fc2NhbGVfYmFyKHBvc2l0aW9uID0gYygicmlnaHQiLCAiYm90dG9tIikpICsKICAgIHRtX3RleHQoImlzb19hMyIsIHNpemUgPSAiQVJFQSIpICsKdG1fc2hhcGUobWV0cm8pICsKICB0bV9zeW1ib2xzKGNvbCA9ICJyZWQiLCBzaXplID0gInBvcDIwMjAiLCBzY2FsZSA9IC41LCBhbHBoYSA9IDEpCmBgYAoKT3IsCgpgYGB7cn0KdG1hcF9tb2RlKCJwbG90IikKdG1fc2hhcGUoV29ybGQpICsKICB0bV9wb2x5Z29ucygiSFBJIikgKwogIHRtX2xheW91dChiZy5jb2xvciA9ICJza3libHVlIiwgaW5uZXIubWFyZ2lucyA9IGMoMCwgLjAyLCAuMDIsIC4wMikpCmBgYAoKClRoZSBtYXAgY2FuIGFsc28gYmUgY2hhbmdlZCB0byBhbiAnaW50ZXJhY3RpdmUnIG1vZGUsIGFzIGluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZSAuLi4KCmBgYHtyfQp0bWFwX21vZGUoInZpZXciKQp0bV9iYXNlbWFwKCJTdGFtZW4uV2F0ZXJjb2xvciIpICsKdG1fc2hhcGUobWV0cm8pICsKICB0bV9zeW1ib2xzKGNvbCA9ICJyZWQiLCBzaXplID0gInBvcDIwMjAiLCBzY2FsZSA9IDIsIGFscGhhID0gMSkKYGBgCgouLi4gYW5kIGluIHRoaXMsCgpgYGB7cn0KdG1hcF9tb2RlKCJ2aWV3IikKV29ybGQgJT4lCiAgZmlsdGVyKGdyZXBsKCJBbWVyaWNhIiwgY29udGluZW50KSkgJT4lCiAgdG1fc2hhcGUoLikgKwogICAgdG1fcG9seWdvbnMoIkhQSSIpCmBgYAoKVXNlIHRoZSBjb2RlIGJsb2NrIGJlbG93IHRvIHByb2R1Y2UgYSBtYXAgb2YgdGhlIEhQSSBzY29yZXMgZm9yIEFmcmljYSB0byB3aGljaCB5b3Ugc2hvdWxkIGFkZCBhIHNjYWxlIGJhciBhbmQgYSBub3J0aCBhcnJvdy4gVGhlIGZpcnN0IHBhcnQgb2YgdGhlIGNvZGUgaXMgY29tcGxldGVkIGZvciB5b3UuCgpgYGB7cn0KV29ybGQgJT4lCiAgZmlsdGVyKGNvbnRpbmVudCA9PSAiQWZyaWNhIikgJT4lCiAgIyBSZXBsYWNlIHRoaXMgY29tbWVudCB3aXRoIHlvdXIgb3duIGNvZGUKYGBgCgojIyMgRmFjZXRpbmcgaW4gdG1hcAoKVGhlcmUgYXJlIHZhcmlvdXMgd2F5cyB0byBjcmVhdGUgZmFjZXRzIGluIHRtYXAuIE9uZSBpcyB0byBjb21iaW5lIHRoZSB2YXJpYWJsZXMgeW91IHdpc2ggdG8gbWFwIHVzaW5nIHRoZSBjKC4uLikgW2NvbWJpbmVdIGZ1bmN0aW9uOgoKYGBge3J9CnRtYXBfbW9kZSgicGxvdCIpCldvcmxkICU+JQogIGZpbHRlcihjb250aW5lbnQgPT0gIkFzaWEiKSAlPiUKICB0bV9zaGFwZSguKSArCiAgdG1fcG9seWdvbnMoYygid2VsbF9iZWluZyIsICJpbmVxdWFsaXR5IiksIHN0eWxlID0gImplbmtzIikgKwogIHRtX2ZhY2V0cyhuY29sID0gMikgKwogIHRtX2xlZ2VuZChwb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIikpCmBgYAoKLi4uIGFsdGhvdWdoLCBtYXBwaW5nIGl0IHRoaXMgd2F5IGltcGxpZXMgdGhhdCB3ZWxsLWJlaW5nIHJpc2VzIHdpdGggaW5lcXVhbGl0eS4gSXMgdGhhdCB0cnVlPwoKYGBge3J9CldvcmxkICU+JQogIGZpbHRlcihjb250aW5lbnQgPT0gIkFzaWEiKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpbmVxdWFsaXR5LCB5ID0gd2VsbF9iZWluZykpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYAoKLi4uIG5vLCBpdCBpc24ndCEgSXQgd291bGQgdGhlcmVmb3JlIGJlIGJldHRlciB0byBjcmVhdGUgYSBuZXcgZXF1YWxpdHkgdmFyaWFibGUgYW5kIG1hcCB0aGF0IHdpdGggd2VsbC1iZWluZzoKCmBgYHtyfQp0bWFwX21vZGUoInBsb3QiKQpXb3JsZCAlPiUKICBmaWx0ZXIoY29udGluZW50ID09ICJBc2lhIikgJT4lCiAgbXV0YXRlKGVxdWFsaXR5ID0gMSAtIGluZXF1YWxpdHkpICU+JQogIHRtX3NoYXBlKC4pICsKICB0bV9wb2x5Z29ucyhjKCJ3ZWxsX2JlaW5nIiwgImVxdWFsaXR5IiksIHN0eWxlID0gImplbmtzIikgKwogIHRtX2ZhY2V0cyhuY29sID0gMikgKwogIHRtX2xlZ2VuZChwb3NpdGlvbiA9IGMoImxlZnQiLCAiYm90dG9tIikpCmBgYAoKT3IsIGluICdpbnRlcmFjdGl2ZScgbW9kZToKCmBgYHtyfQp0bWFwX21vZGUoInZpZXciKQpXb3JsZCAlPiUKICBmaWx0ZXIoY29udGluZW50ID09ICJBc2lhIikgJT4lCiAgbXV0YXRlKGVxdWFsaXR5ID0gMSAtIGluZXF1YWxpdHkpICU+JQogIHRtX3NoYXBlKC4pICsKICB0bV9wb2x5Z29ucyhjKCJ3ZWxsX2JlaW5nIiwgImVxdWFsaXR5IiksIHN0eWxlID0gImplbmtzIikgKwogIHRtX2ZhY2V0cyhzeW5jID0gVFJVRSwgbmNvbCA9IDIpICsKICB0bV9sZWdlbmQocG9zaXRpb24gPSBjKCJsZWZ0IiwgImJvdHRvbSIpKQpgYGAKCih0cnkgc2V0dGluZyBzeW5jIHRvIEZBTFNFIGluIHRoZSBjb2RlIGFib3ZlIGFuZCBzZWUgd2hhdCBoYXBwZW5zKQoKSW4gdGhlIGV4YW1wbGVzIGFib3ZlLCB0aGUgZ2VvZ3JhcGhpY2FsIHJlZ2lvbiBpcyBoZWxkIGNvbnN0YW50IHdoaWxzdCB0aGUgYXR0cmlidXRlIHZhcmlhYmxlIGNoYW5nZXMuIFRoaXMgY2FuIGJlIHJldmVyc2VkLCBhcyBpbiB0aGUgZm9sbG93aW5nIGV4YW1wbGUsIHdoaWNoIGlzIG1vZGlmaWVkIGZyb20gW3RtYXA6IGdldCBzdGFydGVkIV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RtYXAvdmlnbmV0dGVzL3RtYXAtZ2V0c3RhcnRlZC5odG1sKQoKYGBge3J9CmRhdGEoTkxEX211bmkpCnRtYXBfbW9kZSgicGxvdCIpCk5MRF9tdW5pICU+JQogIG11dGF0ZShwZXJjX21lbiA9IHBvcF9tZW4gLyBwb3B1bGF0aW9uICogMTAwKSAlPiUKICB0bV9zaGFwZSguKSArCiAgdG1fcG9seWdvbnMoInBlcmNfbWVuIiwgcGFsZXR0ZSA9ICJSZFlsQnUiKSArCiAgdG1fZmFjZXRzKGJ5ID0gInByb3ZpbmNlIikKYGBgCgpNYXBzIGNhbiBhbHNvIGJlIGFycmFuZ2VkIHNpZGUtYnktc2lkZSB1c2luZyB0bWFwJ3MgYXJyYW5nZSBmdW5jdGlvbjoKCmBgYHtyfQpOTERfbXVuaSAlPiUKICB0bV9zaGFwZSguKSArCiAgdG1fcG9seWdvbnMoInBvcHVsYXRpb24iLCBjb252ZXJ0MmRlbnNpdHkgPSBUUlVFKSAtPiB0bTEKTkxEX211bmkgJT4lCiAgdG1fc2hhcGUoLikgKwogIHRtX2J1YmJsZXMoc2l6ZSA9ICJwb3B1bGF0aW9uIikgLT4gdG0yCnRtYXBfYXJyYW5nZSh0bTEsIHRtMikKYGBgCgpNb2RpZnkgdGhlIGZvbGxvd2luZyBjb2RlIGJsb2NrIHNvIHRoYXQgdGhlIEhQSSBzY29yZXMgZm9yIE5vcnRoIEFtZXJpY2EgYXJlIG1hcHBlZCBhbG9uZ3NpZGUgdGhlIEhQSSBzY29yZXMgZm9yIFNvdXRoIEFtZXJpY2EuIFRoZSBiZWdpbm5pbmcgb2YgdGhlIGNvZGUgaXMgaW5jbHVkZWQgZm9yIHlvdS4KCmBgYHtyfQp0bWFwX21vZGUoInBsb3QiKQpXb3JsZCAlPiUKICBmaWx0ZXIoZ3JlcGwoIkFtZXJpY2EiLCBjb250aW5lbnQpKSAlPiUKICAjIFJlcGxhY2UgdGhpcyBjb21tZW50IHdpdGggeW91ciBvd24gY29kZSAKYGBgCgojIyMgRXhwb3J0aW5nIHRoZSBwbG90cwoKUGxvdHMgY2FuIGJlIHNhdmVkIHVzaW5nIHRoZSB0bWFwX3NhdmUoLi4uKSBmdW5jdGlvbiAtIHNlZSA/dG1hcF9zYXZlIGZvciBhIGxpc3Qgb2YgdGhlIHBvc3NpYmxlIGZpbGUgZm9ybWF0cy4gRm9yIGV4YW1wbGUsCgpgYGB7cn0KV29ybGQgJT4lCiAgZmlsdGVyKGdyZXBsKCJBbWVyaWNhIiwgY29udGluZW50KSkgJT4lCiAgdG1fc2hhcGUoLikgKwogIHRtX3BvbHlnb25zKCJIUEkiKSAtPiBteW1hcAoKdG1hcF9tb2RlKCJwbG90IikKdG1hcF9zYXZlKG15bWFwLCAibXltYXAucGRmIiwgaGVpZ2h0ID0gMjkuNywgd2lkdGggPSAyMS4wLCB1bml0cyA9ICJjbSIpCgp0bWFwX21vZGUoInZpZXciKQp0bWFwX3NhdmUobXltYXAsIGZpbGVuYW1lID0gIm15bWFwLmh0bWwiKQpgYGAKCmBgYHtyfQpicm93c2VVUkwoIm15bWFwLnBkZiIpCmBgYAoKYGBge3J9CmJyb3dzZVVSTCgibXltYXAuaHRtbCIpCmBgYAoKIyMgVG8gRG8KClRvIGVuZCB0aGlzIHNlc3Npb24sIHlvdXIgdGFzayBpcyB0byBwcm9kdWNlIGEgbWFwIG9mIE5vaXNlIFBvbGx1dGlvbiBmcm9tIHJvYWRzIGluIExvbmRvbiwgdXNpbmcgZGF0YSBmcm9tIHRoZSBbTG9uZG9uIERhdGFzdG9yZV0oaHR0cHM6Ly9kYXRhLmxvbmRvbi5nb3YudWsvZGF0YXNldC9ub2lzZS1wb2xsdXRpb24taW4tbG9uZG9uKQoKVGhlIGZpcnN0IHRhc2sgaWYgdG8gZG93bmxvYWQgdGhlIGRhdGEgZnJvbSB0aGUgd2Vic2l0ZSB3aGVyZSB0aGV5IGFyZSBzdG9yZWQgaW4gYSB6aXBwZWQgZm9ybWF0IGFuZCB0aGVuIHVuemlwIHRoZW06CgpgYGB7cn0KZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9kYXRhLmxvbmRvbi5nb3YudWsvZG93bmxvYWQvbm9pc2UtcG9sbHV0aW9uLWluLWxvbmRvbi8xMTU0YTQ5MS1jNjhmLTQ0NTEtOTExMS0wOWJhYzk3NWJlOTkvUm9hZF9MQWVxXzE2aF9Mb25kb24uemlwIiwgIm5vaXNlLnppcCIpCnVuemlwKCJub2lzZS56aXAiKQpgYGAKCk5leHQsIHdlIG5lZWQgdG8gcmVhZCBpbiB0aGUgZGF0YToKCmBgYHtyfQpyZXF1aXJlKHNmKQpyb2FkX25vaXNlIDwtIHN0X3JlYWQoIlJvYWRfTEFlcV8xNmhfTG9uZG9uLnNocCIpCmBgYAoKVGFraW5nIGEgcXVpY2sgbG9vayBhdCB0aGUgZGF0YSwgd2UgY2FuIHNlZSBpdCBpcyBvZiBjbGFzcwoKYGBge3J9CmNsYXNzKHJvYWRfbm9pc2UpCmBgYAoKYW5kIGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgYXR0cmlidXRlIHZhcmlhYmxlCgpgYGB7cn0KbmFtZXMoc3RfZHJvcF9nZW9tZXRyeShyb2FkX25vaXNlKSkKYGBgCgpCZWNhdXNlIGl0IGlzIGFsc28gYSBsYXJnZSBkYXRhIHNldCwKCmBgYHtyfQpucm93KHJvYWRfbm9pc2UpCmBgYAoKd2Ugd2lsbCBqdXN0IG1hcCB0aGUgbG9jYXRpb25zIG9mIHRoZSBub2lzaWVzdCByb2FkcywKCmBgYHtyfQpyb2FkX25vaXNlICU+JQogIGZpbHRlcihOb2lzZUNsYXNzID09ICI+PTc1LjAiKSAlPiUKICBmaWx0ZXIoc3RfaXNfdmFsaWQoLikpIC0+IG5vaXNlX3N1YnNldApgYGAKCihUaGUgYWRkaXRpb25hbCBmaWx0ZXIgdXNpbmcgc3RfaXNfdmFsaWQgaXMgdG8gYXZvaWQgc29tZSBnZW9tZXRyaWMgZXJyb3JzIGluIHRoZSBkYXRhKQoKTm93IHByb2R1Y2UgYSBtYXAgb2YgdGhlIG5vaXNpZXN0IHJvYWRzIHRvIHdoaWNoIHlvdSBjb3VsZCBhZGQgYSBOb3J0aCBhcnJvdyBhbmQgYSBzY2FsZSBiYXIsIGFuZCBzYXZlIGl0IGluIGEgc3VpdGFibGUgZmlsZSBmb3JtYXQuIFRoZSBjb2RlIGJsb2NrIG1heSB0YWtlIGEgbGl0dGxlIGxvbmdlciB0byBydW4gdGhhbiBiZWZvcmUgc28gcGxlYXNlIGJlIHBhdGllbnQuCgpgYGB7cn0Kbm9pc2Vfc3Vic2V0ICU+JQogICMgUmVwbGFjZSB0aGlzIGNvbW1lbnQgd2l0aCB5b3VyIG93biBjb2RlIApgYGAKCiMjIElmIHlvdSBzdGlsbCBoYXZlIHRpbWUuLi4KCklmIHlvdSBoYXZlIGZpbmlzaGVkIHRoZSBleGVyY2lzZXMgYWJvdmUgYW5kIGhhdmUgc29tZSB0aW1lIGxlZnQgb3Zlciwgd29yayB0aHJvdWdoIG9uZSBvZiB0aGVzZSAoeW91IGNhbiBjaG9vc2Ugd2hpY2gpOgoKKEEpIChyZWNvbW1lbmRlZCwgaWYgeW91IGhhdmUgYSBjb3B5IG9mIHRoZSBib29rKQoKQ2hhcHRlciAzIG9mICpBbiBJbnRyb2R1Y3Rpb24gdG8gUiBmb3IgU3BhdGlhbCBBbmFseXNpcyAmIE1hcHBpbmcqIGJ5IENocmlzIEJydW5zZG9uIGFuZCBMZXggQ29tYmVyLgpUaGUgY29kZSAoYnV0IG5vdCB0ZXh0KSBmb3IgdGhlIGNoYXB0ZXIgW2lzIGhlcmVdKGh0dHBzOi8vYm9va2Rvd24ub3JnL2xleGNvbWJlci9icnVuc2RvbmNvbWJlcjJlL0NoMy5odG1sKQoKKEIpCgpXaWNraGFtIEggJiBHcm9sZW11bmQgRywgMjAxNywgKlIgZm9yIERhdGEgU2NpZW5jZSosIENoYXB0ZXIgMjg6IEdyYXBoaWNzIGZvciBDb21tdW5pY2F0aW9uLiBodHRwczovL3I0ZHMuaGFkLmNvLm56L2dyYXBoaWNzLWZvci1jb21tdW5pY2F0aW9uLmh0bWwKCndoaWNoIGlzIGFib3V0IHdheXMgdG8gYmV0dGVyIGNvbW11bmljYXRlIGdyYXBoaWNzLgoKYGBge3J9CmJyb3dzZVVSTCgiaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9ncmFwaGljcy1mb3ItY29tbXVuaWNhdGlvbi5odG1sIikKYGBgCgooQykKCipMZWFmbGV0IGZvciBSKi4gaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0LwoKYGBge3J9CmJyb3dzZVVSTCgiaHR0cHM6Ly9yc3R1ZGlvLmdpdGh1Yi5pby9sZWFmbGV0LyIpCmBgYAoKd2hpY2ggb2ZmZXJzIGFub3RoZXIgd2F5IHRvIGdlbmVyYXRlIGludGVyYWN0aXZlIG1hcHMuCgojIyBGdXJ0aGVyIFJlYWRpbmcKCkxvdmVsYWNlIFIsIE5vd29zYWQgSiAmIE11ZW5jaG93IEosIDIwMTksICpHZW9jb21wdXRhdGlvbiB3aXRoIFIqLCBDaGFwdGVyIDg6IE1ha2luZyBNYXBzIHdpdGggUi4gaHR0cHM6Ly9nZW9jb21wci5yb2JpbmxvdmVsYWNlLm5ldC9hZHYtbWFwLmh0bWwKClRlbm5la2VzIE0sIDIwMTgsIHRtYXA6IFRoZW1hdGljIE1hcHMgaW4gUi4gKkpvdXJuYWwgb2YgU3RhdGlzdGljYWwgU29mdHdhcmUqLCA4NCAoNikuIGh0dHBzOi8vd3d3LmpzdGF0c29mdC5vcmcvYXJ0aWNsZS92aWV3L3YwODRpMDYgCgo=