Objectives
This notebook will demonstrate how to:
- Use functions from the tidyverse to read and write data frames
- Implement and use tidyverse functions to wrangle data (i.e. filter,
mutate, arrange, join)
- Use R pipes (
|>
) to combine multiple operations
- Use the
apply()
function to apply functions across rows
or columns of a matrix
We’ll use the same gene expression dataset we used in the previous notebook. It is a
pre-processed astrocytoma
microarray dataset that we performed a set of differential expression analyses
on.
More tidyverse resources:
Set Up
The tidyverse is a collection of packages that are handy for general
data wrangling, analysis, and visualization. Other packages that are
specifically handy for different biological analyses are found on Bioconductor. If we want to use
a package’s functions we first need to install them.
Our RStudio Server already has the tidyverse
group of
packages installed for you. But if you needed to install it or other
packages available on CRAN, you do it using the
install.packages()
function like this:
install.packages("tidyverse")
.
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr 1.1.4 ✔ readr 2.1.5
✔ forcats 1.0.0 ✔ stringr 1.5.1
✔ ggplot2 3.5.1 ✔ tibble 3.2.1
✔ lubridate 1.9.3 ✔ tidyr 1.3.1
✔ purrr 1.0.2
── 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
Referencing a library’s function with ::
Note that if we had not imported the tidyverse set of packages using
library()
like above, and we wanted to use a tidyverse
function like read_tsv()
, we would need to tell R what
package to find this function in. To do this, we would use
::
to tell R to load in this function from the
readr
package by using readr::read_tsv()
. You
will see this ::
method of referencing libraries within
packages throughout the course. We like to use it in part to remove any
ambiguity in which version of a function we are using; it is not too
uncommon for different packages to use the same name for very different
functions!
Managing directories
Before we can import the data we need, we should double check where R
is looking for files, aka the current working
directory. We can do this by using the getwd()
function, which will tell us what folder we are in.
# Let's check what directory we are in:
getwd()
[1] "/__w/training-modules/training-modules/intro-to-R-tidyverse"
/__w/training-modules/training-modules/intro-to-R-tidyverse
For Rmd files, the working directory is wherever the file is located,
but commands executed in the console may have a different working
directory.
We will want to make a directory for our output and we will call this
directory: results
. But before we create the directory, we
should check if it already exists. We will show two ways that we can do
this.
First, we can use the dir()
function to have R list the
files in our working directory.
# Let's check what files are here
dir()
[1] "00a-rstudio_guide.Rmd"
[2] "00b-debugging_resources.Rmd"
[3] "00c-good_scientific_coding_practices.Rmd"
[4] "01-intro_to_base_R-live.Rmd"
[5] "01-intro_to_base_R.nb.html"
[6] "01-intro_to_base_R.Rmd"
[7] "02-intro_to_ggplot2-live.Rmd"
[8] "02-intro_to_ggplot2.nb.html"
[9] "02-intro_to_ggplot2.Rmd"
[10] "03-intro_to_tidyverse-live.Rmd"
[11] "03-intro_to_tidyverse.nb.html"
[12] "03-intro_to_tidyverse.Rmd"
[13] "data"
[14] "diagrams"
[15] "exercise_01-intro_to_base_R.Rmd"
[16] "exercise_02-intro_to_R.Rmd"
[17] "exercise_03a-intro_to_tidyverse.Rmd"
[18] "exercise_03b-intro_to_tidyverse.Rmd"
[19] "plots"
[20] "README.md"
[21] "screenshots"
[22] "scripts"
00a-rstudio_guide.Rmd
00b-debugging_resources.Rmd
00c-good_scientific_coding_practices.Rmd
01-intro_to_base_R-live.Rmd
01-intro_to_base_R.nb.html
01-intro_to_base_R.Rmd
02-intro_to_ggplot2-live.Rmd
02-intro_to_ggplot2.nb.html
02-intro_to_ggplot2.Rmd
03-intro_to_tidyverse-live.Rmd
03-intro_to_tidyverse.nb.html
03-intro_to_tidyverse.Rmd
data
diagrams
exercise_01-intro_to_base_R.Rmd
exercise_02-intro_to_R.Rmd
exercise_03a-intro_to_tidyverse.Rmd
exercise_03b-intro_to_tidyverse.Rmd
plots
README.md
screenshots
scripts
This shows us there is no folder called “results” yet.
If we want to more pointedly look for “results” in our working
directory we can use the dir.exists()
function.
# Check if the results directory exists
dir.exists("results")
[1] FALSE
If the above says FALSE
that means we will need to
create a results
directory. We’ve previously seen that we
can make directories in R using the base R function
dir.create()
. But we’ve also seen that this function will
throw an error if you try to create a directory that already exists,
which can be frustrating if you are re-running code! A different option
is to use the fs
package, which provides functions for you to interact with your
computer’s file system with a more consistent behavior than the base R
functions. One function from this package is
fs::dir_create()
(note that it has an underscore,
not a period), and much like the base R dir.create()
, it
creates directories. It has some other helpful features too: - It will
simply do nothing if that directory already exists; no errors, and
nothing will get overwritten - It allows creating nested
directories by default, i.e. in one call make directories inside of
other directories
Let’s go ahead and use it to create our results
directory:
# Make a directory within the working directory called 'results'
fs::dir_create("results")
After creating the results directory above, let’s re-run
dir.exists()
to see if now it exists.
# Re-check if the results directory exists
dir.exists("results")
[1] TRUE
The dir.exists()
function will not work on files
themselves. In that case, there is an analogous function called
file.exists()
.
Try using the file.exists()
function to see if the file
gene_results_GSE44971.tsv
exists in the current directory.
Use the code chunk we set up for you below. Note that in our notebooks
(and sometimes elsewhere), wherever you see a
<FILL_IN_THE_BLANK>
like in the chunk below, that is
meant for you to replace (including the angle brackets) with the correct
phrase before you run the chunk (otherwise you will get an error).
# Replace the <PUT_FILE_NAME_HERE> with the name of the file you are looking for
# Remember to use quotes to make it a character string
file.exists(<PUT_FILE_NAME_HERE>)
It doesn’t seem that file exists in our current directory,
but that doesn’t mean it doesn’t exist it all. In fact, this file is
inside the relative path data/
, so let’s check
again if the whole relative path to that file exists.
# This time, use file.path() to form your argument to file.exists()
file.exists(<PUT_PATH_TO_FILE_HERE>)
With the right relative path, we can confirm this file exists.
Read a TSV file
Declare the name of the directory where we will read in the data.
data_dir <- "data"
Although base R has functions to read in data files, the functions in
the readr
package (part of the tidyverse) are faster and
more straightforward to use so we are going to use those here. Because
the file we are reading in is a TSV (tab separated values) file we will
be using the read_tsv
function. There are analogous
functions for CSV (comma separated values) files
(read_csv()
) and other files types.
Read in the differential expression analysis results file
stats_df <- readr::read_tsv(
file.path(data_dir,
"gene_results_GSE44971.tsv")
)
Rows: 6804 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (3): ensembl_id, gene_symbol, contrast
dbl (5): log_fold_change, avg_expression, t_statistic, p_value, adj_p_value
ℹ 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.
Following the template of the previous chunk, use this chunk to read
in the file GSE44971.tsv
that is in the data
folder and save it in the variable gene_df
.
# Use this chunk to read in data from the file `GSE44971.tsv`
gene_df <- readr::read_tsv(
file.path(data_dir,
"GSE44971.tsv")
)
Rows: 20056 Columns: 59
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (1): Gene
dbl (58): GSM1094814, GSM1094815, GSM1094816, GSM1094817, GSM1094818, GSM109...
ℹ 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.
Use this chunk to explore what gene_df
looks like.
# Explore `gene_df`
What information is contained in gene_df
?
R
pipes
One nifty feature that was added to R
in version 4.1 is
the pipe: |>
. Pipes are very handy things that allow you
to funnel the result of one expression to the next, making your code
more streamlined and fluently expressing the flow of data through a
series of operations.
Note: If you are using a version of R
prior to
4.1 (or looking at older code), pipe functionality was available through
the magrittr
package, which used a pipe that looked like
this: %>%
. That pipe was the inspiration for the native
R pipe we are using here. While there are some minor differences, you
can mostly treat them interchangeably as long as you load the
magrittr
package or dplyr
, which also loads
that version of the pipe.
For example, the output from this:
filter(stats_df, contrast == "male_female")
…is the same as the output from this:
stats_df |> filter(contrast == "male_female")
This can make your code cleaner and easier to follow a series of
related commands. Let’s look at an example with our stats of of how the
same functions look with or without pipes:
Example 1: without pipes:
stats_arranged <- arrange(stats_df, t_statistic)
stats_filtered <- filter(stats_arranged, avg_expression > 50)
stats_nopipe <- select(stats_filtered, contrast, log_fold_change, p_value)
UGH, we have to keep track of all of those different intermediate
data frames and type their names so many times here! We could maybe
streamline things by using the same variable name at each stage, but
even then there is a lot of extra typing, and it is easy to get confused
about what has been done where. It’s annoying and makes it harder for
people to read.
Example 2: Same result as 1 but with pipes!
# Example of the same modifications as above but with pipes!
stats_pipe <- stats_df |>
arrange(t_statistic) |>
filter(avg_expression > 50) |>
select(contrast, log_fold_change, p_value)
What the |>
(pipe) is doing here is feeding the
result of the expression on its left into the first argument of the next
function (to its right, or on the next line here). We can then skip that
first argument (the data in these cases), and move right on to the part
we care about at that step: what we are arranging, filtering, or
selecting in this case. The key insight that makes the pipe work here is
to recognize that each of these functions (arrange
,
filter
, and select
) are fundamental
dplyr
(a tidyverse package) functions which work as “data
in, data out.” In other words, these functions operate on data frames,
and return data frames; you give them a data frame, and they give you
back a data frame. Because these functions all follow a “data in, data
out” framework, we can chain them together with pipe and send data all
the way through the…pipeline!
Let’s double check that these versions with and without pipe yield
the same solution by using the base R function
all.equal()
.
all.equal(stats_nopipe, stats_pipe)
[1] TRUE
all.equal()
is letting us know that these two objects
are the same.
Now that hopefully you are convinced that the tidyverse can help you
make your code neater and easier to use and read, let’s go through some
of the popular tidyverse functions and so we can create pipelines like
this.
Common tidyverse functions
Let’s say we wanted to filter this gene expression dataset to
particular sample groups. In order to do this, we would use the function
filter()
as well as a logic statement (usually one that
refers to a column or columns in the data frame).
# Here let's filter stats_df to only keep the gene_symbol "SNCA"
stats_df |>
filter(gene_symbol == "SNCA")
We can use filter()
similarly for numeric
statements.
# Here let's filter the data to rows with average expression values above 50
stats_df |>
filter(avg_expression > 50)
We can apply multiple filters at once, which will require all of them
to be satisfied for every row in the results:
# filter to highly expressed genes with contrast "male_female"
stats_df |>
filter(contrast == "male_female",
avg_expression > 50)
When we are filtering, the %in%
operator can come in
handy if we have multiple items we would like to match. Let’s take a
look at what using %in%
does.
genes_of_interest <- c("SNCA", "CDKN1A")
# Are these genes present in the `gene_symbol` column in stats_df?
stats_df$gene_symbol %in% genes_of_interest
%in%
returns a logical vector that now we can use in
dplyr::filter
.
# filter to keep only genes of interest
stats_df |>
filter(gene_symbol %in% c("SNCA", "CDKN1A"))
Let’s return to our first filter()
and build on to it.
This time, let’s keep only some of the columns from the data frame using
the select()
function. Let’s also save this as a new data
frame called stats_filtered_df
.
# filter to highly expressed "male_female"
# and select gene_symbol, log_fold_change and t_statistic
stats_filtered_df <- stats_df |>
filter(contrast == "male_female",
avg_expression > 50) |>
select(log_fold_change, t_statistic)
Let’s say we wanted to arrange this dataset so that the genes are
arranged by the smallest p values to the largest. In order to do this,
we would use the function arrange()
as well as the column
we would like to sort by (in this case p_value
).
stats_df |>
arrange(p_value)
What if we want to sort from largest to smallest? Like if we want to
see the genes with the highest average expression? We can use the same
function, but instead use the desc()
function and now we
are using avg_expression
column.
# arrange descending by avg_expression
stats_df |>
arrange(desc(avg_expression))
What if we would like to create a new column of values? For that we
use mutate()
function.
stats_df |>
mutate(log10_p_value = -log10(p_value))
What if we want to obtain summary statistics for a column or columns?
The summarize
function allows us to calculate summary
statistics for a column. Here we will use summarize to calculate two
summary statistics of log-fold change across all genes: mean (function
mean()
) and standard deviation (function
sd()
).
stats_df |>
summarize(mean(log_fold_change),
sd(log_fold_change))
What if we’d like to obtain a summary statistics but have them for
various groups? Conveniently named, there’s a function called
group_by()
that seamlessly allows us to do this. Also note
that group_by()
allows us to group by multiple variables at
a time if you want to.
stats_summary_df <- stats_df |>
group_by(contrast) |>
summarize(mean(log_fold_change),
sd(log_fold_change))
Let’s look at a preview of what we made:
stats_summary_df
Here we have the mean log fold change expression per each contrast we
made.
A brief intro to the apply
family of functions
In base R, the apply
family of functions can be an
alternative methods for performing transformations across a data frame,
matrix or other object structures.
One of this family is (shockingly) the function apply()
,
which operates on matrices.
A matrix is similar to a data frame in that it is a rectangular table
of data, but it has an additional constraint: rather than each column
having a type, ALL data in a matrix has the same type.
The first argument to apply()
is the data object we want
to work on. The third argument is the function we will apply to each row
or column of the data object. The second argument in specifies whether
we are applying the function across rows or across columns (1 for rows,
2 for columns).
Remember that gene_df
is a gene x sample gene expression
data frame that has columns of two different types, character and
numeric. Converting it to a matrix will require us to make them all the
same type. We can coerce it into a matrix using
as.matrix()
, in which case R will pick a type that it can
convert everything to. What does it choose?
# Coerce `gene_df` into a matrix
gene_matrix <- as.matrix(gene_df)
# Explore the structure of the `gene_matrix` object
str(gene_matrix)
chr [1:20056, 1:59] "ENSG00000000003" "ENSG00000000005" "ENSG00000000419" ...
- attr(*, "dimnames")=List of 2
..$ : NULL
..$ : chr [1:59] "Gene" "GSM1094814" "GSM1094815" "GSM1094816" ...
While that worked, it is rare that we want numbers converted to text,
so we are going to select only the columns with numeric values before
converting it to a matrix. We can do this most easily by removing the
first column, which contains the gene names stored as character
values.
# Let's save a new matrix object names `gene_num_matrix` containing only
# the numeric values
gene_num_matrix <- as.matrix(gene_df[, -1])
# Explore the structure of the `gene_num_matrix` object
str(gene_num_matrix)
num [1:20056, 1:58] 9.5951 -0.0436 8.5246 1.6013 0.6189 ...
- attr(*, "dimnames")=List of 2
..$ : NULL
..$ : chr [1:58] "GSM1094814" "GSM1094815" "GSM1094816" "GSM1094817" ...
Why do we have a [, -1]
after gene_df
in
the above chunk?
Now that the matrix is all numbers, we can do things like calculate
the column or row statistics using apply()
.
# Calculate row means
gene_means <- apply(gene_num_matrix, 1, mean) # Notice we are using 1 here
# How long will `gene_means` be?
length(gene_means)
[1] 20056
Note that we can obtain the same results if we select just the
columns with numeric values from the gene_df
data frame.
This allows R to do the as.matrix() coercion automatically, and can be a
handy shortcut if you have a mostly numeric data frame.
# Calculate row means using the `gene_df` object after removing the character column
# apply() converts this to a matrix internally
gene_means_from_df <- apply(gene_df[, -1], 1, mean)
# Let's check that the two gene means objects are equal
all.equal(gene_means, gene_means_from_df)
[1] TRUE
Now let’s investigate the same set up, but use 2 to
apply
over the columns of our matrix.
# Calculate sample means
sample_means <- apply(gene_num_matrix, 2, mean) # Notice we use 2 here
# How long will `sample_means` be?
length(sample_means)
[1] 58
We can put the gene names back into the numeric matrix object by
assigning them as rownames.
# Assign the gene names from gene_df$Gene to the `gene_num_matrix` object using
# the `rownames()` function
rownames(gene_num_matrix) <- gene_df$Gene
# Explore the `gene_num_matrix` object
head(gene_num_matrix)
GSM1094814 GSM1094815 GSM1094816 GSM1094817 GSM1094818
ENSG00000000003 9.59510150 8.4785070 12.6802129 8.677614838 10.75552946
GSM1094819 GSM1094820 GSM1094821 GSM1094822 GSM1094823
ENSG00000000003 6.37470691 9.10028584 7.3546860 8.51847190 9.4216113
GSM1094824 GSM1094825 GSM1094826 GSM1094827 GSM1094828
ENSG00000000003 5.0239629 7.89737460 8.1126876 7.03444640 9.6984918
GSM1094829 GSM1094830 GSM1094831 GSM1094832 GSM1094833
ENSG00000000003 13.98689230 10.5868331 7.6836223 8.3862587 11.18932763
GSM1094834 GSM1094835 GSM1094836 GSM1094837 GSM1094838
ENSG00000000003 9.7562003 9.6984918 10.56891510 9.9391025 7.8738131
GSM1094839 GSM1094840 GSM1094841 GSM1094842 GSM1094843
ENSG00000000003 8.6311353 8.58077557 9.1579585 6.3317019 10.1939387
GSM1094844 GSM1094845 GSM1094846 GSM1094847 GSM1094848
ENSG00000000003 10.44364159 9.62435722 16.05075944 6.9334508 8.55180910
GSM1094849 GSM1094850 GSM1094851 GSM1094852 GSM1094853
ENSG00000000003 9.29497760 7.5027098 6.9593119 8.33588532 8.16826110
GSM1094854 GSM1094855 GSM1094856 GSM1094857 GSM1094858
ENSG00000000003 9.8020077 7.92580451 8.5122426 8.5300217 6.45774124
GSM1094859 GSM1094860 GSM1094861 GSM1094862 GSM1094863
ENSG00000000003 8.06834906 6.29704946 9.59510150 8.2198571 6.0207988
GSM1094864 GSM1094865 GSM1094866 GSM1094867 GSM1094868
ENSG00000000003 10.60783176 10.23536609 9.031964212 7.66629540 1.06494502
GSM1094869 GSM1094870 GSM1094871
ENSG00000000003 1.0408332 1.7262079 1.0292255
[ reached getOption("max.print") -- omitted 5 rows ]
Row names like this can be very convenient for keeping matrices
organized, but row names (and column names) can be lost or misordered if
you are not careful, especially during input and output, so treat them
with care.
Although the apply
functions may not be as easy to use
as the tidyverse functions, for some applications, apply
methods can be better suited. In this workshop, we will not delve too
deeply into the various other apply functions (tapply()
,
lapply()
, etc.) but you can read more information about
them here.
The dplyr::join functions
Let’s say we have a scenario where we have two data frames that we
would like to combine. Recall that stats_df
and
gene_df
are data frames that contain information about some
of the same genes. The dplyr::join
family of functions are useful for various scenarios of combining
data frames. For a visual explanation, the tidyexplain
project has some helpful
animations of joins.
For now, we will focus on inner_join()
, which will
combine data frames by only keeping information about matching rows that
are in both data frames. We need to use the by
argument to
designate what column(s) should be used as a key to match the data
frames. In this case we want to match the gene information between the
two, so we will specify that we want to compare values in the
ensembl_id
column from stats_df
to the
Gene
column from gene_df
.
stats_df |>
# Join based on their shared column
# Called ensembl_id in stats_df and called Gene in gene_df
inner_join(gene_df, by = c('ensembl_id' = 'Gene'))
Save data to files
Save to TSV files
Let’s write some of the data frames we created to a file. To do this,
we can use the readr
library of write_()
functions. The first argument of write_tsv()
is the data we
want to write, and the second argument is a character string that
describes the path to the new file we would like to create. Remember
that we created a results
directory to put our output in,
but if we want to save our data to a directory other than our working
directory, we need to specify this. This is what we will use the
file.path()
function for. Let’s look in a bit more detail
what file.path()
does, by examining the results of the
function in the examples below.
# Which of these file paths is what we want to use to save our data to the
# results directory we created at the beginning of this notebook?
file.path("docker-install", "stats_summary.tsv")
[1] "docker-install/stats_summary.tsv"
docker-install/stats_summary.tsv
file.path("results", "stats_summary.tsv")
[1] "results/stats_summary.tsv"
results/stats_summary.tsv
file.path("stats_summary.tsv", "results")
[1] "stats_summary.tsv/results"
stats_summary.tsv/results
Replace <NEW_FILE_PATH>
below with the
file.path()
statement from above that will successfully
save our file to the results
folder.
# Write our data frame to a TSV file
readr::write_tsv(stats_summary_df, <NEW_FILE_PATH>)
Check in your results
directory to see if your new file
has successfully saved.
Save to RDS files
For this example we have been working with data frames, which are
conveniently represented as TSV or CSV tables. However, in other
situations we may want to save more complicated or very large data
structures, RDS (R Data Serialized/Single) files may be a better option
for saving our data. RDS is R’s special file format for holding data
exactly as you have it in your R environment. RDS files can also be
compressed, meaning they will take up less space on your computer. Let’s
save our data to an RDS file in our results
folder. You
will need to replace the .tsv
with .RDS
, but
you can use what we determined as our file path for the last chunk as
your template.
# Write your object to an RDS file
readr::write_rds(stats_summary_df, <PUT_CORRECT_FILE_PATH_HERE>)
Read an RDS file
Since now you have learned the readr
functions:
read_tsv()
, write_tsv()
, and now,
write_rds()
, what do you suppose the function you will need
to read your RDS file is called? Use that function here to re-import
your data in the chunk we set up for you below.
# Read in your RDS file
reimport_df <- <PUT_FUNCTION_NAME>(file.path("results", "stats_summary.RDS"))
As is good practice, we will end this session by printing out our
session info.
Session Info
# Print out the versions and packages we are using in this session
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS
Matrix products: default
BLAS: /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so; LAPACK version 3.10.0
locale:
[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
[7] LC_PAPER=en_US.UTF-8 LC_NAME=C
[9] LC_ADDRESS=C LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
time zone: Etc/UTC
tzcode source: system (glibc)
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] lubridate_1.9.3 forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4
[5] purrr_1.0.2 readr_2.1.5 tidyr_1.3.1 tibble_3.2.1
[9] ggplot2_3.5.1 tidyverse_2.0.0 optparse_1.7.5
loaded via a namespace (and not attached):
[1] sass_0.4.9 utf8_1.2.4 generics_0.1.3 stringi_1.8.3
[5] hms_1.1.3 digest_0.6.35 magrittr_2.0.3 evaluate_0.23
[9] grid_4.4.1 timechange_0.3.0 fastmap_1.1.1 jsonlite_1.8.8
[13] fansi_1.0.6 scales_1.3.0 getopt_1.20.4 jquerylib_0.1.4
[17] cli_3.6.2 crayon_1.5.2 rlang_1.1.3 bit64_4.0.5
[21] munsell_0.5.1 withr_3.0.0 cachem_1.0.8 yaml_2.3.8
[25] parallel_4.4.1 tools_4.4.1 tzdb_0.4.0 colorspace_2.1-0
[29] vctrs_0.6.5 R6_2.5.1 lifecycle_1.0.4 bit_4.0.5
[33] fs_1.6.4 vroom_1.6.5 pkgconfig_2.0.3 pillar_1.9.0
[37] bslib_0.7.0 gtable_0.3.5 glue_1.7.0 xfun_0.43
[41] tidyselect_1.2.1 knitr_1.46 htmltools_0.5.8.1 rmarkdown_2.26
[45] compiler_4.4.1
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIHRpZHl2ZXJzZSIKYXV0aG9yOiAiQ0NETCBmb3IgQUxTRiIKZGF0ZTogMjAyMQpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgoKIyMgT2JqZWN0aXZlcwoKVGhpcyBub3RlYm9vayB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0bzoKCi0gVXNlIGZ1bmN0aW9ucyBmcm9tIHRoZSB0aWR5dmVyc2UgdG8gcmVhZCBhbmQgd3JpdGUgZGF0YSBmcmFtZXMKLSBJbXBsZW1lbnQgYW5kIHVzZSB0aWR5dmVyc2UgZnVuY3Rpb25zIHRvIHdyYW5nbGUgZGF0YSAoaS5lLiBmaWx0ZXIsIG11dGF0ZSwgYXJyYW5nZSwgam9pbikKLSBVc2UgUiBwaXBlcyAoYHw+YCkgdG8gY29tYmluZSBtdWx0aXBsZSBvcGVyYXRpb25zCi0gVXNlIHRoZSBgYXBwbHkoKWAgZnVuY3Rpb24gdG8gYXBwbHkgZnVuY3Rpb25zIGFjcm9zcyByb3dzIG9yIGNvbHVtbnMgb2YgYSBtYXRyaXgKCi0tLQoKV2UnbGwgdXNlIHRoZSBzYW1lIGdlbmUgZXhwcmVzc2lvbiBkYXRhc2V0IHdlIHVzZWQgaW4gdGhlIFtwcmV2aW91cyBub3RlYm9va10oLi8wMi1pbnRyb190b19nZ3Bsb3QyLlJtZCkuCkl0IGlzIGEgcHJlLXByb2Nlc3NlZCBbYXN0cm9jeXRvbWEgbWljcm9hcnJheSBkYXRhc2V0XShodHRwczovL3d3dy5yZWZpbmUuYmlvL2V4cGVyaW1lbnRzL0dTRTQ0OTcxL2dlbmUtZXhwcmVzc2lvbi1kYXRhLWZyb20tcGlsb2N5dGljLWFzdHJvY3l0b21hLXR1bW91ci1zYW1wbGVzLWFuZC1ub3JtYWwtY2VyZWJlbGx1bS1jb250cm9scykgdGhhdCB3ZSBwZXJmb3JtZWQgYSBzZXQgb2YgW2RpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2VzIG9uXSguL3NjcmlwdHMvMDAtc2V0dXAtaW50cm8tdG8tUi5SKS4KCioqTW9yZSB0aWR5dmVyc2UgcmVzb3VyY2VzOioqCgotIFtSIGZvciBEYXRhIFNjaWVuY2VdKGh0dHBzOi8vcjRkcy5oYWRsZXkubnovKQotIFt0aWR5dmVyc2UgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly90aWR5dmVyc2Uub3JnLykKICAtIFtgZHBseXJgIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy8pCiAgLSBbYHJlYWRyYCBkb2N1bWVudGF0aW9uXShodHRwczovL3JlYWRyLnRpZHl2ZXJzZS5vcmcvKQotIFtDaGVhdHNoZWV0IG9mIHRpZHl2ZXJzZSBkYXRhIHRyYW5zZm9ybWF0aW9uXShodHRwczovL2dpdGh1Yi5jb20vcnN0dWRpby9jaGVhdHNoZWV0cy9yYXcvbWFpbi9kYXRhLXRyYW5zZm9ybWF0aW9uLnBkZikKLSBbT25saW5lIHRpZHl2ZXJzZSBib29rIGNoYXB0ZXJdKGh0dHBzOi8vcHJpdmVmbC5naXRodWIuaW8vYWR2cjM4Ym9vay90aWR5dmVyc2UuaHRtbCkKCiMjIFNldCBVcAoKVGhlIHRpZHl2ZXJzZSBpcyBhIGNvbGxlY3Rpb24gb2YgcGFja2FnZXMgdGhhdCBhcmUgaGFuZHkgZm9yIGdlbmVyYWwgZGF0YSB3cmFuZ2xpbmcsIGFuYWx5c2lzLCBhbmQgdmlzdWFsaXphdGlvbi4KT3RoZXIgcGFja2FnZXMgdGhhdCBhcmUgc3BlY2lmaWNhbGx5IGhhbmR5IGZvciBkaWZmZXJlbnQgYmlvbG9naWNhbCBhbmFseXNlcyBhcmUgZm91bmQgb24gW0Jpb2NvbmR1Y3Rvcl0oaHR0cHM6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy8pLgpJZiB3ZSB3YW50IHRvIHVzZSBhIHBhY2thZ2UncyBmdW5jdGlvbnMgd2UgZmlyc3QgbmVlZCB0byBpbnN0YWxsIHRoZW0uCgpPdXIgUlN0dWRpbyBTZXJ2ZXIgYWxyZWFkeSBoYXMgdGhlIGB0aWR5dmVyc2VgIGdyb3VwIG9mIHBhY2thZ2VzIGluc3RhbGxlZCBmb3IgeW91LgpCdXQgaWYgeW91IG5lZWRlZCB0byBpbnN0YWxsIGl0IG9yIG90aGVyIHBhY2thZ2VzIGF2YWlsYWJsZSBvbiBDUkFOLCB5b3UgZG8gaXQgdXNpbmcgdGhlIGBpbnN0YWxsLnBhY2thZ2VzKClgIGZ1bmN0aW9uIGxpa2UgdGhpczogYGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpYC4KCmBgYHtyIHRpZHl2ZXJzZX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyMjIFJlZmVyZW5jaW5nIGEgbGlicmFyeSdzIGZ1bmN0aW9uIHdpdGggYDo6YAoKTm90ZSB0aGF0IGlmIHdlIGhhZCBub3QgaW1wb3J0ZWQgdGhlIHRpZHl2ZXJzZSBzZXQgb2YgcGFja2FnZXMgdXNpbmcgYGxpYnJhcnkoKWAgbGlrZSBhYm92ZSwgYW5kIHdlIHdhbnRlZCB0byB1c2UgYSB0aWR5dmVyc2UgZnVuY3Rpb24gbGlrZSBgcmVhZF90c3YoKWAsIHdlIHdvdWxkIG5lZWQgdG8gdGVsbCBSIHdoYXQgcGFja2FnZSB0byBmaW5kIHRoaXMgZnVuY3Rpb24gaW4uClRvIGRvIHRoaXMsIHdlIHdvdWxkIHVzZSBgOjpgIHRvIHRlbGwgUiB0byBsb2FkIGluIHRoaXMgZnVuY3Rpb24gZnJvbSB0aGUgYHJlYWRyYCBwYWNrYWdlIGJ5IHVzaW5nIGByZWFkcjo6cmVhZF90c3YoKWAuCllvdSB3aWxsIHNlZSB0aGlzIGA6OmAgbWV0aG9kIG9mIHJlZmVyZW5jaW5nIGxpYnJhcmllcyB3aXRoaW4gcGFja2FnZXMgdGhyb3VnaG91dCB0aGUgY291cnNlLgpXZSBsaWtlIHRvIHVzZSBpdCBpbiBwYXJ0IHRvIHJlbW92ZSBhbnkgYW1iaWd1aXR5IGluIHdoaWNoIHZlcnNpb24gb2YgYSBmdW5jdGlvbiB3ZSBhcmUgdXNpbmc7IGl0IGlzIG5vdCB0b28gdW5jb21tb24gZm9yIGRpZmZlcmVudCBwYWNrYWdlcyB0byB1c2UgdGhlIHNhbWUgbmFtZSBmb3IgdmVyeSBkaWZmZXJlbnQgZnVuY3Rpb25zIQoKIyMgTWFuYWdpbmcgZGlyZWN0b3JpZXMKCkJlZm9yZSB3ZSBjYW4gaW1wb3J0IHRoZSBkYXRhIHdlIG5lZWQsIHdlIHNob3VsZCBkb3VibGUgY2hlY2sgd2hlcmUgUiBpcyBsb29raW5nIGZvciBmaWxlcywgYWthIHRoZSBjdXJyZW50ICoqd29ya2luZyBkaXJlY3RvcnkqKi4KV2UgY2FuIGRvIHRoaXMgYnkgdXNpbmcgdGhlIGBnZXR3ZCgpYCBmdW5jdGlvbiwgd2hpY2ggd2lsbCB0ZWxsIHVzIHdoYXQgZm9sZGVyIHdlIGFyZSBpbi4KCmBgYHtyIHdvcmtpbmdkaXIsIGxpdmUgPSBUUlVFfQojIExldCdzIGNoZWNrIHdoYXQgZGlyZWN0b3J5IHdlIGFyZSBpbjoKZ2V0d2QoKQpgYGAKCkZvciBSbWQgZmlsZXMsIHRoZSB3b3JraW5nIGRpcmVjdG9yeSBpcyB3aGVyZXZlciB0aGUgZmlsZSBpcyBsb2NhdGVkLCBidXQgY29tbWFuZHMgZXhlY3V0ZWQgaW4gdGhlIGNvbnNvbGUgbWF5IGhhdmUgYSBkaWZmZXJlbnQgd29ya2luZyBkaXJlY3RvcnkuCgpXZSB3aWxsIHdhbnQgdG8gbWFrZSBhIGRpcmVjdG9yeSBmb3Igb3VyIG91dHB1dCBhbmQgd2Ugd2lsbCBjYWxsIHRoaXMgZGlyZWN0b3J5OiBgcmVzdWx0c2AuCkJ1dCBiZWZvcmUgd2UgY3JlYXRlIHRoZSBkaXJlY3RvcnksIHdlIHNob3VsZCBjaGVjayBpZiBpdCBhbHJlYWR5IGV4aXN0cy4KV2Ugd2lsbCBzaG93IHR3byB3YXlzIHRoYXQgd2UgY2FuIGRvIHRoaXMuCgpGaXJzdCwgd2UgY2FuIHVzZSB0aGUgYGRpcigpYCBmdW5jdGlvbiB0byBoYXZlIFIgbGlzdCB0aGUgZmlsZXMgaW4gb3VyIHdvcmtpbmcgZGlyZWN0b3J5LgoKYGBge3J9CiMgTGV0J3MgY2hlY2sgd2hhdCBmaWxlcyBhcmUgaGVyZQpkaXIoKQpgYGAKClRoaXMgc2hvd3MgdXMgdGhlcmUgaXMgbm8gZm9sZGVyIGNhbGxlZCAicmVzdWx0cyIgeWV0LgoKSWYgd2Ugd2FudCB0byBtb3JlIHBvaW50ZWRseSBsb29rIGZvciAicmVzdWx0cyIgaW4gb3VyIHdvcmtpbmcgZGlyZWN0b3J5IHdlIGNhbiB1c2UgdGhlIGBkaXIuZXhpc3RzKClgIGZ1bmN0aW9uLgoKYGBge3IgY2hlY2stZGlyLCBsaXZlID0gVFJVRX0KIyBDaGVjayBpZiB0aGUgcmVzdWx0cyBkaXJlY3RvcnkgZXhpc3RzCmRpci5leGlzdHMoInJlc3VsdHMiKQpgYGAKCklmIHRoZSBhYm92ZSBzYXlzIGBGQUxTRWAgdGhhdCBtZWFucyB3ZSB3aWxsIG5lZWQgdG8gY3JlYXRlIGEgYHJlc3VsdHNgIGRpcmVjdG9yeS4KV2UndmUgcHJldmlvdXNseSBzZWVuIHRoYXQgd2UgY2FuIG1ha2UgZGlyZWN0b3JpZXMgaW4gUiB1c2luZyB0aGUgYmFzZSBSIGZ1bmN0aW9uIGBkaXIuY3JlYXRlKClgLgpCdXQgd2UndmUgYWxzbyBzZWVuIHRoYXQgdGhpcyBmdW5jdGlvbiB3aWxsIHRocm93IGFuIGVycm9yIGlmIHlvdSB0cnkgdG8gY3JlYXRlIGEgZGlyZWN0b3J5IHRoYXQgYWxyZWFkeSBleGlzdHMsIHdoaWNoIGNhbiBiZSBmcnVzdHJhdGluZyBpZiB5b3UgYXJlIHJlLXJ1bm5pbmcgY29kZSEKQSBkaWZmZXJlbnQgb3B0aW9uIGlzIHRvIHVzZSB0aGUgW2Bmc2BdKGh0dHBzOi8vZnMuci1saWIub3JnLykgcGFja2FnZSwgd2hpY2ggcHJvdmlkZXMgZnVuY3Rpb25zIGZvciB5b3UgdG8gaW50ZXJhY3Qgd2l0aCB5b3VyIGNvbXB1dGVyJ3MgZmlsZSBzeXN0ZW0gd2l0aCBhIG1vcmUgY29uc2lzdGVudCBiZWhhdmlvciB0aGFuIHRoZSBiYXNlIFIgZnVuY3Rpb25zLgpPbmUgZnVuY3Rpb24gZnJvbSB0aGlzIHBhY2thZ2UgaXMgYGZzOjpkaXJfY3JlYXRlKClgIChub3RlIHRoYXQgaXQgaGFzIGFuIF91bmRlcnNjb3JlXywgbm90IGEgcGVyaW9kKSwgYW5kIG11Y2ggbGlrZSB0aGUgYmFzZSBSIGBkaXIuY3JlYXRlKClgLCBpdCBjcmVhdGVzIGRpcmVjdG9yaWVzLiAKSXQgaGFzIHNvbWUgb3RoZXIgaGVscGZ1bCBmZWF0dXJlcyB0b286Ci0gSXQgd2lsbCBzaW1wbHkgZG8gbm90aGluZyBpZiB0aGF0IGRpcmVjdG9yeSBhbHJlYWR5IGV4aXN0czsgbm8gZXJyb3JzLCBhbmQgbm90aGluZyB3aWxsIGdldCBvdmVyd3JpdHRlbgotIEl0IGFsbG93cyBjcmVhdGluZyBfbmVzdGVkXyBkaXJlY3RvcmllcyBieSBkZWZhdWx0LCBpLmUuIGluIG9uZSBjYWxsIG1ha2UgZGlyZWN0b3JpZXMgaW5zaWRlIG9mIG90aGVyIGRpcmVjdG9yaWVzCgpMZXQncyBnbyBhaGVhZCBhbmQgdXNlIGl0IHRvIGNyZWF0ZSBvdXIgYHJlc3VsdHNgIGRpcmVjdG9yeToKCmBgYHtyIGNyZWF0ZS1kaXIsIGxpdmUgPSBUUlVFfQojIE1ha2UgYSBkaXJlY3Rvcnkgd2l0aGluIHRoZSB3b3JraW5nIGRpcmVjdG9yeSBjYWxsZWQgJ3Jlc3VsdHMnCmZzOjpkaXJfY3JlYXRlKCJyZXN1bHRzIikKYGBgCgpBZnRlciBjcmVhdGluZyB0aGUgcmVzdWx0cyBkaXJlY3RvcnkgYWJvdmUsIGxldCdzIHJlLXJ1biBgZGlyLmV4aXN0cygpYCB0byBzZWUgaWYgbm93IGl0IGV4aXN0cy4KCmBgYHtyIGNoZWNrLWRpci1hZ2FpbiwgbGl2ZSA9IFRSVUV9CiMgUmUtY2hlY2sgaWYgdGhlIHJlc3VsdHMgZGlyZWN0b3J5IGV4aXN0cwpkaXIuZXhpc3RzKCJyZXN1bHRzIikKYGBgCgpUaGUgYGRpci5leGlzdHMoKWAgZnVuY3Rpb24gd2lsbCBub3Qgd29yayBvbiBmaWxlcyB0aGVtc2VsdmVzLgpJbiB0aGF0IGNhc2UsIHRoZXJlIGlzIGFuIGFuYWxvZ291cyBmdW5jdGlvbiBjYWxsZWQgYGZpbGUuZXhpc3RzKClgLgoKVHJ5IHVzaW5nIHRoZSBgZmlsZS5leGlzdHMoKWAgZnVuY3Rpb24gdG8gc2VlIGlmIHRoZSBmaWxlIGBnZW5lX3Jlc3VsdHNfR1NFNDQ5NzEudHN2YCBleGlzdHMgaW4gdGhlIGN1cnJlbnQgZGlyZWN0b3J5LgpVc2UgdGhlIGNvZGUgY2h1bmsgd2Ugc2V0IHVwIGZvciB5b3UgYmVsb3cuCk5vdGUgdGhhdCBpbiBvdXIgbm90ZWJvb2tzIChhbmQgc29tZXRpbWVzIGVsc2V3aGVyZSksIHdoZXJldmVyIHlvdSBzZWUgYSBgPEZJTExfSU5fVEhFX0JMQU5LPmAgbGlrZSBpbiB0aGUgY2h1bmsgYmVsb3csIHRoYXQgaXMgbWVhbnQgZm9yIHlvdSB0byByZXBsYWNlIChpbmNsdWRpbmcgdGhlIGFuZ2xlIGJyYWNrZXRzKSB3aXRoIHRoZSBjb3JyZWN0IHBocmFzZSBiZWZvcmUgeW91IHJ1biB0aGUgY2h1bmsgKG90aGVyd2lzZSB5b3Ugd2lsbCBnZXQgYW4gZXJyb3IpLgoKYGBge3IgZmlsZS1jaGVjaywgZXZhbD1GQUxTRX0KIyBSZXBsYWNlIHRoZSA8UFVUX0ZJTEVfTkFNRV9IRVJFPiB3aXRoIHRoZSBuYW1lIG9mIHRoZSBmaWxlIHlvdSBhcmUgbG9va2luZyBmb3IKIyBSZW1lbWJlciB0byB1c2UgcXVvdGVzIHRvIG1ha2UgaXQgYSBjaGFyYWN0ZXIgc3RyaW5nCmZpbGUuZXhpc3RzKDxQVVRfRklMRV9OQU1FX0hFUkU+KQpgYGAKCkl0IGRvZXNuJ3Qgc2VlbSB0aGF0IGZpbGUgZXhpc3RzIGluIG91ciBfY3VycmVudCBkaXJlY3RvcnlfLCBidXQgdGhhdCBkb2Vzbid0IG1lYW4gaXQgZG9lc24ndCBleGlzdCBpdCBhbGwuCkluIGZhY3QsIHRoaXMgZmlsZSBpcyBpbnNpZGUgdGhlIF9yZWxhdGl2ZSBwYXRoXyBgZGF0YS9gLCBzbyBsZXQncyBjaGVjayBhZ2FpbiBpZiB0aGUgd2hvbGUgcmVsYXRpdmUgcGF0aCB0byB0aGF0IGZpbGUgZXhpc3RzLgoKCmBgYHtyIGZpbGUtY2hlY2stcGF0aCwgZXZhbD1GQUxTRX0KIyBUaGlzIHRpbWUsIHVzZSBmaWxlLnBhdGgoKSB0byBmb3JtIHlvdXIgYXJndW1lbnQgdG8gZmlsZS5leGlzdHMoKQpmaWxlLmV4aXN0cyg8UFVUX1BBVEhfVE9fRklMRV9IRVJFPikKYGBgCgpXaXRoIHRoZSByaWdodCByZWxhdGl2ZSBwYXRoLCB3ZSBjYW4gY29uZmlybSB0aGlzIGZpbGUgZXhpc3RzLgoKIyMjIyBSZWFkIGEgVFNWIGZpbGUKCkRlY2xhcmUgdGhlIG5hbWUgb2YgdGhlIGRpcmVjdG9yeSB3aGVyZSB3ZSB3aWxsIHJlYWQgaW4gdGhlIGRhdGEuCgpgYGB7cn0KZGF0YV9kaXIgPC0gImRhdGEiCmBgYAoKQWx0aG91Z2ggYmFzZSBSIGhhcyBmdW5jdGlvbnMgdG8gcmVhZCBpbiBkYXRhIGZpbGVzLCB0aGUgZnVuY3Rpb25zIGluIHRoZSBgcmVhZHJgIHBhY2thZ2UgKHBhcnQgb2YgdGhlIHRpZHl2ZXJzZSkgYXJlIGZhc3RlciBhbmQgbW9yZSBzdHJhaWdodGZvcndhcmQgdG8gdXNlIHNvIHdlIGFyZSBnb2luZyB0byB1c2UgdGhvc2UgaGVyZS4KQmVjYXVzZSB0aGUgZmlsZSB3ZSBhcmUgcmVhZGluZyBpbiBpcyBhIFRTViAodGFiIHNlcGFyYXRlZCB2YWx1ZXMpIGZpbGUgd2Ugd2lsbCBiZSB1c2luZyB0aGUgYHJlYWRfdHN2YCBmdW5jdGlvbi4KVGhlcmUgYXJlIGFuYWxvZ291cyBmdW5jdGlvbnMgZm9yIENTViAoY29tbWEgc2VwYXJhdGVkIHZhbHVlcykgZmlsZXMgKGByZWFkX2NzdigpYCkgYW5kIG90aGVyIGZpbGVzIHR5cGVzLgoKIyMgUmVhZCBpbiB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgcmVzdWx0cyBmaWxlCgpgYGB7ciByZWFkLXJlc3VsdHN9CnN0YXRzX2RmIDwtIHJlYWRyOjpyZWFkX3RzdigKICBmaWxlLnBhdGgoZGF0YV9kaXIsCiAgICAgICAgICAgICJnZW5lX3Jlc3VsdHNfR1NFNDQ5NzEudHN2IikKICApCmBgYAoKRm9sbG93aW5nIHRoZSB0ZW1wbGF0ZSBvZiB0aGUgcHJldmlvdXMgY2h1bmssIHVzZSB0aGlzIGNodW5rIHRvIHJlYWQgaW4gdGhlIGZpbGUgYEdTRTQ0OTcxLnRzdmAgdGhhdCBpcyBpbiB0aGUgYGRhdGFgIGZvbGRlciBhbmQgc2F2ZSBpdCBpbiB0aGUgdmFyaWFibGUgYGdlbmVfZGZgLgoKYGBge3IgcmVhZC1leHByLCBsaXZlID0gVFJVRX0KIyBVc2UgdGhpcyBjaHVuayB0byByZWFkIGluIGRhdGEgZnJvbSB0aGUgZmlsZSBgR1NFNDQ5NzEudHN2YApnZW5lX2RmIDwtIHJlYWRyOjpyZWFkX3RzdigKICBmaWxlLnBhdGgoZGF0YV9kaXIsCiAgICAgICAgICAgICJHU0U0NDk3MS50c3YiKQogICkKYGBgCgpVc2UgdGhpcyBjaHVuayB0byBleHBsb3JlIHdoYXQgYGdlbmVfZGZgIGxvb2tzIGxpa2UuCgpgYGB7ciBleHBsb3JlfQojIEV4cGxvcmUgYGdlbmVfZGZgCgpgYGAKCldoYXQgaW5mb3JtYXRpb24gaXMgY29udGFpbmVkIGluIGBnZW5lX2RmYD8KCiMjIGBSYCBwaXBlcwoKT25lIG5pZnR5IGZlYXR1cmUgdGhhdCB3YXMgYWRkZWQgdG8gYFJgIGluIHZlcnNpb24gNC4xIGlzIHRoZSBwaXBlOiBgfD5gLgpQaXBlcyBhcmUgdmVyeSBoYW5keSB0aGluZ3MgdGhhdCBhbGxvdyB5b3UgdG8gZnVubmVsIHRoZSByZXN1bHQgb2Ygb25lIGV4cHJlc3Npb24gdG8gdGhlIG5leHQsIG1ha2luZyB5b3VyIGNvZGUgbW9yZSBzdHJlYW1saW5lZCBhbmQgZmx1ZW50bHkgZXhwcmVzc2luZyB0aGUgZmxvdyBvZiBkYXRhIHRocm91Z2ggYSBzZXJpZXMgb2Ygb3BlcmF0aW9ucy4KCl9Ob3RlOl8gSWYgeW91IGFyZSB1c2luZyBhIHZlcnNpb24gb2YgYFJgIHByaW9yIHRvIDQuMSAob3IgbG9va2luZyBhdCBvbGRlciBjb2RlKSwgcGlwZSBmdW5jdGlvbmFsaXR5IHdhcyBhdmFpbGFibGUgdGhyb3VnaCB0aGUgYG1hZ3JpdHRyYCBwYWNrYWdlLCB3aGljaCB1c2VkIGEgcGlwZSB0aGF0IGxvb2tlZCBsaWtlIHRoaXM6IGAlPiVgLgpUaGF0IHBpcGUgd2FzIHRoZSBpbnNwaXJhdGlvbiBmb3IgdGhlIG5hdGl2ZSBSIHBpcGUgd2UgYXJlIHVzaW5nIGhlcmUuIApXaGlsZSB0aGVyZSBhcmUgc29tZSBtaW5vciBkaWZmZXJlbmNlcywgeW91IGNhbiBtb3N0bHkgdHJlYXQgdGhlbSBpbnRlcmNoYW5nZWFibHkgYXMgbG9uZyBhcyB5b3UgbG9hZCB0aGUgYG1hZ3JpdHRyYCBwYWNrYWdlIG9yIGBkcGx5cmAsIHdoaWNoIGFsc28gbG9hZHMgdGhhdCB2ZXJzaW9uIG9mIHRoZSBwaXBlLgoKRm9yIGV4YW1wbGUsIHRoZSBvdXRwdXQgZnJvbSB0aGlzOgoKYGBge3IgZmlsdGVyfQpmaWx0ZXIoc3RhdHNfZGYsIGNvbnRyYXN0ID09ICJtYWxlX2ZlbWFsZSIpCmBgYAoKLi4uaXMgdGhlIHNhbWUgYXMgdGhlIG91dHB1dCBmcm9tIHRoaXM6CgpgYGB7ciBmaWx0ZXItcGlwZX0Kc3RhdHNfZGYgfD4gZmlsdGVyKGNvbnRyYXN0ID09ICJtYWxlX2ZlbWFsZSIpCmBgYAoKVGhpcyBjYW4gbWFrZSB5b3VyIGNvZGUgY2xlYW5lciBhbmQgZWFzaWVyIHRvIGZvbGxvdyBhIHNlcmllcyBvZiByZWxhdGVkIGNvbW1hbmRzLgpMZXQncyBsb29rIGF0IGFuIGV4YW1wbGUgd2l0aCBvdXIgc3RhdHMgb2Ygb2YgaG93IHRoZSBzYW1lIGZ1bmN0aW9ucyBsb29rIHdpdGggb3Igd2l0aG91dCBwaXBlczoKCipFeGFtcGxlIDE6KiB3aXRob3V0IHBpcGVzOgoKYGBge3Igc3RlcHMtbm9waXBlfQpzdGF0c19hcnJhbmdlZCA8LSBhcnJhbmdlKHN0YXRzX2RmLCB0X3N0YXRpc3RpYykKc3RhdHNfZmlsdGVyZWQgPC0gZmlsdGVyKHN0YXRzX2FycmFuZ2VkLCBhdmdfZXhwcmVzc2lvbiA+IDUwKQpzdGF0c19ub3BpcGUgPC0gc2VsZWN0KHN0YXRzX2ZpbHRlcmVkLCBjb250cmFzdCwgbG9nX2ZvbGRfY2hhbmdlLCBwX3ZhbHVlKQpgYGAKClVHSCwgd2UgaGF2ZSB0byBrZWVwIHRyYWNrIG9mIGFsbCBvZiB0aG9zZSBkaWZmZXJlbnQgaW50ZXJtZWRpYXRlIGRhdGEgZnJhbWVzIGFuZCB0eXBlIHRoZWlyIG5hbWVzIHNvIG1hbnkgdGltZXMgaGVyZSEKV2UgY291bGQgbWF5YmUgc3RyZWFtbGluZSB0aGluZ3MgYnkgdXNpbmcgdGhlIHNhbWUgdmFyaWFibGUgbmFtZSBhdCBlYWNoIHN0YWdlLCBidXQgZXZlbiB0aGVuIHRoZXJlIGlzIGEgbG90IG9mIGV4dHJhIHR5cGluZywgYW5kIGl0IGlzIGVhc3kgdG8gZ2V0IGNvbmZ1c2VkIGFib3V0IHdoYXQgaGFzIGJlZW4gZG9uZSB3aGVyZS4KSXQncyBhbm5veWluZyBhbmQgbWFrZXMgaXQgaGFyZGVyIGZvciBwZW9wbGUgdG8gcmVhZC4KCipFeGFtcGxlIDI6KiBTYW1lIHJlc3VsdCBhcyAxIGJ1dCB3aXRoIHBpcGVzIQoKYGBge3Igc3RlcHMtcGlwZSwgbGl2ZSA9IFRSVUV9CiMgRXhhbXBsZSBvZiB0aGUgc2FtZSBtb2RpZmljYXRpb25zIGFzIGFib3ZlIGJ1dCB3aXRoIHBpcGVzIQpzdGF0c19waXBlICA8LSBzdGF0c19kZiB8PgogICAgICAgICAgICAgICBhcnJhbmdlKHRfc3RhdGlzdGljKSB8PgogICAgICAgICAgICAgICBmaWx0ZXIoYXZnX2V4cHJlc3Npb24gPiA1MCkgfD4KICAgICAgICAgICAgICAgc2VsZWN0KGNvbnRyYXN0LCBsb2dfZm9sZF9jaGFuZ2UsIHBfdmFsdWUpCmBgYAoKV2hhdCB0aGUgYHw+YCAocGlwZSkgaXMgZG9pbmcgaGVyZSBpcyBmZWVkaW5nIHRoZSByZXN1bHQgb2YgdGhlIGV4cHJlc3Npb24gb24gaXRzIGxlZnQgaW50byB0aGUgZmlyc3QgYXJndW1lbnQgb2YgdGhlIG5leHQgZnVuY3Rpb24gKHRvIGl0cyByaWdodCwgb3Igb24gdGhlIG5leHQgbGluZSBoZXJlKS4KV2UgY2FuIHRoZW4gc2tpcCB0aGF0IGZpcnN0IGFyZ3VtZW50ICh0aGUgZGF0YSBpbiB0aGVzZSBjYXNlcyksIGFuZCBtb3ZlIHJpZ2h0IG9uIHRvIHRoZSBwYXJ0IHdlIGNhcmUgYWJvdXQgYXQgdGhhdCBzdGVwOiB3aGF0IHdlIGFyZSBhcnJhbmdpbmcsIGZpbHRlcmluZywgb3Igc2VsZWN0aW5nIGluIHRoaXMgY2FzZS4KVGhlIGtleSBpbnNpZ2h0IHRoYXQgbWFrZXMgdGhlIHBpcGUgd29yayBoZXJlIGlzIHRvIHJlY29nbml6ZSB0aGF0IGVhY2ggb2YgdGhlc2UgZnVuY3Rpb25zIChgYXJyYW5nZWAsIGBmaWx0ZXJgLCBhbmQgYHNlbGVjdGApIGFyZSBmdW5kYW1lbnRhbCBgZHBseXJgIChhIHRpZHl2ZXJzZSBwYWNrYWdlKSBmdW5jdGlvbnMgd2hpY2ggd29yayBhcyAiZGF0YSBpbiwgZGF0YSBvdXQuIgpJbiBvdGhlciB3b3JkcywgdGhlc2UgZnVuY3Rpb25zIG9wZXJhdGUgb24gZGF0YSBmcmFtZXMsIGFuZCByZXR1cm4gZGF0YSBmcmFtZXM7IHlvdSBnaXZlIHRoZW0gYSBkYXRhIGZyYW1lLCBhbmQgdGhleSBnaXZlIHlvdSBiYWNrIGEgZGF0YSBmcmFtZS4KQmVjYXVzZSB0aGVzZSBmdW5jdGlvbnMgYWxsIGZvbGxvdyBhICJkYXRhIGluLCBkYXRhIG91dCIgZnJhbWV3b3JrLCB3ZSBjYW4gY2hhaW4gdGhlbSB0b2dldGhlciB3aXRoIHBpcGUgYW5kIHNlbmQgZGF0YSBhbGwgdGhlIHdheSB0aHJvdWdoIHRoZS4uLnBpcGVsaW5lIQoKTGV0J3MgZG91YmxlIGNoZWNrIHRoYXQgdGhlc2UgdmVyc2lvbnMgd2l0aCBhbmQgd2l0aG91dCBwaXBlIHlpZWxkIHRoZSBzYW1lIHNvbHV0aW9uIGJ5IHVzaW5nIHRoZSBiYXNlIFIgZnVuY3Rpb24gYGFsbC5lcXVhbCgpYC4KCmBgYHtyIGNoZWNrLXBpcGV9CmFsbC5lcXVhbChzdGF0c19ub3BpcGUsIHN0YXRzX3BpcGUpCmBgYAoKYGFsbC5lcXVhbCgpYCBpcyBsZXR0aW5nIHVzIGtub3cgdGhhdCB0aGVzZSB0d28gb2JqZWN0cyBhcmUgdGhlIHNhbWUuCgpOb3cgdGhhdCBob3BlZnVsbHkgeW91IGFyZSBjb252aW5jZWQgdGhhdCB0aGUgdGlkeXZlcnNlIGNhbiBoZWxwIHlvdSBtYWtlIHlvdXIgY29kZSBuZWF0ZXIgYW5kIGVhc2llciB0byB1c2UgYW5kIHJlYWQsIGxldCdzIGdvIHRocm91Z2ggc29tZSBvZiB0aGUgcG9wdWxhciB0aWR5dmVyc2UgZnVuY3Rpb25zIGFuZCBzbyB3ZSBjYW4gY3JlYXRlIHBpcGVsaW5lcyBsaWtlIHRoaXMuCgoKIyMgQ29tbW9uIHRpZHl2ZXJzZSBmdW5jdGlvbnMKCkxldCdzIHNheSB3ZSB3YW50ZWQgdG8gZmlsdGVyIHRoaXMgZ2VuZSBleHByZXNzaW9uIGRhdGFzZXQgdG8gcGFydGljdWxhciBzYW1wbGUgZ3JvdXBzLgpJbiBvcmRlciB0byBkbyB0aGlzLCB3ZSB3b3VsZCB1c2UgdGhlIGZ1bmN0aW9uIGBmaWx0ZXIoKWAgYXMgd2VsbCBhcyBhIGxvZ2ljIHN0YXRlbWVudCAodXN1YWxseSBvbmUgdGhhdCByZWZlcnMgdG8gYSBjb2x1bW4gb3IgY29sdW1ucyBpbiB0aGUgZGF0YSBmcmFtZSkuCgpgYGB7ciBmaWx0ZXItZ2VuZX0KIyBIZXJlIGxldCdzIGZpbHRlciBzdGF0c19kZiB0byBvbmx5IGtlZXAgdGhlIGdlbmVfc3ltYm9sICJTTkNBIgpzdGF0c19kZiB8PgogIGZpbHRlcihnZW5lX3N5bWJvbCA9PSAiU05DQSIpCmBgYAoKV2UgY2FuIHVzZSBgZmlsdGVyKClgIHNpbWlsYXJseSBmb3IgbnVtZXJpYyBzdGF0ZW1lbnRzLgoKYGBge3IgZmlsdGVyLW51bWVyaWMsIGxpdmUgPSBUUlVFfQojIEhlcmUgbGV0J3MgZmlsdGVyIHRoZSBkYXRhIHRvIHJvd3Mgd2l0aCBhdmVyYWdlIGV4cHJlc3Npb24gdmFsdWVzIGFib3ZlIDUwCnN0YXRzX2RmIHw+CiAgZmlsdGVyKGF2Z19leHByZXNzaW9uID4gNTApCmBgYAoKV2UgY2FuIGFwcGx5IG11bHRpcGxlIGZpbHRlcnMgYXQgb25jZSwgd2hpY2ggd2lsbCByZXF1aXJlIGFsbCBvZiB0aGVtIHRvIGJlIHNhdGlzZmllZCBmb3IgZXZlcnkgcm93IGluIHRoZSByZXN1bHRzOgoKYGBge3IgZmlsdGVyLTIsIGxpdmUgPSBUUlVFfQojIGZpbHRlciB0byBoaWdobHkgZXhwcmVzc2VkIGdlbmVzIHdpdGggY29udHJhc3QgIm1hbGVfZmVtYWxlIgpzdGF0c19kZiB8PgogIGZpbHRlcihjb250cmFzdCA9PSAibWFsZV9mZW1hbGUiLAogICAgICAgICBhdmdfZXhwcmVzc2lvbiA+IDUwKQpgYGAKCldoZW4gd2UgYXJlIGZpbHRlcmluZywgdGhlIGAlaW4lYCBvcGVyYXRvciBjYW4gY29tZSBpbiBoYW5keSBpZiB3ZSBoYXZlIG11bHRpcGxlIGl0ZW1zIHdlIHdvdWxkIGxpa2UgdG8gbWF0Y2guCkxldCdzIHRha2UgYSBsb29rIGF0IHdoYXQgdXNpbmcgYCVpbiVgIGRvZXMuCgpgYGB7ciBpbi1leGFtcGxlLCBldmFsID0gRkFMU0V9CmdlbmVzX29mX2ludGVyZXN0IDwtIGMoIlNOQ0EiLCAiQ0RLTjFBIikKIyBBcmUgdGhlc2UgZ2VuZXMgcHJlc2VudCBpbiB0aGUgYGdlbmVfc3ltYm9sYCBjb2x1bW4gaW4gc3RhdHNfZGY/CnN0YXRzX2RmJGdlbmVfc3ltYm9sICVpbiUgZ2VuZXNfb2ZfaW50ZXJlc3QKYGBgCgpgJWluJWAgcmV0dXJucyBhIGxvZ2ljYWwgdmVjdG9yIHRoYXQgbm93IHdlIGNhbiB1c2UgaW4gYGRwbHlyOjpmaWx0ZXJgLgoKYGBge3IgZmlsdGVyLWluLCBsaXZlID0gVFJVRX0KIyBmaWx0ZXIgdG8ga2VlcCBvbmx5IGdlbmVzIG9mIGludGVyZXN0CnN0YXRzX2RmIHw+CiAgZmlsdGVyKGdlbmVfc3ltYm9sICVpbiUgYygiU05DQSIsICJDREtOMUEiKSkKYGBgCgpMZXQncyByZXR1cm4gdG8gb3VyIGZpcnN0IGBmaWx0ZXIoKWAgYW5kIGJ1aWxkIG9uIHRvIGl0LgpUaGlzIHRpbWUsIGxldCdzIGtlZXAgb25seSBzb21lIG9mIHRoZSBjb2x1bW5zIGZyb20gdGhlIGRhdGEgZnJhbWUgdXNpbmcgdGhlCmBzZWxlY3QoKWAgZnVuY3Rpb24uCkxldCdzIGFsc28gc2F2ZSB0aGlzIGFzIGEgbmV3IGRhdGEgZnJhbWUgY2FsbGVkIGBzdGF0c19maWx0ZXJlZF9kZmAuCgpgYGB7ciBmaWx0ZXItc2VsZWN0LCBsaXZlID0gVFJVRX0KIyBmaWx0ZXIgdG8gaGlnaGx5IGV4cHJlc3NlZCAibWFsZV9mZW1hbGUiCiMgYW5kIHNlbGVjdCBnZW5lX3N5bWJvbCwgbG9nX2ZvbGRfY2hhbmdlIGFuZCB0X3N0YXRpc3RpYwpzdGF0c19maWx0ZXJlZF9kZiA8LSBzdGF0c19kZiB8PgogIGZpbHRlcihjb250cmFzdCA9PSAibWFsZV9mZW1hbGUiLAogICAgICAgICBhdmdfZXhwcmVzc2lvbiA+IDUwKSB8PgogIHNlbGVjdChsb2dfZm9sZF9jaGFuZ2UsIHRfc3RhdGlzdGljKQpgYGAKCkxldCdzIHNheSB3ZSB3YW50ZWQgdG8gYXJyYW5nZSB0aGlzIGRhdGFzZXQgc28gdGhhdCB0aGUgZ2VuZXMgYXJlIGFycmFuZ2VkIGJ5IHRoZSBzbWFsbGVzdCBwIHZhbHVlcyB0byB0aGUgbGFyZ2VzdC4KSW4gb3JkZXIgdG8gZG8gdGhpcywgd2Ugd291bGQgdXNlIHRoZSBmdW5jdGlvbiBgYXJyYW5nZSgpYCBhcyB3ZWxsIGFzIHRoZSBjb2x1bW4gd2Ugd291bGQgbGlrZSB0byBzb3J0IGJ5IChpbiB0aGlzIGNhc2UgYHBfdmFsdWVgKS4KCmBgYHtyIGFycmFuZ2V9CnN0YXRzX2RmIHw+CiAgYXJyYW5nZShwX3ZhbHVlKQpgYGAKCldoYXQgaWYgd2Ugd2FudCB0byBzb3J0IGZyb20gbGFyZ2VzdCB0byBzbWFsbGVzdD8KTGlrZSBpZiB3ZSB3YW50IHRvIHNlZSB0aGUgZ2VuZXMgd2l0aCB0aGUgaGlnaGVzdCBhdmVyYWdlIGV4cHJlc3Npb24/CldlIGNhbiB1c2UgdGhlIHNhbWUgZnVuY3Rpb24sIGJ1dCBpbnN0ZWFkIHVzZSB0aGUgYGRlc2MoKWAgZnVuY3Rpb24gYW5kIG5vdyB3ZSBhcmUgdXNpbmcgYGF2Z19leHByZXNzaW9uYCBjb2x1bW4uCgpgYGB7ciBhcnJhbmdlLWRlc2N9CiMgYXJyYW5nZSBkZXNjZW5kaW5nIGJ5IGF2Z19leHByZXNzaW9uCnN0YXRzX2RmIHw+CiAgYXJyYW5nZShkZXNjKGF2Z19leHByZXNzaW9uKSkKYGBgCgpXaGF0IGlmIHdlIHdvdWxkIGxpa2UgdG8gY3JlYXRlIGEgbmV3IGNvbHVtbiBvZiB2YWx1ZXM/CkZvciB0aGF0IHdlIHVzZSBgbXV0YXRlKClgIGZ1bmN0aW9uLgoKYGBge3IgbXV0YXRlfQpzdGF0c19kZiB8PgogIG11dGF0ZShsb2cxMF9wX3ZhbHVlID0gLWxvZzEwKHBfdmFsdWUpKQpgYGAKCldoYXQgaWYgd2Ugd2FudCB0byBvYnRhaW4gc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBhIGNvbHVtbiBvciBjb2x1bW5zPwpUaGUgYHN1bW1hcml6ZWAgZnVuY3Rpb24gYWxsb3dzIHVzIHRvIGNhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGEgY29sdW1uLgpIZXJlIHdlIHdpbGwgdXNlIHN1bW1hcml6ZSB0byBjYWxjdWxhdGUgdHdvIHN1bW1hcnkgc3RhdGlzdGljcyBvZiBsb2ctZm9sZCBjaGFuZ2UgYWNyb3NzIGFsbCBnZW5lczogbWVhbiAoZnVuY3Rpb24gYG1lYW4oKWApIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gKGZ1bmN0aW9uIGBzZCgpYCkuCgpgYGB7ciBzdW1tYXJpemV9CnN0YXRzX2RmIHw+CiAgc3VtbWFyaXplKG1lYW4obG9nX2ZvbGRfY2hhbmdlKSwKICAgICAgICAgICAgc2QobG9nX2ZvbGRfY2hhbmdlKSkKYGBgCgpXaGF0IGlmIHdlJ2QgbGlrZSB0byBvYnRhaW4gYSBzdW1tYXJ5IHN0YXRpc3RpY3MgYnV0IGhhdmUgdGhlbSBmb3IgdmFyaW91cyBncm91cHM/CkNvbnZlbmllbnRseSBuYW1lZCwgdGhlcmUncyBhIGZ1bmN0aW9uIGNhbGxlZCBgZ3JvdXBfYnkoKWAgdGhhdCBzZWFtbGVzc2x5IGFsbG93cyB1cyB0byBkbyB0aGlzLgpBbHNvIG5vdGUgdGhhdCBgZ3JvdXBfYnkoKWAgYWxsb3dzIHVzIHRvIGdyb3VwIGJ5IG11bHRpcGxlIHZhcmlhYmxlcyBhdCBhIHRpbWUgaWYgeW91IHdhbnQgdG8uCgpgYGB7ciBzdW1tYXJpemUtZ3JvdXBzLCBsaXZlID0gVFJVRX0Kc3RhdHNfc3VtbWFyeV9kZiA8LSBzdGF0c19kZiB8PgogICAgICBncm91cF9ieShjb250cmFzdCkgfD4KICAgICAgc3VtbWFyaXplKG1lYW4obG9nX2ZvbGRfY2hhbmdlKSwKICAgICAgICAgICAgICAgIHNkKGxvZ19mb2xkX2NoYW5nZSkpCmBgYAoKTGV0J3MgbG9vayBhdCBhIHByZXZpZXcgb2Ygd2hhdCB3ZSBtYWRlOgoKYGBge3J9CnN0YXRzX3N1bW1hcnlfZGYKYGBgCgpIZXJlIHdlIGhhdmUgdGhlIG1lYW4gbG9nIGZvbGQgY2hhbmdlIGV4cHJlc3Npb24gcGVyIGVhY2ggY29udHJhc3Qgd2UgbWFkZS4KCiMjIEEgYnJpZWYgaW50cm8gdG8gdGhlIGBhcHBseWAgZmFtaWx5IG9mIGZ1bmN0aW9ucwoKSW4gYmFzZSBSLCB0aGUgYGFwcGx5YCBmYW1pbHkgb2YgZnVuY3Rpb25zIGNhbiBiZSBhbiBhbHRlcm5hdGl2ZSBtZXRob2RzIGZvciBwZXJmb3JtaW5nIHRyYW5zZm9ybWF0aW9ucyBhY3Jvc3MgYSBkYXRhIGZyYW1lLCBtYXRyaXggb3Igb3RoZXIgb2JqZWN0IHN0cnVjdHVyZXMuCgpPbmUgb2YgdGhpcyBmYW1pbHkgaXMgKHNob2NraW5nbHkpIHRoZSBmdW5jdGlvbiBgYXBwbHkoKWAsIHdoaWNoIG9wZXJhdGVzIG9uIG1hdHJpY2VzLgoKQSBtYXRyaXggaXMgc2ltaWxhciB0byBhIGRhdGEgZnJhbWUgaW4gdGhhdCBpdCBpcyBhIHJlY3Rhbmd1bGFyIHRhYmxlIG9mIGRhdGEsIGJ1dCBpdCBoYXMgYW4gYWRkaXRpb25hbCBjb25zdHJhaW50OiByYXRoZXIgdGhhbiBlYWNoIGNvbHVtbiBoYXZpbmcgYSB0eXBlLCBBTEwgZGF0YSBpbiBhIG1hdHJpeCBoYXMgdGhlIHNhbWUgdHlwZS4KClRoZSBmaXJzdCBhcmd1bWVudCB0byBgYXBwbHkoKWAgaXMgdGhlIGRhdGEgb2JqZWN0IHdlIHdhbnQgdG8gd29yayBvbi4KVGhlIHRoaXJkIGFyZ3VtZW50IGlzIHRoZSBmdW5jdGlvbiB3ZSB3aWxsIGFwcGx5IHRvIGVhY2ggcm93IG9yIGNvbHVtbiBvZiB0aGUgZGF0YSBvYmplY3QuClRoZSBzZWNvbmQgYXJndW1lbnQgaW4gc3BlY2lmaWVzIHdoZXRoZXIgd2UgYXJlIGFwcGx5aW5nIHRoZSBmdW5jdGlvbiBhY3Jvc3Mgcm93cyBvciBhY3Jvc3MgY29sdW1ucyAoMSBmb3Igcm93cywgMiBmb3IgY29sdW1ucykuCgpSZW1lbWJlciB0aGF0IGBnZW5lX2RmYCBpcyBhIGdlbmUgeCBzYW1wbGUgZ2VuZSBleHByZXNzaW9uIGRhdGEgZnJhbWUgdGhhdCBoYXMgY29sdW1ucyBvZiB0d28gZGlmZmVyZW50IHR5cGVzLCBjaGFyYWN0ZXIgYW5kIG51bWVyaWMuCkNvbnZlcnRpbmcgaXQgdG8gYSBtYXRyaXggd2lsbCByZXF1aXJlIHVzIHRvIG1ha2UgdGhlbSBhbGwgdGhlIHNhbWUgdHlwZS4KV2UgY2FuIGNvZXJjZSBpdCBpbnRvIGEgbWF0cml4IHVzaW5nIGBhcy5tYXRyaXgoKWAsIGluIHdoaWNoIGNhc2UgUiB3aWxsIHBpY2sgYSB0eXBlIHRoYXQgaXQgY2FuIGNvbnZlcnQgZXZlcnl0aGluZyB0by4KV2hhdCBkb2VzIGl0IGNob29zZT8KCmBgYHtyIG1hdHJpeH0KIyBDb2VyY2UgYGdlbmVfZGZgIGludG8gYSBtYXRyaXgKZ2VuZV9tYXRyaXggPC0gYXMubWF0cml4KGdlbmVfZGYpCmBgYAoKYGBge3IgbWF0cml4LXR5cGUsIGxpdmUgPSBUUlVFfQojIEV4cGxvcmUgdGhlIHN0cnVjdHVyZSBvZiB0aGUgYGdlbmVfbWF0cml4YCBvYmplY3QKc3RyKGdlbmVfbWF0cml4KQpgYGAKCldoaWxlIHRoYXQgd29ya2VkLCBpdCBpcyByYXJlIHRoYXQgd2Ugd2FudCBudW1iZXJzIGNvbnZlcnRlZCB0byB0ZXh0LCBzbyB3ZSBhcmUgZ29pbmcgdG8gc2VsZWN0IG9ubHkgdGhlIGNvbHVtbnMgd2l0aCBudW1lcmljIHZhbHVlcyBiZWZvcmUgY29udmVydGluZyBpdCB0byBhIG1hdHJpeC4KV2UgY2FuIGRvIHRoaXMgbW9zdCBlYXNpbHkgYnkgcmVtb3ZpbmcgdGhlIGZpcnN0IGNvbHVtbiwgd2hpY2ggY29udGFpbnMgdGhlIGdlbmUgbmFtZXMgc3RvcmVkIGFzIGNoYXJhY3RlciB2YWx1ZXMuCgpgYGB7ciBtYXRyaXgtbnVtZXJpYywgbGl2ZSA9IFRSVUV9CiMgTGV0J3Mgc2F2ZSBhIG5ldyBtYXRyaXggb2JqZWN0IG5hbWVzIGBnZW5lX251bV9tYXRyaXhgIGNvbnRhaW5pbmcgb25seQojIHRoZSBudW1lcmljIHZhbHVlcwpnZW5lX251bV9tYXRyaXggPC0gYXMubWF0cml4KGdlbmVfZGZbLCAtMV0pCgojIEV4cGxvcmUgdGhlIHN0cnVjdHVyZSBvZiB0aGUgYGdlbmVfbnVtX21hdHJpeGAgb2JqZWN0CnN0cihnZW5lX251bV9tYXRyaXgpCmBgYAoKV2h5IGRvIHdlIGhhdmUgYSBgWywgLTFdYCBhZnRlciBgZ2VuZV9kZmAgaW4gdGhlIGFib3ZlIGNodW5rPwoKTm93IHRoYXQgdGhlIG1hdHJpeCBpcyBhbGwgbnVtYmVycywgd2UgY2FuIGRvIHRoaW5ncyBsaWtlIGNhbGN1bGF0ZSB0aGUgY29sdW1uIG9yIHJvdyBzdGF0aXN0aWNzIHVzaW5nIGBhcHBseSgpYC4KCmBgYHtyIHJvd21lYW5zfQojIENhbGN1bGF0ZSByb3cgbWVhbnMKZ2VuZV9tZWFucyA8LSBhcHBseShnZW5lX251bV9tYXRyaXgsIDEsIG1lYW4pICMgTm90aWNlIHdlIGFyZSB1c2luZyAxIGhlcmUKCiMgSG93IGxvbmcgd2lsbCBgZ2VuZV9tZWFuc2AgYmU/Cmxlbmd0aChnZW5lX21lYW5zKQpgYGAKCk5vdGUgdGhhdCB3ZSBjYW4gb2J0YWluIHRoZSBzYW1lIHJlc3VsdHMgaWYgd2Ugc2VsZWN0IGp1c3QgdGhlIGNvbHVtbnMgd2l0aCBudW1lcmljIHZhbHVlcyBmcm9tIHRoZSBgZ2VuZV9kZmAgZGF0YSBmcmFtZS4KVGhpcyBhbGxvd3MgUiB0byBkbyB0aGUgYXMubWF0cml4KCkgY29lcmNpb24gYXV0b21hdGljYWxseSwgYW5kIGNhbiBiZSBhIGhhbmR5IHNob3J0Y3V0IGlmIHlvdSBoYXZlIGEgKm1vc3RseSogbnVtZXJpYyBkYXRhIGZyYW1lLgoKYGBge3Igcm93bWVhbnMtZGF0YWZyYW1lfQojIENhbGN1bGF0ZSByb3cgbWVhbnMgdXNpbmcgdGhlIGBnZW5lX2RmYCBvYmplY3QgYWZ0ZXIgcmVtb3ZpbmcgdGhlIGNoYXJhY3RlciBjb2x1bW4KIyBhcHBseSgpIGNvbnZlcnRzIHRoaXMgdG8gYSBtYXRyaXggaW50ZXJuYWxseQpnZW5lX21lYW5zX2Zyb21fZGYgPC0gYXBwbHkoZ2VuZV9kZlssIC0xXSwgMSwgbWVhbikKCiMgTGV0J3MgY2hlY2sgdGhhdCB0aGUgdHdvIGdlbmUgbWVhbnMgb2JqZWN0cyBhcmUgZXF1YWwKYWxsLmVxdWFsKGdlbmVfbWVhbnMsIGdlbmVfbWVhbnNfZnJvbV9kZikKYGBgCgpOb3cgbGV0J3MgaW52ZXN0aWdhdGUgdGhlIHNhbWUgc2V0IHVwLCBidXQgdXNlIDIgdG8gYGFwcGx5YCBvdmVyIHRoZSBjb2x1bW5zIG9mIG91ciBtYXRyaXguCgpgYGB7ciBjb2xtZWFuc30KIyBDYWxjdWxhdGUgc2FtcGxlIG1lYW5zCnNhbXBsZV9tZWFucyA8LSBhcHBseShnZW5lX251bV9tYXRyaXgsIDIsIG1lYW4pICMgTm90aWNlIHdlIHVzZSAyIGhlcmUKCiMgSG93IGxvbmcgd2lsbCBgc2FtcGxlX21lYW5zYCBiZT8KbGVuZ3RoKHNhbXBsZV9tZWFucykKYGBgCgpXZSBjYW4gcHV0IHRoZSBnZW5lIG5hbWVzIGJhY2sgaW50byB0aGUgbnVtZXJpYyBtYXRyaXggb2JqZWN0IGJ5IGFzc2lnbmluZyB0aGVtIGFzIHJvd25hbWVzLgoKYGBge3IgbWF0cml4LXJvd25hbWVzLCBsaXZlID0gVFJVRX0KIyBBc3NpZ24gdGhlIGdlbmUgbmFtZXMgZnJvbSBnZW5lX2RmJEdlbmUgdG8gdGhlIGBnZW5lX251bV9tYXRyaXhgIG9iamVjdCB1c2luZwojIHRoZSBgcm93bmFtZXMoKWAgZnVuY3Rpb24Kcm93bmFtZXMoZ2VuZV9udW1fbWF0cml4KSA8LSBnZW5lX2RmJEdlbmUKCiMgRXhwbG9yZSB0aGUgYGdlbmVfbnVtX21hdHJpeGAgb2JqZWN0CmhlYWQoZ2VuZV9udW1fbWF0cml4KQpgYGAKClJvdyBuYW1lcyBsaWtlIHRoaXMgY2FuIGJlIHZlcnkgY29udmVuaWVudCBmb3Iga2VlcGluZyBtYXRyaWNlcyBvcmdhbml6ZWQsIGJ1dCByb3cgbmFtZXMgKGFuZCBjb2x1bW4gbmFtZXMpIGNhbiBiZSBsb3N0IG9yIG1pc29yZGVyZWQgaWYgeW91IGFyZSBub3QgY2FyZWZ1bCwgZXNwZWNpYWxseSBkdXJpbmcgaW5wdXQgYW5kIG91dHB1dCwgc28gdHJlYXQgdGhlbSB3aXRoIGNhcmUuCgpBbHRob3VnaCB0aGUgYGFwcGx5YCBmdW5jdGlvbnMgbWF5IG5vdCBiZSBhcyBlYXN5IHRvIHVzZSBhcyB0aGUgdGlkeXZlcnNlIGZ1bmN0aW9ucywgZm9yIHNvbWUgYXBwbGljYXRpb25zLCBgYXBwbHlgIG1ldGhvZHMgY2FuIGJlIGJldHRlciBzdWl0ZWQuCkluIHRoaXMgd29ya3Nob3AsIHdlIHdpbGwgbm90IGRlbHZlIHRvbyBkZWVwbHkgaW50byB0aGUgdmFyaW91cyBvdGhlciBhcHBseSBmdW5jdGlvbnMgKGB0YXBwbHkoKWAsIGBsYXBwbHkoKWAsIGV0Yy4pIGJ1dCB5b3UgY2FuIHJlYWQgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGVtIFtoZXJlXShodHRwczovL3d3dy5ndXJ1OTkuY29tL3ItYXBwbHktc2FwcGx5LXRhcHBseS5odG1sKS4KCiMjIFRoZSBkcGx5cjo6am9pbiBmdW5jdGlvbnMKCkxldCdzIHNheSB3ZSBoYXZlIGEgc2NlbmFyaW8gd2hlcmUgd2UgaGF2ZSB0d28gZGF0YSBmcmFtZXMgdGhhdCB3ZSB3b3VsZCBsaWtlIHRvIGNvbWJpbmUuClJlY2FsbCB0aGF0IGBzdGF0c19kZmAgYW5kIGBnZW5lX2RmYCBhcmUgZGF0YSBmcmFtZXMgdGhhdCBjb250YWluIGluZm9ybWF0aW9uIGFib3V0IHNvbWUgb2YgdGhlIHNhbWUgZ2VuZXMuClRoZSBbYGRwbHlyOjpqb2luYCBmYW1pbHkgb2YgZnVuY3Rpb25zXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL211dGF0ZS1qb2lucy5odG1sKSBhcmUgdXNlZnVsIGZvciB2YXJpb3VzIHNjZW5hcmlvcyBvZiBjb21iaW5pbmcgZGF0YSBmcmFtZXMuCkZvciBhIHZpc3VhbCBleHBsYW5hdGlvbiwgdGhlIFtgdGlkeWV4cGxhaW5gIHByb2plY3RdKGh0dHBzOi8vZ2l0aHViLmNvbS9nYWRlbmJ1aWUvdGlkeWV4cGxhaW4pIGhhcyBzb21lIFtoZWxwZnVsIGFuaW1hdGlvbnMgb2Ygam9pbnNdKGh0dHBzOi8vZ2l0aHViLmNvbS9nYWRlbmJ1aWUvdGlkeWV4cGxhaW4jbXV0YXRpbmctam9pbnMpLgoKRm9yIG5vdywgd2Ugd2lsbCBmb2N1cyBvbiBgaW5uZXJfam9pbigpYCwgd2hpY2ggd2lsbCBjb21iaW5lIGRhdGEgZnJhbWVzIGJ5IG9ubHkga2VlcGluZyBpbmZvcm1hdGlvbiBhYm91dCBtYXRjaGluZyByb3dzIHRoYXQgYXJlIGluIGJvdGggZGF0YSBmcmFtZXMuCldlIG5lZWQgdG8gdXNlIHRoZSBgYnlgIGFyZ3VtZW50IHRvIGRlc2lnbmF0ZSB3aGF0IGNvbHVtbihzKSBzaG91bGQgYmUgdXNlZCBhcyBhIGtleSB0byBtYXRjaCB0aGUgZGF0YSBmcmFtZXMuCkluIHRoaXMgY2FzZSB3ZSB3YW50IHRvIG1hdGNoIHRoZSBnZW5lIGluZm9ybWF0aW9uIGJldHdlZW4gdGhlIHR3bywgc28gd2Ugd2lsbCBzcGVjaWZ5IHRoYXQgd2Ugd2FudCB0byBjb21wYXJlIHZhbHVlcyBpbiB0aGUgYGVuc2VtYmxfaWRgIGNvbHVtbiBmcm9tIGBzdGF0c19kZmAgdG8gdGhlIGBHZW5lYCBjb2x1bW4gZnJvbSBgZ2VuZV9kZmAuCgpgYGB7ciBpbm5lci1qb2lufQpzdGF0c19kZiB8PgogICMgSm9pbiBiYXNlZCBvbiB0aGVpciBzaGFyZWQgY29sdW1uCiAgIyBDYWxsZWQgZW5zZW1ibF9pZCBpbiBzdGF0c19kZiBhbmQgY2FsbGVkIEdlbmUgaW4gZ2VuZV9kZgogIGlubmVyX2pvaW4oZ2VuZV9kZiwgYnkgPSBjKCdlbnNlbWJsX2lkJyA9ICdHZW5lJykpCmBgYAoKIyMgU2F2ZSBkYXRhIHRvIGZpbGVzCgojIyMjIFNhdmUgdG8gVFNWIGZpbGVzCgpMZXQncyB3cml0ZSBzb21lIG9mIHRoZSBkYXRhIGZyYW1lcyB3ZSBjcmVhdGVkIHRvIGEgZmlsZS4KVG8gZG8gdGhpcywgd2UgY2FuIHVzZSB0aGUgYHJlYWRyYCBsaWJyYXJ5IG9mIGB3cml0ZV8oKWAgZnVuY3Rpb25zLgpUaGUgZmlyc3QgYXJndW1lbnQgb2YgYHdyaXRlX3RzdigpYCBpcyB0aGUgZGF0YSB3ZSB3YW50IHRvIHdyaXRlLCBhbmQgdGhlIHNlY29uZCBhcmd1bWVudCBpcyBhIGNoYXJhY3RlciBzdHJpbmcgdGhhdCBkZXNjcmliZXMgdGhlIHBhdGggdG8gdGhlIG5ldyBmaWxlIHdlIHdvdWxkIGxpa2UgdG8gY3JlYXRlLgpSZW1lbWJlciB0aGF0IHdlIGNyZWF0ZWQgYSBgcmVzdWx0c2AgZGlyZWN0b3J5IHRvIHB1dCBvdXIgb3V0cHV0IGluLCBidXQgaWYgd2Ugd2FudCB0byBzYXZlIG91ciBkYXRhIHRvIGEgZGlyZWN0b3J5IG90aGVyIHRoYW4gb3VyIHdvcmtpbmcgZGlyZWN0b3J5LCB3ZSBuZWVkIHRvIHNwZWNpZnkgdGhpcy4KVGhpcyBpcyB3aGF0IHdlIHdpbGwgdXNlIHRoZSBgZmlsZS5wYXRoKClgIGZ1bmN0aW9uIGZvci4KTGV0J3MgbG9vayBpbiBhIGJpdCBtb3JlIGRldGFpbCB3aGF0IGBmaWxlLnBhdGgoKWAgZG9lcywgYnkgZXhhbWluaW5nIHRoZSByZXN1bHRzIG9mIHRoZSBmdW5jdGlvbiBpbiB0aGUgZXhhbXBsZXMgYmVsb3cuCgpgYGB7ciBmaWxlLXBhdGgtcXVpen0KIyBXaGljaCBvZiB0aGVzZSBmaWxlIHBhdGhzIGlzIHdoYXQgd2Ugd2FudCB0byB1c2UgdG8gc2F2ZSBvdXIgZGF0YSB0byB0aGUKIyByZXN1bHRzIGRpcmVjdG9yeSB3ZSBjcmVhdGVkIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhpcyBub3RlYm9vaz8KZmlsZS5wYXRoKCJkb2NrZXItaW5zdGFsbCIsICJzdGF0c19zdW1tYXJ5LnRzdiIpCmZpbGUucGF0aCgicmVzdWx0cyIsICJzdGF0c19zdW1tYXJ5LnRzdiIpCmZpbGUucGF0aCgic3RhdHNfc3VtbWFyeS50c3YiLCAicmVzdWx0cyIpCmBgYAoKUmVwbGFjZSBgPE5FV19GSUxFX1BBVEg+YCBiZWxvdyB3aXRoIHRoZSBgZmlsZS5wYXRoKClgIHN0YXRlbWVudCBmcm9tIGFib3ZlIHRoYXQgd2lsbCBzdWNjZXNzZnVsbHkgc2F2ZSBvdXIgZmlsZSB0byB0aGUgYHJlc3VsdHNgIGZvbGRlci4KCmBgYHtyIGV2YWw9RkFMU0V9CiMgV3JpdGUgb3VyIGRhdGEgZnJhbWUgdG8gYSBUU1YgZmlsZQpyZWFkcjo6d3JpdGVfdHN2KHN0YXRzX3N1bW1hcnlfZGYsIDxORVdfRklMRV9QQVRIPikKYGBgCgpDaGVjayBpbiB5b3VyIGByZXN1bHRzYCBkaXJlY3RvcnkgdG8gc2VlIGlmIHlvdXIgbmV3IGZpbGUgaGFzIHN1Y2Nlc3NmdWxseSBzYXZlZC4KCiMjIyMgU2F2ZSB0byBSRFMgZmlsZXMKCkZvciB0aGlzIGV4YW1wbGUgd2UgaGF2ZSBiZWVuIHdvcmtpbmcgd2l0aCBkYXRhIGZyYW1lcywgd2hpY2ggYXJlIGNvbnZlbmllbnRseSByZXByZXNlbnRlZCBhcyBUU1Ygb3IgQ1NWIHRhYmxlcy4KSG93ZXZlciwgaW4gb3RoZXIgc2l0dWF0aW9ucyB3ZSBtYXkgd2FudCB0byBzYXZlIG1vcmUgY29tcGxpY2F0ZWQgb3IgdmVyeSBsYXJnZSBkYXRhIHN0cnVjdHVyZXMsIFJEUyAoUiBEYXRhIFNlcmlhbGl6ZWQvU2luZ2xlKSBmaWxlcyBtYXkgYmUgYSBiZXR0ZXIgb3B0aW9uIGZvciBzYXZpbmcgb3VyIGRhdGEuClJEUyBpcyBSJ3Mgc3BlY2lhbCBmaWxlIGZvcm1hdCBmb3IgaG9sZGluZyBkYXRhIGV4YWN0bHkgYXMgeW91IGhhdmUgaXQgaW4geW91ciBSIGVudmlyb25tZW50LgpSRFMgZmlsZXMgY2FuIGFsc28gYmUgY29tcHJlc3NlZCwgbWVhbmluZyB0aGV5IHdpbGwgdGFrZSB1cCBsZXNzIHNwYWNlIG9uIHlvdXIgY29tcHV0ZXIuCkxldCdzIHNhdmUgb3VyIGRhdGEgdG8gYW4gUkRTIGZpbGUgaW4gb3VyIGByZXN1bHRzYCBmb2xkZXIuCllvdSB3aWxsIG5lZWQgdG8gcmVwbGFjZSB0aGUgYC50c3ZgIHdpdGggYC5SRFNgLCBidXQgeW91IGNhbiB1c2Ugd2hhdCB3ZSBkZXRlcm1pbmVkIGFzIG91ciBmaWxlIHBhdGggZm9yIHRoZSBsYXN0IGNodW5rIGFzIHlvdXIgdGVtcGxhdGUuCgpgYGB7ciBldmFsPUZBTFNFfQojIFdyaXRlIHlvdXIgb2JqZWN0IHRvIGFuIFJEUyBmaWxlCnJlYWRyOjp3cml0ZV9yZHMoc3RhdHNfc3VtbWFyeV9kZiwgPFBVVF9DT1JSRUNUX0ZJTEVfUEFUSF9IRVJFPikKYGBgCgojIyMjIFJlYWQgYW4gUkRTIGZpbGUKClNpbmNlIG5vdyB5b3UgaGF2ZSBsZWFybmVkIHRoZSBgcmVhZHJgIGZ1bmN0aW9uczogYHJlYWRfdHN2KClgLCBgd3JpdGVfdHN2KClgLCBhbmQgbm93LCBgd3JpdGVfcmRzKClgLCB3aGF0IGRvIHlvdSBzdXBwb3NlIHRoZSBmdW5jdGlvbiB5b3Ugd2lsbCBuZWVkIHRvIHJlYWQgeW91ciBSRFMgZmlsZSBpcyBjYWxsZWQ/ClVzZSB0aGF0IGZ1bmN0aW9uIGhlcmUgdG8gcmUtaW1wb3J0IHlvdXIgZGF0YSBpbiB0aGUgY2h1bmsgd2Ugc2V0IHVwIGZvciB5b3UgYmVsb3cuCgpgYGB7ciBldmFsPUZBTFNFfQojIFJlYWQgaW4geW91ciBSRFMgZmlsZQpyZWltcG9ydF9kZiA8LSA8UFVUX0ZVTkNUSU9OX05BTUU+KGZpbGUucGF0aCgicmVzdWx0cyIsICJzdGF0c19zdW1tYXJ5LlJEUyIpKQpgYGAKCkFzIGlzIGdvb2QgcHJhY3RpY2UsIHdlIHdpbGwgZW5kIHRoaXMgc2Vzc2lvbiBieSBwcmludGluZyBvdXQgb3VyIHNlc3Npb24gaW5mby4KCiMjIyBTZXNzaW9uIEluZm8KCmBgYHtyfQojIFByaW50IG91dCB0aGUgdmVyc2lvbnMgYW5kIHBhY2thZ2VzIHdlIGFyZSB1c2luZyBpbiB0aGlzIHNlc3Npb24Kc2Vzc2lvbkluZm8oKQpgYGAK