1 Setup

knitr::opts_chunk$set(
        message = F,
        error = F,
        warning = F,
        comment = NA,
        highlight = T,
        prompt = T
        )
### Set the global option options(stringsAsFactors = FALSE) 
### inside a parent function and restore the option after the parent function exits
if (!require("xfun"))
        {install.packages("xfun", repos = 'http://cran.wu.ac.at/')
        library(xfun)}
xfun::stringsAsStrings()


### install and load some important packages
### https://github.com/tidyverse/tidyverse
if (!require("tidyverse"))
        {install.packages("tidyverse", repos = 'http://cran.wu.ac.at/')
        library(tidyverse)}

### above command installed and loaded the core tidyverse packages:
# ggplot2:    data visualisation
# tibble:     a modern take on data frames
# tidyr:      data tidying
# readr:      data import (csv, tsv, fwf)
# purrr:      functional R programming
# dplyr:      data (frame) manipulation
# stringr:    string manipulation
# forcats:    working with categorial varialbes
# tidyselect: backend for the selecting functions of the 'tidyverse'. (?, new?)


### My reminder for other essential packages:
### Working with times:
# hms, for times.

# lubridate, for date/times.
if (!require("lubridate"))
        {install.packages("lubridate", repos = 'http://cran.wu.ac.at/')
        library(lubridate)}

### Importing other types of data:
# feather, for sharing with Python and other languages.
# haven, for SPSS, SAS and Stata files.
# httr, for web apis.
# jsonlite for JSON.
# readxl, for .xls and .xlsx files.
# rvest, for web scraping.
# xml2, for XML.

### Modelling
# modelr, for modeling within a pipeline
# broom, for turning models into tidy data


### Special packages for this article
# reshape2, restructure and aggregate data using melt and dcast
if (!require("reshape2"))
        {install.packages("reshape2", repos = 'http://cran.wu.ac.at/')
        library(reshape2)}

2 Introduction

This article is the follow-up of Retrieving And Scrapping Archived Data With The Wayback Machine. Here I will display some results from the scrapped archived web site https://www.staticgen.com/ at seven dates, starting from May 2014 to August 2019. The data for this article comes from the previous article, and I will load them with the following code chunk:

### Load dataset
sg_crawllist <- readRDS("data/sg_crawllist.rds")
sg_data_collection <- readRDS("data/sg_data_collection.rds")

My analysis will concentrate on three issues:

  1. Development of the number of static web site generators as displayed by the website https://www.staticgen.com/.
  2. Name of static website generators ranked by the number of stars for their repositories as a proxy for their popularity.
  3. Relative rankings of 18 website generators among each other at seven different dates.

Other data (the number of forks, open issues, and followers on twitter) are not analyzed. They have, in my opinion, only a weak relationship with the diffusion of the web site generator. Perhaps the exclusion of twitter followers needs more reflection:

3 Growth of the Popularity of Static Websites Generators


sg_count = NULL
for (i in 1:length(sg_data_collection)) {
  sg_count[i] <- nrow(sg_data_collection[[i]])
}
sg_quantity <- data.frame(cbind(sg_crawllist[2], sg_count))
sg_quantity$datetime <- as_date(as.POSIXct(sg_quantity$datetime))
names(sg_quantity) <- c("Date of Archived Websites", "Number of Static Generators")
ggplot(sg_quantity, aes(x = `Date of Archived Websites`, y = `Number of Static Generators`)) + 
  geom_line() +
  labs(title = "Growth of the Number of Static Websites Generators")

I started my data scrapping of the archived webpages of https://www.staticgen.com/ in May 2014. At that time the website listed only about 50 generators. Currently (August 2019) the website features 260 static site generators. The plot shows a step and continuous growing popularity of these applications.

4 Taking the first ten generators at every selected date

I am interested in the development of the leading group of website generators measured by their number of repository stars as a proxy for their popularity. The result is a list of 18 generators which were part of the leading group at least at one date under the observation period.


get_first10 <- function(l) {
  names_first10 = NULL
  for (i in 1:length(l)) {
    names_first10 <- c(names_first10, l[[i]]$name[1:10])
  }
  names_first10 <- dplyr::distinct(data.frame(names_first10), names_first10)
  dplyr::rename(names_first10, Name = names_first10)
  return(names_first10)
}

sg_names <- get_first10(sg_data_collection)
saveRDS(sg_names, file = "data/sg_names.rds")
as.list(sg_names)
$names_first10
 [1] "Jekyll"      "Octopress"   "Pelican"     "Middleman"   "Docpad"      "Hexo"        "Metalsmith" 
 [8] "Harp"        "Wintersmith" "Assemble"    "Brunch"      "Hugo"        "GitBook"     "Gatsby"     
[15] "Nuxt"        "Next.js"     "VuePress"    "Docusaurus" 

5 Website generators ranked by their repository stars

5.1 Get ranked data

I have also stored the number of forks but will not display the plots here as they give not valuable insights.


get_sg_data <- function(df, l) {
  sg_df <- data.frame()
  for (i in 1:nrow(df)) {
    row_content = NULL
    sg_vec = NULL
    my_name <- df[i,]
    for (j in 1:length(l)) {
      my_rank <-  which(l[[j]]$name == my_name)
      if (!purrr::is_empty(my_rank)) {
        row_content <- append(row_content, list(Rank = my_rank, 
                                                Stars = as.integer(l[[j]]$repo_stars[my_rank]), 
                                                Forks = as.integer(l[[j]]$repo_forks[my_rank])))
      } else {
        row_content <- append(row_content, list(Rank = NA, Stars = NA, Forks = NA))
      }
    }
  sg_vec <- append(list(my_name), row_content)
  sg_df <- data.frame(force_bind(sg_df, data.frame(sg_vec)))
  }
  
  names(sg_df) <- c("Name", "Rank.Stars.Start", "Stars.Start", "Forks.Start",
                            "Rank.Stars.2015", "Stars.2015", "Forks.2015",
                            "Rank.Stars.2016", "Stars.2016", "Forks.2016",
                            "Rank.Stars.2017", "Stars.2017", "Forks.2017",
                            "Rank.Stars.2018", "Stars.2018", "Forks.2018",
                            "Rank.Stars.2019", "Stars.2019", "Forks.2019",
                            "Rank.Stars.End", "Stars.End", "Forks.End")
  
  return(sg_df)
}

# bit.ly/SO-rbind-colnames
force_bind = function(df1, df2) {
    colnames(df2) = colnames(df1)
    dplyr::bind_rows(df1, df2)
}


sg_names <- readRDS("data/sg_names.rds")
sg_data <- get_sg_data(sg_names, sg_data_collection)
saveRDS(sg_data, file = "data/sg_data.rds")
sg_data
NA

5.2 Facet plot of all 18 generators over time

sg_data <- readRDS("data/sg_data.rds")
sg_temp <- select(sg_data, c("Name", starts_with("Stars")))
order_names <- order(sg_temp$Name)
sg_temp <-  sg_temp[order_names, ]

# SEE: bit.ly/SO-flip-row-col
sg_stars <- data.frame(t(sg_temp[-1]))
colnames(sg_stars) <- sg_temp[, 1]
rownames(sg_stars) <- sg_quantity[, 1]
sg_stars <- as_tibble(rownames_to_column(sg_stars, var = "Date"))
sg_stars$Date <- as.Date(sg_stars$Date)
sg_stars_long  <- melt(sg_stars, id.vars = "Date", 
                 variable.name = "Staticgen", value.name = "Stars")
saveRDS(sg_stars_long, file = "data/sg_stars_long.rds")

p <- ggplot(sg_stars_long, aes(x = Date, y = Stars)) + 
  geom_line(aes(group = Staticgen)) +  
  labs(x = "Date",
     y = "Rank by Repository Stars",
     title = "Comparison of Static Website Generators",
     subtitle = "Ranked by number of repository stars") +
  facet_wrap(~Staticgen, ncol = 3)
p

One can see that Gatsby, Hexo, Hugo, and Jekyll have a long and ongoing growth curve. But there are also with Next.js and Nuxt two newcomers with very positive developments.

6 Bump Chart: Rank changes over time

With this plot, it is difficult to distinguish the relative position of these generators to each other. Instead of absolute values, it is better to use a comparison of the ranking position. This type of plot is called a bump charts. For the following code, I have heavily used explanations and code snippets of various websites:

6.1 ggplot2 theme for bump chars

For a better display, all articles suggest creating a specific theme for ggplot2.

my_theme <- function() {

  # Colors
  color.background = "white"
  color.text = "#22211d"

  # Begin construction of chart
  theme_bw(base_size=15) +

    # Format background colors
    theme(panel.background = element_rect(fill=color.background, color=color.background)) +
    theme(plot.background  = element_rect(fill=color.background, color=color.background)) +
    theme(panel.border     = element_rect(color=color.background)) +
    theme(strip.background = element_rect(fill=color.background, color=color.background)) +

    # Format the grid
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank()) +
    theme(axis.ticks       = element_blank()) +

    # Format the legend
    theme(legend.position = "none") +

    # Format title and axis labels
    theme(plot.title       = element_text(color=color.text, size=20, face = "bold")) +
    theme(axis.title.x     = element_text(size=14, color="black", face = "bold")) +
    theme(axis.title.y     = element_text(size=14, color="black", face = "bold", vjust=1.25)) +
    theme(axis.text.x      = element_text(size=10, vjust=0.5, hjust=0.5, color = color.text)) +
    theme(axis.text.y      = element_text(size=10, color = color.text)) +
    theme(strip.text       = element_text(face = "bold")) +

    # Plot margins
    theme(plot.margin = unit(c(0.35, 0.2, 0.3, 0.35), "cm"))
}

6.2 Bump Chart for 18 Website Generators

sg_data <- readRDS("data/sg_data.rds")
sg_temp <- select(sg_data, c("Name", starts_with("Rank.Stars")))
order_names <- order(sg_temp$Name)
sg_temp <-  sg_temp[order_names, ]

# SEE: bit.ly/SO-flip-row-col
sg_star_rank <- data.frame(t(sg_temp[-1]))
colnames(sg_star_rank) <- sg_temp[, 1]
rownames(sg_star_rank) <- sg_quantity[, 1]
sg_star_rank <- as_tibble(rownames_to_column(sg_star_rank, var = "Date"))
sg_star_rank$Date <- as.Date(sg_star_rank$Date)
sg_star_rank_long  <- melt(sg_star_rank, id.vars = "Date", 
                 variable.name = "Staticgen", value.name = "Rank")
Archive.Nr <- rep(c(1, 2, 3, 4, 5, 6, 7), 18)
sg_star_rank_long <<- data.frame(cbind(sg_star_rank_long, Archive.Nr))



# SEE: https://www.statology.org/how-to-easily-create-a-bump-chart-in-r-using-ggplot2/
ggplot(sg_star_rank_long, aes(x = as.factor(Archive.Nr), y = Rank, group = Staticgen)) +
  geom_line(aes(color = Staticgen, alpha = 1), size = 1) +
  geom_point(aes(color = Staticgen, alpha = 1), size = 2) +
  geom_point(color = "#FFFFFF", size = 1) +
  scale_y_reverse(breaks = 1:nrow(sg_star_rank_long)) + 
  scale_x_discrete(breaks = 1:7) +
  theme(legend.position = 'none') +
  geom_text(data = sg_star_rank_long %>% filter(Archive.Nr == "1"),
            aes(label = Staticgen, x = 0.7) , hjust = .5,
            fontface = "bold",  size = 3) +
  geom_text(data = sg_star_rank_long %>% filter(Archive.Nr == "7"),
            aes(label = Staticgen, x = 7.3) , hjust = 0.5,
            fontface = "bold",  size = 3) +
  labs(x = "1:Jun 2014, 7:Aug 2019, 2-6: Jan (2015-2019)",
       y = "Rank",
       title = "Comparison of Static Website Generators",
       subtitle = "Ranked by number of repository stars") +
  my_theme()

With this bump chart, one can see which generators are rising in their popularity. These relative developments were hidden by an overall positive trend of static website generators.

LS0tCnRpdGxlOiAiQ29tcGFyaW5nIFN0YXRpYyBXZWIgR2VuZXJhdG9ycyBPdmVyIFRpbWUiCmF1dGhvcjogIlBldGVyIEJhdW1nYXJ0bmVyIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgZmlnX2NhcHRpb246IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHBhbmRvY19hcmdzOiAtLW51bWJlci1vZmZzZXQ9MCwwCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiA0CiAgd29yZF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICc0JwogIHBkZl9kb2N1bWVudDoKICAgIHBhbmRvY19hcmdzOiAtLW51bWJlci1vZmZzZXQ9MCwwCiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnNCcKICAgIGxhdGV4X2VuZ2luZTogeGVsYXRleAogIGdpdGh1Yl9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKICBodG1sX2RvY3VtZW50OgogICAgZmlnX2NhcHRpb246IHllcwogICAga2VlcF9tZDogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgcGFuZG9jX2FyZ3M6IC0tbnVtYmVyLW9mZnNldD0wLDAKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDQKLS0tCgojIFNldHVwCgpgYGB7ciBsYWJlbCA9ICJnbG9iYWwtb3B0aW9ucyIsIGhpZ2hsaWdodD1UUlVFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgICAgICAgbWVzc2FnZSA9IEYsCiAgICAgICAgZXJyb3IgPSBGLAogICAgICAgIHdhcm5pbmcgPSBGLAogICAgICAgIGNvbW1lbnQgPSBOQSwKICAgICAgICBoaWdobGlnaHQgPSBULAogICAgICAgIHByb21wdCA9IFQKICAgICAgICApCiMjIyBTZXQgdGhlIGdsb2JhbCBvcHRpb24gb3B0aW9ucyhzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpIAojIyMgaW5zaWRlIGEgcGFyZW50IGZ1bmN0aW9uIGFuZCByZXN0b3JlIHRoZSBvcHRpb24gYWZ0ZXIgdGhlIHBhcmVudCBmdW5jdGlvbiBleGl0cwppZiAoIXJlcXVpcmUoInhmdW4iKSkKICAgICAgICB7aW5zdGFsbC5wYWNrYWdlcygieGZ1biIsIHJlcG9zID0gJ2h0dHA6Ly9jcmFuLnd1LmFjLmF0LycpCiAgICAgICAgbGlicmFyeSh4ZnVuKX0KeGZ1bjo6c3RyaW5nc0FzU3RyaW5ncygpCgoKIyMjIGluc3RhbGwgYW5kIGxvYWQgc29tZSBpbXBvcnRhbnQgcGFja2FnZXMKIyMjIGh0dHBzOi8vZ2l0aHViLmNvbS90aWR5dmVyc2UvdGlkeXZlcnNlCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpCiAgICAgICAge2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIHJlcG9zID0gJ2h0dHA6Ly9jcmFuLnd1LmFjLmF0LycpCiAgICAgICAgbGlicmFyeSh0aWR5dmVyc2UpfQoKIyMjIGFib3ZlIGNvbW1hbmQgaW5zdGFsbGVkIGFuZCBsb2FkZWQgdGhlIGNvcmUgdGlkeXZlcnNlIHBhY2thZ2VzOgojIGdncGxvdDI6ICAgIGRhdGEgdmlzdWFsaXNhdGlvbgojIHRpYmJsZTogICAgIGEgbW9kZXJuIHRha2Ugb24gZGF0YSBmcmFtZXMKIyB0aWR5cjogICAgICBkYXRhIHRpZHlpbmcKIyByZWFkcjogICAgICBkYXRhIGltcG9ydCAoY3N2LCB0c3YsIGZ3ZikKIyBwdXJycjogICAgICBmdW5jdGlvbmFsIFIgcHJvZ3JhbW1pbmcKIyBkcGx5cjogICAgICBkYXRhIChmcmFtZSkgbWFuaXB1bGF0aW9uCiMgc3RyaW5ncjogICAgc3RyaW5nIG1hbmlwdWxhdGlvbgojIGZvcmNhdHM6ICAgIHdvcmtpbmcgd2l0aCBjYXRlZ29yaWFsIHZhcmlhbGJlcwojIHRpZHlzZWxlY3Q6IGJhY2tlbmQgZm9yIHRoZSBzZWxlY3RpbmcgZnVuY3Rpb25zIG9mIHRoZSAndGlkeXZlcnNlJy4gKD8sIG5ldz8pCgoKIyMjIE15IHJlbWluZGVyIGZvciBvdGhlciBlc3NlbnRpYWwgcGFja2FnZXM6CiMjIyBXb3JraW5nIHdpdGggdGltZXM6CiMgaG1zLCBmb3IgdGltZXMuCgojIGx1YnJpZGF0ZSwgZm9yIGRhdGUvdGltZXMuCmlmICghcmVxdWlyZSgibHVicmlkYXRlIikpCiAgICAgICAge2luc3RhbGwucGFja2FnZXMoImx1YnJpZGF0ZSIsIHJlcG9zID0gJ2h0dHA6Ly9jcmFuLnd1LmFjLmF0LycpCiAgICAgICAgbGlicmFyeShsdWJyaWRhdGUpfQoKIyMjIEltcG9ydGluZyBvdGhlciB0eXBlcyBvZiBkYXRhOgojIGZlYXRoZXIsIGZvciBzaGFyaW5nIHdpdGggUHl0aG9uIGFuZCBvdGhlciBsYW5ndWFnZXMuCiMgaGF2ZW4sIGZvciBTUFNTLCBTQVMgYW5kIFN0YXRhIGZpbGVzLgojIGh0dHIsIGZvciB3ZWIgYXBpcy4KIyBqc29ubGl0ZSBmb3IgSlNPTi4KIyByZWFkeGwsIGZvciAueGxzIGFuZCAueGxzeCBmaWxlcy4KIyBydmVzdCwgZm9yIHdlYiBzY3JhcGluZy4KIyB4bWwyLCBmb3IgWE1MLgoKIyMjIE1vZGVsbGluZwojIG1vZGVsciwgZm9yIG1vZGVsaW5nIHdpdGhpbiBhIHBpcGVsaW5lCiMgYnJvb20sIGZvciB0dXJuaW5nIG1vZGVscyBpbnRvIHRpZHkgZGF0YQoKCiMjIyBTcGVjaWFsIHBhY2thZ2VzIGZvciB0aGlzIGFydGljbGUKIyByZXNoYXBlMiwgcmVzdHJ1Y3R1cmUgYW5kIGFnZ3JlZ2F0ZSBkYXRhIHVzaW5nIG1lbHQgYW5kIGRjYXN0CmlmICghcmVxdWlyZSgicmVzaGFwZTIiKSkKICAgICAgICB7aW5zdGFsbC5wYWNrYWdlcygicmVzaGFwZTIiLCByZXBvcyA9ICdodHRwOi8vY3Jhbi53dS5hYy5hdC8nKQogICAgICAgIGxpYnJhcnkocmVzaGFwZTIpfQoKCgpgYGAKCiMgSW50cm9kdWN0aW9uCgpUaGlzIGFydGljbGUgaXMgdGhlIGZvbGxvdy11cCBvZiBbUmV0cmlldmluZyBBbmQgU2NyYXBwaW5nIEFyY2hpdmVkIERhdGEgV2l0aCBUaGUgV2F5YmFjayBNYWNoaW5lXShodHRwczovL3JwdWJzLmNvbS9wYmF1bWdhcnRuZXIvd2F5YmFjaykuIEhlcmUgSSB3aWxsIGRpc3BsYXkgc29tZSByZXN1bHRzIGZyb20gdGhlIHNjcmFwcGVkIGFyY2hpdmVkIHdlYiBzaXRlIGh0dHBzOi8vd3d3LnN0YXRpY2dlbi5jb20vIGF0IHNldmVuIGRhdGVzLCBzdGFydGluZyBmcm9tIE1heSAyMDE0IHRvIEF1Z3VzdCAyMDE5LiBUaGUgZGF0YSBmb3IgdGhpcyBhcnRpY2xlIGNvbWVzIGZyb20gdGhlIHByZXZpb3VzIGFydGljbGUsIGFuZCBJIHdpbGwgbG9hZCB0aGVtIHdpdGggdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rOgoKYGBge3IgbG9hZC1kYXRhfQojIyMgTG9hZCBkYXRhc2V0CnNnX2NyYXdsbGlzdCA8LSByZWFkUkRTKCJkYXRhL3NnX2NyYXdsbGlzdC5yZHMiKQpzZ19kYXRhX2NvbGxlY3Rpb24gPC0gcmVhZFJEUygiZGF0YS9zZ19kYXRhX2NvbGxlY3Rpb24ucmRzIikKYGBgCgoKTXkgYW5hbHlzaXMgd2lsbCBjb25jZW50cmF0ZSBvbiB0aHJlZSBpc3N1ZXM6CgoxLiBEZXZlbG9wbWVudCBvZiB0aGUgbnVtYmVyIG9mIHN0YXRpYyB3ZWIgc2l0ZSBnZW5lcmF0b3JzIGFzIGRpc3BsYXllZCBieSB0aGUgd2Vic2l0ZSBodHRwczovL3d3dy5zdGF0aWNnZW4uY29tLy4KMi4gTmFtZSBvZiBzdGF0aWMgd2Vic2l0ZSBnZW5lcmF0b3JzIHJhbmtlZCBieSB0aGUgbnVtYmVyIG9mIHN0YXJzIGZvciB0aGVpciByZXBvc2l0b3JpZXMgYXMgYSBwcm94eSBmb3IgdGhlaXIgcG9wdWxhcml0eS4KMy4gUmVsYXRpdmUgcmFua2luZ3Mgb2YgMTggd2Vic2l0ZSBnZW5lcmF0b3JzIGFtb25nIGVhY2ggb3RoZXIgYXQgc2V2ZW4gZGlmZmVyZW50IGRhdGVzLgoKT3RoZXIgZGF0YSAodGhlIG51bWJlciBvZiBmb3Jrcywgb3BlbiBpc3N1ZXMsIGFuZCBmb2xsb3dlcnMgb24gdHdpdHRlcikgYXJlIG5vdCBhbmFseXplZC4gIFRoZXkgaGF2ZSwgaW4gbXkgb3Bpbmlvbiwgb25seSBhIHdlYWsgcmVsYXRpb25zaGlwIHdpdGggdGhlIGRpZmZ1c2lvbiBvZiB0aGUgd2ViIHNpdGUgZ2VuZXJhdG9yLiBQZXJoYXBzIHRoZSBleGNsdXNpb24gb2YgdHdpdHRlciBmb2xsb3dlcnMgbmVlZHMgbW9yZSByZWZsZWN0aW9uOgoKKyBUaGUgZGlzcGxheSBvZiB0aGUgbnVtYmVyIG9mIHR3aXR0ZXIgZm9sbG93ZXJzIHN0YXJ0ZWQgb25seSBhcm91bmQgMjAxOS4KKyBBcyB0b2RheSAoMjAxOS0wNy0zMSkgb25seSAzNyBzdGF0aWMgd2Vic2l0ZSBnZW5lcmF0b3JzIGhhdmUgYSB0d2l0dGVyIGFjY291bnQuCisgRXZlbiBsZWFkaW5nIHN0YXRpYyB3ZWJzaXRlIGZyYW1ld29ya3MgKGUuZy4sIE5leHQuanMpIGhhdmUgbm8gdHdpdHRlciBhY2NvdW50LgorIFRoZSBudW1iZXIgb2YgZm9sbG93ZXJzIHJlc3VsdHMgbm90IG9ubHkgZnJvbSB0aGUgcG9wdWxhcml0eSBvZiB0aGUgZ2VuZXJhdG9yIGJ1dCBhbHNvIGZyb20gZXhjaXRpbmcgYW5kIHdlbGwtd3JpdHRlbiB0d2VlZHMuCgojIEdyb3d0aCBvZiB0aGUgUG9wdWxhcml0eSBvZiBTdGF0aWMgV2Vic2l0ZXMgR2VuZXJhdG9ycwoKCmBgYHtyIHNnLW51bWJlcnN9CgpzZ19jb3VudCA9IE5VTEwKZm9yIChpIGluIDE6bGVuZ3RoKHNnX2RhdGFfY29sbGVjdGlvbikpIHsKICBzZ19jb3VudFtpXSA8LSBucm93KHNnX2RhdGFfY29sbGVjdGlvbltbaV1dKQp9CnNnX3F1YW50aXR5IDwtIGRhdGEuZnJhbWUoY2JpbmQoc2dfY3Jhd2xsaXN0WzJdLCBzZ19jb3VudCkpCnNnX3F1YW50aXR5JGRhdGV0aW1lIDwtIGFzX2RhdGUoYXMuUE9TSVhjdChzZ19xdWFudGl0eSRkYXRldGltZSkpCm5hbWVzKHNnX3F1YW50aXR5KSA8LSBjKCJEYXRlIG9mIEFyY2hpdmVkIFdlYnNpdGVzIiwgIk51bWJlciBvZiBTdGF0aWMgR2VuZXJhdG9ycyIpCmdncGxvdChzZ19xdWFudGl0eSwgYWVzKHggPSBgRGF0ZSBvZiBBcmNoaXZlZCBXZWJzaXRlc2AsIHkgPSBgTnVtYmVyIG9mIFN0YXRpYyBHZW5lcmF0b3JzYCkpICsgCiAgZ2VvbV9saW5lKCkgKwogIGxhYnModGl0bGUgPSAiR3Jvd3RoIG9mIHRoZSBOdW1iZXIgb2YgU3RhdGljIFdlYnNpdGVzIEdlbmVyYXRvcnMiKQpgYGAKCkkgc3RhcnRlZCBteSBkYXRhIHNjcmFwcGluZyBvZiB0aGUgYXJjaGl2ZWQgd2VicGFnZXMgb2YgaHR0cHM6Ly93d3cuc3RhdGljZ2VuLmNvbS8gaW4gTWF5IDIwMTQuIEF0IHRoYXQgdGltZSB0aGUgd2Vic2l0ZSBsaXN0ZWQgb25seSBhYm91dCA1MCBnZW5lcmF0b3JzLiBDdXJyZW50bHkgKEF1Z3VzdCAyMDE5KSB0aGUgd2Vic2l0ZSBmZWF0dXJlcyAyNjAgc3RhdGljIHNpdGUgZ2VuZXJhdG9ycy4gVGhlIHBsb3Qgc2hvd3MgYSAgc3RlcCBhbmQgY29udGludW91cyBncm93aW5nIHBvcHVsYXJpdHkgb2YgdGhlc2UgYXBwbGljYXRpb25zLgoKIyBUYWtpbmcgdGhlIGZpcnN0IHRlbiBnZW5lcmF0b3JzIGF0IGV2ZXJ5IHNlbGVjdGVkIGRhdGUKCkkgYW0gaW50ZXJlc3RlZCBpbiB0aGUgZGV2ZWxvcG1lbnQgb2YgdGhlIGxlYWRpbmcgZ3JvdXAgb2Ygd2Vic2l0ZSBnZW5lcmF0b3JzIG1lYXN1cmVkIGJ5IHRoZWlyIG51bWJlciBvZiByZXBvc2l0b3J5IHN0YXJzIGFzIGEgcHJveHkgZm9yIHRoZWlyIHBvcHVsYXJpdHkuIFRoZSByZXN1bHQgaXMgYSBsaXN0IG9mIDE4IGdlbmVyYXRvcnMgd2hpY2ggd2VyZSBwYXJ0IG9mIHRoZSBsZWFkaW5nIGdyb3VwIGF0IGxlYXN0IGF0IG9uZSBkYXRlIHVuZGVyIHRoZSBvYnNlcnZhdGlvbiBwZXJpb2QuCgpgYGB7ciBnZXQtZmlyc3QtMTAtZXZlcnkteWVhcn0KCmdldF9maXJzdDEwIDwtIGZ1bmN0aW9uKGwpIHsKICBuYW1lc19maXJzdDEwID0gTlVMTAogIGZvciAoaSBpbiAxOmxlbmd0aChsKSkgewogICAgbmFtZXNfZmlyc3QxMCA8LSBjKG5hbWVzX2ZpcnN0MTAsIGxbW2ldXSRuYW1lWzE6MTBdKQogIH0KICBuYW1lc19maXJzdDEwIDwtIGRwbHlyOjpkaXN0aW5jdChkYXRhLmZyYW1lKG5hbWVzX2ZpcnN0MTApLCBuYW1lc19maXJzdDEwKQogIGRwbHlyOjpyZW5hbWUobmFtZXNfZmlyc3QxMCwgTmFtZSA9IG5hbWVzX2ZpcnN0MTApCiAgcmV0dXJuKG5hbWVzX2ZpcnN0MTApCn0KCnNnX25hbWVzIDwtIGdldF9maXJzdDEwKHNnX2RhdGFfY29sbGVjdGlvbikKc2F2ZVJEUyhzZ19uYW1lcywgZmlsZSA9ICJkYXRhL3NnX25hbWVzLnJkcyIpCmFzLmxpc3Qoc2dfbmFtZXMpCgoKYGBgCgojIFdlYnNpdGUgZ2VuZXJhdG9ycyByYW5rZWQgYnkgdGhlaXIgcmVwb3NpdG9yeSBzdGFycwoKIyMgR2V0IHJhbmtlZCBkYXRhCgpJIGhhdmUgYWxzbyBzdG9yZWQgdGhlIG51bWJlciBvZiBmb3JrcyBidXQgd2lsbCBub3QgZGlzcGxheSB0aGUgcGxvdHMgaGVyZSBhcyB0aGV5IGdpdmUgbm90IHZhbHVhYmxlIGluc2lnaHRzLgoKYGBge3IgZ2V0LXN0YXJfcmFua19kYXRhfQoKZ2V0X3NnX2RhdGEgPC0gZnVuY3Rpb24oZGYsIGwpIHsKICBzZ19kZiA8LSBkYXRhLmZyYW1lKCkKICBmb3IgKGkgaW4gMTpucm93KGRmKSkgewogICAgcm93X2NvbnRlbnQgPSBOVUxMCiAgICBzZ192ZWMgPSBOVUxMCiAgICBteV9uYW1lIDwtIGRmW2ksXQogICAgZm9yIChqIGluIDE6bGVuZ3RoKGwpKSB7CiAgICAgIG15X3JhbmsgPC0gIHdoaWNoKGxbW2pdXSRuYW1lID09IG15X25hbWUpCiAgICAgIGlmICghcHVycnI6OmlzX2VtcHR5KG15X3JhbmspKSB7CiAgICAgICAgcm93X2NvbnRlbnQgPC0gYXBwZW5kKHJvd19jb250ZW50LCBsaXN0KFJhbmsgPSBteV9yYW5rLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU3RhcnMgPSBhcy5pbnRlZ2VyKGxbW2pdXSRyZXBvX3N0YXJzW215X3JhbmtdKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZvcmtzID0gYXMuaW50ZWdlcihsW1tqXV0kcmVwb19mb3Jrc1tteV9yYW5rXSkpKQogICAgICB9IGVsc2UgewogICAgICAgIHJvd19jb250ZW50IDwtIGFwcGVuZChyb3dfY29udGVudCwgbGlzdChSYW5rID0gTkEsIFN0YXJzID0gTkEsIEZvcmtzID0gTkEpKQogICAgICB9CiAgICB9CiAgc2dfdmVjIDwtIGFwcGVuZChsaXN0KG15X25hbWUpLCByb3dfY29udGVudCkKICBzZ19kZiA8LSBkYXRhLmZyYW1lKGZvcmNlX2JpbmQoc2dfZGYsIGRhdGEuZnJhbWUoc2dfdmVjKSkpCiAgfQogIAogIG5hbWVzKHNnX2RmKSA8LSBjKCJOYW1lIiwgIlJhbmsuU3RhcnMuU3RhcnQiLCAiU3RhcnMuU3RhcnQiLCAiRm9ya3MuU3RhcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhbmsuU3RhcnMuMjAxNSIsICJTdGFycy4yMDE1IiwgIkZvcmtzLjIwMTUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhbmsuU3RhcnMuMjAxNiIsICJTdGFycy4yMDE2IiwgIkZvcmtzLjIwMTYiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhbmsuU3RhcnMuMjAxNyIsICJTdGFycy4yMDE3IiwgIkZvcmtzLjIwMTciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhbmsuU3RhcnMuMjAxOCIsICJTdGFycy4yMDE4IiwgIkZvcmtzLjIwMTgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhbmsuU3RhcnMuMjAxOSIsICJTdGFycy4yMDE5IiwgIkZvcmtzLjIwMTkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJhbmsuU3RhcnMuRW5kIiwgIlN0YXJzLkVuZCIsICJGb3Jrcy5FbmQiKQogIAogIHJldHVybihzZ19kZikKfQoKIyBiaXQubHkvU08tcmJpbmQtY29sbmFtZXMKZm9yY2VfYmluZCA9IGZ1bmN0aW9uKGRmMSwgZGYyKSB7CiAgICBjb2xuYW1lcyhkZjIpID0gY29sbmFtZXMoZGYxKQogICAgZHBseXI6OmJpbmRfcm93cyhkZjEsIGRmMikKfQoKCnNnX25hbWVzIDwtIHJlYWRSRFMoImRhdGEvc2dfbmFtZXMucmRzIikKc2dfZGF0YSA8LSBnZXRfc2dfZGF0YShzZ19uYW1lcywgc2dfZGF0YV9jb2xsZWN0aW9uKQpzYXZlUkRTKHNnX2RhdGEsIGZpbGUgPSAiZGF0YS9zZ19kYXRhLnJkcyIpCnNnX2RhdGEKCmBgYAoKIyMgRmFjZXQgcGxvdCBvZiBhbGwgMTggZ2VuZXJhdG9ycyBvdmVyIHRpbWUKCmBgYHtyIGRyYXctc3RhcnMtZmFjZXRzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD0xMH0Kc2dfZGF0YSA8LSByZWFkUkRTKCJkYXRhL3NnX2RhdGEucmRzIikKc2dfdGVtcCA8LSBzZWxlY3Qoc2dfZGF0YSwgYygiTmFtZSIsIHN0YXJ0c193aXRoKCJTdGFycyIpKSkKb3JkZXJfbmFtZXMgPC0gb3JkZXIoc2dfdGVtcCROYW1lKQpzZ190ZW1wIDwtICBzZ190ZW1wW29yZGVyX25hbWVzLCBdCgojIFNFRTogYml0Lmx5L1NPLWZsaXAtcm93LWNvbApzZ19zdGFycyA8LSBkYXRhLmZyYW1lKHQoc2dfdGVtcFstMV0pKQpjb2xuYW1lcyhzZ19zdGFycykgPC0gc2dfdGVtcFssIDFdCnJvd25hbWVzKHNnX3N0YXJzKSA8LSBzZ19xdWFudGl0eVssIDFdCnNnX3N0YXJzIDwtIGFzX3RpYmJsZShyb3duYW1lc190b19jb2x1bW4oc2dfc3RhcnMsIHZhciA9ICJEYXRlIikpCnNnX3N0YXJzJERhdGUgPC0gYXMuRGF0ZShzZ19zdGFycyREYXRlKQpzZ19zdGFyc19sb25nICA8LSBtZWx0KHNnX3N0YXJzLCBpZC52YXJzID0gIkRhdGUiLCAKICAgICAgICAgICAgICAgICB2YXJpYWJsZS5uYW1lID0gIlN0YXRpY2dlbiIsIHZhbHVlLm5hbWUgPSAiU3RhcnMiKQpzYXZlUkRTKHNnX3N0YXJzX2xvbmcsIGZpbGUgPSAiZGF0YS9zZ19zdGFyc19sb25nLnJkcyIpCgpwIDwtIGdncGxvdChzZ19zdGFyc19sb25nLCBhZXMoeCA9IERhdGUsIHkgPSBTdGFycykpICsgCiAgZ2VvbV9saW5lKGFlcyhncm91cCA9IFN0YXRpY2dlbikpICsgIAogIGxhYnMoeCA9ICJEYXRlIiwKICAgICB5ID0gIlJhbmsgYnkgUmVwb3NpdG9yeSBTdGFycyIsCiAgICAgdGl0bGUgPSAiQ29tcGFyaXNvbiBvZiBTdGF0aWMgV2Vic2l0ZSBHZW5lcmF0b3JzIiwKICAgICBzdWJ0aXRsZSA9ICJSYW5rZWQgYnkgbnVtYmVyIG9mIHJlcG9zaXRvcnkgc3RhcnMiKSArCiAgZmFjZXRfd3JhcCh+U3RhdGljZ2VuLCBuY29sID0gMykKcAoKYGBgCgpPbmUgY2FuIHNlZSB0aGF0IEdhdHNieSwgSGV4bywgSHVnbywgYW5kIEpla3lsbCBoYXZlIGEgbG9uZyBhbmQgb25nb2luZyBncm93dGggY3VydmUuIEJ1dCB0aGVyZSBhcmUgYWxzbyB3aXRoIE5leHQuanMgYW5kIE51eHQgdHdvIG5ld2NvbWVycyB3aXRoIHZlcnkgcG9zaXRpdmUgZGV2ZWxvcG1lbnRzLiAKCiMgQnVtcCBDaGFydDogUmFuayBjaGFuZ2VzIG92ZXIgdGltZQoKV2l0aCB0aGlzIHBsb3QsIGl0IGlzIGRpZmZpY3VsdCB0byBkaXN0aW5ndWlzaCB0aGUgcmVsYXRpdmUgcG9zaXRpb24gb2YgdGhlc2UgZ2VuZXJhdG9ycyB0byBlYWNoIG90aGVyLiBJbnN0ZWFkIG9mIGFic29sdXRlIHZhbHVlcywgaXQgaXMgYmV0dGVyIHRvIHVzZSBhIGNvbXBhcmlzb24gb2YgdGhlIHJhbmtpbmcgcG9zaXRpb24uIFRoaXMgdHlwZSBvZiBwbG90IGlzIGNhbGxlZCBhIGJ1bXAgY2hhcnRzLiBGb3IgdGhlIGZvbGxvd2luZyBjb2RlLCBJIGhhdmUgaGVhdmlseSB1c2VkIGV4cGxhbmF0aW9ucyBhbmQgY29kZSBzbmlwcGV0cyBvZiB2YXJpb3VzIHdlYnNpdGVzOgoKKyBbQnVtcCBDaGFydF0oaHR0cHM6Ly9kb21pbmlra29jaC5naXRodWIuaW8vQnVtcC1DaGFydC8pIGJ5IERvbWluaWsgS29jaCBhdCBbRGF0YSBTY2llbmNlIDQyXShodHRwczovL2RvbWluaWtrb2NoLmdpdGh1Yi5pby8pLgorIFtDb21tdW5pY2F0aW5nIGNoYW5nZXMgaW4gcmFuayBvdmVyIHRpbWVdKGh0dHBzOi8vZGF0YXRvZGlzcGxheS5jb20vYmxvZy9jaGFydC1kZXNpZ24vY29tbXVuaWNhdGluZy1jaGFuZ2VzLXJhbmstdGltZS8pOiBidW1wcyBjaGFydHMgYW5kIHNsb3BlZ3JhcGhzIGJ5IFRpbSBCcm9jayBhdCBbRGF0YSB0byBEaXNwbGF5XShodHRwczovL2RhdGF0b2Rpc3BsYXkuY29tL2luZGV4LnBocCkuIEJyb2NrcyB3ZWJzaXRlIGFsc28gZmVhdHVyZXMgYSBbc3R1bm5pbmcgaW50ZXJhY3RpdmUgZGVtb25zdHJhdGlvbiBvZiBidW1wIGNoYXJ0c10oaHR0cHM6Ly9kYXRhdG9kaXNwbGF5LmNvbS9leGFtcGxlcy9jbzIvKS4gKENsaWNrIGF0IHRoZSBjb3VudHJ5IG5hbWVzISEpCisgW0hvdyB0byBFYXNpbHkgQ3JlYXRlIGEgQnVtcCBDaGFydF0oaHR0cHM6Ly93d3cuc3RhdG9sb2d5Lm9yZy9ob3ctdG8tZWFzaWx5LWNyZWF0ZS1hLWJ1bXAtY2hhcnQtaW4tci11c2luZy1nZ3Bsb3QyLykgaW4gUiBVc2luZyBnZ3Bsb3QyIGF0IFtTdGF0b2xvZ3ldKGh0dHBzOi8vd3d3LnN0YXRvbG9neS5vcmcvKS4KKyBbRG9nIGJyZWVkcyBidW1wIGNoYXJ0XShodHRwczovL2x1aXNkdmEuZ2l0aHViLmlvL3JzdGF0cy9kb2ctYnVtcC1jaGFydC8pIGJ5IEx1aXMgVmVyZGUgQXJyZWdvaXRpYSBhdCBbR2l0aHViLmlvXShodHRwczovL2x1aXNkdmEuZ2l0aHViLmlvLykuIAoKCjwhLS0gYGBge3IgZHJhdy1mb3Jrcy1mYWNldHMsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTEwfSAtLT4KPCEtLSBzZ19kYXRhIDwtIHJlYWRSRFMoImRhdGEvc2dfZGF0YS5yZHMiKSAtLT4KPCEtLSBzZ190ZW1wIDwtIHNlbGVjdChzZ19kYXRhLCBjKCJOYW1lIiwgc3RhcnRzX3dpdGgoIkZvcmtzIikpKSAtLT4KPCEtLSBvcmRlcl9uYW1lcyA8LSBvcmRlcihzZ190ZW1wJE5hbWUpIC0tPgo8IS0tIHNnX3RlbXAgPC0gIHNnX3RlbXBbb3JkZXJfbmFtZXMsIF0gLS0+Cgo8IS0tICMgU0VFOiBiaXQubHkvU08tZmxpcC1yb3ctY29sIC0tPgo8IS0tIHNnX2ZvcmtzIDwtIGRhdGEuZnJhbWUodChzZ190ZW1wWy0xXSkpIC0tPgo8IS0tIGNvbG5hbWVzKHNnX2ZvcmtzKSA8LSBzZ190ZW1wWywgMV0gLS0+CjwhLS0gcm93bmFtZXMoc2dfZm9ya3MpIDwtIHNnX3F1YW50aXR5WywgMV0gLS0+CjwhLS0gc2dfZm9ya3MgPC0gYXNfdGliYmxlKHJvd25hbWVzX3RvX2NvbHVtbihzZ19mb3JrcywgdmFyID0gIkRhdGUiKSkgLS0+CjwhLS0gc2dfZm9ya3MkRGF0ZSA8LSBhcy5EYXRlKHNnX2ZvcmtzJERhdGUpIC0tPgo8IS0tIHNnX2ZvcmtzX2xvbmcgIDwtIG1lbHQoc2dfZm9ya3MsIGlkLnZhcnMgPSAiRGF0ZSIsICAtLT4KPCEtLSAgICAgICAgICAgICAgICAgIHZhcmlhYmxlLm5hbWUgPSAiU3RhdGljZ2VuIiwgdmFsdWUubmFtZSA9ICJGb3JrcyIpIC0tPgo8IS0tIHNhdmVSRFMoc2dfZm9ya3NfbG9uZywgZmlsZSA9ICJkYXRhL3NnX2ZvcmtzX2xvbmcucmRzIikgLS0+Cgo8IS0tIHAgPC0gZ2dwbG90KHNnX2ZvcmtzX2xvbmcsIGFlcyh4ID0gRGF0ZSwgeSA9IEZvcmtzKSkgKyAgLS0+CjwhLS0gICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gU3RhdGljZ2VuKSkgKyAgIC0tPgo8IS0tICAgbGFicyh4ID0gIkRhdGUiLCAtLT4KPCEtLSAgICAgICAgeSA9ICJSYW5rIGJ5IFJlcG9zaXRvcnkgRm9ya3MiLCAtLT4KPCEtLSAgICAgICAgdGl0bGUgPSAiQ29tcGFyaXNvbiBvZiBTdGF0aWMgV2Vic2l0ZSBHZW5lcmF0b3JzIiwgLS0+CjwhLS0gICAgICAgIHN1YnRpdGxlID0gIlJhbmtlZCBieSBudW1iZXIgb2YgcmVwb3NpdG9yeSBmb3JrcyIpICsgLS0+CjwhLS0gICBmYWNldF93cmFwKH5TdGF0aWNnZW4sIG5jb2wgPSAzKSAtLT4KPCEtLSBwIC0tPgo8IS0tIGBgYCAtLT4KCiMjIGdncGxvdDIgdGhlbWUgZm9yIGJ1bXAgY2hhcnMKCkZvciBhIGJldHRlciBkaXNwbGF5LCBhbGwgYXJ0aWNsZXMgc3VnZ2VzdCBjcmVhdGluZyBhIHNwZWNpZmljIHRoZW1lIGZvciBnZ3Bsb3QyLgoKYGBge3IgY3JlYXRlLW15LXRoZW1lfQpteV90aGVtZSA8LSBmdW5jdGlvbigpIHsKCiAgIyBDb2xvcnMKICBjb2xvci5iYWNrZ3JvdW5kID0gIndoaXRlIgogIGNvbG9yLnRleHQgPSAiIzIyMjExZCIKCiAgIyBCZWdpbiBjb25zdHJ1Y3Rpb24gb2YgY2hhcnQKICB0aGVtZV9idyhiYXNlX3NpemU9MTUpICsKCiAgICAjIEZvcm1hdCBiYWNrZ3JvdW5kIGNvbG9ycwogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsPWNvbG9yLmJhY2tncm91bmQsIGNvbG9yPWNvbG9yLmJhY2tncm91bmQpKSArCiAgICB0aGVtZShwbG90LmJhY2tncm91bmQgID0gZWxlbWVudF9yZWN0KGZpbGw9Y29sb3IuYmFja2dyb3VuZCwgY29sb3I9Y29sb3IuYmFja2dyb3VuZCkpICsKICAgIHRoZW1lKHBhbmVsLmJvcmRlciAgICAgPSBlbGVtZW50X3JlY3QoY29sb3I9Y29sb3IuYmFja2dyb3VuZCkpICsKICAgIHRoZW1lKHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD1jb2xvci5iYWNrZ3JvdW5kLCBjb2xvcj1jb2xvci5iYWNrZ3JvdW5kKSkgKwoKICAgICMgRm9ybWF0IHRoZSBncmlkCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUoYXhpcy50aWNrcyAgICAgICA9IGVsZW1lbnRfYmxhbmsoKSkgKwoKICAgICMgRm9ybWF0IHRoZSBsZWdlbmQKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwoKICAgICMgRm9ybWF0IHRpdGxlIGFuZCBheGlzIGxhYmVscwogICAgdGhlbWUocGxvdC50aXRsZSAgICAgICA9IGVsZW1lbnRfdGV4dChjb2xvcj1jb2xvci50ZXh0LCBzaXplPTIwLCBmYWNlID0gImJvbGQiKSkgKwogICAgdGhlbWUoYXhpcy50aXRsZS54ICAgICA9IGVsZW1lbnRfdGV4dChzaXplPTE0LCBjb2xvcj0iYmxhY2siLCBmYWNlID0gImJvbGQiKSkgKwogICAgdGhlbWUoYXhpcy50aXRsZS55ICAgICA9IGVsZW1lbnRfdGV4dChzaXplPTE0LCBjb2xvcj0iYmxhY2siLCBmYWNlID0gImJvbGQiLCB2anVzdD0xLjI1KSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggICAgICA9IGVsZW1lbnRfdGV4dChzaXplPTEwLCB2anVzdD0wLjUsIGhqdXN0PTAuNSwgY29sb3IgPSBjb2xvci50ZXh0KSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnkgICAgICA9IGVsZW1lbnRfdGV4dChzaXplPTEwLCBjb2xvciA9IGNvbG9yLnRleHQpKSArCiAgICB0aGVtZShzdHJpcC50ZXh0ICAgICAgID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArCgogICAgIyBQbG90IG1hcmdpbnMKICAgIHRoZW1lKHBsb3QubWFyZ2luID0gdW5pdChjKDAuMzUsIDAuMiwgMC4zLCAwLjM1KSwgImNtIikpCn0KCmBgYAoKCiMjIEJ1bXAgQ2hhcnQgZm9yIDE4IFdlYnNpdGUgR2VuZXJhdG9ycwoKYGBge3IgYnVtcC1jaGFydC1zdGFyLXJhbmtpbmdzLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0xMH0Kc2dfZGF0YSA8LSByZWFkUkRTKCJkYXRhL3NnX2RhdGEucmRzIikKc2dfdGVtcCA8LSBzZWxlY3Qoc2dfZGF0YSwgYygiTmFtZSIsIHN0YXJ0c193aXRoKCJSYW5rLlN0YXJzIikpKQpvcmRlcl9uYW1lcyA8LSBvcmRlcihzZ190ZW1wJE5hbWUpCnNnX3RlbXAgPC0gIHNnX3RlbXBbb3JkZXJfbmFtZXMsIF0KCiMgU0VFOiBiaXQubHkvU08tZmxpcC1yb3ctY29sCnNnX3N0YXJfcmFuayA8LSBkYXRhLmZyYW1lKHQoc2dfdGVtcFstMV0pKQpjb2xuYW1lcyhzZ19zdGFyX3JhbmspIDwtIHNnX3RlbXBbLCAxXQpyb3duYW1lcyhzZ19zdGFyX3JhbmspIDwtIHNnX3F1YW50aXR5WywgMV0Kc2dfc3Rhcl9yYW5rIDwtIGFzX3RpYmJsZShyb3duYW1lc190b19jb2x1bW4oc2dfc3Rhcl9yYW5rLCB2YXIgPSAiRGF0ZSIpKQpzZ19zdGFyX3JhbmskRGF0ZSA8LSBhcy5EYXRlKHNnX3N0YXJfcmFuayREYXRlKQpzZ19zdGFyX3JhbmtfbG9uZyAgPC0gbWVsdChzZ19zdGFyX3JhbmssIGlkLnZhcnMgPSAiRGF0ZSIsIAogICAgICAgICAgICAgICAgIHZhcmlhYmxlLm5hbWUgPSAiU3RhdGljZ2VuIiwgdmFsdWUubmFtZSA9ICJSYW5rIikKQXJjaGl2ZS5OciA8LSByZXAoYygxLCAyLCAzLCA0LCA1LCA2LCA3KSwgMTgpCnNnX3N0YXJfcmFua19sb25nIDw8LSBkYXRhLmZyYW1lKGNiaW5kKHNnX3N0YXJfcmFua19sb25nLCBBcmNoaXZlLk5yKSkKCgoKIyBTRUU6IGh0dHBzOi8vd3d3LnN0YXRvbG9neS5vcmcvaG93LXRvLWVhc2lseS1jcmVhdGUtYS1idW1wLWNoYXJ0LWluLXItdXNpbmctZ2dwbG90Mi8KZ2dwbG90KHNnX3N0YXJfcmFua19sb25nLCBhZXMoeCA9IGFzLmZhY3RvcihBcmNoaXZlLk5yKSwgeSA9IFJhbmssIGdyb3VwID0gU3RhdGljZ2VuKSkgKwogIGdlb21fbGluZShhZXMoY29sb3IgPSBTdGF0aWNnZW4sIGFscGhhID0gMSksIHNpemUgPSAxKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBTdGF0aWNnZW4sIGFscGhhID0gMSksIHNpemUgPSAyKSArCiAgZ2VvbV9wb2ludChjb2xvciA9ICIjRkZGRkZGIiwgc2l6ZSA9IDEpICsKICBzY2FsZV95X3JldmVyc2UoYnJlYWtzID0gMTpucm93KHNnX3N0YXJfcmFua19sb25nKSkgKyAKICBzY2FsZV94X2Rpc2NyZXRlKGJyZWFrcyA9IDE6NykgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKwogIGdlb21fdGV4dChkYXRhID0gc2dfc3Rhcl9yYW5rX2xvbmcgJT4lIGZpbHRlcihBcmNoaXZlLk5yID09ICIxIiksCiAgICAgICAgICAgIGFlcyhsYWJlbCA9IFN0YXRpY2dlbiwgeCA9IDAuNykgLCBoanVzdCA9IC41LAogICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwgIHNpemUgPSAzKSArCiAgZ2VvbV90ZXh0KGRhdGEgPSBzZ19zdGFyX3JhbmtfbG9uZyAlPiUgZmlsdGVyKEFyY2hpdmUuTnIgPT0gIjciKSwKICAgICAgICAgICAgYWVzKGxhYmVsID0gU3RhdGljZ2VuLCB4ID0gNy4zKSAsIGhqdXN0ID0gMC41LAogICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwgIHNpemUgPSAzKSArCiAgbGFicyh4ID0gIjE6SnVuIDIwMTQsIDc6QXVnIDIwMTksIDItNjogSmFuICgyMDE1LTIwMTkpIiwKICAgICAgIHkgPSAiUmFuayIsCiAgICAgICB0aXRsZSA9ICJDb21wYXJpc29uIG9mIFN0YXRpYyBXZWJzaXRlIEdlbmVyYXRvcnMiLAogICAgICAgc3VidGl0bGUgPSAiUmFua2VkIGJ5IG51bWJlciBvZiByZXBvc2l0b3J5IHN0YXJzIikgKwogIG15X3RoZW1lKCkKYGBgCgoKV2l0aCB0aGlzIGJ1bXAgY2hhcnQsIG9uZSBjYW4gc2VlIHdoaWNoIGdlbmVyYXRvcnMgYXJlIHJpc2luZyBpbiB0aGVpciBwb3B1bGFyaXR5LiBUaGVzZSByZWxhdGl2ZSBkZXZlbG9wbWVudHMgd2VyZSBoaWRkZW4gYnkgYW4gb3ZlcmFsbCBwb3NpdGl2ZSB0cmVuZCBvZiBzdGF0aWMgd2Vic2l0ZSBnZW5lcmF0b3JzLgo=