El objetivo de este workshop es introducir el uso de herramientas que simplifican la creación de paquetes en R. Es una interpretación libre y resumida del curso elaborado por:

Los materiales originales se encuentran en https://github.com/hadley/pkg-dev

Vamos a realizar un paquete de juguete, siguiente este capítulo

Setup

Todo el código de estas notas de clase no debe ser corrido en el .rmd sino en el entorno del paquete

install.packages(c(
  "devtools", "roxygen2",
  "testthat", "covr", "pkgdown"
))
usethis::use_course("http://bit.ly/30kL8QD")

Workflow básico

usethis

La herramienta principal para crear un paquete en R es usethis, un paquete que contiene un conjunto de funciones que simplifican el trabajo

Creamos el paquete con create_package()

Esto crea el directorio con la estructura base del paquete

usethis::create_package('~/Desktop/pqtprueba')

Creamos nuestra función

Ejemplo. Queremos una función que concatene factores con propiedades más copadas que el default

a <- factor(c("character", "in", "the", "streets"))
b <- factor(c("integer", "in", "the", "sheets"))

c(a,b)
[1] 1 2 4 3 2 1 4 3
factor(c(as.character(a), as.character(b)))
[1] character in        the       streets   integer   in        the       sheets   
Levels: character in integer sheets streets the

Cremos la función fbind

fbind <- function(a, b) {
factor(c(as.character(a), as.character(b)))
}

Para incorporarla al paquete utilizamos usethis::use_r("file-name")

usethis::use_r("fbind")
  • Escribimos nuestra función en R/fbind.R

  • Con devtools::load_all() (o ctrl+shift+L) podemos cargar las funciones del paquete para ir probandolas en la consola.

Tenemos nuestra función que hace lo que queremos. Cómo sigue la cosa?

Chequeamos el paquete

Check analiza el paquete para encontrar posibles errors, warnings o notes. Estos son tres niveles de gravedad de los problemas que presenta el paquete.

  • Para que un paquete sea aceptada en CRAN no debe tener ni errores ni warnings, y los notes son inspeccionados por los evaluadores (en general existe un note para los paquetes que se suben por primera vez, y no debería haber otros más que este).

  • Es importante correr muchos check a lo largo del desarrollo del paquete y manterlo limpio. Es mejor solucionar los problemas apenas aparecen.

devtools::check()

También podemos correrlo desde Build –> Check.

Qué nos devuelve?

Nos esta indicando que:

  • No especificamos la licencia del paquete
  • No creamos la documentación para la función fbind

Para crear la licencia usamos el comando usethis::use_mit_license()

Documentación

Para crear la documentación usamos el comando devtools::document() o ctrl+shift+D y el paquete roxygen2

Nos paramos sobre la función –> Code –> Insert roxygen skeleton

Install.

Una vez que el paquete funciona, podemos usar devtools::install() para instalarla y utilizarla

Testing

Es importante incorporar tests automáticos en nuestro paquete, de forma tal que cada vez que realizamos una modificación, automáticamente los test corroboren que no se rompió otra parte del paquete.

Para eso, utilizamos la función use_test que crea un nuevo script en la carpeta tests/testthat donde podemos escribir nuestros tests

usethis::use_test('fbind')

Por ejemplo, para fbind podríamos armar el siguiente test

test_that("fbind works", {

  factor1 <- factor(c('ola','k','ase'))
  factor2 <- factor(c('atr', 'perro', 'cumbia', 'cajeteala', 'piola', 'gato'))
  expect_equal(fbind(factor1,factor2),
               factor(c('ola','k','ase','atr','perro','cumbia','cajeteala','piola','gato')))
})

Aquí utilizamos la función test_that para construir el test, y dentro de ella expect_equal para indicar que el resultado del fbind debería ser dicho factor.

En el script de test tenemos el boton run tests arriba a la derecha y podemos ejecutarlos. El resultado es:

Lo cual indica que el test funciona correctamente.

Ahora cada vez que hagamos un check, también ejecutará todos los tests.

Workflow:

En esta clase omitimos el tema del Check coverage y los checks remotos vía travis (ver notas originales).

Compartir el paquete

Tener un paquete de funciones propio puede servirnos para evitar copiar las funciones en cada script o hacer source. Pero probablemente si el paquete es útil para nosotros, también sea útil para otras personas y querramos compartirlo.

Para eso, vamos a ver tres formas de compartir nuestro trabajo

  1. Proyecto en github
  2. Vignettes
  3. pkgdown
  4. submit a CRAN

Paquete en Github

Hay muchas maneras de crear un repositorio en Github.

  • La opción más simple es crear un repo vacío desde la página, clonarlo y luego pegar lo que ya tengamos en esa carpeta.
  • También podemos usar las funciones del paquete usethis use_git() y use_github().
usethis::use_git()

usethis::use_github()

y desde la terminal

git push --set-upstream origin master

La primera función define nuestra carpeta como un repo de git. La segunda crea un repositorio en github. Finalmente la tercera define el upstream de nuestro repo y pushea el contenido que tiene.

Para que esto funcione tenemos que tener previamente sincronizada nuestra computadora con una sesión en github.

Por ejemplo, para mí, estos comandos crearon el siguiente repo: https://github.com/DiegoKoz/pqtprueba

Flujo de trabajo en github

Una vez que nuestro paquete esta en github, deberíamos subir las actualizaciónes de las funciones con:

  1. Add
  2. Commit
  3. Push
  • Esto es sólo el flujo básico de github y hay muchas más cosas que se pueden hacer. Pero para esta clase lo restringimos a este mínimo. Esto lo podemos hacer desde la terminal o desde el IDE de Rstudio.

  • Un buen lugar para leer más sobre Git y Github con R es https://happygitwithr.com/

Para que la librería tenga una buena presentación, deberíamos agregar un README.

usethis::use_readme_rmd()

Luego de modificar el readme, tenemos que subirlo a github nuevamente!

Vignette

Si queremos que la gente utilice nuestro paquete, lo mejor es que la documentación haga sencillo entender qué hace y para qué sirve.

Para crear una vignette podemos usar el comando

usethis::use_vignette(name = 'prueba_vignette',title = 'Ejemplo de Vignette')
  • No se olviden de commitearlo!!

pkgdown

Ahora lo que queremos es que nuestra librería tenga su propia página web

Para ello, usamos la librería pkgdown

pkgdown::build_site()

hace todo por nosotros!!

Esto crea una carpeta docs/ en el repo, con todos los materiales de la página. Lo que nos falta ahora es publicar la página desde github. Para eso, vamos a

  1. Settings
  2. GitHub Pages
  3. Source –> master branch /docs folder

Para mi, esto genera la página https://diegokoz.github.io/pqtprueba/

Submit a CRAN

Cuando un paquete se encuentra en CRAN es porque fue debidamente revisado y se pude confiar en su performance. Eso implica que para que podamos subir nuestro repositorio a CRAN debemos cumplir varios requisitos.

  • El archivo description debe estar debidamente completado con:
    • Título,
    • Número de versión actualizado (para eso podemos usar usethis::use_version())
    • Autores
    • Descripción
    • Dependencias (Imports: y los paquetes que usa el nuestro)
    • BugReports
    • Si la documentación esta en español debemos aclarar con Language: es
  • La librería no debe tener ningún error, warning o note cuando la chequeamos.
  • Además, podemos hacer chequeos externos con
devtools::check_rhub()
devtools::check_win_devel()

  • Si todas las formas de chequeo funcionan bien, podemos pensar en subirlo a CRAN.

  • Para eso, primero usamos usethis::use_cran_comments() para crear el archivo con comentarios para el revisor de CRAN.

  • Si es rechazado, incorporamos los comentarios del revisor y en el archivo cran-comments.md agregamos arriba

This is a resubmission. Compared to the last submission, I
have:
* First change.
* Second change.
* Third change

---
LS0tCnRpdGxlOiAiRGVzYXJyb2xsbyBkZSBQYXF1ZXRlcyBlbiBSIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpFbCBvYmpldGl2byBkZSBlc3RlIHdvcmtzaG9wIGVzIGludHJvZHVjaXIgZWwgdXNvIGRlIGhlcnJhbWllbnRhcyBxdWUgc2ltcGxpZmljYW4gbGEgY3JlYWNpw7NuIGRlIHBhcXVldGVzIGVuIFIuIEVzIHVuYSBpbnRlcnByZXRhY2nDs24gbGlicmUgeSByZXN1bWlkYSBkZWwgY3Vyc28gZWxhYm9yYWRvIHBvcjoKCiAgKiBKZW5ueSBCcnlhbiBbamVubnlicnlhbi5vcmddKGh0dHBzOi8vamVubnlicnlhbi5vcmcpIFx8IEdpdEh1YiBbamVubnliY10oaHR0cHM6Ly9naXRodWIuY29tL2plbm55YmMpIFx8IFR3aXR0ZXIgW2plbm55YnJ5YW5dKGh0dHBzOi8vdHdpdHRlci5jb20vamVubnlicnlhbikKICAqIEppbSBIZXN0ZXIgW2ppbWhlc3Rlci5jb21dKGh0dHBzOi8vd3d3LmppbWhlc3Rlci5jb20pIFx8IEdpdEh1YiBbamltaGVzdGVyXShodHRwczovL2dpdGh1Yi5jb20vamltaGVzdGVyKSBcfCBUd2l0dGVyIFtqaW1oZXN0ZXJfXShodHRwczovL3R3aXR0ZXIuY29tL2ppbWhlc3Rlcl8pCiAgKiBIYWRsZXkgV2lja2hhbSBbaGFkbGV5Lm56XShodHRwOi8vaGFkbGV5Lm56KSBcfCBHaXRIdWIgW2hhZGxleV0oaHR0cHM6Ly9naXRodWIuY29tL2hhZGxleSkgXHwgVHdpdHRlciBbaGFkbGV5d2lja2hhbV0oaHR0cHM6Ly90d2l0dGVyLmNvbS9oYWRsZXl3aWNrYWhtKQoKCj4gTG9zIG1hdGVyaWFsZXMgb3JpZ2luYWxlcyBzZSBlbmN1ZW50cmFuIGVuIFtodHRwczovL2dpdGh1Yi5jb20vaGFkbGV5L3BrZy1kZXZdKGh0dHBzOi8vZ2l0aHViLmNvbS9oYWRsZXkvcGtnLWRldikKClZhbW9zIGEgcmVhbGl6YXIgdW4gcGFxdWV0ZSBkZSBqdWd1ZXRlLCBzaWd1aWVudGUgW2VzdGUgY2Fww610dWxvXShodHRwczovL3ItcGtncy5vcmcvd2hvbGUtZ2FtZS5odG1sKQoKIyMgU2V0dXAKCj4gVG9kbyBlbCBjw7NkaWdvIGRlIGVzdGFzIG5vdGFzIGRlIGNsYXNlIG5vIGRlYmUgc2VyIGNvcnJpZG8gZW4gZWwgLnJtZCBzaW5vIGVuIGVsIGVudG9ybm8gZGVsIHBhcXVldGUKCmBgYHIKaW5zdGFsbC5wYWNrYWdlcyhjKAogICJkZXZ0b29scyIsICJyb3h5Z2VuMiIsCiAgInRlc3R0aGF0IiwgImNvdnIiLCAicGtnZG93biIKKSkKdXNldGhpczo6dXNlX2NvdXJzZSgiaHR0cDovL2JpdC5seS8zMGtMOFFEIikKYGBgCgojIFdvcmtmbG93IGLDoXNpY28KCiMjIHVzZXRoaXMKCkxhIGhlcnJhbWllbnRhIHByaW5jaXBhbCBwYXJhIGNyZWFyIHVuIHBhcXVldGUgZW4gUiBlcyBgdXNldGhpc2AsIHVuIHBhcXVldGUgcXVlIGNvbnRpZW5lIHVuIGNvbmp1bnRvIGRlIGZ1bmNpb25lcyBxdWUgc2ltcGxpZmljYW4gZWwgdHJhYmFqbwoKIVtdKGltZy9sb2dvdXNldGhpcy5wbmcpCgoKIyMjIENyZWFtb3MgZWwgcGFxdWV0ZSBjb24gYGNyZWF0ZV9wYWNrYWdlKClgCgpFc3RvIGNyZWEgZWwgZGlyZWN0b3JpbyBjb24gbGEgZXN0cnVjdHVyYSBiYXNlIGRlbCBwYXF1ZXRlCgpgYGByCnVzZXRoaXM6OmNyZWF0ZV9wYWNrYWdlKCd+L0Rlc2t0b3AvcHF0cHJ1ZWJhJykKYGBgCiFbXShpbWcvc2FsaWRhX2NyZWF0ZV9wa2cucG5nKQoKIVtdKGltZy9jcmVhdGVfcGFja2FnZS5wbmcpCgojIyMgQ3JlYW1vcyBudWVzdHJhIGZ1bmNpw7NuCgpFamVtcGxvLiBRdWVyZW1vcyB1bmEgZnVuY2nDs24gcXVlIGNvbmNhdGVuZSBmYWN0b3JlcyBjb24gcHJvcGllZGFkZXMgbcOhcyBjb3BhZGFzIHF1ZSBlbCBkZWZhdWx0CgpgYGB7cn0KYSA8LSBmYWN0b3IoYygiY2hhcmFjdGVyIiwgImluIiwgInRoZSIsICJzdHJlZXRzIikpCmIgPC0gZmFjdG9yKGMoImludGVnZXIiLCAiaW4iLCAidGhlIiwgInNoZWV0cyIpKQoKYyhhLGIpCmZhY3RvcihjKGFzLmNoYXJhY3RlcihhKSwgYXMuY2hhcmFjdGVyKGIpKSkKYGBgCgoKQ3JlbW9zIGxhIGZ1bmNpw7NuICBgZmJpbmRgCgpgYGB7cn0KZmJpbmQgPC0gZnVuY3Rpb24oYSwgYikgewpmYWN0b3IoYyhhcy5jaGFyYWN0ZXIoYSksIGFzLmNoYXJhY3RlcihiKSkpCn0KYGBgCgoKUGFyYSBpbmNvcnBvcmFybGEgYWwgcGFxdWV0ZSB1dGlsaXphbW9zIGB1c2V0aGlzOjp1c2VfcigiZmlsZS1uYW1lIilgCgpgYGByCnVzZXRoaXM6OnVzZV9yKCJmYmluZCIpCmBgYAoKLSBFc2NyaWJpbW9zIG51ZXN0cmEgZnVuY2nDs24gZW4gUi9mYmluZC5SCgotIENvbiBgZGV2dG9vbHM6OmxvYWRfYWxsKClgIChvIGN0cmwrc2hpZnQrTCkgcG9kZW1vcyBjYXJnYXIgbGFzIGZ1bmNpb25lcyBkZWwgcGFxdWV0ZSBwYXJhIGlyIHByb2JhbmRvbGFzIGVuIGxhIGNvbnNvbGEuIAoKPiBUZW5lbW9zIG51ZXN0cmEgZnVuY2nDs24gcXVlIGhhY2UgbG8gcXVlIHF1ZXJlbW9zLiBDw7NtbyBzaWd1ZSBsYSBjb3NhPwoKIyMgQ2hlcXVlYW1vcyBlbCBwYXF1ZXRlCgoKYENoZWNrYCBhbmFsaXphIGVsIHBhcXVldGUgcGFyYSBlbmNvbnRyYXIgcG9zaWJsZXMgYGVycm9yc2AsIGB3YXJuaW5nc2AgbyBgbm90ZXNgLiBFc3RvcyBzb24gdHJlcyBuaXZlbGVzIGRlIGdyYXZlZGFkIGRlIGxvcyBwcm9ibGVtYXMgcXVlIHByZXNlbnRhIGVsIHBhcXVldGUuCgotIFBhcmEgcXVlIHVuIHBhcXVldGUgc2VhIGFjZXB0YWRhIGVuIENSQU4gbm8gZGViZSB0ZW5lciBuaSBlcnJvcmVzIG5pIHdhcm5pbmdzLCB5IGxvcyBub3RlcyBzb24gaW5zcGVjY2lvbmFkb3MgcG9yIGxvcyBldmFsdWFkb3JlcyAoZW4gZ2VuZXJhbCBleGlzdGUgdW4gbm90ZSBwYXJhIGxvcyBwYXF1ZXRlcyBxdWUgc2Ugc3ViZW4gcG9yIHByaW1lcmEgdmV6LCB5IG5vIGRlYmVyw61hIGhhYmVyIG90cm9zIG3DoXMgcXVlIGVzdGUpLgoKLSBFcyBpbXBvcnRhbnRlIGNvcnJlciBtdWNob3MgY2hlY2sgYSBsbyBsYXJnbyBkZWwgZGVzYXJyb2xsbyBkZWwgcGFxdWV0ZSB5IG1hbnRlcmxvIGxpbXBpby4gRXMgbWVqb3Igc29sdWNpb25hciBsb3MgcHJvYmxlbWFzIGFwZW5hcyBhcGFyZWNlbi4KCmBgYHIKZGV2dG9vbHM6OmNoZWNrKCkKYGBgCgpUYW1iacOpbiBwb2RlbW9zIGNvcnJlcmxvIGRlc2RlIEJ1aWxkIC0tPiBDaGVjay4KClF1w6kgbm9zIGRldnVlbHZlPwoKIVtdKGltZy9jaGVjay5wbmcpCgoKTm9zIGVzdGEgaW5kaWNhbmRvIHF1ZToKIAogLSBObyBlc3BlY2lmaWNhbW9zIGxhIGxpY2VuY2lhIGRlbCBwYXF1ZXRlCiAtIE5vIGNyZWFtb3MgbGEgZG9jdW1lbnRhY2nDs24gcGFyYSBsYSBmdW5jacOzbiBgZmJpbmRgCiAKUGFyYSBjcmVhciBsYSBsaWNlbmNpYSB1c2Ftb3MgZWwgY29tYW5kbyBgdXNldGhpczo6dXNlX21pdF9saWNlbnNlKClgCgogCiMjIyBEb2N1bWVudGFjacOzbgoKUGFyYSBjcmVhciBsYSBkb2N1bWVudGFjacOzbiB1c2Ftb3MgZWwgY29tYW5kbyBgZGV2dG9vbHM6OmRvY3VtZW50KClgIG8gY3RybCtzaGlmdCtEIHkgZWwgcGFxdWV0ZSBbcm94eWdlbjJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yb3h5Z2VuMi92aWduZXR0ZXMvcm94eWdlbjIuaHRtbCkKIAoKPiBOb3MgcGFyYW1vcyBzb2JyZSBsYSBmdW5jacOzbiAtLT4gQ29kZSAtLT4gSW5zZXJ0IHJveHlnZW4gc2tlbGV0b24KCiMjIEluc3RhbGwuCgpVbmEgdmV6IHF1ZSBlbCBwYXF1ZXRlIGZ1bmNpb25hLCBwb2RlbW9zIHVzYXIgYGRldnRvb2xzOjppbnN0YWxsKClgIHBhcmEgaW5zdGFsYXJsYSB5IHV0aWxpemFybGEKCiMgVGVzdGluZwoKIVtdKGltZy90ZXN0dGhhdC5wbmcpCgpFcyBpbXBvcnRhbnRlIGluY29ycG9yYXIgdGVzdHMgYXV0b23DoXRpY29zIGVuIG51ZXN0cm8gcGFxdWV0ZSwgZGUgZm9ybWEgdGFsIHF1ZSBjYWRhIHZleiBxdWUgcmVhbGl6YW1vcyB1bmEgbW9kaWZpY2FjacOzbiwgYXV0b23DoXRpY2FtZW50ZSBsb3MgdGVzdCBjb3Jyb2JvcmVuIHF1ZSBubyBzZSByb21wacOzIG90cmEgcGFydGUgZGVsIHBhcXVldGUuCgpQYXJhIGVzbywgdXRpbGl6YW1vcyBsYSBmdW5jacOzbiBgdXNlX3Rlc3RgIHF1ZSBjcmVhIHVuIG51ZXZvIHNjcmlwdCBlbiBsYSBjYXJwZXRhIHRlc3RzL3Rlc3R0aGF0IGRvbmRlIHBvZGVtb3MgZXNjcmliaXIgbnVlc3Ryb3MgdGVzdHMKCmBgYHIKdXNldGhpczo6dXNlX3Rlc3QoJ2ZiaW5kJykKYGBgCgpQb3IgZWplbXBsbywgcGFyYSBgZmJpbmRgIHBvZHLDrWFtb3MgYXJtYXIgZWwgc2lndWllbnRlIHRlc3QKCmBgYHIKdGVzdF90aGF0KCJmYmluZCB3b3JrcyIsIHsKCiAgZmFjdG9yMSA8LSBmYWN0b3IoYygnb2xhJywnaycsJ2FzZScpKQogIGZhY3RvcjIgPC0gZmFjdG9yKGMoJ2F0cicsICdwZXJybycsICdjdW1iaWEnLCAnY2FqZXRlYWxhJywgJ3Bpb2xhJywgJ2dhdG8nKSkKICBleHBlY3RfZXF1YWwoZmJpbmQoZmFjdG9yMSxmYWN0b3IyKSwKICAgICAgICAgICAgICAgZmFjdG9yKGMoJ29sYScsJ2snLCdhc2UnLCdhdHInLCdwZXJybycsJ2N1bWJpYScsJ2NhamV0ZWFsYScsJ3Bpb2xhJywnZ2F0bycpKSkKfSkKCmBgYAoKQXF1w60gdXRpbGl6YW1vcyBsYSBmdW5jacOzbiBgdGVzdF90aGF0YCBwYXJhIGNvbnN0cnVpciBlbCB0ZXN0LCB5IGRlbnRybyBkZSBlbGxhIGBleHBlY3RfZXF1YWxgIHBhcmEgaW5kaWNhciBxdWUgZWwgcmVzdWx0YWRvIGRlbCBmYmluZCBkZWJlcsOtYSBzZXIgZGljaG8gZmFjdG9yLiAKCkVuIGVsIHNjcmlwdCBkZSB0ZXN0IHRlbmVtb3MgZWwgYm90b24gX3J1biB0ZXN0c18gYXJyaWJhIGEgbGEgZGVyZWNoYSB5IHBvZGVtb3MgZWplY3V0YXJsb3MuIEVsIHJlc3VsdGFkbyBlczoKCiFbXShpbWcvdGVzdC5wbmcpCgpMbyBjdWFsIGluZGljYSBxdWUgZWwgdGVzdCBmdW5jaW9uYSBjb3JyZWN0YW1lbnRlLgoKPiBBaG9yYSBjYWRhIHZleiBxdWUgaGFnYW1vcyB1biBjaGVjaywgdGFtYmnDqW4gZWplY3V0YXLDoSB0b2RvcyBsb3MgdGVzdHMuIAoKIyMgV29ya2Zsb3c6CgohW10oaW1nL3dvcmtmbG93LnBuZykKCgpFbiBlc3RhIGNsYXNlIG9taXRpbW9zIGVsIHRlbWEgZGVsIENoZWNrIGNvdmVyYWdlIHkgbG9zIGNoZWNrcyByZW1vdG9zIHbDrWEgdHJhdmlzICh2ZXIgbm90YXMgb3JpZ2luYWxlcykuCgoKIyBDb21wYXJ0aXIgZWwgcGFxdWV0ZQoKVGVuZXIgdW4gcGFxdWV0ZSBkZSBmdW5jaW9uZXMgcHJvcGlvIHB1ZWRlIHNlcnZpcm5vcyBwYXJhIGV2aXRhciBjb3BpYXIgbGFzIGZ1bmNpb25lcyBlbiBjYWRhIHNjcmlwdCBvIGhhY2VyIHNvdXJjZS4gUGVybyBwcm9iYWJsZW1lbnRlIHNpIGVsIHBhcXVldGUgZXMgw7p0aWwgcGFyYSBub3NvdHJvcywgdGFtYmnDqW4gc2VhIMO6dGlsIHBhcmEgb3RyYXMgcGVyc29uYXMgeSBxdWVycmFtb3MgY29tcGFydGlybG8uCgpQYXJhIGVzbywgdmFtb3MgYSB2ZXIgdHJlcyBmb3JtYXMgZGUgY29tcGFydGlyIG51ZXN0cm8gdHJhYmFqbwoKMS4gUHJveWVjdG8gZW4gZ2l0aHViCjIuIFZpZ25ldHRlcwozLiBwa2dkb3duCjQuIHN1Ym1pdCBhIENSQU4KCgoKIyMgUGFxdWV0ZSBlbiBHaXRodWIKCgpIYXkgbXVjaGFzIG1hbmVyYXMgZGUgY3JlYXIgdW4gcmVwb3NpdG9yaW8gZW4gR2l0aHViLgoKLSBMYSBvcGNpw7NuIG3DoXMgc2ltcGxlIGVzIGNyZWFyIHVuIHJlcG8gdmFjw61vIGRlc2RlIGxhIHDDoWdpbmEsIGNsb25hcmxvIHkgbHVlZ28gcGVnYXIgbG8gcXVlIHlhIHRlbmdhbW9zIGVuIGVzYSBjYXJwZXRhLgotICBUYW1iacOpbiBwb2RlbW9zIHVzYXIgbGFzIGZ1bmNpb25lcyBkZWwgcGFxdWV0ZSBgdXNldGhpc2AgYHVzZV9naXQoKWAgeSBgdXNlX2dpdGh1YigpYC4gCgoKYGBgcgp1c2V0aGlzOjp1c2VfZ2l0KCkKCnVzZXRoaXM6OnVzZV9naXRodWIoKQpgYGAKeSBkZXNkZSBsYSB0ZXJtaW5hbAoKYGBgdGVybWluYWwKZ2l0IHB1c2ggLS1zZXQtdXBzdHJlYW0gb3JpZ2luIG1hc3RlcgpgYGAKCkxhIHByaW1lcmEgZnVuY2nDs24gZGVmaW5lIG51ZXN0cmEgY2FycGV0YSBjb21vIHVuIHJlcG8gZGUgZ2l0LiBMYSBzZWd1bmRhIGNyZWEgdW4gcmVwb3NpdG9yaW8gZW4gZ2l0aHViLiBGaW5hbG1lbnRlIGxhIHRlcmNlcmEgZGVmaW5lIGVsIHVwc3RyZWFtIGRlIG51ZXN0cm8gcmVwbyB5IHB1c2hlYSBlbCBjb250ZW5pZG8gcXVlIHRpZW5lLgoKUGFyYSBxdWUgZXN0byBmdW5jaW9uZSB0ZW5lbW9zIHF1ZSB0ZW5lciBwcmV2aWFtZW50ZSBzaW5jcm9uaXphZGEgbnVlc3RyYSBjb21wdXRhZG9yYSBjb24gdW5hIHNlc2nDs24gZW4gZ2l0aHViLgoKUG9yIGVqZW1wbG8sIHBhcmEgbcOtLCBlc3RvcyBjb21hbmRvcyBjcmVhcm9uIGVsIHNpZ3VpZW50ZSByZXBvOiBbaHR0cHM6Ly9naXRodWIuY29tL0RpZWdvS296L3BxdHBydWViYV0oaHR0cHM6Ly9naXRodWIuY29tL0RpZWdvS296L3BxdHBydWViYSkKCiMjIyBGbHVqbyBkZSB0cmFiYWpvIGVuIGdpdGh1YgoKVW5hIHZleiBxdWUgbnVlc3RybyBwYXF1ZXRlIGVzdGEgZW4gZ2l0aHViLCBkZWJlcsOtYW1vcyBzdWJpciBsYXMgYWN0dWFsaXphY2nDs25lcyBkZSBsYXMgZnVuY2lvbmVzIGNvbjoKCjEuIEFkZAoyLiBDb21taXQKMy4gUHVzaAoKLSBFc3RvIGVzIHPDs2xvIGVsIGZsdWpvIGLDoXNpY28gZGUgZ2l0aHViIHkgaGF5IG11Y2hhcyBtw6FzIGNvc2FzIHF1ZSBzZSBwdWVkZW4gaGFjZXIuIFBlcm8gcGFyYSBlc3RhIGNsYXNlIGxvIHJlc3RyaW5naW1vcyBhIGVzdGUgbcOtbmltby4gRXN0byBsbyBwb2RlbW9zIGhhY2VyIGRlc2RlIGxhIHRlcm1pbmFsIG8gZGVzZGUgZWwgSURFIGRlIFJzdHVkaW8uIAoKLSBVbiBidWVuIGx1Z2FyIHBhcmEgbGVlciBtw6FzIHNvYnJlIEdpdCB5IEdpdGh1YiBjb24gUiBlcyBbaHR0cHM6Ly9oYXBweWdpdHdpdGhyLmNvbS9dKGh0dHBzOi8vaGFwcHlnaXR3aXRoci5jb20vKQoKUGFyYSBxdWUgbGEgbGlicmVyw61hIHRlbmdhIHVuYSBidWVuYSBwcmVzZW50YWNpw7NuLCBkZWJlcsOtYW1vcyBhZ3JlZ2FyIHVuIFJFQURNRS4KCmBgYHIKdXNldGhpczo6dXNlX3JlYWRtZV9ybWQoKQpgYGAKCkx1ZWdvIGRlIG1vZGlmaWNhciBlbCByZWFkbWUsIHRlbmVtb3MgcXVlIHN1YmlybG8gYSBnaXRodWIgbnVldmFtZW50ZSEKCgojIyBWaWduZXR0ZQoKU2kgcXVlcmVtb3MgcXVlIGxhIGdlbnRlIHV0aWxpY2UgbnVlc3RybyBwYXF1ZXRlLCBsbyBtZWpvciBlcyBxdWUgbGEgZG9jdW1lbnRhY2nDs24gaGFnYSBzZW5jaWxsbyBlbnRlbmRlciBxdcOpIGhhY2UgeSBwYXJhIHF1w6kgc2lydmUuIAoKUGFyYSBjcmVhciB1bmEgdmlnbmV0dGUgcG9kZW1vcyB1c2FyIGVsIGNvbWFuZG8KCmBgYHIKdXNldGhpczo6dXNlX3ZpZ25ldHRlKG5hbWUgPSAncHJ1ZWJhX3ZpZ25ldHRlJyx0aXRsZSA9ICdFamVtcGxvIGRlIFZpZ25ldHRlJykKYGBgCgotIE5vIHNlIG9sdmlkZW4gZGUgY29tbWl0ZWFybG8hIQoKCiMjIHBrZ2Rvd24KCiFbXShpbWcvcGtnZG93bi5wbmcpCgoKQWhvcmEgbG8gcXVlIHF1ZXJlbW9zIGVzIHF1ZSBudWVzdHJhIGxpYnJlcsOtYSB0ZW5nYSBzdSBwcm9waWEgcMOhZ2luYSB3ZWIKClBhcmEgZWxsbywgdXNhbW9zIGxhIGxpYnJlcsOtYSBbcGtnZG93bl0oaHR0cHM6Ly9wa2dkb3duLnItbGliLm9yZy8pCgpgYGByCnBrZ2Rvd246OmJ1aWxkX3NpdGUoKQpgYGAKCmhhY2UgdG9kbyBwb3Igbm9zb3Ryb3MhISAKCiFbXShpbWcvbWluZGJsb3duLmdpZil7d2lkdGg9NTAwfQoKRXN0byBjcmVhIHVuYSBjYXJwZXRhIGRvY3MvIGVuIGVsIHJlcG8sIGNvbiB0b2RvcyBsb3MgbWF0ZXJpYWxlcyBkZSBsYSBww6FnaW5hLiAgTG8gcXVlIG5vcyBmYWx0YSBhaG9yYSBlcyBwdWJsaWNhciBsYSBww6FnaW5hIGRlc2RlIGdpdGh1Yi4gUGFyYSBlc28sIHZhbW9zIGEKCjEuIFNldHRpbmdzCjIuIEdpdEh1YiBQYWdlcwozLiBTb3VyY2UgLS0+IG1hc3RlciBicmFuY2ggL2RvY3MgZm9sZGVyCgpQYXJhIG1pLCBlc3RvIGdlbmVyYSBsYSBww6FnaW5hIFtodHRwczovL2RpZWdva296LmdpdGh1Yi5pby9wcXRwcnVlYmEvXShodHRwczovL2RpZWdva296LmdpdGh1Yi5pby9wcXRwcnVlYmEvKQoKCiMjIFN1Ym1pdCBhIENSQU4KCiFbXShpbWcvbWFjYXVsYXlfY3Vsa2luLmpwZyl7d2lkdGg9NTAwfQoKQ3VhbmRvIHVuIHBhcXVldGUgc2UgZW5jdWVudHJhIGVuIENSQU4gZXMgcG9ycXVlIGZ1ZSBkZWJpZGFtZW50ZSByZXZpc2FkbyB5IHNlIHB1ZGUgY29uZmlhciBlbiBzdSBwZXJmb3JtYW5jZS4gRXNvIGltcGxpY2EgcXVlIHBhcmEgcXVlIHBvZGFtb3Mgc3ViaXIgbnVlc3RybyByZXBvc2l0b3JpbyBhIENSQU4gZGViZW1vcyBjdW1wbGlyIHZhcmlvcyByZXF1aXNpdG9zLgoKLSBFbCBhcmNoaXZvIGRlc2NyaXB0aW9uIGRlYmUgZXN0YXIgZGViaWRhbWVudGUgY29tcGxldGFkbyBjb246CiAgLSBUw610dWxvLCAKICAtIE7Dum1lcm8gZGUgdmVyc2nDs24gYWN0dWFsaXphZG8gKHBhcmEgZXNvIHBvZGVtb3MgdXNhciBgIHVzZXRoaXM6OnVzZV92ZXJzaW9uKClgKQogIC0gQXV0b3JlcwogIC0gRGVzY3JpcGNpw7NuCiAgLSBEZXBlbmRlbmNpYXMgKEltcG9ydHM6IHkgbG9zIHBhcXVldGVzIHF1ZSB1c2EgZWwgbnVlc3RybykKICAtIEJ1Z1JlcG9ydHMKICAtIFNpIGxhIGRvY3VtZW50YWNpw7NuIGVzdGEgZW4gZXNwYcOxb2wgZGViZW1vcyBhY2xhcmFyIGNvbiBgTGFuZ3VhZ2U6IGVzYAogIAogICAKLSBMYSBsaWJyZXLDrWEgbm8gZGViZSB0ZW5lciBuaW5nw7puIGVycm9yLCB3YXJuaW5nIG8gbm90ZSBjdWFuZG8gbGEgY2hlcXVlYW1vcy4KLSBBZGVtw6FzLCBwb2RlbW9zIGhhY2VyIGNoZXF1ZW9zIGV4dGVybm9zIGNvbgoKYGBgcgpkZXZ0b29sczo6Y2hlY2tfcmh1YigpCmRldnRvb2xzOjpjaGVja193aW5fZGV2ZWwoKQpgYGAKIVtdKGltZy9zdWJtaXNzaW9ucy5wbmcpCgotIFNpIHRvZGFzIGxhcyBmb3JtYXMgZGUgY2hlcXVlbyBmdW5jaW9uYW4gYmllbiwgcG9kZW1vcyBwZW5zYXIgZW4gc3ViaXJsbyBhIENSQU4uCgotIFBhcmEgZXNvLCBwcmltZXJvIHVzYW1vcyBgdXNldGhpczo6dXNlX2NyYW5fY29tbWVudHMoKWAgcGFyYSBjcmVhciBlbCBhcmNoaXZvIGNvbiBjb21lbnRhcmlvcyBwYXJhIGVsIHJldmlzb3IgZGUgQ1JBTi4gCgotIFNpIGVzIHJlY2hhemFkbywgaW5jb3Jwb3JhbW9zIGxvcyBjb21lbnRhcmlvcyBkZWwgcmV2aXNvciB5IGVuIGVsIGFyY2hpdm8gY3Jhbi1jb21tZW50cy5tZCBhZ3JlZ2Ftb3MgYXJyaWJhCgpgYGAKVGhpcyBpcyBhIHJlc3VibWlzc2lvbi4gQ29tcGFyZWQgdG8gdGhlIGxhc3Qgc3VibWlzc2lvbiwgSQpoYXZlOgoqIEZpcnN0IGNoYW5nZS4KKiBTZWNvbmQgY2hhbmdlLgoqIFRoaXJkIGNoYW5nZQoKLS0tCmBgYAoKCg==