• Instructions
  • Basic Operations
  • Cleaning and Counting
  • Combining Data
  • Plotting
  • Functional Programming
  • Wrapping Up


Instructions

  1. This exam covers material from R for Data Science. You may find the study guide useful. If you have any questions about scope, please get in touch.

  2. You must complete the exam within 90 minutes.

  3. You may use any books or digital resources you want during this examination, but you may not communicate with any person other than your examiner.

  4. You are required to use the RStudio IDE for the practical portions of this exam. You may use either the desktop edition or rstudio.cloud as you prefer.


There is of course no one correct way to solve each of these questions. I’ve included the solutions that first came to my mind when I did this practice exam. If you find a mistake or a better solution, please feel free to submit a pull request on Github.


You can download the blank sample exam .rmd here. You can download this current .rmd containing the solutions here.

By default, the code for each solution is hidden. You can toggle the code on and off by clicking Code/Hide at the top right of each chunk or Code > Show All Code / Hide All Code at the top right of this document.


library(tidyverse)

Basic Operations

  1. Read the file person.csv and store the result in a tibble called person.
(person <- read_csv(here::here("person.csv")))
ABCDEFGHIJ0123456789
person_id
<chr>
personal_name
<chr>
family_name
<chr>
dyerWilliamDyer
pbFrankPabodie
lakeAndersonLake
roeValentinaRoerich
danforthFrankDanforth
  1. Create a tibble containing only family and personal names, in that order. You do not need to assign this tibble or any others to variables unless explicitly asked to do so. However, as noted in the introduction, you must use the pipe operator %>% and code that follows the tidyverse style guide.
person %>% 
  select(family_name, personal_name)
ABCDEFGHIJ0123456789
family_name
<chr>
personal_name
<chr>
DyerWilliam
PabodieFrank
LakeAnderson
RoerichValentina
DanforthFrank
  1. Create a new tibble containing only the rows in which family names come before the letter M. Your solution should work for tables with more rows than the example, i.e., you cannot rely on row numbers or select specific names.
before_m <- letters[which(letters == "a"):which(letters == "m") - 1]

person %>% 
  mutate(family_name = tolower(family_name),
         family_first_letter = str_sub(family_name, 1, 1)) %>% 
  filter(family_first_letter %in% before_m) %>% 
  select(-family_first_letter)
ABCDEFGHIJ0123456789
person_id
<chr>
personal_name
<chr>
family_name
<chr>
dyerWilliamdyer
lakeAndersonlake
danforthFrankdanforth

Another, much more elegant solution, courtesy of Beatriz Milz:

person %>% filter(family_name < "M")
ABCDEFGHIJ0123456789
person_id
<chr>
personal_name
<chr>
family_name
<chr>
dyerWilliamDyer
lakeAndersonLake
danforthFrankDanforth
  1. Display all the rows in person sorted by family name length with the longest name first.
person %>% 
  arrange(desc(str_length(family_name)))
ABCDEFGHIJ0123456789
person_id
<chr>
personal_name
<chr>
family_name
<chr>
danforthFrankDanforth
pbFrankPabodie
roeValentinaRoerich
dyerWilliamDyer
lakeAndersonLake

Cleaning and Counting

  1. Read the file measurements.csv to create a tibble called measurements. (The strings "rad", "sal", and "temp" in the quantity column stand for “radiation”, “salinity”, and “temperature” respectively.)
(measurements <- read_csv(here::here("measurements.csv")))
ABCDEFGHIJ0123456789
visit_id
<dbl>
visitor
<chr>
quantity
<chr>
reading
<dbl>
619dyerrad9.82
619dyersal0.13
622dyerrad7.80
622dyersal0.09
734pbrad8.41
734lakesal0.05
734pbtemp-21.50
735pbrad7.22
735NAsal0.06
735NAtemp-26.00
  1. Create a tibble containing only rows where none of the values are NA and save in a tibble called cleaned.
(cleaned <- measurements %>% 
  drop_na())
ABCDEFGHIJ0123456789
visit_id
<dbl>
visitor
<chr>
quantity
<chr>
reading
<dbl>
619dyerrad9.82
619dyersal0.13
622dyerrad7.80
622dyersal0.09
734pbrad8.41
734lakesal0.05
734pbtemp-21.50
735pbrad7.22
751pbrad4.35
751pbtemp-18.50
  1. Count the number of measurements of each type of quantity in cleaned. Your result should have one row for each quantity "rad", "sal", and "temp".
cleaned %>% 
  count(quantity)
ABCDEFGHIJ0123456789
quantity
<chr>
n
<int>
rad8
sal7
temp3
  1. Display the minimum and maximum value of reading separately for each quantity in cleaned. Your result should have one row for each quantity "rad", "sal", and "temp".
cleaned %>% 
  group_by(quantity) %>% 
  summarize(reading_min = min(reading),
            reading_max = max(reading))
ABCDEFGHIJ0123456789
quantity
<chr>
reading_min
<dbl>
reading_max
<dbl>
rad1.4611.25
sal0.0541.60
temp-21.50-16.00

Note: You could also use dplyr::across() and a named list of functions! 😎

cleaned %>% 
  group_by(quantity) %>% 
  summarize(across(reading, list(min = min, max = max)))
ABCDEFGHIJ0123456789
quantity
<chr>
reading_min
<dbl>
reading_max
<dbl>
rad1.4611.25
sal0.0541.60
temp-21.50-16.00
  1. Create a tibble in which all salinity ("sal") readings greater than 1 are divided by 100. (This is needed because some people wrote percentages as numbers from 0.0 to 1.0, but others wrote them as 0.0 to 100.0.)
cleaned %>% 
  mutate(reading = case_when(
                      quantity == "sal" & reading > 1 ~ reading/100,
                      TRUE ~ reading)
         )
ABCDEFGHIJ0123456789
visit_id
<dbl>
visitor
<chr>
quantity
<chr>
reading
<dbl>
619dyerrad9.820
619dyersal0.130
622dyerrad7.800
622dyersal0.090
734pbrad8.410
734lakesal0.050
734pbtemp-21.500
735pbrad7.220
751pbrad4.350
751pbtemp-18.500

Combining Data

  1. Read visited.csv and drop rows containing any NAs, assigning the result to a new tibble called visited.
(visited <- read_csv(here::here("visited.csv")) %>% 
  drop_na())
ABCDEFGHIJ0123456789
visit_id
<dbl>
site_id
<chr>
visit_date
<date>
619DR-11927-02-08
622DR-11927-02-10
734DR-31930-01-07
735DR-31930-01-12
751DR-31930-02-26
837MSK-41932-01-14
844DR-11932-03-22
  1. Use an inner join to combine visited with cleaned using the visit_id column for matches.
(combined <- inner_join(visited, cleaned, by = "visit_id"))
ABCDEFGHIJ0123456789
visit_id
<dbl>
site_id
<chr>
visit_date
<date>
visitor
<chr>
quantity
<chr>
reading
<dbl>
619DR-11927-02-08dyerrad9.82
619DR-11927-02-08dyersal0.13
622DR-11927-02-10dyerrad7.80
622DR-11927-02-10dyersal0.09
734DR-31930-01-07pbrad8.41
734DR-31930-01-07lakesal0.05
734DR-31930-01-07pbtemp-21.50
735DR-31930-01-12pbrad7.22
751DR-31930-02-26pbrad4.35
751DR-31930-02-26pbtemp-18.50
  1. Find the highest radiation ("rad") reading at each site. (Sites are identified by values in the site_id column.)
(max_rad <- combined %>% 
  pivot_wider(names_from = quantity, values_from = reading) %>% 
  group_by(site_id) %>% 
  summarize(max_rad = max(rad, na.rm = TRUE)))
ABCDEFGHIJ0123456789
site_id
<chr>
max_rad
<dbl>
DR-111.25
DR-38.41
MSK-41.46
  1. Find the date of the highest radiation reading at each site.
combined %>% 
  pivot_wider(names_from = quantity, values_from = reading) %>% 
  group_by(site_id, visit_date) %>% 
  summarize(max_rad = max(rad, na.rm = TRUE)) %>% 
  semi_join(max_rad) %>% 
  select(visit_date, everything())
ABCDEFGHIJ0123456789
visit_date
<date>
site_id
<chr>
max_rad
<dbl>
1932-03-22DR-111.25
1930-01-07DR-38.41
1932-01-14MSK-41.46

Plotting

  1. The code below is supposed to read the file home-range-database.csv to create a tibble called hra_raw, but contains a bug. Describe and fix the problem. (There are several ways to fix it: please use whichever you prefer.)
hra_raw <- read_csv(here::here("data", "home-range-database.csv"))

Note: The file home-range-database.csv is currently saved in the root directory of the project, not in a subdirectory called data, as the code above would suggest. You could either create the data folder and move the data file there, or you could update the code that imports the data (as I demonstrate below).

(hra_raw <- read_csv(here::here("home-range-database.csv")))
ABCDEFGHIJ0123456789
taxon
<chr>
common.name
<chr>
class
<chr>
order
<chr>
lake fishesamerican eelactinopterygiianguilliformes
river fishesblacktail redhorseactinopterygiicypriniformes
river fishescentral stonerolleractinopterygiicypriniformes
river fishesrosyside daceactinopterygiicypriniformes
river fisheslongnose daceactinopterygiicypriniformes
river fishesmuskellungeactinopterygiiesociformes
marine fishespollackactinopterygiigadiformes
marine fishessaitheactinopterygiigadiformes
marine fisheslined surgeonfishactinopterygiiperciformes
marine fishesorangespine unicornfishactinopterygiiperciformes
  1. Convert the class column (which is text) to create a factor column class_fct and assign the result to a tibble hra. Use forcats to order the factor levels as:
    1. mammalia
    2. reptilia
    3. aves
    4. actinopterygii
(hra <- hra_raw %>% 
  mutate(class_fct = factor(class, levels = c("mammalia", 
                                              "reptilia", 
                                              "aves", 
                                              "actinopterygii"))) %>% 
   relocate(class_fct, .after = class))
ABCDEFGHIJ0123456789
taxon
<chr>
common.name
<chr>
class
<chr>
class_fct
<fctr>
lake fishesamerican eelactinopterygiiactinopterygii
river fishesblacktail redhorseactinopterygiiactinopterygii
river fishescentral stonerolleractinopterygiiactinopterygii
river fishesrosyside daceactinopterygiiactinopterygii
river fisheslongnose daceactinopterygiiactinopterygii
river fishesmuskellungeactinopterygiiactinopterygii
marine fishespollackactinopterygiiactinopterygii
marine fishessaitheactinopterygiiactinopterygii
marine fisheslined surgeonfishactinopterygiiactinopterygii
marine fishesorangespine unicornfishactinopterygiiactinopterygii
  1. Create a scatterplot showing the relationship between log10.mass and log10.hra in hra.
hra %>% 
  ggplot(aes(log10.mass, log10.hra)) + 
  geom_point(size = 2, alpha = 0.7) + 
  theme_minimal()

  1. Colorize the points in the scatterplot by class_fct.
hra %>% 
  ggplot(aes(log10.mass, log10.hra, color = class_fct)) + 
  geom_point(size = 2, alpha = 0.7) + 
  scale_color_viridis_d(end = .9) + 
  theme_minimal()

  1. Display a scatterplot showing only data for birds (class aves) and fit a linear regression to that data using the lm function.
hra %>% 
  filter(class == "aves") %>% 
  ggplot(aes(log10.mass, log10.hra)) + 
  geom_point(size = 2, alpha = 0.7) + 
  geom_smooth(method = "lm") + 
  labs(title = "Linear relationship between home range and mass for Aves") + 
  theme_minimal()

Functional Programming

  1. Write a function called summarize_table that takes a title string and a tibble as input and returns a string that says something like, “title has # rows and # columns”. For example, summarize_table('our table', person) should return the string "our table has 5 rows and 3 columns".
summarize_table <- function(title, df) { 
  nrow <- nrow(df)
  ncol <- ncol(df) 
  
  glue::glue("{title} has {nrow} rows and {ncol} columns.")
}

summarize_table("our table", person)
## our table has 5 rows and 3 columns.
  1. Write another function called show_columns that takes a string and a tibble as input and returns a string that says something like, “table has columns name, name, name”. For example, show_columns('person', person) should return the string "person has columns person_id, personal_name, family_name".
show_columns <- function(title, df) { 
  col_names <- names(df) %>% 
    str_c(collapse = ", ")

glue::glue("{title} has columns {col_names}")
}

show_columns('person', person)
## person has columns person_id, personal_name, family_name
  1. The function rows_from_file returns the first N rows from a table in a CSV file given the file’s name and the number of rows desired. Modify it so that if no value is specified for the number of rows, a default of 3 is used.
rows_from_file <- function(filename, num_rows) {
      readr::read_csv(filename) %>% head(n = num_rows)
    }

rows_from_file("measurements.csv") # should show 3 rows
rows_from_file <- function(filename, num_rows = 3) {
  readr::read_csv(filename) %>% head(n = num_rows)
}

rows_from_file("measurements.csv")
ABCDEFGHIJ0123456789
visit_id
<dbl>
visitor
<chr>
quantity
<chr>
reading
<dbl>
619dyerrad9.82
619dyersal0.13
622dyerrad7.80
  1. The function long_name checks whether a string is longer than 4 characters. Use this function and a function from purrr to create a logical vector that contains the value TRUE where family names in the tibble person are longer than 4 characters, and FALSE where they are 4 characters or less.
long_name <- function(name) {
      stringr::str_length(name) > 4
    }
person %>% 
  mutate(long_family_name = map_lgl(family_name, long_name))
ABCDEFGHIJ0123456789
person_id
<chr>
personal_name
<chr>
family_name
<chr>
long_family_name
<lgl>
dyerWilliamDyerFALSE
pbFrankPabodieTRUE
lakeAndersonLakeFALSE
roeValentinaRoerichTRUE
danforthFrankDanforthTRUE

Wrapping Up

  1. Modify the YAML header of this file so that a table of contents is automatically created each time this document is knit, and fix any errors that are preventing the document from knitting cleanly.
---
title: "Tidyverse Exam Version 2.0"
output:
html_document:
    theme: flatly
---

Corrected YAML header:

---
title: "Tidyverse Exam Version 2.0"
output:
  html_document:
    theme: flatly
    toc: TRUE
---

Note: You need to add an indentation after html_document: and add toc: TRUE


You can read more about the RStudio Instructor Training and Certification Program here. There is another sample exam available with solutions, courtesy of Marly Gotti. I wrote about my own experience with the training and shared some of my exam prep materials here. Feel free to reach out with any questions!


LS0tCnRpdGxlOiAiVGlkeXZlcnNlIFNhbXBsZSBFeGFtIHYyLjAgPGJyPlNvbHV0aW9ucyIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0aGVtZTogZmxhdGx5CiAgICBjc3M6ICJjc3MvY3VzdG9tLmNzcyIKYXV0aG9yOiAiW0JyZW5kYW4gQ3VsbGVuXShodHRwczovL2JjdWxsZW4ucmJpbmQuaW8vKSIKLS0tCgo8YnI+CjxjZW50ZXI+CmBgYHtyIG91dC53aWR0aD0iNTAlIiwgZmlnLnBvcz0iYyIsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJpbWcvdGlkeXZlcnNlX2tleS5wbmciKQpgYGAKPC9jZW50ZXI+Cjxicj4KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByb3dzLnByaW50ID0gMTApCmBgYAoKYGBge3IgZWNobz1GQUxTRX0KbGlicmFyeShtZXRhdGhpcykKbWV0YSgpICU+JSAKICBtZXRhX2Rlc2NyaXB0aW9uKCJBIHNvbHV0aW9uIGd1aWRlIHRvIHRoZSBzYW1wbGUgdGlkeXZlcnNlIGV4YW0gdmVyc2lvbiAyLjAgZm9yIHRoZSBSU3R1ZGlvIEluc3RydWN0b3IgVHJhaW5pbmcgYW5kIENlcnRpZmljYXRpb24gUHJvZ3JhbSIpICU+JSAKICBtZXRhX25hbWUoImdpdGh1Yi1yZXBvIiA9ICJicmVuZGFuaGN1bGxlbi90aWR5dmVyc2Utc2FtcGxlLWV4YW0tdjIuMCIpICU+JSAKICBtZXRhX3ZpZXdwb3J0KCkgJT4lIAogIG1ldGFfc29jaWFsKAogICAgdGl0bGUgPSAiVGlkeXZlcnNlIFNhbXBsZSBFeGFtIHYyLjAgU29sdXRpb25zIiwKICAgIHVybCA9ICJodHRwczovL3RpZHl2ZXJzZS1leGFtLXYyLXNvbHV0aW9ucy5uZXRsaWZ5LmFwcC8iLAogICAgaW1hZ2UgPSAiaHR0cHM6Ly9pbWFnZXMudW5zcGxhc2guY29tL3Bob3RvLTE1MTIzMTQ4ODkzNTctZTE1N2MyMmY5MzhkP2l4bGliPXJiLTEuMi4xJml4aWQ9ZXlKaGNIQmZhV1FpT2pFeU1EZDkmYXV0bz1mb3JtYXQmZml0PWNyb3Amdz0xNTAyJnE9ODAiLAogICAgb2dfYXV0aG9yID0gIkJyZW5kYW4gQ3VsbGVuIiwKICAgIG9nX3R5cGUgPSAiYXJ0aWNsZSIsCiAgICB0d2l0dGVyX2NhcmRfdHlwZSA9ICJzdW1tYXJ5X2xhcmdlX2ltYWdlIiwKICAgIHR3aXR0ZXJfY3JlYXRvciA9ICJAX2JjdWxsZW4iKQpgYGAKCiMjIyBJbnN0cnVjdGlvbnMKCjEuIFRoaXMgZXhhbSBjb3ZlcnMgbWF0ZXJpYWwgZnJvbSBbUiBmb3IgRGF0YSBTY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykuIFlvdSBtYXkgZmluZCB0aGUgW3N0dWR5IGd1aWRlXShodHRwczovL2pybm9sZC5naXRodWIuaW8vcjRkcy1leGVyY2lzZS1zb2x1dGlvbnMvKSB1c2VmdWwuIElmIHlvdSBoYXZlIGFueSBxdWVzdGlvbnMgYWJvdXQgc2NvcGUsIHBsZWFzZSBnZXQgaW4gdG91Y2guCgoyLiBZb3UgbXVzdCBjb21wbGV0ZSB0aGUgZXhhbSB3aXRoaW4gOTAgbWludXRlcy4KCjMuIFlvdSBtYXkgdXNlIGFueSBib29rcyBvciBkaWdpdGFsIHJlc291cmNlcyB5b3Ugd2FudCBkdXJpbmcgdGhpcyBleGFtaW5hdGlvbiwgYnV0IHlvdSBtYXkgbm90IGNvbW11bmljYXRlIHdpdGggYW55IHBlcnNvbiBvdGhlciB0aGFuIHlvdXIgZXhhbWluZXIuCgo0LiBZb3UgYXJlIHJlcXVpcmVkIHRvIHVzZSB0aGUgUlN0dWRpbyBJREUgZm9yIHRoZSBwcmFjdGljYWwgcG9ydGlvbnMgb2YgdGhpcyBleGFtLiBZb3UgbWF5IHVzZSBlaXRoZXIgdGhlIGRlc2t0b3AgZWRpdGlvbiBvciByc3R1ZGlvLmNsb3VkIGFzIHlvdSBwcmVmZXIuCgo8YnI+Cgo6OjphbGVydApUaGVyZSBpcyBvZiBjb3Vyc2Ugbm8gb25lIGNvcnJlY3Qgd2F5IHRvIHNvbHZlIGVhY2ggb2YgdGhlc2UgcXVlc3Rpb25zLiBJJ3ZlIGluY2x1ZGVkIHRoZSBzb2x1dGlvbnMgdGhhdCBmaXJzdCBjYW1lIHRvIG15IG1pbmQgd2hlbiBJIGRpZCB0aGlzIHByYWN0aWNlIGV4YW0uIElmIHlvdSBmaW5kIGEgbWlzdGFrZSBvciBhIGJldHRlciBzb2x1dGlvbiwgcGxlYXNlIGZlZWwgZnJlZSB0byBzdWJtaXQgYSBwdWxsIHJlcXVlc3Qgb24gW0dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2JyZW5kYW5oY3VsbGVuL3RpZHl2ZXJzZS1zYW1wbGUtZXhhbS12Mi4wKS4KOjo6CgoKOjo6ZG93bmxvYWQKPGJyPgpZb3UgY2FuIGRvd25sb2FkIHRoZSBibGFuayBzYW1wbGUgZXhhbSBgLnJtZGAgW2hlcmVdKHNhbXBsZV9leGFtX3YyLjAuUm1kKS4gWW91IGNhbiBkb3dubG9hZCB0aGlzIGN1cnJlbnQgYC5ybWRgIGNvbnRhaW5pbmcgdGhlIHNvbHV0aW9ucyBbaGVyZV0oaW5kZXguUm1kKS4KOjo6Cgo6Ojp0b2dnbGUKQnkgZGVmYXVsdCwgdGhlIGNvZGUgZm9yIGVhY2ggc29sdXRpb24gaXMgaGlkZGVuLiBZb3UgY2FuIHRvZ2dsZSB0aGUgY29kZSBvbiBhbmQgb2ZmIGJ5IGNsaWNraW5nIGBDb2RlYC9gSGlkZWAgYXQgdGhlIHRvcCByaWdodCBvZiBlYWNoIGNodW5rIG9yIGBDb2RlID4gU2hvdyBBbGwgQ29kZSAvIEhpZGUgQWxsIENvZGVgIGF0IHRoZSB0b3AgcmlnaHQgb2YgdGhpcyBkb2N1bWVudC4KOjo6Cgo8YnI+CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93In0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKCiMjIyBCYXNpYyBPcGVyYXRpb25zCgoxLiAgUmVhZCB0aGUgZmlsZSBbYHBlcnNvbi5jc3ZgXSguL3BlcnNvbi5jc3YpIGFuZCBzdG9yZSB0aGUgcmVzdWx0IGluIGEgdGliYmxlIGNhbGxlZCBgcGVyc29uYC4gCgpgYGB7cn0KKHBlcnNvbiA8LSByZWFkX2NzdihoZXJlOjpoZXJlKCJwZXJzb24uY3N2IikpKQpgYGAKCjIuICBDcmVhdGUgYSB0aWJibGUgY29udGFpbmluZyBvbmx5IGZhbWlseSBhbmQgcGVyc29uYWwgbmFtZXMsIGluIHRoYXQgb3JkZXIuIFlvdSBkbyBub3QgbmVlZCB0byBhc3NpZ24gdGhpcyB0aWJibGUgb3IgYW55IG90aGVycyB0byB2YXJpYWJsZXMgdW5sZXNzIGV4cGxpY2l0bHkgYXNrZWQgdG8gZG8gc28uIEhvd2V2ZXIsIGFzIG5vdGVkIGluIHRoZSBpbnRyb2R1Y3Rpb24sIHlvdSAqbXVzdCogdXNlIHRoZSBwaXBlIG9wZXJhdG9yIGAlPiVgIGFuZCBjb2RlIHRoYXQgZm9sbG93cyB0aGUgdGlkeXZlcnNlIHN0eWxlIGd1aWRlLgoKYGBge3J9CnBlcnNvbiAlPiUgCiAgc2VsZWN0KGZhbWlseV9uYW1lLCBwZXJzb25hbF9uYW1lKQpgYGAKCjMuICBDcmVhdGUgYSBuZXcgdGliYmxlIGNvbnRhaW5pbmcgb25seSB0aGUgcm93cyBpbiB3aGljaCBmYW1pbHkgbmFtZXMgY29tZSAqYmVmb3JlKiB0aGUgbGV0dGVyIGBNYC4gWW91ciBzb2x1dGlvbiBzaG91bGQgd29yayBmb3IgdGFibGVzIHdpdGggbW9yZSByb3dzIHRoYW4gdGhlIGV4YW1wbGUsIGkuZS4sIHlvdSBjYW5ub3QgcmVseSBvbiByb3cgbnVtYmVycyBvciBzZWxlY3Qgc3BlY2lmaWMgbmFtZXMuCgpgYGB7cn0KYmVmb3JlX20gPC0gbGV0dGVyc1t3aGljaChsZXR0ZXJzID09ICJhIik6d2hpY2gobGV0dGVycyA9PSAibSIpIC0gMV0KCnBlcnNvbiAlPiUgCiAgbXV0YXRlKGZhbWlseV9uYW1lID0gdG9sb3dlcihmYW1pbHlfbmFtZSksCiAgICAgICAgIGZhbWlseV9maXJzdF9sZXR0ZXIgPSBzdHJfc3ViKGZhbWlseV9uYW1lLCAxLCAxKSkgJT4lIAogIGZpbHRlcihmYW1pbHlfZmlyc3RfbGV0dGVyICVpbiUgYmVmb3JlX20pICU+JSAKICBzZWxlY3QoLWZhbWlseV9maXJzdF9sZXR0ZXIpCmBgYAoKQW5vdGhlciwgbXVjaCBtb3JlIGVsZWdhbnQgc29sdXRpb24sIGNvdXJ0ZXN5IG9mIFtCZWF0cml6IE1pbHpdKGh0dHBzOi8vZWR1Y2F0aW9uLnJzdHVkaW8uY29tL3RyYWluZXJzL3Blb3BsZS9taWx6K2JlYXRyaXovKToKCmBgYHtyfQpwZXJzb24gJT4lIGZpbHRlcihmYW1pbHlfbmFtZSA8ICJNIikKYGBgCgo0LiAgRGlzcGxheSBhbGwgdGhlIHJvd3MgaW4gYHBlcnNvbmAgc29ydGVkIGJ5IGZhbWlseSBuYW1lIGxlbmd0aCB3aXRoIHRoZSBsb25nZXN0IG5hbWUgZmlyc3QuCgpgYGB7cn0KcGVyc29uICU+JSAKICBhcnJhbmdlKGRlc2Moc3RyX2xlbmd0aChmYW1pbHlfbmFtZSkpKQpgYGAKCiMjIyBDbGVhbmluZyBhbmQgQ291bnRpbmcKCjEuICBSZWFkIHRoZSBmaWxlIFtgbWVhc3VyZW1lbnRzLmNzdmBdKC4vbWVhc3VyZW1lbnRzLmNzdikgdG8gY3JlYXRlIGEgdGliYmxlIGNhbGxlZCBgbWVhc3VyZW1lbnRzYC4gKFRoZSBzdHJpbmdzIGAicmFkImAsIGAic2FsImAsIGFuZCBgInRlbXAiYCBpbiB0aGUgYHF1YW50aXR5YCBjb2x1bW4gc3RhbmQgZm9yICJyYWRpYXRpb24iLCAic2FsaW5pdHkiLCBhbmQgInRlbXBlcmF0dXJlIiByZXNwZWN0aXZlbHkuKQoKYGBge3J9CihtZWFzdXJlbWVudHMgPC0gcmVhZF9jc3YoaGVyZTo6aGVyZSgibWVhc3VyZW1lbnRzLmNzdiIpKSkKYGBgCgoyLiAgQ3JlYXRlIGEgdGliYmxlIGNvbnRhaW5pbmcgb25seSByb3dzIHdoZXJlICpub25lKiBvZiB0aGUgdmFsdWVzIGFyZSBgTkFgIGFuZCBzYXZlIGluIGEgdGliYmxlIGNhbGxlZCBgY2xlYW5lZGAuCgpgYGB7cn0KKGNsZWFuZWQgPC0gbWVhc3VyZW1lbnRzICU+JSAKICBkcm9wX25hKCkpCmBgYAoKMy4gIENvdW50IHRoZSBudW1iZXIgb2YgbWVhc3VyZW1lbnRzIG9mIGVhY2ggdHlwZSBvZiBxdWFudGl0eSBpbiBgY2xlYW5lZGAuIFlvdXIgcmVzdWx0IHNob3VsZCBoYXZlIG9uZSByb3cgZm9yIGVhY2ggcXVhbnRpdHkgYCJyYWQiYCwgYCJzYWwiYCwgYW5kIGAidGVtcCJgLgoKYGBge3J9CmNsZWFuZWQgJT4lIAogIGNvdW50KHF1YW50aXR5KQpgYGAKCgo0LiAgRGlzcGxheSB0aGUgbWluaW11bSBhbmQgbWF4aW11bSB2YWx1ZSBvZiBgcmVhZGluZ2Agc2VwYXJhdGVseSBmb3IgZWFjaCBxdWFudGl0eSBpbiBgY2xlYW5lZGAuIFlvdXIgcmVzdWx0IHNob3VsZCBoYXZlIG9uZSByb3cgZm9yIGVhY2ggcXVhbnRpdHkgYCJyYWQiYCwgYCJzYWwiYCwgYW5kIGAidGVtcCJgLgoKYGBge3J9CmNsZWFuZWQgJT4lIAogIGdyb3VwX2J5KHF1YW50aXR5KSAlPiUgCiAgc3VtbWFyaXplKHJlYWRpbmdfbWluID0gbWluKHJlYWRpbmcpLAogICAgICAgICAgICByZWFkaW5nX21heCA9IG1heChyZWFkaW5nKSkKYGBgCgoqKk5vdGU6IFlvdSBjb3VsZCBhbHNvIHVzZSBgZHBseXI6OmFjcm9zcygpYCBhbmQgYSBuYW1lZCBsaXN0IG9mIGZ1bmN0aW9ucyEqKiBgciBlbW86OmppKCJzdW5nbGFzc2VzIilgCgpgYGB7cn0KY2xlYW5lZCAlPiUgCiAgZ3JvdXBfYnkocXVhbnRpdHkpICU+JSAKICBzdW1tYXJpemUoYWNyb3NzKHJlYWRpbmcsIGxpc3QobWluID0gbWluLCBtYXggPSBtYXgpKSkKYGBgCgoKNS4gIENyZWF0ZSBhIHRpYmJsZSBpbiB3aGljaCBhbGwgc2FsaW5pdHkgKGAic2FsImApIHJlYWRpbmdzIGdyZWF0ZXIgdGhhbiAxIGFyZSBkaXZpZGVkIGJ5IDEwMC4gKFRoaXMgaXMgbmVlZGVkIGJlY2F1c2Ugc29tZSBwZW9wbGUgd3JvdGUgcGVyY2VudGFnZXMgYXMgbnVtYmVycyBmcm9tIDAuMCB0byAxLjAsIGJ1dCBvdGhlcnMgd3JvdGUgdGhlbSBhcyAwLjAgdG8gMTAwLjAuKQoKYGBge3J9CmNsZWFuZWQgJT4lIAogIG11dGF0ZShyZWFkaW5nID0gY2FzZV93aGVuKAogICAgICAgICAgICAgICAgICAgICAgcXVhbnRpdHkgPT0gInNhbCIgJiByZWFkaW5nID4gMSB+IHJlYWRpbmcvMTAwLAogICAgICAgICAgICAgICAgICAgICAgVFJVRSB+IHJlYWRpbmcpCiAgICAgICAgICkKYGBgCgojIyMgQ29tYmluaW5nIERhdGEKCjEuICBSZWFkIFtgdmlzaXRlZC5jc3ZgXSguL3Zpc2l0ZWQuY3N2KSBhbmQgZHJvcCByb3dzIGNvbnRhaW5pbmcgYW55IGBOQWBzLCBhc3NpZ25pbmcgdGhlIHJlc3VsdCB0byBhIG5ldyB0aWJibGUgY2FsbGVkIGB2aXNpdGVkYC4KCmBgYHtyfQoodmlzaXRlZCA8LSByZWFkX2NzdihoZXJlOjpoZXJlKCJ2aXNpdGVkLmNzdiIpKSAlPiUgCiAgZHJvcF9uYSgpKQpgYGAKCjIuICBVc2UgYW4gaW5uZXIgam9pbiB0byBjb21iaW5lIGB2aXNpdGVkYCB3aXRoIGBjbGVhbmVkYCB1c2luZyB0aGUgYHZpc2l0X2lkYCBjb2x1bW4gZm9yIG1hdGNoZXMuCgpgYGB7cn0KKGNvbWJpbmVkIDwtIGlubmVyX2pvaW4odmlzaXRlZCwgY2xlYW5lZCwgYnkgPSAidmlzaXRfaWQiKSkKYGBgCgoKMy4gIEZpbmQgdGhlIGhpZ2hlc3QgcmFkaWF0aW9uIChgInJhZCJgKSByZWFkaW5nIGF0IGVhY2ggc2l0ZS4gKFNpdGVzIGFyZSBpZGVudGlmaWVkIGJ5IHZhbHVlcyBpbiB0aGUgYHNpdGVfaWRgIGNvbHVtbi4pCgpgYGB7cn0KKG1heF9yYWQgPC0gY29tYmluZWQgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBxdWFudGl0eSwgdmFsdWVzX2Zyb20gPSByZWFkaW5nKSAlPiUgCiAgZ3JvdXBfYnkoc2l0ZV9pZCkgJT4lIAogIHN1bW1hcml6ZShtYXhfcmFkID0gbWF4KHJhZCwgbmEucm0gPSBUUlVFKSkpCmBgYAoKNC4gIEZpbmQgdGhlIGRhdGUgb2YgdGhlIGhpZ2hlc3QgcmFkaWF0aW9uIHJlYWRpbmcgYXQgZWFjaCBzaXRlLgoKYGBge3J9CmNvbWJpbmVkICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcXVhbnRpdHksIHZhbHVlc19mcm9tID0gcmVhZGluZykgJT4lIAogIGdyb3VwX2J5KHNpdGVfaWQsIHZpc2l0X2RhdGUpICU+JSAKICBzdW1tYXJpemUobWF4X3JhZCA9IG1heChyYWQsIG5hLnJtID0gVFJVRSkpICU+JSAKICBzZW1pX2pvaW4obWF4X3JhZCkgJT4lIAogIHNlbGVjdCh2aXNpdF9kYXRlLCBldmVyeXRoaW5nKCkpCmBgYAoKCiMjIyBQbG90dGluZwoKMS4gIFRoZSBjb2RlIGJlbG93IGlzIHN1cHBvc2VkIHRvIHJlYWQgdGhlIGZpbGUgYGhvbWUtcmFuZ2UtZGF0YWJhc2UuY3N2YCB0byBjcmVhdGUgYSB0aWJibGUgY2FsbGVkIGBocmFfcmF3YCwgYnV0IGNvbnRhaW5zIGEgYnVnLiBEZXNjcmliZSBhbmQgZml4IHRoZSBwcm9ibGVtLiAoVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBmaXggaXQ6IHBsZWFzZSB1c2Ugd2hpY2hldmVyIHlvdSBwcmVmZXIuKQoKYGBge3IgZXZhbCA9IEZBTFNFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93In0KaHJhX3JhdyA8LSByZWFkX2NzdihoZXJlOjpoZXJlKCJkYXRhIiwgImhvbWUtcmFuZ2UtZGF0YWJhc2UuY3N2IikpCmBgYAoKKipOb3RlOiBUaGUgZmlsZSBgaG9tZS1yYW5nZS1kYXRhYmFzZS5jc3ZgIGlzIGN1cnJlbnRseSBzYXZlZCBpbiB0aGUgcm9vdCBkaXJlY3Rvcnkgb2YgdGhlIHByb2plY3QsIG5vdCBpbiBhIHN1YmRpcmVjdG9yeSBjYWxsZWQgYGRhdGFgLCBhcyB0aGUgY29kZSBhYm92ZSB3b3VsZCBzdWdnZXN0LiBZb3UgY291bGQgZWl0aGVyIGNyZWF0ZSB0aGUgYGRhdGFgIGZvbGRlciBhbmQgbW92ZSB0aGUgZGF0YSBmaWxlIHRoZXJlLCBvciB5b3UgY291bGQgdXBkYXRlIHRoZSBjb2RlIHRoYXQgaW1wb3J0cyB0aGUgZGF0YSAoYXMgSSBkZW1vbnN0cmF0ZSBiZWxvdykuKioKCmBgYHtyfQooaHJhX3JhdyA8LSByZWFkX2NzdihoZXJlOjpoZXJlKCJob21lLXJhbmdlLWRhdGFiYXNlLmNzdiIpKSkKYGBgCgoKMi4gIENvbnZlcnQgdGhlIGBjbGFzc2AgY29sdW1uICh3aGljaCBpcyB0ZXh0KSB0byBjcmVhdGUgYSBmYWN0b3IgY29sdW1uIGBjbGFzc19mY3RgIGFuZCBhc3NpZ24gdGhlIHJlc3VsdCB0byBhIHRpYmJsZSBgaHJhYC4gVXNlIGBmb3JjYXRzYCB0byBvcmRlciB0aGUgZmFjdG9yIGxldmVscyBhczoKICAgIDEuICBtYW1tYWxpYQogICAgMi4gIHJlcHRpbGlhCiAgICAzLiAgYXZlcwogICAgNC4gIGFjdGlub3B0ZXJ5Z2lpCiAgICAKYGBge3J9CihocmEgPC0gaHJhX3JhdyAlPiUgCiAgbXV0YXRlKGNsYXNzX2ZjdCA9IGZhY3RvcihjbGFzcywgbGV2ZWxzID0gYygibWFtbWFsaWEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJyZXB0aWxpYSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImF2ZXMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhY3Rpbm9wdGVyeWdpaSIpKSkgJT4lIAogICByZWxvY2F0ZShjbGFzc19mY3QsIC5hZnRlciA9IGNsYXNzKSkKYGBgCgozLiAgQ3JlYXRlIGEgc2NhdHRlcnBsb3Qgc2hvd2luZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYGxvZzEwLm1hc3NgIGFuZCBgbG9nMTAuaHJhYCBpbiBgaHJhYC4KCmBgYHtyfQpocmEgJT4lIAogIGdncGxvdChhZXMobG9nMTAubWFzcywgbG9nMTAuaHJhKSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhbHBoYSA9IDAuNykgKyAKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKNC4gIENvbG9yaXplIHRoZSBwb2ludHMgaW4gdGhlIHNjYXR0ZXJwbG90IGJ5IGBjbGFzc19mY3RgLgoKYGBge3J9CmhyYSAlPiUgCiAgZ2dwbG90KGFlcyhsb2cxMC5tYXNzLCBsb2cxMC5ocmEsIGNvbG9yID0gY2xhc3NfZmN0KSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhbHBoYSA9IDAuNykgKyAKICBzY2FsZV9jb2xvcl92aXJpZGlzX2QoZW5kID0gLjkpICsgCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKNS4gIERpc3BsYXkgYSBzY2F0dGVycGxvdCBzaG93aW5nIG9ubHkgZGF0YSBmb3IgYmlyZHMgKGNsYXNzIGBhdmVzYCkgYW5kIGZpdCBhIGxpbmVhciByZWdyZXNzaW9uIHRvIHRoYXQgZGF0YSB1c2luZyB0aGUgYGxtYCBmdW5jdGlvbi4KCmBgYHtyfQpocmEgJT4lIAogIGZpbHRlcihjbGFzcyA9PSAiYXZlcyIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvZzEwLm1hc3MsIGxvZzEwLmhyYSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMiwgYWxwaGEgPSAwLjcpICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKyAKICBsYWJzKHRpdGxlID0gIkxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBob21lIHJhbmdlIGFuZCBtYXNzIGZvciBBdmVzIikgKyAKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKIyMjIEZ1bmN0aW9uYWwgUHJvZ3JhbW1pbmcKCjEuICBXcml0ZSBhIGZ1bmN0aW9uIGNhbGxlZCBgc3VtbWFyaXplX3RhYmxlYCB0aGF0IHRha2VzIGEgdGl0bGUgc3RyaW5nIGFuZCBhIHRpYmJsZSBhcyBpbnB1dCBhbmQgcmV0dXJucyBhIHN0cmluZyB0aGF0IHNheXMgc29tZXRoaW5nIGxpa2UsICIqdGl0bGUqIGhhcyAqIyogcm93cyBhbmQgKiMqIGNvbHVtbnMiLiBGb3IgZXhhbXBsZSwgYHN1bW1hcml6ZV90YWJsZSgnb3VyIHRhYmxlJywgcGVyc29uKWAgc2hvdWxkIHJldHVybiB0aGUgc3RyaW5nIGAib3VyIHRhYmxlIGhhcyA1IHJvd3MgYW5kIDMgY29sdW1ucyJgLgoKCmBgYHtyfQpzdW1tYXJpemVfdGFibGUgPC0gZnVuY3Rpb24odGl0bGUsIGRmKSB7IAogIG5yb3cgPC0gbnJvdyhkZikKICBuY29sIDwtIG5jb2woZGYpIAogIAogIGdsdWU6OmdsdWUoInt0aXRsZX0gaGFzIHtucm93fSByb3dzIGFuZCB7bmNvbH0gY29sdW1ucy4iKQp9CgpzdW1tYXJpemVfdGFibGUoIm91ciB0YWJsZSIsIHBlcnNvbikKYGBgCgoyLiAgV3JpdGUgYW5vdGhlciBmdW5jdGlvbiBjYWxsZWQgYHNob3dfY29sdW1uc2AgdGhhdCB0YWtlcyBhIHN0cmluZyBhbmQgYSB0aWJibGUgYXMgaW5wdXQgYW5kIHJldHVybnMgYSBzdHJpbmcgdGhhdCBzYXlzIHNvbWV0aGluZyBsaWtlLCAiKnRhYmxlKiBoYXMgY29sdW1ucyAqbmFtZSosICpuYW1lKiwgKm5hbWUqIi4gRm9yIGV4YW1wbGUsIGBzaG93X2NvbHVtbnMoJ3BlcnNvbicsIHBlcnNvbilgIHNob3VsZCByZXR1cm4gdGhlIHN0cmluZyBgInBlcnNvbiBoYXMgY29sdW1ucyBwZXJzb25faWQsIHBlcnNvbmFsX25hbWUsIGZhbWlseV9uYW1lImAuCgoKYGBge3J9CnNob3dfY29sdW1ucyA8LSBmdW5jdGlvbih0aXRsZSwgZGYpIHsgCiAgY29sX25hbWVzIDwtIG5hbWVzKGRmKSAlPiUgCiAgICBzdHJfYyhjb2xsYXBzZSA9ICIsICIpCgpnbHVlOjpnbHVlKCJ7dGl0bGV9IGhhcyBjb2x1bW5zIHtjb2xfbmFtZXN9IikKfQoKc2hvd19jb2x1bW5zKCdwZXJzb24nLCBwZXJzb24pCmBgYAoKCjMuICBUaGUgZnVuY3Rpb24gYHJvd3NfZnJvbV9maWxlYCByZXR1cm5zIHRoZSBmaXJzdCAqTiogcm93cyBmcm9tIGEgdGFibGUgaW4gYSBDU1YgZmlsZSBnaXZlbiB0aGUgZmlsZSdzIG5hbWUgYW5kIHRoZSBudW1iZXIgb2Ygcm93cyBkZXNpcmVkLiBNb2RpZnkgaXQgc28gdGhhdCBpZiBubyB2YWx1ZSBpcyBzcGVjaWZpZWQgZm9yIHRoZSBudW1iZXIgb2Ygcm93cywgYSBkZWZhdWx0IG9mIDMgaXMgdXNlZC4KICAgIApgYGB7ciBldmFsPUZBTFNFLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1zaG93In0Kcm93c19mcm9tX2ZpbGUgPC0gZnVuY3Rpb24oZmlsZW5hbWUsIG51bV9yb3dzKSB7CiAgICAgIHJlYWRyOjpyZWFkX2NzdihmaWxlbmFtZSkgJT4lIGhlYWQobiA9IG51bV9yb3dzKQogICAgfQoKcm93c19mcm9tX2ZpbGUoIm1lYXN1cmVtZW50cy5jc3YiKSAjIHNob3VsZCBzaG93IDMgcm93cwpgYGAKCgpgYGB7cn0Kcm93c19mcm9tX2ZpbGUgPC0gZnVuY3Rpb24oZmlsZW5hbWUsIG51bV9yb3dzID0gMykgewogIHJlYWRyOjpyZWFkX2NzdihmaWxlbmFtZSkgJT4lIGhlYWQobiA9IG51bV9yb3dzKQp9Cgpyb3dzX2Zyb21fZmlsZSgibWVhc3VyZW1lbnRzLmNzdiIpCmBgYAoKCjQuICBUaGUgZnVuY3Rpb24gYGxvbmdfbmFtZWAgY2hlY2tzIHdoZXRoZXIgYSBzdHJpbmcgaXMgbG9uZ2VyIHRoYW4gNCBjaGFyYWN0ZXJzLiBVc2UgdGhpcyBmdW5jdGlvbiBhbmQgYSBmdW5jdGlvbiBmcm9tIGBwdXJycmAgdG8gY3JlYXRlIGEgbG9naWNhbCB2ZWN0b3IgdGhhdCBjb250YWlucyB0aGUgdmFsdWUgYFRSVUVgIHdoZXJlIGZhbWlseSBuYW1lcyBpbiB0aGUgdGliYmxlIGBwZXJzb25gIGFyZSBsb25nZXIgdGhhbiA0IGNoYXJhY3RlcnMsIGFuZCBgRkFMU0VgIHdoZXJlIHRoZXkgYXJlIDQgY2hhcmFjdGVycyBvciBsZXNzLgogICAgCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICJmb2xkLXNob3cifQpsb25nX25hbWUgPC0gZnVuY3Rpb24obmFtZSkgewogICAgICBzdHJpbmdyOjpzdHJfbGVuZ3RoKG5hbWUpID4gNAogICAgfQpgYGAKCgpgYGB7cn0KcGVyc29uICU+JSAKICBtdXRhdGUobG9uZ19mYW1pbHlfbmFtZSA9IG1hcF9sZ2woZmFtaWx5X25hbWUsIGxvbmdfbmFtZSkpCmBgYAoKIyMjIFdyYXBwaW5nIFVwCgoxLiAgTW9kaWZ5IHRoZSBZQU1MIGhlYWRlciBvZiB0aGlzIGZpbGUgc28gdGhhdCBhIHRhYmxlIG9mIGNvbnRlbnRzIGlzIGF1dG9tYXRpY2FsbHkgY3JlYXRlZCBlYWNoIHRpbWUgdGhpcyBkb2N1bWVudCBpcyBrbml0LCBhbmQgZml4IGFueSBlcnJvcnMgdGhhdCBhcmUgcHJldmVudGluZyB0aGUgZG9jdW1lbnQgZnJvbSBrbml0dGluZyBjbGVhbmx5LgoKYGBgCi0tLQp0aXRsZTogIlRpZHl2ZXJzZSBFeGFtIFZlcnNpb24gMi4wIgpvdXRwdXQ6Cmh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogZmxhdGx5Ci0tLQpgYGAKCioqQ29ycmVjdGVkIFlBTUwgaGVhZGVyOioqCgpgYGAKLS0tCnRpdGxlOiAiVGlkeXZlcnNlIEV4YW0gVmVyc2lvbiAyLjAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IGZsYXRseQogICAgdG9jOiBUUlVFCi0tLQpgYGAKCioqTm90ZTogWW91IG5lZWQgdG8gYWRkIGFuIGluZGVudGF0aW9uIGFmdGVyIGBodG1sX2RvY3VtZW50OmAgYW5kIGFkZCBgdG9jOiBUUlVFYCoqCgo8YnI+Cgo6Ojpib29rCllvdSBjYW4gcmVhZCBtb3JlIGFib3V0IHRoZSBSU3R1ZGlvIEluc3RydWN0b3IgVHJhaW5pbmcgYW5kIENlcnRpZmljYXRpb24gUHJvZ3JhbSBbaGVyZV0oaHR0cHM6Ly9lZHVjYXRpb24ucnN0dWRpby5jb20vdHJhaW5lcnMvKS4gVGhlcmUgaXMgW2Fub3RoZXIgc2FtcGxlIGV4YW1dKGh0dHBzOi8vZWR1Y2F0aW9uLnJzdHVkaW8uY29tL2Jsb2cvMjAyMC8wMi9pbnN0cnVjdG9yLWNlcnRpZmljYXRpb24tZXhhbXMvI3RpZHl2ZXJzZS1jZXJ0aWZpY2F0aW9uLWV4YW0pIGF2YWlsYWJsZSB3aXRoIFtzb2x1dGlvbnNdKGh0dHBzOi8vbWFybHljb3JtYXIuZ2l0aHViLmlvL3RpZHl2ZXJzZV9zYW1wbGVfZXhhbS9zYW1wbGVfZXhhbV9zb2xzL3NvbHMuaHRtbCksIGNvdXJ0ZXN5IG9mIFtNYXJseSBHb3R0aV0oaHR0cHM6Ly93d3cubWFybHlnb3R0aS5jb20vKS4gSSB3cm90ZSBhYm91dCBteSBvd24gZXhwZXJpZW5jZSB3aXRoIHRoZSB0cmFpbmluZyBhbmQgc2hhcmVkIHNvbWUgb2YgbXkgZXhhbSBwcmVwIG1hdGVyaWFscyBbaGVyZV0oaHR0cHM6Ly9iY3VsbGVuLnJiaW5kLmlvL3Bvc3QvMjAyMC0wOS0wMy1yZWZsZWN0aW9ucy1vbi1yc3R1ZGlvLWluc3RydWN0b3ItdHJhaW5pbmcvKS4gRmVlbCBmcmVlIHRvIFtyZWFjaCBvdXRdKGh0dHBzOi8vdHdpdHRlci5jb20vX2JjdWxsZW4pIHdpdGggYW55IHF1ZXN0aW9ucyEKOjo6Cgo8YnI+Cg==