Shiny

¿Qué es Shiny?

Es una librerĂ­a dentro de R que nos permite crear aplicaciones web interactivas utilizando cĂłdigo directamente del lenguaje de programaciĂłn R, sin necesidad de conocimientos avanzados en HTML, JavaScript o CSS. La librerĂ­a facilita el desarrollo de de aplicaciones web, permitiendo a los usuarios interactuar con el cĂłdigo de R en un navegador y mediante elementos de la interfaz de usuario como menĂșs desplegables o controles deslizantes.

Historia

Desde el 31 de Julio, 2012 siendo el primer lanzamiento de Shiny, la invención de Joe Cheng actualmente CTO (Chief Technology Officer) en RStudio, ha ganado popularidad por su facilidad de uso y la potencia de aplicaciones que se pueden construir con dicha librería. Sin embargo, cabe mencionar que Shiny no solo se aplica a R sino que también ha sido creado para su uso con Python desde el 2022.

En los primeros años del desarrollo, Shiny estuvo cerca de convertirse en un framework de Interfaz de Usuario, un UI para R. La idea era que los usuarios pudieran construir interfaces gråficas complejas directamente de R, como si fuera un sistema de diseño visual. El verdadero hallazgo de Shiny estuvo cuando se tomó la decisión de darle un enfoque distinto al uso de una tecnología y dandose cuenta de que el verdadero valor estaba en dar poder a los cientificos de datos para comunicar sus hallazgos de forma interactiva, no en competir con desarrolladores web.

Usos principales:

ÂżCĂłmo instalar?

  1. Abrir R y correr el siguiente cĂłdigo de R

install.packages ("Shiny") 

IMPORTANTE! Shiny viene con otro paquete, llamado bslib en el que se construyen también interfaces gråficas.

Ejemplo de Shiny Apps predefinida

Escribe este cĂłdigo en un script de R:

library(shiny) 
runExample("01_hello") 

Estructura de Shiny

La estructura de Shiny consta de:

A continuaciĂłn, se explican.

- FunciĂłn del objeto de interfaz de usuario

El objeto de interfaz de usuario controla el diseño y la apariencia de la aplicación.

library (shiny) 
library (bslib) 

UI <- page_sidebar ( 
  title = "Anual Sales",      #Titulo 
  sidebar = sidebar( 
    sliderInput(              #Control deslizante 
      inputId = "bins",    
      label = "Histograma",   #NĂșmero de columnas en un histograma 
      min = 2015,             #Valor minimo en el histograma 
      max = 2026,             #Valor mĂĄximo en el histograma 
      value = 2025            #Valor en el que aparece el histograma 
    ) 
  ), 
  plotOutput(outputId = "distPlot") 
) 

- FunciĂłn del servidor

Contiene las instrucciones que la computadora necesita para compilar la aplicaciĂłn.

server <- function (input, output) { 
  output$distPlot <- renderPlot({ 
    x     <- faithful$waiting 
    bins  <- seq(min(x), max(x), length.out = input$bins +1) 
    hist(x, breaks = bins, col = "#007bc2", border = "white", 
         xlab = "Waiting time to next eruption (in mins)", 
         main = "Histogram of waiting times") 

    }) 
} 

-Llamado a la funciĂłn de shinyApp

Crea objetos de aplicaciĂłn Shiny a partir de un par explĂ­cito de interfaz de usuario/servidor.

library(shiny)

shinyApp(ui = ui, server = server)

Ejemplos de uso

Es importante saber que en Shiny podemos crear widgets de control que son de gran utilidad para la interfaz de usuario. Shiny incluye una serie de widgets predefinidos, cada uno creado con una funciĂłn R cuyo nombre es claro. Por decir 2 conocidas, actionbutton que crea un boton de acciĂłn y otra sliderInput que crea una barra deslizante.

A continuaciĂłn se brindan los widgets con su respectivo cĂłdigo (se debe recordar escribir la funciĂłn UI):

-actionButton

    # Buttons
    card(
      card_header("Buttons"),
      actionButton("action", "Action"),
      submitButton("Submit")
    ),
  • CheckboxGroupInput
    # Single checkbox
    card(
      card_header("Single checkbox"),
      checkboxInput("checkbox", "Choice A", value = TRUE)
    ),
  • Checkbox Input
   # Checkbox group
    card(
      card_header("Checkbox group"),
      checkboxGroupInput(
        "checkGroup",
        "Select all that apply",
        choices = list("Choice 1" = 1, "Choice 2" = 2, "Choice 3" = 3),
        selected = 1
      )
    ),
  • dateInput
    card(
      card_header("Date input"),
      dateInput("date", "Select date", value = "2014-01-01")
    ),

-dateRangeInput

    # Date range input
    card(
      card_header("Date range input"),
      dateRangeInput("dates", "Select dates")
    ),

-FileInput

    # File input
    card(
      card_header("File input"),
      fileInput("file", label = NULL)
    ),

-helpText

 # Help text
    card(
      card_header("Help text"),
      helpText(
        "Note: help text isn't a true widget,",
        "but it provides an easy way to add text to",
        "accompany other widgets."
      )
    ),

Esto por mencionar algunas y se deberĂ­an ver asĂ­.

Ahora bien, para agregar un objeto a la interfaz de usuario existen diversas opciones segĂșn se necesite.

  • dataTableOutput -> Crea tablas de datos
  • imageOutput -> Imagenes
  • plotOutput -> Plot
  • textOutput -> text

Finalmente, existen funciones en la interfaz de usuario que le dicen a Shiny donde desplegar el objeto. Lo siguiente es decirle a Shiny como construir el objeto. Para esto, existen las siguientes funciones:

  • renderDataTable -> Crea tablas de datos
  • renderImage -> Imagenes
  • renderPlot -> Plots
  • renderTable -> Data frames, matrix y otras tablas estructuradas

Finalmente, las aplicaciones shiny se pueden facilmente compartiendolas mediante un URL que se escribe asĂ­:

library(shiny)
runUrl( "<the weblink>")
LS0tIA0KdGl0bGU6ICJBcHAgU2hpbnkiIA0Kb3V0cHV0OiBodG1sX25vdGVib29rIA0KLS0tIA0KDQogDQpbU2hpbnldKGh0dHBzOi8vc2hpbnkucG9zaXQuY28vci9nZXRzdGFydGVkL3NoaW55LWJhc2ljcy9sZXNzb24xLykgDQoNCg0KKirCv1F1w6kgZXMgU2hpbnk/KiogDQoNCjxpbWcgc3JjPSAiaHR0cHM6Ly9hdXJpZ2FpdC5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMjQvMDEvc2hpbnktb2ctZmIuanBnIiB3aWR0aD0gIjMwMCIgaGVpZ2h0PSIxOTAiPg0KDQpFcyB1bmEgbGlicmVyw61hIGRlbnRybyBkZSBSIHF1ZSBub3MgcGVybWl0ZSBjcmVhciBhcGxpY2FjaW9uZXMgd2ViIGludGVyYWN0aXZhcyB1dGlsaXphbmRvIGPDs2RpZ28gZGlyZWN0YW1lbnRlIGRlbCBsZW5ndWFqZSBkZSBwcm9ncmFtYWNpw7NuIFIsIHNpbiBuZWNlc2lkYWQgZGUgY29ub2NpbWllbnRvcyBhdmFuemFkb3MgZW4gSFRNTCwgSmF2YVNjcmlwdCBvIENTUy4gIExhIGxpYnJlcsOtYSBmYWNpbGl0YSBlbCBkZXNhcnJvbGxvIGRlIGRlIGFwbGljYWNpb25lcyB3ZWIsIHBlcm1pdGllbmRvIGEgbG9zIHVzdWFyaW9zIGludGVyYWN0dWFyIGNvbiBlbCBjw7NkaWdvIGRlIFIgZW4gdW4gbmF2ZWdhZG9yIHkgbWVkaWFudGUgZWxlbWVudG9zIGRlIGxhIGludGVyZmF6IGRlIHVzdWFyaW8gY29tbyBtZW7DunMgZGVzcGxlZ2FibGVzIG8gY29udHJvbGVzIGRlc2xpemFudGVzLiANCg0KIA0KDQoqKipIaXN0b3JpYSoqKiANCg0KDQpEZXNkZSBlbCAzMSBkZSBKdWxpbywgMjAxMiBzaWVuZG8gZWwgcHJpbWVyIGxhbnphbWllbnRvIGRlIFNoaW55LCBsYSBpbnZlbmNpw7NuIGRlIEpvZSBDaGVuZyBhY3R1YWxtZW50ZSBDVE8gKENoaWVmIFRlY2hub2xvZ3kgT2ZmaWNlcikgZW4gUlN0dWRpbywgaGEgZ2FuYWRvIHBvcHVsYXJpZGFkIHBvciBzdSBmYWNpbGlkYWQgZGUgdXNvIHkgbGEgcG90ZW5jaWEgZGUgYXBsaWNhY2lvbmVzIHF1ZSBzZSBwdWVkZW4gY29uc3RydWlyIGNvbiBkaWNoYSBsaWJyZXLDrWEuICBTaW4gZW1iYXJnbywgY2FiZSBtZW5jaW9uYXIgcXVlIFNoaW55IG5vIHNvbG8gc2UgYXBsaWNhIGEgUiBzaW5vIHF1ZSB0YW1iacOpbiBoYSBzaWRvIGNyZWFkbyBwYXJhIHN1IHVzbyBjb24gUHl0aG9uIGRlc2RlIGVsIDIwMjIuICAgDQoNCg0KRW4gbG9zIHByaW1lcm9zIGHDsW9zIGRlbCBkZXNhcnJvbGxvLCBTaGlueSBlc3R1dm8gY2VyY2EgZGUgY29udmVydGlyc2UgZW4gdW4gZnJhbWV3b3JrIGRlIEludGVyZmF6IGRlIFVzdWFyaW8sIHVuIFVJIHBhcmEgUi4gIExhIGlkZWEgZXJhIHF1ZSBsb3MgdXN1YXJpb3MgcHVkaWVyYW4gY29uc3RydWlyIGludGVyZmFjZXMgZ3LDoWZpY2FzIGNvbXBsZWphcyBkaXJlY3RhbWVudGUgZGUgUiwgY29tbyBzaSBmdWVyYSB1biBzaXN0ZW1hIGRlIGRpc2XDsW8gdmlzdWFsLiAgRWwgdmVyZGFkZXJvIGhhbGxhemdvIGRlIFNoaW55IGVzdHV2byBjdWFuZG8gc2UgdG9tw7MgbGEgZGVjaXNpw7NuIGRlIGRhcmxlIHVuIGVuZm9xdWUgZGlzdGludG8gYWwgdXNvIGRlIHVuYSB0ZWNub2xvZ8OtYSB5IGRhbmRvc2UgY3VlbnRhIGRlIHF1ZSBlbCB2ZXJkYWRlcm8gdmFsb3IgZXN0YWJhIGVuIGRhciBwb2RlciBhIGxvcyBjaWVudGlmaWNvcyBkZSBkYXRvcyBwYXJhIGNvbXVuaWNhciBzdXMgaGFsbGF6Z29zIGRlIGZvcm1hIGludGVyYWN0aXZhLCBubyBlbiBjb21wZXRpciBjb24gZGVzYXJyb2xsYWRvcmVzIHdlYi4gDQoNCiMjIyAgIFVzb3MgcHJpbmNpcGFsZXM6DQoNCi0gQ3JlYSBwYW5lbGVzIHF1ZSBtb25pdG9yZWVuIGluZGljYWRvcmVzIGNsYXZlIGRlIHJlbmRpbWllbnRvIGRlIGFsdG8gbml2ZWwsIGZhY2lsaXRhbmRvIGEgbGEgdmV6IGVsIGFuw6FsaXNpcyBkZXRhbGxhZG8gZGUgbGFzIG3DqXRyaWNhcyBxdWUgcmVxdWllcmVuIG1heW9yIGludmVzdGlnYWNpw7NuLiANCg0KLSBSZWVtcGxhemEgY2llbnRvcyBkZSBww6FnaW5hcyBkZSBQREYgY29uIGFwbGljYWNpb25lcyBpbnRlcmFjdGl2YXMgcXVlIHBlcm1pdGFuIGFsIHVzdWFyaW8gYWNjZWRlciBkaXJlY3RhbWVudGUgYSBsYSBzZWNjacOzbiBkZSByZXN1bHRhZG9zIHF1ZSBsZSBpbnRlcmVzYS4gDQoNCi0gQ29tdW5pY2EgbW9kZWxvcyBjb21wbGVqb3MgYSB1biBww7pibGljbyBubyB0w6ljbmljbyBtZWRpYW50ZSB2aXN1YWxpemFjaW9uZXMgaW5mb3JtYXRpdmFzIHkgYW7DoWxpc2lzIGRlIHNlbnNpYmlsaWRhZCBpbnRlcmFjdGl2b3MuIA0KDQotIE9mcmVjZSBhbsOhbGlzaXMgZGUgZGF0b3MgZGUgYXV0b3NlcnZpY2lvIHBhcmEgZmx1am9zIGRlIHRyYWJham8gY29tdW5lcywgcmVlbXBsYXphbmRvIGxhcyBzb2xpY2l0dWRlcyBwb3IgY29ycmVvIGVsZWN0csOzbmljbyBjb24gdW5hIGFwbGljYWNpw7NuIFNoaW55IHF1ZSBwZXJtaXRlIGEgbG9zIHVzdWFyaW9zIGNhcmdhciBzdXMgcHJvcGlvcyBkYXRvcyB5IHJlYWxpemFyIGFuw6FsaXNpcyBlc3TDoW5kYXIuIFBvbiBhIGRpc3Bvc2ljacOzbiBkZSB1c3VhcmlvcyBzaW4gY29ub2NpbWllbnRvcyBkZSBwcm9ncmFtYWNpw7NuIGFuw6FsaXNpcyBhdmFuemFkb3MgZGUgUi4gDQoNCi0gQ3JlYSBkZW1vc3RyYWNpb25lcyBpbnRlcmFjdGl2YXMgcGFyYSBsYSBlbnNlw7FhbnphIGRlIGNvbmNlcHRvcyBkZSBlc3RhZMOtc3RpY2EgeSBjaWVuY2lhIGRlIGRhdG9zIHF1ZSBwZXJtaXRhbiBhIGxvcyBlc3R1ZGlhbnRlcyBtb2RpZmljYXIgcGFyw6FtZXRyb3MgeSBvYnNlcnZhciBsb3MgZWZlY3RvcyBkZSBlc29zIGNhbWJpb3MgZW4gdW4gYW7DoWxpc2lzLiANCg0KIyMgwr9Dw7NtbyBpbnN0YWxhcj8gDQoNCiogUGFzb3M6IA0KDQoxLiBBYnJpciBSIHkgY29ycmVyIGVsIHNpZ3VpZW50ZSBjw7NkaWdvIGRlIFIgDQoNCmBgYHtyfSANCg0KaW5zdGFsbC5wYWNrYWdlcyAoIlNoaW55IikgDQoNCmBgYCANCg0KDQoqKklNUE9SVEFOVEUhKiogU2hpbnkgdmllbmUgY29uIG90cm8gcGFxdWV0ZSwgbGxhbWFkbyAqKipic2xpYioqKiBlbiBlbCBxdWUgc2UgY29uc3RydXllbiB0YW1iacOpbiBpbnRlcmZhY2VzIGdyw6FmaWNhcy4gDQoNCg0KDQojIyMgRWplbXBsbyBkZSBTaGlueSBBcHBzIHByZWRlZmluaWRhDQoNCkVzY3JpYmUgZXN0ZSBjw7NkaWdvIGVuIHVuIHNjcmlwdCBkZSBSOiANCg0KYGBge3J9IA0KbGlicmFyeShzaGlueSkgDQpydW5FeGFtcGxlKCIwMV9oZWxsbyIpIA0KYGBgIA0KDQogDQojIyBFc3RydWN0dXJhIGRlIFNoaW55IA0KDQpMYSBlc3RydWN0dXJhIGRlIFNoaW55IGNvbnN0YSBkZTogDQoNCiANCg0KLSBVbmEgZnVuY2nDs24gZGVsIHNlcnZpZG9yIA0KDQotIFVuIG9iamV0byBkZSBpbnRlcmZheiBkZSB1c3VhcmlvIA0KDQotIFVuIGxsYW1hZG8gYSBsYSBmdW5jacOzbiBkZSBTaGlubnlhcHAgDQoNCg0KDQpBIGNvbnRpbnVhY2nDs24sIHNlIGV4cGxpY2FuLiANCg0KIA0KDQojIyMgICAgICAtIEZ1bmNpw7NuIGRlbCBvYmpldG8gZGUgaW50ZXJmYXogZGUgdXN1YXJpbyANCg0KRWwgb2JqZXRvIGRlIGludGVyZmF6IGRlIHVzdWFyaW8gY29udHJvbGEgZWwgZGlzZcOxbyB5IGxhIGFwYXJpZW5jaWEgZGUgbGEgYXBsaWNhY2nDs24uIA0KDQoNCmBgYHtyfSANCmxpYnJhcnkgKHNoaW55KSANCmxpYnJhcnkgKGJzbGliKSANCmBgYCANCg0KIA0KDQpgYGB7cn0gDQoNClVJIDwtIHBhZ2Vfc2lkZWJhciAoIA0KICB0aXRsZSA9ICJBbnVhbCBTYWxlcyIsICAgICAgI1RpdHVsbyANCiAgc2lkZWJhciA9IHNpZGViYXIoIA0KICAgIHNsaWRlcklucHV0KCAgICAgICAgICAgICAgI0NvbnRyb2wgZGVzbGl6YW50ZSANCiAgICAgIGlucHV0SWQgPSAiYmlucyIsICAgIA0KICAgICAgbGFiZWwgPSAiSGlzdG9ncmFtYSIsICAgI07Dum1lcm8gZGUgY29sdW1uYXMgZW4gdW4gaGlzdG9ncmFtYSANCiAgICAgIG1pbiA9IDIwMTUsICAgICAgICAgICAgICNWYWxvciBtaW5pbW8gZW4gZWwgaGlzdG9ncmFtYSANCiAgICAgIG1heCA9IDIwMjYsICAgICAgICAgICAgICNWYWxvciBtw6F4aW1vIGVuIGVsIGhpc3RvZ3JhbWEgDQogICAgICB2YWx1ZSA9IDIwMjUgICAgICAgICAgICAjVmFsb3IgZW4gZWwgcXVlIGFwYXJlY2UgZWwgaGlzdG9ncmFtYSANCiAgICApIA0KICApLCANCiAgcGxvdE91dHB1dChvdXRwdXRJZCA9ICJkaXN0UGxvdCIpIA0KKSANCmBgYCANCg0KIA0KDQoNCiMjIyAgICAgICAgLSBGdW5jacOzbiBkZWwgc2Vydmlkb3IgDQoNCkNvbnRpZW5lIGxhcyBpbnN0cnVjY2lvbmVzIHF1ZSBsYSBjb21wdXRhZG9yYSBuZWNlc2l0YSBwYXJhIGNvbXBpbGFyIGxhIGFwbGljYWNpw7NuLg0KDQpgYGB7cn0gDQpzZXJ2ZXIgPC0gZnVuY3Rpb24gKGlucHV0LCBvdXRwdXQpIHsgDQogIG91dHB1dCRkaXN0UGxvdCA8LSByZW5kZXJQbG90KHsgDQogICAgeCAgICAgPC0gZmFpdGhmdWwkd2FpdGluZyANCiAgICBiaW5zICA8LSBzZXEobWluKHgpLCBtYXgoeCksIGxlbmd0aC5vdXQgPSBpbnB1dCRiaW5zICsxKSANCiAgICBoaXN0KHgsIGJyZWFrcyA9IGJpbnMsIGNvbCA9ICIjMDA3YmMyIiwgYm9yZGVyID0gIndoaXRlIiwgDQogICAgICAgICB4bGFiID0gIldhaXRpbmcgdGltZSB0byBuZXh0IGVydXB0aW9uIChpbiBtaW5zKSIsIA0KICAgICAgICAgbWFpbiA9ICJIaXN0b2dyYW0gb2Ygd2FpdGluZyB0aW1lcyIpIA0KDQogICAgfSkgDQp9IA0KYGBgIA0KDQoNCiMjIyAgICAgICAgIC1MbGFtYWRvIGEgbGEgZnVuY2nDs24gZGUgc2hpbnlBcHANCg0KQ3JlYSBvYmpldG9zIGRlIGFwbGljYWNpw7NuIFNoaW55IGEgcGFydGlyIGRlIHVuIHBhciBleHBsw61jaXRvIGRlIGludGVyZmF6IGRlIHVzdWFyaW8vc2Vydmlkb3IuIA0KDQpgYGB7cn0NCmxpYnJhcnkoc2hpbnkpDQoNCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikNCmBgYA0KDQoNCiMjIyAgRWplbXBsb3MgZGUgdXNvDQoNCkVzIGltcG9ydGFudGUgc2FiZXIgcXVlIGVuIFNoaW55IHBvZGVtb3MgY3JlYXIgd2lkZ2V0cyBkZSBjb250cm9sIHF1ZSBzb24gZGUgZ3JhbiB1dGlsaWRhZCBwYXJhIGxhIGludGVyZmF6IGRlIHVzdWFyaW8uICBTaGlueSBpbmNsdXllIHVuYSBzZXJpZSBkZSB3aWRnZXRzIHByZWRlZmluaWRvcywgY2FkYSB1bm8gY3JlYWRvIGNvbiB1bmEgZnVuY2nDs24gUiBjdXlvIG5vbWJyZSBlcyBjbGFyby4gIFBvciBkZWNpciAyIGNvbm9jaWRhcywgKioqYWN0aW9uYnV0dG9uKioqIHF1ZSBjcmVhIHVuIGJvdG9uIGRlIGFjY2nDs24geSBvdHJhICoqKnNsaWRlcklucHV0KioqIHF1ZSBjcmVhIHVuYSBiYXJyYSBkZXNsaXphbnRlLg0KDQpBIGNvbnRpbnVhY2nDs24gc2UgYnJpbmRhbiBsb3Mgd2lkZ2V0cyBjb24gc3UgcmVzcGVjdGl2byBjw7NkaWdvIChzZSBkZWJlIHJlY29yZGFyIGVzY3JpYmlyIGxhIGZ1bmNpw7NuIFVJKToNCg0KLWFjdGlvbkJ1dHRvbg0KDQpgYGB7cn0NCiAgICAjIEJ1dHRvbnMNCiAgICBjYXJkKA0KICAgICAgY2FyZF9oZWFkZXIoIkJ1dHRvbnMiKSwNCiAgICAgIGFjdGlvbkJ1dHRvbigiYWN0aW9uIiwgIkFjdGlvbiIpLA0KICAgICAgc3VibWl0QnV0dG9uKCJTdWJtaXQiKQ0KICAgICksDQpgYGANCg0KLSBDaGVja2JveEdyb3VwSW5wdXQNCg0KYGBge3J9DQogICAgIyBTaW5nbGUgY2hlY2tib3gNCiAgICBjYXJkKA0KICAgICAgY2FyZF9oZWFkZXIoIlNpbmdsZSBjaGVja2JveCIpLA0KICAgICAgY2hlY2tib3hJbnB1dCgiY2hlY2tib3giLCAiQ2hvaWNlIEEiLCB2YWx1ZSA9IFRSVUUpDQogICAgKSwNCmBgYA0KDQotIENoZWNrYm94IElucHV0DQoNCmBgYHtyfQ0KICAgIyBDaGVja2JveCBncm91cA0KICAgIGNhcmQoDQogICAgICBjYXJkX2hlYWRlcigiQ2hlY2tib3ggZ3JvdXAiKSwNCiAgICAgIGNoZWNrYm94R3JvdXBJbnB1dCgNCiAgICAgICAgImNoZWNrR3JvdXAiLA0KICAgICAgICAiU2VsZWN0IGFsbCB0aGF0IGFwcGx5IiwNCiAgICAgICAgY2hvaWNlcyA9IGxpc3QoIkNob2ljZSAxIiA9IDEsICJDaG9pY2UgMiIgPSAyLCAiQ2hvaWNlIDMiID0gMyksDQogICAgICAgIHNlbGVjdGVkID0gMQ0KICAgICAgKQ0KICAgICksDQpgYGANCg0KLSBkYXRlSW5wdXQNCg0KYGBge3J9DQogICAgY2FyZCgNCiAgICAgIGNhcmRfaGVhZGVyKCJEYXRlIGlucHV0IiksDQogICAgICBkYXRlSW5wdXQoImRhdGUiLCAiU2VsZWN0IGRhdGUiLCB2YWx1ZSA9ICIyMDE0LTAxLTAxIikNCiAgICApLA0KYGBgDQoNCi1kYXRlUmFuZ2VJbnB1dA0KDQpgYGB7cn0NCiAgICAjIERhdGUgcmFuZ2UgaW5wdXQNCiAgICBjYXJkKA0KICAgICAgY2FyZF9oZWFkZXIoIkRhdGUgcmFuZ2UgaW5wdXQiKSwNCiAgICAgIGRhdGVSYW5nZUlucHV0KCJkYXRlcyIsICJTZWxlY3QgZGF0ZXMiKQ0KICAgICksDQpgYGANCg0KLUZpbGVJbnB1dA0KYGBge3J9DQogICAgIyBGaWxlIGlucHV0DQogICAgY2FyZCgNCiAgICAgIGNhcmRfaGVhZGVyKCJGaWxlIGlucHV0IiksDQogICAgICBmaWxlSW5wdXQoImZpbGUiLCBsYWJlbCA9IE5VTEwpDQogICAgKSwNCmBgYA0KDQotaGVscFRleHQNCg0KYGBge3J9DQogIyBIZWxwIHRleHQNCiAgICBjYXJkKA0KICAgICAgY2FyZF9oZWFkZXIoIkhlbHAgdGV4dCIpLA0KICAgICAgaGVscFRleHQoDQogICAgICAgICJOb3RlOiBoZWxwIHRleHQgaXNuJ3QgYSB0cnVlIHdpZGdldCwiLA0KICAgICAgICAiYnV0IGl0IHByb3ZpZGVzIGFuIGVhc3kgd2F5IHRvIGFkZCB0ZXh0IHRvIiwNCiAgICAgICAgImFjY29tcGFueSBvdGhlciB3aWRnZXRzLiINCiAgICAgICkNCiAgICApLA0KYGBgDQoNCkVzdG8gcG9yIG1lbmNpb25hciBhbGd1bmFzIHkgc2UgZGViZXLDrWFuIHZlciBhc8OtLg0KDQohW10oaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dldHN0YXJ0ZWQvc2hpbnktYmFzaWNzL2xlc3NvbjMvaW1hZ2VzL3dpZGdldHMtZ2FsbGVyeS5wbmcpDQogDQogDQogDQogDQpBaG9yYSBiaWVuLCBwYXJhIGFncmVnYXIgdW4gb2JqZXRvIGEgbGEgaW50ZXJmYXogZGUgdXN1YXJpbyBleGlzdGVuIGRpdmVyc2FzIG9wY2lvbmVzIHNlZ8O6biBzZSBuZWNlc2l0ZS4NCg0KLSBkYXRhVGFibGVPdXRwdXQgICAgIC0+ICAgICAgQ3JlYSB0YWJsYXMgZGUgZGF0b3MNCi0gaW1hZ2VPdXRwdXQgICAgICAgICAtPiAgICAgIEltYWdlbmVzDQotIHBsb3RPdXRwdXQgICAgICAgICAgLT4gICAgICBQbG90DQotIHRleHRPdXRwdXQgICAgICAgICAgLT4gICAgICB0ZXh0DQoNCg0KIEZpbmFsbWVudGUsIGV4aXN0ZW4gZnVuY2lvbmVzIGVuIGxhIGludGVyZmF6IGRlIHVzdWFyaW8gcXVlIGxlIGRpY2VuIGEgU2hpbnkgZG9uZGUgZGVzcGxlZ2FyIGVsIG9iamV0by4gIExvIHNpZ3VpZW50ZSBlcyBkZWNpcmxlIGEgU2hpbnkgY29tbyBjb25zdHJ1aXIgZWwgb2JqZXRvLiAgUGFyYSBlc3RvLCBleGlzdGVuIGxhcyBzaWd1aWVudGVzIGZ1bmNpb25lczoNCiANCiAtIHJlbmRlckRhdGFUYWJsZSAgICAtPiAgICAgIENyZWEgdGFibGFzIGRlIGRhdG9zDQogLSByZW5kZXJJbWFnZSAgICAgICAgLT4gICAgICBJbWFnZW5lcw0KIC0gcmVuZGVyUGxvdCAgICAgICAgIC0+ICAgICAgUGxvdHMNCiAtIHJlbmRlclRhYmxlICAgICAgICAtPiAgICAgIERhdGEgZnJhbWVzLCBtYXRyaXggeSBvdHJhcyB0YWJsYXMgZXN0cnVjdHVyYWRhcw0KIA0KIA0KIA0KIA0KIEZpbmFsbWVudGUsIGxhcyBhcGxpY2FjaW9uZXMgc2hpbnkgc2UgcHVlZGVuIGZhY2lsbWVudGUgY29tcGFydGllbmRvbGFzIG1lZGlhbnRlIHVuIFVSTCBxdWUgc2UgZXNjcmliZSBhc8OtOg0KYGBge3J9DQpsaWJyYXJ5KHNoaW55KQ0KcnVuVXJsKCAiPHRoZSB3ZWJsaW5rPiIpDQpgYGANCg0KIA0KDQogDQo=