knitr::opts_chunk$set(
    echo = TRUE,
    message = FALSE,
    warning = FALSE
)

Spracované a inšpirované Notebookom Jason Locklin: Introduction to R for Education Data Analysis and Visualization

Práca s údajmi

Tradičná práca s databázou

Pre prácu s údajmi (databázou) používame najčastejšie dátový typ .data.frame.. Je to tabuľka, ktorá pozostáva zo stĺpcov rozličných typov. Jeden riadok pritom predstavuje jeden záznam databázy.

Príklad

Majme údaje o žiakoch, ktoré predstavujú tri premenné - Meno, Vek a Body:

# Working with data frames

  Meno = c("Jana", "Jozef", "Mária")
  Vek = c(10, 11, 9)
  Body = c(85, 92, 78)

Tieto tri premenné nie sú zatiaľ nijako prepojené, predstavujú izolované stĺpce tabuľky. Do tabuľky ich spojíme nasledovne

udaje <- data.frame(Meno,Vek,Body)
print(udaje)

Vysvetlenie: DataFrame má tri stĺpce: Meno, Vek a Body. Niektoré operácie s údajmi organizovanými v .data.frame. sú uvedené nasledovne

print(udaje$Vek)                 # takto adresujeme jednotlivé premenné v data.frame
[1] 10 11  9
print(mean(udaje$Vek))           # priemerny vek
[1] 10
print(udaje[Meno=="Jozef",])     # adresovanie celého riadku
print(udaje[3,])                 # ina moznost adresovania celeho riadku
print(udaje[,2:3])               # vypisanie druheho a tretieho stlpca tabulky
print(udaje[1,1])                # vypisanie jednej bunky tabulky
[1] "Jana"
summary(udaje)                   # zakladna deskriptivna statistika celej tabulky
     Meno                Vek            Body     
 Length:3           Min.   : 9.0   Min.   :78.0  
 Class :character   1st Qu.: 9.5   1st Qu.:81.5  
 Mode  :character   Median :10.0   Median :85.0  
                    Mean   :10.0   Mean   :85.0  
                    3rd Qu.:10.5   3rd Qu.:88.5  
                    Max.   :11.0   Max.   :92.0  

Ak chceme pridať k tabuľke dodatočný stĺpec, potom to robíme nasledovne

MaAuto <- c(TRUE,FALSE,TRUE)
udaje <- cbind(udaje,MaAuto)
print(udaje)

Ak chceme pridať riadok, potom

# New record (must match column order/types)
novy.riadok <- data.frame(Meno = "Diana", Vek = 22.485, Body = 42,MaAuto = FALSE)

# Append
udaje <- rbind(udaje, novy.riadok)
print(udaje)

Tabuľky v prostredí kableextra

library(knitr)
library(kableExtra)
kable(
  udaje,
#  format,
digits = 2,
#  row.names = NA,
#  col.names = NA,
  align=c("l","c","l","r"),
  caption = "Toto je tabuľka"
#  label = NULL,
#  format.args = list(),
#  escape = TRUE,
 # ...
) %>%
      kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center")
Toto je tabuľka
Meno Vek Body MaAuto
Jana 10 85 TRUE
Jozef 11 92 FALSE
Mária 9 78 TRUE
NA
NA
NA
NA
NA

Tidyverse - moderná práca s údajmi

Tidyverse je súbor knižníc, ktoré majú zjednodušiť prácu s údajmi. Majú jednotný komunikačný štandard, vzájomne sa doplňujú.

# Load tidyverse
library(tidyverse)

dplyr - pre manipuláciu s údajmi

.dplyr. poskytuje základné možnosti manipulácie s údajmi, ako napr.:

  1. filter(): vyberá riadky

  2. select(): vyberá stĺpce

  3. mutate(): vytvára nové stĺpce tabuľky

  4. arrange(): triedi riadky

  5. summarise(): sumarizuje

V nasledovnej ukážke využijeme tzv. .pipes. %>% alebo %<% umožňuje posielať výsledky z jednej funkcie priamo do volanie nasledovnej funkcie. To umožňuje ľahšiu čitateľnosť kódov, konvencia sa ujala a má široké použitie.

Výber a triedenie

# výber a následné triedenie
udaje %>%
  filter(Body > 50) %>%     # vybera zaznamy s poctom bodov viac, ako 50
  arrange(desc(Body)) %>%     # vysledny subor triedi zostupne podla premennej Body
kable %>%
    kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center"
  )
Meno Vek Body MaAuto
Jozef 11 92 FALSE
Jana 10 85 TRUE
Mária 9 78 TRUE

Zoskupenie a sumarizácia

# Zoskupí and sumarizuje
udaje %>%
  group_by(MaAuto) %>%      # zoskupi zaznamy podla premennej MaAuto a vypocita za kazdu skupinu jej priemer Body
  summarise(                # a taktiez spocita pocetnosti oboch skupin
    Priem.Body = mean(Body),
    count = n()
  ) %>%
 kable(
    caption = "Priemerné Body podľa premennej MaAuto",
    col.names = c("Má Auto", "Priemer Body", "Počet"),
    align = "c"
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center"
  )
Priemerné Body podľa premennej MaAuto
Má Auto Priemer Body Počet
FALSE 67.0 2
TRUE 81.5 2

Vytváranie novej premennej

# Vytváranie novej premennej
udaje %>%
  mutate(
    grade = case_when(     # vytvara novu premennu grade podla nasledovnej relacnej schemy
      Body >= 90 ~ "A",
      Body >= 80 ~ "B",
      Body >= 70 ~ "C",
      TRUE ~ "D"
    ),
    VekPoPlnoletosti = round(Vek-18,0)
  ) %>% 
  kable %>%
   kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center"
  ) 
Meno Vek Body MaAuto grade VekPoPlnoletosti
Jana 10.000 85 TRUE B -8
Jozef 11.000 92 FALSE A -7
Mária 9.000 78 TRUE C -9
Diana 22.485 42 FALSE D 4

Import údajov z otv. databáz

  1. Mendeley Data Tuto sa dostaneme z Mendeley Data, kde si údaje viete voľne stiahnúť. Údaje sa vzťahujú k už publikovaným článkom vo vydavateľstve Elsevier. Výber sa dá urobiť jednoducho zadaním kľúčových slov.
  2. Kaggle Data Tuto sa dostaneme z Kaggle Datasets, kde si údaje viete voľne stiahnúť. Údaje sa vzťahujú k projektom podporovaným Kaggle. Výber sa dá urobiť jednoducho zadaním kľúčových slov.
  3. Databázy knižníc R - .library(datasets). alebo .library(wooldridge). ale aj iné - stačí si dať príkaz data()
library(datasets)
# datasets available in the 'datasets' package - nasledovne kody za mna urobil Chat GPT
ds <- as.data.frame(utils::data(package = "datasets")$results)[, c("Item","Title")]
knitr::kable(head(ds, 20), col.names = c("Dataset", "Title"))   # prvych 20 databaz
Dataset Title
AirPassengers Monthly Airline Passenger Numbers 1949-1960
BJsales Sales Data with Leading Indicator
BJsales.lead (BJsales) Sales Data with Leading Indicator
BOD Biochemical Oxygen Demand
CO2 Carbon Dioxide Uptake in Grass Plants
ChickWeight Weight versus age of chicks on different diets
DNase Elisa assay of DNase
EuStockMarkets Daily Closing Prices of Major European Stock Indices, 1991-1998
Formaldehyde Determination of Formaldehyde
HairEyeColor Hair and Eye Color of Statistics Students
Harman23.cor Harman Example 2.3
Harman74.cor Harman Example 7.4
Indometh Pharmacokinetics of Indomethacin
InsectSprays Effectiveness of Insect Sprays
JohnsonJohnson Quarterly Earnings per Johnson & Johnson Share
LakeHuron Level of Lake Huron 1875-1972
LifeCycleSavings Intercountry Life-Cycle Savings Data
Loblolly Growth of Loblolly Pine Trees
Nile Flow of the River Nile
Orange Growth of Orange Trees
# kniznica datasets obsahuje databazu nazvanu CO2. Mozeme sa na nu odvolavat nasledovne, ako napr. 
head(CO2)

Môžeme použiť aj databázu určenú pre ekobometriu - package Wooldridge

# install.packages("wooldridge")
library(wooldridge)
ds <- as.data.frame(utils::data(package = "wooldridge")$results)[, c("Item","Title")]
knitr::kable(head(ds, 20), col.names = c("Dataset", "Title")) %>%
    kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center"
  )
Dataset Title
admnrev admnrev
affairs affairs
airfare airfare
alcohol alcohol
apple apple
approval approval
athlet1 athlet1
athlet2 athlet2
attend attend
audit audit
barium barium
beauty beauty
benefits benefits
beveridge beveridge
big9salary big9salary
bwght bwght
bwght2 bwght2
campus campus
card card
catholic catholic

Import údajov z .csv alebo .xls

Ja som si zvolil údaje z [Abosede Tiamiyu: Environmental, Social, and Governance Reporting Evidencing Firm Performance in Emerging Economy]{https://data.mendeley.com/datasets/7k8pjhsrwb/1}. Na stránke sa nachádza súbor .Dataset ESG and Firm Performance.xlsx., ktorý som si stiahol a exportoval do formátu csv. Ako oddeľovač položiek som si zvolil bodkočiarku (semicolon ;), vyžívam desatinnú bodku a nie čiarku a tiež textové premenné uvádzam apostrofmi “. V prvom riadku sa nachádzajú názvy stĺpcov, ktoré neskôr budú vystupovať ako premenné. Tie obsahujú medzery, čo je v zázve premennej neprípustné a nahradil som ich podtrhovátkom”.”.

Náhľad na xls databázu otvorenú v tabuľkovom procesore
Náhľad na xls databázu otvorenú v tabuľkovom procesore
Náhľad na csv databázu otvorenú v textovom procesore
Náhľad na csv databázu otvorenú v textovom procesore

Potom už stačí importovať údaje do .data.frame., a to nasledovne

udaje <- read.csv2("udaje/HealthRiskData_Kunova.csv",header=TRUE,sep=";",dec=".")
head(udaje)                                             # niekolko prvych riadkov
colnames(udaje)                                         # nazvy premennych
 [1] "BirthAge"       "Height"         "Gender"         "Weight"         "BodyFat"       
 [6] "VisceralFat"    "SkeletonMuscle" "BodyAge"        "RM"             "BMI"           
[11] "SystolicBP"     "DiastolicBP"    "Pulse"          "SugarF"         "SugarPP"       
[16] "Waist"          "X"             

Grafy

ggplot2 - knižnica pre grafy

Výber a následné triedenie

library(dplyr)

udaje.2013 <- udaje %>%
  filter(YEARS == 2013) %>%
  select(RETURN.ON.ASSETS, ESG.INDEX, DEBT.TO.ASSET, FIRM.SIZE)

Knižnica .ggplot2. je v súčasnosti najčastejšie používaná grafická knižnica, pričom predpripravené kódy k jednotlivým obrázkom si viete nájsť v R Graph Gallery. Tu si uvedieme jednoduchšie z nich.

Scatter plot

# Basic scatter plot
library(ggplot2)
ggplot(udaje.2013, aes(x = FIRM.SIZE, y = ESG.INDEX)) +            # specifikacia osi
  geom_point() +                                                   # typ grafu - scatterplot
  theme_minimal() +
  labs(title = "ESG index", x = "Veľkosť firmy", y = "Score")      # oznacenie osi

Boxplot

# Bar plot with grouping
library(ggplot2)

library(ggplot2)

ggplot(udaje, aes(x = factor(YEARS), y = ESG.INDEX)) +        # specifikacia osi
  geom_boxplot(fill = "lightblue", color = "darkblue") +      # typ grafu - boxplot
  labs(                                                       # oznacenie osi, nazov grafu
    title = "ESG Index by Years",
    x = "Year",
    y = "ESG Index"
  ) +
  theme_minimal()

Základné štatistiky.

knitr - tabuľka

library(dplyr)
library(knitr)

# Summarise basic statistics
esg.stats <- udaje %>%
  filter(YEARS %in% 2013:2016) %>%
  group_by(YEARS) %>%
  summarise(
    n     = n(),
    mean  = mean(ESG.INDEX, na.rm = TRUE),
    sd    = sd(ESG.INDEX, na.rm = TRUE),
    min   = min(ESG.INDEX, na.rm = TRUE),
    q25   = quantile(ESG.INDEX, 0.25, na.rm = TRUE),
    median= median(ESG.INDEX, na.rm = TRUE),
    q75   = quantile(ESG.INDEX, 0.75, na.rm = TRUE),
    max   = max(ESG.INDEX, na.rm = TRUE),
    .groups = "drop"
  )

# Create knitr table
kable(esg.stats, digits = 2, caption = "Basic statistics of ESG Index (2013–2016)")

alebo krajšie tabuľky s pomocou .kableExtra.:

library(dplyr)
library(knitr)
library(kableExtra)

# Summarise basic statistics
esg.stats <- udaje %>%
  filter(YEARS %in% 2013:2016) %>%
  group_by(YEARS) %>%
  summarise(
    n      = n(),
    mean   = mean(ESG.INDEX, na.rm = TRUE),
    sd     = sd(ESG.INDEX, na.rm = TRUE),
    min    = min(ESG.INDEX, na.rm = TRUE),
    q25    = quantile(ESG.INDEX, 0.25, na.rm = TRUE),
    median = median(ESG.INDEX, na.rm = TRUE),
    q75    = quantile(ESG.INDEX, 0.75, na.rm = TRUE),
    max    = max(ESG.INDEX, na.rm = TRUE),
    .groups = "drop"
  )

# Create styled kableExtra table
esg.stats %>%
  kable(digits = 2, caption = "Basic statistics of ESG Index (2013–2016)") %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed")) %>%
  column_spec(1, bold = TRUE) %>%          # make years bold
  row_spec(0, bold = TRUE, background = "#f2f2f2") %>%  # style header row
  add_header_above(c(" " = 2, "ESG Index Statistics" = 7))

t-test: Porovnanie priemeru ESG indexu v rokoch 2013 a 2015

t.test.result <- t.test(
  udaje$ESG.INDEX[udaje$YEARS == 2013],
  udaje$ESG.INDEX[udaje$YEARS == 2015]
)

print(t.test.result)

ANOVA: Comparing Reading Scores Across Programs

anova.result <- aov(ESG.INDEX ~ YEARS, data = udaje)
summary(anova.result)

Linear Regression: Predicting Math Scores

model <- lm(ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET, data = udaje.2013)
summary(model)
# install.packages(c("broom", "kableExtra", "dplyr", "stringr"))
library(broom)
library(dplyr)
library(kableExtra)
library(stringr)

# Your model (already fitted)
# model <- lm(ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET, data = udaje.2013)

coef.tbl <- tidy(model, conf.int = TRUE) %>%
  mutate(
    term = recode(term,
      "(Intercept)" = "Intercept",
      "RETURN.ON.ASSETS" = "Return on Assets",
      "FIRM.SIZE" = "Firm Size",
      "DEBT.TO.ASSET" = "Debt to Asset"
    ),
    stars = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      p.value < 0.1   ~ "·",
      TRUE            ~ ""
    )
  ) %>%
  transmute(
    Term = term,
    Estimate = estimate,
    `Std. Error` = std.error,
    `t value` = statistic,
    `p value` = p.value,
    `95% CI` = str_c("[", round(conf.low, 3), ", ", round(conf.high, 3), "]"),
    Sig = stars
  )

coef.tbl %>%
  kable(
    digits = 3,
    caption = "OLS Regression Coefficients (ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET)"
  ) %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed")) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#f2f2f2") %>%
  footnote(
    general = "Signif. codes: *** p<0.001, ** p<0.01, * p<0.05, · p<0.1.",
    threeparttable = TRUE
  )
fit.tbl <- glance(model) %>%
  transmute(
    `R-squared` = r.squared,
    `Adj. R-squared` = adj.r.squared,
    `F-statistic` = statistic,
    `F p-value` = p.value,
    `AIC` = AIC,
    `BIC` = BIC,
    `Num. obs.` = nobs
  )

fit.tbl %>%
  kable(digits = 3, caption = "Model Fit Statistics") %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("condensed"))

Info zdroje pre ďalšie štúdium

R Project

Posit

Community Resources

LS0tCnRpdGxlOiAiUHLDoWNhIHMgZGF0YWLDoXpvdSIKYXV0aG9yOiAiTmF0w6FsaWEgS3Vub3bDoSIKZGF0ZTogIk1hcmVjIDIwMjYiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0aGVtZTogdW5pdGVkCiAgICBoaWdobGlnaHQ6IHRhbmdvCmVkaXRvcl9vcHRpb25zOiAKICBtYXJrZG93bjogCiAgICB3cmFwOiA3MgotLS0KCmBgYHtyfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgICBlY2hvID0gVFJVRSwKICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgIHdhcm5pbmcgPSBGQUxTRQopCmBgYAoKU3ByYWNvdmFuw6kgYSBpbsWhcGlyb3ZhbsOpIE5vdGVib29rb20KW0phc29uIExvY2tsaW46IEludHJvZHVjdGlvbiB0byBSIGZvciBFZHVjYXRpb24gRGF0YSBBbmFseXNpcyBhbmQgVmlzdWFsaXphdGlvbl0oaHR0cDovL3JwdWJzLmNvbS9qYXNvbkwvTGFuZEwpe3RhcmdldD0iX2JsYW5rIiByZWw9Im5vb3BlbmVyIn0KCgojIFByw6FjYSBzIMO6ZGFqbWkKCiMjIFRyYWRpxI1uw6EgcHLDoWNhIHMgZGF0YWLDoXpvdQoKUHJlIHByw6FjdSBzIMO6ZGFqbWkgKGRhdGFiw6F6b3UpIHBvdcW+w612YW1lIG5hasSNYXN0ZWrFoWllIGTDoXRvdsO9IHR5cCAuZGF0YS5mcmFtZS4uIEplIHRvIHRhYnXEvmthLCBrdG9yw6EgcG96b3N0w6F2YSB6byBzdMS6cGNvdiByb3psacSNbsO9Y2ggdHlwb3YuIEplZGVuIHJpYWRvayBwcml0b20gcHJlZHN0YXZ1amUgamVkZW4gesOhem5hbSBkYXRhYsOhenkuCgojIyMgUHLDrWtsYWQKCk1ham1lIMO6ZGFqZSBvIMW+aWFrb2NoLCBrdG9yw6kgcHJlZHN0YXZ1asO6IHRyaSBwcmVtZW5uw6kgLSBNZW5vLCBWZWsgYSBCb2R5OgoKYGBge3J9CiMgV29ya2luZyB3aXRoIGRhdGEgZnJhbWVzCgogIE1lbm8gPSBjKCJKYW5hIiwgIkpvemVmIiwgIk3DoXJpYSIpCiAgVmVrID0gYygxMCwgMTEsIDkpCiAgQm9keSA9IGMoODUsIDkyLCA3OCkKYGBgCgpUaWV0byB0cmkgcHJlbWVubsOpIG5pZSBzw7ogemF0aWHEviBuaWpha28gcHJlcG9qZW7DqSwgcHJlZHN0YXZ1asO6IGl6b2xvdmFuw6kgc3TEunBjZSB0YWJ1xL5reS4gRG8gdGFidcS+a3kgaWNoIHNwb2rDrW1lIG5hc2xlZG92bmUKCmBgYHtyfQp1ZGFqZSA8LSBkYXRhLmZyYW1lKE1lbm8sVmVrLEJvZHkpCnByaW50KHVkYWplKQpgYGAKCgpWeXN2ZXRsZW5pZTogRGF0YUZyYW1lIG3DoSB0cmkgc3TEunBjZTogTWVubywgVmVrIGEgQm9keS4gTmlla3RvcsOpIG9wZXLDoWNpZSBzIMO6ZGFqbWkgb3JnYW5pem92YW7DvW1pIHYgLmRhdGEuZnJhbWUuIHPDuiB1dmVkZW7DqSBuYXNsZWRvdm5lCgpgYGB7cn0KcHJpbnQodWRhamUkVmVrKSAgICAgICAgICAgICAgICAgIyB0YWt0byBhZHJlc3VqZW1lIGplZG5vdGxpdsOpIHByZW1lbm7DqSB2IGRhdGEuZnJhbWUKcHJpbnQobWVhbih1ZGFqZSRWZWspKSAgICAgICAgICAgIyBwcmllbWVybnkgdmVrCnByaW50KHVkYWplW01lbm89PSJKb3plZiIsXSkgICAgICMgYWRyZXNvdmFuaWUgY2Vsw6lobyByaWFka3UKcHJpbnQodWRhamVbMyxdKSAgICAgICAgICAgICAgICAgIyBpbmEgbW96bm9zdCBhZHJlc292YW5pYSBjZWxlaG8gcmlhZGt1CnByaW50KHVkYWplWywyOjNdKSAgICAgICAgICAgICAgICMgdnlwaXNhbmllIGRydWhlaG8gYSB0cmV0aWVobyBzdGxwY2EgdGFidWxreQpwcmludCh1ZGFqZVsxLDFdKSAgICAgICAgICAgICAgICAjIHZ5cGlzYW5pZSBqZWRuZWogYnVua3kgdGFidWxreQpzdW1tYXJ5KHVkYWplKSAgICAgICAgICAgICAgICAgICAjIHpha2xhZG5hIGRlc2tyaXB0aXZuYSBzdGF0aXN0aWthIGNlbGVqIHRhYnVsa3kKYGBgCgpBayBjaGNlbWUgcHJpZGHFpSBrIHRhYnXEvmtlIGRvZGF0b8SNbsO9IHN0xLpwZWMsIHBvdG9tIHRvIHJvYsOtbWUgbmFzbGVkb3ZuZQoKYGBge3J9Ck1hQXV0byA8LSBjKFRSVUUsRkFMU0UsVFJVRSkKdWRhamUgPC0gY2JpbmQodWRhamUsTWFBdXRvKQpwcmludCh1ZGFqZSkKYGBgCgpBayBjaGNlbWUgcHJpZGHFpSByaWFkb2ssIHBvdG9tCgpgYGB7cn0KIyBOZXcgcmVjb3JkIChtdXN0IG1hdGNoIGNvbHVtbiBvcmRlci90eXBlcykKbm92eS5yaWFkb2sgPC0gZGF0YS5mcmFtZShNZW5vID0gIkRpYW5hIiwgVmVrID0gMjIuNDg1LCBCb2R5ID0gNDIsTWFBdXRvID0gRkFMU0UpCgojIEFwcGVuZAp1ZGFqZSA8LSByYmluZCh1ZGFqZSwgbm92eS5yaWFkb2spCnByaW50KHVkYWplKQpgYGAKCiMjIyBUYWJ1xL5reSB2IHByb3N0cmVkw60ga2FibGVleHRyYQoKCmBgYHtyfQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmthYmxlKAogIHVkYWplLAojICBmb3JtYXQsCmRpZ2l0cyA9IDIsCiMgIHJvdy5uYW1lcyA9IE5BLAojICBjb2wubmFtZXMgPSBOQSwKICBhbGlnbj1jKCJsIiwiYyIsImwiLCJyIiksCiAgY2FwdGlvbiA9ICJUb3RvIGplIHRhYnXEvmthIgojICBsYWJlbCA9IE5VTEwsCiMgIGZvcm1hdC5hcmdzID0gbGlzdCgpLAojICBlc2NhcGUgPSBUUlVFLAogIyAuLi4KKSAlPiUKICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwKICAgIGZ1bGxfd2lkdGggPSBGQUxTRSwKICAgIHBvc2l0aW9uID0gImNlbnRlciIpCgoKCgoKYGBgCgoKCiMjIFRpZHl2ZXJzZSAtIG1vZGVybsOhIHByw6FjYSBzIMO6ZGFqbWkKClRpZHl2ZXJzZSBqZSBzw7pib3Iga25pxb5uw61jLCBrdG9yw6kgbWFqw7ogemplZG5vZHXFoWnFpSBwcsOhY3UgcyDDumRham1pLiBNYWrDuiBqZWRub3Ruw70ga29tdW5pa2HEjW7DvSDFoXRhbmRhcmQsIHZ6w6Fqb21uZSBzYSBkb3BsxYh1asO6LgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBMb2FkIHRpZHl2ZXJzZQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIyMgIGRwbHlyIC0gcHJlIG1hbmlwdWzDoWNpdSBzIMO6ZGFqbWkKCi5kcGx5ci4gcG9za3l0dWplIHrDoWtsYWRuw6kgbW/Fvm5vc3RpIG1hbmlwdWzDoWNpZSBzIMO6ZGFqbWksIGFrbyBuYXByLjogCgoxLiBmaWx0ZXIoKTogdnliZXLDoSByaWFka3kgCgoxLiBzZWxlY3QoKTogdnliZXLDoSBzdMS6cGNlIAoKMS4gbXV0YXRlKCk6IHZ5dHbDoXJhIG5vdsOpIHN0xLpwY2UgdGFidcS+a3kgCgoxLiBhcnJhbmdlKCk6IHRyaWVkaSByaWFka3kgCgoxLiBzdW1tYXJpc2UoKTogc3VtYXJpenVqZQoKViBuYXNsZWRvdm5laiB1a8Ohxb5rZSB2eXXFvmlqZW1lIHR6di4gLnBpcGVzLiAlPiUgYWxlYm8gJTwlIHVtb8W+xYh1amUgcG9zaWVsYcWlIHbDvXNsZWRreSB6IGplZG5laiBmdW5rY2llIHByaWFtbyBkbyB2b2xhbmllIG5hc2xlZG92bmVqIGZ1bmtjaWUuIFRvIHVtb8W+xYh1amUgxL5haMWhaXUgxI1pdGF0ZcS+bm9zxaUga8OzZG92LCBrb252ZW5jaWEgc2EgdWphbGEgYSBtw6EgxaFpcm9rw6kgcG91xb5pdGllLgoKIyMjIyBWw71iZXIgYSB0cmllZGVuaWUKCmBgYHtyfQojIHbDvWJlciBhIG7DoXNsZWRuw6kgdHJpZWRlbmllCnVkYWplICU+JQogIGZpbHRlcihCb2R5ID4gNTApICU+JSAgICAgIyB2eWJlcmEgemF6bmFteSBzIHBvY3RvbSBib2RvdiB2aWFjLCBha28gNTAKICBhcnJhbmdlKGRlc2MoQm9keSkpICU+JSAgICAgIyB2eXNsZWRueSBzdWJvciB0cmllZGkgem9zdHVwbmUgcG9kbGEgcHJlbWVubmVqIEJvZHkKa2FibGUgJT4lCiAgICBrYWJsZV9zdHlsaW5nKAogICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIiksCiAgICBmdWxsX3dpZHRoID0gRkFMU0UsCiAgICBwb3NpdGlvbiA9ICJjZW50ZXIiCiAgKQpgYGAKCiMjIyMgWm9za3VwZW5pZSBhIHN1bWFyaXrDoWNpYQoKYGBge3J9CiMgWm9za3Vww60gYW5kIHN1bWFyaXp1amUKdWRhamUgJT4lCiAgZ3JvdXBfYnkoTWFBdXRvKSAlPiUgICAgICAjIHpvc2t1cGkgemF6bmFteSBwb2RsYSBwcmVtZW5uZWogTWFBdXRvIGEgdnlwb2NpdGEgemEga2F6ZHUgc2t1cGludSBqZWogcHJpZW1lciBCb2R5CiAgc3VtbWFyaXNlKCAgICAgICAgICAgICAgICAjIGEgdGFrdGlleiBzcG9jaXRhIHBvY2V0bm9zdGkgb2JvY2ggc2t1cGluCiAgICBQcmllbS5Cb2R5ID0gbWVhbihCb2R5KSwKICAgIGNvdW50ID0gbigpCiAgKSAlPiUKIGthYmxlKAogICAgY2FwdGlvbiA9ICJQcmllbWVybsOpIEJvZHkgcG9kxL5hIHByZW1lbm5laiBNYUF1dG8iLAogICAgY29sLm5hbWVzID0gYygiTcOhIEF1dG8iLCAiUHJpZW1lciBCb2R5IiwgIlBvxI1ldCIpLAogICAgYWxpZ24gPSAiYyIKICApICU+JQogIGthYmxlX3N0eWxpbmcoCiAgICBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSwKICAgIGZ1bGxfd2lkdGggPSBGQUxTRSwKICAgIHBvc2l0aW9uID0gImNlbnRlciIKICApCmBgYAoKIyMjIyBWeXR2w6FyYW5pZSBub3ZlaiBwcmVtZW5uZWoKCmBgYHtyfQojIFZ5dHbDoXJhbmllIG5vdmVqIHByZW1lbm5lagp1ZGFqZSAlPiUKICBtdXRhdGUoCiAgICBncmFkZSA9IGNhc2Vfd2hlbiggICAgICMgdnl0dmFyYSBub3Z1IHByZW1lbm51IGdyYWRlIHBvZGxhIG5hc2xlZG92bmVqIHJlbGFjbmVqIHNjaGVteQogICAgICBCb2R5ID49IDkwIH4gIkEiLAogICAgICBCb2R5ID49IDgwIH4gIkIiLAogICAgICBCb2R5ID49IDcwIH4gIkMiLAogICAgICBUUlVFIH4gIkQiCiAgICApLAogICAgVmVrUG9QbG5vbGV0b3N0aSA9IHJvdW5kKFZlay0xOCwwKQogICkgJT4lIAogIGthYmxlICU+JQogICBrYWJsZV9zdHlsaW5nKAogICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIiksCiAgICBmdWxsX3dpZHRoID0gRkFMU0UsCiAgICBwb3NpdGlvbiA9ICJjZW50ZXIiCiAgKSAKYGBgCgoKIyMgSW1wb3J0IMO6ZGFqb3YgeiBvdHYuIGRhdGFiw6F6CgoKMS4gKk1lbmRlbGV5IERhdGEqIFR1dG8gc2EgZG9zdGFuZW1lIHogW01lbmRlbGV5IERhdGFdKGh0dHBzOi8vZGF0YS5tZW5kZWxleS5jb20vKXt0YXJnZXQ9Ii5ibGFuayIgcmVsPSJub29wZW5lciJ9LCBrZGUgc2kgw7pkYWplIHZpZXRlIHZvxL5uZSBzdGlhaG7DusWlLiDDmmRhamUgc2EgdnrFpWFodWrDuiBrIHXFviBwdWJsaWtvdmFuw71tIMSNbMOhbmtvbSB2byB2eWRhdmF0ZcS+c3R2ZSBFbHNldmllci4gVsO9YmVyIHNhIGTDoSB1cm9iacWlIGplZG5vZHVjaG8gemFkYW7DrW0ga8S+w7rEjW92w71jaCBzbG92LgoyLiAqS2FnZ2xlIERhdGEqICAgVHV0byBzYSBkb3N0YW5lbWUgeiBbS2FnZ2xlIERhdGFzZXRzXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzKXt0YXJnZXQ9Ii5ibGFuayIgcmVsPSJub29wZW5lciJ9LCBrZGUgc2kgw7pkYWplIHZpZXRlIHZvxL5uZSBzdGlhaG7DusWlLiDDmmRhamUgc2EgdnrFpWFodWrDuiBrIHByb2pla3RvbSBwb2Rwb3JvdmFuw71tIEthZ2dsZS4gVsO9YmVyIHNhIGTDoSB1cm9iacWlIGplZG5vZHVjaG8gemFkYW7DrW0ga8S+w7rEjW92w71jaCBzbG92LgozLiBEYXRhYsOhenkga25pxb5uw61jIFIgLSAubGlicmFyeShkYXRhc2V0cykuIGFsZWJvIC5saWJyYXJ5KHdvb2xkcmlkZ2UpLiBhbGUgYWogaW7DqSAtIHN0YcSNw60gc2kgZGHFpSBwcsOta2F6IGRhdGEoKQoKYGBge3J9CmxpYnJhcnkoZGF0YXNldHMpCiMgZGF0YXNldHMgYXZhaWxhYmxlIGluIHRoZSAnZGF0YXNldHMnIHBhY2thZ2UgLSBuYXNsZWRvdm5lIGtvZHkgemEgbW5hIHVyb2JpbCBDaGF0IEdQVApkcyA8LSBhcy5kYXRhLmZyYW1lKHV0aWxzOjpkYXRhKHBhY2thZ2UgPSAiZGF0YXNldHMiKSRyZXN1bHRzKVssIGMoIkl0ZW0iLCJUaXRsZSIpXQprbml0cjo6a2FibGUoaGVhZChkcywgMjApLCBjb2wubmFtZXMgPSBjKCJEYXRhc2V0IiwgIlRpdGxlIikpICAgIyBwcnZ5Y2ggMjAgZGF0YWJhegojIGtuaXpuaWNhIGRhdGFzZXRzIG9ic2FodWplIGRhdGFiYXp1IG5henZhbnUgQ08yLiBNb3plbWUgc2EgbmEgbnUgb2R2b2xhdmF0IG5hc2xlZG92bmUsIGFrbyBuYXByLiAKaGVhZChDTzIpCmBgYAoKTcO0xb5lbWUgcG91xb5pxaUgYWogZGF0YWLDoXp1IHVyxI1lbsO6IHByZSBla29ib21ldHJpdSAtIHBhY2thZ2UgV29vbGRyaWRnZQoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygid29vbGRyaWRnZSIpCmxpYnJhcnkod29vbGRyaWRnZSkKZHMgPC0gYXMuZGF0YS5mcmFtZSh1dGlsczo6ZGF0YShwYWNrYWdlID0gIndvb2xkcmlkZ2UiKSRyZXN1bHRzKVssIGMoIkl0ZW0iLCJUaXRsZSIpXQprbml0cjo6a2FibGUoaGVhZChkcywgMjApLCBjb2wubmFtZXMgPSBjKCJEYXRhc2V0IiwgIlRpdGxlIikpICU+JQogICAga2FibGVfc3R5bGluZygKICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiLCAicmVzcG9uc2l2ZSIpLAogICAgZnVsbF93aWR0aCA9IEZBTFNFLAogICAgcG9zaXRpb24gPSAiY2VudGVyIgogICkKYGBgCgojIyMgSW1wb3J0IMO6ZGFqb3YgeiAuY3N2IGFsZWJvIC54bHMKCkphIHNvbSBzaSB6dm9saWwgw7pkYWplIHogW0Fib3NlZGUgVGlhbWl5dTogRW52aXJvbm1lbnRhbCwgU29jaWFsLCBhbmQgR292ZXJuYW5jZSBSZXBvcnRpbmcgRXZpZGVuY2luZyBGaXJtIFBlcmZvcm1hbmNlIGluIEVtZXJnaW5nIEVjb25vbXlde2h0dHBzOi8vZGF0YS5tZW5kZWxleS5jb20vZGF0YXNldHMvN2s4cGpoc3J3Yi8xfS4gTmEgc3Ryw6Fua2Ugc2EgbmFjaMOhZHphIHPDumJvciAuRGF0YXNldCBFU0cgYW5kIEZpcm0gUGVyZm9ybWFuY2UueGxzeC4sIGt0b3LDvSBzb20gc2kgc3RpYWhvbCBhIGV4cG9ydG92YWwgZG8gZm9ybcOhdHUgY3N2LiBBa28gb2RkZcS+b3ZhxI0gcG9sb8W+aWVrIHNvbSBzaSB6dm9saWwgYm9ka2/EjWlhcmt1IChzZW1pY29sb24gOyksIHZ5xb7DrXZhbSBkZXNhdGlubsO6IGJvZGt1IGEgbmllIMSNaWFya3UgYSB0aWXFviB0ZXh0b3bDqSBwcmVtZW5uw6kgdXbDoWR6YW0gYXBvc3Ryb2ZtaSAiLiBWIHBydm9tIHJpYWRrdSBzYSBuYWNow6FkemFqw7ogbsOhenZ5IHN0xLpwY292LCBrdG9yw6kgbmVza8O0ciBidWTDuiB2eXN0dXBvdmHFpSBha28gcHJlbWVubsOpLiBUaWUgb2JzYWh1asO6IG1lZHplcnksIMSNbyBqZSB2IHrDoXp2ZSBwcmVtZW5uZWogbmVwcsOtcHVzdG7DqSBhIG5haHJhZGlsIHNvbSBpY2ggcG9kdHJob3bDoXRrb20gIi4iLiAgCgohW07DoWjEvmFkIG5hIHhscyBkYXRhYsOhenUgb3R2b3JlbsO6IHYgdGFidcS+a292b20gcHJvY2Vzb3JlXShvYnJhemt5L3VkYWpleGxzLmpwZyl7d2lkdGg9MTAwJX0KCiFbTsOhaMS+YWQgbmEgY3N2IGRhdGFiw6F6dSBvdHZvcmVuw7ogdiB0ZXh0b3ZvbSBwcm9jZXNvcmVdKG9icmF6a3kvdWRhamVjc3YuanBnKXt3aWR0aD0xMDAlfQoKUG90b20gdcW+IHN0YcSNw60gaW1wb3J0b3ZhxaUgw7pkYWplIGRvIC5kYXRhLmZyYW1lLiwgYSB0byBuYXNsZWRvdm5lCgpgYGB7cn0KdWRhamUgPC0gcmVhZC5jc3YyKCJ1ZGFqZS9IZWFsdGhSaXNrRGF0YV9LdW5vdmEuY3N2IixoZWFkZXI9VFJVRSxzZXA9IjsiLGRlYz0iLiIpCmhlYWQodWRhamUpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBuaWVrb2xrbyBwcnZ5Y2ggcmlhZGtvdgpjb2xuYW1lcyh1ZGFqZSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbmF6dnkgcHJlbWVubnljaApgYGAKCiMjIEdyYWZ5CgoKIyMjIGdncGxvdDIgLSBrbmnFvm5pY2EgcHJlIGdyYWZ5CgpWw71iZXIgYSBuw6FzbGVkbsOpIHRyaWVkZW5pZQpgYGB7cn0KbGlicmFyeShkcGx5cikKCnVkYWplLjIwMTMgPC0gdWRhamUgJT4lCiAgZmlsdGVyKFlFQVJTID09IDIwMTMpICU+JQogIHNlbGVjdChSRVRVUk4uT04uQVNTRVRTLCBFU0cuSU5ERVgsIERFQlQuVE8uQVNTRVQsIEZJUk0uU0laRSkKYGBgCgpLbmnFvm5pY2EgLmdncGxvdDIuIGplIHYgc8O6xI1hc25vc3RpIG5hasSNYXN0ZWrFoWllIHBvdcW+w612YW7DoSBncmFmaWNrw6Ega25pxb5uaWNhLCBwcmnEjW9tIHByZWRwcmlwcmF2ZW7DqSBrw7NkeSBrIGplZG5vdGxpdsO9bSBvYnLDoXprb20gc2kgdmlldGUgbsOhanPFpSB2IFtSIEdyYXBoIEdhbGxlcnldKGh0dHBzOi8vci1ncmFwaC1nYWxsZXJ5LmNvbS8pLiBUdSBzaSB1dmVkaWVtZSBqZWRub2R1Y2jFoWllIHogbmljaC4KCiMjIyMgU2NhdHRlciBwbG90CgpgYGB7cn0KIyBCYXNpYyBzY2F0dGVyIHBsb3QKbGlicmFyeShnZ3Bsb3QyKQpnZ3Bsb3QodWRhamUuMjAxMywgYWVzKHggPSBGSVJNLlNJWkUsIHkgPSBFU0cuSU5ERVgpKSArICAgICAgICAgICAgIyBzcGVjaWZpa2FjaWEgb3NpCiAgZ2VvbV9wb2ludCgpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHR5cCBncmFmdSAtIHNjYXR0ZXJwbG90CiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkVTRyBpbmRleCIsIHggPSAiVmXEvmtvc8WlIGZpcm15IiwgeSA9ICJTY29yZSIpICAgICAgIyBvem5hY2VuaWUgb3NpCmBgYAoKIyMjIyBCb3hwbG90CgpgYGB7cn0KIyBCYXIgcGxvdCB3aXRoIGdyb3VwaW5nCmxpYnJhcnkoZ2dwbG90MikKCmxpYnJhcnkoZ2dwbG90MikKCmdncGxvdCh1ZGFqZSwgYWVzKHggPSBmYWN0b3IoWUVBUlMpLCB5ID0gRVNHLklOREVYKSkgKyAgICAgICAgIyBzcGVjaWZpa2FjaWEgb3NpCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZGFya2JsdWUiKSArICAgICAgIyB0eXAgZ3JhZnUgLSBib3hwbG90CiAgbGFicyggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvem5hY2VuaWUgb3NpLCBuYXpvdiBncmFmdQogICAgdGl0bGUgPSAiRVNHIEluZGV4IGJ5IFllYXJzIiwKICAgIHggPSAiWWVhciIsCiAgICB5ID0gIkVTRyBJbmRleCIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIFrDoWtsYWRuw6kgxaF0YXRpc3Rpa3kuIAoKCiMjIGtuaXRyIC0gdGFidcS+a2EKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGtuaXRyKQoKIyBTdW1tYXJpc2UgYmFzaWMgc3RhdGlzdGljcwplc2cuc3RhdHMgPC0gdWRhamUgJT4lCiAgZmlsdGVyKFlFQVJTICVpbiUgMjAxMzoyMDE2KSAlPiUKICBncm91cF9ieShZRUFSUykgJT4lCiAgc3VtbWFyaXNlKAogICAgbiAgICAgPSBuKCksCiAgICBtZWFuICA9IG1lYW4oRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgc2QgICAgPSBzZChFU0cuSU5ERVgsIG5hLnJtID0gVFJVRSksCiAgICBtaW4gICA9IG1pbihFU0cuSU5ERVgsIG5hLnJtID0gVFJVRSksCiAgICBxMjUgICA9IHF1YW50aWxlKEVTRy5JTkRFWCwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbj0gbWVkaWFuKEVTRy5JTkRFWCwgbmEucm0gPSBUUlVFKSwKICAgIHE3NSAgID0gcXVhbnRpbGUoRVNHLklOREVYLCAwLjc1LCBuYS5ybSA9IFRSVUUpLAogICAgbWF4ICAgPSBtYXgoRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCiMgQ3JlYXRlIGtuaXRyIHRhYmxlCmthYmxlKGVzZy5zdGF0cywgZGlnaXRzID0gMiwgY2FwdGlvbiA9ICJCYXNpYyBzdGF0aXN0aWNzIG9mIEVTRyBJbmRleCAoMjAxM+KAkzIwMTYpIikKYGBgCgphbGVibyBrcmFqxaFpZSB0YWJ1xL5reSBzIHBvbW9jb3UgLmthYmxlRXh0cmEuOgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgU3VtbWFyaXNlIGJhc2ljIHN0YXRpc3RpY3MKZXNnLnN0YXRzIDwtIHVkYWplICU+JQogIGZpbHRlcihZRUFSUyAlaW4lIDIwMTM6MjAxNikgJT4lCiAgZ3JvdXBfYnkoWUVBUlMpICU+JQogIHN1bW1hcmlzZSgKICAgIG4gICAgICA9IG4oKSwKICAgIG1lYW4gICA9IG1lYW4oRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgc2QgICAgID0gc2QoRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgbWluICAgID0gbWluKEVTRy5JTkRFWCwgbmEucm0gPSBUUlVFKSwKICAgIHEyNSAgICA9IHF1YW50aWxlKEVTRy5JTkRFWCwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbiA9IG1lZGlhbihFU0cuSU5ERVgsIG5hLnJtID0gVFJVRSksCiAgICBxNzUgICAgPSBxdWFudGlsZShFU0cuSU5ERVgsIDAuNzUsIG5hLnJtID0gVFJVRSksCiAgICBtYXggICAgPSBtYXgoRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCiMgQ3JlYXRlIHN0eWxlZCBrYWJsZUV4dHJhIHRhYmxlCmVzZy5zdGF0cyAlPiUKICBrYWJsZShkaWdpdHMgPSAyLCBjYXB0aW9uID0gIkJhc2ljIHN0YXRpc3RpY3Mgb2YgRVNHIEluZGV4ICgyMDEz4oCTMjAxNikiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSwgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICBjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSkgJT4lICAgICAgICAgICMgbWFrZSB5ZWFycyBib2xkCiAgcm93X3NwZWMoMCwgYm9sZCA9IFRSVUUsIGJhY2tncm91bmQgPSAiI2YyZjJmMiIpICU+JSAgIyBzdHlsZSBoZWFkZXIgcm93CiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIiA9IDIsICJFU0cgSW5kZXggU3RhdGlzdGljcyIgPSA3KSkKYGBgCgoKCiMjIyMgdC10ZXN0OiBQb3Jvdm5hbmllIHByaWVtZXJ1IEVTRyBpbmRleHUgdiByb2tvY2ggMjAxMyBhIDIwMTUKCmBgYHtyfQp0LnRlc3QucmVzdWx0IDwtIHQudGVzdCgKICB1ZGFqZSRFU0cuSU5ERVhbdWRhamUkWUVBUlMgPT0gMjAxM10sCiAgdWRhamUkRVNHLklOREVYW3VkYWplJFlFQVJTID09IDIwMTVdCikKCnByaW50KHQudGVzdC5yZXN1bHQpCmBgYAoKCiMjIyMgQU5PVkE6IENvbXBhcmluZyBSZWFkaW5nIFNjb3JlcyBBY3Jvc3MgUHJvZ3JhbXMKCmBgYHtyfQphbm92YS5yZXN1bHQgPC0gYW92KEVTRy5JTkRFWCB+IFlFQVJTLCBkYXRhID0gdWRhamUpCnN1bW1hcnkoYW5vdmEucmVzdWx0KQpgYGAKCiMjIyMgTGluZWFyIFJlZ3Jlc3Npb246IFByZWRpY3RpbmcgTWF0aCBTY29yZXMKCmBgYHtyfQptb2RlbCA8LSBsbShFU0cuSU5ERVggfiBSRVRVUk4uT04uQVNTRVRTICsgRklSTS5TSVpFICsgREVCVC5UTy5BU1NFVCwgZGF0YSA9IHVkYWplLjIwMTMpCnN1bW1hcnkobW9kZWwpCmBgYAoKCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKGMoImJyb29tIiwgImthYmxlRXh0cmEiLCAiZHBseXIiLCAic3RyaW5nciIpKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoc3RyaW5ncikKCiMgWW91ciBtb2RlbCAoYWxyZWFkeSBmaXR0ZWQpCiMgbW9kZWwgPC0gbG0oRVNHLklOREVYIH4gUkVUVVJOLk9OLkFTU0VUUyArIEZJUk0uU0laRSArIERFQlQuVE8uQVNTRVQsIGRhdGEgPSB1ZGFqZS4yMDEzKQoKY29lZi50YmwgPC0gdGlkeShtb2RlbCwgY29uZi5pbnQgPSBUUlVFKSAlPiUKICBtdXRhdGUoCiAgICB0ZXJtID0gcmVjb2RlKHRlcm0sCiAgICAgICIoSW50ZXJjZXB0KSIgPSAiSW50ZXJjZXB0IiwKICAgICAgIlJFVFVSTi5PTi5BU1NFVFMiID0gIlJldHVybiBvbiBBc3NldHMiLAogICAgICAiRklSTS5TSVpFIiA9ICJGaXJtIFNpemUiLAogICAgICAiREVCVC5UTy5BU1NFVCIgPSAiRGVidCB0byBBc3NldCIKICAgICksCiAgICBzdGFycyA9IGNhc2Vfd2hlbigKICAgICAgcC52YWx1ZSA8IDAuMDAxIH4gIioqKiIsCiAgICAgIHAudmFsdWUgPCAwLjAxICB+ICIqKiIsCiAgICAgIHAudmFsdWUgPCAwLjA1ICB+ICIqIiwKICAgICAgcC52YWx1ZSA8IDAuMSAgIH4gIsK3IiwKICAgICAgVFJVRSAgICAgICAgICAgIH4gIiIKICAgICkKICApICU+JQogIHRyYW5zbXV0ZSgKICAgIFRlcm0gPSB0ZXJtLAogICAgRXN0aW1hdGUgPSBlc3RpbWF0ZSwKICAgIGBTdGQuIEVycm9yYCA9IHN0ZC5lcnJvciwKICAgIGB0IHZhbHVlYCA9IHN0YXRpc3RpYywKICAgIGBwIHZhbHVlYCA9IHAudmFsdWUsCiAgICBgOTUlIENJYCA9IHN0cl9jKCJbIiwgcm91bmQoY29uZi5sb3csIDMpLCAiLCAiLCByb3VuZChjb25mLmhpZ2gsIDMpLCAiXSIpLAogICAgU2lnID0gc3RhcnMKICApCgpjb2VmLnRibCAlPiUKICBrYWJsZSgKICAgIGRpZ2l0cyA9IDMsCiAgICBjYXB0aW9uID0gIk9MUyBSZWdyZXNzaW9uIENvZWZmaWNpZW50cyAoRVNHLklOREVYIH4gUkVUVVJOLk9OLkFTU0VUUyArIEZJUk0uU0laRSArIERFQlQuVE8uQVNTRVQpIgogICkgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UsIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFRSVUUpICU+JQogIHJvd19zcGVjKDAsIGJvbGQgPSBUUlVFLCBiYWNrZ3JvdW5kID0gIiNmMmYyZjIiKSAlPiUKICBmb290bm90ZSgKICAgIGdlbmVyYWwgPSAiU2lnbmlmLiBjb2RlczogKioqIHA8MC4wMDEsICoqIHA8MC4wMSwgKiBwPDAuMDUsIMK3IHA8MC4xLiIsCiAgICB0aHJlZXBhcnR0YWJsZSA9IFRSVUUKICApCmBgYAoKYGBge3J9CmZpdC50YmwgPC0gZ2xhbmNlKG1vZGVsKSAlPiUKICB0cmFuc211dGUoCiAgICBgUi1zcXVhcmVkYCA9IHIuc3F1YXJlZCwKICAgIGBBZGouIFItc3F1YXJlZGAgPSBhZGouci5zcXVhcmVkLAogICAgYEYtc3RhdGlzdGljYCA9IHN0YXRpc3RpYywKICAgIGBGIHAtdmFsdWVgID0gcC52YWx1ZSwKICAgIGBBSUNgID0gQUlDLAogICAgYEJJQ2AgPSBCSUMsCiAgICBgTnVtLiBvYnMuYCA9IG5vYnMKICApCgpmaXQudGJsICU+JQogIGthYmxlKGRpZ2l0cyA9IDMsIGNhcHRpb24gPSAiTW9kZWwgRml0IFN0YXRpc3RpY3MiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSwgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJjb25kZW5zZWQiKSkKYGBgCgoKCgoKCiMgSW5mbyB6ZHJvamUgcHJlIMSPYWzFoWllIMWhdMO6ZGl1bQoKIyMjIyBSIFByb2plY3QKCi0gICBbUiBQcm9qZWN0IEhvbWVwYWdlXShodHRwczovL3d3dy5yLXByb2plY3Qub3JnLykgLSBCYXNlIFIgZG93bmxvYWRzLAogICAgbmV3cywgYW5kIGxlYXJuaW5nIHJlc291cmNlcwoKIyMjIyBQb3NpdAoKLSAgIFtSIFN0dWRpbyBEZXNrdG9wXShodHRwczovL3Bvc2l0LmNvL2Rvd25sb2FkL3JzdHVkaW8tZGVza3RvcC8pIC0KICAgIEZlYXR1cmUgcmljaCBlbnZpcm9ubWVudCBmb3Igd29ya2luZyB3aXRoIGRhdGEgaW4gUgotICAgW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pIC0gQ29tcHJlaGVuc2l2ZSBvbmxpbmUKICAgIGJvb2sKLSAgIFtSU3R1ZGlvCiAgICBDaGVhdHNoZWV0c10oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLykgLSBRdWljawogICAgcmVmZXJlbmNlIGd1aWRlcwoKIyMjIyBDb21tdW5pdHkgUmVzb3VyY2VzCgotICAgW1ItYmxvZ2dlcnNdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLykgLSBCbG9nIGFnZ3JlZ2F0b3IgZm9yIFIKICAgIG5ld3MgYW5kIHR1dG9yaWFscwotICAgW1N0YWNrIE92ZXJmbG93IC0gUgogICAgdGFnXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy90YWdnZWQvcikgLSBRJkEgY29tbXVuaXR5Ci0gICBbcmRyci5pbyBTbmlwcGV0c10oaHR0cHM6Ly9yZHJyLmlvL3NuaXBwZXRzLykgLSBUZXN0IFIgY29kZSBzbmlwcGV0cwogICAgb25saW5lCi0gICBbQ291cnNlcmEgLSBSCiAgICBQcm9ncmFtbWluZ10oaHR0cHM6Ly93d3cuY291cnNlcmEub3JnL2xlYXJuL3ItcHJvZ3JhbW1pbmcpIC0gT25saW5lCiAgICBjb3Vyc2UKLSAgIFtEYXRhQ2FtcCAtIEludHJvZHVjdGlvbiB0bwogICAgUl0oaHR0cHM6Ly93d3cuZGF0YWNhbXAuY29tL2NvdXJzZXMvZnJlZS1pbnRyb2R1Y3Rpb24tdG8tcikgLQogICAgSW50ZXJhY3RpdmUgbGVhcm5pbmcgcGxhdGZvcm0KCg==