Stylin’ your markdowns

Styling a Markdown document

Check out Yan Holtz’s “Pimp my RMD: a few tips for R Markdown” for some good examples.

Picking themes

Themes are used to style your R markdown. These are HTML and CSS styles that allow you to present your data in different ways. To use them, you’ll install the theme package the way you install any other, then set options in the YAML. I haven’t tried anything other than the HTML output – there are other things you’ll have to install on your computer if you want to try some of the others, such as PDF output.

Here’s the YAML for this document:

---
title: "Stylin' your markdowns"
date: "2021-11-10"
output:
  rmdformats::readthedown:
    self_contained: true
    code_download: true
    toc_depth: 4
    df_print: paged
    code_folding: hide
---

Theme options

There are a lot of options, coming from theme packages. In general:

  • A regular R Markdown document can use most of the free Bootswatch themes.
  • The package prettydocs re-creates themes that are default on Github. They’re very fast to knit, but not very visually appealling.
  • The package rmdformats has several well designed themes, including this one, but they have fewer options in terms of tables of contents, colors, etc.
  • The package tufte re-creates Edward Tufte’s very distinctive handout and book style - it takes forever to knit, but creates a beautiful PDF book.
  • The package bookdown creates book-style websites, like the one at our R Study Guide; blogdown creates full websites.

This setup chunk has several options you probably want to change from the defaults. Normally you’d hide all of this from your audience by changing the first line to the default after the three ticks:

      {r setup, include=FALSE}

Different themes have different options for your document. For example, the standard themes have an option called code folding which lets you put a “hide” and “show” button on each code chunk you use.

You might choose to use different themes depending on the audience. Usually , the theme will just ignore any options it doesn’t have. (It sometimes throws up an error, like trying to do toc_float: true on a rmdformats theme.)

After installation

Once you’ve installed a theme, it will be available in your New File menu as a template:

There is one thing that’s really confusing in this: some of these templates require you to create a folder to hold all of the related files. However, if you keep the “self-contained” selection in the YAML, it’s not required. I usually have to move these files back to my default directory, or the paths don’t work.


The setup chunk

When you use a theme, there will be a default set of setup options. Here’s how I usually change mine:

To get some good tips on how to deal with markdown chunks, the YAML and errors, look at Piping Hot Data’s gifs on “Chunk options” and “YAML errors”
# You need these first to set up some of these options. 
library(knitr)
library(rmdformats)

## Global options
options(max.print="75")
opts_chunk$set(echo=TRUE,    # change to TRUE if you want to see the code chunks 
                 cache=FALSE,    # generally change to FALSE - you don't need cache for simple documents.
               prompt=FALSE,
               tidy=FALSE,    # change to FALSE - It generates an annoying error with out. 
               comment=NA,
               message=FALSE,  
               warning=TRUE)  #consider changing to TRUE to see things you might not notice otherwise.



opts_knit$set(width=75)


# You might set some other defaults here for packages you usually use. 
# 
#
# And add in packages you almost always use: 
# 
library(tidyverse)
library(lubridate)
library(janitor)
library(forcats) # working with factors
library(scales) # turning numbers into something readable

# here are the table packages we're going to use
library(reactable)
library(gt)
library(formattable)
library (DT)

Styling tables

Now that you’re set up, I’ll show you some things you can do with your output of tables that will make it easier to work with. First, I want to make a data frame that is good for this kind of thing: It should be less than about 1,000 rows, and should have a mix of types of data and groups.

Pretty tables

There are two types of display tables:

  • Interactive tables, that allow sorting, searching and even changing data. These are good for sharing with teammates and exploring your data more.

  • Nice looking static tables, which are good for formal results. They look more like a printed government report.

Why formatted numbers matter

As you’ve found, one of the biggest annoyances about R is the inability to “format” numbers in a default way. In Excel, once you told the program that you wanted to see dollar signs and commas, it always showed it to you that way. We have to do this through either formatting a table, or by converting numbers to text (since “$” and “,” aren’t numbers!).

For static tables, it doesn’t matter whether numbers stay as numbers – once they’re in the sort order you want, they can be presented as text fields. But if you want people to be able play around with the result, you need something that lets you format numbers into something readable, but keep the underlying information with the proper type. It’s the difference between what you see and what the computer sees that matters.


Table packages

A full examination of the different table packages is on R for the Rest of Us blog, “How to Make Beautiful Tables in R”. It’s a little out of date, but it gives a good overview of the options.

While I’m at it, I’ve created a little data frame with my understanding of which features each package has, and then styled them using the reactable library.

library(reactable)

table_packages <- tribble (
  ~"pkg", ~"fmt_nums", ~"interactive", ~"nested", ~"sparklines", ~"positioning",
  "DT (datatables)", TRUE, TRUE, FALSE, FALSE, FALSE,
  "formattable", TRUE, FALSE, TRUE, TRUE,  FALSE,
  "gt", TRUE, FALSE, TRUE, TRUE,  TRUE,
  "kablextra", FALSE, FALSE, TRUE, TRUE, TRUE, 
  "reactable", TRUE, TRUE, TRUE, TRUE, TRUE
)

(Press the “Code” button here if you want to see how words were replaced with symbols)

reactable ( table_packages, 
            fullWidth = FALSE, compact=TRUE, width=500,
            defaultColDef= colDef (align="center", maxWidth=75),
            theme=reactableTheme( color="gray", style=list (fontFamily = "Work Sans, sans-serif", fontSize="70%")),
            columns = list ( 
                 pkg = colDef (minWidth=100, align="left", name = "Package name"), 
                 fmt_nums  = colDef (name = "Formatted #s?", 
                                     cell = function (value) {
                                        if (value) "\u2713" else "\u2718"
                                     }), 
                 interactive  = colDef (name = "Interactive?", 
                                     cell = function (value) {
                                        if (value) "\u2713" else "\u2718"
                                     }), 
                  nested   = colDef (name = "Nested tables?", 
                                     cell = function (value) {
                                        if (value) "\u2713" else "\u2718"
                                     }),
                                  fmt_nums  = colDef (name = "Nested?", 
                                     cell = function (value) {
                                        if (value) "\u2713" else "\u2718"
                                     }), 
                  sparklines  = colDef (name = "Sparklines?", 
                                     cell = function (value) {
                                        if (value) "\u2713" else "\u2718"
                                     }), 
                  positioning  = colDef (name = "Positioning / Fixed headers?", 
                                     cell = function (value) {
                                        if (value) "\u2713" else "\u2718"
                                     })
            )                                 
)

None of these packages is good with very large tables. The DT package will error out if it’s too big. Unfortunately the kableextra package will just keep printing – all of the rows – until it runs out of memory. You sometimes have to restart your machine if this happens, so be careful not to give it too much room to kill itself.


Ranking and sorting

We’ll use a piece of the PPP data we’ve been working with an example. In this case, we’ll work on displaying the names of lenders in some reasonable order – the frequency that they show up in Arizona loans.

Summarizing the data

I’ve loaded some PPP loan data from Arizona as an example:

load ( url ("https://github.com/cronkitedata/rstudyguide/blob/master/data/az_ppp_zipcodes.Rda?raw=true"))

Lender ranks

In our final table, we want to smush together all of the small lenders into an “Other” group. But first we have to see what a good cutoff might be. This is called a cumulative disribution graph, which shows what total percent of all loans were done by the top companies. I’ll use the gt package to show you how to make a better looking table at the same time. gt is good for static tables because it’s relatively simple code with powerful nesting and formatting options. But it’s not so great if you want a large table that you can sort and filter.

lender_ranks <- 
  az_ppp_zip %>%
  group_by (lender) %>%
  summarise (lender_num  =  n() ,
             lender_zips = n_distinct (census_zip) , 
             lender_amt = sum (initial_amt)
  ) %>%
  # a new function: min_rank, to provide the lowest number rank to the highest number of loans.
  mutate (lender_rank = min_rank ( desc(lender_num)))

gt table

How big is big?

First, I create a dataset that groups the lenders into chunks – top 50, top 100, top 200, and everything smaller than the top 200 based on the number of loans given in Arizona. This uses two things that you haven’t seen: the function cut, which we’ll use on the rank of the lender. This creates a factor variable that uses numbers internally to sort / arrange but shows you the words you specify in the labels= argument.

lender_sizes <-
  lender_ranks %>%
  # group the lenders into categories by the number of loans they have
  mutate ( lender_group = cut (lender_rank, breaks= c( -Inf, 50, 100, 200, Inf), 
                               labels=c( " Top 50", "51-100", "101-200", "Over 200") )) %>%
  group_by (lender_group) %>%
  # new : the "across" operator does the same thing to a group of variables, in this case summing them.
  summarise  (across ( c( lender_num, lender_amt), sum ), 
              avg_lender_zipcodes = mean( lender_zips)) %>%
  # This is a new function: cumsum, which accumulated the totals as it goes. 
  mutate ( cum_loans = cumsum ( lender_amt / sum(lender_amt))) 

Now, displaying it is done using the gt library, which is relatively easy to understand and works well on small, informative static tables.

# now we get to tell R how to make  a table
lender_sizes %>% 
 gt ( rowname_col = "lender_group") %>%
  tab_header ( title="Percent of loans in Arizona by Lender Size") %>%
  cols_label ( lender_num="# of loans", 
                    lender_amt = "$ loaned \n (in 1000s)", 
                    avg_lender_zipcodes = "Avg. # of zip codes", 
                    cum_loans = "Cum % by group") %>%
  fmt_number (columns = c(2,4) , decimals=0, sep_mark=",") %>%
  fmt_currency (columns = 3, decimals=0, scale_by = 1/1000) %>%
  fmt_percent ( columns=5, decimals=1) %>%
  tab_options ( table.font.size = "80%", 
                table.font.color = "slategray")  
Percent of loans in Arizona by Lender Size
# of loans $ loaned (in 1000s) Avg. # of zip codes Cum % by group
Top 50 97,889 $8,721,633 154 81.0%
51-100 8,736 $1,017,653 70 90.5%
101-200 3,214 $588,440 18 96.0%
Over 200 2,350 $434,013 2 100.0%

This suggests that collapsing all of the lender above the top 100 into an “Other” group won’t lose us much in the way of comparisons. (It could, however, remove some interesting information for small markets like Native American lands or border towns with small populations.)


reactable

The reactable table seems to be the one that a lot of people are using now instead of DT. It can handle somewhat larger tables than datatables, and has more options for calculating subtotals and for styling. Here’s an example printing out some information on zip codes from Arizona:

sticky_style <- list(position = "sticky", left = 0, background = "#fff", zIndex = 1,
                     borderRight = "1px solid #eee")

az_by_zipcode %>%
  select (zcta, zipcode_city,  zcta_ethnic, tot_pop, usps_businesses,  median_inc_2018) %>%
  reactable (
    searchable=TRUE,
    defaultPageSize = 5,
    columns = list (
      zcta = colDef(name="Zip code", style=sticky_style, headerStyle=sticky_style), 
      zipcode_city = colDef(name="City"), 
      zcta_ethnic = colDef (name="Ethnicity"),
      tot_pop = colDef(name="Population", format=colFormat(separators=TRUE, digits=0)), 
      usps_businesses = colDef(name= "# businesses", format=colFormat( separators=TRUE)),
      median_inc_2018 = colDef(name="Median income", format=colFormat(prefix="$", separators=TRUE))
      
    )

)
LS0tCnRpdGxlOiAiU3R5bGluJyB5b3VyIG1hcmtkb3ducyIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgcm1kZm9ybWF0czo6cmVhZHRoZWRvd246CiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdG9jX2RlcHRoOiA0CiAgICBkZl9wcmludDogcGFnZWQKICAgIGNvZGVfZm9sZGluZzogaGlkZQotLS0KCgoKIyMgU3R5bGluZyBhIE1hcmtkb3duIGRvY3VtZW50IHsudGFic2V0fQoKCkNoZWNrIG91dCBZYW4gSG9sdHoncyAiW1BpbXAgbXkgUk1EOiBhIGZldyB0aXBzIGZvciBSIE1hcmtkb3duXShodHRwczovL2hvbHR6eS5naXRodWIuaW8vUGltcC1teS1ybWQvKSIgZm9yIHNvbWUgZ29vZCBleGFtcGxlcy4KCiMjIyBQaWNraW5nIHRoZW1lcwoKVGhlbWVzIGFyZSB1c2VkIHRvIHN0eWxlIHlvdXIgUiBtYXJrZG93bi4gVGhlc2UgYXJlIEhUTUwgYW5kIENTUyBzdHlsZXMgdGhhdCBhbGxvdyB5b3UgdG8gcHJlc2VudCB5b3VyIGRhdGEgaW4gZGlmZmVyZW50IHdheXMuIFRvIHVzZSB0aGVtLCB5b3UnbGwgaW5zdGFsbCB0aGUgdGhlbWUgcGFja2FnZSB0aGUgd2F5IHlvdSBpbnN0YWxsIGFueSBvdGhlciwgdGhlbiBzZXQgb3B0aW9ucyBpbiB0aGUgWUFNTC4gSSBoYXZlbid0IHRyaWVkIGFueXRoaW5nIG90aGVyIHRoYW4gdGhlIEhUTUwgb3V0cHV0IC0tIHRoZXJlIGFyZSBvdGhlciB0aGluZ3MgeW91J2xsIGhhdmUgdG8gaW5zdGFsbCBvbiB5b3VyIGNvbXB1dGVyIGlmIHlvdSB3YW50IHRvIHRyeSBzb21lIG9mIHRoZSBvdGhlcnMsIHN1Y2ggYXMgUERGIG91dHB1dC4gCgoKSGVyZSdzIHRoZSBZQU1MIGZvciB0aGlzIGRvY3VtZW50OiAKCmBgYAotLS0KdGl0bGU6ICJTdHlsaW4nIHlvdXIgbWFya2Rvd25zIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBybWRmb3JtYXRzOjpyZWFkdGhlZG93bjoKICAgIHNlbGZfY29udGFpbmVkOiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2NfZGVwdGg6IDQKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQpgYGAKCioqKgoKIyMjIFRoZW1lIG9wdGlvbnMgCgoKVGhlcmUgYXJlIGEgbG90IG9mIG9wdGlvbnMsIGNvbWluZyBmcm9tIHRoZW1lIHBhY2thZ2VzLiBJbiBnZW5lcmFsOiAKCiogIEEgcmVndWxhciBSIE1hcmtkb3duIGRvY3VtZW50IGNhbiB1c2UgbW9zdCBvZiB0aGUgW2ZyZWUgQm9vdHN3YXRjaCB0aGVtZXNdKGh0dHBzOi8vYm9vdHN3YXRjaC5jb20vKS4gCiogIFRoZSBwYWNrYWdlIGBwcmV0dHlkb2NzYCByZS1jcmVhdGVzIHRoZW1lcyB0aGF0IGFyZSBkZWZhdWx0IG9uIEdpdGh1Yi4gVGhleSdyZSB2ZXJ5IGZhc3QgdG8ga25pdCwgYnV0IG5vdCB2ZXJ5IHZpc3VhbGx5IGFwcGVhbGxpbmcuIAoqICBUaGUgcGFja2FnZSBgcm1kZm9ybWF0c2AgaGFzIHNldmVyYWwgd2VsbCBkZXNpZ25lZCB0aGVtZXMsIGluY2x1ZGluZyB0aGlzIG9uZSwgYnV0IHRoZXkgaGF2ZSBmZXdlciBvcHRpb25zIGluIHRlcm1zIG9mIHRhYmxlcyBvZiBjb250ZW50cywgY29sb3JzLCBldGMuIAoqICBUaGUgcGFja2FnZSBgdHVmdGVgIHJlLWNyZWF0ZXMgRWR3YXJkIFR1ZnRlJ3MgdmVyeSBkaXN0aW5jdGl2ZSBoYW5kb3V0IGFuZCBib29rIHN0eWxlIC0gaXQgdGFrZXMgZm9yZXZlciB0byBrbml0LCBidXQgY3JlYXRlcyBhIGJlYXV0aWZ1bCBQREYgYm9vay4gCiogIFRoZSBwYWNrYWdlIGBib29rZG93bmAgY3JlYXRlcyBib29rLXN0eWxlIHdlYnNpdGVzLCBsaWtlIHRoZSBvbmUgYXQgb3VyIFIgU3R1ZHkgR3VpZGU7IGBibG9nZG93bmAgY3JlYXRlcyBmdWxsIHdlYnNpdGVzLiAKClRoaXMgc2V0dXAgY2h1bmsgaGFzIHNldmVyYWwgb3B0aW9ucyB5b3UgcHJvYmFibHkgd2FudCB0byBjaGFuZ2UgZnJvbSB0aGUgZGVmYXVsdHMuIE5vcm1hbGx5IHlvdSdkIGhpZGUgYWxsIG9mIHRoaXMgZnJvbSB5b3VyIGF1ZGllbmNlIGJ5IGNoYW5naW5nIHRoZSBmaXJzdCBsaW5lIHRvIHRoZSBkZWZhdWx0IGFmdGVyIHRoZSB0aHJlZSB0aWNrczoKCgogICAgICAgICAge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CgoKCgpEaWZmZXJlbnQgdGhlbWVzIGhhdmUgZGlmZmVyZW50IG9wdGlvbnMgZm9yIHlvdXIgZG9jdW1lbnQuIEZvciBleGFtcGxlLCB0aGUgc3RhbmRhcmQgdGhlbWVzIGhhdmUgYW4gb3B0aW9uIGNhbGxlZCBgY29kZSBmb2xkaW5nYCB3aGljaCBsZXRzIHlvdSBwdXQgYSAiaGlkZSIgYW5kICJzaG93IiBidXR0b24gb24gZWFjaCBjb2RlIGNodW5rIHlvdSB1c2UuIAoKWW91IG1pZ2h0IGNob29zZSB0byB1c2UgZGlmZmVyZW50IHRoZW1lcyBkZXBlbmRpbmcgb24gdGhlIGF1ZGllbmNlLiBVc3VhbGx5ICwgdGhlIHRoZW1lIHdpbGwganVzdCBpZ25vcmUgYW55IG9wdGlvbnMgaXQgZG9lc24ndCBoYXZlLiAoSXQgc29tZXRpbWVzIHRocm93cyB1cCBhbiBlcnJvciwgbGlrZSB0cnlpbmcgdG8gZG8gYHRvY19mbG9hdDogdHJ1ZWAgb24gYSBybWRmb3JtYXRzIHRoZW1lLikKCiMjIyMgQWZ0ZXIgaW5zdGFsbGF0aW9uCgpPbmNlIHlvdSd2ZSBpbnN0YWxsZWQgYSB0aGVtZSwgaXQgd2lsbCBiZSBhdmFpbGFibGUgaW4geW91ciBOZXcgRmlsZSBtZW51IGFzIGEgdGVtcGxhdGU6IAoKIVtdKHRlbXBsYXRlcy5wbmcpClRoZXJlIGlzIG9uZSB0aGluZyB0aGF0J3MgcmVhbGx5IGNvbmZ1c2luZyBpbiB0aGlzOiBzb21lIG9mIHRoZXNlIHRlbXBsYXRlcyByZXF1aXJlIHlvdSB0byBjcmVhdGUgYSBmb2xkZXIgdG8gaG9sZCBhbGwgb2YgdGhlIHJlbGF0ZWQgZmlsZXMuIEhvd2V2ZXIsIGlmIHlvdSBrZWVwIHRoZSAgInNlbGYtY29udGFpbmVkIiBzZWxlY3Rpb24gaW4gdGhlIFlBTUwsIGl0J3Mgbm90IHJlcXVpcmVkLiBJIHVzdWFsbHkgaGF2ZSB0byBtb3ZlIHRoZXNlIGZpbGVzIGJhY2sgdG8gbXkgZGVmYXVsdCBkaXJlY3RvcnksIG9yIHRoZSBwYXRocyBkb24ndCB3b3JrLiAKCioqKgoKIyMjIFRoZSBzZXR1cCBjaHVuawoKCgpXaGVuIHlvdSB1c2UgYSB0aGVtZSwgdGhlcmUgd2lsbCBiZSBhIGRlZmF1bHQgc2V0IG9mIHNldHVwIG9wdGlvbnMuIEhlcmUncyBob3cgSSB1c3VhbGx5IGNoYW5nZSBtaW5lOgoKCjxkaXYgc3R5bGU9ImNvbG9yOm1hcm9vbjtmb250LXdlaWdodDpib2xkIj4KVG8gZ2V0IHNvbWUgZ29vZCB0aXBzIG9uIGhvdyB0byBkZWFsIHdpdGggbWFya2Rvd24gY2h1bmtzLCB0aGUgWUFNTCBhbmQgZXJyb3JzLCBsb29rIGF0ICBbUGlwaW5nIEhvdCBEYXRhJ3MgZ2lmc10oaHR0cHM6Ly93d3cucGlwaW5naG90ZGF0YS5jb20vcG9zdHMvMjAyMC0wOS0wNy1pbnRyb2R1Y2luZy10aGUtcnN0dWRpby1pZGUtYW5kLXItbWFya2Rvd24vKSBvbiAiQ2h1bmsgb3B0aW9ucyIgYW5kICJZQU1MIGVycm9ycyIgPC9kaXY+CgoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPVRSVUUsIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQoKCiMgWW91IG5lZWQgdGhlc2UgZmlyc3QgdG8gc2V0IHVwIHNvbWUgb2YgdGhlc2Ugb3B0aW9ucy4gCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkocm1kZm9ybWF0cykKCiMjIEdsb2JhbCBvcHRpb25zCm9wdGlvbnMobWF4LnByaW50PSI3NSIpCm9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwgICAgIyBjaGFuZ2UgdG8gVFJVRSBpZiB5b3Ugd2FudCB0byBzZWUgdGhlIGNvZGUgY2h1bmtzIAoJICAgICAgICAgICAgIGNhY2hlPUZBTFNFLCAgICAjIGdlbmVyYWxseSBjaGFuZ2UgdG8gRkFMU0UgLSB5b3UgZG9uJ3QgbmVlZCBjYWNoZSBmb3Igc2ltcGxlIGRvY3VtZW50cy4KICAgICAgICAgICAgICAgcHJvbXB0PUZBTFNFLAogICAgICAgICAgICAgICB0aWR5PUZBTFNFLCAgICAjIGNoYW5nZSB0byBGQUxTRSAtIEl0IGdlbmVyYXRlcyBhbiBhbm5veWluZyBlcnJvciB3aXRoIG91dC4gCiAgICAgICAgICAgICAgIGNvbW1lbnQ9TkEsCiAgICAgICAgICAgICAgIG1lc3NhZ2U9RkFMU0UsICAKICAgICAgICAgICAgICAgd2FybmluZz1UUlVFKSAgI2NvbnNpZGVyIGNoYW5naW5nIHRvIFRSVUUgdG8gc2VlIHRoaW5ncyB5b3UgbWlnaHQgbm90IG5vdGljZSBvdGhlcndpc2UuCgoKCm9wdHNfa25pdCRzZXQod2lkdGg9NzUpCgoKIyBZb3UgbWlnaHQgc2V0IHNvbWUgb3RoZXIgZGVmYXVsdHMgaGVyZSBmb3IgcGFja2FnZXMgeW91IHVzdWFsbHkgdXNlLiAKIyAKIwojIEFuZCBhZGQgaW4gcGFja2FnZXMgeW91IGFsbW9zdCBhbHdheXMgdXNlOiAKIyAKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KGphbml0b3IpCmxpYnJhcnkoZm9yY2F0cykgIyB3b3JraW5nIHdpdGggZmFjdG9ycwpsaWJyYXJ5KHNjYWxlcykgIyB0dXJuaW5nIG51bWJlcnMgaW50byBzb21ldGhpbmcgcmVhZGFibGUKCiMgaGVyZSBhcmUgdGhlIHRhYmxlIHBhY2thZ2VzIHdlJ3JlIGdvaW5nIHRvIHVzZQpsaWJyYXJ5KHJlYWN0YWJsZSkKbGlicmFyeShndCkKbGlicmFyeShmb3JtYXR0YWJsZSkKbGlicmFyeSAoRFQpCgpgYGAKCgoqKioKCiMjIFN0eWxpbmcgdGFibGVzIHsudGFic2V0fQoKTm93IHRoYXQgeW91J3JlIHNldCB1cCwgSSdsbCBzaG93IHlvdSBzb21lIHRoaW5ncyB5b3UgY2FuIGRvIHdpdGggeW91ciBvdXRwdXQgb2YgdGFibGVzIHRoYXQgd2lsbCBtYWtlIGl0IGVhc2llciB0byB3b3JrIHdpdGguIEZpcnN0LCBJIHdhbnQgdG8gbWFrZSBhIGRhdGEgZnJhbWUgdGhhdCBpcyBnb29kIGZvciB0aGlzIGtpbmQgb2YgdGhpbmc6IEl0IHNob3VsZCBiZSBsZXNzIHRoYW4gYWJvdXQgMSwwMDAgcm93cywgYW5kIHNob3VsZCBoYXZlIGEgbWl4IG9mIHR5cGVzIG9mIGRhdGEgYW5kIGdyb3Vwcy4gCgoKIyMjIFByZXR0eSB0YWJsZXMKClRoZXJlIGFyZSB0d28gdHlwZXMgb2YgZGlzcGxheSB0YWJsZXM6CgoqIEludGVyYWN0aXZlIHRhYmxlcywgdGhhdCBhbGxvdyBzb3J0aW5nLCBzZWFyY2hpbmcgYW5kIGV2ZW4gY2hhbmdpbmcgZGF0YS4gVGhlc2UgYXJlIGdvb2QgZm9yIHNoYXJpbmcgd2l0aCB0ZWFtbWF0ZXMgYW5kIGV4cGxvcmluZyB5b3VyIGRhdGEgbW9yZS4gCgoqIE5pY2UgbG9va2luZyBzdGF0aWMgdGFibGVzLCB3aGljaCBhcmUgZ29vZCBmb3IgZm9ybWFsIHJlc3VsdHMuIFRoZXkgbG9vayBtb3JlIGxpa2UgYSBwcmludGVkIGdvdmVybm1lbnQgcmVwb3J0LiAKIAogCgojIyMjIFdoeSBmb3JtYXR0ZWQgbnVtYmVycyBtYXR0ZXIKCkFzIHlvdSd2ZSBmb3VuZCwgb25lIG9mIHRoZSBiaWdnZXN0IGFubm95YW5jZXMgYWJvdXQgUiBpcyB0aGUgaW5hYmlsaXR5IHRvICJmb3JtYXQiIG51bWJlcnMgaW4gYSBkZWZhdWx0IHdheS4gSW4gRXhjZWwsIG9uY2UgeW91IHRvbGQgdGhlIHByb2dyYW0gdGhhdCB5b3Ugd2FudGVkIHRvIHNlZSBkb2xsYXIgc2lnbnMgYW5kIGNvbW1hcywgaXQgYWx3YXlzIHNob3dlZCBpdCB0byB5b3UgdGhhdCB3YXkuIFdlIGhhdmUgdG8gZG8gdGhpcyB0aHJvdWdoIGVpdGhlciBmb3JtYXR0aW5nIGEgdGFibGUsIG9yIGJ5IGNvbnZlcnRpbmcgbnVtYmVycyB0byB0ZXh0IChzaW5jZSAiJCIgYW5kICIsIiBhcmVuJ3QgbnVtYmVycyEpLgoKRm9yIHN0YXRpYyB0YWJsZXMsIGl0IGRvZXNuJ3QgbWF0dGVyIHdoZXRoZXIgbnVtYmVycyBzdGF5IGFzIG51bWJlcnMgLS0gb25jZSB0aGV5J3JlIGluIHRoZSBzb3J0IG9yZGVyIHlvdSB3YW50LCB0aGV5IGNhbiBiZSBwcmVzZW50ZWQgYXMgdGV4dCBmaWVsZHMuIEJ1dCBpZiB5b3Ugd2FudCBwZW9wbGUgdG8gYmUgYWJsZSBwbGF5IGFyb3VuZCB3aXRoIHRoZSByZXN1bHQsIHlvdSBuZWVkIHNvbWV0aGluZyB0aGF0IGxldHMgeW91IGZvcm1hdCBudW1iZXJzIGludG8gc29tZXRoaW5nIHJlYWRhYmxlLCBidXQga2VlcCB0aGUgdW5kZXJseWluZyBpbmZvcm1hdGlvbiB3aXRoIHRoZSBwcm9wZXIgdHlwZS4gSXQncyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHdoYXQgeW91IHNlZSBhbmQgd2hhdCB0aGUgY29tcHV0ZXIgc2VlcyB0aGF0IG1hdHRlcnMuIAoKKioqCgojIyMgVGFibGUgcGFja2FnZXMKCkEgZnVsbCBleGFtaW5hdGlvbiBvZiB0aGUgZGlmZmVyZW50IHRhYmxlIHBhY2thZ2VzIGlzIG9uIFIgZm9yIHRoZSBSZXN0IG9mIFVzIGJsb2csICJbSG93IHRvIE1ha2UgQmVhdXRpZnVsIFRhYmxlcyBpbiBSXShodHRwczovL3Jmb3J0aGVyZXN0b2Z1cy5jb20vMjAxOS8xMS9ob3ctdG8tbWFrZS1iZWF1dGlmdWwtdGFibGVzLWluLXIvKSIuIEl0J3MgYSBsaXR0bGUgb3V0IG9mIGRhdGUsIGJ1dCBpdCBnaXZlcyBhIGdvb2Qgb3ZlcnZpZXcgb2YgdGhlIG9wdGlvbnMuIAoKV2hpbGUgSSdtIGF0IGl0LCAgSSd2ZSBjcmVhdGVkIGEgbGl0dGxlIGRhdGEgZnJhbWUgd2l0aCBteSB1bmRlcnN0YW5kaW5nIG9mIHdoaWNoIGZlYXR1cmVzIGVhY2ggcGFja2FnZSBoYXMsIGFuZCB0aGVuIHN0eWxlZCB0aGVtIHVzaW5nIHRoZSBgcmVhY3RhYmxlYCBsaWJyYXJ5LiAKCgpgYGB7ciBjcmVhdGVfZGF0YXNldCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcGFnZWQucHJpbnQ9VFJVRX0KbGlicmFyeShyZWFjdGFibGUpCgp0YWJsZV9wYWNrYWdlcyA8LSB0cmliYmxlICgKICB+InBrZyIsIH4iZm10X251bXMiLCB+ImludGVyYWN0aXZlIiwgfiJuZXN0ZWQiLCB+InNwYXJrbGluZXMiLCB+InBvc2l0aW9uaW5nIiwKICAiRFQgKGRhdGF0YWJsZXMpIiwgVFJVRSwgVFJVRSwgRkFMU0UsIEZBTFNFLCBGQUxTRSwKICAiZm9ybWF0dGFibGUiLCBUUlVFLCBGQUxTRSwgVFJVRSwgVFJVRSwgIEZBTFNFLAogICJndCIsIFRSVUUsIEZBTFNFLCBUUlVFLCBUUlVFLCAgVFJVRSwKICAia2FibGV4dHJhIiwgRkFMU0UsIEZBTFNFLCBUUlVFLCBUUlVFLCBUUlVFLCAKICAicmVhY3RhYmxlIiwgVFJVRSwgVFJVRSwgVFJVRSwgVFJVRSwgVFJVRQopCmBgYAoKKFByZXNzIHRoZSAiQ29kZSIgYnV0dG9uIGhlcmUgaWYgeW91IHdhbnQgdG8gc2VlIGhvdyB3b3JkcyB3ZXJlIHJlcGxhY2VkIHdpdGggc3ltYm9scykgIAoKCmBgYHtyIGRpc3BsYXlfdGFibGVfcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PVRSVUV9CgpyZWFjdGFibGUgKCB0YWJsZV9wYWNrYWdlcywgCiAgICAgICAgICAgIGZ1bGxXaWR0aCA9IEZBTFNFLCBjb21wYWN0PVRSVUUsIHdpZHRoPTUwMCwKICAgICAgICAgICAgZGVmYXVsdENvbERlZj0gY29sRGVmIChhbGlnbj0iY2VudGVyIiwgbWF4V2lkdGg9NzUpLAogICAgICAgICAgICB0aGVtZT1yZWFjdGFibGVUaGVtZSggY29sb3I9ImdyYXkiLCBzdHlsZT1saXN0IChmb250RmFtaWx5ID0gIldvcmsgU2Fucywgc2Fucy1zZXJpZiIsIGZvbnRTaXplPSI3MCUiKSksCiAgICAgICAgICAgIGNvbHVtbnMgPSBsaXN0ICggCiAgICAgICAgICAgICAgICAgcGtnID0gY29sRGVmIChtaW5XaWR0aD0xMDAsIGFsaWduPSJsZWZ0IiwgbmFtZSA9ICJQYWNrYWdlIG5hbWUiKSwgCiAgICAgICAgICAgICAgICAgZm10X251bXMgID0gY29sRGVmIChuYW1lID0gIkZvcm1hdHRlZCAjcz8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwgPSBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh2YWx1ZSkgIlx1MjcxMyIgZWxzZSAiXHUyNzE4IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSksIAogICAgICAgICAgICAgICAgIGludGVyYWN0aXZlICA9IGNvbERlZiAobmFtZSA9ICJJbnRlcmFjdGl2ZT8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwgPSBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh2YWx1ZSkgIlx1MjcxMyIgZWxzZSAiXHUyNzE4IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSksIAogICAgICAgICAgICAgICAgICBuZXN0ZWQgICA9IGNvbERlZiAobmFtZSA9ICJOZXN0ZWQgdGFibGVzPyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbCA9IGZ1bmN0aW9uICh2YWx1ZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHZhbHVlKSAiXHUyNzEzIiBlbHNlICJcdTI3MTgiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZtdF9udW1zICA9IGNvbERlZiAobmFtZSA9ICJOZXN0ZWQ/IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxsID0gZnVuY3Rpb24gKHZhbHVlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAodmFsdWUpICJcdTI3MTMiIGVsc2UgIlx1MjcxOCIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pLCAKICAgICAgICAgICAgICAgICAgc3BhcmtsaW5lcyAgPSBjb2xEZWYgKG5hbWUgPSAiU3BhcmtsaW5lcz8iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbGwgPSBmdW5jdGlvbiAodmFsdWUpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh2YWx1ZSkgIlx1MjcxMyIgZWxzZSAiXHUyNzE4IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSksIAogICAgICAgICAgICAgICAgICBwb3NpdGlvbmluZyAgPSBjb2xEZWYgKG5hbWUgPSAiUG9zaXRpb25pbmcgLyBGaXhlZCBoZWFkZXJzPyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2VsbCA9IGZ1bmN0aW9uICh2YWx1ZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHZhbHVlKSAiXHUyNzEzIiBlbHNlICJcdTI3MTgiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KQogICAgICAgICAgICApICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCikKCgpgYGAKCgo8cD48L3A+CgpOb25lIG9mIHRoZXNlIHBhY2thZ2VzIGlzIGdvb2Qgd2l0aCB2ZXJ5IGxhcmdlIHRhYmxlcy4gVGhlIERUIHBhY2thZ2Ugd2lsbCBlcnJvciBvdXQgaWYgaXQncyB0b28gYmlnLiBVbmZvcnR1bmF0ZWx5IHRoZSBrYWJsZWV4dHJhIHBhY2thZ2Ugd2lsbCBqdXN0IGtlZXAgcHJpbnRpbmcgLS0gYWxsIG9mIHRoZSByb3dzIC0tIHVudGlsIGl0IHJ1bnMgb3V0IG9mIG1lbW9yeS4gWW91IHNvbWV0aW1lcyBoYXZlIHRvIHJlc3RhcnQgeW91ciBtYWNoaW5lIGlmIHRoaXMgaGFwcGVucywgc28gYmUgY2FyZWZ1bCBub3QgdG8gZ2l2ZSBpdCB0b28gbXVjaCByb29tIHRvIGtpbGwgaXRzZWxmLiAKCioqKiAKCiMjIFJhbmtpbmcgYW5kIHNvcnRpbmcgIHsudGFic2V0fQoKV2UnbGwgdXNlIGEgcGllY2Ugb2YgdGhlIFBQUCBkYXRhIHdlJ3ZlIGJlZW4gd29ya2luZyB3aXRoIGFuIGV4YW1wbGUuICBJbiB0aGlzIGNhc2UsIHdlJ2xsIHdvcmsgb24gZGlzcGxheWluZyB0aGUgbmFtZXMgb2YgbGVuZGVycyBpbiBzb21lIHJlYXNvbmFibGUgb3JkZXIgLS0gdGhlIGZyZXF1ZW5jeSB0aGF0IHRoZXkgc2hvdyB1cCBpbiBBcml6b25hIGxvYW5zLiAKCgojIyMgU3VtbWFyaXppbmcgdGhlIGRhdGEKCkkndmUgbG9hZGVkIHNvbWUgUFBQIGxvYW4gZGF0YSBmcm9tIEFyaXpvbmEgYXMgYW4gZXhhbXBsZTogCgpgYGB7cn0KCgpsb2FkICggdXJsICgiaHR0cHM6Ly9naXRodWIuY29tL2Nyb25raXRlZGF0YS9yc3R1ZHlndWlkZS9ibG9iL21hc3Rlci9kYXRhL2F6X3BwcF96aXBjb2Rlcy5SZGE/cmF3PXRydWUiKSkKCmBgYAoKCiMjIyMgTGVuZGVyIHJhbmtzCgpJbiBvdXIgZmluYWwgdGFibGUsIHdlIHdhbnQgdG8gc211c2ggdG9nZXRoZXIgYWxsIG9mIHRoZSBzbWFsbCBsZW5kZXJzIGludG8gYW4gIk90aGVyIiBncm91cC4gQnV0IGZpcnN0IHdlIGhhdmUgdG8gc2VlIHdoYXQgYSBnb29kIGN1dG9mZiBtaWdodCBiZS4gVGhpcyBpcyBjYWxsZWQgYSBjdW11bGF0aXZlIGRpc3JpYnV0aW9uIGdyYXBoLCB3aGljaCBzaG93cyB3aGF0IHRvdGFsIHBlcmNlbnQgb2YgYWxsIGxvYW5zIHdlcmUgZG9uZSBieSB0aGUgdG9wICBjb21wYW5pZXMuIEknbGwgdXNlIHRoZSBgZ3RgIHBhY2thZ2UgdG8gc2hvdyB5b3UgaG93IHRvIG1ha2UgYSBiZXR0ZXIgbG9va2luZyB0YWJsZSBhdCB0aGUgc2FtZSB0aW1lLiBgZ3RgIGlzIGdvb2QgZm9yIHN0YXRpYyB0YWJsZXMgYmVjYXVzZSBpdCdzIHJlbGF0aXZlbHkgc2ltcGxlIGNvZGUgd2l0aCBwb3dlcmZ1bCBuZXN0aW5nIGFuZCBmb3JtYXR0aW5nIG9wdGlvbnMuIEJ1dCBpdCdzIG5vdCBzbyBncmVhdCBpZiB5b3Ugd2FudCBhIGxhcmdlIHRhYmxlIHRoYXQgeW91IGNhbiBzb3J0IGFuZCBmaWx0ZXIuIAoKCmBgYHtyIHNlbGVjdF96aXBzX2RhdGEsICBjbGFzcy5zb3VyY2U9ImZvbGQtc2hvdyJ9CgoKbGVuZGVyX3JhbmtzIDwtIAogIGF6X3BwcF96aXAgJT4lCiAgZ3JvdXBfYnkgKGxlbmRlcikgJT4lCiAgc3VtbWFyaXNlIChsZW5kZXJfbnVtICA9ICBuKCkgLAogICAgICAgICAgICAgbGVuZGVyX3ppcHMgPSBuX2Rpc3RpbmN0IChjZW5zdXNfemlwKSAsIAogICAgICAgICAgICAgbGVuZGVyX2FtdCA9IHN1bSAoaW5pdGlhbF9hbXQpCiAgKSAlPiUKICAjIGEgbmV3IGZ1bmN0aW9uOiBtaW5fcmFuaywgdG8gcHJvdmlkZSB0aGUgbG93ZXN0IG51bWJlciByYW5rIHRvIHRoZSBoaWdoZXN0IG51bWJlciBvZiBsb2Fucy4KICBtdXRhdGUgKGxlbmRlcl9yYW5rID0gbWluX3JhbmsgKCBkZXNjKGxlbmRlcl9udW0pKSkKYGBgCgoqKioKCiMjIyBndCB0YWJsZSAKCgojIyMjIEhvdyBiaWcgaXMgYmlnPwoKRmlyc3QsIEkgY3JlYXRlIGEgZGF0YXNldCB0aGF0IGdyb3VwcyB0aGUgbGVuZGVycyBpbnRvIGNodW5rcyAtLSB0b3AgNTAsIHRvcCAxMDAsIHRvcCAyMDAsIGFuZCBldmVyeXRoaW5nIHNtYWxsZXIgdGhhbiB0aGUgdG9wIDIwMCBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIGxvYW5zIGdpdmVuIGluIEFyaXpvbmEuIFRoaXMgdXNlcyB0d28gdGhpbmdzIHRoYXQgeW91IGhhdmVuJ3Qgc2VlbjogdGhlIGZ1bmN0aW9uIGBjdXRgLCB3aGljaCB3ZSdsbCB1c2Ugb24gdGhlIHJhbmsgb2YgdGhlIGxlbmRlci4gVGhpcyBjcmVhdGVzIGEgZmFjdG9yIHZhcmlhYmxlIHRoYXQgdXNlcyBudW1iZXJzIGludGVybmFsbHkgdG8gc29ydCAvIGFycmFuZ2UgYnV0IHNob3dzIHlvdSB0aGUgd29yZHMgeW91IHNwZWNpZnkgaW4gdGhlIGBsYWJlbHM9YCBhcmd1bWVudC4gCgpgYGB7ciBjdXRyYW5rcywgY2xhc3Muc291cmNlPSJmb2xkLXNob3cifQpsZW5kZXJfc2l6ZXMgPC0KICBsZW5kZXJfcmFua3MgJT4lCiAgIyBncm91cCB0aGUgbGVuZGVycyBpbnRvIGNhdGVnb3JpZXMgYnkgdGhlIG51bWJlciBvZiBsb2FucyB0aGV5IGhhdmUKICBtdXRhdGUgKCBsZW5kZXJfZ3JvdXAgPSBjdXQgKGxlbmRlcl9yYW5rLCBicmVha3M9IGMoIC1JbmYsIDUwLCAxMDAsIDIwMCwgSW5mKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YyggIiBUb3AgNTAiLCAiNTEtMTAwIiwgIjEwMS0yMDAiLCAiT3ZlciAyMDAiKSApKSAlPiUKICBncm91cF9ieSAobGVuZGVyX2dyb3VwKSAlPiUKICAjIG5ldyA6IHRoZSAiYWNyb3NzIiBvcGVyYXRvciBkb2VzIHRoZSBzYW1lIHRoaW5nIHRvIGEgZ3JvdXAgb2YgdmFyaWFibGVzLCBpbiB0aGlzIGNhc2Ugc3VtbWluZyB0aGVtLgogIHN1bW1hcmlzZSAgKGFjcm9zcyAoIGMoIGxlbmRlcl9udW0sIGxlbmRlcl9hbXQpLCBzdW0gKSwgCiAgICAgICAgICAgICAgYXZnX2xlbmRlcl96aXBjb2RlcyA9IG1lYW4oIGxlbmRlcl96aXBzKSkgJT4lCiAgIyBUaGlzIGlzIGEgbmV3IGZ1bmN0aW9uOiBjdW1zdW0sIHdoaWNoIGFjY3VtdWxhdGVkIHRoZSB0b3RhbHMgYXMgaXQgZ29lcy4gCiAgbXV0YXRlICggY3VtX2xvYW5zID0gY3Vtc3VtICggbGVuZGVyX2FtdCAvIHN1bShsZW5kZXJfYW10KSkpIApgYGAKCk5vdywgZGlzcGxheWluZyBpdCBpcyBkb25lIHVzaW5nIHRoZSBgZ3RgIGxpYnJhcnksIHdoaWNoIGlzIHJlbGF0aXZlbHkgZWFzeSB0byB1bmRlcnN0YW5kIGFuZCB3b3JrcyB3ZWxsIG9uIHNtYWxsLCBpbmZvcm1hdGl2ZSBzdGF0aWMgdGFibGVzLiAKCmBgYHtyIHNob3dyYW5rc30KIyBub3cgd2UgZ2V0IHRvIHRlbGwgUiBob3cgdG8gbWFrZSAgYSB0YWJsZQpsZW5kZXJfc2l6ZXMgJT4lIAogZ3QgKCByb3duYW1lX2NvbCA9ICJsZW5kZXJfZ3JvdXAiKSAlPiUKICB0YWJfaGVhZGVyICggdGl0bGU9IlBlcmNlbnQgb2YgbG9hbnMgaW4gQXJpem9uYSBieSBMZW5kZXIgU2l6ZSIpICU+JQogIGNvbHNfbGFiZWwgKCBsZW5kZXJfbnVtPSIjIG9mIGxvYW5zIiwgCiAgICAgICAgICAgICAgICAgICAgbGVuZGVyX2FtdCA9ICIkIGxvYW5lZCBcbiAoaW4gMTAwMHMpIiwgCiAgICAgICAgICAgICAgICAgICAgYXZnX2xlbmRlcl96aXBjb2RlcyA9ICJBdmcuICMgb2YgemlwIGNvZGVzIiwgCiAgICAgICAgICAgICAgICAgICAgY3VtX2xvYW5zID0gIkN1bSAlIGJ5IGdyb3VwIikgJT4lCiAgZm10X251bWJlciAoY29sdW1ucyA9IGMoMiw0KSAsIGRlY2ltYWxzPTAsIHNlcF9tYXJrPSIsIikgJT4lCiAgZm10X2N1cnJlbmN5IChjb2x1bW5zID0gMywgZGVjaW1hbHM9MCwgc2NhbGVfYnkgPSAxLzEwMDApICU+JQogIGZtdF9wZXJjZW50ICggY29sdW1ucz01LCBkZWNpbWFscz0xKSAlPiUKICB0YWJfb3B0aW9ucyAoIHRhYmxlLmZvbnQuc2l6ZSA9ICI4MCUiLCAKICAgICAgICAgICAgICAgIHRhYmxlLmZvbnQuY29sb3IgPSAic2xhdGVncmF5IikgIAogIAogIApgYGAKClRoaXMgc3VnZ2VzdHMgdGhhdCBjb2xsYXBzaW5nIGFsbCBvZiB0aGUgbGVuZGVyIGFib3ZlIHRoZSB0b3AgMTAwIGludG8gYW4gIk90aGVyIiBncm91cCB3b24ndCBsb3NlIHVzIG11Y2ggaW4gdGhlIHdheSBvZiBjb21wYXJpc29ucy4gKEl0IGNvdWxkLCBob3dldmVyLCByZW1vdmUgc29tZSBpbnRlcmVzdGluZyBpbmZvcm1hdGlvbiBmb3Igc21hbGwgbWFya2V0cyBsaWtlIE5hdGl2ZSBBbWVyaWNhbiBsYW5kcyBvciBib3JkZXIgdG93bnMgd2l0aCBzbWFsbCBwb3B1bGF0aW9ucy4pCgoqKioKCiMjIyByZWFjdGFibGUKClRoZSBgcmVhY3RhYmxlYCB0YWJsZSBzZWVtcyB0byBiZSB0aGUgb25lIHRoYXQgYSBsb3Qgb2YgcGVvcGxlIGFyZSB1c2luZyBub3cgaW5zdGVhZCBvZiBEVC4gSXQgY2FuIGhhbmRsZSBzb21ld2hhdCBsYXJnZXIgdGFibGVzIHRoYW4gZGF0YXRhYmxlcywgYW5kIGhhcyBtb3JlIG9wdGlvbnMgZm9yIGNhbGN1bGF0aW5nIHN1YnRvdGFscyBhbmQgZm9yIHN0eWxpbmcuIEhlcmUncyBhbiBleGFtcGxlIHByaW50aW5nIG91dCBzb21lIGluZm9ybWF0aW9uIG9uIHppcCBjb2RlcyBmcm9tIEFyaXpvbmE6CgoKYGBge3J9CnN0aWNreV9zdHlsZSA8LSBsaXN0KHBvc2l0aW9uID0gInN0aWNreSIsIGxlZnQgPSAwLCBiYWNrZ3JvdW5kID0gIiNmZmYiLCB6SW5kZXggPSAxLAogICAgICAgICAgICAgICAgICAgICBib3JkZXJSaWdodCA9ICIxcHggc29saWQgI2VlZSIpCgphel9ieV96aXBjb2RlICU+JQogIHNlbGVjdCAoemN0YSwgemlwY29kZV9jaXR5LCAgemN0YV9ldGhuaWMsIHRvdF9wb3AsIHVzcHNfYnVzaW5lc3NlcywgIG1lZGlhbl9pbmNfMjAxOCkgJT4lCiAgcmVhY3RhYmxlICgKICAgIHNlYXJjaGFibGU9VFJVRSwKICAgIGRlZmF1bHRQYWdlU2l6ZSA9IDUsCiAgICBjb2x1bW5zID0gbGlzdCAoCiAgICAgIHpjdGEgPSBjb2xEZWYobmFtZT0iWmlwIGNvZGUiLCBzdHlsZT1zdGlja3lfc3R5bGUsIGhlYWRlclN0eWxlPXN0aWNreV9zdHlsZSksIAogICAgICB6aXBjb2RlX2NpdHkgPSBjb2xEZWYobmFtZT0iQ2l0eSIpLCAKICAgICAgemN0YV9ldGhuaWMgPSBjb2xEZWYgKG5hbWU9IkV0aG5pY2l0eSIpLAogICAgICB0b3RfcG9wID0gY29sRGVmKG5hbWU9IlBvcHVsYXRpb24iLCBmb3JtYXQ9Y29sRm9ybWF0KHNlcGFyYXRvcnM9VFJVRSwgZGlnaXRzPTApKSwgCiAgICAgIHVzcHNfYnVzaW5lc3NlcyA9IGNvbERlZihuYW1lPSAiIyBidXNpbmVzc2VzIiwgZm9ybWF0PWNvbEZvcm1hdCggc2VwYXJhdG9ycz1UUlVFKSksCiAgICAgIG1lZGlhbl9pbmNfMjAxOCA9IGNvbERlZihuYW1lPSJNZWRpYW4gaW5jb21lIiwgZm9ybWF0PWNvbEZvcm1hdChwcmVmaXg9IiQiLCBzZXBhcmF0b3JzPVRSVUUpKQogICAgICAKICAgICkKCikKCgpgYGAKCg==