Preparación de Datos


Paso 1. Instalación y carga del paquete Tidyverse

if (!requireNamespace("tidyverse", quietly = TRUE)) 
  install.packages("tidyverse") 
library(tidyverse) 


Paso 2. Función para construir rutas.

construir_ruta <- function(...){
  return(file.path(getwd(), ...))
} 


Paso 3. Obtener las rutas de datos y datos de origen (data-meses)

ruta_datos <- construir_ruta("data") 
ruta_datos_de_origen <- construir_ruta("data", "data-meses")


Paso 4. Cargar y mostrar la lista de archivos csv correspondiente a los datos mensuales del proyecto.

archivos_csv <- list.files(path = ruta_datos_de_origen, pattern = "\\.csv$", full.names = FALSE, recursive = TRUE)
print(archivos_csv)


Paso 5. Iterar sobre los archivos csv para visualizar las columnas.

for (archivo in archivos_csv) { 
  tryCatch({
    datos <- read.csv(file.path(ruta_datos_de_origen, archivo)) 
    print(paste('NOMBRE ARCHIVO: ', archivo))
    print(colnames(datos))
    str(datos)
    cat("\n")
  }, error = function(e) {
    message("Error leyendo archivo: ", archivo, "\n", e)
  })
}


Paso 6. Leer y combinar todos los archivos en un único dataframe.

dataframe_combinado <- archivos_csv %>%
  map_df(~ read_csv(file.path(ruta_datos_de_origen, .))) # Combina automáticamente todos los datos
str(dataframe_combinado)
head(dataframe_combinado)


Paso 7. Exportar el dataframe a csv.

write.csv(dataframe_combinado, file.path(ruta_datos, "2024-tripdata.csv"), row.names = FALSE)




Limpieza de Datos


Paso 1. Instalar y cargar paquete Lubridate

install.packages('lubridate')
library(lubridate)


Paso 2. Importar nuevas data y visualizar resumen de datos

data <- read_csv(file.path(ruta_datos, '2024-tripdata.csv'))
head(data)
summary(data)
str(data)
glimpse(data)


Paso 3. Eliminar columnas innecesarias

data <- data %>% 
  select(-c('start_lat', 'start_lng', 'end_lat', 'end_lng'))
glimpse(data)


Paso 4. Renombrar las columnas

tripdata_rename <- rename(data, 
                          id_recorrido = ride_id,
                          tipo_bicicleta = rideable_type,
                          inicio_recorrido = started_at,
                          final_recorrido = ended_at,
                          estacion_inicial = start_station_name,
                          estacion_final = end_station_name,
                          id_estacion_inicial = start_station_id,
                          id_estacion_final = end_station_id,
                          tipo_cliente = member_casual)
glimpse(tripdata_rename)


Paso 5. Eliminar registros duplicados

tripdata_rename <- tripdata_rename %>%  distinct() 


Paso 6. Identificar tipo de usuarios y tipos de bicicletas

tripdata_rename %>% 
  group_by(tipo_bicicleta,tipo_cliente) %>%
  summarize(n())


Paso 7. Mutar la dataframe:

tripdata_new <- tripdata_rename %>% 
  mutate(tiempo_recorrido = as.numeric(difftime(final_recorrido, inicio_recorrido, units = 'mins')),
         mes = format(as.Date(inicio_recorrido), "%B"),
         dia_semana = weekdays(as.Date(inicio_recorrido)),
         tipo_cliente = recode(tipo_cliente,
                               "member" = 'suscriptor',
                               "casual" = 'cliente'),
         tipo_bicicleta = recode(tipo_bicicleta,
                                 'classic_bike' = 'bicicleta clasica',
                                 'electric_bike' = 'bicicleta electrica',
                                 'electric_scooter' = 'scooter electrico'))
glimpse(tripdata_new)
head(tripdata_new)


Paso 8. Contar valores inferiores a cero en la columna tiempo_recorrido

valores_negativos <- sum(tripdata_new$tiempo_recorrido < 0, na.rm = TRUE)
print(paste("Valores menores a 0 en tiempo_recorrido:", valores_negativos))


Paso 9. Fitrar recorrido con valores superiores a cero.

tripdata_new <- tripdata_new %>% 
  filter(tiempo_recorrido > 0)


Paso 10. Tratar valores nulos

  1. Mostrar valores nulos, cantidad y porcentaje por columnas.
    colSums(is.na(tripdata_new))
    porcentaje_nulos <- sapply(tripdata_new[, c("id_estacion_inicial", "estacion_inicial", "id_estacion_final", "estacion_final")], function(x) mean(is.na(x)) * 100)
    print(porcentaje_nulos)
  1. Reemplazar valores nulos.
    tripdata_new <- tripdata_new %>%
      replace_na(list(
        estacion_inicial = "Desconocido",
        id_estacion_inicial = "Desconocido",
        estacion_final = "Desconocido",
        id_estacion_final = "Desconocido"
      ))


Paso 11. Formatear columnas tipo caracter

tripdata_new <- tripdata_new %>%
  mutate(
    across(where(is.character), ~ str_trim(.)),       # Eliminar espacios en blanco
    estacion_inicial = str_to_title(estacion_inicial),  # Inicial mayúscula
    estacion_final = str_to_title(estacion_final)     # Igual para final
  )
head(tripdata_new)


Paso 12. Manejo de columnas en conflicto

  1. Agrupar las columnas con datos en conflicto por incosistencia de datos y contar las ocurrencias.
    datos_conflictos_inicial <- tripdata_new %>%
      group_by(id_estacion_inicial, estacion_inicial) %>%
      summarize(conteo = n(), .groups = "drop") %>%
      ungroup() %>% group_by(id_estacion_inicial) %>%
      filter(n() > 1) 
    print(datos_conflictos_inicial)
  1. Eliminar columnas para evitar inconsistencia de datos y sesgo.
tripdata_new <- tripdata_new %>% 
  select(-c('id_estacion_inicial','estacion_inicial','id_estacion_final','estacion_final'))
glimpse(tripdata_new)


Paso 13. Exportar dataframe limpio a csv.

write.csv(tripdata_new, file.path(ruta_datos, "2024-tripdata-clean.csv"), row.names = FALSE)



Análisis y Visualización de Datos


Paso 1. Cargar los paquetes necesarios.

install.packages('scales')
library(scales)
library(ggplot2)


Paso 2. Importar datos limpios.

data_clean <- read_csv(file.path(ruta_datos, '2024-tripdata-clean.csv'))
glimpse(data_clean)
head(data_clean)


Paso 3. Análisis descriptivos de tiempo de recorrido

summary(data_clean$tiempo_recorrido)


Paso 4. Análisis comparativo entre clientes y suscriptores

aggregate(data_clean$tiempo_recorrido ~ data_clean$tipo_cliente, FUN = mean)
aggregate(data_clean$tiempo_recorrido ~ data_clean$tipo_cliente, FUN = median)
aggregate(data_clean$tiempo_recorrido ~ data_clean$tipo_cliente, FUN = max)
aggregate(data_clean$tiempo_recorrido ~ data_clean$tipo_cliente, FUN = min)


Paso 5. Definir días de la semana y meses del año

data_clean$mes <- ordered(data_clean$mes, levels=c('enero','febrero','marzo','abril','mayo','junio','julio','agosto','septiembre','octubre','noviembre','diciembre'))
data_clean$dia_semana <- ordered(data_clean$dia_semana, levels=c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado","domingo"))


Paso 6. Vectores ordenados

  1. Días de la semana ordenados de lunes a domingo
  dias_semana_ordenados <- c("lunes", "martes", "miércoles", "jueves", "viernes", "sábado", "domingo")
  1. Meses del año, ordenados de enero a diciembre.
  meses_ordenados <- c("enero", "febrero", "marzo", "abril", "mayo", "junio", 
                         "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre")



Paso 7. Definir una paleta de colores específica para cada tipo de cliente

colores_clientes <- c("suscriptor" = "#1b9e77", "cliente" = "#5F02D9")


Paso 8. Visualizar tiempo promedio diario entre clientes y suscriptores

aggregate(data_clean$tiempo_recorrido ~ data_clean$tipo_cliente + data_clean$dia_semana, FUN = mean)


Paso 9. Visualizaciones Periodo Mensual

  1. Resumen: cantidad de recorridos y tiempo promedio de recorridos agrupados por tipo de cliente y mes
# Verificar los valores únicos en la columna 'tipo_cliente'
unique(data_clean$tipo_cliente)
# Resumir datos por mes
data_resumen_mes <- data_clean %>%
  mutate(mes = factor(mes, levels = meses_ordenados)) %>%
  group_by(tipo_cliente, mes) %>%
  summarise(
    cantidad_recorridos = n(),
    tiempo_promedio = mean(tiempo_recorrido, na.rm = TRUE),
    .groups = "drop"
  )
  1. Visualización: Tendencia de recorridos mensuales por tipo de cliente
# Visualización: Gráfico de área por tendencias mensuales de recorridos
ggplot(data_resumen_mes,aes(x = mes, y = cantidad_recorridos, group = tipo_cliente, fill = tipo_cliente)) +
  geom_area(position = "stack") +
  scale_fill_manual(values = colores_clientes) + # Aplicar la paleta de colores específica
  scale_y_continuous(labels = comma) + # Cambia el formato del eje Y
  labs(title = "Tendencias Mensuales de Recorridos por Tipo de Cliente",
       subtitle = "Cyclistics, Chicago 2024", # Subtítulo
       x = "Meses",
       y = "Cantidad de Recorridos") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5),  # Alinea el título a la derecha
    plot.subtitle = element_text(hjust = 0.5), # Alinea el subtítulo a la derecha
    axis.text.x = element_text(margin = margin(t = 30, b=5)), # Añadir margen superior a las etiquetas del eje X 
    axis.text.y = element_text(margin = margin(r = 30, l=5)), # Añadir margen derecho a las etiquetas del eje Y
    plot.margin = margin(t = 20, b = 20, l = 20, r = 20) # Añadir margen superior para subir el título y subtítulo
  ) +
  annotate("text", x = Inf, y = -Inf, label = "Fuente: Cyclistic", hjust = 1.1, vjust = -0.5, size = 3)


  1. Visualización: Tiempo medio recorrido mensual por tipo de cliente
ggplot(data_resumen_mes,aes(x = mes, y = tiempo_promedio, group = tipo_cliente, fill = tipo_cliente)) +
  geom_area(position = "stack") +
  scale_fill_manual(values = colores_clientes) + # Aplicar la paleta de colores específica
  scale_y_continuous(labels = comma) + # Cambia el formato del eje Y
  labs(title = "Tiempo Promedio Mensual Recorrido por Tipo de Cliente",
       subtitle = "Cyclistics, Chicago 2024", # Subtítulo
       x = "Meses",
       y = "Tiempo promedio (min)") +
  theme_minimal() +
  theme(
    plot.title = element_text(hjust = 0.5),  # Alinea el título a la derecha
    plot.subtitle = element_text(hjust = 0.5), # Alinea el subtítulo a la derecha
    axis.text.x = element_text(margin = margin(t = 30, b=5)), # Añadir margen superior a las etiquetas del eje X 
    axis.text.y = element_text(margin = margin(r = 30, l=5)), # Añadir margen derecho a las etiquetas del eje Y
    plot.margin = margin(t = 20, b = 20, l = 20, r = 20) # Añadir margen superior para subir el título y subtítulo
  ) +
  annotate("text", x = Inf, y = -Inf, label = "Fuente: Cyclistic", hjust = 1.1, vjust = -0.5, size = 3)



Paso 10. Visualizaciones Periodo Diario

  1. Resumen: cantidad de recorridos y tiempo medio agrupados por tipo de cliente y dia de la semana.
  # Resumir datos para dias de la semana
  data_resumen_semana <- data_clean %>%
    mutate(dia_semana = factor(dia_semana, levels = dias_semana_ordenados),
           hora_inicio = hour(inicio_recorrido),
           hora_final = hour(final_recorrido)) %>%
    group_by(tipo_cliente, dia_semana) %>%
    summarise(
      cantidad_recorridos = n(),
      tiempo_promedio = mean(tiempo_recorrido, na.rm = TRUE),
      .groups = "drop"
    ) 
  1. Visualización: “Total de Recorridos Diarios por Tipo de Cliente”
# Visualización: Número de recorridos
ggplot(data_resumen_semana, aes(x = dia_semana, y = cantidad_recorridos, fill = tipo_cliente)) +
  geom_col(position = "dodge") +
  scale_fill_manual(values = colores_clientes) + # Aplicar la paleta de colores específica
  scale_y_continuous(labels = comma) + # Cambia el formato del eje Y
  labs(
    title = "Total de Recorridos Diarios por Tipo de Cliente",
    subtitle = "Cyclistics, Chicago 2024",
    x = "Día de la Semana", y = "Número de Recorridos"
  ) +
  theme_minimal() +
  theme( 
    plot.title = element_text(hjust = 0.5), # Centra el título 
    plot.subtitle = element_text(hjust = 0.5), # Centra el subtítulo
    axis.text.x = element_text(margin = margin(t = 30, b=5)), # Añadir margen superior a las etiquetas del eje X 
    axis.text.y = element_text(margin = margin(r = 30, l=5)), # Añadir margen derecho a las etiquetas del eje Y
    plot.margin = margin(t = 20, b = 20, l = 20, r = 20) # Añadir margen superior para subir el título y subtítulo
    ) +
  annotate("text", x = Inf, y = -Inf, label = "Fuente: Cyclistic", hjust = 1.1, vjust = -0.5, size = 3)


  1. Visualización: ” Tiempo Medio Diario por Tipo de Cliente”
# Visualización: Tiempo promedio  de recorridos por tipo de cliente en dias de la semana
ggplot(data_resumen_semana, aes(x = dia_semana, y = tiempo_promedio, fill = tipo_cliente)) +
  geom_col(position = "dodge") +
  scale_fill_manual(values = colores_clientes) + # Aplicar la paleta de colores específica
  scale_y_continuous(labels = comma) + # Cambia el formato del eje Y
  labs(
    title = "Tiempo Promedio de Recorridos Durante la Semana por Tipo de Cliente",
    subtitle = "Cyclistics, Chicago 2024",
    x = "Día de la Semana", y = "Tiempo Promedio (min)"
  ) +
  theme_minimal() +
  theme( 
    plot.title = element_text(hjust = 0.5), # Centra el título 
    plot.subtitle = element_text(hjust = 0.5), # Centra el subtítulo
    axis.text.x = element_text(margin = margin(t = 30, b=5)), # Añadir margen superior a las etiquetas del eje X 
    axis.text.y = element_text(margin = margin(r = 30, l=5)), # Añadir margen derecho a las etiquetas del eje Y
  ) +
  annotate("text", x = Inf, y = -Inf, label = "Fuente: Cyclistic", hjust = 1.1, vjust = -0.5, size = 3)

LS0tDQp0aXRsZTogIkN5Y2xpc3RpY3MiDQphdXRob3I6ICJtYXJlbHkiDQpkYXRlOiAiMTgtMDEtMjAyNCINCm91dHB1dDoNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdGhlbWU6IHNwYWNlbGFiDQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQojICBQcmVwYXJhY2nDs24gZGUgRGF0b3MNCg0KXA0KKipQYXNvIDEuKiogSW5zdGFsYWNpw7NuIHkgY2FyZ2EgZGVsIHBhcXVldGUgYFRpZHl2ZXJzZWANCg0KYGBge3J9DQppZiAoIXJlcXVpcmVOYW1lc3BhY2UoInRpZHl2ZXJzZSIsIHF1aWV0bHkgPSBUUlVFKSkgDQogIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpIA0KbGlicmFyeSh0aWR5dmVyc2UpIA0KYGBgDQoNClwNCioqUGFzbyAyLioqIEZ1bmNpw7NuIHBhcmEgY29uc3RydWlyIHJ1dGFzLg0KDQpgYGB7cn0NCmNvbnN0cnVpcl9ydXRhIDwtIGZ1bmN0aW9uKC4uLil7DQogIHJldHVybihmaWxlLnBhdGgoZ2V0d2QoKSwgLi4uKSkNCn0gDQpgYGANCg0KXA0KKipQYXNvIDMuKiogT2J0ZW5lciBsYXMgcnV0YXMgZGUgZGF0b3MgeSBkYXRvcyBkZSBvcmlnZW4gKGRhdGEtbWVzZXMpDQoNCmBgYHtyfQ0KcnV0YV9kYXRvcyA8LSBjb25zdHJ1aXJfcnV0YSgiZGF0YSIpIA0KcnV0YV9kYXRvc19kZV9vcmlnZW4gPC0gY29uc3RydWlyX3J1dGEoImRhdGEiLCAiZGF0YS1tZXNlcyIpDQpgYGANCg0KXA0KKipQYXNvIDQuKiogQ2FyZ2FyIHkgbW9zdHJhciBsYSBsaXN0YSBkZSBhcmNoaXZvcyBjc3YgY29ycmVzcG9uZGllbnRlIGEgbG9zIGRhdG9zIG1lbnN1YWxlcyBkZWwgcHJveWVjdG8uDQoNCmBgYHtyfQ0KYXJjaGl2b3NfY3N2IDwtIGxpc3QuZmlsZXMocGF0aCA9IHJ1dGFfZGF0b3NfZGVfb3JpZ2VuLCBwYXR0ZXJuID0gIlxcLmNzdiQiLCBmdWxsLm5hbWVzID0gRkFMU0UsIHJlY3Vyc2l2ZSA9IFRSVUUpDQpwcmludChhcmNoaXZvc19jc3YpDQpgYGANCg0KXA0KKipQYXNvIDUuKiogSXRlcmFyIHNvYnJlIGxvcyBhcmNoaXZvcyBjc3YgcGFyYSB2aXN1YWxpemFyIGxhcyBjb2x1bW5hcy4NCg0KYGBge3J9DQpmb3IgKGFyY2hpdm8gaW4gYXJjaGl2b3NfY3N2KSB7IA0KICB0cnlDYXRjaCh7DQogICAgZGF0b3MgPC0gcmVhZC5jc3YoZmlsZS5wYXRoKHJ1dGFfZGF0b3NfZGVfb3JpZ2VuLCBhcmNoaXZvKSkgDQogICAgcHJpbnQocGFzdGUoJ05PTUJSRSBBUkNISVZPOiAnLCBhcmNoaXZvKSkNCiAgICBwcmludChjb2xuYW1lcyhkYXRvcykpDQogICAgc3RyKGRhdG9zKQ0KICAgIGNhdCgiXG4iKQ0KICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsNCiAgICBtZXNzYWdlKCJFcnJvciBsZXllbmRvIGFyY2hpdm86ICIsIGFyY2hpdm8sICJcbiIsIGUpDQogIH0pDQp9DQpgYGANCg0KXA0KKipQYXNvIDYuKiogTGVlciB5IGNvbWJpbmFyIHRvZG9zIGxvcyBhcmNoaXZvcyBlbiB1biDDum5pY28gZGF0YWZyYW1lLg0KDQpgYGB7cn0NCmRhdGFmcmFtZV9jb21iaW5hZG8gPC0gYXJjaGl2b3NfY3N2ICU+JQ0KICBtYXBfZGYofiByZWFkX2NzdihmaWxlLnBhdGgocnV0YV9kYXRvc19kZV9vcmlnZW4sIC4pKSkgIyBDb21iaW5hIGF1dG9tw6F0aWNhbWVudGUgdG9kb3MgbG9zIGRhdG9zDQpzdHIoZGF0YWZyYW1lX2NvbWJpbmFkbykNCmhlYWQoZGF0YWZyYW1lX2NvbWJpbmFkbykNCmBgYA0KDQpcDQoqKlBhc28gNy4qKiBFeHBvcnRhciBlbCBkYXRhZnJhbWUgYSBjc3YuDQoNCmBgYHtyfQ0Kd3JpdGUuY3N2KGRhdGFmcmFtZV9jb21iaW5hZG8sIGZpbGUucGF0aChydXRhX2RhdG9zLCAiMjAyNC10cmlwZGF0YS5jc3YiKSwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANCg0KXA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KXA0KDQojIExpbXBpZXphIGRlIERhdG9zIHsjc2VjLWxpbXBpZXphLWRlLWRhdG9zfQ0KDQpcDQoqKlBhc28gMS4qKiBJbnN0YWxhciB5IGNhcmdhciBwYXF1ZXRlIGBMdWJyaWRhdGVgDQoNCmBgYHtyfQ0KaW5zdGFsbC5wYWNrYWdlcygnbHVicmlkYXRlJykNCmxpYnJhcnkobHVicmlkYXRlKQ0KYGBgDQoNClwNCioqUGFzbyAyLioqIEltcG9ydGFyIG51ZXZhcyBkYXRhIHkgdmlzdWFsaXphciByZXN1bWVuIGRlIGRhdG9zDQoNCmBgYHtyfQ0KZGF0YSA8LSByZWFkX2NzdihmaWxlLnBhdGgocnV0YV9kYXRvcywgJzIwMjQtdHJpcGRhdGEuY3N2JykpDQpoZWFkKGRhdGEpDQpzdW1tYXJ5KGRhdGEpDQpzdHIoZGF0YSkNCmdsaW1wc2UoZGF0YSkNCmBgYA0KDQpcDQoqKlBhc28gMy4qKiBFbGltaW5hciBjb2x1bW5hcyBpbm5lY2VzYXJpYXMNCg0KYGBge3J9DQpkYXRhIDwtIGRhdGEgJT4lIA0KICBzZWxlY3QoLWMoJ3N0YXJ0X2xhdCcsICdzdGFydF9sbmcnLCAnZW5kX2xhdCcsICdlbmRfbG5nJykpDQpnbGltcHNlKGRhdGEpDQpgYGANCg0KXA0KKipQYXNvIDQuKiogUmVub21icmFyIGxhcyBjb2x1bW5hcw0KDQpgYGB7cn0NCnRyaXBkYXRhX3JlbmFtZSA8LSByZW5hbWUoZGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3JlY29ycmlkbyA9IHJpZGVfaWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHRpcG9fYmljaWNsZXRhID0gcmlkZWFibGVfdHlwZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgaW5pY2lvX3JlY29ycmlkbyA9IHN0YXJ0ZWRfYXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZpbmFsX3JlY29ycmlkbyA9IGVuZGVkX2F0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICBlc3RhY2lvbl9pbmljaWFsID0gc3RhcnRfc3RhdGlvbl9uYW1lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBlc3RhY2lvbl9maW5hbCA9IGVuZF9zdGF0aW9uX25hbWUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGlkX2VzdGFjaW9uX2luaWNpYWwgPSBzdGFydF9zdGF0aW9uX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBpZF9lc3RhY2lvbl9maW5hbCA9IGVuZF9zdGF0aW9uX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0aXBvX2NsaWVudGUgPSBtZW1iZXJfY2FzdWFsKQ0KZ2xpbXBzZSh0cmlwZGF0YV9yZW5hbWUpDQoNCmBgYA0KDQpcDQoqKlBhc28gNS4qKiBFbGltaW5hciByZWdpc3Ryb3MgZHVwbGljYWRvcw0KDQpgYGB7cn0NCnRyaXBkYXRhX3JlbmFtZSA8LSB0cmlwZGF0YV9yZW5hbWUgJT4lICBkaXN0aW5jdCgpIA0KYGBgDQoNClwNCioqUGFzbyA2LioqIElkZW50aWZpY2FyIHRpcG8gZGUgdXN1YXJpb3MgeSB0aXBvcyBkZSBiaWNpY2xldGFzDQoNCmBgYHtyfQ0KdHJpcGRhdGFfcmVuYW1lICU+JSANCiAgZ3JvdXBfYnkodGlwb19iaWNpY2xldGEsdGlwb19jbGllbnRlKSAlPiUNCiAgc3VtbWFyaXplKG4oKSkNCmBgYA0KDQpcDQoqKlBhc28gNy4qKiBNdXRhciBsYSBkYXRhZnJhbWU6DQoNCi0gICBDcmVhciBudWV2YXMgY29sdW1uYXM6IG1lcyB5IGRpYV9zZW1hbmEuDQoNCi0gICBSZW5vbWJyYXIgbG9zIHRpbXBvcyBkZSBjbGllbnRlcyB5IHRpcG9zIGRlIGJpY2ljbGV0YXMuDQoNCi0gICBNb3N0cmFyIHJlc3VsdGFkby4NCg0KYGBge3J9DQp0cmlwZGF0YV9uZXcgPC0gdHJpcGRhdGFfcmVuYW1lICU+JSANCiAgbXV0YXRlKHRpZW1wb19yZWNvcnJpZG8gPSBhcy5udW1lcmljKGRpZmZ0aW1lKGZpbmFsX3JlY29ycmlkbywgaW5pY2lvX3JlY29ycmlkbywgdW5pdHMgPSAnbWlucycpKSwNCiAgICAgICAgIG1lcyA9IGZvcm1hdChhcy5EYXRlKGluaWNpb19yZWNvcnJpZG8pLCAiJUIiKSwNCiAgICAgICAgIGRpYV9zZW1hbmEgPSB3ZWVrZGF5cyhhcy5EYXRlKGluaWNpb19yZWNvcnJpZG8pKSwNCiAgICAgICAgIHRpcG9fY2xpZW50ZSA9IHJlY29kZSh0aXBvX2NsaWVudGUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm1lbWJlciIgPSAnc3VzY3JpcHRvcicsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNhc3VhbCIgPSAnY2xpZW50ZScpLA0KICAgICAgICAgdGlwb19iaWNpY2xldGEgPSByZWNvZGUodGlwb19iaWNpY2xldGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnY2xhc3NpY19iaWtlJyA9ICdiaWNpY2xldGEgY2xhc2ljYScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZWxlY3RyaWNfYmlrZScgPSAnYmljaWNsZXRhIGVsZWN0cmljYScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZWxlY3RyaWNfc2Nvb3RlcicgPSAnc2Nvb3RlciBlbGVjdHJpY28nKSkNCmdsaW1wc2UodHJpcGRhdGFfbmV3KQ0KaGVhZCh0cmlwZGF0YV9uZXcpDQpgYGANCg0KXA0KKipQYXNvIDguKiogQ29udGFyIHZhbG9yZXMgaW5mZXJpb3JlcyBhIGNlcm8gZW4gbGEgY29sdW1uYSBgdGllbXBvX3JlY29ycmlkb2ANCg0KYGBge3J9DQp2YWxvcmVzX25lZ2F0aXZvcyA8LSBzdW0odHJpcGRhdGFfbmV3JHRpZW1wb19yZWNvcnJpZG8gPCAwLCBuYS5ybSA9IFRSVUUpDQpwcmludChwYXN0ZSgiVmFsb3JlcyBtZW5vcmVzIGEgMCBlbiB0aWVtcG9fcmVjb3JyaWRvOiIsIHZhbG9yZXNfbmVnYXRpdm9zKSkNCmBgYA0KDQpcDQoqKlBhc28gOS4qKiBGaXRyYXIgcmVjb3JyaWRvIGNvbiB2YWxvcmVzIHN1cGVyaW9yZXMgYSBjZXJvLg0KDQpgYGB7cn0NCnRyaXBkYXRhX25ldyA8LSB0cmlwZGF0YV9uZXcgJT4lIA0KICBmaWx0ZXIodGllbXBvX3JlY29ycmlkbyA+IDApDQpgYGANCg0KIyMjICB7I3NlYy12YWxvcmVzLW51bG9zfQ0KDQpcDQoqKlBhc28gMTAuKiogVHJhdGFyIHZhbG9yZXMgbnVsb3MNCg0KMS4gIE1vc3RyYXIgdmFsb3JlcyBudWxvcywgY2FudGlkYWQgeSBwb3JjZW50YWplIHBvciBjb2x1bW5hcy4NCg0KYGBge3J9DQogICAgY29sU3Vtcyhpcy5uYSh0cmlwZGF0YV9uZXcpKQ0KICAgIHBvcmNlbnRhamVfbnVsb3MgPC0gc2FwcGx5KHRyaXBkYXRhX25ld1ssIGMoImlkX2VzdGFjaW9uX2luaWNpYWwiLCAiZXN0YWNpb25faW5pY2lhbCIsICJpZF9lc3RhY2lvbl9maW5hbCIsICJlc3RhY2lvbl9maW5hbCIpXSwgZnVuY3Rpb24oeCkgbWVhbihpcy5uYSh4KSkgKiAxMDApDQogICAgcHJpbnQocG9yY2VudGFqZV9udWxvcykNCmBgYA0KDQoyLiAgUmVlbXBsYXphciB2YWxvcmVzIG51bG9zLg0KDQpgYGB7cn0NCiAgICB0cmlwZGF0YV9uZXcgPC0gdHJpcGRhdGFfbmV3ICU+JQ0KICAgICAgcmVwbGFjZV9uYShsaXN0KA0KICAgICAgICBlc3RhY2lvbl9pbmljaWFsID0gIkRlc2Nvbm9jaWRvIiwNCiAgICAgICAgaWRfZXN0YWNpb25faW5pY2lhbCA9ICJEZXNjb25vY2lkbyIsDQogICAgICAgIGVzdGFjaW9uX2ZpbmFsID0gIkRlc2Nvbm9jaWRvIiwNCiAgICAgICAgaWRfZXN0YWNpb25fZmluYWwgPSAiRGVzY29ub2NpZG8iDQogICAgICApKQ0KYGBgDQoNClwNCioqUGFzbyAxMS4qKiBGb3JtYXRlYXIgY29sdW1uYXMgdGlwbyBjYXJhY3Rlcg0KDQpgYGB7cn0NCnRyaXBkYXRhX25ldyA8LSB0cmlwZGF0YV9uZXcgJT4lDQogIG11dGF0ZSgNCiAgICBhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgfiBzdHJfdHJpbSguKSksICAgICAgICMgRWxpbWluYXIgZXNwYWNpb3MgZW4gYmxhbmNvDQogICAgZXN0YWNpb25faW5pY2lhbCA9IHN0cl90b190aXRsZShlc3RhY2lvbl9pbmljaWFsKSwgICMgSW5pY2lhbCBtYXnDunNjdWxhDQogICAgZXN0YWNpb25fZmluYWwgPSBzdHJfdG9fdGl0bGUoZXN0YWNpb25fZmluYWwpICAgICAjIElndWFsIHBhcmEgZmluYWwNCiAgKQ0KaGVhZCh0cmlwZGF0YV9uZXcpDQpgYGANCg0KXA0KKipQYXNvIDEyLioqIE1hbmVqbyBkZSBjb2x1bW5hcyBlbiBjb25mbGljdG8NCg0KMS4gIEFncnVwYXIgbGFzIGNvbHVtbmFzIGNvbiBkYXRvcyBlbiBjb25mbGljdG8gcG9yIGluY29zaXN0ZW5jaWEgZGUgZGF0b3MgeSBjb250YXIgbGFzIG9jdXJyZW5jaWFzLg0KDQpgYGB7cn0NCiAgICBkYXRvc19jb25mbGljdG9zX2luaWNpYWwgPC0gdHJpcGRhdGFfbmV3ICU+JQ0KICAgICAgZ3JvdXBfYnkoaWRfZXN0YWNpb25faW5pY2lhbCwgZXN0YWNpb25faW5pY2lhbCkgJT4lDQogICAgICBzdW1tYXJpemUoY29udGVvID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgICAgIHVuZ3JvdXAoKSAlPiUgZ3JvdXBfYnkoaWRfZXN0YWNpb25faW5pY2lhbCkgJT4lDQogICAgICBmaWx0ZXIobigpID4gMSkgDQogICAgcHJpbnQoZGF0b3NfY29uZmxpY3Rvc19pbmljaWFsKQ0KYGBgDQoNCjIuICBFbGltaW5hciBjb2x1bW5hcyBwYXJhIGV2aXRhciBpbmNvbnNpc3RlbmNpYSBkZSBkYXRvcyB5IHNlc2dvLg0KDQpgYGB7cn0NCnRyaXBkYXRhX25ldyA8LSB0cmlwZGF0YV9uZXcgJT4lIA0KICBzZWxlY3QoLWMoJ2lkX2VzdGFjaW9uX2luaWNpYWwnLCdlc3RhY2lvbl9pbmljaWFsJywnaWRfZXN0YWNpb25fZmluYWwnLCdlc3RhY2lvbl9maW5hbCcpKQ0KZ2xpbXBzZSh0cmlwZGF0YV9uZXcpDQpgYGANCg0KXA0KKipQYXNvIDEzLioqIEV4cG9ydGFyIGRhdGFmcmFtZSBsaW1waW8gYSBjc3YuDQoNCmBgYHtyfQ0Kd3JpdGUuY3N2KHRyaXBkYXRhX25ldywgZmlsZS5wYXRoKHJ1dGFfZGF0b3MsICIyMDI0LXRyaXBkYXRhLWNsZWFuLmNzdiIpLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpcDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIEFuw6FsaXNpcyB5IFZpc3VhbGl6YWNpw7NuIGRlIERhdG9zDQoNClwNCioqUGFzbyAxLioqIENhcmdhciBsb3MgcGFxdWV0ZXMgbmVjZXNhcmlvcy4NCg0KYGBge3J9DQppbnN0YWxsLnBhY2thZ2VzKCdzY2FsZXMnKQ0KbGlicmFyeShzY2FsZXMpDQpsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KXA0KKipQYXNvIDIuKiogSW1wb3J0YXIgZGF0b3MgbGltcGlvcy4NCg0KYGBge3J9DQpkYXRhX2NsZWFuIDwtIHJlYWRfY3N2KGZpbGUucGF0aChydXRhX2RhdG9zLCAnMjAyNC10cmlwZGF0YS1jbGVhbi5jc3YnKSkNCmdsaW1wc2UoZGF0YV9jbGVhbikNCmhlYWQoZGF0YV9jbGVhbikNCmBgYA0KDQpcDQoqKlBhc28gMy4qKiBBbsOhbGlzaXMgZGVzY3JpcHRpdm9zIGRlIHRpZW1wbyBkZSByZWNvcnJpZG8NCg0KYGBge3J9DQpzdW1tYXJ5KGRhdGFfY2xlYW4kdGllbXBvX3JlY29ycmlkbykNCmBgYA0KDQpcDQoqKlBhc28gNC4qKiBBbsOhbGlzaXMgY29tcGFyYXRpdm8gZW50cmUgY2xpZW50ZXMgeSBzdXNjcmlwdG9yZXMNCg0KYGBge3J9DQphZ2dyZWdhdGUoZGF0YV9jbGVhbiR0aWVtcG9fcmVjb3JyaWRvIH4gZGF0YV9jbGVhbiR0aXBvX2NsaWVudGUsIEZVTiA9IG1lYW4pDQphZ2dyZWdhdGUoZGF0YV9jbGVhbiR0aWVtcG9fcmVjb3JyaWRvIH4gZGF0YV9jbGVhbiR0aXBvX2NsaWVudGUsIEZVTiA9IG1lZGlhbikNCmFnZ3JlZ2F0ZShkYXRhX2NsZWFuJHRpZW1wb19yZWNvcnJpZG8gfiBkYXRhX2NsZWFuJHRpcG9fY2xpZW50ZSwgRlVOID0gbWF4KQ0KYWdncmVnYXRlKGRhdGFfY2xlYW4kdGllbXBvX3JlY29ycmlkbyB+IGRhdGFfY2xlYW4kdGlwb19jbGllbnRlLCBGVU4gPSBtaW4pDQpgYGANCg0KXA0KKipQYXNvIDUuKiogRGVmaW5pciBkw61hcyBkZSBsYSBzZW1hbmEgeSBtZXNlcyBkZWwgYcOxbw0KDQpgYGB7cn0NCmRhdGFfY2xlYW4kbWVzIDwtIG9yZGVyZWQoZGF0YV9jbGVhbiRtZXMsIGxldmVscz1jKCdlbmVybycsJ2ZlYnJlcm8nLCdtYXJ6bycsJ2FicmlsJywnbWF5bycsJ2p1bmlvJywnanVsaW8nLCdhZ29zdG8nLCdzZXB0aWVtYnJlJywnb2N0dWJyZScsJ25vdmllbWJyZScsJ2RpY2llbWJyZScpKQ0KZGF0YV9jbGVhbiRkaWFfc2VtYW5hIDwtIG9yZGVyZWQoZGF0YV9jbGVhbiRkaWFfc2VtYW5hLCBsZXZlbHM9YygibHVuZXMiLCAibWFydGVzIiwgIm1pw6lyY29sZXMiLCAianVldmVzIiwgInZpZXJuZXMiLCAic8OhYmFkbyIsImRvbWluZ28iKSkNCmBgYA0KDQpcDQoqKlBhc28gNi4qKiBWZWN0b3JlcyBvcmRlbmFkb3MNCg0KMS4gIETDrWFzIGRlIGxhIHNlbWFuYSBvcmRlbmFkb3MgZGUgbHVuZXMgYSBkb21pbmdvDQoNCmBgYHtyfQ0KICBkaWFzX3NlbWFuYV9vcmRlbmFkb3MgPC0gYygibHVuZXMiLCAibWFydGVzIiwgIm1pw6lyY29sZXMiLCAianVldmVzIiwgInZpZXJuZXMiLCAic8OhYmFkbyIsICJkb21pbmdvIikNCmBgYA0KDQoyLiAgTWVzZXMgZGVsIGHDsW8sIG9yZGVuYWRvcyBkZSBlbmVybyBhIGRpY2llbWJyZS4NCg0KYGBge3J9DQogIG1lc2VzX29yZGVuYWRvcyA8LSBjKCJlbmVybyIsICJmZWJyZXJvIiwgIm1hcnpvIiwgImFicmlsIiwgIm1heW8iLCAianVuaW8iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAianVsaW8iLCAiYWdvc3RvIiwgInNlcHRpZW1icmUiLCAib2N0dWJyZSIsICJub3ZpZW1icmUiLCAiZGljaWVtYnJlIikNCmBgYA0KDQpcDQpcDQoqKlBhc28gNy4qKiBEZWZpbmlyIHVuYSBwYWxldGEgZGUgY29sb3JlcyBlc3BlY8OtZmljYSBwYXJhIGNhZGEgdGlwbyBkZSBjbGllbnRlDQoNCmBgYHtyfQ0KY29sb3Jlc19jbGllbnRlcyA8LSBjKCJzdXNjcmlwdG9yIiA9ICIjMWI5ZTc3IiwgImNsaWVudGUiID0gIiM1RjAyRDkiKQ0KYGBgDQoNClwNCioqUGFzbyA4LioqIFZpc3VhbGl6YXIgdGllbXBvIHByb21lZGlvIGRpYXJpbyBlbnRyZSBjbGllbnRlcyB5IHN1c2NyaXB0b3Jlcw0KDQpgYGB7cn0NCmFnZ3JlZ2F0ZShkYXRhX2NsZWFuJHRpZW1wb19yZWNvcnJpZG8gfiBkYXRhX2NsZWFuJHRpcG9fY2xpZW50ZSArIGRhdGFfY2xlYW4kZGlhX3NlbWFuYSwgRlVOID0gbWVhbikNCmBgYA0KDQpcDQoqKlBhc28gOS4qKiBWaXN1YWxpemFjaW9uZXMgUGVyaW9kbyBNZW5zdWFsDQoNCjEuICAqKlJlc3VtZW46KiogY2FudGlkYWQgZGUgcmVjb3JyaWRvcyB5IHRpZW1wbyBwcm9tZWRpbyBkZSByZWNvcnJpZG9zIGFncnVwYWRvcyBwb3IgdGlwbyBkZSBjbGllbnRlIHkgbWVzDQoNCmBgYHtyfQ0KIyBWZXJpZmljYXIgbG9zIHZhbG9yZXMgw7puaWNvcyBlbiBsYSBjb2x1bW5hICd0aXBvX2NsaWVudGUnDQp1bmlxdWUoZGF0YV9jbGVhbiR0aXBvX2NsaWVudGUpDQojIFJlc3VtaXIgZGF0b3MgcG9yIG1lcw0KZGF0YV9yZXN1bWVuX21lcyA8LSBkYXRhX2NsZWFuICU+JQ0KICBtdXRhdGUobWVzID0gZmFjdG9yKG1lcywgbGV2ZWxzID0gbWVzZXNfb3JkZW5hZG9zKSkgJT4lDQogIGdyb3VwX2J5KHRpcG9fY2xpZW50ZSwgbWVzKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIGNhbnRpZGFkX3JlY29ycmlkb3MgPSBuKCksDQogICAgdGllbXBvX3Byb21lZGlvID0gbWVhbih0aWVtcG9fcmVjb3JyaWRvLCBuYS5ybSA9IFRSVUUpLA0KICAgIC5ncm91cHMgPSAiZHJvcCINCiAgKQ0KYGBgDQoNCjIuICAqKlZpc3VhbGl6YWNpw7NuOioqIFRlbmRlbmNpYSBkZSByZWNvcnJpZG9zIG1lbnN1YWxlcyBwb3IgdGlwbyBkZSBjbGllbnRlDQoNCmBgYHtyfQ0KIyBWaXN1YWxpemFjacOzbjogR3LDoWZpY28gZGUgw6FyZWEgcG9yIHRlbmRlbmNpYXMgbWVuc3VhbGVzIGRlIHJlY29ycmlkb3MNCmdncGxvdChkYXRhX3Jlc3VtZW5fbWVzLGFlcyh4ID0gbWVzLCB5ID0gY2FudGlkYWRfcmVjb3JyaWRvcywgZ3JvdXAgPSB0aXBvX2NsaWVudGUsIGZpbGwgPSB0aXBvX2NsaWVudGUpKSArDQogIGdlb21fYXJlYShwb3NpdGlvbiA9ICJzdGFjayIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19jbGllbnRlcykgKyAjIEFwbGljYXIgbGEgcGFsZXRhIGRlIGNvbG9yZXMgZXNwZWPDrWZpY2ENCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArICMgQ2FtYmlhIGVsIGZvcm1hdG8gZGVsIGVqZSBZDQogIGxhYnModGl0bGUgPSAiVGVuZGVuY2lhcyBNZW5zdWFsZXMgZGUgUmVjb3JyaWRvcyBwb3IgVGlwbyBkZSBDbGllbnRlIiwNCiAgICAgICBzdWJ0aXRsZSA9ICJDeWNsaXN0aWNzLCBDaGljYWdvIDIwMjQiLCAjIFN1YnTDrXR1bG8NCiAgICAgICB4ID0gIk1lc2VzIiwNCiAgICAgICB5ID0gIkNhbnRpZGFkIGRlIFJlY29ycmlkb3MiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCAgIyBBbGluZWEgZWwgdMOtdHVsbyBhIGxhIGRlcmVjaGENCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwgIyBBbGluZWEgZWwgc3VidMOtdHVsbyBhIGxhIGRlcmVjaGENCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IDMwLCBiPTUpKSwgIyBBw7FhZGlyIG1hcmdlbiBzdXBlcmlvciBhIGxhcyBldGlxdWV0YXMgZGVsIGVqZSBYIA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbihyID0gMzAsIGw9NSkpLCAjIEHDsWFkaXIgbWFyZ2VuIGRlcmVjaG8gYSBsYXMgZXRpcXVldGFzIGRlbCBlamUgWQ0KICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAyMCwgYiA9IDIwLCBsID0gMjAsIHIgPSAyMCkgIyBBw7FhZGlyIG1hcmdlbiBzdXBlcmlvciBwYXJhIHN1YmlyIGVsIHTDrXR1bG8geSBzdWJ0w610dWxvDQogICkgKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAtSW5mLCBsYWJlbCA9ICJGdWVudGU6IEN5Y2xpc3RpYyIsIGhqdXN0ID0gMS4xLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKQ0KDQpgYGANCg0KIVtdKGltYWdlcy90ZW5kZW5jaWFfdG90YWxfbnVtZXJvX3JlY29ycmlkb3NfbWVzX3RpcG9fY2xpZW50ZS5wbmcpXA0KDQozLiAgKipWaXN1YWxpemFjacOzbjoqKiBUaWVtcG8gbWVkaW8gcmVjb3JyaWRvIG1lbnN1YWwgcG9yIHRpcG8gZGUgY2xpZW50ZQ0KDQpgYGB7cn0NCmdncGxvdChkYXRhX3Jlc3VtZW5fbWVzLGFlcyh4ID0gbWVzLCB5ID0gdGllbXBvX3Byb21lZGlvLCBncm91cCA9IHRpcG9fY2xpZW50ZSwgZmlsbCA9IHRpcG9fY2xpZW50ZSkpICsNCiAgZ2VvbV9hcmVhKHBvc2l0aW9uID0gInN0YWNrIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX2NsaWVudGVzKSArICMgQXBsaWNhciBsYSBwYWxldGEgZGUgY29sb3JlcyBlc3BlY8OtZmljYQ0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgIyBDYW1iaWEgZWwgZm9ybWF0byBkZWwgZWplIFkNCiAgbGFicyh0aXRsZSA9ICJUaWVtcG8gUHJvbWVkaW8gTWVuc3VhbCBSZWNvcnJpZG8gcG9yIFRpcG8gZGUgQ2xpZW50ZSIsDQogICAgICAgc3VidGl0bGUgPSAiQ3ljbGlzdGljcywgQ2hpY2FnbyAyMDI0IiwgIyBTdWJ0w610dWxvDQogICAgICAgeCA9ICJNZXNlcyIsDQogICAgICAgeSA9ICJUaWVtcG8gcHJvbWVkaW8gKG1pbikiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCAgIyBBbGluZWEgZWwgdMOtdHVsbyBhIGxhIGRlcmVjaGENCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwgIyBBbGluZWEgZWwgc3VidMOtdHVsbyBhIGxhIGRlcmVjaGENCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IDMwLCBiPTUpKSwgIyBBw7FhZGlyIG1hcmdlbiBzdXBlcmlvciBhIGxhcyBldGlxdWV0YXMgZGVsIGVqZSBYIA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbihyID0gMzAsIGw9NSkpLCAjIEHDsWFkaXIgbWFyZ2VuIGRlcmVjaG8gYSBsYXMgZXRpcXVldGFzIGRlbCBlamUgWQ0KICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKHQgPSAyMCwgYiA9IDIwLCBsID0gMjAsIHIgPSAyMCkgIyBBw7FhZGlyIG1hcmdlbiBzdXBlcmlvciBwYXJhIHN1YmlyIGVsIHTDrXR1bG8geSBzdWJ0w610dWxvDQogICkgKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAtSW5mLCBsYWJlbCA9ICJGdWVudGU6IEN5Y2xpc3RpYyIsIGhqdXN0ID0gMS4xLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKQ0KYGBgDQoNCiFbXShpbWFnZXMvVGllbXBvX3Byb21lZGlvX3JlY29ycmlkb19tZXNfdGlwb19jbGllbnRlLnBuZylcDQpcDQpQYXNvIDEwLiBWaXN1YWxpemFjaW9uZXMgUGVyaW9kbyBEaWFyaW8NCg0KMS4gICoqUmVzdW1lbjoqKiBjYW50aWRhZCBkZSByZWNvcnJpZG9zIHkgdGllbXBvIG1lZGlvIGFncnVwYWRvcyBwb3IgdGlwbyBkZSBjbGllbnRlIHkgZGlhIGRlIGxhIHNlbWFuYS4NCg0KYGBge3J9DQogICMgUmVzdW1pciBkYXRvcyBwYXJhIGRpYXMgZGUgbGEgc2VtYW5hDQogIGRhdGFfcmVzdW1lbl9zZW1hbmEgPC0gZGF0YV9jbGVhbiAlPiUNCiAgICBtdXRhdGUoZGlhX3NlbWFuYSA9IGZhY3RvcihkaWFfc2VtYW5hLCBsZXZlbHMgPSBkaWFzX3NlbWFuYV9vcmRlbmFkb3MpLA0KICAgICAgICAgICBob3JhX2luaWNpbyA9IGhvdXIoaW5pY2lvX3JlY29ycmlkbyksDQogICAgICAgICAgIGhvcmFfZmluYWwgPSBob3VyKGZpbmFsX3JlY29ycmlkbykpICU+JQ0KICAgIGdyb3VwX2J5KHRpcG9fY2xpZW50ZSwgZGlhX3NlbWFuYSkgJT4lDQogICAgc3VtbWFyaXNlKA0KICAgICAgY2FudGlkYWRfcmVjb3JyaWRvcyA9IG4oKSwNCiAgICAgIHRpZW1wb19wcm9tZWRpbyA9IG1lYW4odGllbXBvX3JlY29ycmlkbywgbmEucm0gPSBUUlVFKSwNCiAgICAgIC5ncm91cHMgPSAiZHJvcCINCiAgICApIA0KYGBgDQoNCjIuICAqKlZpc3VhbGl6YWNpw7NuOioqICJUb3RhbCBkZSBSZWNvcnJpZG9zIERpYXJpb3MgcG9yIFRpcG8gZGUgQ2xpZW50ZSINCg0KYGBge3J9DQojIFZpc3VhbGl6YWNpw7NuOiBOw7ptZXJvIGRlIHJlY29ycmlkb3MNCmdncGxvdChkYXRhX3Jlc3VtZW5fc2VtYW5hLCBhZXMoeCA9IGRpYV9zZW1hbmEsIHkgPSBjYW50aWRhZF9yZWNvcnJpZG9zLCBmaWxsID0gdGlwb19jbGllbnRlKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19jbGllbnRlcykgKyAjIEFwbGljYXIgbGEgcGFsZXRhIGRlIGNvbG9yZXMgZXNwZWPDrWZpY2ENCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArICMgQ2FtYmlhIGVsIGZvcm1hdG8gZGVsIGVqZSBZDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVG90YWwgZGUgUmVjb3JyaWRvcyBEaWFyaW9zIHBvciBUaXBvIGRlIENsaWVudGUiLA0KICAgIHN1YnRpdGxlID0gIkN5Y2xpc3RpY3MsIENoaWNhZ28gMjAyNCIsDQogICAgeCA9ICJEw61hIGRlIGxhIFNlbWFuYSIsIHkgPSAiTsO6bWVybyBkZSBSZWNvcnJpZG9zIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoIA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCAjIENlbnRyYSBlbCB0w610dWxvIA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLCAjIENlbnRyYSBlbCBzdWJ0w610dWxvDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQobWFyZ2luID0gbWFyZ2luKHQgPSAzMCwgYj01KSksICMgQcOxYWRpciBtYXJnZW4gc3VwZXJpb3IgYSBsYXMgZXRpcXVldGFzIGRlbCBlamUgWCANCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4ociA9IDMwLCBsPTUpKSwgIyBBw7FhZGlyIG1hcmdlbiBkZXJlY2hvIGEgbGFzIGV0aXF1ZXRhcyBkZWwgZWplIFkNCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbih0ID0gMjAsIGIgPSAyMCwgbCA9IDIwLCByID0gMjApICMgQcOxYWRpciBtYXJnZW4gc3VwZXJpb3IgcGFyYSBzdWJpciBlbCB0w610dWxvIHkgc3VidMOtdHVsbw0KICAgICkgKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSBJbmYsIHkgPSAtSW5mLCBsYWJlbCA9ICJGdWVudGU6IEN5Y2xpc3RpYyIsIGhqdXN0ID0gMS4xLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzKQ0KYGBgDQoNCiFbXShpbWFnZXMvTnVtZXJvX3JlY29ycmlkb3NfZGlhcmlvc190aXBvX2NsaWVudGVzLnBuZylcDQoNCjMuICAqKlZpc3VhbGl6YWNpw7NuOioqICIgVGllbXBvIE1lZGlvIERpYXJpbyBwb3IgVGlwbyBkZSBDbGllbnRlIg0KDQpgYGB7cn0NCiMgVmlzdWFsaXphY2nDs246IFRpZW1wbyBwcm9tZWRpbyAgZGUgcmVjb3JyaWRvcyBwb3IgdGlwbyBkZSBjbGllbnRlIGVuIGRpYXMgZGUgbGEgc2VtYW5hDQpnZ3Bsb3QoZGF0YV9yZXN1bWVuX3NlbWFuYSwgYWVzKHggPSBkaWFfc2VtYW5hLCB5ID0gdGllbXBvX3Byb21lZGlvLCBmaWxsID0gdGlwb19jbGllbnRlKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19jbGllbnRlcykgKyAjIEFwbGljYXIgbGEgcGFsZXRhIGRlIGNvbG9yZXMgZXNwZWPDrWZpY2ENCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArICMgQ2FtYmlhIGVsIGZvcm1hdG8gZGVsIGVqZSBZDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVGllbXBvIFByb21lZGlvIGRlIFJlY29ycmlkb3MgRHVyYW50ZSBsYSBTZW1hbmEgcG9yIFRpcG8gZGUgQ2xpZW50ZSIsDQogICAgc3VidGl0bGUgPSAiQ3ljbGlzdGljcywgQ2hpY2FnbyAyMDI0IiwNCiAgICB4ID0gIkTDrWEgZGUgbGEgU2VtYW5hIiwgeSA9ICJUaWVtcG8gUHJvbWVkaW8gKG1pbikiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSggDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksICMgQ2VudHJhIGVsIHTDrXR1bG8gDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksICMgQ2VudHJhIGVsIHN1YnTDrXR1bG8NCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IDMwLCBiPTUpKSwgIyBBw7FhZGlyIG1hcmdlbiBzdXBlcmlvciBhIGxhcyBldGlxdWV0YXMgZGVsIGVqZSBYIA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KG1hcmdpbiA9IG1hcmdpbihyID0gMzAsIGw9NSkpLCAjIEHDsWFkaXIgbWFyZ2VuIGRlcmVjaG8gYSBsYXMgZXRpcXVldGFzIGRlbCBlamUgWQ0KICApICsNCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gSW5mLCB5ID0gLUluZiwgbGFiZWwgPSAiRnVlbnRlOiBDeWNsaXN0aWMiLCBoanVzdCA9IDEuMSwgdmp1c3QgPSAtMC41LCBzaXplID0gMykNCmBgYA0KDQohW10oaW1hZ2VzL1RpZW1wb19wcm9tZWRpb19yZWNvcnJpZG9fZGlhcmlvX3RpcG9fY2xpZW50ZS5wbmcpDQo=