Escalas

Previamente comentamos que el mapeo de una variable en un elemento geométrico, por ejemplo cuando le asignamos distintos colores a los puntos que representan cada tipo de temperatura, usa una escala para definir, en este caso, que color le corresponde a cada elemento.

También cambiamos la apariencia del relleno (o fill) de los contornos y la forma de los puntos (o shape). Para esto, {ggplot2} siempre usa una escala que podemos modificar de acuerdo a nuestro gusto y teniendo en cuenta cómo queremos comunicar nuestros resultados.

Por supuesto, modificar una escala implica sumar una nueva capa al gráfico sumando una nueva función. Todas las funciones de escala comienzan con scale (de escala en inglés), el tipo de apariencia que queremos modificar (color, fill, shape, etc) y en muchos casos un nombre o una característica de esa escala.

Para mostrar como funciona, vamos a descargar datos de las estaciones meteorológicas del Servicio Meteorológico Nacional para el mes de agosto de 2020 y con suerte al final de este documento tendremos un gráfico listo para publicar.

Pero por supuesto, primero tenemos que manipular los datos para poder utilizarlos. En particular necesitaremos información de las estaciones, sus metadatos, que se encuentran en un archivo distinto. Y de paso calculamos la variable que nos interesa: la temperatura máxima media para agosto.

observaciones <- readr::read_csv("datos/observaciones_smn.csv") %>% 
  group_by(station) %>% 
  summarise(tmax_media = mean(tmax, na.rm = TRUE),
            tmax_var = sd(tmax, na.rm = TRUE),)

estaciones <- read_csv("datos/estaciones_smn.csv") 

observaciones <- left_join(observaciones, estaciones, by = c("station" = "nombre")) %>% 
  filter(provincia != "ANTARTIDA")
observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point() 

Por ahora este gráfico no nos dice nada, necesitamos agregarle una capa con el mapa tal cual hicimos previamente.

mapa <- rnaturalearth::ne_states(country = c("argentina"), returnclass = "sf")

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point() +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) 

Escala de colores y otras características

Si bien ahora podemos identificar donde están las estaciones, no tenemos información de la temperatura máxima media. Podríamos cambiar el color de los puntos para que representen el valor que toma esa variable en cada estación.

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media)) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) 

Pero esta escala de colores que usa {ggplot2} por defecto no es de las mejores, es difícil diferenciar entre los valores. Existen infinitas paletas de colores que se pueden usar en ggplot, algunas por ejemplo buscan poder distinguirse si imprimimos el gráfico en blanco y negro, otras están diseñadas para que personas con daltonismo puedan distinguir los colores. Una escala o paleta de colores muy usada es viridis que fue creada justamente para resolver este y otros problemas. También existe otra gran familia de paletas de colores llamada ColorBrewer.

Vamos a probar la paleta “YlOrRd” , esta paleta es secuencial y es justo lo que necesitamos para visualizar una variable continua como la temperatura. Cómo estamos modificando el color, la función a usar será scale_color_distiller(), la hermana continua de scale_color_brewer():

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media)) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1)

En este caso también agregamos el argumento direction = 1 para que la paleta de colores los muestre de más oscuros a más claros. Esta decisión es estética y muchas veces depende de las variables a graficar.

Desafío

A modo de prueba, cambia la paleta de colores actual por la de Viridis. Para eso tenés que usar scale_color_viridis_c(). La “c” del final viene de continuous y se usa para variables continuas, mientras que si los datos son discretos o categorías, se usa “d” al final.

Nuestro gráfico va quedando mejor y podemos aprovechar la capacidad de {ggplot2} de mapear variables a los elementos del gráfico y visualizar la variabilidad de la temperatura máxima modificando el tamaño de los puntos de acuerdo al desvío estándar.

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var)) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1)
## Warning: Removed 1 rows containing missing values (geom_point).

Es interesante ver como las temperaturas máximas medias mayores también tienen mayor variabilidad. Pero ahora algunos puntos se superponen y es posible que no estemos viendo algunas estaciones. Vamos a arreglar eso agregando transparencia y de paso modificar el tamaño de los puntos con la escala correspondiente scale_size_area() y sacar la legenda con guide = NULL.

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var), alpha = 0.7) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1) +
  scale_size_area(max_size = 4, guide = NULL)
## Warning: Removed 1 rows containing missing values (geom_point).

Escalas de ejes

Ahora que la información del gráfico se ve bien, pasemos a los ejes. Al igual que para el color, el tamaño y otros elementos del gráfico, para los ejes también existen funciones “scale”. En este caso las escalas que modifican los ejes justamente comienzan con scala_x_ o scale_y_ según sea el caso y hay una gran variedad de opciones dependiendo del tipo de dato que estamos graficando en cada eje.

Si en el eje y graficamos una variable discreta entonces podremos modificar su aspecto con scale_y_discrete(). En este caso la función geom_sf() automáticamente modifica la apariencia de los ejes y no requieren de mucho trabajo.

Pero queremos que nuestro gráfico quede listo para publicar y la “W” de west (oeste en inglés) puede no ser muy amigable. Modifiquemos entonces el eje x con la función scale_x_continuous(). En este caso queremos modificar las etiquetas o labels y para eso usaremos la función LonLabel() del paquete {metR}. Esta función recibe una función anónima que toma cada longitud y el cambia el “°W° por”°O”.

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var), alpha = 0.7) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1) +
  scale_size_area(max_size = 4, guide = NULL) +
  scale_x_continuous(labels = function(x) LonLabel(x, west = "°O"))
## Warning: Removed 1 rows containing missing values (geom_point).

Elementos de texto

Ya sumamos 3 escalas y el gráfico ya se ve muy bien. ¿Cómo hacemos si queremos identificar estaciones individuales? Por ahora es difícil, pero podríamos agregar etiquetas de texto con el nombre de cada estación al lado de cada punto usando geom_text(), y en este caso la apariencia está dada por label o etiqueta:

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var), alpha = 0.7) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1) +
  scale_size_area(max_size = 4, guide = NULL) +
  scale_x_continuous(labels = function(x) LonLabel(x, west = "°O")) +
  geom_text(aes(label = station)) 
## Warning: Removed 1 rows containing missing values (geom_point).

Pero nos olvidamos que tenemos más de 100 estaciones, es imposible agregarle etiquetas a todos. Pero podríamos querer resaltar algunos, tal vez los de una región en particular o los que cumplen con la condición de tener las mayores temperaturas máximas medias. Para eso vamos a generarnos una nueva tabla con las estaciones que queremos resaltar y de paso usarla dentro de geom_text().

extremos_temperatura <- observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  filter(tmax_media == max(tmax_media, na.rm = TRUE) |
           tmax_media == min(tmax_media, na.rm = TRUE)) # Estaciones con extremos!

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var), alpha = 0.7) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1) +
  scale_size_area(max_size = 4, guide = NULL) +
  scale_x_continuous(labels = function(x) LonLabel(x, west = "°O")) +
  geom_text(aes(label = station),
            data = extremos_temperatura, # Esta capa usa la tabla extremos_temperatura!
            size = 2.5) 
## Warning: Removed 1 rows containing missing values (geom_point).

Del código anterior surge algo muy importante: es posible generar capas en un gráfico usando una data.frame distinto al que usamos para graficar las capas anteriores. Esto es útil principalmente para definir etiquetas o resaltar determinadas observaciones.

Y el truco está en que ambos data.frames tienen las variables lon y lat y entonces {ggplot2} puede identificar en que parte del gráfico (en que valores de x y en que valores de y) colocar cada elemento.

Veamos ahora una (de varias) maneras agregar o modificar elementos de texto en el gráfico. Vamos a usar una nueva función (y una nueva capa!), labs():

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var), alpha = 0.7) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1) +
  scale_size_area(max_size = 4, guide = NULL) +
  scale_x_continuous(labels = function(x) LonLabel(x, west = "°O")) +
  geom_text(aes(label = station),
            data = extremos_temperatura, # Esta capa usa la tabla extremos_temperatura!
            size = 2.5) +
  labs(title = "Temperatura máxima media",
       subtitle = "Agosto",
       caption = "El tamaño de cada circulo representa la variabilidad",
       x = "Longitud",
       y = "Latitud",
       color = "")
## Warning: Removed 1 rows containing missing values (geom_point).

Agregamos un título, un subtitulo, el epígrafe de la figura (caption) para las aclaraciones y cambiamos el nombre de los ejes para que se vean mejor. Pero ademas eliminamos el nombre de la leyenda porque era un poco redundante (ya está en el título).

Temas

Nos queda una última cosa por hacer, cambiar la apariencia global del gráfico. {ggplot2} tiene muchos temas disponibles y para todos los gustos. Pero además hay otros paquetes que extienden las posibilidades, por ejemplo {ggthemes}.

Por defecto {ggplot2} usa theme_grey(), probemos theme_light():

observaciones %>% 
  filter(provincia != "ANTARTIDA") %>% 
  ggplot(aes(lon, lat)) +
  geom_point(aes(color = tmax_media, size = tmax_var), alpha = 0.7) +
  geom_sf(data = mapa, fill = NA, color = "black", size = 0.2, inherit.aes = FALSE) +
  scale_color_distiller(palette = "YlOrRd", direction = 1) +
  scale_size_area(max_size = 4, guide = NULL) +
  scale_x_continuous(labels = function(x) LonLabel(x, west = "°O")) +
  geom_text(aes(label = station),
            data = extremos_temperatura, # Esta capa usa la tabla extremos_temperatura!
            size = 2.5) +
  labs(title = "Temperatura máxima media",
       subtitle = "Agosto",
       caption = "El tamaño de cada circulo representa la variabilidad",
       x = "Longitud",
       y = "Latitud",
       color = "") +
  theme_light()
## Warning: Removed 1 rows containing missing values (geom_point).

Desafío

Ahora es tu turno. Elegí un tema que te guste y probalo. Además, si se te ocurre algún título mejor modificalo!

Junto con las funciones theme_...(), hay una función llamada theme() que permite cambiar la apariencia de cualquier elemento del gráfico. Tiene casi infinitas opciones y si algún momento te desvelas intentando cambiar esa línea o ese borde, seguro que theme() tiene alguna opción para hacer eso.

LS0tCnRpdGxlOiAiQXBhcmllbmNpYSBkZSBncsOhZmljb3MiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBoaWdobGlnaHQ6IHRhbmdvCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KG1ldFIpCmBgYAoKIyMgRXNjYWxhcwoKW1ByZXZpYW1lbnRlXSgwNi1ncmFmaWNvcy1JLmh0bWwpIGNvbWVudGFtb3MgcXVlIGVsICptYXBlbyogZGUgdW5hIHZhcmlhYmxlIGVuIHVuIGVsZW1lbnRvIGdlb23DqXRyaWNvLCBwb3IgZWplbXBsbyBjdWFuZG8gbGUgYXNpZ25hbW9zIGRpc3RpbnRvcyBjb2xvcmVzIGEgbG9zIHB1bnRvcyBxdWUgcmVwcmVzZW50YW4gY2FkYSB0aXBvIGRlIHRlbXBlcmF0dXJhLCB1c2EgdW5hICoqZXNjYWxhKiogcGFyYSBkZWZpbmlyLCBlbiBlc3RlIGNhc28sIHF1ZSBjb2xvciBsZSBjb3JyZXNwb25kZSBhIGNhZGEgZWxlbWVudG8uIAoKVGFtYmnDqW4gY2FtYmlhbW9zIGxhIGFwYXJpZW5jaWEgZGVsIHJlbGxlbm8gKG8gYGZpbGxgKSBkZSBsb3MgY29udG9ybm9zIHkgbGEgZm9ybWEgZGUgbG9zIHB1bnRvcyAobyBgc2hhcGVgKS4gUGFyYSBlc3RvLCB7Z2dwbG90Mn0gc2llbXByZSB1c2EgdW5hIGVzY2FsYSBxdWUgcG9kZW1vcyBtb2RpZmljYXIgZGUgYWN1ZXJkbyBhIG51ZXN0cm8gZ3VzdG8geSB0ZW5pZW5kbyBlbiBjdWVudGEgY8OzbW8gcXVlcmVtb3MgY29tdW5pY2FyIG51ZXN0cm9zIHJlc3VsdGFkb3MuIAoKUG9yIHN1cHVlc3RvLCBtb2RpZmljYXIgdW5hIGVzY2FsYSBpbXBsaWNhIHN1bWFyIHVuYSBudWV2YSBjYXBhIGFsIGdyw6FmaWNvIHN1bWFuZG8gdW5hIG51ZXZhIGZ1bmNpw7NuLiBUb2RhcyBsYXMgZnVuY2lvbmVzIGRlIGVzY2FsYSBjb21pZW56YW4gY29uIGBzY2FsZWAgKGRlIGVzY2FsYSBlbiBpbmdsw6lzKSwgZWwgdGlwbyBkZSBhcGFyaWVuY2lhIHF1ZSBxdWVyZW1vcyBtb2RpZmljYXIgKGBjb2xvcmAsIGBmaWxsYCwgYHNoYXBlYCwgZXRjKSB5IGVuIG11Y2hvcyBjYXNvcyB1biBub21icmUgbyB1bmEgY2FyYWN0ZXLDrXN0aWNhIGRlIGVzYSBlc2NhbGEuIAoKUGFyYSBtb3N0cmFyIGNvbW8gZnVuY2lvbmEsIHZhbW9zIGEgZGVzY2FyZ2FyIGRhdG9zIGRlIGxhcyBlc3RhY2lvbmVzIG1ldGVvcm9sw7NnaWNhcyBkZWwgU2VydmljaW8gTWV0ZW9yb2zDs2dpY28gTmFjaW9uYWwgcGFyYSBlbCBtZXMgZGUgYWdvc3RvIGRlIDIwMjAgeSBjb24gc3VlcnRlIGFsIGZpbmFsIGRlIGVzdGUgZG9jdW1lbnRvIHRlbmRyZW1vcyB1biBncsOhZmljbyBsaXN0byBwYXJhIHB1YmxpY2FyLgoKUGVybyBwb3Igc3VwdWVzdG8sIHByaW1lcm8gdGVuZW1vcyBxdWUgbWFuaXB1bGFyIGxvcyBkYXRvcyBwYXJhIHBvZGVyIHV0aWxpemFybG9zLiBFbiBwYXJ0aWN1bGFyIG5lY2VzaXRhcmVtb3MgaW5mb3JtYWNpw7NuIGRlIGxhcyBlc3RhY2lvbmVzLCBzdXMgbWV0YWRhdG9zLCBxdWUgc2UgZW5jdWVudHJhbiBlbiB1biBhcmNoaXZvIGRpc3RpbnRvLiBZIGRlIHBhc28gY2FsY3VsYW1vcyBsYSB2YXJpYWJsZSBxdWUgbm9zIGludGVyZXNhOiBsYSB0ZW1wZXJhdHVyYSBtw6F4aW1hIG1lZGlhIHBhcmEgYWdvc3RvLgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgPC0gcmVhZHI6OnJlYWRfY3N2KCJkYXRvcy9vYnNlcnZhY2lvbmVzX3Ntbi5jc3YiKSAlPiUgCiAgZ3JvdXBfYnkoc3RhdGlvbikgJT4lIAogIHN1bW1hcmlzZSh0bWF4X21lZGlhID0gbWVhbih0bWF4LCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICB0bWF4X3ZhciA9IHNkKHRtYXgsIG5hLnJtID0gVFJVRSksKQoKZXN0YWNpb25lcyA8LSByZWFkX2NzdigiZGF0b3MvZXN0YWNpb25lc19zbW4uY3N2IikgCgpvYnNlcnZhY2lvbmVzIDwtIGxlZnRfam9pbihvYnNlcnZhY2lvbmVzLCBlc3RhY2lvbmVzLCBieSA9IGMoInN0YXRpb24iID0gIm5vbWJyZSIpKSAlPiUgCiAgZmlsdGVyKHByb3ZpbmNpYSAhPSAiQU5UQVJUSURBIikKYGBgCgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcG9pbnQoKSAKYGBgCgpQb3IgYWhvcmEgZXN0ZSBncsOhZmljbyBubyBub3MgZGljZSBuYWRhLCBuZWNlc2l0YW1vcyBhZ3JlZ2FybGUgdW5hIGNhcGEgY29uIGVsIG1hcGEgdGFsIGN1YWwgaGljaW1vcyBwcmV2aWFtZW50ZS4gCgpgYGB7cn0KbWFwYSA8LSBybmF0dXJhbGVhcnRoOjpuZV9zdGF0ZXMoY291bnRyeSA9IGMoImFyZ2VudGluYSIpLCByZXR1cm5jbGFzcyA9ICJzZiIpCgpvYnNlcnZhY2lvbmVzICU+JSAKICBmaWx0ZXIocHJvdmluY2lhICE9ICJBTlRBUlRJREEiKSAlPiUgCiAgZ2dwbG90KGFlcyhsb24sIGxhdCkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc2YoZGF0YSA9IG1hcGEsIGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC4yLCBpbmhlcml0LmFlcyA9IEZBTFNFKSAKYGBgCgoKIyMjIEVzY2FsYSBkZSBjb2xvcmVzIHkgb3RyYXMgY2FyYWN0ZXLDrXN0aWNhcwoKU2kgYmllbiBhaG9yYSBwb2RlbW9zIGlkZW50aWZpY2FyIGRvbmRlIGVzdMOhbiBsYXMgZXN0YWNpb25lcywgbm8gdGVuZW1vcyBpbmZvcm1hY2nDs24gZGUgbGEgdGVtcGVyYXR1cmEgbcOheGltYSBtZWRpYS4gUG9kcsOtYW1vcyBjYW1iaWFyIGVsIGNvbG9yIGRlIGxvcyBwdW50b3MgcGFyYSBxdWUgcmVwcmVzZW50ZW4gZWwgdmFsb3IgcXVlIHRvbWEgZXNhIHZhcmlhYmxlIGVuIGNhZGEgZXN0YWNpw7NuLgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdG1heF9tZWRpYSkpICsKICBnZW9tX3NmKGRhdGEgPSBtYXBhLCBmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuMiwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgCmBgYAoKUGVybyBlc3RhIGVzY2FsYSBkZSBjb2xvcmVzIHF1ZSB1c2Ege2dncGxvdDJ9IHBvciBkZWZlY3RvIG5vIGVzIGRlIGxhcyBtZWpvcmVzLCBlcyBkaWbDrWNpbCBkaWZlcmVuY2lhciBlbnRyZSBsb3MgdmFsb3Jlcy4gRXhpc3RlbiBpbmZpbml0YXMgcGFsZXRhcyBkZSBjb2xvcmVzIHF1ZSBzZSBwdWVkZW4gdXNhciBlbiBnZ3Bsb3QsIGFsZ3VuYXMgcG9yIGVqZW1wbG8gYnVzY2FuIHBvZGVyIGRpc3Rpbmd1aXJzZSBzaSBpbXByaW1pbW9zIGVsIGdyw6FmaWNvIGVuIGJsYW5jbyB5IG5lZ3JvLCBvdHJhcyBlc3TDoW4gZGlzZcOxYWRhcyBwYXJhIHF1ZSBwZXJzb25hcyBjb24gZGFsdG9uaXNtbyBwdWVkYW4gZGlzdGluZ3VpciBsb3MgY29sb3Jlcy4gVW5hIGVzY2FsYSBvIHBhbGV0YSBkZSBjb2xvcmVzIG11eSB1c2FkYSBlcyBbKip2aXJpZGlzKipdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy92aXJpZGlzL3ZpZ25ldHRlcy9pbnRyby10by12aXJpZGlzLmh0bWwpIHF1ZSBmdWUgY3JlYWRhIGp1c3RhbWVudGUgcGFyYSByZXNvbHZlciBlc3RlIHkgb3Ryb3MgcHJvYmxlbWFzLiBUYW1iacOpbiBleGlzdGUgb3RyYSBncmFuIGZhbWlsaWEgZGUgcGFsZXRhcyBkZSBjb2xvcmVzIGxsYW1hZGEgWyoqQ29sb3JCcmV3ZXIqKl0oaHR0cHM6Ly9jb2xvcmJyZXdlcjIub3JnLykuCgpWYW1vcyBhIHByb2JhciBsYSBwYWxldGEgIllsT3JSZCIgLCBlc3RhIHBhbGV0YSBlcyAqc2VjdWVuY2lhbCogeSBlcyBqdXN0byBsbyBxdWUgbmVjZXNpdGFtb3MgcGFyYSB2aXN1YWxpemFyIHVuYSB2YXJpYWJsZSBjb250aW51YSBjb21vIGxhIHRlbXBlcmF0dXJhLiBDw7NtbyBlc3RhbW9zIG1vZGlmaWNhbmRvIGVsICpjb2xvciosIGxhIGZ1bmNpw7NuIGEgdXNhciBzZXLDoSBgc2NhbGVfY29sb3JfZGlzdGlsbGVyKClgLCBsYSBoZXJtYW5hICpjb250aW51YSogZGUgYHNjYWxlX2NvbG9yX2JyZXdlcigpYDoKCmBgYHtyfQpvYnNlcnZhY2lvbmVzICU+JSAKICBmaWx0ZXIocHJvdmluY2lhICE9ICJBTlRBUlRJREEiKSAlPiUgCiAgZ2dwbG90KGFlcyhsb24sIGxhdCkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHRtYXhfbWVkaWEpKSArCiAgZ2VvbV9zZihkYXRhID0gbWFwYSwgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAwLjIsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIocGFsZXR0ZSA9ICJZbE9yUmQiLCBkaXJlY3Rpb24gPSAxKQpgYGAKCkVuIGVzdGUgY2FzbyB0YW1iacOpbiBhZ3JlZ2Ftb3MgZWwgYXJndW1lbnRvIGBkaXJlY3Rpb24gPSAxYCBwYXJhIHF1ZSBsYSBwYWxldGEgZGUgY29sb3JlcyBsb3MgbXVlc3RyZSBkZSBtw6FzIG9zY3Vyb3MgYSBtw6FzIGNsYXJvcy4gRXN0YSBkZWNpc2nDs24gZXMgZXN0w6l0aWNhIHkgbXVjaGFzIHZlY2VzIGRlcGVuZGUgZGUgbGFzIHZhcmlhYmxlcyBhIGdyYWZpY2FyLiAKCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgpBIG1vZG8gZGUgcHJ1ZWJhLCBjYW1iaWEgbGEgcGFsZXRhIGRlIGNvbG9yZXMgYWN0dWFsIHBvciBsYSBkZSBWaXJpZGlzLiBQYXJhIGVzbyB0ZW7DqXMgcXVlIHVzYXIgYHNjYWxlX2NvbG9yX3ZpcmlkaXNfYygpYC4gTGEgImMiIGRlbCBmaW5hbCB2aWVuZSBkZSAqY29udGludW91cyogeSBzZSB1c2EgcGFyYSB2YXJpYWJsZXMgY29udGludWFzLCBtaWVudHJhcyBxdWUgc2kgbG9zIGRhdG9zIHNvbiBkaXNjcmV0b3MgbyBjYXRlZ29yw61hcywgc2UgdXNhICJkIiBhbCBmaW5hbC4KOjo6CgpOdWVzdHJvIGdyw6FmaWNvIHZhIHF1ZWRhbmRvIG1lam9yIHkgcG9kZW1vcyBhcHJvdmVjaGFyIGxhIGNhcGFjaWRhZCBkZSB7Z2dwbG90Mn0gZGUgKm1hcGVhciogdmFyaWFibGVzIGEgbG9zIGVsZW1lbnRvcyBkZWwgZ3LDoWZpY28geSB2aXN1YWxpemFyIGxhIHZhcmlhYmlsaWRhZCBkZSBsYSB0ZW1wZXJhdHVyYSBtw6F4aW1hIG1vZGlmaWNhbmRvIGVsIHRhbWHDsW8gZGUgbG9zIHB1bnRvcyBkZSBhY3VlcmRvIGFsIGRlc3bDrW8gZXN0w6FuZGFyLgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdG1heF9tZWRpYSwgc2l6ZSA9IHRtYXhfdmFyKSkgKwogIGdlb21fc2YoZGF0YSA9IG1hcGEsIGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC4yLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKHBhbGV0dGUgPSAiWWxPclJkIiwgZGlyZWN0aW9uID0gMSkKYGBgCgpFcyBpbnRlcmVzYW50ZSB2ZXIgY29tbyBsYXMgdGVtcGVyYXR1cmFzIG3DoXhpbWFzIG1lZGlhcyBtYXlvcmVzIHRhbWJpw6luIHRpZW5lbiBtYXlvciB2YXJpYWJpbGlkYWQuIFBlcm8gYWhvcmEgYWxndW5vcyBwdW50b3Mgc2Ugc3VwZXJwb25lbiB5IGVzIHBvc2libGUgcXVlIG5vIGVzdGVtb3MgdmllbmRvIGFsZ3VuYXMgZXN0YWNpb25lcy4gVmFtb3MgYSBhcnJlZ2xhciBlc28gYWdyZWdhbmRvIHRyYW5zcGFyZW5jaWEgeSBkZSBwYXNvIG1vZGlmaWNhciBlbCB0YW1hw7FvIGRlIGxvcyBwdW50b3MgY29uIGxhIGVzY2FsYSBjb3JyZXNwb25kaWVudGUgYHNjYWxlX3NpemVfYXJlYSgpYCB5IHNhY2FyIGxhIGxlZ2VuZGEgY29uIGBndWlkZSA9IE5VTExgLgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdG1heF9tZWRpYSwgc2l6ZSA9IHRtYXhfdmFyKSwgYWxwaGEgPSAwLjcpICsKICBnZW9tX3NmKGRhdGEgPSBtYXBhLCBmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuMiwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihwYWxldHRlID0gIllsT3JSZCIsIGRpcmVjdGlvbiA9IDEpICsKICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemUgPSA0LCBndWlkZSA9IE5VTEwpCmBgYAoKIyMjIEVzY2FsYXMgZGUgZWplcwoKQWhvcmEgcXVlIGxhIGluZm9ybWFjacOzbiBkZWwgZ3LDoWZpY28gc2UgdmUgYmllbiwgcGFzZW1vcyBhIGxvcyBlamVzLiBBbCBpZ3VhbCBxdWUgcGFyYSBlbCBjb2xvciwgZWwgdGFtYcOxbyB5IG90cm9zIGVsZW1lbnRvcyBkZWwgZ3LDoWZpY28sIHBhcmEgbG9zIGVqZXMgdGFtYmnDqW4gZXhpc3RlbiBmdW5jaW9uZXMgInNjYWxlIi4gRW4gZXN0ZSBjYXNvIGxhcyBlc2NhbGFzIHF1ZSBtb2RpZmljYW4gbG9zIGVqZXMganVzdGFtZW50ZSBjb21pZW56YW4gY29uIGBzY2FsYV94X2AgbyBgc2NhbGVfeV9gIHNlZ8O6biBzZWEgZWwgY2FzbyB5IGhheSB1bmEgZ3JhbiB2YXJpZWRhZCBkZSBvcGNpb25lcyBkZXBlbmRpZW5kbyBkZWwgdGlwbyBkZSBkYXRvIHF1ZSBlc3RhbW9zIGdyYWZpY2FuZG8gZW4gY2FkYSBlamUuIAoKU2kgZW4gZWwgZWplIHkgZ3JhZmljYW1vcyB1bmEgdmFyaWFibGUgZGlzY3JldGEgZW50b25jZXMgcG9kcmVtb3MgbW9kaWZpY2FyIHN1IGFzcGVjdG8gY29uIGBzY2FsZV95X2Rpc2NyZXRlKClgLiBFbiBlc3RlIGNhc28gbGEgZnVuY2nDs24gYGdlb21fc2YoKWAgYXV0b23DoXRpY2FtZW50ZSBtb2RpZmljYSBsYSBhcGFyaWVuY2lhIGRlIGxvcyBlamVzIHkgbm8gcmVxdWllcmVuIGRlIG11Y2hvIHRyYWJham8uIAoKUGVybyBxdWVyZW1vcyBxdWUgbnVlc3RybyBncsOhZmljbyBxdWVkZSBsaXN0byBwYXJhIHB1YmxpY2FyIHkgbGEgIlciIGRlIHdlc3QgKG9lc3RlIGVuIGluZ2zDqXMpIHB1ZWRlIG5vIHNlciBtdXkgYW1pZ2FibGUuIE1vZGlmaXF1ZW1vcyBlbnRvbmNlcyBlbCBlamUgeCBjb24gbGEgZnVuY2nDs24gYHNjYWxlX3hfY29udGludW91cygpYC4gRW4gZXN0ZSBjYXNvIHF1ZXJlbW9zIG1vZGlmaWNhciBsYXMgZXRpcXVldGFzIG8gYGxhYmVsc2AgeSBwYXJhIGVzbyB1c2FyZW1vcyBsYSBmdW5jacOzbiBgTG9uTGFiZWwoKWAgZGVsIHBhcXVldGUge21ldFJ9LiBFc3RhIGZ1bmNpw7NuIHJlY2liZSB1bmEgZnVuY2nDs24gYW7Ds25pbWEgcXVlIHRvbWEgY2FkYSBsb25naXR1ZCB5IGVsIGNhbWJpYSBlbCAiwrBXwrAgcG9yICLCsE8iLgoKCmBgYHtyfQpvYnNlcnZhY2lvbmVzICU+JSAKICBmaWx0ZXIocHJvdmluY2lhICE9ICJBTlRBUlRJREEiKSAlPiUgCiAgZ2dwbG90KGFlcyhsb24sIGxhdCkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHRtYXhfbWVkaWEsIHNpemUgPSB0bWF4X3ZhciksIGFscGhhID0gMC43KSArCiAgZ2VvbV9zZihkYXRhID0gbWFwYSwgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAwLjIsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIocGFsZXR0ZSA9ICJZbE9yUmQiLCBkaXJlY3Rpb24gPSAxKSArCiAgc2NhbGVfc2l6ZV9hcmVhKG1heF9zaXplID0gNCwgZ3VpZGUgPSBOVUxMKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGZ1bmN0aW9uKHgpIExvbkxhYmVsKHgsIHdlc3QgPSAiwrBPIikpCmBgYAoKCiMjIEVsZW1lbnRvcyBkZSB0ZXh0bwoKWWEgc3VtYW1vcyAzIGVzY2FsYXMgeSBlbCBncsOhZmljbyB5YSBzZSB2ZSBtdXkgYmllbi4gwr9Dw7NtbyBoYWNlbW9zIHNpIHF1ZXJlbW9zIGlkZW50aWZpY2FyIGVzdGFjaW9uZXMgaW5kaXZpZHVhbGVzPyBQb3IgYWhvcmEgZXMgZGlmw61jaWwsIHBlcm8gcG9kcsOtYW1vcyBhZ3JlZ2FyIGV0aXF1ZXRhcyBkZSB0ZXh0byBjb24gZWwgbm9tYnJlIGRlIGNhZGEgZXN0YWNpw7NuIGFsIGxhZG8gZGUgY2FkYSBwdW50byB1c2FuZG8gYGdlb21fdGV4dCgpYCwgeSBlbiBlc3RlIGNhc28gbGEgYXBhcmllbmNpYSBlc3TDoSBkYWRhIHBvciBgbGFiZWxgIG8gZXRpcXVldGE6CgpgYGB7cn0Kb2JzZXJ2YWNpb25lcyAlPiUgCiAgZmlsdGVyKHByb3ZpbmNpYSAhPSAiQU5UQVJUSURBIikgJT4lIAogIGdncGxvdChhZXMobG9uLCBsYXQpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0bWF4X21lZGlhLCBzaXplID0gdG1heF92YXIpLCBhbHBoYSA9IDAuNykgKwogIGdlb21fc2YoZGF0YSA9IG1hcGEsIGZpbGwgPSBOQSwgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC4yLCBpbmhlcml0LmFlcyA9IEZBTFNFKSArCiAgc2NhbGVfY29sb3JfZGlzdGlsbGVyKHBhbGV0dGUgPSAiWWxPclJkIiwgZGlyZWN0aW9uID0gMSkgKwogIHNjYWxlX3NpemVfYXJlYShtYXhfc2l6ZSA9IDQsIGd1aWRlID0gTlVMTCkgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBmdW5jdGlvbih4KSBMb25MYWJlbCh4LCB3ZXN0ID0gIsKwTyIpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHN0YXRpb24pKSAKYGBgCgpQZXJvIG5vcyBvbHZpZGFtb3MgcXVlIHRlbmVtb3MgbcOhcyBkZSAxMDAgZXN0YWNpb25lcywgZXMgaW1wb3NpYmxlIGFncmVnYXJsZSBldGlxdWV0YXMgYSB0b2Rvcy4gUGVybyBwb2Ryw61hbW9zIHF1ZXJlciByZXNhbHRhciBhbGd1bm9zLCB0YWwgdmV6IGxvcyBkZSB1bmEgcmVnacOzbiBlbiBwYXJ0aWN1bGFyIG8gbG9zIHF1ZSBjdW1wbGVuIGNvbiBsYSBjb25kaWNpw7NuIGRlIHRlbmVyIGxhcyBtYXlvcmVzIHRlbXBlcmF0dXJhcyBtw6F4aW1hcyBtZWRpYXMuIFBhcmEgZXNvIHZhbW9zIGEgZ2VuZXJhcm5vcyB1bmEgbnVldmEgdGFibGEgY29uIGxhcyBlc3RhY2lvbmVzIHF1ZSBxdWVyZW1vcyByZXNhbHRhciB5IGRlIHBhc28gdXNhcmxhIGRlbnRybyBkZSBgZ2VvbV90ZXh0KClgLgoKYGBge3J9CmV4dHJlbW9zX3RlbXBlcmF0dXJhIDwtIG9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBmaWx0ZXIodG1heF9tZWRpYSA9PSBtYXgodG1heF9tZWRpYSwgbmEucm0gPSBUUlVFKSB8CiAgICAgICAgICAgdG1heF9tZWRpYSA9PSBtaW4odG1heF9tZWRpYSwgbmEucm0gPSBUUlVFKSkgIyBFc3RhY2lvbmVzIGNvbiBleHRyZW1vcyEKCm9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdG1heF9tZWRpYSwgc2l6ZSA9IHRtYXhfdmFyKSwgYWxwaGEgPSAwLjcpICsKICBnZW9tX3NmKGRhdGEgPSBtYXBhLCBmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuMiwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihwYWxldHRlID0gIllsT3JSZCIsIGRpcmVjdGlvbiA9IDEpICsKICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemUgPSA0LCBndWlkZSA9IE5VTEwpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gZnVuY3Rpb24oeCkgTG9uTGFiZWwoeCwgd2VzdCA9ICLCsE8iKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzdGF0aW9uKSwKICAgICAgICAgICAgZGF0YSA9IGV4dHJlbW9zX3RlbXBlcmF0dXJhLCAjIEVzdGEgY2FwYSB1c2EgbGEgdGFibGEgZXh0cmVtb3NfdGVtcGVyYXR1cmEhCiAgICAgICAgICAgIHNpemUgPSAyLjUpIApgYGAKCjo6OiB7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQoKRGVsIGPDs2RpZ28gYW50ZXJpb3Igc3VyZ2UgYWxnbyBtdXkgaW1wb3J0YW50ZTogZXMgcG9zaWJsZSBnZW5lcmFyIGNhcGFzIGVuIHVuIGdyw6FmaWNvIHVzYW5kbyB1bmEgZGF0YS5mcmFtZSAqZGlzdGludG8qIGFsIHF1ZSB1c2Ftb3MgcGFyYSBncmFmaWNhciBsYXMgY2FwYXMgYW50ZXJpb3Jlcy4gRXN0byBlcyDDunRpbCBwcmluY2lwYWxtZW50ZSBwYXJhIGRlZmluaXIgZXRpcXVldGFzIG8gcmVzYWx0YXIgZGV0ZXJtaW5hZGFzIG9ic2VydmFjaW9uZXMuIAoKWSBlbCB0cnVjbyBlc3TDoSBlbiBxdWUgYW1ib3MgZGF0YS5mcmFtZXMgdGllbmVuIGxhcyB2YXJpYWJsZXMgYGxvbmAgeSBgbGF0YCB5IGVudG9uY2VzIHtnZ3Bsb3QyfSBwdWVkZSBpZGVudGlmaWNhciBlbiBxdWUgcGFydGUgZGVsIGdyw6FmaWNvIChlbiBxdWUgdmFsb3JlcyBkZSB4IHkgZW4gcXVlIHZhbG9yZXMgZGUgeSkgY29sb2NhciBjYWRhIGVsZW1lbnRvLgo6OjoKClZlYW1vcyBhaG9yYSB1bmEgKGRlIHZhcmlhcykgbWFuZXJhcyBhZ3JlZ2FyIG8gbW9kaWZpY2FyIGVsZW1lbnRvcyBkZSB0ZXh0byBlbiBlbCBncsOhZmljby4gVmFtb3MgYSB1c2FyIHVuYSBudWV2YSBmdW5jacOzbiAoeSB1bmEgbnVldmEgY2FwYSEpLCBgbGFicygpYDoKCmBgYHtyfQpvYnNlcnZhY2lvbmVzICU+JSAKICBmaWx0ZXIocHJvdmluY2lhICE9ICJBTlRBUlRJREEiKSAlPiUgCiAgZ2dwbG90KGFlcyhsb24sIGxhdCkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHRtYXhfbWVkaWEsIHNpemUgPSB0bWF4X3ZhciksIGFscGhhID0gMC43KSArCiAgZ2VvbV9zZihkYXRhID0gbWFwYSwgZmlsbCA9IE5BLCBjb2xvciA9ICJibGFjayIsIHNpemUgPSAwLjIsIGluaGVyaXQuYWVzID0gRkFMU0UpICsKICBzY2FsZV9jb2xvcl9kaXN0aWxsZXIocGFsZXR0ZSA9ICJZbE9yUmQiLCBkaXJlY3Rpb24gPSAxKSArCiAgc2NhbGVfc2l6ZV9hcmVhKG1heF9zaXplID0gNCwgZ3VpZGUgPSBOVUxMKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGZ1bmN0aW9uKHgpIExvbkxhYmVsKHgsIHdlc3QgPSAiwrBPIikpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3RhdGlvbiksCiAgICAgICAgICAgIGRhdGEgPSBleHRyZW1vc190ZW1wZXJhdHVyYSwgIyBFc3RhIGNhcGEgdXNhIGxhIHRhYmxhIGV4dHJlbW9zX3RlbXBlcmF0dXJhIQogICAgICAgICAgICBzaXplID0gMi41KSArCiAgbGFicyh0aXRsZSA9ICJUZW1wZXJhdHVyYSBtw6F4aW1hIG1lZGlhIiwKICAgICAgIHN1YnRpdGxlID0gIkFnb3N0byIsCiAgICAgICBjYXB0aW9uID0gIkVsIHRhbWHDsW8gZGUgY2FkYSBjaXJjdWxvIHJlcHJlc2VudGEgbGEgdmFyaWFiaWxpZGFkIiwKICAgICAgIHggPSAiTG9uZ2l0dWQiLAogICAgICAgeSA9ICJMYXRpdHVkIiwKICAgICAgIGNvbG9yID0gIiIpCmBgYAoKQWdyZWdhbW9zIHVuIHTDrXR1bG8sIHVuIHN1YnRpdHVsbywgZWwgZXDDrWdyYWZlIGRlIGxhIGZpZ3VyYSAoKmNhcHRpb24qKSBwYXJhIGxhcyBhY2xhcmFjaW9uZXMgeSBjYW1iaWFtb3MgZWwgbm9tYnJlIGRlIGxvcyBlamVzIHBhcmEgcXVlIHNlIHZlYW4gbWVqb3IuIFBlcm8gYWRlbWFzIGVsaW1pbmFtb3MgZWwgbm9tYnJlIGRlIGxhIGxleWVuZGEgcG9ycXVlIGVyYSB1biBwb2NvIHJlZHVuZGFudGUgKHlhIGVzdMOhIGVuIGVsIHTDrXR1bG8pLiAKCiMjIFRlbWFzCgpOb3MgcXVlZGEgdW5hIMO6bHRpbWEgY29zYSBwb3IgaGFjZXIsIGNhbWJpYXIgbGEgYXBhcmllbmNpYSBnbG9iYWwgZGVsIGdyw6FmaWNvLiB7Z2dwbG90Mn0gdGllbmUgbXVjaG9zICp0ZW1hcyogZGlzcG9uaWJsZXMgeSBwYXJhIHRvZG9zIGxvcyBndXN0b3MuIFBlcm8gYWRlbcOhcyBoYXkgb3Ryb3MgcGFxdWV0ZXMgcXVlIGV4dGllbmRlbiBsYXMgcG9zaWJpbGlkYWRlcywgcG9yIGVqZW1wbG8gW3tnZ3RoZW1lc31dKGh0dHBzOi8vZ2l0aHViLmNvbS9qcm5vbGQvZ2d0aGVtZXMpLgoKUG9yIGRlZmVjdG8ge2dncGxvdDJ9IHVzYSBgdGhlbWVfZ3JleSgpYCwgcHJvYmVtb3MgYHRoZW1lX2xpZ2h0KClgOgoKYGBge3J9Cm9ic2VydmFjaW9uZXMgJT4lIAogIGZpbHRlcihwcm92aW5jaWEgIT0gIkFOVEFSVElEQSIpICU+JSAKICBnZ3Bsb3QoYWVzKGxvbiwgbGF0KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdG1heF9tZWRpYSwgc2l6ZSA9IHRtYXhfdmFyKSwgYWxwaGEgPSAwLjcpICsKICBnZW9tX3NmKGRhdGEgPSBtYXBhLCBmaWxsID0gTkEsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuMiwgaW5oZXJpdC5hZXMgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG9yX2Rpc3RpbGxlcihwYWxldHRlID0gIllsT3JSZCIsIGRpcmVjdGlvbiA9IDEpICsKICBzY2FsZV9zaXplX2FyZWEobWF4X3NpemUgPSA0LCBndWlkZSA9IE5VTEwpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gZnVuY3Rpb24oeCkgTG9uTGFiZWwoeCwgd2VzdCA9ICLCsE8iKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBzdGF0aW9uKSwKICAgICAgICAgICAgZGF0YSA9IGV4dHJlbW9zX3RlbXBlcmF0dXJhLCAjIEVzdGEgY2FwYSB1c2EgbGEgdGFibGEgZXh0cmVtb3NfdGVtcGVyYXR1cmEhCiAgICAgICAgICAgIHNpemUgPSAyLjUpICsKICBsYWJzKHRpdGxlID0gIlRlbXBlcmF0dXJhIG3DoXhpbWEgbWVkaWEiLAogICAgICAgc3VidGl0bGUgPSAiQWdvc3RvIiwKICAgICAgIGNhcHRpb24gPSAiRWwgdGFtYcOxbyBkZSBjYWRhIGNpcmN1bG8gcmVwcmVzZW50YSBsYSB2YXJpYWJpbGlkYWQiLAogICAgICAgeCA9ICJMb25naXR1ZCIsCiAgICAgICB5ID0gIkxhdGl0dWQiLAogICAgICAgY29sb3IgPSAiIikgKwogIHRoZW1lX2xpZ2h0KCkKYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgpBaG9yYSBlcyB0dSB0dXJuby4gRWxlZ8OtIHVuIFt0ZW1hIHF1ZSB0ZSBndXN0ZV0oaHR0cHM6Ly9lcy5yNGRzLmhhZGxleS5uei9pbWFnZXMvdmlzdWFsaXphdGlvbi10aGVtZXMucG5nKXsuYWxlcnQtbGlua30geSBwcm9iYWxvLiBBZGVtw6FzLCBzaSBzZSB0ZSBvY3VycmUgYWxnw7puIHTDrXR1bG8gbWVqb3IgbW9kaWZpY2FsbyEgCjo6OgoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CgpKdW50byBjb24gbGFzIGZ1bmNpb25lcyBgdGhlbWVfLi4uKClgLCBoYXkgdW5hIGZ1bmNpw7NuIGxsYW1hZGEgYHRoZW1lKClgIHF1ZSBwZXJtaXRlIGNhbWJpYXIgbGEgYXBhcmllbmNpYSBkZSBjdWFscXVpZXIgZWxlbWVudG8gZGVsIGdyw6FmaWNvLiBUaWVuZSBjYXNpIGluZmluaXRhcyBvcGNpb25lcyB5IHNpIGFsZ8O6biBtb21lbnRvIHRlIGRlc3ZlbGFzIGludGVudGFuZG8gY2FtYmlhciBlc2EgbMOtbmVhIG8gZXNlIGJvcmRlLCBzZWd1cm8gcXVlIGB0aGVtZSgpYCB0aWVuZSBhbGd1bmEgb3BjacOzbiBwYXJhIGhhY2VyIGVzby4KCjo6OgoKPGRpdiBjbGFzcz0iYnRuLWdyb3VwIiByb2xlPSJncm91cCIgYXJpYS1sYWJlbD0iTmF2ZWdhY2nDs24iPgogIDxhIGhyZWY9ICIwOS1ncmFmaWNvcy1JSS5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPkFudGVyaW9yPC9hPgogIDxhIGhyZWY9ICIxMS1yZXBvcnRlcy1JSS5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPlNpZ3VpZW50ZTwvYT4KPC9kaXY+Cg==