The COVID-19 pandemics is a lot of bad things, but it’s also a source of data to play with…

Here we’re gonna use the data gathered by the John’s Hopkins Hospital and published on GitHub, for: - confirmed cases, and - deaths.

In case the links above die at some point, here is a version of these files as of April 21st, 2020:

In fact, a quick lookup on the Internet will now return plenty of versions for this data, with even cleaner (tidy) versions, including country population, etc. There are even R packages to get this. The point here is to learn how to read a given dataset, clean it and use it, so we’ll stay with the JHU dataset.

1 Data wrangling

  • First, load the tidyverse and lubridate packages
library(tidyverse)
library(lubridate)
theme_set(theme_bw())
  • Load the raw .csv files from the links above and store them in confirmed_raw and deaths_raw. You can provide read_csv() with an url. The numbers given in these tables are cumulative numbers of confirmed cases and deaths.
urlConfirmed <- "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv"
urlDeaths <- "https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_deaths_global.csv"
confirmed_raw <- read_csv(urlConfirmed)
deaths_raw    <- read_csv(urlDeaths)
  • Using successive operations with the pipe operator %>% create a function tidyfy_jhh(df, name), that, given a tibble df (i.e., the data from JHH we just loaded), will:
    • Rename the columns Province/State and Country/Region to state and country
    • Remove the lines from the Diamond Princess
    • Remove the columns from state, Lat and Long
    • Make the table tidy by having 3 columns : country, date and count
    • Using the mdy() function from the lubridate package, convert the date column to a R Date format
    • Some countries appear several times as their numbers are separated by provinces or states. Summarize the counts per country and per date as the total number of cases per country and per day.
      • Give this column the name of the type of case we look at (confirmed or deaths, string that id provided in the name parameter of the function tidyfy_jhh(df, name). For this, use the notation summarize(!!sym(name):= your_result_here) to tell R that name should be used as a column name)
tidyfy_jhh <- function(df, name) {
    df %>% 
        rename(state   = `Province/State`,
               country = `Country/Region`) %>%
        filter(country != "Diamond Princess") %>%
        select(-c(state, Lat, Long)) %>%
        pivot_longer(cols      = -country,
                     names_to  = "date",
                     values_to = "count") %>% 
        mutate(date = mdy(date)) %>% 
        group_by(country, date) %>% 
        summarize(!!sym(name):=sum(count)) %>% 
        ungroup()
}
  • Apply this function to the raw tibbles to get tidy confirmed and deaths tibbles.
confirmed <- tidyfy_jhh(confirmed_raw, 'confirmed')
deaths    <- tidyfy_jhh(deaths_raw, 'deaths')

In case you didn’t manage to get there, here are the csv files containing:

Now, join these three tibbles in alldata, and successively:

  • Add the columns newcases and newdeaths containing the number of new cases and deaths every day, as confirmed and deaths contain cumulative numbers. For this look into the lag() function.
  • Filter rows to remove data for which the number of cases is lower than the minimum number of cases in China (for easier comparison). This day will be considered as Day 1.
  • Add a day column containing the day number since Day 1.
  • Filter rows to have only countries for which we have at least one week of data
MIN <- min(confirmed$confirmed[confirmed$country == "China"])
alldata <- confirmed %>% 
            full_join(deaths) %>% 
            group_by(country) %>% 
            mutate(newcases  = confirmed - lag(confirmed),
                   newdeaths = deaths - lag(deaths)) %>% 
            filter(confirmed >= MIN) %>% 
            mutate(day=1:n()) %>% 
            filter(n() >= 7) %>% 
            ungroup()

In case you didn’t manage to get there, here is a csv file containing alldata so that you can continue with the exercise.

2 Plotting

  • Using ggplot2, plot (P1) side by side the number of confirmed cases and deaths vs time in 5 countries of your choice.
    • Using ggrepel, add the country names as annotation
    • Do the plot using linear scale, then a log scale
library(ggplot2)
library(ggrepel)

countries <- c("China","Italy","France","Spain","US")
colors <- c("black","red","seagreen","orange","royalblue")

labs <- alldata %>% 
            filter(country %in% countries) %>%
            group_by(country) %>% 
            filter(date==last(date)) %>% 
            ungroup()

P1a <- alldata %>% 
    filter(country %in% countries) %>%
    ggplot(aes(x=date, y=confirmed, color=country))+
        geom_line(size=1)+
        scale_color_manual(values=colors)+
        xlab("Days")+
        ylab("Number of Confirmed cases ")+
        theme(legend.position = 'none')+
        ggtitle("Confirmed cases")+
        scale_y_log10(
            breaks = 10^(seq(0, 10, by = 1)), 
            minor_breaks = rep(1:9, 2*5+1)*(10^rep(0:10, each=9)), 
            labels = scales::trans_format("log10", 
                scales::math_format(10^.x)))+
        annotation_logticks(sides ='lr')+
        geom_label_repel(data=labs, 
            show.legend=FALSE, 
            force=10,nudge_x=2,
            aes(x=date, y=confirmed, 
                col=country, 
                label = country))

P1b <- alldata %>% 
    filter(country %in% countries) %>%
    ggplot(aes(x=date, y=deaths, color=country))+
        geom_line(size=1)+
        scale_color_manual(values=colors)+
        xlab("Days")+
        ylab("Number of Deaths")+
        theme(legend.position = 'none')+
        ggtitle("Deaths")+
        scale_y_log10(
            breaks = 10^(seq(0, 10, by = 1)), 
            minor_breaks = rep(1:9, 2*5+1)*(10^rep(0:10, each=9)), 
            labels = scales::trans_format("log10", 
                scales::math_format(10^.x)))+
        geom_label_repel(data=labs, 
            show.legend=FALSE, 
            force=10,nudge_x=2,
            aes(x=date, y=deaths, 
                col=country, 
                label = country))
library(patchwork)
P1a + P1b

  • Plot (P2) the death ratio (i.e. deaths per confirmed cases) per country averaged over time as a barplot, for 50 random countries.
    • Try ordering the countries per decreasing ratio using reorder()
    • Flip the plot to have horizontal bars
    • Is there anything wrong?
alldata %>% 
    group_by(country) %>% 
    summarize(ratio = mean(deaths/confirmed*100)) %>% 
    sample_n(50) %>% 
    ggplot(aes(x=reorder(country,ratio), y=ratio, fill=country))+
        geom_bar(stat="identity", position="dodge") +
        ylab("Death ratio [%]")+
        coord_flip()+
        theme(axis.title.y=element_blank(),
              axis.ticks.y=element_blank(),
              legend.position = 'none')+
        ggtitle("Death ratio")

  • To better see the effect of confinement regulation, it’s probably more interesting to plot the number of new confirmed cases.
    • Plot as a barplot the number of new confirmed cases per day for 2 countries of your choice, and add a vertical line corresponding to the date of application of confinement rules. For example, in France and Italy it was on March 17th and March 10th, respectively.
    • Add a smooth line to smooth out the effect or irregular numbers reporting. You can play with the span parameter to have a more or less smooth curve.
conf_france <- "2020-03-17"
conf_italy  <- "2020-03-10"
confdates <- tibble(country = c("France", "Italy"),
                    date    = ymd(c(conf_france, conf_italy)))
colors <- c('royalblue','orange')
alldata %>% 
    filter(country %in% c("France", "Italy")) %>%
    filter(newcases>0) %>% 
    ggplot(aes(x=date, y=newcases, fill=country))+
        scale_fill_manual(values=colors)+
        scale_color_manual(values=colors)+
        geom_bar(stat = 'identity', position = 'dodge', alpha=.7)+
        geom_smooth(span=.15, se=FALSE, aes(color=country), show.legend = FALSE)+
        geom_vline(data=confdates, aes(xintercept = date, color=country), 
                   lty=2, show.legend = FALSE)+
        scale_x_date(minor_breaks='1 week')+
        coord_cartesian(y=c(0,.6e5))+
        labs(x="Date",
             y="New confirmed cases",
             fill="")+
        ggtitle("New confirmed cases") -> P1
alldata %>% 
    filter(country %in% c("France", "Italy")) %>%
    filter(newdeaths>0) %>% 
    filter(date>as.Date("2020-02-25")) %>% 
    ggplot(aes(x=date, y=newdeaths, fill=country))+
        scale_fill_manual(values=colors)+
        scale_color_manual(values=colors)+
        geom_bar(stat = 'identity', position = 'dodge', alpha=.7)+
        geom_smooth(span=.15, se=FALSE, aes(color=country), show.legend = FALSE)+
        geom_vline(data=confdates, aes(xintercept = date, color=country), 
                   lty=2, show.legend = FALSE)+
        scale_x_date(minor_breaks='1 week')+
        labs(x="Date",
             y="New deaths",
             fill="")+
        ggtitle("New deaths") -> P2
P1 + P2 + plot_layout(guides = "collect") & theme(legend.position='bottom')

3 Fitting

  • Load the library broom (vignette). broom allows tidying the output of fit functions such as lm() or nls(), taking the text output and making it into a tibble.
  • Now then, using the same procedure as in the example above, fit the total Confirmed cases for the 5 countries you chose earlier for the 100 first days using a logistic function, and plot the data and their fit, and show the results of the fits. Here is how the plot should look like without ever running a for loop:
library(broom)
library(minpack.lm)
countries <- c("China","Italy","France","Spain")
colors    <- c("black","red","seagreen","orange")
fits <- alldata %>% 
    filter(country %in% countries) %>% 
    filter(day <= 100) %>% 
    nest(data=-country) %>% 
    mutate(fit=map(data, ~ nlsLM(data = .,
               confirmed ~ a/(1+exp(-b*(day-c))), 
               start=list(a=max(.$confirmed), b=.1, c=15))),
           tidied = map(fit, tidy),
           augmented = map(fit, augment))
fits %>% unnest(augmented) %>% 
    ggplot(aes(x=day, color=country))+
        geom_point(aes(y=confirmed), alpha=.2, size=3)+
        geom_line(aes(y=.fitted))+
        scale_color_manual(values=colors, name="")+
        theme(legend.position='top')+
        xlab("Day since Day 1")+
        ylab("Total confirmed cases")

fits %>% unnest(tidied) %>% 
  select(country, term, estimate) %>% 
  pivot_wider(names_from = term, 
              values_from = estimate)
## # A tibble: 4 x 4
##   country       a      b     c
##   <chr>     <dbl>  <dbl> <dbl>
## 1 China    82196. 0.214   19.0
## 2 France  187901. 0.127   36.8
## 3 Italy   228437. 0.0923  37.4
## 4 Spain   233623. 0.122   28.3

In fact, this exponential model is rather wrong… A better model of epidemics dynamics is done by the SIR model, for Susceptible, Infectious, Recovered. A description of the SIR modeling through R can be found here, for example.

LS0tCnRpdGxlIDogIlIgRXhlcmNpc2VzIC0gQ09WSUQiCmRhdGUgIDogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogICAgaHRtbF9kb2N1bWVudDoKICAgICAgICB0b2MgICAgICAgICAgICA6IHRydWUKICAgICAgICB0b2NfZmxvYXQgICAgICA6IHRydWUKICAgICAgICB0b2NfZGVwdGggICAgICA6IDQKICAgICAgICBoaWdobGlnaHQgICAgICA6IHRhbmdvCiAgICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICAgICAgY29kZV9kb3dubG9hZCAgOiB0cnVlCnBhcmFtczogCiAgICBzb2x1dGlvbjoKICAgICAgICB2YWx1ZTogdHJ1ZQotLS0KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CmJsb2NrcXVvdGUgewogIGJhY2tncm91bmQ6ICNFOUY5RkY7CiAgYm9yZGVyLWxlZnQ6IDVweCBzb2xpZCAjMDI2MDg2OwogIG1hcmdpbjogMS41ZW0gMTBweDsKICBwYWRkaW5nOiAwLjVlbSAxMHB4OwogIGZvbnQtc2l6ZTogMWVtOwp9Cjwvc3R5bGU+CgoKVGhlIENPVklELTE5IHBhbmRlbWljcyBpcyBhIGxvdCBvZiBiYWQgdGhpbmdzLCBidXQgaXQncyBhbHNvIGEgc291cmNlIG9mIGRhdGEgdG8gcGxheSB3aXRoLi4uCgpIZXJlIHdlJ3JlIGdvbm5hIHVzZSB0aGUgZGF0YSBnYXRoZXJlZCBieSB0aGUgSm9obidzIEhvcGtpbnMgSG9zcGl0YWwgYW5kIHB1Ymxpc2hlZCBvbiBbR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkpLCBmb3I6Ci0gW2NvbmZpcm1lZCBjYXNlc10oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L21hc3Rlci9jc3NlX2NvdmlkXzE5X2RhdGEvY3NzZV9jb3ZpZF8xOV90aW1lX3Nlcmllcy90aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2KSwgYW5kIAotIFtkZWF0aHNdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9DU1NFR0lTYW5kRGF0YS9DT1ZJRC0xOS9tYXN0ZXIvY3NzZV9jb3ZpZF8xOV9kYXRhL2Nzc2VfY292aWRfMTlfdGltZV9zZXJpZXMvdGltZV9zZXJpZXNfY292aWQxOV9kZWF0aHNfZ2xvYmFsLmNzdikuCgpJbiBjYXNlIHRoZSBsaW5rcyBhYm92ZSBkaWUgYXQgc29tZSBwb2ludCwgaGVyZSBpcyBhIHZlcnNpb24gb2YgdGhlc2UgZmlsZXMgYXMgb2YgQXByaWwgMjFzdCwgMjAyMDoKCi0gW2NvbmZpcm1lZCBjYXNlc10oRGF0YS90aW1lX3Nlcmllc19jb3ZpZDE5X2NvbmZpcm1lZF9nbG9iYWwuY3N2KQotIFtkZWF0aHNdKERhdGEvdGltZV9zZXJpZXNfY292aWQxOV9kZWF0aHNfZ2xvYmFsLmNzdikKCkluIGZhY3QsIGEgW3F1aWNrIGxvb2t1cF0oaHR0cHM6Ly93d3cuc3RhdHNhbmRyLmNvbS9ibG9nL3RvcC1yLXJlc291cmNlcy1vbi1jb3ZpZC0xOS1jb3JvbmF2aXJ1cy8jci1zaGlueS1hcHBzKSBvbiB0aGUgSW50ZXJuZXQgd2lsbCBub3cgcmV0dXJuIHBsZW50eSBvZiB2ZXJzaW9ucyBmb3IgdGhpcyBkYXRhLCB3aXRoIGV2ZW4gY2xlYW5lciAodGlkeSkgdmVyc2lvbnMsIGluY2x1ZGluZyBjb3VudHJ5IHBvcHVsYXRpb24sIGV0Yy4gVGhlcmUgYXJlIGV2ZW4gUiBwYWNrYWdlcyB0byBnZXQgdGhpcy4KVGhlIHBvaW50IGhlcmUgaXMgdG8gbGVhcm4gaG93IHRvIHJlYWQgYSBnaXZlbiBkYXRhc2V0LCBjbGVhbiBpdCBhbmQgdXNlIGl0LCBzbyB3ZSdsbCBzdGF5IHdpdGggdGhlIEpIVSBkYXRhc2V0LgoKCiMgRGF0YSB3cmFuZ2xpbmcKCi0gRmlyc3QsIGxvYWQgdGhlIGB0aWR5dmVyc2VgIGFuZCBgbHVicmlkYXRlYCBwYWNrYWdlcwoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0UsIGZpZy5hc3A9MS41fQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShsdWJyaWRhdGUpCnRoZW1lX3NldCh0aGVtZV9idygpKQpgYGAKCi0gTG9hZCB0aGUgcmF3IC5jc3YgZmlsZXMgZnJvbSB0aGUgbGlua3MgYWJvdmUgYW5kIHN0b3JlIHRoZW0gaW4gYGNvbmZpcm1lZF9yYXdgIGFuZCBgZGVhdGhzX3Jhd2AuIFlvdSBjYW4gcHJvdmlkZSBgcmVhZF9jc3YoKWB7LlJ9IHdpdGggYW4gdXJsLiBUaGUgbnVtYmVycyBnaXZlbiBpbiB0aGVzZSB0YWJsZXMgYXJlIGN1bXVsYXRpdmUgbnVtYmVycyBvZiBjb25maXJtZWQgY2FzZXMgYW5kIGRlYXRocy4KCmBgYHtyIGluY2x1ZGU9cGFyYW1zJHNvbHV0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFLCBmaWcuYXNwPTEuNX0KdXJsQ29uZmlybWVkIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vQ1NTRUdJU2FuZERhdGEvQ09WSUQtMTkvbWFzdGVyL2Nzc2VfY292aWRfMTlfZGF0YS9jc3NlX2NvdmlkXzE5X3RpbWVfc2VyaWVzL3RpbWVfc2VyaWVzX2NvdmlkMTlfY29uZmlybWVkX2dsb2JhbC5jc3YiCnVybERlYXRocyA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0NTU0VHSVNhbmREYXRhL0NPVklELTE5L21hc3Rlci9jc3NlX2NvdmlkXzE5X2RhdGEvY3NzZV9jb3ZpZF8xOV90aW1lX3Nlcmllcy90aW1lX3Nlcmllc19jb3ZpZDE5X2RlYXRoc19nbG9iYWwuY3N2Igpjb25maXJtZWRfcmF3IDwtIHJlYWRfY3N2KHVybENvbmZpcm1lZCkKZGVhdGhzX3JhdyAgICA8LSByZWFkX2Nzdih1cmxEZWF0aHMpCmBgYAoKLSBVc2luZyBzdWNjZXNzaXZlIG9wZXJhdGlvbnMgd2l0aCB0aGUgcGlwZSBvcGVyYXRvciBgJT4lYHsuUn0gY3JlYXRlIGEgZnVuY3Rpb24gYHRpZHlmeV9qaGgoZGYsIG5hbWUpYHsuUn0sIHRoYXQsIGdpdmVuIGEgdGliYmxlIGBkZmAgKF9pLmUuXywgdGhlIGRhdGEgZnJvbSBKSEggd2UganVzdCBsb2FkZWQpLCB3aWxsOgogICAgKyBSZW5hbWUgdGhlIGNvbHVtbnMgYFByb3ZpbmNlL1N0YXRlYCBhbmQgYENvdW50cnkvUmVnaW9uYCB0byBgc3RhdGVgIGFuZCBgY291bnRyeWAKICAgICsgUmVtb3ZlIHRoZSBsaW5lcyBmcm9tIHRoZSBgRGlhbW9uZCBQcmluY2Vzc2AKICAgICsgUmVtb3ZlIHRoZSBjb2x1bW5zIGZyb20gYHN0YXRlYCwgYExhdGAgYW5kIGBMb25nYAogICAgKyBNYWtlIHRoZSB0YWJsZSB0aWR5IGJ5IGhhdmluZyAzIGNvbHVtbnMgOiBgY291bnRyeWAsIGBkYXRlYCBhbmQgYGNvdW50YAogICAgKyBVc2luZyB0aGUgYG1keSgpYHsuUn0gZnVuY3Rpb24gZnJvbSB0aGUgYGx1YnJpZGF0ZWAgcGFja2FnZSwgY29udmVydCB0aGUgYGRhdGVgIGNvbHVtbiB0byBhIFIgRGF0ZSBmb3JtYXQKICAgICsgU29tZSBjb3VudHJpZXMgYXBwZWFyIHNldmVyYWwgdGltZXMgYXMgdGhlaXIgbnVtYmVycyBhcmUgc2VwYXJhdGVkIGJ5IHByb3ZpbmNlcyBvciBzdGF0ZXMuIFN1bW1hcml6ZSB0aGUgY291bnRzIHBlciBjb3VudHJ5IGFuZCBwZXIgZGF0ZSBhcyB0aGUgdG90YWwgbnVtYmVyIG9mIGNhc2VzIHBlciBjb3VudHJ5IGFuZCBwZXIgZGF5LiAKICAgICAgICArIEdpdmUgdGhpcyBjb2x1bW4gdGhlIG5hbWUgb2YgdGhlIHR5cGUgb2YgY2FzZSB3ZSBsb29rIGF0IChjb25maXJtZWQgb3IgZGVhdGhzLCBzdHJpbmcgdGhhdCBpZCBwcm92aWRlZCBpbiB0aGUgYG5hbWVgIHBhcmFtZXRlciBvZiB0aGUgZnVuY3Rpb24gYHRpZHlmeV9qaGgoZGYsIG5hbWUpYHsuUn0uIEZvciB0aGlzLCB1c2UgdGhlIG5vdGF0aW9uIGBzdW1tYXJpemUoISFzeW0obmFtZSk6PSB5b3VyX3Jlc3VsdF9oZXJlKWB7LlJ9IHRvIHRlbGwgUiB0aGF0IGBuYW1lYCBzaG91bGQgYmUgdXNlZCBhcyBhIGNvbHVtbiBuYW1lKQoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0UsIGZpZy5hc3A9MS41fQp0aWR5ZnlfamhoIDwtIGZ1bmN0aW9uKGRmLCBuYW1lKSB7CiAgICBkZiAlPiUgCiAgICAgICAgcmVuYW1lKHN0YXRlICAgPSBgUHJvdmluY2UvU3RhdGVgLAogICAgICAgICAgICAgICBjb3VudHJ5ID0gYENvdW50cnkvUmVnaW9uYCkgJT4lCiAgICAgICAgZmlsdGVyKGNvdW50cnkgIT0gIkRpYW1vbmQgUHJpbmNlc3MiKSAlPiUKICAgICAgICBzZWxlY3QoLWMoc3RhdGUsIExhdCwgTG9uZykpICU+JQogICAgICAgIHBpdm90X2xvbmdlcihjb2xzICAgICAgPSAtY291bnRyeSwKICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gID0gImRhdGUiLAogICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiY291bnQiKSAlPiUgCiAgICAgICAgbXV0YXRlKGRhdGUgPSBtZHkoZGF0ZSkpICU+JSAKICAgICAgICBncm91cF9ieShjb3VudHJ5LCBkYXRlKSAlPiUgCiAgICAgICAgc3VtbWFyaXplKCEhc3ltKG5hbWUpOj1zdW0oY291bnQpKSAlPiUgCiAgICAgICAgdW5ncm91cCgpCn0KYGBgCgotIEFwcGx5IHRoaXMgZnVuY3Rpb24gdG8gdGhlIHJhdyB0aWJibGVzIHRvIGdldCB0aWR5IGBjb25maXJtZWRgIGFuZCBgZGVhdGhzYCB0aWJibGVzLgoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0UsIGZpZy5hc3A9MS41fQpjb25maXJtZWQgPC0gdGlkeWZ5X2poaChjb25maXJtZWRfcmF3LCAnY29uZmlybWVkJykKZGVhdGhzICAgIDwtIHRpZHlmeV9qaGgoZGVhdGhzX3JhdywgJ2RlYXRocycpCmBgYAoKPiBJbiBjYXNlIHlvdSBkaWRuJ3QgbWFuYWdlIHRvIGdldCB0aGVyZSwgaGVyZSBhcmUgdGhlIGNzdiBmaWxlcyBjb250YWluaW5nOgo+IAo+IC0gW2NvbmZpcm1lZF0oRGF0YS9jb3ZpZF9jb25maXJtZWQuY3N2KQo+IC0gW2RlYXRoc10oRGF0YS9jb3ZpZF9kZWF0aHMuY3N2KQoKTm93LCBqb2luIHRoZXNlIHRocmVlIHRpYmJsZXMgaW4gYGFsbGRhdGFgLCBhbmQgc3VjY2Vzc2l2ZWx5OgoKLSBBZGQgdGhlIGNvbHVtbnMgYG5ld2Nhc2VzYCBhbmQgYG5ld2RlYXRoc2AgY29udGFpbmluZyB0aGUgbnVtYmVyIG9mIG5ldyBjYXNlcyBhbmQgZGVhdGhzIGV2ZXJ5IGRheSwgYXMgYGNvbmZpcm1lZGAgYW5kIGBkZWF0aHNgIGNvbnRhaW4gY3VtdWxhdGl2ZSBudW1iZXJzLiBGb3IgdGhpcyBsb29rIGludG8gdGhlIGBsYWcoKWB7LlJ9IGZ1bmN0aW9uLgotIEZpbHRlciByb3dzIHRvIHJlbW92ZSBkYXRhIGZvciB3aGljaCB0aGUgbnVtYmVyIG9mIGNhc2VzIGlzIGxvd2VyIHRoYW4gdGhlIG1pbmltdW0gbnVtYmVyIG9mIGNhc2VzIGluIENoaW5hIChmb3IgZWFzaWVyIGNvbXBhcmlzb24pLiBUaGlzIGRheSB3aWxsIGJlIGNvbnNpZGVyZWQgYXMgYERheSAxYC4KLSBBZGQgYSBgZGF5YCBjb2x1bW4gY29udGFpbmluZyB0aGUgZGF5IG51bWJlciBzaW5jZSBgRGF5IDFgLgotIEZpbHRlciByb3dzIHRvIGhhdmUgb25seSBjb3VudHJpZXMgZm9yIHdoaWNoIHdlIGhhdmUgYXQgbGVhc3Qgb25lIHdlZWsgb2YgZGF0YQoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0UsIGZpZy5hc3A9MS41fQpNSU4gPC0gbWluKGNvbmZpcm1lZCRjb25maXJtZWRbY29uZmlybWVkJGNvdW50cnkgPT0gIkNoaW5hIl0pCmFsbGRhdGEgPC0gY29uZmlybWVkICU+JSAKICAgICAgICAgICAgZnVsbF9qb2luKGRlYXRocykgJT4lIAogICAgICAgICAgICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgICAgICAgICAgIG11dGF0ZShuZXdjYXNlcyAgPSBjb25maXJtZWQgLSBsYWcoY29uZmlybWVkKSwKICAgICAgICAgICAgICAgICAgIG5ld2RlYXRocyA9IGRlYXRocyAtIGxhZyhkZWF0aHMpKSAlPiUgCiAgICAgICAgICAgIGZpbHRlcihjb25maXJtZWQgPj0gTUlOKSAlPiUgCiAgICAgICAgICAgIG11dGF0ZShkYXk9MTpuKCkpICU+JSAKICAgICAgICAgICAgZmlsdGVyKG4oKSA+PSA3KSAlPiUgCiAgICAgICAgICAgIHVuZ3JvdXAoKQpgYGAKCj4gSW4gY2FzZSB5b3UgZGlkbid0IG1hbmFnZSB0byBnZXQgdGhlcmUsIFtoZXJlIGlzIGEgY3N2IGZpbGVdKERhdGEvY292aWRfYWxsZGF0YS5jc3YpIGNvbnRhaW5pbmcgYGFsbGRhdGFgIHNvIHRoYXQgeW91IGNhbiBjb250aW51ZSB3aXRoIHRoZSBleGVyY2lzZS4KCgojIFBsb3R0aW5nCgotIFVzaW5nIGBnZ3Bsb3QyYCwgcGxvdCAoUDEpICoqc2lkZSBieSBzaWRlKiogdGhlIG51bWJlciBvZiAqKmNvbmZpcm1lZCoqIGNhc2VzIGFuZCAqKmRlYXRocyoqIHZzIHRpbWUgaW4gNSBjb3VudHJpZXMgb2YgeW91ciBjaG9pY2UuCiAgICAtIFVzaW5nIGBnZ3JlcGVsYCwgYWRkIHRoZSBjb3VudHJ5IG5hbWVzIGFzIGFubm90YXRpb24KICAgIC0gRG8gdGhlIHBsb3QgdXNpbmcgbGluZWFyIHNjYWxlLCB0aGVuIGEgbG9nIHNjYWxlCgpgYGB7ciBpbmNsdWRlPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRSwgZmlnLmFzcD0uOH0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdncmVwZWwpCgpjb3VudHJpZXMgPC0gYygiQ2hpbmEiLCJJdGFseSIsIkZyYW5jZSIsIlNwYWluIiwiVVMiKQpjb2xvcnMgPC0gYygiYmxhY2siLCJyZWQiLCJzZWFncmVlbiIsIm9yYW5nZSIsInJveWFsYmx1ZSIpCgpsYWJzIDwtIGFsbGRhdGEgJT4lIAogICAgICAgICAgICBmaWx0ZXIoY291bnRyeSAlaW4lIGNvdW50cmllcykgJT4lCiAgICAgICAgICAgIGdyb3VwX2J5KGNvdW50cnkpICU+JSAKICAgICAgICAgICAgZmlsdGVyKGRhdGU9PWxhc3QoZGF0ZSkpICU+JSAKICAgICAgICAgICAgdW5ncm91cCgpCgpQMWEgPC0gYWxsZGF0YSAlPiUgCiAgICBmaWx0ZXIoY291bnRyeSAlaW4lIGNvdW50cmllcykgJT4lCiAgICBnZ3Bsb3QoYWVzKHg9ZGF0ZSwgeT1jb25maXJtZWQsIGNvbG9yPWNvdW50cnkpKSsKICAgICAgICBnZW9tX2xpbmUoc2l6ZT0xKSsKICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNvbG9ycykrCiAgICAgICAgeGxhYigiRGF5cyIpKwogICAgICAgIHlsYWIoIk51bWJlciBvZiBDb25maXJtZWQgY2FzZXMgIikrCiAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSsKICAgICAgICBnZ3RpdGxlKCJDb25maXJtZWQgY2FzZXMiKSsKICAgICAgICBzY2FsZV95X2xvZzEwKAogICAgICAgICAgICBicmVha3MgPSAxMF4oc2VxKDAsIDEwLCBieSA9IDEpKSwgCiAgICAgICAgICAgIG1pbm9yX2JyZWFrcyA9IHJlcCgxOjksIDIqNSsxKSooMTBecmVwKDA6MTAsIGVhY2g9OSkpLCAKICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjp0cmFuc19mb3JtYXQoImxvZzEwIiwgCiAgICAgICAgICAgICAgICBzY2FsZXM6Om1hdGhfZm9ybWF0KDEwXi54KSkpKwogICAgICAgIGFubm90YXRpb25fbG9ndGlja3Moc2lkZXMgPSdscicpKwogICAgICAgIGdlb21fbGFiZWxfcmVwZWwoZGF0YT1sYWJzLCAKICAgICAgICAgICAgc2hvdy5sZWdlbmQ9RkFMU0UsIAogICAgICAgICAgICBmb3JjZT0xMCxudWRnZV94PTIsCiAgICAgICAgICAgIGFlcyh4PWRhdGUsIHk9Y29uZmlybWVkLCAKICAgICAgICAgICAgICAgIGNvbD1jb3VudHJ5LCAKICAgICAgICAgICAgICAgIGxhYmVsID0gY291bnRyeSkpCgpQMWIgPC0gYWxsZGF0YSAlPiUgCiAgICBmaWx0ZXIoY291bnRyeSAlaW4lIGNvdW50cmllcykgJT4lCiAgICBnZ3Bsb3QoYWVzKHg9ZGF0ZSwgeT1kZWF0aHMsIGNvbG9yPWNvdW50cnkpKSsKICAgICAgICBnZW9tX2xpbmUoc2l6ZT0xKSsKICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNvbG9ycykrCiAgICAgICAgeGxhYigiRGF5cyIpKwogICAgICAgIHlsYWIoIk51bWJlciBvZiBEZWF0aHMiKSsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpKwogICAgICAgIGdndGl0bGUoIkRlYXRocyIpKwogICAgICAgIHNjYWxlX3lfbG9nMTAoCiAgICAgICAgICAgIGJyZWFrcyA9IDEwXihzZXEoMCwgMTAsIGJ5ID0gMSkpLCAKICAgICAgICAgICAgbWlub3JfYnJlYWtzID0gcmVwKDE6OSwgMio1KzEpKigxMF5yZXAoMDoxMCwgZWFjaD05KSksIAogICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnRyYW5zX2Zvcm1hdCgibG9nMTAiLCAKICAgICAgICAgICAgICAgIHNjYWxlczo6bWF0aF9mb3JtYXQoMTBeLngpKSkrCiAgICAgICAgZ2VvbV9sYWJlbF9yZXBlbChkYXRhPWxhYnMsIAogICAgICAgICAgICBzaG93LmxlZ2VuZD1GQUxTRSwgCiAgICAgICAgICAgIGZvcmNlPTEwLG51ZGdlX3g9MiwKICAgICAgICAgICAgYWVzKHg9ZGF0ZSwgeT1kZWF0aHMsIAogICAgICAgICAgICAgICAgY29sPWNvdW50cnksIAogICAgICAgICAgICAgICAgbGFiZWwgPSBjb3VudHJ5KSkKbGlicmFyeShwYXRjaHdvcmspClAxYSArIFAxYgpgYGAKCi0gUGxvdCAoUDIpIHRoZSBkZWF0aCByYXRpbyAoKmkuZS4qIGRlYXRocyBwZXIgY29uZmlybWVkIGNhc2VzKSBwZXIgY291bnRyeSBhdmVyYWdlZCBvdmVyIHRpbWUgYXMgYSBiYXJwbG90LCBmb3IgNTAgcmFuZG9tIGNvdW50cmllcy4KICAgICsgVHJ5IG9yZGVyaW5nIHRoZSBjb3VudHJpZXMgcGVyIGRlY3JlYXNpbmcgcmF0aW8gdXNpbmcgYHJlb3JkZXIoKWB7LlJ9CiAgICArIEZsaXAgdGhlIHBsb3QgdG8gaGF2ZSBob3Jpem9udGFsIGJhcnMKICAgICsgSXMgdGhlcmUgYW55dGhpbmcgd3Jvbmc/CgpgYGB7ciBpbmNsdWRlPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRSwgZmlnLmFzcD0xLjd9CmFsbGRhdGEgJT4lIAogICAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogICAgc3VtbWFyaXplKHJhdGlvID0gbWVhbihkZWF0aHMvY29uZmlybWVkKjEwMCkpICU+JSAKICAgIHNhbXBsZV9uKDUwKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihjb3VudHJ5LHJhdGlvKSwgeT1yYXRpbywgZmlsbD1jb3VudHJ5KSkrCiAgICAgICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbj0iZG9kZ2UiKSArCiAgICAgICAgeWxhYigiRGVhdGggcmF0aW8gWyVdIikrCiAgICAgICAgY29vcmRfZmxpcCgpKwogICAgICAgIHRoZW1lKGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpKwogICAgICAgIGdndGl0bGUoIkRlYXRoIHJhdGlvIikKYGBgCgotIFRvIGJldHRlciBzZWUgdGhlIGVmZmVjdCBvZiBjb25maW5lbWVudCByZWd1bGF0aW9uLCBpdCdzIHByb2JhYmx5IG1vcmUgaW50ZXJlc3RpbmcgdG8gcGxvdCB0aGUgbnVtYmVyIG9mIG5ldyBjb25maXJtZWQgY2FzZXMuIAogICAgLSBQbG90IGFzIGEgYmFycGxvdCB0aGUgbnVtYmVyIG9mIG5ldyBjb25maXJtZWQgY2FzZXMgcGVyIGRheSBmb3IgMiBjb3VudHJpZXMgb2YgeW91ciBjaG9pY2UsIGFuZCBhZGQgYSB2ZXJ0aWNhbCBsaW5lIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGRhdGUgb2YgYXBwbGljYXRpb24gb2YgY29uZmluZW1lbnQgcnVsZXMuIEZvciBleGFtcGxlLCBpbiBGcmFuY2UgYW5kIEl0YWx5IGl0IHdhcyBvbiBNYXJjaCAxN3RoIGFuZCBNYXJjaCAxMHRoLCByZXNwZWN0aXZlbHkuCiAgICAtIEFkZCBhIHNtb290aCBsaW5lIHRvIHNtb290aCBvdXQgdGhlIGVmZmVjdCBvciBpcnJlZ3VsYXIgbnVtYmVycyByZXBvcnRpbmcuIFlvdSBjYW4gcGxheSB3aXRoIHRoZSBgc3BhbmAgcGFyYW1ldGVyIHRvIGhhdmUgYSBtb3JlIG9yIGxlc3Mgc21vb3RoIGN1cnZlLgoKYGBge3IgaW5jbHVkZT1wYXJhbXMkc29sdXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmNvbmZfZnJhbmNlIDwtICIyMDIwLTAzLTE3Igpjb25mX2l0YWx5ICA8LSAiMjAyMC0wMy0xMCIKY29uZmRhdGVzIDwtIHRpYmJsZShjb3VudHJ5ID0gYygiRnJhbmNlIiwgIkl0YWx5IiksCiAgICAgICAgICAgICAgICAgICAgZGF0ZSAgICA9IHltZChjKGNvbmZfZnJhbmNlLCBjb25mX2l0YWx5KSkpCmNvbG9ycyA8LSBjKCdyb3lhbGJsdWUnLCdvcmFuZ2UnKQphbGxkYXRhICU+JSAKICAgIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiRnJhbmNlIiwgIkl0YWx5IikpICU+JQogICAgZmlsdGVyKG5ld2Nhc2VzPjApICU+JSAKICAgIGdncGxvdChhZXMoeD1kYXRlLCB5PW5ld2Nhc2VzLCBmaWxsPWNvdW50cnkpKSsKICAgICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JzKSsKICAgICAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWNvbG9ycykrCiAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIHBvc2l0aW9uID0gJ2RvZGdlJywgYWxwaGE9LjcpKwogICAgICAgIGdlb21fc21vb3RoKHNwYW49LjE1LCBzZT1GQUxTRSwgYWVzKGNvbG9yPWNvdW50cnkpLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSsKICAgICAgICBnZW9tX3ZsaW5lKGRhdGE9Y29uZmRhdGVzLCBhZXMoeGludGVyY2VwdCA9IGRhdGUsIGNvbG9yPWNvdW50cnkpLCAKICAgICAgICAgICAgICAgICAgIGx0eT0yLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSsKICAgICAgICBzY2FsZV94X2RhdGUobWlub3JfYnJlYWtzPScxIHdlZWsnKSsKICAgICAgICBjb29yZF9jYXJ0ZXNpYW4oeT1jKDAsLjZlNSkpKwogICAgICAgIGxhYnMoeD0iRGF0ZSIsCiAgICAgICAgICAgICB5PSJOZXcgY29uZmlybWVkIGNhc2VzIiwKICAgICAgICAgICAgIGZpbGw9IiIpKwogICAgICAgIGdndGl0bGUoIk5ldyBjb25maXJtZWQgY2FzZXMiKSAtPiBQMQphbGxkYXRhICU+JSAKICAgIGZpbHRlcihjb3VudHJ5ICVpbiUgYygiRnJhbmNlIiwgIkl0YWx5IikpICU+JQogICAgZmlsdGVyKG5ld2RlYXRocz4wKSAlPiUgCiAgICBmaWx0ZXIoZGF0ZT5hcy5EYXRlKCIyMDIwLTAyLTI1IikpICU+JSAKICAgIGdncGxvdChhZXMoeD1kYXRlLCB5PW5ld2RlYXRocywgZmlsbD1jb3VudHJ5KSkrCiAgICAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbG9ycykrCiAgICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jb2xvcnMpKwogICAgICAgIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLCBwb3NpdGlvbiA9ICdkb2RnZScsIGFscGhhPS43KSsKICAgICAgICBnZW9tX3Ntb290aChzcGFuPS4xNSwgc2U9RkFMU0UsIGFlcyhjb2xvcj1jb3VudHJ5KSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkrCiAgICAgICAgZ2VvbV92bGluZShkYXRhPWNvbmZkYXRlcywgYWVzKHhpbnRlcmNlcHQgPSBkYXRlLCBjb2xvcj1jb3VudHJ5KSwgCiAgICAgICAgICAgICAgICAgICBsdHk9Miwgc2hvdy5sZWdlbmQgPSBGQUxTRSkrCiAgICAgICAgc2NhbGVfeF9kYXRlKG1pbm9yX2JyZWFrcz0nMSB3ZWVrJykrCiAgICAgICAgbGFicyh4PSJEYXRlIiwKICAgICAgICAgICAgIHk9Ik5ldyBkZWF0aHMiLAogICAgICAgICAgICAgZmlsbD0iIikrCiAgICAgICAgZ2d0aXRsZSgiTmV3IGRlYXRocyIpIC0+IFAyClAxICsgUDIgKyBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSdib3R0b20nKQpgYGAKCgojIEZpdHRpbmcKCi0gTG9hZCB0aGUgbGlicmFyeSBgYnJvb21gIChbdmlnbmV0dGVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9icm9vbS92aWduZXR0ZXMvYnJvb20uaHRtbCkpLiBgYnJvb21gIGFsbG93cyB0aWR5aW5nIHRoZSBvdXRwdXQgb2YgZml0IGZ1bmN0aW9ucyBzdWNoIGFzIGBsbSgpYCBvciBgbmxzKClgLCB0YWtpbmcgdGhlIHRleHQgb3V0cHV0IGFuZCBtYWtpbmcgaXQgaW50byBhIHRpYmJsZS4gCi0gTm93IHRoZW4sIHVzaW5nIHRoZSBzYW1lIHByb2NlZHVyZSBhcyBpbiB0aGUgZXhhbXBsZSBhYm92ZSwgZml0IHRoZSB0b3RhbCBDb25maXJtZWQgY2FzZXMgZm9yIHRoZSA1IGNvdW50cmllcyB5b3UgY2hvc2UgZWFybGllciAqKmZvciB0aGUgMTAwIGZpcnN0IGRheXMqKiB1c2luZyBhIFtsb2dpc3RpYyBmdW5jdGlvbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTG9naXN0aWNfZnVuY3Rpb24pLCBhbmQgcGxvdCB0aGUgZGF0YSBhbmQgdGhlaXIgZml0LCBhbmQgc2hvdyB0aGUgcmVzdWx0cyBvZiB0aGUgZml0cy4gSGVyZSBpcyBob3cgdGhlIHBsb3Qgc2hvdWxkIGxvb2sgbGlrZSB3aXRob3V0IGV2ZXIgcnVubmluZyBhIGBmb3IgbG9vcGA6CgpgYGB7ciBlY2hvPXBhcmFtcyRzb2x1dGlvbiwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1GQUxTRX0KbGlicmFyeShicm9vbSkKbGlicmFyeShtaW5wYWNrLmxtKQpjb3VudHJpZXMgPC0gYygiQ2hpbmEiLCJJdGFseSIsIkZyYW5jZSIsIlNwYWluIikKY29sb3JzICAgIDwtIGMoImJsYWNrIiwicmVkIiwic2VhZ3JlZW4iLCJvcmFuZ2UiKQpmaXRzIDwtIGFsbGRhdGEgJT4lIAogICAgZmlsdGVyKGNvdW50cnkgJWluJSBjb3VudHJpZXMpICU+JSAKICAgIGZpbHRlcihkYXkgPD0gMTAwKSAlPiUgCiAgICBuZXN0KGRhdGE9LWNvdW50cnkpICU+JSAKICAgIG11dGF0ZShmaXQ9bWFwKGRhdGEsIH4gbmxzTE0oZGF0YSA9IC4sCiAgICAgICAgICAgICAgIGNvbmZpcm1lZCB+IGEvKDErZXhwKC1iKihkYXktYykpKSwgCiAgICAgICAgICAgICAgIHN0YXJ0PWxpc3QoYT1tYXgoLiRjb25maXJtZWQpLCBiPS4xLCBjPTE1KSkpLAogICAgICAgICAgIHRpZGllZCA9IG1hcChmaXQsIHRpZHkpLAogICAgICAgICAgIGF1Z21lbnRlZCA9IG1hcChmaXQsIGF1Z21lbnQpKQpmaXRzICU+JSB1bm5lc3QoYXVnbWVudGVkKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9ZGF5LCBjb2xvcj1jb3VudHJ5KSkrCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeT1jb25maXJtZWQpLCBhbHBoYT0uMiwgc2l6ZT0zKSsKICAgICAgICBnZW9tX2xpbmUoYWVzKHk9LmZpdHRlZCkpKwogICAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9Y29sb3JzLCBuYW1lPSIiKSsKICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb249J3RvcCcpKwogICAgICAgIHhsYWIoIkRheSBzaW5jZSBEYXkgMSIpKwogICAgICAgIHlsYWIoIlRvdGFsIGNvbmZpcm1lZCBjYXNlcyIpCmZpdHMgJT4lIHVubmVzdCh0aWRpZWQpICU+JSAKICBzZWxlY3QoY291bnRyeSwgdGVybSwgZXN0aW1hdGUpICU+JSAKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGVybSwgCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkKYGBgCgpJbiBmYWN0LCB0aGlzIGV4cG9uZW50aWFsIG1vZGVsIGlzIHJhdGhlciB3cm9uZy4uLiBBIGJldHRlciBtb2RlbCBvZiBlcGlkZW1pY3MgZHluYW1pY3MgaXMgZG9uZSBieSB0aGUgU0lSIG1vZGVsLCBmb3IgU3VzY2VwdGlibGUsIEluZmVjdGlvdXMsIFJlY292ZXJlZC4gQSBkZXNjcmlwdGlvbiBvZiB0aGUgU0lSIG1vZGVsaW5nIHRocm91Z2ggUiBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vc3RhZmYubWF0aC5zdS5zZS9ob2VobGUvYmxvZy8yMDIwLzAzLzE2L2ZsYXR0ZW5pbmd0aGVjdXJ2ZS5odG1sKSwgZm9yIGV4YW1wbGUuCg==