Introducción:
(TL;DR: cómo obtener datos de 251070 proyectos de kickstarter.)
kickstarter es un sitio web de micromecenazgo (crowfunding) en el que, básicamente, personas o equipos de todo el mundo muestran sus proyectos con el fin de conseguir fondos para llevarlos a cabo.
(Si quieren saber un poco más, wikipedia tiene un artículo bastante completo.)
Estos proyectos se agrupan por categorías, se plantean un tiempo para recaudar un monto específico, consiguen backers o respaldadores… pero sobre todo, al final, alcanzan el propósito o no lo hacen: finalmente son exitosos o fracasan en su cometido.
Vamos en esta primera parte a plasmar en la notebook a ver cómo obtener los datos principales de 251070 proyectos que durante 2017 estuvieron en la plataforma.
Descargando los datos:
Lo primero es darle las gracias y gran part del mérito a la gente de webrobots, quienes scrapearon la página de kicstarter para poner a disposición datos de 2014 hasta 2018. Ya que es de su página de donde obtenemos los datos sin tener que hacer nosotros mismos el scrapping.
Nota: remover el parámetro “eval=FALSE” si quiere que se ejecute la descarga. Yo deshabilité la descarga para la notebook debido a que ya hice la descarga y toma un buen tiempo; además, para no hacer un uso innecesario de recursos.
# Primero definimos las urls. En este caso, yo elegí descargar solamente las 2017. Con las 2018 tuve problemas
url <- c("https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-01-15T22_21_04_985Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-02-15T22_22_48_377Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-03-15T22_20_55_874Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-04-15T22_21_18_122Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-05-15T22_21_11_300Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-06-15T22_20_03_059Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-07-15T22_20_48_951Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-08-15T22_20_51_958Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-09-15T22_20_48_432Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-10-15T10_20_38_271Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-11-15T10_21_04_919Z.zip",
"https://s3.amazonaws.com/weruns/forfun/Kickstarter/Kickstarter_2017-12-15T10_20_51_610Z.zip")
# Creamos una función que extrae los n últimos caracteres
substrRight <- function(x, n){
substr(x, nchar(x)-n+1, nchar(x))
}
# Aplicamos la función recién creada para extraer los último 28 caracteres de la url (año, mes y consecutivo)
# con los caracteres extraídos nombramos los archivos y los guardamos dentro de la carpeta data dentro del proyecto
library(here)
for (i in 1:length(url)) {
path_name <- paste(here("./data/"), substrRight(url[i], 28))
download.file(url[i], path_name)
}
Descomprimiendo los datos:
Una vez que hemos descargado os datos, vamos a descomprimirlos (la lectura desde el zip me produjo errores que preferí evitar)
library(here)
# hacemos una lista de todos los archivos .zip descargados
all_files <- list.files(here("data"))
# Establecemos dónde los vamos a guardar
outdir <- here("data/unzipped")
# función de apoyo para extraer parte del nombre y usarlo para renombrar
substrLeft <- function(x, n){
substr(x, 2, n+1)
}
# Renombrado masivo
setwd(here("/data"))
for(i in 1:length(all_files)) {
file.rename(from = all_files[i], to = substrLeft(all_files[i], 7))
}
setwd(here())
# Extraemos y guardamos
for(i in 1:length(all_files)) {
f <- paste(here("data") , all_files[i], sep = "/")
thisOutDir <- paste(outdir, all_files[i], sep = "/")
unzip(f, exdir = thisOutDir)
}
# Lista de todos los descomprimidos
all_abs_paths <- list.files(here("data/unzipped"), pattern = ".csv", recursive = TRUE, full.names = TRUE)
# Guardamos la lista de todos los descomprimidos
saveRDS(all_abs_paths, here("data/all_files_unzipped.rds"))
Ordenando los datos:
Ahora que tenemos todos los datos descomprimidos, los vamos a leer para juntarlos en un único dataframe/set de datos: tenemos una carpeta para cada mes del año 2017 (12 carpetas), cada una con 34.66 archivos en promedio. Un total de 416 archivos que juntos suman 8.2 GiB.
library(here)
setwd(here())
# Importamos la lista de archivos de todos los archivos
all_files <- readRDS(here("data/all_files_unzipped.rds"))
library(stringr)
# Arreglamos los nombres de los archivos en tanto que colnames, para que purrr retenga el periodo del que proviene el dato
names(all_files) <- all_files %>%
str_remove(".csv") %>%
str_sub(79, 80)
# Con purrr aplicamos read_csv de la librería readr a todos los archivos listados: los leemos todos
setwd(here("data/unzipped"))
df <- purrr::safely(purrr::map_df(all_files, readr::read_csv, .id = "periodo"))
setwd(here())
# Tenemos variables de tiempo como número. Les damos el formato adecuado
df[, 15:18] <- lapply(df[, 15:18], as.Date.POSIXct) # convertir el número a fecha
# Si tomamos los casos que no son NAs en estas últimas columnas, y las dividimos sobre el total:
# las columnas * las filas divididas:
(((ncol(df)-29) * nrow(df)) - (sum(is.na(df[, 30:ncol(df)])))) / ((ncol(df)-29) * nrow(df))
# Nos damos cuenta que la proporción de casos no ameritan el esfuerzo de la limpieza ni deja lugar a la
# imputación: entonces las dejamos de lado.
df <- df[, 1:29]
# De las variables a disposición, realmente me interesa, sobre todo, el texto y el estado final.
# Pero necesitamos el periodo, y los pledges, usd_rate, goal y otros datos pueden resultar de utilidad para modelos
# que tomen como entrada Procesamiento del Lenguaje Natural (NLP), que es el centro del asunto.
df_text <- df[, c(1:2, 4:9, 11:22, 24:25)]
# Debido a que se presentan problemas con la RAM disponible a la hora de escribir los archivos en R, los partimos
# en unidades más pequeñas.
df_text1 <- df_text[1:528584, ]
df_text2 <- df_text[528585:1057170, ]
df_text3 <- df_text[1057171:1585756, ]
df_text4 <- df_text[1585757:2114333, ]
# Y las guardamos
saveRDS(df_text1, "./data/text_sets/data_text1.rds")
saveRDS(df_text2, "./data/text_sets/data_text2.rds")
saveRDS(df_text3, "./data/text_sets/data_text3.rds")
saveRDS(df_text4, "./data/text_sets/data_text4.rds")
Limpiando datos:
Ahora que tenemos todos los datos, nos damos cuenta fácil que cada proyecto aparece muchas veces: un proyecto puede aparecer en enero, febrero, marzo… y su estado va cambiando y el recaudo, los backers. Pero lo que nos interesa saber es su estado final: si logró o no recaudar el dinero.
Es por eso que necesitamos retener solamente el último estado del proyecto, su última aparición, que es el periodo máximo de cada proyecto.
library(here)
# Leemos todos los set de datos
df1 <- readRDS(here("/data/text_sets/data_text1.rds"))
df2 <- readRDS(here("/data/text_sets/data_text2.rds"))
df3 <- readRDS(here("/data/text_sets/data_text3.rds"))
df4 <- readRDS(here("/data/text_sets/data_text4.rds"))
# los juntamos
df <- rbind(df1, df2, df3, df4)
# borramos los sets parciales (sólo por liberar espacio)
rm(list = c("df1", "df2", "df3", "df4"))
# verificamos el tamaño del nuevo set
format(object.size(df), units = "Mb")
library(dplyr)
library(stringr)
# El periodo lo pasamos de mes y año a solamente mes (todos son 2017)
df <- df %>%
mutate(periodo = as.integer(str_sub(periodo, 6, 7)))
# Pasamos el id (identificador del proyecto) a character.
df$id <- as.character(df$id)
# Retenemos de cada proyecto la fila en la que tiene su último mes, y eliminamos los casos en los que haya duplicados
# en cuanto a periodo e id.
df_unique <- df %>%
group_by(id) %>%
filter(periodo == max(periodo)) %>%
distinct(id, periodo, .keep_all = TRUE)
# Revisamos cuánto ocupa ahora el nuevo set
format(object.size(df_unique), units = "Mb")
# Le damos una mirada al set
glimpse(df_unique)
# Y lo guardamos
saveRDS(df_unique, here("/data/text_sets/df.rds"))
library(here)
library(dplyr)
df <- readRDS(here("/data/text_sets/df.rds"))
df$periodo <- as.Date(paste("01", as.character(df$periodo), "2017", sep = "-"), tz = "UTC", format = "%d-%m-%Y")
df$usd_goal <- df$static_usd_rate * df$goal
df$state <- as.factor(df$state)
df$country <- as.factor(df$country)
df$currency <- as.factor(df$currency)
df$currency_symbol <- as.factor(df$currency_symbol)
df$currency_trailing_code <- as.factor(df$currency_trailing_code)
df$staff_pick <- as.factor(df$staff_pick)
df <- df %>%
filter(!state %in% c("live", "canceled", "suspended"))
df_text <- df[, c("blurb", "state")]
rm(df)
# En caso de que quieran aplicar la transformación de categorías
#df_text$bin_state <- ifelse(df_text$state == "successful", "successful", "failed")
library(cldr)
lang <- detectLanguage(df_text$blurb)
df_text_eng <- df_text[lang$detectedLanguageCode == 0, ]
saveRDS(df_text_eng, here("/data/text_sets/df_text_eng.rds"))
write.csv(df_text_eng, here("/data/text_sets/df_text_eng.csv"))
Nota final:
De esta manera se resumen todos los pasos para conformar el csv que es sobre el que estaré trabajando, el cual pueden descargar desde my Google drive aquí o desde Kaggle aquí o a través de la API.
Esto porque no quiero repetir más este proceso y porque espero pronto trabajar sobre el set de datos en Kaggle, aprovechando que ahora disponen GPUs gratuitas y en Google colab donde también disponen GPUs gratuitas. Todo ello en Python, hasta donde he leído por ahora.
Gracias por seguirme hasta aquí.
LS0tCnRpdGxlOiAiS2lja3N0YXJ0ZXIgMjAxNyBnZXR0aW5nLCBjbGVhbmluZyBhbmQgdGlkeWluZyBkYXRhIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBJbnRyb2R1Y2Npw7NuOiAKIyMjIChUTDtEUjogY8OzbW8gb2J0ZW5lciBkYXRvcyBkZSAyNTEwNzAgcHJveWVjdG9zIGRlIGtpY2tzdGFydGVyLikKCltraWNrc3RhcnRlcl0oaHR0cHM6Ly93d3cua2lja3N0YXJ0ZXIuY29tLz9sYW5nPWVzKSBlcyB1biBzaXRpbyB3ZWIgZGUgbWljcm9tZWNlbmF6Z28gKGNyb3dmdW5kaW5nKSBlbiBlbCBxdWUsIGLDoXNpY2FtZW50ZSwgcGVyc29uYXMgbyBlcXVpcG9zIGRlIHRvZG8gZWwgbXVuZG8gbXVlc3RyYW4gc3VzIHByb3llY3RvcyBjb24gZWwgZmluIGRlIGNvbnNlZ3VpciBmb25kb3MgcGFyYSBsbGV2YXJsb3MgYSBjYWJvLiAKCihTaSBxdWllcmVuIHNhYmVyIHVuIHBvY28gbcOhcywgW3dpa2lwZWRpYV0oaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvS2lja3N0YXJ0ZXIpIHRpZW5lIHVuIGFydMOtY3VsbyBiYXN0YW50ZSBjb21wbGV0by4pCgpFc3RvcyBwcm95ZWN0b3Mgc2UgYWdydXBhbiBwb3IgY2F0ZWdvcsOtYXMsIHNlIHBsYW50ZWFuIHVuIHRpZW1wbyBwYXJhIHJlY2F1ZGFyIHVuIG1vbnRvIGVzcGVjw61maWNvLCBjb25zaWd1ZW4gYmFja2VycyBvIHJlc3BhbGRhZG9yZXMuLi4gcGVybyBzb2JyZSB0b2RvLCBhbCBmaW5hbCwgYWxjYW56YW4gZWwgcHJvcMOzc2l0byBvIG5vIGxvIGhhY2VuOiBmaW5hbG1lbnRlIHNvbiBleGl0b3NvcyBvIGZyYWNhc2FuIGVuIHN1IGNvbWV0aWRvLgoKVmFtb3MgZW4gZXN0YSBwcmltZXJhIHBhcnRlIGEgcGxhc21hciBlbiBsYSBub3RlYm9vayBhIHZlciBjw7NtbyBvYnRlbmVyIGxvcyBkYXRvcyBwcmluY2lwYWxlcyBkZSAyNTEwNzAgcHJveWVjdG9zIHF1ZSBkdXJhbnRlIDIwMTcgZXN0dXZpZXJvbiBlbiBsYSBwbGF0YWZvcm1hLgoKIyMgRGVzY2FyZ2FuZG8gbG9zIGRhdG9zOgoKTG8gcHJpbWVybyBlcyBkYXJsZSBsYXMgZ3JhY2lhcyB5IGdyYW4gcGFydCBkZWwgbcOpcml0byBhIFtsYSBnZW50ZSBkZSB3ZWJyb2JvdHNdKGh0dHBzOi8vd2Vicm9ib3RzLmlvL2Fib3V0LXVzLyksIHF1aWVuZXMgc2NyYXBlYXJvbiBsYSBww6FnaW5hIGRlIGtpY3N0YXJ0ZXIgcGFyYSBwb25lciBhIGRpc3Bvc2ljacOzbiBkYXRvcyBkZSAyMDE0IGhhc3RhIDIwMTguIFlhIHF1ZSBlcyBkZSBzdSBww6FnaW5hIGRlIGRvbmRlIG9idGVuZW1vcyBsb3MgZGF0b3Mgc2luIHRlbmVyIHF1ZSBoYWNlciBub3NvdHJvcyBtaXNtb3MgZWwgc2NyYXBwaW5nLiAKCiMjIyBOb3RhOiByZW1vdmVyIGVsIHBhcsOhbWV0cm8gImV2YWw9RkFMU0UiIHNpIHF1aWVyZSBxdWUgc2UgZWplY3V0ZSBsYSBkZXNjYXJnYS4gWW8gZGVzaGFiaWxpdMOpIGxhIGRlc2NhcmdhIHBhcmEgbGEgbm90ZWJvb2sgZGViaWRvIGEgcXVlIHlhIGhpY2UgbGEgZGVzY2FyZ2EgeSB0b21hIHVuIGJ1ZW4gdGllbXBvOyBhZGVtw6FzLCBwYXJhIG5vIGhhY2VyIHVuIHVzbyBpbm5lY2VzYXJpbyBkZSByZWN1cnNvcy4KCmBgYHtyLCBkb3dubG9hZF9kYXRhLCBjYWNoZT1UUlVFLCBldmFsPUZBTFNFfQojIFByaW1lcm8gZGVmaW5pbW9zIGxhcyB1cmxzLiBFbiBlc3RlIGNhc28sIHlvIGVsZWfDrSBkZXNjYXJnYXIgc29sYW1lbnRlIGxhcyAyMDE3LiBDb24gbGFzIDIwMTggdHV2ZSBwcm9ibGVtYXMKdXJsIDwtIGMoImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDEtMTVUMjJfMjFfMDRfOTg1Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDItMTVUMjJfMjJfNDhfMzc3Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDMtMTVUMjJfMjBfNTVfODc0Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDQtMTVUMjJfMjFfMThfMTIyWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDUtMTVUMjJfMjFfMTFfMzAwWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDYtMTVUMjJfMjBfMDNfMDU5Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDctMTVUMjJfMjBfNDhfOTUxWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDgtMTVUMjJfMjBfNTFfOTU4Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMDktMTVUMjJfMjBfNDhfNDMyWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMTAtMTVUMTBfMjBfMzhfMjcxWi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMTEtMTVUMTBfMjFfMDRfOTE5Wi56aXAiLCAKICAgICAgICAgImh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS93ZXJ1bnMvZm9yZnVuL0tpY2tzdGFydGVyL0tpY2tzdGFydGVyXzIwMTctMTItMTVUMTBfMjBfNTFfNjEwWi56aXAiKQoKIyBDcmVhbW9zIHVuYSBmdW5jacOzbiBxdWUgZXh0cmFlIGxvcyBuIMO6bHRpbW9zIGNhcmFjdGVyZXMKc3Vic3RyUmlnaHQgPC0gZnVuY3Rpb24oeCwgbil7CiAgc3Vic3RyKHgsIG5jaGFyKHgpLW4rMSwgbmNoYXIoeCkpCn0KCiMgQXBsaWNhbW9zIGxhIGZ1bmNpw7NuIHJlY2nDqW4gY3JlYWRhIHBhcmEgZXh0cmFlciBsb3Mgw7psdGltbyAyOCBjYXJhY3RlcmVzIGRlIGxhIHVybCAoYcOxbywgbWVzIHkgY29uc2VjdXRpdm8pCiMgY29uIGxvcyBjYXJhY3RlcmVzIGV4dHJhw61kb3Mgbm9tYnJhbW9zIGxvcyBhcmNoaXZvcyB5IGxvcyBndWFyZGFtb3MgZGVudHJvIGRlIGxhIGNhcnBldGEgZGF0YSBkZW50cm8gZGVsIHByb3llY3RvCgpsaWJyYXJ5KGhlcmUpCmZvciAoaSBpbiAxOmxlbmd0aCh1cmwpKSB7CiAgcGF0aF9uYW1lIDwtIHBhc3RlKGhlcmUoIi4vZGF0YS8iKSwgc3Vic3RyUmlnaHQodXJsW2ldLCAyOCkpCiAgZG93bmxvYWQuZmlsZSh1cmxbaV0sIHBhdGhfbmFtZSkKfQpgYGAKCiMjIERlc2NvbXByaW1pZW5kbyBsb3MgZGF0b3M6CgpVbmEgdmV6IHF1ZSBoZW1vcyBkZXNjYXJnYWRvIG9zIGRhdG9zLCB2YW1vcyBhIGRlc2NvbXByaW1pcmxvcyAobGEgbGVjdHVyYSBkZXNkZSBlbCB6aXAgbWUgcHJvZHVqbyBlcnJvcmVzIHF1ZSBwcmVmZXLDrSBldml0YXIpCgpgYGB7ciwgdW56aXBwaW5nLCBjYWNoZT1UUlVFLCBldmFsPUZBTFNFfQpsaWJyYXJ5KGhlcmUpCgojIGhhY2Vtb3MgdW5hIGxpc3RhIGRlIHRvZG9zIGxvcyBhcmNoaXZvcyAuemlwIGRlc2NhcmdhZG9zCmFsbF9maWxlcyA8LSBsaXN0LmZpbGVzKGhlcmUoImRhdGEiKSkKIyBFc3RhYmxlY2Vtb3MgZMOzbmRlIGxvcyB2YW1vcyBhIGd1YXJkYXIKb3V0ZGlyIDwtIGhlcmUoImRhdGEvdW56aXBwZWQiKQoKIyBmdW5jacOzbiBkZSBhcG95byBwYXJhIGV4dHJhZXIgcGFydGUgZGVsIG5vbWJyZSB5IHVzYXJsbyBwYXJhIHJlbm9tYnJhcgpzdWJzdHJMZWZ0IDwtIGZ1bmN0aW9uKHgsIG4pewogIHN1YnN0cih4LCAyLCBuKzEpCn0KCiMgUmVub21icmFkbyBtYXNpdm8Kc2V0d2QoaGVyZSgiL2RhdGEiKSkKCmZvcihpIGluIDE6bGVuZ3RoKGFsbF9maWxlcykpIHsKICBmaWxlLnJlbmFtZShmcm9tID0gYWxsX2ZpbGVzW2ldLCB0byA9IHN1YnN0ckxlZnQoYWxsX2ZpbGVzW2ldLCA3KSkKfQpzZXR3ZChoZXJlKCkpCgojIEV4dHJhZW1vcyB5IGd1YXJkYW1vcwpmb3IoaSBpbiAxOmxlbmd0aChhbGxfZmlsZXMpKSB7CiAgZiA8LSBwYXN0ZShoZXJlKCJkYXRhIikgLCBhbGxfZmlsZXNbaV0sIHNlcCA9ICIvIikKICB0aGlzT3V0RGlyIDwtIHBhc3RlKG91dGRpciwgYWxsX2ZpbGVzW2ldLCBzZXAgPSAiLyIpCiAgdW56aXAoZiwgZXhkaXIgPSB0aGlzT3V0RGlyKQp9CgojIExpc3RhIGRlIHRvZG9zIGxvcyBkZXNjb21wcmltaWRvcwphbGxfYWJzX3BhdGhzIDwtIGxpc3QuZmlsZXMoaGVyZSgiZGF0YS91bnppcHBlZCIpLCBwYXR0ZXJuID0gIi5jc3YiLCByZWN1cnNpdmUgPSBUUlVFLCBmdWxsLm5hbWVzID0gVFJVRSkKCiMgR3VhcmRhbW9zIGxhIGxpc3RhIGRlIHRvZG9zIGxvcyBkZXNjb21wcmltaWRvcwpzYXZlUkRTKGFsbF9hYnNfcGF0aHMsIGhlcmUoImRhdGEvYWxsX2ZpbGVzX3VuemlwcGVkLnJkcyIpKQpgYGAKCiMjIE9yZGVuYW5kbyBsb3MgZGF0b3M6CgpBaG9yYSBxdWUgdGVuZW1vcyB0b2RvcyBsb3MgZGF0b3MgZGVzY29tcHJpbWlkb3MsIGxvcyB2YW1vcyBhIGxlZXIgcGFyYSBqdW50YXJsb3MgZW4gdW4gw7puaWNvIGRhdGFmcmFtZS9zZXQgZGUgZGF0b3M6IHRlbmVtb3MgdW5hIGNhcnBldGEgcGFyYSBjYWRhIG1lcyBkZWwgYcOxbyAyMDE3ICgxMiBjYXJwZXRhcyksIGNhZGEgdW5hIGNvbiAzNC42NiBhcmNoaXZvcyBlbiBwcm9tZWRpby4gVW4gdG90YWwgZGUgNDE2IGFyY2hpdm9zIHF1ZSBqdW50b3Mgc3VtYW4gOC4yIEdpQi4KCmBgYHtyLCBvcmRlbmFuZG8sIGNhY2hlPVRSVUUsIGV2YWw9RkFMU0V9CmxpYnJhcnkoaGVyZSkKc2V0d2QoaGVyZSgpKQoKIyBJbXBvcnRhbW9zIGxhIGxpc3RhIGRlIGFyY2hpdm9zIGRlIHRvZG9zIGxvcyBhcmNoaXZvcwphbGxfZmlsZXMgPC0gcmVhZFJEUyhoZXJlKCJkYXRhL2FsbF9maWxlc191bnppcHBlZC5yZHMiKSkKCmxpYnJhcnkoc3RyaW5ncikKCiMgQXJyZWdsYW1vcyBsb3Mgbm9tYnJlcyBkZSBsb3MgYXJjaGl2b3MgZW4gdGFudG8gcXVlIGNvbG5hbWVzLCBwYXJhIHF1ZSBwdXJyciByZXRlbmdhIGVsIHBlcmlvZG8gZGVsIHF1ZSBwcm92aWVuZSBlbCBkYXRvCm5hbWVzKGFsbF9maWxlcykgPC0gYWxsX2ZpbGVzICU+JSAKICBzdHJfcmVtb3ZlKCIuY3N2IikgJT4lIAogIHN0cl9zdWIoNzksIDgwKQoKIyBDb24gcHVycnIgYXBsaWNhbW9zIHJlYWRfY3N2IGRlIGxhIGxpYnJlcsOtYSByZWFkciBhIHRvZG9zIGxvcyBhcmNoaXZvcyBsaXN0YWRvczogbG9zIGxlZW1vcyB0b2RvcwpzZXR3ZChoZXJlKCJkYXRhL3VuemlwcGVkIikpCmRmIDwtIHB1cnJyOjpzYWZlbHkocHVycnI6Om1hcF9kZihhbGxfZmlsZXMsIHJlYWRyOjpyZWFkX2NzdiwgLmlkID0gInBlcmlvZG8iKSkKCnNldHdkKGhlcmUoKSkKCiMgVGVuZW1vcyB2YXJpYWJsZXMgZGUgdGllbXBvIGNvbW8gbsO6bWVyby4gTGVzIGRhbW9zIGVsIGZvcm1hdG8gYWRlY3VhZG8KZGZbLCAxNToxOF0gPC0gbGFwcGx5KGRmWywgMTU6MThdLCBhcy5EYXRlLlBPU0lYY3QpICMgY29udmVydGlyIGVsIG7Dum1lcm8gYSBmZWNoYQoKIyBTaSB0b21hbW9zIGxvcyBjYXNvcyBxdWUgbm8gc29uIE5BcyBlbiBlc3RhcyDDumx0aW1hcyBjb2x1bW5hcywgeSBsYXMgZGl2aWRpbW9zIHNvYnJlIGVsIHRvdGFsOgojIGxhcyBjb2x1bW5hcyAqIGxhcyBmaWxhcyBkaXZpZGlkYXM6IAooKChuY29sKGRmKS0yOSkgKiBucm93KGRmKSkgLSAoc3VtKGlzLm5hKGRmWywgMzA6bmNvbChkZildKSkpKSAvICgobmNvbChkZiktMjkpICogbnJvdyhkZikpIAoKIyBOb3MgZGFtb3MgY3VlbnRhIHF1ZSBsYSBwcm9wb3JjacOzbiBkZSBjYXNvcyBubyBhbWVyaXRhbiBlbCBlc2Z1ZXJ6byBkZSBsYSBsaW1waWV6YSBuaSBkZWphIGx1Z2FyIGEgbGEKIyBpbXB1dGFjacOzbjogZW50b25jZXMgbGFzIGRlamFtb3MgZGUgbGFkby4KZGYgPC0gZGZbLCAxOjI5XQoKIyBEZSBsYXMgdmFyaWFibGVzIGEgZGlzcG9zaWNpw7NuLCByZWFsbWVudGUgbWUgaW50ZXJlc2EsIHNvYnJlIHRvZG8sIGVsIHRleHRvIHkgZWwgZXN0YWRvIGZpbmFsLgojIFBlcm8gbmVjZXNpdGFtb3MgZWwgcGVyaW9kbywgeSBsb3MgcGxlZGdlcywgdXNkX3JhdGUsIGdvYWwgeSBvdHJvcyBkYXRvcyBwdWVkZW4gcmVzdWx0YXIgZGUgdXRpbGlkYWQgcGFyYSBtb2RlbG9zCiMgcXVlIHRvbWVuIGNvbW8gZW50cmFkYSBQcm9jZXNhbWllbnRvIGRlbCBMZW5ndWFqZSBOYXR1cmFsIChOTFApLCBxdWUgZXMgZWwgY2VudHJvIGRlbCBhc3VudG8uCmRmX3RleHQgPC0gZGZbLCBjKDE6MiwgNDo5LCAxMToyMiwgMjQ6MjUpXQoKIyBEZWJpZG8gYSBxdWUgc2UgcHJlc2VudGFuIHByb2JsZW1hcyBjb24gbGEgUkFNIGRpc3BvbmlibGUgYSBsYSBob3JhIGRlIGVzY3JpYmlyIGxvcyBhcmNoaXZvcyBlbiBSLCBsb3MgcGFydGltb3MKIyBlbiB1bmlkYWRlcyBtw6FzIHBlcXVlw7Fhcy4KZGZfdGV4dDEgPC0gZGZfdGV4dFsxOjUyODU4NCwgXQpkZl90ZXh0MiA8LSBkZl90ZXh0WzUyODU4NToxMDU3MTcwLCBdCmRmX3RleHQzIDwtIGRmX3RleHRbMTA1NzE3MToxNTg1NzU2LCBdCmRmX3RleHQ0IDwtIGRmX3RleHRbMTU4NTc1NzoyMTE0MzMzLCBdCgojIFkgbGFzIGd1YXJkYW1vcwpzYXZlUkRTKGRmX3RleHQxLCAiLi9kYXRhL3RleHRfc2V0cy9kYXRhX3RleHQxLnJkcyIpCnNhdmVSRFMoZGZfdGV4dDIsICIuL2RhdGEvdGV4dF9zZXRzL2RhdGFfdGV4dDIucmRzIikKc2F2ZVJEUyhkZl90ZXh0MywgIi4vZGF0YS90ZXh0X3NldHMvZGF0YV90ZXh0My5yZHMiKQpzYXZlUkRTKGRmX3RleHQ0LCAiLi9kYXRhL3RleHRfc2V0cy9kYXRhX3RleHQ0LnJkcyIpCmBgYAoKIyMgTGltcGlhbmRvIGRhdG9zOgoKQWhvcmEgcXVlIHRlbmVtb3MgdG9kb3MgbG9zIGRhdG9zLCBub3MgZGFtb3MgY3VlbnRhIGbDoWNpbCBxdWUgY2FkYSBwcm95ZWN0byBhcGFyZWNlIG11Y2hhcyB2ZWNlczogdW4gcHJveWVjdG8gcHVlZGUgYXBhcmVjZXIgZW4gZW5lcm8sIGZlYnJlcm8sIG1hcnpvLi4uIHkgc3UgZXN0YWRvIHZhIGNhbWJpYW5kbyB5IGVsIHJlY2F1ZG8sIGxvcyBiYWNrZXJzLiBQZXJvIGxvIHF1ZSBub3MgaW50ZXJlc2Egc2FiZXIgZXMgc3UgZXN0YWRvIGZpbmFsOiBzaSBsb2dyw7MgbyBubyByZWNhdWRhciBlbCBkaW5lcm8uCgpFcyBwb3IgZXNvIHF1ZSBuZWNlc2l0YW1vcyByZXRlbmVyIHNvbGFtZW50ZSBlbCDDumx0aW1vIGVzdGFkbyBkZWwgcHJveWVjdG8sIHN1IMO6bHRpbWEgYXBhcmljacOzbiwgcXVlIGVzIGVsIHBlcmlvZG8gbcOheGltbyBkZSBjYWRhIHByb3llY3RvLgoKYGBge3IsIHVuaXF1ZSwgY2FjaGU9VFJVRSwgZXZhbD1GQUxTRX0KbGlicmFyeShoZXJlKQoKIyBMZWVtb3MgdG9kb3MgbG9zIHNldCBkZSBkYXRvcwpkZjEgPC0gcmVhZFJEUyhoZXJlKCIvZGF0YS90ZXh0X3NldHMvZGF0YV90ZXh0MS5yZHMiKSkKZGYyIDwtIHJlYWRSRFMoaGVyZSgiL2RhdGEvdGV4dF9zZXRzL2RhdGFfdGV4dDIucmRzIikpCmRmMyA8LSByZWFkUkRTKGhlcmUoIi9kYXRhL3RleHRfc2V0cy9kYXRhX3RleHQzLnJkcyIpKQpkZjQgPC0gcmVhZFJEUyhoZXJlKCIvZGF0YS90ZXh0X3NldHMvZGF0YV90ZXh0NC5yZHMiKSkKCiMgbG9zIGp1bnRhbW9zCmRmIDwtIHJiaW5kKGRmMSwgZGYyLCBkZjMsIGRmNCkKCiMgYm9ycmFtb3MgbG9zIHNldHMgcGFyY2lhbGVzIChzw7NsbyBwb3IgbGliZXJhciBlc3BhY2lvKQpybShsaXN0ID0gYygiZGYxIiwgImRmMiIsICJkZjMiLCAiZGY0IikpCgojIHZlcmlmaWNhbW9zIGVsIHRhbWHDsW8gZGVsIG51ZXZvIHNldApmb3JtYXQob2JqZWN0LnNpemUoZGYpLCB1bml0cyA9ICJNYiIpCgpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHN0cmluZ3IpCgojIEVsIHBlcmlvZG8gbG8gcGFzYW1vcyBkZSBtZXMgeSBhw7FvIGEgc29sYW1lbnRlIG1lcyAodG9kb3Mgc29uIDIwMTcpCmRmIDwtIGRmICU+JSAKICAgIG11dGF0ZShwZXJpb2RvID0gYXMuaW50ZWdlcihzdHJfc3ViKHBlcmlvZG8sIDYsIDcpKSkKCiMgUGFzYW1vcyBlbCBpZCAoaWRlbnRpZmljYWRvciBkZWwgcHJveWVjdG8pIGEgY2hhcmFjdGVyLgpkZiRpZCA8LSBhcy5jaGFyYWN0ZXIoZGYkaWQpCgojIFJldGVuZW1vcyBkZSBjYWRhIHByb3llY3RvIGxhIGZpbGEgZW4gbGEgcXVlIHRpZW5lIHN1IMO6bHRpbW8gbWVzLCB5IGVsaW1pbmFtb3MgbG9zIGNhc29zIGVuIGxvcyBxdWUgaGF5YSBkdXBsaWNhZG9zCiMgZW4gY3VhbnRvIGEgcGVyaW9kbyBlIGlkLgpkZl91bmlxdWUgPC0gZGYgJT4lIAogICAgZ3JvdXBfYnkoaWQpICU+JSAKICAgIGZpbHRlcihwZXJpb2RvID09IG1heChwZXJpb2RvKSkgJT4lIAogICAgZGlzdGluY3QoaWQsIHBlcmlvZG8sIC5rZWVwX2FsbCA9IFRSVUUpCgojIFJldmlzYW1vcyBjdcOhbnRvIG9jdXBhIGFob3JhIGVsIG51ZXZvIHNldApmb3JtYXQob2JqZWN0LnNpemUoZGZfdW5pcXVlKSwgdW5pdHMgPSAiTWIiKQoKIyBMZSBkYW1vcyB1bmEgbWlyYWRhIGFsIHNldApnbGltcHNlKGRmX3VuaXF1ZSkKCiMgWSBsbyBndWFyZGFtb3MKc2F2ZVJEUyhkZl91bmlxdWUsIGhlcmUoIi9kYXRhL3RleHRfc2V0cy9kZi5yZHMiKSkKYGBgCgoKYGBge3IsIGZpbmFsX2Zvcm1hdCwgY2FjaGU9VFJVRX0KbGlicmFyeShoZXJlKQpsaWJyYXJ5KGRwbHlyKQpkZiA8LSByZWFkUkRTKGhlcmUoIi9kYXRhL3RleHRfc2V0cy9kZi5yZHMiKSkKZGYkcGVyaW9kbyA8LSBhcy5EYXRlKHBhc3RlKCIwMSIsIGFzLmNoYXJhY3RlcihkZiRwZXJpb2RvKSwgIjIwMTciLCBzZXAgPSAiLSIpLCB0eiA9ICJVVEMiLCBmb3JtYXQgPSAiJWQtJW0tJVkiKQpkZiR1c2RfZ29hbCA8LSBkZiRzdGF0aWNfdXNkX3JhdGUgKiBkZiRnb2FsCmRmJHN0YXRlIDwtIGFzLmZhY3RvcihkZiRzdGF0ZSkKZGYkY291bnRyeSA8LSBhcy5mYWN0b3IoZGYkY291bnRyeSkKZGYkY3VycmVuY3kgPC0gYXMuZmFjdG9yKGRmJGN1cnJlbmN5KQpkZiRjdXJyZW5jeV9zeW1ib2wgPC0gYXMuZmFjdG9yKGRmJGN1cnJlbmN5X3N5bWJvbCkKZGYkY3VycmVuY3lfdHJhaWxpbmdfY29kZSA8LSBhcy5mYWN0b3IoZGYkY3VycmVuY3lfdHJhaWxpbmdfY29kZSkKZGYkc3RhZmZfcGljayA8LSBhcy5mYWN0b3IoZGYkc3RhZmZfcGljaykKZGYgPC0gZGYgJT4lIAogICAgZmlsdGVyKCFzdGF0ZSAlaW4lIGMoImxpdmUiLCAiY2FuY2VsZWQiLCAic3VzcGVuZGVkIikpCgpkZl90ZXh0IDwtIGRmWywgYygiYmx1cmIiLCAic3RhdGUiKV0Kcm0oZGYpCiMgRW4gY2FzbyBkZSBxdWUgcXVpZXJhbiBhcGxpY2FyIGxhIHRyYW5zZm9ybWFjacOzbiBkZSBjYXRlZ29yw61hcwojZGZfdGV4dCRiaW5fc3RhdGUgPC0gaWZlbHNlKGRmX3RleHQkc3RhdGUgPT0gInN1Y2Nlc3NmdWwiLCAic3VjY2Vzc2Z1bCIsICJmYWlsZWQiKQoKbGlicmFyeShjbGRyKQpsYW5nIDwtIGRldGVjdExhbmd1YWdlKGRmX3RleHQkYmx1cmIpCmRmX3RleHRfZW5nIDwtIGRmX3RleHRbbGFuZyRkZXRlY3RlZExhbmd1YWdlQ29kZSA9PSAwLCBdCgpzYXZlUkRTKGRmX3RleHRfZW5nLCBoZXJlKCIvZGF0YS90ZXh0X3NldHMvZGZfdGV4dF9lbmcucmRzIikpCndyaXRlLmNzdihkZl90ZXh0X2VuZywgaGVyZSgiL2RhdGEvdGV4dF9zZXRzL2RmX3RleHRfZW5nLmNzdiIpKQpgYGAKCiMjIE5vdGEgZmluYWw6CgpEZSBlc3RhIG1hbmVyYSBzZSByZXN1bWVuIHRvZG9zIGxvcyBwYXNvcyBwYXJhIGNvbmZvcm1hciBlbCBjc3YgcXVlIGVzIHNvYnJlIGVsIHF1ZSBlc3RhcsOpIHRyYWJhamFuZG8sIGVsIGN1YWwgcHVlZGVuIGRlc2NhcmdhciBkZXNkZSBteSBHb29nbGUgZHJpdmUgW2FxdcOtXShodHRwczovL2RyaXZlLmdvb2dsZS5jb20vb3Blbj9pZD0xMmxUVDZ0TWJrc2ltWEVKNU02MDVMV1doTGxfTWpRb3opIG8gZGVzZGUgS2FnZ2xlIFthcXXDrV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9vc2NhcnZpbGxhL2tpY2tzdGFydGVyLW5scCkgbyBhIHRyYXbDqXMgZGUgbGEgW0FQSV0oa2FnZ2xlIGRhdGFzZXRzIGRvd25sb2FkIC1kIG9zY2FydmlsbGEva2lja3N0YXJ0ZXItbmxwKS4KCkVzdG8gcG9ycXVlIG5vIHF1aWVybyByZXBldGlyIG3DoXMgZXN0ZSBwcm9jZXNvIHkgcG9ycXVlIGVzcGVybyBwcm9udG8gdHJhYmFqYXIgc29icmUgZWwgc2V0IGRlIGRhdG9zIGVuIEthZ2dsZSwgYXByb3ZlY2hhbmRvIHF1ZSBhaG9yYSBkaXNwb25lbiBbR1BVcyBncmF0dWl0YXNdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGFuc2JlY2tlci9ydW5uaW5nLWthZ2dsZS1rZXJuZWxzLXdpdGgtYS1ncHUpIHkgZW4gW0dvb2dsZSBjb2xhYl0oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL25vdGVib29rcy93ZWxjb21lLmlweW5iI3JlY2VudD10cnVlKSBkb25kZSB0YW1iacOpbiBkaXNwb25lbiBHUFVzIGdyYXR1aXRhcy4gVG9kbyBlbGxvIGVuIFB5dGhvbiwgaGFzdGEgZG9uZGUgaGUgbGXDrWRvIHBvciBhaG9yYS4KCkdyYWNpYXMgcG9yIHNlZ3Vpcm1lIGhhc3RhIGFxdcOtLg==