1 Introduction

Ever wanted to make your figures more interactive?

Today we are going to talk about making interactive plots using Plotly. Plotly in a variety of programming languages, but today we will be just talking about using it in R. All of the plotly documentation can be found here.

If you have not already installed plotly, please do so here.

install.packages("plotly")

Here are some useful links to find info about using ggplotly.

Before we start, there are two basic ways to use plot in R using plotly:

  • Using ggplotly() - this is what we will go over today because it has the same syntax as ggplot() which we have already learned
  • Using plot_ly() - there is slightly more functionality in this function, but the syntax is all new, so I’d suggest if you can do what you want with ggplotly(), do that. The syntax is not particularly hard so don’t be scared to use it if interactive plots are something you’re very interested in.

When you are googling about using plotly, you will find a combination of ggplotly() and plot_ly() approaches, and some parts of the code are interchangable. The easiesy way to see which parts are, is to try.

Also note, Google gets a bit confused when googling “ggplotly” and often returns information about just ggplot, so read extra carefully when problem solving.

2 Load libraries

library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.3.3     ✓ purrr   0.3.4
## ✓ tibble  3.1.0     ✓ dplyr   1.0.5
## ✓ tidyr   1.1.3     ✓ stringr 1.4.0
## ✓ readr   1.4.0     ✓ forcats 0.5.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(plotly) # for making interactive plots
## 
## Attaching package: 'plotly'
## The following object is masked from 'package:ggplot2':
## 
##     last_plot
## The following object is masked from 'package:stats':
## 
##     filter
## The following object is masked from 'package:graphics':
## 
##     layout
library(htmlwidgets) # for saving html files
library(palmerpenguins) # for our penguins data

Let’s look at penguins_raw this time, a df that has a bit more data than the penguins df.

head(penguins_raw)
## # A tibble: 6 x 17
##   studyName `Sample Number` Species       Region Island  Stage   `Individual ID`
##   <chr>               <dbl> <chr>         <chr>  <chr>   <chr>   <chr>          
## 1 PAL0708                 1 Adelie Pengu… Anvers Torger… Adult,… N1A1           
## 2 PAL0708                 2 Adelie Pengu… Anvers Torger… Adult,… N1A2           
## 3 PAL0708                 3 Adelie Pengu… Anvers Torger… Adult,… N2A1           
## 4 PAL0708                 4 Adelie Pengu… Anvers Torger… Adult,… N2A2           
## 5 PAL0708                 5 Adelie Pengu… Anvers Torger… Adult,… N3A1           
## 6 PAL0708                 6 Adelie Pengu… Anvers Torger… Adult,… N3A2           
## # … with 10 more variables: Clutch Completion <chr>, Date Egg <date>,
## #   Culmen Length (mm) <dbl>, Culmen Depth (mm) <dbl>,
## #   Flipper Length (mm) <dbl>, Body Mass (g) <dbl>, Sex <chr>,
## #   Delta 15 N (o/oo) <dbl>, Delta 13 C (o/oo) <dbl>, Comments <chr>
head(penguins)
## # A tibble: 6 x 8
##   species island bill_length_mm bill_depth_mm flipper_length_… body_mass_g sex  
##   <fct>   <fct>           <dbl>         <dbl>            <int>       <int> <fct>
## 1 Adelie  Torge…           39.1          18.7              181        3750 male 
## 2 Adelie  Torge…           39.5          17.4              186        3800 fema…
## 3 Adelie  Torge…           40.3          18                195        3250 fema…
## 4 Adelie  Torge…           NA            NA                 NA          NA <NA> 
## 5 Adelie  Torge…           36.7          19.3              193        3450 fema…
## 6 Adelie  Torge…           39.3          20.6              190        3650 male 
## # … with 1 more variable: year <int>
bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`)) +
  geom_point()

bill_depth_length
## Warning: Removed 2 rows containing missing values (geom_point).

Making plot interactive.

You can learn more about the ggplotly() function, including its arguments here.

ggplotly(bill_depth_length)

Wow. That was easy!

Adding a title, and changing the theme.

bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`)) +
  geom_point() +
  theme_minimal() +
  labs(title = "Understanding Penguin Bill Dimensions")

ggplotly(bill_depth_length)

2.1 Using tooltip

What if we want to hover over each point and be able to tell which Island the penguin was found on?

bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`,
             text = Island)) +
  geom_point() +
  theme_minimal() +
  labs(title = "Understanding Penguin Bill Dimensions")

ggplotly(bill_depth_length,
         tooltip = "text")

To be able to hover with a variable, it has to be indicated someplace in your ggplot2 call.

bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`,
             text = Island, group = `Individual ID`)) +
  geom_point() +
  theme_minimal() +
  labs(title = "Understanding Penguin Bill Dimensions")

ggplotly(bill_depth_length,
         tooltip = c("text", "Individual ID")) # hover test will be in this order

You can use paste to add some information you’d like to see in each of the hover texts, here, we are indicating Island: Island

bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`,
             text = paste("Island:", Island))) +
  geom_point() +
  theme_minimal() +
  labs(title = "Understanding Penguin Bill Dimensions")

ggplotly(bill_depth_length,
         tooltip = "text")

2.1.1 Hover label aesthetics

Changing hover label aesthetics and the fonts of your plot.

# setting fonts for the plot
font <- list(
  family = "Roboto Condensed",
  size = 15,
  color = "white")

# setting hover label specs
label <- list(
  bgcolor = "#FF0000",
  bordercolor = "transparent",
  font = font) # we can do this bc we already set font

# plotting like normal
bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`,
             text = paste("Island:", Island))) +
  geom_point() +
  theme_minimal() +
  labs(title = "A Deep Dive (ha) Into \nUnderstanding Penguin Bill Dimensions")
# use\n to bring your text to another line

# amending our ggplotly call to include new fonts and hover label specs
ggplotly(bill_depth_length, tooltip = "text") %>%
  style(hoverlabel = label) %>%
  layout(font = font)

2.2 Dynamic ticks

Keep your axis labels so when you zoom, you can see where you are on your plot.

ggplotly(bill_depth_length,
         tooltip = "text",
         dynamicTicks = TRUE)

Don’t forget you can use things like faceting, that we have gone over previously in Session 10.

bill_depth_length <- penguins %>%
  ggplot(aes(x = bill_length_mm, y = bill_depth_mm, color = species,
             text = paste("Island:", island))) +
  geom_point() +
  theme_minimal() +
  theme(legend.position = "none") +
  labs(title = "Understanding Penguin Bill Dimensions",
       x = "Culmen Bill Length (mm)",
       y = "Culmen Bill Depth (mm)") +
  facet_wrap(~species)

ggplotly(bill_depth_length,
         tooltip = "text")

2.3 Animating

Add frame in your aesthetics mapping to tell plotly what column to animate over.

# add frame
bill_depth_length <- penguins_raw %>%
  ggplot(aes(x = `Culmen Length (mm)`, y = `Culmen Depth (mm)`,
             frame = Island, text = `Individual ID`)) +
  geom_point() +
  theme_minimal() +
  labs(title = "Understanding Penguin Bill Dimensions")

ggplotly(bill_depth_length,
         tooltip = "text")

2.4 Saving your plots

Assign the plot you want to save to an object, and use the function saveWidget() to save it. You can find the documentation here.

# assign ggplotly plot to an object
ggplotly_to_save <- ggplotly(bill_depth_length,
                             tooltip = "text")

# save
saveWidget(widget = ggplotly_to_save,
           file = "ggplotlying.html")

3 Breakout rooms

Back to birds. Let’s re-create the birds joined dataset from the end of Session 3 on joining.

# create directory for data to go
dir.create('data/birds/', recursive = TRUE)
## Warning in dir.create("data/birds/", recursive = TRUE): 'data/birds' already
## exists
# preparing to download
# denote bird file url
birds_url <-
'https://raw.githubusercontent.com/biodash/biodash.github.io/master/assets/data/birds/backyard-birds_Ohio.tsv'
# denote file name
birds_file <- 'data/birds/backyard-birds_Ohio.tsv'

# get file
download.file(url = birds_url, 
              destfile = birds_file)

# read in birds data
birds <- read_tsv(file = 'data/birds/backyard-birds_Ohio.tsv')
## 
## ── Column specification ────────────────────────────────────────────────────────
## cols(
##   class = col_character(),
##   order = col_character(),
##   family = col_character(),
##   genus = col_character(),
##   species = col_character(),
##   locality = col_character(),
##   stateProvince = col_character(),
##   decimalLatitude = col_double(),
##   decimalLongitude = col_double(),
##   eventDate = col_datetime(format = ""),
##   species_en = col_character(),
##   range = col_character()
## )

Look at your new df.

head(birds)
## # A tibble: 6 x 12
##   class order   family  genus  species   locality  stateProvince decimalLatitude
##   <chr> <chr>   <chr>   <chr>  <chr>     <chr>     <chr>                   <dbl>
## 1 Aves  Passer… Corvid… Cyano… Cyanocit… 44805 As… Ohio                     40.9
## 2 Aves  Passer… Corvid… Cyano… Cyanocit… 45244 Ci… Ohio                     39.1
## 3 Aves  Passer… Corvid… Cyano… Cyanocit… 44132 Eu… Ohio                     41.6
## 4 Aves  Passer… Corvid… Cyano… Cyanocit… 45242 Ci… Ohio                     39.2
## 5 Aves  Passer… Corvid… Cyano… Cyanocit… 45246 Ci… Ohio                     39.3
## 6 Aves  Passer… Corvid… Cyano… Cyanocit… 44484 Wa… Ohio                     41.2
## # … with 4 more variables: decimalLongitude <dbl>, eventDate <dttm>,
## #   species_en <chr>, range <chr>
dim(birds)
## [1] 311441     12

3.1 Exercise 1

Filter your new birds df to only inclue Blue Jays. Check to see how many bald eagle sightings there were in Ohio.

bald_eagle <- birds %>%
  filter(species_en == "Bald Eagle")

# what do we have?
head(bald_eagle)
## # A tibble: 6 x 12
##   class order   family  genus  species   locality  stateProvince decimalLatitude
##   <chr> <chr>   <chr>   <chr>  <chr>     <chr>     <chr>                   <dbl>
## 1 Aves  Accipi… Accipi… Halia… Haliaeet… Mentor    Ohio                     41.7
## 2 Aves  Accipi… Accipi… Halia… Haliaeet… 45742 Li… Ohio                     39.3
## 3 Aves  Accipi… Accipi… Halia… Haliaeet… Moreland… Ohio                     41.4
## 4 Aves  Accipi… Accipi… Halia… Haliaeet… Eastlake  Ohio                     41.7
## 5 Aves  Accipi… Accipi… Halia… Haliaeet… 44060 Me… Ohio                     41.7
## 6 Aves  Accipi… Accipi… Halia… Haliaeet… 44839 Hu… Ohio                     41.4
## # … with 4 more variables: decimalLongitude <dbl>, eventDate <dttm>,
## #   species_en <chr>, range <chr>
# check our df dimensions
dim(bald_eagle)
## [1] 381  12

3.2 Exercise 2

Create a map that plots all the bald eagles found around Ohio. Color the points blue. Make sure the aspect ratio of Ohio looks reasonable to you.

library(maps)
## 
## Attaching package: 'maps'
## The following object is masked from 'package:purrr':
## 
##     map
# get map of the states
states <- map_data("state")

# filter states to only include ohio
ohio <- states %>%
  filter(region == "ohio")

# plot
ggplot(data = ohio,
       aes(x = long, y = lat, group = group)) +
  geom_polygon(color = "black", fill = "white") +   
  geom_point(data = bald_eagle,                 
             aes(x = decimalLongitude, y = decimalLatitude, group = NULL),
             color = "blue", alpha = 0.2) +
  coord_fixed(1.2) +
  labs(title = 'Bald Eagles round Ohio')

3.3 Exercise 3

Make your plot interactive so you can hover and and see the locality of each bald eagle observation.

bald_eagles_ohio <- 
  ggplot(data = ohio,
         aes(x = long, y = lat, group = group)) +
  geom_polygon(color = "black", fill = "white") +   
  geom_point(data = bald_eagle,                 
             aes(x = decimalLongitude, y = decimalLatitude, group = NULL,
                 text = locality),
             color = "blue", alpha = 0.2) +
  coord_fixed(1.2) +
  labs(title = 'Bald Eagles Around Ohio')
## Warning: Ignoring unknown aesthetics: text
ggplotly(bald_eagles_ohio,
         tooltip = "text")

3.4 Exercise 4

Change the hover text so that the background color is red, clean up your axis labels, and make all the fonts for the plot Arial.

# setting fonts for the plot
eagle_font <- list(
  family = "Arial",
  size = 15,
  color = "white")

# setting hover label specs
eagle_label <- list(
  bgcolor = "red",
  bordercolor = "transparent",
  font = eagle_font) # we can do this bc we already set font

bald_eagles_ohio <- 
  ggplot(data = ohio,
         aes(x = long, y = lat, group = group)) +
  geom_polygon(color = "black", fill = "white") +   
  geom_point(data = bald_eagle,                 
             aes(x = decimalLongitude, y = decimalLatitude, group = NULL,
                 text = locality),
             color = "blue", alpha = 0.2) +
  coord_fixed(1.2) +
  labs(title = 'Bald Eagles around the Ohio',
       x = "Latitude",
       y = "Longitude")
## Warning: Ignoring unknown aesthetics: text
# amending our ggplotly call to include new fonts and hover label specs
ggplotly(bald_eagles_ohio, tooltip = "text") %>%
  style(hoverlabel = eagle_label) %>%
  layout(font = eagle_font)

4 Bonus

4.1 Bonus 1

Let’s go back to the Gapminder data we looked at in the instructional part of Session 10 on faceting, animating, and multi-plotting.

# install.packages("gapminder") # if you weren't at Session 10
library(gapminder)
head(gapminder)
## # A tibble: 6 x 6
##   country     continent  year lifeExp      pop gdpPercap
##   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
## 1 Afghanistan Asia       1952    28.8  8425333      779.
## 2 Afghanistan Asia       1957    30.3  9240934      821.
## 3 Afghanistan Asia       1962    32.0 10267083      853.
## 4 Afghanistan Asia       1967    34.0 11537966      836.
## 5 Afghanistan Asia       1972    36.1 13079460      740.
## 6 Afghanistan Asia       1977    38.4 14880372      786.

Make a bubble-style plot that shows the life expectancy vs. GDP per capita over 1952 to 2007 for all countries. Color by continent, and indicate population by size. Use your knowledge of making plots to alter it such that you think it is descriptive and aesthetic.

gapminder_font <- list(
  family = "Roboto Condensed")

gapminder_bubble <- gapminder %>%
  ggplot(aes(x = gdpPercap, y = lifeExp, 
             fill = continent, size = pop, 
             text = paste(
               "Country:", country,
               "\nLife expectancy:", round(lifeExp,1),
               "\nGDP per capita:", round(gdpPercap,0)))) +
  geom_point(aes(frame = year), color = "black", shape = 21, stroke = 0.2) +
  scale_x_log10() +
  theme_minimal() +
  theme(plot.title = element_text(size = 18)) +
  labs(title = "Changing Life Expectancy and GDP Per Capita Worldwide \nFrom 1952 to 2007",
       x = "GDP per capita (in International Dollars)",
       y = "Life Expectancy (years)",
       fill = "",
       size = "")
## Warning: Ignoring unknown aesthetics: frame
ggplotly(gapminder_bubble, 
         tooltip = c("text")) %>%
  layout(font = gapminder_font)
LS0tCnRpdGxlOiAiUGxvdGx5IgphdXRob3I6ICJKZXNzaWNhIENvb3BlcnN0b25lIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRoZW1lOiBmbGF0bHkKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgojIEludHJvZHVjdGlvbgpFdmVyIHdhbnRlZCB0byBtYWtlIHlvdXIgZmlndXJlcyBtb3JlIGludGVyYWN0aXZlPwoKVG9kYXkgd2UgYXJlIGdvaW5nIHRvIHRhbGsgYWJvdXQgbWFraW5nIGludGVyYWN0aXZlIHBsb3RzIHVzaW5nIFtQbG90bHldKGh0dHBzOi8vcGxvdGx5LmNvbS8pLiAgUGxvdGx5IGluIGEgdmFyaWV0eSBvZiBwcm9ncmFtbWluZyBsYW5ndWFnZXMsIGJ1dCB0b2RheSB3ZSB3aWxsIGJlIGp1c3QgdGFsa2luZyBhYm91dCB1c2luZyBpdCBpbiBbUl0oaHR0cHM6Ly9wbG90bHkuY29tL3IvKS4gIEFsbCBvZiB0aGUgcGxvdGx5IGRvY3VtZW50YXRpb24gY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcGxvdGx5L3Bsb3RseS5wZGYpLiAgCgpJZiB5b3UgaGF2ZSBub3QgYWxyZWFkeSBpbnN0YWxsZWQgcGxvdGx5LCBwbGVhc2UgZG8gc28gaGVyZS4KYGBge3IsIGV2YWwgPSBGQUxTRX0KaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikKYGBgCgpIZXJlIGFyZSBzb21lIHVzZWZ1bCBsaW5rcyB0byBmaW5kIGluZm8gYWJvdXQgdXNpbmcgYGdncGxvdGx5YC4KCiogW0Jhc2ljIGdncGxvdDIgY2hhcnRzXShodHRwczovL3Bsb3RseS5jb20vZ2dwbG90Mi8jbGF5b3V0LW9wdGlvbnMpCiogW1Bsb3RseSBSIGxpYnJhcnkgZnVuZGFtZW50YWxzXShodHRwczovL3Bsb3RseS5jb20vci9wbG90bHktZnVuZGFtZW50YWxzLykKKiBbSW50cm8gdG8gYGdncGxvdGx5KClgXShodHRwczovL3Bsb3RseS1yLmNvbS9vdmVydmlldy5odG1sI2ludHJvLWdncGxvdGx5KQoqIFtVc2luZyBgbGF5b3V0KClgXShodHRwczovL3Bsb3RseS5jb20vci9yZWZlcmVuY2UvbGF5b3V0LyMpCiogW2BnZ3Bsb3RseSgpYCB0b29sdGlwc10oaHR0cHM6Ly9wbG90bHktci5jb20vY29udHJvbGxpbmctdG9vbHRpcHMuaHRtbCN0b29sdGlwLXRleHQtZ2dwbG90bHkpCgpCZWZvcmUgd2Ugc3RhcnQsIHRoZXJlIGFyZSB0d28gYmFzaWMgd2F5cyB0byB1c2UgcGxvdCBpbiBSIHVzaW5nIHBsb3RseToKCiogVXNpbmcgW2BnZ3Bsb3RseSgpYF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3Bsb3RseS92ZXJzaW9ucy80LjkuMy90b3BpY3MvZ2dwbG90bHkpIC0gdGhpcyBpcyB3aGF0IHdlIHdpbGwgZ28gb3ZlciB0b2RheSBiZWNhdXNlIGl0IGhhcyB0aGUgc2FtZSBzeW50YXggYXMgYGdncGxvdCgpYCB3aGljaCB3ZSBoYXZlIGFscmVhZHkgbGVhcm5lZAoqIFVzaW5nIFtgcGxvdF9seSgpYF0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3Bsb3RseS92ZXJzaW9ucy80LjkuMy90b3BpY3MvcGxvdF9seSkgLSB0aGVyZSBpcyBzbGlnaHRseSBtb3JlIGZ1bmN0aW9uYWxpdHkgaW4gdGhpcyBmdW5jdGlvbiwgYnV0IHRoZSBzeW50YXggaXMgYWxsIG5ldywgc28gSSdkIHN1Z2dlc3QgaWYgeW91IGNhbiBkbyB3aGF0IHlvdSB3YW50IHdpdGggYGdncGxvdGx5KClgLCBkbyB0aGF0LiAgVGhlIHN5bnRheCBpcyBub3QgcGFydGljdWxhcmx5IGhhcmQgc28gZG9uJ3QgYmUgc2NhcmVkIHRvIHVzZSBpdCBpZiBpbnRlcmFjdGl2ZSBwbG90cyBhcmUgc29tZXRoaW5nIHlvdSdyZSB2ZXJ5IGludGVyZXN0ZWQgaW4uCgpXaGVuIHlvdSBhcmUgZ29vZ2xpbmcgYWJvdXQgdXNpbmcgcGxvdGx5LCB5b3Ugd2lsbCBmaW5kIGEgY29tYmluYXRpb24gb2YgYGdncGxvdGx5KClgIGFuZCBgcGxvdF9seSgpYCBhcHByb2FjaGVzLCBhbmQgc29tZSBwYXJ0cyBvZiB0aGUgY29kZSBhcmUgaW50ZXJjaGFuZ2FibGUuICBUaGUgZWFzaWVzeSB3YXkgdG8gc2VlIHdoaWNoIHBhcnRzIGFyZSwgaXMgdG8gdHJ5LgoKQWxzbyBub3RlLCBHb29nbGUgZ2V0cyBhIGJpdCBjb25mdXNlZCB3aGVuIGdvb2dsaW5nICJnZ3Bsb3RseSIgYW5kIG9mdGVuIHJldHVybnMgaW5mb3JtYXRpb24gYWJvdXQganVzdCBnZ3Bsb3QsIHNvIHJlYWQgZXh0cmEgY2FyZWZ1bGx5IHdoZW4gcHJvYmxlbSBzb2x2aW5nLgoKIyBMb2FkIGxpYnJhcmllcwpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KSAjIGZvciBtYWtpbmcgaW50ZXJhY3RpdmUgcGxvdHMKbGlicmFyeShodG1sd2lkZ2V0cykgIyBmb3Igc2F2aW5nIGh0bWwgZmlsZXMKbGlicmFyeShwYWxtZXJwZW5ndWlucykgIyBmb3Igb3VyIHBlbmd1aW5zIGRhdGEKYGBgCgpMZXQncyBsb29rIGF0IGBwZW5ndWluc19yYXdgIHRoaXMgdGltZSwgYSBkZiB0aGF0IGhhcyBhIGJpdCBtb3JlIGRhdGEgdGhhbiB0aGUgYHBlbmd1aW5zYCBkZi4KYGBge3J9CmhlYWQocGVuZ3VpbnNfcmF3KQpoZWFkKHBlbmd1aW5zKQpgYGAKCmBgYHtyfQpiaWxsX2RlcHRoX2xlbmd0aCA8LSBwZW5ndWluc19yYXcgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYEN1bG1lbiBMZW5ndGggKG1tKWAsIHkgPSBgQ3VsbWVuIERlcHRoIChtbSlgKSkgKwogIGdlb21fcG9pbnQoKQoKYmlsbF9kZXB0aF9sZW5ndGgKYGBgCgpNYWtpbmcgcGxvdCBpbnRlcmFjdGl2ZS4KCllvdSBjYW4gbGVhcm4gbW9yZSBhYm91dCB0aGUgYGdncGxvdGx5KClgIGZ1bmN0aW9uLCBpbmNsdWRpbmcgaXRzIGFyZ3VtZW50cyAgW2hlcmVdKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9wbG90bHkvdmVyc2lvbnMvNC45LjMvdG9waWNzL2dncGxvdGx5KS4KYGBge3J9CmdncGxvdGx5KGJpbGxfZGVwdGhfbGVuZ3RoKQpgYGAKCldvdy4gIFRoYXQgd2FzIGVhc3khCgpBZGRpbmcgYSB0aXRsZSwgYW5kIGNoYW5naW5nIHRoZSB0aGVtZS4KYGBge3J9CmJpbGxfZGVwdGhfbGVuZ3RoIDwtIHBlbmd1aW5zX3JhdyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBgQ3VsbWVuIExlbmd0aCAobW0pYCwgeSA9IGBDdWxtZW4gRGVwdGggKG1tKWApKSArCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSAiVW5kZXJzdGFuZGluZyBQZW5ndWluIEJpbGwgRGltZW5zaW9ucyIpCgpnZ3Bsb3RseShiaWxsX2RlcHRoX2xlbmd0aCkKYGBgCgojIyBVc2luZyB0b29sdGlwCldoYXQgaWYgd2Ugd2FudCB0byBob3ZlciBvdmVyIGVhY2ggcG9pbnQgYW5kIGJlIGFibGUgdG8gdGVsbCB3aGljaCBgSXNsYW5kYCB0aGUgcGVuZ3VpbiB3YXMgZm91bmQgb24/CmBgYHtyfQpiaWxsX2RlcHRoX2xlbmd0aCA8LSBwZW5ndWluc19yYXcgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYEN1bG1lbiBMZW5ndGggKG1tKWAsIHkgPSBgQ3VsbWVuIERlcHRoIChtbSlgLAogICAgICAgICAgICAgdGV4dCA9IElzbGFuZCkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9ICJVbmRlcnN0YW5kaW5nIFBlbmd1aW4gQmlsbCBEaW1lbnNpb25zIikKCmdncGxvdGx5KGJpbGxfZGVwdGhfbGVuZ3RoLAogICAgICAgICB0b29sdGlwID0gInRleHQiKQpgYGAKVG8gYmUgYWJsZSB0byBob3ZlciB3aXRoIGEgdmFyaWFibGUsIGl0IGhhcyB0byBiZSBpbmRpY2F0ZWQgc29tZXBsYWNlIGluIHlvdXIgZ2dwbG90MiBjYWxsLgpgYGB7cn0KYmlsbF9kZXB0aF9sZW5ndGggPC0gcGVuZ3VpbnNfcmF3ICU+JQogIGdncGxvdChhZXMoeCA9IGBDdWxtZW4gTGVuZ3RoIChtbSlgLCB5ID0gYEN1bG1lbiBEZXB0aCAobW0pYCwKICAgICAgICAgICAgIHRleHQgPSBJc2xhbmQsIGdyb3VwID0gYEluZGl2aWR1YWwgSURgKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIlVuZGVyc3RhbmRpbmcgUGVuZ3VpbiBCaWxsIERpbWVuc2lvbnMiKQoKZ2dwbG90bHkoYmlsbF9kZXB0aF9sZW5ndGgsCiAgICAgICAgIHRvb2x0aXAgPSBjKCJ0ZXh0IiwgIkluZGl2aWR1YWwgSUQiKSkgIyBob3ZlciB0ZXN0IHdpbGwgYmUgaW4gdGhpcyBvcmRlcgpgYGAKCllvdSBjYW4gdXNlIGBwYXN0ZWAgdG8gYWRkIHNvbWUgaW5mb3JtYXRpb24geW91J2QgbGlrZSB0byBzZWUgaW4gZWFjaCBvZiB0aGUgaG92ZXIgdGV4dHMsIGhlcmUsIHdlIGFyZSBpbmRpY2F0aW5nIElzbGFuZDogYElzbGFuZGAKYGBge3J9CmJpbGxfZGVwdGhfbGVuZ3RoIDwtIHBlbmd1aW5zX3JhdyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBgQ3VsbWVuIExlbmd0aCAobW0pYCwgeSA9IGBDdWxtZW4gRGVwdGggKG1tKWAsCiAgICAgICAgICAgICB0ZXh0ID0gcGFzdGUoIklzbGFuZDoiLCBJc2xhbmQpKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIlVuZGVyc3RhbmRpbmcgUGVuZ3VpbiBCaWxsIERpbWVuc2lvbnMiKQoKZ2dwbG90bHkoYmlsbF9kZXB0aF9sZW5ndGgsCiAgICAgICAgIHRvb2x0aXAgPSAidGV4dCIpCmBgYAoKIyMjIEhvdmVyIGxhYmVsIGFlc3RoZXRpY3MKQ2hhbmdpbmcgaG92ZXIgbGFiZWwgYWVzdGhldGljcyBhbmQgdGhlIGZvbnRzIG9mIHlvdXIgcGxvdC4KYGBge3J9CiMgc2V0dGluZyBmb250cyBmb3IgdGhlIHBsb3QKZm9udCA8LSBsaXN0KAogIGZhbWlseSA9ICJSb2JvdG8gQ29uZGVuc2VkIiwKICBzaXplID0gMTUsCiAgY29sb3IgPSAid2hpdGUiKQoKIyBzZXR0aW5nIGhvdmVyIGxhYmVsIHNwZWNzCmxhYmVsIDwtIGxpc3QoCiAgYmdjb2xvciA9ICIjRkYwMDAwIiwKICBib3JkZXJjb2xvciA9ICJ0cmFuc3BhcmVudCIsCiAgZm9udCA9IGZvbnQpICMgd2UgY2FuIGRvIHRoaXMgYmMgd2UgYWxyZWFkeSBzZXQgZm9udAoKIyBwbG90dGluZyBsaWtlIG5vcm1hbApiaWxsX2RlcHRoX2xlbmd0aCA8LSBwZW5ndWluc19yYXcgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYEN1bG1lbiBMZW5ndGggKG1tKWAsIHkgPSBgQ3VsbWVuIERlcHRoIChtbSlgLAogICAgICAgICAgICAgdGV4dCA9IHBhc3RlKCJJc2xhbmQ6IiwgSXNsYW5kKSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicyh0aXRsZSA9ICJBIERlZXAgRGl2ZSAoaGEpIEludG8gXG5VbmRlcnN0YW5kaW5nIFBlbmd1aW4gQmlsbCBEaW1lbnNpb25zIikKIyB1c2VcbiB0byBicmluZyB5b3VyIHRleHQgdG8gYW5vdGhlciBsaW5lCgojIGFtZW5kaW5nIG91ciBnZ3Bsb3RseSBjYWxsIHRvIGluY2x1ZGUgbmV3IGZvbnRzIGFuZCBob3ZlciBsYWJlbCBzcGVjcwpnZ3Bsb3RseShiaWxsX2RlcHRoX2xlbmd0aCwgdG9vbHRpcCA9ICJ0ZXh0IikgJT4lCiAgc3R5bGUoaG92ZXJsYWJlbCA9IGxhYmVsKSAlPiUKICBsYXlvdXQoZm9udCA9IGZvbnQpCmBgYAojIyBEeW5hbWljIHRpY2tzCktlZXAgeW91ciBheGlzIGxhYmVscyBzbyB3aGVuIHlvdSB6b29tLCB5b3UgY2FuIHNlZSB3aGVyZSB5b3UgYXJlIG9uIHlvdXIgcGxvdC4KYGBge3J9CmdncGxvdGx5KGJpbGxfZGVwdGhfbGVuZ3RoLAogICAgICAgICB0b29sdGlwID0gInRleHQiLAogICAgICAgICBkeW5hbWljVGlja3MgPSBUUlVFKQpgYGAKCgpEb24ndCBmb3JnZXQgeW91IGNhbiB1c2UgdGhpbmdzIGxpa2UgZmFjZXRpbmcsIHRoYXQgd2UgaGF2ZSBnb25lIG92ZXIgcHJldmlvdXNseSBpbiBbU2Vzc2lvbiAxMF0oaHR0cHM6Ly9iaW9kYXNoLmdpdGh1Yi5pby9jb2RlY2x1Yi8xMF9mYWNldGluZy1hbmltYXRpbmcvKS4KYGBge3J9CmJpbGxfZGVwdGhfbGVuZ3RoIDwtIHBlbmd1aW5zICU+JQogIGdncGxvdChhZXMoeCA9IGJpbGxfbGVuZ3RoX21tLCB5ID0gYmlsbF9kZXB0aF9tbSwgY29sb3IgPSBzcGVjaWVzLAogICAgICAgICAgICAgdGV4dCA9IHBhc3RlKCJJc2xhbmQ6IiwgaXNsYW5kKSkpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgbGFicyh0aXRsZSA9ICJVbmRlcnN0YW5kaW5nIFBlbmd1aW4gQmlsbCBEaW1lbnNpb25zIiwKICAgICAgIHggPSAiQ3VsbWVuIEJpbGwgTGVuZ3RoIChtbSkiLAogICAgICAgeSA9ICJDdWxtZW4gQmlsbCBEZXB0aCAobW0pIikgKwogIGZhY2V0X3dyYXAofnNwZWNpZXMpCgpnZ3Bsb3RseShiaWxsX2RlcHRoX2xlbmd0aCwKICAgICAgICAgdG9vbHRpcCA9ICJ0ZXh0IikKYGBgCgojIyBBbmltYXRpbmcKQWRkIGBmcmFtZWAgaW4geW91ciBhZXN0aGV0aWNzIG1hcHBpbmcgdG8gdGVsbCBwbG90bHkgd2hhdCBjb2x1bW4gdG8gYW5pbWF0ZSBvdmVyLgpgYGB7cn0KIyBhZGQgZnJhbWUKYmlsbF9kZXB0aF9sZW5ndGggPC0gcGVuZ3VpbnNfcmF3ICU+JQogIGdncGxvdChhZXMoeCA9IGBDdWxtZW4gTGVuZ3RoIChtbSlgLCB5ID0gYEN1bG1lbiBEZXB0aCAobW0pYCwKICAgICAgICAgICAgIGZyYW1lID0gSXNsYW5kLCB0ZXh0ID0gYEluZGl2aWR1YWwgSURgKSkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIlVuZGVyc3RhbmRpbmcgUGVuZ3VpbiBCaWxsIERpbWVuc2lvbnMiKQoKZ2dwbG90bHkoYmlsbF9kZXB0aF9sZW5ndGgsCiAgICAgICAgIHRvb2x0aXAgPSAidGV4dCIpCmBgYAoKIyMgU2F2aW5nIHlvdXIgcGxvdHMKQXNzaWduIHRoZSBwbG90IHlvdSB3YW50IHRvIHNhdmUgdG8gYW4gb2JqZWN0LCBhbmQgdXNlIHRoZSBmdW5jdGlvbiBgc2F2ZVdpZGdldCgpYCB0byBzYXZlIGl0LiAgWW91IGNhbiBmaW5kIHRoZSBkb2N1bWVudGF0aW9uIFtoZXJlXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvaHRtbHdpZGdldHMvdmVyc2lvbnMvMS41LjMvdG9waWNzL3NhdmVXaWRnZXQpLgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojIGFzc2lnbiBnZ3Bsb3RseSBwbG90IHRvIGFuIG9iamVjdApnZ3Bsb3RseV90b19zYXZlIDwtIGdncGxvdGx5KGJpbGxfZGVwdGhfbGVuZ3RoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvb2x0aXAgPSAidGV4dCIpCgojIHNhdmUKc2F2ZVdpZGdldCh3aWRnZXQgPSBnZ3Bsb3RseV90b19zYXZlLAogICAgICAgICAgIGZpbGUgPSAiZ2dwbG90bHlpbmcuaHRtbCIpCmBgYAoKCiMgQnJlYWtvdXQgcm9vbXMKQmFjayB0byBiaXJkcy4gIExldCdzIHJlLWNyZWF0ZSB0aGUgYmlyZHMgam9pbmVkIGRhdGFzZXQgZnJvbSB0aGUgZW5kIG9mIFtTZXNzaW9uIDNdKGh0dHBzOi8vYmlvZGFzaC5naXRodWIuaW8vY29kZWNsdWIvczAzX2pvaW5pbmctZGF0YXNldHMvI2V4ZXJjaXNlLTcpIG9uIGpvaW5pbmcuCgpgYGB7cn0KIyBjcmVhdGUgZGlyZWN0b3J5IGZvciBkYXRhIHRvIGdvCmRpci5jcmVhdGUoJ2RhdGEvYmlyZHMvJywgcmVjdXJzaXZlID0gVFJVRSkKCiMgcHJlcGFyaW5nIHRvIGRvd25sb2FkCiMgZGVub3RlIGJpcmQgZmlsZSB1cmwKYmlyZHNfdXJsIDwtCidodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYmlvZGFzaC9iaW9kYXNoLmdpdGh1Yi5pby9tYXN0ZXIvYXNzZXRzL2RhdGEvYmlyZHMvYmFja3lhcmQtYmlyZHNfT2hpby50c3YnCiMgZGVub3RlIGZpbGUgbmFtZQpiaXJkc19maWxlIDwtICdkYXRhL2JpcmRzL2JhY2t5YXJkLWJpcmRzX09oaW8udHN2JwoKIyBnZXQgZmlsZQpkb3dubG9hZC5maWxlKHVybCA9IGJpcmRzX3VybCwgCiAgICAgICAgICAgICAgZGVzdGZpbGUgPSBiaXJkc19maWxlKQoKIyByZWFkIGluIGJpcmRzIGRhdGEKYmlyZHMgPC0gcmVhZF90c3YoZmlsZSA9ICdkYXRhL2JpcmRzL2JhY2t5YXJkLWJpcmRzX09oaW8udHN2JykKYGBgCgpMb29rIGF0IHlvdXIgbmV3IGRmLgpgYGB7cn0KaGVhZChiaXJkcykKZGltKGJpcmRzKQpgYGAKIyMgRXhlcmNpc2UgMQpGaWx0ZXIgeW91ciBuZXcgYGJpcmRzYCBkZiB0byBvbmx5IGluY2x1ZSBCbHVlIEpheXMuICBDaGVjayB0byBzZWUgaG93IG1hbnkgYmFsZCBlYWdsZSBzaWdodGluZ3MgdGhlcmUgd2VyZSBpbiBPaGlvLgoKYGBge3J9CmJhbGRfZWFnbGUgPC0gYmlyZHMgJT4lCiAgZmlsdGVyKHNwZWNpZXNfZW4gPT0gIkJhbGQgRWFnbGUiKQoKIyB3aGF0IGRvIHdlIGhhdmU/CmhlYWQoYmFsZF9lYWdsZSkKCiMgY2hlY2sgb3VyIGRmIGRpbWVuc2lvbnMKZGltKGJhbGRfZWFnbGUpCmBgYAoKIyMgRXhlcmNpc2UgMgpDcmVhdGUgYSBtYXAgdGhhdCBwbG90cyBhbGwgdGhlIGJhbGQgZWFnbGVzIGZvdW5kIGFyb3VuZCBPaGlvLiAgQ29sb3IgdGhlIHBvaW50cyBibHVlLiAgTWFrZSBzdXJlIHRoZSBhc3BlY3QgcmF0aW8gb2YgT2hpbyBsb29rcyByZWFzb25hYmxlIHRvIHlvdS4KCmBgYHtyfQpsaWJyYXJ5KG1hcHMpCgojIGdldCBtYXAgb2YgdGhlIHN0YXRlcwpzdGF0ZXMgPC0gbWFwX2RhdGEoInN0YXRlIikKCiMgZmlsdGVyIHN0YXRlcyB0byBvbmx5IGluY2x1ZGUgb2hpbwpvaGlvIDwtIHN0YXRlcyAlPiUKICBmaWx0ZXIocmVnaW9uID09ICJvaGlvIikKCiMgcGxvdApnZ3Bsb3QoZGF0YSA9IG9oaW8sCiAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApKSArCiAgZ2VvbV9wb2x5Z29uKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsgICAKICBnZW9tX3BvaW50KGRhdGEgPSBiYWxkX2VhZ2xlLCAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICBhZXMoeCA9IGRlY2ltYWxMb25naXR1ZGUsIHkgPSBkZWNpbWFsTGF0aXR1ZGUsIGdyb3VwID0gTlVMTCksCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgYWxwaGEgPSAwLjIpICsKICBjb29yZF9maXhlZCgxLjIpICsKICBsYWJzKHRpdGxlID0gJ0JhbGQgRWFnbGVzIHJvdW5kIE9oaW8nKQpgYGAKCiMjIEV4ZXJjaXNlIDMKCk1ha2UgeW91ciBwbG90IGludGVyYWN0aXZlIHNvIHlvdSBjYW4gaG92ZXIgYW5kIGFuZCBzZWUgdGhlIGxvY2FsaXR5IG9mIGVhY2ggYmFsZCBlYWdsZSBvYnNlcnZhdGlvbi4KYGBge3J9CmJhbGRfZWFnbGVzX29oaW8gPC0gCiAgZ2dwbG90KGRhdGEgPSBvaGlvLAogICAgICAgICBhZXMoeCA9IGxvbmcsIHkgPSBsYXQsIGdyb3VwID0gZ3JvdXApKSArCiAgZ2VvbV9wb2x5Z29uKGNvbG9yID0gImJsYWNrIiwgZmlsbCA9ICJ3aGl0ZSIpICsgICAKICBnZW9tX3BvaW50KGRhdGEgPSBiYWxkX2VhZ2xlLCAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICBhZXMoeCA9IGRlY2ltYWxMb25naXR1ZGUsIHkgPSBkZWNpbWFsTGF0aXR1ZGUsIGdyb3VwID0gTlVMTCwKICAgICAgICAgICAgICAgICB0ZXh0ID0gbG9jYWxpdHkpLAogICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIGFscGhhID0gMC4yKSArCiAgY29vcmRfZml4ZWQoMS4yKSArCiAgbGFicyh0aXRsZSA9ICdCYWxkIEVhZ2xlcyBBcm91bmQgT2hpbycpCgpnZ3Bsb3RseShiYWxkX2VhZ2xlc19vaGlvLAogICAgICAgICB0b29sdGlwID0gInRleHQiKQpgYGAKCiMjIEV4ZXJjaXNlIDQKQ2hhbmdlIHRoZSBob3ZlciB0ZXh0IHNvIHRoYXQgdGhlIGJhY2tncm91bmQgY29sb3IgaXMgcmVkLCBjbGVhbiB1cCB5b3VyIGF4aXMgbGFiZWxzLCBhbmQgbWFrZSBhbGwgdGhlIGZvbnRzIGZvciB0aGUgcGxvdCBBcmlhbC4KYGBge3J9CiMgc2V0dGluZyBmb250cyBmb3IgdGhlIHBsb3QKZWFnbGVfZm9udCA8LSBsaXN0KAogIGZhbWlseSA9ICJBcmlhbCIsCiAgc2l6ZSA9IDE1LAogIGNvbG9yID0gIndoaXRlIikKCiMgc2V0dGluZyBob3ZlciBsYWJlbCBzcGVjcwplYWdsZV9sYWJlbCA8LSBsaXN0KAogIGJnY29sb3IgPSAicmVkIiwKICBib3JkZXJjb2xvciA9ICJ0cmFuc3BhcmVudCIsCiAgZm9udCA9IGVhZ2xlX2ZvbnQpICMgd2UgY2FuIGRvIHRoaXMgYmMgd2UgYWxyZWFkeSBzZXQgZm9udAoKYmFsZF9lYWdsZXNfb2hpbyA8LSAKICBnZ3Bsb3QoZGF0YSA9IG9oaW8sCiAgICAgICAgIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCkpICsKICBnZW9tX3BvbHlnb24oY29sb3IgPSAiYmxhY2siLCBmaWxsID0gIndoaXRlIikgKyAgIAogIGdlb21fcG9pbnQoZGF0YSA9IGJhbGRfZWFnbGUsICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgIGFlcyh4ID0gZGVjaW1hbExvbmdpdHVkZSwgeSA9IGRlY2ltYWxMYXRpdHVkZSwgZ3JvdXAgPSBOVUxMLAogICAgICAgICAgICAgICAgIHRleHQgPSBsb2NhbGl0eSksCiAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgYWxwaGEgPSAwLjIpICsKICBjb29yZF9maXhlZCgxLjIpICsKICBsYWJzKHRpdGxlID0gJ0JhbGQgRWFnbGVzIGFyb3VuZCB0aGUgT2hpbycsCiAgICAgICB4ID0gIkxhdGl0dWRlIiwKICAgICAgIHkgPSAiTG9uZ2l0dWRlIikKCiMgYW1lbmRpbmcgb3VyIGdncGxvdGx5IGNhbGwgdG8gaW5jbHVkZSBuZXcgZm9udHMgYW5kIGhvdmVyIGxhYmVsIHNwZWNzCmdncGxvdGx5KGJhbGRfZWFnbGVzX29oaW8sIHRvb2x0aXAgPSAidGV4dCIpICU+JQogIHN0eWxlKGhvdmVybGFiZWwgPSBlYWdsZV9sYWJlbCkgJT4lCiAgbGF5b3V0KGZvbnQgPSBlYWdsZV9mb250KQpgYGAKCiMgQm9udXMKIyMgQm9udXMgMQpMZXQncyBnbyBiYWNrIHRvIHRoZSBHYXBtaW5kZXIgZGF0YSB3ZSBsb29rZWQgYXQgaW4gdGhlIGluc3RydWN0aW9uYWwgcGFydCBvZiBbU2Vzc2lvbiAxMF0oaHR0cHM6Ly9iaW9kYXNoLmdpdGh1Yi5pby9jb2RlY2x1Yi8xMF9mYWNldGluZy1hbmltYXRpbmcvKSBvbiBmYWNldGluZywgYW5pbWF0aW5nLCBhbmQgbXVsdGktcGxvdHRpbmcuCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJnYXBtaW5kZXIiKSAjIGlmIHlvdSB3ZXJlbid0IGF0IFNlc3Npb24gMTAKbGlicmFyeShnYXBtaW5kZXIpCmhlYWQoZ2FwbWluZGVyKQpgYGAKCk1ha2UgYSBidWJibGUtc3R5bGUgcGxvdCB0aGF0IHNob3dzIHRoZSBsaWZlIGV4cGVjdGFuY3kgdnMuIEdEUCBwZXIgY2FwaXRhIG92ZXIgMTk1MiB0byAyMDA3IGZvciBhbGwgY291bnRyaWVzLiAgQ29sb3IgYnkgY29udGluZW50LCBhbmQgaW5kaWNhdGUgcG9wdWxhdGlvbiBieSBzaXplLiAgVXNlIHlvdXIga25vd2xlZGdlIG9mIG1ha2luZyBwbG90cyB0byBhbHRlciBpdCBzdWNoIHRoYXQgeW91IHRoaW5rIGl0IGlzIGRlc2NyaXB0aXZlIGFuZCBhZXN0aGV0aWMuCmBgYHtyfQpnYXBtaW5kZXJfZm9udCA8LSBsaXN0KAogIGZhbWlseSA9ICJSb2JvdG8gQ29uZGVuc2VkIikKCmdhcG1pbmRlcl9idWJibGUgPC0gZ2FwbWluZGVyICU+JQogIGdncGxvdChhZXMoeCA9IGdkcFBlcmNhcCwgeSA9IGxpZmVFeHAsIAogICAgICAgICAgICAgZmlsbCA9IGNvbnRpbmVudCwgc2l6ZSA9IHBvcCwgCiAgICAgICAgICAgICB0ZXh0ID0gcGFzdGUoCiAgICAgICAgICAgICAgICJDb3VudHJ5OiIsIGNvdW50cnksCiAgICAgICAgICAgICAgICJcbkxpZmUgZXhwZWN0YW5jeToiLCByb3VuZChsaWZlRXhwLDEpLAogICAgICAgICAgICAgICAiXG5HRFAgcGVyIGNhcGl0YToiLCByb3VuZChnZHBQZXJjYXAsMCkpKSkgKwogIGdlb21fcG9pbnQoYWVzKGZyYW1lID0geWVhciksIGNvbG9yID0gImJsYWNrIiwgc2hhcGUgPSAyMSwgc3Ryb2tlID0gMC4yKSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4KSkgKwogIGxhYnModGl0bGUgPSAiQ2hhbmdpbmcgTGlmZSBFeHBlY3RhbmN5IGFuZCBHRFAgUGVyIENhcGl0YSBXb3JsZHdpZGUgXG5Gcm9tIDE5NTIgdG8gMjAwNyIsCiAgICAgICB4ID0gIkdEUCBwZXIgY2FwaXRhIChpbiBJbnRlcm5hdGlvbmFsIERvbGxhcnMpIiwKICAgICAgIHkgPSAiTGlmZSBFeHBlY3RhbmN5ICh5ZWFycykiLAogICAgICAgZmlsbCA9ICIiLAogICAgICAgc2l6ZSA9ICIiKQoKZ2dwbG90bHkoZ2FwbWluZGVyX2J1YmJsZSwgCiAgICAgICAgIHRvb2x0aXAgPSBjKCJ0ZXh0IikpICU+JQogIGxheW91dChmb250ID0gZ2FwbWluZGVyX2ZvbnQpCmBgYAoKCgo=