In this guided practice we will build two shiny apps. You can find
the app.R files in the folders 2_gapminder and
2_Old_Faithful_Geyser.
Old Faithful Geyser
Our first shiny app is inspired on the example provided by https://shiny.posit.co/r/gallery/start-simple/faithful/.
We will be displaying eruption data for the Old Faithful geyser.
Let’s start by building the basic structure of our app:
library(shiny)
# Define the ui object and it's basic layout, as well as the title
ui <- fluidPage(
# Application title
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
# Sidebar
sidebarPanel(
),
# Main panel
mainPanel(
)
)
)
# Define server object
server <- function(input, output) {
}
# Combine ui and server
shinyApp(ui = ui, server = server)
You should be able to run the app and see the title, as well as the
empty panels.
We can now proceed to design our ui by defining our
input and output. Our app will display a histogram, allowing users to
determine the number of bars (or bins) on the visualization. We
therefore need to define a numeric input (where the user will tell us
the desired number of bars) and a plot output (our histogram). If you
are not familiar with histograms, you can use ?hist()
to
find out more about the function we will be using to draw our plot.
In this case, we will employ the sliderInput, a frequently used
widget that enables users to choose a number within a defined interval
(in this case, numbers between 1 and 50). The default value will be set
to 30.
library(shiny)
# Develop the ui
ui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
#our input
sliderInput(inputId = "bins",
label ="Number of bins:",
min = 1,
max = 50,
value = 30)
),
mainPanel(
#our output
plotOutput(outputId="distPlot")
)
)
)
server <- function(input, output) {
}
shinyApp(ui = ui, server = server)
Note that, even although the app may not yet fulfill its purpose, we
can now interact with the input widget.
Let’s make the app actually do it’s job by designing the server.
Remember we will need to refer to the ids we defined in the ui
(distPlot
fot the output and bins
for the
input).
Given our intention to generate a plot output, the suitable render
function is renderPlot()
. We can call this toy data set
using faithful; in this case, we are interested on its second
column, which contains information on eruptions. In order to generate a
plot with the number of columns the user will decide, we will include
this input in the function defining the histogram breaks.
library(shiny)
ui <- fluidPage(
titlePanel("Old Faithful Geyser Data"),
sidebarLayout(
sidebarPanel(
sliderInput(inputId = "bins",
label ="Number of bins:",
min = 1,
max = 50,
value = 30)
),
mainPanel(
plotOutput(outputId="distPlot")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$distPlot <- renderPlot({
# generate bins based on input$bins from ui
x <- faithful[, 2]
bins <- seq(min(x), max(x), length.out = input$bins + 1)
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}
shinyApp(ui = ui, server = server)
We are done! Let’s move on to a more complex app.
gapminder
Our second shiny app is inspired on the example provided by https://akshi8.shinyapps.io/Gapminder/
In this case, we will be exploring the population, life-expectancy
and GDP per capita for different countries over the years. Users can
select the indicator to compare, choose specific countries, and specify
the years they wish to examine.
We will include plots and a table, and we will also organize the
information in our app using tabs.
Let’s start by building the basic structure of our app:
library(tidyverse)
library(shiny)
library(gapminder)
library(countrycode)
library(plotly)
library(scales)
library(DT)
#our dataset, imported from the gapminder package:
gapminder <- gapminder
#some preprocessing: as Kuwuait will break the scale for some years, let's remove it from the database
gapminder <- gapminder %>%
filter(country!='Kuwait')
#### our ui
ui <- fluidPage(
titlePanel("Gapminder Data"),
tabsetPanel(
tabPanel(title = 'time series',
#a layout option: sidebar and main panel
sidebarLayout(
sidebarPanel(
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plot"),
tabPanel("Table")
)
)
)
),
tabPanel(title = 'distribution by year',
#a different option: using a well panel
wellPanel(
)
)
)
)
#### Server
server <- function(input, output) {
}
shinyApp(ui = ui, server = server)
You should be able to run the app and view the app’s title, along
with the two primary tabs and the two sub-tabs nested within the first
main tab.
We can now proceed to design our ui by defining our
input and output, as well as subtitles explaining more clearly the
content of each tab (using h3()
). In the first main tab,
our app will display an option to choose the country (note that in this
case the user can select multiple options; this becomes explicitly clear
when defining multiple = TRUE
) and the variable to display
(in this case a single option; this becomes explicitly clear when
stating multiple = FALSE
).
On the other hand, in our second tab the user will have the option of
selecting the year of the data. By setting animate = TRUE
,
we provide users with the option to view an animation that automatically
cycles through all available years in succession by pressing on the play
button at the bottom right corner of the widget.
While we will determine our outputs (and their ids!) at this stage,
we won’t be able to visualize them until after designing the app’s
server.
library(tidyverse)
library(shiny)
library(gapminder)
library(countrycode)
library(plotly)
library(scales)
library(DT)
gapminder <- gapminder
gapminder <- gapminder %>%
filter(country!='Kuwait')
#### ui
ui <- fluidPage(
titlePanel("Gapminder Data"),
tabsetPanel(
tabPanel(title = 'time series',
sidebarLayout(
sidebarPanel(
selectizeInput(inputId ='countries',
label='Choose the countries',
choices = unique(gapminder$country),
multiple = TRUE,
selected = "United States") ,
selectizeInput(inputId ='inputvariable',
label='Choose the variable to display',
choices = c("lifeExp","pop","gdpPercap"),
multiple = FALSE,
selected = "lifeExp")
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plot",
h3("Time series plot"),
plotlyOutput("plot_1")),
tabPanel("Table",
h3("Time series table"),
dataTableOutput("table"))
)
)
)
),
tabPanel(title = 'distribution by year',
wellPanel(
sliderInput(inputId ='yr',
label='Choose the year',
min = min(gapminder$year),
max = max(gapminder$year),
value = min(gapminder$year),
step = 5,
animate = TRUE)
),
h3("Distribution by year of life-expectancy and GDP per capita"),
plotlyOutput("plot_2")
)
)
)
#### Server
server <- function(input, output) {
}
shinyApp(ui = ui, server = server)
Let’s make the app actually do it’s job by designing the server:
library(tidyverse)
library(shiny)
library(gapminder)
library(countrycode)
library(plotly)
library(scales)
library(DT)
gapminder <- gapminder
gapminder <- gapminder %>%
filter(country!='Kuwait')
#one of the options when designing a server is to predefine functions our server will draw on when producing our outputs:
plot_1 <- function(countries, var){
plt <- gapminder %>%
pivot_longer(lifeExp:gdpPercap) %>%
filter(country %in% countries, name==var) %>%
ggplot(aes(year, value, color = continent,group = country))+
geom_line()+
geom_point()
ggplotly(plt)
}
plot_2 <- function(yr){
plt <- gapminder %>%
filter(year==yr) %>%
ggplot(aes(gdpPercap, lifeExp, color=continent,size=pop,
text = paste(country,
'<br>GDP per capita: ',number(gdpPercap, 1),
'<br>Population: ', number(pop/10000,1),'M'))) +
geom_point()+
theme_minimal()
ggplotly(plt, tooltip = 'text')
}
#### ui
ui <- fluidPage(
titlePanel("Gapminder Data"),
tabsetPanel(
tabPanel(title = 'time series',
sidebarLayout(
sidebarPanel(
selectizeInput(inputId ='countries',
label='Choose the countries',
choices = unique(gapminder$country),
multiple = TRUE,
selected = "United States") ,
selectizeInput(inputId ='inputvariable',
label='Choose the variable to display',
choices = c("lifeExp","pop","gdpPercap"),
multiple = FALSE,
selected = "lifeExp")
),
mainPanel(
tabsetPanel(type = "tabs",
tabPanel("Plot",
h3("Time series plot"),
plotlyOutput("plot_1")),
tabPanel("Table",
h3("Time series table"),
dataTableOutput("table"))
)
)
)
),
tabPanel(title = 'distribution by year',
wellPanel(
sliderInput(inputId ='yr',
label='Choose the year',
min = min(gapminder$year),
max = max(gapminder$year),
value = min(gapminder$year),
step = 5,
animate = TRUE)
),
h3("Distribution by year of life-expectancy and GDP per capita"),
plotlyOutput("plot_2")
)
)
)
#### Server
server <- function(input, output) {
## first plot
output$plot_1 <- renderPlotly({
plot_1(countries = input$countries, var = input$inputvariable)
})
## table
output$table <- renderDataTable({
gapminder %>%
filter(country %in% input$countries) %>%
select(country,continent, year, input$inputvariable )
},
extensions = 'Buttons',
options = list(
paging = TRUE,
searching = TRUE,
fixedColumns = TRUE,
autoWidth = TRUE,
ordering = TRUE,
dom = 'tB',
buttons = c('copy', 'csv', 'excel')
),
class = "display")
## second plot
output$plot_2 <- renderPlotly({ plot_2(yr = input$yr)})
}
shinyApp(ui = ui, server = server)
Note that there should be a correspondence between render functions
and the type of output (plotlyOutput
and
renderPlotly
, dataTableOutput
and
renderDataTable
). Each output will be defined using its
unique ID, and a render function will be applied, invoking the relevant
inputs (of course, also through their ids).
Note that plotly
visualizations offer users even more
freedom to explore our data as they come equipped with a wide range of
functionalities. These include the ability to zoom in and examine
specific portions of the visualization, or filter a continent of
interest by simply clicking on the legends.
Moreover, within renderDataTable
, we’ve incorporated
additional arguments that enable users to copy the table information or
export it in CSV or Excel formats. This allows users to utilize the data
for creating their own visualizations.
Feel free to add new elements or improve those already in the app to
practice!
LS0tCnRpdGxlOiAiU2hpbnkgQXBwcyIKc3VidGl0bGU6ICJHdWlkZWQgcHJhY3RpY2UiCmF1dGhvcjogIiIKZGF0ZTogIiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSW4gdGhpcyBndWlkZWQgcHJhY3RpY2Ugd2Ugd2lsbCBidWlsZCB0d28gc2hpbnkgYXBwcy4gWW91IGNhbiBmaW5kIHRoZSBhcHAuUiBmaWxlcyBpbiB0aGUgZm9sZGVycyAyX2dhcG1pbmRlciBhbmQgMl9PbGRfRmFpdGhmdWxfR2V5c2VyLgoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRkFMU0Usd2FybmluZyA9IEZBTFNFKQpgYGAKCiMjIE9sZCBGYWl0aGZ1bCBHZXlzZXIKCk91ciBmaXJzdCBzaGlueSBhcHAgaXMgaW5zcGlyZWQgb24gdGhlIGV4YW1wbGUgcHJvdmlkZWQgYnkgaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dhbGxlcnkvc3RhcnQtc2ltcGxlL2ZhaXRoZnVsLy4gV2Ugd2lsbCBiZSBkaXNwbGF5aW5nIGVydXB0aW9uIGRhdGEgZm9yIHRoZSBPbGQgRmFpdGhmdWwgZ2V5c2VyLgoKTGV0J3Mgc3RhcnQgYnkgYnVpbGRpbmcgdGhlIGJhc2ljIHN0cnVjdHVyZSBvZiBvdXIgYXBwOgoKYGBge3IsZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpsaWJyYXJ5KHNoaW55KQoKIyBEZWZpbmUgdGhlIHVpIG9iamVjdCBhbmQgaXQncyBiYXNpYyBsYXlvdXQsIGFzIHdlbGwgYXMgdGhlIHRpdGxlCnVpIDwtIGZsdWlkUGFnZSgKICAKICAjIEFwcGxpY2F0aW9uIHRpdGxlCiAgdGl0bGVQYW5lbCgiT2xkIEZhaXRoZnVsIEdleXNlciBEYXRhIiksCiAgc2lkZWJhckxheW91dCgKICAgICMgU2lkZWJhciAKICAgIHNpZGViYXJQYW5lbCgKICAgICAgCiAgICApLAogICAgIyBNYWluIHBhbmVsCiAgICBtYWluUGFuZWwoCiAgICAgIAogICAgKQogICkKKQoKIyBEZWZpbmUgc2VydmVyIG9iamVjdApzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIAogIAp9CgojIENvbWJpbmUgdWkgYW5kIHNlcnZlciAKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCllvdSBzaG91bGQgYmUgYWJsZSB0byBydW4gdGhlIGFwcCBhbmQgc2VlIHRoZSB0aXRsZSwgYXMgd2VsbCBhcyB0aGUgZW1wdHkgcGFuZWxzLgoKV2UgY2FuIG5vdyBwcm9jZWVkIHRvIGRlc2lnbiBvdXIgKip1aSoqIGJ5IGRlZmluaW5nIG91ciBpbnB1dCBhbmQgb3V0cHV0LiAKT3VyIGFwcCB3aWxsIGRpc3BsYXkgYSBoaXN0b2dyYW0sIGFsbG93aW5nIHVzZXJzIHRvIGRldGVybWluZSB0aGUgbnVtYmVyIG9mIGJhcnMgKG9yIGJpbnMpIG9uIHRoZSB2aXN1YWxpemF0aW9uLiBXZSB0aGVyZWZvcmUgbmVlZCB0byBkZWZpbmUgYSBudW1lcmljIGlucHV0ICh3aGVyZSB0aGUgdXNlciB3aWxsIHRlbGwgdXMgdGhlIGRlc2lyZWQgbnVtYmVyIG9mIGJhcnMpIGFuZCBhIHBsb3Qgb3V0cHV0IChvdXIgaGlzdG9ncmFtKS4gSWYgeW91IGFyZSBub3QgZmFtaWxpYXIgd2l0aCBoaXN0b2dyYW1zLCB5b3UgY2FuIHVzZSBgP2hpc3QoKWAgdG8gZmluZCBvdXQgbW9yZSBhYm91dCB0aGUgZnVuY3Rpb24gd2Ugd2lsbCBiZSB1c2luZyB0byBkcmF3IG91ciBwbG90LgoKSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIGVtcGxveSB0aGUgc2xpZGVySW5wdXQsIGEgZnJlcXVlbnRseSB1c2VkIHdpZGdldCB0aGF0IGVuYWJsZXMgdXNlcnMgdG8gY2hvb3NlIGEgbnVtYmVyIHdpdGhpbiBhIGRlZmluZWQgaW50ZXJ2YWwgKGluIHRoaXMgY2FzZSwgbnVtYmVycyBiZXR3ZWVuIDEgYW5kIDUwKS4gVGhlIGRlZmF1bHQgdmFsdWUgd2lsbCBiZSBzZXQgdG8gMzAuCgpgYGB7cixldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmxpYnJhcnkoc2hpbnkpCgojIERldmVsb3AgdGhlIHVpIAp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiT2xkIEZhaXRoZnVsIEdleXNlciBEYXRhIiksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgI291ciBpbnB1dAogICAgICBzbGlkZXJJbnB1dChpbnB1dElkID0gImJpbnMiLAogICAgICAgICAgICAgICAgICBsYWJlbCA9Ik51bWJlciBvZiBiaW5zOiIsCiAgICAgICAgICAgICAgICAgIG1pbiA9IDEsCiAgICAgICAgICAgICAgICAgIG1heCA9IDUwLAogICAgICAgICAgICAgICAgICB2YWx1ZSA9IDMwKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgI291ciBvdXRwdXQKICAgICAgcGxvdE91dHB1dChvdXRwdXRJZD0iZGlzdFBsb3QiKQogICAgKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICAKICAKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCk5vdGUgdGhhdCwgZXZlbiBhbHRob3VnaCB0aGUgYXBwIG1heSBub3QgeWV0IGZ1bGZpbGwgaXRzIHB1cnBvc2UsIHdlIGNhbiBub3cgaW50ZXJhY3Qgd2l0aCB0aGUgaW5wdXQgd2lkZ2V0LgoKTGV0J3MgbWFrZSB0aGUgYXBwIGFjdHVhbGx5IGRvIGl0J3Mgam9iIGJ5IGRlc2lnbmluZyB0aGUgc2VydmVyLiBSZW1lbWJlciB3ZSB3aWxsIG5lZWQgdG8gcmVmZXIgdG8gdGhlIGlkcyB3ZSBkZWZpbmVkIGluIHRoZSB1aSAoYGRpc3RQbG90YCBmb3QgdGhlIG91dHB1dCBhbmQgYGJpbnNgIGZvciB0aGUgaW5wdXQpLgoKR2l2ZW4gb3VyIGludGVudGlvbiB0byBnZW5lcmF0ZSBhIHBsb3Qgb3V0cHV0LCB0aGUgc3VpdGFibGUgcmVuZGVyIGZ1bmN0aW9uIGlzIGByZW5kZXJQbG90KClgLiBXZSBjYW4gY2FsbCB0aGlzIHRveSBkYXRhIHNldCB1c2luZyAqZmFpdGhmdWwqOyBpbiB0aGlzIGNhc2UsIHdlIGFyZSBpbnRlcmVzdGVkIG9uIGl0cyBzZWNvbmQgY29sdW1uLCB3aGljaCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiBlcnVwdGlvbnMuIEluIG9yZGVyIHRvIGdlbmVyYXRlIGEgcGxvdCB3aXRoIHRoZSBudW1iZXIgb2YgY29sdW1ucyB0aGUgdXNlciB3aWxsIGRlY2lkZSwgd2Ugd2lsbCBpbmNsdWRlIHRoaXMgaW5wdXQgaW4gdGhlIGZ1bmN0aW9uIGRlZmluaW5nIHRoZSBoaXN0b2dyYW0gYnJlYWtzLgoKYGBge3IsZXZhbD1GQUxTRSwgaW5jbHVkZT1UUlVFfQpsaWJyYXJ5KHNoaW55KQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIk9sZCBGYWl0aGZ1bCBHZXlzZXIgRGF0YSIpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIHNsaWRlcklucHV0KGlucHV0SWQgPSAiYmlucyIsCiAgICAgICAgICAgICAgICAgIGxhYmVsID0iTnVtYmVyIG9mIGJpbnM6IiwKICAgICAgICAgICAgICAgICAgbWluID0gMSwKICAgICAgICAgICAgICAgICAgbWF4ID0gNTAsCiAgICAgICAgICAgICAgICAgIHZhbHVlID0gMzApCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICBwbG90T3V0cHV0KG91dHB1dElkPSJkaXN0UGxvdCIpCiAgICApCiAgKQopCgojIERlZmluZSBzZXJ2ZXIgbG9naWMgcmVxdWlyZWQgdG8gZHJhdyBhIGhpc3RvZ3JhbQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIG91dHB1dCRkaXN0UGxvdCA8LSByZW5kZXJQbG90KHsKICAgICMgZ2VuZXJhdGUgYmlucyBiYXNlZCBvbiBpbnB1dCRiaW5zIGZyb20gdWkKICAgIHggICAgPC0gZmFpdGhmdWxbLCAyXQogICAgYmlucyA8LSBzZXEobWluKHgpLCBtYXgoeCksIGxlbmd0aC5vdXQgPSBpbnB1dCRiaW5zICsgMSkKICAgIAogICAgIyBkcmF3IHRoZSBoaXN0b2dyYW0gd2l0aCB0aGUgc3BlY2lmaWVkIG51bWJlciBvZiBiaW5zCiAgICBoaXN0KHgsIGJyZWFrcyA9IGJpbnMsIGNvbCA9ICdkYXJrZ3JheScsIGJvcmRlciA9ICd3aGl0ZScpCiAgfSkKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCldlIGFyZSBkb25lISBMZXQncyBtb3ZlIG9uIHRvIGEgbW9yZSBjb21wbGV4IGFwcC4KCiMjIGdhcG1pbmRlcgoKT3VyIHNlY29uZCBzaGlueSBhcHAgaXMgaW5zcGlyZWQgb24gdGhlIGV4YW1wbGUgcHJvdmlkZWQgYnkgaHR0cHM6Ly9ha3NoaTguc2hpbnlhcHBzLmlvL0dhcG1pbmRlci8KCkluIHRoaXMgY2FzZSwgd2Ugd2lsbCBiZSBleHBsb3JpbmcgdGhlIHBvcHVsYXRpb24sIGxpZmUtZXhwZWN0YW5jeSBhbmQgR0RQIHBlciBjYXBpdGEgZm9yIGRpZmZlcmVudCBjb3VudHJpZXMgb3ZlciB0aGUgeWVhcnMuIFVzZXJzIGNhbiBzZWxlY3QgdGhlIGluZGljYXRvciB0byBjb21wYXJlLCBjaG9vc2Ugc3BlY2lmaWMgY291bnRyaWVzLCBhbmQgc3BlY2lmeSB0aGUgeWVhcnMgdGhleSB3aXNoIHRvIGV4YW1pbmUuCgpXZSB3aWxsIGluY2x1ZGUgcGxvdHMgYW5kIGEgdGFibGUsIGFuZCB3ZSB3aWxsIGFsc28gb3JnYW5pemUgdGhlIGluZm9ybWF0aW9uIGluIG91ciBhcHAgdXNpbmcgdGFicy4KCkxldCdzIHN0YXJ0IGJ5IGJ1aWxkaW5nIHRoZSBiYXNpYyBzdHJ1Y3R1cmUgb2Ygb3VyIGFwcDoKCmBgYHtyLGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZ2FwbWluZGVyKQpsaWJyYXJ5KGNvdW50cnljb2RlKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoRFQpCgojb3VyIGRhdGFzZXQsIGltcG9ydGVkIGZyb20gdGhlIGdhcG1pbmRlciBwYWNrYWdlOgoKZ2FwbWluZGVyIDwtIGdhcG1pbmRlcgoKI3NvbWUgcHJlcHJvY2Vzc2luZzogYXMgS3V3dWFpdCB3aWxsIGJyZWFrIHRoZSBzY2FsZSBmb3Igc29tZSB5ZWFycywgbGV0J3MgcmVtb3ZlIGl0IGZyb20gdGhlIGRhdGFiYXNlCgpnYXBtaW5kZXIgPC0gZ2FwbWluZGVyICU+JSAKICBmaWx0ZXIoY291bnRyeSE9J0t1d2FpdCcpCgojIyMjIG91ciB1aQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIkdhcG1pbmRlciBEYXRhIiksCiAgdGFic2V0UGFuZWwoCiAgICB0YWJQYW5lbCh0aXRsZSA9ICd0aW1lIHNlcmllcycsCiAgICAgICAgICAgICAjYSBsYXlvdXQgb3B0aW9uOiBzaWRlYmFyIGFuZCBtYWluIHBhbmVsCiAgICAgICAgICAgICBzaWRlYmFyTGF5b3V0KAogICAgICAgICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgIG1haW5QYW5lbCgKICAgICAgICAgICAgICAgICB0YWJzZXRQYW5lbCh0eXBlID0gInRhYnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJQbG90IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJUYWJsZSIpCiAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICApCiAgICAgICAgICAgICApCiAgICApLAogICAgdGFiUGFuZWwodGl0bGUgPSAnZGlzdHJpYnV0aW9uIGJ5IHllYXInLAogICAgICAgICAgICAgI2EgZGlmZmVyZW50IG9wdGlvbjogdXNpbmcgYSB3ZWxsIHBhbmVsCiAgICAgICAgICAgICB3ZWxsUGFuZWwoCiAgICAgICAgICAgICAgIAogICAgICAgICAgICAgKQogICAgKQogICkKKQoKCiMjIyMgU2VydmVyCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIAp9CgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAoKWW91IHNob3VsZCBiZSBhYmxlIHRvIHJ1biB0aGUgYXBwIGFuZCB2aWV3IHRoZSBhcHAncyB0aXRsZSwgYWxvbmcgd2l0aCB0aGUgdHdvIHByaW1hcnkgdGFicyBhbmQgdGhlIHR3byBzdWItdGFicyBuZXN0ZWQgd2l0aGluIHRoZSBmaXJzdCBtYWluIHRhYi4KCldlIGNhbiBub3cgcHJvY2VlZCB0byBkZXNpZ24gb3VyICoqdWkqKiBieSBkZWZpbmluZyBvdXIgaW5wdXQgYW5kIG91dHB1dCwgYXMgd2VsbCBhcyBzdWJ0aXRsZXMgZXhwbGFpbmluZyBtb3JlIGNsZWFybHkgdGhlIGNvbnRlbnQgb2YgZWFjaCB0YWIgKHVzaW5nIGBoMygpYCkuIEluIHRoZSBmaXJzdCBtYWluIHRhYiwgb3VyIGFwcCB3aWxsIGRpc3BsYXkgYW4gb3B0aW9uIHRvIGNob29zZSB0aGUgY291bnRyeSAobm90ZSB0aGF0IGluIHRoaXMgY2FzZSB0aGUgdXNlciBjYW4gc2VsZWN0IG11bHRpcGxlIG9wdGlvbnM7IHRoaXMgYmVjb21lcyBleHBsaWNpdGx5IGNsZWFyIHdoZW4gZGVmaW5pbmcgYG11bHRpcGxlID0gVFJVRWApIGFuZCB0aGUgdmFyaWFibGUgdG8gZGlzcGxheSAoaW4gdGhpcyBjYXNlIGEgc2luZ2xlIG9wdGlvbjsgdGhpcyBiZWNvbWVzIGV4cGxpY2l0bHkgY2xlYXIgd2hlbiBzdGF0aW5nIGBtdWx0aXBsZSA9IEZBTFNFYCkuIAoKT24gdGhlIG90aGVyIGhhbmQsIGluIG91ciBzZWNvbmQgdGFiIHRoZSB1c2VyIHdpbGwgaGF2ZSB0aGUgb3B0aW9uIG9mIHNlbGVjdGluZyB0aGUgeWVhciBvZiB0aGUgZGF0YS4gQnkgc2V0dGluZyBgYW5pbWF0ZSA9IFRSVUVgLCAgd2UgcHJvdmlkZSB1c2VycyB3aXRoIHRoZSBvcHRpb24gdG8gdmlldyBhbiBhbmltYXRpb24gdGhhdCBhdXRvbWF0aWNhbGx5IGN5Y2xlcyB0aHJvdWdoIGFsbCBhdmFpbGFibGUgeWVhcnMgaW4gc3VjY2Vzc2lvbiBieSBwcmVzc2luZyBvbiB0aGUgcGxheSBidXR0b24gYXQgdGhlIGJvdHRvbSByaWdodCBjb3JuZXIgb2YgdGhlIHdpZGdldC4gCgpXaGlsZSB3ZSB3aWxsIGRldGVybWluZSBvdXIgb3V0cHV0cyAoYW5kIHRoZWlyIGlkcyEpIGF0IHRoaXMgc3RhZ2UsIHdlIHdvbid0IGJlIGFibGUgdG8gdmlzdWFsaXplIHRoZW0gdW50aWwgYWZ0ZXIgZGVzaWduaW5nIHRoZSBhcHAncyBzZXJ2ZXIuCgpgYGB7cixldmFsPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KGdhcG1pbmRlcikKbGlicmFyeShjb3VudHJ5Y29kZSkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KERUKQoKZ2FwbWluZGVyIDwtIGdhcG1pbmRlcgoKZ2FwbWluZGVyIDwtIGdhcG1pbmRlciAlPiUgCiAgZmlsdGVyKGNvdW50cnkhPSdLdXdhaXQnKQoKIyMjIyB1aQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIkdhcG1pbmRlciBEYXRhIiksCiAgdGFic2V0UGFuZWwoCiAgICB0YWJQYW5lbCh0aXRsZSA9ICd0aW1lIHNlcmllcycsCiAgICAgICAgICAgICBzaWRlYmFyTGF5b3V0KAogICAgICAgICAgICAgICBzaWRlYmFyUGFuZWwoCiAgICAgICAgICAgICAgICAgc2VsZWN0aXplSW5wdXQoaW5wdXRJZCA9J2NvdW50cmllcycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsPSdDaG9vc2UgdGhlIGNvdW50cmllcycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNob2ljZXMgPSB1bmlxdWUoZ2FwbWluZGVyJGNvdW50cnkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdWx0aXBsZSA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gIlVuaXRlZCBTdGF0ZXMiKSAsICAKICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICBzZWxlY3RpemVJbnB1dChpbnB1dElkID0naW5wdXR2YXJpYWJsZScsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsPSdDaG9vc2UgdGhlIHZhcmlhYmxlIHRvIGRpc3BsYXknLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gIGMoImxpZmVFeHAiLCJwb3AiLCJnZHBQZXJjYXAiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdWx0aXBsZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdGVkID0gImxpZmVFeHAiKQogICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICBtYWluUGFuZWwoCiAgICAgICAgICAgICAgICAgdGFic2V0UGFuZWwodHlwZSA9ICJ0YWJzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJQYW5lbCgiUGxvdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGgzKCJUaW1lIHNlcmllcyBwbG90IiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdGx5T3V0cHV0KCJwbG90XzEiKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhYlBhbmVsKCJUYWJsZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGgzKCJUaW1lIHNlcmllcyB0YWJsZSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGFUYWJsZU91dHB1dCgidGFibGUiKSkKICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICkKICAgICksCiAgICB0YWJQYW5lbCh0aXRsZSA9ICdkaXN0cmlidXRpb24gYnkgeWVhcicsCiAgICAgICAgICAgICB3ZWxsUGFuZWwoCiAgICAgICAgICAgICAgIHNsaWRlcklucHV0KGlucHV0SWQgPSd5cicsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD0nQ2hvb3NlIHRoZSB5ZWFyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluID0gbWluKGdhcG1pbmRlciR5ZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KGdhcG1pbmRlciR5ZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBtaW4oZ2FwbWluZGVyJHllYXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdGVwID0gNSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgYW5pbWF0ZSA9IFRSVUUpCiAgICAgICAgICAgICApLAogICAgICAgICAgICAgaDMoIkRpc3RyaWJ1dGlvbiBieSB5ZWFyIG9mIGxpZmUtZXhwZWN0YW5jeSBhbmQgR0RQIHBlciBjYXBpdGEiKSwKICAgICAgICAgICAgIHBsb3RseU91dHB1dCgicGxvdF8yIikgCiAgICAgICAgICAgICAKICAgICkKICApCikKCgojIyMjIFNlcnZlcgoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICAKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCkxldCdzIG1ha2UgdGhlIGFwcCBhY3R1YWxseSBkbyBpdCdzIGpvYiBieSBkZXNpZ25pbmcgdGhlIHNlcnZlcjoKCmBgYHtyLGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZ2FwbWluZGVyKQpsaWJyYXJ5KGNvdW50cnljb2RlKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoRFQpCgpnYXBtaW5kZXIgPC0gZ2FwbWluZGVyCgpnYXBtaW5kZXIgPC0gZ2FwbWluZGVyICU+JSAKICBmaWx0ZXIoY291bnRyeSE9J0t1d2FpdCcpCgojb25lIG9mIHRoZSBvcHRpb25zIHdoZW4gZGVzaWduaW5nIGEgc2VydmVyIGlzIHRvIHByZWRlZmluZSBmdW5jdGlvbnMgb3VyIHNlcnZlciB3aWxsIGRyYXcgb24gd2hlbiBwcm9kdWNpbmcgb3VyIG91dHB1dHM6CgpwbG90XzEgPC0gZnVuY3Rpb24oY291bnRyaWVzLCB2YXIpewogIAogIHBsdCA8LSBnYXBtaW5kZXIgJT4lCiAgICBwaXZvdF9sb25nZXIobGlmZUV4cDpnZHBQZXJjYXApICU+JSAKICAgIGZpbHRlcihjb3VudHJ5ICVpbiUgY291bnRyaWVzLCBuYW1lPT12YXIpICU+JSAgCiAgICBnZ3Bsb3QoYWVzKHllYXIsIHZhbHVlLCBjb2xvciA9IGNvbnRpbmVudCxncm91cCA9IGNvdW50cnkpKSsgCiAgICBnZW9tX2xpbmUoKSsKICAgIGdlb21fcG9pbnQoKQogIAogIGdncGxvdGx5KHBsdCkKfQoKcGxvdF8yIDwtIGZ1bmN0aW9uKHlyKXsKICAKICBwbHQgPC0gZ2FwbWluZGVyICU+JQogICAgZmlsdGVyKHllYXI9PXlyKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKGdkcFBlcmNhcCwgbGlmZUV4cCwgY29sb3I9Y29udGluZW50LHNpemU9cG9wLCAKICAgICAgICAgICAgICAgdGV4dCA9IHBhc3RlKGNvdW50cnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPkdEUCBwZXIgY2FwaXRhOiAnLG51bWJlcihnZHBQZXJjYXAsIDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj5Qb3B1bGF0aW9uOiAnLCBudW1iZXIocG9wLzEwMDAwLDEpLCdNJykpKSArCiAgICBnZW9tX3BvaW50KCkrCiAgICB0aGVtZV9taW5pbWFsKCkKICAKICBnZ3Bsb3RseShwbHQsIHRvb2x0aXAgPSAndGV4dCcpCn0KCiMjIyMgdWkKCnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJHYXBtaW5kZXIgRGF0YSIpLAogIHRhYnNldFBhbmVsKAogICAgdGFiUGFuZWwodGl0bGUgPSAndGltZSBzZXJpZXMnLAogICAgICAgICAgICAgc2lkZWJhckxheW91dCgKICAgICAgICAgICAgICAgc2lkZWJhclBhbmVsKAogICAgICAgICAgICAgICAgIHNlbGVjdGl6ZUlucHV0KGlucHV0SWQgPSdjb3VudHJpZXMnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD0nQ2hvb3NlIHRoZSBjb3VudHJpZXMnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaG9pY2VzID0gdW5pcXVlKGdhcG1pbmRlciRjb3VudHJ5KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXVsdGlwbGUgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZCA9ICJVbml0ZWQgU3RhdGVzIikgLCAgCiAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgc2VsZWN0aXplSW5wdXQoaW5wdXRJZCA9J2lucHV0dmFyaWFibGUnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD0nQ2hvb3NlIHRoZSB2YXJpYWJsZSB0byBkaXNwbGF5JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9ICBjKCJsaWZlRXhwIiwicG9wIiwiZ2RwUGVyY2FwIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXVsdGlwbGUgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RlZCA9ICJsaWZlRXhwIikKICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgbWFpblBhbmVsKAogICAgICAgICAgICAgICAgIHRhYnNldFBhbmVsKHR5cGUgPSAidGFicyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFiUGFuZWwoIlBsb3QiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoMygiVGltZSBzZXJpZXMgcGxvdCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBsb3RseU91dHB1dCgicGxvdF8xIikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YWJQYW5lbCgiVGFibGUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoMygiVGltZSBzZXJpZXMgdGFibGUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhVGFibGVPdXRwdXQoInRhYmxlIikpCiAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICApCiAgICAgICAgICAgICApCiAgICApLAogICAgdGFiUGFuZWwodGl0bGUgPSAnZGlzdHJpYnV0aW9uIGJ5IHllYXInLAogICAgICAgICAgICAgd2VsbFBhbmVsKAogICAgICAgICAgICAgICBzbGlkZXJJbnB1dChpbnB1dElkID0neXInLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWw9J0Nob29zZSB0aGUgeWVhcicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbiA9IG1pbihnYXBtaW5kZXIkeWVhciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heCA9IG1heChnYXBtaW5kZXIkeWVhciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlID0gbWluKGdhcG1pbmRlciR5ZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlcCA9IDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGFuaW1hdGUgPSBUUlVFKQogICAgICAgICAgICAgKSwKICAgICAgICAgICAgIGgzKCJEaXN0cmlidXRpb24gYnkgeWVhciBvZiBsaWZlLWV4cGVjdGFuY3kgYW5kIEdEUCBwZXIgY2FwaXRhIiksCiAgICAgICAgICAgICBwbG90bHlPdXRwdXQoInBsb3RfMiIpIAogICAgICAgICAgICAgCiAgICApCiAgKQopCgoKIyMjIyBTZXJ2ZXIKCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KSB7CiAgIyMgZmlyc3QgcGxvdAogIG91dHB1dCRwbG90XzEgPC0gcmVuZGVyUGxvdGx5KHsgCiAgICBwbG90XzEoY291bnRyaWVzID0gaW5wdXQkY291bnRyaWVzLCB2YXIgPSBpbnB1dCRpbnB1dHZhcmlhYmxlKQogIH0pCiAgCiAgIyMgdGFibGUKICBvdXRwdXQkdGFibGUgPC0gcmVuZGVyRGF0YVRhYmxlKHsKICAgIGdhcG1pbmRlciAlPiUKICAgICAgZmlsdGVyKGNvdW50cnkgJWluJSBpbnB1dCRjb3VudHJpZXMpICU+JSAKICAgICAgc2VsZWN0KGNvdW50cnksY29udGluZW50LCB5ZWFyLCBpbnB1dCRpbnB1dHZhcmlhYmxlICkKICB9LCAKICBleHRlbnNpb25zID0gJ0J1dHRvbnMnLAogIG9wdGlvbnMgPSBsaXN0KAogICAgcGFnaW5nID0gVFJVRSwKICAgIHNlYXJjaGluZyA9IFRSVUUsCiAgICBmaXhlZENvbHVtbnMgPSBUUlVFLAogICAgYXV0b1dpZHRoID0gVFJVRSwKICAgIG9yZGVyaW5nID0gVFJVRSwKICAgIGRvbSA9ICd0QicsCiAgICBidXR0b25zID0gYygnY29weScsICdjc3YnLCAnZXhjZWwnKQogICksCiAgY2xhc3MgPSAiZGlzcGxheSIpCiAgCiAgIyMgc2Vjb25kIHBsb3QKICBvdXRwdXQkcGxvdF8yIDwtIHJlbmRlclBsb3RseSh7IHBsb3RfMih5ciA9IGlucHV0JHlyKX0pCn0KCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCgoKTm90ZSB0aGF0IHRoZXJlIHNob3VsZCBiZSBhIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gcmVuZGVyIGZ1bmN0aW9ucyBhbmQgdGhlIHR5cGUgb2Ygb3V0cHV0IChgcGxvdGx5T3V0cHV0YCBhbmQgYHJlbmRlclBsb3RseWAsIGBkYXRhVGFibGVPdXRwdXRgIGFuZCBgcmVuZGVyRGF0YVRhYmxlYCkuIEVhY2ggb3V0cHV0IHdpbGwgYmUgZGVmaW5lZCB1c2luZyBpdHMgdW5pcXVlIElELCBhbmQgYSByZW5kZXIgZnVuY3Rpb24gd2lsbCBiZSBhcHBsaWVkLCBpbnZva2luZyB0aGUgcmVsZXZhbnQgaW5wdXRzIChvZiBjb3Vyc2UsIGFsc28gdGhyb3VnaCB0aGVpciBpZHMpLgoKTm90ZSB0aGF0IGBwbG90bHlgIHZpc3VhbGl6YXRpb25zIG9mZmVyIHVzZXJzIGV2ZW4gbW9yZSBmcmVlZG9tIHRvIGV4cGxvcmUgb3VyIGRhdGEgYXMgdGhleSBjb21lIGVxdWlwcGVkIHdpdGggYSB3aWRlIHJhbmdlIG9mIGZ1bmN0aW9uYWxpdGllcy4gVGhlc2UgaW5jbHVkZSB0aGUgYWJpbGl0eSB0byB6b29tIGluIGFuZCBleGFtaW5lIHNwZWNpZmljIHBvcnRpb25zIG9mIHRoZSB2aXN1YWxpemF0aW9uLCBvciBmaWx0ZXIgYSBjb250aW5lbnQgb2YgaW50ZXJlc3QgYnkgc2ltcGx5IGNsaWNraW5nIG9uIHRoZSBsZWdlbmRzLgoKTW9yZW92ZXIsIHdpdGhpbiBgcmVuZGVyRGF0YVRhYmxlYCwgd2UndmUgaW5jb3Jwb3JhdGVkIGFkZGl0aW9uYWwgYXJndW1lbnRzIHRoYXQgZW5hYmxlIHVzZXJzIHRvIGNvcHkgdGhlIHRhYmxlIGluZm9ybWF0aW9uIG9yIGV4cG9ydCBpdCBpbiBDU1Ygb3IgRXhjZWwgZm9ybWF0cy4gVGhpcyBhbGxvd3MgdXNlcnMgdG8gdXRpbGl6ZSB0aGUgZGF0YSBmb3IgY3JlYXRpbmcgdGhlaXIgb3duIHZpc3VhbGl6YXRpb25zLgoKRmVlbCBmcmVlIHRvIGFkZCBuZXcgZWxlbWVudHMgb3IgaW1wcm92ZSB0aG9zZSBhbHJlYWR5IGluIHRoZSBhcHAgdG8gcHJhY3RpY2Uh