Introduction

What is Shiny?

Shiny is the coding package I used to make the Timmy’s Index cost calculator! It’s a ‘package’ (collection of codes) specifically for making web apps and specialises in interactive options.

What is R?

R is the programming language (like Python or Javascript) that I code in. It’s a common choice for people who work with statistics and data, but less common for websites or complex functionalities. For instance, I made this storymap using a combo of Javascript and HTML. I don’t think you could do anything like it in R.

What is RStudio?

RStudio is an ‘Integrated Development Environment’ that allows you to code in R. Think of R as the ‘back of the house’ doing all the heavy coding work and RStudio as the ‘front of the house’ that allows you to place your ‘orders’ (code requests) to R.

Disclaimer: I am by no means any kind of expert in Shiny or R in general! So I make no guarantees that I’m taking the most ‘efficient’ or optimal route (case in point: unless your wifi is fantastic, you probably won’t be able to load this Shiny app - but I’ll show it to you live and explain what’s wrong with it, even though it got a great mark as a class project :) ).

Pre-Meeting Set-Up

This is totally optional but would be helpful if you’d like to follow along with what I’m doing ‘live’!

1: Install R and RStudio

Not going to lie, I haven’t done this for years and don’t fully remember how it goes. But it shouldn’t be too different from any other kind of app installation! Feel free to shoot me a message/email if you run into problems. RStudio Desktop - Posit

2: Install the packages you need: packages are collections of code that extend the abilities of R.

This can be done by typing in the console of RStudio, one line at a time:

install.packages(“shiny”)
install.packages(“tidyverse”)
install.packages(“shinythemes”)
install.packages(“bslib”)
install.packages(“shinydashboard”)

3: These are the files that I’m using for the demo! Feel free to download them ahead of time. But you absolutely don’t have to.

Completed shiny app

Note: Shiny apps, for whatever reason, must be titled ‘app’. Or so I’ve been told. So you need to put each shiny app in a different folder since you can’t have two R files with the same name in the same folder.

Blank shiny app (this is for you to fill in as we go)

This R notebook.

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"

3) Code is very step-by-step and procedural. You usually have to define everything, which can be frustrating, but also gives you a lot of flexibility to customize.

When your program does as it’s programmed to do

4) If you’re sharing code with others, it’s good practice to comment on your code.

It can also help you if you’re going back to your work later and need to review/remember what you’ve done.

You can add comments using # .


# This is a comment! Usually, comment text is in a different colour.

print("This is not a comment. This is me telling the computer to print something.")

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:

  1. 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.
  2. 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).
  3. 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.

Other things to check out if you’re interested

Definitive textbook on using Shiny: Welcome | Mastering Shiny

Some featured Shiny apps: Shiny for R Gallery

LS0tDQp0aXRsZTogIkRlbW8gU2hpbnkgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciwgZWNobyA9IEZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNoaW55KQ0KbGlicmFyeShzaGlueXRoZW1lcykNCmxpYnJhcnkoYnNsaWIpDQpsaWJyYXJ5KHNoaW55ZGFzaGJvYXJkKQ0KDQpgYGANCg0KIyBJbnRyb2R1Y3Rpb24NCg0KIyMgV2hhdCBpcyBTaGlueT8NClNoaW55IGlzIHRoZSBjb2RpbmcgcGFja2FnZSBJIHVzZWQgdG8gbWFrZSB0aGUgW1RpbW154oCZcyBJbmRleCBjb3N0IGNhbGN1bGF0b3JdKGh0dHBzOi8vZ2FicmllbGxld29uZy5zaGlueWFwcHMuaW8vVGltbXlzSW5kZXhEZW1vLykhIEl04oCZcyBhIOKAmHBhY2thZ2XigJkgKGNvbGxlY3Rpb24gb2YgY29kZXMpIHNwZWNpZmljYWxseSBmb3IgbWFraW5nIHdlYiBhcHBzIGFuZCBzcGVjaWFsaXNlcyBpbiBpbnRlcmFjdGl2ZSBvcHRpb25zLiANCg0KIyMgV2hhdCBpcyBSPw0KUiBpcyB0aGUgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UgKGxpa2UgUHl0aG9uIG9yIEphdmFzY3JpcHQpIHRoYXQgSSBjb2RlIGluLiBJdOKAmXMgYSBjb21tb24gY2hvaWNlIGZvciBwZW9wbGUgd2hvIHdvcmsgd2l0aCBzdGF0aXN0aWNzIGFuZCBkYXRhLCBidXQgbGVzcyBjb21tb24gZm9yIHdlYnNpdGVzIG9yIGNvbXBsZXggZnVuY3Rpb25hbGl0aWVzLiBGb3IgaW5zdGFuY2UsIEkgbWFkZSBbdGhpcyBzdG9yeW1hcF0oaHR0cHM6Ly9hbnRpZXZpY3Rpb25tYXBwaW5ncHJvamVjdC5naXRodWIuaW8vcHJveWVjdG9fanVhcmljdWFfZW5nLykgdXNpbmcgYSBjb21ibyBvZiBKYXZhc2NyaXB0IGFuZCBIVE1MLiBJIGRvbuKAmXQgdGhpbmsgeW91IGNvdWxkIGRvIGFueXRoaW5nIGxpa2UgaXQgaW4gUi4gDQoNCiMjIFdoYXQgaXMgUlN0dWRpbz8NClJTdHVkaW8gaXMgYW4g4oCYSW50ZWdyYXRlZCBEZXZlbG9wbWVudCBFbnZpcm9ubWVudOKAmSB0aGF0IGFsbG93cyB5b3UgdG8gY29kZSBpbiBSLiBUaGluayBvZiBSIGFzIHRoZSDigJhiYWNrIG9mIHRoZSBob3VzZeKAmSBkb2luZyBhbGwgdGhlIGhlYXZ5IGNvZGluZyB3b3JrIGFuZCBSU3R1ZGlvIGFzIHRoZSDigJhmcm9udCBvZiB0aGUgaG91c2XigJkgdGhhdCBhbGxvd3MgeW91IHRvIHBsYWNlIHlvdXIg4oCYb3JkZXJz4oCZIChjb2RlIHJlcXVlc3RzKSB0byBSLiANCg0KKioqRGlzY2xhaW1lcio6IEkgYW0gYnkgbm8gbWVhbnMgYW55IGtpbmQgb2YgZXhwZXJ0IGluIFNoaW55IG9yIFIgaW4gZ2VuZXJhbCEgU28gSSBtYWtlIG5vIGd1YXJhbnRlZXMgdGhhdCBJ4oCZbSB0YWtpbmcgdGhlIG1vc3Qg4oCYZWZmaWNpZW504oCZIG9yIG9wdGltYWwgcm91dGUgKGNhc2UgaW4gcG9pbnQ6IHVubGVzcyB5b3VyIHdpZmkgaXMgZmFudGFzdGljLCB5b3UgcHJvYmFibHkgd29u4oCZdCBiZSBhYmxlIHRvIGxvYWQgdGhpcyBbU2hpbnkgYXBwXShodHRwczovL2dhYnJpZWxsZXdvbmcuc2hpbnlhcHBzLmlvL3NkYTQ5MF9haXJibmIvKSAtIGJ1dCBJ4oCZbGwgc2hvdyBpdCB0byB5b3UgbGl2ZSBhbmQgZXhwbGFpbiB3aGF04oCZcyB3cm9uZyB3aXRoIGl0LCBldmVuIHRob3VnaCBpdCBnb3QgYSBncmVhdCBtYXJrIGFzIGEgY2xhc3MgcHJvamVjdCA6KSApLioqIA0KDQojIFByZS1NZWV0aW5nIFNldC1VcA0KDQoqVGhpcyBpcyB0b3RhbGx5IG9wdGlvbmFsIGJ1dCB3b3VsZCBiZSBoZWxwZnVsIGlmIHlvdeKAmWQgbGlrZSB0byBmb2xsb3cgYWxvbmcgd2l0aCB3aGF0IEnigJltIGRvaW5nIOKAmGxpdmXigJkhKg0KDQojIyAxOiBJbnN0YWxsIFIgYW5kIFJTdHVkaW8NCk5vdCBnb2luZyB0byBsaWUsIEkgaGF2ZW7igJl0IGRvbmUgdGhpcyBmb3IgeWVhcnMgYW5kIGRvbuKAmXQgZnVsbHkgcmVtZW1iZXIgaG93IGl0IGdvZXMuIEJ1dCBpdCBzaG91bGRu4oCZdCBiZSB0b28gZGlmZmVyZW50IGZyb20gYW55IG90aGVyIGtpbmQgb2YgYXBwIGluc3RhbGxhdGlvbiEgRmVlbCBmcmVlIHRvIHNob290IG1lIGEgbWVzc2FnZS9lbWFpbCBpZiB5b3UgcnVuIGludG8gcHJvYmxlbXMuDQpbUlN0dWRpbyBEZXNrdG9wIC0gUG9zaXRdKGh0dHBzOi8vcG9zaXQuY28vZG93bmxvYWQvcnN0dWRpby1kZXNrdG9wLykNCg0KIyMgMjogSW5zdGFsbCB0aGUgcGFja2FnZXMgeW91IG5lZWQ6IHBhY2thZ2VzIGFyZSBjb2xsZWN0aW9ucyBvZiBjb2RlIHRoYXQgZXh0ZW5kIHRoZSBhYmlsaXRpZXMgb2YgUi4gDQoNClRoaXMgY2FuIGJlIGRvbmUgYnkgdHlwaW5nIGluIHRoZSBjb25zb2xlIG9mIFJTdHVkaW8sIG9uZSBsaW5lIGF0IGEgdGltZToNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmluc3RhbGwucGFja2FnZXMo4oCcc2hpbnnigJ0pDQppbnN0YWxsLnBhY2thZ2VzKOKAnHRpZHl2ZXJzZeKAnSkNCmluc3RhbGwucGFja2FnZXMo4oCcc2hpbnl0aGVtZXPigJ0pDQppbnN0YWxsLnBhY2thZ2VzKOKAnGJzbGli4oCdKQ0KaW5zdGFsbC5wYWNrYWdlcyjigJxzaGlueWRhc2hib2FyZOKAnSkNCg0KYGBgDQoNCiMjIDM6IFRoZXNlIGFyZSB0aGUgZmlsZXMgdGhhdCBJ4oCZbSB1c2luZyBmb3IgdGhlIGRlbW8hIEZlZWwgZnJlZSB0byBkb3dubG9hZCB0aGVtIGFoZWFkIG9mIHRpbWUuIEJ1dCB5b3UgYWJzb2x1dGVseSBkb27igJl0IGhhdmUgdG8uIA0KDQpbQ29tcGxldGVkIHNoaW55IGFwcF0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2RyaXZlL2ZvbGRlcnMvMXpJSDlVeko2c3JzRWxiSi1DMlg3aHZlWlhqV1NIODNyP3VzcD1kcml2ZV9saW5rKQ0KDQoqTm90ZTogU2hpbnkgYXBwcywgZm9yIHdoYXRldmVyIHJlYXNvbiwgbXVzdCBiZSB0aXRsZWQg4oCYYXBw4oCZLiBPciBzbyBJ4oCZdmUgYmVlbiB0b2xkLiBTbyB5b3UgbmVlZCB0byBwdXQgZWFjaCBzaGlueSBhcHAgaW4gYSBkaWZmZXJlbnQgZm9sZGVyIHNpbmNlIHlvdSBjYW7igJl0IGhhdmUgdHdvIFIgZmlsZXMgd2l0aCB0aGUgc2FtZSBuYW1lIGluIHRoZSBzYW1lIGZvbGRlci4qIA0KDQpbQmxhbmsgc2hpbnkgYXBwXShodHRwczovL2RyaXZlLmdvb2dsZS5jb20vZHJpdmUvZm9sZGVycy8xXzZ1MnRrdTl5T25tMnJiWFpzM1BUZEprdEF3eXEtYVE/dXNwPWRyaXZlX2xpbmspICh0aGlzIGlzIGZvciB5b3UgdG8gZmlsbCBpbiBhcyB3ZSBnbykNCg0KW1RoaXMgUiBub3RlYm9vay5dKGh0dHBzOi8vcnB1YnMuY29tL2dhYnJpZWxsZV93b25nL3NoaW55ZGVtbzIwMjUxMDAyKQ0KDQojIFdoYXQgV2UncmUgQ292ZXJpbmcNCipZb3UgZG9u4oCZdCBuZWVkIHRvIHJlYWQgdGhpcyBhaGVhZCBvZiB0aW1lOyBpdOKAmXMgYmFzaWNhbGx5IG15IG5vdGVzLioNCg0KIyMgMTogQ29kaW5nIFJldmlldw0KSWYgeW91IGhhdmVu4oCZdCBjb2RlZCBmb3IgYSB3aGlsZSwgbm8gd29ycmllcyEgSGVyZeKAmXMgc29tZSBiYXNpYyBzeW50YXgvZ3JvdW5kIGd1aWRlbGluZXMgdG8gcmVtZW1iZXI6DQoNCiMjIyAxKSBDb21tb24gdGVybXM6DQotICoqVmFyaWFibGVzKio6IHRoZXNlIGFyZSB0aGUgYnVpbGRpbmcgYmxvY2tzIG9mIHlvdXIgY29kZSEgSXTigJlzIGhvdyB5b3Ugc3RvcmUgaW5mb3JtYXRpb24gd2hpbGUgY29kaW5nLiANCkluIFIsIHZhcmlhYmxlcyBjYW4gYmUgZGVmaW5lZCB1c2luZyANCmB2YXJpYWJsZU5hbWUgPC0gIndoYXRldmVyIHlvdSB3YW50IHRoZSB2YXJpYWJsZSB0byBiZSJgLiBUaGVyZSBhcmUgZGlmZmVyZW50IGtpbmRzIG9mIHZhcmlhYmxlcyBsaWtlIGludGVnZXJzIHZzLiB0cnVlL2ZhbHNlLCBidXQgd2UgZG9u4oCZdCByZWFsbHkgbmVlZCB0byB3b3JyeSBhYm91dCB0aGF0IHJpZ2h0IG5vdy4NCi0gKipGdW5jdGlvbnMqKjogZnVuY3Rpb25zIGFyZSBsaWtlIGNvbGxlY3Rpb25zIG9mIGNvZGUgdGhhdCBhbGxvdyB5b3UgdG8gZG8gc29tZXRoaW5nLiBGb3IgZXhhbXBsZSwgaW4gUiwgdGhlIGZ1bmN0aW9uIGBnZ3Bsb3QoKWAgYWxsb3dzIHlvdSB0byBtYWtlIGEgZ3JhcGguIA0KLSAqKlBhY2thZ2VzKio6IFIgb24gaXRzIG93biBpc27igJl0IHRoYXQgcG93ZXJmdWwsIHNvIG90aGVyIHBlb3BsZSBoYXZlIGNyZWF0ZWQgcGFja2FnZXMgb2YgZnVuY3Rpb25zIHRoYXQgY2FuIGJlIGluc3RhbGxlZCB0byBleHBhbmQgaXRzIGNhcGFiaWxpdGllcy4gDQogIC0gQ29tbW9uIFIgcGFja2FnZXM6IGh0dHBzOi8vc3VwcG9ydC5wb3NpdC5jby9oYy9lbi11cy9hcnRpY2xlcy8yMDEwNTc5ODctUXVpY2stbGlzdC1vZi11c2VmdWwtUi1wYWNrYWdlcyANCg0KIyMjIDIpIENvZGUgaXMgY2FzZSBzZW5zaXRpdmUhIEl04oCZcyBnb29kIHRvIGhhdmUgYSBzeXN0ZW0gZm9yIG5hbWluZyB2YXJpYWJsZXMgc28geW91IGRvbuKAmXQgZ2V0IHRyaXBwZWQgdXAuDQpGb3IgaW5zdGFuY2U6IA0KYGNhbWVsQ2FzZVZhcmlhYmxlIDwtICJUaGlzIGlzIGNhbWVsIGNhc2UiYA0Kb3INCmB1bmRlcnNjb3Jlc19iZXR3ZWVuX3dvcmRzIDwtICJUaGlzIGlzIGEgYml0IG1vcmUgY29tbW9uImANCg0KIyMjIDMpIENvZGUgaXMgdmVyeSBzdGVwLWJ5LXN0ZXAgYW5kIHByb2NlZHVyYWwuIFlvdSB1c3VhbGx5IGhhdmUgdG8gZGVmaW5lIGV2ZXJ5dGhpbmcsIHdoaWNoIGNhbiBiZSBmcnVzdHJhdGluZywgYnV0IGFsc28gZ2l2ZXMgeW91IGEgbG90IG9mIGZsZXhpYmlsaXR5IHRvIGN1c3RvbWl6ZS4NCg0KWypXaGVuIHlvdXIgcHJvZ3JhbSBkb2VzIGFzIGl04oCZcyBwcm9ncmFtbWVkIHRvIGRvKl0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vc2hvcnRzL21ybXFSb1JEckZnKQ0KDQojIyMgNCkgSWYgeW914oCZcmUgc2hhcmluZyBjb2RlIHdpdGggb3RoZXJzLCBpdOKAmXMgZ29vZCBwcmFjdGljZSB0byBjb21tZW50IG9uIHlvdXIgY29kZS4gDQpJdCBjYW4gYWxzbyBoZWxwIHlvdSBpZiB5b3XigJlyZSBnb2luZyBiYWNrIHRvIHlvdXIgd29yayBsYXRlciBhbmQgbmVlZCB0byByZXZpZXcvcmVtZW1iZXIgd2hhdCB5b3XigJl2ZSBkb25lLiANCg0KWW91IGNhbiBhZGQgY29tbWVudHMgdXNpbmcgIyAuIA0KDQpgYGB7cn0NCg0KIyBUaGlzIGlzIGEgY29tbWVudCEgVXN1YWxseSwgY29tbWVudCB0ZXh0IGlzIGluIGEgZGlmZmVyZW50IGNvbG91ci4NCg0KcHJpbnQoIlRoaXMgaXMgbm90IGEgY29tbWVudC4gVGhpcyBpcyBtZSB0ZWxsaW5nIHRoZSBjb21wdXRlciB0byBwcmludCBzb21ldGhpbmcuIikNCg0KYGBgDQoNCiMjIyA1KSBSIHVzZXMgTWFya2Rvd24hIA0KDQpUaGlzIG1lYW5zIHRoYXQgZm9yIHRleHQgZm9ybWF0dGluZywgaW5zdGVhZCBvZiBiZWluZyBhYmxlIHRvIGp1c3QgYm9sZCBvciBtYWtlIHRleHQgYSBoZWFkZXIgYnkgcHJlc3NpbmcgYSBidXR0b24sIHdlIGhhdmUgdG8gdGVsbCB0aGUgY29tcHV0ZXIgaG93IHdlIHdhbnQgdGV4dCBmb3JtYXR0ZWQgdXNpbmcgY29kZSAoc2lkZSBub3RlIGZvciBhbGwgeW91IHNvY2lhbCBtZWRpYSBwZW9wbGU6IHlvdSBjYW4gdXNlIHRoaXMgdG8gZm9ybWF0IHRleHQgb24gRGlzY29yZCB0b28gOikpICkNCg0KRm9yIGV4YW1wbGUsIG9uIGl0cyBvd24gbGluZSwgIyDigJxUaGlzIHdvdWxkIGNyZWF0ZSBhIGZpcnN0LWxldmVsIChsYXJnZSkgaGVhZGVy4oCdDQoNCkluIFNoaW55LCB3ZSB1c2UgaDEoIlRoaXMgaXMgYSBmaXJzdCBsZXZlbCBoZWFkZXIpIGFuZCBzdHJvbmcoIlRoaXMgbWFrZXMgYm9sZCB0ZXh0LikuDQoNCiMjIDI6IFRoZSAnUGFydHMnIG9mIGEgU2hpbnkgQXBwDQoNCkF0IGl0cyBtb3N0IGJhc2ljLCBTaGlueSBhcHBzIGhhdmUgdGhyZWUgcGFydHM6DQoNCjEuIFVJOiB0aGlzIGlzIHRoZSDigJhmcm9udCBlbmTigJkuIEl04oCZcyB3aGF0IGEgdXNlciBzZWVzLiBJbiBoZXJlLCB3ZSBpbmNsdWRlOg0KICAgIC0gW01vc3Qgb2ZdIHRoZSB0ZXh0IHRoYXQgd2UgaW5jbHVkZSBpbiB0aGUgd2VicGFnZS4gDQogICAgLSBBbnkgaW50ZXJhY3RpdmUgY29tcG9uZW50cyBsaWtlIGJ1dHRvbnMgb3Igc2xpZGVycy4NCiAgICAtIEFlc3RoZXRpYyB0aGVtZSBzZXR0aW5ncy4NCjIuIFNlcnZlcjogdGhpcyBpcyB0aGUg4oCYYmFjayBlbmTigJkuIEl04oCZcyB0aGUgY29kZSB0aGF0IG1ha2VzIHRoZSBpbnRlcmFjdGl2ZSBzdHVmZiBpbnRlcmFjdGl2ZSEgSGVyZSwgd2UgaW5jbHVkZSBvdXRwdXQgc3RhdGVtZW50cyB0aGF0IHRlbGwgdGhlIGNvbXB1dGVyIGhvdyB0byBpbnRlcmFjdCB3aXRoIG91ciBVSSBhbmQgZG8g4oCYc3R1ZmbigJkgKEkga25vdyB0aGF04oCZcyB2YWd1ZSEgV2XigJlsbCBzZWUgd2hhdCB0aGF0IG1lYW5zIGEgYml0IGxhdGVyKS4gDQozLiBDYWxsIGZ1bmN0aW9uOiB0aGlzIGlzIGFsd2F5cyBgc2hpbnlBcHAodWksIHNlcnZlcilgLiBJdCB0ZWxscyB0aGUgY29tcHV0ZXIgdG8gcnVuIHlvdXIgYXBwLiANCg0KDQojIyAzOiBCdWlsZGluZyB0aGUgSW5wdXRzIChVSSkNCg0KKipCdXQgZmlyc3QsIGxpYnJhcmllcyEqKg0KUmVtZW1iZXIgdGhvc2UgcGFja2FnZXMgd2UgaW5zdGFsbGVkPyBXZSBvbmx5IG5lZWQgdG8gaW5zdGFsbCBwYWNrYWdlcyBvbmNlIGJ1dCB0aGV5IG5lZWQgdG8gYmUgbG9hZGVkIGV2ZXJ5IHRpbWUgd2UgaGF2ZSBhIG5ldyBmaWxlLiBXZSBkbyB0aGF0IHdpdGggdGhlIGNvbW1hbmQgYGxpYnJhcnkoKWAuDQoNCkNvcHkgdGhlIGJlbG93IHRvIHRoZSB0b3Agb2YgeW91ciBTaGlueSBhcHA6DQoNCmBgYHtyfQ0KDQojIExvYWRpbmcgbmVjZXNzYXJ5IHBhY2thZ2VzDQpsaWJyYXJ5KHNoaW55KSANCg0KIyBUaGUgZm9sbG93aW5nIGFyZSBmb3IgZnVydGhlciBjdXN0b21pemluZyBTaGlueS4gV2UgZG9uJ3QgdXNlIHRoZW0gYWxsIGhlcmUuDQpsaWJyYXJ5KHNoaW55dGhlbWVzKQ0KbGlicmFyeShic2xpYikNCmxpYnJhcnkoc2hpbnlkYXNoYm9hcmQpDQoNCmBgYA0KDQoqKkE6IEJsb2NrIFN0cnVjdHVyZSoqDQpJbWFnaW5lIHRoYXQgeW91ciBVSSBpcyBhIGJsYW5rIHJlY3RhbmdsZSB0aGF0IHlvdSBuZWVkIHRvIGRpdmlkZSB1cCBpbnRvIGJveGVzLCB3aXRoIGVhY2ggYm94IGJlaW5nIGEgZGlmZmVyZW50IHBhcnQgb2YgdGhlIHNpdGUuIA0KDQpUaGUgY29tbW9uIHdheSB0byBkbyB0aGlzIGlzIGJ5IHVzaW5nIHRoZSBgZmx1aWRQYWdlKClgIGZ1bmN0aW9uLiBIYXZpbmcganVzdCBgZmx1aWRQYWdlKClgIGlzIGxpa2UgaGF2aW5nIGV2ZXJ5dGhpbmcgYWxsIGluIG9uZSByZWN0YW5nbGUsIHdpdGggbm8gZGl2aWRlcnMgd2hhdHNvZXZlci4NCg0KQnV0IHNvbWV0aW1lcyB3ZSB3YW50IGRpdmlkZXJzISBNYXliZSB3ZSB3YW50IGFuIGV4dHJhIGJveCBvbiB0aGUgc2lkZS4gVGhhdOKAmXMgYSBgc2lkZWJhckxheW91dCgpYCwgc28gd2UgYWRkIHRoYXQgd2l0aGluIHRoZSBgZmx1aWRQYWdlKClgOg0KDQpgYGB7cn0NCg0KdWkgPC0gZmx1aWRQYWdlKA0KICBzaWRlYmFyTGF5b3V0KCkNCikNCg0KYGBgDQoNCldoZW4gd2UgdXNlIGEgYHNpZGViYXJMYXlvdXRgLCB3ZSBzdGlsbCBuZWVkIHRvIGRlZmluZSBlYWNoIG9mIHRoZSBwYW5lbHMgd2l0aGluIGl0LiBIZXJlLCBJIGFtIGFkZGluZyBib3RoIHRoZSBgbWFpblBhbmVsKClgIGFuZCB0aGUgYHNpZGViYXJQYW5lbCgpYC4gWW914oCZbGwgYWxzbyBub3RpY2UgSSBhZGRlZCBpbnN0cnVjdGlvbnMgdG8gc3BlY2lmeSB0aGF0IEkgd2FudCB0aGUgc2lkZWJhciB0byBiZSBvbiB0aGUgbGVmdCAoYnkgZGVmYXVsdCwgaXTigJlzIG9uIHRoZSByaWdodCksIGFuZCBJIGFkZGVkIHRleHQgdG8gZWFjaCBvZiB0aGUgcGFuZWxzIHVzaW5nIHRoZSDigJhwYXJhZ3JhcGjigJkgKGBwKClgKSB0ZXh0IG9wdGlvbi4gDQoNCmBgYHtyfQ0KDQp1aSA8LSBmbHVpZFBhZ2UoDQoJc2lkZWJhckxheW91dChwb3NpdGlvbiA9ICJsZWZ0IiwNCgkJbWFpblBhbmVsKHAoIkhlcmUgbGllcyB0aGUgbWFpbiBwYW5lbC4iKSksDQoJCXNpZGViYXJQYW5lbChwKCJBbmQgdGhpcyBpcyB0aGUgc2lkZSBwYW5lbCIpKSwNCiAgKQ0KKQ0KDQpgYGANCkluIHRoZSBUaW1teeKAmXMgaW5kZXgsIEkgZGlkIHNvbWV0aGluZyBzbGlnaHRseSBtb3JlIGNvbXBsZXggd2l0aCBkYXNoYm9hcmQgbGF5b3V0cyBhbmQgYm94ZXMsIGJ1dCB3ZSB3b27igJl0IGdvIHRoZXJlIGZvciBub3cuDQoNCioqQjogQWRkaW5nIGlucHV0cyoqDQoNCklucHV0cyBpbiBTaGlueSBhcmUgdGhpbmdzIHRoYXQgcGVvcGxlIGNhbiBpbnRlcmFjdCB3aXRoIC0gc2xpZGVycywgYnV0dG9ucywgcmFkaW8gYnV0dG9ucywgY2hlY2tib3hlcywgZXRjLiBBbmQgbGF0ZXIsIHdlIGNhbiDigJhsaW5rIHRoZW0gdXDigJkgdG8gY2VydGFpbiBvdXRwdXRzIHRvIG1ha2UgdGhhdCBvdXRwdXQgcmVhY3RpdmUuDQotIEEgbGlzdCBvZiBjb21tb24gaW5wdXRzOiBbU2hpbnkgLSBVSSBJbnB1dHMgXShodHRwczovL3NoaW55LnBvc2l0LmNvL3IvZ2V0c3RhcnRlZC9idWlsZC1hbi1hcHAvcmVhY3RpdmUtZmxvdy91aS1pbnB1dHMuaHRtbCkNCi0gVGhlIGJhc2ljIGZvcm11bGEgZm9yIGFuIGlucHV0IGlzIGBpbnB1dEZ1bmN0aW9uKGlucHV0TmFtZSwgbGFiZWwsIG9wdGlvbmFsQ3VzdG9taXphdGlvbnMpYC4NCg0KRm9yIHRvZGF5LCB0byBvdXIgYG1haW5QYW5lbCgpYCwgd2XigJlyZSBnb2luZyB0byBhZGQgdHdvIHNsaWRlciBpbnB1dHMgdGhhdCBzb21lb25lIGNhbiB1c2UgdG8gc3BlY2lmeSBob3cgZmFyIGF3YXkgdGhlaXIgaG9tZSBpcyBmcm9tIGEgVGltIEhvcnRvbnMgYW5kIGhvdyBtdWNoIHNob3VsZCBiZSBjaGFyZ2VkIHBlciBrbSBvZiB0cmF2ZWw6DQoNCmBgYHtyfQ0KDQptYWluUGFuZWwocCgiSGVyZSBsaWVzIHRoZSBtYWluIHBhbmVsLiIpLA0KICAgICAgICAgIHNsaWRlcklucHV0KCJkaXN0YW5jZV90aW1zIiwgDQogICAgICAgICAgICAgICAgICAgICAgc3Ryb25nKCJIb3VycyB0byB0aGUgbmVhcmVzdCBUaW0gSG9ydG9ucyIpLCANCiAgICAgICAgICAgICAgICAgICAgICBtaW4gPSAwLCBtYXggPSAxMCwgdmFsdWUgPSAyLCBzdGVwID0gMC41KSwNCiAgICAgICAgICANCiAgICAgICAgICBzbGlkZXJJbnB1dCgieW91IHRyeSB0aGlzIG9uZSEgY2FsbCBpdCBjb3N0X2ttX3RyYXZlbC4gDQogICAgICAgICAgICAgICAgICAgICAgTWFrZSBpdCByYW5nZSBmcm9tIDAtNSwgd2l0aCBhIGRlZmF1bHQgdmFsdWUgb2YgMC43MiBhbmQgYSBzdGVwIG9mIDAuMDEiKQ0KICAgICAgICAgICkNCg0KDQpgYGANCg0KSW4gdGhpcyBjYXNlLCBmb3IgdGhlIGZpcnN0IGlucHV0LCBgc2xpZGVySW5wdXQoKSBgaXMgdGhlIGZ1bmN0aW9uIChjcmVhdGluZyBhIHNsaWRlciksIOKAnGRpc3RhbmNlX3RpbXPigJ0gaXMgdGhlIG5hbWUgSSBhc3NpZ25lZCB0byB0aGUgc2xpZGVyLCB0aGUgdGV4dCB3aXRoaW4gc3Ryb25nKCkgaXMgdGhlIGxhYmVsLCBtaW4gaXMgdGhlIHNtYWxsZXN0IG51bWJlciBhbGxvd2VkIG9uIHRoZSBzbGlkZXIsIG1heCBpcyB0aGUgbGFyZ2VzdCwgdmFsdWUgaXMgdGhlIGRlZmF1bHQgdmFsdWUsIGFuZCBzdGVwIGlzIGhvdyBtdWNoIHRoZSB2YWx1ZSB3aWxsIGNoYW5nZSBieSB3aGVuIHlvdSBtb3ZlIHRoZSBzbGlkZXIuIA0KDQoqKkM6IEFkZGluZyB0aGUgb3V0cHV0cyAocGFydCAxKikqKjoNCg0KRm9yIGV2ZXJ5IGlucHV0LCB0aGVyZSBtdXN0IGJlIGFuIG91dHB1dCEgV2XigJlsbCBiZSBjb2RpbmcgdGhlIGZ1bmN0aW9uYWxpdHkgb2YgdGhlIG91dHB1dCBpbiB0aGUgc2VydmVyIHNlY3Rpb24sIGJ1dCBmb3Igbm93LCB3ZSBuZWVkIHRvIHRlbGwgdGhlIG91dHB1dCB3aGVyZSBpdOKAmXMgZ29pbmcgdG8gZ28gaW4gb3VyIHdlYnBhZ2UuIEhlcmUsIHdl4oCZcmUgZ29pbmcgdG8gYWRkIHRoZSBvdXRwdXQgKHRoZSBjYWxjdWxhdGlvbikgdG8gb3VyIHNpZGViYXI6DQoNCmBgYHtyfQ0KDQpzaWRlYmFyUGFuZWwocCgiQW5kIHRoaXMgaXMgdGhlIHNpZGUgcGFuZWwiKSwNCiAgICAgICAgICAgICB0ZXh0T3V0cHV0KCJ0cmF2ZWxfY29zdCIpLCkNCg0KYGBgDQoNCkp1c3QgbGlrZSB0aGVyZSBhcmUgbWFueSBraW5kcyBvZiBpbnB1dHMsIHlvdSBjYW4gaGF2ZSBtYW55IGtpbmRzIG9mIG91dHB1dHMsIGluY2x1ZGluZyBpbnRlcmFjdGl2ZSBncmFwaHMsIG1hcHMsIGltYWdlcywgdGFibGVzLCBVSSBldGMuIC0gW1NoaW55IC0gRGlzcGxheSByZWFjdGl2ZSBvdXRwdXRdKGh0dHBzOi8vc2hpbnkucG9zaXQuY28vci9nZXRzdGFydGVkL3NoaW55LWJhc2ljcy9sZXNzb240LykuIA0KDQojIyA0OiBCdWlsZGluZyB0aGUgT3V0cHV0cyAoU2VydmVyKQ0KUmlnaHQgbm93LCB0aGF0IGB0ZXh0T3V0cHV0KClgIGlzIGRvaW5nIG5vdGhpbmcsIGJlY2F1c2Ugd2UgaGF2ZW7igJl0IHRvbGQgaXQgd2hhdCB0aGUgb3V0cHV0IHNob3VsZCBiZS4gU28gbm93LCB3ZeKAmWxsIGJlIGRvaW5nIHRoYXQgaW4gdGhlIHNlcnZlciBzZWN0aW9uIG9mIHRoZSBjb2RlLiANCg0KVGhlIGJhc2ljIGZvcm11bGEgZm9yIGFuIG91dHB1dCBpcyBgb3V0cHV0JG91dHB1dE5hbWUgPC0gcmVuZGVyVHlwZSh7IGFkZF9pbnN0cnVjdGlvbnNfaGVyZX0pYC4gVGhpcyBpcyB0ZWxsaW5nIHRoZSBjb21wdXRlciB0aGF0IHRoZSBvdXRwdXQgdmFyaWFibGUgYG91dHB1dE5hbWVgIHNob3VsZCBgcmVuZGVyYCAoY3JlYXRlKSBhIHBhcnRpY3VsYXIgYFR5cGVgLCB3aGV0aGVyIHRoYXQgYmUgYSBtYXAsIGFuIGltYWdlLCBvciBpbiBvdXIgY2FzZSwgdGV4dCB0aGF0IGNoYW5nZXMgYmFzZWQgb24gdGhlIGlucHV0LiBXZSBhZGQgdGhlIGluc3RydWN0aW9ucyBmb3IgaG93IHRvIHRyZWF0IHRoZSBvdXRwdXQgaW4gYGFkZF9pbnN0cnVjdGlvbnNfaGVyZWAuIA0KDQpGb3Igb3VyIGFwcGxpY2F0aW9uLCB3ZSB3YW50IHRvIHJlbmRlciAoY3JlYXRlKSBUZXh0IHRoYXQgY2hhbmdlcyBiYXNlZCBvbiB0aGUgaW5wdXQgb2YgdGhlIG51bWJlcnMuIFdlIGRvIHRoaXMgd2l0aDoNCg0KYGBge3J9DQoNCm91dHB1dCR0cmF2ZWxfY29zdCA8LSByZW5kZXJUZXh0KHsNCiAgcGFzdGUoIlRoZSB0cmF2ZWwgY29zdCBpczogJCIsIGlucHV0JGRpc3RhbmNlX3RpbXMgKiA3MCAqIDIgKiBpbnB1dCRjb3N0X2ttX3RyYXZlbCkNCn0pDQoNCmBgYA0KDQpUaGUgcGFzdGUgc3RhdGVtZW50IHRlbGxzIHRoZSBjb21wdXRlciB0aGUgb3V0cHV0IHNob3VsZCBiZSB0aGUgdGV4dCAo4oCcVGhlIHRyYXZlbCBjb3N04oCm4oCdKSBQTFVTIGEgY2FsY3VsYXRpb24gYmFzZWQgb24gdGhlIGlucHV0IHN0YXRlbWVudC4gDQoNCklmIHlvdSBydW4gdGhpcyBhcyBpcywgeW914oCZbGwgbm90aWNlIGEgZmV3IHdlaXJkIGZvcm1hdHRpbmcgcXVpcmtzIC0gdGhlcmUgaXMgYSBzcGFjZSBiZXR3ZWVuIHRoZSAkIGFuZCB0aGUgbnVtYmVyLCBhbmQgdGhlIG51bWJlciBpc27igJl0IGZvcm1hdHRlZCBhcyB3ZSBub3JtYWxseSB3b3VsZCBtb25leSAoaS5lLiwgJCAzNDAuMiBpbnN0ZWFkIG9mICQzNDAuMjApLiBJIGZpeGVkIHRoYXQgd2l0aCBhIHNsaWdodGx5IG1vcmUgY29tcGxpY2F0ZWQgcmVuZGVyIGJlbG93Og0KDQpgYGB7cn0NCg0Kb3V0cHV0JHRyYXZlbF9jb3N0IDwtIHJlbmRlclRleHQoew0KICAgIG1pbGVhZ2VfdG90YWwgPC0gZm9ybWF0QyhpbnB1dCRkaXN0YW5jZV90aW1zICogNzAgKiAyKiBpbnB1dCRjb3N0X2ttX3RyYXZlbCwgZm9ybWF0ID0gImYiLCBkaWdpdHMgPSAyLCBiaWcubWFyayA9ICIsIikNCiAgICBwYXN0ZSgiVGhlIGVzdGltYXRlZCByb3VuZC10cmlwIHRyYXZlbCBjb3N0cyBiYXNlZCBvbiB0aGlzIGxlbmd0aCBvZiB0aW1lIGFuZCBtaWxlYWdlIGNvc3QgaXM6IiwgcGFzdGUwKCIkIiwgbWlsZWFnZV90b3RhbCwgIi4iKSkNCiAgfSkNCmBgYA0KDQpJbiB0aGlzIHJlbmRlciwgSSBmaXJzdCBjcmVhdGUgYSBuZXcgdmFyaWFibGUsIGBtaWxlYWdlX3RvdGFsYCwgd2hpY2ggaXMgYSBmb3JtYXR0ZWQgdmVyc2lvbiBvZiB0aGUgY2FsY3VsYXRpb24uIEkgdGhlbiBhZGQgdGhlIHBhc3RlIHN0YXRlbWVudCwgYW5kIGEgc2Vjb25kIHBhc3RlIHN0YXRlbWVudCB3aXRoaW4gaXQgZm9yIHRoZSBjYWxjdWxhdGlvbiB3aXRoIGEgJCBpbmNsdWRlZC4gDQoNCiMjIDU6IFNvbWUgQWVzdGhldGljIENoYW5nZXMgKGJhY2sgdG8gdGhlIFVJISkNCg0KSWYgeW91IGxvYWQgdXAgd2hhdCB3ZSBoYXZlIG5vdywgaXTigJlzIGZ1bmN0aW9uYWwsIGJ1dCBub3QgcHJldHR5LiBXZSBjYW4gY2hhbmdlIHRoYXQgYmFjayBpbiB0aGUgVUkgc2VjdGlvbiENCg0KVGhlIGVhc2llc3Qgd2F5IHRvIGNoYW5nZSB0aGVtZXMgaXMgdG8gYWRkIGEgcHJlLW1hZGUgdGhlbWUuIFRoZXJlIGFyZSBhIGJ1bmNoIGF2YWlsYWJsZSBvbmxpbmUuIEkgbGlrZSB0aGUgdGhlbWVzIG9mZmVyZWQgYnkgW2JzbGliXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL2JzbGliLykgYW5kIFtzaGlueXBhY2thZ2UuXShodHRwczovL3JzdHVkaW8uZ2l0aHViLmlvL3NoaW55dGhlbWVzLykgRm9yIGV4YW1wbGU6DQoNCmBgYHtyfQ0KZmx1aWRQYWdlKHRoZW1lID0gc2hpbnlUaGVtZSgic2ltcGxleCIpLA0KICAgICAgICAgIHNpZGViYXJMYXlvdXQoKSkNCmBgYA0KQnV0IHNvbWV0aW1lcywgeW91IHdhbnQgZXZlbiBtb3JlIGNvbnRyb2wgb3ZlciB5b3VyIHRoZW1lISBZb3UgY2FuIGRvIHRoYXQgd2l0aCBjdXN0b21pemluZyB0aGUgb3B0aW9ucyBpbiBic2xpYiwgYW5kLCBmb3IgZXZlbiBtb3JlIGN1c3RvbWl6YWJpbGl0eSwgc29tZSBrbm93bGVkZ2Ugb2YgSFRNTCBhbmQgQ1NTOg0KDQpgYGB7cn0NCg0KIHRoZW1lID0gYnNfdGhlbWUoYmcgPSAid2hpdGUiLCAjIGNvbG91ciBzZXR0aW5ncw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmcgPSAiYmxhY2siLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW1hcnkgPSAiI0RDMEYyRCIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJhc2VfZm9udCA9IGZvbnRfZ29vZ2xlKCJOb3RvIFNhbnMiKSwgIyBmb250IHNldHRpbmdzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2RlX2ZvbnQgPSBmb250X2dvb2dsZSgiTm90byBTYW5zIikpLA0KICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgdGFncyRoZWFkKCAjIHNvbWUgY3VzdG9tIEhUTUwgdG8gY2hhbmdlIHRoZSBhZXN0aGV0aWMgb2YgdGhlIHNpdGUuIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHVzdWFsbHkgcHJlZmVyYWJsZSB0byBkbyB0aGlzIGluIGEgc2VwYXJhdGUgZG9jdW1lbnQgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgaWYgeW91J3JlIGRvaW5nIGFueXRoaW5nIGNvbXBsZXgNCiAgICAgICAgICAgICAgICAgIHRhZ3Mkc3R5bGUoSFRNTCgiYm9keSB7cGFkZGluZzogMjVweDt9IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoMiB7Zm9udF9zaXplOiAyMHB4O30NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaDMge2ZvbnRfc2l6ZTogMTZweDt9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAge2ZvbnRfc2l6ZTogMTRweDt9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIikpDQogICAgICAgICAgICAgICAgKQ0KDQpgYGANCg0KIyMgNjogUnVuIGFuZCBwdWJsaXNoIHRoZSBhcHANCg0KVGhlIGxhc3Qgc3RlcCAod2VsbCwgYWN0dWFsbHkgb25lIEkgZG8gYWxsIHRoZSB0aW1lKSwgaXMgdG8gcnVuIHRoZSBhcHAhDQoNCllvdSBjYW4gYWxzbyBwdWJsaXNoIHlvdXIgYXBwcyBvbmxpbmUgZm9yIGZyZWUgKHdpdGggc29tZSBzcGFjZS9hY2Nlc3MgbGltaXRhdGlvbnMpOiBbU2hpbnkgLSBTaGFyZSB5b3VyIGFwcHNdKGh0dHBzOi8vc2hpbnkucG9zaXQuY28vci9nZXRzdGFydGVkL3NoaW55LWJhc2ljcy9sZXNzb243LykuDQoNCiMgT3RoZXIgdGhpbmdzIHRvIGNoZWNrIG91dCBpZiB5b3XigJlyZSBpbnRlcmVzdGVkDQoNCkRlZmluaXRpdmUgdGV4dGJvb2sgb24gdXNpbmcgU2hpbnk6IFtXZWxjb21lIHwgTWFzdGVyaW5nIFNoaW55XShodHRwczovL21hc3RlcmluZy1zaGlueS5vcmcvaW5kZXguaHRtbCkNCg0KU29tZSBmZWF0dXJlZCBTaGlueSBhcHBzOiBbU2hpbnkgZm9yIFIgR2FsbGVyeV0oaHR0cHM6Ly9zaGlueS5wb3NpdC5jby9yL2dhbGxlcnkvKQ0KDQoNCg==