Associated Material

Zoom notes: Zoom Notes 06 - Tidying Data

Readings:


The pre-processing of data as a proportion of a data analysis workflow can be quite substantial, but this step is extremely vital - poor data in = poor results out. The main purpose of this module is transforming our data into something worthy of analysis and will cover data cleaning, tidying and ‘reshaping’. This will be followed by how to increase the utility of our data by combining it with other datasets in Module 7 - Combining Data.

Let us first delve into the data cleaning and tidying.


Cleaning/Tidying

The goal of cleaning and tidying our data is to deal with the imperfections that exist with real-world datasets and make them ready for analysis. Because we’re focusing on how to do this inside R we’re going to assume that you are already starting with ‘rectangular’ data - all rows are the same length, and all columns are the same length. Sometimes there is a ‘pre-R’ phase which requires you to make the data rectangular - check out this material from Data Carpentry if this is something you first need to do.

If you have humans as part of the data collecting process it’s only a matter of time before there is a data entry issue that needs to be ‘cleaned’ out.

Some common tasks at this stage include:

  • ensuring that missing data is coded correctly
  • dealing with typos
  • removing unneeded whitespace
  • converting columns to be the correct data type
  • making nice column names

Unless you are in a fortunate position to inherit ‘cleaned’ data these jobs will fall on you to ensure that later on they don’t cause issues when it comes time to do your statistics.

For this section we’re going to make use of the following packages that are part of the Tidyverse: readr for reading in and parsing data, tidyr which has functions for dealing with missing data, and stringr which has functions relating to dealing with character vectors.

All are loaded as part of loading the tidyverse package. You can see under the “Attaching packages” heading exactly which packages are being loaded.

library(tidyverse)
#> ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
#> ✔ dplyr     1.1.2     ✔ readr     2.1.4
#> ✔ forcats   1.0.0     ✔ stringr   1.5.0
#> ✔ ggplot2   3.4.2     ✔ tibble    3.2.1
#> ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
#> ✔ purrr     1.0.1     
#> ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
#> ✖ dplyr::filter() masks stats::filter()
#> ✖ dplyr::lag()    masks stats::lag()
#> ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

An extra package is janitor, which designed for cleaning.

# install.packages("janitor")
library(janitor)
#> 
#> Attaching package: 'janitor'
#> The following objects are masked from 'package:stats':
#> 
#>     chisq.test, fisher.test

The data for this section is derived from untidy-portal-data. xlsx. It is part of the Portal Project Teaching Database https://doi.org/10.6084/m9.figshare.1314459.v10 available on FigShare under a CC-0 license.

The original file does not have the data in a rectangular format so we have organised the data so it can be read into R and made it available to download either by navigating to https://raw.githubusercontent.com/rtis-training/2023-s2-r4ssp/main/data/rodents_untidy.csv and using Save As “rodents_untidy.csv” in you data directory or by using the folowing R command (ensure you match the directory name to your directory set up):

download.file(url = "https://raw.githubusercontent.com/rtis-training/2023-s2-r4ssp/main/data/rodents_untidy.csv", 
      destfile = "data/rodents_untidy.csv")

Until now we’ve been using the read.csv (“read-dot-csv”) function that is part of base R, but readr has equivalent functions, namely read_csv (“read-underscore-csv”) which provides some extra features such as a progress bar, displaying the data types of all the columns as part of the reading, and it will create the special version of a data.frame called a tibble. For more in-depth details about tibbles check out the R for Data Science - Tibbles chapter. The main benefit we’ll utilise is the way it prints to the screen which includes some formatting and inclusion of the column data types under the column names.

# note the use of the "read-underscore-csv"
rodents <- read_csv("data/rodents_untidy.csv")
#> Rows: 41 Columns: 6
#> ── Column specification ────────────────────────────────────────────────────────
#> Delimiter: ","
#> chr (6): Plot location, Date collected, Family, Genus, Species, Weight
#> 
#> ℹ Use `spec()` to retrieve the full column specification for this data.
#> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

The first thing we can look at as part of the data loading is the column names and what the datatype read_csv had a best guess at the type of data the column had.

rodents %>% head()
#> # A tibble: 6 × 6
#>   `Plot location` `Date collected` Family       Genus     Species     Weight
#>   <chr>           <chr>            <chr>        <chr>     <chr>       <chr> 
#> 1 1_slope         01/09/14         Heteromyidae Dipodomys merriami    40    
#> 2 1_slope         01/09/14         Heteromyidae Dipodomys merriami    36    
#> 3 1_slope         01/09/14         Heteromyidae Dipodomys spectabilis 135   
#> 4 1_rocks         01/09/14         Heteromyidae Dipodomys merriami    39    
#> 5 1_grass         01/20/14         Heteromyidae Dipodomys merriami    43    
#> 6 1_rocks         01/20/14         Heteromyidae Dipodomys spectabilis 144

It can be difficult if you have many columns to see them all, so instead you can see exactly what each column was loaded in as using spec.

spec(rodents)
#> cols(
#>   `Plot location` = col_character(),
#>   `Date collected` = col_character(),
#>   Family = col_character(),
#>   Genus = col_character(),
#>   Species = col_character(),
#>   Weight = col_character()
#> )

Cleaning data values

From this list of columns, without knowing much about the data itself, Weight might seen odd that it is a character type, rather than a numeric, so we’ll focus in on this column to start with

rodents$Weight
#>  [1] "40"   "36"   "135"  "39"   "43"   "144"  "51"   "44"   "146"  "-999"
#> [11] "44"   "38"   "-999" "220"  "38"   "48"   "143"  "35"   "43"   "37"  
#> [21] "150"  "45"   "-999" "157"  "-999" "218"  "127"  "52"   "42"   "24"  
#> [31] "23"   "232"  "22"   "?"    "?"    NA     NA     "182"  "42"   "115" 
#> [41] "190"

From this we can see that we have come “?” characters, is what has caused the column to be read in as characters instead of numbers (remember that a vector must be all the same data type).

Lets change the “?” to be a NA. We’ll do this through sub-setting, where we find the elements that are “?” and assign them to now be NA.

rodents$Weight[rodents$Weight == "?"] <- NA

rodents$Weight
#>  [1] "40"   "36"   "135"  "39"   "43"   "144"  "51"   "44"   "146"  "-999"
#> [11] "44"   "38"   "-999" "220"  "38"   "48"   "143"  "35"   "43"   "37"  
#> [21] "150"  "45"   "-999" "157"  "-999" "218"  "127"  "52"   "42"   "24"  
#> [31] "23"   "232"  "22"   NA     NA     NA     NA     "182"  "42"   "115" 
#> [41] "190"

Now that we have removed the characters, lets turn rodents$Weight into a numeric datatype

rodents$Weight <- as.numeric(rodents$Weight)

Next as part of our cleaning we might want to change the -999 entries in rodents$Weight to be NA

rodents$Weight[rodents$Weight == -999] <- NA

rodents$Weight
#>  [1]  40  36 135  39  43 144  51  44 146  NA  44  38  NA 220  38  48 143  35  43
#> [20]  37 150  45  NA 157  NA 218 127  52  42  24  23 232  22  NA  NA  NA  NA 182
#> [39]  42 115 190


Cleaning names

You many have noticed that read_csv has kept the column names as they were in the file, and they actually violate the rules we have for variables in R - namely they contain a space - which can make dealing with them problematic.

names(rodents)
#> [1] "Plot location"  "Date collected" "Family"         "Genus"         
#> [5] "Species"        "Weight"

We’re now going to clean the names up, and this is where the janitor package is extremely helpful. Because we’re only going to use a single function from the package we can call is directly using the <package>::<function> notation, rather than using library.

rodents <- rodents %>% janitor::clean_names()

names(rodents)
#> [1] "plot_location"  "date_collected" "family"         "genus"         
#> [5] "species"        "weight"

janitor has many options for how it cleans the names with the default being ‘snake’, but many others can be selected and supplied as the case parameter e.g. clean_names(case = ‘lower_camel’) for camel case starting with a lower case letter.


Tidying

The three principles of tidy tabular data are:

  1. Each column is a variable or property that is being measured
  2. Each row is an observation
  3. A single cell should contain a single piece of information

Ideally these principles are followed from the very start when data collection is occurring

In our rodents dataset, the plot_location column violates rule number 3, it contains more than a single piece of information.

We can use the separate function from tidyr to split this column into 2 columns using the ’_’ as our field delimiter.

rodents <- rodents %>% separate(plot_location, into = c("plot", "location"), sep = '_')
#> Warning: Expected 2 pieces. Missing pieces filled with `NA` in 7 rows [35, 36, 37, 38,
#> 39, 40, 41].

head(rodents)
#> # A tibble: 6 × 7
#>   plot  location date_collected family       genus     species     weight
#>   <chr> <chr>    <chr>          <chr>        <chr>     <chr>        <dbl>
#> 1 1     slope    01/09/14       Heteromyidae Dipodomys merriami        40
#> 2 1     slope    01/09/14       Heteromyidae Dipodomys merriami        36
#> 3 1     slope    01/09/14       Heteromyidae Dipodomys spectabilis    135
#> 4 1     rocks    01/09/14       Heteromyidae Dipodomys merriami        39
#> 5 1     grass    01/20/14       Heteromyidae Dipodomys merriami        43
#> 6 1     rocks    01/20/14       Heteromyidae Dipodomys spectabilis    144

Notice how the final 7 rows didn’t have a location, and so were automatically filled with NAs. There is still more that could be done to clean and tidy this particular dataset but we’ll leave it for now.



Shaping Data

When we’re thinking about the ‘shape’ of the data, we’re thinking about in which direction are we adding observations - are we adding them as new columns onto the side making the data wider, or are we adding them as rows onto the bottom making the data longer?

In our framework for making tidy data we created a system that made long data, in that each row was an observation. Sometimes however we need to reshape this data into a wide format.

In the first example of this section we’ll look at, we have yearly population data downloaded from Gapminder.

This data was originally downloaded from Gapminder and selecting the indicator “Population”, then “Total population”

Either go to this page and use Save As “gapminder_yearly_population_total.csv”, saving it into your data/ directory or by using the R command (adjust the directory “data” to match your directory name exactly):

download.file(url = "https://raw.githubusercontent.com/rtis-training/2023-s2-r4ssp/main/data/gapminder_yearly_population_millions_total.csv", 
      destfile = "data/gapminder_yearly_population_millions_total.csv")
gapminder_yearly_pop <- read_csv("data/gapminder_yearly_population_millions_total.csv")
#> Rows: 197 Columns: 302
#> ── Column specification ────────────────────────────────────────────────────────
#> Delimiter: ","
#> chr   (1): country
#> dbl (301): 1800, 1801, 1802, 1803, 1804, 1805, 1806, 1807, 1808, 1809, 1810,...
#> 
#> ℹ Use `spec()` to retrieve the full column specification for this data.
#> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
dim(gapminder_yearly_pop)
#> [1] 197 302
head(gapminder_yearly_pop)
#> # A tibble: 6 × 302
#>   country         `1800`  `1801`  `1802`  `1803`  `1804`  `1805`  `1806`  `1807`
#>   <chr>            <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl>
#> 1 Afghanistan    3.28    3.28    3.28    3.28    3.28    3.28    3.28    3.28   
#> 2 Angola         1.57    1.57    1.57    1.57    1.57    1.57    1.57    1.57   
#> 3 Albania        0.4     0.402   0.404   0.405   0.407   0.409   0.411   0.413  
#> 4 Andorra        0.00265 0.00265 0.00265 0.00265 0.00265 0.00265 0.00265 0.00265
#> 5 United Arab E… 0.0402  0.0402  0.0402  0.0402  0.0402  0.0402  0.0402  0.0402 
#> 6 Argentina      0.534   0.52    0.506   0.492   0.479   0.466   0.453   0.441  
#> # ℹ 293 more variables: `1808` <dbl>, `1809` <dbl>, `1810` <dbl>, `1811` <dbl>,
#> #   `1812` <dbl>, `1813` <dbl>, `1814` <dbl>, `1815` <dbl>, `1816` <dbl>,
#> #   `1817` <dbl>, `1818` <dbl>, `1819` <dbl>, `1820` <dbl>, `1821` <dbl>,
#> #   `1822` <dbl>, `1823` <dbl>, `1824` <dbl>, `1825` <dbl>, `1826` <dbl>,
#> #   `1827` <dbl>, `1828` <dbl>, `1829` <dbl>, `1830` <dbl>, `1831` <dbl>,
#> #   `1832` <dbl>, `1833` <dbl>, `1834` <dbl>, `1835` <dbl>, `1836` <dbl>,
#> #   `1837` <dbl>, `1838` <dbl>, `1839` <dbl>, `1840` <dbl>, `1841` <dbl>, …

Wide to long

You can see that in this case we have a column for each year, and our rows relate to a particular country. This might be a valid for a final table, however it will cause some challenges if we want to perform some analysis or visualisation on it. While it is possible to perform operations across a row, it fights against how the dataframe structure itself is constructed - each column is a single vector and therefore has to be the same datatype, but a row is made of multiple vectors each of which could be a different datatype. Hence the majority of operations in R are column focused.

E.g If we want to plot the change in population across time for a country is that in the current format we can’t do it because we use a single column for each axis.

If we think to our tidy data principles, year is a variable that is being measured alongside population, so we should have a column ‘year’ into which we can store the year values.

The operation that will take us from a wide format to a long format is pivot_longer from tidyr. It has a cols parameter where we specify the columns that will be gathered into 2 named columns - one that will contain the names from the columns - names_to, the second, will contain the values that were in the columns - values_to. cols uses the same syntax as select from dplyr and if a - is used it will use all the columns not specified.

# Using the 'positive' selection of columns
gapminder_yearly_pop_long <- gapminder_yearly_pop %>% pivot_longer(cols = `1800`:`2100`, names_to = "year", values_to = "population")
gapminder_yearly_pop_long
#> # A tibble: 59,297 × 3
#>    country     year  population
#>    <chr>       <chr>      <dbl>
#>  1 Afghanistan 1800        3.28
#>  2 Afghanistan 1801        3.28
#>  3 Afghanistan 1802        3.28
#>  4 Afghanistan 1803        3.28
#>  5 Afghanistan 1804        3.28
#>  6 Afghanistan 1805        3.28
#>  7 Afghanistan 1806        3.28
#>  8 Afghanistan 1807        3.28
#>  9 Afghanistan 1808        3.28
#> 10 Afghanistan 1809        3.28
#> # ℹ 59,287 more rows

# Using the 'negative' selection of columns
gapminder_yearly_pop_long <- gapminder_yearly_pop %>% pivot_longer(cols = -country, names_to = "year", values_to = "population")
gapminder_yearly_pop_long
#> # A tibble: 59,297 × 3
#>    country     year  population
#>    <chr>       <chr>      <dbl>
#>  1 Afghanistan 1800        3.28
#>  2 Afghanistan 1801        3.28
#>  3 Afghanistan 1802        3.28
#>  4 Afghanistan 1803        3.28
#>  5 Afghanistan 1804        3.28
#>  6 Afghanistan 1805        3.28
#>  7 Afghanistan 1806        3.28
#>  8 Afghanistan 1807        3.28
#>  9 Afghanistan 1808        3.28
#> 10 Afghanistan 1809        3.28
#> # ℹ 59,287 more rows

Note that because the column names violate the rules for R variables in that they start with a number, we have to deal with the problematic names by enclosing them with backticks `.

Long to wide

Sometimes we would like to take our data from a long format and into a wide format. For our example we want to create a table that will let us see distances between an origin and a destination within 500 miles of each other. To do this we’re going to use the flights data from the nycflights13 package.

The table that we create will essentially be a look-up table that will let us quickly reference the distance between origin/destination pairs.

# install.packages(nycflights13)
library(nycflights13)

glimpse(flights)
#> Rows: 336,776
#> Columns: 19
#> $ year           <int> 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2013, 2…
#> $ month          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
#> $ day            <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
#> $ dep_time       <int> 517, 533, 542, 544, 554, 554, 555, 557, 557, 558, 558, …
#> $ sched_dep_time <int> 515, 529, 540, 545, 600, 558, 600, 600, 600, 600, 600, …
#> $ dep_delay      <dbl> 2, 4, 2, -1, -6, -4, -5, -3, -3, -2, -2, -2, -2, -2, -1…
#> $ arr_time       <int> 830, 850, 923, 1004, 812, 740, 913, 709, 838, 753, 849,…
#> $ sched_arr_time <int> 819, 830, 850, 1022, 837, 728, 854, 723, 846, 745, 851,…
#> $ arr_delay      <dbl> 11, 20, 33, -18, -25, 12, 19, -14, -8, 8, -2, -3, 7, -1…
#> $ carrier        <chr> "UA", "UA", "AA", "B6", "DL", "UA", "B6", "EV", "B6", "…
#> $ flight         <int> 1545, 1714, 1141, 725, 461, 1696, 507, 5708, 79, 301, 4…
#> $ tailnum        <chr> "N14228", "N24211", "N619AA", "N804JB", "N668DN", "N394…
#> $ origin         <chr> "EWR", "LGA", "JFK", "JFK", "LGA", "EWR", "EWR", "LGA",…
#> $ dest           <chr> "IAH", "IAH", "MIA", "BQN", "ATL", "ORD", "FLL", "IAD",…
#> $ air_time       <dbl> 227, 227, 160, 183, 116, 150, 158, 53, 140, 138, 149, 1…
#> $ distance       <dbl> 1400, 1416, 1089, 1576, 762, 719, 1065, 229, 944, 733, …
#> $ hour           <dbl> 5, 5, 5, 5, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 6, 6, 6…
#> $ minute         <dbl> 15, 29, 40, 45, 0, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59, 0…
#> $ time_hour      <dttm> 2013-01-01 05:00:00, 2013-01-01 05:00:00, 2013-01-01 0…

First lets select those three columns we need and filter so that the distance is within 500 miles.

flights_long <- flights %>% select(origin, dest, distance) %>% 
  filter(distance <= 500)

Next we want remove duplicate rows, this can be done using distinct()

flights_long <- flights_long %>% distinct()

You can see that our long data has ncol(flights_long) columns, and nrow(flights_long) rows.

dim(flights_long)
#> [1] 64  3

We now want to make each of our destinations its own column and fill in the corresponding air time as the value. To do this, we can use pivot_wider. The main arguments are names_from which is the column you want take the new column names from, and values_from which is the column that will be used to fill in the new column values.

flights_long %>% pivot_wider(origin, names_from = "dest", values_from = "distance")
#> Warning: Specifying the `id_cols` argument by position was deprecated in tidyr 1.3.0.
#> ℹ Please explicitly name `id_cols`, like `id_cols = origin`.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.
#> # A tibble: 3 × 31
#>   origin   IAD   BOS   BWI   BUF   ROC   SYR   RDU   CMH   PIT   DCA   CLE   DTW
#>   <chr>  <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 LGA      229   184   185   292   254   198   431   479   335   214   419    NA
#> 2 JFK      228   187   184   301   264   209   427   483   340   213   425    NA
#> 3 EWR      212   200   169   282   246   195   416   463   319   199   404   488
#> # ℹ 18 more variables: BTV <dbl>, PHL <dbl>, PWM <dbl>, CAK <dbl>, ALB <dbl>,
#> #   BDL <dbl>, MHT <dbl>, GSO <dbl>, RIC <dbl>, ORF <dbl>, CRW <dbl>,
#> #   PVD <dbl>, ACK <dbl>, BGR <dbl>, ILM <dbl>, MVY <dbl>, CHO <dbl>, LGA <dbl>

Conclusion

LS0tCnRpdGxlOiAiVGlkeWluZyIKZGF0ZTogIlNlbWVzdGVyIDIsIDIwMjMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoa25pdHIpCgprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgY29tbWVudCA9ICIjPiIsCiAgZmlnLnBhdGggPSAiZmlndXJlcy8wNi8iLCAjIHVzZSBvbmx5IGZvciBzaW5nbGUgUm1kIGZpbGVzCiAgY29sbGFwc2UgPSBUUlVFLAogIGVjaG8gPSBUUlVFCikKYGBgCgoKPiAjIyMjIEFzc29jaWF0ZWQgTWF0ZXJpYWwKPgo+IFpvb20gbm90ZXM6IFtab29tIE5vdGVzIDA2IC0gVGlkeWluZyBEYXRhXSh6b29tX25vdGVzXzA2X3RpZHlpbmcuaHRtbCkKPgo+IFJlYWRpbmdzOgo+Cj4gLSBbUiBmb3IgRGF0YSBTY2llbmNlIENoYXB0ZXIgMTBdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovdGliYmxlcy5odG1sKQo+IC0gW1IgZm9yIERhdGEgU2NpZW5jZSBDaGFwdGVyIDExXShodHRwczovL3I0ZHMuaGFkLmNvLm56L2RhdGEtaW1wb3J0Lmh0bWwpCj4gLSBbUiBmb3IgRGF0YSBTY2llbmNlIENoYXB0ZXIgMTJdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovdGlkeS1kYXRhLmh0bWwpCj4gLSBbUiBmb3IgRGF0YSBTY2llbmNlIENoYXB0ZXIgMTRdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovc3RyaW5ncy5odG1sKQoKXAoKClRoZSBwcmUtcHJvY2Vzc2luZyBvZiBkYXRhIGFzIGEgcHJvcG9ydGlvbiBvZiBhIGRhdGEgYW5hbHlzaXMgd29ya2Zsb3cgY2FuIGJlIHF1aXRlIHN1YnN0YW50aWFsLCBidXQgdGhpcyBzdGVwIGlzIGV4dHJlbWVseSB2aXRhbCAtIHBvb3IgZGF0YSBpbiA9IHBvb3IgcmVzdWx0cyBvdXQuIFRoZSBtYWluIHB1cnBvc2Ugb2YgdGhpcyBtb2R1bGUgaXMgdHJhbnNmb3JtaW5nIG91ciBkYXRhIGludG8gc29tZXRoaW5nIHdvcnRoeSBvZiBhbmFseXNpcyBhbmQgd2lsbCBjb3ZlciBkYXRhIGNsZWFuaW5nLCB0aWR5aW5nIGFuZCAncmVzaGFwaW5nJy4gVGhpcyB3aWxsIGJlIGZvbGxvd2VkIGJ5IGhvdyB0byBpbmNyZWFzZSB0aGUgdXRpbGl0eSBvZiBvdXIgZGF0YSBieSBjb21iaW5pbmcgaXQgd2l0aCBvdGhlciBkYXRhc2V0cyBpbiBbTW9kdWxlIDcgLSBDb21iaW5pbmcgRGF0YV0oMDctY29tYmluaW5nLmh0bWwpLgoKTGV0IHVzIGZpcnN0IGRlbHZlIGludG8gdGhlIGRhdGEgY2xlYW5pbmcgYW5kIHRpZHlpbmcuCgpcCgojIyBDbGVhbmluZy9UaWR5aW5nCgpUaGUgZ29hbCBvZiBjbGVhbmluZyBhbmQgdGlkeWluZyBvdXIgZGF0YSBpcyB0byBkZWFsIHdpdGggdGhlIGltcGVyZmVjdGlvbnMgdGhhdCBleGlzdCB3aXRoIHJlYWwtd29ybGQgZGF0YXNldHMgYW5kIG1ha2UgdGhlbSByZWFkeSBmb3IgYW5hbHlzaXMuIEJlY2F1c2Ugd2UncmUgZm9jdXNpbmcgb24gaG93IHRvIGRvIHRoaXMgaW5zaWRlIFIgd2UncmUgZ29pbmcgdG8gYXNzdW1lIHRoYXQgeW91IGFyZSBhbHJlYWR5IHN0YXJ0aW5nIHdpdGggJ3JlY3Rhbmd1bGFyJyBkYXRhIC0gYWxsIHJvd3MgYXJlIHRoZSBzYW1lIGxlbmd0aCwgYW5kIGFsbCBjb2x1bW5zIGFyZSB0aGUgc2FtZSBsZW5ndGguIFNvbWV0aW1lcyB0aGVyZSBpcyBhICdwcmUtUicgcGhhc2Ugd2hpY2ggcmVxdWlyZXMgeW91IHRvIG1ha2UgdGhlIGRhdGEgcmVjdGFuZ3VsYXIgLSBjaGVjayBvdXQgdGhpcyBtYXRlcmlhbCBmcm9tIFtEYXRhIENhcnBlbnRyeV0oaHR0cHM6Ly9kYXRhY2FycGVudHJ5Lm9yZy9zcHJlYWRzaGVldC1lY29sb2d5LWxlc3Nvbi8pIGlmIHRoaXMgaXMgc29tZXRoaW5nIHlvdSBmaXJzdCBuZWVkIHRvIGRvLgoKSWYgeW91IGhhdmUgaHVtYW5zIGFzIHBhcnQgb2YgdGhlIGRhdGEgY29sbGVjdGluZyBwcm9jZXNzIGl0J3Mgb25seSBhIG1hdHRlciBvZiB0aW1lIGJlZm9yZSB0aGVyZSBpcyBhIGRhdGEgZW50cnkgaXNzdWUgdGhhdCBuZWVkcyB0byBiZSAnY2xlYW5lZCcgb3V0LgoKU29tZSBjb21tb24gdGFza3MgYXQgdGhpcyBzdGFnZSBpbmNsdWRlOgoKLSBlbnN1cmluZyB0aGF0IG1pc3NpbmcgZGF0YSBpcyBjb2RlZCBjb3JyZWN0bHkKLSBkZWFsaW5nIHdpdGggdHlwb3MKLSByZW1vdmluZyB1bm5lZWRlZCB3aGl0ZXNwYWNlCi0gY29udmVydGluZyBjb2x1bW5zIHRvIGJlIHRoZSBjb3JyZWN0IGRhdGEgdHlwZQotIG1ha2luZyBuaWNlIGNvbHVtbiBuYW1lcwoKVW5sZXNzIHlvdSBhcmUgaW4gYSBmb3J0dW5hdGUgcG9zaXRpb24gdG8gaW5oZXJpdCAnY2xlYW5lZCcgZGF0YSB0aGVzZSBqb2JzIHdpbGwgZmFsbCBvbiB5b3UgdG8gZW5zdXJlIHRoYXQgbGF0ZXIgb24gdGhleSBkb24ndCBjYXVzZSBpc3N1ZXMgd2hlbiBpdCBjb21lcyB0aW1lIHRvIGRvIHlvdXIgc3RhdGlzdGljcy4KCkZvciB0aGlzIHNlY3Rpb24gd2UncmUgZ29pbmcgdG8gbWFrZSB1c2Ugb2YgdGhlIGZvbGxvd2luZyBwYWNrYWdlcyB0aGF0IGFyZSBwYXJ0IG9mIHRoZSBUaWR5dmVyc2U6IGByZWFkcmAgZm9yIHJlYWRpbmcgaW4gYW5kIHBhcnNpbmcgZGF0YSwgYHRpZHlyYCB3aGljaCBoYXMgZnVuY3Rpb25zIGZvciBkZWFsaW5nIHdpdGggbWlzc2luZyBkYXRhLCBhbmQgYHN0cmluZ3JgIHdoaWNoIGhhcyBmdW5jdGlvbnMgcmVsYXRpbmcgdG8gZGVhbGluZyB3aXRoIGNoYXJhY3RlciB2ZWN0b3JzLgoKQWxsIGFyZSBsb2FkZWQgYXMgcGFydCBvZiBsb2FkaW5nIHRoZSBgdGlkeXZlcnNlYCBwYWNrYWdlLiBZb3UgY2FuIHNlZSB1bmRlciB0aGUgIkF0dGFjaGluZyBwYWNrYWdlcyIgaGVhZGluZyBleGFjdGx5IHdoaWNoIHBhY2thZ2VzIGFyZSBiZWluZyBsb2FkZWQuCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKQW4gZXh0cmEgcGFja2FnZSBpcyBgamFuaXRvcmAsIHdoaWNoIGRlc2lnbmVkIGZvciBjbGVhbmluZy4KCmBgYHtyfQojIGluc3RhbGwucGFja2FnZXMoImphbml0b3IiKQpsaWJyYXJ5KGphbml0b3IpCmBgYAoKCj4gVGhlIGRhdGEgZm9yIHRoaXMgc2VjdGlvbiBpcyBkZXJpdmVkIGZyb20gW3VudGlkeS1wb3J0YWwtZGF0YS4KeGxzeF0oaHR0cHM6Ly9maWdzaGFyZS5jb20vbmRvd25sb2FkZXIvZmlsZXMvMjQ0Njk0MjQpLiBJdCBpcyBwYXJ0IG9mIHRoZSBbUG9ydGFsIFByb2plY3QgVGVhY2hpbmcgRGF0YWJhc2VdKGh0dHBzOi8vZmlnc2hhcmUuY29tL2FydGljbGVzL1BvcnRhbF9Qcm9qZWN0X1RlYWNoaW5nX0RhdGFiYXNlLzEzMTQ0NTkpIGh0dHBzOi8vZG9pLm9yZy8xMC42MDg0L205LmZpZ3NoYXJlLjEzMTQ0NTkudjEwIGF2YWlsYWJsZSBvbiBGaWdTaGFyZSB1bmRlciBhIENDLTAgbGljZW5zZS4KPiAKPiBUaGUgb3JpZ2luYWwgZmlsZSBkb2VzIG5vdCBoYXZlIHRoZSBkYXRhIGluIGEgcmVjdGFuZ3VsYXIgZm9ybWF0IHNvIHdlIGhhdmUgb3JnYW5pc2VkIHRoZSBkYXRhIHNvIGl0IGNhbiBiZSByZWFkIGludG8gUiBhbmQgbWFkZSBpdCBhdmFpbGFibGUgdG8gZG93bmxvYWQgZWl0aGVyIGJ5IG5hdmlnYXRpbmcgdG8gW2h0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9ydGlzLXRyYWluaW5nLzIwMjMtczItcjRzc3AvbWFpbi9kYXRhL3JvZGVudHNfdW50aWR5LmNzdl0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3J0aXMtdHJhaW5pbmcvMjAyMy1zMi1yNHNzcC9tYWluL2RhdGEvcm9kZW50c191bnRpZHkuY3N2KSBhbmQgdXNpbmcgYFNhdmUgQXNgICJyb2RlbnRzX3VudGlkeS5jc3YiIGluIHlvdSBkYXRhIGRpcmVjdG9yeSBvciBieSB1c2luZyB0aGUgZm9sb3dpbmcgUiBjb21tYW5kIChlbnN1cmUgeW91IG1hdGNoIHRoZSBkaXJlY3RvcnkgbmFtZSB0byB5b3VyIGRpcmVjdG9yeSBzZXQgdXApOgo+IAo+IGBgYHtyLCBldmFsID0gRkFMU0V9Cj4gZG93bmxvYWQuZmlsZSh1cmwgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3J0aXMtdHJhaW5pbmcvMjAyMy1zMi1yNHNzcC9tYWluL2RhdGEvcm9kZW50c191bnRpZHkuY3N2IiwgCj4gICAgICAgZGVzdGZpbGUgPSAiZGF0YS9yb2RlbnRzX3VudGlkeS5jc3YiKQo+IGBgYAoKVW50aWwgbm93IHdlJ3ZlIGJlZW4gdXNpbmcgdGhlIGByZWFkLmNzdmAgKCJyZWFkLWRvdC1jc3YiKSBmdW5jdGlvbiB0aGF0IGlzIHBhcnQgb2YgYmFzZSBSLCBidXQgYHJlYWRyYCBoYXMgZXF1aXZhbGVudCBmdW5jdGlvbnMsIG5hbWVseSBgcmVhZF9jc3ZgICgicmVhZC11bmRlcnNjb3JlLWNzdiIpIHdoaWNoIHByb3ZpZGVzIHNvbWUgZXh0cmEgZmVhdHVyZXMgc3VjaCBhcyBhIHByb2dyZXNzIGJhciwgZGlzcGxheWluZyB0aGUgZGF0YSB0eXBlcyBvZiBhbGwgdGhlIGNvbHVtbnMgYXMgcGFydCBvZiB0aGUgcmVhZGluZywgYW5kIGl0IHdpbGwgY3JlYXRlIHRoZSBzcGVjaWFsIHZlcnNpb24gb2YgYSBkYXRhLmZyYW1lIGNhbGxlZCBhIGB0aWJibGVgLiBGb3IgbW9yZSBpbi1kZXB0aCBkZXRhaWxzIGFib3V0IGB0aWJibGVzYCBjaGVjayBvdXQgdGhlIFtSIGZvciBEYXRhIFNjaWVuY2UgLSBUaWJibGVzXShodHRwczovL3I0ZHMuaGFkLmNvLm56L3RpYmJsZXMuaHRtbCkgY2hhcHRlci4gVGhlIG1haW4gYmVuZWZpdCB3ZSdsbCB1dGlsaXNlIGlzIHRoZSB3YXkgaXQgcHJpbnRzIHRvIHRoZSBzY3JlZW4gd2hpY2ggaW5jbHVkZXMgc29tZSBmb3JtYXR0aW5nIGFuZCBpbmNsdXNpb24gb2YgdGhlIGNvbHVtbiBkYXRhIHR5cGVzIHVuZGVyIHRoZSBjb2x1bW4gbmFtZXMuCgpgYGB7cn0KIyBub3RlIHRoZSB1c2Ugb2YgdGhlICJyZWFkLXVuZGVyc2NvcmUtY3N2Igpyb2RlbnRzIDwtIHJlYWRfY3N2KCJkYXRhL3JvZGVudHNfdW50aWR5LmNzdiIpCmBgYAoKVGhlIGZpcnN0IHRoaW5nIHdlIGNhbiBsb29rIGF0IGFzIHBhcnQgb2YgdGhlIGRhdGEgbG9hZGluZyBpcyB0aGUgY29sdW1uIG5hbWVzIGFuZCB3aGF0IHRoZSBkYXRhdHlwZSBgcmVhZF9jc3ZgIGhhZCBhIGJlc3QgZ3Vlc3MgYXQgdGhlIHR5cGUgb2YgZGF0YSB0aGUgY29sdW1uIGhhZC4KCmBgYHtyfQpyb2RlbnRzICU+JSBoZWFkKCkKYGBgCgpJdCBjYW4gYmUgZGlmZmljdWx0IGlmIHlvdSBoYXZlIG1hbnkgY29sdW1ucyB0byBzZWUgdGhlbSBhbGwsIHNvIGluc3RlYWQgeW91IGNhbiBzZWUgZXhhY3RseSB3aGF0IGVhY2ggY29sdW1uIHdhcyBsb2FkZWQgaW4gYXMgdXNpbmcgYHNwZWNgLgoKYGBge3J9CnNwZWMocm9kZW50cykKYGBgCgojIyMgQ2xlYW5pbmcgZGF0YSB2YWx1ZXMKCkZyb20gdGhpcyBsaXN0IG9mIGNvbHVtbnMsIHdpdGhvdXQga25vd2luZyBtdWNoIGFib3V0IHRoZSBkYXRhIGl0c2VsZiwgX1dlaWdodF8gbWlnaHQgc2VlbiBvZGQgdGhhdCBpdCBpcyBhIGNoYXJhY3RlciB0eXBlLCByYXRoZXIgdGhhbiBhIG51bWVyaWMsIHNvIHdlJ2xsIGZvY3VzIGluIG9uIHRoaXMgY29sdW1uIHRvIHN0YXJ0IHdpdGgKCmBgYHtyfQpyb2RlbnRzJFdlaWdodApgYGAKCkZyb20gdGhpcyB3ZSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSBjb21lICI/IiBjaGFyYWN0ZXJzLCBpcyB3aGF0IGhhcyBjYXVzZWQgdGhlIGNvbHVtbiB0byBiZSByZWFkIGluIGFzIGNoYXJhY3RlcnMgaW5zdGVhZCBvZiBudW1iZXJzIChyZW1lbWJlciB0aGF0IGEgdmVjdG9yIG11c3QgYmUgYWxsIHRoZSBzYW1lIGRhdGEgdHlwZSkuCgpMZXRzIGNoYW5nZSB0aGUgIj8iIHRvIGJlIGEgYE5BYC4gV2UnbGwgZG8gdGhpcyB0aHJvdWdoIHN1Yi1zZXR0aW5nLCB3aGVyZSB3ZSBmaW5kIHRoZSBlbGVtZW50cyB0aGF0IGFyZSAiPyIgYW5kIGFzc2lnbiB0aGVtIHRvIG5vdyBiZSBgTkFgLgoKYGBge3J9CnJvZGVudHMkV2VpZ2h0W3JvZGVudHMkV2VpZ2h0ID09ICI/Il0gPC0gTkEKCnJvZGVudHMkV2VpZ2h0CmBgYAoKCk5vdyB0aGF0IHdlIGhhdmUgcmVtb3ZlZCB0aGUgY2hhcmFjdGVycywgbGV0cyB0dXJuIGByb2RlbnRzJFdlaWdodGAgaW50byBhIG51bWVyaWMgZGF0YXR5cGUKCmBgYHtyfQpyb2RlbnRzJFdlaWdodCA8LSBhcy5udW1lcmljKHJvZGVudHMkV2VpZ2h0KQpgYGAKCgoKTmV4dCBhcyBwYXJ0IG9mIG91ciBjbGVhbmluZyB3ZSBtaWdodCB3YW50IHRvIGNoYW5nZSB0aGUgYC05OTlgIGVudHJpZXMgaW4gcm9kZW50cyRXZWlnaHQgdG8gYmUgYE5BYAoKYGBge3J9CnJvZGVudHMkV2VpZ2h0W3JvZGVudHMkV2VpZ2h0ID09IC05OTldIDwtIE5BCgpyb2RlbnRzJFdlaWdodApgYGAKClwKCiMjIyBDbGVhbmluZyBuYW1lcwoKWW91IG1hbnkgaGF2ZSBub3RpY2VkIHRoYXQgYHJlYWRfY3N2YCBoYXMga2VwdCB0aGUgY29sdW1uIG5hbWVzIGFzIHRoZXkgd2VyZSBpbiB0aGUgZmlsZSwgYW5kIHRoZXkgYWN0dWFsbHkgdmlvbGF0ZSB0aGUgcnVsZXMgd2UgaGF2ZSBmb3IgdmFyaWFibGVzIGluIFIgLSBuYW1lbHkgdGhleSBjb250YWluIGEgc3BhY2UgLSB3aGljaCBjYW4gbWFrZSBkZWFsaW5nIHdpdGggdGhlbSBwcm9ibGVtYXRpYy4KCmBgYHtyfQpuYW1lcyhyb2RlbnRzKQpgYGAKCldlJ3JlIG5vdyBnb2luZyB0byBjbGVhbiB0aGUgbmFtZXMgdXAsIGFuZCB0aGlzIGlzIHdoZXJlIHRoZSBgamFuaXRvcmAgcGFja2FnZSBpcyBleHRyZW1lbHkgaGVscGZ1bC4gQmVjYXVzZSB3ZSdyZSBvbmx5IGdvaW5nIHRvIHVzZSBhIHNpbmdsZSBmdW5jdGlvbiBmcm9tIHRoZSBwYWNrYWdlIHdlIGNhbiBjYWxsIGlzIGRpcmVjdGx5IHVzaW5nIHRoZSBgPHBhY2thZ2U+Ojo8ZnVuY3Rpb24+YCBub3RhdGlvbiwgcmF0aGVyIHRoYW4gdXNpbmcgYGxpYnJhcnlgLgoKCmBgYHtyfQpyb2RlbnRzIDwtIHJvZGVudHMgJT4lIGphbml0b3I6OmNsZWFuX25hbWVzKCkKCm5hbWVzKHJvZGVudHMpCmBgYAoKPiBgamFuaXRvcmAgaGFzIG1hbnkgb3B0aW9ucyBmb3IgaG93IGl0IGNsZWFucyB0aGUgbmFtZXMgd2l0aCB0aGUgZGVmYXVsdCBiZWluZyAnc25ha2UnLCBidXQgbWFueSBvdGhlcnMgY2FuIGJlIHNlbGVjdGVkIGFuZCBzdXBwbGllZCBhcyB0aGUgYGNhc2VgIHBhcmFtZXRlciBlLmcuIGNsZWFuX25hbWVzKGNhc2UgPSAnbG93ZXJfY2FtZWwnKSBmb3IgY2FtZWwgY2FzZSBzdGFydGluZyB3aXRoIGEgbG93ZXIgY2FzZSBsZXR0ZXIuCgpcCgojIyMgVGlkeWluZwoKVGhlIHRocmVlIHByaW5jaXBsZXMgb2YgdGlkeSB0YWJ1bGFyIGRhdGEgYXJlOgoKMS4gRWFjaCBjb2x1bW4gaXMgYSB2YXJpYWJsZSBvciBwcm9wZXJ0eSB0aGF0IGlzIGJlaW5nIG1lYXN1cmVkIAoyLiBFYWNoIHJvdyBpcyBhbiBvYnNlcnZhdGlvbgozLiBBIHNpbmdsZSBjZWxsIHNob3VsZCBjb250YWluIGEgc2luZ2xlIHBpZWNlIG9mIGluZm9ybWF0aW9uCgpJZGVhbGx5IHRoZXNlIHByaW5jaXBsZXMgYXJlIGZvbGxvd2VkIGZyb20gdGhlIHZlcnkgc3RhcnQgd2hlbiBkYXRhIGNvbGxlY3Rpb24gaXMgb2NjdXJyaW5nCgpJbiBvdXIgcm9kZW50cyBkYXRhc2V0LCB0aGUgKnBsb3RfbG9jYXRpb24qIGNvbHVtbiB2aW9sYXRlcyBydWxlIG51bWJlciAzLCBpdCBjb250YWlucyBtb3JlIHRoYW4gYSBzaW5nbGUgcGllY2Ugb2YgaW5mb3JtYXRpb24uCgpXZSBjYW4gdXNlIHRoZSBgc2VwYXJhdGVgIGZ1bmN0aW9uIGZyb20gYHRpZHlyYCB0byBzcGxpdCB0aGlzIGNvbHVtbiBpbnRvIDIgY29sdW1ucyB1c2luZyB0aGUgJ18nIGFzIG91ciBmaWVsZCBkZWxpbWl0ZXIuCgpgYGB7cn0Kcm9kZW50cyA8LSByb2RlbnRzICU+JSBzZXBhcmF0ZShwbG90X2xvY2F0aW9uLCBpbnRvID0gYygicGxvdCIsICJsb2NhdGlvbiIpLCBzZXAgPSAnXycpCgpoZWFkKHJvZGVudHMpCmBgYAoKTm90aWNlIGhvdyB0aGUgZmluYWwgNyByb3dzIGRpZG4ndCBoYXZlIGEgbG9jYXRpb24sIGFuZCBzbyB3ZXJlIGF1dG9tYXRpY2FsbHkgZmlsbGVkIHdpdGggYE5BYHMuIFRoZXJlIGlzIHN0aWxsIG1vcmUgdGhhdCBjb3VsZCBiZSBkb25lIHRvIGNsZWFuIGFuZCB0aWR5IHRoaXMgcGFydGljdWxhciBkYXRhc2V0IGJ1dCB3ZSdsbCBsZWF2ZSBpdCBmb3Igbm93LgoKXAoKXAoKCiMjIFNoYXBpbmcgRGF0YQoKV2hlbiB3ZSdyZSB0aGlua2luZyBhYm91dCB0aGUgJ3NoYXBlJyBvZiB0aGUgZGF0YSwgd2UncmUgdGhpbmtpbmcgYWJvdXQgaW4gd2hpY2ggZGlyZWN0aW9uIGFyZSB3ZSBhZGRpbmcgb2JzZXJ2YXRpb25zIC0gYXJlIHdlIGFkZGluZyB0aGVtIGFzIG5ldyBjb2x1bW5zIG9udG8gdGhlIHNpZGUgbWFraW5nIHRoZSBkYXRhICoqd2lkZXIqKiwgb3IgYXJlIHdlIGFkZGluZyB0aGVtIGFzIHJvd3Mgb250byB0aGUgYm90dG9tIG1ha2luZyB0aGUgZGF0YSAqKmxvbmdlcioqPwoKSW4gb3VyIGZyYW1ld29yayBmb3IgbWFraW5nIHRpZHkgZGF0YSB3ZSBjcmVhdGVkIGEgc3lzdGVtIHRoYXQgbWFkZSAqKmxvbmcqKiBkYXRhLCBpbiB0aGF0IGVhY2ggcm93IHdhcyBhbiBvYnNlcnZhdGlvbi4gU29tZXRpbWVzIGhvd2V2ZXIgd2UgbmVlZCB0byByZXNoYXBlIHRoaXMgZGF0YSBpbnRvIGEgKip3aWRlKiogZm9ybWF0LgoKCkluIHRoZSBmaXJzdCBleGFtcGxlIG9mIHRoaXMgc2VjdGlvbiB3ZSdsbCBsb29rIGF0LCB3ZSBoYXZlIHllYXJseSBwb3B1bGF0aW9uIGRhdGEgZG93bmxvYWRlZCBmcm9tIEdhcG1pbmRlci4gCgo+IFRoaXMgZGF0YSB3YXMgb3JpZ2luYWxseSBkb3dubG9hZGVkIGZyb20gW0dhcG1pbmRlcl0oaHR0cHM6Ly93d3cuZ2FwbWluZGVyLm9yZy9kYXRhLykgYW5kIHNlbGVjdGluZyB0aGUgaW5kaWNhdG9yICJQb3B1bGF0aW9uIiwgdGhlbiAiVG90YWwgcG9wdWxhdGlvbiIKPiAKPiBFaXRoZXIgZ28gdG8gW3RoaXMgcGFnZV0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3J0aXMtdHJhaW5pbmcvMjAyMy1zMi1yNHNzcC9tYWluL2RhdGEvZ2FwbWluZGVyX3llYXJseV9wb3B1bGF0aW9uX21pbGxpb25zX3RvdGFsLmNzdikgYW5kIHVzZSBgU2F2ZSBBc2AgImdhcG1pbmRlcl95ZWFybHlfcG9wdWxhdGlvbl90b3RhbC5jc3YiLCBzYXZpbmcgaXQgaW50byB5b3VyIGBkYXRhL2AgZGlyZWN0b3J5IG9yIGJ5IHVzaW5nIHRoZSBSIGNvbW1hbmQgKGFkanVzdCB0aGUgZGlyZWN0b3J5ICJkYXRhIiB0byBtYXRjaCB5b3VyIGRpcmVjdG9yeSBuYW1lIGV4YWN0bHkpOgo+IAo+IGBgYHtyLCBldmFsID0gRkFMU0V9Cj4gZG93bmxvYWQuZmlsZSh1cmwgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3J0aXMtdHJhaW5pbmcvMjAyMy1zMi1yNHNzcC9tYWluL2RhdGEvZ2FwbWluZGVyX3llYXJseV9wb3B1bGF0aW9uX21pbGxpb25zX3RvdGFsLmNzdiIsIAo+ICAgICAgIGRlc3RmaWxlID0gImRhdGEvZ2FwbWluZGVyX3llYXJseV9wb3B1bGF0aW9uX21pbGxpb25zX3RvdGFsLmNzdiIpCj4gYGBgCgoKYGBge3J9CmdhcG1pbmRlcl95ZWFybHlfcG9wIDwtIHJlYWRfY3N2KCJkYXRhL2dhcG1pbmRlcl95ZWFybHlfcG9wdWxhdGlvbl9taWxsaW9uc190b3RhbC5jc3YiKQpkaW0oZ2FwbWluZGVyX3llYXJseV9wb3ApCmhlYWQoZ2FwbWluZGVyX3llYXJseV9wb3ApCmBgYAoKIyMjIFdpZGUgdG8gbG9uZwoKWW91IGNhbiBzZWUgdGhhdCBpbiB0aGlzIGNhc2Ugd2UgaGF2ZSBhIGNvbHVtbiBmb3IgZWFjaCB5ZWFyLCBhbmQgb3VyIHJvd3MgcmVsYXRlIHRvIGEgcGFydGljdWxhciBjb3VudHJ5LiBUaGlzIG1pZ2h0IGJlIGEgdmFsaWQgZm9yIGEgZmluYWwgdGFibGUsIGhvd2V2ZXIgaXQgd2lsbCBjYXVzZSBzb21lIGNoYWxsZW5nZXMgaWYgd2Ugd2FudCB0byBwZXJmb3JtIHNvbWUgYW5hbHlzaXMgb3IgdmlzdWFsaXNhdGlvbiBvbiBpdC4gV2hpbGUgaXQgaXMgcG9zc2libGUgdG8gcGVyZm9ybSBvcGVyYXRpb25zIGFjcm9zcyBhIHJvdywgaXQgZmlnaHRzIGFnYWluc3QgaG93IHRoZSBkYXRhZnJhbWUgc3RydWN0dXJlIGl0c2VsZiBpcyBjb25zdHJ1Y3RlZCAtIGVhY2ggY29sdW1uIGlzIGEgc2luZ2xlIHZlY3RvciBhbmQgdGhlcmVmb3JlIGhhcyB0byBiZSB0aGUgc2FtZSBkYXRhdHlwZSwgYnV0IGEgcm93IGlzIG1hZGUgb2YgbXVsdGlwbGUgdmVjdG9ycyBlYWNoIG9mIHdoaWNoIGNvdWxkIGJlIGEgZGlmZmVyZW50IGRhdGF0eXBlLiBIZW5jZSB0aGUgbWFqb3JpdHkgb2Ygb3BlcmF0aW9ucyBpbiBSIGFyZSBjb2x1bW4gZm9jdXNlZC4gCgpFLmcgSWYgd2Ugd2FudCB0byBwbG90IHRoZSBjaGFuZ2UgaW4gcG9wdWxhdGlvbiBhY3Jvc3MgdGltZSBmb3IgYSBjb3VudHJ5IGlzIHRoYXQgaW4gdGhlIGN1cnJlbnQgZm9ybWF0IHdlIGNhbid0IGRvIGl0IGJlY2F1c2Ugd2UgdXNlIGEgc2luZ2xlIGNvbHVtbiBmb3IgZWFjaCBheGlzLgoKSWYgd2UgdGhpbmsgdG8gb3VyIHRpZHkgZGF0YSBwcmluY2lwbGVzLCB5ZWFyIGlzIGEgdmFyaWFibGUgdGhhdCBpcyBiZWluZyBtZWFzdXJlZCBhbG9uZ3NpZGUgcG9wdWxhdGlvbiwgc28gd2Ugc2hvdWxkIGhhdmUgYSBjb2x1bW4gJ3llYXInIGludG8gd2hpY2ggd2UgY2FuIHN0b3JlIHRoZSB5ZWFyIHZhbHVlcy4KCgoKVGhlIG9wZXJhdGlvbiB0aGF0IHdpbGwgdGFrZSB1cyBmcm9tIGEgd2lkZSBmb3JtYXQgdG8gYSBsb25nIGZvcm1hdCBpcyBgcGl2b3RfbG9uZ2VyYCBmcm9tIGB0aWR5cmAuIEl0IGhhcyBhIGBjb2xzYCBwYXJhbWV0ZXIgd2hlcmUgd2Ugc3BlY2lmeSB0aGUgY29sdW1ucyB0aGF0IHdpbGwgYmUgZ2F0aGVyZWQgaW50byAyIG5hbWVkIGNvbHVtbnMgLSBvbmUgdGhhdCB3aWxsIGNvbnRhaW4gdGhlIG5hbWVzIGZyb20gdGhlIGNvbHVtbnMgLSBgbmFtZXNfdG9gLCB0aGUgc2Vjb25kLCB3aWxsIGNvbnRhaW4gdGhlIHZhbHVlcyB0aGF0IHdlcmUgaW4gdGhlIGNvbHVtbnMgLSBgdmFsdWVzX3RvYC4gYGNvbHNgIHVzZXMgdGhlIHNhbWUgc3ludGF4IGFzIGBzZWxlY3RgIGZyb20gYGRwbHlyYCBhbmQgaWYgYSBgLWAgaXMgdXNlZCBpdCB3aWxsIHVzZSBhbGwgdGhlIGNvbHVtbnMgX25vdF8gc3BlY2lmaWVkLgoKYGBge3J9CiMgVXNpbmcgdGhlICdwb3NpdGl2ZScgc2VsZWN0aW9uIG9mIGNvbHVtbnMKZ2FwbWluZGVyX3llYXJseV9wb3BfbG9uZyA8LSBnYXBtaW5kZXJfeWVhcmx5X3BvcCAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSBgMTgwMGA6YDIxMDBgLCBuYW1lc190byA9ICJ5ZWFyIiwgdmFsdWVzX3RvID0gInBvcHVsYXRpb24iKQpnYXBtaW5kZXJfeWVhcmx5X3BvcF9sb25nCgojIFVzaW5nIHRoZSAnbmVnYXRpdmUnIHNlbGVjdGlvbiBvZiBjb2x1bW5zCmdhcG1pbmRlcl95ZWFybHlfcG9wX2xvbmcgPC0gZ2FwbWluZGVyX3llYXJseV9wb3AgJT4lIHBpdm90X2xvbmdlcihjb2xzID0gLWNvdW50cnksIG5hbWVzX3RvID0gInllYXIiLCB2YWx1ZXNfdG8gPSAicG9wdWxhdGlvbiIpCmdhcG1pbmRlcl95ZWFybHlfcG9wX2xvbmcKYGBgCgpOb3RlIHRoYXQgYmVjYXVzZSB0aGUgY29sdW1uIG5hbWVzIHZpb2xhdGUgdGhlIHJ1bGVzIGZvciBSIHZhcmlhYmxlcyBpbiB0aGF0IHRoZXkgc3RhcnQgd2l0aCBhIG51bWJlciwgd2UgaGF2ZSB0byBkZWFsIHdpdGggdGhlIHByb2JsZW1hdGljIG5hbWVzIGJ5IGVuY2xvc2luZyB0aGVtIHdpdGggYmFja3RpY2tzIGBgYCBgIGBgYC4KCiMjIyBMb25nIHRvIHdpZGUKClNvbWV0aW1lcyB3ZSB3b3VsZCBsaWtlIHRvIHRha2Ugb3VyIGRhdGEgZnJvbSBhIGxvbmcgZm9ybWF0IGFuZCBpbnRvIGEgd2lkZSBmb3JtYXQuIEZvciBvdXIgZXhhbXBsZSB3ZSB3YW50IHRvIGNyZWF0ZSBhIHRhYmxlIHRoYXQgd2lsbCBsZXQgdXMgc2VlIGRpc3RhbmNlcyBiZXR3ZWVuIGFuIG9yaWdpbiBhbmQgYSBkZXN0aW5hdGlvbiB3aXRoaW4gNTAwIG1pbGVzIG9mIGVhY2ggb3RoZXIuIFRvIGRvIHRoaXMgd2UncmUgZ29pbmcgdG8gdXNlIHRoZSBgZmxpZ2h0c2AgZGF0YSBmcm9tIHRoZSBgbnljZmxpZ2h0czEzYCBwYWNrYWdlLgoKVGhlIHRhYmxlIHRoYXQgd2UgY3JlYXRlIHdpbGwgZXNzZW50aWFsbHkgYmUgYSBsb29rLXVwIHRhYmxlIHRoYXQgd2lsbCBsZXQgdXMgcXVpY2tseSByZWZlcmVuY2UgdGhlIGRpc3RhbmNlIGJldHdlZW4gb3JpZ2luL2Rlc3RpbmF0aW9uIHBhaXJzLiAKCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKG55Y2ZsaWdodHMxMykKbGlicmFyeShueWNmbGlnaHRzMTMpCgpnbGltcHNlKGZsaWdodHMpCmBgYAoKCgpGaXJzdCBsZXRzIHNlbGVjdCB0aG9zZSB0aHJlZSBjb2x1bW5zIHdlIG5lZWQgYW5kIGZpbHRlciBzbyB0aGF0IHRoZSBkaXN0YW5jZSBpcyB3aXRoaW4gNTAwIG1pbGVzLgpgYGB7cn0KZmxpZ2h0c19sb25nIDwtIGZsaWdodHMgJT4lIHNlbGVjdChvcmlnaW4sIGRlc3QsIGRpc3RhbmNlKSAlPiUgCiAgZmlsdGVyKGRpc3RhbmNlIDw9IDUwMCkKYGBgCgpOZXh0IHdlIHdhbnQgcmVtb3ZlIGR1cGxpY2F0ZSByb3dzLCB0aGlzIGNhbiBiZSBkb25lIHVzaW5nIGBkaXN0aW5jdCgpYApgYGB7cn0KZmxpZ2h0c19sb25nIDwtIGZsaWdodHNfbG9uZyAlPiUgZGlzdGluY3QoKQpgYGAKCllvdSBjYW4gc2VlIHRoYXQgb3VyIGxvbmcgZGF0YSBoYXMgYG5jb2woZmxpZ2h0c19sb25nKWAgY29sdW1ucywgYW5kIGBucm93KGZsaWdodHNfbG9uZylgIHJvd3MuCgpgYGB7cn0KZGltKGZsaWdodHNfbG9uZykKYGBgCgpXZSBub3cgd2FudCB0byBtYWtlIGVhY2ggb2Ygb3VyIGRlc3RpbmF0aW9ucyBpdHMgb3duIGNvbHVtbiBhbmQgZmlsbCBpbiB0aGUgY29ycmVzcG9uZGluZyBhaXIgdGltZSBhcyB0aGUgdmFsdWUuIFRvIGRvIHRoaXMsIHdlIGNhbiB1c2UgYHBpdm90X3dpZGVyYC4gVGhlIG1haW4gYXJndW1lbnRzIGFyZSBgbmFtZXNfZnJvbWAgd2hpY2ggaXMgdGhlIGNvbHVtbiB5b3Ugd2FudCB0YWtlIHRoZSBuZXcgY29sdW1uIG5hbWVzIGZyb20sIGFuZCBgdmFsdWVzX2Zyb21gIHdoaWNoIGlzIHRoZSBjb2x1bW4gdGhhdCB3aWxsIGJlIHVzZWQgdG8gZmlsbCBpbiB0aGUgbmV3IGNvbHVtbiB2YWx1ZXMuCgpgYGB7cn0KZmxpZ2h0c19sb25nICU+JSBwaXZvdF93aWRlcihvcmlnaW4sIG5hbWVzX2Zyb20gPSAiZGVzdCIsIHZhbHVlc19mcm9tID0gImRpc3RhbmNlIikKYGBgCgoKIyMgQ29uY2x1c2lvbgo=