1 Overview

There are 10 challenges total- none are in the “continuous colors” section, but you can use that section to complete the tenth challenge on your own. Upload your knitted html document by next Wednesday to Sakai!

Note that this lab depends on many packages; on the RStudio Cloud project for the lab deliverable, I have pre-installed them all (I think). We’ve left the installation instructions here in the lab document for demonstration purposes.

2 Slides for today

knitr::include_url("slides/03-slides.html")

3 Packages

Other packages will be needed to be installed as you go- reveal the first code chunks when in doubt!

library(tidyverse)

4 Read in the data

sounds <- read_csv(here::here("data", "animal_sounds_summary.csv"))

5 Colour vs fill aesthetic

Fill and colour scales in ggplot2 can use the same palettes. Some shapes such as lines only accept the colour aesthetic, while others, such as polygons, accept both colour and fill aesthetics. In the latter case, the colour refers to the border of the shape, and the fill to the interior.


All symbols have a foreground colour, so if we add color = "navy", they all are affected.

s + geom_point(aes(shape = z), size = 4, colour = "navy") 


While all symbols have a foreground colour, symbols 21-25 also take a background colour (fill). So if we add fill = "orchid", only the last row of symbols are affected.

s + geom_point(aes(shape = z), size = 4, colour = "navy", fill = "orchid") 

6 Data

For the rest of today, we’ll play with the sounds dataset. This data was derived from the R package wordbankr, an R interface to access Wordbank- an open source database of children’s vocabulary development. The tool used to measure children’s language and communicative development in this database is the MacArthur-Bates Communicative Development Inventories (MB-CDI). The MD-CDI is a parent-reported questionnaire.

Here is a glimpse of the data:

glimpse(sounds)
Rows: 33
Columns: 7
$ age             <dbl> 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 11, 12, 12, 12, …
$ sound           <chr> "cockadoodledoo", "meow", "woof woof", "cockadoodledoo…
$ kids_produce    <dbl> 1, 0, 3, 0, 2, 2, 0, 5, 4, 0, 5, 12, 0, 12, 28, 9, 125…
$ kids_understand <dbl> 3, 10, 12, 2, 21, 22, 9, 41, 40, 4, 36, 32, 16, 59, 59…
$ kids_respond    <dbl> 35, 35, 35, 91, 93, 93, 139, 145, 143, 94, 94, 94, 141…
$ prop_produce    <dbl> 0.02857143, 0.00000000, 0.08571429, 0.00000000, 0.0215…
$ prop_understand <dbl> 0.08571429, 0.28571429, 0.34285714, 0.02197802, 0.2258…

Note that the unit of observation here is one-row-per-age-group/animal sound.

Variables you need for this lab:

  • age: child age in months
  • sound: a string describing a type of animal sound
  • kids_produce: the number of parents who answered “yes, my child produces this animal sound” (note that if the child produces a sound it is assumed that they understand it as well)
  • kids_respond: the number of parents who responded to this question at all
  • prop_produce: the proportion of kids whose parents endorsed that their child produces this animal sound, out of all questionnaires administered (i.e., kids_produce / kids_respond)

Other variables in this dataset:

  • kids_understand: the number of parents who answered “yes, my child understands what this animal sound means” (note that a child can understand the sound but not produce it)
  • prop_understand: the proportion of kids whose parents endorsed that their child understands this animal sound, out of all questionnaires administered (i.e., kids_understand / kids_respond)

7 Discrete vs continuous variables

Refresher Content:

For a refresher (and more detailed deep-dive), check out: “WHAT IS THE DIFFERENCE BETWEEN CATEGORICAL, ORDINAL AND NUMERICAL VARIABLES?”

In order to use color with your data, most importantly, you need to know if you’re dealing with discrete or continuous variables.

7.1 Discrete color palettes

Discrete color palettes work best when you want to color by a qualitative variable. Qualitative variables tend to be either categorical or ordinal. Different variables can be qualitative or quantitative depending on context.

In this dataset, sound is a categorical variable with 3 possible values:

sounds %>% 
  distinct(sound) %>% 
  knitr::kable()
sound
cockadoodledoo
meow
woof woof

We could map arbitrary numbers onto each of these sounds, like 1, 2, and 3- but the numbers still would not mean anything. That is, there is no intrinsic ordering to these categories. Examples of common pure categorical variables are race or ethnicity, gender, hair color, eye color, etc. Coloring by sound is used as a way to distinguish the data for different sounds from each other (read more here: http://serialmentor.com/dataviz/color-basics.html#color-as-a-tool-to-distinguish)

7.2 Continuous color palettes

Continuous color palettes work best when you want to color by a quantitative variable. Quantitative variables tend to be either ordinal or continuous. In this dataset, age (in months) can only take on a limited set of values:

sounds %>% 
  distinct(age) %>% 
  pull
 [1]  8  9 10 11 12 13 14 15 16 17 18

However, in the following plots, we’ll treat age as a continuous variable plotted across the x-axis. In some contexts, this kind of variable could be treated as a ordinal variable. However, for color purposes, this would not ideal here since there are 11 “categories” (see http://serialmentor.com/dataviz/color-pitfalls.html). Age has a natural and meaningful order: a child who is 9 months old is 1 month older than one who is 8 months old. So, we’ll use that natural ordering to our advantage and not use color to represent age as a variable. When you do apply a continuous color palette, you’ll want to use color to your advantage to represent data values.

8 Know your data

Challenge #1:
  • How many variables?

    • Which variables are continuous?
    • Which ones are categorical or ordinal?
  • How many total kids do we have data for?

  • How many ages (in months)?

    • How many kids per age?
  • How many types of animal sounds? What are they?

Let’s start just by getting a feel for how many kids produce each kind of sound, across the full age range. We could make a table:

sounds %>% 
  group_by(sound) %>% 
  summarize(total_produce = sum(kids_produce)) %>% 
  knitr::kable()
sound total_produce
cockadoodledoo 148
meow 681
woof woof 940

Or we could make a simple bar plot:

ggplot(sounds, aes(x = sound, y = kids_produce)) + 
  geom_col() +
  labs(x = "Sound", y = "Total Children Producing")

For this kind of plot, we don’t really need color. What if we want to see how the number of kids who produce each sound varies by age? We’ll change the x-axis to age and instead facet_wrap by sound, and make the y-axis a proportion instead of counts.

ggplot(sounds, aes(x = age, y = prop_produce)) + 
  geom_col() +
  labs(x = "Age (mos)", y = "Proportion of Children Producing") +
  facet_wrap(~sound)

The bar geom makes this a little hard to read and compare across facets though. Let’s try points instead.

ggplot(sounds, aes(x = age, y = prop_produce)) + 
  geom_point() +
  labs(x = "Age (mos)", y = "Proportion of Children Producing") +
  facet_wrap(~sound)

That is a little better! Facets allow us to parse the relationship between two quantitative variables (here, age and proportion of kids producing) by a qualitative variable (here, type of sound). Another way we could do this, instead of facetting, is to use color. This would make it easier to compare proportions at each age.

9 Discrete colors

Let’s start with a base plot with age (in months) along the x-axis and the proportion of children producing each word along the y-axis, using points as the geometric object. Set the size of the points to 2 and change the x- and y-axis labels to “Age (months)” and “Proportion of Children Producing”, respectively.

ggplot(sounds, aes(x = age, y = prop_produce)) + 
  geom_point(size = 2) +
  labs(x = "Age (months)", y = "Proportion of Children Producing")

9.1 Default discrete palette

Challenge #2:

Take the plot we just made, and edit the code to map the color of the points to the type of sound produced at the geom level. The colors that show up are the default discrete palette in ggplot2.

ggplot(sounds, aes(x = age, y = prop_produce)) + 
  geom_point(aes(color = sound), size = 2) +
  labs(x = "Age (months)", y = "Proportion of Children Producing")
Challenge #3:

Try adding geom_line() to this plot to connect the dots. Does this look right? Use ?geom_line to figure out how this geom connects the dots by default, and which aesthetic can be used to connect cases together. Try editing your code to draw 3 black lines- one for each sound.

# Does this look right? no!
ggplot(sounds, aes(x = age, y = prop_produce)) + 
  geom_line() +
  geom_point(aes(color = sound), size = 2) +
  labs(x = "Age (months)", y = "Proportion of Children Producing") 

# A possible solution
ggplot(sounds, aes(x = age, y = prop_produce)) + 
  geom_line(aes(group = sound)) +
  geom_point(aes(color = sound), size = 2) +
  labs(x = "Age (months)", y = "Proportion of Children Producing") 

Challenge #4:

Make two plots:

  1. Recreate the plot above, but this time map color to the type of sound produced for both the point and line geoms. Pay attention to the order of the layers you are adding- you may wish to place geom_line before geom_point so the lines are always “painted” underneath the points.

  2. Instead of geom_line, add a loess line using geom_smooth. Use ?geom_smooth to figure out how to get rid of the grey standard error ribbon. You may also want to increase the line width.

# Does this look right? yes!
ggplot(sounds, aes(x = age, y = prop_produce, color = sound)) + 
  geom_line() +
  geom_point(size = 2) +
  labs(x = "Age (months)", y = "Proportion of Children Producing") 

ggplot(sounds, aes(x = age, 
                         y = prop_produce, 
                         color = sound)) + 
  geom_smooth(se = FALSE, lwd = .5) +
  geom_point(size = 2)  +
  labs(x = "Age (months)", y = "Proportion of Children Producing") 

Why does this work? To tell geom_line how to connect your dots, you can either:

  • Map the group aesthetic (so aes(group = sound)), or
  • Map the color aesthetic globally (aes(color = sound).

Because geom_line understands the color aesthetic, it will try to draw separate lines for each color. Here that translates to three lines, one for each sound, which is what we want!

9.2 Brief aside: factors

At this point, our plot is looking pretty good. But you may have noticed that the legend order doesn’t match the order of the lines in the plot. Question: why is this an issue?

What determines the order of levels in the legend? The order of levels in the underlying factor:

levels(as.factor(sounds$sound))
[1] "cockadoodledoo" "meow"           "woof woof"     

In this case, since we haven’t set them, R will pick an order for us.

We could manually re-order the levels of the factor, but different plots might necessitate different factor ordering, and if we have more than two or three levels, typing them repeatedly gets tedious fast. Instead, let’s have R do it!

The forcats package, is for categorical variables and has lots of useful functions, including some for re-ordering levels. There are lots of functions in forcats, and you can install & load it separately, although forcats is loaded with the tidyverse.

install.packages("forcats")
library(forcats)

We’ll use the fct_reorder2 function, which by default will re-order the levels of a factor based on the order of occurrence of one variable (y in the docs) when the dataframe is sorted by another variable (x in the docs):

# "Sort the dataframe by age, find the last occurrence of each level of sounds$sound in order of prop_produce
fct_reorder2(
  as.factor(sounds$sound), 
  sounds$age, # variable "x"
  sounds$prop_produce # varible "y"
) %>% levels
[1] "woof woof"      "meow"           "cockadoodledoo"

This (somehwat convoluted) procedure is very useful for when you have a line chart of two quantitative variables, colored by a factor variable. Let’s see the difference:

sounds <- sounds %>% 
  mutate(sound = as.factor(sound))

sound_traj <- ggplot(sounds, aes(x = age, 
                         y = prop_produce, 
                         color = fct_reorder2(sound, age, prop_produce))) +
  geom_smooth(se = FALSE, lwd = .5) +
  geom_point(size = 2) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       color = "sound")
sound_traj

MUCH BETTER! Save your plot object as sound_traj. Now we can start playing with the actual colors.

9.3 Set luminance and saturation (chromaticity)

The default qualitative palette works fine here. The addition of scale_color_hue changes nothing.

sound_traj +
  scale_color_hue()

We can also change these settings within the default color palette, where the arguments are:

  • h = range of hues to use, in [0, 360]
  • l = luminance (lightness)
  • c = chroma (intensity of color)
# Change hue (l and c are defaults)
sound_traj +
  scale_color_hue(h = c(0, 90), l = 65, c = 100)

# Use luminance=45, instead of default 65
sound_traj +
  scale_color_hue(l = 45)

# Reduce saturation (chroma) from 100 to 50, and increase luminance
sound_traj +
  scale_color_hue(l = 75, c = 50)

9.4 Set discrete colors

We can change the actual colors used by adding the layer scale_color_manual or scale_fill_manual. Confusion between which to use when is often the cause of much frustration!

To name more than one color, which you often want to do, use c(). In the parentheses, named colors and hex colors are always in quotes.

sound_traj +
  scale_color_manual(values = c("cornflowerblue", 
                                "seagreen", "coral"))

There are many named colors available in R!

Challenge #5:

View the code blocks below. Copy and paste the code to run them in your own file. Why do neither of the following code blocks change the colors of the points and lines? Use your words :) (the answer is below the challenge, but try to trouble-shoot on your own first)

ggplot(sounds, aes(x = age, 
                         y = prop_produce, 
                         color = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(se = FALSE, lwd = .5) +
  geom_point(size = 2) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       color = "sound") +
  scale_fill_manual(values = c("cornflowerblue",
                               "seagreen", "coral"))

ggplot(sounds, aes(x = age, 
                         y = prop_produce, 
                         fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(se = FALSE, lwd = .5) +
  geom_point(size = 2) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound") +
  scale_fill_manual(values = c("cornflowerblue", 
                               "seagreen", "coral"))

Answers:

  • In the first, we used scale_fill_manual, but the in the global aesthetics, we mapped the color, not fill, aesthetic onto the sound variable.
  • In the second, we did define the fill aesthetic and used scale_fill_manual, so that is good. But geom_line only understands the color aesthetic, not fill. And for geom_point, the default shape for is 19, which does not understand the fill aesthetic.
Challenge #6:

Start with this plot:

sound_traj

Add a black outline to the points, and color the inside of the points and the lines by sound using the default discrete color palette. You may also wish to edit the legends on this plot: geom_smooth has an argument called show.legend = FALSE. See if you prefer the plot with this change.

If this was easy, try applying the same custom color palette to the inside of the points and to the lines.

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(aes(color = fct_reorder2(sound, age, prop_produce)),
              se = FALSE, lwd = .5, show.legend = FALSE) +
  geom_point(size = 2, shape = 21) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound")

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(aes(color = fct_reorder2(sound, age, prop_produce)),
              se = FALSE, lwd = .5, show.legend = FALSE) +
  geom_point(size = 2, shape = 21) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound") +
  scale_fill_manual(values = c("cornflowerblue", 
                               "seagreen", "coral")) +
  scale_color_manual(values = c("cornflowerblue", 
                               "seagreen", "coral"))

You can also define your color palette as a vector outside of ggplot2. Below, I made an object called my_colors outside of ggplot2. To use it, we call that object within the scale_colour_manual function.

my_colors <- c("cadetblue", "steelblue", "salmon") # quote color names
sound_traj +
  scale_color_manual(values = my_colors) # note: not in quotes

Challenge #7:

Define a custom color palette using hexadecimal colors (#rrggbb), and apply it using scale_color_manual to your sound_traj plot. Some basic ones are here:

https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/

Parse the hexadecimal string like so: #rrggbb, where rr, gg, and bb refer to color intensity in the red, green, and blue channels, respectively.

# from https://github.com/mwaskom/seaborn/blob/master/seaborn/palettes.py
sb_colorblind <- c("#0072B2", "#009E73", "#D55E00",
                        "#CC79A7", "#F0E442", "#56B4E9")
sound_traj +
  scale_colour_manual(values = sb_colorblind)

9.5 Built-in discrete palettes

9.5.1 Colorbrewer

To use Colorbrewer palettes, you’ll need to install the RColorBrewer package from CRAN. This chunk of code tells you how:

install.packages("RColorBrewer")
library(RColorBrewer)

Colorbrewer has a few qualitative palettes named: Accent, Dark2, Paired, Pastel1, Pastel2, Set1, Set2, Set3. Here is how to view them:

brewer.pal(5, "Dark2") # list 5 hex colors
[1] "#1B9E77" "#D95F02" "#7570B3" "#E7298A" "#66A61E"
display.brewer.pal(5, "Dark2") # view 5 hex colors

And here is how you use them:

sound_traj +
  scale_color_brewer(palette = "Dark2")

9.5.2 Wes Anderson palettes

My favorite! To use Wes Anderson palettes, you’ll need to install the wesanderson package from CRAN. This chunk of code tells you how:

install.packages("wesanderson")
library(wesanderson)
names(wes_palettes) # all the palette names
 [1] "BottleRocket1"  "BottleRocket2"  "Rushmore1"      "Rushmore"      
 [5] "Royal1"         "Royal2"         "Zissou1"        "Darjeeling1"   
 [9] "Darjeeling2"    "Chevalier1"     "FantasticFox1"  "Moonrise1"     
[13] "Moonrise2"      "Moonrise3"      "Cavalcanti1"    "GrandBudapest1"
[17] "GrandBudapest2" "IsleofDogs1"    "IsleofDogs2"   
wes_palette("GrandBudapest2") # view named palette

wes_palette("GrandBudapest2")[1:4] # list first 4 hex colors
[1] "#E6A0C4" "#C6CDF7" "#D8A499" "#7294D4"
wes_palette("GrandBudapest2")[c(1,4)] # list colors 1 and 4
[1] "#E6A0C4" "#7294D4"

To use these palettes, use scale_color_manual where values is set to wes_palette("name"). For example:

sound_traj +
  scale_color_manual(values = wes_palette("Darjeeling1"))

sound_traj +
  scale_color_manual(values = wes_palette("FantasticFox1"))

Challenge #8:

What if you just don’t want to use the colors in the order they are in? Use a wes_palette of your choice. Using our code from above, try picking the last 3 colors of a palette. Add it to your sound_traj plot.

If this was easy, try using colors 2, 3, and 5 instead.

sound_traj +
  scale_color_manual(values = wes_palette("Darjeeling1")[3:5])

sound_traj +
  scale_color_manual(values = wes_palette("FantasticFox1")[c(2, 3, 5)])

9.5.3 ggthemes palettes

To use these palettes, you’ll need to install the ggthemes package from CRAN. This chunk of code tells you how:

install.packages("ggthemes")
library(ggthemes)
sound_traj +
  scale_color_fivethirtyeight()

sound_traj +
  scale_color_economist()

9.5.4 ggsci Palettes

ggsci provides color palettes designed to match with the aesthetics of a wide variety of scientific publishers:

library(ggsci)

sound_traj + scale_color_nejm()

9.5.5 Palettes from the Queen Bee

To use Beyonce palettes, you’ll need to install the beyonce package from GitHub using devtools::install_github(). This chunk of code tells you how:

install.packages("devtools")
devtools::install_github("dill/beyonce")
library(beyonce)

Note that a number of students had installation problems with this package! Move on if you do.

beyonce_palette(18)

sound_traj +
  scale_color_manual(values = beyonce_palette(18)[3:5])

Here we’ll only use the first, fourth, and fifth colors in the palette.

sound_traj +
  scale_color_manual(values = beyonce_palette(18)[c(1, 4, 5)])

9.5.6 Viridis palettes

“Use the color scales in this package to make plots that are pretty, better represent your data, easier to read by those with colorblindness, and print well in grey scale.”

To use, you’ll need to install the viridis package from CRAN. This chunk of code tells you how:

install.packages("viridis")
library(viridis)

Read more here in the viridis vignette. The default argument for discrete is FALSE, so to use the discrete palettes you need to set discrete = TRUE. There are four colormap options available:

  • “magma” (or “A”),
  • “inferno” (or “B”),
  • “plasma” (or “C”),
  • “viridis” (or “D”, the default option).
sound_traj +
  scale_color_viridis(discrete = TRUE) +
  theme_minimal()

sound_traj +
  scale_color_viridis(discrete = TRUE, option = "plasma") +
  theme_minimal()

Challenge #9:

Use the viridis package to color the points by and the lines by sound; make the outline of the points “midnightblue”. Pick any colormap option, and play with theme_bw or theme_minimal to see what you like.

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(aes(color = fct_reorder2(sound, age, prop_produce)),
              se = FALSE, lwd = .5, show.legend = FALSE) +
  geom_point(size = 2, shape = 21, colour = "midnightblue") +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound") +
  scale_fill_viridis(discrete = TRUE) +
  scale_color_viridis(discrete = TRUE) +
  theme_minimal()

9.6 Greyscale for discrete

Use scale_color_grey or scale_fill_grey, or sometimes both depending on your geoms and the aesthetics they understand.

sound_traj +
  scale_color_grey() +
  theme_minimal()

Set start and end

sound_traj +
  scale_color_grey(start = 0.2, end = .8) 

Make the same plot but make points outlined in black

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(aes(color = fct_reorder2(sound, age, prop_produce)),
              se = FALSE, lwd = .5, show.legend = FALSE) +
  geom_point(size = 2, shape = 21) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound") +
  scale_fill_grey(start = 0.3, end = 1) +
  scale_color_grey(start = 0.3, end = 1) 

Suggest redundancy in greyscale- try changing line type instead of line (or in addition to) line color.

Change line type by sound, set color to black.

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(aes(lty = fct_reorder2(sound, age, prop_produce)), color = "black",
              se = FALSE, lwd = .5, show.legend = FALSE) +
  geom_point(size = 2, shape = 21) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound") +
  scale_fill_grey(start = 0.3, end = 1) 

Change both!

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   fill = fct_reorder2(sound, age, prop_produce))) + 
  geom_smooth(aes(color = fct_reorder2(sound, age, prop_produce),
                  lty = fct_reorder2(sound, age, prop_produce)),
              se = FALSE, lwd = .5, show.legend = FALSE) +
  geom_point(size = 2, shape = 21) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing", 
       fill = "sound") +
  scale_fill_grey(start = 0.3, end = .8) +
  scale_color_grey(start = 0.3, end = .8) 

9.7 Colorblind-friendly palettes

The colorblindr package can be used to “simulate colorblindness in production-ready R figures.” To use this package, you’ll need to first install the cowplot package from GitHub using devtools::install_github(). You’ll also need to install the colorspace package from CRAN. Finally, you can then use devtools::install_github() again to install the colorblindr package. This code chunk shows you how to do all 3 installs to use the colorblindr package:

devtools::install_github("wilkelab/cowplot")
install.packages("colorspace", repos = "http://R-Forge.R-project.org")
devtools::install_github("clauswilke/colorblindr")

To use:

# save a ggplot object
my_sound_traj <- sound_traj +
  scale_color_manual(values = beyonce_palette(18)[c(1, 4, 5)])

View that figure after color-vision-deficiency simulation:

# remotes::install_github("clauswilke/colorblindr")
library(colorblindr)
Error in library(colorblindr): there is no package called 'colorblindr'
cvd_grid(my_sound_traj)
Error in cvd_grid(my_sound_traj): could not find function "cvd_grid"

You can also use the colorblind-friendly palette in this package using scale_color_OkabeIto and scale_fill_OkabeIto:

cb_sound_traj <- sound_traj +
  scale_color_OkabeIto()
Error in scale_color_OkabeIto(): could not find function "scale_color_OkabeIto"
cb_sound_traj
Error in eval(expr, envir, enclos): object 'cb_sound_traj' not found
cvd_grid(cb_sound_traj)
Error in cvd_grid(cb_sound_traj): could not find function "cvd_grid"

You can still use this colorblind-friendly palette without the colorblindr package though. Here are the colors!

The Cookbook for R provided the matching hex colors too to make life easier:

cbbPalette <- c("#000000", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")

# To use for line and point colors, add
sound_traj +
  scale_colour_manual(values = cbbPalette[c(3, 7, 8)])

9.8 Repel labels

library(ggrepel)

sounds <- sounds %>%
  mutate(label = case_when(
    age == max(age) ~ sound))

ggplot(sounds, aes(x = age, 
                   y = prop_produce, 
                   color = fct_reorder2(sound, age, prop_produce))) +
  geom_smooth(se = FALSE, lwd = .5) +
  geom_point(size = 2) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing") +
  geom_text_repel(aes(label = label),
                  nudge_x = 1,
                  direction = "y",
                  na.rm = TRUE) +
  guides(color = FALSE)

10 Continuous colors

N.B. All of the example plots below are great examples of how not to use continuous colors. I’m showing these so you can see how to work with continuous color palettes, and to make this topic flow easier for you I’m sticking with original dataset.

10.1 Default continuous palette

Let’s map color to a continuous variable. For this, we are returning to geom_line instead of geom_smooth, because the latter doesn’t respond to continuous color palettes.

sound_by_age <- ggplot(sounds, aes(x = age, 
                                   y = prop_produce, 
                                   color = age)) +
  geom_line(aes(group = sound), lwd = .5) +
  geom_point(size = 2) +
  labs(x = "Age (months)", 
       y = "Proportion of Children Producing")
sound_by_age

10.2 Color choice with continuous variables

With discrete colors, we used either scale_color_manual or scale_fill_manual (and sometimes both were needed!). For continuous colors, we use either scale_color_gradient or scale_fill_gradient.

sound_by_age +
  scale_color_gradient()

You can reverse the gradient scale…

sound_by_age +
  scale_color_gradient(trans = "reverse")

sound_by_age +
  scale_color_gradient(low = "white", high = "red")

We can make this same plot using a custom greyscale gradient.

sound_by_age +
  scale_color_gradient(low = "grey90", high = "black")

So scale_color_gradient gives you a sequential gradient, but you may want a diverging color scheme instead. For that, you can use scale_color_gradient2

# Diverging color scheme
med_age <- sounds %>% 
  summarize(mos = median(age)) %>% 
  pull()
sound_by_age +
  scale_color_gradient2(midpoint = med_age,
                      low="blue", mid="white", high="red" )

10.3 Built-in continuous palettes

10.3.1 Use RColorBrewer

Again, to use you need to install and load the RColorBrewer palette.

library(RColorBrewer)

Then use scale_color_gradientn.

sound_by_age +
  scale_color_gradientn(colours = brewer.pal(n=5, name="PuBuGn"))

Reverse the colors…

sound_by_age +
  scale_color_gradientn(colours = rev(brewer.pal(n=5, name="PuBuGn")))

10.3.2 Viridis

Read more here in the viridis vignette

library(viridis)

The default is the viridis palette within the viridis package!

Note! For discrete == FALSE (the default) all other arguments are as to scale_fill_gradientn or scale_color_gradientn. (Also note that _gradient_n_ is not a typo- the n versions of those functions allow multi-color gradients).

sound_by_age +
  scale_color_viridis()

sound_by_age +
  scale_color_viridis(option = "magma")

Read the help function for ?scale_color_viridis. We’ll use the “inferno” palette in reverse.

sound_by_age +
  scale_color_viridis(option = "inferno", begin = 1, end = 0)

11 Final challenge (#10)

Challenge #10:

Using new data, make three new plots. Use any geom that makes sense. The plots should:

  • Have x- and y-axes that are each quantitative variables.
  • Apply a non-default color palette, either coloring by a qualitative variable (discrete colors) or a quantitative variable (continuous colors). This list of R color palettes has even more ideas than we could cover in class.
  1. In the first plot, you must wield color carefully and effectively. The addition of the color/fill aesthetics must be done in a way that the interpretation of the plot improves. Also, you must show how your colors fare for colorblind viewers. Include 2-3 sentences about why you made the plot that you did. What questions does your plot answers (or perhaps what questions does your plot raise)?

  2. In the second plot, you must make a greyscale version of your first plot! And again, it must look good and make sense.

  3. In the third plot, you must use color badly. Make a plot where the colors are either redundant, confusing, or just generally non-sensical. Explain why this last visualization fails.

Some data ideas:

LS0tCnRpdGxlOiAiTGFiIDAzOiBDb2xvcnMgd2l0aCBBbmltYWwgU291bmRzIgpzdWJ0aXRsZTogIkJNSSA1LzYyNSIKYXV0aG9yOiAiQWxpc29uIEhpbGwgdy8gbWlub3IgdHdlYWtzIGJ5IFN0ZXZlbiBCZWRyaWNrIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICB0b2NfZGVwdGg6IDIKICAgIG51bWJlcl9zZWN0aW9uczogVFJVRQogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQpgYGB7ciBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFLCBjYWNoZSA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3IgPSBUUlVFLCBjb21tZW50ID0gTkEsIHdhcm5pbmcgPSBGQUxTRSwgZXJyb3JzID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgdGlkeSA9IEZBTFNFLCBjYWNoZSA9IEZBTFNFKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKbGlicmFyeSh3ZXNhbmRlcnNvbikKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShiZXlvbmNlKQpsaWJyYXJ5KHZpcmlkaXMpCmBgYAoKIyBPdmVydmlldwoKVGhlcmUgYXJlIDEwIGNoYWxsZW5nZXMgdG90YWwtIG5vbmUgYXJlIGluIHRoZSAiY29udGludW91cyBjb2xvcnMiIHNlY3Rpb24sIGJ1dCB5b3UgY2FuIHVzZSB0aGF0IHNlY3Rpb24gdG8gY29tcGxldGUgdGhlIHRlbnRoIGNoYWxsZW5nZSBvbiB5b3VyIG93bi4gVXBsb2FkIHlvdXIga25pdHRlZCBodG1sIGRvY3VtZW50IGJ5IG5leHQgV2VkbmVzZGF5IHRvIFNha2FpIQoKTm90ZSB0aGF0IHRoaXMgbGFiIGRlcGVuZHMgb24gX21hbnlfIHBhY2thZ2VzOyBvbiB0aGUgUlN0dWRpbyBDbG91ZCBwcm9qZWN0IGZvciB0aGUgbGFiIGRlbGl2ZXJhYmxlLCBJIGhhdmUgcHJlLWluc3RhbGxlZCB0aGVtIGFsbCAoSSB0aGluaykuIFdlJ3ZlIGxlZnQgdGhlIGluc3RhbGxhdGlvbiBpbnN0cnVjdGlvbnMgaGVyZSBpbiB0aGUgbGFiIGRvY3VtZW50IGZvciBkZW1vbnN0cmF0aW9uIHB1cnBvc2VzLgoKIyBTbGlkZXMgZm9yIHRvZGF5CgpgYGB7cn0Ka25pdHI6OmluY2x1ZGVfdXJsKCJzbGlkZXMvMDMtc2xpZGVzLmh0bWwiKQpgYGAKCiMgUGFja2FnZXMKCk90aGVyIHBhY2thZ2VzIHdpbGwgYmUgbmVlZGVkIHRvIGJlIGluc3RhbGxlZCBhcyB5b3UgZ28tIHJldmVhbCB0aGUgZmlyc3QgY29kZSBjaHVua3Mgd2hlbiBpbiBkb3VidCEKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIFJlYWQgaW4gdGhlIGRhdGEKCmBgYHtyfQpzb3VuZHMgPC0gcmVhZF9jc3YoaGVyZTo6aGVyZSgiZGF0YSIsICJhbmltYWxfc291bmRzX3N1bW1hcnkuY3N2IikpCmBgYAoKIyBDb2xvdXIgdnMgZmlsbCBhZXN0aGV0aWMKCkZpbGwgYW5kIGNvbG91ciBzY2FsZXMgaW4gZ2dwbG90MiBjYW4gdXNlIHRoZSBzYW1lIHBhbGV0dGVzLiBTb21lIHNoYXBlcyBzdWNoIGFzIGxpbmVzIG9ubHkgYWNjZXB0IHRoZSBgY29sb3VyYCBhZXN0aGV0aWMsIHdoaWxlIG90aGVycywgc3VjaCBhcyBwb2x5Z29ucywgYWNjZXB0IGJvdGggYGNvbG91cmAgYW5kIGBmaWxsYCBhZXN0aGV0aWNzLiBJbiB0aGUgbGF0dGVyIGNhc2UsIHRoZSBgY29sb3VyYCByZWZlcnMgdG8gdGhlIGJvcmRlciBvZiB0aGUgc2hhcGUsIGFuZCB0aGUgYGZpbGxgIHRvIHRoZSBpbnRlcmlvci4KCgoKYGBge3IgZWNobyA9IEZBTFNFfQojIyBBIGxvb2sgYXQgYWxsIDI1IHN5bWJvbHMKZGYgPC0gZGF0YS5mcmFtZSh4ID0gMTo1LCAKICAgICAgICAgICAgICAgICAgeSA9IHJlcChyZXYoc2VxKDAsIDI0LCBieSA9IDUpKSwgZWFjaCA9IDUpLCAKICAgICAgICAgICAgICAgICAgeiA9IDE6MjUpCnMgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IHgsIHkgPSB5KSkgKyAKICBzY2FsZV9zaGFwZV9pZGVudGl0eSgpICsgCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHosIHkgPSB5IC0gMSkpICsgCiAgdGhlbWVfdm9pZCgpCnMgKyBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IHopLCBzaXplID0gNCkgCmBgYAoKLS0tCgpBbGwgc3ltYm9scyBoYXZlIGEgZm9yZWdyb3VuZCBjb2xvdXIsIHNvIGlmIHdlIGFkZCBgY29sb3IgPSAibmF2eSJgLCB0aGV5IGFsbCBhcmUgYWZmZWN0ZWQuCgpgYGB7cn0KcyArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0geiksIHNpemUgPSA0LCBjb2xvdXIgPSAibmF2eSIpIApgYGAKCi0tLQoKV2hpbGUgYWxsIHN5bWJvbHMgaGF2ZSBhIGZvcmVncm91bmQgY29sb3VyLCBzeW1ib2xzIDIxLTI1IGFsc28gdGFrZSBhIGJhY2tncm91bmQgY29sb3VyIChmaWxsKS4gU28gaWYgd2UgYWRkIGBmaWxsID0gIm9yY2hpZCJgLCBvbmx5IHRoZSBsYXN0IHJvdyBvZiBzeW1ib2xzIGFyZSBhZmZlY3RlZC4KCmBgYHtyfQpzICsgZ2VvbV9wb2ludChhZXMoc2hhcGUgPSB6KSwgc2l6ZSA9IDQsIGNvbG91ciA9ICJuYXZ5IiwgZmlsbCA9ICJvcmNoaWQiKSAKYGBgCgoKIyBEYXRhCgpGb3IgdGhlIHJlc3Qgb2YgdG9kYXksIHdlJ2xsIHBsYXkgd2l0aCB0aGUgYHNvdW5kc2AgZGF0YXNldC4gVGhpcyBkYXRhIHdhcyBkZXJpdmVkIGZyb20gdGhlIFIgcGFja2FnZSBbYHdvcmRiYW5rcmBdKGh0dHA6Ly9sYW5nY29nLmdpdGh1Yi5pby93b3JkYmFua3IvKSwgYW4gUiBpbnRlcmZhY2UgdG8gYWNjZXNzIFtXb3JkYmFua10oaHR0cDovL3dvcmRiYW5rLnN0YW5mb3JkLmVkdSktIGFuIG9wZW4gc291cmNlIGRhdGFiYXNlIG9mIGNoaWxkcmVuJ3Mgdm9jYWJ1bGFyeSBkZXZlbG9wbWVudC4gVGhlIHRvb2wgdXNlZCB0byBtZWFzdXJlIGNoaWxkcmVuJ3MgbGFuZ3VhZ2UgYW5kIGNvbW11bmljYXRpdmUgZGV2ZWxvcG1lbnQgaW4gdGhpcyBkYXRhYmFzZSBpcyB0aGUgW01hY0FydGh1ci1CYXRlcyBDb21tdW5pY2F0aXZlIERldmVsb3BtZW50IEludmVudG9yaWVzIChNQi1DREkpXShodHRwOi8vbWItY2RpLnN0YW5mb3JkLmVkdSkuIFRoZSBNRC1DREkgaXMgYSBwYXJlbnQtcmVwb3J0ZWQgcXVlc3Rpb25uYWlyZS4KCkhlcmUgaXMgYSBnbGltcHNlIG9mIHRoZSBkYXRhOgoKYGBge3J9CmdsaW1wc2Uoc291bmRzKQpgYGAKCgpOb3RlIHRoYXQgdGhlIHVuaXQgb2Ygb2JzZXJ2YXRpb24gaGVyZSBpcyBvbmUtcm93LXBlci1hZ2UtZ3JvdXAvYW5pbWFsIHNvdW5kLgoKClZhcmlhYmxlcyB5b3UgbmVlZCBmb3IgdGhpcyBsYWI6CgotIGBhZ2VgOiBjaGlsZCBhZ2UgaW4gbW9udGhzCi0gYHNvdW5kYDogYSBzdHJpbmcgZGVzY3JpYmluZyBhIHR5cGUgb2YgYW5pbWFsIHNvdW5kCi0gYGtpZHNfcHJvZHVjZWA6IHRoZSBudW1iZXIgb2YgcGFyZW50cyB3aG8gYW5zd2VyZWQgInllcywgbXkgY2hpbGQgcHJvZHVjZXMgdGhpcyBhbmltYWwgc291bmQiIChub3RlIHRoYXQgaWYgdGhlIGNoaWxkIHByb2R1Y2VzIGEgc291bmQgaXQgaXMgYXNzdW1lZCB0aGF0IHRoZXkgdW5kZXJzdGFuZCBpdCBhcyB3ZWxsKQotIGBraWRzX3Jlc3BvbmRgOiB0aGUgbnVtYmVyIG9mIHBhcmVudHMgd2hvIHJlc3BvbmRlZCB0byB0aGlzIHF1ZXN0aW9uIGF0IGFsbAotIGBwcm9wX3Byb2R1Y2VgOiB0aGUgcHJvcG9ydGlvbiBvZiBraWRzIHdob3NlIHBhcmVudHMgZW5kb3JzZWQgdGhhdCB0aGVpciBjaGlsZCBwcm9kdWNlcyB0aGlzIGFuaW1hbCBzb3VuZCwgb3V0IG9mIGFsbCBxdWVzdGlvbm5haXJlcyBhZG1pbmlzdGVyZWQgKGkuZS4sIGBraWRzX3Byb2R1Y2UgLyBraWRzX3Jlc3BvbmRgKQoKT3RoZXIgdmFyaWFibGVzIGluIHRoaXMgZGF0YXNldDoKCi0gYGtpZHNfdW5kZXJzdGFuZGA6IHRoZSBudW1iZXIgb2YgcGFyZW50cyB3aG8gYW5zd2VyZWQgInllcywgbXkgY2hpbGQgdW5kZXJzdGFuZHMgd2hhdCB0aGlzIGFuaW1hbCBzb3VuZCBtZWFucyIgKG5vdGUgdGhhdCBhIGNoaWxkIGNhbiB1bmRlcnN0YW5kIHRoZSBzb3VuZCBidXQgbm90IHByb2R1Y2UgaXQpCi0gYHByb3BfdW5kZXJzdGFuZGA6IHRoZSBwcm9wb3J0aW9uIG9mIGtpZHMgd2hvc2UgcGFyZW50cyBlbmRvcnNlZCB0aGF0IHRoZWlyIGNoaWxkIHVuZGVyc3RhbmRzIHRoaXMgYW5pbWFsIHNvdW5kLCBvdXQgb2YgYWxsIHF1ZXN0aW9ubmFpcmVzIGFkbWluaXN0ZXJlZCAoaS5lLiwgYGtpZHNfdW5kZXJzdGFuZCAvIGtpZHNfcmVzcG9uZGApCgoKCiMgRGlzY3JldGUgdnMgY29udGludW91cyB2YXJpYWJsZXMKCjxkaXYgY2xhc3M9InBhbmVsIHBhbmVsLXByaW1hcnkiPgogIDxkaXYgY2xhc3M9InBhbmVsLWhlYWRpbmciPlJlZnJlc2hlciBDb250ZW50OjwvZGl2PgogIDxkaXYgY2xhc3M9InBhbmVsLWJvZHkiPgpGb3IgYSByZWZyZXNoZXIgKGFuZCBtb3JlIGRldGFpbGVkIGRlZXAtZGl2ZSksIGNoZWNrIG91dDogWyJXSEFUIElTIFRIRSBESUZGRVJFTkNFIEJFVFdFRU4gQ0FURUdPUklDQUwsIE9SRElOQUwgQU5EIE5VTUVSSUNBTCBWQVJJQUJMRVM/Il0oaHR0cHM6Ly9zdGF0cy5vYXJjLnVjbGEuZWR1L290aGVyL211bHQtcGtnL3doYXRzdGF0L3doYXQtaXMtdGhlLWRpZmZlcmVuY2UtYmV0d2Vlbi1jYXRlZ29yaWNhbC1vcmRpbmFsLWFuZC1pbnRlcnZhbC12YXJpYWJsZXMvKQo8L2Rpdj4KPC9kaXY+CgpJbiBvcmRlciB0byB1c2UgY29sb3Igd2l0aCB5b3VyIGRhdGEsIG1vc3QgaW1wb3J0YW50bHksIHlvdSBuZWVkIHRvIGtub3cgaWYgeW914oCZcmUgZGVhbGluZyB3aXRoIGRpc2NyZXRlIG9yIGNvbnRpbnVvdXMgdmFyaWFibGVzLiAKCiMjIERpc2NyZXRlIGNvbG9yIHBhbGV0dGVzCgpEaXNjcmV0ZSBjb2xvciBwYWxldHRlcyB3b3JrIGJlc3Qgd2hlbiB5b3Ugd2FudCB0byBjb2xvciBieSBhIHF1YWxpdGF0aXZlIHZhcmlhYmxlLiBRdWFsaXRhdGl2ZSB2YXJpYWJsZXMgdGVuZCB0byBiZSBlaXRoZXIgY2F0ZWdvcmljYWwgb3Igb3JkaW5hbC4gRGlmZmVyZW50IHZhcmlhYmxlcyBjYW4gYmUgcXVhbGl0YXRpdmUgb3IgcXVhbnRpdGF0aXZlIGRlcGVuZGluZyBvbiBjb250ZXh0LiAKCkluIHRoaXMgZGF0YXNldCwgYHNvdW5kYCBpcyBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIHdpdGggMyBwb3NzaWJsZSB2YWx1ZXM6CmBgYHtyfQpzb3VuZHMgJT4lIAogIGRpc3RpbmN0KHNvdW5kKSAlPiUgCiAga25pdHI6OmthYmxlKCkKYGBgCgpXZSBjb3VsZCBtYXAgYXJiaXRyYXJ5IG51bWJlcnMgb250byBlYWNoIG9mIHRoZXNlIHNvdW5kcywgbGlrZSAxLCAyLCBhbmQgMy0gYnV0IHRoZSBudW1iZXJzIHN0aWxsIHdvdWxkIG5vdCBtZWFuIGFueXRoaW5nLiBUaGF0IGlzLCB0aGVyZSBpcyBubyBpbnRyaW5zaWMgb3JkZXJpbmcgdG8gdGhlc2UgY2F0ZWdvcmllcy4gRXhhbXBsZXMgb2YgY29tbW9uIHB1cmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGFyZSByYWNlIG9yIGV0aG5pY2l0eSwgZ2VuZGVyLCBoYWlyIGNvbG9yLCBleWUgY29sb3IsIGV0Yy4gQ29sb3JpbmcgYnkgc291bmQgaXMgdXNlZCBhcyBhIHdheSB0byAqZGlzdGluZ3Vpc2gqIHRoZSBkYXRhIGZvciBkaWZmZXJlbnQgc291bmRzIGZyb20gZWFjaCBvdGhlciAocmVhZCBtb3JlIGhlcmU6IGh0dHA6Ly9zZXJpYWxtZW50b3IuY29tL2RhdGF2aXovY29sb3ItYmFzaWNzLmh0bWwjY29sb3ItYXMtYS10b29sLXRvLWRpc3Rpbmd1aXNoKQoKIyMgQ29udGludW91cyBjb2xvciBwYWxldHRlcwoKQ29udGludW91cyBjb2xvciBwYWxldHRlcyB3b3JrIGJlc3Qgd2hlbiB5b3Ugd2FudCB0byBjb2xvciBieSBhIHF1YW50aXRhdGl2ZSB2YXJpYWJsZS4gUXVhbnRpdGF0aXZlIHZhcmlhYmxlcyB0ZW5kIHRvIGJlIGVpdGhlciBvcmRpbmFsIG9yIGNvbnRpbnVvdXMuIEluIHRoaXMgZGF0YXNldCwgYGFnZWAgKGluIG1vbnRocykgY2FuIG9ubHkgdGFrZSBvbiBhIGxpbWl0ZWQgc2V0IG9mIHZhbHVlczoKCmBgYHtyfQpzb3VuZHMgJT4lIAogIGRpc3RpbmN0KGFnZSkgJT4lIAogIHB1bGwKYGBgCgpIb3dldmVyLCBpbiB0aGUgZm9sbG93aW5nIHBsb3RzLCB3ZSdsbCB0cmVhdCBhZ2UgYXMgYSBjb250aW51b3VzIHZhcmlhYmxlIHBsb3R0ZWQgYWNyb3NzIHRoZSB4LWF4aXMuIEluIHNvbWUgY29udGV4dHMsIHRoaXMga2luZCBvZiB2YXJpYWJsZSBjb3VsZCBiZSB0cmVhdGVkIGFzIGEgb3JkaW5hbCB2YXJpYWJsZS4gSG93ZXZlciwgZm9yIGNvbG9yIHB1cnBvc2VzLCB0aGlzIHdvdWxkIG5vdCBpZGVhbCBoZXJlIHNpbmNlIHRoZXJlIGFyZSAxMSAiY2F0ZWdvcmllcyIgKHNlZSBodHRwOi8vc2VyaWFsbWVudG9yLmNvbS9kYXRhdml6L2NvbG9yLXBpdGZhbGxzLmh0bWwpLiBBZ2UgaGFzIGEgbmF0dXJhbCBhbmQgbWVhbmluZ2Z1bCBvcmRlcjogYSBjaGlsZCB3aG8gaXMgOSBtb250aHMgb2xkIGlzIDEgbW9udGggb2xkZXIgdGhhbiBvbmUgd2hvIGlzIDggbW9udGhzIG9sZC4gU28sIHdlJ2xsIHVzZSB0aGF0IG5hdHVyYWwgb3JkZXJpbmcgdG8gb3VyIGFkdmFudGFnZSBhbmQgbm90IHVzZSBjb2xvciB0byByZXByZXNlbnQgYWdlIGFzIGEgdmFyaWFibGUuIFdoZW4geW91ICpkbyogYXBwbHkgYSBjb250aW51b3VzIGNvbG9yIHBhbGV0dGUsIHlvdSdsbCB3YW50IHRvIHVzZSBjb2xvciB0byB5b3VyIGFkdmFudGFnZSB0byBbcmVwcmVzZW50IGRhdGEgdmFsdWVzXShodHRwOi8vc2VyaWFsbWVudG9yLmNvbS9kYXRhdml6L2NvbG9yLWJhc2ljcy5odG1sI2NvbG9yLXRvLXJlcHJlc2VudC1kYXRhLXZhbHVlcykuCgojIEtub3cgeW91ciBkYXRhCgo8ZGl2IGNsYXNzPSJwYW5lbCBwYW5lbC1zdWNjZXNzIj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1oZWFkaW5nIj5DaGFsbGVuZ2UgIzE6PC9kaXY+CiAgPGRpdiBjbGFzcz0icGFuZWwtYm9keSI+Ci0gSG93IG1hbnkgdmFyaWFibGVzPwogICAgLSBXaGljaCB2YXJpYWJsZXMgYXJlIGNvbnRpbnVvdXM/CiAgICAtIFdoaWNoIG9uZXMgYXJlIGNhdGVnb3JpY2FsIG9yIG9yZGluYWw/Ci0gSG93IG1hbnkgdG90YWwga2lkcyBkbyB3ZSBoYXZlIGRhdGEgZm9yPwotIEhvdyBtYW55IGFnZXMgKGluIG1vbnRocyk/CiAgLSBIb3cgbWFueSBraWRzIHBlciBhZ2U/Ci0gSG93IG1hbnkgdHlwZXMgb2YgYW5pbWFsIHNvdW5kcz8gV2hhdCBhcmUgdGhleT8KICA8L2Rpdj4KPC9kaXY+CgpMZXQncyBzdGFydCBqdXN0IGJ5IGdldHRpbmcgYSBmZWVsIGZvciBob3cgbWFueSBraWRzIHByb2R1Y2UgZWFjaCBraW5kIG9mIHNvdW5kLCBhY3Jvc3MgdGhlIGZ1bGwgYWdlIHJhbmdlLiBXZSBjb3VsZCBtYWtlIGEgdGFibGU6CgpgYGB7cn0Kc291bmRzICU+JSAKICBncm91cF9ieShzb3VuZCkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbF9wcm9kdWNlID0gc3VtKGtpZHNfcHJvZHVjZSkpICU+JSAKICBrbml0cjo6a2FibGUoKQpgYGAKCk9yIHdlIGNvdWxkIG1ha2UgYSBzaW1wbGUgYmFyIHBsb3Q6CgpgYGB7cn0KZ2dwbG90KHNvdW5kcywgYWVzKHggPSBzb3VuZCwgeSA9IGtpZHNfcHJvZHVjZSkpICsgCiAgZ2VvbV9jb2woKSArCiAgbGFicyh4ID0gIlNvdW5kIiwgeSA9ICJUb3RhbCBDaGlsZHJlbiBQcm9kdWNpbmciKQpgYGAKCkZvciB0aGlzIGtpbmQgb2YgcGxvdCwgd2UgZG9uJ3QgcmVhbGx5IG5lZWQgY29sb3IuIFdoYXQgaWYgd2Ugd2FudCB0byBzZWUgaG93IHRoZSBudW1iZXIgb2Yga2lkcyB3aG8gcHJvZHVjZSBlYWNoIHNvdW5kIHZhcmllcyBieSBhZ2U/IFdlJ2xsIGNoYW5nZSB0aGUgeC1heGlzIHRvIGFnZSBhbmQgaW5zdGVhZCBgZmFjZXRfd3JhcGAgYnkgYHNvdW5kYCwgYW5kIG1ha2UgdGhlIHktYXhpcyBhIHByb3BvcnRpb24gaW5zdGVhZCBvZiBjb3VudHMuCgpgYGB7cn0KZ2dwbG90KHNvdW5kcywgYWVzKHggPSBhZ2UsIHkgPSBwcm9wX3Byb2R1Y2UpKSArIAogIGdlb21fY29sKCkgKwogIGxhYnMoeCA9ICJBZ2UgKG1vcykiLCB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikgKwogIGZhY2V0X3dyYXAofnNvdW5kKQpgYGAKClRoZSBiYXIgZ2VvbSBtYWtlcyB0aGlzIGEgbGl0dGxlIGhhcmQgdG8gcmVhZCBhbmQgY29tcGFyZSBhY3Jvc3MgZmFjZXRzIHRob3VnaC4gTGV0J3MgdHJ5IHBvaW50cyBpbnN0ZWFkLgoKYGBge3J9CmdncGxvdChzb3VuZHMsIGFlcyh4ID0gYWdlLCB5ID0gcHJvcF9wcm9kdWNlKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGxhYnMoeCA9ICJBZ2UgKG1vcykiLCB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikgKwogIGZhY2V0X3dyYXAofnNvdW5kKQpgYGAKClRoYXQgaXMgYSBsaXR0bGUgYmV0dGVyISBGYWNldHMgYWxsb3cgdXMgdG8gcGFyc2UgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHR3byBxdWFudGl0YXRpdmUgdmFyaWFibGVzIChoZXJlLCBhZ2UgYW5kIHByb3BvcnRpb24gb2Yga2lkcyBwcm9kdWNpbmcpIGJ5IGEgcXVhbGl0YXRpdmUgdmFyaWFibGUgKGhlcmUsIHR5cGUgb2Ygc291bmQpLiBBbm90aGVyIHdheSB3ZSBjb3VsZCBkbyB0aGlzLCBpbnN0ZWFkIG9mIGZhY2V0dGluZywgaXMgdG8gdXNlIGNvbG9yLiBUaGlzIHdvdWxkIG1ha2UgaXQgZWFzaWVyIHRvIGNvbXBhcmUgcHJvcG9ydGlvbnMgYXQgZWFjaCBhZ2UuCgoKCgojIERpc2NyZXRlIGNvbG9ycwoKTGV0J3Mgc3RhcnQgd2l0aCBhIGJhc2UgcGxvdCB3aXRoIGFnZSAoaW4gbW9udGhzKSBhbG9uZyB0aGUgeC1heGlzIGFuZCB0aGUgcHJvcG9ydGlvbiBvZiBjaGlsZHJlbiBwcm9kdWNpbmcgZWFjaCB3b3JkIGFsb25nIHRoZSB5LWF4aXMsIHVzaW5nIHBvaW50cyBhcyB0aGUgZ2VvbWV0cmljIG9iamVjdC4gU2V0IHRoZSBzaXplIG9mIHRoZSBwb2ludHMgdG8gMiBhbmQgY2hhbmdlIHRoZSB4LSBhbmQgeS1heGlzIGxhYmVscyB0byAiQWdlIChtb250aHMpIiBhbmQgIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIiwgcmVzcGVjdGl2ZWx5LiAKCmBgYHtyfQpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgeSA9IHByb3BfcHJvZHVjZSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMikgKwogIGxhYnMoeCA9ICJBZ2UgKG1vbnRocykiLCB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikKYGBgCgoKCiMjIERlZmF1bHQgZGlzY3JldGUgcGFsZXR0ZQoKPGRpdiBjbGFzcz0icGFuZWwgcGFuZWwtc3VjY2VzcyI+CiAgPGRpdiBjbGFzcz0icGFuZWwtaGVhZGluZyI+Q2hhbGxlbmdlICMyOjwvZGl2PgogIDxkaXYgY2xhc3M9InBhbmVsLWJvZHkiPgpUYWtlIHRoZSBwbG90IHdlIGp1c3QgbWFkZSwgYW5kIGVkaXQgdGhlIGNvZGUgdG8gbWFwIHRoZSBjb2xvciBvZiB0aGUgcG9pbnRzIHRvIHRoZSB0eXBlIG9mIHNvdW5kIHByb2R1Y2VkICphdCB0aGUgZ2VvbSBsZXZlbCouIFRoZSBjb2xvcnMgdGhhdCBzaG93IHVwIGFyZSB0aGUgZGVmYXVsdCBkaXNjcmV0ZSBwYWxldHRlIGluIGBnZ3Bsb3QyYC4KCmBgYHtyfQpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgeSA9IHByb3BfcHJvZHVjZSkpICsgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBzb3VuZCksIHNpemUgPSAyKSArCiAgbGFicyh4ID0gIkFnZSAobW9udGhzKSIsIHkgPSAiUHJvcG9ydGlvbiBvZiBDaGlsZHJlbiBQcm9kdWNpbmciKQpgYGAKICA8L2Rpdj4KPC9kaXY+CgoKPGRpdiBjbGFzcz0icGFuZWwgcGFuZWwtc3VjY2VzcyI+CiAgPGRpdiBjbGFzcz0icGFuZWwtaGVhZGluZyI+Q2hhbGxlbmdlICMzOjwvZGl2PgogIDxkaXYgY2xhc3M9InBhbmVsLWJvZHkiPgpUcnkgYWRkaW5nIGBnZW9tX2xpbmUoKWAgdG8gdGhpcyBwbG90IHRvIGNvbm5lY3QgdGhlIGRvdHMuIERvZXMgdGhpcyBsb29rIHJpZ2h0PyBVc2UgYD9nZW9tX2xpbmVgIHRvIGZpZ3VyZSBvdXQgaG93IHRoaXMgZ2VvbSBjb25uZWN0cyB0aGUgZG90cyBieSBkZWZhdWx0LCBhbmQgd2hpY2ggYWVzdGhldGljIGNhbiBiZSB1c2VkIHRvIGNvbm5lY3QgY2FzZXMgdG9nZXRoZXIuIFRyeSBlZGl0aW5nIHlvdXIgY29kZSB0byBkcmF3IDMgYmxhY2sgbGluZXMtIG9uZSBmb3IgZWFjaCBzb3VuZC4KCgpgYGB7cn0KIyBEb2VzIHRoaXMgbG9vayByaWdodD8gbm8hCmdncGxvdChzb3VuZHMsIGFlcyh4ID0gYWdlLCB5ID0gcHJvcF9wcm9kdWNlKSkgKyAKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBzb3VuZCksIHNpemUgPSAyKSArCiAgbGFicyh4ID0gIkFnZSAobW9udGhzKSIsIHkgPSAiUHJvcG9ydGlvbiBvZiBDaGlsZHJlbiBQcm9kdWNpbmciKSAKYGBgCgoKYGBge3J9CiMgQSBwb3NzaWJsZSBzb2x1dGlvbgpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgeSA9IHByb3BfcHJvZHVjZSkpICsgCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IHNvdW5kKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gc291bmQpLCBzaXplID0gMikgKwogIGxhYnMoeCA9ICJBZ2UgKG1vbnRocykiLCB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikgCmBgYAoKICA8L2Rpdj4KPC9kaXY+Cgo8ZGl2IGNsYXNzPSJwYW5lbCBwYW5lbC1zdWNjZXNzIj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1oZWFkaW5nIj5DaGFsbGVuZ2UgIzQ6PC9kaXY+CiAgPGRpdiBjbGFzcz0icGFuZWwtYm9keSI+Ck1ha2UgdHdvIHBsb3RzOgoKMS4gUmVjcmVhdGUgdGhlIHBsb3QgYWJvdmUsIGJ1dCB0aGlzIHRpbWUgbWFwIGNvbG9yIHRvIHRoZSB0eXBlIG9mIHNvdW5kIHByb2R1Y2VkIGZvciBib3RoIHRoZSBwb2ludCBhbmQgbGluZSBnZW9tcy4gUGF5IGF0dGVudGlvbiB0byB0aGUgb3JkZXIgb2YgdGhlIGxheWVycyB5b3UgYXJlIGFkZGluZy0geW91IG1heSB3aXNoIHRvIHBsYWNlIGBnZW9tX2xpbmVgICpiZWZvcmUqIGBnZW9tX3BvaW50YCBzbyB0aGUgbGluZXMgYXJlIGFsd2F5cyAicGFpbnRlZCIgdW5kZXJuZWF0aCB0aGUgcG9pbnRzLgoKMi4gSW5zdGVhZCBvZiBgZ2VvbV9saW5lYCwgYWRkIGEgbG9lc3MgbGluZSB1c2luZyBgZ2VvbV9zbW9vdGhgLiBVc2UgYD9nZW9tX3Ntb290aGAgdG8gZmlndXJlIG91dCBob3cgdG8gZ2V0IHJpZCBvZiB0aGUgZ3JleSBzdGFuZGFyZCBlcnJvciByaWJib24uIFlvdSBtYXkgYWxzbyB3YW50IHRvIGluY3JlYXNlIHRoZSBsaW5lIHdpZHRoLiAKCmBgYHtyfQojIERvZXMgdGhpcyBsb29rIHJpZ2h0PyB5ZXMhCmdncGxvdChzb3VuZHMsIGFlcyh4ID0gYWdlLCB5ID0gcHJvcF9wcm9kdWNlLCBjb2xvciA9IHNvdW5kKSkgKyAKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgKwogIGxhYnMoeCA9ICJBZ2UgKG1vbnRocykiLCB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikgCgpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gcHJvcF9wcm9kdWNlLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gc291bmQpKSArIAogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIGx3ZCA9IC41KSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgICsKICBsYWJzKHggPSAiQWdlIChtb250aHMpIiwgeSA9ICJQcm9wb3J0aW9uIG9mIENoaWxkcmVuIFByb2R1Y2luZyIpIApgYGAKCiAgPC9kaXY+CjwvZGl2PgoKV2h5IGRvZXMgdGhpcyB3b3JrPyBUbyB0ZWxsIGBnZW9tX2xpbmVgIGhvdyB0byBjb25uZWN0IHlvdXIgZG90cywgeW91IGNhbiBlaXRoZXI6CgotIE1hcCB0aGUgYGdyb3VwYCBhZXN0aGV0aWMgKHNvIGBhZXMoZ3JvdXAgPSBzb3VuZClgKSwgb3IgCi0gTWFwIHRoZSBgY29sb3JgIGFlc3RoZXRpYyBnbG9iYWxseSAoYGFlcyhjb2xvciA9IHNvdW5kKWAuIAoKQmVjYXVzZSBgZ2VvbV9saW5lYCB1bmRlcnN0YW5kcyB0aGUgYGNvbG9yYCBhZXN0aGV0aWMsIGl0IHdpbGwgdHJ5IHRvIGRyYXcgc2VwYXJhdGUgbGluZXMgZm9yIGVhY2ggY29sb3IuIEhlcmUgdGhhdCB0cmFuc2xhdGVzIHRvIHRocmVlIGxpbmVzLCBvbmUgZm9yIGVhY2ggc291bmQsIHdoaWNoIGlzIHdoYXQgd2Ugd2FudCEKCiMjIEJyaWVmIGFzaWRlOiBmYWN0b3JzCgpBdCB0aGlzIHBvaW50LCBvdXIgcGxvdCBpcyBsb29raW5nIHByZXR0eSBnb29kLiBCdXQgeW91IG1heSBoYXZlIG5vdGljZWQgdGhhdCB0aGUgbGVnZW5kIG9yZGVyIGRvZXNuJ3QgbWF0Y2ggdGhlIG9yZGVyIG9mIHRoZSBsaW5lcyBpbiB0aGUgcGxvdC4gKipRdWVzdGlvbjoqKiB3aHkgaXMgdGhpcyBhbiBpc3N1ZT8KCldoYXQgZGV0ZXJtaW5lcyB0aGUgb3JkZXIgb2YgbGV2ZWxzIGluIHRoZSBsZWdlbmQ/IFRoZSBvcmRlciBvZiBsZXZlbHMgaW4gdGhlIHVuZGVybHlpbmcgZmFjdG9yOgoKYGBge3J9CmxldmVscyhhcy5mYWN0b3Ioc291bmRzJHNvdW5kKSkKYGBgCgpJbiB0aGlzIGNhc2UsIHNpbmNlIHdlIGhhdmVuJ3Qgc2V0IHRoZW0sIFIgd2lsbCBwaWNrIGFuIG9yZGVyIGZvciB1cy4KCldlIF9jb3VsZF8gbWFudWFsbHkgcmUtb3JkZXIgdGhlIGxldmVscyBvZiB0aGUgZmFjdG9yLCBidXQgZGlmZmVyZW50IHBsb3RzIG1pZ2h0IG5lY2Vzc2l0YXRlIGRpZmZlcmVudCBmYWN0b3Igb3JkZXJpbmcsIGFuZCBpZiB3ZSBoYXZlIG1vcmUgdGhhbiB0d28gb3IgdGhyZWUgbGV2ZWxzLCB0eXBpbmcgdGhlbSByZXBlYXRlZGx5IGdldHMgdGVkaW91cyBmYXN0LiBJbnN0ZWFkLCBsZXQncyBoYXZlIFIgZG8gaXQhCgpUaGUgW2Bmb3JjYXRzYCBwYWNrYWdlXShodHRwOi8vZm9yY2F0cy50aWR5dmVyc2Uub3JnKSwgaXMgYGZvcmAgYGNhdGBlZ29yaWNhbCB2YXJpYWJsZXMgYW5kIGhhcyBsb3RzIG9mIHVzZWZ1bCBmdW5jdGlvbnMsIGluY2x1ZGluZyBzb21lIGZvciByZS1vcmRlcmluZyBsZXZlbHMuIFRoZXJlIGFyZSBsb3RzIG9mIGZ1bmN0aW9ucyBpbiBgZm9yY2F0c2AsIGFuZCB5b3UgY2FuIGluc3RhbGwgJiBsb2FkIGl0IHNlcGFyYXRlbHksIGFsdGhvdWdoIGBmb3JjYXRzYCBpcyBsb2FkZWQgd2l0aCB0aGUgYHRpZHl2ZXJzZWAuCgpgYGB7ciBldmFsID0gRkFMU0V9Cmluc3RhbGwucGFja2FnZXMoImZvcmNhdHMiKQpsaWJyYXJ5KGZvcmNhdHMpCmBgYAoKV2UnbGwgdXNlIHRoZSBgZmN0X3Jlb3JkZXIyYCBmdW5jdGlvbiwgd2hpY2ggYnkgZGVmYXVsdCB3aWxsIHJlLW9yZGVyIHRoZSBsZXZlbHMgb2YgYSBmYWN0b3IgYmFzZWQgb24gdGhlIG9yZGVyIG9mIG9jY3VycmVuY2Ugb2Ygb25lIHZhcmlhYmxlIChgeWAgaW4gdGhlIGRvY3MpIHdoZW4gdGhlIGRhdGFmcmFtZSBpcyBfc29ydGVkXyBieSBhbm90aGVyIHZhcmlhYmxlIChgeGAgaW4gdGhlIGRvY3MpOgoKYGBge3J9CiMgIlNvcnQgdGhlIGRhdGFmcmFtZSBieSBhZ2UsIGZpbmQgdGhlIGxhc3Qgb2NjdXJyZW5jZSBvZiBlYWNoIGxldmVsIG9mIHNvdW5kcyRzb3VuZCBpbiBvcmRlciBvZiBwcm9wX3Byb2R1Y2UKZmN0X3Jlb3JkZXIyKAogIGFzLmZhY3Rvcihzb3VuZHMkc291bmQpLCAKICBzb3VuZHMkYWdlLCAjIHZhcmlhYmxlICJ4IgogIHNvdW5kcyRwcm9wX3Byb2R1Y2UgIyB2YXJpYmxlICJ5IgopICU+JSBsZXZlbHMKYGBgCgoKClRoaXMgKHNvbWVod2F0IGNvbnZvbHV0ZWQpIHByb2NlZHVyZSBpcyB2ZXJ5IHVzZWZ1bCBmb3Igd2hlbiB5b3UgaGF2ZSBhIGxpbmUgY2hhcnQgb2YgdHdvIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMsIGNvbG9yZWQgYnkgYSBmYWN0b3IgdmFyaWFibGUuIExldCdzIHNlZSB0aGUgZGlmZmVyZW5jZToKCmBgYHtyfQpzb3VuZHMgPC0gc291bmRzICU+JSAKICBtdXRhdGUoc291bmQgPSBhcy5mYWN0b3Ioc291bmQpKQoKc291bmRfdHJhaiA8LSBnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gcHJvcF9wcm9kdWNlLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSkpKSArCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgbHdkID0gLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgbGFicyh4ID0gIkFnZSAobW9udGhzKSIsIAogICAgICAgeSA9ICJQcm9wb3J0aW9uIG9mIENoaWxkcmVuIFByb2R1Y2luZyIsIAogICAgICAgY29sb3IgPSAic291bmQiKQpzb3VuZF90cmFqCmBgYAoKTVVDSCBCRVRURVIhIFNhdmUgeW91ciBwbG90IG9iamVjdCBhcyBgc291bmRfdHJhamAuIE5vdyB3ZSBjYW4gc3RhcnQgcGxheWluZyB3aXRoIHRoZSBhY3R1YWwgY29sb3JzLgoKIyMgU2V0IGx1bWluYW5jZSBhbmQgc2F0dXJhdGlvbiAoY2hyb21hdGljaXR5KQoKVGhlIGRlZmF1bHQgcXVhbGl0YXRpdmUgcGFsZXR0ZSB3b3JrcyBmaW5lIGhlcmUuIFRoZSBhZGRpdGlvbiBvZiBbYHNjYWxlX2NvbG9yX2h1ZWBdKGh0dHA6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3NjYWxlX2h1ZS5odG1sKSBjaGFuZ2VzIG5vdGhpbmcuCgpgYGB7cn0Kc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfaHVlKCkKYGBgCgpXZSBjYW4gYWxzbyBjaGFuZ2UgdGhlc2Ugc2V0dGluZ3Mgd2l0aGluIHRoZSBkZWZhdWx0IGNvbG9yIHBhbGV0dGUsIHdoZXJlIHRoZSBhcmd1bWVudHMgYXJlOgoKLSBgaGAgPSByYW5nZSBvZiBodWVzIHRvIHVzZSwgaW4gWzAsIDM2MF0KLSBgbGAgPSBsdW1pbmFuY2UgKGxpZ2h0bmVzcykKLSBgY2AgPSBjaHJvbWEgKGludGVuc2l0eSBvZiBjb2xvcikKCmBgYHtyfQojIENoYW5nZSBodWUgKGwgYW5kIGMgYXJlIGRlZmF1bHRzKQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9odWUoaCA9IGMoMCwgOTApLCBsID0gNjUsIGMgPSAxMDApCgojIFVzZSBsdW1pbmFuY2U9NDUsIGluc3RlYWQgb2YgZGVmYXVsdCA2NQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9odWUobCA9IDQ1KQoKIyBSZWR1Y2Ugc2F0dXJhdGlvbiAoY2hyb21hKSBmcm9tIDEwMCB0byA1MCwgYW5kIGluY3JlYXNlIGx1bWluYW5jZQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9odWUobCA9IDc1LCBjID0gNTApCmBgYAoKIyMgU2V0IGRpc2NyZXRlIGNvbG9ycwoKV2UgY2FuIGNoYW5nZSB0aGUgYWN0dWFsIGNvbG9ycyB1c2VkIGJ5IGFkZGluZyB0aGUgbGF5ZXIgYHNjYWxlX2NvbG9yX21hbnVhbGAgb3IgYHNjYWxlX2ZpbGxfbWFudWFsYC4gQ29uZnVzaW9uIGJldHdlZW4gd2hpY2ggdG8gdXNlIHdoZW4gaXMgb2Z0ZW4gdGhlIGNhdXNlIG9mIG11Y2ggZnJ1c3RyYXRpb24hCgpUbyBuYW1lIG1vcmUgdGhhbiBvbmUgY29sb3IsIHdoaWNoIHlvdSBvZnRlbiB3YW50IHRvIGRvLCB1c2UgYGMoKWAuIEluIHRoZSBwYXJlbnRoZXNlcywgbmFtZWQgY29sb3JzIGFuZCBoZXggY29sb3JzIGFyZSBhbHdheXMgaW4gcXVvdGVzLgoKYGBge3J9CnNvdW5kX3RyYWogKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJjb3JuZmxvd2VyYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZWFncmVlbiIsICJjb3JhbCIpKQpgYGAKClRoZXJlIGFyZSBtYW55IFtuYW1lZCBjb2xvcnNdKGh0dHA6Ly93d3cuc3RhdC5jb2x1bWJpYS5lZHUvfnR6aGVuZy9maWxlcy9SY29sb3IucGRmKSBhdmFpbGFibGUgaW4gUiEKCjxkaXYgY2xhc3M9InBhbmVsIHBhbmVsLXN1Y2Nlc3MiPgogIDxkaXYgY2xhc3M9InBhbmVsLWhlYWRpbmciPkNoYWxsZW5nZSAjNTo8L2Rpdj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1ib2R5Ij4KVmlldyB0aGUgY29kZSBibG9ja3MgYmVsb3cuIENvcHkgYW5kIHBhc3RlIHRoZSBjb2RlIHRvIHJ1biB0aGVtIGluIHlvdXIgb3duIGZpbGUuIFdoeSBkbyBuZWl0aGVyIG9mIHRoZSBmb2xsb3dpbmcgY29kZSBibG9ja3MgY2hhbmdlIHRoZSBjb2xvcnMgb2YgdGhlIHBvaW50cyBhbmQgbGluZXM/IFVzZSB5b3VyIHdvcmRzIDopICoodGhlIGFuc3dlciBpcyBiZWxvdyB0aGUgY2hhbGxlbmdlLCBidXQgdHJ5IHRvIHRyb3VibGUtc2hvb3Qgb24geW91ciBvd24gZmlyc3QpKgoKYGBge3J9CmdncGxvdChzb3VuZHMsIGFlcyh4ID0gYWdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBwcm9wX3Byb2R1Y2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBmY3RfcmVvcmRlcjIoc291bmQsIGFnZSwgcHJvcF9wcm9kdWNlKSkpICsgCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgbHdkID0gLjUpICsKICBnZW9tX3BvaW50KHNpemUgPSAyKSArCiAgbGFicyh4ID0gIkFnZSAobW9udGhzKSIsIAogICAgICAgeSA9ICJQcm9wb3J0aW9uIG9mIENoaWxkcmVuIFByb2R1Y2luZyIsIAogICAgICAgY29sb3IgPSAic291bmQiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiY29ybmZsb3dlcmJsdWUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNlYWdyZWVuIiwgImNvcmFsIikpCmBgYAoKCgpgYGB7cn0KZ2dwbG90KHNvdW5kcywgYWVzKHggPSBhZ2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHByb3BfcHJvZHVjZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSkpKSArIAogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIGx3ZCA9IC41KSArCiAgZ2VvbV9wb2ludChzaXplID0gMikgKwogIGxhYnMoeCA9ICJBZ2UgKG1vbnRocykiLCAKICAgICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBDaGlsZHJlbiBQcm9kdWNpbmciLCAKICAgICAgIGZpbGwgPSAic291bmQiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiY29ybmZsb3dlcmJsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZWFncmVlbiIsICJjb3JhbCIpKQpgYGAKICA8L2Rpdj4KPC9kaXY+CgpBbnN3ZXJzOiAKCi0gSW4gdGhlIGZpcnN0LCB3ZSB1c2VkIGBzY2FsZV9maWxsX21hbnVhbGAsIGJ1dCB0aGUgaW4gdGhlIGdsb2JhbCBhZXN0aGV0aWNzLCB3ZSBtYXBwZWQgdGhlIGBjb2xvcmAsIG5vdCBgZmlsbGAsIGFlc3RoZXRpYyBvbnRvIHRoZSBgc291bmRgIHZhcmlhYmxlLgotIEluIHRoZSBzZWNvbmQsIHdlIGRpZCBkZWZpbmUgdGhlIGBmaWxsYCBhZXN0aGV0aWMgYW5kIHVzZWQgYHNjYWxlX2ZpbGxfbWFudWFsYCwgc28gdGhhdCBpcyBnb29kLiBCdXQgYGdlb21fbGluZWAgb25seSB1bmRlcnN0YW5kcyB0aGUgYGNvbG9yYCBhZXN0aGV0aWMsIG5vdCBgZmlsbGAuIEFuZCBmb3IgYGdlb21fcG9pbnRgLCB0aGUgZGVmYXVsdCBzaGFwZSBmb3IgaXMgMTksIHdoaWNoIGRvZXMgbm90IHVuZGVyc3RhbmQgdGhlIGBmaWxsYCBhZXN0aGV0aWMuCgo8ZGl2IGNsYXNzPSJwYW5lbCBwYW5lbC1zdWNjZXNzIj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1oZWFkaW5nIj5DaGFsbGVuZ2UgIzY6PC9kaXY+CiAgPGRpdiBjbGFzcz0icGFuZWwtYm9keSI+ClN0YXJ0IHdpdGggdGhpcyBwbG90OgoKYGBge3J9CnNvdW5kX3RyYWoKYGBgCgpBZGQgYSBibGFjayBvdXRsaW5lIHRvIHRoZSBwb2ludHMsIGFuZCBjb2xvciB0aGUgaW5zaWRlIG9mIHRoZSBwb2ludHMgYW5kIHRoZSBsaW5lcyBieSBgc291bmRgIHVzaW5nIHRoZSBkZWZhdWx0IGRpc2NyZXRlIGNvbG9yIHBhbGV0dGUuIFlvdSBtYXkgYWxzbyB3aXNoIHRvIGVkaXQgdGhlIGxlZ2VuZHMgb24gdGhpcyBwbG90OiBgZ2VvbV9zbW9vdGhgIGhhcyBhbiBhcmd1bWVudCBjYWxsZWQgYHNob3cubGVnZW5kID0gRkFMU0VgLiBTZWUgaWYgeW91IHByZWZlciB0aGUgcGxvdCB3aXRoIHRoaXMgY2hhbmdlLiAKCklmIHRoaXMgd2FzIGVhc3ksIHRyeSBhcHBseWluZyB0aGUgc2FtZSBjdXN0b20gY29sb3IgcGFsZXR0ZSB0byB0aGUgaW5zaWRlIG9mIHRoZSBwb2ludHMgYW5kIHRvIHRoZSBsaW5lcy4gCgpgYGB7cn0KZ2dwbG90KHNvdW5kcywgYWVzKHggPSBhZ2UsIAogICAgICAgICAgICAgICAgICAgeSA9IHByb3BfcHJvZHVjZSwgCiAgICAgICAgICAgICAgICAgICBmaWxsID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSkpKSArIAogIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IGZjdF9yZW9yZGVyMihzb3VuZCwgYWdlLCBwcm9wX3Byb2R1Y2UpKSwKICAgICAgICAgICAgICBzZSA9IEZBTFNFLCBsd2QgPSAuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIHNoYXBlID0gMjEpICsKICBsYWJzKHggPSAiQWdlIChtb250aHMpIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIiwgCiAgICAgICBmaWxsID0gInNvdW5kIikKYGBgCgoKCmBgYHtyfQpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgCiAgICAgICAgICAgICAgICAgICB5ID0gcHJvcF9wcm9kdWNlLCAKICAgICAgICAgICAgICAgICAgIGZpbGwgPSBmY3RfcmVvcmRlcjIoc291bmQsIGFnZSwgcHJvcF9wcm9kdWNlKSkpICsgCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSkpLAogICAgICAgICAgICAgIHNlID0gRkFMU0UsIGx3ZCA9IC41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgc2hhcGUgPSAyMSkgKwogIGxhYnMoeCA9ICJBZ2UgKG1vbnRocykiLCAKICAgICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBDaGlsZHJlbiBQcm9kdWNpbmciLCAKICAgICAgIGZpbGwgPSAic291bmQiKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiY29ybmZsb3dlcmJsdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZWFncmVlbiIsICJjb3JhbCIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoImNvcm5mbG93ZXJibHVlIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAic2VhZ3JlZW4iLCAiY29yYWwiKSkKYGBgCgoKICA8L2Rpdj4KPC9kaXY+CgpZb3UgY2FuIGFsc28gZGVmaW5lIHlvdXIgY29sb3IgcGFsZXR0ZSBhcyBhIHZlY3RvciBvdXRzaWRlIG9mIGBnZ3Bsb3QyYC4gQmVsb3csIEkgbWFkZSBhbiBvYmplY3QgY2FsbGVkIGBteV9jb2xvcnNgIG91dHNpZGUgb2YgYGdncGxvdDJgLiBUbyB1c2UgaXQsIHdlIGNhbGwgdGhhdCBvYmplY3Qgd2l0aGluIHRoZSBgc2NhbGVfY29sb3VyX21hbnVhbGAgZnVuY3Rpb24uCgpgYGB7cn0KbXlfY29sb3JzIDwtIGMoImNhZGV0Ymx1ZSIsICJzdGVlbGJsdWUiLCAic2FsbW9uIikgIyBxdW90ZSBjb2xvciBuYW1lcwpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gbXlfY29sb3JzKSAjIG5vdGU6IG5vdCBpbiBxdW90ZXMKYGBgCgoKPGRpdiBjbGFzcz0icGFuZWwgcGFuZWwtc3VjY2VzcyI+CiAgPGRpdiBjbGFzcz0icGFuZWwtaGVhZGluZyI+Q2hhbGxlbmdlICM3OjwvZGl2PgogIDxkaXYgY2xhc3M9InBhbmVsLWJvZHkiPgpEZWZpbmUgYSBjdXN0b20gY29sb3IgcGFsZXR0ZSB1c2luZyBoZXhhZGVjaW1hbCBjb2xvcnMgKCNycmdnYmIpLCBhbmQgYXBwbHkgaXQgdXNpbmcgYHNjYWxlX2NvbG9yX21hbnVhbGAgdG8geW91ciBgc291bmRfdHJhamAgcGxvdC4gU29tZSBiYXNpYyBvbmVzIGFyZSBoZXJlOiAKCmh0dHBzOi8vc2FzaGF0Lm1lLzIwMTcvMDEvMTEvbGlzdC1vZi0yMC1zaW1wbGUtZGlzdGluY3QtY29sb3JzLwoKUGFyc2UgdGhlIGhleGFkZWNpbWFsIHN0cmluZyBsaWtlIHNvOiAjcnJnZ2JiLCB3aGVyZSByciwgZ2csIGFuZCBiYiByZWZlciB0byBjb2xvciBpbnRlbnNpdHkgaW4gdGhlIHJlZCwgZ3JlZW4sIGFuZCBibHVlIGNoYW5uZWxzLCByZXNwZWN0aXZlbHkuIAoKCmBgYHtyfQojIGZyb20gaHR0cHM6Ly9naXRodWIuY29tL213YXNrb20vc2VhYm9ybi9ibG9iL21hc3Rlci9zZWFib3JuL3BhbGV0dGVzLnB5CnNiX2NvbG9yYmxpbmQgPC0gYygiIzAwNzJCMiIsICIjMDA5RTczIiwgIiNENTVFMDAiLAogICAgICAgICAgICAgICAgICAgICAgICAiI0NDNzlBNyIsICIjRjBFNDQyIiwgIiM1NkI0RTkiKQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHNiX2NvbG9yYmxpbmQpCmBgYAoKICA8L2Rpdj4KPC9kaXY+CgojIyBCdWlsdC1pbiBkaXNjcmV0ZSBwYWxldHRlcwoKIyMjIENvbG9yYnJld2VyCgpUbyB1c2UgQ29sb3JicmV3ZXIgcGFsZXR0ZXMsIHlvdSdsbCBuZWVkIHRvIGluc3RhbGwgdGhlIGBSQ29sb3JCcmV3ZXJgIHBhY2thZ2UgZnJvbSBDUkFOLiBUaGlzIGNodW5rIG9mIGNvZGUgdGVsbHMgeW91IGhvdzoKCmBgYHtyIGV2YWwgPSBGQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmBgYAoKQ29sb3JicmV3ZXIgaGFzIGEgZmV3IHF1YWxpdGF0aXZlIHBhbGV0dGVzIG5hbWVkOiBBY2NlbnQsIERhcmsyLCBQYWlyZWQsIFBhc3RlbDEsIFBhc3RlbDIsIFNldDEsIFNldDIsIFNldDMuIEhlcmUgaXMgaG93IHRvIHZpZXcgdGhlbToKCmBgYHtyfQpicmV3ZXIucGFsKDUsICJEYXJrMiIpICMgbGlzdCA1IGhleCBjb2xvcnMKZGlzcGxheS5icmV3ZXIucGFsKDUsICJEYXJrMiIpICMgdmlldyA1IGhleCBjb2xvcnMKYGBgCgpBbmQgaGVyZSBpcyBob3cgeW91IHVzZSB0aGVtOgoKYGBge3J9CnNvdW5kX3RyYWogKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikKYGBgCgoKIyMjIFdlcyBBbmRlcnNvbiBwYWxldHRlcyAKCk15IGZhdm9yaXRlISBUbyB1c2UgV2VzIEFuZGVyc29uIHBhbGV0dGVzLCB5b3UnbGwgbmVlZCB0byBpbnN0YWxsIHRoZSBgd2VzYW5kZXJzb25gIHBhY2thZ2UgZnJvbSBDUkFOLiBUaGlzIGNodW5rIG9mIGNvZGUgdGVsbHMgeW91IGhvdzoKCmBgYHtyIGV2YWwgPSBGQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygid2VzYW5kZXJzb24iKQpsaWJyYXJ5KHdlc2FuZGVyc29uKQpgYGAKCgoKYGBge3J9Cm5hbWVzKHdlc19wYWxldHRlcykgIyBhbGwgdGhlIHBhbGV0dGUgbmFtZXMKd2VzX3BhbGV0dGUoIkdyYW5kQnVkYXBlc3QyIikgIyB2aWV3IG5hbWVkIHBhbGV0dGUKd2VzX3BhbGV0dGUoIkdyYW5kQnVkYXBlc3QyIilbMTo0XSAjIGxpc3QgZmlyc3QgNCBoZXggY29sb3JzCndlc19wYWxldHRlKCJHcmFuZEJ1ZGFwZXN0MiIpW2MoMSw0KV0gIyBsaXN0IGNvbG9ycyAxIGFuZCA0CmBgYAoKVG8gdXNlIHRoZXNlIHBhbGV0dGVzLCB1c2UgYHNjYWxlX2NvbG9yX21hbnVhbGAgd2hlcmUgYHZhbHVlc2AgaXMgc2V0IHRvIGB3ZXNfcGFsZXR0ZSgibmFtZSIpYC4gRm9yIGV4YW1wbGU6CgpgYGB7cn0Kc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHdlc19wYWxldHRlKCJEYXJqZWVsaW5nMSIpKQoKc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHdlc19wYWxldHRlKCJGYW50YXN0aWNGb3gxIikpCmBgYAoKCjxkaXYgY2xhc3M9InBhbmVsIHBhbmVsLXN1Y2Nlc3MiPgogIDxkaXYgY2xhc3M9InBhbmVsLWhlYWRpbmciPkNoYWxsZW5nZSAjODo8L2Rpdj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1ib2R5Ij4KV2hhdCBpZiB5b3UganVzdCBkb24ndCB3YW50IHRvIHVzZSB0aGUgY29sb3JzIGluIHRoZSBvcmRlciB0aGV5IGFyZSBpbj8gVXNlIGEgYHdlc19wYWxldHRlYCBvZiB5b3VyIGNob2ljZS4gVXNpbmcgb3VyIGNvZGUgZnJvbSBhYm92ZSwgdHJ5IHBpY2tpbmcgdGhlIGxhc3QgMyBjb2xvcnMgb2YgYSBwYWxldHRlLiBBZGQgaXQgdG8geW91ciBgc291bmRfdHJhamAgcGxvdC4KCklmIHRoaXMgd2FzIGVhc3ksIHRyeSB1c2luZyBjb2xvcnMgMiwgMywgYW5kIDUgaW5zdGVhZC4KCmBgYHtyfQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmcxIilbMzo1XSkKCnNvdW5kX3RyYWogKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSB3ZXNfcGFsZXR0ZSgiRmFudGFzdGljRm94MSIpW2MoMiwgMywgNSldKQpgYGAKICA8L2Rpdj4KPC9kaXY+CgoKIyMjIGBnZ3RoZW1lc2AgcGFsZXR0ZXMKClRvIHVzZSB0aGVzZSBwYWxldHRlcywgeW91J2xsIG5lZWQgdG8gaW5zdGFsbCB0aGUgYGdndGhlbWVzYCBwYWNrYWdlIGZyb20gQ1JBTi4gVGhpcyBjaHVuayBvZiBjb2RlIHRlbGxzIHlvdSBob3c6CgpgYGB7ciBldmFsID0gRkFMU0V9Cmluc3RhbGwucGFja2FnZXMoImdndGhlbWVzIikKbGlicmFyeShnZ3RoZW1lcykKYGBgCgoKCmBgYHtyfQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9maXZldGhpcnR5ZWlnaHQoKQoKc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfZWNvbm9taXN0KCkKYGBgCgojIyMgYGdnc2NpYCBQYWxldHRlcwoKW2BnZ3NjaWBdKGh0dHBzOi8vbmFueC5tZS9nZ3NjaS8pIHByb3ZpZGVzIGNvbG9yIHBhbGV0dGVzIGRlc2lnbmVkIHRvIG1hdGNoIHdpdGggdGhlIGFlc3RoZXRpY3Mgb2YgYSB3aWRlIHZhcmlldHkgb2Ygc2NpZW50aWZpYyBwdWJsaXNoZXJzOgoKYGBge3J9CmxpYnJhcnkoZ2dzY2kpCgpzb3VuZF90cmFqICsgc2NhbGVfY29sb3JfbmVqbSgpCmBgYAoKCiMjIyBQYWxldHRlcyBmcm9tIHRoZSBRdWVlbiBCZWUKClRvIHVzZSBbQmV5b25jZSBwYWxldHRlc10oaHR0cHM6Ly9naXRodWIuY29tL2RpbGwvYmV5b25jZSksIHlvdSdsbCBuZWVkIHRvIGluc3RhbGwgdGhlIGBiZXlvbmNlYCBwYWNrYWdlIGZyb20gR2l0SHViIHVzaW5nIGBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoKWAuIFRoaXMgY2h1bmsgb2YgY29kZSB0ZWxscyB5b3UgaG93OgoKYGBge3IgZXZhbCA9IEZBTFNFfQppbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiZGlsbC9iZXlvbmNlIikKbGlicmFyeShiZXlvbmNlKQpgYGAKCk5vdGUgdGhhdCBhIG51bWJlciBvZiBzdHVkZW50cyBoYWQgaW5zdGFsbGF0aW9uIHByb2JsZW1zIHdpdGggdGhpcyBwYWNrYWdlISBNb3ZlIG9uIGlmIHlvdSBkby4KCmBgYHtyfQpiZXlvbmNlX3BhbGV0dGUoMTgpCmBgYAoKYGBge3J9CnNvdW5kX3RyYWogKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBiZXlvbmNlX3BhbGV0dGUoMTgpWzM6NV0pCmBgYAoKSGVyZSB3ZSdsbCBvbmx5IHVzZSB0aGUgZmlyc3QsIGZvdXJ0aCwgYW5kIGZpZnRoIGNvbG9ycyBpbiB0aGUgcGFsZXR0ZS4KCmBgYHtyfQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYmV5b25jZV9wYWxldHRlKDE4KVtjKDEsIDQsIDUpXSkKYGBgCgojIyMgVmlyaWRpcyBwYWxldHRlcwoKPiAiVXNlIHRoZSBjb2xvciBzY2FsZXMgaW4gdGhpcyBwYWNrYWdlIHRvIG1ha2UgcGxvdHMgdGhhdCBhcmUgcHJldHR5LCBiZXR0ZXIgcmVwcmVzZW50IHlvdXIgZGF0YSwgZWFzaWVyIHRvIHJlYWQgYnkgdGhvc2Ugd2l0aCBjb2xvcmJsaW5kbmVzcywgYW5kIHByaW50IHdlbGwgaW4gZ3JleSBzY2FsZS4iCgpUbyB1c2UsIHlvdSdsbCBuZWVkIHRvIGluc3RhbGwgdGhlIGB2aXJpZGlzYCBwYWNrYWdlIGZyb20gQ1JBTi4gVGhpcyBjaHVuayBvZiBjb2RlIHRlbGxzIHlvdSBob3c6CgpgYGB7ciBldmFsID0gRkFMU0V9Cmluc3RhbGwucGFja2FnZXMoInZpcmlkaXMiKQpsaWJyYXJ5KHZpcmlkaXMpCmBgYAoKUmVhZCBtb3JlIGhlcmUgaW4gdGhlIFt2aXJpZGlzIHZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmlyaWRpcy92aWduZXR0ZXMvaW50cm8tdG8tdmlyaWRpcy5odG1sKS4gVGhlIGRlZmF1bHQgYXJndW1lbnQgZm9yIGBkaXNjcmV0ZWAgaXMgRkFMU0UsIHNvIHRvIHVzZSB0aGUgZGlzY3JldGUgcGFsZXR0ZXMgeW91IG5lZWQgdG8gc2V0IGBkaXNjcmV0ZSA9IFRSVUVgLiBUaGVyZSBhcmUgZm91ciBjb2xvcm1hcCBvcHRpb25zIGF2YWlsYWJsZToKCi0gIm1hZ21hIiAob3IgIkEiKSwKLSAiaW5mZXJubyIgKG9yICJCIiksCi0gInBsYXNtYSIgKG9yICJDIiksCi0gInZpcmlkaXMiIChvciAiRCIsIHRoZSBkZWZhdWx0IG9wdGlvbikuCgpgYGB7cn0Kc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUpICsKICB0aGVtZV9taW5pbWFsKCkKCnNvdW5kX3RyYWogKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXMoZGlzY3JldGUgPSBUUlVFLCBvcHRpb24gPSAicGxhc21hIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCjxkaXYgY2xhc3M9InBhbmVsIHBhbmVsLXN1Y2Nlc3MiPgogIDxkaXYgY2xhc3M9InBhbmVsLWhlYWRpbmciPkNoYWxsZW5nZSAjOTo8L2Rpdj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1ib2R5Ij4KVXNlIHRoZSBgdmlyaWRpc2AgcGFja2FnZSB0byBjb2xvciB0aGUgcG9pbnRzIGJ5IGFuZCB0aGUgbGluZXMgYnkgYHNvdW5kYDsgbWFrZSB0aGUgb3V0bGluZSBvZiB0aGUgcG9pbnRzICJtaWRuaWdodGJsdWUiLiBQaWNrIGFueSBjb2xvcm1hcCBvcHRpb24sIGFuZCBwbGF5IHdpdGggYHRoZW1lX2J3YCBvciBgdGhlbWVfbWluaW1hbGAgdG8gc2VlIHdoYXQgeW91IGxpa2UuCgpgYGB7cn0KZ2dwbG90KHNvdW5kcywgYWVzKHggPSBhZ2UsIAogICAgICAgICAgICAgICAgICAgeSA9IHByb3BfcHJvZHVjZSwgCiAgICAgICAgICAgICAgICAgICBmaWxsID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSkpKSArIAogIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IGZjdF9yZW9yZGVyMihzb3VuZCwgYWdlLCBwcm9wX3Byb2R1Y2UpKSwKICAgICAgICAgICAgICBzZSA9IEZBTFNFLCBsd2QgPSAuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIHNoYXBlID0gMjEsIGNvbG91ciA9ICJtaWRuaWdodGJsdWUiKSArCiAgbGFicyh4ID0gIkFnZSAobW9udGhzKSIsIAogICAgICAgeSA9ICJQcm9wb3J0aW9uIG9mIENoaWxkcmVuIFByb2R1Y2luZyIsIAogICAgICAgZmlsbCA9ICJzb3VuZCIpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMoZGlzY3JldGUgPSBUUlVFKSArCiAgc2NhbGVfY29sb3JfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCiAgPC9kaXY+CjwvZGl2PgoKCgoKIyMgR3JleXNjYWxlIGZvciBkaXNjcmV0ZQoKVXNlIGBzY2FsZV9jb2xvcl9ncmV5YCBvciBgc2NhbGVfZmlsbF9ncmV5YCwgb3Igc29tZXRpbWVzIGJvdGggZGVwZW5kaW5nIG9uIHlvdXIgZ2VvbXMgYW5kIHRoZSBhZXN0aGV0aWNzIHRoZXkgdW5kZXJzdGFuZC4KCmBgYHtyfQpzb3VuZF90cmFqICsKICBzY2FsZV9jb2xvcl9ncmV5KCkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKClNldCBzdGFydCBhbmQgZW5kCgpgYGB7cn0Kc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfZ3JleShzdGFydCA9IDAuMiwgZW5kID0gLjgpIApgYGAKCgpNYWtlIHRoZSBzYW1lIHBsb3QgYnV0IG1ha2UgcG9pbnRzIG91dGxpbmVkIGluIGJsYWNrCgpgYGB7cn0KZ2dwbG90KHNvdW5kcywgYWVzKHggPSBhZ2UsIAogICAgICAgICAgICAgICAgICAgeSA9IHByb3BfcHJvZHVjZSwgCiAgICAgICAgICAgICAgICAgICBmaWxsID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSkpKSArIAogIGdlb21fc21vb3RoKGFlcyhjb2xvciA9IGZjdF9yZW9yZGVyMihzb3VuZCwgYWdlLCBwcm9wX3Byb2R1Y2UpKSwKICAgICAgICAgICAgICBzZSA9IEZBTFNFLCBsd2QgPSAuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIHNoYXBlID0gMjEpICsKICBsYWJzKHggPSAiQWdlIChtb250aHMpIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIiwgCiAgICAgICBmaWxsID0gInNvdW5kIikgKwogIHNjYWxlX2ZpbGxfZ3JleShzdGFydCA9IDAuMywgZW5kID0gMSkgKwogIHNjYWxlX2NvbG9yX2dyZXkoc3RhcnQgPSAwLjMsIGVuZCA9IDEpIApgYGAKClN1Z2dlc3QgcmVkdW5kYW5jeSBpbiBncmV5c2NhbGUtIHRyeSBjaGFuZ2luZyBsaW5lIHR5cGUgaW5zdGVhZCBvZiBsaW5lIChvciBpbiBhZGRpdGlvbiB0bykgbGluZSBjb2xvci4KCgpDaGFuZ2UgbGluZSB0eXBlIGJ5IGBzb3VuZGAsIHNldCBjb2xvciB0byBibGFjay4KCmBgYHtyfQpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgCiAgICAgICAgICAgICAgICAgICB5ID0gcHJvcF9wcm9kdWNlLCAKICAgICAgICAgICAgICAgICAgIGZpbGwgPSBmY3RfcmVvcmRlcjIoc291bmQsIGFnZSwgcHJvcF9wcm9kdWNlKSkpICsgCiAgZ2VvbV9zbW9vdGgoYWVzKGx0eSA9IGZjdF9yZW9yZGVyMihzb3VuZCwgYWdlLCBwcm9wX3Byb2R1Y2UpKSwgY29sb3IgPSAiYmxhY2siLAogICAgICAgICAgICAgIHNlID0gRkFMU0UsIGx3ZCA9IC41LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaXplID0gMiwgc2hhcGUgPSAyMSkgKwogIGxhYnMoeCA9ICJBZ2UgKG1vbnRocykiLCAKICAgICAgIHkgPSAiUHJvcG9ydGlvbiBvZiBDaGlsZHJlbiBQcm9kdWNpbmciLCAKICAgICAgIGZpbGwgPSAic291bmQiKSArCiAgc2NhbGVfZmlsbF9ncmV5KHN0YXJ0ID0gMC4zLCBlbmQgPSAxKSAKYGBgCgpDaGFuZ2UgYm90aCEKCmBgYHtyfQpnZ3Bsb3Qoc291bmRzLCBhZXMoeCA9IGFnZSwgCiAgICAgICAgICAgICAgICAgICB5ID0gcHJvcF9wcm9kdWNlLCAKICAgICAgICAgICAgICAgICAgIGZpbGwgPSBmY3RfcmVvcmRlcjIoc291bmQsIGFnZSwgcHJvcF9wcm9kdWNlKSkpICsgCiAgZ2VvbV9zbW9vdGgoYWVzKGNvbG9yID0gZmN0X3Jlb3JkZXIyKHNvdW5kLCBhZ2UsIHByb3BfcHJvZHVjZSksCiAgICAgICAgICAgICAgICAgIGx0eSA9IGZjdF9yZW9yZGVyMihzb3VuZCwgYWdlLCBwcm9wX3Byb2R1Y2UpKSwKICAgICAgICAgICAgICBzZSA9IEZBTFNFLCBsd2QgPSAuNSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIsIHNoYXBlID0gMjEpICsKICBsYWJzKHggPSAiQWdlIChtb250aHMpIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIiwgCiAgICAgICBmaWxsID0gInNvdW5kIikgKwogIHNjYWxlX2ZpbGxfZ3JleShzdGFydCA9IDAuMywgZW5kID0gLjgpICsKICBzY2FsZV9jb2xvcl9ncmV5KHN0YXJ0ID0gMC4zLCBlbmQgPSAuOCkgCmBgYAoKIyMgQ29sb3JibGluZC1mcmllbmRseSBwYWxldHRlcwoKVGhlIFtgY29sb3JibGluZHJgIHBhY2thZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9jbGF1c3dpbGtlL2NvbG9yYmxpbmRyKSBjYW4gYmUgdXNlZCB0byAic2ltdWxhdGUgY29sb3JibGluZG5lc3MgaW4gcHJvZHVjdGlvbi1yZWFkeSBSIGZpZ3VyZXMuIiBUbyB1c2UgdGhpcyBwYWNrYWdlLCB5b3UnbGwgbmVlZCB0byBmaXJzdCBpbnN0YWxsIHRoZSBgY293cGxvdGAgcGFja2FnZSBmcm9tIEdpdEh1YiB1c2luZyBgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKClgLiBZb3UnbGwgYWxzbyBuZWVkIHRvIGluc3RhbGwgdGhlIGBjb2xvcnNwYWNlYCBwYWNrYWdlIGZyb20gQ1JBTi4gRmluYWxseSwgeW91IGNhbiB0aGVuIHVzZSBgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKClgIGFnYWluIHRvIGluc3RhbGwgdGhlIGBjb2xvcmJsaW5kcmAgcGFja2FnZS4gVGhpcyBjb2RlIGNodW5rIHNob3dzIHlvdSBob3cgdG8gZG8gYWxsIDMgaW5zdGFsbHMgdG8gdXNlIHRoZSBgY29sb3JibGluZHJgIHBhY2thZ2U6CgpgYGB7ciBldmFsID0gRkFMU0V9CmRldnRvb2xzOjppbnN0YWxsX2dpdGh1Yigid2lsa2VsYWIvY293cGxvdCIpCmluc3RhbGwucGFja2FnZXMoImNvbG9yc3BhY2UiLCByZXBvcyA9ICJodHRwOi8vUi1Gb3JnZS5SLXByb2plY3Qub3JnIikKZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJjbGF1c3dpbGtlL2NvbG9yYmxpbmRyIikKYGBgCgpUbyB1c2U6CmBgYHtyfQojIHNhdmUgYSBnZ3Bsb3Qgb2JqZWN0Cm15X3NvdW5kX3RyYWogPC0gc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGJleW9uY2VfcGFsZXR0ZSgxOClbYygxLCA0LCA1KV0pCmBgYAoKVmlldyB0aGF0IGZpZ3VyZSBhZnRlciBjb2xvci12aXNpb24tZGVmaWNpZW5jeSBzaW11bGF0aW9uOgoKYGBge3J9CiMgcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImNsYXVzd2lsa2UvY29sb3JibGluZHIiKQpsaWJyYXJ5KGNvbG9yYmxpbmRyKQpjdmRfZ3JpZChteV9zb3VuZF90cmFqKQpgYGAKCllvdSBjYW4gYWxzbyB1c2UgdGhlIGNvbG9yYmxpbmQtZnJpZW5kbHkgcGFsZXR0ZSBpbiB0aGlzIHBhY2thZ2UgdXNpbmcgYHNjYWxlX2NvbG9yX09rYWJlSXRvYCBhbmQgYHNjYWxlX2ZpbGxfT2thYmVJdG9gOgoKYGBge3J9CmNiX3NvdW5kX3RyYWogPC0gc291bmRfdHJhaiArCiAgc2NhbGVfY29sb3JfT2thYmVJdG8oKQoKY2Jfc291bmRfdHJhagpjdmRfZ3JpZChjYl9zb3VuZF90cmFqKQpgYGAKCllvdSBjYW4gc3RpbGwgdXNlIHRoaXMgY29sb3JibGluZC1mcmllbmRseSBwYWxldHRlIHdpdGhvdXQgdGhlIGBjb2xvcmJsaW5kcmAgcGFja2FnZSB0aG91Z2guIFtIZXJlXShodHRwOi8vamZseS5pYW0udS10b2t5by5hYy5qcC9jb2xvci8pIGFyZSB0aGUgY29sb3JzIQoKIVtdKGh0dHA6Ly9qZmx5LmlhbS51LXRva3lvLmFjLmpwL2NvbG9yL2ltYWdlL3BhbGxldGUuanBnKQoKVGhlIFtDb29rYm9vayBmb3IgUl0oaHR0cDovL3d3dy5jb29rYm9vay1yLmNvbS9HcmFwaHMvQ29sb3JzXyhnZ3Bsb3QyKS8jYS1jb2xvcmJsaW5kLWZyaWVuZGx5LXBhbGV0dGUpIHByb3ZpZGVkIHRoZSBtYXRjaGluZyBoZXggY29sb3JzIHRvbyB0byBtYWtlIGxpZmUgZWFzaWVyOgpgYGB7cn0KY2JiUGFsZXR0ZSA8LSBjKCIjMDAwMDAwIiwgIiNFNjlGMDAiLCAiIzU2QjRFOSIsICIjMDA5RTczIiwgIiNGMEU0NDIiLCAiIzAwNzJCMiIsICIjRDU1RTAwIiwgIiNDQzc5QTciKQoKIyBUbyB1c2UgZm9yIGxpbmUgYW5kIHBvaW50IGNvbG9ycywgYWRkCnNvdW5kX3RyYWogKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gY2JiUGFsZXR0ZVtjKDMsIDcsIDgpXSkKYGBgCgojIyBSZXBlbCBsYWJlbHMKCmBgYHtyfQpsaWJyYXJ5KGdncmVwZWwpCgpzb3VuZHMgPC0gc291bmRzICU+JQogIG11dGF0ZShsYWJlbCA9IGNhc2Vfd2hlbigKICAgIGFnZSA9PSBtYXgoYWdlKSB+IHNvdW5kKSkKCmdncGxvdChzb3VuZHMsIGFlcyh4ID0gYWdlLCAKICAgICAgICAgICAgICAgICAgIHkgPSBwcm9wX3Byb2R1Y2UsIAogICAgICAgICAgICAgICAgICAgY29sb3IgPSBmY3RfcmVvcmRlcjIoc291bmQsIGFnZSwgcHJvcF9wcm9kdWNlKSkpICsKICBnZW9tX3Ntb290aChzZSA9IEZBTFNFLCBsd2QgPSAuNSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsKICBsYWJzKHggPSAiQWdlIChtb250aHMpIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikgKwogIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWwgPSBsYWJlbCksCiAgICAgICAgICAgICAgICAgIG51ZGdlX3ggPSAxLAogICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAieSIsCiAgICAgICAgICAgICAgICAgIG5hLnJtID0gVFJVRSkgKwogIGd1aWRlcyhjb2xvciA9IEZBTFNFKQpgYGAKCgoKIyBDb250aW51b3VzIGNvbG9ycwoKKk4uQi4gQWxsIG9mIHRoZSBleGFtcGxlIHBsb3RzIGJlbG93IGFyZSBncmVhdCBleGFtcGxlcyBvZiBob3cgKipub3QqKiB0byB1c2UgY29udGludW91cyBjb2xvcnMuIEknbSBzaG93aW5nIHRoZXNlIHNvIHlvdSBjYW4gc2VlIGhvdyB0byB3b3JrIHdpdGggY29udGludW91cyBjb2xvciBwYWxldHRlcywgYW5kIHRvIG1ha2UgdGhpcyB0b3BpYyBmbG93IGVhc2llciBmb3IgeW91IEknbSBzdGlja2luZyB3aXRoIG9yaWdpbmFsIGRhdGFzZXQuKgoKIyMgRGVmYXVsdCBjb250aW51b3VzIHBhbGV0dGUKCkxldOKAmXMgbWFwIGNvbG9yIHRvIGEgY29udGludW91cyB2YXJpYWJsZS4gRm9yIHRoaXMsIHdlIGFyZSByZXR1cm5pbmcgdG8gYGdlb21fbGluZWAgaW5zdGVhZCBvZiBgZ2VvbV9zbW9vdGhgLCBiZWNhdXNlIHRoZSBsYXR0ZXIgZG9lc24ndCByZXNwb25kIHRvIGNvbnRpbnVvdXMgY29sb3IgcGFsZXR0ZXMuCgpgYGB7cn0Kc291bmRfYnlfYWdlIDwtIGdncGxvdChzb3VuZHMsIGFlcyh4ID0gYWdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gcHJvcF9wcm9kdWNlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGFnZSkpICsKICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gc291bmQpLCBsd2QgPSAuNSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsKICBsYWJzKHggPSAiQWdlIChtb250aHMpIiwgCiAgICAgICB5ID0gIlByb3BvcnRpb24gb2YgQ2hpbGRyZW4gUHJvZHVjaW5nIikKc291bmRfYnlfYWdlCmBgYAoKCgojIyBDb2xvciBjaG9pY2Ugd2l0aCBjb250aW51b3VzIHZhcmlhYmxlcyAKCldpdGggZGlzY3JldGUgY29sb3JzLCB3ZSB1c2VkIGVpdGhlciBgc2NhbGVfY29sb3JfbWFudWFsYCBvciBgc2NhbGVfZmlsbF9tYW51YWxgIChhbmQgc29tZXRpbWVzIGJvdGggd2VyZSBuZWVkZWQhKS4gRm9yIGNvbnRpbnVvdXMgY29sb3JzLCB3ZSB1c2UgZWl0aGVyIGBzY2FsZV9jb2xvcl9ncmFkaWVudGAgb3IgYHNjYWxlX2ZpbGxfZ3JhZGllbnRgLgoKYGBge3J9CnNvdW5kX2J5X2FnZSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQoKQpgYGAKCllvdSBjYW4gcmV2ZXJzZSB0aGUgZ3JhZGllbnQgc2NhbGUuLi4KCmBgYHtyfQpzb3VuZF9ieV9hZ2UgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50KHRyYW5zID0gInJldmVyc2UiKQpgYGAKCgpgYGB7cn0Kc291bmRfYnlfYWdlICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAid2hpdGUiLCBoaWdoID0gInJlZCIpCmBgYAoKV2UgY2FuIG1ha2UgdGhpcyBzYW1lIHBsb3QgdXNpbmcgYSBjdXN0b20gZ3JleXNjYWxlIGdyYWRpZW50LgoKYGBge3J9CnNvdW5kX2J5X2FnZSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gImdyZXk5MCIsIGhpZ2ggPSAiYmxhY2siKQpgYGAKCgoKU28gYHNjYWxlX2NvbG9yX2dyYWRpZW50YCBnaXZlcyB5b3UgYSBzZXF1ZW50aWFsIGdyYWRpZW50LCBidXQgeW91IG1heSB3YW50IGEgZGl2ZXJnaW5nIGNvbG9yIHNjaGVtZSBpbnN0ZWFkLiBGb3IgdGhhdCwgeW91IGNhbiB1c2UgYHNjYWxlX2NvbG9yX2dyYWRpZW50MmAKCgpgYGB7cn0KIyBEaXZlcmdpbmcgY29sb3Igc2NoZW1lCm1lZF9hZ2UgPC0gc291bmRzICU+JSAKICBzdW1tYXJpemUobW9zID0gbWVkaWFuKGFnZSkpICU+JSAKICBwdWxsKCkKc291bmRfYnlfYWdlICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudDIobWlkcG9pbnQgPSBtZWRfYWdlLAogICAgICAgICAgICAgICAgICAgICAgbG93PSJibHVlIiwgbWlkPSJ3aGl0ZSIsIGhpZ2g9InJlZCIgKQpgYGAKCiMjIEJ1aWx0LWluIGNvbnRpbnVvdXMgcGFsZXR0ZXMKCiMjIyBVc2UgYFJDb2xvckJyZXdlcmAgCgpBZ2FpbiwgdG8gdXNlIHlvdSBuZWVkIHRvIGluc3RhbGwgYW5kIGxvYWQgdGhlIGBSQ29sb3JCcmV3ZXJgIHBhbGV0dGUuIAoKYGBge3IgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKYGBgCgpUaGVuIHVzZSBgc2NhbGVfY29sb3JfZ3JhZGllbnRuYC4KCmBgYHtyfQpzb3VuZF9ieV9hZ2UgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gYnJld2VyLnBhbChuPTUsIG5hbWU9IlB1QnVHbiIpKQpgYGAKClJldmVyc2UgdGhlIGNvbG9ycy4uLgoKYGBge3J9CnNvdW5kX2J5X2FnZSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG91cnMgPSByZXYoYnJld2VyLnBhbChuPTUsIG5hbWU9IlB1QnVHbiIpKSkKYGBgCgojIyMgVmlyaWRpcwoKUmVhZCBtb3JlIGhlcmUgaW4gdGhlIFt2aXJpZGlzIHZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmlyaWRpcy92aWduZXR0ZXMvaW50cm8tdG8tdmlyaWRpcy5odG1sKQoKYGBge3IgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KHZpcmlkaXMpCmBgYAoKVGhlIGRlZmF1bHQgaXMgdGhlIGB2aXJpZGlzYCBwYWxldHRlIHdpdGhpbiB0aGUgYHZpcmlkaXNgIHBhY2thZ2UhCgpOb3RlISBGb3IgZGlzY3JldGUgPT0gRkFMU0UgKHRoZSBkZWZhdWx0KSBhbGwgb3RoZXIgYXJndW1lbnRzIGFyZSBhcyB0byBgc2NhbGVfZmlsbF9ncmFkaWVudG5gIG9yIGBzY2FsZV9jb2xvcl9ncmFkaWVudG5gLiAoQWxzbyBub3RlIHRoYXQgYF9ncmFkaWVudF9uX2AgaXMgbm90IGEgdHlwby0gdGhlIF9uXyB2ZXJzaW9ucyBvZiB0aG9zZSBmdW5jdGlvbnMgYWxsb3cgbXVsdGktY29sb3IgZ3JhZGllbnRzKS4KCmBgYHtyfQpzb3VuZF9ieV9hZ2UgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKQpgYGAKCmBgYHtyfQpzb3VuZF9ieV9hZ2UgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikKYGBgCgpSZWFkIHRoZSBoZWxwIGZ1bmN0aW9uIGZvciBgP3NjYWxlX2NvbG9yX3ZpcmlkaXNgLiBXZSdsbCB1c2UgdGhlICJpbmZlcm5vIiBwYWxldHRlIF9pbiByZXZlcnNlXy4KCmBgYHtyfQpzb3VuZF9ieV9hZ2UgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXMob3B0aW9uID0gImluZmVybm8iLCBiZWdpbiA9IDEsIGVuZCA9IDApCmBgYAoKCgojIEZpbmFsIGNoYWxsZW5nZSAoIzEwKQoKPGRpdiBjbGFzcz0icGFuZWwgcGFuZWwtc3VjY2VzcyI+CiAgPGRpdiBjbGFzcz0icGFuZWwtaGVhZGluZyI+Q2hhbGxlbmdlICMxMDo8L2Rpdj4KICA8ZGl2IGNsYXNzPSJwYW5lbC1ib2R5Ij4KVXNpbmcgbmV3IGRhdGEsIG1ha2UgdGhyZWUgbmV3IHBsb3RzLiBVc2UgYW55IGBnZW9tYCB0aGF0IG1ha2VzIHNlbnNlLiBUaGUgcGxvdHMgc2hvdWxkOgoKLSBIYXZlIHgtIGFuZCB5LWF4ZXMgdGhhdCBhcmUgZWFjaCBxdWFudGl0YXRpdmUgdmFyaWFibGVzLgotIEFwcGx5IGEgbm9uLWRlZmF1bHQgY29sb3IgcGFsZXR0ZSwgZWl0aGVyIGNvbG9yaW5nIGJ5IGEgcXVhbGl0YXRpdmUgdmFyaWFibGUgKGRpc2NyZXRlIGNvbG9ycykgb3IgYSBxdWFudGl0YXRpdmUgdmFyaWFibGUgKGNvbnRpbnVvdXMgY29sb3JzKS4gVGhpcyBsaXN0IG9mIFtSIGNvbG9yIHBhbGV0dGVzXShodHRwczovL2dpdGh1Yi5jb20vRW1pbEh2aXRmZWxkdC9yLWNvbG9yLXBhbGV0dGVzKSBoYXMgZXZlbiBtb3JlIGlkZWFzIHRoYW4gd2UgY291bGQgY292ZXIgaW4gY2xhc3MuCgoKMS4gSW4gdGhlIGZpcnN0IHBsb3QsIHlvdSBtdXN0ICoqd2llbGQgY29sb3IgY2FyZWZ1bGx5IGFuZCBlZmZlY3RpdmVseSoqLiBUaGUgYWRkaXRpb24gb2YgdGhlIGNvbG9yL2ZpbGwgYWVzdGhldGljcyBtdXN0IGJlIGRvbmUgaW4gYSB3YXkgdGhhdCB0aGUgaW50ZXJwcmV0YXRpb24gb2YgdGhlIHBsb3QgaW1wcm92ZXMuIEFsc28sIHlvdSBtdXN0IHNob3cgaG93IHlvdXIgY29sb3JzIGZhcmUgZm9yIGNvbG9yYmxpbmQgdmlld2Vycy4gSW5jbHVkZSAyLTMgc2VudGVuY2VzIGFib3V0IHdoeSB5b3UgbWFkZSB0aGUgcGxvdCB0aGF0IHlvdSBkaWQuIFdoYXQgcXVlc3Rpb25zIGRvZXMgeW91ciBwbG90IGFuc3dlcnMgKG9yIHBlcmhhcHMgd2hhdCBxdWVzdGlvbnMgZG9lcyB5b3VyIHBsb3QgcmFpc2UpPwoKMi4gSW4gdGhlIHNlY29uZCBwbG90LCB5b3UgbXVzdCAqKm1ha2UgYSBncmV5c2NhbGUgdmVyc2lvbiBvZiB5b3VyIGZpcnN0IHBsb3QhKiogQW5kIGFnYWluLCBpdCBtdXN0IGxvb2sgZ29vZCBhbmQgbWFrZSBzZW5zZS4KCjMuIEluIHRoZSB0aGlyZCBwbG90LCB5b3UgbXVzdCAqKnVzZSBjb2xvciBiYWRseSoqLiBNYWtlIGEgcGxvdCB3aGVyZSB0aGUgY29sb3JzIGFyZSBlaXRoZXIgcmVkdW5kYW50LCBjb25mdXNpbmcsIG9yIGp1c3QgZ2VuZXJhbGx5IG5vbi1zZW5zaWNhbC4gRXhwbGFpbiB3aHkgdGhpcyBsYXN0IHZpc3VhbGl6YXRpb24gZmFpbHMuCgogICAgCiAgICA8L2Rpdj4KPC9kaXY+CgpTb21lIGRhdGEgaWRlYXM6CgotIE1hY0FydGh1ci1CYXRlcyBDb21tdW5pY2F0aXZlIERldmVsb3BtZW50IEludmVudG9yeSAoTUItQ0RJKSwgYSBmYW1pbHkgb2YgcGFyZW50LXJlcG9ydCBxdWVzdGlvbm5haXJlcyBtZWFzdXJpbmcgY2hpbGRyZW4ncyB2b2NhYnVsYXJ5IHVuZGVyc3RhbmRpbmcgYW5kIHByb2R1Y3Rpb24KICAgIC0gW1IgcGFja2FnZSBgd29yZGJhbmtyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRiYW5rci9pbmRleC5odG1sKQogICAgLSBTZWUgbXkgY29kZSBbaGVyZV0oMDNhLW1lb3ctY2xlYW5pbmcuaHRtbCkKLSBQT1RVUyBFeGVjdXRpdmUgT3JkZXJzCiAgICAtIFtEYXRhXShodHRwczovL3d3dy5mZWRlcmFscmVnaXN0ZXIuZ292L2V4ZWN1dGl2ZS1vcmRlcnMpCiAgICAtIEZvbGxvdyBCb2IgUnVkaXMnIGNvZGUtdGhyb3VnaCBbaGVyZV0oaHR0cHM6Ly9ydWQuaXMvYi8yMDE4LzA0LzE4L2V4YW1pbmluZy1wb3R1cy1leGVjdXRpdmUtb3JkZXJzLykKLSBOYXRpb25hbCBFbGVjdHJvbmljIEluanVyeSBTdXJ2ZWlsbGFuY2UgU3lzdGVtIChORUlTUykKICAgIC0gW1IgcGFja2FnZSBgbmVpc3NgXShodHRwczovL2dpdGh1Yi5jb20vaGFkbGV5L25laXNzKQogICAgLSBGb2xsb3cgSnVsaWEgU2lsZ2UncyBjb2RlLXRocm91Z2ggW2hlcmVdKGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy95b3VyLWZsb29yLykKLSBGbGlnaHRzCiAgICAtIFtSIHBhY2thZ2UgYHBud2ZsaWdodHMxNGBdKGh0dHBzOi8vZ2l0aHViLmNvbS9pc21heWMvcG53ZmxpZ2h0czE0KQogICAgLSBbUiBwYWNrYWdlIGBueWNmbGlnaHRzMTNgXShodHRwczovL2dpdGh1Yi5jb20vaGFkbGV5L255Y2ZsaWdodHMxMykKLSBCdWlsZGluZyBQZXJtaXRzCiAgICAtIFNlZSBjb2RlIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vaGFkbGV5L2J1aWxkaW5nLXBlcm1pdHMpCiAgICAtIFdhdGNoIFlvdVR1YmUgY29kZS10aHJvdWdoIFtoZXJlXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWdvNUF1MDFKcnZzKQotIENvY2t0YWlsIEJhbGFuY2UKICAgIC0gW0RhdGFdKGh0dHBzOi8vZ2l0aHViLmNvbS9rYXJuZXNreS9jb2NrdGFpbC1iYWxhbmNlKQotIE5BU0EgV2VhdGhlcgogICAgLSBbRGF0YV0oaHR0cHM6Ly9naXRodWIuY29tL2hhZGxleS9uYXNhd2VhdGhlcikKLSBTb2NpYWwgU2VjdXJpdHkgQWRtaW5pc3RyYXRpb24gQmFieSBOYW1lcwogICAgLSBbUiBwYWNrYWdlIGBiYWJ5bmFtZXNgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvYmFieW5hbWVzL2luZGV4Lmh0bWwpCiAgICAtIEZvbGxvdyBKdWxpYSBTaWxnZSAqJ015IEJhYnkgQm9vbWVyIE5hbWUgTWlnaHQgSGF2ZSBCZWVuICJEZWJiaWUiJyo6IGh0dHBzOi8vanVsaWFzaWxnZS5jb20vYmxvZy9teS1iYWJ5LWJvb21lci1uYW1lLwogICAgLSBGb2xsb3cgSGlsYXJ5IFBhcmtlcjogKkhpbGFyeTogVGhlIE1vc3QgUG9pc29uZWQgQmFieSBOYW1lIGluIFVTIEhpc3RvcnkqOiBodHRwczovL2hpbGFyeXBhcmtlci5jb20vMjAxMy8wMS8zMC9oaWxhcnktdGhlLW1vc3QtcG9pc29uZWQtYmFieS1uYW1lLWluLXVzLWhpc3RvcnkvCi0gWW91dGggQmVoYXZpb3IgUmlzayBTdXJ2ZWlsbGFuY2UgU3lzdGVtCiAgICAtIFtSIHBhY2thZ2UgYHlyYnNzYF0oaHR0cHM6Ly9naXRodWIuY29tL2hhZGxleS95cmJzcykKICAgIC0gU29tZSBbZ29vZCBpZGVhcyBoZXJlXShodHRwczovL3d3dy5jZGMuZ292L25jaGhzdHAvbmV3c3Jvb20vMjAxMi95cmJzLWdyYXBoaWNzMjAxMi5odG1sKQotIEd1biBzYWxlcwogICAgLSBbRGF0YV0oaHR0cHM6Ly9naXRodWIuY29tL2hhZGxleS9ndW4tc2FsZXMpCiAgICAtIFNvbWUgW2V4YW1wbGUgcGxvdHMgaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL05ZVGltZXMvZ3Vuc2FsZXMvYmxvYi9tYXN0ZXIvb3V0L3Bsb3RzLnBkZikKLSBHYXBtaW5kZXIKICAgIC0gW1IgcGFja2FnZSBgZ2FwbWluZGVyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dhcG1pbmRlci9pbmRleC5odG1sKQogICAgLSBTb21lIFtleGFtcGxlIHBsb3RzIGhlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2dncGxvdDItdHV0b3JpYWwvYmxvYi9tYXN0ZXIvZ2FwbWluZGVyLWdncGxvdDItc2NhdHRlcnBsb3QubWQpCgoKCgo=

Creative Commons License