What We’re Covering
You don’t need to read this ahead of time; it’s basically my
notes.
1: Coding Review
If you haven’t coded for a while, no worries! Here’s some basic
syntax/ground guidelines to remember:
1) Common terms:
- Variables: these are the building blocks of your
code! It’s how you store information while coding. In R, variables can
be defined using
variableName <- "whatever you want the variable to be"
.
There are different kinds of variables like integers vs. true/false, but
we don’t really need to worry about that right now.
- Functions: functions are like collections of code
that allow you to do something. For example, in R, the function
ggplot()
allows you to make a graph.
- Packages: R on its own isn’t that powerful, so
other people have created packages of functions that can be installed to
expand its capabilities.
2) Code is case sensitive! It’s good to have a system for naming
variables so you don’t get tripped up.
For instance:
camelCaseVariable <- "This is camel case"
or
underscores_between_words <- "This is a bit more common"
5) R uses Markdown!
This means that for text formatting, instead of being able to just
bold or make text a header by pressing a button, we have to tell the
computer how we want text formatted using code (side note for all you
social media people: you can use this to format text on Discord too :))
)
For example, on its own line, # “This would create a first-level
(large) header”
In Shiny, we use h1(“This is a first level header) and strong(”This
makes bold text.).
2: The ‘Parts’ of a Shiny App
At its most basic, Shiny apps have three parts:
- UI: this is the ‘front end’. It’s what a user sees. In here, we
include:
- [Most of] the text that we include in the webpage.
- Any interactive components like buttons or sliders.
- Aesthetic theme settings.
- Server: this is the ‘back end’. It’s the code that makes the
interactive stuff interactive! Here, we include output statements that
tell the computer how to interact with our UI and do ‘stuff’ (I know
that’s vague! We’ll see what that means a bit later).
- Call function: this is always
shinyApp(ui, server)
. It
tells the computer to run your app.
3: Building the Inputs (UI)
But first, libraries! Remember those packages we
installed? We only need to install packages once but they need to be
loaded every time we have a new file. We do that with the command
library()
.
Copy the below to the top of your Shiny app:
# Loading necessary packages
library(shiny)
# The following are for further customizing Shiny. We don't use them all here.
library(shinythemes)
library(bslib)
library(shinydashboard)
A: Block Structure Imagine that your UI is a blank
rectangle that you need to divide up into boxes, with each box being a
different part of the site.
The common way to do this is by using the fluidPage()
function. Having just fluidPage()
is like having everything
all in one rectangle, with no dividers whatsoever.
But sometimes we want dividers! Maybe we want an extra box on the
side. That’s a sidebarLayout()
, so we add that within the
fluidPage()
:
ui <- fluidPage(
sidebarLayout()
)
When we use a sidebarLayout
, we still need to define
each of the panels within it. Here, I am adding both the
mainPanel()
and the sidebarPanel()
. You’ll
also notice I added instructions to specify that I want the sidebar to
be on the left (by default, it’s on the right), and I added text to each
of the panels using the ‘paragraph’ (p()
) text option.
ui <- fluidPage(
sidebarLayout(position = "left",
mainPanel(p("Here lies the main panel.")),
sidebarPanel(p("And this is the side panel")),
)
)
In the Timmy’s index, I did something slightly more complex with
dashboard layouts and boxes, but we won’t go there for now.
B: Adding inputs
Inputs in Shiny are things that people can interact with - sliders,
buttons, radio buttons, checkboxes, etc. And later, we can ‘link them
up’ to certain outputs to make that output reactive. - A list of common
inputs: Shiny
- UI Inputs - The basic formula for an input is
inputFunction(inputName, label, optionalCustomizations)
.
For today, to our mainPanel()
, we’re going to add two
slider inputs that someone can use to specify how far away their home is
from a Tim Hortons and how much should be charged per km of travel:
mainPanel(p("Here lies the main panel."),
sliderInput("distance_tims",
strong("Hours to the nearest Tim Hortons"),
min = 0, max = 10, value = 2, step = 0.5),
sliderInput("you try this one! call it cost_km_travel.
Make it range from 0-5, with a default value of 0.72 and a step of 0.01")
)
In this case, for the first input, sliderInput()
is the
function (creating a slider), “distance_tims” is the name I assigned to
the slider, the text within strong() is the label, min is the smallest
number allowed on the slider, max is the largest, value is the default
value, and step is how much the value will change by when you move the
slider.
**C: Adding the outputs (part 1*)**:
For every input, there must be an output! We’ll be coding the
functionality of the output in the server section, but for now, we need
to tell the output where it’s going to go in our webpage. Here, we’re
going to add the output (the calculation) to our sidebar:
sidebarPanel(p("And this is the side panel"),
textOutput("travel_cost"),)
Just like there are many kinds of inputs, you can have many kinds of
outputs, including interactive graphs, maps, images, tables, UI etc. -
Shiny -
Display reactive output.
4: Building the Outputs (Server)
Right now, that textOutput()
is doing nothing, because
we haven’t told it what the output should be. So now, we’ll be doing
that in the server section of the code.
The basic formula for an output is
output$outputName <- renderType({ add_instructions_here})
.
This is telling the computer that the output variable
outputName
should render
(create) a particular
Type
, whether that be a map, an image, or in our case, text
that changes based on the input. We add the instructions for how to
treat the output in add_instructions_here
.
For our application, we want to render (create) Text that changes
based on the input of the numbers. We do this with:
output$travel_cost <- renderText({
paste("The travel cost is: $", input$distance_tims * 70 * 2 * input$cost_km_travel)
})
The paste statement tells the computer the output should be the text
(“The travel cost…”) PLUS a calculation based on the input
statement.
If you run this as is, you’ll notice a few weird formatting quirks -
there is a space between the $ and the number, and the number isn’t
formatted as we normally would money (i.e., $ 340.2 instead of $340.20).
I fixed that with a slightly more complicated render below:
output$travel_cost <- renderText({
mileage_total <- formatC(input$distance_tims * 70 * 2* input$cost_km_travel, format = "f", digits = 2, big.mark = ",")
paste("The estimated round-trip travel costs based on this length of time and mileage cost is:", paste0("$", mileage_total, "."))
})
In this render, I first create a new variable,
mileage_total
, which is a formatted version of the
calculation. I then add the paste statement, and a second paste
statement within it for the calculation with a $ included.
5: Some Aesthetic Changes (back to the UI!)
If you load up what we have now, it’s functional, but not pretty. We
can change that back in the UI section!
The easiest way to change themes is to add a pre-made theme. There
are a bunch available online. I like the themes offered by bslib and shinypackage. For
example:
fluidPage(theme = shinyTheme("simplex"),
sidebarLayout())
But sometimes, you want even more control over your theme! You can do
that with customizing the options in bslib, and, for even more
customizability, some knowledge of HTML and CSS:
theme = bs_theme(bg = "white", # colour settings
fg = "black",
primary = "#DC0F2D",
base_font = font_google("Noto Sans"), # font settings
code_font = font_google("Noto Sans")),
tags$head( # some custom HTML to change the aesthetic of the site.
# usually preferable to do this in a separate document
# if you're doing anything complex
tags$style(HTML("body {padding: 25px;}
h2 {font_size: 20px;}
h3 {font_size: 16px;}
p {font_size: 14px;}
"))
)
6: Run and publish the app
The last step (well, actually one I do all the time), is to run the
app!
You can also publish your apps online for free (with some
space/access limitations): Shiny -
Share your apps.
LS0tDQp0aXRsZTogIkRlbW8gU2hpbnkgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNoaW55KQ0KbGlicmFyeShzaGlueXRoZW1lcykNCmxpYnJhcnkoYnNsaWIpDQpsaWJyYXJ5KHNoaW55ZGFzaGJvYXJkKQ0KDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KIyMgV2hhdCBpcyBTaGlueT8NClNoaW55IGlzIHRoZSBjb2RpbmcgcGFja2FnZSBJIHVzZWQgdG8gbWFrZSB0aGUgW1RpbW154oCZcyBJbmRleCBjb3N0IGNhbGN1bGF0b3JdKGh0dHBzOi8vZ2FicmllbGxld29uZy5zaGlueWFwcHMuaW8vVGltbXlzSW5kZXhEZW1vLykhIEl04oCZcyBhIOKAmHBhY2thZ2XigJkgKGNvbGxlY3Rpb24gb2YgY29kZXMpIHNwZWNpZmljYWxseSBmb3IgbWFraW5nIHdlYiBhcHBzIGFuZCBzcGVjaWFsaXNlcyBpbiBpbnRlcmFjdGl2ZSBvcHRpb25zLiANCg0KIyMgV2hhdCBpcyBSPw0KUiBpcyB0aGUgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgKGxpa2UgUHl0aG9uIG9yIEphdmFzY3JpcHQpIHRoYXQgSSBjb2RlIGluLiBJdOKAmXMgYSBjb21tb24gY2hvaWNlIGZvciBwZW9wbGUgd2hvIHdvcmsgd2l0aCBzdGF0aXN0aWNzIGFuZCBkYXRhLCBidXQgbGVzcyBjb21tb24gZm9yIHdlYnNpdGVzIG9yIGNvbXBsZXggZnVuY3Rpb25hbGl0aWVzLiBGb3IgaW5zdGFuY2UsIEkgbWFkZSBbdGhpcyBzdG9yeW1hcF0oaHR0cHM6Ly9hbnRpZXZpY3Rpb25tYXBwaW5ncHJvamVjdC5naXRodWIuaW8vcHJveWVjdG9fanVhcmljdWFfZW5nLykgdXNpbmcgYSBjb21ibyBvZiBKYXZhc2NyaXB0IGFuZCBIVE1MLiBJIGRvbuKAmXQgdGhpbmsgeW91IGNvdWxkIGRvIGFueXRoaW5nIGxpa2UgaXQgaW4gUi4gDQoNCiMjIFdoYXQgaXMgUlN0dWRpbz8NClJTdHVkaW8gaXMgYW4g4oCYSW50ZWdyYXRlZCBEZXZlbG9wbWVudCBFbnZpcm9ubWVudOKAmSB0aGF0IGFsbG93cyB5b3UgdG8gY29kZSBpbiBSLiBUaGluayBvZiBSIGFzIHRoZSDigJhiYWNrIG9mIHRoZSBob3VzZeKAmSBkb2luZyBhbGwgdGhlIGhlYXZ5IGNvZGluZyB3b3JrIGFuZCBSU3R1ZGlvIGFzIHRoZSDigJhmcm9udCBvZiB0aGUgaG91c2XigJkgdGhhdCBhbGxvd3MgeW91IHRvIHBsYWNlIHlvdXIg4oCYb3JkZXJz4oCZIChjb2RlIHJlcXVlc3RzKSB0byBSLiANCg0KKioqRGlzY2xhaW1lcio6IEkgYW0gYnkgbm8gbWVhbnMgYW55IGtpbmQgb2YgZXhwZXJ0IGluIFNoaW55IG9yIFIgaW4gZ2VuZXJhbCEgU28gSSBtYWtlIG5vIGd1YXJhbnRlZXMgdGhhdCBJ4oCZbSB0YWtpbmcgdGhlIG1vc3Qg4oCYZWZmaWNpZW504oCZIG9yIG9wdGltYWwgcm91dGUgKGNhc2UgaW4gcG9pbnQ6IHVubGVzcyB5b3VyIHdpZmkgaXMgZmFudGFzdGljLCB5b3UgcHJvYmFibHkgd29u4oCZdCBiZSBhYmxlIHRvIGxvYWQgdGhpcyBbU2hpbnkgYXBwXShodHRwczovL2dhYnJpZWxsZXdvbmcuc2hpbnlhcHBzLmlvL3NkYTQ5MF9haXJibmIvKSAtIGJ1dCBJ4oCZbGwgc2hvdyBpdCB0byB5b3UgbGl2ZSBhbmQgZXhwbGFpbiB3aGF04oCZcyB3cm9uZyB3aXRoIGl0LCBldmVuIHRob3VnaCBpdCBnb3QgYSBncmVhdCBtYXJrIGFzIGEgY2xhc3MgcHJvamVjdCA6KSApLioqIA0KDQojIFByZS1NZWV0aW5nIFNldC1VcA0KDQoqVGhpcyBpcyB0b3RhbGx5IG9wdGlvbmFsIGJ1dCB3b3VsZCBiZSBoZWxwZnVsIGlmIHlvdeKAmWQgbGlrZSB0byBmb2xsb3cgYWxvbmcgd2l0aCB3aGF0IEnigJltIGRvaW5nIOKAmGxpdmXigJkhKg0KDQojIyAxOiBJbnN0YWxsIFIgYW5kIFJTdHVkaW8NCk5vdCBnb2luZyB0byBsaWUsIEkgaGF2ZW7igJl0IGRvbmUgdGhpcyBmb3IgeWVhcnMgYW5kIGRvbuKAmXQgZnVsbHkgcmVtZW1iZXIgaG93IGl0IGdvZXMuIEJ1dCBpdCBzaG91bGRu4oCZdCBiZSB0b28gZGlmZmVyZW50IGZyb20gYW55IG90aGVyIGtpbmQgb2YgYXBwIGluc3RhbGxhdGlvbiEgRmVlbCBmcmVlIHRvIHNob290IG1lIGEgbWVzc2FnZS9lbWFpbCBpZiB5b3UgcnVuIGludG8gcHJvYmxlbXMuDQpbUlN0dWRpbyBEZXNrdG9wIC0gUG9zaXRdKGh0dHBzOi8vcG9zaXQuY28vZG93bmxvYWQvcnN0dWRpby1kZXNrdG9wLykNCg0KIyMgMjogSW5zdGFsbCB0aGUgcGFja2FnZXMgeW91IG5lZWQ6IHBhY2thZ2VzIGFyZSBjb2xsZWN0aW9ucyBvZiBjb2RlIHRoYXQgZXh0ZW5kIHRoZSBhYmlsaXRpZXMgb2YgUi4gDQoNClRoaXMgY2FuIGJlIGRvbmUgYnkgdHlwaW5nIGluIHRoZSBjb25zb2xlIG9mIFJTdHVkaW8sIG9uZSBsaW5lIGF0IGEgdGltZToNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmluc3RhbGwucGFja2FnZXMo4oCcc2hpbnnigJ0pDQppbnN0YWxsLnBhY2thZ2VzKOKAnHRpZHl2ZXJzZeKAnSkNCmluc3RhbGwucGFja2FnZXMo4oCcc2hpbnl0aGVtZXPigJ0pDQppbnN0YWxsLnBhY2thZ2VzKOKAnGJzbGli4oCdKQ0KaW5zdGFsbC5wYWNrYWdlcyjigJxzaGlueWRhc2hib2FyZOKAnSkNCg0KYGBgDQoNCiMjIDM6IFRoZXNlIGFyZSB0aGUgZmlsZXMgdGhhdCBJ4oCZbSB1c2luZyBmb3IgdGhlIGRlbW8hIEZlZWwgZnJlZSB0byBkb3dubG9hZCB0aGVtIGFoZWFkIG9mIHRpbWUuIEJ1dCB5b3UgYWJzb2x1dGVseSBkb27igJl0IGhhdmUgdG8uIA0KDQpbQ29tcGxldGVkIHNoaW55IGFwcF0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2RyaXZlL2ZvbGRlcnMvMXpJSDlVeko2c3JzRWxiSi1DMlg3aHZlWlhqV1NIODNyP3VzcD1kcml2ZV9saW5rKQ0KDQoqTm90ZTogU2hpbnkgYXBwcywgZm9yIHdoYXRldmVyIHJlYXNvbiwgbXVzdCBiZSB0aXRsZWQg4oCYYXBw4oCZLiBPciBzbyBJ4oCZdmUgYmVlbiB0b2xkLiBTbyB5b3UgbmVlZCB0byBwdXQgZWFjaCBzaGlueSBhcHAgaW4gYSBkaWZmZXJlbnQgZm9sZGVyIHNpbmNlIHlvdSBjYW7igJl0IGhhdmUgdHdvIFIgZmlsZXMgd2l0aCB0aGUgc2FtZSBuYW1lIGluIHRoZSBzYW1lIGZvbGRlci4qIA0KDQpbQmxhbmsgc2hpbnkgYXBwXShodHRwczovL2RyaXZlLmdvb2dsZS5jb20vZHJpdmUvZm9sZGVycy8xXzZ1MnRrdTl5T25tMnJiWFpzM1BUZEprdEF3eXEtYVE/dXNwPWRyaXZlX2xpbmspICh0aGlzIGlzIGZvciB5b3UgdG8gZmlsbCBpbiBhcyB3ZSBnbykNCg0KW1RoaXMgUiBub3RlYm9vay5dKGh0dHBzOi8vcnB1YnMuY29tL2dhYnJpZWxsZV93b25nL3NoaW55ZGVtbzIwMjUxMDAyKQ0KDQojIFdoYXQgV2UncmUgQ292ZXJpbmcNCipZb3UgZG9u4oCZdCBuZWVkIHRvIHJlYWQgdGhpcyBhaGVhZCBvZiB0aW1lOyBpdOKAmXMgYmFzaWNhbGx5IG15IG5vdGVzLioNCg0KIyMgMTogQ29kaW5nIFJldmlldw0KSWYgeW91IGhhdmVu4oCZdCBjb2RlZCBmb3IgYSB3aGlsZSwgbm8gd29ycmllcyEgSGVyZeKAmXMgc29tZSBiYXNpYyBzeW50YXgvZ3JvdW5kIGd1aWRlbGluZXMgdG8gcmVtZW1iZXI6DQoNCiMjIyAxKSBDb21tb24gdGVybXM6DQotICoqVmFyaWFibGVzKio6IHRoZXNlIGFyZSB0aGUgYnVpbGRpbmcgYmxvY2tzIG9mIHlvdXIgY29kZSEgSXTigJlzIGhvdyB5b3Ugc3RvcmUgaW5mb3JtYXRpb24gd2hpbGUgY29kaW5nLiANCkluIFIsIHZhcmlhYmxlcyBjYW4gYmUgZGVmaW5lZCB1c2luZyANCmB2YXJpYWJsZU5hbWUgPC0gIndoYXRldmVyIHlvdSB3YW50IHRoZSB2YXJpYWJsZSB0byBiZSJgLiBUaGVyZSBhcmUgZGlmZmVyZW50IGtpbmRzIG9mIHZhcmlhYmxlcyBsaWtlIGludGVnZXJzIHZzLiB0cnVlL2ZhbHNlLCBidXQgd2UgZG9u4oCZdCByZWFsbHkgbmVlZCB0byB3b3JyeSBhYm91dCB0aGF0IHJpZ2h0IG5vdy4NCi0gKipGdW5jdGlvbnMqKjogZnVuY3Rpb25zIGFyZSBsaWtlIGNvbGxlY3Rpb25zIG9mIGNvZGUgdGhhdCBhbGxvdyB5b3UgdG8gZG8gc29tZXRoaW5nLiBGb3IgZXhhbXBsZSwgaW4gUiwgdGhlIGZ1bmN0aW9uIGBnZ3Bsb3QoKWAgYWxsb3dzIHlvdSB0byBtYWtlIGEgZ3JhcGguIA0KLSAqKlBhY2thZ2VzKio6IFIgb24gaXRzIG93biBpc27igJl0IHRoYXQgcG93ZXJmdWwsIHNvIG90aGVyIHBlb3BsZSBoYXZlIGNyZWF0ZWQgcGFja2FnZXMgb2YgZnVuY3Rpb25zIHRoYXQgY2FuIGJlIGluc3RhbGxlZCB0byBleHBhbmQgaXRzIGNhcGFiaWxpdGllcy4gDQogIC0gQ29tbW9uIFIgcGFja2FnZXM6IGh0dHBzOi8vc3VwcG9ydC5wb3NpdC5jby9oYy9lbi11cy9hcnRpY2xlcy8yMDEwNTc5ODctUXVpY2stbGlzdC1vZi11c2VmdWwtUi1wYWNrYWdlcyANCg0KIyMjIDIpIENvZGUgaXMgY2FzZSBzZW5zaXRpdmUhIEl04oCZcyBnb29kIHRvIGhhdmUgYSBzeXN0ZW0gZm9yIG5hbWluZyB2YXJpYWJsZXMgc28geW91IGRvbuKAmXQgZ2V0IHRyaXBwZWQgdXAuDQpGb3IgaW5zdGFuY2U6IA0KYGNhbWVsQ2FzZVZhcmlhYmxlIDwtICJUaGlzIGlzIGNhbWVsIGNhc2UiYA0Kb3INCmB1bmRlcnNjb3Jlc19iZXR3ZWVuX3dvcmRzIDwtICJUaGlzIGlzIGEgYml0IG1vcmUgY29tbW9uImANCg0KIyMjIDMpIENvZGUgaXMgdmVyeSBzdGVwLWJ5LXN0ZXAgYW5kIHByb2NlZHVyYWwuIFlvdSB1c3VhbGx5IGhhdmUgdG8gZGVmaW5lIGV2ZXJ5dGhpbmcsIHdoaWNoIGNhbiBiZSBmcnVzdHJhdGluZywgYnV0IGFsc28gZ2l2ZXMgeW91IGEgbG90IG9mIGZsZXhpYmlsaXR5IHRvIGN1c3RvbWl6ZS4NCg0KWypXaGVuIHlvdXIgcHJvZ3JhbSBkb2VzIGFzIGl04oCZcyBwcm9ncmFtbWVkIHRvIGRvKl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vc2hvcnRzL21ybXFSb1JEckZnKQ0KDQojIyMgNCkgSWYgeW914oCZcmUgc2hhcmluZyBjb2RlIHdpdGggb3RoZXJzLCBpdOKAmXMgZ29vZCBwcmFjdGljZSB0byBjb21tZW50IG9uIHlvdXIgY29kZS4gDQpJdCBjYW4gYWxzbyBoZWxwIHlvdSBpZiB5b3XigJlyZSBnb2luZyBiYWNrIHRvIHlvdXIgd29yayBsYXRlciBhbmQgbmVlZCB0byByZXZpZXcvcmVtZW1iZXIgd2hhdCB5b3XigJl2ZSBkb25lLiANCg0KWW91IGNhbiBhZGQgY29tbWVudHMgdXNpbmcgIyAuIA0KDQpgYGB7cn0NCg0KIyBUaGlzIGlzIGEgY29tbWVudCEgVXN1YWxseSwgY29tbWVudCB0ZXh0IGlzIGluIGEgZGlmZmVyZW50IGNvbG91ci4NCg0KcHJpbnQoIlRoaXMgaXMgbm90IGEgY29tbWVudC4gVGhpcyBpcyBtZSB0ZWxsaW5nIHRoZSBjb21wdXRlciB0byBwcmludCBzb21ldGhpbmcuIikNCg0KYGBgDQoNCiMjIyA1KSBSIHVzZXMgTWFya2Rvd24hIA0KDQpUaGlzIG1lYW5zIHRoYXQgZm9yIHRleHQgZm9ybWF0dGluZywgaW5zdGVhZCBvZiBiZWluZyBhYmxlIHRvIGp1c3QgYm9sZCBvciBtYWtlIHRleHQgYSBoZWFkZXIgYnkgcHJlc3NpbmcgYSBidXR0b24sIHdlIGhhdmUgdG8gdGVsbCB0aGUgY29tcHV0ZXIgaG93IHdlIHdhbnQgdGV4dCBmb3JtYXR0ZWQgdXNpbmcgY29kZSAoc2lkZSBub3RlIGZvciBhbGwgeW91IHNvY2lhbCBtZWRpYSBwZW9wbGU6IHlvdSBjYW4gdXNlIHRoaXMgdG8gZm9ybWF0IHRleHQgb24gRGlzY29yZCB0b28gOikpICkNCg0KRm9yIGV4YW1wbGUsIG9uIGl0cyBvd24gbGluZSwgIyDigJxUaGlzIHdvdWxkIGNyZWF0ZSBhIGZpcnN0LWxldmVsIChsYXJnZSkgaGVhZGVy4oCdDQoNCkluIFNoaW55LCB3ZSB1c2UgaDEoIlRoaXMgaXMgYSBmaXJzdCBsZXZlbCBoZWFkZXIpIGFuZCBzdHJvbmcoIlRoaXMgbWFrZXMgYm9sZCB0ZXh0LikuDQoNCiMjIDI6IFRoZSAnUGFydHMnIG9mIGEgU2hpbnkgQXBwDQoNCkF0IGl0cyBtb3N0IGJhc2ljLCBTaGlueSBhcHBzIGhhdmUgdGhyZWUgcGFydHM6DQoNCjEuIFVJOiB0aGlzIGlzIHRoZSDigJhmcm9udCBlbmTigJkuIEl04oCZcyB3aGF0IGEgdXNlciBzZWVzLiBJbiBoZXJlLCB3ZSBpbmNsdWRlOg0KICAgIC0gW01vc3Qgb2ZdIHRoZSB0ZXh0IHRoYXQgd2UgaW5jbHVkZSBpbiB0aGUgd2VicGFnZS4gDQogICAgLSBBbnkgaW50ZXJhY3RpdmUgY29tcG9uZW50cyBsaWtlIGJ1dHRvbnMgb3Igc2xpZGVycy4NCiAgICAtIEFlc3RoZXRpYyB0aGVtZSBzZXR0aW5ncy4NCjIuIFNlcnZlcjogdGhpcyBpcyB0aGUg4oCYYmFjayBlbmTigJkuIEl04oCZcyB0aGUgY29kZSB0aGF0IG1ha2VzIHRoZSBpbnRlcmFjdGl2ZSBzdHVmZiBpbnRlcmFjdGl2ZSEgSGVyZSwgd2UgaW5jbHVkZSBvdXRwdXQgc3RhdGVtZW50cyB0aGF0IHRlbGwgdGhlIGNvbXB1dGVyIGhvdyB0byBpbnRlcmFjdCB3aXRoIG91ciBVSSBhbmQgZG8g4oCYc3R1ZmbigJkgKEkga25vdyB0aGF04oCZcyB2YWd1ZSEgV2XigJlsbCBzZWUgd2hhdCB0aGF0IG1lYW5zIGEgYml0IGxhdGVyKS4gDQozLiBDYWxsIGZ1bmN0aW9uOiB0aGlzIGlzIGFsd2F5cyBgc2hpbnlBcHAodWksIHNlcnZlcilgLiBJdCB0ZWxscyB0aGUgY29tcHV0ZXIgdG8gcnVuIHlvdXIgYXBwLiANCg0KDQojIyAzOiBCdWlsZGluZyB0aGUgSW5wdXRzIChVSSkNCg0KKipCdXQgZmlyc3QsIGxpYnJhcmllcyEqKg0KUmVtZW1iZXIgdGhvc2UgcGFja2FnZXMgd2UgaW5zdGFsbGVkPyBXZSBvbmx5IG5lZWQgdG8gaW5zdGFsbCBwYWNrYWdlcyBvbmNlIGJ1dCB0aGV5IG5lZWQgdG8gYmUgbG9hZGVkIGV2ZXJ5IHRpbWUgd2UgaGF2ZSBhIG5ldyBmaWxlLiBXZSBkbyB0aGF0IHdpdGggdGhlIGNvbW1hbmQgYGxpYnJhcnkoKWAuDQoNCkNvcHkgdGhlIGJlbG93IHRvIHRoZSB0b3Agb2YgeW91ciBTaGlueSBhcHA6DQoNCmBgYHtyfQ0KDQojIExvYWRpbmcgbmVjZXNzYXJ5IHBhY2thZ2VzDQpsaWJyYXJ5KHNoaW55KSANCg0KIyBUaGUgZm9sbG93aW5nIGFyZSBmb3IgZnVydGhlciBjdXN0b21pemluZyBTaGlueS4gV2UgZG9uJ3QgdXNlIHRoZW0gYWxsIGhlcmUuDQpsaWJyYXJ5KHNoaW55dGhlbWVzKQ0KbGlicmFyeShic2xpYikNCmxpYnJhcnkoc2hpbnlkYXNoYm9hcmQpDQoNCmBgYA0KDQoqKkE6IEJsb2NrIFN0cnVjdHVyZSoqDQpJbWFnaW5lIHRoYXQgeW91ciBVSSBpcyBhIGJsYW5rIHJlY3RhbmdsZSB0aGF0IHlvdSBuZWVkIHRvIGRpdmlkZSB1cCBpbnRvIGJveGVzLCB3aXRoIGVhY2ggYm94IGJlaW5nIGEgZGlmZmVyZW50IHBhcnQgb2YgdGhlIHNpdGUuIA0KDQpUaGUgY29tbW9uIHdheSB0byBkbyB0aGlzIGlzIGJ5IHVzaW5nIHRoZSBgZmx1aWRQYWdlKClgIGZ1bmN0aW9uLiBIYXZpbmcganVzdCBgZmx1aWRQYWdlKClgIGlzIGxpa2UgaGF2aW5nIGV2ZXJ5dGhpbmcgYWxsIGluIG9uZSByZWN0YW5nbGUsIHdpdGggbm8gZGl2aWRlcnMgd2hhdHNvZXZlci4NCg0KQnV0IHNvbWV0aW1lcyB3ZSB3YW50IGRpdmlkZXJzISBNYXliZSB3ZSB3YW50IGFuIGV4dHJhIGJveCBvbiB0aGUgc2lkZS4gVGhhdOKAmXMgYSBgc2lkZWJhckxheW91dCgpYCwgc28gd2UgYWRkIHRoYXQgd2l0aGluIHRoZSBgZmx1aWRQYWdlKClgOg0KDQpgYGB7cn0NCg0KdWkgPC0gZmx1aWRQYWdlKA0KICBzaWRlYmFyTGF5b3V0KCkNCikNCg0KYGBgDQoNCldoZW4gd2UgdXNlIGEgYHNpZGViYXJMYXlvdXRgLCB3ZSBzdGlsbCBuZWVkIHRvIGRlZmluZSBlYWNoIG9mIHRoZSBwYW5lbHMgd2l0aGluIGl0LiBIZXJlLCBJIGFtIGFkZGluZyBib3RoIHRoZSBgbWFpblBhbmVsKClgIGFuZCB0aGUgYHNpZGViYXJQYW5lbCgpYC4gWW914oCZbGwgYWxzbyBub3RpY2UgSSBhZGRlZCBpbnN0cnVjdGlvbnMgdG8gc3BlY2lmeSB0aGF0IEkgd2FudCB0aGUgc2lkZWJhciB0byBiZSBvbiB0aGUgbGVmdCAoYnkgZGVmYXVsdCwgaXTigJlzIG9uIHRoZSByaWdodCksIGFuZCBJIGFkZGVkIHRleHQgdG8gZWFjaCBvZiB0aGUgcGFuZWxzIHVzaW5nIHRoZSDigJhwYXJhZ3JhcGjigJkgKGBwKClgKSB0ZXh0IG9wdGlvbi4gDQoNCmBgYHtyfQ0KDQp1aSA8LSBmbHVpZFBhZ2UoDQoJc2lkZWJhckxheW91dChwb3NpdGlvbiA9ICJsZWZ0IiwNCgkJbWFpblBhbmVsKHAoIkhlcmUgbGllcyB0aGUgbWFpbiBwYW5lbC4iKSksDQoJCXNpZGViYXJQYW5lbChwKCJBbmQgdGhpcyBpcyB0aGUgc2lkZSBwYW5lbCIpKSwNCiAgKQ0KKQ0KDQpgYGANCkluIHRoZSBUaW1teeKAmXMgaW5kZXgsIEkgZGlkIHNvbWV0aGluZyBzbGlnaHRseSBtb3JlIGNvbXBsZXggd2l0aCBkYXNoYm9hcmQgbGF5b3V0cyBhbmQgYm94ZXMsIGJ1dCB3ZSB3b27igJl0IGdvIHRoZXJlIGZvciBub3cuDQoNCioqQjogQWRkaW5nIGlucHV0cyoqDQoNCklucHV0cyBpbiBTaGlueSBhcmUgdGhpbmdzIHRoYXQgcGVvcGxlIGNhbiBpbnRlcmFjdCB3aXRoIC0gc2xpZGVycywgYnV0dG9ucywgcmFkaW8gYnV0dG9ucywgY2hlY2tib3hlcywgZXRjLiBBbmQgbGF0ZXIsIHdlIGNhbiDigJhsaW5rIHRoZW0gdXDigJkgdG8gY2VydGFpbiBvdXRwdXRzIHRvIG1ha2UgdGhhdCBvdXRwdXQgcmVhY3RpdmUuDQotIEEgbGlzdCBvZiBjb21tb24gaW5wdXRzOiBbU2hpbnkgLSBVSSBJbnB1dHMgXShodHRwczovL3NoaW55LnBvc2l0LmNvL3IvZ2V0c3RhcnRlZC9idWlsZC1hbi1hcHAvcmVhY3RpdmUtZmxvdy91aS1pbnB1dHMuaHRtbCkNCi0gVGhlIGJhc2ljIGZvcm11bGEgZm9yIGFuIGlucHV0IGlzIGBpbnB1dEZ1bmN0aW9uKGlucHV0TmFtZSwgbGFiZWwsIG9wdGlvbmFsQ3VzdG9taXphdGlvbnMpYC4NCg0KRm9yIHRvZGF5LCB0byBvdXIgYG1haW5QYW5lbCgpYCwgd2XigJlyZSBnb2luZyB0byBhZGQgdHdvIHNsaWRlciBpbnB1dHMgdGhhdCBzb21lb25lIGNhbiB1c2UgdG8gc3BlY2lmeSBob3cgZmFyIGF3YXkgdGhlaXIgaG9tZSBpcyBmcm9tIGEgVGltIEhvcnRvbnMgYW5kIGhvdyBtdWNoIHNob3VsZCBiZSBjaGFyZ2VkIHBlciBrbSBvZiB0cmF2ZWw6DQoNCmBgYHtyfQ0KDQptYWluUGFuZWwocCgiSGVyZSBsaWVzIHRoZSBtYWluIHBhbmVsLiIpLA0KICAgICAgICAgIHNsaWRlcklucHV0KCJkaXN0YW5jZV90aW1zIiwgDQogICAgICAgICAgICAgICAgICAgICAgc3Ryb25nKCJIb3VycyB0byB0aGUgbmVhcmVzdCBUaW0gSG9ydG9ucyIpLCANCiAgICAgICAgICAgICAgICAgICAgICBtaW4gPSAwLCBtYXggPSAxMCwgdmFsdWUgPSAyLCBzdGVwID0gMC41KSwNCiAgICAgICAgICANCiAgICAgICAgICBzbGlkZXJJbnB1dCgieW91IHRyeSB0aGlzIG9uZSEgY2FsbCBpdCBjb3N0X2ttX3RyYXZlbC4gDQogICAgICAgICAgICAgICAgICAgICAgTWFrZSBpdCByYW5nZSBmcm9tIDAtNSwgd2l0aCBhIGRlZmF1bHQgdmFsdWUgb2YgMC43MiBhbmQgYSBzdGVwIG9mIDAuMDEiKQ0KICAgICAgICAgICkNCg0KDQpgYGANCg0KSW4gdGhpcyBjYXNlLCBmb3IgdGhlIGZpcnN0IGlucHV0LCBgc2xpZGVySW5wdXQoKSBgaXMgdGhlIGZ1bmN0aW9uIChjcmVhdGluZyBhIHNsaWRlciksIOKAnGRpc3RhbmNlX3RpbXPigJ0gaXMgdGhlIG5hbWUgSSBhc3NpZ25lZCB0byB0aGUgc2xpZGVyLCB0aGUgdGV4dCB3aXRoaW4gc3Ryb25nKCkgaXMgdGhlIGxhYmVsLCBtaW4gaXMgdGhlIHNtYWxsZXN0IG51bWJlciBhbGxvd2VkIG9uIHRoZSBzbGlkZXIsIG1heCBpcyB0aGUgbGFyZ2VzdCwgdmFsdWUgaXMgdGhlIGRlZmF1bHQgdmFsdWUsIGFuZCBzdGVwIGlzIGhvdyBtdWNoIHRoZSB2YWx1ZSB3aWxsIGNoYW5nZSBieSB3aGVuIHlvdSBtb3ZlIHRoZSBzbGlkZXIuIA0KDQoqKkM6IEFkZGluZyB0aGUgb3V0cHV0cyAocGFydCAxKikqKjoNCg0KRm9yIGV2ZXJ5IGlucHV0LCB0aGVyZSBtdXN0IGJlIGFuIG91dHB1dCEgV2XigJlsbCBiZSBjb2RpbmcgdGhlIGZ1bmN0aW9uYWxpdHkgb2YgdGhlIG91dHB1dCBpbiB0aGUgc2VydmVyIHNlY3Rpb24sIGJ1dCBmb3Igbm93LCB3ZSBuZWVkIHRvIHRlbGwgdGhlIG91dHB1dCB3aGVyZSBpdOKAmXMgZ29pbmcgdG8gZ28gaW4gb3VyIHdlYnBhZ2UuIEhlcmUsIHdl4oCZcmUgZ29pbmcgdG8gYWRkIHRoZSBvdXRwdXQgKHRoZSBjYWxjdWxhdGlvbikgdG8gb3VyIHNpZGViYXI6DQoNCmBgYHtyfQ0KDQpzaWRlYmFyUGFuZWwocCgiQW5kIHRoaXMgaXMgdGhlIHNpZGUgcGFuZWwiKSwNCiAgICAgICAgICAgICB0ZXh0T3V0cHV0KCJ0cmF2ZWxfY29zdCIpLCkNCg0KYGBgDQoNCkp1c3QgbGlrZSB0aGVyZSBhcmUgbWFueSBraW5kcyBvZiBpbnB1dHMsIHlvdSBjYW4gaGF2ZSBtYW55IGtpbmRzIG9mIG91dHB1dHMsIGluY2x1ZGluZyBpbnRlcmFjdGl2ZSBncmFwaHMsIG1hcHMsIGltYWdlcywgdGFibGVzLCBVSSBldGMuIC0gW1NoaW55IC0gRGlzcGxheSByZWFjdGl2ZSBvdXRwdXRdKGh0dHBzOi8vc2hpbnkucG9zaXQuY28vci9nZXRzdGFydGVkL3NoaW55LWJhc2ljcy9sZXNzb240LykuIA0KDQojIyA0OiBCdWlsZGluZyB0aGUgT3V0cHV0cyAoU2VydmVyKQ0KUmlnaHQgbm93LCB0aGF0IGB0ZXh0T3V0cHV0KClgIGlzIGRvaW5nIG5vdGhpbmcsIGJlY2F1c2Ugd2UgaGF2ZW7igJl0IHRvbGQgaXQgd2hhdCB0aGUgb3V0cHV0IHNob3VsZCBiZS4gU28gbm93LCB3ZeKAmWxsIGJlIGRvaW5nIHRoYXQgaW4gdGhlIHNlcnZlciBzZWN0aW9uIG9mIHRoZSBjb2RlLiANCg0KVGhlIGJhc2ljIGZvcm11bGEgZm9yIGFuIG91dHB1dCBpcyBgb3V0cHV0JG91dHB1dE5hbWUgPC0gcmVuZGVyVHlwZSh7IGFkZF9pbnN0cnVjdGlvbnNfaGVyZX0pYC4gVGhpcyBpcyB0ZWxsaW5nIHRoZSBjb21wdXRlciB0aGF0IHRoZSBvdXRwdXQgdmFyaWFibGUgYG91dHB1dE5hbWVgIHNob3VsZCBgcmVuZGVyYCAoY3JlYXRlKSBhIHBhcnRpY3VsYXIgYFR5cGVgLCB3aGV0aGVyIHRoYXQgYmUgYSBtYXAsIGFuIGltYWdlLCBvciBpbiBvdXIgY2FzZSwgdGV4dCB0aGF0IGNoYW5nZXMgYmFzZWQgb24gdGhlIGlucHV0LiBXZSBhZGQgdGhlIGluc3RydWN0aW9ucyBmb3IgaG93IHRvIHRyZWF0IHRoZSBvdXRwdXQgaW4gYGFkZF9pbnN0cnVjdGlvbnNfaGVyZWAuIA0KDQpGb3Igb3VyIGFwcGxpY2F0aW9uLCB3ZSB3YW50IHRvIHJlbmRlciAoY3JlYXRlKSBUZXh0IHRoYXQgY2hhbmdlcyBiYXNlZCBvbiB0aGUgaW5wdXQgb2YgdGhlIG51bWJlcnMuIFdlIGRvIHRoaXMgd2l0aDoNCg0KYGBge3J9DQoNCm91dHB1dCR0cmF2ZWxfY29zdCA8LSByZW5kZXJUZXh0KHsNCiAgcGFzdGUoIlRoZSB0cmF2ZWwgY29zdCBpczogJCIsIGlucHV0JGRpc3RhbmNlX3RpbXMgKiA3MCAqIDIgKiBpbnB1dCRjb3N0X2ttX3RyYXZlbCkNCn0pDQoNCmBgYA0KDQpUaGUgcGFzdGUgc3RhdGVtZW50IHRlbGxzIHRoZSBjb21wdXRlciB0aGUgb3V0cHV0IHNob3VsZCBiZSB0aGUgdGV4dCAo4oCcVGhlIHRyYXZlbCBjb3N04oCm4oCdKSBQTFVTIGEgY2FsY3VsYXRpb24gYmFzZWQgb24gdGhlIGlucHV0IHN0YXRlbWVudC4gDQoNCklmIHlvdSBydW4gdGhpcyBhcyBpcywgeW914oCZbGwgbm90aWNlIGEgZmV3IHdlaXJkIGZvcm1hdHRpbmcgcXVpcmtzIC0gdGhlcmUgaXMgYSBzcGFjZSBiZXR3ZWVuIHRoZSAkIGFuZCB0aGUgbnVtYmVyLCBhbmQgdGhlIG51bWJlciBpc27igJl0IGZvcm1hdHRlZCBhcyB3ZSBub3JtYWxseSB3b3VsZCBtb25leSAoaS5lLiwgJCAzNDAuMiBpbnN0ZWFkIG9mICQzNDAuMjApLiBJIGZpeGVkIHRoYXQgd2l0aCBhIHNsaWdodGx5IG1vcmUgY29tcGxpY2F0ZWQgcmVuZGVyIGJlbG93Og0KDQpgYGB7cn0NCg0Kb3V0cHV0JHRyYXZlbF9jb3N0IDwtIHJlbmRlclRleHQoew0KICAgIG1pbGVhZ2VfdG90YWwgPC0gZm9ybWF0QyhpbnB1dCRkaXN0YW5jZV90aW1zICogNzAgKiAyKiBpbnB1dCRjb3N0X2ttX3RyYXZlbCwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSAyLCBiaWcubWFyayA9ICIsIikNCiAgICBwYXN0ZSgiVGhlIGVzdGltYXRlZCByb3VuZC10cmlwIHRyYXZlbCBjb3N0cyBiYXNlZCBvbiB0aGlzIGxlbmd0aCBvZiB0aW1lIGFuZCBtaWxlYWdlIGNvc3QgaXM6IiwgcGFzdGUwKCIkIiwgbWlsZWFnZV90b3RhbCwgIi4iKSkNCiAgfSkNCmBgYA0KDQpJbiB0aGlzIHJlbmRlciwgSSBmaXJzdCBjcmVhdGUgYSBuZXcgdmFyaWFibGUsIGBtaWxlYWdlX3RvdGFsYCwgd2hpY2ggaXMgYSBmb3JtYXR0ZWQgdmVyc2lvbiBvZiB0aGUgY2FsY3VsYXRpb24uIEkgdGhlbiBhZGQgdGhlIHBhc3RlIHN0YXRlbWVudCwgYW5kIGEgc2Vjb25kIHBhc3RlIHN0YXRlbWVudCB3aXRoaW4gaXQgZm9yIHRoZSBjYWxjdWxhdGlvbiB3aXRoIGEgJCBpbmNsdWRlZC4gDQoNCiMjIDU6IFNvbWUgQWVzdGhldGljIENoYW5nZXMgKGJhY2sgdG8gdGhlIFVJISkNCg0KSWYgeW91IGxvYWQgdXAgd2hhdCB3ZSBoYXZlIG5vdywgaXTigJlzIGZ1bmN0aW9uYWwsIGJ1dCBub3QgcHJldHR5LiBXZSBjYW4gY2hhbmdlIHRoYXQgYmFjayBpbiB0aGUgVUkgc2VjdGlvbiENCg0KVGhlIGVhc2llc3Qgd2F5IHRvIGNoYW5nZSB0aGVtZXMgaXMgdG8gYWRkIGEgcHJlLW1hZGUgdGhlbWUuIFRoZXJlIGFyZSBhIGJ1bmNoIGF2YWlsYWJsZSBvbmxpbmUuIEkgbGlrZSB0aGUgdGhlbWVzIG9mZmVyZWQgYnkgW2JzbGliXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2JzbGliLykgYW5kIFtzaGlueXBhY2thZ2UuXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL3NoaW55dGhlbWVzLykgRm9yIGV4YW1wbGU6DQoNCmBgYHtyfQ0KZmx1aWRQYWdlKHRoZW1lID0gc2hpbnlUaGVtZSgic2ltcGxleCIpLA0KICAgICAgICAgIHNpZGViYXJMYXlvdXQoKSkNCmBgYA0KQnV0IHNvbWV0aW1lcywgeW91IHdhbnQgZXZlbiBtb3JlIGNvbnRyb2wgb3ZlciB5b3VyIHRoZW1lISBZb3UgY2FuIGRvIHRoYXQgd2l0aCBjdXN0b21pemluZyB0aGUgb3B0aW9ucyBpbiBic2xpYiwgYW5kLCBmb3IgZXZlbiBtb3JlIGN1c3RvbWl6YWJpbGl0eSwgc29tZSBrbm93bGVkZ2Ugb2YgSFRNTCBhbmQgQ1NTOg0KDQpgYGB7cn0NCg0KIHRoZW1lID0gYnNfdGhlbWUoYmcgPSAid2hpdGUiLCAjIGNvbG91ciBzZXR0aW5ncw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmcgPSAiYmxhY2siLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW1hcnkgPSAiI0RDMEYyRCIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhc2VfZm9udCA9IGZvbnRfZ29vZ2xlKCJOb3RvIFNhbnMiKSwgIyBmb250IHNldHRpbmdzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2RlX2ZvbnQgPSBmb250X2dvb2dsZSgiTm90byBTYW5zIikpLA0KICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgdGFncyRoZWFkKCAjIHNvbWUgY3VzdG9tIEhUTUwgdG8gY2hhbmdlIHRoZSBhZXN0aGV0aWMgb2YgdGhlIHNpdGUuIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHVzdWFsbHkgcHJlZmVyYWJsZSB0byBkbyB0aGlzIGluIGEgc2VwYXJhdGUgZG9jdW1lbnQgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgaWYgeW91J3JlIGRvaW5nIGFueXRoaW5nIGNvbXBsZXgNCiAgICAgICAgICAgICAgICAgIHRhZ3Mkc3R5bGUoSFRNTCgiYm9keSB7cGFkZGluZzogMjVweDt9IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoMiB7Zm9udF9zaXplOiAyMHB4O30NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaDMge2ZvbnRfc2l6ZTogMTZweDt9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAge2ZvbnRfc2l6ZTogMTRweDt9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikpDQogICAgICAgICAgICAgICAgKQ0KDQpgYGANCg0KIyMgNjogUnVuIGFuZCBwdWJsaXNoIHRoZSBhcHANCg0KVGhlIGxhc3Qgc3RlcCAod2VsbCwgYWN0dWFsbHkgb25lIEkgZG8gYWxsIHRoZSB0aW1lKSwgaXMgdG8gcnVuIHRoZSBhcHAhDQoNCllvdSBjYW4gYWxzbyBwdWJsaXNoIHlvdXIgYXBwcyBvbmxpbmUgZm9yIGZyZWUgKHdpdGggc29tZSBzcGFjZS9hY2Nlc3MgbGltaXRhdGlvbnMpOiBbU2hpbnkgLSBTaGFyZSB5b3VyIGFwcHNdKGh0dHBzOi8vc2hpbnkucG9zaXQuY28vci9nZXRzdGFydGVkL3NoaW55LWJhc2ljcy9sZXNzb243LykuDQoNCiMgT3RoZXIgdGhpbmdzIHRvIGNoZWNrIG91dCBpZiB5b3XigJlyZSBpbnRlcmVzdGVkDQoNCkRlZmluaXRpdmUgdGV4dGJvb2sgb24gdXNpbmcgU2hpbnk6IFtXZWxjb21lIHwgTWFzdGVyaW5nIFNoaW55XShodHRwczovL21hc3RlcmluZy1zaGlueS5vcmcvaW5kZXguaHRtbCkNCg0KU29tZSBmZWF0dXJlZCBTaGlueSBhcHBzOiBbU2hpbnkgZm9yIFIgR2FsbGVyeV0oaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dhbGxlcnkvKQ0KDQoNCg==