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==