1. Introducción

Hacer cuadros/tablas para mostrar resultados es una parte importante (y time consuming) de cualquier informe o investigación. En este tutorial explicaré como presentar nuestros resultados en tablas en documentos (ya sean estos .html, .pdf etc..) creados a partir de archivos .Rmd.

Veremos que presentar los resultados (previamente almacenados en un df) como un cuadro en el documento final es muy sencillo, sólo tenemos que llamar a la función kable(), pero si queremos tener más flexibilidad y opciones para mejorar nuestras tablas tendremos que usar otros paquetes como kableextra, formatable, flextable, DT o más recientemente gt y reactable

En este hilo de Twitter se habló sobre cuales eran los mejores paquetes R para hacer tablas. En este post, David Keyes hace un muy buen tutorial sobre los paquetes disponibles para hacer tablas en R.

Al igual que con los gráficos, para hacer buenas tablas 1 hace falta tener en cuenta algunos aspectos. Este post proporciona 10 reglas o consejos para generar tablas efectivas. En este otro post, Thomas Mock implementa esas 10 reglas con el paquete gt.

En el siguiente apartado utilizaremos unos datos de ejemplo para obtener unos resultados que queremos mostrar como tablas en nuestro informe. Esto nos servirá para practicar un poco más con dplyr y aprender a crear tablas desde documentos .Rmd, ya que una vez tengamos los resultados que queremos mostrar, presentaremos distintas posibilidades para mostrarlos en tablas/cuadros good-looking.



2. Resultados para tablas

En esta sección utilizaremos lo que hemos aprendido de dplyr para obtener los resultados que queremos mostrar en los cuadros. Con dplyr calcularemos los estadísticos que queremos mostrar almacenándolos en un dataframe/tibble. Una vez tengamos los resultados en un dataframe nos quedará la tarea de presentarlos como cuadros en el documento final. En un primer momento (esta sección) utilizaremos la función knitr::kable() para hacer tablas y en los siguientes apartados presentaremos paquetes especialmente diseñados para hacer tablas.

2.1 Datos que usaremos

Ilustraremos la creación de tablas con datos de la segunda ronda de PIACC (Programa para la Evaluación Internacional de las Competencias de los adultos de la OCDE. Información detallada sobre el programa PIAAC puede encontrarse en su pagina web http://www.oecd.org/skills/piaac/. Los datos se obtuvieron a partir del paquete RPIAAC creado Por Jose C. Pernias, para finalmente seleccionar 10 variables de 4 países: ESP, FRA, GBR, ITA. No recuerdo el año de los datos!!

Cargamos los datos que vamos a utilizar:

my_url <- "https://raw.githubusercontent.com/perezp44/iris_data/master/data/PIAAC_data_small.csv"
df <- read_csv(my_url)

Miramos un poco los datos:

#- remotes::install_github("perezp44/pjpv2020.01")
df_aa <- pjpv2020.01::pjp_f_estadisticos_basicos(df) #- estadísticos básicos del df
df_bb <- pjpv2020.01::pjp_f_unique_values(df)        #- valores únicos de cada variable de df


2.2 Obteniendo resultados (con dplyr)

Para iniciar nuestra andadura con las tablas, intentaremos mostrar en tablas algunos resultados. Por ejemplo:


    1. Cuadro con el número de observaciones de cada país (lo hacemos de 2 formas)
df_tt_1 <- df %>% 
           count(Country)

df_tt_1 <- df %>% 
           group_by(Country) %>% 
           summarise(NN = n())

knitr::kable(df_tt_1)
Country NN
ESP 1991
FRA 3346
GBR 1075
ITA 1610


    1. Añadir a la tabla el % que representa cada país en el Total (también lo hacemos de 2 formas)
df_tt_2 <- df %>% 
           group_by(Country) %>% 
           summarise(NN = n(), percent = n()/nrow(.) )

df_tt_2 <- df %>% 
           group_by(Country) %>%
           summarise (NN = n()) %>%
           mutate(percent = NN / sum(NN))

knitr::kable(df_tt_2)
Country NN percent
ESP 1991 0.2481925
FRA 3346 0.4171030
GBR 1075 0.1340065
ITA 1610 0.2006981


    1. Porcentaje de Hombres y Mujeres en cada país
df_tt_3 <- df %>% 
          count(Country, Gender) %>%
          group_by(Country) %>%
          mutate(percent = n / sum(n)) %>%
          select(-n) %>%
          pivot_wider(names_from = Gender, 
                      values_from = percent) %>% 
          ungroup()

knitr::kable(df_tt_3)
Country Female Male
ESP 0.4841788 0.5158212
FRA 0.5065750 0.4934250
GBR 0.6893023 0.3106977
ITA 0.4850932 0.5149068


    1. Porcentaje de Hombres y Mujeres en cada nivel educativo
df_tt_4 <- df %>% 
           count(Education, Gender) %>% 
           group_by(Education) %>%
           mutate(percent = n / sum(n)) %>%
           select(-n) %>%
           pivot_wider(names_from = Education, 
                       values_from = percent) %>% 
           ungroup()

knitr::kable(df_tt_4)
Gender Primary Secondary Tertiary Upper_second NA
Female 0.4377224 0.4804831 0.6159875 0.579646 0.8
Male 0.5622776 0.5195169 0.3840125 0.420354 0.2


    1. Calculamos el salario medio por país
df_tt_5 <- df %>% 
           group_by(Country) %>% 
           summarise(W_hora_medio = mean(Wage_hour , na.rm = TRUE), 
                     W_mes_medio  = mean(Wage_month, na.rm = TRUE)) %>% ungroup()


knitr::kable(df_tt_5)
Country W_hora_medio W_mes_medio
ESP 9.386217 1437.340
FRA 12.850094 1992.606
GBR 10.624071 1507.472
ITA 11.746581 1808.500


    1. Calculamos la media, el mínimo, el máximo y la desviación típica de Wage_month
df_tt_6 <- df %>% 
           group_by(Country) %>% 
           summarise(W_medio  = mean(Wage_month, na.rm = TRUE) ,
                     W_minimo = min(Wage_month, na.rm = TRUE)  ,
                     W_maximo = max(Wage_month, na.rm = TRUE)  ,
                     W_sd = sd(Wage_month, na.rm = TRUE) ) %>% 
          ungroup()

knitr::kable(df_tt_6)
Country W_medio W_minimo W_maximo W_sd
ESP 1437.340 110 3800 702.6642
FRA 1992.606 115 5850 906.5507
GBR 1507.472 116 10000 1003.7897
ITA 1808.500 150 5000 819.6863


    1. Salario medio en España (Hombres y Mujeres)
df_tt_7 <- df %>% 
           filter(Country == "ESP") %>% 
           group_by(Gender) %>% 
           summarise(W_hora_medio = mean(Wage_hour, na.rm = TRUE), 
                     W_mes_medio = mean(Wage_month, na.rm = TRUE) ) %>%
           ungroup()

knitr::kable(df_tt_7)
Gender W_hora_medio W_mes_medio
Female 8.789178 1245.076
Male 9.946525 1617.810


    1. Salario medio en los 4 los países (Hombres y Mujeres)
df_tt_8 <- df %>% 
           group_by(Country, Gender) %>% 
           summarise(W_hora_medio = mean(Wage_hour, na.rm = TRUE), 
                     W_mes_medio = mean(Wage_month, na.rm = TRUE) ) %>% 
           ungroup()

knitr::kable(df_tt_8)
Country Gender W_hora_medio W_mes_medio
ESP Female 8.789178 1245.076
ESP Male 9.946525 1617.810
FRA Female 12.443723 1802.254
FRA Male 13.266840 2188.032
GBR Female 10.609256 1377.953
GBR Male 10.656991 1794.817
ITA Female 11.584393 1629.828
ITA Male 11.898988 1976.826


    1. ¿Cuanto más cobran los hombres (en %)?
df_tt_9 <- df %>% 
           group_by(Country, Gender) %>% 
           summarise(W_mes_medio = mean(Wage_month, na.rm = TRUE)) %>% 
           ungroup() %>% 
           pivot_wider(names_from = Gender, values_from = W_mes_medio) %>% 
           mutate(dif_W = Male-Female, dif_percent_W = dif_W/Female)

knitr::kable(df_tt_9)
Country Female Male dif_W dif_percent_W
ESP 1245.076 1617.810 372.7344 0.2993669
FRA 1802.254 2188.032 385.7784 0.2140533
GBR 1377.953 1794.817 416.8640 0.3025240
ITA 1629.828 1976.826 346.9983 0.2129048


    1. Numeracy Score por país y nivel de estudios. La tabla nos va a salir alargada
df_tt_10 <- df %>% 
            group_by(Country, Education) %>% 
            summarise(Numeracy_media = mean(Numeracy_score, na.rm = TRUE)) %>% 
            ungroup() 

knitr::kable(df_tt_10)
Country Education Numeracy_media
ESP Primary 213.0135
ESP Secondary 245.1109
ESP Tertiary 282.1070
ESP Upper_second 265.7699
FRA Primary 184.9306
FRA Secondary 246.6609
FRA Tertiary 304.0075
FRA Upper_second 289.0088
FRA NA 197.6502
GBR Primary 207.3143
GBR Secondary 252.9237
GBR Tertiary 286.1454
GBR Upper_second 263.2052
GBR NA 213.1912
ITA Primary 200.7297
ITA Secondary 257.5339
ITA Tertiary 279.7166
ITA Upper_second 271.6400
    1. Hagamos la anterior tabla más ancha (Una columna para cada país)
df_tt_11 <- df_tt_10 %>% 
            pivot_wider(names_from = Education, 
                        values_from = Numeracy_media)

knitr::kable(df_tt_11)
Country Primary Secondary Tertiary Upper_second NA
ESP 213.0135 245.1109 282.1070 265.7699 NA
FRA 184.9306 246.6609 304.0075 289.0088 197.6502
GBR 207.3143 252.9237 286.1454 263.2052 213.1912
ITA 200.7297 257.5339 279.7166 271.6400 NA


Bien, no está mal, pero quiero ordenar la tabla de menor a mayor nivelo educativo

    1. Reordenando la tabla 11

Resulta que en el cuadro, las categorías de niveles de estudio se han ordenado alfabéticamente pero quiero ordenarla de menor a mayor nivel de estudios. Se puede hacer de otras maneras, pero lo haremos usando factores.

La variable Education tiene 4 categorías: Primary, Secondary, Upper-second y Tertiary; ADEMÁS estas categorías están/son fijas, no cambian. En estos casos es mejor tratar esas variables como factores; PERO si miramos la clase de la variable “Education” con class(df$Education) veremos que es character, así que vamos a convertir la variable “Education” a factor.

Podríamos usar la función as.factor(), pero una vez más usaremos pkgs del tidyverse; en este caso el pkg “forcats” y la función as_factor()

df <- df %>% 
      mutate(Education = forcats::as_factor(Education))

levels(df$Education)
#> [1] "Primary"      "Tertiary"     "Secondary"    "Upper_second"


Como vemos, la variable Education, que ahora es un factor, tiene cuatro categorías o “levels”

Resulta que el nombre de las 4 categorías está en inglés y lo queremos en castellano. Lo haremos con forcats::fct_recode(). Para saber un poco más sobre como manipular factores debes ir aquí

df <- df %>% 
      mutate(Education = forcats::fct_recode(Education,
                    "Primaria"         = "Primary",
                    "Secundaria"       = "Secondary", 
                    "Secundaria_post"  = "Upper_second",
                    "Terciaria"        = "Tertiary" )) 

df %>% count(Education)
Education n
Primaria 562
Terciaria 1914
Secundaria 4637
Secundaria_post 904
NA 5


Como volvemos a ver, la variable “Education”, que ahora es un factor tiene 4 “levels” pero están ordenados de forma que no nos conviene, queremos cambiar el orden de los factores con forcast::fct_relevel() para que se muestren los cuatro grupos educativos de menor a mayor nivel.

df <- df %>% 
      mutate(Education = forcats::fct_relevel(Education, 
                             "Primaria", "Secundaria", "Secundaria_post")) 

df %>% count(Education)
Education n
Primaria 562
Secundaria 4637
Secundaria_post 904
Terciaria 1914
NA 5

Como se puede ver en el cuadro de arriba, ya hemos cambiado el orden de los “levels” para que se muestren de menor a mayor nivel educativo, así que volvamos a rehacer el cuadro con el Numeracy Score por países:

df_tt_12 <- df %>% 
            group_by(Country, Education) %>% 
            summarise(Numeracy_media = mean(Numeracy_score, na.rm = TRUE)) %>% 
            ungroup() %>%  
            pivot_wider(names_from = Education, 
                        values_from = Numeracy_media) 

knitr::kable(df_tt_12)
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213.0135 245.1109 265.7699 282.1070 NA
FRA 184.9306 246.6609 289.0088 304.0075 197.6502
GBR 207.3143 252.9237 263.2052 286.1454 213.1912
ITA 200.7297 257.5339 271.6400 279.7166 NA


2.3 Obteniendo resultados (con janitor)

Si los resultados que queremos mostrar son cosas básicas como porcentajes, frecuencias etc… obviamente podemos calcularlos nosotros con dplyr, pero para estos casos el paquete janitor, concretamente la función janitor::tabyl() nos facilita mucho la tarea2. Para ver todas las posibilidades que tiene el paquete janitor puedes ir a su página web aquí.

1 y 2. Cuadro con el número de observaciones de cada país

df_tt_1_j <- df %>% janitor::tabyl(Country)
knitr::kable(df_tt_1_j)
Country n percent
ESP 1991 0.2481925
FRA 3346 0.4171030
GBR 1075 0.1340065
ITA 1610 0.2006981
  1. Porcentaje de Hombres y Mujeres en cada país
df_tt_3_j <- df %>% janitor::tabyl(Country, Gender)

df_tt_3_j <- df %>% janitor::tabyl(Country, Gender) %>% 
                    janitor::adorn_percentages(denominator = "row")
df_tt_3_j
Country Female Male
ESP 0.4841788 0.5158212
FRA 0.5065750 0.4934250
GBR 0.6893023 0.3106977
ITA 0.4850932 0.5149068
  1. Porcentaje de Hombres y Mujeres en cada nivel educativo
df_tt_4_j <- df %>% janitor::tabyl(Gender, Education) %>% 
                    janitor:: adorn_percentages(denominator = "col")
df_tt_4_j
Gender Primaria Secundaria Secundaria_post Terciaria NA_
Female 0.4377224 0.4804831 0.579646 0.6159875 0.8
Male 0.5622776 0.5195169 0.420354 0.3840125 0.2

Para ver todas las posibilidades que tiene el paquete janitor puedes ir a su página web aquí


Hay otros paquetes con funcionalidades parecidas a janitor. Por ejemplo, el paquete freqtables o el paquete sjmisc.



3. Tablas con knitr::kable()


Bueno, ya sabemos, aunque en realidad lo vimos al presentar dplyr y tidyr, como ir generando un dataframe con los estadísticos descriptivos que nos interesa mostrar. En realidad esos df ya son tablas de datos que puedo exportar a Excel para trabajarlas a nuestro gusto; pero en el curso hacemos todo desde R.

Ya habéis visto que con la función knitr::kable() puedo mostrarlas en mi documento final sólo tengo que ejecutar lo siguiente: knitr::kable(my_df).


En los próximos apartados mostraremos algunos paquetes específicos para formatear los cuadros y hacerlos BONITOS, pero también conviene saber que kable() también tiene opciones para personalizar un poco nuestras tablas. Por ejemplo:

knitr::kable(df_tt_12, 
             align = "c", 
             caption = "Numeracy Score por país",
             digits = 2, 
             format.args = list(decimal.mark = ",", big.mark = "."))
Numeracy Score por país
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213,01 245,11 265,77 282,11 NA
FRA 184,93 246,66 289,01 304,01 197,65
GBR 207,31 252,92 263,21 286,15 213,19
ITA 200,73 257,53 271,64 279,72 NA


3.1 kableExtra pkg

kableExtra es un package de que permite mejorar las tablas creadas con knitr::kable().

La descripción de kableExtra en CRAN es:

Build complex HTML or ‘LaTeX’ tables using ‘kable()’ from ‘knitr’ and the piping syntax from ‘magrittr’. Function ‘kable()’ is a light weight table generator coming from ‘knitr’. This package simplifies the way to manipulate the HTML or ‘LaTeX’ codes generated by ‘kable()’ and allows users to construct complex tables and customize styles using a readable syntax.

Aquí tienes la vignette para elaborar tablas para documentos html.

Para mejorar las tablas usaremos la función kableExtra::kable_styling():

knitr::kable(df_tt_12) %>% 
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"))
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213.0135 245.1109 265.7699 282.1070 NA
FRA 184.9306 246.6609 289.0088 304.0075 197.6502
GBR 207.3143 252.9237 263.2052 286.1454 213.1912
ITA 200.7297 257.5339 271.6400 279.7166 NA

Se le puede poner un scroll:

df_tt_12 %>%
  knitr::kable() %>%
  kableExtra::kable_styling(font_size = 11) %>%
  kableExtra::scroll_box(width = "50%", height = "60%")
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213.0135 245.1109 265.7699 282.1070 NA
FRA 184.9306 246.6609 289.0088 304.0075 197.6502
GBR 207.3143 252.9237 263.2052 286.1454 213.1912
ITA 200.7297 257.5339 271.6400 279.7166 NA


Se puede incluso poner las tablas junto con el texto.

knitr::kable(df_tt_12) %>% 
  kableExtra::kable_styling(bootstrap_options = "striped", 
                            full_width = F, 
                            position = "float_right")
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213.0135 245.1109 265.7699 282.1070 NA
FRA 184.9306 246.6609 289.0088 304.0075 197.6502
GBR 207.3143 252.9237 263.2052 286.1454 213.1912
ITA 200.7297 257.5339 271.6400 279.7166 NA



Para ello tienes que usar la opción position = "float_right dentro de kableExtra::kable_styling().




Si tenemos una tabla muy larga, podemos fijar el encabezamiento de la tabla con fixed_thead

knitr::kable(df_tt_12) %>%
  kableExtra::kable_styling(fixed_thead = list(enabled = T, 
                                               background = "lightblue"))
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213.0135 245.1109 265.7699 282.1070 NA
FRA 184.9306 246.6609 289.0088 304.0075 197.6502
GBR 207.3143 252.9237 263.2052 286.1454 213.1912
ITA 200.7297 257.5339 271.6400 279.7166 NA

Se pueden formatear columnas por separado con kableExtra::column_spec(), o las filas con kableExtra::row_spec(). La tabla no nos saldrá muy chula pero nos sirve para ver las posibilidades de kable_styling().

library(kableExtra)
knitr::kable(df_tt_12) %>%
  kableExtra::kable_styling(full_width = F) %>%
  column_spec(1, bold = T, border_right = T) %>%
  column_spec(3, width = "20em", background = "yellow") %>% 
  row_spec(3:4, bold = T, color = "white", background = "#D7261E") %>% 
  row_spec(0, angle = 10)
Country Primaria Secundaria Secundaria_post Terciaria NA
ESP 213.0135 245.1109 265.7699 282.1070 NA
FRA 184.9306 246.6609 289.0088 304.0075 197.6502
GBR 207.3143 252.9237 263.2052 286.1454 213.1912
ITA 200.7297 257.5339 271.6400 279.7166 NA

Puedes ver las posibilidades de este pkg en su vignette. Fijate que tabla más chula hay allí:

mtcars[1:8, 1:8] %>%
  kbl() %>%
  kable_paper(full_width = F) %>%
  column_spec(2, color = spec_color(mtcars$mpg[1:8]),
              link = "https://haozhu233.github.io/kableExtra/") %>%
  column_spec(6, color = "white",
              background = spec_color(mtcars$drat[1:8], end = 0.7),
              popover = paste("am:", mtcars$am[1:8]))
mpg cyl disp hp drat wt qsec vs
Mazda RX4 21.0 6 160.0 110 3.90 2.620 16.46 0
Mazda RX4 Wag 21.0 6 160.0 110 3.90 2.875 17.02 0
Datsun 710 22.8 4 108.0 93 3.85 2.320 18.61 1
Hornet 4 Drive 21.4 6 258.0 110 3.08 3.215 19.44 1
Hornet Sportabout 18.7 8 360.0 175 3.15 3.440 17.02 0
Valiant 18.1 6 225.0 105 2.76 3.460 20.22 1
Duster 360 14.3 8 360.0 245 3.21 3.570 15.84 0
Merc 240D 24.4 4 146.7 62 3.69 3.190 20.00 1




4. Otros paquetes para tablas

Uno de los paquetes que se pueden usar para generar tablas es pander, pero no lo incluyo en el tutorial porque en realidad, su objetivo es más general, su objetivo es facilitar la exportación de objetos R a pandoc. De hecho, pander puede ser una alternativa a knitr; de hecho, el título de su pagina web es: pander: An R Pandoc Writer.

Si presento, de forma sencilla los paquetes formatable y dos paquetes muy prometedores como flextable y gt, pero existen más posibilidades, más paquetes para hacer tablas en R.



4.1 formatable pkg

El paquete formattable

is designed for applying formatting on vectors and data frames to make data presentation easier, richer, more flexible and hopefully convey more information.

En estos dos post: aquí y aquí explican con detalle las posibilidades del paquete formattable.

Para ilustrar las posibilidades de formattable utilizo uno de los ejemplos de su página web:

library(formattable)
df %>% formattable(list(
  age = color_tile("white", "orange"),
  grade = formatter("span", style = x ~ ifelse(x == "A", 
    style(color = "green", font.weight = "bold"), NA)),
  area(col = c(test1_score, test2_score)) ~ normalize_bar("pink", 0.2),
  final_score = formatter("span",
    style = x ~ style(color = ifelse(rank(-x) <= 3, "green", "gray")),
    x ~ sprintf("%.2f (rank: %02d)", x, rank(-x))),
  registered = formatter("span",
    style = x ~ style(color = ifelse(x, "green", "red")),
    x ~ icontext(ifelse(x, "ok", "remove"), ifelse(x, "Yes", "No")))
))
id name age grade test1_score test2_score final_score registered
1 Bob 28 C 8.9 9.1 9.00 (rank: 06) Yes
2 Ashley 27 A 9.5 9.1 9.30 (rank: 03) No
3 James 30 A 9.6 9.2 9.40 (rank: 02) Yes
4 David 28 C 8.9 9.1 9.00 (rank: 06) No
5 Jenny 29 B 9.1 8.9 9.00 (rank: 06) Yes
6 Hans 29 B 9.3 8.5 8.90 (rank: 08) Yes
7 Leo 27 B 9.3 9.2 9.25 (rank: 04) Yes
8 John 27 A 9.9 9.3 9.60 (rank: 01) No
9 Emily 31 C 8.5 9.1 8.80 (rank: 09) No
10 Lee 30 C 8.6 8.8 8.70 (rank: 10) No


4.2 flextable pkg

El paquete flextable es relativamente nuevo y tiene la ventaja de que provee un entorno para crear de forma relativamente sencilla tablas estadísticas para informes. Una de sus ventajas es que puede generar tablas para documentos html, Word, Powerpoint y pdf. Aquí tienes un bookdown sobre flextable.

Las tablas hechas con flextable pueden exportarse a :

  • Documentos html, Word y PowerPoint hechos a través de ficheros Rmarkdown
  • Documentos Word y Powerpoint hechos con el paquete officer
  • Documentos pdf a través del paquete pagedown
  • Además las flextables pueden ser transformadas a gráficos R o como imágenes (png, pdf y jpeg).

Si quieres ver las posibilidades de este paquete tendrás que ir a su página web. Como ejemplo:

library(flextable)

ft <- flextable::flextable(head(iris), col_keys = c("Sepal.Length", "Sepal.Width", "Petal.Length", "Petal.Width", "Species" ))

ft %>% autofit() %>%  align(align = "center", part = "all")


4.3 gt package

El paquete gt también es relativamente nuevo pero creo que se está convirtiendo en el paquete de referencia para hacer tablas en R. Su página web está aquí. Hay que entender un poco su terminología, pero una vez lo haces, hacer tablas con gt es muy sencillo.

Podemos empezar con una web con algunos ejemplos de tablas hechas con gt o ir a su página web o a su vignette introductoria, o a este post donde se anuncia la aparición de gt(v0.2).

Por su parte, Thomas Mock ha realizado una serie de fantásticos post y presentaciones acerca del paquete gt; por ejemplo: Beautiful tables in R, 10+ Guidelines for Better Tables in R, Functions and Themes for gt tables, Embedding custom HTML in gt tables y gt - a (G)rammar of (T)ables.

Si quieres ver el material de un tutorial de gt diseñado por su desarrollador, Richard Iannone, puedes encontrarlo en este repo.

Hacer una tabla básica con gt es tan fácil como hacer gt(my_df). Veámoslo:

library(gt) #-remotes::install_github("rstudio/gt")
df_tt_4 %>% gt()
Gender Primary Secondary Tertiary Upper_second NA
Female 0.4377224 0.4804831 0.6159875 0.579646 0.8
Male 0.5622776 0.5195169 0.3840125 0.420354 0.2


Nos queda arreglar/tunear/añadir elementos a nuestra tabla. Para ello, lo primero es conocer como se llaman los distintos elementos de una tabla hecha con gt:


La tabla que hemos hecho antes con gt, es una tabla muy básica, sólo tiene dos elementos: the “Column Labels” y the “Table Body” (que es donde están los valores). Vamos a añadir más elementos a nuestra tabla:

  • Añadimos una parte más, “Table header”, con la función tab_header(). Con ella podemos añadir un título y subtitulo a la tabla. Además podemos dar formato al título, usando markdown!!, con la función md()
gt_tbl <- df_tt_4 %>% gt()

gt_tbl <- gt_tbl %>% tab_header(title = md("**Genero y nivel educativo**"),
                      subtitle = md("Porcentaje de *H y M* en cada nivel educativo"))
gt_tbl
Genero y nivel educativo
Porcentaje de H y M en cada nivel educativo
Gender Primary Secondary Tertiary Upper_second NA
Female 0.4377224 0.4804831 0.6159875 0.579646 0.8
Male 0.5622776 0.5195169 0.3840125 0.420354 0.2


  • Añadimos una nota al pie (“source note”) en “Table footer” con la función tab_source_note(). Se pueden poner varias notas al pie.
gt_tbl <- gt_tbl %>% 
          tab_source_note(md("Fuente: datos de [PIAAC](http://www.oecd.org/skills/piaac/)")) %>%
          tab_source_note(md("Obtenidos a partir del paquete RPIAAC"))
  
gt_tbl  
Genero y nivel educativo
Porcentaje de H y M en cada nivel educativo
Gender Primary Secondary Tertiary Upper_second NA
Female 0.4377224 0.4804831 0.6159875 0.579646 0.8
Male 0.5622776 0.5195169 0.3840125 0.420354 0.2
Fuente: datos de PIAAC
Obtenidos a partir del paquete RPIAAC


  • Podemos añadir notas (al pie) en los valores de la tabla. Para ello se usa la función tab_footnote(). La función auxiliar cells_body() se usa para especificar que celdas de datos tendrán la marca de la llamada al pie de página. Se puede incluso usar condiciones para seleccionar que celdas llevarán la footnote.
# Añadimos la misma nota al pie a dos celdas de la tabla
# Para seleccionar las celdas que llevarán la footnote se usa la f. `cell_body()`
gt_tbl <- gt_tbl %>% tab_footnote("Por encima del 60%", cells_body(c(Tertiary, `NA`), rows = 1) )

# Show the gt Table
gt_tbl
Genero y nivel educativo
Porcentaje de H y M en cada nivel educativo
Gender Primary Secondary Tertiary Upper_second NA
Female 0.4377224 0.4804831 1 0.6159875 0.579646 1 0.8
Male 0.5622776 0.5195169 0.3840125 0.420354 0.2
Fuente: datos de PIAAC
Obtenidos a partir del paquete RPIAAC
1 Por encima del 60%


  • The “Stub” es el área a la derecha de la tabla que contiene los nombres de filas. Se puede generar fácilmente un stub con gt(rowname_col = "nombre")

An easy way to generate a Stub part is by specifying a stub column in the gt() function with the rowname_col argument. Alternatively, we can have an input dataset with a column named rowname—this magic column will signal to gt that that column should be used as the stub, making row labels. Let’s add a stub with our islands_tbl dataset by modifying the call to gt():

gt_tbl <- df_tt_4 %>% 
          gt(rowname_col = "Gender") %>% 
          tab_stubhead(label = md("**Género**"))

gt_tbl
Género Primary Secondary Tertiary Upper_second NA
Female 0.4377224 0.4804831 0.6159875 0.579646 0.8
Male 0.5622776 0.5195169 0.3840125 0.420354 0.2


Con gt se pueden hacer muchas más cosas, puedes verlas en su página web, o en esta secuencia de posts de Thomas Mock: 10+ Guidelines for Better Tables in R, Functions and Themes for gt tables, Embedding custom HTML in gt tables y gt - a (G)rammar of (T)ables.


Un ejemplo que me ha gustado es este de Sharla Gelfand en el que añade una columna de imágenes a su tabla. Voy a hacerlo yo con 3 imágenes de Wikimedia Commons y una tabla con datos del data.frame iris.

Para ello, primero, añado una columna con los url’s de las fotos al data.frame iris:

urls_lirios <- c("https://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/Iris_latifolia-Lac_Aule-Laruns-2522~2013_07_29.JPG/1280px-Iris_latifolia-Lac_Aule-Laruns-2522~2013_07_29.JPG",
                 "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Lilium_michiganense_2.jpg/310px-Lilium_michiganense_2.jpg",
                 "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bd/Nacho_Vegas_Fib.jpg/800px-Nacho_Vegas_Fib.jpg")

iris_con_urls <- iris %>% group_by(Species) %>% slice_max(Sepal.Length, n = 1) %>% add_column(urls_lirios) %>% ungroup()

Para después transformar los urls a imágenes con:

iris_con_urls %>% gt() %>% 
  gt::text_transform(locations = cells_body(columns = c(urls_lirios)),
                     fn = function(x) {gt::web_image(x, height = 30)})
Sepal.Length Sepal.Width Petal.Length Petal.Width Species urls_lirios
5.8 4.0 1.2 0.2 setosa
7.0 3.2 4.7 1.4 versicolor
7.9 3.8 6.4 2.0 virginica

En este post se muestra como incluir, no solo imágenes, sino también gráficos ggplot en una tabla hecha con gt. Para ello usa el paquete gtExtras. Por ejemplo está maravilla de tabla:

tt_08_img_02_tabla-ganadores-tour.png



5. Tablas interactivas

Aquí tienes un tutorial exclusivamente dedicado a tablas interactivas. Aquí sólo presentaré algún ejemplo de tabla interactiva con los paquetes DT, reactable y rpivottable.


5.1 DT package

El paquete DT es “a wrapper of the JavaScript library DataTables”. La mejor forma de aprender a hacer tablas con DT es su página web.

El autor de DT es Yihui Xie el desarrollador de knitr. El principal atractivo del paquete es que permite hacer tablas interactivas con funcionalidades como filtrar, paginar, ordenar, etc… los valores de las tablas.

Hacer tablas básicas con DT es muy sencillo. Por ejemplo:


DT::datatable(iris)


Las tablas hechas con DT tienen muchas más posibilidades. Como ejemplo la siguiente tabla.La mejor forma de aprender a hacer tablas con DT es su página web

DT::datatable(iris, filter = 'top', 
              options = list(pageLength = 5, autoWidth = TRUE ))


Una posibilidad de las tablas hechas con DT es que se pueden descargar. Lo explican en detalle aquí:

iris %>% DT::datatable(extensions = 'Buttons', 
               options = list(dom = 'Blfrtip', 
                              buttons = c('copy', 'csv', 'excel', 'pdf', 'print'), 
                              pageLength = 5, autoWidth = TRUE ))



5.2 reactable package

El paquete reactable también permite hacer tablas interactivas, esta vez se basa en la librería de JS React.

El uso básico es tan sencillo como:

reactable::reactable(iris)

Pero se pueden hacer tablas tan fabulosas como esta o esta o esta.



5.3 rpivotTable package

The rpivotTable package is an R htmlwidget built around the pivottable JS library.

El paquete rpivotTable genera tablas que permiten al usuario generar sus propios resultados.

Como las tablas que genera el paquete rpivotTable pueden, según las opciones que utilice el usuario de la tabla, cambiar mucho de tamaño, voy a poner un ejemplo de esta tabla pero al final de este documento, después de la bibliografía.

Crear una tabla con rpivotTable es muy sencillo. Podéis verla tabla al final del documento.

library(rpivotTable) #- remotes::install_github(c("ramnathv/htmlwidgets", "smartinsightsfromdata/rpivotTable"))
rpivotTable(df, rows = "Gender", cols = c("Country"), width = "100%", height = "400px")




6. Tablas para modelos

R es un lenguaje/entorno para hacer análisis estadísticos, así que muchas veces se han de presentar los resultados de estimación de algún modelo estadístico, PERO, los resultados de estimación de estos modelos, en general, no se almacenan en data.frames, sino en listas. ¿Es esto importante? Sí, si lo queremos es presentar los resultados de estimación en una tabla. La razón consiste en que los paquetes para tablas que hemos visto anteriormente (gt, DT, etc …) solo generan tablas de resultados almacenados en data.frames, así que no los podremos usar, al menos directamente, para hacer tablas de resultados asociados a modelos estadísticos. Afortunadamente hay unos cuantos paquetes en R cuyo propósito es precisamente ese, presentar los resultados de estimación de algún modelo o técnica estadística como tablas.

Por ejemplo, vamos a estimar un modelo de regresión lineal. Utilizaremos estos datos:

urla <- "https://raw.githubusercontent.com/perezp44/iris_data/master/data/PIAAC_data_small.csv"
df <- read_csv(urla)

Con ellos estimamos el siguiente modelo lineal:

my_model <- lm(Wage_hour ~ Numeracy_score + Gender , data = df)
my_model
#> 
#> Call:
#> lm(formula = Wage_hour ~ Numeracy_score + Gender, data = df)
#> 
#> Coefficients:
#>    (Intercept)  Numeracy_score      GenderMale  
#>        3.46022         0.02988         0.52874

Generalmente hay funciones específicas para mostrar los resultados de estimación, por ejemplo la función summary():

summary(my_model)
#> 
#> Call:
#> lm(formula = Wage_hour ~ Numeracy_score + Gender, data = df)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -10.903  -3.453  -1.059   2.097 104.179 
#> 
#> Coefficients:
#>                Estimate Std. Error t value             Pr(>|t|)    
#> (Intercept)    3.460222   0.351822   9.835 < 0.0000000000000002 ***
#> Numeracy_score 0.029881   0.001325  22.554 < 0.0000000000000002 ***
#> GenderMale     0.528745   0.134064   3.944            0.0000808 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 5.973 on 7992 degrees of freedom
#>   (27 observations deleted due to missingness)
#> Multiple R-squared:  0.06324,    Adjusted R-squared:  0.063 
#> F-statistic: 269.8 on 2 and 7992 DF,  p-value: < 0.00000000000000022

Los resultados mostrados por summary() permiten hacerse una idea de los resultados de la estimación pero no suelen ser adecuados para presentarlos en un informe o en un artículo científico.

Hay bastantes paquetes que permiten obtener tablas con los resultados de la estimación de modelos estadísticos, por ejemplo, el paquete finalfit y muchos otros, sin embargo, en este tutorial solo se mostrará algún ejemplo para los paquetes stargazer, modelsummary, gtsummary y sjPlot



6.1 Tablas con stargazer

El paquete stargazer permite fácilmente presentar tablas con resultados de regresión. Permite crear tablas en latex, html o ascii. Aquí hay una cheatsheet sobre stargazer

stargazer::stargazer(my_model, type = "html")
Dependent variable:
Wage_hour
Numeracy_score 0.030***
(0.001)
GenderMale 0.529***
(0.134)
Constant 3.460***
(0.352)
Observations 7,995
R2 0.063
Adjusted R2 0.063
Residual Std. Error 5.973 (df = 7992)
F Statistic 269.760*** (df = 2; 7992)
Note: p<0.1; p<0.05; p<0.01


Se pueden presentar los resultados de varios modelos en una tabla. Veámoslo. Para ello vamos a estimar tres modelos y los almacenaremos en una lista:

df <- df %>% mutate(Numeracy_score_b = ifelse(Numeracy_score > mean(Numeracy_score, na.rm = TRUE), 1, 0)) #- binary variable for logit model

my_models <- list()
my_models[['W:  OLS 1']]   <- lm( Wage_hour         ~ Numeracy_score + Gender , df)
my_models[['Nu: OLS 2']]   <- lm( Numeracy_score    ~ Education + Gender , df)
my_models[['Nu: Logit 1']] <- glm( Numeracy_score_b ~ Education + Gender , df, family = binomial())

Una vez hemos estimado los 3 modelos y almacenado sus resultados de estimación en la lista my_models

stargazer::stargazer(my_models, type = "html", title = "Results", align = TRUE)
Results
Dependent variable:
Wage_hour Numeracy_score Numeracy_score_b
OLS OLS logistic
(1) (2) (3)
Numeracy_score 0.030***
(0.001)
EducationSecondary 46.752*** 1.788***
(1.983) (0.134)
EducationTertiary 89.531*** 3.424***
(2.137) (0.143)
EducationUpper_second 75.903*** 2.857***
(2.388) (0.150)
GenderMale 0.529*** 12.947*** 0.448***
(0.134) (1.000) (0.049)
Constant 3.460*** 196.447*** -2.270***
(0.352) (1.955) (0.134)
Observations 7,995 8,014 8,014
R2 0.063 0.229
Adjusted R2 0.063 0.229
Log Likelihood -4,894.853
Akaike Inf. Crit. 9,799.706
Residual Std. Error 5.973 (df = 7992) 44.382 (df = 8009)
F Statistic 269.760*** (df = 2; 7992) 596.336*** (df = 4; 8009)
Note: p<0.1; p<0.05; p<0.01


6.2 Tablas con modelsummary

El paquete modelsummary es más moderno, y también permite hacer tablas resumen de estimaciones de modelos. Por ejemplo:

library(modelsummary) #- remotes::install_github('vincentarelbundock/modelsummary')

mm <- modelsummary::msummary(my_models, title = "Resultados de estimación")
mm
Resultados de estimación
W: OLS 1 Nu: OLS 2 Nu: Logit 1
(Intercept) 3.460 196.447 −2.270
(0.352) (1.955) (0.134)
Numeracy_score 0.030
(0.001)
GenderMale 0.529 12.947 0.448
(0.134) (1.000) (0.049)
EducationSecondary 46.752 1.788
(1.983) (0.134)
EducationTertiary 89.531 3.424
(2.137) (0.143)
EducationUpper_second 75.903 2.857
(2.388) (0.150)
Num.Obs. 7995 8014 8014
R2 0.063 0.229
R2 Adj. 0.063 0.229
AIC 51272.9 83541.3 9799.7
BIC 51300.8 83583.2 9834.7
Log.Lik. −25632.432 −41764.631 −4894.853
RMSE 5.97 44.37 0.46

Tiene muchas más posibilidades.



6.3 Tablas con gtsummary

El paquete gtsummary permite, además de realizar tablas resumen de data.frames, confeccionar tablas de modelos de regresión de forma sencilla y muy configurables.

Como he dicho gtsummary tiene muchas posibilidades, algunas de ellas puedes verlas en este video, en estas transparencias, o en este post.

Como ejemplos de su uso, una tabla resumen de un data.frame:

iris %>% gtsummary::tbl_summary()
Characteristic N = 1501
Sepal.Length 5.80 (5.10, 6.40)
Sepal.Width 3.00 (2.80, 3.30)
Petal.Length 4.35 (1.60, 5.10)
Petal.Width 1.30 (0.30, 1.80)
Species
setosa 50 (33%)
versicolor 50 (33%)
virginica 50 (33%)
1 Median (IQR); n (%)


Ahora una tabla de un modelo de regresión:

gtsummary::tbl_regression(my_model)
Characteristic Beta 95% CI1 p-value
Numeracy_score 0.03 0.03, 0.03 <0.001
Gender
Female
Male 0.53 0.27, 0.79 <0.001
1 CI = Confidence Interval


6.4 Tablas con sjPlot

El paquete sjPlot tiene muchas posibilidades, pero en concreto puede hacer tablas de modelos:

sjPlot::tab_model(my_model)
  Wage hour
Predictors Estimates CI p
(Intercept) 3.46 2.77 – 4.15 <0.001
Numeracy score 0.03 0.03 – 0.03 <0.001
Gender [Male] 0.53 0.27 – 0.79 <0.001
Observations 7995
R2 / R2 adjusted 0.063 / 0.063

Tiene otras utilidades, por ejemplo:

sjPlot::plot_model(my_model)



6.5 Otros paquetes

Hay muchos más paquetes especializados en hacer tablas con resultados de la estimación de modelos. Aquí solo mostraré algo de 3 de ellos, los paquetes report y apaTables, pero existen otros paquetes como:

  • flipRegression. En este post explican como usar sus funciones.

  • papaja, es un paquete para producir documentos y tablas conformes a la American Psychological Association (APA) manuscript guidelines (6th Edition).

  • academicWriteR, está relacionado con papaja. Es un paquete con algunas funciones útiles para escribir informes estadísticos. Por ejemplo la función count_word().


paquete report

Según pone en su página web:

It automatically produces reports of models and dataframes according to best practice guidelines (e.g., APA’s style guide), ensuring standardization and quality in results reporting.

library(report) #- devtools::install_github("neuropsychology/report")
rr <- report(my_model)


as.report_table(rr)


También permite obtener una descripción verbal de los resultados en texto:

as.report_text(rr)


paquete apaTables

Según su página web:

This package creates Word files (.doc files) containing APA style tables for several types of analyses. Using this package minimizes transcription errors and reduces the number commands needed by the user.

library(apaTables) #- install.packages("apaTables",dep = TRUE)
apa.reg.table(my_model)




7. Bibliografía/Recursos:

  • En este hilo de Twitter se habla sobre los mejores paquetes R para hacer tablas.

  • David Keyes señala aquí, que quieres hacer tablas para Word, posiblemente la mejor opción sea el paquete flextable

  • Si lo que necesitas es hacer tablas para documentos pdf, puedes empezar por aquí. Al final remite a este otro documento. También puedes usar este generador de tablas.

  • Si quieres tablas de porcentajes etc… aquí plantean varias posibilidades para hacerlas.

  • Si quieres hacer tablas parecidas a las que se hacen con PROC FREQ de SAS o CROSSTABS en SPSS puedes usar la función CrossTable() del paquete gmodels. En este post muestran otras alternativas.

  • Si necesitas hacer 3-way tables, puedes hacerlas con stats::xtabs(). Para después imprimirla con ftable(my_table). Lo explicanaquí y aquí. Por ejemplo:

my_table <- stats::xtabs(~ Education + Country + Gender, data = df)
stats::ftable(my_table) 

Otra alternativa para tablas de frecuencias de 3 variables es usar janitor::tabyl(X1, X2, X3). Lo explican aquí

df %>% janitor::tabyl(Education, Country, Gender)
  • Más paquetes relacionados con tablas: printr, questionr, …

  • Los paquetes rhandsontable y excelR permiten crear tablas editables por el usuario.

  • El paquete expss hace tablas similares a las de SPSS.

  • En este post hacen un repaso de paquetes que permiten crear tablas con características especiales como que sus valores sean editables por el usuario, que tengan formato condicionado a los valores de las tablas, que se puedan ordenar y filtrar sus valores etc …

  • Un post con las tablas ganadoras del “2020 RStudio Table Contest”.





5.3 Drag & drop pivot tables: rpivotTable

El paquete rpivotTable:

The rpivotTable package is an R htmlwidget built around the pivottable library.


rpivotTable::rpivotTable(df, rows = "Gender", cols = c("Country"), width = "100%", height = "400px")

















  1. En realidad en castellano debería llamarlos cuadros, pero …↩︎

  2. Evidentemente, en R hay muchos otros paquetes para hacer tablas básicas. Algunas de ellas son resumidas en este post↩︎

LS0tCnRpdGxlOiAiVGFibGFzIGVuIGRvY3VtZW50b3MgUm1hcmtkb3duIgphdXRob3I6ICJQZWRybyBKLiBQw6lyZXogKHBlZHJvLmoucGVyZXpAdXYuZXMpLiBVbml2ZXJzaXRhdCBkZSBWYWzDqG5jaWEgPGJyPiA8YnI+IFdlYiBkZWwgY3Vyc286IDxodHRwczovL3BlcmV6cDQ0LmdpdGh1Yi5pby9pbnRyby1kcy0yMi0yMy13ZWIvPiIKZGF0ZTogIk5vdmllbWJyZSBkZSAyMDE5IChhY3R1YWxpemFkbyBlbCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkLSVtLSVZJylgKSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjc3M6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJzdHlsZXNfcGpwLmNzcyIpCiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGV4dG1hdGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgaW5jbHVkZXM6CiAgICAgIGFmdGVyX2JvZHk6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJmb290ZXIuaHRtbCIpIAogICAgICBpbl9oZWFkZXI6IAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImdvb2dsZS1hbmFseXRpY3MuaHRtbCIpIAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImZhdmljb24tc29sLmh0bWwiKQogICAgZGZfcHJpbnQ6IGthYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCi0tLQoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKYGBge3IgY2h1bmstc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAjcmVzdWx0cyA9ICJob2xkIiwKICAgICAgICAgICAgICAgICAgICAgIGNhY2hlID0gRkFMU0UsIGNhY2hlLnBhdGggPSAiL2NhY2hlcy8iLCBjb21tZW50ID0gIiM+IiwKICAgICAgICAgICAgICAgICAgICAgICNmaWcud2lkdGggPSA3LCAjZmlnLmhlaWdodD0gNywKICAgICAgICAgICAgICAgICAgICAgICNvdXQud2lkdGggPSA3LCBvdXQuaGVpZ2h0ID0gNywKICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gVFJVRSwgIGZpZy5zaG93ID0gImhvbGQiLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFzcCA9IDcvOSwgb3V0LndpZHRoID0gIjYwJSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQoKIy0gcGFyYSBtZWpvcmFyIGxvcyBncsOhZmljb3MsIGJ1ZW5vIGVuIHJlYWxpZGFkIHBhcmEgcXVlIHNlIHZlYW4gaWd1YWwgZW4gZGlzdGludG9zIFNPCiMtIGh0dHBzOi8vd3d3Lmp1bXBpbmdyaXZlcnMuY29tL2Jsb2cvci1rbml0ci1tYXJrZG93bi1wbmctcGRmLWdyYXBoaWNzLwprbml0cjo6b3B0c19jaHVuayRzZXQoZGV2ID0gInBuZyIsIGRldi5hcmdzID0gbGlzdCh0eXBlID0gImNhaXJvLXBuZyIpKQpgYGAKCmBgYHtyIG9wdGlvbnMtc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpICMtIHBhcmEgcXVpdGFyIGxhIG5vdGFjacOzbiBjaWVudMOtZmljYQpvcHRpb25zKCJ5YW1sLmV2YWwuZXhwciIgPSBUUlVFKSAjLSBodHRwczovL2dpdGh1Yi5jb20vdmlraW5nL3IteWFtbC9pc3N1ZXMvNDcgIChsbyBwdXNlIHggZWwgcGIgY29uIGVsIHdhcm5pbmcpIEVuIHJlYWxpZGFkIGNyZW8gcXVlIG1lam9yIHNlcsOtYSBwb25lcmxvIGVuIFJQcm9maWxlCmBgYAoKCmBgYHtyIGtsaXBweSwgZWNobyA9IEZBTFNFfQprbGlwcHk6OmtsaXBweShwb3NpdGlvbiA9IGMoInRvcCIsICJyaWdodCIpKSAjLSByZW1vdGVzOjppbnN0YWxsX2dpdGh1Yigicmxlc3VyL2tsaXBweSIpCmBgYAoKLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgMS4gSW50cm9kdWNjacOzbgoKSGFjZXIgY3VhZHJvcy90YWJsYXMgcGFyYSBtb3N0cmFyIHJlc3VsdGFkb3MgZXMgdW5hIHBhcnRlIGltcG9ydGFudGUgKHkgdGltZSBjb25zdW1pbmcpIGRlIGN1YWxxdWllciBpbmZvcm1lIG8gaW52ZXN0aWdhY2nDs24uIEVuIGVzdGUgdHV0b3JpYWwgZXhwbGljYXLDqSBjb21vIHByZXNlbnRhciBudWVzdHJvcyByZXN1bHRhZG9zIGVuIHRhYmxhcyBlbiBkb2N1bWVudG9zICh5YSBzZWFuIGVzdG9zIC5odG1sLCAucGRmIGV0Yy4uKSBjcmVhZG9zIGEgcGFydGlyIGRlIGFyY2hpdm9zIGAuUm1kYC4KCgoKVmVyZW1vcyBxdWUgcHJlc2VudGFyIGxvcyByZXN1bHRhZG9zIChwcmV2aWFtZW50ZSBhbG1hY2VuYWRvcyBlbiB1biBkZikgY29tbyB1biBjdWFkcm8gZW4gZWwgZG9jdW1lbnRvIGZpbmFsIGVzIG11eSBzZW5jaWxsbywgc8OzbG8gdGVuZW1vcyBxdWUgbGxhbWFyIGEgbGEgZnVuY2nDs24gYGthYmxlKClgLCBwZXJvIHNpIHF1ZXJlbW9zIHRlbmVyIG3DoXMgZmxleGliaWxpZGFkIHkgb3BjaW9uZXMgcGFyYSBtZWpvcmFyIG51ZXN0cmFzIHRhYmxhcyB0ZW5kcmVtb3MgcXVlIHVzYXIgb3Ryb3MgcGFxdWV0ZXMgY29tbyBga2FibGVleHRyYWAsIGBmb3JtYXRhYmxlYCwgYGZsZXh0YWJsZWAsIGBEVGAgbyBtw6FzIHJlY2llbnRlbWVudGUgYGd0YCB5IGByZWFjdGFibGVgCgoKRW4gW2VzdGUgaGlsb10oaHR0cHM6Ly90d2l0dGVyLmNvbS9Hb2xkYmVyZ0RhdGEvc3RhdHVzLzEyOTI5MzU0NTQxOTU1MTEzMDIpIGRlIFR3aXR0ZXIgc2UgaGFibMOzIHNvYnJlIGN1YWxlcyBlcmFuIGxvcyBtZWpvcmVzIHBhcXVldGVzIFIgcGFyYSBoYWNlciB0YWJsYXMuIEVuIFtlc3RlIHBvc3RdKGh0dHBzOi8vcmZvcnRoZXJlc3RvZnVzLmNvbS8yMDE5LzExL2hvdy10by1tYWtlLWJlYXV0aWZ1bC10YWJsZXMtaW4tci8pLCBEYXZpZCBLZXllcyBoYWNlIHVuIG11eSBidWVuIHR1dG9yaWFsIHNvYnJlIGxvcyBwYXF1ZXRlcyBkaXNwb25pYmxlcyBwYXJhIGhhY2VyIHRhYmxhcyBlbiBSLgoKQWwgaWd1YWwgcXVlIGNvbiBsb3MgZ3LDoWZpY29zLCBwYXJhIGhhY2VyIGJ1ZW5hcyB0YWJsYXMgXltFbiByZWFsaWRhZCBlbiBjYXN0ZWxsYW5vIGRlYmVyw61hIGxsYW1hcmxvcyBjdWFkcm9zLCBwZXJvIC4uLl0gaGFjZSBmYWx0YSB0ZW5lciBlbiBjdWVudGEgYWxndW5vcyBhc3BlY3Rvcy4gRXN0ZSBbcG9zdF0oaHR0cHM6Ly9wYXVsdmFuZGVybGFrZW4uY29tLzIwMjAvMDkvMDEvMTAtZ3VpZGVsaW5lcy10by1iZXR0ZXItdGFibGUtZGVzaWduLykgcHJvcG9yY2lvbmEgMTAgcmVnbGFzIG8gY29uc2Vqb3MgcGFyYSBnZW5lcmFyIHRhYmxhcyBlZmVjdGl2YXMuIEVuIGVzdGUgb3RybyBbcG9zdF0oaHR0cHM6Ly90aGVtb2NrdXAuYmxvZy9wb3N0cy8yMDIwLTA5LTA0LTEwLXRhYmxlLXJ1bGVzLWluLXIvI2d1aWRlbGluZXMtd2l0aC1ndCksIFRob21hcyBNb2NrIGltcGxlbWVudGEgZXNhcyAxMCByZWdsYXMgY29uIGVsIHBhcXVldGUgYGd0YC4KCkVuIGVsIHNpZ3VpZW50ZSBhcGFydGFkbyB1dGlsaXphcmVtb3MgdW5vcyBkYXRvcyBkZSBlamVtcGxvIHBhcmEgb2J0ZW5lciB1bm9zIHJlc3VsdGFkb3MgcXVlIHF1ZXJlbW9zIG1vc3RyYXIgY29tbyB0YWJsYXMgZW4gbnVlc3RybyBpbmZvcm1lLiBFc3RvIG5vcyBzZXJ2aXLDoSBwYXJhIHByYWN0aWNhciB1biBwb2NvIG3DoXMgY29uIGBkcGx5cmAgeSBhcHJlbmRlciBhIGNyZWFyIHRhYmxhcyBkZXNkZSBkb2N1bWVudG9zIC5SbWQsIHlhIHF1ZSB1bmEgdmV6IHRlbmdhbW9zIGxvcyByZXN1bHRhZG9zIHF1ZSBxdWVyZW1vcyBtb3N0cmFyLCBwcmVzZW50YXJlbW9zIGRpc3RpbnRhcyBwb3NpYmlsaWRhZGVzIHBhcmEgbW9zdHJhcmxvcyBlbiB0YWJsYXMvY3VhZHJvcyBnb29kLWxvb2tpbmcuCgotLS0tLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgoKIyAyLiBSZXN1bHRhZG9zIHBhcmEgdGFibGFzCgoKRW4gZXN0YSBzZWNjacOzbiB1dGlsaXphcmVtb3MgbG8gcXVlIGhlbW9zIGFwcmVuZGlkbyBkZSAqKmRwbHlyKiogIHBhcmEgb2J0ZW5lciBsb3MgcmVzdWx0YWRvcyBxdWUgcXVlcmVtb3MgbW9zdHJhciBlbiBsb3MgY3VhZHJvcy4gQ29uIGRwbHlyIGNhbGN1bGFyZW1vcyBsb3MgZXN0YWTDrXN0aWNvcyBxdWUgcXVlcmVtb3MgbW9zdHJhciBhbG1hY2Vuw6FuZG9sb3MgZW4gdW4gZGF0YWZyYW1lL3RpYmJsZS4gVW5hIHZleiB0ZW5nYW1vcyBsb3MgcmVzdWx0YWRvcyBlbiB1biBkYXRhZnJhbWUgbm9zIHF1ZWRhcsOhIGxhIHRhcmVhIGRlIHByZXNlbnRhcmxvcyBjb21vIGN1YWRyb3MgZW4gZWwgZG9jdW1lbnRvIGZpbmFsLiBFbiB1biBwcmltZXIgbW9tZW50byAoZXN0YSBzZWNjacOzbikgdXRpbGl6YXJlbW9zIGxhIGZ1bmNpw7NuIGBrbml0cjo6a2FibGUoKWAgcGFyYSBoYWNlciB0YWJsYXMgeSBlbiBsb3Mgc2lndWllbnRlcyBhcGFydGFkb3MgcHJlc2VudGFyZW1vcyBwYXF1ZXRlcyBlc3BlY2lhbG1lbnRlIGRpc2XDsWFkb3MgcGFyYSBoYWNlciB0YWJsYXMuCgoKIyMgMi4xIERhdG9zIHF1ZSB1c2FyZW1vcwoKCklsdXN0cmFyZW1vcyBsYSBjcmVhY2nDs24gZGUgdGFibGFzIGNvbiBkYXRvcyBkZSBsYSBzZWd1bmRhIHJvbmRhIGRlICoqUElBQ0MqKiAoUHJvZ3JhbWEgcGFyYSBsYSBFdmFsdWFjacOzbiBJbnRlcm5hY2lvbmFsIGRlIGxhcyBDb21wZXRlbmNpYXMgZGUgbG9zIGFkdWx0b3MgZGUgbGEgT0NERS4gSW5mb3JtYWNpw7NuIGRldGFsbGFkYSBzb2JyZSBlbCBwcm9ncmFtYSBQSUFBQyBwdWVkZSBlbmNvbnRyYXJzZSBlbiBzdSBwYWdpbmEgd2ViIDxodHRwOi8vd3d3Lm9lY2Qub3JnL3NraWxscy9waWFhYy8+LiBMb3MgZGF0b3Mgc2Ugb2J0dXZpZXJvbiBhIHBhcnRpciBkZWwgcGFxdWV0ZSBbUlBJQUFDXShodHRwczovL2dpdGh1Yi5jb20vamNwZXJuaWFzL1JQSUFBQykgY3JlYWRvIFBvciBKb3NlIEMuIFBlcm5pYXMsIHBhcmEgZmluYWxtZW50ZSBzZWxlY2Npb25hciAxMCB2YXJpYWJsZXMgZGUgNCBwYcOtc2VzOiBFU1AsIEZSQSwgR0JSLCBJVEEuIE5vIHJlY3VlcmRvIGVsIGHDsW8gZGUgbG9zIGRhdG9zISEKCgpDYXJnYW1vcyBsb3MgZGF0b3MgcXVlIHZhbW9zIGEgdXRpbGl6YXI6CgpgYGB7ciwgZWNobyA9IFRSVUV9Cm15X3VybCA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3BlcmV6cDQ0L2lyaXNfZGF0YS9tYXN0ZXIvZGF0YS9QSUFBQ19kYXRhX3NtYWxsLmNzdiIKZGYgPC0gcmVhZF9jc3YobXlfdXJsKQpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFfQpkZl9jb3B5IDwtIGRmCmBgYAoKCk1pcmFtb3MgdW4gcG9jbyBsb3MgZGF0b3M6CgpgYGB7cn0KIy0gcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoInBlcmV6cDQ0L3BqcHYyMDIwLjAxIikKZGZfYWEgPC0gcGpwdjIwMjAuMDE6OnBqcF9mX2VzdGFkaXN0aWNvc19iYXNpY29zKGRmKSAjLSBlc3RhZMOtc3RpY29zIGLDoXNpY29zIGRlbCBkZgpkZl9iYiA8LSBwanB2MjAyMC4wMTo6cGpwX2ZfdW5pcXVlX3ZhbHVlcyhkZikgICAgICAgICMtIHZhbG9yZXMgw7puaWNvcyBkZSBjYWRhIHZhcmlhYmxlIGRlIGRmCmBgYAoKCjxicj4KCiMjIDIuMiAgT2J0ZW5pZW5kbyByZXN1bHRhZG9zIChjb24gZHBseXIpCgoKUGFyYSBpbmljaWFyIG51ZXN0cmEgYW5kYWR1cmEgY29uIGxhcyB0YWJsYXMsIGludGVudGFyZW1vcyBtb3N0cmFyIGVuIHRhYmxhcyBhbGd1bm9zIHJlc3VsdGFkb3MuIFBvciBlamVtcGxvOgoKPGJyPgoKLSAxLiBDdWFkcm8gY29uIGVsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyBkZSBjYWRhIHBhw61zIChsbyBoYWNlbW9zIGRlIDIgZm9ybWFzKQoKYGBge3IsIGVjaG8gPSBUUlVFfQpkZl90dF8xIDwtIGRmICU+JSAKICAgICAgICAgICBjb3VudChDb3VudHJ5KQoKZGZfdHRfMSA8LSBkZiAlPiUgCiAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSkgJT4lIAogICAgICAgICAgIHN1bW1hcmlzZShOTiA9IG4oKSkKCmtuaXRyOjprYWJsZShkZl90dF8xKQpgYGAKCgo8YnI+CgotIDIuIEHDsWFkaXIgYSBsYSB0YWJsYSBlbCAlIHF1ZSByZXByZXNlbnRhIGNhZGEgcGHDrXMgZW4gZWwgVG90YWwgKHRhbWJpw6luIGxvIGhhY2Vtb3MgZGUgMiBmb3JtYXMpCgoKYGBge3IsIGVjaG8gPSBUUlVFfQpkZl90dF8yIDwtIGRmICU+JSAKICAgICAgICAgICBncm91cF9ieShDb3VudHJ5KSAlPiUgCiAgICAgICAgICAgc3VtbWFyaXNlKE5OID0gbigpLCBwZXJjZW50ID0gbigpL25yb3coLikgKQoKZGZfdHRfMiA8LSBkZiAlPiUgCiAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSkgJT4lCiAgICAgICAgICAgc3VtbWFyaXNlIChOTiA9IG4oKSkgJT4lCiAgICAgICAgICAgbXV0YXRlKHBlcmNlbnQgPSBOTiAvIHN1bShOTikpCgprbml0cjo6a2FibGUoZGZfdHRfMikKYGBgCgo8YnI+CgotIDMuIFBvcmNlbnRhamUgZGUgSG9tYnJlcyB5IE11amVyZXMgZW4gY2FkYSBwYcOtcwoKYGBge3IsIGVjaG8gPSBUUlVFfQpkZl90dF8zIDwtIGRmICU+JSAKICAgICAgICAgIGNvdW50KENvdW50cnksIEdlbmRlcikgJT4lCiAgICAgICAgICBncm91cF9ieShDb3VudHJ5KSAlPiUKICAgICAgICAgIG11dGF0ZShwZXJjZW50ID0gbiAvIHN1bShuKSkgJT4lCiAgICAgICAgICBzZWxlY3QoLW4pICU+JQogICAgICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IEdlbmRlciwgCiAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHBlcmNlbnQpICU+JSAKICAgICAgICAgIHVuZ3JvdXAoKQoKa25pdHI6OmthYmxlKGRmX3R0XzMpCmBgYAoKCgoKYGBge3IsIGV2YWwgPSBGQUxTRSwgZWNobyA9IEZBTFNFfQojLSB1biBjaHVuayBxIHVzYWJhIGVuIFNVRQp6ejIgPC0gIGRmICU+JQogIGNvdW50KENvdW50cnksIEdlbmRlcikgJT4lCiAgZ3JvdXBfYnkoQ291bnRyeSkgJT4lICAgICAgICAgIAogIG11dGF0ZShwcm9wID0gcHJvcC50YWJsZShuKSkgJT4lIAogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBHZW5kZXIsIAogICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gbjpwcm9wKSAlPiUgCiAgdW5ncm91cCgpIAprbml0cjo6a2FibGUoenoyLCBkaWdpdHMgPSA0KQoKY2MgPC0gZGZfQ1YgJT4lIHNlbGVjdChJU0NDQUEsIFNUQVRVUywgQ09OVFJBVEFOVEUueCkgJT4lIAogIHNwbGl0KC4kU1RBVFVTKSAlPiUgICBwdXJycjo6bWFwKHRpZHlyOjpzcHJlYWQsIElTQ0NBQSwgQ09OVFJBVEFOVEUueCkKYGBgCgoKCgo8YnI+CgotIDQuIFBvcmNlbnRhamUgZGUgSG9tYnJlcyB5IE11amVyZXMgZW4gY2FkYSBuaXZlbCBlZHVjYXRpdm8KCmBgYHtyLCBlY2hvID0gVFJVRX0KZGZfdHRfNCA8LSBkZiAlPiUgCiAgICAgICAgICAgY291bnQoRWR1Y2F0aW9uLCBHZW5kZXIpICU+JSAKICAgICAgICAgICBncm91cF9ieShFZHVjYXRpb24pICU+JQogICAgICAgICAgIG11dGF0ZShwZXJjZW50ID0gbiAvIHN1bShuKSkgJT4lCiAgICAgICAgICAgc2VsZWN0KC1uKSAlPiUKICAgICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gRWR1Y2F0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHBlcmNlbnQpICU+JSAKICAgICAgICAgICB1bmdyb3VwKCkKCmtuaXRyOjprYWJsZShkZl90dF80KQpgYGAKCgo8YnI+CgoKCi0gNS4gQ2FsY3VsYW1vcyBlbCBzYWxhcmlvIG1lZGlvIHBvciBwYcOtcwoKCmBgYHtyLCBlY2hvID0gVFJVRX0KZGZfdHRfNSA8LSBkZiAlPiUgCiAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSkgJT4lIAogICAgICAgICAgIHN1bW1hcmlzZShXX2hvcmFfbWVkaW8gPSBtZWFuKFdhZ2VfaG91ciAsIG5hLnJtID0gVFJVRSksIAogICAgICAgICAgICAgICAgICAgICBXX21lc19tZWRpbyAgPSBtZWFuKFdhZ2VfbW9udGgsIG5hLnJtID0gVFJVRSkpICU+JSB1bmdyb3VwKCkKCgprbml0cjo6a2FibGUoZGZfdHRfNSkKYGBgCgo8YnI+CgotIDYuIENhbGN1bGFtb3MgbGEgbWVkaWEsIGVsIG3DrW5pbW8sIGVsIG3DoXhpbW8geSBsYSBkZXN2aWFjacOzbiB0w61waWNhIGRlIFdhZ2VfbW9udGgKCmBgYHtyLCBlY2hvID0gVFJVRX0KZGZfdHRfNiA8LSBkZiAlPiUgCiAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSkgJT4lIAogICAgICAgICAgIHN1bW1hcmlzZShXX21lZGlvICA9IG1lYW4oV2FnZV9tb250aCwgbmEucm0gPSBUUlVFKSAsCiAgICAgICAgICAgICAgICAgICAgIFdfbWluaW1vID0gbWluKFdhZ2VfbW9udGgsIG5hLnJtID0gVFJVRSkgICwKICAgICAgICAgICAgICAgICAgICAgV19tYXhpbW8gPSBtYXgoV2FnZV9tb250aCwgbmEucm0gPSBUUlVFKSAgLAogICAgICAgICAgICAgICAgICAgICBXX3NkID0gc2QoV2FnZV9tb250aCwgbmEucm0gPSBUUlVFKSApICU+JSAKICAgICAgICAgIHVuZ3JvdXAoKQoKa25pdHI6OmthYmxlKGRmX3R0XzYpCmBgYAoKPGJyPgoKLSA3LiBTYWxhcmlvIG1lZGlvIGVuIEVzcGHDsWEgKEhvbWJyZXMgeSBNdWplcmVzKQoKCmBgYHtyLCBlY2hvID0gVFJVRX0KZGZfdHRfNyA8LSBkZiAlPiUgCiAgICAgICAgICAgZmlsdGVyKENvdW50cnkgPT0gIkVTUCIpICU+JSAKICAgICAgICAgICBncm91cF9ieShHZW5kZXIpICU+JSAKICAgICAgICAgICBzdW1tYXJpc2UoV19ob3JhX21lZGlvID0gbWVhbihXYWdlX2hvdXIsIG5hLnJtID0gVFJVRSksIAogICAgICAgICAgICAgICAgICAgICBXX21lc19tZWRpbyA9IG1lYW4oV2FnZV9tb250aCwgbmEucm0gPSBUUlVFKSApICU+JQogICAgICAgICAgIHVuZ3JvdXAoKQoKa25pdHI6OmthYmxlKGRmX3R0XzcpCmBgYAoKPGJyPgoKLSA4LiBTYWxhcmlvIG1lZGlvIGVuIGxvcyA0IGxvcyBwYcOtc2VzIChIb21icmVzIHkgTXVqZXJlcykKCmBgYHtyLCBlY2hvID0gVFJVRX0KZGZfdHRfOCA8LSBkZiAlPiUgCiAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSwgR2VuZGVyKSAlPiUgCiAgICAgICAgICAgc3VtbWFyaXNlKFdfaG9yYV9tZWRpbyA9IG1lYW4oV2FnZV9ob3VyLCBuYS5ybSA9IFRSVUUpLCAKICAgICAgICAgICAgICAgICAgICAgV19tZXNfbWVkaW8gPSBtZWFuKFdhZ2VfbW9udGgsIG5hLnJtID0gVFJVRSkgKSAlPiUgCiAgICAgICAgICAgdW5ncm91cCgpCgprbml0cjo6a2FibGUoZGZfdHRfOCkKYGBgCgo8YnI+CgotIDkuIMK/Q3VhbnRvIG3DoXMgY29icmFuIGxvcyBob21icmVzIChlbiAlKT8KCmBgYHtyLCBlY2hvID0gVFJVRX0KZGZfdHRfOSA8LSBkZiAlPiUgCiAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSwgR2VuZGVyKSAlPiUgCiAgICAgICAgICAgc3VtbWFyaXNlKFdfbWVzX21lZGlvID0gbWVhbihXYWdlX21vbnRoLCBuYS5ybSA9IFRSVUUpKSAlPiUgCiAgICAgICAgICAgdW5ncm91cCgpICU+JSAKICAgICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gR2VuZGVyLCB2YWx1ZXNfZnJvbSA9IFdfbWVzX21lZGlvKSAlPiUgCiAgICAgICAgICAgbXV0YXRlKGRpZl9XID0gTWFsZS1GZW1hbGUsIGRpZl9wZXJjZW50X1cgPSBkaWZfVy9GZW1hbGUpCgprbml0cjo6a2FibGUoZGZfdHRfOSkKYGBgCgo8YnI+CgoKLSAxMC4gTnVtZXJhY3kgU2NvcmUgcG9yIHBhw61zIHkgbml2ZWwgZGUgZXN0dWRpb3MuIExhIHRhYmxhIG5vcyB2YSBhIHNhbGlyIGFsYXJnYWRhCgoKYGBge3IsIGVjaG8gPSBUUlVFfQpkZl90dF8xMCA8LSBkZiAlPiUgCiAgICAgICAgICAgIGdyb3VwX2J5KENvdW50cnksIEVkdWNhdGlvbikgJT4lIAogICAgICAgICAgICBzdW1tYXJpc2UoTnVtZXJhY3lfbWVkaWEgPSBtZWFuKE51bWVyYWN5X3Njb3JlLCBuYS5ybSA9IFRSVUUpKSAlPiUgCiAgICAgICAgICAgIHVuZ3JvdXAoKSAKCmtuaXRyOjprYWJsZShkZl90dF8xMCkKYGBgCgoKLSAxMS4gSGFnYW1vcyBsYSBhbnRlcmlvciB0YWJsYSBtw6FzIGFuY2hhIChVbmEgY29sdW1uYSBwYXJhIGNhZGEgcGHDrXMpCgoKYGBge3IsIGVjaG8gPSBUUlVFfQpkZl90dF8xMSA8LSBkZl90dF8xMCAlPiUgCiAgICAgICAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBFZHVjYXRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IE51bWVyYWN5X21lZGlhKQoKa25pdHI6OmthYmxlKGRmX3R0XzExKQpgYGAKCjxicj4KCkJpZW4sIG5vIGVzdMOhIG1hbCwgcGVybyBxdWllcm8gb3JkZW5hciBsYSB0YWJsYSBkZSBtZW5vciBhIG1heW9yIG5pdmVsbyBlZHVjYXRpdm8KCi0gMTIuIFJlb3JkZW5hbmRvIGxhIHRhYmxhIDExCgpSZXN1bHRhIHF1ZSBlbiBlbCBjdWFkcm8sIGxhcyBjYXRlZ29yw61hcyBkZSBuaXZlbGVzIGRlIGVzdHVkaW8gc2UgaGFuIG9yZGVuYWRvIGFsZmFiw6l0aWNhbWVudGUgcGVybyBxdWllcm8gb3JkZW5hcmxhIGRlIG1lbm9yIGEgbWF5b3Igbml2ZWwgZGUgZXN0dWRpb3MuIFNlIHB1ZWRlIGhhY2VyIGRlIG90cmFzIG1hbmVyYXMsIHBlcm8gbG8gaGFyZW1vcyB1c2FuZG8gKipmYWN0b3JlcyoqLgoKTGEgdmFyaWFibGUgRWR1Y2F0aW9uIHRpZW5lIDQgY2F0ZWdvcsOtYXM6IFByaW1hcnksIFNlY29uZGFyeSwgVXBwZXItc2Vjb25kIHkgVGVydGlhcnk7ICoqQURFTcOBUyoqIGVzdGFzIGNhdGVnb3LDrWFzIGVzdMOhbi9zb24gZmlqYXMsIG5vIGNhbWJpYW4uIEVuIGVzdG9zIGNhc29zIGVzIG1lam9yIHRyYXRhciBlc2FzIHZhcmlhYmxlcyBjb21vICoqZmFjdG9yZXMqKjsgUEVSTyBzaSBtaXJhbW9zIGxhIGNsYXNlIGRlIGxhIHZhcmlhYmxlICJFZHVjYXRpb24iIGNvbiBgY2xhc3MoZGYkRWR1Y2F0aW9uKWAgdmVyZW1vcyBxdWUgZXMgYHIgY2xhc3MoZGYkRWR1Y2F0aW9uKWAsIGFzw60gcXVlIHZhbW9zIGEgY29udmVydGlyIGxhIHZhcmlhYmxlICJFZHVjYXRpb24iIGEgZmFjdG9yLgoKClBvZHLDrWFtb3MgdXNhciBsYSBmdW5jacOzbiBgYXMuZmFjdG9yKClgLCBwZXJvIHVuYSB2ZXogbcOhcyB1c2FyZW1vcyBwa2dzIGRlbCB0aWR5dmVyc2U7IGVuIGVzdGUgY2FzbyBlbCBwa2cgIioqZm9yY2F0cyoqIiB5IGxhIGZ1bmNpw7NuIGBhc19mYWN0b3IoKWAKCgpgYGB7ciwgZWNobyA9IFRSVUV9CmRmIDwtIGRmICU+JSAKICAgICAgbXV0YXRlKEVkdWNhdGlvbiA9IGZvcmNhdHM6OmFzX2ZhY3RvcihFZHVjYXRpb24pKQoKbGV2ZWxzKGRmJEVkdWNhdGlvbikKYGBgCgo8YnI+CgpDb21vIHZlbW9zLCBsYSB2YXJpYWJsZSBgRWR1Y2F0aW9uYCwgcXVlIGFob3JhIGVzIHVuIGZhY3RvciwgdGllbmUgY3VhdHJvIGNhdGVnb3LDrWFzIG8gImxldmVscyIKClJlc3VsdGEgcXVlIGVsIG5vbWJyZSBkZSBsYXMgNCBjYXRlZ29yw61hcyBlc3TDoSBlbiBpbmdsw6lzIHkgbG8gcXVlcmVtb3MgZW4gY2FzdGVsbGFuby4gTG8gaGFyZW1vcyBjb24gYGZvcmNhdHM6OmZjdF9yZWNvZGUoKWAuIFBhcmEgc2FiZXIgdW4gcG9jbyBtw6FzIHNvYnJlIGNvbW8gbWFuaXB1bGFyIGZhY3RvcmVzIGRlYmVzIGlyIFthcXXDrV0oaHR0cDovL3I0ZHMuaGFkLmNvLm56L2ZhY3RvcnMuaHRtbCkKCgpgYGB7ciwgZWNobyA9IFRSVUV9CmRmIDwtIGRmICU+JSAKICAgICAgbXV0YXRlKEVkdWNhdGlvbiA9IGZvcmNhdHM6OmZjdF9yZWNvZGUoRWR1Y2F0aW9uLAogICAgICAgICAgICAgICAgICAgICJQcmltYXJpYSIgICAgICAgICA9ICJQcmltYXJ5IiwKICAgICAgICAgICAgICAgICAgICAiU2VjdW5kYXJpYSIgICAgICAgPSAiU2Vjb25kYXJ5IiwgCiAgICAgICAgICAgICAgICAgICAgIlNlY3VuZGFyaWFfcG9zdCIgID0gIlVwcGVyX3NlY29uZCIsCiAgICAgICAgICAgICAgICAgICAgIlRlcmNpYXJpYSIgICAgICAgID0gIlRlcnRpYXJ5IiApKSAKCmRmICU+JSBjb3VudChFZHVjYXRpb24pCmBgYAoKPGJyPgoKQ29tbyB2b2x2ZW1vcyBhIHZlciwgbGEgdmFyaWFibGUgIkVkdWNhdGlvbiIsIHF1ZSBhaG9yYSBlcyB1biBmYWN0b3IgdGllbmUgNCAibGV2ZWxzIiBwZXJvIGVzdMOhbiBvcmRlbmFkb3MgZGUgZm9ybWEgcXVlIG5vIG5vcyBjb252aWVuZSwgcXVlcmVtb3MgY2FtYmlhciBlbCBvcmRlbiBkZSBsb3MgZmFjdG9yZXMgIGNvbiBgZm9yY2FzdDo6ZmN0X3JlbGV2ZWwoKWAgcGFyYSBxdWUgc2UgbXVlc3RyZW4gbG9zIGN1YXRybyBncnVwb3MgZWR1Y2F0aXZvcyBkZSBtZW5vciBhIG1heW9yIG5pdmVsLgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFfQpkZiA8LSBkZiAlPiUgCiAgICAgIG11dGF0ZShFZHVjYXRpb24gPSBmb3JjYXRzOjpmY3RfcmVsZXZlbChFZHVjYXRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICJQcmltYXJpYSIsICJTZWN1bmRhcmlhIiwgIlNlY3VuZGFyaWFfcG9zdCIpKSAKCmRmICU+JSBjb3VudChFZHVjYXRpb24pCmBgYAoKQ29tbyBzZSBwdWVkZSB2ZXIgZW4gZWwgY3VhZHJvIGRlIGFycmliYSwgeWEgaGVtb3MgY2FtYmlhZG8gZWwgb3JkZW4gZGUgbG9zICJsZXZlbHMiIHBhcmEgcXVlIHNlIG11ZXN0cmVuIGRlIG1lbm9yIGEgbWF5b3Igbml2ZWwgZWR1Y2F0aXZvLCBhc8OtIHF1ZSB2b2x2YW1vcyBhIHJlaGFjZXIgZWwgY3VhZHJvIGNvbiBlbCBOdW1lcmFjeSBTY29yZSBwb3IgcGHDrXNlczoKCgpgYGB7ciwgZWNobyA9IFRSVUV9CmRmX3R0XzEyIDwtIGRmICU+JSAKICAgICAgICAgICAgZ3JvdXBfYnkoQ291bnRyeSwgRWR1Y2F0aW9uKSAlPiUgCiAgICAgICAgICAgIHN1bW1hcmlzZShOdW1lcmFjeV9tZWRpYSA9IG1lYW4oTnVtZXJhY3lfc2NvcmUsIG5hLnJtID0gVFJVRSkpICU+JSAKICAgICAgICAgICAgdW5ncm91cCgpICU+JSAgCiAgICAgICAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBFZHVjYXRpb24sIAogICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IE51bWVyYWN5X21lZGlhKSAKCmtuaXRyOjprYWJsZShkZl90dF8xMikKYGBgCgoKCjxicj4KCiMjIDIuMyAgT2J0ZW5pZW5kbyByZXN1bHRhZG9zIChjb24gamFuaXRvcikKCgpTaSBsb3MgcmVzdWx0YWRvcyBxdWUgcXVlcmVtb3MgbW9zdHJhciBzb24gY29zYXMgYsOhc2ljYXMgY29tbyBwb3JjZW50YWplcywgZnJlY3VlbmNpYXMgZXRjLi4uIG9idmlhbWVudGUgcG9kZW1vcyBjYWxjdWxhcmxvcyBub3NvdHJvcyBjb24gYGRwbHlyYCwgcGVybyBwYXJhIGVzdG9zIGNhc29zIGVsIHBhcXVldGUgYGphbml0b3JgLCBjb25jcmV0YW1lbnRlIGxhIGZ1bmNpw7NuIGBqYW5pdG9yOjp0YWJ5bCgpYCBub3MgZmFjaWxpdGEgbXVjaG8gbGEgdGFyZWFeW0V2aWRlbnRlbWVudGUsIGVuIFIgaGF5IG11Y2hvcyBvdHJvcyBwYXF1ZXRlcyBwYXJhIGhhY2VyIHRhYmxhcyBiw6FzaWNhcy4gQWxndW5hcyBkZSBlbGxhcyBzb24gcmVzdW1pZGFzIGVuIGVzdGUgW3Bvc3RdKGh0dHBzOi8vZGFiYmxpbmd3aXRoZGF0YS53b3JkcHJlc3MuY29tLzIwMTcvMTIvMjAvbXktZmF2b3VyaXRlLXItcGFja2FnZS1mb3ItZnJlcXVlbmN5LXRhYmxlcy8pe3RhcmdldD0iX2JsYW5rIn1dLiBQYXJhIHZlciB0b2RhcyBsYXMgcG9zaWJpbGlkYWRlcyBxdWUgdGllbmUgZWwgcGFxdWV0ZSBgamFuaXRvcmAgcHVlZGVzIGlyIGEgc3UgcMOhZ2luYSB3ZWIgW2FxdcOtXShodHRwOi8vc2ZpcmtlLmdpdGh1Yi5pby9qYW5pdG9yLyl7dGFyZ2V0PSJfYmxhbmsifS4KCgoKMSB5IDIuIEN1YWRybyBjb24gZWwgbsO6bWVybyBkZSBvYnNlcnZhY2lvbmVzIGRlIGNhZGEgcGHDrXMKCgpgYGB7cn0KZGZfdHRfMV9qIDwtIGRmICU+JSBqYW5pdG9yOjp0YWJ5bChDb3VudHJ5KQprbml0cjo6a2FibGUoZGZfdHRfMV9qKQpgYGAKCjMuIFBvcmNlbnRhamUgZGUgSG9tYnJlcyB5IE11amVyZXMgZW4gY2FkYSBwYcOtcwoKCmBgYHtyfQpkZl90dF8zX2ogPC0gZGYgJT4lIGphbml0b3I6OnRhYnlsKENvdW50cnksIEdlbmRlcikKCmRmX3R0XzNfaiA8LSBkZiAlPiUgamFuaXRvcjo6dGFieWwoQ291bnRyeSwgR2VuZGVyKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgamFuaXRvcjo6YWRvcm5fcGVyY2VudGFnZXMoZGVub21pbmF0b3IgPSAicm93IikKZGZfdHRfM19qCmBgYAoKNC4gUG9yY2VudGFqZSBkZSBIb21icmVzIHkgTXVqZXJlcyBlbiBjYWRhIG5pdmVsIGVkdWNhdGl2bwoKCmBgYHtyfQpkZl90dF80X2ogPC0gZGYgJT4lIGphbml0b3I6OnRhYnlsKEdlbmRlciwgRWR1Y2F0aW9uKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgamFuaXRvcjo6IGFkb3JuX3BlcmNlbnRhZ2VzKGRlbm9taW5hdG9yID0gImNvbCIpCmRmX3R0XzRfagpgYGAKCgpQYXJhIHZlciB0b2RhcyBsYXMgcG9zaWJpbGlkYWRlcyBxdWUgdGllbmUgZWwgcGFxdWV0ZSBgamFuaXRvcmAgcHVlZGVzIGlyIGEgc3UgcMOhZ2luYSB3ZWIgW2FxdcOtXShodHRwOi8vc2ZpcmtlLmdpdGh1Yi5pby9qYW5pdG9yLykKCjxicj4KSGF5IG90cm9zIHBhcXVldGVzIGNvbiBmdW5jaW9uYWxpZGFkZXMgcGFyZWNpZGFzIGEgYGphbml0b3JgLiBQb3IgZWplbXBsbywgZWwgcGFxdWV0ZSBbYGZyZXF0YWJsZXNgXShodHRwczovL2dpdGh1Yi5jb20vYnJhZC1jYW5uZWxsL2ZyZXF0YWJsZXMpIG8gZWwgcGFxdWV0ZSBbYHNqbWlzY2BdKGh0dHBzOi8vc3RyZW5nZWphY2tlLmdpdGh1Yi5pby9zam1pc2MvaW5kZXguaHRtbCkuCgotLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgMy4gVGFibGFzIGNvbiBga25pdHI6OmthYmxlKClgCgo8YnI+CgpCdWVubywgeWEgc2FiZW1vcywgYXVucXVlIGVuIHJlYWxpZGFkIGxvIHZpbW9zIGFsIHByZXNlbnRhciBgZHBseXJgIHkgYHRpZHlyYCwgY29tbyBpciBnZW5lcmFuZG8gdW4gZGF0YWZyYW1lIGNvbiBsb3MgZXN0YWTDrXN0aWNvcyBkZXNjcmlwdGl2b3MgcXVlIG5vcyBpbnRlcmVzYSBtb3N0cmFyLiBFbiByZWFsaWRhZCBlc29zIGRmIHlhIHNvbiB0YWJsYXMgZGUgZGF0b3MgcXVlIHB1ZWRvIGV4cG9ydGFyIGEgRXhjZWwgcGFyYSB0cmFiYWphcmxhcyBhIG51ZXN0cm8gZ3VzdG87IHBlcm8gZW4gZWwgY3Vyc28gaGFjZW1vcyB0b2RvIGRlc2RlIFIuCgpZYSBoYWLDqWlzIHZpc3RvIHF1ZSBjb24gbGEgZnVuY2nDs24gYGtuaXRyOjprYWJsZSgpYCBwdWVkbyBtb3N0cmFybGFzIGVuIG1pIGRvY3VtZW50byBmaW5hbCAgc8OzbG8gdGVuZ28gcXVlIGVqZWN1dGFyIGxvIHNpZ3VpZW50ZTogYGtuaXRyOjprYWJsZShteV9kZilgLgoKPGJyPgoKRW4gbG9zIHByw7N4aW1vcyBhcGFydGFkb3MgbW9zdHJhcmVtb3MgYWxndW5vcyBwYXF1ZXRlcyBlc3BlY8OtZmljb3MgcGFyYSBmb3JtYXRlYXIgbG9zIGN1YWRyb3MgeSBoYWNlcmxvcyBCT05JVE9TLCBwZXJvIHRhbWJpw6luIGNvbnZpZW5lIHNhYmVyIHF1ZSAqKmthYmxlKCkgdGFtYmnDqW4gdGllbmUgb3BjaW9uZXMqKiBwYXJhIHBlcnNvbmFsaXphciB1biBwb2NvIG51ZXN0cmFzIHRhYmxhcy4gUG9yIGVqZW1wbG86CgpgYGB7ciwgZWNobyA9IFRSVUV9CmtuaXRyOjprYWJsZShkZl90dF8xMiwgCiAgICAgICAgICAgICBhbGlnbiA9ICJjIiwgCiAgICAgICAgICAgICBjYXB0aW9uID0gIk51bWVyYWN5IFNjb3JlIHBvciBwYcOtcyIsCiAgICAgICAgICAgICBkaWdpdHMgPSAyLCAKICAgICAgICAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChkZWNpbWFsLm1hcmsgPSAiLCIsIGJpZy5tYXJrID0gIi4iKSkKYGBgCgoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDMuMSBga2FibGVFeHRyYWAgcGtnCgpbYGthYmxlRXh0cmFgXShodHRwOi8vaGFvemh1MjMzLmdpdGh1Yi5pby9rYWJsZUV4dHJhLykgZXMgdW4gcGFja2FnZSBkZSBxdWUgcGVybWl0ZSBtZWpvcmFyIGxhcyB0YWJsYXMgY3JlYWRhcyBjb24gYGtuaXRyOjprYWJsZSgpYC4gCgpMYSBkZXNjcmlwY2nDs24gZGUgYGthYmxlRXh0cmFgIGVuIENSQU4gZXM6IAoKPiBCdWlsZCBjb21wbGV4IEhUTUwgb3IgJ0xhVGVYJyB0YWJsZXMgdXNpbmcgJ2thYmxlKCknIGZyb20gJ2tuaXRyJyBhbmQgdGhlIHBpcGluZyBzeW50YXggZnJvbSAnbWFncml0dHInLiBGdW5jdGlvbiAna2FibGUoKScgaXMgYSBsaWdodCB3ZWlnaHQgdGFibGUgZ2VuZXJhdG9yIGNvbWluZyBmcm9tICdrbml0cicuIFRoaXMgcGFja2FnZSBzaW1wbGlmaWVzIHRoZSB3YXkgdG8gbWFuaXB1bGF0ZSB0aGUgSFRNTCBvciAnTGFUZVgnIGNvZGVzIGdlbmVyYXRlZCBieSAna2FibGUoKScgYW5kIGFsbG93cyB1c2VycyB0byBjb25zdHJ1Y3QgY29tcGxleCB0YWJsZXMgYW5kIGN1c3RvbWl6ZSBzdHlsZXMgdXNpbmcgYSByZWFkYWJsZSBzeW50YXguCgoKW0FxdcOtXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMva2FibGVFeHRyYS92aWduZXR0ZXMvYXdlc29tZV90YWJsZV9pbl9odG1sLmh0bWwpIHRpZW5lcyBsYSB2aWduZXR0ZSBwYXJhIGVsYWJvcmFyIHRhYmxhcyBwYXJhIGRvY3VtZW50b3MgaHRtbC4KCgpQYXJhIG1lam9yYXIgbGFzIHRhYmxhcyB1c2FyZW1vcyBsYSBmdW5jacOzbiBga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygpYDoKCgoKYGBge3J9CmtuaXRyOjprYWJsZShkZl90dF8xMikgJT4lIAogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpCmBgYAoKClNlIGxlIHB1ZWRlIHBvbmVyIHVuIHNjcm9sbDoKCmBgYHtyfQpkZl90dF8xMiAlPiUKICBrbml0cjo6a2FibGUoKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZvbnRfc2l6ZSA9IDExKSAlPiUKICBrYWJsZUV4dHJhOjpzY3JvbGxfYm94KHdpZHRoID0gIjUwJSIsIGhlaWdodCA9ICI2MCUiKQpgYGAKCjxicj4KCgpTZSBwdWVkZSBpbmNsdXNvIHBvbmVyIGxhcyB0YWJsYXMganVudG8gY29uIGVsIHRleHRvLgoKCgpgYGB7cn0Ka25pdHI6OmthYmxlKGRmX3R0XzEyKSAlPiUgCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICJmbG9hdF9yaWdodCIpCmBgYAoKPGJyPjxicj4KClBhcmEgZWxsbyB0aWVuZXMgcXVlIHVzYXIgbGEgb3BjacOzbiBgcG9zaXRpb24gPSAiZmxvYXRfcmlnaHRgIGRlbnRybyBkZSBga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygpYC4KCgo8YnI+PGJyPjxicj4KCgpTaSB0ZW5lbW9zIHVuYSB0YWJsYSBtdXkgbGFyZ2EsIHBvZGVtb3MgZmlqYXIgZWwgZW5jYWJlemFtaWVudG8gZGUgbGEgdGFibGEgY29uIGBmaXhlZF90aGVhZGAKCmBgYHtyfQprbml0cjo6a2FibGUoZGZfdHRfMTIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZml4ZWRfdGhlYWQgPSBsaXN0KGVuYWJsZWQgPSBULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gImxpZ2h0Ymx1ZSIpKQpgYGAKClNlIHB1ZWRlbiBmb3JtYXRlYXIgY29sdW1uYXMgcG9yIHNlcGFyYWRvIGNvbiBga2FibGVFeHRyYTo6Y29sdW1uX3NwZWMoKWAsIG8gbGFzIGZpbGFzIGNvbiBga2FibGVFeHRyYTo6cm93X3NwZWMoKWAuIExhIHRhYmxhIG5vIG5vcyBzYWxkcsOhIG11eSBjaHVsYSBwZXJvIG5vcyBzaXJ2ZSBwYXJhIHZlciBsYXMgcG9zaWJpbGlkYWRlcyBkZSBga2FibGVfc3R5bGluZygpYC4KCgpgYGB7cn0KbGlicmFyeShrYWJsZUV4dHJhKQprbml0cjo6a2FibGUoZGZfdHRfMTIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYpICU+JQogIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBULCBib3JkZXJfcmlnaHQgPSBUKSAlPiUKICBjb2x1bW5fc3BlYygzLCB3aWR0aCA9ICIyMGVtIiwgYmFja2dyb3VuZCA9ICJ5ZWxsb3ciKSAlPiUgCiAgcm93X3NwZWMoMzo0LCBib2xkID0gVCwgY29sb3IgPSAid2hpdGUiLCBiYWNrZ3JvdW5kID0gIiNENzI2MUUiKSAlPiUgCiAgcm93X3NwZWMoMCwgYW5nbGUgPSAxMCkKYGBgCgoKCgpQdWVkZXMgdmVyIGxhcyBwb3NpYmlsaWRhZGVzIGRlIGVzdGUgcGtnIGVuIHN1IFt2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2thYmxlRXh0cmEvdmlnbmV0dGVzL2F3ZXNvbWVfdGFibGVfaW5faHRtbC5odG1sKS4gRmlqYXRlIHF1ZSB0YWJsYSBtw6FzIGNodWxhIGhheSBhbGzDrToKCmBgYHtyfQptdGNhcnNbMTo4LCAxOjhdICU+JQogIGtibCgpICU+JQogIGthYmxlX3BhcGVyKGZ1bGxfd2lkdGggPSBGKSAlPiUKICBjb2x1bW5fc3BlYygyLCBjb2xvciA9IHNwZWNfY29sb3IobXRjYXJzJG1wZ1sxOjhdKSwKICAgICAgICAgICAgICBsaW5rID0gImh0dHBzOi8vaGFvemh1MjMzLmdpdGh1Yi5pby9rYWJsZUV4dHJhLyIpICU+JQogIGNvbHVtbl9zcGVjKDYsIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gc3BlY19jb2xvcihtdGNhcnMkZHJhdFsxOjhdLCBlbmQgPSAwLjcpLAogICAgICAgICAgICAgIHBvcG92ZXIgPSBwYXN0ZSgiYW06IiwgbXRjYXJzJGFtWzE6OF0pKQpgYGAKCgoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMgNC4gT3Ryb3MgcGFxdWV0ZXMgcGFyYSB0YWJsYXMKClVubyBkZSBsb3MgcGFxdWV0ZXMgcXVlIHNlIHB1ZWRlbiB1c2FyIHBhcmEgZ2VuZXJhciB0YWJsYXMgZXMgW2BwYW5kZXJgXShodHRwOi8vcmFwcG9ydGVyLmdpdGh1Yi5pby9wYW5kZXIvKSwgcGVybyBubyBsbyBpbmNsdXlvIGVuIGVsIHR1dG9yaWFsIHBvcnF1ZSBlbiByZWFsaWRhZCwgc3Ugb2JqZXRpdm8gZXMgbcOhcyBnZW5lcmFsLCBzdSBvYmpldGl2byBlcyBmYWNpbGl0YXIgbGEgZXhwb3J0YWNpw7NuIGRlIG9iamV0b3MgUiBhIGBwYW5kb2NgLiBEZSBoZWNobywgYHBhbmRlcmAgcHVlZGUgc2VyIHVuYSBhbHRlcm5hdGl2YSBhIGBrbml0cmA7IGRlIGhlY2hvLCBlbCB0w610dWxvIGRlIHN1IHBhZ2luYSB3ZWIgZXM6IGBwYW5kZXI6IEFuIFIgUGFuZG9jIFdyaXRlcmAuCgpTaSBwcmVzZW50bywgZGUgZm9ybWEgc2VuY2lsbGEgbG9zIHBhcXVldGVzIGBmb3JtYXRhYmxlYCB5IGRvcyBwYXF1ZXRlcyBtdXkgcHJvbWV0ZWRvcmVzIGNvbW8gYGZsZXh0YWJsZWAgeSBgZ3RgLCBwZXJvIGV4aXN0ZW4gbcOhcyBwb3NpYmlsaWRhZGVzLCBtw6FzIHBhcXVldGVzIHBhcmEgaGFjZXIgdGFibGFzIGVuIFIuCgoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDQuMSBgZm9ybWF0YWJsZWAgcGtnCgpFbCBwYXF1ZXRlIFtgZm9ybWF0dGFibGVgXShodHRwczovL2dpdGh1Yi5jb20vcmVua3VuLWtlbi9mb3JtYXR0YWJsZSkKCj4gaXMgZGVzaWduZWQgZm9yIGFwcGx5aW5nIGZvcm1hdHRpbmcgb24gdmVjdG9ycyBhbmQgZGF0YSBmcmFtZXMgdG8gbWFrZSBkYXRhIHByZXNlbnRhdGlvbiBlYXNpZXIsIHJpY2hlciwgbW9yZSBmbGV4aWJsZSBhbmQgaG9wZWZ1bGx5IGNvbnZleSBtb3JlIGluZm9ybWF0aW9uLgoKRW4gZXN0b3MgZG9zIHBvc3Q6IFthcXXDrV0oaHR0cHM6Ly93d3cuZGlzcGxheXIuY29tL2Zvcm1hdHRhYmxlLz91dG1fbWVkaXVtPUZlZWQmdXRtX3NvdXJjZT1TeW5kaWNhdGlvbikgeSBbYXF1w61dKGh0dHBzOi8vd3d3LmxpdHRsZW1pc3NkYXRhLmNvbS9ibG9nL3ByZXR0eXRhYmxlcykgZXhwbGljYW4gY29uIGRldGFsbGUgbGFzIHBvc2liaWxpZGFkZXMgZGVsIHBhcXVldGUgYGZvcm1hdHRhYmxlLmAKCgpQYXJhIGlsdXN0cmFyIGxhcyBwb3NpYmlsaWRhZGVzIGRlIGBmb3JtYXR0YWJsZWAgdXRpbGl6byB1bm8gZGUgbG9zIGVqZW1wbG9zIGRlIHN1IFtww6FnaW5hIHdlYl0oaHR0cHM6Ly9yZW5rdW4ta2VuLmdpdGh1Yi5pby9mb3JtYXR0YWJsZS8pOgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KZGYgPC0gZGF0YS5mcmFtZSgKICBpZCA9IDE6MTAsCiAgbmFtZSA9IGMoIkJvYiIsICJBc2hsZXkiLCAiSmFtZXMiLCAiRGF2aWQiLCAiSmVubnkiLCAKICAgICJIYW5zIiwgIkxlbyIsICJKb2huIiwgIkVtaWx5IiwgIkxlZSIpLCAKICBhZ2UgPSBjKDI4LCAyNywgMzAsIDI4LCAyOSwgMjksIDI3LCAyNywgMzEsIDMwKSwKICBncmFkZSA9IGMoIkMiLCAiQSIsICJBIiwgIkMiLCAiQiIsICJCIiwgIkIiLCAiQSIsICJDIiwgIkMiKSwKICB0ZXN0MV9zY29yZSA9IGMoOC45LCA5LjUsIDkuNiwgOC45LCA5LjEsIDkuMywgOS4zLCA5LjksIDguNSwgOC42KSwKICB0ZXN0Ml9zY29yZSA9IGMoOS4xLCA5LjEsIDkuMiwgOS4xLCA4LjksIDguNSwgOS4yLCA5LjMsIDkuMSwgOC44KSwKICBmaW5hbF9zY29yZSA9IGMoOSwgOS4zLCA5LjQsIDksIDksIDguOSwgOS4yNSwgOS42LCA4LjgsIDguNyksCiAgcmVnaXN0ZXJlZCA9IGMoVFJVRSwgRkFMU0UsIFRSVUUsIEZBTFNFLCBUUlVFLCBUUlVFLCBUUlVFLCBGQUxTRSwgRkFMU0UsIEZBTFNFKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKCmBgYHtyfQpsaWJyYXJ5KGZvcm1hdHRhYmxlKQpkZiAlPiUgZm9ybWF0dGFibGUobGlzdCgKICBhZ2UgPSBjb2xvcl90aWxlKCJ3aGl0ZSIsICJvcmFuZ2UiKSwKICBncmFkZSA9IGZvcm1hdHRlcigic3BhbiIsIHN0eWxlID0geCB+IGlmZWxzZSh4ID09ICJBIiwgCiAgICBzdHlsZShjb2xvciA9ICJncmVlbiIsIGZvbnQud2VpZ2h0ID0gImJvbGQiKSwgTkEpKSwKICBhcmVhKGNvbCA9IGModGVzdDFfc2NvcmUsIHRlc3QyX3Njb3JlKSkgfiBub3JtYWxpemVfYmFyKCJwaW5rIiwgMC4yKSwKICBmaW5hbF9zY29yZSA9IGZvcm1hdHRlcigic3BhbiIsCiAgICBzdHlsZSA9IHggfiBzdHlsZShjb2xvciA9IGlmZWxzZShyYW5rKC14KSA8PSAzLCAiZ3JlZW4iLCAiZ3JheSIpKSwKICAgIHggfiBzcHJpbnRmKCIlLjJmIChyYW5rOiAlMDJkKSIsIHgsIHJhbmsoLXgpKSksCiAgcmVnaXN0ZXJlZCA9IGZvcm1hdHRlcigic3BhbiIsCiAgICBzdHlsZSA9IHggfiBzdHlsZShjb2xvciA9IGlmZWxzZSh4LCAiZ3JlZW4iLCAicmVkIikpLAogICAgeCB+IGljb250ZXh0KGlmZWxzZSh4LCAib2siLCAicmVtb3ZlIiksIGlmZWxzZSh4LCAiWWVzIiwgIk5vIikpKQopKQpgYGAKCgotLS0tLS0tLS0tLS0tLQoKPGJyPgoKIyMgNC4yIGBmbGV4dGFibGVgIHBrZwoKRWwgcGFxdWV0ZSBbYGZsZXh0YWJsZWBdKGh0dHBzOi8vZGF2aWRnb2hlbC5naXRodWIuaW8vZmxleHRhYmxlL2FydGljbGVzL292ZXJ2aWV3Lmh0bWwpIGVzIHJlbGF0aXZhbWVudGUgbnVldm8geSB0aWVuZSBsYSB2ZW50YWphIGRlIHF1ZSBwcm92ZWUgdW4gZW50b3JubyBwYXJhIGNyZWFyIGRlIGZvcm1hIHJlbGF0aXZhbWVudGUgc2VuY2lsbGEgdGFibGFzIGVzdGFkw61zdGljYXMgcGFyYSBpbmZvcm1lcy4gVW5hIGRlIHN1cyB2ZW50YWphcyBlcyBxdWUgcHVlZGUgZ2VuZXJhciB0YWJsYXMgcGFyYSBkb2N1bWVudG9zIGh0bWwsIFdvcmQsIFBvd2VycG9pbnQgeSBwZGYuIFtBcXXDrV0oaHR0cHM6Ly9hcmRhdGEtZnIuZ2l0aHViLmlvL2ZsZXh0YWJsZS1ib29rLykgdGllbmVzIHVuIGJvb2tkb3duIHNvYnJlIGZsZXh0YWJsZS4KCkxhcyB0YWJsYXMgaGVjaGFzIGNvbiBgZmxleHRhYmxlYCBwdWVkZW4gZXhwb3J0YXJzZSBhIDoKCiAgLSBEb2N1bWVudG9zIGh0bWwsIFdvcmQgeSBQb3dlclBvaW50IGhlY2hvcyBhIHRyYXbDqXMgZGUgZmljaGVyb3MgUm1hcmtkb3duICAKICAtIERvY3VtZW50b3MgV29yZCB5IFBvd2VycG9pbnQgaGVjaG9zIGNvbiBlbCBwYXF1ZXRlIFtgb2ZmaWNlcmBdKGh0dHBzOi8vZGF2aWRnb2hlbC5naXRodWIuaW8vb2ZmaWNlci8pCiAgLSBEb2N1bWVudG9zIHBkZiBhIHRyYXbDqXMgZGVsIHBhcXVldGUgW2BwYWdlZG93bmBdKGh0dHBzOi8vZ2l0aHViLmNvbS9yc3R1ZGlvL3BhZ2Vkb3duKSAgICAKICAtIEFkZW3DoXMgbGFzIGZsZXh0YWJsZXMgcHVlZGVuIHNlciB0cmFuc2Zvcm1hZGFzIGEgZ3LDoWZpY29zIFIgbyBjb21vIGltw6FnZW5lcyAocG5nLCBwZGYgeSBqcGVnKS4KICAKU2kgcXVpZXJlcyB2ZXIgbGFzIHBvc2liaWxpZGFkZXMgZGUgZXN0ZSBwYXF1ZXRlIHRlbmRyw6FzIHF1ZSBpciBhIHN1IFtww6FnaW5hIHdlYl0oaHR0cHM6Ly9kYXZpZGdvaGVsLmdpdGh1Yi5pby9mbGV4dGFibGUvYXJ0aWNsZXMvb3ZlcnZpZXcuaHRtbCkuIENvbW8gZWplbXBsbzoKCmBgYHtyfQpsaWJyYXJ5KGZsZXh0YWJsZSkKCmZ0IDwtIGZsZXh0YWJsZTo6ZmxleHRhYmxlKGhlYWQoaXJpcyksIGNvbF9rZXlzID0gYygiU2VwYWwuTGVuZ3RoIiwgIlNlcGFsLldpZHRoIiwgIlBldGFsLkxlbmd0aCIsICJQZXRhbC5XaWR0aCIsICJTcGVjaWVzIiApKQoKZnQgJT4lIGF1dG9maXQoKSAlPiUgIGFsaWduKGFsaWduID0gImNlbnRlciIsIHBhcnQgPSAiYWxsIikKYGBgCgoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCgojIyA0LjMgYGd0YCBwYWNrYWdlCgpFbCBwYXF1ZXRlIGBndGAgdGFtYmnDqW4gZXMgcmVsYXRpdmFtZW50ZSBudWV2byBwZXJvIGNyZW8gcXVlIHNlIGVzdMOhIGNvbnZpcnRpZW5kbyBlbiBlbCBwYXF1ZXRlIGRlIHJlZmVyZW5jaWEgcGFyYSBoYWNlciB0YWJsYXMgZW4gUi4gU3UgcMOhZ2luYSB3ZWIgZXN0w6EgW2FxdcOtXShodHRwczovL2d0LnJzdHVkaW8uY29tLykuIEhheSBxdWUgZW50ZW5kZXIgdW4gcG9jbyBzdSB0ZXJtaW5vbG9nw61hLCBwZXJvIHVuYSB2ZXogbG8gaGFjZXMsIGhhY2VyIHRhYmxhcyBjb24gYGd0YCBlcyBtdXkgc2VuY2lsbG8uCgpQb2RlbW9zIGVtcGV6YXIgY29uIHVuYSB3ZWIgY29uIFthbGd1bm9zIGVqZW1wbG9zXShodHRwczovL2ZybTE3ODkuZ2l0aHViLmlvL2d0X2V4YW1wbGVzL2luZGV4Lmh0bWwpIGRlIHRhYmxhcyBoZWNoYXMgY29uIGBndGAgbyBpciBhIHN1IFtww6FnaW5hIHdlYl0oaHR0cHM6Ly9ndC5yc3R1ZGlvLmNvbS8pIG8gYSBzdSBbdmlnbmV0dGUgaW50cm9kdWN0b3JpYV0oaHR0cHM6Ly9ndC5yc3R1ZGlvLmNvbS9hcnRpY2xlcy9pbnRyby1jcmVhdGluZy1ndC10YWJsZXMuaHRtbCksIG8gYSBbZXN0ZSBwb3N0XShodHRwczovL2Jsb2cucnN0dWRpby5jb20vMjAyMC8wNC8wOC9ncmVhdC1sb29raW5nLXRhYmxlcy1ndC0wLTIvKSBkb25kZSBzZSBhbnVuY2lhIGxhIGFwYXJpY2nDs24gZGUgYGd0KHYwLjIpYC4KClBvciBzdSBwYXJ0ZSwgW1Rob21hcyBNb2NrXShodHRwczovL3R3aXR0ZXIuY29tL3Rob21hc19tb2NrP2xhbmc9ZXMpIGhhIHJlYWxpemFkbyB1bmEgc2VyaWUgZGUgZmFudMOhc3RpY29zIHBvc3QgeSBwcmVzZW50YWNpb25lcyBhY2VyY2EgZGVsIHBhcXVldGUgYGd0YDsgcG9yIGVqZW1wbG86IFtCZWF1dGlmdWwgdGFibGVzIGluIFJdKGh0dHBzOi8vdGhlbW9ja3VwLmJsb2cvc3RhdGljL3NsaWRlcy9pbnRyby10YWJsZXMuaHRtbCMxKSwgWzEwKyBHdWlkZWxpbmVzIGZvciBCZXR0ZXIgVGFibGVzIGluIFJdKGh0dHBzOi8vdGhlbW9ja3VwLmJsb2cvcG9zdHMvMjAyMC0wOS0wNC0xMC10YWJsZS1ydWxlcy1pbi1yLyksIFtGdW5jdGlvbnMgYW5kIFRoZW1lcyBmb3IgZ3QgdGFibGVzXShodHRwczovL3RoZW1vY2t1cC5ibG9nL3Bvc3RzLzIwMjAtMDktMjYtZnVuY3Rpb25zLWFuZC10aGVtZXMtZm9yLWd0LXRhYmxlcy8pLCBbRW1iZWRkaW5nIGN1c3RvbSBIVE1MIGluIGd0IHRhYmxlc10oaHR0cHM6Ly90aGVtb2NrdXAuYmxvZy9wb3N0cy8yMDIwLTEwLTMxLWVtYmVkZGluZy1jdXN0b20tZmVhdHVyZXMtaW4tZ3QtdGFibGVzLykgeSBbZ3QgLSBhIChHKXJhbW1hciBvZiAoVClhYmxlc10oaHR0cHM6Ly90aGVtb2NrdXAuYmxvZy9wb3N0cy8yMDIwLTA1LTE2LWd0LWEtZ3JhbW1lci1vZi10YWJsZXMvKS4KClNpIHF1aWVyZXMgdmVyIGVsIG1hdGVyaWFsIGRlIHVuIHR1dG9yaWFsIGRlIGBndGAgZGlzZcOxYWRvIHBvciBzdSBkZXNhcnJvbGxhZG9yLCBbUmljaGFyZCBJYW5ub25lXShodHRwczovL2dpdGh1Yi5jb20vcmljaC1pYW5ub25lKSwgcHVlZGVzIGVuY29udHJhcmxvIGVuIFtlc3RlIHJlcG9dKGh0dHBzOi8vZ2l0aHViLmNvbS9yaWNoLWlhbm5vbmUvZ3Qtd29ya3Nob3AtMjAyMCkuCgoKSGFjZXIgdW5hIHRhYmxhIGLDoXNpY2EgY29uIGBndGAgZXMgdGFuIGbDoWNpbCBjb21vIGhhY2VyIGBndChteV9kZilgLiBWZcOhbW9zbG86CgpgYGB7cn0KbGlicmFyeShndCkgIy1yZW1vdGVzOjppbnN0YWxsX2dpdGh1YigicnN0dWRpby9ndCIpCmRmX3R0XzQgJT4lIGd0KCkKYGBgCgo8YnI+CgpOb3MgcXVlZGEgYXJyZWdsYXIvdHVuZWFyL2HDsWFkaXIgZWxlbWVudG9zIGEgbnVlc3RyYSB0YWJsYS4gUGFyYSBlbGxvLCBsbyBwcmltZXJvIGVzIGNvbm9jZXIgY29tbyBzZSBsbGFtYW4gbG9zIGRpc3RpbnRvcyBlbGVtZW50b3MgZGUgdW5hIHRhYmxhIGhlY2hhIGNvbiBgZ3RgOgoKYGBge3IgLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBUUlVFLCBvdXQud2lkdGggPSAiODAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1hZ2VuZXMiLCAidHRfMDhfaW1nXzAxX3RhYmxhcy1ndC1wYXJ0ZXMuc3ZnIikpCmBgYAoKPGJyPgoKTGEgdGFibGEgcXVlIGhlbW9zIGhlY2hvIGFudGVzIGNvbiBgZ3RgLCBlcyB1bmEgdGFibGEgbXV5IGLDoXNpY2EsIHPDs2xvIHRpZW5lIGRvcyBlbGVtZW50b3M6IHRoZSAiQ29sdW1uIExhYmVscyIgeSB0aGUgIlRhYmxlIEJvZHkiIChxdWUgZXMgZG9uZGUgZXN0w6FuIGxvcyB2YWxvcmVzKS4gVmFtb3MgYSBhw7FhZGlyIG3DoXMgZWxlbWVudG9zIGEgbnVlc3RyYSB0YWJsYToKCgotIEHDsWFkaW1vcyB1bmEgcGFydGUgbcOhcywgIlRhYmxlIGhlYWRlciIsIGNvbiBsYSBmdW5jacOzbiBgdGFiX2hlYWRlcigpYC4gQ29uIGVsbGEgcG9kZW1vcyBhw7FhZGlyIHVuIHTDrXR1bG8geSBzdWJ0aXR1bG8gYSBsYSB0YWJsYS4gQWRlbcOhcyBwb2RlbW9zIGRhciBmb3JtYXRvIGFsIHTDrXR1bG8sICoqdXNhbmRvIG1hcmtkb3duISEqKiwgY29uIGxhIGZ1bmNpw7NuIGBtZCgpYAoKCmBgYHtyfQpndF90YmwgPC0gZGZfdHRfNCAlPiUgZ3QoKQoKZ3RfdGJsIDwtIGd0X3RibCAlPiUgdGFiX2hlYWRlcih0aXRsZSA9IG1kKCIqKkdlbmVybyB5IG5pdmVsIGVkdWNhdGl2byoqIiksCiAgICAgICAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9IG1kKCJQb3JjZW50YWplIGRlICpIIHkgTSogZW4gY2FkYSBuaXZlbCBlZHVjYXRpdm8iKSkKZ3RfdGJsCmBgYAoKPGJyPgoKLSBBw7FhZGltb3MgdW5hIG5vdGEgYWwgcGllICgic291cmNlIG5vdGUiKSBlbiAiVGFibGUgZm9vdGVyIiBjb24gbGEgZnVuY2nDs24gYHRhYl9zb3VyY2Vfbm90ZSgpYC4gU2UgcHVlZGVuIHBvbmVyIHZhcmlhcyBub3RhcyBhbCBwaWUuCgoKYGBge3J9Cmd0X3RibCA8LSBndF90YmwgJT4lIAogICAgICAgICAgdGFiX3NvdXJjZV9ub3RlKG1kKCJGdWVudGU6IGRhdG9zIGRlIFtQSUFBQ10oaHR0cDovL3d3dy5vZWNkLm9yZy9za2lsbHMvcGlhYWMvKSIpKSAlPiUKICAgICAgICAgIHRhYl9zb3VyY2Vfbm90ZShtZCgiT2J0ZW5pZG9zIGEgcGFydGlyIGRlbCBwYXF1ZXRlIFJQSUFBQyIpKQogIApndF90YmwgIApgYGAKCjxicj4KCi0gUG9kZW1vcyBhw7FhZGlyIG5vdGFzIChhbCBwaWUpIGVuIGxvcyB2YWxvcmVzIGRlIGxhIHRhYmxhLiBQYXJhIGVsbG8gc2UgdXNhIGxhIGZ1bmNpw7NuIGB0YWJfZm9vdG5vdGUoKWAuIExhIGZ1bmNpw7NuIGF1eGlsaWFyIGBjZWxsc19ib2R5KClgIHNlIHVzYSBwYXJhIGVzcGVjaWZpY2FyIHF1ZSBjZWxkYXMgZGUgZGF0b3MgdGVuZHLDoW4gbGEgbWFyY2EgZGUgbGEgbGxhbWFkYSBhbCBwaWUgZGUgcMOhZ2luYS4gU2UgcHVlZGUgaW5jbHVzbyB1c2FyIGNvbmRpY2lvbmVzIHBhcmEgc2VsZWNjaW9uYXIgcXVlIGNlbGRhcyBsbGV2YXLDoW4gbGEgZm9vdG5vdGUuCgpgYGB7cn0KIyBBw7FhZGltb3MgbGEgbWlzbWEgbm90YSBhbCBwaWUgYSBkb3MgY2VsZGFzIGRlIGxhIHRhYmxhCiMgUGFyYSBzZWxlY2Npb25hciBsYXMgY2VsZGFzIHF1ZSBsbGV2YXLDoW4gbGEgZm9vdG5vdGUgc2UgdXNhIGxhIGYuIGBjZWxsX2JvZHkoKWAKZ3RfdGJsIDwtIGd0X3RibCAlPiUgdGFiX2Zvb3Rub3RlKCJQb3IgZW5jaW1hIGRlbCA2MCUiLCBjZWxsc19ib2R5KGMoVGVydGlhcnksIGBOQWApLCByb3dzID0gMSkgKQoKIyBTaG93IHRoZSBndCBUYWJsZQpndF90YmwKYGBgCgo8YnI+CgotIFRoZSAiU3R1YiIgZXMgZWwgw6FyZWEgYSBsYSBkZXJlY2hhIGRlIGxhIHRhYmxhIHF1ZSBjb250aWVuZSBsb3Mgbm9tYnJlcyBkZSBmaWxhcy4gU2UgcHVlZGUgZ2VuZXJhciBmw6FjaWxtZW50ZSB1biBzdHViIGNvbiBgZ3Qocm93bmFtZV9jb2wgPSAibm9tYnJlIilgCgpBbiBlYXN5IHdheSB0byBnZW5lcmF0ZSBhIFN0dWIgcGFydCBpcyBieSBzcGVjaWZ5aW5nIGEgc3R1YiBjb2x1bW4gaW4gdGhlIGd0KCkgZnVuY3Rpb24gd2l0aCB0aGUgcm93bmFtZV9jb2wgYXJndW1lbnQuIEFsdGVybmF0aXZlbHksIHdlIGNhbiBoYXZlIGFuIGlucHV0IGRhdGFzZXQgd2l0aCBhIGNvbHVtbiBuYW1lZCByb3duYW1l4oCUdGhpcyBtYWdpYyBjb2x1bW4gd2lsbCBzaWduYWwgdG8gZ3QgdGhhdCB0aGF0IGNvbHVtbiBzaG91bGQgYmUgdXNlZCBhcyB0aGUgc3R1YiwgbWFraW5nIHJvdyBsYWJlbHMuIExldOKAmXMgYWRkIGEgc3R1YiB3aXRoIG91ciBpc2xhbmRzX3RibCBkYXRhc2V0IGJ5IG1vZGlmeWluZyB0aGUgY2FsbCB0byBndCgpOgoKCmBgYHtyfQpndF90YmwgPC0gZGZfdHRfNCAlPiUgCiAgICAgICAgICBndChyb3duYW1lX2NvbCA9ICJHZW5kZXIiKSAlPiUgCiAgICAgICAgICB0YWJfc3R1YmhlYWQobGFiZWwgPSBtZCgiKipHw6luZXJvKioiKSkKCmd0X3RibApgYGAKCjxicj4KCkNvbiBgZ3RgIHNlIHB1ZWRlbiBoYWNlciBtdWNoYXMgbcOhcyBjb3NhcywgcHVlZGVzIHZlcmxhcyBlbiBzdSBbYHDDoWdpbmEgd2ViYF0oaHR0cHM6Ly9ndC5yc3R1ZGlvLmNvbS8pLCBvIGVuIGVzdGEgc2VjdWVuY2lhIGRlIHBvc3RzIGRlIFRob21hcyBNb2NrOiBbMTArIEd1aWRlbGluZXMgZm9yIEJldHRlciBUYWJsZXMgaW4gUl0oaHR0cHM6Ly90aGVtb2NrdXAuYmxvZy9wb3N0cy8yMDIwLTA5LTA0LTEwLXRhYmxlLXJ1bGVzLWluLXIvKSwgW0Z1bmN0aW9ucyBhbmQgVGhlbWVzIGZvciBndCB0YWJsZXNdKGh0dHBzOi8vdGhlbW9ja3VwLmJsb2cvcG9zdHMvMjAyMC0wOS0yNi1mdW5jdGlvbnMtYW5kLXRoZW1lcy1mb3ItZ3QtdGFibGVzLyksIFtFbWJlZGRpbmcgY3VzdG9tIEhUTUwgaW4gZ3QgdGFibGVzXShodHRwczovL3RoZW1vY2t1cC5ibG9nL3Bvc3RzLzIwMjAtMTAtMzEtZW1iZWRkaW5nLWN1c3RvbS1mZWF0dXJlcy1pbi1ndC10YWJsZXMvKSB5IFtndCAtIGEgKEcpcmFtbWFyIG9mIChUKWFibGVzXShodHRwczovL3RoZW1vY2t1cC5ibG9nL3Bvc3RzLzIwMjAtMDUtMTYtZ3QtYS1ncmFtbWVyLW9mLXRhYmxlcy8pLgoKPGJyPgoKVW4gZWplbXBsbyBxdWUgbWUgaGEgZ3VzdGFkbyBlcyBbZXN0ZV0oaHR0cHM6Ly9naXRodWIuY29tL3NoYXJsYWdlbGZhbmQvdHdvZnVuY3Rpb25zbW9zdGRheXMvdHJlZS9tYXN0ZXIvMjAyMC8wNS8xNikgZGUgW1NoYXJsYSBHZWxmYW5kXShodHRwczovL2dpdGh1Yi5jb20vc2hhcmxhZ2VsZmFuZCkgZW4gZWwgcXVlIGHDsWFkZSB1bmEgY29sdW1uYSBkZSBpbcOhZ2VuZXMgYSBzdSB0YWJsYS4gVm95IGEgaGFjZXJsbyB5byBjb24gMyBpbcOhZ2VuZXMgZGUgV2lraW1lZGlhIENvbW1vbnMgeSB1bmEgdGFibGEgY29uIGRhdG9zIGRlbCBkYXRhLmZyYW1lIGlyaXMuCgpQYXJhIGVsbG8sIHByaW1lcm8sIGHDsWFkbyB1bmEgY29sdW1uYSBjb24gbG9zICB1cmwncyBkZSBsYXMgZm90b3MgYWwgZGF0YS5mcmFtZSBgaXJpc2A6CgpgYGB7cn0KdXJsc19saXJpb3MgPC0gYygiaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy90aHVtYi8yLzJkL0lyaXNfbGF0aWZvbGlhLUxhY19BdWxlLUxhcnVucy0yNTIyfjIwMTNfMDdfMjkuSlBHLzEyODBweC1JcmlzX2xhdGlmb2xpYS1MYWNfQXVsZS1MYXJ1bnMtMjUyMn4yMDEzXzA3XzI5LkpQRyIsCiAgICAgICAgICAgICAgICAgImh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvdGh1bWIvMi8yMS9MaWxpdW1fbWljaGlnYW5lbnNlXzIuanBnLzMxMHB4LUxpbGl1bV9taWNoaWdhbmVuc2VfMi5qcGciLAogICAgICAgICAgICAgICAgICJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iL2IvYmQvTmFjaG9fVmVnYXNfRmliLmpwZy84MDBweC1OYWNob19WZWdhc19GaWIuanBnIikKCmlyaXNfY29uX3VybHMgPC0gaXJpcyAlPiUgZ3JvdXBfYnkoU3BlY2llcykgJT4lIHNsaWNlX21heChTZXBhbC5MZW5ndGgsIG4gPSAxKSAlPiUgYWRkX2NvbHVtbih1cmxzX2xpcmlvcykgJT4lIHVuZ3JvdXAoKQpgYGAKClBhcmEgZGVzcHXDqXMgdHJhbnNmb3JtYXIgbG9zIHVybHMgYSBpbcOhZ2VuZXMgY29uOgoKYGBge3J9CmlyaXNfY29uX3VybHMgJT4lIGd0KCkgJT4lIAogIGd0Ojp0ZXh0X3RyYW5zZm9ybShsb2NhdGlvbnMgPSBjZWxsc19ib2R5KGNvbHVtbnMgPSBjKHVybHNfbGlyaW9zKSksCiAgICAgICAgICAgICAgICAgICAgIGZuID0gZnVuY3Rpb24oeCkge2d0Ojp3ZWJfaW1hZ2UoeCwgaGVpZ2h0ID0gMzApfSkKYGBgCgoKRW4gW2VzdGUgcG9zdF0oaHR0cHM6Ly9iam5ub3dhay5uZXRsaWZ5LmFwcC8yMDIxLzEwLzA0L3ItYmVhdXRpZnVsLXRhYmxlcy13aXRoLWd0LWFuZC1ndGV4dHJhcy8pIHNlIG11ZXN0cmEgY29tbyBpbmNsdWlyLCBubyBzb2xvIGltw6FnZW5lcywgc2lubyB0YW1iacOpbiBncsOhZmljb3MgZ2dwbG90IGVuIHVuYSB0YWJsYSBoZWNoYSBjb24gYGd0YC4gUGFyYSBlbGxvIHVzYSBlbCBwYXF1ZXRlIFtgZ3RFeHRyYXNgXShodHRwczovL2p0aG9tYXNtb2NrLmdpdGh1Yi5pby9ndEV4dHJhcy9pbmRleC5odG1sKS4gUG9yIGVqZW1wbG8gZXN0w6EgbWFyYXZpbGxhIGRlIHRhYmxhOiAKCgpgYGB7ciAsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IFRSVUUsIG91dC53aWR0aCA9ICI4MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wOF9pbWdfMDJfdGFibGEtZ2FuYWRvcmVzLXRvdXIucG5nIikpCmBgYAoKCgp0dF8wOF9pbWdfMDJfdGFibGEtZ2FuYWRvcmVzLXRvdXIucG5nCgoKLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgojIDUuIFRhYmxhcyBpbnRlcmFjdGl2YXMKCltBcXXDrV0oaHR0cHM6Ly90aGlua3IuZnIvdGFibGVhdXgtaW50ZXJhY3RpZnMtYXZlYy1yLXBvdXItc2hpbnktZXQtdm9zLXBhZ2VzLXdlYi8pIHRpZW5lcyB1biB0dXRvcmlhbCBleGNsdXNpdmFtZW50ZSBkZWRpY2FkbyBhIHRhYmxhcyBpbnRlcmFjdGl2YXMuIEFxdcOtIHPDs2xvIHByZXNlbnRhcsOpIGFsZ8O6biBlamVtcGxvIGRlIHRhYmxhIGludGVyYWN0aXZhIGNvbiBsb3MgcGFxdWV0ZXMgYERUYCwgYHJlYWN0YWJsZWAgeSBgcnBpdm90dGFibGVgLgoKPGJyPgoKIyMgNS4xIGBEVGAgcGFja2FnZQoKRWwgcGFxdWV0ZSBbRFRdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9EVC9pbmRleC5odG1sKSBlcyAiYSB3cmFwcGVyIG9mIHRoZSBKYXZhU2NyaXB0IGxpYnJhcnkgRGF0YVRhYmxlcyIuIExhIG1lam9yIGZvcm1hIGRlIGFwcmVuZGVyIGEgaGFjZXIgdGFibGFzIGNvbiBEVCBlcyBzdSBbcMOhZ2luYSB3ZWJdKGh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vRFQvKS4KCkVsIGF1dG9yIGRlIGBEVGAgZXMgW1lpaHVpIFhpZV0oaHR0cHM6Ly95aWh1aS5vcmcvZW4vKSBlbCBkZXNhcnJvbGxhZG9yIGRlIGBrbml0cmAuIEVsIHByaW5jaXBhbCBhdHJhY3Rpdm8gZGVsIHBhcXVldGUgZXMgcXVlIHBlcm1pdGUgaGFjZXIgdGFibGFzIGludGVyYWN0aXZhcyBjb24gZnVuY2lvbmFsaWRhZGVzIGNvbW8gZmlsdHJhciwgcGFnaW5hciwgb3JkZW5hciwgZXRjLi4uIGxvcyB2YWxvcmVzIGRlIGxhcyB0YWJsYXMuIAoKSGFjZXIgdGFibGFzIGLDoXNpY2FzIGNvbiBEVCBlcyBtdXkgc2VuY2lsbG8uIFBvciBlamVtcGxvOiAKCjxicj4KCmBgYHtyLCBlY2hvID0gVFJVRX0KRFQ6OmRhdGF0YWJsZShpcmlzKQpgYGAKCjxicj4KCkxhcyB0YWJsYXMgaGVjaGFzIGNvbiBEVCB0aWVuZW4gbXVjaGFzIG3DoXMgcG9zaWJpbGlkYWRlcy4gQ29tbyBlamVtcGxvIGxhIHNpZ3VpZW50ZSB0YWJsYS5MYSBtZWpvciBmb3JtYSBkZSBhcHJlbmRlciBhIGhhY2VyIHRhYmxhcyBjb24gRFQgZXMgc3UgW3DDoWdpbmEgd2ViXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL0RULykKCmBgYHtyLCBlY2hvID0gVFJVRX0KRFQ6OmRhdGF0YWJsZShpcmlzLCBmaWx0ZXIgPSAndG9wJywgCiAgICAgICAgICAgICAgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDUsIGF1dG9XaWR0aCA9IFRSVUUgKSkKYGBgCgo8YnI+CgpVbmEgcG9zaWJpbGlkYWQgZGUgbGFzIHRhYmxhcyBoZWNoYXMgY29uIGBEVGAgZXMgcXVlIHNlIHB1ZWRlbiBkZXNjYXJnYXIuIExvIGV4cGxpY2FuIGVuIGRldGFsbGUgW2FxdcOtXShodHRwczovL21hcnRpbmN0Yy5naXRodWIuaW8vYmxvZy92aWduZXR0ZS1kb3dubG9hZGFibGUtdGFibGVzLWluLXJtYXJrZG93bi13aXRoLXRoZS1kdC1wYWNrYWdlLyk6CgoKYGBge3J9CmlyaXMgJT4lIERUOjpkYXRhdGFibGUoZXh0ZW5zaW9ucyA9ICdCdXR0b25zJywgCiAgICAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KGRvbSA9ICdCbGZydGlwJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ1dHRvbnMgPSBjKCdjb3B5JywgJ2NzdicsICdleGNlbCcsICdwZGYnLCAncHJpbnQnKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhZ2VMZW5ndGggPSA1LCBhdXRvV2lkdGggPSBUUlVFICkpCmBgYAoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tCgoKIyMgNS4yIGByZWFjdGFibGVgIHBhY2thZ2UKCkVsIHBhcXVldGUgW2ByZWFjdGFibGVgXShodHRwczovL2dsaW4uZ2l0aHViLmlvL3JlYWN0YWJsZS9pbmRleC5odG1sKSB0YW1iacOpbiBwZXJtaXRlIGhhY2VyIHRhYmxhcyBpbnRlcmFjdGl2YXMsIGVzdGEgdmV6IHNlIGJhc2EgZW4gbGEgbGlicmVyw61hIGRlIEpTIFJlYWN0LgoKRWwgdXNvIGLDoXNpY28gZXMgdGFuIHNlbmNpbGxvIGNvbW86CgpgYGB7cn0KcmVhY3RhYmxlOjpyZWFjdGFibGUoaXJpcykKYGBgCgpQZXJvIHNlIHB1ZWRlbiBoYWNlciB0YWJsYXMgdGFuIGZhYnVsb3NhcyBjb21vIFtlc3RhXShodHRwczovL2dsaW4uZ2l0aHViLmlvL3JlYWN0YWJsZS9hcnRpY2xlcy90d2l0dGVyLWZvbGxvd2Vycy90d2l0dGVyLWZvbGxvd2Vycy5odG1sKSBvIFtlc3RhXShodHRwczovL2dsaW4uZ2l0aHViLmlvL3JlYWN0YWJsZS9hcnRpY2xlcy9uYmEtYm94LXNjb3JlL25iYS1ib3gtc2NvcmUuaHRtbCkgbyBbZXN0YV0oaHR0cHM6Ly9nbGluLmdpdGh1Yi5pby9yZWFjdGFibGUvYXJ0aWNsZXMvd29tZW5zLXdvcmxkLWN1cC93b21lbnMtd29ybGQtY3VwLmh0bWwpLgoKCjxicj4KCi0tLS0tLS0tLS0tLS0tLQoKIyMgNS4zIGBycGl2b3RUYWJsZWAgcGFja2FnZQoKPiBUaGUgcnBpdm90VGFibGUgcGFja2FnZSBpcyBhbiBSIGh0bWx3aWRnZXQgYnVpbHQgYXJvdW5kIHRoZSBwaXZvdHRhYmxlIEpTIGxpYnJhcnkuCgpFbCBwYXF1ZXRlIFtgcnBpdm90VGFibGVgXShodHRwczovL2dpdGh1Yi5jb20vc21hcnRpbnNpZ2h0c2Zyb21kYXRhL3JwaXZvdFRhYmxlKSBnZW5lcmEgdGFibGFzIHF1ZSBwZXJtaXRlbiBhbCB1c3VhcmlvIGdlbmVyYXIgc3VzIHByb3Bpb3MgcmVzdWx0YWRvcy4KCkNvbW8gbGFzIHRhYmxhcyBxdWUgZ2VuZXJhIGVsIHBhcXVldGUgYHJwaXZvdFRhYmxlYCBwdWVkZW4sICBzZWfDum4gbGFzIG9wY2lvbmVzIHF1ZSB1dGlsaWNlIGVsIHVzdWFyaW8gZGUgbGEgdGFibGEsIGNhbWJpYXIgbXVjaG8gZGUgdGFtYcOxbywgdm95IGEgcG9uZXIgdW4gZWplbXBsbyBkZSBlc3RhIHRhYmxhIHBlcm8gYWwgZmluYWwgZGUgZXN0ZSBkb2N1bWVudG8sIGRlc3B1w6lzIGRlIGxhIGJpYmxpb2dyYWbDrWEuIAoKQ3JlYXIgdW5hIHRhYmxhIGNvbiBgcnBpdm90VGFibGVgIGVzIG11eSBzZW5jaWxsby4gUG9kw6lpcyB2ZXJsYSB0YWJsYSBhbCBmaW5hbCBkZWwgZG9jdW1lbnRvLgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmxpYnJhcnkocnBpdm90VGFibGUpICMtIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKGMoInJhbW5hdGh2L2h0bWx3aWRnZXRzIiwgInNtYXJ0aW5zaWdodHNmcm9tZGF0YS9ycGl2b3RUYWJsZSIpKQpycGl2b3RUYWJsZShkZiwgcm93cyA9ICJHZW5kZXIiLCBjb2xzID0gYygiQ291bnRyeSIpLCB3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjQwMHB4IikKYGBgCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKIyA2LiBUYWJsYXMgcGFyYSBtb2RlbG9zCgpSIGVzIHVuIGxlbmd1YWplL2VudG9ybm8gcGFyYSBoYWNlciBhbsOhbGlzaXMgZXN0YWTDrXN0aWNvcywgYXPDrSBxdWUgbXVjaGFzIHZlY2VzIHNlIGhhbiBkZSBwcmVzZW50YXIgbG9zIHJlc3VsdGFkb3MgZGUgZXN0aW1hY2nDs24gZGUgYWxnw7puIG1vZGVsbyBlc3RhZMOtc3RpY28sIFBFUk8sIGxvcyByZXN1bHRhZG9zIGRlIGVzdGltYWNpw7NuIGRlIGVzdG9zIG1vZGVsb3MsIGVuIGdlbmVyYWwsIG5vIHNlIGFsbWFjZW5hbiBlbiBkYXRhLmZyYW1lcywgc2lubyBlbiBsaXN0YXMuIMK/RXMgZXN0byBpbXBvcnRhbnRlPyBTw60sIHNpIGxvIHF1ZXJlbW9zIGVzICBwcmVzZW50YXIgbG9zIHJlc3VsdGFkb3MgZGUgZXN0aW1hY2nDs24gZW4gdW5hIHRhYmxhLiBMYSByYXrDs24gY29uc2lzdGUgZW4gcXVlIGxvcyBwYXF1ZXRlcyBwYXJhIHRhYmxhcyBxdWUgaGVtb3MgdmlzdG8gYW50ZXJpb3JtZW50ZSAoYGd0YCwgYERUYCwgZXRjIC4uLikgc29sbyBnZW5lcmFuIHRhYmxhcyBkZSByZXN1bHRhZG9zIGFsbWFjZW5hZG9zIGVuIGRhdGEuZnJhbWVzLCBhc8OtIHF1ZSBubyBsb3MgcG9kcmVtb3MgdXNhciwgYWwgbWVub3MgZGlyZWN0YW1lbnRlLCBwYXJhIGhhY2VyIHRhYmxhcyBkZSByZXN1bHRhZG9zIGFzb2NpYWRvcyBhIG1vZGVsb3MgZXN0YWTDrXN0aWNvcy4gQWZvcnR1bmFkYW1lbnRlIGhheSB1bm9zIGN1YW50b3MgcGFxdWV0ZXMgZW4gUiBjdXlvIHByb3DDs3NpdG8gZXMgcHJlY2lzYW1lbnRlIGVzZSwgcHJlc2VudGFyIGxvcyByZXN1bHRhZG9zIGRlIGVzdGltYWNpw7NuIGRlIGFsZ8O6biBtb2RlbG8gbyB0w6ljbmljYSBlc3RhZMOtc3RpY2EgY29tbyB0YWJsYXMuCgpQb3IgZWplbXBsbywgdmFtb3MgYSBlc3RpbWFyIHVuIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbC4gVXRpbGl6YXJlbW9zIGVzdG9zIGRhdG9zOgoKYGBge3J9CnVybGEgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wZXJlenA0NC9pcmlzX2RhdGEvbWFzdGVyL2RhdGEvUElBQUNfZGF0YV9zbWFsbC5jc3YiCmRmIDwtIHJlYWRfY3N2KHVybGEpCmBgYAoKQ29uIGVsbG9zIGVzdGltYW1vcyBlbCBzaWd1aWVudGUgbW9kZWxvIGxpbmVhbDoKCgpgYGB7cn0KbXlfbW9kZWwgPC0gbG0oV2FnZV9ob3VyIH4gTnVtZXJhY3lfc2NvcmUgKyBHZW5kZXIgLCBkYXRhID0gZGYpCm15X21vZGVsCmBgYAoKR2VuZXJhbG1lbnRlIGhheSBmdW5jaW9uZXMgZXNwZWPDrWZpY2FzIHBhcmEgbW9zdHJhciBsb3MgcmVzdWx0YWRvcyBkZSBlc3RpbWFjacOzbiwgcG9yIGVqZW1wbG8gbGEgZnVuY2nDs24gYHN1bW1hcnkoKWA6CgpgYGB7cn0Kc3VtbWFyeShteV9tb2RlbCkKYGBgCgpMb3MgcmVzdWx0YWRvcyBtb3N0cmFkb3MgcG9yIGBzdW1tYXJ5KClgIHBlcm1pdGVuIGhhY2Vyc2UgdW5hIGlkZWEgZGUgbG9zIHJlc3VsdGFkb3MgZGUgbGEgZXN0aW1hY2nDs24gcGVybyBubyBzdWVsZW4gc2VyIGFkZWN1YWRvcyBwYXJhIHByZXNlbnRhcmxvcyBlbiB1biBpbmZvcm1lIG8gZW4gdW4gYXJ0w61jdWxvIGNpZW50w61maWNvLiAKCkhheSBiYXN0YW50ZXMgcGFxdWV0ZXMgcXVlIHBlcm1pdGVuIG9idGVuZXIgdGFibGFzIGNvbiBsb3MgcmVzdWx0YWRvcyBkZSBsYSBlc3RpbWFjacOzbiBkZSBtb2RlbG9zIGVzdGFkw61zdGljb3MsIHBvciBlamVtcGxvLCBlbCBwYXF1ZXRlIFtgZmluYWxmaXRgXShodHRwczovL3d3dy5kYXRhc3VyZy5uZXQvMjAxOC8wNS8xNi9lbGVnYW50LXJlZ3Jlc3Npb24tcmVzdWx0cy10YWJsZXMtYW5kLXBsb3RzLXRoZS1maW5hbGZpdC1wYWNrYWdlLykgeSBtdWNob3Mgb3Ryb3MsIHNpbiBlbWJhcmdvLCBlbiBlc3RlIHR1dG9yaWFsIHNvbG8gc2UgbW9zdHJhcsOhIGFsZ8O6biBlamVtcGxvIHBhcmEgbG9zIHBhcXVldGVzIFtgc3RhcmdhemVyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3N0YXJnYXplci9zdGFyZ2F6ZXIucGRmKSwgIFtgbW9kZWxzdW1tYXJ5YF0oaHR0cHM6Ly9naXRodWIuY29tL3ZpbmNlbnRhcmVsYnVuZG9jay9tb2RlbHN1bW1hcnkpLCAgW2BndHN1bW1hcnlgXShodHRwczovL2dpdGh1Yi5jb20vemFib3JlL2d0c3VtbWFyeSkgeSBbYHNqUGxvdGBdKGh0dHBzOi8vc3RyZW5nZWphY2tlLmdpdGh1Yi5pby9zalBsb3QvaW5kZXguaHRtbCkKCi0tLS0tLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgoKIyMgNi4xIFRhYmxhcyBjb24gYHN0YXJnYXplcmAKCkVsIHBhcXVldGUgW2BzdGFyZ2F6ZXJgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc3RhcmdhemVyL3ZpZ25ldHRlcy9zdGFyZ2F6ZXIucGRmKSBwZXJtaXRlIGbDoWNpbG1lbnRlIHByZXNlbnRhciB0YWJsYXMgY29uIHJlc3VsdGFkb3MgZGUgcmVncmVzacOzbi4gUGVybWl0ZSBjcmVhciB0YWJsYXMgZW4gbGF0ZXgsIGh0bWwgbyBhc2NpaS4gW0FxdcOtXShodHRwczovL3d3dy5qYWtlcnVzcy5jb20vY2hlYXRzaGVldHMvc3RhcmdhemVyLykgaGF5IHVuYSBjaGVhdHNoZWV0IHNvYnJlIGBzdGFyZ2F6ZXJgCgpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIn0Kc3RhcmdhemVyOjpzdGFyZ2F6ZXIobXlfbW9kZWwsIHR5cGUgPSAiaHRtbCIpCmBgYAoKCjxicj4KClNlIHB1ZWRlbiBwcmVzZW50YXIgbG9zIHJlc3VsdGFkb3MgZGUgdmFyaW9zIG1vZGVsb3MgZW4gdW5hIHRhYmxhLiBWZcOhbW9zbG8uIFBhcmEgZWxsbyB2YW1vcyBhIGVzdGltYXIgdHJlcyBtb2RlbG9zIHkgbG9zIGFsbWFjZW5hcmVtb3MgZW4gdW5hIGxpc3RhOgoKYGBge3IsIHJlc3VsdHMgPSAiYXNpcyJ9CmRmIDwtIGRmICU+JSBtdXRhdGUoTnVtZXJhY3lfc2NvcmVfYiA9IGlmZWxzZShOdW1lcmFjeV9zY29yZSA+IG1lYW4oTnVtZXJhY3lfc2NvcmUsIG5hLnJtID0gVFJVRSksIDEsIDApKSAjLSBiaW5hcnkgdmFyaWFibGUgZm9yIGxvZ2l0IG1vZGVsCgpteV9tb2RlbHMgPC0gbGlzdCgpCm15X21vZGVsc1tbJ1c6ICBPTFMgMSddXSAgIDwtIGxtKCBXYWdlX2hvdXIgICAgICAgICB+IE51bWVyYWN5X3Njb3JlICsgR2VuZGVyICwgZGYpCm15X21vZGVsc1tbJ051OiBPTFMgMiddXSAgIDwtIGxtKCBOdW1lcmFjeV9zY29yZSAgICB+IEVkdWNhdGlvbiArIEdlbmRlciAsIGRmKQpteV9tb2RlbHNbWydOdTogTG9naXQgMSddXSA8LSBnbG0oIE51bWVyYWN5X3Njb3JlX2IgfiBFZHVjYXRpb24gKyBHZW5kZXIgLCBkZiwgZmFtaWx5ID0gYmlub21pYWwoKSkKYGBgCgpVbmEgdmV6IGhlbW9zIGVzdGltYWRvIGxvcyAzIG1vZGVsb3MgeSBhbG1hY2VuYWRvIHN1cyByZXN1bHRhZG9zIGRlIGVzdGltYWNpw7NuIGVuIGxhIGxpc3RhIGBteV9tb2RlbHNgCgpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIn0Kc3RhcmdhemVyOjpzdGFyZ2F6ZXIobXlfbW9kZWxzLCB0eXBlID0gImh0bWwiLCB0aXRsZSA9ICJSZXN1bHRzIiwgYWxpZ24gPSBUUlVFKQpgYGAKCi0tLS0tLS0tLS0tLS0tCgo8YnI+CgojIyA2LjIgVGFibGFzIGNvbiBgbW9kZWxzdW1tYXJ5YAoKRWwgcGFxdWV0ZSBbYG1vZGVsc3VtbWFyeWBdKGh0dHBzOi8vdmluY2VudGFyZWxidW5kb2NrLmdpdGh1Yi5pby9tb2RlbHN1bW1hcnkvKSBlcyBtw6FzIG1vZGVybm8sIHkgdGFtYmnDqW4gcGVybWl0ZSBoYWNlciB0YWJsYXMgcmVzdW1lbiBkZSBlc3RpbWFjaW9uZXMgZGUgbW9kZWxvcy4gUG9yIGVqZW1wbG86CgpgYGB7cn0KbGlicmFyeShtb2RlbHN1bW1hcnkpICMtIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCd2aW5jZW50YXJlbGJ1bmRvY2svbW9kZWxzdW1tYXJ5JykKCm1tIDwtIG1vZGVsc3VtbWFyeTo6bXN1bW1hcnkobXlfbW9kZWxzLCB0aXRsZSA9ICJSZXN1bHRhZG9zIGRlIGVzdGltYWNpw7NuIikKbW0KYGBgCgpUaWVuZSBtdWNoYXMgbcOhcyBwb3NpYmlsaWRhZGVzLgoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDYuMyBUYWJsYXMgY29uIGBndHN1bW1hcnlgCgpFbCBwYXF1ZXRlIFtgZ3RzdW1tYXJ5YF0oaHR0cHM6Ly9naXRodWIuY29tL3phYm9yZS9ndHN1bW1hcnkpIHBlcm1pdGUsIGFkZW3DoXMgZGUgcmVhbGl6YXIgdGFibGFzIHJlc3VtZW4gZGUgZGF0YS5mcmFtZXMsIGNvbmZlY2Npb25hciB0YWJsYXMgZGUgbW9kZWxvcyBkZSByZWdyZXNpw7NuIGRlIGZvcm1hIHNlbmNpbGxhIHkgbXV5IGNvbmZpZ3VyYWJsZXMuCgpDb21vIGhlIGRpY2hvIGBndHN1bW1hcnlgIHRpZW5lIG11Y2hhcyBwb3NpYmlsaWRhZGVzLCBhbGd1bmFzIGRlIGVsbGFzIHB1ZWRlcyB2ZXJsYXMgZW4gZXN0ZSBbdmlkZW9dKGh0dHBzOi8veW91dHUuYmUvVTJTNkxiTU40MkkpLCBlbiBlc3RhcyBbdHJhbnNwYXJlbmNpYXNdKGh0dHBzOi8vd3d3LmVtaWx5emFib3IuY29tL2NsZXZlbGFuZC1yLWd0c3VtbWFyeS8pLCBvIGVuIGVzdGUgW3Bvc3RdKGh0dHBzOi8vZWR1Y2F0aW9uLnJzdHVkaW8uY29tL2Jsb2cvMjAyMC8wNy9ndHN1bW1hcnkvKS4gCgpDb21vIGVqZW1wbG9zIGRlIHN1IHVzbywgdW5hIHRhYmxhIHJlc3VtZW4gZGUgdW4gZGF0YS5mcmFtZToKCmBgYHtyfQppcmlzICU+JSBndHN1bW1hcnk6OnRibF9zdW1tYXJ5KCkKYGBgCgo8YnI+CgpBaG9yYSB1bmEgdGFibGEgZGUgdW4gbW9kZWxvIGRlIHJlZ3Jlc2nDs246CgpgYGB7cn0KZ3RzdW1tYXJ5Ojp0YmxfcmVncmVzc2lvbihteV9tb2RlbCkKYGBgCgoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDYuNCBUYWJsYXMgY29uIGBzalBsb3RgCgpFbCBwYXF1ZXRlIFtgc2pQbG90YF0oaHR0cHM6Ly9zdHJlbmdlamFja2UuZ2l0aHViLmlvL3NqUGxvdC8pIHRpZW5lIG11Y2hhcyBwb3NpYmlsaWRhZGVzLCBwZXJvIGVuIGNvbmNyZXRvIHB1ZWRlIGhhY2VyIHRhYmxhcyBkZSBtb2RlbG9zOgoKYGBge3J9CnNqUGxvdDo6dGFiX21vZGVsKG15X21vZGVsKQpgYGAKCgpUaWVuZSBvdHJhcyB1dGlsaWRhZGVzLCBwb3IgZWplbXBsbzoKCmBgYHtyfQpzalBsb3Q6OnBsb3RfbW9kZWwobXlfbW9kZWwpCmBgYAoKCi0tLS0tLS0tLS0tLS0tCgo8YnI+CgojIyA2LjUgT3Ryb3MgcGFxdWV0ZXMKCkhheSBtdWNob3MgbcOhcyBwYXF1ZXRlcyBlc3BlY2lhbGl6YWRvcyBlbiBoYWNlciB0YWJsYXMgY29uIHJlc3VsdGFkb3MgZGUgbGEgZXN0aW1hY2nDs24gZGUgbW9kZWxvcy4gQXF1w60gc29sbyBtb3N0cmFyw6kgYWxnbyBkZSAzIGRlIGVsbG9zLCBsb3MgcGFxdWV0ZXMgYHJlcG9ydGAgeSBgYXBhVGFibGVzYCwgcGVybyBleGlzdGVuIG90cm9zIHBhcXVldGVzIGNvbW86ICAKCiAgLSBbYGZsaXBSZWdyZXNzaW9uYF0oaHR0cHM6Ly9naXRodWIuY29tL0Rpc3BsYXlyL2ZsaXBSZWdyZXNzaW9uKS4gRW4gW2VzdGUgcG9zdF0oaHR0cHM6Ly93d3cuZGlzcGxheXIuY29tLzUtYWx0ZXJuYXRpdmVzLXRvLXRoZS1kZWZhdWx0LXItb3V0cHV0cy1mb3ItZ2xtcy1hbmQtbGluZWFyLW1vZGVscy8pIGV4cGxpY2FuIGNvbW8gdXNhciBzdXMgZnVuY2lvbmVzLiAgCgogIC0gW2BwYXBhamFgXShodHRwczovL2dpdGh1Yi5jb20vY3JzaC9wYXBhamEpLCBlcyB1biBwYXF1ZXRlIHBhcmEgcHJvZHVjaXIgZG9jdW1lbnRvcyB5IHRhYmxhcyBjb25mb3JtZXMgYSBsYSBBbWVyaWNhbiBQc3ljaG9sb2dpY2FsIEFzc29jaWF0aW9uIChBUEEpIG1hbnVzY3JpcHQgZ3VpZGVsaW5lcyAoNnRoIEVkaXRpb24pLiAgIAoKICAtIFtgYWNhZGVtaWNXcml0ZVJgXShodHRwczovL3d3dy5qdmNhc2lsbGFzLmNvbS9hY2FkZW1pY1dyaXRlUi8pLCBlc3TDoSByZWxhY2lvbmFkbyBjb24gYHBhcGFqYWAuIEVzIHVuIHBhcXVldGUgY29uIGFsZ3VuYXMgZnVuY2lvbmVzIMO6dGlsZXMgcGFyYSBlc2NyaWJpciBpbmZvcm1lcyBlc3RhZMOtc3RpY29zLiBQb3IgZWplbXBsbyBsYSBmdW5jacOzbiBgY291bnRfd29yZCgpYC4KCjxicj4KCiMjIyBwYXF1ZXRlIGByZXBvcnRgCgpTZWfDum4gcG9uZSBlbiBzdSBbcMOhZ2luYSB3ZWJdKGh0dHBzOi8vZWFzeXN0YXRzLmdpdGh1Yi5pby9yZXBvcnQvKToKCj4gSXQgYXV0b21hdGljYWxseSBwcm9kdWNlcyByZXBvcnRzIG9mIG1vZGVscyBhbmQgZGF0YWZyYW1lcyBhY2NvcmRpbmcgdG8gYmVzdCBwcmFjdGljZSBndWlkZWxpbmVzIChlLmcuLCBBUEHigJlzIHN0eWxlIGd1aWRlKSwgZW5zdXJpbmcgc3RhbmRhcmRpemF0aW9uIGFuZCBxdWFsaXR5IGluIHJlc3VsdHMgcmVwb3J0aW5nLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbGlicmFyeShyZXBvcnQpICMtIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigibmV1cm9wc3ljaG9sb2d5L3JlcG9ydCIpCnJyIDwtIHJlcG9ydChteV9tb2RlbCkKYGBgCgo8YnI+CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQphcy5yZXBvcnRfdGFibGUocnIpCmBgYAoKCjxicj4KClRhbWJpw6luIHBlcm1pdGUgb2J0ZW5lciB1bmEgZGVzY3JpcGNpw7NuIHZlcmJhbCBkZSBsb3MgcmVzdWx0YWRvcyBlbiB0ZXh0bzoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmFzLnJlcG9ydF90ZXh0KHJyKQpgYGAKCjxicj4KCiMjIyBwYXF1ZXRlIGBhcGFUYWJsZXNgCgpTZWfDum4gc3UgW3DDoWdpbmEgd2ViXShodHRwczovL2RzdGFubGV5NC5naXRodWIuaW8vYXBhVGFibGVzL2luZGV4Lmh0bWwpOgoKPiBUaGlzIHBhY2thZ2UgY3JlYXRlcyBXb3JkIGZpbGVzICguZG9jIGZpbGVzKSBjb250YWluaW5nIEFQQSBzdHlsZSB0YWJsZXMgZm9yIHNldmVyYWwgdHlwZXMgb2YgYW5hbHlzZXMuIFVzaW5nIHRoaXMgcGFja2FnZSBtaW5pbWl6ZXMgdHJhbnNjcmlwdGlvbiBlcnJvcnMgYW5kIHJlZHVjZXMgdGhlIG51bWJlciBjb21tYW5kcyBuZWVkZWQgYnkgdGhlIHVzZXIuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGFwYVRhYmxlcykgIy0gaW5zdGFsbC5wYWNrYWdlcygiYXBhVGFibGVzIixkZXAgPSBUUlVFKQphcGEucmVnLnRhYmxlKG15X21vZGVsKQpgYGAKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCgojIDcuIEJpYmxpb2dyYWbDrWEvUmVjdXJzb3M6CgotIEVuIFtlc3RlIGhpbG9dKGh0dHBzOi8vdHdpdHRlci5jb20vR29sZGJlcmdEYXRhL3N0YXR1cy8xMjkyOTM1NDU0MTk1NTExMzAyKSBkZSBUd2l0dGVyIHNlIGhhYmxhIHNvYnJlIGxvcyBtZWpvcmVzIHBhcXVldGVzIFIgcGFyYSBoYWNlciB0YWJsYXMuCgotIFtEYXZpZCBLZXllc10oaHR0cHM6Ly9yZm9ydGhlcmVzdG9mdXMuY29tL2F1dGhvci9kZ2tleWVzLykgc2XDsWFsYSBbYXF1w61dKGh0dHBzOi8vcmZvcnRoZXJlc3RvZnVzLmNvbS8yMDE5LzExL2hvdy10by1tYWtlLWJlYXV0aWZ1bC10YWJsZXMtaW4tci8pLCBxdWUgIHF1aWVyZXMgaGFjZXIgdGFibGFzIHBhcmEgV29yZCwgcG9zaWJsZW1lbnRlIGxhIG1lam9yIG9wY2nDs24gc2VhIGVsIHBhcXVldGUgW2ZsZXh0YWJsZV0oaHR0cHM6Ly9kYXZpZGdvaGVsLmdpdGh1Yi5pby9mbGV4dGFibGUvaW5kZXguaHRtbCkKCgotIFNpIGxvIHF1ZSBuZWNlc2l0YXMgZXMgaGFjZXIgdGFibGFzIHBhcmEgZG9jdW1lbnRvcyBwZGYsIHB1ZWRlcyBlbXBlemFyIHBvciBbYXF1w61dKGh0dHBzOi8vc2hhcmxlZW53LnJiaW5kLmlvL3Bvc3QvdGFibGVzX2luX3BkZi9tYWtpbmctcGRmLXRhYmxlcy1pbi1yLykuIEFsIGZpbmFsIHJlbWl0ZSBhIFtlc3RlIG90cm8gZG9jdW1lbnRvXShodHRwczovL2hhb3podTIzMy5naXRodWIuaW8va2FibGVFeHRyYS9hd2Vzb21lX3RhYmxlX2luX3BkZi5wZGYpLiBUYW1iacOpbiBwdWVkZXMgdXNhciBlc3RlIFtnZW5lcmFkb3IgZGUgdGFibGFzXShodHRwczovL3d3dy50YWJsZXNnZW5lcmF0b3IuY29tLykuCgoKLSBTaSBxdWllcmVzIHRhYmxhcyBkZSBwb3JjZW50YWplcyBldGMuLi4gW2FxdcOtXShodHRwczovL2RhYmJsaW5nd2l0aGRhdGEud29yZHByZXNzLmNvbS8yMDE3LzEyLzIwL215LWZhdm91cml0ZS1yLXBhY2thZ2UtZm9yLWZyZXF1ZW5jeS10YWJsZXMvKSBwbGFudGVhbiB2YXJpYXMgcG9zaWJpbGlkYWRlcyBwYXJhIGhhY2VybGFzLgoKLSBTaSBxdWllcmVzIGhhY2VyIHRhYmxhcyBwYXJlY2lkYXMgYSBsYXMgcXVlIHNlIGhhY2VuIGNvbiAgUFJPQyBGUkVRIGRlIFNBUyBvIENST1NTVEFCUyBlbiBTUFNTIHB1ZWRlcyB1c2FyIGxhIGZ1bmNpw7NuIGBDcm9zc1RhYmxlKClgIGRlbCBwYXF1ZXRlIGBnbW9kZWxzYC4gRW4gW2VzdGUgcG9zdF0oaHR0cHM6Ly9kYWJibGluZ3dpdGhkYXRhLndvcmRwcmVzcy5jb20vMjAxOC8wMi8yNi9yLXBhY2thZ2VzLWZvci1zdW1tYXJpc2luZy1kYXRhLXBhcnQtMi8pIG11ZXN0cmFuIG90cmFzIGFsdGVybmF0aXZhcy4KCi0gU2kgbmVjZXNpdGFzIGhhY2VyICoqMy13YXkgdGFibGVzKiosIHB1ZWRlcyBoYWNlcmxhcyBjb24gYHN0YXRzOjp4dGFicygpYC4gUGFyYSBkZXNwdcOpcyBpbXByaW1pcmxhIGNvbiBgZnRhYmxlKG15X3RhYmxlKWAuIExvIGV4cGxpY2FuW2FxdcOtXShodHRwczovL3JzdHVkaW8tcHVicy1zdGF0aWMuczMuYW1hem9uYXdzLmNvbS8zMDg1OTFfNmRmZjU2NmQzODM5NDZjODgxY2I1YjZhNzM1YTc5ZmQuaHRtbCkgeSBbYXF1w61dKGh0dHBzOi8vd3d3LnN0YXRtZXRob2RzLm5ldC9zdGF0cy9mcmVxdWVuY2llcy5odG1sKS4gUG9yIGVqZW1wbG86CgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbXlfdGFibGUgPC0gc3RhdHM6Onh0YWJzKH4gRWR1Y2F0aW9uICsgQ291bnRyeSArIEdlbmRlciwgZGF0YSA9IGRmKQpzdGF0czo6ZnRhYmxlKG15X3RhYmxlKSAKYGBgCgpPdHJhIGFsdGVybmF0aXZhIHBhcmEgdGFibGFzIGRlIGZyZWN1ZW5jaWFzIGRlIDMgdmFyaWFibGVzIGVzIHVzYXIgYGphbml0b3I6OnRhYnlsKFgxLCBYMiwgWDMpYC4gTG8gZXhwbGljYW4gW2FxdcOtXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvamFuaXRvci92aWduZXR0ZXMvdGFieWxzLmh0bWwpCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGYgJT4lIGphbml0b3I6OnRhYnlsKEVkdWNhdGlvbiwgQ291bnRyeSwgR2VuZGVyKQpgYGAKCgotIE3DoXMgcGFxdWV0ZXMgcmVsYWNpb25hZG9zIGNvbiB0YWJsYXM6IFtwcmludHJdKGh0dHBzOi8veWlodWkubmFtZS9wcmludHIvKSwgW3F1ZXN0aW9ucl0oaHR0cHM6Ly9qdWJhLmdpdGh1Yi5pby9xdWVzdGlvbnIvKSwgLi4uCgotIExvcyBwYXF1ZXRlcyBbYHJoYW5kc29udGFibGVgXShodHRwczovL2pyb3dlbi5naXRodWIuaW8vcmhhbmRzb250YWJsZS8pIHkgW2BleGNlbFJgXShodHRwczovL3N3ZWNoaHlhLmdpdGh1Yi5pby9leGNlbFIvKSBwZXJtaXRlbiBjcmVhciB0YWJsYXMgZWRpdGFibGVzIHBvciBlbCB1c3VhcmlvLgoKCi0gRWwgcGFxdWV0ZSBbYGV4cHNzYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2V4cHNzL3ZpZ25ldHRlcy90YWJsZXMtd2l0aC1sYWJlbHMuaHRtbCkgaGFjZSB0YWJsYXMgc2ltaWxhcmVzIGEgbGFzIGRlIFNQU1MuCgotIEVuIGVzdGUgW3Bvc3RdKGh0dHBzOi8vYXBwc2lsb24uY29tL2ZvcmdldC1hYm91dC1leGNlbC11c2Utci1zaGlueS1wYWNrYWdlcy1pbnN0ZWFkLykgaGFjZW4gdW4gcmVwYXNvIGRlIHBhcXVldGVzIHF1ZSBwZXJtaXRlbiBjcmVhciB0YWJsYXMgY29uIGNhcmFjdGVyw61zdGljYXMgZXNwZWNpYWxlcyBjb21vIHF1ZSBzdXMgdmFsb3JlcyBzZWFuIGVkaXRhYmxlcyBwb3IgZWwgdXN1YXJpbywgcXVlIHRlbmdhbiBmb3JtYXRvIGNvbmRpY2lvbmFkbyBhIGxvcyB2YWxvcmVzIGRlIGxhcyB0YWJsYXMsIHF1ZSBzZSBwdWVkYW4gb3JkZW5hciB5IGZpbHRyYXIgc3VzIHZhbG9yZXMgZXRjIC4uLgoKLSBVbiBbcG9zdF0oaHR0cHM6Ly9ibG9nLnJzdHVkaW8uY29tLzIwMjAvMTIvMjMvd2lubmVycy1vZi10aGUtMjAyMC1yc3R1ZGlvLXRhYmxlLWNvbnRlc3QvKSBjb24gbGFzIHRhYmxhcyBnYW5hZG9yYXMgZGVsICIyMDIwIFJTdHVkaW8gVGFibGUgQ29udGVzdCIuCgoKPGJyPjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCjxicj4KCgojIDUuMyBEcmFnICYgZHJvcCBwaXZvdCB0YWJsZXM6IGBycGl2b3RUYWJsZWAKCkVsIHBhcXVldGUgW2BycGl2b3RUYWJsZWBdKGh0dHBzOi8vZ2l0aHViLmNvbS9zbWFydGluc2lnaHRzZnJvbWRhdGEvcnBpdm90VGFibGUpOgoKPiBUaGUgcnBpdm90VGFibGUgcGFja2FnZSBpcyBhbiBSIGh0bWx3aWRnZXQgYnVpbHQgYXJvdW5kIHRoZSBwaXZvdHRhYmxlIGxpYnJhcnkuCgo8YnI+CgpgYGB7cn0KcnBpdm90VGFibGU6OnJwaXZvdFRhYmxlKGRmLCByb3dzID0gIkdlbmRlciIsIGNvbHMgPSBjKCJDb3VudHJ5IiksIHdpZHRoID0gIjEwMCUiLCBoZWlnaHQgPSAiNDAwcHgiKQpgYGAKCgo8YnI+PGJyPjxicj48YnI+PGJyPjxicj48YnI+PGJyPjxicj48YnI+PGJyPjxicj48YnI+PGJyPjxicj4KCgo=