plotly

There are two main ways to create a plotly object: either by transforming a ggplot2 object into a plotly object via ggplotly() or by directly initializing a plotly object with plot_ly()/plot_geo()/plot_mapbox(). Plotly has a rich and complex set of features. The most common features are:

  • Tooltip “hover” info
  • Zoom in and out of graphs
  • Users can export graphs as an image
  • Integrating multiple graphs
  • Template hover info
  • Animations and moving graphics

One can feed a ggplot to plotly to render ggplot via plotly by using ggplotly() - a wrapper of ggplot. Compared to the base R plotting function plot(), plot_ly() is more technical and poorly documented. However, the following factors may make plotly the best option:

  • graphs presented in a digital/online format
  • users interact with the graph
  • more customizable than ggplot
  • rendering graphics in a higher resolution

In this note, we introduce the basic statistical graphics using the plotly package. plotly graphics automatically contain interactive elements that allow users to modify, explore, and experience the visualized data in new ways.

The coding effort is similar to that of SAS ODS graphics. To use plot_ly(), we need to install (if not done) and load the plotly package. We use the well-known iris data set in the following plots. A nice plotly cheat sheet can be found at https://pengdsci.github.io/STA553VIZ/w06/r_plotly_cheat_sheet.pdf

The grammar of plot_ly() is similar to that of ggplot(). By specifying text option, we can customize the hover text. If text option is not specified, the default hover text is just the x= and y= coordinates of that data point, and the variables in the color= option and marks= option. In addition, we can group the data points by color and size just as in ggplot2. To add a layer in the graph, such as fitting a linear regression line, we use add_trace.

ScatterPlot

The Default Plot

First, we make a simple interactive scatter plot using sepal length and width. We can view the information about the variables and color coding information in the hover text. The labels of axes and legend titles and labels are default.

plot_ly(
    data = iris,
    x = ~Sepal.Length,  # Horizontal axis 
    y = ~Sepal.Width,   # Vertical axis 
    color = ~factor(Species),  # must be a numeric factor
     type = "scatter",
     mode = "markers")

Including Additional Information Through hovertemplate

We can also add additional information to the plot to enhance the interactivity of the plot. For example, we can

  1. modify the point size using the value of a numerical variable variable;

  2. add text to the hover text using text option to show the class label;

  3. formulate the hover text using hovertemplate option.

hovertemplate

plot_ly(
    data = iris,
    x = ~Sepal.Length,         # Horizontal axis 
    y = ~Sepal.Width,          # Vertical axis 
    customdata = ~Petal.Width,
    color = ~factor(Species),  # must be a numeric factor
     hovertext = ~Species,          # show the species in the hovertext
     hoverlabel = ~Petal.Width,
     ####
     marker = list(size = ~Petal.Length, sizeref = .05, sizemode = 'area'),
     #
     alpha  = 0.9,
     type = "scatter",
     mode = "markers",
     ## using the following hovertemplate() to add the information of the
     ## Two numerical variables to the hover text.
     hovertemplate = paste('<b>Sepal Width<b>: %{y}',
                           '<br><b>Sepal Length</b>: %{x}',
                           '<br><b>Petal Length</b>: %{marker.size:,}',
                           '<br><b>Petal Width</b>: %{customdata}',
                           '<br><b>Species</b>: %{hovertext}',
                           "<extra></extra>")
     ###
)

Enhancing the Plot with Layout() Function

Titles and axis labels are important in any visualization, to include a meaningful title, informative labels, and annotations to the plotly plot, we can use layout() function. The following code only gives you some design ideas you can use to enhance your plotly charts. The detailed list of configurations can be found from the plotly’s reference page at https://plotly.com/r/reference/layout/

plot_ly(
    data = iris,
    x = ~Sepal.Length,  # Horizontal axis 
    y = ~Sepal.Width,   # Vertical axis 
    color = ~factor(Species),  # must be a numeric factor
    #text = ~Species,
    text = ~paste("Petal Length: ", Petal.Length,
                   "<br>Petal Width: ", Petal.Width,
                   "<br>Species: ", Species), 
     # Show the species in the hover text
     ## using the following hovertemplate() to add the information of the
     ## Two numerical variables to the hover text.
     ### Use the following hover template to display more information
     hovertemplate = paste('<i><b>Sepal Width<b></i>: %{y}',
                           '<br><b>Sepal Length</b>:  %{x}',
                           '<br><b>%{text}</b>'),
     alpha  = 0.6,
     marker = list(size = ~Petal.Length, sizeref = .05, sizemode = 'area' ),
     type = "scatter",
     mode = "markers",
     ## graphic size
     width = 700,
     height = 500
   ) %>%
    layout(  
      ### Title 
      title =list(text = "Sepal Length vs Sepal Width", 
                  font = list(family = "Times New Roman",  # HTML font family  
                                size = 18,
                               color = "red")), 
      ### legend
      legend = list(title = list(text = 'species',
                                 font = list(family = "Courier New",
                                               size = 14,
                                              color = "green")),
                    bgcolor = "ivory",
                    bordercolor = "navy",
                    groupclick = "togglegroup",  # one of  "toggleitem" AND "togglegroup".
                    orientation = "v"  # Sets the orientation of the legend.
                    ),
      ## margin of the plot
      margin = list(
              b = 100,
              l = 100,
              t = 100,
              r = 50
      ),
      ## Background
      plot_bgcolor ='#f7f7f7', 
      ## Axes labels
             xaxis = list( 
                    title=list(text = 'Sepal Length',
                               font = list(family = 'Arial')),
                    zerolinecolor = 'red', 
                    zerolinewidth = 2, 
                    gridcolor = 'white'), 
            yaxis = list( 
                    title=list(text = 'Sepal Width',
                               font = list(family = 'Arial')),
                    zerolinecolor = 'purple', 
                    zerolinewidth = 2, 
                    gridcolor = 'white'),
       ## annotations
       annotations = list(  
                     x = 0.7,   # between 0 and 1. 0 = left, 1 = right
                     y = 1.5,   # between 0 and 1, 0 = bottom, 1 = top
                  font = list(size = 12,
                              color = "darkred"),   
                  text = "The point size is proportional to the sepal length",   
                  xref = "paper",  # "container" spans the entire `width` of the 
                                   #  lot. "paper" refers to the width of the 
                                   #  plotting area only. yref = "paper",  
                                   #  same as xref.
               xanchor = "center", #  horizontal alignment with respect to its x position
               yanchor = "bottom", #  similar to xanchor  
             showarrow = FALSE)
    )

We also write a theme just like we did in the regular ggplot. The following is an example.

myPlotlyLayout <- function(anyObjName){  # anyString is required initial argument.
                                        # it can be any string a,b,c, .........
  layout(anyObjName,  
      ### Title 
      title =list(text = "Sepal Length vs Sepal Width", 
                          font = list(family = "Times New Roman",  # HTML font family  
                                        size = 18,
                                       color = "red")), 
      ### legend
      legend = list(title = list(text = 'species',
                                 font = list(family = "Courier New",
                                               size = 14,
                                              color = "green")),
                    bgcolor = "ivory",
                    bordercolor = "navy",
                    groupclick = "togglegroup",  # one of  "toggleitem" AND "togglegroup".
                    orientation = "v"  # Sets the orientation of the legend.
                    
                    ),
      ## margin of the plot
      margin = list(
              b = 120,
              l = 50,
              t = 120,
              r = 50
      ),
      ## Background
      plot_bgcolor ='#f7f7f7', 
      ## Axes labels
             xaxis = list( 
                    title=list(text = 'Sepal Length',
                               font = list(family = 'Arial')),
                    zerolinecolor = 'red', 
                    zerolinewidth = 2, 
                    gridcolor = 'white'), 
            yaxis = list( 
                    title=list(text = 'Sepal Width',
                               font = list(family = 'Arial')),
                    zerolinecolor = 'purple', 
                    zerolinewidth = 2, 
                    gridcolor = 'white'),
       ## annotations
       annotations = list(  
                     x = 0.7,   # between 0 and 1. 0 = left, 1 = right
                     y = 0.9,   # between 0 and 1, 0 = bottom, 1 = top
                  font = list(size = 12,
                              color = "darkred"),   
                  text = "The point size is proportional to the sepal length",   
                  xref = "paper",  # "container" spans the entire `width` of the plot. 
                                   # "paper" refers to the width of the plotting area only.  
                  yref = "paper",  #  same as xref
               xanchor = "center", #  horizontal alignment with respect to its x position
               yanchor = "bottom", #  similar to xanchor  
             showarrow = FALSE  
           )
  )
       }
plot_ly(
    data = iris,
    x = ~Sepal.Length,  # Horizontal axis 
    y = ~Sepal.Width,   # Vertical axis 
    color = ~factor(Species),  # must be a numeric factor
     text = ~Species,     # show the species in the hover text
     ## using the following hovertemplate() to add the information of the
     ## Two numerical variables to the hover text.
     hovertemplate = paste('<i><b>Sepal Width<b></i>: %{y}',
                           '<br><b>Sepal Length</b>:  %{x}',
                           '<br><b>%{text}</b>'),
     alpha  = 0.9,
     marker = list(size = ~Petal.Length, sizeref = .05, sizemode = 'area' ),
     type = "scatter",
     mode = "markers",
     ## graphic size
     width = 700,
    height = 500) %>% myPlotlyLayout()

External Images for plotly Charts

As we did in the base R and ggplot, we illustrate how to add images to plotly charts: inserting an image and setting an image background.

Inserting Images to plotly Charts

The following example shows how to use layout function to insert an external image to a plotly scatter plot. Comparing the steps of inserting an external image to the base R and ggplot, it is relatively straightforward and flexible to perform the same task in plotly. See the comments in the code to place the image in an appropriate location.

plot_ly(
    data = iris,
    x = ~Sepal.Length,         # Horizontal axis 
    y = ~Sepal.Width,          # Vertical axis 
    customdata = ~Petal.Width,
    color = ~factor(Species),  # must be a numeric factor
     hovertext = ~Species,          # show the species in the hover text
     hoverlabel = ~Petal.Width,
     ####
     marker = list(size = ~Petal.Length, sizeref = .05, sizemode = 'area'),
     #
     alpha  = 0.9,
     type = "scatter",
     mode = "markers",
     ## using the following hovertemplate() to add the information of the
     ## two numerical variable to the hover text.
     hovertemplate = paste('<b>Sepal Width<b>: %{y}',
                           '<br><b>Sepal Length</b>: %{x}',
                           '<br><b>Petal Length</b>: %{marker.size:,}',
                           '<br><b>Petal Width</b>: %{customdata}',
                           '<br><b>Species</b>: %{hovertext}',
                           "<extra></extra>") ) %>%
    layout( 
      images = list(  
      list(
        source = "https://pengdsci.github.io/STA553VIZ/w06/img/iris.jpeg",  
        xref="paper",  # without assuming having a coordinate system, we
        yref="paper",  # can use paper size to set up a relative location
                       # to place an image.
                       # We can also use  xref="x domain", yref="y domain", 
                       # to set up a coordinate system to place an image.
        x = 0,     # value between 0 and 1 - representing percentage from left
                   # hand side of x (0) and the right hand side of x (1).
        y = 1,     # x = 0, y = 1 ==> "topleft"
        sizex = .2,   #  image size - horizontal 
        sizey = .2,   #  vertical size
        xanchor="left",  # image location - 
        yanchor="top" ,
        opacity = 0.6    # adjusting image opacity
      )  
    )  
  )

Setting Image Background for plotly Charts

pal <- c("#332288", "#117733", "#882255")
pal <- setNames(pal, c("virginica", "setosa", "versicolor"))

plot_ly(
    data = iris,
    x = ~Sepal.Length,         # Horizontal axis 
    y = ~Sepal.Width,          # Vertical axis 
    customdata = ~Petal.Width,
    color = ~factor(Species),   # must be a numeric factor
    colors = pal,               # custom color palette 
     hovertext = ~Species,      # show the species in the hover text
     hoverlabel = ~Petal.Width,
     ####
     marker = list(size = ~Petal.Length, sizeref = .05, sizemode = 'area'),
     #
     alpha  = 0.9,
     type = "scatter",
     mode = "markers",
     ## using the following hovertemplate() to add the information of the
     ## Two numerical variables to the hover text.
     hovertemplate = paste('<b>Sepal Width<b>: %{y}',
                           '<br><b>Sepal Length</b>: %{x}',
                           '<br><b>Petal Length</b>: %{marker.size:,}',
                           '<br><b>Petal Width</b>: %{customdata}',
                           '<br><b>Species</b>: %{hovertext}',
                           "<extra></extra>") 
     ) %>%
       layout(
         images = list(
           list(
           # Add images
           source =  "https://pengdsci.github.io/STA553VIZ/w06/img/irisbg.jpg",
           xref = "x",
           yref = "y",
           x = 4,
           y = 4.5,
           sizex = 7,
           sizey = 3,
           sizing = "stretch",
           opacity = 0.5,
           layer = "below"
         )
       )
    )

Animated Graphs with plot_ly

When a data set involves a time variable, we also use movement to represent the time variable. plot_ly() can create animated graphs. The following is an example using the built-in gapminder data set in the library gapminder that displays the relationship between life expectancy and GDP per capita of countries over time (every 5 years).

pal.IBM <- c("#332288", "#117733", "#0072B2","#D55E00", "#882255")
pal.IBM <- setNames(pal.IBM, c("Asia", "Europe", "Africa", "Americas", "Oceania"))

df <- gapminder 
fig <- df %>%
  plot_ly(
    x = ~gdpPercap, 
    y = ~lifeExp, 
    size = ~(2*log(pop)-11)^2,
    color = ~continent, 
    colors = pal.IBM,   # custom colors
    #marker = list(size = ~(log(pop)-10),  sizemode = 'area'),
    frame = ~year,      # the time variable to
    # to display in the hover
    text = ~paste("Country:", country,
                  "<br>Continent:", continent,
                  "<br>Year:", year,
                  "<br>LifeExp:", lifeExp,
                  "<br>Pop:", pop,
                  "<br>gdpPerCap:", gdpPercap),
    hoverinfo = "text",
    type = 'scatter',
    mode = 'markers'
  )
fig <- fig %>% layout(
    xaxis = list(
      type = "log"
    )
  )

fig

Rendering A GGPLOT with ggplotly

We can also render a ggplot using ggplotly to bring interactivity to the plot. The next is a customary theme to lay out ggplots.

myplot.theme_new <- function() {
  theme(
    #ggplot margins
     plot.margin = margin(t = 50,  # Top margin
                          r = 30,  # Right margin
                          b = 30,  # Bottom margin
                          l = 30), # Left margin
    ## ggplot titles
    plot.title = element_text(face = "bold", 
                              size = 12,
                              family = "sans", 
                              color = "navy",
                              hjust = 0.5,
                              margin=margin(0,0,30,0)), # left(0),right(1)
    # add border 1)
    panel.border = element_rect(colour = NA, 
                                fill = NA, 
                                linetype = 2),
    # color background 2)
    panel.background = element_rect(fill = "#f6f6f6"),
    # modify grid 3)
    panel.grid.major.x = element_line(colour = 'white', 
                                      linetype = 3, 
                                      size = 0.5),
    panel.grid.minor.x = element_blank(),
    panel.grid.major.y =  element_line(colour = 'white', 
                                       linetype = 3, 
                                       size = 0.5),
    panel.grid.minor.y = element_blank(),
    # modify text, axis, and color 4) and 5)
    axis.text = element_text(colour = "navy", 
                             #face = "italic", 
                             size = 7,
                             #family = "Times New Roman"
                             ),
    axis.title = element_text(colour = "navy", 
                              size = 7,
                              #family = "Times New Roman"
                              ),
    axis.ticks = element_line(colour = "navy"),
    # legend at the bottom 6)
    legend.position = "bottom",
    legend.key.size = unit(0.6, 'cm'), #change legend key size
    legend.key.height = unit(0.6, 'cm'), #change legend key height
    legend.key.width = unit(0.6, 'cm'), #change legend key width
    #legend.title = element_text(size=8), #change legend title font size
    legend.title=element_blank(),  # remove all legend titles
    legend.key = element_rect(fill = "white"),
    #####
    legend.text = element_text(size=8)) #change legend text font size
}

The following plot uses the above theme and passes the correlation coefficient to the annotated text.

p <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
             #aes(color = factor(Species)) +
             aes(label = Species, label1 = Petal.Length, label2 =  Petal.Width) + 
             ## The labels in the above aes() will be part of the hover text.
             geom_point(size = iris$Petal.Length, alpha = 0.7) +
             stat_smooth(method = lm, se=FALSE, size = 0.5) +   # add a linear regression line
             #scale_color_manual(values=c("dodgerblue4", "darkolivegreen4", "darkred")) +
             labs(
                 x = "Sepal Length",
                 y = "Sepal Width",
                 title = "Association between Sepal Length and Width") +
             myplot.theme_new() + 
              annotate(geom="text" , 
                       x=6.8, 
                       y=2,
                       label=paste("The Pearson correlation coefficient r = ",                          
                                   round(cor(iris$Sepal.Length, iris$Sepal.Width),3)), 
                          size = 2,
                          color = "navy") + 
               coord_fixed(1)    ## This changes the aspect ratio of the graph
ggplotly(p)
p <- ggplot(iris, aes(x = Sepal.Length, y = Sepal.Width)) +
             # aes(color = factor(Species)) +
             # to add more information about the variables in the data set
             # use labels to denote the variable names inside the function aes()
             aes(label=Species, label2=Petal.Length, label3=Petal.Width) +
             geom_point(size = iris$Petal.Width, alpha = 0.7) +
             stat_smooth(method = lm, se=FALSE, size = 0.3) +
             #scale_color_manual(values=c("dodgerblue4", "darkolivegreen4", "darkred")) +
             labs(
                 x = "Sepal Length",
                 y = "Sepal Width",
                 title = "Association between Sepal Length and Width") +
             myplot.theme_new() + 
              annotate(geom="text" , 
                       x=6.8, 
                       y=2,
                       label=paste("The Pearson correlation coefficient r = ",                          
                                   round(cor(iris$Sepal.Length, iris$Sepal.Width),3)), 
                          size = 2,
                          color = "navy") + 
               coord_fixed(1)    ## This changes the aspect ratio of the graph
ggplotly(p)

Remark: It turns that ggplotly cannot display colors due to its recent updates. Hope that this issue will be fixed soon.

Bar & Pie Chart

Barplot

We will create a summarized data set to make bar plots. We define a data set to store the mean of sepal length and sepal width by species using the dyplr and tidyr approaches.

barplotdata = aggregate(iris[,1:4], by = list(iris$Species), FUN = mean)
kable(head(barplotdata))
Group.1 Sepal.Length Sepal.Width Petal.Length Petal.Width
setosa 5.006 3.428 1.462 0.246
versicolor 5.936 2.770 4.260 1.326
virginica 6.588 2.974 5.552 2.026

Next, we draw a group bar chart.

plot_ly(
  data = barplotdata,
   x = ~Group.1,
   y = ~Sepal.Length,
   type = "bar",
   name = "sepal.length.avg",
   ## graphic size
   width = 700,
   height = 400) %>%
    add_trace(y=~Sepal.Width, name = "sepal.width.avg") %>%
    add_trace(y=~Petal.Length, name = "petal.length.avg") %>%
    add_trace(y=~Petal.Width, name = "petal.width.avg") %>%
    layout( yaxis = list(title ="Mean"),
            xaxis = list(title = "Species"),
            title = "Group Means of Iris attributes",
                  ## margin of the plot
      margin = list(
              b = 50,
              l = 100,
              t = 120,
              r = 50
      ))

Pie Chart

We first define a subset from the iris data by filtering out observations with a sepal length of less than 5. The pie chart will be created to see the distribution of species in the subset of the iris data. Keep in mind that the pie chart is constructed based on a frequency table.

# define a working data set
subiris <- iris[iris$Sepal.Length > 5,5]
## Create a frequency table in the form of the data frame.
piedata = data.frame(cate =as.vector(unique(subiris)), 
                     freq = as.vector(table(subiris)))
# define a color vector
colors <- c('rgb(211,94,96)', 'rgb(128,133,133)', 'rgb(144,103,167)')
# make a pie chart
plot_ly(piedata, 
        labels = ~cate, 
        values = ~freq, 
        type = 'pie',
        textposition = 'inside',
        textinfo = 'label + percent',
        insidetextfont = list(color = '#FFFFFF'),
        #hoverinfo = 'text',
        marker = list(colors = colors,
                      line = list(color = '#FFFFFF', width = 1)),
                      #The 'pull' attribute can also be used to create space between the sectors
        showlegend = TRUE) %>% 
         layout(title = 'Distribution of Species',
                xaxis = list(showgrid = FALSE, zeroline = FALSE, 
                             showticklabels = FALSE),
                yaxis = list(showgrid = FALSE, zeroline = FALSE, 
                             showticklabels = FALSE),
                      ## margin of the plot
      margin = list(
              b = 50,
              l = 100,
              t = 120,
              r = 50
      ))

Histogram & Desnity

Histograms and density curves are used to display the distribution of numerical random variables. When comparing the distributions of different random variables, we can overlay the histograms or density curves.

Comparing Distributions Using Histograms

We can overlay histograms to compare the distributions of multiple random variables.

plot_ly(
  data = iris,
   x = ~ Sepal.Length,
   type = "histogram",
   nbinsx = 10, 
   name = "sepal.length",
   alpha = .5,
   marker = list(line = list(color = "darkgray",  width = 2)) ) %>%
   ## Adding additional histograms and stacking them
    add_histogram(x = ~Sepal.Width,
                name = "sepal.width", nbinsx = 10, alpha = 0.5,
                marker = list(line = list(color = "darkgray",  width = 2))) %>%
    add_histogram(x = ~Petal.Length,
                name = "petal.length",nbinsx = 10, alpha = 0.5,
                marker = list(line = list(color = "darkgray",  width = 2))) %>%
    add_histogram(x = ~Petal.Width,
                name = "petal.width",nbinsx = 10, alpha = 0.5,
                marker = list(line = list(color = "darkgray",  width = 2))) %>%
  layout(barmode = "overlay",
         title = "Histogram of Iris Attribute",
         xaxis = list(title = "Iris Attributes",
                      zeroline = TRUE),
         yaxis = list(title = "Count",
                      zeroline =TRUE),
               ## margin of the plot
      margin = list(
              b = 50,
              l = 100,
              t = 120,
              r = 50
      ))

The issue is that the above overlaid histograms cannot be easy to distinguish when comparing more than two distributions in general. The ridgeline histogram can help in general. The following is an example of ridgeline histograms.

ggplot(iris, aes(x = Sepal.Length, y = Species, group = Species, fill = Species)) +
  geom_density_ridges(stat = "binline", bins = 20, scale = 2.2) +
  scale_y_discrete(expand = c(0, 0)) +
  scale_x_continuous(expand = c(0, 0)) +
  coord_cartesian(clip = "off") +
  theme_ridges()

Density Curve

It is relatively easy to use density curves to compare multiple distributions. Assume that we want to compare the distribution of the sepal length of the tree iris flowers. One way to do this comparison is to plot the three estimated density curves.

# define three densities
sepal.len.setosa <- iris[which(iris$Species == "setosa"),]
setosa <- density(sepal.len.setosa$Sepal.Length)
sepal.len.versicolor <- iris[which(iris$Species == "versicolor"),]
versicolor <- density(sepal.len.versicolor$Sepal.Length)
sepal.len.virginica <- iris[which(iris$Species == "virginica"),]
virginica <- density(sepal.len.virginica$Sepal.Length)
# plot density curves
fig <- plot_ly(x = ~virginica$x, 
               y = ~virginica$y, 
               type = 'scatter', #A character string specifying the trace type
               mode = 'lines', 
               name = 'virginica', 
               fill = 'tozeroy')  %>% 
           # adding more density curves
       add_trace(x = ~versicolor$x, 
                 y = ~versicolor$y, 
                 name = 'versicolor', 
                 fill = 'tozeroy')  %>% 
       add_trace(x = ~setosa$x, 
                 y = ~setosa$y, 
                 name = 'setosa', 
                 fill = 'tozeroy')  %>%   
       layout(xaxis = list(title = 'Sepal Length'),
              yaxis = list(title = 'Density'))
fig

The above overlaid density plots (with a certain level of transparency) are relatively easy to visualize.

ridgeDensity <- ggplot(iris, aes(x = Sepal.Length, y = Species)) +
  geom_density_ridges() +
  geom_density_interactive(aes(tooltip = interaction(Sepal.Length, Species),
                               data_id = interaction(Sepal.Length, Species)),
                           size = 1, hover_nearest = TRUE)

ridgeDensity

# girafe(ggobj = ridgeDensity)

Note: ridgeline plots do not work well with ggplotly to bring interactivity to the plots. There are some workarounds, but none is good enough for professional presentation.

Boxplot

Drawing a boxplot is straightforward in plotly.

plot_ly(
  data = iris,
  y = ~ Sepal.Length,
  x = ~Species,
  type = "box",
  color = ~Species,
  boxpoints = "all",
  boxmean = TRUE,
  showlegend = FALSE ) %>%
   layout(title = "Histogram of Iris Attribute",
         xaxis = list(title = "Species",
                      zeroline = TRUE),
         yaxis = list(title = "Sepal Length",
                      zeroline =TRUE))

The non-interactive ggplot boxplot is given by

summarized.iris = iris %>% select(-Species) %>%
  pivot_longer(everything()) 

 g.iris =  ggplot(summarized.iris, aes(x=name,y=value, fill=name)) +
           geom_boxplot() +
           labs(
                 x = "Measure Types",
                 y = "Numerical Measures",
                 title = "Association between Sepal Length and Width") +
           myplot.theme_new()
 ###
 g.iris

ggplotly adds interactivity to the plot, but cannot add colors in the moment.

summarized.iris = iris %>% select(-Species) %>%
  pivot_longer(everything()) 

 g.iris =  ggplot(summarized.iris, aes(x = name, y = value)) +
           geom_boxplot() +
          labs(
                 x = "Measure Types",
                 y = "Numerical Measures",
                 title = "Association between Sepal Length and Width") +
           myplot.theme_new()
      
 ###
  ggplotly(g.iris)

Serial Plot

Visualizing time series seems to be relatively easier since the objective is to inspect the pattern such as trend, seasonality, special shits, etc. to assist in model identification, such as determining the best length of the history of your time series data for time series forecasting, types of exponential smoothing, order of differencing, MA and AR in ARIMA framework, etc.

stock <- read.csv('https://raw.githubusercontent.com/pengdsci/sta553.html/main/data/finance-charts-apple.csv')
##
fig <- plot_ly(stock, type = 'scatter', mode = 'lines')    %>%
       add_trace(x = ~Date, y = ~AAPL.High)    %>%
       layout(showlegend = F, 
              title='Time Series with Rangeslider',
              xaxis = list(rangeslider = list(visible = T)))  %>%
       layout(xaxis = list(zerolinecolor = 'blue',
                      zerolinewidth = 2,
                      gridcolor = '#ffffff'),
              yaxis = list(zerolinecolor = '#ffffff',
                      zerolinewidth = 2,
                      gridcolor = '#fff'),
              plot_bgcolor='#e5ecf6', width = 800, height = 400)
fig

There are also other libraries one can use to produce interactive serial plots.

# This plot uses the plot function: hccharh() and hcaes() in the library `hicharter`
hc <-stock %>%
   hchart(
    "line", 
    hcaes(x = Date, y = AAPL.High)
  )
hc

The following interactive serial plot also included forecasted values and the forcasting confidence band.

appl.high = stock$AAPL.High
# n= length(appl.high)
# plot(1:n, appl.high, type = 'l')
x <- forecast(ets(appl.high), h = 48)
hc <- hchart(x)
hc

Plotly Maps

Several map libraries are available in R. In this example, we use the plot_geo() function from plotly to plot on a map.

## preparing data
poc <- read_csv("https://raw.githubusercontent.com/pengdsci/sta553.html/main/data/POC.csv")[,c(7,8,9, 17)]
poc.site <- poc[poc$POC == 1,]
# geo styling
geostyle <- list(scope = 'usa',
                 projection = list(type = 'albers usa'),
                 showland = TRUE,
                 landcolor = toRGB("lightblue"),
                 subunitcolor = toRGB("purple"),
                 countrycolor = toRGB("navy"),
                 countrywidth = 0.75,
                 subunitwidth = 0.5
               )
## plotting map
fig <- plot_geo(poc.site, lat = ~ycoord, lon = ~xcoord) %>%
       add_markers(text = ~ SITE_DESCRIPTION, 
                   color = "red", 
                   symbol = "circle", 
                   size = I(10), 
                   hoverinfo = "text" )   %>%
        layout( title = 'POC Risk Sites', geo = geostyle)
fig

Conclusion

This note focuses on using plotly library and its dependencies to create various interactive plots. However, plotly is only one such library that can produce interactive graphics. There are several other commonly used libraries with different strengths. Here are a few of them

Data integration. Collect raw data and turn it into clean, analytics-ready information by performing data replication, ingestion, and transformation. Then store it in a data lake or data warehouse.

Goal definition. Define the business objective you’re trying to achieve and the data insights you seek. For example, are you trying to optimize a production process or track the ROI of your marketing efforts?

Visualization design. Design begins with selecting KPIs and types of graphs, charts, and maps that best tell your story. Keeping your visualizations clean and simple will help users understand and work with the data.

Collaboration and sharing. Allow all approved users to explore the data freely to uncover their own insights. Your software should allow users to embed your visualizations in other applications and to engage with them on their mobile devices.

LS0tDQp0aXRsZTogIkludGVyYWN0aXZlIFN0YXRpc3RpY3MgR3JhcGhpY3Mgd2l0aCBwbG90bHkiDQphdXRob3I6ICJDaGVuZyBQZW5nIg0KZGF0ZTogIldlc3QgQ2hlc3RlciBVbml2ZXJzaXR5Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCmVkaXRvcl9vcHRpb25zOg0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KZGl2I1RPQyBsaSB7DQogICAgbGlzdC1zdHlsZTpub25lOw0KICAgIGJhY2tncm91bmQtY29sb3I6bGlnaHRncmF5Ow0KICAgIGJhY2tncm91bmQtaW1hZ2U6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLXJlcGVhdDpub25lOw0KICAgIGJhY2tncm91bmQtcG9zaXRpb246MDsNCiAgICBmb250LWZhbWlseTogQXJpYWwsIEhlbHZldGljYSwgc2Fucy1zZXJpZjsNCiAgICBjb2xvcjogIzc4MGMwYzsNCn0NCg0KaDEudGl0bGUgew0KICBmb250LXNpemU6IDI0cHg7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOw0KICBmb250LXZhcmlhbnQtY2Fwczogbm9ybWFsOw0KfQ0KaDQuYXV0aG9yIHsgDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCn0NCmg0LmRhdGUgeyANCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgY29sb3I6IERhcmtCbHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMSB7IA0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMiB7IA0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IA0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7DQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCg0KLyogVGFiIGZlYXR1cmVzICovDQoubmF2PmxpPmEgew0KICAgIHBvc2l0aW9uOiByZWxhdGl2ZTsNCiAgICBkaXNwbGF5OiBibG9jazsNCiAgICBwYWRkaW5nOiAycHggMTVweDsNCiAgICBjb2xvcjogIzk5MDAwMDsNCn0NCi5uYXYtcGlsbHM+bGkuYWN0aXZlPmEsIC5uYXYtcGlsbHM+bGkuYWN0aXZlPmE6aG92ZXIsIC5uYXYtcGlsbHM+bGkuYWN0aXZlPmE6Zm9jdXMgew0KICAgIGNvbG9yOiAjZmZmZmZmOw0KICAgIGJhY2tncm91bmQtY29sb3I6ICM5OTAwMDA7DQp9DQovKg0KbmF2LXBpbGxzPmxpOm50aC1jaGlsZCgyKSB7DQogICAgYmFja2dyb3VuZDogZ3JlZW47DQogfQ0KICovDQo8L3N0eWxlPg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCm9wdGlvbnMocmVwb3MgPSBsaXN0KENSQU49Imh0dHA6Ly9jcmFuLnJzdHVkaW8uY29tLyIpKQ0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCiAgIGxpYnJhcnkodGlkeXZlcnNlKQ0KfQ0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpDQogICBsaWJyYXJ5KGtuaXRyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJjb3dwbG90IikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImNvd3Bsb3QiKQ0KICAgbGlicmFyeShjb3dwbG90KQ0KfQ0KaWYgKCFyZXF1aXJlKCJsYXRleDJleHAiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygibGF0ZXgyZXhwIikNCiAgIGxpYnJhcnkobGF0ZXgyZXhwKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwbG90bHkiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGxvdGx5IikNCiAgIGxpYnJhcnkocGxvdGx5KQ0KfQ0KaWYgKCFyZXF1aXJlKCJnYXBtaW5kZXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiZ2FwbWluZGVyIikNCiAgIGxpYnJhcnkoZ2FwbWluZGVyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwbmciKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoInBuZyIpICAgIA0KICAgIGxpYnJhcnkoInBuZyIpDQp9DQppZiAoIXJlcXVpcmUoIlJDdXJsIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJSQ3VybCIpICAgIA0KICAgIGxpYnJhcnkoIlJDdXJsIikNCn0NCmlmICghcmVxdWlyZSgiY29sb3VycGlja2VyIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJjb2xvdXJwaWNrZXIiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiY29sb3VycGlja2VyIikNCn0NCmlmICghcmVxdWlyZSgiZ2dhbmltYXRlIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnZ2FuaW1hdGUiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ2dhbmltYXRlIikNCn0NCmlmICghcmVxdWlyZSgiZ2lmc2tpIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnaWZza2kiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ2lmc2tpIikNCn0NCmlmICghcmVxdWlyZSgibWFnaWNrIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJtYWdpY2siKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgibWFnaWNrIikNCn0NCmlmICghcmVxdWlyZSgiZ3JEZXZpY2VzIikpIHsNCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJnckRldmljZXMiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiZ3JEZXZpY2VzIikNCn0NCmlmICghcmVxdWlyZSgianBlZyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygianBlZyIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJqcGVnIikNCn0NCmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImdncmlkZ2VzIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdncmlkZ2VzIikNCn0NCmlmICghcmVxdWlyZSgicGx5ciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicGx5ciIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJwbHlyIikNCn0NCmlmICghcmVxdWlyZSgiZ2dpcmFwaCIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dpcmFwaCIpICAgICAgICAgICAgICANCiAgICBsaWJyYXJ5KCJnZ2lyYXBoIikNCn0NCmlmICghcmVxdWlyZSgiaGlnaGNoYXJ0ZXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImhpZ2hjaGFydGVyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImhpZ2hjaGFydGVyIikNCn0NCmlmICghcmVxdWlyZSgiZm9yZWNhc3QiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImZvcmVjYXN0IikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImZvcmVjYXN0IikNCn0NCiMjIA0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BKQ0KYGBgDQoNClwNCg0KXA0KDQojIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQ0KDQojIyBwbG90bHkNCg0KVGhlcmUgYXJlIHR3byBtYWluIHdheXMgdG8gY3JlYXRlIGEgYHBsb3RseWAgb2JqZWN0OiBlaXRoZXIgYnkgdHJhbnNmb3JtaW5nIGEgZ2dwbG90MiBvYmplY3QgaW50byBhIHBsb3RseSBvYmplY3QgdmlhIGBnZ3Bsb3RseSgpYCBvciBieSBkaXJlY3RseSBpbml0aWFsaXppbmcgYSBwbG90bHkgb2JqZWN0IHdpdGggYHBsb3RfbHkoKWAvYHBsb3RfZ2VvKClgL2BwbG90X21hcGJveCgpYC4gUGxvdGx5IGhhcyBhIHJpY2ggYW5kIGNvbXBsZXggc2V0IG9mIGZlYXR1cmVzLiBUaGUgbW9zdCBjb21tb24gZmVhdHVyZXMgYXJlOg0KDQoqIFRvb2x0aXAg4oCcaG92ZXLigJ0gaW5mbw0KKiBab29tIGluIGFuZCBvdXQgb2YgZ3JhcGhzDQoqIFVzZXJzIGNhbiBleHBvcnQgZ3JhcGhzIGFzIGFuIGltYWdlDQoqIEludGVncmF0aW5nIG11bHRpcGxlIGdyYXBocw0KKiBUZW1wbGF0ZSBob3ZlciBpbmZvDQoqIEFuaW1hdGlvbnMgYW5kIG1vdmluZyBncmFwaGljcw0KDQpPbmUgY2FuIGZlZWQgYSBgZ2dwbG90YCB0byBgcGxvdGx5YCB0byByZW5kZXIgZ2dwbG90IHZpYSBgcGxvdGx5YCBieSB1c2luZyBgZ2dwbG90bHkoKWAgLSBhIHdyYXBwZXIgb2YgYGdncGxvdGAuIENvbXBhcmVkIHRvIHRoZSBiYXNlIFIgcGxvdHRpbmcgZnVuY3Rpb24gYHBsb3QoKWAsIGBwbG90X2x5KClgIGlzIG1vcmUgdGVjaG5pY2FsIGFuZCBwb29ybHkgZG9jdW1lbnRlZC4gSG93ZXZlciwgdGhlIGZvbGxvd2luZyBmYWN0b3JzIG1heSBtYWtlIGBwbG90bHlgIHRoZSBiZXN0IG9wdGlvbjoNCg0KKiBncmFwaHMgcHJlc2VudGVkIGluIGEgZGlnaXRhbC9vbmxpbmUgZm9ybWF0DQoqIHVzZXJzIGludGVyYWN0IHdpdGggdGhlIGdyYXBoDQoqIG1vcmUgY3VzdG9taXphYmxlIHRoYW4gZ2dwbG90IA0KKiByZW5kZXJpbmcgZ3JhcGhpY3MgaW4gYSBoaWdoZXIgcmVzb2x1dGlvbg0KDQpJbiB0aGlzIG5vdGUsIHdlIGludHJvZHVjZSB0aGUgYmFzaWMgc3RhdGlzdGljYWwgZ3JhcGhpY3MgdXNpbmcgdGhlIGBwbG90bHlgIHBhY2thZ2UuIGBwbG90bHlgIGdyYXBoaWNzIGF1dG9tYXRpY2FsbHkgY29udGFpbiBpbnRlcmFjdGl2ZSBlbGVtZW50cyB0aGF0IGFsbG93IHVzZXJzIHRvIG1vZGlmeSwgZXhwbG9yZSwgYW5kIGV4cGVyaWVuY2UgdGhlIHZpc3VhbGl6ZWQgZGF0YSBpbiBuZXcgd2F5cy4gDQoNClRoZSBjb2RpbmcgZWZmb3J0IGlzIHNpbWlsYXIgdG8gdGhhdCBvZiBTQVMgT0RTIGdyYXBoaWNzLiBUbyB1c2UgYHBsb3RfbHkoKWAsIHdlIG5lZWQgdG8gaW5zdGFsbCAoaWYgbm90IGRvbmUpIGFuZCBsb2FkIHRoZSBgcGxvdGx5YCBwYWNrYWdlLiBXZSB1c2UgdGhlIHdlbGwta25vd24gaXJpcyBkYXRhIHNldCBpbiB0aGUgZm9sbG93aW5nIHBsb3RzLiBBIG5pY2UgYHBsb3RseWAgY2hlYXQgc2hlZXQgY2FuIGJlIGZvdW5kIGF0IDxodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTNWSVovdzA2L3JfcGxvdGx5X2NoZWF0X3NoZWV0LnBkZj4NCg0KDQpUaGUgZ3JhbW1hciBvZiBgcGxvdF9seSgpYCBpcyBzaW1pbGFyIHRvIHRoYXQgb2YgYGdncGxvdCgpYC4gQnkgc3BlY2lmeWluZyBgdGV4dGAgb3B0aW9uLCB3ZSBjYW4gY3VzdG9taXplIHRoZSBob3ZlciB0ZXh0LiBJZiBgdGV4dGAgb3B0aW9uIGlzIG5vdCBzcGVjaWZpZWQsIHRoZSBkZWZhdWx0IGhvdmVyIHRleHQgaXMganVzdCB0aGUgYHg9YCBhbmQgYHk9YCBjb29yZGluYXRlcyBvZiB0aGF0IGRhdGEgcG9pbnQsIGFuZCB0aGUgdmFyaWFibGVzIGluIHRoZSBgY29sb3I9YCBvcHRpb24gYW5kIGBtYXJrcz1gIG9wdGlvbi4gSW4gYWRkaXRpb24sIHdlIGNhbiBncm91cCB0aGUgZGF0YSBwb2ludHMgYnkgY29sb3IgYW5kIHNpemUganVzdCBhcyBpbiBnZ3Bsb3QyLiBUbyBhZGQgYSBsYXllciBpbiB0aGUgZ3JhcGgsIHN1Y2ggYXMgZml0dGluZyBhIGxpbmVhciByZWdyZXNzaW9uIGxpbmUsIHdlIHVzZSBgYWRkX3RyYWNlYC4NCg0KDQoNCg0KIyMgU2NhdHRlclBsb3QNCg0KDQojIyMgVGhlIERlZmF1bHQgUGxvdA0KDQpGaXJzdCwgd2UgbWFrZSBhIHNpbXBsZSBpbnRlcmFjdGl2ZSBzY2F0dGVyIHBsb3QgdXNpbmcgc2VwYWwgbGVuZ3RoIGFuZCB3aWR0aC4gV2UgY2FuIHZpZXcgPGZvbnQgY29sb3IgPSAiYmx1ZSI+dGhlIGluZm9ybWF0aW9uIGFib3V0IHRoZSB2YXJpYWJsZXMgYW5kIGNvbG9yIGNvZGluZyBpbmZvcm1hdGlvbiA8L2ZvbnQ+IGluIHRoZSBob3ZlciB0ZXh0LiBUaGUgbGFiZWxzIG9mIGF4ZXMgYW5kIGxlZ2VuZCB0aXRsZXMgYW5kIGxhYmVscyBhcmUgZGVmYXVsdC4NCg0KDQpgYGB7cn0NCnBsb3RfbHkoDQogICAgZGF0YSA9IGlyaXMsDQogICAgeCA9IH5TZXBhbC5MZW5ndGgsICAjIEhvcml6b250YWwgYXhpcyANCiAgICB5ID0gflNlcGFsLldpZHRoLCAgICMgVmVydGljYWwgYXhpcyANCiAgICBjb2xvciA9IH5mYWN0b3IoU3BlY2llcyksICAjIG11c3QgYmUgYSBudW1lcmljIGZhY3Rvcg0KICAgICB0eXBlID0gInNjYXR0ZXIiLA0KICAgICBtb2RlID0gIm1hcmtlcnMiKQ0KYGBgDQoNCg0KDQojIyMgSW5jbHVkaW5nIEFkZGl0aW9uYWwgSW5mb3JtYXRpb24gVGhyb3VnaCBgaG92ZXJ0ZW1wbGF0ZWANCg0KV2UgY2FuIGFsc28gYWRkIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gdG8gdGhlIHBsb3QgdG8gZW5oYW5jZSB0aGUgaW50ZXJhY3Rpdml0eSBvZiB0aGUgcGxvdC4gRm9yIGV4YW1wbGUsIHdlIGNhbiANCg0KKDEpIG1vZGlmeSB0aGUgcG9pbnQgc2l6ZSB1c2luZyB0aGUgdmFsdWUgb2YgYSBudW1lcmljYWwgdmFyaWFibGUgdmFyaWFibGU7IA0KDQooMikgYWRkIHRleHQgdG8gdGhlIGhvdmVyIHRleHQgdXNpbmcgYHRleHRgIG9wdGlvbiB0byBzaG93IHRoZSBjbGFzcyBsYWJlbDsgDQoNCigzKSBmb3JtdWxhdGUgdGhlIGhvdmVyIHRleHQgdXNpbmcgYGhvdmVydGVtcGxhdGVgIG9wdGlvbi4NCg0KYGhvdmVydGVtcGxhdGVgIA0KDQoNCmBgYHtyfQ0KcGxvdF9seSgNCiAgICBkYXRhID0gaXJpcywNCiAgICB4ID0gflNlcGFsLkxlbmd0aCwgICAgICAgICAjIEhvcml6b250YWwgYXhpcyANCiAgICB5ID0gflNlcGFsLldpZHRoLCAgICAgICAgICAjIFZlcnRpY2FsIGF4aXMgDQogICAgY3VzdG9tZGF0YSA9IH5QZXRhbC5XaWR0aCwNCiAgICBjb2xvciA9IH5mYWN0b3IoU3BlY2llcyksICAjIG11c3QgYmUgYSBudW1lcmljIGZhY3Rvcg0KICAgICBob3ZlcnRleHQgPSB+U3BlY2llcywgICAgICAgICAgIyBzaG93IHRoZSBzcGVjaWVzIGluIHRoZSBob3ZlcnRleHQNCiAgICAgaG92ZXJsYWJlbCA9IH5QZXRhbC5XaWR0aCwNCiAgICAgIyMjIw0KICAgICBtYXJrZXIgPSBsaXN0KHNpemUgPSB+UGV0YWwuTGVuZ3RoLCBzaXplcmVmID0gLjA1LCBzaXplbW9kZSA9ICdhcmVhJyksDQogICAgICMNCiAgICAgYWxwaGEgID0gMC45LA0KICAgICB0eXBlID0gInNjYXR0ZXIiLA0KICAgICBtb2RlID0gIm1hcmtlcnMiLA0KICAgICAjIyB1c2luZyB0aGUgZm9sbG93aW5nIGhvdmVydGVtcGxhdGUoKSB0byBhZGQgdGhlIGluZm9ybWF0aW9uIG9mIHRoZQ0KICAgICAjIyBUd28gbnVtZXJpY2FsIHZhcmlhYmxlcyB0byB0aGUgaG92ZXIgdGV4dC4NCiAgICAgaG92ZXJ0ZW1wbGF0ZSA9IHBhc3RlKCc8Yj5TZXBhbCBXaWR0aDxiPjogJXt5fScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPjxiPlNlcGFsIExlbmd0aDwvYj46ICV7eH0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj48Yj5QZXRhbCBMZW5ndGg8L2I+OiAle21hcmtlci5zaXplOix9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+PGI+UGV0YWwgV2lkdGg8L2I+OiAle2N1c3RvbWRhdGF9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+PGI+U3BlY2llczwvYj46ICV7aG92ZXJ0ZXh0fScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAiPGV4dHJhPjwvZXh0cmE+IikNCiAgICAgIyMjDQopDQpgYGANCg0KDQoNCiMjIyBFbmhhbmNpbmcgdGhlIFBsb3Qgd2l0aCBMYXlvdXQoKSBGdW5jdGlvbg0KDQpUaXRsZXMgYW5kIGF4aXMgbGFiZWxzIGFyZSBpbXBvcnRhbnQgaW4gYW55IHZpc3VhbGl6YXRpb24sIHRvIGluY2x1ZGUgYSBtZWFuaW5nZnVsIHRpdGxlLCBpbmZvcm1hdGl2ZSBsYWJlbHMsIGFuZCBhbm5vdGF0aW9ucyB0byB0aGUgcGxvdGx5IHBsb3QsIHdlIGNhbiB1c2UgbGF5b3V0KCkgZnVuY3Rpb24uIFRoZSBmb2xsb3dpbmcgY29kZSBvbmx5IGdpdmVzIHlvdSBzb21lIGRlc2lnbiBpZGVhcyB5b3UgY2FuIHVzZSB0byBlbmhhbmNlIHlvdXIgcGxvdGx5IGNoYXJ0cy4gVGhlIGRldGFpbGVkIGxpc3Qgb2YgY29uZmlndXJhdGlvbnMgY2FuIGJlIGZvdW5kIGZyb20gdGhlIHBsb3RseSdzIHJlZmVyZW5jZSBwYWdlIGF0IDxodHRwczovL3Bsb3RseS5jb20vci9yZWZlcmVuY2UvbGF5b3V0Lz4NCg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD02fQ0KcGxvdF9seSgNCiAgICBkYXRhID0gaXJpcywNCiAgICB4ID0gflNlcGFsLkxlbmd0aCwgICMgSG9yaXpvbnRhbCBheGlzIA0KICAgIHkgPSB+U2VwYWwuV2lkdGgsICAgIyBWZXJ0aWNhbCBheGlzIA0KICAgIGNvbG9yID0gfmZhY3RvcihTcGVjaWVzKSwgICMgbXVzdCBiZSBhIG51bWVyaWMgZmFjdG9yDQogICAgI3RleHQgPSB+U3BlY2llcywNCiAgICB0ZXh0ID0gfnBhc3RlKCJQZXRhbCBMZW5ndGg6ICIsIFBldGFsLkxlbmd0aCwNCiAgICAgICAgICAgICAgICAgICAiPGJyPlBldGFsIFdpZHRoOiAiLCBQZXRhbC5XaWR0aCwNCiAgICAgICAgICAgICAgICAgICAiPGJyPlNwZWNpZXM6ICIsIFNwZWNpZXMpLCANCiAgICAgIyBTaG93IHRoZSBzcGVjaWVzIGluIHRoZSBob3ZlciB0ZXh0DQogICAgICMjIHVzaW5nIHRoZSBmb2xsb3dpbmcgaG92ZXJ0ZW1wbGF0ZSgpIHRvIGFkZCB0aGUgaW5mb3JtYXRpb24gb2YgdGhlDQogICAgICMjIFR3byBudW1lcmljYWwgdmFyaWFibGVzIHRvIHRoZSBob3ZlciB0ZXh0Lg0KICAgICAjIyMgVXNlIHRoZSBmb2xsb3dpbmcgaG92ZXIgdGVtcGxhdGUgdG8gZGlzcGxheSBtb3JlIGluZm9ybWF0aW9uDQogICAgIGhvdmVydGVtcGxhdGUgPSBwYXN0ZSgnPGk+PGI+U2VwYWwgV2lkdGg8Yj48L2k+OiAle3l9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+PGI+U2VwYWwgTGVuZ3RoPC9iPjogICV7eH0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj48Yj4le3RleHR9PC9iPicpLA0KICAgICBhbHBoYSAgPSAwLjYsDQogICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IH5QZXRhbC5MZW5ndGgsIHNpemVyZWYgPSAuMDUsIHNpemVtb2RlID0gJ2FyZWEnICksDQogICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgICMjIGdyYXBoaWMgc2l6ZQ0KICAgICB3aWR0aCA9IDcwMCwNCiAgICAgaGVpZ2h0ID0gNTAwDQogICApICU+JQ0KICAgIGxheW91dCggIA0KICAgICAgIyMjIFRpdGxlIA0KICAgICAgdGl0bGUgPWxpc3QodGV4dCA9ICJTZXBhbCBMZW5ndGggdnMgU2VwYWwgV2lkdGgiLCANCiAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJUaW1lcyBOZXcgUm9tYW4iLCAgIyBIVE1MIGZvbnQgZmFtaWx5ICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDE4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gInJlZCIpKSwgDQogICAgICAjIyMgbGVnZW5kDQogICAgICBsZWdlbmQgPSBsaXN0KHRpdGxlID0gbGlzdCh0ZXh0ID0gJ3NwZWNpZXMnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIkNvdXJpZXIgTmV3IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDE0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZWVuIikpLA0KICAgICAgICAgICAgICAgICAgICBiZ2NvbG9yID0gIml2b3J5IiwNCiAgICAgICAgICAgICAgICAgICAgYm9yZGVyY29sb3IgPSAibmF2eSIsDQogICAgICAgICAgICAgICAgICAgIGdyb3VwY2xpY2sgPSAidG9nZ2xlZ3JvdXAiLCAgIyBvbmUgb2YgICJ0b2dnbGVpdGVtIiBBTkQgInRvZ2dsZWdyb3VwIi4NCiAgICAgICAgICAgICAgICAgICAgb3JpZW50YXRpb24gPSAidiIgICMgU2V0cyB0aGUgb3JpZW50YXRpb24gb2YgdGhlIGxlZ2VuZC4NCiAgICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICMjIG1hcmdpbiBvZiB0aGUgcGxvdA0KICAgICAgbWFyZ2luID0gbGlzdCgNCiAgICAgICAgICAgICAgYiA9IDEwMCwNCiAgICAgICAgICAgICAgbCA9IDEwMCwNCiAgICAgICAgICAgICAgdCA9IDEwMCwNCiAgICAgICAgICAgICAgciA9IDUwDQogICAgICApLA0KICAgICAgIyMgQmFja2dyb3VuZA0KICAgICAgcGxvdF9iZ2NvbG9yID0nI2Y3ZjdmNycsIA0KICAgICAgIyMgQXhlcyBsYWJlbHMNCiAgICAgICAgICAgICB4YXhpcyA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgICB0aXRsZT1saXN0KHRleHQgPSAnU2VwYWwgTGVuZ3RoJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250ID0gbGlzdChmYW1pbHkgPSAnQXJpYWwnKSksDQogICAgICAgICAgICAgICAgICAgIHplcm9saW5lY29sb3IgPSAncmVkJywgDQogICAgICAgICAgICAgICAgICAgIHplcm9saW5ld2lkdGggPSAyLCANCiAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ3doaXRlJyksIA0KICAgICAgICAgICAgeWF4aXMgPSBsaXN0KCANCiAgICAgICAgICAgICAgICAgICAgdGl0bGU9bGlzdCh0ZXh0ID0gJ1NlcGFsIFdpZHRoJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250ID0gbGlzdChmYW1pbHkgPSAnQXJpYWwnKSksDQogICAgICAgICAgICAgICAgICAgIHplcm9saW5lY29sb3IgPSAncHVycGxlJywgDQogICAgICAgICAgICAgICAgICAgIHplcm9saW5ld2lkdGggPSAyLCANCiAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJ3doaXRlJyksDQogICAgICAgIyMgYW5ub3RhdGlvbnMNCiAgICAgICBhbm5vdGF0aW9ucyA9IGxpc3QoICANCiAgICAgICAgICAgICAgICAgICAgIHggPSAwLjcsICAgIyBiZXR3ZWVuIDAgYW5kIDEuIDAgPSBsZWZ0LCAxID0gcmlnaHQNCiAgICAgICAgICAgICAgICAgICAgIHkgPSAxLjUsICAgIyBiZXR3ZWVuIDAgYW5kIDEsIDAgPSBib3R0b20sIDEgPSB0b3ANCiAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KHNpemUgPSAxMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImRhcmtyZWQiKSwgICANCiAgICAgICAgICAgICAgICAgIHRleHQgPSAiVGhlIHBvaW50IHNpemUgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSBzZXBhbCBsZW5ndGgiLCAgIA0KICAgICAgICAgICAgICAgICAgeHJlZiA9ICJwYXBlciIsICAjICJjb250YWluZXIiIHNwYW5zIHRoZSBlbnRpcmUgYHdpZHRoYCBvZiB0aGUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgIGxvdC4gInBhcGVyIiByZWZlcnMgdG8gdGhlIHdpZHRoIG9mIHRoZSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAgcGxvdHRpbmcgYXJlYSBvbmx5LiB5cmVmID0gInBhcGVyIiwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICBzYW1lIGFzIHhyZWYuDQogICAgICAgICAgICAgICB4YW5jaG9yID0gImNlbnRlciIsICMgIGhvcml6b250YWwgYWxpZ25tZW50IHdpdGggcmVzcGVjdCB0byBpdHMgeCBwb3NpdGlvbg0KICAgICAgICAgICAgICAgeWFuY2hvciA9ICJib3R0b20iLCAjICBzaW1pbGFyIHRvIHhhbmNob3IgIA0KICAgICAgICAgICAgIHNob3dhcnJvdyA9IEZBTFNFKQ0KICAgICkNCmBgYA0KDQpXZSBhbHNvIHdyaXRlIGEgdGhlbWUganVzdCBsaWtlIHdlIGRpZCBpbiB0aGUgcmVndWxhciBnZ3Bsb3QuIFRoZSBmb2xsb3dpbmcgaXMgYW4gZXhhbXBsZS4NCg0KYGBge3J9DQpteVBsb3RseUxheW91dCA8LSBmdW5jdGlvbihhbnlPYmpOYW1lKXsgICMgYW55U3RyaW5nIGlzIHJlcXVpcmVkIGluaXRpYWwgYXJndW1lbnQuDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBpdCBjYW4gYmUgYW55IHN0cmluZyBhLGIsYywgLi4uLi4uLi4uDQogIGxheW91dChhbnlPYmpOYW1lLCAgDQogICAgICAjIyMgVGl0bGUgDQogICAgICB0aXRsZSA9bGlzdCh0ZXh0ID0gIlNlcGFsIExlbmd0aCB2cyBTZXBhbCBXaWR0aCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBmb250ID0gbGlzdChmYW1pbHkgPSAiVGltZXMgTmV3IFJvbWFuIiwgICMgSFRNTCBmb250IGZhbWlseSAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDE4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAicmVkIikpLCANCiAgICAgICMjIyBsZWdlbmQNCiAgICAgIGxlZ2VuZCA9IGxpc3QodGl0bGUgPSBsaXN0KHRleHQgPSAnc3BlY2llcycsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250ID0gbGlzdChmYW1pbHkgPSAiQ291cmllciBOZXciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JlZW4iKSksDQogICAgICAgICAgICAgICAgICAgIGJnY29sb3IgPSAiaXZvcnkiLA0KICAgICAgICAgICAgICAgICAgICBib3JkZXJjb2xvciA9ICJuYXZ5IiwNCiAgICAgICAgICAgICAgICAgICAgZ3JvdXBjbGljayA9ICJ0b2dnbGVncm91cCIsICAjIG9uZSBvZiAgInRvZ2dsZWl0ZW0iIEFORCAidG9nZ2xlZ3JvdXAiLg0KICAgICAgICAgICAgICAgICAgICBvcmllbnRhdGlvbiA9ICJ2IiAgIyBTZXRzIHRoZSBvcmllbnRhdGlvbiBvZiB0aGUgbGVnZW5kLg0KICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgKSwNCiAgICAgICMjIG1hcmdpbiBvZiB0aGUgcGxvdA0KICAgICAgbWFyZ2luID0gbGlzdCgNCiAgICAgICAgICAgICAgYiA9IDEyMCwNCiAgICAgICAgICAgICAgbCA9IDUwLA0KICAgICAgICAgICAgICB0ID0gMTIwLA0KICAgICAgICAgICAgICByID0gNTANCiAgICAgICksDQogICAgICAjIyBCYWNrZ3JvdW5kDQogICAgICBwbG90X2JnY29sb3IgPScjZjdmN2Y3JywgDQogICAgICAjIyBBeGVzIGxhYmVscw0KICAgICAgICAgICAgIHhheGlzID0gbGlzdCggDQogICAgICAgICAgICAgICAgICAgIHRpdGxlPWxpc3QodGV4dCA9ICdTZXBhbCBMZW5ndGgnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICdBcmlhbCcpKSwNCiAgICAgICAgICAgICAgICAgICAgemVyb2xpbmVjb2xvciA9ICdyZWQnLCANCiAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDIsIA0KICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAnd2hpdGUnKSwgDQogICAgICAgICAgICB5YXhpcyA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgICB0aXRsZT1saXN0KHRleHQgPSAnU2VwYWwgV2lkdGgnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICdBcmlhbCcpKSwNCiAgICAgICAgICAgICAgICAgICAgemVyb2xpbmVjb2xvciA9ICdwdXJwbGUnLCANCiAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDIsIA0KICAgICAgICAgICAgICAgICAgICBncmlkY29sb3IgPSAnd2hpdGUnKSwNCiAgICAgICAjIyBhbm5vdGF0aW9ucw0KICAgICAgIGFubm90YXRpb25zID0gbGlzdCggIA0KICAgICAgICAgICAgICAgICAgICAgeCA9IDAuNywgICAjIGJldHdlZW4gMCBhbmQgMS4gMCA9IGxlZnQsIDEgPSByaWdodA0KICAgICAgICAgICAgICAgICAgICAgeSA9IDAuOSwgICAjIGJldHdlZW4gMCBhbmQgMSwgMCA9IGJvdHRvbSwgMSA9IHRvcA0KICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3Qoc2l6ZSA9IDEyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZGFya3JlZCIpLCAgIA0KICAgICAgICAgICAgICAgICAgdGV4dCA9ICJUaGUgcG9pbnQgc2l6ZSBpcyBwcm9wb3J0aW9uYWwgdG8gdGhlIHNlcGFsIGxlbmd0aCIsICAgDQogICAgICAgICAgICAgICAgICB4cmVmID0gInBhcGVyIiwgICMgImNvbnRhaW5lciIgc3BhbnMgdGhlIGVudGlyZSBgd2lkdGhgIG9mIHRoZSBwbG90LiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAicGFwZXIiIHJlZmVycyB0byB0aGUgd2lkdGggb2YgdGhlIHBsb3R0aW5nIGFyZWEgb25seS4gIA0KICAgICAgICAgICAgICAgICAgeXJlZiA9ICJwYXBlciIsICAjICBzYW1lIGFzIHhyZWYNCiAgICAgICAgICAgICAgIHhhbmNob3IgPSAiY2VudGVyIiwgIyAgaG9yaXpvbnRhbCBhbGlnbm1lbnQgd2l0aCByZXNwZWN0IHRvIGl0cyB4IHBvc2l0aW9uDQogICAgICAgICAgICAgICB5YW5jaG9yID0gImJvdHRvbSIsICMgIHNpbWlsYXIgdG8geGFuY2hvciAgDQogICAgICAgICAgICAgc2hvd2Fycm93ID0gRkFMU0UgIA0KICAgICAgICAgICApDQogICkNCiAgICAgICB9DQpgYGANCg0KDQoNCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0NCnBsb3RfbHkoDQogICAgZGF0YSA9IGlyaXMsDQogICAgeCA9IH5TZXBhbC5MZW5ndGgsICAjIEhvcml6b250YWwgYXhpcyANCiAgICB5ID0gflNlcGFsLldpZHRoLCAgICMgVmVydGljYWwgYXhpcyANCiAgICBjb2xvciA9IH5mYWN0b3IoU3BlY2llcyksICAjIG11c3QgYmUgYSBudW1lcmljIGZhY3Rvcg0KICAgICB0ZXh0ID0gflNwZWNpZXMsICAgICAjIHNob3cgdGhlIHNwZWNpZXMgaW4gdGhlIGhvdmVyIHRleHQNCiAgICAgIyMgdXNpbmcgdGhlIGZvbGxvd2luZyBob3ZlcnRlbXBsYXRlKCkgdG8gYWRkIHRoZSBpbmZvcm1hdGlvbiBvZiB0aGUNCiAgICAgIyMgVHdvIG51bWVyaWNhbCB2YXJpYWJsZXMgdG8gdGhlIGhvdmVyIHRleHQuDQogICAgIGhvdmVydGVtcGxhdGUgPSBwYXN0ZSgnPGk+PGI+U2VwYWwgV2lkdGg8Yj48L2k+OiAle3l9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+PGI+U2VwYWwgTGVuZ3RoPC9iPjogICV7eH0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj48Yj4le3RleHR9PC9iPicpLA0KICAgICBhbHBoYSAgPSAwLjksDQogICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IH5QZXRhbC5MZW5ndGgsIHNpemVyZWYgPSAuMDUsIHNpemVtb2RlID0gJ2FyZWEnICksDQogICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgICMjIGdyYXBoaWMgc2l6ZQ0KICAgICB3aWR0aCA9IDcwMCwNCiAgICBoZWlnaHQgPSA1MDApICU+JSBteVBsb3RseUxheW91dCgpDQpgYGANCg0KDQoNCiMjIyBFeHRlcm5hbCBJbWFnZXMgZm9yIHBsb3RseSBDaGFydHMNCg0KQXMgd2UgZGlkIGluIHRoZSBiYXNlIFIgYW5kIGdncGxvdCwgd2UgaWxsdXN0cmF0ZSBob3cgdG8gYWRkIGltYWdlcyB0byBwbG90bHkgY2hhcnRzOiBpbnNlcnRpbmcgYW4gaW1hZ2UgYW5kIHNldHRpbmcgYW4gaW1hZ2UgYmFja2dyb3VuZC4NCg0KDQoqKkluc2VydGluZyBJbWFnZXMgdG8gYHBsb3RseWAgQ2hhcnRzKioNCg0KVGhlIGZvbGxvd2luZyBleGFtcGxlIHNob3dzIGhvdyB0byB1c2UgbGF5b3V0IGZ1bmN0aW9uIHRvIGluc2VydCBhbiBleHRlcm5hbCBpbWFnZSB0byBhIHBsb3RseSBzY2F0dGVyIHBsb3QuIENvbXBhcmluZyB0aGUgc3RlcHMgb2YgaW5zZXJ0aW5nIGFuIGV4dGVybmFsIGltYWdlIHRvIHRoZSBiYXNlIFIgYW5kIGdncGxvdCwgaXQgaXMgcmVsYXRpdmVseSBzdHJhaWdodGZvcndhcmQgYW5kIGZsZXhpYmxlIHRvIHBlcmZvcm0gdGhlIHNhbWUgdGFzayBpbiBwbG90bHkuIFNlZSB0aGUgY29tbWVudHMgaW4gdGhlIGNvZGUgdG8gcGxhY2UgdGhlIGltYWdlIGluIGFuIGFwcHJvcHJpYXRlIGxvY2F0aW9uLg0KDQpgYGB7cn0NCnBsb3RfbHkoDQogICAgZGF0YSA9IGlyaXMsDQogICAgeCA9IH5TZXBhbC5MZW5ndGgsICAgICAgICAgIyBIb3Jpem9udGFsIGF4aXMgDQogICAgeSA9IH5TZXBhbC5XaWR0aCwgICAgICAgICAgIyBWZXJ0aWNhbCBheGlzIA0KICAgIGN1c3RvbWRhdGEgPSB+UGV0YWwuV2lkdGgsDQogICAgY29sb3IgPSB+ZmFjdG9yKFNwZWNpZXMpLCAgIyBtdXN0IGJlIGEgbnVtZXJpYyBmYWN0b3INCiAgICAgaG92ZXJ0ZXh0ID0gflNwZWNpZXMsICAgICAgICAgICMgc2hvdyB0aGUgc3BlY2llcyBpbiB0aGUgaG92ZXIgdGV4dA0KICAgICBob3ZlcmxhYmVsID0gflBldGFsLldpZHRoLA0KICAgICAjIyMjDQogICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IH5QZXRhbC5MZW5ndGgsIHNpemVyZWYgPSAuMDUsIHNpemVtb2RlID0gJ2FyZWEnKSwNCiAgICAgIw0KICAgICBhbHBoYSAgPSAwLjksDQogICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgICMjIHVzaW5nIHRoZSBmb2xsb3dpbmcgaG92ZXJ0ZW1wbGF0ZSgpIHRvIGFkZCB0aGUgaW5mb3JtYXRpb24gb2YgdGhlDQogICAgICMjIHR3byBudW1lcmljYWwgdmFyaWFibGUgdG8gdGhlIGhvdmVyIHRleHQuDQogICAgIGhvdmVydGVtcGxhdGUgPSBwYXN0ZSgnPGI+U2VwYWwgV2lkdGg8Yj46ICV7eX0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj48Yj5TZXBhbCBMZW5ndGg8L2I+OiAle3h9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+PGI+UGV0YWwgTGVuZ3RoPC9iPjogJXttYXJrZXIuc2l6ZTosfScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPjxiPlBldGFsIFdpZHRoPC9iPjogJXtjdXN0b21kYXRhfScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPjxiPlNwZWNpZXM8L2I+OiAle2hvdmVydGV4dH0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIjxleHRyYT48L2V4dHJhPiIpICkgJT4lDQogICAgbGF5b3V0KCANCiAgICAgIGltYWdlcyA9IGxpc3QoICANCiAgICAgIGxpc3QoDQogICAgICAgIHNvdXJjZSA9ICJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTNWSVovdzA2L2ltZy9pcmlzLmpwZWciLCAgDQogICAgICAgIHhyZWY9InBhcGVyIiwgICMgd2l0aG91dCBhc3N1bWluZyBoYXZpbmcgYSBjb29yZGluYXRlIHN5c3RlbSwgd2UNCiAgICAgICAgeXJlZj0icGFwZXIiLCAgIyBjYW4gdXNlIHBhcGVyIHNpemUgdG8gc2V0IHVwIGEgcmVsYXRpdmUgbG9jYXRpb24NCiAgICAgICAgICAgICAgICAgICAgICAgIyB0byBwbGFjZSBhbiBpbWFnZS4NCiAgICAgICAgICAgICAgICAgICAgICAgIyBXZSBjYW4gYWxzbyB1c2UgIHhyZWY9InggZG9tYWluIiwgeXJlZj0ieSBkb21haW4iLCANCiAgICAgICAgICAgICAgICAgICAgICAgIyB0byBzZXQgdXAgYSBjb29yZGluYXRlIHN5c3RlbSB0byBwbGFjZSBhbiBpbWFnZS4NCiAgICAgICAgeCA9IDAsICAgICAjIHZhbHVlIGJldHdlZW4gMCBhbmQgMSAtIHJlcHJlc2VudGluZyBwZXJjZW50YWdlIGZyb20gbGVmdA0KICAgICAgICAgICAgICAgICAgICMgaGFuZCBzaWRlIG9mIHggKDApIGFuZCB0aGUgcmlnaHQgaGFuZCBzaWRlIG9mIHggKDEpLg0KICAgICAgICB5ID0gMSwgICAgICMgeCA9IDAsIHkgPSAxID09PiAidG9wbGVmdCINCiAgICAgICAgc2l6ZXggPSAuMiwgICAjICBpbWFnZSBzaXplIC0gaG9yaXpvbnRhbCANCiAgICAgICAgc2l6ZXkgPSAuMiwgICAjICB2ZXJ0aWNhbCBzaXplDQogICAgICAgIHhhbmNob3I9ImxlZnQiLCAgIyBpbWFnZSBsb2NhdGlvbiAtIA0KICAgICAgICB5YW5jaG9yPSJ0b3AiICwNCiAgICAgICAgb3BhY2l0eSA9IDAuNiAgICAjIGFkanVzdGluZyBpbWFnZSBvcGFjaXR5DQogICAgICApICANCiAgICApICANCiAgKQ0KYGBgDQoNCg0KDQoqKlNldHRpbmcgSW1hZ2UgQmFja2dyb3VuZCBmb3IgcGxvdGx5IENoYXJ0cyoqDQoNCg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTR9DQpwYWwgPC0gYygiIzMzMjI4OCIsICIjMTE3NzMzIiwgIiM4ODIyNTUiKQ0KcGFsIDwtIHNldE5hbWVzKHBhbCwgYygidmlyZ2luaWNhIiwgInNldG9zYSIsICJ2ZXJzaWNvbG9yIikpDQoNCnBsb3RfbHkoDQogICAgZGF0YSA9IGlyaXMsDQogICAgeCA9IH5TZXBhbC5MZW5ndGgsICAgICAgICAgIyBIb3Jpem9udGFsIGF4aXMgDQogICAgeSA9IH5TZXBhbC5XaWR0aCwgICAgICAgICAgIyBWZXJ0aWNhbCBheGlzIA0KICAgIGN1c3RvbWRhdGEgPSB+UGV0YWwuV2lkdGgsDQogICAgY29sb3IgPSB+ZmFjdG9yKFNwZWNpZXMpLCAgICMgbXVzdCBiZSBhIG51bWVyaWMgZmFjdG9yDQogICAgY29sb3JzID0gcGFsLCAgICAgICAgICAgICAgICMgY3VzdG9tIGNvbG9yIHBhbGV0dGUgDQogICAgIGhvdmVydGV4dCA9IH5TcGVjaWVzLCAgICAgICMgc2hvdyB0aGUgc3BlY2llcyBpbiB0aGUgaG92ZXIgdGV4dA0KICAgICBob3ZlcmxhYmVsID0gflBldGFsLldpZHRoLA0KICAgICAjIyMjDQogICAgIG1hcmtlciA9IGxpc3Qoc2l6ZSA9IH5QZXRhbC5MZW5ndGgsIHNpemVyZWYgPSAuMDUsIHNpemVtb2RlID0gJ2FyZWEnKSwNCiAgICAgIw0KICAgICBhbHBoYSAgPSAwLjksDQogICAgIHR5cGUgPSAic2NhdHRlciIsDQogICAgIG1vZGUgPSAibWFya2VycyIsDQogICAgICMjIHVzaW5nIHRoZSBmb2xsb3dpbmcgaG92ZXJ0ZW1wbGF0ZSgpIHRvIGFkZCB0aGUgaW5mb3JtYXRpb24gb2YgdGhlDQogICAgICMjIFR3byBudW1lcmljYWwgdmFyaWFibGVzIHRvIHRoZSBob3ZlciB0ZXh0Lg0KICAgICBob3ZlcnRlbXBsYXRlID0gcGFzdGUoJzxiPlNlcGFsIFdpZHRoPGI+OiAle3l9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICc8YnI+PGI+U2VwYWwgTGVuZ3RoPC9iPjogJXt4fScsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAnPGJyPjxiPlBldGFsIExlbmd0aDwvYj46ICV7bWFya2VyLnNpemU6LH0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj48Yj5QZXRhbCBXaWR0aDwvYj46ICV7Y3VzdG9tZGF0YX0nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgJzxicj48Yj5TcGVjaWVzPC9iPjogJXtob3ZlcnRleHR9JywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICI8ZXh0cmE+PC9leHRyYT4iKSANCiAgICAgKSAlPiUNCiAgICAgICBsYXlvdXQoDQogICAgICAgICBpbWFnZXMgPSBsaXN0KA0KICAgICAgICAgICBsaXN0KA0KICAgICAgICAgICAjIEFkZCBpbWFnZXMNCiAgICAgICAgICAgc291cmNlID0gICJodHRwczovL3Blbmdkc2NpLmdpdGh1Yi5pby9TVEE1NTNWSVovdzA2L2ltZy9pcmlzYmcuanBnIiwNCiAgICAgICAgICAgeHJlZiA9ICJ4IiwNCiAgICAgICAgICAgeXJlZiA9ICJ5IiwNCiAgICAgICAgICAgeCA9IDQsDQogICAgICAgICAgIHkgPSA0LjUsDQogICAgICAgICAgIHNpemV4ID0gNywNCiAgICAgICAgICAgc2l6ZXkgPSAzLA0KICAgICAgICAgICBzaXppbmcgPSAic3RyZXRjaCIsDQogICAgICAgICAgIG9wYWNpdHkgPSAwLjUsDQogICAgICAgICAgIGxheWVyID0gImJlbG93Ig0KICAgICAgICAgKQ0KICAgICAgICkNCiAgICApDQoNCmBgYA0KDQoNCiMjIyBBbmltYXRlZCBHcmFwaHMgd2l0aCBwbG90X2x5DQoNCldoZW4gYSBkYXRhIHNldCBpbnZvbHZlcyBhIHRpbWUgdmFyaWFibGUsIHdlIGFsc28gdXNlIG1vdmVtZW50IHRvIHJlcHJlc2VudCB0aGUgdGltZSB2YXJpYWJsZS4gYHBsb3RfbHkoKWAgY2FuIGNyZWF0ZSBhbmltYXRlZCBncmFwaHMuIFRoZSBmb2xsb3dpbmcgaXMgYW4gZXhhbXBsZSB1c2luZyB0aGUgYnVpbHQtaW4gYGdhcG1pbmRlciBkYXRhIHNldGAgaW4gdGhlIGxpYnJhcnkgYGdhcG1pbmRlcmAgdGhhdCBkaXNwbGF5cyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gbGlmZSBleHBlY3RhbmN5IGFuZCBHRFAgcGVyIGNhcGl0YSBvZiBjb3VudHJpZXMgb3ZlciB0aW1lIChldmVyeSA1IHllYXJzKS4NCg0KYGBge3J9DQpwYWwuSUJNIDwtIGMoIiMzMzIyODgiLCAiIzExNzczMyIsICIjMDA3MkIyIiwiI0Q1NUUwMCIsICIjODgyMjU1IikNCnBhbC5JQk0gPC0gc2V0TmFtZXMocGFsLklCTSwgYygiQXNpYSIsICJFdXJvcGUiLCAiQWZyaWNhIiwgIkFtZXJpY2FzIiwgIk9jZWFuaWEiKSkNCg0KZGYgPC0gZ2FwbWluZGVyIA0KZmlnIDwtIGRmICU+JQ0KICBwbG90X2x5KA0KICAgIHggPSB+Z2RwUGVyY2FwLCANCiAgICB5ID0gfmxpZmVFeHAsIA0KICAgIHNpemUgPSB+KDIqbG9nKHBvcCktMTEpXjIsDQogICAgY29sb3IgPSB+Y29udGluZW50LCANCiAgICBjb2xvcnMgPSBwYWwuSUJNLCAgICMgY3VzdG9tIGNvbG9ycw0KICAgICNtYXJrZXIgPSBsaXN0KHNpemUgPSB+KGxvZyhwb3ApLTEwKSwgIHNpemVtb2RlID0gJ2FyZWEnKSwNCiAgICBmcmFtZSA9IH55ZWFyLCAgICAgICMgdGhlIHRpbWUgdmFyaWFibGUgdG8NCiAgICAjIHRvIGRpc3BsYXkgaW4gdGhlIGhvdmVyDQogICAgdGV4dCA9IH5wYXN0ZSgiQ291bnRyeToiLCBjb3VudHJ5LA0KICAgICAgICAgICAgICAgICAgIjxicj5Db250aW5lbnQ6IiwgY29udGluZW50LA0KICAgICAgICAgICAgICAgICAgIjxicj5ZZWFyOiIsIHllYXIsDQogICAgICAgICAgICAgICAgICAiPGJyPkxpZmVFeHA6IiwgbGlmZUV4cCwNCiAgICAgICAgICAgICAgICAgICI8YnI+UG9wOiIsIHBvcCwNCiAgICAgICAgICAgICAgICAgICI8YnI+Z2RwUGVyQ2FwOiIsIGdkcFBlcmNhcCksDQogICAgaG92ZXJpbmZvID0gInRleHQiLA0KICAgIHR5cGUgPSAnc2NhdHRlcicsDQogICAgbW9kZSA9ICdtYXJrZXJzJw0KICApDQpmaWcgPC0gZmlnICU+JSBsYXlvdXQoDQogICAgeGF4aXMgPSBsaXN0KA0KICAgICAgdHlwZSA9ICJsb2ciDQogICAgKQ0KICApDQoNCmZpZw0KYGBgDQoNCg0KDQoNCg0KIyMjIFJlbmRlcmluZyBBIEdHUExPVCB3aXRoIGBnZ3Bsb3RseWANCg0KV2UgY2FuIGFsc28gcmVuZGVyIGEgZ2dwbG90IHVzaW5nIGdncGxvdGx5IHRvIGJyaW5nIGludGVyYWN0aXZpdHkgdG8gdGhlIHBsb3QuIFRoZSBuZXh0IGlzIGEgY3VzdG9tYXJ5IHRoZW1lIHRvIGxheSBvdXQgZ2dwbG90cy4gDQoNCg0KYGBge3J9DQpteXBsb3QudGhlbWVfbmV3IDwtIGZ1bmN0aW9uKCkgew0KICB0aGVtZSgNCiAgICAjZ2dwbG90IG1hcmdpbnMNCiAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4odCA9IDUwLCAgIyBUb3AgbWFyZ2luDQogICAgICAgICAgICAgICAgICAgICAgICAgIHIgPSAzMCwgICMgUmlnaHQgbWFyZ2luDQogICAgICAgICAgICAgICAgICAgICAgICAgIGIgPSAzMCwgICMgQm90dG9tIG1hcmdpbg0KICAgICAgICAgICAgICAgICAgICAgICAgICBsID0gMzApLCAjIExlZnQgbWFyZ2luDQogICAgIyMgZ2dwbG90IHRpdGxlcw0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSAic2FucyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAibmF2eSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDAuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbj1tYXJnaW4oMCwwLDMwLDApKSwgIyBsZWZ0KDApLHJpZ2h0KDEpDQogICAgIyBhZGQgYm9yZGVyIDEpDQogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9IE5BLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IE5BLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZXR5cGUgPSAyKSwNCiAgICAjIGNvbG9yIGJhY2tncm91bmQgMikNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiI2Y2ZjZmNiIpLA0KICAgICMgbW9kaWZ5IGdyaWQgMykNCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3VyID0gJ3doaXRlJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAwLjUpLA0KICAgIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSAgZWxlbWVudF9saW5lKGNvbG91ciA9ICd3aGl0ZScsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZXR5cGUgPSAzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAwLjUpLA0KICAgIHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAjIG1vZGlmeSB0ZXh0LCBheGlzLCBhbmQgY29sb3IgNCkgYW5kIDUpDQogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJuYXZ5IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICNmYWNlID0gIml0YWxpYyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gNywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI2ZhbWlseSA9ICJUaW1lcyBOZXcgUm9tYW4iDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICksDQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSAibmF2eSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZmFtaWx5ID0gIlRpbWVzIE5ldyBSb21hbiINCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksDQogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShjb2xvdXIgPSAibmF2eSIpLA0KICAgICMgbGVnZW5kIGF0IHRoZSBib3R0b20gNikNCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuNiwgJ2NtJyksICNjaGFuZ2UgbGVnZW5kIGtleSBzaXplDQogICAgbGVnZW5kLmtleS5oZWlnaHQgPSB1bml0KDAuNiwgJ2NtJyksICNjaGFuZ2UgbGVnZW5kIGtleSBoZWlnaHQNCiAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjYsICdjbScpLCAjY2hhbmdlIGxlZ2VuZCBrZXkgd2lkdGgNCiAgICAjbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksICNjaGFuZ2UgbGVnZW5kIHRpdGxlIGZvbnQgc2l6ZQ0KICAgIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCksICAjIHJlbW92ZSBhbGwgbGVnZW5kIHRpdGxlcw0KICAgIGxlZ2VuZC5rZXkgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpLA0KICAgICMjIyMjDQogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkgI2NoYW5nZSBsZWdlbmQgdGV4dCBmb250IHNpemUNCn0NCmBgYA0KDQoNClRoZSBmb2xsb3dpbmcgcGxvdCB1c2VzIHRoZSBhYm92ZSB0aGVtZSBhbmQgcGFzc2VzIHRoZSBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCB0byB0aGUgYW5ub3RhdGVkIHRleHQuIA0KDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcCA8LSBnZ3Bsb3QoaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTZXBhbC5XaWR0aCkpICsNCiAgICAgICAgICAgICAjYWVzKGNvbG9yID0gZmFjdG9yKFNwZWNpZXMpKSArDQogICAgICAgICAgICAgYWVzKGxhYmVsID0gU3BlY2llcywgbGFiZWwxID0gUGV0YWwuTGVuZ3RoLCBsYWJlbDIgPSAgUGV0YWwuV2lkdGgpICsgDQogICAgICAgICAgICAgIyMgVGhlIGxhYmVscyBpbiB0aGUgYWJvdmUgYWVzKCkgd2lsbCBiZSBwYXJ0IG9mIHRoZSBob3ZlciB0ZXh0Lg0KICAgICAgICAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IGlyaXMkUGV0YWwuTGVuZ3RoLCBhbHBoYSA9IDAuNykgKw0KICAgICAgICAgICAgIHN0YXRfc21vb3RoKG1ldGhvZCA9IGxtLCBzZT1GQUxTRSwgc2l6ZSA9IDAuNSkgKyAgICMgYWRkIGEgbGluZWFyIHJlZ3Jlc3Npb24gbGluZQ0KICAgICAgICAgICAgICNzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoImRvZGdlcmJsdWU0IiwgImRhcmtvbGl2ZWdyZWVuNCIsICJkYXJrcmVkIikpICsNCiAgICAgICAgICAgICBsYWJzKA0KICAgICAgICAgICAgICAgICB4ID0gIlNlcGFsIExlbmd0aCIsDQogICAgICAgICAgICAgICAgIHkgPSAiU2VwYWwgV2lkdGgiLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJBc3NvY2lhdGlvbiBiZXR3ZWVuIFNlcGFsIExlbmd0aCBhbmQgV2lkdGgiKSArDQogICAgICAgICAgICAgbXlwbG90LnRoZW1lX25ldygpICsgDQogICAgICAgICAgICAgIGFubm90YXRlKGdlb209InRleHQiICwgDQogICAgICAgICAgICAgICAgICAgICAgIHg9Ni44LCANCiAgICAgICAgICAgICAgICAgICAgICAgeT0yLA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbD1wYXN0ZSgiVGhlIFBlYXJzb24gY29ycmVsYXRpb24gY29lZmZpY2llbnQgciA9ICIsICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoY29yKGlyaXMkU2VwYWwuTGVuZ3RoLCBpcmlzJFNlcGFsLldpZHRoKSwzKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAibmF2eSIpICsgDQogICAgICAgICAgICAgICBjb29yZF9maXhlZCgxKSAgICAjIyBUaGlzIGNoYW5nZXMgdGhlIGFzcGVjdCByYXRpbyBvZiB0aGUgZ3JhcGgNCmdncGxvdGx5KHApDQpgYGANCg0KDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcCA8LSBnZ3Bsb3QoaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTZXBhbC5XaWR0aCkpICsNCiAgICAgICAgICAgICAjIGFlcyhjb2xvciA9IGZhY3RvcihTcGVjaWVzKSkgKw0KICAgICAgICAgICAgICMgdG8gYWRkIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSBzZXQNCiAgICAgICAgICAgICAjIHVzZSBsYWJlbHMgdG8gZGVub3RlIHRoZSB2YXJpYWJsZSBuYW1lcyBpbnNpZGUgdGhlIGZ1bmN0aW9uIGFlcygpDQogICAgICAgICAgICAgYWVzKGxhYmVsPVNwZWNpZXMsIGxhYmVsMj1QZXRhbC5MZW5ndGgsIGxhYmVsMz1QZXRhbC5XaWR0aCkgKw0KICAgICAgICAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IGlyaXMkUGV0YWwuV2lkdGgsIGFscGhhID0gMC43KSArDQogICAgICAgICAgICAgc3RhdF9zbW9vdGgobWV0aG9kID0gbG0sIHNlPUZBTFNFLCBzaXplID0gMC4zKSArDQogICAgICAgICAgICAgI3NjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9YygiZG9kZ2VyYmx1ZTQiLCAiZGFya29saXZlZ3JlZW40IiwgImRhcmtyZWQiKSkgKw0KICAgICAgICAgICAgIGxhYnMoDQogICAgICAgICAgICAgICAgIHggPSAiU2VwYWwgTGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgeSA9ICJTZXBhbCBXaWR0aCIsDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIkFzc29jaWF0aW9uIGJldHdlZW4gU2VwYWwgTGVuZ3RoIGFuZCBXaWR0aCIpICsNCiAgICAgICAgICAgICBteXBsb3QudGhlbWVfbmV3KCkgKyANCiAgICAgICAgICAgICAgYW5ub3RhdGUoZ2VvbT0idGV4dCIgLCANCiAgICAgICAgICAgICAgICAgICAgICAgeD02LjgsIA0KICAgICAgICAgICAgICAgICAgICAgICB5PTIsDQogICAgICAgICAgICAgICAgICAgICAgIGxhYmVsPXBhc3RlKCJUaGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBjb2VmZmljaWVudCByID0gIiwgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb3VuZChjb3IoaXJpcyRTZXBhbC5MZW5ndGgsIGlyaXMkU2VwYWwuV2lkdGgpLDMpKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJuYXZ5IikgKyANCiAgICAgICAgICAgICAgIGNvb3JkX2ZpeGVkKDEpICAgICMjIFRoaXMgY2hhbmdlcyB0aGUgYXNwZWN0IHJhdGlvIG9mIHRoZSBncmFwaA0KZ2dwbG90bHkocCkNCmBgYA0KDQoNCioqUmVtYXJrKio6IEl0IHR1cm5zIHRoYXQgYGdncGxvdGx5YCBjYW5ub3QgZGlzcGxheSBjb2xvcnMgZHVlIHRvIGl0cyByZWNlbnQgdXBkYXRlcy4gSG9wZSB0aGF0IHRoaXMgaXNzdWUgd2lsbCBiZSBmaXhlZCBzb29uLg0KDQoNCg0KDQoNCg0KDQoNCg0KDQojIyBCYXIgJiBQaWUgQ2hhcnQNCg0KIyMjIEJhcnBsb3QNCg0KV2Ugd2lsbCBjcmVhdGUgYSBzdW1tYXJpemVkIGRhdGEgc2V0IHRvIG1ha2UgYmFyIHBsb3RzLiBXZSBkZWZpbmUgYSBkYXRhIHNldCB0byBzdG9yZSB0aGUgbWVhbiBvZiBzZXBhbCBsZW5ndGggYW5kIHNlcGFsIHdpZHRoIGJ5IHNwZWNpZXMgdXNpbmcgdGhlIGBkeXBscmAgYW5kIGB0aWR5cmAgYXBwcm9hY2hlcy4NCg0KYGBge3J9DQpiYXJwbG90ZGF0YSA9IGFnZ3JlZ2F0ZShpcmlzWywxOjRdLCBieSA9IGxpc3QoaXJpcyRTcGVjaWVzKSwgRlVOID0gbWVhbikNCmthYmxlKGhlYWQoYmFycGxvdGRhdGEpKQ0KYGBgDQoNCk5leHQsIHdlIGRyYXcgYSBncm91cCBiYXIgY2hhcnQuDQoNCmBgYHtyfQ0KcGxvdF9seSgNCiAgZGF0YSA9IGJhcnBsb3RkYXRhLA0KICAgeCA9IH5Hcm91cC4xLA0KICAgeSA9IH5TZXBhbC5MZW5ndGgsDQogICB0eXBlID0gImJhciIsDQogICBuYW1lID0gInNlcGFsLmxlbmd0aC5hdmciLA0KICAgIyMgZ3JhcGhpYyBzaXplDQogICB3aWR0aCA9IDcwMCwNCiAgIGhlaWdodCA9IDQwMCkgJT4lDQogICAgYWRkX3RyYWNlKHk9flNlcGFsLldpZHRoLCBuYW1lID0gInNlcGFsLndpZHRoLmF2ZyIpICU+JQ0KICAgIGFkZF90cmFjZSh5PX5QZXRhbC5MZW5ndGgsIG5hbWUgPSAicGV0YWwubGVuZ3RoLmF2ZyIpICU+JQ0KICAgIGFkZF90cmFjZSh5PX5QZXRhbC5XaWR0aCwgbmFtZSA9ICJwZXRhbC53aWR0aC5hdmciKSAlPiUNCiAgICBsYXlvdXQoIHlheGlzID0gbGlzdCh0aXRsZSA9Ik1lYW4iKSwNCiAgICAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJTcGVjaWVzIiksDQogICAgICAgICAgICB0aXRsZSA9ICJHcm91cCBNZWFucyBvZiBJcmlzIGF0dHJpYnV0ZXMiLA0KICAgICAgICAgICAgICAgICAgIyMgbWFyZ2luIG9mIHRoZSBwbG90DQogICAgICBtYXJnaW4gPSBsaXN0KA0KICAgICAgICAgICAgICBiID0gNTAsDQogICAgICAgICAgICAgIGwgPSAxMDAsDQogICAgICAgICAgICAgIHQgPSAxMjAsDQogICAgICAgICAgICAgIHIgPSA1MA0KICAgICAgKSkNCmBgYA0KDQoNCg0KIyMjIFBpZSBDaGFydA0KDQpXZSBmaXJzdCBkZWZpbmUgYSBzdWJzZXQgZnJvbSB0aGUgaXJpcyBkYXRhIGJ5IGZpbHRlcmluZyBvdXQgb2JzZXJ2YXRpb25zIHdpdGggYSBzZXBhbCBsZW5ndGggb2YgbGVzcyB0aGFuIDUuIFRoZSBwaWUgY2hhcnQgd2lsbCBiZSBjcmVhdGVkIHRvIHNlZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHNwZWNpZXMgaW4gdGhlIHN1YnNldCBvZiB0aGUgaXJpcyBkYXRhLiBLZWVwIGluIG1pbmQgdGhhdCB0aGUgcGllIGNoYXJ0IGlzIGNvbnN0cnVjdGVkIGJhc2VkIG9uIGEgZnJlcXVlbmN5IHRhYmxlLg0KDQoNCmBgYHtyfQ0KIyBkZWZpbmUgYSB3b3JraW5nIGRhdGEgc2V0DQpzdWJpcmlzIDwtIGlyaXNbaXJpcyRTZXBhbC5MZW5ndGggPiA1LDVdDQojIyBDcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUgaW4gdGhlIGZvcm0gb2YgdGhlIGRhdGEgZnJhbWUuDQpwaWVkYXRhID0gZGF0YS5mcmFtZShjYXRlID1hcy52ZWN0b3IodW5pcXVlKHN1YmlyaXMpKSwgDQogICAgICAgICAgICAgICAgICAgICBmcmVxID0gYXMudmVjdG9yKHRhYmxlKHN1YmlyaXMpKSkNCiMgZGVmaW5lIGEgY29sb3IgdmVjdG9yDQpjb2xvcnMgPC0gYygncmdiKDIxMSw5NCw5NiknLCAncmdiKDEyOCwxMzMsMTMzKScsICdyZ2IoMTQ0LDEwMywxNjcpJykNCiMgbWFrZSBhIHBpZSBjaGFydA0KcGxvdF9seShwaWVkYXRhLCANCiAgICAgICAgbGFiZWxzID0gfmNhdGUsIA0KICAgICAgICB2YWx1ZXMgPSB+ZnJlcSwgDQogICAgICAgIHR5cGUgPSAncGllJywNCiAgICAgICAgdGV4dHBvc2l0aW9uID0gJ2luc2lkZScsDQogICAgICAgIHRleHRpbmZvID0gJ2xhYmVsICsgcGVyY2VudCcsDQogICAgICAgIGluc2lkZXRleHRmb250ID0gbGlzdChjb2xvciA9ICcjRkZGRkZGJyksDQogICAgICAgICNob3ZlcmluZm8gPSAndGV4dCcsDQogICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3JzID0gY29sb3JzLA0KICAgICAgICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KGNvbG9yID0gJyNGRkZGRkYnLCB3aWR0aCA9IDEpKSwNCiAgICAgICAgICAgICAgICAgICAgICAjVGhlICdwdWxsJyBhdHRyaWJ1dGUgY2FuIGFsc28gYmUgdXNlZCB0byBjcmVhdGUgc3BhY2UgYmV0d2VlbiB0aGUgc2VjdG9ycw0KICAgICAgICBzaG93bGVnZW5kID0gVFJVRSkgJT4lIA0KICAgICAgICAgbGF5b3V0KHRpdGxlID0gJ0Rpc3RyaWJ1dGlvbiBvZiBTcGVjaWVzJywNCiAgICAgICAgICAgICAgICB4YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3d0aWNrbGFiZWxzID0gRkFMU0UpLA0KICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSksDQogICAgICAgICAgICAgICAgICAgICAgIyMgbWFyZ2luIG9mIHRoZSBwbG90DQogICAgICBtYXJnaW4gPSBsaXN0KA0KICAgICAgICAgICAgICBiID0gNTAsDQogICAgICAgICAgICAgIGwgPSAxMDAsDQogICAgICAgICAgICAgIHQgPSAxMjAsDQogICAgICAgICAgICAgIHIgPSA1MA0KICAgICAgKSkNCmBgYA0KDQoNCiMjIEhpc3RvZ3JhbSAmIERlc25pdHkNCg0KSGlzdG9ncmFtcyBhbmQgZGVuc2l0eSBjdXJ2ZXMgYXJlIHVzZWQgdG8gZGlzcGxheSB0aGUgZGlzdHJpYnV0aW9uIG9mIG51bWVyaWNhbCByYW5kb20gdmFyaWFibGVzLiBXaGVuIGNvbXBhcmluZyB0aGUgZGlzdHJpYnV0aW9ucyBvZiBkaWZmZXJlbnQgcmFuZG9tIHZhcmlhYmxlcywgd2UgY2FuIG92ZXJsYXkgdGhlIGhpc3RvZ3JhbXMgb3IgZGVuc2l0eSBjdXJ2ZXMuIA0KDQojIyMgQ29tcGFyaW5nIERpc3RyaWJ1dGlvbnMgVXNpbmcgSGlzdG9ncmFtcw0KDQpXZSBjYW4gb3ZlcmxheSBoaXN0b2dyYW1zIHRvIGNvbXBhcmUgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgbXVsdGlwbGUgcmFuZG9tIHZhcmlhYmxlcy4NCg0KYGBge3J9DQpwbG90X2x5KA0KICBkYXRhID0gaXJpcywNCiAgIHggPSB+IFNlcGFsLkxlbmd0aCwNCiAgIHR5cGUgPSAiaGlzdG9ncmFtIiwNCiAgIG5iaW5zeCA9IDEwLCANCiAgIG5hbWUgPSAic2VwYWwubGVuZ3RoIiwNCiAgIGFscGhhID0gLjUsDQogICBtYXJrZXIgPSBsaXN0KGxpbmUgPSBsaXN0KGNvbG9yID0gImRhcmtncmF5IiwgIHdpZHRoID0gMikpICkgJT4lDQogICAjIyBBZGRpbmcgYWRkaXRpb25hbCBoaXN0b2dyYW1zIGFuZCBzdGFja2luZyB0aGVtDQogICAgYWRkX2hpc3RvZ3JhbSh4ID0gflNlcGFsLldpZHRoLA0KICAgICAgICAgICAgICAgIG5hbWUgPSAic2VwYWwud2lkdGgiLCBuYmluc3ggPSAxMCwgYWxwaGEgPSAwLjUsDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChsaW5lID0gbGlzdChjb2xvciA9ICJkYXJrZ3JheSIsICB3aWR0aCA9IDIpKSkgJT4lDQogICAgYWRkX2hpc3RvZ3JhbSh4ID0gflBldGFsLkxlbmd0aCwNCiAgICAgICAgICAgICAgICBuYW1lID0gInBldGFsLmxlbmd0aCIsbmJpbnN4ID0gMTAsIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QobGluZSA9IGxpc3QoY29sb3IgPSAiZGFya2dyYXkiLCAgd2lkdGggPSAyKSkpICU+JQ0KICAgIGFkZF9oaXN0b2dyYW0oeCA9IH5QZXRhbC5XaWR0aCwNCiAgICAgICAgICAgICAgICBuYW1lID0gInBldGFsLndpZHRoIixuYmluc3ggPSAxMCwgYWxwaGEgPSAwLjUsDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdChsaW5lID0gbGlzdChjb2xvciA9ICJkYXJrZ3JheSIsICB3aWR0aCA9IDIpKSkgJT4lDQogIGxheW91dChiYXJtb2RlID0gIm92ZXJsYXkiLA0KICAgICAgICAgdGl0bGUgPSAiSGlzdG9ncmFtIG9mIElyaXMgQXR0cmlidXRlIiwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJJcmlzIEF0dHJpYnV0ZXMiLA0KICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5lID0gVFJVRSksDQogICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiQ291bnQiLA0KICAgICAgICAgICAgICAgICAgICAgIHplcm9saW5lID1UUlVFKSwNCiAgICAgICAgICAgICAgICMjIG1hcmdpbiBvZiB0aGUgcGxvdA0KICAgICAgbWFyZ2luID0gbGlzdCgNCiAgICAgICAgICAgICAgYiA9IDUwLA0KICAgICAgICAgICAgICBsID0gMTAwLA0KICAgICAgICAgICAgICB0ID0gMTIwLA0KICAgICAgICAgICAgICByID0gNTANCiAgICAgICkpDQpgYGANCg0KVGhlIGlzc3VlIGlzIHRoYXQgdGhlIGFib3ZlIG92ZXJsYWlkIGhpc3RvZ3JhbXMgY2Fubm90IGJlIGVhc3kgdG8gZGlzdGluZ3Vpc2ggd2hlbiBjb21wYXJpbmcgbW9yZSB0aGFuIHR3byBkaXN0cmlidXRpb25zIGluIGdlbmVyYWwuIFRoZSByaWRnZWxpbmUgaGlzdG9ncmFtIGNhbiBoZWxwIGluIGdlbmVyYWwuIFRoZSBmb2xsb3dpbmcgaXMgYW4gZXhhbXBsZSBvZiByaWRnZWxpbmUgaGlzdG9ncmFtcy4NCg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9DQpnZ3Bsb3QoaXJpcywgYWVzKHggPSBTZXBhbC5MZW5ndGgsIHkgPSBTcGVjaWVzLCBncm91cCA9IFNwZWNpZXMsIGZpbGwgPSBTcGVjaWVzKSkgKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiYmlubGluZSIsIGJpbnMgPSAyMCwgc2NhbGUgPSAyLjIpICsNCiAgc2NhbGVfeV9kaXNjcmV0ZShleHBhbmQgPSBjKDAsIDApKSArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArDQogIGNvb3JkX2NhcnRlc2lhbihjbGlwID0gIm9mZiIpICsNCiAgdGhlbWVfcmlkZ2VzKCkNCmBgYA0KDQoNCg0KIyMjIERlbnNpdHkgQ3VydmUNCg0KSXQgaXMgcmVsYXRpdmVseSBlYXN5IHRvIHVzZSBkZW5zaXR5IGN1cnZlcyB0byBjb21wYXJlIG11bHRpcGxlIGRpc3RyaWJ1dGlvbnMuIEFzc3VtZSB0aGF0IHdlIHdhbnQgdG8gY29tcGFyZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBzZXBhbCBsZW5ndGggb2YgdGhlIHRyZWUgaXJpcyBmbG93ZXJzLiBPbmUgd2F5IHRvIGRvIHRoaXMgY29tcGFyaXNvbiBpcyB0byBwbG90IHRoZSB0aHJlZSBlc3RpbWF0ZWQgZGVuc2l0eSBjdXJ2ZXMuDQoNCmBgYHtyfQ0KIyBkZWZpbmUgdGhyZWUgZGVuc2l0aWVzDQpzZXBhbC5sZW4uc2V0b3NhIDwtIGlyaXNbd2hpY2goaXJpcyRTcGVjaWVzID09ICJzZXRvc2EiKSxdDQpzZXRvc2EgPC0gZGVuc2l0eShzZXBhbC5sZW4uc2V0b3NhJFNlcGFsLkxlbmd0aCkNCnNlcGFsLmxlbi52ZXJzaWNvbG9yIDwtIGlyaXNbd2hpY2goaXJpcyRTcGVjaWVzID09ICJ2ZXJzaWNvbG9yIiksXQ0KdmVyc2ljb2xvciA8LSBkZW5zaXR5KHNlcGFsLmxlbi52ZXJzaWNvbG9yJFNlcGFsLkxlbmd0aCkNCnNlcGFsLmxlbi52aXJnaW5pY2EgPC0gaXJpc1t3aGljaChpcmlzJFNwZWNpZXMgPT0gInZpcmdpbmljYSIpLF0NCnZpcmdpbmljYSA8LSBkZW5zaXR5KHNlcGFsLmxlbi52aXJnaW5pY2EkU2VwYWwuTGVuZ3RoKQ0KIyBwbG90IGRlbnNpdHkgY3VydmVzDQpmaWcgPC0gcGxvdF9seSh4ID0gfnZpcmdpbmljYSR4LCANCiAgICAgICAgICAgICAgIHkgPSB+dmlyZ2luaWNhJHksIA0KICAgICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywgI0EgY2hhcmFjdGVyIHN0cmluZyBzcGVjaWZ5aW5nIHRoZSB0cmFjZSB0eXBlDQogICAgICAgICAgICAgICBtb2RlID0gJ2xpbmVzJywgDQogICAgICAgICAgICAgICBuYW1lID0gJ3ZpcmdpbmljYScsIA0KICAgICAgICAgICAgICAgZmlsbCA9ICd0b3plcm95JykgICU+JSANCiAgICAgICAgICAgIyBhZGRpbmcgbW9yZSBkZW5zaXR5IGN1cnZlcw0KICAgICAgIGFkZF90cmFjZSh4ID0gfnZlcnNpY29sb3IkeCwgDQogICAgICAgICAgICAgICAgIHkgPSB+dmVyc2ljb2xvciR5LCANCiAgICAgICAgICAgICAgICAgbmFtZSA9ICd2ZXJzaWNvbG9yJywgDQogICAgICAgICAgICAgICAgIGZpbGwgPSAndG96ZXJveScpICAlPiUgDQogICAgICAgYWRkX3RyYWNlKHggPSB+c2V0b3NhJHgsIA0KICAgICAgICAgICAgICAgICB5ID0gfnNldG9zYSR5LCANCiAgICAgICAgICAgICAgICAgbmFtZSA9ICdzZXRvc2EnLCANCiAgICAgICAgICAgICAgICAgZmlsbCA9ICd0b3plcm95JykgICU+JSAgIA0KICAgICAgIGxheW91dCh4YXhpcyA9IGxpc3QodGl0bGUgPSAnU2VwYWwgTGVuZ3RoJyksDQogICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICdEZW5zaXR5JykpDQpmaWcNCmBgYA0KDQoNClRoZSBhYm92ZSBvdmVybGFpZCBkZW5zaXR5IHBsb3RzICh3aXRoIGEgY2VydGFpbiBsZXZlbCBvZiB0cmFuc3BhcmVuY3kpIGFyZSByZWxhdGl2ZWx5IGVhc3kgdG8gdmlzdWFsaXplLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTN9DQpyaWRnZURlbnNpdHkgPC0gZ2dwbG90KGlyaXMsIGFlcyh4ID0gU2VwYWwuTGVuZ3RoLCB5ID0gU3BlY2llcykpICsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsNCiAgZ2VvbV9kZW5zaXR5X2ludGVyYWN0aXZlKGFlcyh0b29sdGlwID0gaW50ZXJhY3Rpb24oU2VwYWwuTGVuZ3RoLCBTcGVjaWVzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhX2lkID0gaW50ZXJhY3Rpb24oU2VwYWwuTGVuZ3RoLCBTcGVjaWVzKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMSwgaG92ZXJfbmVhcmVzdCA9IFRSVUUpDQoNCnJpZGdlRGVuc2l0eQ0KIyBnaXJhZmUoZ2dvYmogPSByaWRnZURlbnNpdHkpDQpgYGANCg0KKipOb3RlKio6IGByaWRnZWxpbmVgIHBsb3RzIGRvIG5vdCB3b3JrIHdlbGwgd2l0aCBgZ2dwbG90bHlgIHRvIGJyaW5nIGludGVyYWN0aXZpdHkgdG8gdGhlIHBsb3RzLiBUaGVyZSBhcmUgc29tZSB3b3JrYXJvdW5kcywgYnV0IG5vbmUgaXMgZ29vZCBlbm91Z2ggZm9yIHByb2Zlc3Npb25hbCBwcmVzZW50YXRpb24uDQoNCg0KDQoNCg0KIyMgQm94cGxvdA0KDQpEcmF3aW5nIGEgYm94cGxvdCBpcyBzdHJhaWdodGZvcndhcmQgaW4gYHBsb3RseWAuDQoNCmBgYHtyfQ0KcGxvdF9seSgNCiAgZGF0YSA9IGlyaXMsDQogIHkgPSB+IFNlcGFsLkxlbmd0aCwNCiAgeCA9IH5TcGVjaWVzLA0KICB0eXBlID0gImJveCIsDQogIGNvbG9yID0gflNwZWNpZXMsDQogIGJveHBvaW50cyA9ICJhbGwiLA0KICBib3htZWFuID0gVFJVRSwNCiAgc2hvd2xlZ2VuZCA9IEZBTFNFICkgJT4lDQogICBsYXlvdXQodGl0bGUgPSAiSGlzdG9ncmFtIG9mIElyaXMgQXR0cmlidXRlIiwNCiAgICAgICAgIHhheGlzID0gbGlzdCh0aXRsZSA9ICJTcGVjaWVzIiwNCiAgICAgICAgICAgICAgICAgICAgICB6ZXJvbGluZSA9IFRSVUUpLA0KICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gIlNlcGFsIExlbmd0aCIsDQogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmUgPVRSVUUpKQ0KYGBgDQoNCg0KVGhlIG5vbi1pbnRlcmFjdGl2ZSBnZ3Bsb3QgYm94cGxvdCBpcyBnaXZlbiBieQ0KDQpgYGB7cn0NCnN1bW1hcml6ZWQuaXJpcyA9IGlyaXMgJT4lIHNlbGVjdCgtU3BlY2llcykgJT4lDQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCkpIA0KDQogZy5pcmlzID0gIGdncGxvdChzdW1tYXJpemVkLmlyaXMsIGFlcyh4PW5hbWUseT12YWx1ZSwgZmlsbD1uYW1lKSkgKw0KICAgICAgICAgICBnZW9tX2JveHBsb3QoKSArDQogICAgICAgICAgIGxhYnMoDQogICAgICAgICAgICAgICAgIHggPSAiTWVhc3VyZSBUeXBlcyIsDQogICAgICAgICAgICAgICAgIHkgPSAiTnVtZXJpY2FsIE1lYXN1cmVzIiwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiQXNzb2NpYXRpb24gYmV0d2VlbiBTZXBhbCBMZW5ndGggYW5kIFdpZHRoIikgKw0KICAgICAgICAgICBteXBsb3QudGhlbWVfbmV3KCkNCiAjIyMNCiBnLmlyaXMNCmBgYA0KDQoNCmBnZ3Bsb3RseWAgYWRkcyBpbnRlcmFjdGl2aXR5IHRvIHRoZSBwbG90LCBidXQgY2Fubm90IGFkZCBjb2xvcnMgaW4gdGhlIG1vbWVudC4NCg0KYGBge3J9DQpzdW1tYXJpemVkLmlyaXMgPSBpcmlzICU+JSBzZWxlY3QoLVNwZWNpZXMpICU+JQ0KICBwaXZvdF9sb25nZXIoZXZlcnl0aGluZygpKSANCg0KIGcuaXJpcyA9ICBnZ3Bsb3Qoc3VtbWFyaXplZC5pcmlzLCBhZXMoeCA9IG5hbWUsIHkgPSB2YWx1ZSkpICsNCiAgICAgICAgICAgZ2VvbV9ib3hwbG90KCkgKw0KICAgICAgICAgIGxhYnMoDQogICAgICAgICAgICAgICAgIHggPSAiTWVhc3VyZSBUeXBlcyIsDQogICAgICAgICAgICAgICAgIHkgPSAiTnVtZXJpY2FsIE1lYXN1cmVzIiwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiQXNzb2NpYXRpb24gYmV0d2VlbiBTZXBhbCBMZW5ndGggYW5kIFdpZHRoIikgKw0KICAgICAgICAgICBteXBsb3QudGhlbWVfbmV3KCkNCiAgICAgIA0KICMjIw0KICBnZ3Bsb3RseShnLmlyaXMpDQpgYGANCg0KDQoNCiMjIFNlcmlhbCBQbG90DQoNClZpc3VhbGl6aW5nIHRpbWUgc2VyaWVzIHNlZW1zIHRvIGJlIHJlbGF0aXZlbHkgZWFzaWVyIHNpbmNlIHRoZSBvYmplY3RpdmUgaXMgdG8gaW5zcGVjdCB0aGUgcGF0dGVybiBzdWNoIGFzIHRyZW5kLCBzZWFzb25hbGl0eSwgc3BlY2lhbCBzaGl0cywgZXRjLiB0byBhc3Npc3QgaW4gbW9kZWwgaWRlbnRpZmljYXRpb24sIHN1Y2ggYXMgZGV0ZXJtaW5pbmcgdGhlIGJlc3QgbGVuZ3RoIG9mIHRoZSBoaXN0b3J5IG9mIHlvdXIgdGltZSBzZXJpZXMgZGF0YSBmb3IgdGltZSBzZXJpZXMgZm9yZWNhc3RpbmcsIHR5cGVzIG9mIGV4cG9uZW50aWFsIHNtb290aGluZywgb3JkZXIgb2YgZGlmZmVyZW5jaW5nLCBNQSBhbmQgQVIgaW4gQVJJTUEgZnJhbWV3b3JrLCBldGMuIA0KDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NH0NCnN0b2NrIDwtIHJlYWQuY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVuZ2RzY2kvc3RhNTUzLmh0bWwvbWFpbi9kYXRhL2ZpbmFuY2UtY2hhcnRzLWFwcGxlLmNzdicpDQojIw0KZmlnIDwtIHBsb3RfbHkoc3RvY2ssIHR5cGUgPSAnc2NhdHRlcicsIG1vZGUgPSAnbGluZXMnKSAgICAlPiUNCiAgICAgICBhZGRfdHJhY2UoeCA9IH5EYXRlLCB5ID0gfkFBUEwuSGlnaCkgICAgJT4lDQogICAgICAgbGF5b3V0KHNob3dsZWdlbmQgPSBGLCANCiAgICAgICAgICAgICAgdGl0bGU9J1RpbWUgU2VyaWVzIHdpdGggUmFuZ2VzbGlkZXInLA0KICAgICAgICAgICAgICB4YXhpcyA9IGxpc3QocmFuZ2VzbGlkZXIgPSBsaXN0KHZpc2libGUgPSBUKSkpICAlPiUNCiAgICAgICBsYXlvdXQoeGF4aXMgPSBsaXN0KHplcm9saW5lY29sb3IgPSAnYmx1ZScsDQogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJyNmZmZmZmYnKSwNCiAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHplcm9saW5lY29sb3IgPSAnI2ZmZmZmZicsDQogICAgICAgICAgICAgICAgICAgICAgemVyb2xpbmV3aWR0aCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gJyNmZmYnKSwNCiAgICAgICAgICAgICAgcGxvdF9iZ2NvbG9yPScjZTVlY2Y2Jywgd2lkdGggPSA4MDAsIGhlaWdodCA9IDQwMCkNCmZpZw0KYGBgDQoNClRoZXJlIGFyZSBhbHNvIG90aGVyIGxpYnJhcmllcyBvbmUgY2FuIHVzZSB0byBwcm9kdWNlIGludGVyYWN0aXZlIHNlcmlhbCBwbG90cy4NCg0KDQpgYGB7cn0NCiMgVGhpcyBwbG90IHVzZXMgdGhlIHBsb3QgZnVuY3Rpb246IGhjY2hhcmgoKSBhbmQgaGNhZXMoKSBpbiB0aGUgbGlicmFyeSBgaGljaGFydGVyYA0KaGMgPC1zdG9jayAlPiUNCiAgIGhjaGFydCgNCiAgICAibGluZSIsIA0KICAgIGhjYWVzKHggPSBEYXRlLCB5ID0gQUFQTC5IaWdoKQ0KICApDQpoYw0KYGBgDQoNCg0KVGhlIGZvbGxvd2luZyBpbnRlcmFjdGl2ZSBzZXJpYWwgcGxvdCBhbHNvIGluY2x1ZGVkIGZvcmVjYXN0ZWQgdmFsdWVzIGFuZCB0aGUgZm9yY2FzdGluZyBjb25maWRlbmNlIGJhbmQuDQoNCmBgYHtyfQ0KYXBwbC5oaWdoID0gc3RvY2skQUFQTC5IaWdoDQojIG49IGxlbmd0aChhcHBsLmhpZ2gpDQojIHBsb3QoMTpuLCBhcHBsLmhpZ2gsIHR5cGUgPSAnbCcpDQp4IDwtIGZvcmVjYXN0KGV0cyhhcHBsLmhpZ2gpLCBoID0gNDgpDQpoYyA8LSBoY2hhcnQoeCkNCmhjDQpgYGANCg0KDQoNCg0KIyMgUGxvdGx5IE1hcHMNCg0KU2V2ZXJhbCBtYXAgbGlicmFyaWVzIGFyZSBhdmFpbGFibGUgaW4gUi4gSW4gdGhpcyBleGFtcGxlLCB3ZSB1c2UgdGhlIGBwbG90X2dlbygpYCBmdW5jdGlvbiBmcm9tIGBwbG90bHlgIHRvIHBsb3Qgb24gYSBtYXAuDQoNCg0KYGBge3J9DQojIyBwcmVwYXJpbmcgZGF0YQ0KcG9jIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcGVuZ2RzY2kvc3RhNTUzLmh0bWwvbWFpbi9kYXRhL1BPQy5jc3YiKVssYyg3LDgsOSwgMTcpXQ0KcG9jLnNpdGUgPC0gcG9jW3BvYyRQT0MgPT0gMSxdDQojIGdlbyBzdHlsaW5nDQpnZW9zdHlsZSA8LSBsaXN0KHNjb3BlID0gJ3VzYScsDQogICAgICAgICAgICAgICAgIHByb2plY3Rpb24gPSBsaXN0KHR5cGUgPSAnYWxiZXJzIHVzYScpLA0KICAgICAgICAgICAgICAgICBzaG93bGFuZCA9IFRSVUUsDQogICAgICAgICAgICAgICAgIGxhbmRjb2xvciA9IHRvUkdCKCJsaWdodGJsdWUiKSwNCiAgICAgICAgICAgICAgICAgc3VidW5pdGNvbG9yID0gdG9SR0IoInB1cnBsZSIpLA0KICAgICAgICAgICAgICAgICBjb3VudHJ5Y29sb3IgPSB0b1JHQigibmF2eSIpLA0KICAgICAgICAgICAgICAgICBjb3VudHJ5d2lkdGggPSAwLjc1LA0KICAgICAgICAgICAgICAgICBzdWJ1bml0d2lkdGggPSAwLjUNCiAgICAgICAgICAgICAgICkNCiMjIHBsb3R0aW5nIG1hcA0KZmlnIDwtIHBsb3RfZ2VvKHBvYy5zaXRlLCBsYXQgPSB+eWNvb3JkLCBsb24gPSB+eGNvb3JkKSAlPiUNCiAgICAgICBhZGRfbWFya2Vycyh0ZXh0ID0gfiBTSVRFX0RFU0NSSVBUSU9OLCANCiAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCANCiAgICAgICAgICAgICAgICAgICBzeW1ib2wgPSAiY2lyY2xlIiwgDQogICAgICAgICAgICAgICAgICAgc2l6ZSA9IEkoMTApLCANCiAgICAgICAgICAgICAgICAgICBob3ZlcmluZm8gPSAidGV4dCIgKSAgICU+JQ0KICAgICAgICBsYXlvdXQoIHRpdGxlID0gJ1BPQyBSaXNrIFNpdGVzJywgZ2VvID0gZ2Vvc3R5bGUpDQpmaWcNCmBgYA0KDQoNCiMjIENvbmNsdXNpb24NCg0KVGhpcyBub3RlIGZvY3VzZXMgb24gdXNpbmcgYHBsb3RseWAgbGlicmFyeSBhbmQgaXRzIGRlcGVuZGVuY2llcyB0byBjcmVhdGUgdmFyaW91cyBpbnRlcmFjdGl2ZSBwbG90cy4gSG93ZXZlciwgYHBsb3RseWAgaXMgb25seSBvbmUgc3VjaCBsaWJyYXJ5IHRoYXQgY2FuIHByb2R1Y2UgaW50ZXJhY3RpdmUgZ3JhcGhpY3MuIFRoZXJlIGFyZSBzZXZlcmFsIG90aGVyIGNvbW1vbmx5IHVzZWQgbGlicmFyaWVzIHdpdGggZGlmZmVyZW50IHN0cmVuZ3Rocy4gSGVyZSBhcmUgYSBmZXcgb2YgdGhlbQ0KDQoNCioqRGF0YSBpbnRlZ3JhdGlvbioqLiBDb2xsZWN0IHJhdyBkYXRhIGFuZCB0dXJuIGl0IGludG8gY2xlYW4sIGFuYWx5dGljcy1yZWFkeSBpbmZvcm1hdGlvbiBieSBwZXJmb3JtaW5nIGRhdGEgcmVwbGljYXRpb24sIGluZ2VzdGlvbiwgYW5kIHRyYW5zZm9ybWF0aW9uLiBUaGVuIHN0b3JlIGl0IGluIGEgZGF0YSBsYWtlIG9yIGRhdGEgd2FyZWhvdXNlLg0KDQoqKkdvYWwgZGVmaW5pdGlvbioqLiBEZWZpbmUgdGhlIGJ1c2luZXNzIG9iamVjdGl2ZSB5b3XigJlyZSB0cnlpbmcgdG8gYWNoaWV2ZSBhbmQgdGhlIGRhdGEgaW5zaWdodHMgeW91IHNlZWsuIEZvciBleGFtcGxlLCBhcmUgeW91IHRyeWluZyB0byBvcHRpbWl6ZSBhIHByb2R1Y3Rpb24gcHJvY2VzcyBvciB0cmFjayB0aGUgUk9JIG9mIHlvdXIgbWFya2V0aW5nIGVmZm9ydHM/DQoNCioqVmlzdWFsaXphdGlvbiBkZXNpZ24qKi4gRGVzaWduIGJlZ2lucyB3aXRoIHNlbGVjdGluZyBLUElzIGFuZCB0eXBlcyBvZiBncmFwaHMsIGNoYXJ0cywgYW5kIG1hcHMgdGhhdCBiZXN0IHRlbGwgeW91ciBzdG9yeS4gS2VlcGluZyB5b3VyIHZpc3VhbGl6YXRpb25zIGNsZWFuIGFuZCBzaW1wbGUgd2lsbCBoZWxwIHVzZXJzIHVuZGVyc3RhbmQgYW5kIHdvcmsgd2l0aCB0aGUgZGF0YS4NCg0KKipDb2xsYWJvcmF0aW9uIGFuZCBzaGFyaW5nKiouIEFsbG93IGFsbCBhcHByb3ZlZCB1c2VycyB0byBleHBsb3JlIHRoZSBkYXRhIGZyZWVseSB0byB1bmNvdmVyIHRoZWlyIG93biBpbnNpZ2h0cy4gWW91ciBzb2Z0d2FyZSBzaG91bGQgYWxsb3cgdXNlcnMgdG8gZW1iZWQgeW91ciB2aXN1YWxpemF0aW9ucyBpbiBvdGhlciBhcHBsaWNhdGlvbnMgYW5kIHRvIGVuZ2FnZSB3aXRoIHRoZW0gb24gdGhlaXIgbW9iaWxlIGRldmljZXMuDQo=