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")
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
- Proyecto en github
- Vignettes
- pkgdown
- 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:
- Add
- Commit
- 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
- Settings
- GitHub Pages
- 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==