Esta es una R Markdown Notebook con todos los contenidos del curso Uso de APIs para recuperación de información bibliométrica. Todos los códigos se encuentran disponible también por separado en scripts de R.

1 Consulta manual de APIs

Las consultas a cualquier API pueden hacerse de manera manual en R. Para ello, la forma más básica es usando los paquetes httr y rjson. El primero permite hacer peticiones a través de HTTP (GET, PATCH, POST, HEAD, PUT y DELETE) desde R y el segundo trabajar con archivos JSON.

library(httr)
library(rjson)

Una vez iniciadas los dos paquetes, el primer paso es construir una query, la cual almacenaremos en una variable con dicha denominación. En este caso vamos a buscar una revista en la base de datos de DOAJ usando su API. Para ello, se usa el endpoint de DOAJ para buscar revistas científicas (https://doaj.org/api/v1/search/journals/) y se añade la query issn:2504-0537 para indicar que busque dicho ISSN.

query <- 'https://doaj.org/api/v1/search/journals/issn:2504-0537'

En siguiente lugar se realiza dicha petición a la API usando para ello la función GET() del paquete httr. El resultado lo almacenamos en la variable getdata. Una vez hecha la consulta obtendremos una respuesta, siendo de relevancia los metadatos relativos al status y content-type para poder conocer cómo ha ido la consulta.

getdata <- httr::GET(url = query)
getdata
Response [https://doaj.org/api/v1/search/journals/issn:2504-0537]
  Date: 2020-10-20 22:44
  Status: 200
  Content-Type: application/json
  Size: 2.88 kB

De manera opcional se pueden especificar en la función GET() campos como los headers o user_agent.

getdata <- httr::GET(url = query,  httr::user_agent('httr'))
getdata
Response [https://doaj.org/api/v1/search/journals/issn:2504-0537]
  Date: 2020-10-20 22:44
  Status: 200
  Content-Type: application/json
  Size: 2.88 kB

Si la consulta ha funcionado correctamente, es posible revisar con la función fromJSON() todo el JSON que devuelve la API (en caso de que este sea el formato empleado). Una vez extraída la respuesta en formato JSON y con codificación UTF-8, es posible consultar campos concretos, como el número de resultados que devuelve la API.

getdata_json <- rjson::fromJSON(httr::content(getdata, type='text', encoding = 'UTF-8'))
getdata_json$total
[1] 1

De igual manera, como en este caso concreto hemos realizado la búsqueda de un ISSN lo normal es que devuelva un único resultado. Por ello podemos dirigirnos al primer resultado, que será con bastante seguridad la revista que queramos, y extraer de ella metadatos de la misma como el título, sistema de revisión, si es Open Access y licencia.

getdata_json$results[[1]]$bibjson$title
[1] "Frontiers in Research Metrics and Analytics "
getdata_json$results[[1]]$bibjson$editorial_review$process
[1] "Blind peer review"
getdata_json$results[[1]]$bibjson$license[[1]]$open_access
[1] TRUE
getdata_json$results[[1]]$bibjson$license[[1]]$type
[1] "CC BY"

De igual manera, DOAJ cuenta con una API para buscar artículos científicos. Su funcionamiento no difiere del anterior. Usamos otro endpoint y establecemos una query, en este caso el DOI de un artículo científico, y tras ello recuperamos el JSON y del primer resultado extraemos varios metadatos.

query <- 'https://doaj.org/api/v1/search/articles/doi:10.3389/fpsyg.2013.00479'
getdata <- httr::GET(url = query)
getdata_json <- rjson::fromJSON(httr::content(getdata, type='text', encoding = 'UTF-8'))
getdata_json$results[[1]]$bibjson$title
[1] "The influence of catch trials on the consolidation of motor memory in force field adaptation tasks"
getdata_json$results[[1]]$bibjson$journal$title
[1] "Frontiers in Psychology"

1.1 Automatización del proceso

Como la consulta de cada registro se hace de manera separada, es posible automatizar este proceso para realizar múltiples consultas. Para ello, se puede generar un bucle for que realice tantas consultas como ISSNs queramos consultar.

for(issn in c('2504-0537', '2624-9898', '2297-2668')){
  
  query <- paste0('https://doaj.org/api/v1/search/journals/issn:', issn)
  getdata <- httr::GET(url = query)
  getdata_json <- rjson::fromJSON(httr::content(getdata, type='text', encoding = 'UTF-8'))
  
  j_title <- getdata_json$results[[1]]$bibjson$title
  j_er <- getdata_json$results[[1]]$bibjson$editorial_review$process
  
  print(c(issn, j_title, j_er))
}
[1] "2504-0537"                                    "Frontiers in Research Metrics and Analytics "
[3] "Blind peer review"                           
[1] "2624-9898"                     "Frontiers in Computer Science" "Blind peer review"            
[1] "2297-2668"                       "Frontiers in Digital Humanities" "Blind peer review"              

De igual manera, se pueden crear funciones con ello para, por ejemplo, almacenar en un data.frame los campos que queramos recuperar de cada consulta.

doaj_data <- function(dois){
  df <- data.frame(issn = character(),
                   title = character(),
                   editorial_review = character(),
                   stringsAsFactors = FALSE)
  
  for(issn in dois){
    query <- paste0('https://doaj.org/api/v1/search/journals/issn:', issn)
    getdata <- httr::GET(url = query)
    getdata_json <- rjson::fromJSON(httr::content(getdata, type='text', encoding = 'UTF-8'))
    
    j_title <- getdata_json$results[[1]]$bibjson$title
    j_er <- getdata_json$results[[1]]$bibjson$editorial_review$process
    
    
    df <- rbind.data.frame(df, data.frame(issn=issn, title=j_title, editorial_review=j_er))
  }
  
  return(df)
}

Así, con una simple función podemos repetir muchas consultas y almacenar los metadatos.

df <- doaj_data(c('2504-0537', '2624-9898', '2297-2668'))
df

No obstante, es necesario tener en cuenta en estos casos los posibles errores que pueden aparecer durante todo el proceso. Por ejemplo, cuando la API no encuentre resultados para nuestra consulta y quiera extraer metadatos de ahí.

doaj_data(c('issn', '2504-0537', '2624-9898', '2297-2668'))
Error in getdata_json$results[[1]] : subíndice fuera de  los límites

En este caso se puede solucionar rápidamente modificando la anterior funcion para que compruebe tras la petición que la respuesta es correcta y que además nos ha devuelto al menos un resultado. Aunque este no será el único error que podremos obtener y se requiere mucha más atención en este apartado.

doaj_data <- function(dois){
  df <- data.frame(issn = character(),
                   title = character(),
                   editorial_review = character(),
                   stringsAsFactors = FALSE)
  
  for(issn in dois){
    query <- paste0('https://doaj.org/api/v1/search/journals/issn:', issn)
    getdata <- httr::GET(url = query)
    getdata_json <- rjson::fromJSON(httr::content(getdata, type='text', encoding = 'UTF-8'))
    
    if(status_code(getdata) == 200 & getdata$headers$`x-total-count` > 0) {
      j_title <- getdata_json$results[[1]]$bibjson$title
      j_er <- getdata_json$results[[1]]$bibjson$editorial_review$process
      
      df <- rbind.data.frame(df, data.frame(issn=issn, title=j_title, editorial_review=j_er))
    }
  }
  
  return(df)
}

df <- doaj_data(c('issn', '2504-0537', '2624-9898', '2297-2668'))
df

2 El uso de paquetes

2.1 DOAJ

Pese a ello. no es necesario profundizar tanto ni tener muchos conocimientos en programación. Muchas de las APIs cuentan con paquetes para muchos lenguajes de programación, facilitando con ello las consultas. En el caso de DOAJ existe el paquete jaod.

#install.packages('jaod')
library(jaod)

Cada paquete tiene un funcionamiento diferente, es por ello que antes de usarlo es necesario revisar su documentación.

help(jaod)

Para el caso de jaod, la búsqueda de resvistas y extracción de metadatos se resume mucho gracias a la función jaod_journal_search():

res <- jaod::jaod_journal_search('issn:2504-0537')
res$results$bibjson.title
[1] "Frontiers in Research Metrics and Analytics "

Gracias a ello, en este caso nos permite simplificar mucho el trabajo. Por ejemplo, podemos crear el mismo bucle de consultas que antes, pero empleando menos código y garantizando un mejor funcionamiento.

doaj_data <- function(dois){
  df <- data.frame(issn = character(),
                   title = character(),
                   editorial_review = character(),
                   stringsAsFactors = FALSE)
  
  for(issn in dois){
    getdata <- jaod::jaod_journal_search(paste0('issn:', issn))
    
    if(getdata$total > 0){
      df <- rbind.data.frame(df, data.frame(issn=issn, title=getdata$results$bibjson.title, editorial_review=getdata$results$bibjson.editorial_review.process))
    }
  }
  
  return(df)
}

df_do <- doaj_data(c('issn', '2504-0537', '2624-9898', '2297-2668'))
df_do

2.2 Crossref

Crossref cuenta con un paquete en R llamado rcrossref con el que se pueden realizar consultas de manera muy eficiente a su API.

#install.packages('rcrossref')
library(rcrossref)

Dentro de las funciones que ofrece este paquete hay varias destacadas. En primer lugar está cr_citation_count(), la cual recupera el número de citas de un trabajo, se trata de un conteo propio de Crossref que no tiene que coincidir con el de otros servicios.

df_cr <- rcrossref::cr_citation_count(doi='10.1002/asi.23309')
df_cr

Como ventaja, es posible realizar de manera simultánea varias consultas de DOI.

dois <- c('10.1002/asi.23309', '10.1016/j.joi.2007.02.001', '10.1007/s11192-014-1264-0')
df_cr <- rcrossref::cr_citation_count(doi = dois)
df_cr

Otra función interesante es cr_journals(), la cual permite buscar y obtener numerosa información de revistas científicas. Por un lado, es posible buscar revistas usando uno o varios términos. Con esta función los metadatos recuperados vienen dados en forma de lista, siendo el elemento data el que los incluye.

df_cr <- rcrossref::cr_journals(query = 'library and information science')
df_cr$data

De igual manera, es posible buscar información sobre una o varias revistas concretas usando su ISSN.

df_cr <- rcrossref::cr_journals(issn = '1699-2407')
df_cr$data

issns <- c('1699-2407', '0165-5515')
df_cr <- rcrossref::cr_journals(issn = issns)
df_cr$data

Además de la información sobre la revista, es posible recuperar trabajos científicos publicados en ella usando el parámetro works = TRUE. Por defecto son 20 los trabajos científicos o revistas que recupera, pero mediante el parámetro limit se puede alterar dicho resultado, aunque el máximo es 1000.

df_cr <- rcrossref::cr_journals(issn = '1699-2407', works = TRUE, limit = 50)
df_cr$data

Los artículos recuperados de la revista consultada pueden ser además ordenados mediante los parámetros sort y order. Por ejemplo, ordenados por el número de citas recibidas en orden descendiente.

df_cr <- rcrossref::cr_journals(issn = '1699-2407', works = TRUE, sort = 'is-referenced-by-count', order = 'desc')
df_cr$data

En relación con la recuperación de trabajos científicos de una revista, también es posible establecer subbúsquedas con el parámetro flq. Por ejemplo, artículos en los que el nombre del autor sea Torres-Salinas.

df_cr <- rcrossref::cr_journals(issn = '1699-2407', works = TRUE, flq = c(`query.author`='Torres-Salinas'))
df_cr$data

De igual manera que con las revistas, existe una función para realizar consultas de artículos científicos. Con cr_works() podemos hacer dichas consultas y su funcionamiento es muy similar al de las revistas, compartiendo muchos de sus parámetros. Podemos buscar así artículos mediante un término y ordenar los resutlados por citas.

df_cr <- rcrossref::cr_works(query = 'library', sort = 'is-referenced-by-count', order = 'desc')
df_cr$data

También es posible buscar directamente varios DOI. En este caso, podemos incluso indicar con el parámetro .progress = 'text' que nos muestre el progreso de dicha consulta.

dois <- c('10.1002/asi.23309', '10.1016/j.joi.2007.02.001', '10.1007/s11192-014-1264-0')
df_cr <- rcrossref::cr_works(dois = dois)
df_cr$data

Como pasa con las revistas, también existe un limite de 1000 resutados. No obstante, aquí podemos aumentar ese limite usando un cursor. Para ello fijamos un máximo en cursor_max y limit. El primero es el número de publicaciones total que queremos recuperar como máximo, mientras que el segundo hace referencia a los chunks de peticiones, es decir las subpeticiones que se realizan hasta alcanzar el máximo, de manera que este valor no puede ser mayor. Esta consulta también admite el uso del parámetro .progress aunque con un valor diferente al anterior.

df_cr <- rcrossref::cr_works(query = 'twitter', cursor = '*', cursor_max = 300, limit = 200)
df_cr$data

De manera paralela a la consulta, es posible llevar a cabo una búsqueda por facetas con el parámetro facet. Aunque solo está disponible para determinados campos. Es preferible emplearla sin que devuelva los metadatos de las publicaciones para que sea más rápida y permita obtener de manera sencilla un vistazo general a nuestra consulta.

df_cr <- rcrossref::cr_works(query = 'Twitter', facet = 'publisher-name:*', limit = 0)
df_cr$facets$`publisher-name`

2.3 Web of Science

Web of Science puede ser consultada a través del paquete wosr.

#install.packages('wosr')
library(wosr)

Para usarla ese necesario introducir unas credenciales mediante la función auth(). Hay dos posibilidades para ello: usar un usuario y contraseña o dejar ambos campos como NULL y establecer conexión VPN con una institución suscrita a ella. Este último es nuestro caso.

sid <- wosr::auth(username = NULL, password = NULL)

No son muchas las funcionalidades que ofrece este paquete. Se puede, por ejemplo, realizar con query_wos() una consulta y ver el número de resultados que hay para ella.

wosr::query_wos('TS = scientometrics', sid = sid)
Matching records: 2,454

Asimismo, con el parámetro editions se pueden ajustar las ediciones a consultar.

wosr::query_wos('TS = scientometrics', sid = sid, editions = c('SCI', 'SSCI'))
Matching records: 1,629

Es posible descargar dichos registros, es por ello que es mejor realizar en primer lugar la consulta para conocer el alcance. Para ello hay que usar la función pull_wos().

df_ws <- wosr::pull_wos('TS = scientometrics', sid = sid)

Gracias a esta función podemos descargar más de 500 registros de una vez, aunque los datos están subdivididos en una lista en base a campo como la revista, información de autor…

df_ws$publication

2.4 Scopus

En el caso de Scopus, es posible recuperar información de su base de datos a través del paquete rscopus.

#install.packages('rscopus')
library(rscopus)

En este caso, es necesario disponer de una clave de acceso a la API para hacer uso de todas sus funcionalidades. Para ello es necesario registrarse y solicitarlo a través de su página web. Existen dos opciones para introducir la clave. En primer lugar introduciéndola como una parámetro en cada una de las funciones (get_api_key()) o estableciéndola de base en la sesión (set_api_key()).

key <- rscopus::get_api_key(api_key = '')
rscopus::set_api_key(api_key = '')

Es posible comprobar si la sesión tiene establecida dicha clave con la función have_api_key().

rscopus::have_api_key()
[1] TRUE

En primer lugar, es posible usar la función process_author_name() para buscar autores. Esta es de utilidad ya que para acceder a información de estos necesitamos su identificador, obtenido aquí. Con verbose = FALSE ocultamos mensajes, como la URL de la petición.

author_sc <- rscopus::process_author_name(last_name = 'Moed', first_name = 'Henk F.', verbose = FALSE)
author_sc$au_id
[1] "7003555412"

Es posible también recuperar metadatos de los autores mediante la función author_retrieval(). Se puede tanto buscar autores por nombre y apellidos como indicar el id de uno de ellos.

author_sc <- rscopus::author_retrieval(last_name = 'Moed', first_name = 'Henk F.', verbose = FALSE)
author_sc <- rscopus::author_retrieval(au_id = '7003555412', verbose = FALSE)
author_sc$content$`author-retrieval-response`[[1]]$coredata$`document-count`
[1] "132"
author_sc$content$`author-retrieval-response`[[1]]$coredata$`cited-by-count`
[1] "4496"

Con la función author_df() se pueden buscar contenidos de un autor. Para garantizar que estos son de un autor, lo correcto es primero buscar su identificador y luego contenidos en base a ello.

df_sc <- rscopus::author_df(last_name = 'Moed', first_name = 'Henk F.', verbose = FALSE)
df_sc <- rscopus::author_df(au_id = '7003555412', verbose = FALSE)
df_sc

Con la función scopus_search() se pueden realizar consultas en Scopus a tavés de su API. Para hacer más manejables los datos es necesario usar tras ello la función gen_entries_to_df(). En función del parámetro count se puede usar el view para obtener más o menos información de los registros.

df_sc <- rscopus::scopus_search(query = 'TITLE-ABS-KEY(altmetrics)', count = 10, view = 'COMPLETE', verbose = FALSE)
df_sc <- rscopus::gen_entries_to_df(df_sc$entries)
head(df_sc$df)

2.5 Unpaywall

Para acceder a la API de Unpaywall es necesario instalar el paquete roadoi.

#install.packages('roadoi')
library(roadoi)

Aquí podemos usar la función oadoi_fetch() para consultar directamente varios DOIs. No obstante, existe una limitación de 100.000 consultas diarias y es necesario que te identifiques mediante un correo electrónico.

dois <- c('10.7326/m18-2101', '10.1093/biosci/biz088')
df_un <- roadoi::oadoi_fetch(dois = dois, email = 'usuario@correo.com')
df_un

Existe la opción de que la función muestre el porcentaje de progreso de la consulta, algo que es de utilidad en consultas más extensas. Para ello solo hay que añadir a la función el parámetro .progress = 'text'.

dois <- c('10.7326/m18-2101', '10.1093/biosci/biz088', '10.1126/science.aat7693', '10.1038/s41467-019-12808-z',
          '10.1136/bmj.k5094', '10.1126/science.aax0848', '10.1016/s0140-6736(19)30041-8', '10.1038/s41586-019-1666-5')
df_un <- roadoi::oadoi_fetch(dois = dois, email = 'usuario@correo.com')
df_un

El resultado aparece en una variable de tipo tbl_df. Es por ello que algunos de los campos, como oa_location, incluyen a su vez otro data.frame en lugar de un valor atómico.

df_un[2,]$oa_locations[[1]]

Estos campos requieren de un procesamiento posterior. Por ejemplo, generando otro data.frame en el que cada DOI se desdoble tantas veces como URLs tenga.

# Para esta operación es necesario tener instalado y cargado el paquete dplyr
library(dplyr)

dplyr::mutate(df_un, urls = purrr::map(best_oa_location, 'url') %>% 
                purrr::map_if(purrr::is_empty, ~ NA_character_) %>%
                purrr::flatten_chr()
              )[c('doi', 'urls')]

O que en caso de localizar un error en los DOIs no se detenga la consulta, que es una de las limitaciones, y tras ello quedarnos con aquellos campos que nos interesen, por ejemplo el DOI y si es Open Access.

# Para esta operación es necesario tener instalado y cargado el paquete dplyr
#library(dplyr)

dois <- c('10.7326/m18-2101', '10.1093/biosci/biz088', '10.1126/science.aat7693', '10.1038/s41467-019-12808-z',
          '10.1136/bmj.k5094', '-', '10.1016/s0140-6736(19)30041-8', '10.1038/s41586-019-1666-5')

df_un <- purrr::map(dois, 
                    .f = purrr::safely(function(x) roadoi::oadoi_fetch(x, email = 'usuario@correo.com')))
Request failed [404]. Retrying in 1.2 seconds...
Request failed [404]. Retrying in 2.1 seconds...
df_un <- purrr::map_df(df_un, 'result')
df_un <- df_un[, c('doi', 'is_oa')]
df_un

2.6 arXiv

En el caso de arXiv, también es posible conectarse a la API mediante un paquete llamado aRxiv.

#install.packages('aRxiv')
library(aRxiv)

Con la función arxiv_search() es posible realizar consultas. El resultado obtenido es un data.frame con las distintas publicaciones, ofreciendo al respecto distintos metadatos para cada una de ellas.

df_ar <- aRxiv::arxiv_search('au:"Rodrigo Costas" AND ti:altmetrics')
df_ar

Además, es posible ordernar los resultados con el parámetro sort_by. Por ejemplo, por fecha de envío en orden descendente (parámetro ascending = FALSE).

df_ar <- aRxiv::arxiv_search('au:"Rodrigo Costas" AND ti:altmetrics', sort_by = 'submitted', ascending = FALSE)
df_ar

Es posible recuperar directamente el número total de preprints que se ajustan a la consulta. Para ello se usa la función arxiv_count. Una opción útil para conocer el alcance de la consulta.

aRxiv::arxiv_count('submittedDate:[20180101 TO 20191231]')
[1] 295832

Al respecto es necesario remarcar que la API cuenta con una limitación de 50.000 registros por consulta. Asimimso, por defecto la función arxiv_search() está limitada a 10 resultados, para alterarlo es necesario usar el parámetro limit. Es por ello que una buena estrategia de búsqueda es la de consultar primer el total de documentos que hay para la consulta que queremos y luego realizarla.

aRxiv::arxiv_count('submittedDate:20190512*')
[1] 213
df_ar <- aRxiv::arxiv_search('submittedDate:20190512*', sort_by = 'submitted', ascending = FALSE, limit = 300)
retrieved batch 1
retrieved batch 2
retrieved batch 3
df_ar

2.7 Altmetric.com

Otra de las API que cuentan con un paquete en R es Altmetric. Este se llama rAltmetric.

#install.packages('rAltmetric')
library(rAltmetric)

Mediante la función altmetrics es posible realizar una consulta, usando para ello un DOI, PMID, arxiv o ISBN. Su funcionamiento es sin emgargo similar al de DOAJ. Con cada consulta solo podemos incluir un identificador. Aunque, el resultado no es un data.frame es un tipo de objeto altmetric que es necesario modificar.

alt_doi <- rAltmetric::altmetrics(doi = '10.1038/480426a')
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
alt_doi
Altmetrics on: "365 days: 2011 in review" with altmetric_id: 502878 published in Nature.
alt_arx <- rAltmetric::altmetrics(arxiv = '1905.08233')
alt_arx
Altmetrics on: "Few-Shot Adversarial Learning of Realistic Neural Talking Head Models" with altmetric_id: 60733304 published in arXiv.

La conversión a un data.frame es también de manera individual y requiere el uso de la función altmetric_data.

df_alt <- rAltmetric::altmetric_data(alt_doi)

De este modo, se puede crear una función con la que automatizar todo este proceso. Por ejemplo con un bucle que consulte tantos DOIs como queramos y que tras ello los vaya combinando en un data.frame. Decir que no todos los data.frame tienen los mismo campos, por lo que se generarán algunos vacíos.

# Para esta operación es necesario tener instalado y cargado el paquete dplyr
#library(dplyr)

altmetric_data <- function(dois){
  df <- data.frame()
  for(doi in dois){
    alt <- rAltmetric::altmetrics(doi = doi)
    df_alt <- rAltmetric::altmetric_data(alt)
    df <- dplyr::bind_rows(df, df_alt)
  }
  
  return(df)
}

altmetric_data(dois[1:2])

Por último en esta API, pese a que se puede usar de manera gratuita, es posible que tras realizar varias consultas su uso quede limitado. Es por ello que para su correcto funcionamiento requiere de una key, la cual se puede introducir con el parámetro apikey.

rAltmetric::altmetrics(apikey = 'TU_KEY')

3 Caso práctico

Para ejemplificar la utilidad de estas herramientas, vamos a considerar dos casos prácticos.

3.1 Ejemplo 1

En esta caso queremos hacer una búsqueda en Web of Science y tras ello consultar cuantos de estos registros se encuentran en Altmetric.com y obtener su Altmetric Attention Score.

En primer lugar realizamos la consulta en Web of Science usando su API.

#library(wosr)
sid <- wosr::auth(username = NULL, password = NULL)

wosr::query_wos('TS = (Wikipedia AND altmetric*)', sid = sid)
df_ej1 <- wosr::pull_wos('TS = (Twitter AND altmetric*)', sid = sid)

Tras ello realizamos la consulta a Altmetric.com. Para ello usamos la función antes propuesta que realiza en bucle todas las consultas y después seleccionamos del conjunto de datos resultante el DOI y el Altmetric Attencion Score.

#library(rAltmetric)

dois <- df_ej1$publication$doi

# Para esta operación es necesario tener instalado y cargado el paquete dplyr
#library(dplyr)

altmetric_data <- function(dois){
  df <- data.frame()
  for(doi in dois){
    alt <- rAltmetric::altmetrics(doi = doi)
    df_alt <- rAltmetric::altmetric_data(alt)
    df <- dplyr::bind_rows(df, df_alt)
  }
  
  return(df)
}

df_ej1_alt <- altmetric_data(dois[1:2])
df_ej1_alt <- df_ej1_alt[,c('doi', 'score')]
df_ej1_alt

3.2 Ejemplo 2

Para este segundo vamos a partir de un archivo CSV exportado desde Dimensions. Lo vamos a importar a RStudio y en Unpaywall vamos a consultar los DOIs para generar un data.frame con el título y si es Open Access. Por último, este último lo exportaremos en formato CSV.

Para ello empezamos importando el archivo de Dimensions (Dimensions.csv). En este caso el archivo se encuentra en el mismo directorio que el script, es por ello que empleo una ruta relativa. También puede establecerse la ruta completa hasta el mismo.

df_dm <- read.table('Dimensions.csv', header = TRUE, skip = 1, sep = ',', quote = '"', comment.char = '', stringsAsFactors=FALSE)

Una vez importado el archvio, almacenamos los DOI en una variable y los usamos en la petición a Unpawall.

dois <- df_dm$DOI

#library(roadoi)

# Para esta operación es necesario tener instalado y cargado el paquete dplyr
#library(dplyr)

df_dm_un <- purrr::map(dois, 
                       .f = purrr::safely(function(x) roadoi::oadoi_fetch(x, email = 'usuario@correo.com')))
df_dm_un <- purrr::map_df(df_dm_un, 'result')
df_dm_un <- df_dm_un[, c('title', 'is_oa')]
df_dm_un
write.csv2(df_dm_un, 'Dimensions_OA.csv', row.names = FALSE, fileEncoding = 'utf-8')
LS0tDQp0aXRsZTogJ1VzbyBkZSBBUElzIHBhcmEgcmVjdXBlcmFjacOzbiBkZSBpbmZvcm1hY2nDs24gYmlibGlvbcOpdHJpY2EnDQphdXRob3I6ICdXZW5jZXNsYW8gQXJyb3lvIE1hY2hhZG8nDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdG9jOiB5ZXMNCi0tLQ0KDQpFc3RhIGVzIHVuYSBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2sgY29uIHRvZG9zIGxvcyBjb250ZW5pZG9zIGRlbCBjdXJzbyAqKlVzbyBkZSBBUElzIHBhcmEgcmVjdXBlcmFjacOzbiBkZSBpbmZvcm1hY2nDs24gYmlibGlvbcOpdHJpY2EqKi4gVG9kb3MgbG9zIGPDs2RpZ29zIHNlIGVuY3VlbnRyYW4gZGlzcG9uaWJsZSB0YW1iacOpbiBwb3Igc2VwYXJhZG8gZW4gc2NyaXB0cyBkZSBSLg0KDQoNCiMgQ29uc3VsdGEgbWFudWFsIGRlIEFQSXMNCkxhcyBjb25zdWx0YXMgYSBjdWFscXVpZXIgQVBJIHB1ZWRlbiBoYWNlcnNlIGRlIG1hbmVyYSBtYW51YWwgZW4gUi4gUGFyYSBlbGxvLCBsYSBmb3JtYSBtw6FzIGLDoXNpY2EgZXMgdXNhbmRvIGxvcyBwYXF1ZXRlcyBgaHR0cmAgeSBgcmpzb25gLiBFbCBwcmltZXJvIHBlcm1pdGUgaGFjZXIgcGV0aWNpb25lcyBhIHRyYXbDqXMgZGUgSFRUUCAoR0VULCBQQVRDSCwgUE9TVCwgSEVBRCwgUFVUIHkgREVMRVRFKSBkZXNkZSBSIHkgZWwgc2VndW5kbyB0cmFiYWphciBjb24gYXJjaGl2b3MgSlNPTi4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGh0dHIpDQpsaWJyYXJ5KHJqc29uKQ0KYGBgDQoNCg0KVW5hIHZleiBpbmljaWFkYXMgbG9zIGRvcyBwYXF1ZXRlcywgZWwgcHJpbWVyIHBhc28gZXMgY29uc3RydWlyIHVuYSAqcXVlcnkqLCBsYSBjdWFsIGFsbWFjZW5hcmVtb3MgZW4gdW5hIHZhcmlhYmxlIGNvbiBkaWNoYSBkZW5vbWluYWNpw7NuLiBFbiBlc3RlIGNhc28gdmFtb3MgYSBidXNjYXIgdW5hIHJldmlzdGEgZW4gbGEgYmFzZSBkZSBkYXRvcyBkZSBET0FKIHVzYW5kbyBzdSBbQVBJXShodHRwczovL2RvYWoub3JnL2FwaS92MS9kb2NzKS4gUGFyYSBlbGxvLCBzZSB1c2EgZWwgZW5kcG9pbnQgZGUgRE9BSiBwYXJhIGJ1c2NhciByZXZpc3RhcyBjaWVudMOtZmljYXMgKGBodHRwczovL2RvYWoub3JnL2FwaS92MS9zZWFyY2gvam91cm5hbHMvYCkgeSBzZSBhw7FhZGUgbGEgKnF1ZXJ5KiBgaXNzbjoyNTA0LTA1MzdgIHBhcmEgaW5kaWNhciBxdWUgYnVzcXVlIGRpY2hvIElTU04uDQpgYGB7cn0NCnF1ZXJ5IDwtICdodHRwczovL2RvYWoub3JnL2FwaS92MS9zZWFyY2gvam91cm5hbHMvaXNzbjoyNTA0LTA1MzcnDQpgYGANCg0KDQpFbiBzaWd1aWVudGUgbHVnYXIgc2UgcmVhbGl6YSBkaWNoYSBwZXRpY2nDs24gYSBsYSBBUEkgdXNhbmRvIHBhcmEgZWxsbyBsYSBmdW5jacOzbiBgR0VUKClgIGRlbCBwYXF1ZXRlIGBodHRyYC4gRWwgcmVzdWx0YWRvIGxvIGFsbWFjZW5hbW9zIGVuIGxhIHZhcmlhYmxlIGBnZXRkYXRhYC4gVW5hIHZleiBoZWNoYSBsYSBjb25zdWx0YSBvYnRlbmRyZW1vcyB1bmEgcmVzcHVlc3RhLCBzaWVuZG8gZGUgcmVsZXZhbmNpYSBsb3MgbWV0YWRhdG9zIHJlbGF0aXZvcyBhbCAqc3RhdHVzKiB5ICpjb250ZW50LXR5cGUqIHBhcmEgcG9kZXIgY29ub2NlciBjw7NtbyBoYSBpZG8gbGEgY29uc3VsdGEuDQpgYGB7cn0NCmdldGRhdGEgPC0gaHR0cjo6R0VUKHVybCA9IHF1ZXJ5KQ0KZ2V0ZGF0YQ0KYGBgDQoNCg0KRGUgbWFuZXJhIG9wY2lvbmFsIHNlIHB1ZWRlbiBlc3BlY2lmaWNhciBlbiBsYSBmdW5jacOzbiBgR0VUKClgIGNhbXBvcyBjb21vIGxvcyAqaGVhZGVycyogbyAqdXNlcl9hZ2VudCouDQpgYGB7cn0NCmdldGRhdGEgPC0gaHR0cjo6R0VUKHVybCA9IHF1ZXJ5LCAgaHR0cjo6dXNlcl9hZ2VudCgnaHR0cicpKQ0KZ2V0ZGF0YQ0KYGBgDQoNCg0KU2kgbGEgY29uc3VsdGEgaGEgZnVuY2lvbmFkbyBjb3JyZWN0YW1lbnRlLCBlcyBwb3NpYmxlIHJldmlzYXIgY29uIGxhIGZ1bmNpw7NuIGBmcm9tSlNPTigpYCB0b2RvIGVsIEpTT04gcXVlIGRldnVlbHZlIGxhIEFQSSAoZW4gY2FzbyBkZSBxdWUgZXN0ZSBzZWEgZWwgZm9ybWF0byBlbXBsZWFkbykuIFVuYSB2ZXogZXh0cmHDrWRhIGxhIHJlc3B1ZXN0YSBlbiBmb3JtYXRvIEpTT04geSBjb24gY29kaWZpY2FjacOzbiBVVEYtOCwgZXMgcG9zaWJsZSBjb25zdWx0YXIgY2FtcG9zIGNvbmNyZXRvcywgY29tbyBlbCBuw7ptZXJvIGRlIHJlc3VsdGFkb3MgcXVlIGRldnVlbHZlIGxhIEFQSS4NCmBgYHtyfQ0KZ2V0ZGF0YV9qc29uIDwtIHJqc29uOjpmcm9tSlNPTihodHRyOjpjb250ZW50KGdldGRhdGEsIHR5cGU9J3RleHQnLCBlbmNvZGluZyA9ICdVVEYtOCcpKQ0KZ2V0ZGF0YV9qc29uJHRvdGFsDQpgYGANCg0KDQpEZSBpZ3VhbCBtYW5lcmEsIGNvbW8gZW4gZXN0ZSBjYXNvIGNvbmNyZXRvIGhlbW9zIHJlYWxpemFkbyBsYSBiw7pzcXVlZGEgZGUgdW4gSVNTTiBsbyBub3JtYWwgZXMgcXVlIGRldnVlbHZhIHVuIMO6bmljbyByZXN1bHRhZG8uIFBvciBlbGxvIHBvZGVtb3MgZGlyaWdpcm5vcyBhbCBwcmltZXIgcmVzdWx0YWRvLCBxdWUgc2Vyw6EgY29uIGJhc3RhbnRlIHNlZ3VyaWRhZCBsYSByZXZpc3RhIHF1ZSBxdWVyYW1vcywgeSBleHRyYWVyIGRlIGVsbGEgbWV0YWRhdG9zIGRlIGxhIG1pc21hIGNvbW8gZWwgdMOtdHVsbywgc2lzdGVtYSBkZSByZXZpc2nDs24sIHNpIGVzIE9wZW4gQWNjZXNzIHkgbGljZW5jaWEuDQpgYGB7cn0NCmdldGRhdGFfanNvbiRyZXN1bHRzW1sxXV0kYmlianNvbiR0aXRsZQ0KZ2V0ZGF0YV9qc29uJHJlc3VsdHNbWzFdXSRiaWJqc29uJGVkaXRvcmlhbF9yZXZpZXckcHJvY2Vzcw0KZ2V0ZGF0YV9qc29uJHJlc3VsdHNbWzFdXSRiaWJqc29uJGxpY2Vuc2VbWzFdXSRvcGVuX2FjY2Vzcw0KZ2V0ZGF0YV9qc29uJHJlc3VsdHNbWzFdXSRiaWJqc29uJGxpY2Vuc2VbWzFdXSR0eXBlDQpgYGANCg0KDQpEZSBpZ3VhbCBtYW5lcmEsIERPQUogY3VlbnRhIGNvbiB1bmEgQVBJIHBhcmEgYnVzY2FyIGFydMOtY3Vsb3MgY2llbnTDrWZpY29zLiBTdSBmdW5jaW9uYW1pZW50byBubyBkaWZpZXJlIGRlbCBhbnRlcmlvci4gVXNhbW9zIG90cm8gZW5kcG9pbnQgeSBlc3RhYmxlY2Vtb3MgdW5hICpxdWVyeSosIGVuIGVzdGUgY2FzbyBlbCBET0kgZGUgdW4gYXJ0w61jdWxvIGNpZW50w61maWNvLCB5IHRyYXMgZWxsbyByZWN1cGVyYW1vcyBlbCBKU09OIHkgZGVsIHByaW1lciByZXN1bHRhZG8gZXh0cmFlbW9zIHZhcmlvcyBtZXRhZGF0b3MuDQpgYGB7cn0NCnF1ZXJ5IDwtICdodHRwczovL2RvYWoub3JnL2FwaS92MS9zZWFyY2gvYXJ0aWNsZXMvZG9pOjEwLjMzODkvZnBzeWcuMjAxMy4wMDQ3OScNCmdldGRhdGEgPC0gaHR0cjo6R0VUKHVybCA9IHF1ZXJ5KQ0KZ2V0ZGF0YV9qc29uIDwtIHJqc29uOjpmcm9tSlNPTihodHRyOjpjb250ZW50KGdldGRhdGEsIHR5cGU9J3RleHQnLCBlbmNvZGluZyA9ICdVVEYtOCcpKQ0KZ2V0ZGF0YV9qc29uJHJlc3VsdHNbWzFdXSRiaWJqc29uJHRpdGxlDQpnZXRkYXRhX2pzb24kcmVzdWx0c1tbMV1dJGJpYmpzb24kam91cm5hbCR0aXRsZQ0KYGBgIA0KDQoNCiMjIEF1dG9tYXRpemFjacOzbiBkZWwgcHJvY2Vzbw0KQ29tbyBsYSBjb25zdWx0YSBkZSBjYWRhIHJlZ2lzdHJvIHNlIGhhY2UgZGUgbWFuZXJhIHNlcGFyYWRhLCBlcyBwb3NpYmxlIGF1dG9tYXRpemFyIGVzdGUgcHJvY2VzbyBwYXJhIHJlYWxpemFyIG3Dumx0aXBsZXMgY29uc3VsdGFzLiBQYXJhIGVsbG8sIHNlIHB1ZWRlIGdlbmVyYXIgdW4gYnVjbGUgYGZvcmAgcXVlIHJlYWxpY2UgdGFudGFzIGNvbnN1bHRhcyBjb21vIElTU05zIHF1ZXJhbW9zIGNvbnN1bHRhci4NCmBgYHtyfQ0KZm9yKGlzc24gaW4gYygnMjUwNC0wNTM3JywgJzI2MjQtOTg5OCcsICcyMjk3LTI2NjgnKSl7DQogIA0KICBxdWVyeSA8LSBwYXN0ZTAoJ2h0dHBzOi8vZG9hai5vcmcvYXBpL3YxL3NlYXJjaC9qb3VybmFscy9pc3NuOicsIGlzc24pDQogIGdldGRhdGEgPC0gaHR0cjo6R0VUKHVybCA9IHF1ZXJ5KQ0KICBnZXRkYXRhX2pzb24gPC0gcmpzb246OmZyb21KU09OKGh0dHI6OmNvbnRlbnQoZ2V0ZGF0YSwgdHlwZT0ndGV4dCcsIGVuY29kaW5nID0gJ1VURi04JykpDQogIA0KICBqX3RpdGxlIDwtIGdldGRhdGFfanNvbiRyZXN1bHRzW1sxXV0kYmlianNvbiR0aXRsZQ0KICBqX2VyIDwtIGdldGRhdGFfanNvbiRyZXN1bHRzW1sxXV0kYmlianNvbiRlZGl0b3JpYWxfcmV2aWV3JHByb2Nlc3MNCiAgDQogIHByaW50KGMoaXNzbiwgal90aXRsZSwgal9lcikpDQp9DQpgYGANCg0KDQpEZSBpZ3VhbCBtYW5lcmEsIHNlIHB1ZWRlbiBjcmVhciBmdW5jaW9uZXMgY29uIGVsbG8gcGFyYSwgcG9yIGVqZW1wbG8sIGFsbWFjZW5hciBlbiB1biBgZGF0YS5mcmFtZWAgbG9zIGNhbXBvcyBxdWUgcXVlcmFtb3MgcmVjdXBlcmFyIGRlIGNhZGEgY29uc3VsdGEuDQpgYGB7cn0NCmRvYWpfZGF0YSA8LSBmdW5jdGlvbihkb2lzKXsNCiAgZGYgPC0gZGF0YS5mcmFtZShpc3NuID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgdGl0bGUgPSBjaGFyYWN0ZXIoKSwNCiAgICAgICAgICAgICAgICAgICBlZGl0b3JpYWxfcmV2aWV3ID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KICANCiAgZm9yKGlzc24gaW4gZG9pcyl7DQogICAgcXVlcnkgPC0gcGFzdGUwKCdodHRwczovL2RvYWoub3JnL2FwaS92MS9zZWFyY2gvam91cm5hbHMvaXNzbjonLCBpc3NuKQ0KICAgIGdldGRhdGEgPC0gaHR0cjo6R0VUKHVybCA9IHF1ZXJ5KQ0KICAgIGdldGRhdGFfanNvbiA8LSByanNvbjo6ZnJvbUpTT04oaHR0cjo6Y29udGVudChnZXRkYXRhLCB0eXBlPSd0ZXh0JywgZW5jb2RpbmcgPSAnVVRGLTgnKSkNCiAgICANCiAgICBqX3RpdGxlIDwtIGdldGRhdGFfanNvbiRyZXN1bHRzW1sxXV0kYmlianNvbiR0aXRsZQ0KICAgIGpfZXIgPC0gZ2V0ZGF0YV9qc29uJHJlc3VsdHNbWzFdXSRiaWJqc29uJGVkaXRvcmlhbF9yZXZpZXckcHJvY2Vzcw0KICAgIA0KICAgIA0KICAgIGRmIDwtIHJiaW5kLmRhdGEuZnJhbWUoZGYsIGRhdGEuZnJhbWUoaXNzbj1pc3NuLCB0aXRsZT1qX3RpdGxlLCBlZGl0b3JpYWxfcmV2aWV3PWpfZXIpKQ0KICB9DQogIA0KICByZXR1cm4oZGYpDQp9DQpgYGANCg0KDQpBc8OtLCBjb24gdW5hIHNpbXBsZSBmdW5jacOzbiBwb2RlbW9zIHJlcGV0aXIgbXVjaGFzIGNvbnN1bHRhcyB5IGFsbWFjZW5hciBsb3MgbWV0YWRhdG9zLg0KYGBge3J9DQpkZiA8LSBkb2FqX2RhdGEoYygnMjUwNC0wNTM3JywgJzI2MjQtOTg5OCcsICcyMjk3LTI2NjgnKSkNCmRmDQpgYGANCg0KDQpObyBvYnN0YW50ZSwgZXMgbmVjZXNhcmlvIHRlbmVyIGVuIGN1ZW50YSBlbiBlc3RvcyBjYXNvcyBsb3MgcG9zaWJsZXMgZXJyb3JlcyBxdWUgcHVlZGVuIGFwYXJlY2VyIGR1cmFudGUgdG9kbyBlbCBwcm9jZXNvLiBQb3IgZWplbXBsbywgY3VhbmRvIGxhIEFQSSBubyBlbmN1ZW50cmUgcmVzdWx0YWRvcyBwYXJhIG51ZXN0cmEgY29uc3VsdGEgeSBxdWllcmEgZXh0cmFlciBtZXRhZGF0b3MgZGUgYWjDrS4NCmBgYHtyIGVycm9yPVRSVUV9DQpkb2FqX2RhdGEoYygnaXNzbicsICcyNTA0LTA1MzcnLCAnMjYyNC05ODk4JywgJzIyOTctMjY2OCcpKQ0KYGBgDQoNCg0KRW4gZXN0ZSBjYXNvIHNlIHB1ZWRlIHNvbHVjaW9uYXIgcsOhcGlkYW1lbnRlIG1vZGlmaWNhbmRvIGxhIGFudGVyaW9yIGZ1bmNpb24gcGFyYSBxdWUgY29tcHJ1ZWJlIHRyYXMgbGEgcGV0aWNpw7NuIHF1ZSBsYSByZXNwdWVzdGEgZXMgY29ycmVjdGEgeSBxdWUgYWRlbcOhcyBub3MgaGEgZGV2dWVsdG8gYWwgbWVub3MgdW4gcmVzdWx0YWRvLiBBdW5xdWUgZXN0ZSBubyBzZXLDoSBlbCDDum5pY28gZXJyb3IgcXVlIHBvZHJlbW9zIG9idGVuZXIgeSBzZSByZXF1aWVyZSBtdWNoYSBtw6FzIGF0ZW5jacOzbiBlbiBlc3RlIGFwYXJ0YWRvLg0KYGBge3J9DQpkb2FqX2RhdGEgPC0gZnVuY3Rpb24oZG9pcyl7DQogIGRmIDwtIGRhdGEuZnJhbWUoaXNzbiA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgIHRpdGxlID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgZWRpdG9yaWFsX3JldmlldyA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCiAgDQogIGZvcihpc3NuIGluIGRvaXMpew0KICAgIHF1ZXJ5IDwtIHBhc3RlMCgnaHR0cHM6Ly9kb2FqLm9yZy9hcGkvdjEvc2VhcmNoL2pvdXJuYWxzL2lzc246JywgaXNzbikNCiAgICBnZXRkYXRhIDwtIGh0dHI6OkdFVCh1cmwgPSBxdWVyeSkNCiAgICBnZXRkYXRhX2pzb24gPC0gcmpzb246OmZyb21KU09OKGh0dHI6OmNvbnRlbnQoZ2V0ZGF0YSwgdHlwZT0ndGV4dCcsIGVuY29kaW5nID0gJ1VURi04JykpDQogICAgDQogICAgaWYoc3RhdHVzX2NvZGUoZ2V0ZGF0YSkgPT0gMjAwICYgZ2V0ZGF0YSRoZWFkZXJzJGB4LXRvdGFsLWNvdW50YCA+IDApIHsNCiAgICAgIGpfdGl0bGUgPC0gZ2V0ZGF0YV9qc29uJHJlc3VsdHNbWzFdXSRiaWJqc29uJHRpdGxlDQogICAgICBqX2VyIDwtIGdldGRhdGFfanNvbiRyZXN1bHRzW1sxXV0kYmlianNvbiRlZGl0b3JpYWxfcmV2aWV3JHByb2Nlc3MNCiAgICAgIA0KICAgICAgZGYgPC0gcmJpbmQuZGF0YS5mcmFtZShkZiwgZGF0YS5mcmFtZShpc3NuPWlzc24sIHRpdGxlPWpfdGl0bGUsIGVkaXRvcmlhbF9yZXZpZXc9al9lcikpDQogICAgfQ0KICB9DQogIA0KICByZXR1cm4oZGYpDQp9DQoNCmRmIDwtIGRvYWpfZGF0YShjKCdpc3NuJywgJzI1MDQtMDUzNycsICcyNjI0LTk4OTgnLCAnMjI5Ny0yNjY4JykpDQpkZg0KYGBgDQoNCg0KIyBFbCB1c28gZGUgcGFxdWV0ZXMNCiMjIERPQUoNClBlc2UgYSBlbGxvLiBubyBlcyBuZWNlc2FyaW8gcHJvZnVuZGl6YXIgdGFudG8gbmkgdGVuZXIgbXVjaG9zIGNvbm9jaW1pZW50b3MgZW4gcHJvZ3JhbWFjacOzbi4gTXVjaGFzIGRlIGxhcyBBUElzIGN1ZW50YW4gY29uIHBhcXVldGVzIHBhcmEgbXVjaG9zIGxlbmd1YWplcyBkZSBwcm9ncmFtYWNpw7NuLCBmYWNpbGl0YW5kbyBjb24gZWxsbyBsYXMgY29uc3VsdGFzLiBFbiBlbCBjYXNvIGRlIERPQUogZXhpc3RlIGVsIHBhcXVldGUgW2BqYW9kYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2phb2QvamFvZC5wZGYpLg0KYGBge3IgcmVzdWx0cz0naGlkZSd9DQojaW5zdGFsbC5wYWNrYWdlcygnamFvZCcpDQpsaWJyYXJ5KGphb2QpDQpgYGANCg0KDQpDYWRhIHBhcXVldGUgdGllbmUgdW4gZnVuY2lvbmFtaWVudG8gZGlmZXJlbnRlLCBlcyBwb3IgZWxsbyBxdWUgYW50ZXMgZGUgdXNhcmxvIGVzIG5lY2VzYXJpbyByZXZpc2FyIHN1IGRvY3VtZW50YWNpw7NuLg0KYGBge3J9DQpoZWxwKGphb2QpDQpgYGANCg0KDQpQYXJhIGVsIGNhc28gZGUgYGphb2RgLCBsYSBiw7pzcXVlZGEgZGUgcmVzdmlzdGFzIHkgZXh0cmFjY2nDs24gZGUgbWV0YWRhdG9zIHNlIHJlc3VtZSBtdWNobyBncmFjaWFzIGEgbGEgZnVuY2nDs24gYGphb2Rfam91cm5hbF9zZWFyY2goKWA6DQpgYGB7ciB3YXJuaW5nPVRSVUV9DQpyZXMgPC0gamFvZDo6amFvZF9qb3VybmFsX3NlYXJjaCgnaXNzbjoyNTA0LTA1MzcnKQ0KcmVzJHJlc3VsdHMkYmlianNvbi50aXRsZQ0KYGBgDQoNCg0KR3JhY2lhcyBhIGVsbG8sIGVuIGVzdGUgY2FzbyBub3MgcGVybWl0ZSBzaW1wbGlmaWNhciBtdWNobyBlbCB0cmFiYWpvLiBQb3IgZWplbXBsbywgcG9kZW1vcyBjcmVhciBlbCBtaXNtbyBidWNsZSBkZSBjb25zdWx0YXMgcXVlIGFudGVzLCBwZXJvIGVtcGxlYW5kbyBtZW5vcyBjw7NkaWdvIHkgZ2FyYW50aXphbmRvIHVuIG1lam9yIGZ1bmNpb25hbWllbnRvLg0KYGBge3J9DQpkb2FqX2RhdGEgPC0gZnVuY3Rpb24oZG9pcyl7DQogIGRmIDwtIGRhdGEuZnJhbWUoaXNzbiA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgIHRpdGxlID0gY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgZWRpdG9yaWFsX3JldmlldyA9IGNoYXJhY3RlcigpLA0KICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCiAgDQogIGZvcihpc3NuIGluIGRvaXMpew0KICAgIGdldGRhdGEgPC0gamFvZDo6amFvZF9qb3VybmFsX3NlYXJjaChwYXN0ZTAoJ2lzc246JywgaXNzbikpDQogICAgDQogICAgaWYoZ2V0ZGF0YSR0b3RhbCA+IDApew0KICAgICAgZGYgPC0gcmJpbmQuZGF0YS5mcmFtZShkZiwgZGF0YS5mcmFtZShpc3NuPWlzc24sIHRpdGxlPWdldGRhdGEkcmVzdWx0cyRiaWJqc29uLnRpdGxlLCBlZGl0b3JpYWxfcmV2aWV3PWdldGRhdGEkcmVzdWx0cyRiaWJqc29uLmVkaXRvcmlhbF9yZXZpZXcucHJvY2VzcykpDQogICAgfQ0KICB9DQogIA0KICByZXR1cm4oZGYpDQp9DQoNCmRmX2RvIDwtIGRvYWpfZGF0YShjKCdpc3NuJywgJzI1MDQtMDUzNycsICcyNjI0LTk4OTgnLCAnMjI5Ny0yNjY4JykpDQpkZl9kbw0KYGBgDQoNCg0KIyMgQ3Jvc3NyZWYNCkNyb3NzcmVmIGN1ZW50YSBjb24gdW4gcGFxdWV0ZSBlbiBSIGxsYW1hZG8gW2ByY3Jvc3NyZWZgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcmNyb3NzcmVmL3Jjcm9zc3JlZi5wZGYpIGNvbiBlbCBxdWUgc2UgcHVlZGVuIHJlYWxpemFyIGNvbnN1bHRhcyBkZSBtYW5lcmEgbXV5IGVmaWNpZW50ZSBhIHN1IEFQSS4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojaW5zdGFsbC5wYWNrYWdlcygncmNyb3NzcmVmJykNCmxpYnJhcnkocmNyb3NzcmVmKQ0KYGBgDQoNCg0KRGVudHJvIGRlIGxhcyBmdW5jaW9uZXMgcXVlIG9mcmVjZSBlc3RlIHBhcXVldGUgaGF5IHZhcmlhcyBkZXN0YWNhZGFzLiBFbiBwcmltZXIgbHVnYXIgZXN0w6EgYGNyX2NpdGF0aW9uX2NvdW50KClgLCBsYSBjdWFsIHJlY3VwZXJhIGVsIG7Dum1lcm8gZGUgY2l0YXMgZGUgdW4gdHJhYmFqbywgc2UgdHJhdGEgZGUgdW4gY29udGVvIHByb3BpbyBkZSBDcm9zc3JlZiBxdWUgbm8gdGllbmUgcXVlIGNvaW5jaWRpciBjb24gZWwgZGUgb3Ryb3Mgc2VydmljaW9zLg0KYGBge3J9DQpkZl9jciA8LSByY3Jvc3NyZWY6OmNyX2NpdGF0aW9uX2NvdW50KGRvaT0nMTAuMTAwMi9hc2kuMjMzMDknKQ0KZGZfY3INCmBgYA0KDQoNCkNvbW8gdmVudGFqYSwgZXMgcG9zaWJsZSByZWFsaXphciBkZSBtYW5lcmEgc2ltdWx0w6FuZWEgdmFyaWFzIGNvbnN1bHRhcyBkZSBET0kuDQpgYGB7cn0NCmRvaXMgPC0gYygnMTAuMTAwMi9hc2kuMjMzMDknLCAnMTAuMTAxNi9qLmpvaS4yMDA3LjAyLjAwMScsICcxMC4xMDA3L3MxMTE5Mi0wMTQtMTI2NC0wJykNCmRmX2NyIDwtIHJjcm9zc3JlZjo6Y3JfY2l0YXRpb25fY291bnQoZG9pID0gZG9pcykNCmRmX2NyDQpgYGANCg0KDQpPdHJhIGZ1bmNpw7NuIGludGVyZXNhbnRlIGVzIGBjcl9qb3VybmFscygpYCwgbGEgY3VhbCBwZXJtaXRlIGJ1c2NhciB5IG9idGVuZXIgbnVtZXJvc2EgaW5mb3JtYWNpw7NuIGRlIHJldmlzdGFzIGNpZW50w61maWNhcy4gUG9yIHVuIGxhZG8sIGVzIHBvc2libGUgYnVzY2FyIHJldmlzdGFzIHVzYW5kbyB1bm8gbyB2YXJpb3MgdMOpcm1pbm9zLiBDb24gZXN0YSBmdW5jacOzbiBsb3MgbWV0YWRhdG9zIHJlY3VwZXJhZG9zIHZpZW5lbiBkYWRvcyBlbiBmb3JtYSBkZSBsaXN0YSwgc2llbmRvIGVsIGVsZW1lbnRvIGBkYXRhYCBlbCBxdWUgbG9zIGluY2x1eWUuDQpgYGB7cn0NCmRmX2NyIDwtIHJjcm9zc3JlZjo6Y3Jfam91cm5hbHMocXVlcnkgPSAnbGlicmFyeSBhbmQgaW5mb3JtYXRpb24gc2NpZW5jZScpDQpkZl9jciRkYXRhDQpgYGANCg0KDQpEZSBpZ3VhbCBtYW5lcmEsIGVzIHBvc2libGUgYnVzY2FyIGluZm9ybWFjacOzbiBzb2JyZSB1bmEgbyB2YXJpYXMgcmV2aXN0YXMgY29uY3JldGFzIHVzYW5kbyBzdSBJU1NOLg0KYGBge3J9DQpkZl9jciA8LSByY3Jvc3NyZWY6OmNyX2pvdXJuYWxzKGlzc24gPSAnMTY5OS0yNDA3JykNCmRmX2NyJGRhdGENCg0KaXNzbnMgPC0gYygnMTY5OS0yNDA3JywgJzAxNjUtNTUxNScpDQpkZl9jciA8LSByY3Jvc3NyZWY6OmNyX2pvdXJuYWxzKGlzc24gPSBpc3NucykNCmRmX2NyJGRhdGENCmBgYA0KDQoNCkFkZW3DoXMgZGUgbGEgaW5mb3JtYWNpw7NuIHNvYnJlIGxhIHJldmlzdGEsIGVzIHBvc2libGUgcmVjdXBlcmFyIHRyYWJham9zIGNpZW50w61maWNvcyBwdWJsaWNhZG9zIGVuIGVsbGEgdXNhbmRvIGVsIHBhcsOhbWV0cm8gYHdvcmtzID0gVFJVRWAuIFBvciBkZWZlY3RvIHNvbiAyMCBsb3MgdHJhYmFqb3MgY2llbnTDrWZpY29zIG8gcmV2aXN0YXMgcXVlIHJlY3VwZXJhLCBwZXJvIG1lZGlhbnRlIGVsIHBhcsOhbWV0cm8gYGxpbWl0YCBzZSBwdWVkZSBhbHRlcmFyIGRpY2hvIHJlc3VsdGFkbywgYXVucXVlIGVsIG3DoXhpbW8gZXMgMTAwMC4NCmBgYHtyfQ0KZGZfY3IgPC0gcmNyb3NzcmVmOjpjcl9qb3VybmFscyhpc3NuID0gJzE2OTktMjQwNycsIHdvcmtzID0gVFJVRSwgbGltaXQgPSA1MCkNCmRmX2NyJGRhdGENCmBgYA0KDQoNCkxvcyBhcnTDrWN1bG9zIHJlY3VwZXJhZG9zIGRlIGxhIHJldmlzdGEgY29uc3VsdGFkYSBwdWVkZW4gc2VyIGFkZW3DoXMgb3JkZW5hZG9zIG1lZGlhbnRlIGxvcyBwYXLDoW1ldHJvcyBgc29ydGAgeSBgb3JkZXJgLiBQb3IgZWplbXBsbywgb3JkZW5hZG9zIHBvciBlbCBuw7ptZXJvIGRlIGNpdGFzIHJlY2liaWRhcyBlbiBvcmRlbiBkZXNjZW5kaWVudGUuDQpgYGB7cn0NCmRmX2NyIDwtIHJjcm9zc3JlZjo6Y3Jfam91cm5hbHMoaXNzbiA9ICcxNjk5LTI0MDcnLCB3b3JrcyA9IFRSVUUsIHNvcnQgPSAnaXMtcmVmZXJlbmNlZC1ieS1jb3VudCcsIG9yZGVyID0gJ2Rlc2MnKQ0KZGZfY3IkZGF0YQ0KYGBgDQoNCg0KRW4gcmVsYWNpw7NuIGNvbiBsYSByZWN1cGVyYWNpw7NuIGRlIHRyYWJham9zIGNpZW50w61maWNvcyBkZSB1bmEgcmV2aXN0YSwgdGFtYmnDqW4gZXMgcG9zaWJsZSBlc3RhYmxlY2VyIHN1YmLDunNxdWVkYXMgY29uIGVsIHBhcsOhbWV0cm8gYGZscWAuIFBvciBlamVtcGxvLCBhcnTDrWN1bG9zIGVuIGxvcyBxdWUgZWwgbm9tYnJlIGRlbCBhdXRvciBzZWEgVG9ycmVzLVNhbGluYXMuDQpgYGB7cn0NCmRmX2NyIDwtIHJjcm9zc3JlZjo6Y3Jfam91cm5hbHMoaXNzbiA9ICcxNjk5LTI0MDcnLCB3b3JrcyA9IFRSVUUsIGZscSA9IGMoYHF1ZXJ5LmF1dGhvcmA9J1RvcnJlcy1TYWxpbmFzJykpDQpkZl9jciRkYXRhDQpgYGANCg0KDQpEZSBpZ3VhbCBtYW5lcmEgcXVlIGNvbiBsYXMgcmV2aXN0YXMsIGV4aXN0ZSB1bmEgZnVuY2nDs24gcGFyYSByZWFsaXphciBjb25zdWx0YXMgZGUgYXJ0w61jdWxvcyBjaWVudMOtZmljb3MuIENvbiBgY3Jfd29ya3MoKWAgcG9kZW1vcyBoYWNlciBkaWNoYXMgY29uc3VsdGFzIHkgc3UgZnVuY2lvbmFtaWVudG8gZXMgbXV5IHNpbWlsYXIgYWwgZGUgbGFzIHJldmlzdGFzLCBjb21wYXJ0aWVuZG8gbXVjaG9zIGRlIHN1cyBwYXLDoW1ldHJvcy4gUG9kZW1vcyBidXNjYXIgYXPDrSBhcnTDrWN1bG9zIG1lZGlhbnRlIHVuIHTDqXJtaW5vIHkgb3JkZW5hciBsb3MgcmVzdXRsYWRvcyBwb3IgY2l0YXMuDQpgYGB7cn0NCmRmX2NyIDwtIHJjcm9zc3JlZjo6Y3Jfd29ya3MocXVlcnkgPSAnbGlicmFyeScsIHNvcnQgPSAnaXMtcmVmZXJlbmNlZC1ieS1jb3VudCcsIG9yZGVyID0gJ2Rlc2MnKQ0KZGZfY3IkZGF0YQ0KYGBgDQoNCg0KVGFtYmnDqW4gZXMgcG9zaWJsZSBidXNjYXIgZGlyZWN0YW1lbnRlIHZhcmlvcyBET0kuIEVuIGVzdGUgY2FzbywgcG9kZW1vcyBpbmNsdXNvIGluZGljYXIgY29uIGVsIHBhcsOhbWV0cm8gYC5wcm9ncmVzcyA9ICd0ZXh0J2AgcXVlIG5vcyBtdWVzdHJlIGVsIHByb2dyZXNvIGRlIGRpY2hhIGNvbnN1bHRhLg0KYGBge3J9DQpkb2lzIDwtIGMoJzEwLjEwMDIvYXNpLjIzMzA5JywgJzEwLjEwMTYvai5qb2kuMjAwNy4wMi4wMDEnLCAnMTAuMTAwNy9zMTExOTItMDE0LTEyNjQtMCcpDQpkZl9jciA8LSByY3Jvc3NyZWY6OmNyX3dvcmtzKGRvaXMgPSBkb2lzKQ0KZGZfY3IkZGF0YQ0KYGBgDQoNCg0KQ29tbyBwYXNhIGNvbiBsYXMgcmV2aXN0YXMsIHRhbWJpw6luIGV4aXN0ZSB1biBsaW1pdGUgZGUgMTAwMCByZXN1dGFkb3MuIE5vIG9ic3RhbnRlLCBhcXXDrSBwb2RlbW9zIGF1bWVudGFyIGVzZSBsaW1pdGUgdXNhbmRvIHVuICpjdXJzb3IqLiBQYXJhIGVsbG8gZmlqYW1vcyB1biBtw6F4aW1vIGVuIGBjdXJzb3JfbWF4YCB5IGBsaW1pdGAuIEVsIHByaW1lcm8gZXMgZWwgbsO6bWVybyBkZSBwdWJsaWNhY2lvbmVzIHRvdGFsIHF1ZSBxdWVyZW1vcyByZWN1cGVyYXIgY29tbyBtw6F4aW1vLCBtaWVudHJhcyBxdWUgZWwgc2VndW5kbyBoYWNlIHJlZmVyZW5jaWEgYSBsb3MgKmNodW5rcyogZGUgcGV0aWNpb25lcywgZXMgZGVjaXIgbGFzIHN1YnBldGljaW9uZXMgcXVlIHNlIHJlYWxpemFuIGhhc3RhIGFsY2FuemFyIGVsIG3DoXhpbW8sIGRlIG1hbmVyYSBxdWUgZXN0ZSB2YWxvciBubyBwdWVkZSBzZXIgbWF5b3IuIEVzdGEgY29uc3VsdGEgdGFtYmnDqW4gYWRtaXRlIGVsIHVzbyBkZWwgcGFyw6FtZXRybyBgLnByb2dyZXNzYCBhdW5xdWUgY29uIHVuIHZhbG9yIGRpZmVyZW50ZSBhbCBhbnRlcmlvci4gDQpgYGB7cn0NCmRmX2NyIDwtIHJjcm9zc3JlZjo6Y3Jfd29ya3MocXVlcnkgPSAndHdpdHRlcicsIGN1cnNvciA9ICcqJywgY3Vyc29yX21heCA9IDMwMCwgbGltaXQgPSAyMDApDQpkZl9jciRkYXRhDQpgYGANCg0KDQpEZSBtYW5lcmEgcGFyYWxlbGEgYSBsYSBjb25zdWx0YSwgZXMgcG9zaWJsZSBsbGV2YXIgYSBjYWJvIHVuYSBiw7pzcXVlZGEgcG9yIGZhY2V0YXMgY29uIGVsIHBhcsOhbWV0cm8gYGZhY2V0YC4gQXVucXVlIHNvbG8gZXN0w6EgZGlzcG9uaWJsZSBwYXJhIGRldGVybWluYWRvcyBjYW1wb3MuIEVzIHByZWZlcmlibGUgZW1wbGVhcmxhIHNpbiBxdWUgZGV2dWVsdmEgbG9zIG1ldGFkYXRvcyBkZSBsYXMgcHVibGljYWNpb25lcyBwYXJhIHF1ZSBzZWEgbcOhcyByw6FwaWRhIHkgcGVybWl0YSBvYnRlbmVyIGRlIG1hbmVyYSBzZW5jaWxsYSB1biB2aXN0YXpvIGdlbmVyYWwgYSBudWVzdHJhIGNvbnN1bHRhLg0KYGBge3J9DQpkZl9jciA8LSByY3Jvc3NyZWY6OmNyX3dvcmtzKHF1ZXJ5ID0gJ1R3aXR0ZXInLCBmYWNldCA9ICdwdWJsaXNoZXItbmFtZToqJywgbGltaXQgPSAwKQ0KZGZfY3IkZmFjZXRzJGBwdWJsaXNoZXItbmFtZWANCmBgYA0KDQoNCiMjIFdlYiBvZiBTY2llbmNlDQpXZWIgb2YgU2NpZW5jZSBwdWVkZSBzZXIgY29uc3VsdGFkYSBhIHRyYXbDqXMgZGVsIHBhcXVldGUgW2B3b3NyYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvc3Ivd29zci5wZGYpLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCd3b3NyJykNCmxpYnJhcnkod29zcikNCmBgYA0KDQoNCg0KUGFyYSB1c2FybGEgZXNlIG5lY2VzYXJpbyBpbnRyb2R1Y2lyIHVuYXMgY3JlZGVuY2lhbGVzIG1lZGlhbnRlIGxhIGZ1bmNpw7NuIGBhdXRoKClgLiBIYXkgZG9zIHBvc2liaWxpZGFkZXMgcGFyYSBlbGxvOiB1c2FyIHVuIHVzdWFyaW8geSBjb250cmFzZcOxYSBvIGRlamFyIGFtYm9zIGNhbXBvcyBjb21vIGBOVUxMYCB5IGVzdGFibGVjZXIgY29uZXhpw7NuIFZQTiBjb24gdW5hIGluc3RpdHVjacOzbiBzdXNjcml0YSBhIGVsbGEuIEVzdGUgw7psdGltbyBlcyBudWVzdHJvIGNhc28uDQpgYGB7cn0NCnNpZCA8LSB3b3NyOjphdXRoKHVzZXJuYW1lID0gTlVMTCwgcGFzc3dvcmQgPSBOVUxMKQ0KYGBgDQoNCg0KTm8gc29uIG11Y2hhcyBsYXMgZnVuY2lvbmFsaWRhZGVzIHF1ZSBvZnJlY2UgZXN0ZSBwYXF1ZXRlLiBTZSBwdWVkZSwgcG9yIGVqZW1wbG8sIHJlYWxpemFyIGNvbiBgcXVlcnlfd29zKClgIHVuYSBjb25zdWx0YSB5IHZlciBlbCBuw7ptZXJvIGRlIHJlc3VsdGFkb3MgcXVlIGhheSBwYXJhIGVsbGEuDQpgYGB7cn0NCndvc3I6OnF1ZXJ5X3dvcygnVFMgPSBzY2llbnRvbWV0cmljcycsIHNpZCA9IHNpZCkNCmBgYA0KDQoNCkFzaW1pc21vLCBjb24gZWwgcGFyw6FtZXRybyBgZWRpdGlvbnNgIHNlIHB1ZWRlbiBhanVzdGFyIGxhcyBlZGljaW9uZXMgYSBjb25zdWx0YXIuDQpgYGB7cn0NCndvc3I6OnF1ZXJ5X3dvcygnVFMgPSBzY2llbnRvbWV0cmljcycsIHNpZCA9IHNpZCwgZWRpdGlvbnMgPSBjKCdTQ0knLCAnU1NDSScpKQ0KYGBgDQoNCg0KRXMgcG9zaWJsZSBkZXNjYXJnYXIgZGljaG9zIHJlZ2lzdHJvcywgZXMgcG9yIGVsbG8gcXVlIGVzIG1lam9yIHJlYWxpemFyIGVuIHByaW1lciBsdWdhciBsYSBjb25zdWx0YSBwYXJhIGNvbm9jZXIgZWwgYWxjYW5jZS4gUGFyYSBlbGxvIGhheSBxdWUgdXNhciBsYSBmdW5jacOzbiBgcHVsbF93b3MoKWAuDQpgYGB7ciByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRmX3dzIDwtIHdvc3I6OnB1bGxfd29zKCdUUyA9IHNjaWVudG9tZXRyaWNzJywgc2lkID0gc2lkKQ0KYGBgDQoNCg0KR3JhY2lhcyBhIGVzdGEgZnVuY2nDs24gcG9kZW1vcyBkZXNjYXJnYXIgbcOhcyBkZSA1MDAgcmVnaXN0cm9zIGRlIHVuYSB2ZXosIGF1bnF1ZSBsb3MgZGF0b3MgZXN0w6FuIHN1YmRpdmlkaWRvcyBlbiB1bmEgbGlzdGEgZW4gYmFzZSBhIGNhbXBvIGNvbW8gbGEgcmV2aXN0YSwgaW5mb3JtYWNpw7NuIGRlIGF1dG9yLi4uDQpgYGB7cn0NCmRmX3dzJHB1YmxpY2F0aW9uDQpgYGANCg0KDQojIyBTY29wdXMNCkVuIGVsIGNhc28gZGUgU2NvcHVzLCBlcyBwb3NpYmxlIHJlY3VwZXJhciBpbmZvcm1hY2nDs24gZGUgc3UgYmFzZSBkZSBkYXRvcyBhIHRyYXbDqXMgZGVsIHBhcXVldGUgW2Byc2NvcHVzYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3JzY29wdXMvcnNjb3B1cy5wZGYpLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCdyc2NvcHVzJykNCmxpYnJhcnkocnNjb3B1cykNCmBgYA0KDQoNCkVuIGVzdGUgY2FzbywgZXMgbmVjZXNhcmlvIGRpc3BvbmVyIGRlIHVuYSBjbGF2ZSBkZSBhY2Nlc28gYSBsYSBBUEkgcGFyYSBoYWNlciB1c28gZGUgdG9kYXMgc3VzIGZ1bmNpb25hbGlkYWRlcy4gUGFyYSBlbGxvIGVzIG5lY2VzYXJpbyByZWdpc3RyYXJzZSB5IHNvbGljaXRhcmxvIGEgdHJhdsOpcyBkZSBzdSBbcMOhZ2luYSB3ZWJdKGh0dHBzOi8vZGV2LmVsc2V2aWVyLmNvbS8pLiBFeGlzdGVuIGRvcyBvcGNpb25lcyBwYXJhIGludHJvZHVjaXIgbGEgY2xhdmUuIEVuIHByaW1lciBsdWdhciBpbnRyb2R1Y2nDqW5kb2xhIGNvbW8gdW5hIHBhcsOhbWV0cm8gZW4gY2FkYSB1bmEgZGUgbGFzIGZ1bmNpb25lcyAoYGdldF9hcGlfa2V5KClgKSBvIGVzdGFibGVjacOpbmRvbGEgZGUgYmFzZSBlbiBsYSBzZXNpw7NuIChgc2V0X2FwaV9rZXkoKWApLg0KYGBge3IgZXZhbD1GQUxTRX0NCmtleSA8LSByc2NvcHVzOjpnZXRfYXBpX2tleShhcGlfa2V5ID0gJycpDQpyc2NvcHVzOjpzZXRfYXBpX2tleShhcGlfa2V5ID0gJycpDQpgYGANCg0KYGBge3IgaW5jbHVkZSA9IEZBTFNFfQ0KcnNjb3B1czo6c2V0X2FwaV9rZXkoYXBpX2tleSA9ICc5ZWY1YjQ5MWMyZjNmN2ViOGJkZmFiODkwMzljOWU0OScpDQpgYGANCg0KDQpFcyBwb3NpYmxlIGNvbXByb2JhciBzaSBsYSBzZXNpw7NuIHRpZW5lIGVzdGFibGVjaWRhIGRpY2hhIGNsYXZlIGNvbiBsYSBmdW5jacOzbiBgaGF2ZV9hcGlfa2V5KClgLg0KYGBge3J9DQpyc2NvcHVzOjpoYXZlX2FwaV9rZXkoKQ0KYGBgDQoNCg0KRW4gcHJpbWVyIGx1Z2FyLCBlcyBwb3NpYmxlIHVzYXIgbGEgZnVuY2nDs24gYHByb2Nlc3NfYXV0aG9yX25hbWUoKWAgcGFyYSBidXNjYXIgYXV0b3Jlcy4gRXN0YSBlcyBkZSB1dGlsaWRhZCB5YSBxdWUgcGFyYSBhY2NlZGVyIGEgaW5mb3JtYWNpw7NuIGRlIGVzdG9zIG5lY2VzaXRhbW9zIHN1IGlkZW50aWZpY2Fkb3IsIG9idGVuaWRvIGFxdcOtLiBDb24gYHZlcmJvc2UgPSBGQUxTRWAgb2N1bHRhbW9zIG1lbnNhamVzLCBjb21vIGxhIFVSTCBkZSBsYSBwZXRpY2nDs24uDQpgYGB7cn0NCmF1dGhvcl9zYyA8LSByc2NvcHVzOjpwcm9jZXNzX2F1dGhvcl9uYW1lKGxhc3RfbmFtZSA9ICdNb2VkJywgZmlyc3RfbmFtZSA9ICdIZW5rIEYuJywgdmVyYm9zZSA9IEZBTFNFKQ0KYXV0aG9yX3NjJGF1X2lkDQpgYGANCg0KDQpFcyBwb3NpYmxlIHRhbWJpw6luIHJlY3VwZXJhciBtZXRhZGF0b3MgZGUgbG9zIGF1dG9yZXMgbWVkaWFudGUgbGEgZnVuY2nDs24gYGF1dGhvcl9yZXRyaWV2YWwoKWAuIFNlIHB1ZWRlIHRhbnRvIGJ1c2NhciBhdXRvcmVzIHBvciBub21icmUgeSBhcGVsbGlkb3MgY29tbyBpbmRpY2FyIGVsIGlkIGRlIHVubyBkZSBlbGxvcy4NCmBgYHtyfQ0KYXV0aG9yX3NjIDwtIHJzY29wdXM6OmF1dGhvcl9yZXRyaWV2YWwobGFzdF9uYW1lID0gJ01vZWQnLCBmaXJzdF9uYW1lID0gJ0hlbmsgRi4nLCB2ZXJib3NlID0gRkFMU0UpDQphdXRob3Jfc2MgPC0gcnNjb3B1czo6YXV0aG9yX3JldHJpZXZhbChhdV9pZCA9ICc3MDAzNTU1NDEyJywgdmVyYm9zZSA9IEZBTFNFKQ0KYXV0aG9yX3NjJGNvbnRlbnQkYGF1dGhvci1yZXRyaWV2YWwtcmVzcG9uc2VgW1sxXV0kY29yZWRhdGEkYGRvY3VtZW50LWNvdW50YA0KYXV0aG9yX3NjJGNvbnRlbnQkYGF1dGhvci1yZXRyaWV2YWwtcmVzcG9uc2VgW1sxXV0kY29yZWRhdGEkYGNpdGVkLWJ5LWNvdW50YA0KYGBgDQoNCg0KQ29uIGxhIGZ1bmNpw7NuIGBhdXRob3JfZGYoKWAgc2UgcHVlZGVuIGJ1c2NhciBjb250ZW5pZG9zIGRlIHVuIGF1dG9yLiBQYXJhIGdhcmFudGl6YXIgcXVlIGVzdG9zIHNvbiBkZSB1biBhdXRvciwgbG8gY29ycmVjdG8gZXMgcHJpbWVybyBidXNjYXIgc3UgaWRlbnRpZmljYWRvciB5IGx1ZWdvIGNvbnRlbmlkb3MgZW4gYmFzZSBhIGVsbG8uIA0KYGBge3J9DQpkZl9zYyA8LSByc2NvcHVzOjphdXRob3JfZGYobGFzdF9uYW1lID0gJ01vZWQnLCBmaXJzdF9uYW1lID0gJ0hlbmsgRi4nLCB2ZXJib3NlID0gRkFMU0UpDQpkZl9zYyA8LSByc2NvcHVzOjphdXRob3JfZGYoYXVfaWQgPSAnNzAwMzU1NTQxMicsIHZlcmJvc2UgPSBGQUxTRSkNCmRmX3NjDQpgYGANCg0KDQpDb24gbGEgZnVuY2nDs24gYHNjb3B1c19zZWFyY2goKWAgc2UgcHVlZGVuIHJlYWxpemFyIGNvbnN1bHRhcyBlbiBTY29wdXMgYSB0YXbDqXMgZGUgc3UgQVBJLiBQYXJhIGhhY2VyIG3DoXMgbWFuZWphYmxlcyBsb3MgZGF0b3MgZXMgbmVjZXNhcmlvIHVzYXIgdHJhcyBlbGxvIGxhIGZ1bmNpw7NuIGBnZW5fZW50cmllc190b19kZigpYC4gRW4gZnVuY2nDs24gZGVsIHBhcsOhbWV0cm8gYGNvdW50YCBzZSBwdWVkZSB1c2FyIGVsIGB2aWV3YCBwYXJhIG9idGVuZXIgbcOhcyBvIG1lbm9zIGluZm9ybWFjacOzbiBkZSBsb3MgcmVnaXN0cm9zLg0KYGBge3J9DQpkZl9zYyA8LSByc2NvcHVzOjpzY29wdXNfc2VhcmNoKHF1ZXJ5ID0gJ1RJVExFLUFCUy1LRVkoYWx0bWV0cmljcyknLCBjb3VudCA9IDEwLCB2aWV3ID0gJ0NPTVBMRVRFJywgdmVyYm9zZSA9IEZBTFNFKQ0KZGZfc2MgPC0gcnNjb3B1czo6Z2VuX2VudHJpZXNfdG9fZGYoZGZfc2MkZW50cmllcykNCmhlYWQoZGZfc2MkZGYpDQpgYGANCg0KDQojIyBVbnBheXdhbGwNClBhcmEgYWNjZWRlciBhIGxhIEFQSSBkZSBVbnBheXdhbGwgZXMgbmVjZXNhcmlvIGluc3RhbGFyIGVsIHBhcXVldGUgW2Byb2Fkb2lgXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvcm9hZG9pL3JvYWRvaS5wZGYpLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCdyb2Fkb2knKQ0KbGlicmFyeShyb2Fkb2kpDQpgYGANCg0KDQpBcXXDrSBwb2RlbW9zIHVzYXIgbGEgZnVuY2nDs24gYG9hZG9pX2ZldGNoKClgIHBhcmEgY29uc3VsdGFyIGRpcmVjdGFtZW50ZSB2YXJpb3MgRE9Jcy4gTm8gb2JzdGFudGUsIGV4aXN0ZSB1bmEgbGltaXRhY2nDs24gZGUgMTAwLjAwMCBjb25zdWx0YXMgZGlhcmlhcyB5IGVzIG5lY2VzYXJpbyBxdWUgdGUgaWRlbnRpZmlxdWVzIG1lZGlhbnRlIHVuIGNvcnJlbyBlbGVjdHLDs25pY28uDQpgYGB7cn0NCmRvaXMgPC0gYygnMTAuNzMyNi9tMTgtMjEwMScsICcxMC4xMDkzL2Jpb3NjaS9iaXowODgnKQ0KZGZfdW4gPC0gcm9hZG9pOjpvYWRvaV9mZXRjaChkb2lzID0gZG9pcywgZW1haWwgPSAndXN1YXJpb0Bjb3JyZW8uY29tJykNCmRmX3VuDQpgYGANCg0KDQpFeGlzdGUgbGEgb3BjacOzbiBkZSBxdWUgbGEgZnVuY2nDs24gbXVlc3RyZSBlbCBwb3JjZW50YWplIGRlIHByb2dyZXNvIGRlIGxhIGNvbnN1bHRhLCBhbGdvIHF1ZSBlcyBkZSB1dGlsaWRhZCBlbiBjb25zdWx0YXMgbcOhcyBleHRlbnNhcy4gUGFyYSBlbGxvIHNvbG8gaGF5IHF1ZSBhw7FhZGlyIGEgbGEgZnVuY2nDs24gZWwgcGFyw6FtZXRybyBgLnByb2dyZXNzID0gJ3RleHQnYC4NCmBgYHtyfQ0KZG9pcyA8LSBjKCcxMC43MzI2L20xOC0yMTAxJywgJzEwLjEwOTMvYmlvc2NpL2JpejA4OCcsICcxMC4xMTI2L3NjaWVuY2UuYWF0NzY5MycsICcxMC4xMDM4L3M0MTQ2Ny0wMTktMTI4MDgteicsDQogICAgICAgICAgJzEwLjExMzYvYm1qLms1MDk0JywgJzEwLjExMjYvc2NpZW5jZS5hYXgwODQ4JywgJzEwLjEwMTYvczAxNDAtNjczNigxOSkzMDA0MS04JywgJzEwLjEwMzgvczQxNTg2LTAxOS0xNjY2LTUnKQ0KZGZfdW4gPC0gcm9hZG9pOjpvYWRvaV9mZXRjaChkb2lzID0gZG9pcywgZW1haWwgPSAndXN1YXJpb0Bjb3JyZW8uY29tJykNCmRmX3VuDQpgYGANCg0KDQpFbCByZXN1bHRhZG8gYXBhcmVjZSBlbiB1bmEgdmFyaWFibGUgZGUgdGlwbyBgdGJsX2RmYC4gRXMgcG9yIGVsbG8gcXVlIGFsZ3Vub3MgZGUgbG9zIGNhbXBvcywgY29tbyBgb2FfbG9jYXRpb25gLCBpbmNsdXllbiBhIHN1IHZleiBvdHJvIGRhdGEuZnJhbWUgZW4gbHVnYXIgZGUgdW4gdmFsb3IgYXTDs21pY28uIA0KYGBge3J9DQpkZl91blsyLF0kb2FfbG9jYXRpb25zW1sxXV0NCmBgYA0KDQoNCkVzdG9zIGNhbXBvcyByZXF1aWVyZW4gZGUgdW4gcHJvY2VzYW1pZW50byBwb3N0ZXJpb3IuIFBvciBlamVtcGxvLCBnZW5lcmFuZG8gb3RybyBkYXRhLmZyYW1lIGVuIGVsIHF1ZSBjYWRhIERPSSBzZSBkZXNkb2JsZSB0YW50YXMgdmVjZXMgY29tbyBVUkxzIHRlbmdhLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgUGFyYSBlc3RhIG9wZXJhY2nDs24gZXMgbmVjZXNhcmlvIHRlbmVyIGluc3RhbGFkbyB5IGNhcmdhZG8gZWwgcGFxdWV0ZSBkcGx5cg0KbGlicmFyeShkcGx5cikNCg0KZHBseXI6Om11dGF0ZShkZl91biwgdXJscyA9IHB1cnJyOjptYXAoYmVzdF9vYV9sb2NhdGlvbiwgJ3VybCcpICU+JSANCiAgICAgICAgICAgICAgICBwdXJycjo6bWFwX2lmKHB1cnJyOjppc19lbXB0eSwgfiBOQV9jaGFyYWN0ZXJfKSAlPiUNCiAgICAgICAgICAgICAgICBwdXJycjo6ZmxhdHRlbl9jaHIoKQ0KICAgICAgICAgICAgICApW2MoJ2RvaScsICd1cmxzJyldDQpgYGANCg0KDQpPIHF1ZSBlbiBjYXNvIGRlIGxvY2FsaXphciB1biBlcnJvciBlbiBsb3MgRE9JcyBubyBzZSBkZXRlbmdhIGxhIGNvbnN1bHRhLCBxdWUgZXMgdW5hIGRlIGxhcyBsaW1pdGFjaW9uZXMsIHkgdHJhcyBlbGxvIHF1ZWRhcm5vcyBjb24gYXF1ZWxsb3MgY2FtcG9zIHF1ZSBub3MgaW50ZXJlc2VuLCBwb3IgZWplbXBsbyBlbCBET0kgeSBzaSBlcyBPcGVuIEFjY2Vzcy4NCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQojIFBhcmEgZXN0YSBvcGVyYWNpw7NuIGVzIG5lY2VzYXJpbyB0ZW5lciBpbnN0YWxhZG8geSBjYXJnYWRvIGVsIHBhcXVldGUgZHBseXINCiNsaWJyYXJ5KGRwbHlyKQ0KDQpkb2lzIDwtIGMoJzEwLjczMjYvbTE4LTIxMDEnLCAnMTAuMTA5My9iaW9zY2kvYml6MDg4JywgJzEwLjExMjYvc2NpZW5jZS5hYXQ3NjkzJywgJzEwLjEwMzgvczQxNDY3LTAxOS0xMjgwOC16JywNCiAgICAgICAgICAnMTAuMTEzNi9ibWouazUwOTQnLCAnLScsICcxMC4xMDE2L3MwMTQwLTY3MzYoMTkpMzAwNDEtOCcsICcxMC4xMDM4L3M0MTU4Ni0wMTktMTY2Ni01JykNCg0KZGZfdW4gPC0gcHVycnI6Om1hcChkb2lzLCANCiAgICAgICAgICAgICAgICAgICAgLmYgPSBwdXJycjo6c2FmZWx5KGZ1bmN0aW9uKHgpIHJvYWRvaTo6b2Fkb2lfZmV0Y2goeCwgZW1haWwgPSAndXN1YXJpb0Bjb3JyZW8uY29tJykpKQ0KDQpkZl91biA8LSBwdXJycjo6bWFwX2RmKGRmX3VuLCAncmVzdWx0JykNCmRmX3VuIDwtIGRmX3VuWywgYygnZG9pJywgJ2lzX29hJyldDQpkZl91bg0KYGBgDQoNCg0KIyMgYXJYaXYNCkVuIGVsIGNhc28gZGUgYXJYaXYsIHRhbWJpw6luIGVzIHBvc2libGUgY29uZWN0YXJzZSBhIGxhIEFQSSBtZWRpYW50ZSB1biBwYXF1ZXRlIGxsYW1hZG8gW2BhUnhpdmBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9hUnhpdi9hUnhpdi5wZGYpLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCdhUnhpdicpDQpsaWJyYXJ5KGFSeGl2KQ0KYGBgDQoNCg0KQ29uIGxhIGZ1bmNpw7NuIGBhcnhpdl9zZWFyY2goKWAgZXMgcG9zaWJsZSByZWFsaXphciBjb25zdWx0YXMuIEVsIHJlc3VsdGFkbyBvYnRlbmlkbyBlcyB1biBgZGF0YS5mcmFtZWAgY29uIGxhcyBkaXN0aW50YXMgcHVibGljYWNpb25lcywgb2ZyZWNpZW5kbyBhbCByZXNwZWN0byBkaXN0aW50b3MgbWV0YWRhdG9zIHBhcmEgY2FkYSB1bmEgZGUgZWxsYXMuDQpgYGB7cn0NCmRmX2FyIDwtIGFSeGl2Ojphcnhpdl9zZWFyY2goJ2F1OiJSb2RyaWdvIENvc3RhcyIgQU5EIHRpOmFsdG1ldHJpY3MnKQ0KZGZfYXINCmBgYA0KDQoNCkFkZW3DoXMsIGVzIHBvc2libGUgb3JkZXJuYXIgbG9zIHJlc3VsdGFkb3MgY29uIGVsIHBhcsOhbWV0cm8gYHNvcnRfYnlgLiBQb3IgZWplbXBsbywgcG9yIGZlY2hhIGRlIGVudsOtbyBlbiBvcmRlbiBkZXNjZW5kZW50ZSAocGFyw6FtZXRybyBgYXNjZW5kaW5nID0gRkFMU0VgKS4gDQpgYGB7cn0NCmRmX2FyIDwtIGFSeGl2Ojphcnhpdl9zZWFyY2goJ2F1OiJSb2RyaWdvIENvc3RhcyIgQU5EIHRpOmFsdG1ldHJpY3MnLCBzb3J0X2J5ID0gJ3N1Ym1pdHRlZCcsIGFzY2VuZGluZyA9IEZBTFNFKQ0KZGZfYXINCmBgYA0KDQoNCkVzIHBvc2libGUgcmVjdXBlcmFyIGRpcmVjdGFtZW50ZSBlbCBuw7ptZXJvIHRvdGFsIGRlIHByZXByaW50cyBxdWUgc2UgYWp1c3RhbiBhIGxhIGNvbnN1bHRhLiBQYXJhIGVsbG8gc2UgdXNhIGxhIGZ1bmNpw7NuIGBhcnhpdl9jb3VudGAuIFVuYSBvcGNpw7NuIMO6dGlsIHBhcmEgY29ub2NlciBlbCBhbGNhbmNlIGRlIGxhIGNvbnN1bHRhLg0KYGBge3J9DQphUnhpdjo6YXJ4aXZfY291bnQoJ3N1Ym1pdHRlZERhdGU6WzIwMTgwMTAxIFRPIDIwMTkxMjMxXScpDQpgYGANCg0KDQpBbCByZXNwZWN0byBlcyBuZWNlc2FyaW8gcmVtYXJjYXIgcXVlIGxhIEFQSSBjdWVudGEgY29uIHVuYSBsaW1pdGFjacOzbiBkZSA1MC4wMDAgcmVnaXN0cm9zIHBvciBjb25zdWx0YS4gQXNpbWltc28sIHBvciBkZWZlY3RvIGxhIGZ1bmNpw7NuIGBhcnhpdl9zZWFyY2goKWAgZXN0w6EgbGltaXRhZGEgYSAxMCByZXN1bHRhZG9zLCBwYXJhIGFsdGVyYXJsbyBlcyBuZWNlc2FyaW8gdXNhciBlbCBwYXLDoW1ldHJvIGBsaW1pdGAuIEVzIHBvciBlbGxvIHF1ZSB1bmEgYnVlbmEgZXN0cmF0ZWdpYSBkZSBiw7pzcXVlZGEgZXMgbGEgZGUgY29uc3VsdGFyIHByaW1lciBlbCB0b3RhbCBkZSBkb2N1bWVudG9zIHF1ZSBoYXkgcGFyYSBsYSBjb25zdWx0YSBxdWUgcXVlcmVtb3MgeSBsdWVnbyByZWFsaXphcmxhLg0KYGBge3J9DQphUnhpdjo6YXJ4aXZfY291bnQoJ3N1Ym1pdHRlZERhdGU6MjAxOTA1MTIqJykNCmRmX2FyIDwtIGFSeGl2Ojphcnhpdl9zZWFyY2goJ3N1Ym1pdHRlZERhdGU6MjAxOTA1MTIqJywgc29ydF9ieSA9ICdzdWJtaXR0ZWQnLCBhc2NlbmRpbmcgPSBGQUxTRSwgbGltaXQgPSAzMDApDQpkZl9hcg0KYGBgDQoNCg0KIyMgQWx0bWV0cmljLmNvbQ0KT3RyYSBkZSBsYXMgQVBJIHF1ZSBjdWVudGFuIGNvbiB1biBwYXF1ZXRlIGVuIFIgZXMgQWx0bWV0cmljLiBFc3RlIHNlIGxsYW1hIFtgckFsdG1ldHJpY2BdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yQWx0bWV0cmkvckFsdG1ldHJpYy5wZGYpLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNpbnN0YWxsLnBhY2thZ2VzKCdyQWx0bWV0cmljJykNCmxpYnJhcnkockFsdG1ldHJpYykNCmBgYA0KDQoNCk1lZGlhbnRlIGxhIGZ1bmNpw7NuIGBhbHRtZXRyaWNzYCBlcyBwb3NpYmxlIHJlYWxpemFyIHVuYSBjb25zdWx0YSwgdXNhbmRvIHBhcmEgZWxsbyB1biBET0ksIFBNSUQsIGFyeGl2IG8gSVNCTi4gU3UgZnVuY2lvbmFtaWVudG8gZXMgc2luIGVtZ2FyZ28gc2ltaWxhciBhbCBkZSBET0FKLiBDb24gY2FkYSBjb25zdWx0YSBzb2xvIHBvZGVtb3MgaW5jbHVpciB1biBpZGVudGlmaWNhZG9yLiBBdW5xdWUsIGVsIHJlc3VsdGFkbyBubyBlcyB1biBkYXRhLmZyYW1lIGVzIHVuIHRpcG8gZGUgb2JqZXRvIGBhbHRtZXRyaWNgIHF1ZSBlcyBuZWNlc2FyaW8gbW9kaWZpY2FyLg0KYGBge3J9DQphbHRfZG9pIDwtIHJBbHRtZXRyaWM6OmFsdG1ldHJpY3MoZG9pID0gJzEwLjEwMzgvNDgwNDI2YScpDQphbHRfZG9pDQoNCmFsdF9hcnggPC0gckFsdG1ldHJpYzo6YWx0bWV0cmljcyhhcnhpdiA9ICcxOTA1LjA4MjMzJykNCmFsdF9hcngNCmBgYA0KDQoNCkxhIGNvbnZlcnNpw7NuIGEgdW4gZGF0YS5mcmFtZSBlcyB0YW1iacOpbiBkZSBtYW5lcmEgaW5kaXZpZHVhbCB5IHJlcXVpZXJlIGVsIHVzbyBkZSBsYSBmdW5jacOzbiBgYWx0bWV0cmljX2RhdGFgLg0KYGBge3J9DQpkZl9hbHQgPC0gckFsdG1ldHJpYzo6YWx0bWV0cmljX2RhdGEoYWx0X2RvaSkNCmBgYA0KDQoNCkRlIGVzdGUgbW9kbywgc2UgcHVlZGUgY3JlYXIgdW5hIGZ1bmNpw7NuIGNvbiBsYSBxdWUgYXV0b21hdGl6YXIgdG9kbyBlc3RlIHByb2Nlc28uIFBvciBlamVtcGxvIGNvbiB1biBidWNsZSBxdWUgY29uc3VsdGUgdGFudG9zIERPSXMgY29tbyBxdWVyYW1vcyB5IHF1ZSB0cmFzIGVsbG8gbG9zIHZheWEgY29tYmluYW5kbyBlbiB1biBkYXRhLmZyYW1lLiBEZWNpciBxdWUgbm8gdG9kb3MgbG9zIGRhdGEuZnJhbWUgdGllbmVuIGxvcyBtaXNtbyBjYW1wb3MsIHBvciBsbyBxdWUgc2UgZ2VuZXJhcsOhbiBhbGd1bm9zIHZhY8Otb3MuDQpgYGB7cn0NCiMgUGFyYSBlc3RhIG9wZXJhY2nDs24gZXMgbmVjZXNhcmlvIHRlbmVyIGluc3RhbGFkbyB5IGNhcmdhZG8gZWwgcGFxdWV0ZSBkcGx5cg0KI2xpYnJhcnkoZHBseXIpDQoNCmFsdG1ldHJpY19kYXRhIDwtIGZ1bmN0aW9uKGRvaXMpew0KICBkZiA8LSBkYXRhLmZyYW1lKCkNCiAgZm9yKGRvaSBpbiBkb2lzKXsNCiAgICBhbHQgPC0gckFsdG1ldHJpYzo6YWx0bWV0cmljcyhkb2kgPSBkb2kpDQogICAgZGZfYWx0IDwtIHJBbHRtZXRyaWM6OmFsdG1ldHJpY19kYXRhKGFsdCkNCiAgICBkZiA8LSBkcGx5cjo6YmluZF9yb3dzKGRmLCBkZl9hbHQpDQogIH0NCiAgDQogIHJldHVybihkZikNCn0NCg0KYWx0bWV0cmljX2RhdGEoZG9pc1sxOjJdKQ0KYGBgDQoNCg0KUG9yIMO6bHRpbW8gZW4gZXN0YSBBUEksIHBlc2UgYSBxdWUgc2UgcHVlZGUgdXNhciBkZSBtYW5lcmEgZ3JhdHVpdGEsIGVzIHBvc2libGUgcXVlIHRyYXMgcmVhbGl6YXIgdmFyaWFzIGNvbnN1bHRhcyBzdSB1c28gcXVlZGUgbGltaXRhZG8uIEVzIHBvciBlbGxvIHF1ZSBwYXJhIHN1IGNvcnJlY3RvIGZ1bmNpb25hbWllbnRvIHJlcXVpZXJlIGRlIHVuYSBga2V5YCwgbGEgY3VhbCBzZSBwdWVkZSBpbnRyb2R1Y2lyIGNvbiBlbCBwYXLDoW1ldHJvIGBhcGlrZXlgLg0KYGBge3IgZXZhbD1GQUxTRX0NCnJBbHRtZXRyaWM6OmFsdG1ldHJpY3MoYXBpa2V5ID0gJ1RVX0tFWScpDQpgYGANCg0KDQojIENhc28gcHLDoWN0aWNvDQpQYXJhIGVqZW1wbGlmaWNhciBsYSB1dGlsaWRhZCBkZSBlc3RhcyBoZXJyYW1pZW50YXMsIHZhbW9zIGEgY29uc2lkZXJhciBkb3MgY2Fzb3MgcHLDoWN0aWNvcy4NCg0KDQojIyBFamVtcGxvIDENCkVuIGVzdGEgY2FzbyBxdWVyZW1vcyBoYWNlciB1bmEgYsO6c3F1ZWRhIGVuIFdlYiBvZiBTY2llbmNlIHkgdHJhcyBlbGxvIGNvbnN1bHRhciBjdWFudG9zIGRlIGVzdG9zIHJlZ2lzdHJvcyBzZSBlbmN1ZW50cmFuIGVuIEFsdG1ldHJpYy5jb20geSBvYnRlbmVyIHN1IEFsdG1ldHJpYyBBdHRlbnRpb24gU2NvcmUuDQoNCg0KRW4gcHJpbWVyIGx1Z2FyIHJlYWxpemFtb3MgbGEgY29uc3VsdGEgZW4gV2ViIG9mIFNjaWVuY2UgdXNhbmRvIHN1IEFQSS4NCmBgYHtyIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI2xpYnJhcnkod29zcikNCnNpZCA8LSB3b3NyOjphdXRoKHVzZXJuYW1lID0gTlVMTCwgcGFzc3dvcmQgPSBOVUxMKQ0KDQp3b3NyOjpxdWVyeV93b3MoJ1RTID0gKFdpa2lwZWRpYSBBTkQgYWx0bWV0cmljKiknLCBzaWQgPSBzaWQpDQoNCmRmX2VqMSA8LSB3b3NyOjpwdWxsX3dvcygnVFMgPSAoVHdpdHRlciBBTkQgYWx0bWV0cmljKiknLCBzaWQgPSBzaWQpDQpgYGANCg0KDQpUcmFzIGVsbG8gcmVhbGl6YW1vcyBsYSBjb25zdWx0YSBhIEFsdG1ldHJpYy5jb20uIFBhcmEgZWxsbyB1c2Ftb3MgbGEgZnVuY2nDs24gYW50ZXMgcHJvcHVlc3RhIHF1ZSByZWFsaXphIGVuIGJ1Y2xlIHRvZGFzIGxhcyBjb25zdWx0YXMgeSBkZXNwdcOpcyBzZWxlY2Npb25hbW9zIGRlbCBjb25qdW50byBkZSBkYXRvcyByZXN1bHRhbnRlIGVsIERPSSB5IGVsIEFsdG1ldHJpYyBBdHRlbmNpb24gU2NvcmUuDQpgYGB7ciByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiNsaWJyYXJ5KHJBbHRtZXRyaWMpDQoNCmRvaXMgPC0gZGZfZWoxJHB1YmxpY2F0aW9uJGRvaQ0KDQojIFBhcmEgZXN0YSBvcGVyYWNpw7NuIGVzIG5lY2VzYXJpbyB0ZW5lciBpbnN0YWxhZG8geSBjYXJnYWRvIGVsIHBhcXVldGUgZHBseXINCiNsaWJyYXJ5KGRwbHlyKQ0KDQphbHRtZXRyaWNfZGF0YSA8LSBmdW5jdGlvbihkb2lzKXsNCiAgZGYgPC0gZGF0YS5mcmFtZSgpDQogIGZvcihkb2kgaW4gZG9pcyl7DQogICAgYWx0IDwtIHJBbHRtZXRyaWM6OmFsdG1ldHJpY3MoZG9pID0gZG9pKQ0KICAgIGRmX2FsdCA8LSByQWx0bWV0cmljOjphbHRtZXRyaWNfZGF0YShhbHQpDQogICAgZGYgPC0gZHBseXI6OmJpbmRfcm93cyhkZiwgZGZfYWx0KQ0KICB9DQogIA0KICByZXR1cm4oZGYpDQp9DQoNCmRmX2VqMV9hbHQgPC0gYWx0bWV0cmljX2RhdGEoZG9pc1sxOjJdKQ0KZGZfZWoxX2FsdCA8LSBkZl9lajFfYWx0WyxjKCdkb2knLCAnc2NvcmUnKV0NCmBgYA0KDQoNCg0KYGBge3J9DQpkZl9lajFfYWx0DQpgYGANCg0KDQojIyBFamVtcGxvIDINClBhcmEgZXN0ZSBzZWd1bmRvIHZhbW9zIGEgcGFydGlyIGRlIHVuIGFyY2hpdm8gQ1NWIGV4cG9ydGFkbyBkZXNkZSBEaW1lbnNpb25zLiBMbyB2YW1vcyBhIGltcG9ydGFyIGEgUlN0dWRpbyB5IGVuIFVucGF5d2FsbCB2YW1vcyBhIGNvbnN1bHRhciBsb3MgRE9JcyBwYXJhIGdlbmVyYXIgdW4gYGRhdGEuZnJhbWVgIGNvbiBlbCB0w610dWxvIHkgc2kgZXMgT3BlbiBBY2Nlc3MuIFBvciDDumx0aW1vLCBlc3RlIMO6bHRpbW8gbG8gZXhwb3J0YXJlbW9zIGVuIGZvcm1hdG8gQ1NWLg0KDQoNClBhcmEgZWxsbyBlbXBlemFtb3MgaW1wb3J0YW5kbyBlbCBhcmNoaXZvIGRlIERpbWVuc2lvbnMgKCpEaW1lbnNpb25zLmNzdiopLiBFbiBlc3RlIGNhc28gZWwgYXJjaGl2byBzZSBlbmN1ZW50cmEgZW4gZWwgbWlzbW8gZGlyZWN0b3JpbyBxdWUgZWwgc2NyaXB0LCBlcyBwb3IgZWxsbyBxdWUgZW1wbGVvIHVuYSBydXRhIHJlbGF0aXZhLiBUYW1iacOpbiBwdWVkZSBlc3RhYmxlY2Vyc2UgbGEgcnV0YSBjb21wbGV0YSBoYXN0YSBlbCBtaXNtby4NCmBgYHtyfQ0KZGZfZG0gPC0gcmVhZC50YWJsZSgnRGltZW5zaW9ucy5jc3YnLCBoZWFkZXIgPSBUUlVFLCBza2lwID0gMSwgc2VwID0gJywnLCBxdW90ZSA9ICciJywgY29tbWVudC5jaGFyID0gJycsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQpgYGANCg0KDQpVbmEgdmV6IGltcG9ydGFkbyBlbCBhcmNodmlvLCBhbG1hY2VuYW1vcyBsb3MgRE9JIGVuIHVuYSB2YXJpYWJsZSB5IGxvcyB1c2Ftb3MgZW4gbGEgcGV0aWNpw7NuIGEgVW5wYXdhbGwuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZG9pcyA8LSBkZl9kbSRET0kNCg0KI2xpYnJhcnkocm9hZG9pKQ0KDQojIFBhcmEgZXN0YSBvcGVyYWNpw7NuIGVzIG5lY2VzYXJpbyB0ZW5lciBpbnN0YWxhZG8geSBjYXJnYWRvIGVsIHBhcXVldGUgZHBseXINCiNsaWJyYXJ5KGRwbHlyKQ0KDQpkZl9kbV91biA8LSBwdXJycjo6bWFwKGRvaXMsIA0KICAgICAgICAgICAgICAgICAgICAgICAuZiA9IHB1cnJyOjpzYWZlbHkoZnVuY3Rpb24oeCkgcm9hZG9pOjpvYWRvaV9mZXRjaCh4LCBlbWFpbCA9ICd1c3VhcmlvQGNvcnJlby5jb20nKSkpDQpkZl9kbV91biA8LSBwdXJycjo6bWFwX2RmKGRmX2RtX3VuLCAncmVzdWx0JykNCmRmX2RtX3VuIDwtIGRmX2RtX3VuWywgYygndGl0bGUnLCAnaXNfb2EnKV0NCmBgYA0KDQoNCmBgYHtyfQ0KZGZfZG1fdW4NCmBgYA0KDQoNCmBgYHtyfQ0Kd3JpdGUuY3N2MihkZl9kbV91biwgJ0RpbWVuc2lvbnNfT0EuY3N2Jywgcm93Lm5hbWVzID0gRkFMU0UsIGZpbGVFbmNvZGluZyA9ICd1dGYtOCcpDQpgYGA=