suppressPackageStartupMessages({
  library(magrittr)
  library(tidyverse)
  library(knitr)
  library(kableExtra)
  library(WikidataQueryServiceR)
  import::from(polloi, compress)
})
options(knitr.kable.NA = "—")
wikidata_link <- function(id) {
  linked_cells <- text_spec(id, link = paste0("https://www.wikidata.org/entity/", id))
  linked_cells[is.na(id)] <- ""
  return(linked_cells)
}
make_list <- function(items) {
  if (all(is.na(items))) {
    return("—")
  } else {
    return(sprintf("<ul style = \"padding-left: 0;\">%s</ul>", paste0(sprintf("<li>%s</li>", items), collapse = "")))
  }
}
maybe_make_list <- function(x) {
  if (length(x) == 1) {
    if (is.na(x)) return("—")
    else return(x)
  } else {
    return(make_list(x))
  }
}
make_mini_table <- function(x) {
  if (is.na(x[[1]]$item)) return("—")
  map_dfr(x, as_data_frame) %>%
    mutate(item = wikidata_link(item)) %>%
    kable(col.names = NULL, escape = FALSE) %>%
    kable_styling("striped", full_width = TRUE) %>%
    column_spec(1, width = "100px")
}
suppressMessages({
  examples <- read_tsv("data/example.tsv")
  taxons <- read_rds("data/wikidata_taxons_refined.rds")
  commons <- read_rds("data/wikidata_organisms_known_by_common_name.rds")
})

This project (tracked in T197986) is about quantifying the state of incompleteness of Q-items on Wikidata. Unfortunately the question of inconsistency is an enormous research project and quantifying it would require concrete (and automatable) definition. It is also outside of the ability of this report’s author (Mikhail Popov) and the scope of the Wikimedia Audiences Product Analytics team. The code, queries, and data are available in the corresponding repository on GitHub.

Disclaimer: in order to facilitate the analysis, the data was limited to English labels/descriptions/aliases/taxon common names. Due to the prevalence of English on Wikidata, it could be safe to assume that however bad the statistics look in English, they look much worse in other languages.

The following are examples which were highlighted in the deck Wikidata completeness and quality issues:

formatted_examples <- examples %>%
  mutate(entity = wikidata_link(entity),
         description = ifelse(is.na(description), "—", description),
         language = c("en" = "English", "es" = "Spanish")[language],
         instance_of = sprintf("%s (%s)", instance_of_label, wikidata_link(instance_of)),
         taxon_common_name = ifelse(is.na(taxon_common_name), "—", taxon_common_name),
         group = sprintf("%s (%s)", group_label, wikidata_link(group)),
         group = ifelse(group == "NA ()", "—", group)) %>%
  select(-c(instance_of_label, group_label)) %>%
  group_by(entity, language, label, description, instance_of, group) %>%
  summarize(taxon_common_names = paste0(unique(taxon_common_name), collapse = ", "),
            aliases = make_list(alias)) %>%
  ungroup %>%
  arrange(entity, language)
formatted_examples %>%
  kable(col.names = c(
    "Entity", "Language", "Label", "Description",
    "Instance of", "Group", "Taxon common name(s)", "Alias(es)"
  ), escape = FALSE, caption = "Examples highlighted in the \"Wikidata completeness and quality issues\" slides.") %>%
  kable_styling() %>%
  collapse_rows(columns = 1:2, valign = "top")
Examples highlighted in the "Wikidata completeness and quality issues" slides.
Entity Language Label Description Instance of Group Taxon common name(s) Alias(es)
Q11946202 English butterfly insect of the order Lepidoptera group of organisms known by one particular common name (Q55983715) Lepidoptera (Q28319)
  • Rhopalocera
Spanish Rhopalocera clado obsoleto de insectos del orden Lepidoptera group of organisms known by one particular common name (Q55983715) lepidópteros (Q28319)
  • ropalócero
  • ropalocero
  • mariposas diurnas
Q28319 English Lepidoptera order of insects that includes butterflies and moths taxon (Q16521) Butterflies and Moths
  • lepidopterans
Spanish lepidópteros orden de insectos holometábolos taxon (Q16521)
  • mariposas
  • lepidopteros
  • lepidoptero
  • Lepidoptera
  • lepidóptero
  • mariposa
Q311230 English Epiphyllum oxypetalum species of plant taxon (Q16521) Dutchman's pipe cactus, Dutchman's Pipe Cactus
Spanish Epiphyllum oxypetalum especie de planta taxon (Q16521) Nopalillo Criollo
  • Phyllocactus oxypetalus
  • Phyllocactus grandis
  • Epiphyllum latifrons
  • Epiphyllum grande
  • Epiphyllum acuminatum
  • Cereus latifrons
  • Cereus oxypetalus
Q319469 English Salamandridae family of amphibians taxon (Q16521)
Spanish Salamandridae taxon (Q16521)
  • Salamandrininae
  • Salamandrinae
  • Salamándridos
  • Salamandridos
  • Salamándrido
  • Salamandrido
  • Pleurodelinae
Q3469592 English salamander animal group of organisms known by one particular common name (Q55983715) Salamandridae (Q319469)

Search Indexing

Example 1a Example 1b Example 1c Example 1d
Example 1a Example 1b Example 1c Example 1d
Searching for “butterfly” with English as UI language Searching for “mariposa” with English as UI language Searching for “mariposa” with Spanish as UI language Searching for “mariposas” with Spanish as UI language

One might expect Q11946202 (Rhopalocera) to show when searching for “mariposa” with Spanish as the display language because that’s the common name for butterfly in Spanish, but because it has “mariposas” as an alias while Q28319 (lepidópteros) has “mariposa” as an alias, Q28319 is shown higher than Q11946202 until an “s” is added.

Unfortunately that’s just how information retrieval works. Exact matches yield higher scores than partial matches. During my time with Search Platform team as part of Discovery (RIP), the most important thing I learned was: search is hard.

Details

In my chat with Stas (Senior Performance Engineer, Search Platform):

Stas: labels & descriptions are indexed
      some statement values are indexed too
      aliases are indexed as labels
      statements are indexed twofold - as P123=Value in dedicated field
      and also values are added into all field
  Me: what determines when a statement value is indexed?
Stas: value type. right now only item and string valued statements are indexed
      oh and external ID (which is a kind of string)
      but in general if you have a lot of similar items the order may not be what you want

Searching for “salamander animal”:

Even though “salamander animal” contains both the label and the description for salamander entity (Q3469592), it only shows up halfway in the top 20 results because:

Right now it’s a combination of item weight and query score, and the weights between those are pretty much invented out of the thin air so now we’re collecting click statistics to try and make them more based in reality

So that’s the answer to the problem of why some items don’t show up in the top 10 autocomplete suggest feature.

Cool Trick: since statements with item & string values are indexed, it’s possible to search with qualifiers too: haswbstatement:P31=Q55983715[P642=Q319469]. In its Cirrus index entry, we can see that:

"statement_keywords":[
  "P31=Q55983715",
  "P31=Q55983715[P642=Q319469]"
]

Taxons

Note: technical limitations (SPARQL queries timing out) prevented us from compiling a dataset of all taxons on Wikidata, and we limited our dataset to those which had either: (1) at least one English alias, or (2) at least one English taxon common name. However, we can at least count how many taxons there are on Wikidata:

SELECT (COUNT(?item) AS ?n_taxons) WHERE {
  ?item wdt:P31 wd:Q16521.
}
today <- lubridate::today()
n_taxons <- query_wikidata("SELECT (COUNT(?item) AS ?n_taxons) WHERE { ?item wdt:P31 wd:Q16521.}")$n_taxons

As of 2018-12-12, there are 2.49M items on Wikidata which are instances of taxon. This means that our dataset of 122.06K items – those which had at least one English alias or English taxon common name – is approximately 4.91% of all taxon items on Wikidata.

Beyond that it is hard to say how many taxons even have labels. If we try use Wikidata Query Service to count how many taxons have an English label, the following query times out:

SELECT (COUNT(?item) AS ?n_taxons) WHERE {
  ?item wdt:P31 wd:Q16521.
  ?item rdfs:label ?itemLabel.
  FILTER(LANG(?itemLabel) = "en").
}

Let’s take a look at a few taxons to get a sense of what data they may have available:

taxons %>%
  filter(entity %in% c("Q28319", "Q311230", "Q1000270", "Q59392949", "Q1034859", "Q1035244", "Q25327", "Q29995", "Q1010571")) %>%
  arrange(desc(entity)) %>%
  mutate(
    label = ifelse(is.na(sqoop_label), "—", sqoop_label),
    description = ifelse(is.na(sqoop_description), "—", sqoop_description),
    item = sprintf("%s (%s)", label, wikidata_link(entity)),
    aliases = map_chr(query_aliases, maybe_make_list),
    taxon_common_names = map_chr(query_taxon_common_names, maybe_make_list)
  ) %>%
  select(item, description, aliases, taxon_common_names) %>%
  kable(escape = FALSE, caption = "Taxons on Wikidata",
        col.names = c("Item", "Description", "Alias(es)", "Taxon common name(s)")) %>%
  kable_styling(bootstrap_options = "striped")
Taxons on Wikidata
Item Description Alias(es) Taxon common name(s)
— (Q59392949) Crinipellis carecomoeis
Epiphyllum oxypetalum (Q311230) species of plant
  • Dutchman's Pipe Cactus
  • Dutchman's pipe cactus
European Otter (Q29995) otter
  • Common otter
  • Eurasian otter
  • Eurasian river otter
  • Lutra lutra
  • Old World otter
  • common otter
  • lutra lutra
  • Common Otter
  • Common otter
  • Eurasian Otter
  • European Otter
  • European River Otter
  • Old World Otter
Lepidoptera (Q28319) order of insects that includes butterflies and moths lepidopterans Butterflies and Moths
Coccinellidae (Q25327) family of insects
  • Lady Birds
  • Lady beetles
  • Lady bugs
  • Lady-Birds
  • LadyBirds
  • LadyBugs
  • Ladybeetles
  • Ladybird beetles
  • Ladybirds
  • Ladybugs
Caquetá titi (Q1035244) titi monkey endemic to Colombia
  • Callicebus caquetensis
  • Caqueta titi
  • Caquet· titi monkey
  • bushy-bearded titi
  • red-bearded titi
  • Caquet· titi monkey
  • Caquetá Titi
  • Caquetá Titi Monkey
  • Caquetá Tití Monkey
Quercus acutissima (Q1034859) species of plant
  • Bristle oak
  • Chinese oak
sawtooth oak
mizuna (Q1010571) Brassica rapa var. laciniifolia
Epinephelus coeruleopunctatus (Q1000270) species of fish Whitespotted grouper
  • Garrupa
  • Ocellated Rock-cod
  • Rock Cod
  • Small-spotted Rock Cod
  • Snowy Grouper
  • Vieille Cuisinier
  • White-spotted Grouper
  • White-spotted Reef-cod
  • White-spotted Rockcod
  • Whitespotted Grouper
  • Whitespotted Rockcod
  • Whitespotted grouper

When the items are indexed, the index includes labels (if any), descriptions (if any), aliases (if any), and any values of statements which are plain text or Q-items. So if an item is, say, a person who is an instance of (P31) of human (Q5), then their index includes “P31=Q5” but not that they’re a “human”. However, if the value is plain text (e.g. taxon common name), then that gets included in the index and can be searched for. For example, Wikimedia Foundation, Inc. (Q180) has a statement for property “IPv4 routing prefix” (P3761) so if one does a full-text search for “198.35.26.0/23” (the value currently in that statement), WMF is the first result listed.

So one way to assess the searchability of taxons on Wikidata is to assess how many have lebels, descriptions, aliases, and taxon common names (P1843). Among the 122,059 collected taxons, we have following completeness statistics:

taxon_completeness <- taxons %>%
  select(entity, sqoop_label, sqoop_description, query_aliases, query_taxon_common_names) %>%
  transmute(
    item = entity,
    `alias(es)` = !map_lgl(query_aliases, ~ all(is.na(.x))),
    `taxon common name(s)` = !map_lgl(query_taxon_common_names, ~ all(is.na(.x))),
    label = !is.na(sqoop_label),
    description = !is.na(sqoop_description),
  ) %>%
  gather(has, val, -item) %>%
  arrange(item, has, val)
taxon_completeness %>%
  filter(val) %>%
  group_by(item) %>%
  summarize(n_has = n(), has = paste0(has, collapse = ", ")) %>%
  count(n_has, has) %>%
  arrange(desc(n)) %>%
  mutate(prop = sprintf("%.3f%%", 100 * n / sum(n))) %>%
  kable(escape = FALSE, caption = "English info completeness of taxons on Wikidata",
        col.names = c("Fields available for a taxon", "Info available (in English)", "Items in dataset", "Proportion of dataset")) %>%
  kable_styling(bootstrap_options = "striped")
English info completeness of taxons on Wikidata
Fields available for a taxon Info available (in English) Items in dataset Proportion of dataset
3 description, label, taxon common name(s) 53736 44.025%
4 alias(es), description, label, taxon common name(s) 30660 25.119%
3 alias(es), description, label 30477 24.969%
2 alias(es), label 5561 4.556%
2 label, taxon common name(s) 974 0.798%
3 alias(es), label, taxon common name(s) 650 0.533%
1 alias(es) 1 0.001%

And, conversely, the following missingness statistics:

taxon_completeness %>%
  filter(!val) %>%
  group_by(item) %>%
  summarize(n_has = n(), has = paste0(has, collapse = ", ")) %>%
  count(n_has, has) %>%
  arrange(desc(n)) %>%
  mutate(prop = sprintf("%.3f%%", 100 * n / sum(n))) %>%
  kable(escape = FALSE, caption = "English info missingness of taxons on Wikidata",
        col.names = c("Fields NOT available for a taxon", "Info NOT available (in English)", "Items in dataset", "Proportion of dataset")) %>%
  kable_styling(bootstrap_options = "striped")
English info missingness of taxons on Wikidata
Fields NOT available for a taxon Info NOT available (in English) Items in dataset Proportion of dataset
1 alias(es) 53736 58.793%
1 taxon common name(s) 30477 33.345%
2 description, taxon common name(s) 5561 6.084%
2 alias(es), description 974 1.066%
1 description 650 0.711%
3 description, label, taxon common name(s) 1 0.001%

Those are combinations of missing fields. The following are per-field completeness & missingness statistics:

info_completeness <- taxon_completeness %>%
  group_by(has) %>%
  summarize(n = sum(val), total = n())
info_completeness_n <- set_names(info_completeness$n, info_completeness$has)
info_completeness %>%
  transmute(has = has,
            prop1 = sprintf("%s (%.3f%%)", compress(n), 100 * n / total),
            prop2 = sprintf("%s (%.3f%%)", compress(total - n), 100 * (total - n) / total)) %>%
  kable(col.names = c("Information a taxon item may have", "How many have a value (in English)", "How many do NOT have a value (in English)"),
        caption = sprintf("English completeness of items among a subset of %s taxons", compress(nrow(taxons))),
        align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = "striped")
English completeness of items among a subset of 122.06K taxons
Information a taxon item may have How many have a value (in English) How many do NOT have a value (in English)
alias(es) 67.35K (55.177%) 54.71K (44.823%)
description 114.87K (94.113%) 7.19K (5.887%)
label 122.06K (99.999%) 1 (0.001%)
taxon common name(s) 86.02K (70.474%) 36.04K (29.526%)

Considering that the collected dataset only included taxons which had at least one alias or at least one taxon common name, the missingness of those two items – both of which aid a lot in search – is rather concerning.

Again, these numbers are not representative of all 2.49M taxons. As a reminder, the dataset of 122.06K taxons studied was limited to those which had (1) at least one English alias or (2) at least one English taxon common name. It’s not clear how many taxons on Wikidata do have a description or a label, but we can at least say that of 2.49M, only 67.35K (2.708%) taxons have at least one alias in English and only 86.02K (3.459%) taxons have at least one taxon common name in English.

Note: A follow-up of this work should include all languages. We restricted this initial exploration to English as that is the analyst’s primary language.

Groups of organisms known by one particular common name

An item may also be an instance of “groups of organisms known by one particular common name” (Q55983715). The following are some examples of such items:

commons %>%
  filter(item %in% c("Q5", "Q11946202", "Q3469592", "Q11065036", "Q17128757")) %>%
  arrange(desc(item)) %>%
  mutate(
    item = sprintf("%s (%s)", label, wikidata_link(item)),
    description = ifelse(is.na(description), "—", description),
    aliases = map(aliases, maybe_make_list),
    groups = map(groups, make_mini_table),
    different_from = map(different_from, make_mini_table)
  ) %>%
  select(-label) %>%
  kable(escape = FALSE,
        col.names = c("Item", "Description", "Alias(es)",
                      "Group(s) (Item, Label)", "Different From (Item, Label)"),
        caption = "Wikidata entities that are instances of 'groups of organisms known by one particular common name'") %>%
  kable_styling(full_width = TRUE) %>%
  column_spec(1, width = "100px") %>%
  column_spec(2, width = "150px")
Wikidata entities that are instances of 'groups of organisms known by one particular common name'
Item Description Alias(es) Group(s) (Item, Label) Different From (Item, Label)
human (Q5) common name of Homo sapiens, unique extant species of the genus Homo
  • person
  • homosapiens
  • human being
  • humankind
  • people
Q15978631 Homo sapiens
Q171283 Homo
Q3238275 Homo sapiens sapiens
Q2472587 people
Q95074 fictional character
salamander (Q3469592) animal
Q319469 Salamandridae
heather (Q17128757)
Q16870634 Heather
Q7498915 Heather
butterfly (Q11946202) insect of the order Lepidoptera Rhopalocera
Q28319 Lepidoptera
Q28319 Lepidoptera
Bedla (Q11065036)
Q1090886 Lepiota
Q11723904 Sericeomyces
Q1297274 Leucoagaricus
Q1300898 Leucocoprinus
Q19748 Macrolepiota
Q604880 Chlorophyllum
Q606310 Chamaemyces
Q606322 Cystolepiota
Q80469 Echinoderma

Similar the work on taxons, we can calculate some completeness statistics on these items. Although unlike the case with taxons, these (English-focused) statistics apply to all instances found on Wikidata.

commons %>%
  transmute(
    label = !is.na(label),
    description = !is.na(description),
    `alias(es)` = map_int(aliases, ~ sum(!is.na(.x))) > 0,
    `at least one "of" qualifier` = map_int(groups, ~ sum(!is.na(.x[[1]]$item))) > 0,
    `at least one "different from" statement` = map_int(different_from, ~ sum(!is.na(.x[[1]]$item))) > 0
  ) %>%
  gather(has, val) %>%
  mutate(has = factor(has, c("label", "description", "alias(es)", "at least one \"of\" qualifier", "at least one \"different from\" statement"))) %>%
  group_by(has) %>%
  summarize(prop = sprintf("%.1f%%", 100 * mean(val))) %>%
  kable(col.names = c("Information an item may have", "How many have a value (in English)"),
        caption = "English completeness of items which are instances of 'group of organisms known by one particular common name'",
        align = c("l", "r")) %>%
  kable_styling("striped") %>%
  group_rows(index = c("Indexed for search as text" = 3, "Indexed for search as statement_keywords" = 2))
English completeness of items which are instances of 'group of organisms known by one particular common name'
Information an item may have How many have a value (in English)
Indexed for search as text
label 73.0%
description 41.5%
alias(es) 10.9%
Indexed for search as statement_keywords
at least one "of" qualifier 18.1%
at least one "different from" statement 6.6%

As before, we can also look at combinations of missing fields to determine how many items would be easily found by searching (e.g. an item which has a label, a description, and an alias would be more likely to be found by someone looking for it than an item with only, say, a label):

commons_completeness <- commons %>%
  transmute(
    item = item,
    label = !is.na(label),
    description = !is.na(description),
    `alias(es)` = map_int(aliases, ~ sum(!is.na(.x))) > 0,
    `at least one "of" qualifier` = map_int(groups, ~ sum(!is.na(.x[[1]]$item))) > 0,
    `at least one "different from" statement` = map_int(different_from, ~ sum(!is.na(.x[[1]]$item))) > 0
  ) %>%
  gather(has, val, -item) %>%
  mutate(has = factor(has, c("label", "description", "alias(es)", "at least one \"of\" qualifier", "at least one \"different from\" statement"))) %>%
  filter(val) %>%
  group_by(item) %>%
  summarize(n_has = n(), has = paste0(has, collapse = ", ")) %>%
  count(n_has, has) %>%
  arrange(desc(n_has), desc(n)) %>%
  mutate(prop = sprintf("%.2f%%", 100 * n / sum(n)))
commons_completeness %>%
  dplyr::select(-n_has) %>%
  kable(escape = FALSE, caption = "English information completeness of 'group of organisms known by one particular common name' instances on Wikidata",
        col.names = c("Fields of information available (in English)", "Items on Wikidata", "Proportion among all such instances")) %>%
  kable_styling(bootstrap_options = "striped") %>%
  group_rows(index = auto_index(commons_completeness$n_has), group_label = "Fields available for an item")
English information completeness of 'group of organisms known by one particular common name' instances on Wikidata
Fields of information available (in English) Items on Wikidata Proportion among all such instances
5
label, description, alias(es), at least one "of" qualifier, at least one "different from" statement 7 1.19%
4
label, description, alias(es), at least one "of" qualifier 12 2.05%
label, description, at least one "of" qualifier, at least one "different from" statement 9 1.54%
label, description, alias(es), at least one "different from" statement 8 1.37%
3
label, description, at least one "of" qualifier 56 9.56%
label, description, alias(es) 39 6.66%
label, description, at least one "different from" statement 16 2.73%
label, alias(es), at least one "of" qualifier 2 0.34%
label, at least one "of" qualifier, at least one "different from" statement 2 0.34%
label, alias(es), at least one "different from" statement 1 0.17%
2
label, description 158 26.96%
label, at least one "of" qualifier 27 4.61%
label, alias(es) 14 2.39%
label, at least one "different from" statement 5 0.85%
description, at least one "of" qualifier 2 0.34%
1
label 198 33.79%
at least one "of" qualifier 20 3.41%
description 8 1.37%
at least one "different from" statement 2 0.34%
LS0tCnRpdGxlOiAiVDE5Nzk4NiIKZGVzY3JpcHRpb246IHwgCiAgUmVwb3J0IG9uIHRoZSBzdGF0ZSBvZiBXaWtpZGF0YSBpbmNvbXBsZXRlbmVzcwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiByZWFkYWJsZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKYGBge3Igc2V0dXB9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShtYWdyaXR0cikKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKICBsaWJyYXJ5KGtuaXRyKQogIGxpYnJhcnkoa2FibGVFeHRyYSkKICBsaWJyYXJ5KFdpa2lkYXRhUXVlcnlTZXJ2aWNlUikKICBpbXBvcnQ6OmZyb20ocG9sbG9pLCBjb21wcmVzcykKfSkKb3B0aW9ucyhrbml0ci5rYWJsZS5OQSA9ICLigJQiKQp3aWtpZGF0YV9saW5rIDwtIGZ1bmN0aW9uKGlkKSB7CiAgbGlua2VkX2NlbGxzIDwtIHRleHRfc3BlYyhpZCwgbGluayA9IHBhc3RlMCgiaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL2VudGl0eS8iLCBpZCkpCiAgbGlua2VkX2NlbGxzW2lzLm5hKGlkKV0gPC0gIiIKICByZXR1cm4obGlua2VkX2NlbGxzKQp9Cm1ha2VfbGlzdCA8LSBmdW5jdGlvbihpdGVtcykgewogIGlmIChhbGwoaXMubmEoaXRlbXMpKSkgewogICAgcmV0dXJuKCLigJQiKQogIH0gZWxzZSB7CiAgICByZXR1cm4oc3ByaW50ZigiPHVsIHN0eWxlID0gXCJwYWRkaW5nLWxlZnQ6IDA7XCI+JXM8L3VsPiIsIHBhc3RlMChzcHJpbnRmKCI8bGk+JXM8L2xpPiIsIGl0ZW1zKSwgY29sbGFwc2UgPSAiIikpKQogIH0KfQptYXliZV9tYWtlX2xpc3QgPC0gZnVuY3Rpb24oeCkgewogIGlmIChsZW5ndGgoeCkgPT0gMSkgewogICAgaWYgKGlzLm5hKHgpKSByZXR1cm4oIuKAlCIpCiAgICBlbHNlIHJldHVybih4KQogIH0gZWxzZSB7CiAgICByZXR1cm4obWFrZV9saXN0KHgpKQogIH0KfQptYWtlX21pbmlfdGFibGUgPC0gZnVuY3Rpb24oeCkgewogIGlmIChpcy5uYSh4W1sxXV0kaXRlbSkpIHJldHVybigi4oCUIikKICBtYXBfZGZyKHgsIGFzX2RhdGFfZnJhbWUpICU+JQogICAgbXV0YXRlKGl0ZW0gPSB3aWtpZGF0YV9saW5rKGl0ZW0pKSAlPiUKICAgIGthYmxlKGNvbC5uYW1lcyA9IE5VTEwsIGVzY2FwZSA9IEZBTFNFKSAlPiUKICAgIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVFJVRSkgJT4lCiAgICBjb2x1bW5fc3BlYygxLCB3aWR0aCA9ICIxMDBweCIpCn0KYGBgCmBgYHtyIGRhdGF9CnN1cHByZXNzTWVzc2FnZXMoewogIGV4YW1wbGVzIDwtIHJlYWRfdHN2KCJkYXRhL2V4YW1wbGUudHN2IikKICB0YXhvbnMgPC0gcmVhZF9yZHMoImRhdGEvd2lraWRhdGFfdGF4b25zX3JlZmluZWQucmRzIikKICBjb21tb25zIDwtIHJlYWRfcmRzKCJkYXRhL3dpa2lkYXRhX29yZ2FuaXNtc19rbm93bl9ieV9jb21tb25fbmFtZS5yZHMiKQp9KQpgYGAKClRoaXMgcHJvamVjdCAodHJhY2tlZCBpbiBbVDE5Nzk4Nl0oaHR0cHM6Ly9waGFicmljYXRvci53aWtpbWVkaWEub3JnL1QxOTc5ODYpKSBpcyBhYm91dCBxdWFudGlmeWluZyB0aGUgc3RhdGUgb2YgaW5jb21wbGV0ZW5lc3Mgb2YgUS1pdGVtcyBvbiBXaWtpZGF0YS4gVW5mb3J0dW5hdGVseSB0aGUgcXVlc3Rpb24gb2YgaW5jb25zaXN0ZW5jeSBpcyBhbiBlbm9ybW91cyByZXNlYXJjaCBwcm9qZWN0IGFuZCBxdWFudGlmeWluZyBpdCB3b3VsZCByZXF1aXJlIGNvbmNyZXRlIChhbmQgYXV0b21hdGFibGUpIGRlZmluaXRpb24uIEl0IGlzIGFsc28gb3V0c2lkZSBvZiB0aGUgYWJpbGl0eSBvZiB0aGlzIHJlcG9ydCdzIGF1dGhvciAoW01pa2hhaWwgUG9wb3ZdKGh0dHBzOi8vbWV0YS53aWtpbWVkaWEub3JnL3dpa2kvVXNlcjpNUG9wb3ZfKFdNRikpKSBhbmQgdGhlIHNjb3BlIG9mIHRoZSBbV2lraW1lZGlhIEF1ZGllbmNlcyBQcm9kdWN0IEFuYWx5dGljcyB0ZWFtXShodHRwczovL21lZGlhd2lraS5jb20vd2lraS9Qcm9kdWN0X0FuYWx5dGljcykuIFRoZSBjb2RlLCBxdWVyaWVzLCBhbmQgZGF0YSBhcmUgYXZhaWxhYmxlIGluIFt0aGUgY29ycmVzcG9uZGluZyByZXBvc2l0b3J5IG9uIEdpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3dpa2ltZWRpYS1yZXNlYXJjaC9TRG9DLVQxOTc5ODYpLgoKKipEaXNjbGFpbWVyKio6IGluIG9yZGVyIHRvIGZhY2lsaXRhdGUgdGhlIGFuYWx5c2lzLCB0aGUgZGF0YSB3YXMgbGltaXRlZCB0byBFbmdsaXNoIGxhYmVscy9kZXNjcmlwdGlvbnMvYWxpYXNlcy90YXhvbiBjb21tb24gbmFtZXMuIER1ZSB0byB0aGUgcHJldmFsZW5jZSBvZiBFbmdsaXNoIG9uIFdpa2lkYXRhLCBpdCBjb3VsZCBiZSBzYWZlIHRvIGFzc3VtZSB0aGF0IGhvd2V2ZXIgYmFkIHRoZSBzdGF0aXN0aWNzIGxvb2sgaW4gRW5nbGlzaCwgdGhleSBsb29rIG11Y2ggd29yc2UgaW4gb3RoZXIgbGFuZ3VhZ2VzLgoKVGhlIGZvbGxvd2luZyBhcmUgZXhhbXBsZXMgd2hpY2ggd2VyZSBoaWdobGlnaHRlZCBpbiB0aGUgZGVjayBbV2lraWRhdGEgY29tcGxldGVuZXNzIGFuZCBxdWFsaXR5IGlzc3Vlc10oaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vcHJlc2VudGF0aW9uL2QvMWRBTnU5NHk5QUExNnQ2Zk5faV9oYWdxbHNkSTkycU1tZXpoSWIxNEYtYm8vZWRpdD91c3A9c2hhcmluZyk6CgpgYGB7ciBleGFtcGxlc30KZm9ybWF0dGVkX2V4YW1wbGVzIDwtIGV4YW1wbGVzICU+JQogIG11dGF0ZShlbnRpdHkgPSB3aWtpZGF0YV9saW5rKGVudGl0eSksCiAgICAgICAgIGRlc2NyaXB0aW9uID0gaWZlbHNlKGlzLm5hKGRlc2NyaXB0aW9uKSwgIuKAlCIsIGRlc2NyaXB0aW9uKSwKICAgICAgICAgbGFuZ3VhZ2UgPSBjKCJlbiIgPSAiRW5nbGlzaCIsICJlcyIgPSAiU3BhbmlzaCIpW2xhbmd1YWdlXSwKICAgICAgICAgaW5zdGFuY2Vfb2YgPSBzcHJpbnRmKCIlcyAoJXMpIiwgaW5zdGFuY2Vfb2ZfbGFiZWwsIHdpa2lkYXRhX2xpbmsoaW5zdGFuY2Vfb2YpKSwKICAgICAgICAgdGF4b25fY29tbW9uX25hbWUgPSBpZmVsc2UoaXMubmEodGF4b25fY29tbW9uX25hbWUpLCAi4oCUIiwgdGF4b25fY29tbW9uX25hbWUpLAogICAgICAgICBncm91cCA9IHNwcmludGYoIiVzICglcykiLCBncm91cF9sYWJlbCwgd2lraWRhdGFfbGluayhncm91cCkpLAogICAgICAgICBncm91cCA9IGlmZWxzZShncm91cCA9PSAiTkEgKCkiLCAi4oCUIiwgZ3JvdXApKSAlPiUKICBzZWxlY3QoLWMoaW5zdGFuY2Vfb2ZfbGFiZWwsIGdyb3VwX2xhYmVsKSkgJT4lCiAgZ3JvdXBfYnkoZW50aXR5LCBsYW5ndWFnZSwgbGFiZWwsIGRlc2NyaXB0aW9uLCBpbnN0YW5jZV9vZiwgZ3JvdXApICU+JQogIHN1bW1hcml6ZSh0YXhvbl9jb21tb25fbmFtZXMgPSBwYXN0ZTAodW5pcXVlKHRheG9uX2NvbW1vbl9uYW1lKSwgY29sbGFwc2UgPSAiLCAiKSwKICAgICAgICAgICAgYWxpYXNlcyA9IG1ha2VfbGlzdChhbGlhcykpICU+JQogIHVuZ3JvdXAgJT4lCiAgYXJyYW5nZShlbnRpdHksIGxhbmd1YWdlKQpmb3JtYXR0ZWRfZXhhbXBsZXMgJT4lCiAga2FibGUoY29sLm5hbWVzID0gYygKICAgICJFbnRpdHkiLCAiTGFuZ3VhZ2UiLCAiTGFiZWwiLCAiRGVzY3JpcHRpb24iLAogICAgIkluc3RhbmNlIG9mIiwgIkdyb3VwIiwgIlRheG9uIGNvbW1vbiBuYW1lKHMpIiwgIkFsaWFzKGVzKSIKICApLCBlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJFeGFtcGxlcyBoaWdobGlnaHRlZCBpbiB0aGUgXCJXaWtpZGF0YSBjb21wbGV0ZW5lc3MgYW5kIHF1YWxpdHkgaXNzdWVzXCIgc2xpZGVzLiIpICU+JQogIGthYmxlX3N0eWxpbmcoKSAlPiUKICBjb2xsYXBzZV9yb3dzKGNvbHVtbnMgPSAxOjIsIHZhbGlnbiA9ICJ0b3AiKQpgYGAKCgoKIyMgU2VhcmNoIEluZGV4aW5nCgp8IEV4YW1wbGUgMWEgfCBFeGFtcGxlIDFiIHwgRXhhbXBsZSAxYyB8IEV4YW1wbGUgMWQgfAp8Oi0tLS0tLS0tLS06fDotLS0tLS0tLS0tOnw6LS0tLS0tLS0tLTp8Oi0tLS0tLS0tLS06fAp8IVtFeGFtcGxlIDFhXShpbWFnZXMvYnV0dGVyZmx5LWEucG5nKXwhW0V4YW1wbGUgMWJdKGltYWdlcy9idXR0ZXJmbHktYi5wbmcpfCFbRXhhbXBsZSAxY10oaW1hZ2VzL2J1dHRlcmZseS1jLnBuZyl8IVtFeGFtcGxlIDFkXShpbWFnZXMvYnV0dGVyZmx5LWQucG5nKXwKfFNlYXJjaGluZyBmb3IgImJ1dHRlcmZseSIgd2l0aCBFbmdsaXNoIGFzIFVJIGxhbmd1YWdlfFNlYXJjaGluZyBmb3IgIm1hcmlwb3NhIiB3aXRoIEVuZ2xpc2ggYXMgVUkgbGFuZ3VhZ2V8U2VhcmNoaW5nIGZvciAibWFyaXBvc2EiIHdpdGggU3BhbmlzaCBhcyBVSSBsYW5ndWFnZXxTZWFyY2hpbmcgZm9yICJtYXJpcG9zYXMiIHdpdGggU3BhbmlzaCBhcyBVSSBsYW5ndWFnZXwKCk9uZSBtaWdodCBleHBlY3QgUTExOTQ2MjAyIChSaG9wYWxvY2VyYSkgdG8gc2hvdyB3aGVuIHNlYXJjaGluZyBmb3IgIm1hcmlwb3NhIiB3aXRoIFNwYW5pc2ggYXMgdGhlIGRpc3BsYXkgbGFuZ3VhZ2UgYmVjYXVzZSB0aGF0J3MgdGhlIGNvbW1vbiBuYW1lIGZvciBidXR0ZXJmbHkgaW4gU3BhbmlzaCwgYnV0IGJlY2F1c2UgaXQgaGFzICJtYXJpcG9zYXMiIGFzIGFuIGFsaWFzIHdoaWxlIFEyODMxOSAobGVwaWTDs3B0ZXJvcykgaGFzICJtYXJpcG9zYSIgYXMgYW4gYWxpYXMsIFEyODMxOSBpcyBzaG93biBoaWdoZXIgdGhhbiBRMTE5NDYyMDIgdW50aWwgYW4gInMiIGlzIGFkZGVkLgoKVW5mb3J0dW5hdGVseSB0aGF0J3MganVzdCBob3cgaW5mb3JtYXRpb24gcmV0cmlldmFsIHdvcmtzLiBFeGFjdCBtYXRjaGVzIHlpZWxkIGhpZ2hlciBzY29yZXMgdGhhbiBwYXJ0aWFsIG1hdGNoZXMuIER1cmluZyBteSB0aW1lIHdpdGggU2VhcmNoIFBsYXRmb3JtIHRlYW0gYXMgcGFydCBvZiBEaXNjb3ZlcnkgKFJJUCksIHRoZSBtb3N0IGltcG9ydGFudCB0aGluZyBJIGxlYXJuZWQgd2FzOiAqKl9zZWFyY2ggaXMgaGFyZF8qKi4KCiMjIyBEZXRhaWxzCgpJbiBteSBjaGF0IHdpdGggU3RhcyAoU2VuaW9yIFBlcmZvcm1hbmNlIEVuZ2luZWVyLCBTZWFyY2ggUGxhdGZvcm0pOgoKYGBgClN0YXM6IGxhYmVscyAmIGRlc2NyaXB0aW9ucyBhcmUgaW5kZXhlZAogICAgICBzb21lIHN0YXRlbWVudCB2YWx1ZXMgYXJlIGluZGV4ZWQgdG9vCiAgICAgIGFsaWFzZXMgYXJlIGluZGV4ZWQgYXMgbGFiZWxzCiAgICAgIHN0YXRlbWVudHMgYXJlIGluZGV4ZWQgdHdvZm9sZCAtIGFzIFAxMjM9VmFsdWUgaW4gZGVkaWNhdGVkIGZpZWxkCiAgICAgIGFuZCBhbHNvIHZhbHVlcyBhcmUgYWRkZWQgaW50byBhbGwgZmllbGQKICBNZTogd2hhdCBkZXRlcm1pbmVzIHdoZW4gYSBzdGF0ZW1lbnQgdmFsdWUgaXMgaW5kZXhlZD8KU3RhczogdmFsdWUgdHlwZS4gcmlnaHQgbm93IG9ubHkgaXRlbSBhbmQgc3RyaW5nIHZhbHVlZCBzdGF0ZW1lbnRzIGFyZSBpbmRleGVkCiAgICAgIG9oIGFuZCBleHRlcm5hbCBJRCAod2hpY2ggaXMgYSBraW5kIG9mIHN0cmluZykKICAgICAgYnV0IGluIGdlbmVyYWwgaWYgeW91IGhhdmUgYSBsb3Qgb2Ygc2ltaWxhciBpdGVtcyB0aGUgb3JkZXIgbWF5IG5vdCBiZSB3aGF0IHlvdSB3YW50CmBgYAoKU2VhcmNoaW5nIGZvciAic2FsYW1hbmRlciBhbmltYWwiOgoKLSBbU2VhcmNoIHJlc3VsdHMgcGFnZV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvaW5kZXgucGhwP3NlYXJjaD1zYWxhbWFuZGVyK2FuaW1hbCkKLSBbQ2lycnVzIHF1ZXJ5IGR1bXBdKGh0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93L2luZGV4LnBocD9zZWFyY2g9c2FsYW1hbmRlcithbmltYWwmY2lycnVzRHVtcFF1ZXJ5PXllcykKLSBbQ2lycnVzIHJlc3VsdHMgZHVtcF0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3cvaW5kZXgucGhwP3NlYXJjaD1zYWxhbWFuZGVyK2FuaW1hbCZjaXJydXNEdW1wUmVzdWx0PXllcykKCkV2ZW4gdGhvdWdoICJzYWxhbWFuZGVyIGFuaW1hbCIgY29udGFpbnMgYm90aCB0aGUgbGFiZWwgYW5kIHRoZSBkZXNjcmlwdGlvbiBmb3Igc2FsYW1hbmRlciBlbnRpdHkgKFtRMzQ2OTU5Ml0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTM0Njk1OTIpKSwgaXQgb25seSBzaG93cyB1cCBoYWxmd2F5IGluIHRoZSB0b3AgMjAgcmVzdWx0cyBiZWNhdXNlOgoKPiBSaWdodCBub3cgaXQncyBhIGNvbWJpbmF0aW9uIG9mIGl0ZW0gd2VpZ2h0IGFuZCBxdWVyeSBzY29yZSwgYW5kIHRoZSB3ZWlnaHRzIGJldHdlZW4gdGhvc2UgYXJlIHByZXR0eSBtdWNoIGludmVudGVkIG91dCBvZiB0aGUgdGhpbiBhaXIKPiBzbyBub3cgd2UncmUgY29sbGVjdGluZyBjbGljayBzdGF0aXN0aWNzIHRvIHRyeSBhbmQgbWFrZSB0aGVtIG1vcmUgYmFzZWQgaW4gcmVhbGl0eQoKU28gdGhhdCdzIHRoZSBhbnN3ZXIgdG8gdGhlIHByb2JsZW0gb2Ygd2h5IHNvbWUgaXRlbXMgZG9uJ3Qgc2hvdyB1cCBpbiB0aGUgdG9wIDEwIGF1dG9jb21wbGV0ZSBzdWdnZXN0IGZlYXR1cmUuCgoqKkNvb2wgVHJpY2sqKjogc2luY2Ugc3RhdGVtZW50cyB3aXRoIGl0ZW0gJiBzdHJpbmcgdmFsdWVzIGFyZSBpbmRleGVkLCBpdCdzIHBvc3NpYmxlIHRvIHNlYXJjaCB3aXRoIHF1YWxpZmllcnMgdG9vOiBbYGhhc3dic3RhdGVtZW50OlAzMT1RNTU5ODM3MTVbUDY0Mj1RMzE5NDY5XWBdKGh0dHBzOi8vd3d3Lndpa2lkYXRhLm9yZy93L2luZGV4LnBocD9zZWFyY2g9JnNlYXJjaD1oYXN3YnN0YXRlbWVudCUzQVAzMSUzRFE1NTk4MzcxNSU1QlA2NDIlM0RRMzE5NDY5JTVEJnRpdGxlPVNwZWNpYWwlM0FTZWFyY2gmZ289R28pLiBJbiBbaXRzIENpcnJ1cyBpbmRleCBlbnRyeV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTM0Njk1OTI/YWN0aW9uPWNpcnJ1c0R1bXApLCB3ZSBjYW4gc2VlIHRoYXQ6CgpgYGBKU09OCiJzdGF0ZW1lbnRfa2V5d29yZHMiOlsKICAiUDMxPVE1NTk4MzcxNSIsCiAgIlAzMT1RNTU5ODM3MTVbUDY0Mj1RMzE5NDY5XSIKXQpgYGAKCiMjIFRheG9ucwoKKipOb3RlKio6IHRlY2huaWNhbCBsaW1pdGF0aW9ucyAoU1BBUlFMIHF1ZXJpZXMgdGltaW5nIG91dCkgcHJldmVudGVkIHVzIGZyb20gY29tcGlsaW5nIGEgZGF0YXNldCBvZiBhbGwgdGF4b25zIG9uIFdpa2lkYXRhLCBhbmQgd2UgbGltaXRlZCBvdXIgZGF0YXNldCB0byB0aG9zZSB3aGljaCBoYWQgZWl0aGVyOiAoMSkgYXQgbGVhc3Qgb25lIEVuZ2xpc2ggYWxpYXMsIG9yICgyKSBhdCBsZWFzdCBvbmUgRW5nbGlzaCB0YXhvbiBjb21tb24gbmFtZS4gSG93ZXZlciwgd2UgY2FuIGF0IGxlYXN0IFtjb3VudCBob3cgbWFueSB0YXhvbnMgdGhlcmUgYXJlIG9uIFdpa2lkYXRhXShodHRwczovL3F1ZXJ5Lndpa2lkYXRhLm9yZy8jU0VMRUNUJTIwJTI4Q09VTlQlMjglM0ZpdGVtJTI5JTIwQVMlMjAlM0ZuX3RheG9ucyUyOSUyMFdIRVJFJTIwJTdCJTBBJTIwJTIwJTNGaXRlbSUyMHdkdCUzQVAzMSUyMHdkJTNBUTE2NTIxLiUwQSU3RCk6CgpgYGBTUEFSUUwKU0VMRUNUIChDT1VOVCg/aXRlbSkgQVMgP25fdGF4b25zKSBXSEVSRSB7CiAgP2l0ZW0gd2R0OlAzMSB3ZDpRMTY1MjEuCn0KYGBgCmBgYHtyIG5fdGF4b25zLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFfQp0b2RheSA8LSBsdWJyaWRhdGU6OnRvZGF5KCkKbl90YXhvbnMgPC0gcXVlcnlfd2lraWRhdGEoIlNFTEVDVCAoQ09VTlQoP2l0ZW0pIEFTID9uX3RheG9ucykgV0hFUkUgeyA/aXRlbSB3ZHQ6UDMxIHdkOlExNjUyMS59Iikkbl90YXhvbnMKYGBgCgpBcyBvZiBgciB0b2RheWAsIHRoZXJlIGFyZSBgciBjb21wcmVzcyhuX3RheG9ucylgIGl0ZW1zIG9uIFdpa2lkYXRhIHdoaWNoIGFyZSBpbnN0YW5jZXMgb2YgdGF4b24uIFRoaXMgbWVhbnMgdGhhdCBvdXIgZGF0YXNldCBvZiBgciBjb21wcmVzcyhucm93KHRheG9ucykpYCBpdGVtcyAtLSB0aG9zZSB3aGljaCBoYWQgYXQgbGVhc3Qgb25lIEVuZ2xpc2ggYWxpYXMgb3IgRW5nbGlzaCB0YXhvbiBjb21tb24gbmFtZSAtLSBpcyBhcHByb3hpbWF0ZWx5IGByIHNwcmludGYoIiUuMmYlJSIsIDEwMCAqIG5yb3codGF4b25zKSAvIG5fdGF4b25zKWAgb2YgYWxsIHRheG9uIGl0ZW1zIG9uIFdpa2lkYXRhLgoKQmV5b25kIHRoYXQgaXQgaXMgaGFyZCB0byBzYXkgaG93IG1hbnkgdGF4b25zIGV2ZW4gaGF2ZSBsYWJlbHMuIElmIHdlIHRyeSB1c2UgV2lraWRhdGEgUXVlcnkgU2VydmljZSB0byBjb3VudCBob3cgbWFueSB0YXhvbnMgaGF2ZSBhbiBFbmdsaXNoIGxhYmVsLCB0aGUgZm9sbG93aW5nIFtxdWVyeV0oaHR0cHM6Ly9xdWVyeS53aWtpZGF0YS5vcmcvI1NFTEVDVCUyMCUyOENPVU5UJTI4JTNGaXRlbSUyOSUyMEFTJTIwJTNGbl90YXhvbnMlMjklMjBXSEVSRSUyMCU3QiUwQSUyMCUyMCUzRml0ZW0lMjB3ZHQlM0FQMzElMjB3ZCUzQVExNjUyMS4lMEElMjAlMjAlM0ZpdGVtJTIwcmRmcyUzQWxhYmVsJTIwJTNGaXRlbUxhYmVsLiUwQSUyMCUyMEZJTFRFUiUyOExBTkclMjglM0ZpdGVtTGFiZWwlMjklMjAlM0QlMjAlMjJlbiUyMiUyOS4lMEElN0QpIHRpbWVzIG91dDoKCmBgYFNQQVJRTApTRUxFQ1QgKENPVU5UKD9pdGVtKSBBUyA/bl90YXhvbnMpIFdIRVJFIHsKICA/aXRlbSB3ZHQ6UDMxIHdkOlExNjUyMS4KICA/aXRlbSByZGZzOmxhYmVsID9pdGVtTGFiZWwuCiAgRklMVEVSKExBTkcoP2l0ZW1MYWJlbCkgPSAiZW4iKS4KfQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IGEgZmV3IHRheG9ucyB0byBnZXQgYSBzZW5zZSBvZiB3aGF0IGRhdGEgdGhleSBtYXkgaGF2ZSBhdmFpbGFibGU6CgpgYGB7ciBleGFtcGxlX3RheG9uc30KdGF4b25zICU+JQogIGZpbHRlcihlbnRpdHkgJWluJSBjKCJRMjgzMTkiLCAiUTMxMTIzMCIsICJRMTAwMDI3MCIsICJRNTkzOTI5NDkiLCAiUTEwMzQ4NTkiLCAiUTEwMzUyNDQiLCAiUTI1MzI3IiwgIlEyOTk5NSIsICJRMTAxMDU3MSIpKSAlPiUKICBhcnJhbmdlKGRlc2MoZW50aXR5KSkgJT4lCiAgbXV0YXRlKAogICAgbGFiZWwgPSBpZmVsc2UoaXMubmEoc3Fvb3BfbGFiZWwpLCAi4oCUIiwgc3Fvb3BfbGFiZWwpLAogICAgZGVzY3JpcHRpb24gPSBpZmVsc2UoaXMubmEoc3Fvb3BfZGVzY3JpcHRpb24pLCAi4oCUIiwgc3Fvb3BfZGVzY3JpcHRpb24pLAogICAgaXRlbSA9IHNwcmludGYoIiVzICglcykiLCBsYWJlbCwgd2lraWRhdGFfbGluayhlbnRpdHkpKSwKICAgIGFsaWFzZXMgPSBtYXBfY2hyKHF1ZXJ5X2FsaWFzZXMsIG1heWJlX21ha2VfbGlzdCksCiAgICB0YXhvbl9jb21tb25fbmFtZXMgPSBtYXBfY2hyKHF1ZXJ5X3RheG9uX2NvbW1vbl9uYW1lcywgbWF5YmVfbWFrZV9saXN0KQogICkgJT4lCiAgc2VsZWN0KGl0ZW0sIGRlc2NyaXB0aW9uLCBhbGlhc2VzLCB0YXhvbl9jb21tb25fbmFtZXMpICU+JQogIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBjYXB0aW9uID0gIlRheG9ucyBvbiBXaWtpZGF0YSIsCiAgICAgICAgY29sLm5hbWVzID0gYygiSXRlbSIsICJEZXNjcmlwdGlvbiIsICJBbGlhcyhlcykiLCAiVGF4b24gY29tbW9uIG5hbWUocykiKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIikKYGBgCgpXaGVuIHRoZSBpdGVtcyBhcmUgaW5kZXhlZCwgdGhlIGluZGV4IGluY2x1ZGVzIGxhYmVscyAoaWYgYW55KSwgZGVzY3JpcHRpb25zIChpZiBhbnkpLCBhbGlhc2VzIChpZiBhbnkpLCBhbmQgYW55IHZhbHVlcyBvZiBzdGF0ZW1lbnRzIHdoaWNoIGFyZSBwbGFpbiB0ZXh0IG9yIFEtaXRlbXMuIFNvIGlmIGFuIGl0ZW0gaXMsIHNheSwgYSBwZXJzb24gd2hvIGlzIGFuIGluc3RhbmNlIG9mIChQMzEpIG9mIGh1bWFuIChRNSksIHRoZW4gdGhlaXIgaW5kZXggaW5jbHVkZXMgIlAzMT1RNSIgYnV0ICpub3QqIHRoYXQgdGhleSdyZSBhICJodW1hbiIuIEhvd2V2ZXIsIGlmIHRoZSB2YWx1ZSBpcyBwbGFpbiB0ZXh0IChlLmcuIHRheG9uIGNvbW1vbiBuYW1lKSwgdGhlbiB0aGF0IGdldHMgaW5jbHVkZWQgaW4gdGhlIGluZGV4IGFuZCBjYW4gYmUgc2VhcmNoZWQgZm9yLiBGb3IgZXhhbXBsZSwgV2lraW1lZGlhIEZvdW5kYXRpb24sIEluYy4gKFtRMTgwXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvd2lraS9RMTgwKSkgaGFzIGEgc3RhdGVtZW50IGZvciBwcm9wZXJ0eSAiSVB2NCByb3V0aW5nIHByZWZpeCIgKFtQMzc2MV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUHJvcGVydHk6UDM3NjEpKSBzbyBpZiBvbmUgZG9lcyBhIFtmdWxsLXRleHQgc2VhcmNoIGZvciAiMTk4LjM1LjI2LjAvMjMiXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvdy9pbmRleC5waHA/c2VhcmNoPTE5OC4zNS4yNi4wJTJGMjMmdGl0bGU9U3BlY2lhbCUzQVNlYXJjaCZmdWxsdGV4dD0xKSAodGhlIHZhbHVlIGN1cnJlbnRseSBpbiB0aGF0IHN0YXRlbWVudCksIFdNRiBpcyB0aGUgZmlyc3QgcmVzdWx0IGxpc3RlZC4KClNvIG9uZSB3YXkgdG8gYXNzZXNzIHRoZSBzZWFyY2hhYmlsaXR5IG9mIHRheG9ucyBvbiBXaWtpZGF0YSBpcyB0byBhc3Nlc3MgaG93IG1hbnkgaGF2ZSBsZWJlbHMsIGRlc2NyaXB0aW9ucywgYWxpYXNlcywgYW5kIHRheG9uIGNvbW1vbiBuYW1lcyAoW1AxODQzXShodHRwczovL3d3dy53aWtpZGF0YS5vcmcvd2lraS9Qcm9wZXJ0eTpQMTg0MykpLiBBbW9uZyB0aGUgYHIgcHJldHR5TnVtKG5yb3codGF4b25zKSwgIiwiKWAgY29sbGVjdGVkIHRheG9ucywgd2UgaGF2ZSBmb2xsb3dpbmcgY29tcGxldGVuZXNzIHN0YXRpc3RpY3M6CgpgYGB7ciB0YXhvbl9jb21wbGV0ZW5lc3N9CnRheG9uX2NvbXBsZXRlbmVzcyA8LSB0YXhvbnMgJT4lCiAgc2VsZWN0KGVudGl0eSwgc3Fvb3BfbGFiZWwsIHNxb29wX2Rlc2NyaXB0aW9uLCBxdWVyeV9hbGlhc2VzLCBxdWVyeV90YXhvbl9jb21tb25fbmFtZXMpICU+JQogIHRyYW5zbXV0ZSgKICAgIGl0ZW0gPSBlbnRpdHksCiAgICBgYWxpYXMoZXMpYCA9ICFtYXBfbGdsKHF1ZXJ5X2FsaWFzZXMsIH4gYWxsKGlzLm5hKC54KSkpLAogICAgYHRheG9uIGNvbW1vbiBuYW1lKHMpYCA9ICFtYXBfbGdsKHF1ZXJ5X3RheG9uX2NvbW1vbl9uYW1lcywgfiBhbGwoaXMubmEoLngpKSksCiAgICBsYWJlbCA9ICFpcy5uYShzcW9vcF9sYWJlbCksCiAgICBkZXNjcmlwdGlvbiA9ICFpcy5uYShzcW9vcF9kZXNjcmlwdGlvbiksCiAgKSAlPiUKICBnYXRoZXIoaGFzLCB2YWwsIC1pdGVtKSAlPiUKICBhcnJhbmdlKGl0ZW0sIGhhcywgdmFsKQpgYGAKCmBgYHtyIGNvbXBsZXRlbmVzc19jb21iaW5hdGlvbnN9CnRheG9uX2NvbXBsZXRlbmVzcyAlPiUKICBmaWx0ZXIodmFsKSAlPiUKICBncm91cF9ieShpdGVtKSAlPiUKICBzdW1tYXJpemUobl9oYXMgPSBuKCksIGhhcyA9IHBhc3RlMChoYXMsIGNvbGxhcHNlID0gIiwgIikpICU+JQogIGNvdW50KG5faGFzLCBoYXMpICU+JQogIGFycmFuZ2UoZGVzYyhuKSkgJT4lCiAgbXV0YXRlKHByb3AgPSBzcHJpbnRmKCIlLjNmJSUiLCAxMDAgKiBuIC8gc3VtKG4pKSkgJT4lCiAga2FibGUoZXNjYXBlID0gRkFMU0UsIGNhcHRpb24gPSAiRW5nbGlzaCBpbmZvIGNvbXBsZXRlbmVzcyBvZiB0YXhvbnMgb24gV2lraWRhdGEiLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkZpZWxkcyBhdmFpbGFibGUgZm9yIGEgdGF4b24iLCAiSW5mbyBhdmFpbGFibGUgKGluIEVuZ2xpc2gpIiwgIkl0ZW1zIGluIGRhdGFzZXQiLCAiUHJvcG9ydGlvbiBvZiBkYXRhc2V0IikpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpCmBgYAoKQW5kLCBjb252ZXJzZWx5LCB0aGUgZm9sbG93aW5nIG1pc3NpbmduZXNzIHN0YXRpc3RpY3M6CgpgYGB7ciBtaXNzaW5nbmVzc19jb21iaW5hdGlvbnN9CnRheG9uX2NvbXBsZXRlbmVzcyAlPiUKICBmaWx0ZXIoIXZhbCkgJT4lCiAgZ3JvdXBfYnkoaXRlbSkgJT4lCiAgc3VtbWFyaXplKG5faGFzID0gbigpLCBoYXMgPSBwYXN0ZTAoaGFzLCBjb2xsYXBzZSA9ICIsICIpKSAlPiUKICBjb3VudChuX2hhcywgaGFzKSAlPiUKICBhcnJhbmdlKGRlc2MobikpICU+JQogIG11dGF0ZShwcm9wID0gc3ByaW50ZigiJS4zZiUlIiwgMTAwICogbiAvIHN1bShuKSkpICU+JQogIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBjYXB0aW9uID0gIkVuZ2xpc2ggaW5mbyBtaXNzaW5nbmVzcyBvZiB0YXhvbnMgb24gV2lraWRhdGEiLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkZpZWxkcyBOT1QgYXZhaWxhYmxlIGZvciBhIHRheG9uIiwgIkluZm8gTk9UIGF2YWlsYWJsZSAoaW4gRW5nbGlzaCkiLCAiSXRlbXMgaW4gZGF0YXNldCIsICJQcm9wb3J0aW9uIG9mIGRhdGFzZXQiKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIikKYGBgCgpUaG9zZSBhcmUgKmNvbWJpbmF0aW9ucyogb2YgbWlzc2luZyBmaWVsZHMuIFRoZSBmb2xsb3dpbmcgYXJlIHBlci1maWVsZCBjb21wbGV0ZW5lc3MgJiBtaXNzaW5nbmVzcyBzdGF0aXN0aWNzOgoKYGBge3IgaW5mb19jb21wbGV0ZW5lc3N9CmluZm9fY29tcGxldGVuZXNzIDwtIHRheG9uX2NvbXBsZXRlbmVzcyAlPiUKICBncm91cF9ieShoYXMpICU+JQogIHN1bW1hcml6ZShuID0gc3VtKHZhbCksIHRvdGFsID0gbigpKQppbmZvX2NvbXBsZXRlbmVzc19uIDwtIHNldF9uYW1lcyhpbmZvX2NvbXBsZXRlbmVzcyRuLCBpbmZvX2NvbXBsZXRlbmVzcyRoYXMpCmluZm9fY29tcGxldGVuZXNzICU+JQogIHRyYW5zbXV0ZShoYXMgPSBoYXMsCiAgICAgICAgICAgIHByb3AxID0gc3ByaW50ZigiJXMgKCUuM2YlJSkiLCBjb21wcmVzcyhuKSwgMTAwICogbiAvIHRvdGFsKSwKICAgICAgICAgICAgcHJvcDIgPSBzcHJpbnRmKCIlcyAoJS4zZiUlKSIsIGNvbXByZXNzKHRvdGFsIC0gbiksIDEwMCAqICh0b3RhbCAtIG4pIC8gdG90YWwpKSAlPiUKICBrYWJsZShjb2wubmFtZXMgPSBjKCJJbmZvcm1hdGlvbiBhIHRheG9uIGl0ZW0gbWF5IGhhdmUiLCAiSG93IG1hbnkgaGF2ZSBhIHZhbHVlIChpbiBFbmdsaXNoKSIsICJIb3cgbWFueSBkbyBOT1QgaGF2ZSBhIHZhbHVlIChpbiBFbmdsaXNoKSIpLAogICAgICAgIGNhcHRpb24gPSBzcHJpbnRmKCJFbmdsaXNoIGNvbXBsZXRlbmVzcyBvZiBpdGVtcyBhbW9uZyBhIHN1YnNldCBvZiAlcyB0YXhvbnMiLCBjb21wcmVzcyhucm93KHRheG9ucykpKSwKICAgICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpCmBgYAoKQ29uc2lkZXJpbmcgdGhhdCB0aGUgY29sbGVjdGVkIGRhdGFzZXQgKm9ubHkqIGluY2x1ZGVkIHRheG9ucyB3aGljaCBoYWQgYXQgbGVhc3Qgb25lIGFsaWFzIG9yIGF0IGxlYXN0IG9uZSB0YXhvbiBjb21tb24gbmFtZSwgdGhlIG1pc3NpbmduZXNzIG9mIHRob3NlIHR3byBpdGVtcyAtLSBib3RoIG9mIHdoaWNoIGFpZCBhIGxvdCBpbiBzZWFyY2ggLS0gaXMgcmF0aGVyIGNvbmNlcm5pbmcuCgpBZ2FpbiwgdGhlc2UgbnVtYmVycyBhcmUgKm5vdCogcmVwcmVzZW50YXRpdmUgb2YgYWxsIGByIGNvbXByZXNzKG5fdGF4b25zKWAgdGF4b25zLiBBcyBhIHJlbWluZGVyLCB0aGUgZGF0YXNldCBvZiBgciBjb21wcmVzcyhucm93KHRheG9ucykpYCB0YXhvbnMgc3R1ZGllZCB3YXMgbGltaXRlZCB0byB0aG9zZSB3aGljaCBoYWQgKDEpIGF0IGxlYXN0IG9uZSBFbmdsaXNoIGFsaWFzIG9yICgyKSBhdCBsZWFzdCBvbmUgRW5nbGlzaCB0YXhvbiBjb21tb24gbmFtZS4gSXQncyBub3QgY2xlYXIgaG93IG1hbnkgdGF4b25zIG9uIFdpa2lkYXRhIGRvIGhhdmUgYSBkZXNjcmlwdGlvbiBvciBhIGxhYmVsLCBidXQgd2UgY2FuIGF0IGxlYXN0IHNheSB0aGF0IG9mIGByIGNvbXByZXNzKG5fdGF4b25zKWAsIG9ubHkgYHIgY29tcHJlc3MoIGluZm9fY29tcGxldGVuZXNzX25bImFsaWFzKGVzKSJdKWAgKGByIHNwcmludGYoIiUuM2YlJSIsIDEwMCAqIGluZm9fY29tcGxldGVuZXNzX25bImFsaWFzKGVzKSJdIC8gbl90YXhvbnMpYCkgdGF4b25zIGhhdmUgYXQgbGVhc3Qgb25lIGFsaWFzIGluIEVuZ2xpc2ggYW5kIG9ubHkgYHIgY29tcHJlc3MoaW5mb19jb21wbGV0ZW5lc3NfblsidGF4b24gY29tbW9uIG5hbWUocykiXSlgIChgciBzcHJpbnRmKCIlLjNmJSUiLCAxMDAgKiBpbmZvX2NvbXBsZXRlbmVzc19uWyJ0YXhvbiBjb21tb24gbmFtZShzKSJdIC8gbl90YXhvbnMpYCkgdGF4b25zIGhhdmUgYXQgbGVhc3Qgb25lIHRheG9uIGNvbW1vbiBuYW1lIGluIEVuZ2xpc2guCgoqKk5vdGUqKjogQSBmb2xsb3ctdXAgb2YgdGhpcyB3b3JrIHNob3VsZCBpbmNsdWRlIGFsbCBsYW5ndWFnZXMuIFdlIHJlc3RyaWN0ZWQgdGhpcyBpbml0aWFsIGV4cGxvcmF0aW9uIHRvIEVuZ2xpc2ggYXMgdGhhdCBpcyB0aGUgYW5hbHlzdCdzIHByaW1hcnkgbGFuZ3VhZ2UuCgojIyBHcm91cHMgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lCgpBbiBpdGVtIG1heSBhbHNvIGJlIGFuIGluc3RhbmNlIG9mICJncm91cHMgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lIiAoW1E1NTk4MzcxNV0oaHR0cHM6Ly93d3cud2lraWRhdGEub3JnL3dpa2kvUTU1OTgzNzE1KSkuIFRoZSBmb2xsb3dpbmcgYXJlIHNvbWUgZXhhbXBsZXMgb2Ygc3VjaCBpdGVtczoKCmBgYHtyIGV4YW1wbGVfY29tbW9uc30KY29tbW9ucyAlPiUKICBmaWx0ZXIoaXRlbSAlaW4lIGMoIlE1IiwgIlExMTk0NjIwMiIsICJRMzQ2OTU5MiIsICJRMTEwNjUwMzYiLCAiUTE3MTI4NzU3IikpICU+JQogIGFycmFuZ2UoZGVzYyhpdGVtKSkgJT4lCiAgbXV0YXRlKAogICAgaXRlbSA9IHNwcmludGYoIiVzICglcykiLCBsYWJlbCwgd2lraWRhdGFfbGluayhpdGVtKSksCiAgICBkZXNjcmlwdGlvbiA9IGlmZWxzZShpcy5uYShkZXNjcmlwdGlvbiksICLigJQiLCBkZXNjcmlwdGlvbiksCiAgICBhbGlhc2VzID0gbWFwKGFsaWFzZXMsIG1heWJlX21ha2VfbGlzdCksCiAgICBncm91cHMgPSBtYXAoZ3JvdXBzLCBtYWtlX21pbmlfdGFibGUpLAogICAgZGlmZmVyZW50X2Zyb20gPSBtYXAoZGlmZmVyZW50X2Zyb20sIG1ha2VfbWluaV90YWJsZSkKICApICU+JQogIHNlbGVjdCgtbGFiZWwpICU+JQogIGthYmxlKGVzY2FwZSA9IEZBTFNFLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkl0ZW0iLCAiRGVzY3JpcHRpb24iLCAiQWxpYXMoZXMpIiwKICAgICAgICAgICAgICAgICAgICAgICJHcm91cChzKSAoSXRlbSwgTGFiZWwpIiwgIkRpZmZlcmVudCBGcm9tIChJdGVtLCBMYWJlbCkiKSwKICAgICAgICBjYXB0aW9uID0gIldpa2lkYXRhIGVudGl0aWVzIHRoYXQgYXJlIGluc3RhbmNlcyBvZiAnZ3JvdXBzIG9mIG9yZ2FuaXNtcyBrbm93biBieSBvbmUgcGFydGljdWxhciBjb21tb24gbmFtZSciKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBUUlVFKSAlPiUKICBjb2x1bW5fc3BlYygxLCB3aWR0aCA9ICIxMDBweCIpICU+JQogIGNvbHVtbl9zcGVjKDIsIHdpZHRoID0gIjE1MHB4IikKYGBgCgpTaW1pbGFyIHRoZSB3b3JrIG9uIHRheG9ucywgd2UgY2FuIGNhbGN1bGF0ZSBzb21lIGNvbXBsZXRlbmVzcyBzdGF0aXN0aWNzIG9uIHRoZXNlIGl0ZW1zLiBBbHRob3VnaCB1bmxpa2UgdGhlIGNhc2Ugd2l0aCB0YXhvbnMsIHRoZXNlIChFbmdsaXNoLWZvY3VzZWQpIHN0YXRpc3RpY3MgYXBwbHkgdG8gKmFsbCogaW5zdGFuY2VzIGZvdW5kIG9uIFdpa2lkYXRhLgoKYGBge3J9CmNvbW1vbnMgJT4lCiAgdHJhbnNtdXRlKAogICAgbGFiZWwgPSAhaXMubmEobGFiZWwpLAogICAgZGVzY3JpcHRpb24gPSAhaXMubmEoZGVzY3JpcHRpb24pLAogICAgYGFsaWFzKGVzKWAgPSBtYXBfaW50KGFsaWFzZXMsIH4gc3VtKCFpcy5uYSgueCkpKSA+IDAsCiAgICBgYXQgbGVhc3Qgb25lICJvZiIgcXVhbGlmaWVyYCA9IG1hcF9pbnQoZ3JvdXBzLCB+IHN1bSghaXMubmEoLnhbWzFdXSRpdGVtKSkpID4gMCwKICAgIGBhdCBsZWFzdCBvbmUgImRpZmZlcmVudCBmcm9tIiBzdGF0ZW1lbnRgID0gbWFwX2ludChkaWZmZXJlbnRfZnJvbSwgfiBzdW0oIWlzLm5hKC54W1sxXV0kaXRlbSkpKSA+IDAKICApICU+JQogIGdhdGhlcihoYXMsIHZhbCkgJT4lCiAgbXV0YXRlKGhhcyA9IGZhY3RvcihoYXMsIGMoImxhYmVsIiwgImRlc2NyaXB0aW9uIiwgImFsaWFzKGVzKSIsICJhdCBsZWFzdCBvbmUgXCJvZlwiIHF1YWxpZmllciIsICJhdCBsZWFzdCBvbmUgXCJkaWZmZXJlbnQgZnJvbVwiIHN0YXRlbWVudCIpKSkgJT4lCiAgZ3JvdXBfYnkoaGFzKSAlPiUKICBzdW1tYXJpemUocHJvcCA9IHNwcmludGYoIiUuMWYlJSIsIDEwMCAqIG1lYW4odmFsKSkpICU+JQogIGthYmxlKGNvbC5uYW1lcyA9IGMoIkluZm9ybWF0aW9uIGFuIGl0ZW0gbWF5IGhhdmUiLCAiSG93IG1hbnkgaGF2ZSBhIHZhbHVlIChpbiBFbmdsaXNoKSIpLAogICAgICAgIGNhcHRpb24gPSAiRW5nbGlzaCBjb21wbGV0ZW5lc3Mgb2YgaXRlbXMgd2hpY2ggYXJlIGluc3RhbmNlcyBvZiAnZ3JvdXAgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lJyIsCiAgICAgICAgYWxpZ24gPSBjKCJsIiwgInIiKSkgJT4lCiAga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JQogIGdyb3VwX3Jvd3MoaW5kZXggPSBjKCJJbmRleGVkIGZvciBzZWFyY2ggYXMgdGV4dCIgPSAzLCAiSW5kZXhlZCBmb3Igc2VhcmNoIGFzIHN0YXRlbWVudF9rZXl3b3JkcyIgPSAyKSkKYGBgCgpBcyBiZWZvcmUsIHdlIGNhbiBhbHNvIGxvb2sgYXQgY29tYmluYXRpb25zIG9mIG1pc3NpbmcgZmllbGRzIHRvIGRldGVybWluZSBob3cgbWFueSBpdGVtcyB3b3VsZCBiZSBlYXNpbHkgZm91bmQgYnkgc2VhcmNoaW5nIChlLmcuIGFuIGl0ZW0gd2hpY2ggaGFzIGEgbGFiZWwsIGEgZGVzY3JpcHRpb24sIGFuZCBhbiBhbGlhcyB3b3VsZCBiZSBtb3JlIGxpa2VseSB0byBiZSBmb3VuZCBieSBzb21lb25lIGxvb2tpbmcgZm9yIGl0IHRoYW4gYW4gaXRlbSB3aXRoIG9ubHksIHNheSwgYSBsYWJlbCk6CgpgYGB7cn0KY29tbW9uc19jb21wbGV0ZW5lc3MgPC0gY29tbW9ucyAlPiUKICB0cmFuc211dGUoCiAgICBpdGVtID0gaXRlbSwKICAgIGxhYmVsID0gIWlzLm5hKGxhYmVsKSwKICAgIGRlc2NyaXB0aW9uID0gIWlzLm5hKGRlc2NyaXB0aW9uKSwKICAgIGBhbGlhcyhlcylgID0gbWFwX2ludChhbGlhc2VzLCB+IHN1bSghaXMubmEoLngpKSkgPiAwLAogICAgYGF0IGxlYXN0IG9uZSAib2YiIHF1YWxpZmllcmAgPSBtYXBfaW50KGdyb3VwcywgfiBzdW0oIWlzLm5hKC54W1sxXV0kaXRlbSkpKSA+IDAsCiAgICBgYXQgbGVhc3Qgb25lICJkaWZmZXJlbnQgZnJvbSIgc3RhdGVtZW50YCA9IG1hcF9pbnQoZGlmZmVyZW50X2Zyb20sIH4gc3VtKCFpcy5uYSgueFtbMV1dJGl0ZW0pKSkgPiAwCiAgKSAlPiUKICBnYXRoZXIoaGFzLCB2YWwsIC1pdGVtKSAlPiUKICBtdXRhdGUoaGFzID0gZmFjdG9yKGhhcywgYygibGFiZWwiLCAiZGVzY3JpcHRpb24iLCAiYWxpYXMoZXMpIiwgImF0IGxlYXN0IG9uZSBcIm9mXCIgcXVhbGlmaWVyIiwgImF0IGxlYXN0IG9uZSBcImRpZmZlcmVudCBmcm9tXCIgc3RhdGVtZW50IikpKSAlPiUKICBmaWx0ZXIodmFsKSAlPiUKICBncm91cF9ieShpdGVtKSAlPiUKICBzdW1tYXJpemUobl9oYXMgPSBuKCksIGhhcyA9IHBhc3RlMChoYXMsIGNvbGxhcHNlID0gIiwgIikpICU+JQogIGNvdW50KG5faGFzLCBoYXMpICU+JQogIGFycmFuZ2UoZGVzYyhuX2hhcyksIGRlc2MobikpICU+JQogIG11dGF0ZShwcm9wID0gc3ByaW50ZigiJS4yZiUlIiwgMTAwICogbiAvIHN1bShuKSkpCmNvbW1vbnNfY29tcGxldGVuZXNzICU+JQogIGRwbHlyOjpzZWxlY3QoLW5faGFzKSAlPiUKICBrYWJsZShlc2NhcGUgPSBGQUxTRSwgY2FwdGlvbiA9ICJFbmdsaXNoIGluZm9ybWF0aW9uIGNvbXBsZXRlbmVzcyBvZiAnZ3JvdXAgb2Ygb3JnYW5pc21zIGtub3duIGJ5IG9uZSBwYXJ0aWN1bGFyIGNvbW1vbiBuYW1lJyBpbnN0YW5jZXMgb24gV2lraWRhdGEiLAogICAgICAgIGNvbC5uYW1lcyA9IGMoIkZpZWxkcyBvZiBpbmZvcm1hdGlvbiBhdmFpbGFibGUgKGluIEVuZ2xpc2gpIiwgIkl0ZW1zIG9uIFdpa2lkYXRhIiwgIlByb3BvcnRpb24gYW1vbmcgYWxsIHN1Y2ggaW5zdGFuY2VzIikpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIpICU+JQogIGdyb3VwX3Jvd3MoaW5kZXggPSBhdXRvX2luZGV4KGNvbW1vbnNfY29tcGxldGVuZXNzJG5faGFzKSwgZ3JvdXBfbGFiZWwgPSAiRmllbGRzIGF2YWlsYWJsZSBmb3IgYW4gaXRlbSIpCmBgYAo=