Last updated: 2025-03-24

Checks: 7 0

Knit directory: analysis-user-group/

This reproducible R Markdown analysis was created with workflowr (version 1.7.1). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.


Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.

Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.

The command set.seed(1337) was run prior to running the code in the R Markdown file. Setting a seed ensures that any results that rely on randomness, e.g. subsampling or permutations, are reproducible.

Great job! Recording the operating system, R version, and package versions is critical for reproducibility.

Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.

Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.

Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.

The results in this page were generated with repository version 96c7594. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.

Note that you need to be careful to ensure that all relevant files for the analysis have been committed to Git prior to generating the results (you can use wflow_publish or wflow_git_commit). workflowr only checks the R Markdown file, but you know if there are other scripts or data files that it depends on. Below is the status of the Git repository when the results were generated:


Ignored files:
    Ignored:    .DS_Store
    Ignored:    .Rhistory
    Ignored:    analysis/.DS_Store
    Ignored:    analysis/.Rhistory
    Ignored:    analysis/adit/.DS_Store

Untracked files:
    Untracked:  analysis/adit/head_index.html

Unstaged changes:
    Modified:   workflow.R

Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.


These are the previous versions of the repository in which changes were made to the R Markdown (analysis/202502_tidyverse1.Rmd) and HTML (docs/202502_tidyverse1.html) files. If you’ve configured a remote Git repository (see ?wflow_git_remote), click on the hyperlinks in the table below to view the files as they were in that past version.

File Version Author Date Message
html 517dcf9 DrThomasOneil 2025-03-24 Build site.
html 6a4432b DrThomasOneil 2025-03-24 Build site.
html 1f218f3 DrThomasOneil 2025-03-24 Build site.
html dca90e9 DrThomasOneil 2025-03-24 Build site.
html 1d1c076 DrThomasOneil 2025-03-24 Build site.
html 956ba9b DrThomasOneil 2025-03-24 Build site.
html d600337 DrThomasOneil 2025-03-18 Build site.
html b537f69 DrThomasOneil 2025-03-18 Build site.
html b7982e9 DrThomasOneil 2025-03-16 Build site.
html fc5dd81 DrThomasOneil 2025-03-16 Build site.
html 8e1c1a3 DrThomasOneil 2025-03-16 Build site.
html 36dc5fc DrThomasOneil 2025-03-03 Build site.
html 49bb28c DrThomasOneil 2025-03-03 Build site.
Rmd 97303a5 DrThomasOneil 2025-03-03 wflow_publish(c("analysis/*.Rmd"))
html 8258922 DrThomasOneil 2025-03-03 Build site.
html 71646a5 DrThomasOneil 2025-02-27 Build site.
html c7f5738 DrThomasOneil 2025-02-24 Build site.
html 79d09b1 DrThomasOneil 2025-02-24 Build site.
html a5c9f2c DrThomasOneil 2025-02-24 Build site.
html ca2c086 DrThomasOneil 2025-02-24 Build site.
html fca0503 DrThomasOneil 2025-02-24 Build site.
html af42fba DrThomasOneil 2025-02-24 Build site.
html 1aeefc7 DrThomasOneil 2025-02-20 Build site.
html 5fe30de DrThomasOneil 2025-02-20 Build site.
Rmd 477062c DrThomasOneil 2025-02-20 wflow_publish(c("analysis/*.Rmd"))
Rmd a48de9d DrThomasOneil 2025-02-20 new data

Data manipulation and plotting

Data manipulation and plotting are two of the most important tasks in data analysis. In this tutorial, we will learn how to manipulate data and create plots using the dplyr and ggplot2 packages in R. But first, let’s learn how to load data into R.

Setup

Packages

Below is the list of packages that we will be using in this tutorial. Remember, packages need to be installed only once, but they need to be loaded every time you start a new R session.

# Uncomment the following line to install the package if you haven't already! 
# install.packages(c("dplyr", "ggplot2", "readxl"))

# Load the packages
library(dplyr)
library(ggplot2)
library(readxl)

Create a new project

Set the directory to a common folder you’ll continue to use for these workshops. From here, you’ll be able to create subfolders in R for all new workshops.

# Set your working directory
knitr::opts_knit$set(root.dir = "/Users/thomasoneil/Desktop/analysis-user-group")

# Create a new folder for todays workshop
dir.create("20250210_Tidyverse")

Download the relevant data

knitr::opts_knit$set(root.dir = "/Users/thomasoneil/Desktop/analysis-user-group/20250210_Tidyverse")
#Download files
download.file("https://raw.githubusercontent.com/DrThomasOneil/analysis-user-group/refs/heads/main/docs/assets/202502_introSeries/data.rds", "data.rds", method = "curl")
download.file("https://raw.githubusercontent.com/DrThomasOneil/analysis-user-group/refs/heads/main/docs/assets/202502_introSeries/data.csv", "data.csv", method = "curl")
download.file("https://raw.githubusercontent.com/DrThomasOneil/analysis-user-group/refs/heads/main/docs/assets/202502_introSeries/data.tsv", "data.tsv", method = "curl")
download.file("https://raw.githubusercontent.com/DrThomasOneil/analysis-user-group/refs/heads/main/docs/assets/202502_introSeries/data.xlsx", "data.xlsx", method = "curl")

If these downloads dont work, run this in your console:

set.seed(123) 

data <- data.frame(
  patient_id = 1:1000,
  age = sample(18:90, 1000, replace = TRUE),
  sex = sample(c("Male", "Female"), 1000, replace = TRUE),
  height = rnorm(1000, mean = 170, sd = 10)
)

data$weight <- 0.3 * data$height + rnorm(1000, mean = 19, sd = 5)
data$bmi <- data$weight / (data$height / 100)^2

data$disease_status <- rbinom(1000, 1, prob = plogis(0.05 * (data$age - 50) + 0.1 * (data$bmi - 25)))

data$biomarker <- rnorm(1000, mean = 5, sd = 1) + 2 * data$disease_status

data$on_medication <- rbinom(1000, 1, prob = ifelse(data$disease_status == 1, 0.7, 0.1))

Loading data into R

There are several ways to load data into R. This will largely depend on the format of the data you are working with. Some of the most common ways to load data into R are:

  • Loading comma-seperated files (.csv)

  • Loading tab-delimited files (.tsv)

  • Loading Excel files (.xslx)

  • Loading RDS files (R’s native file format)

Loading comma-seperated files (.csv)

One common way is to use the read.csv() function to read a CSV file. For example, to read a file named data.csv, you can use the following code:

data <- read.csv("data.csv")
head(data)

Loading tab-delimited files (.tsv)

Another way to load data into R is to use the read.table() function. This function can be used to read tab-delimited files, as well as files with other delimiters. For example, to read a tab-delimited file named data.txt, you can use the following code.

data <- read.table("data.txt", sep = "\t", header = TRUE) 
# sep = "\t" specifies that the file is tab-delimited. 
# header = TRUE specifies that the first row contains column names. 
head(data)

Loading Excel files (.xslx)

Finally, a lot of collaborators will often give us data in the form of an Excel file. To read an Excel file into R, you can use the readxl package. First, you need to install the package using the following code:

# Uncomment the following line to install the package if you haven't already! 
# install.packages("readxl")
library(readxl)
data <- readxl::read_excel("data.xlsx")
head(data)

Loading RDS files

RDS files are R’s native file format. You can save an R object to an RDS file using the saveRDS() function, and then load it back into R using the readRDS() function. For example, to save an object named data to a file named data.rds, you can use the following code:

# Let's first save the data as an RDS file. 
saveRDS(data, "data.rds") 

data <- readRDS("data.rds") # To load the data back into R
head(data)

Simulating data in R

For todays tutorial, we will be simulating our own patient cohort data. We will create a data frame with 1000 rows and 5 columns. The columns will be named patient_id, age, sex, weight, and height.

We will make use of two functions to generate the data: sample() and rnorm().

  • The sample() function is used to generate random samples from a vector.

  • The rnorm() function is used to generate random numbers from a normal distribution.

set.seed(123) 

# Simulating dataset
data <- data.frame(
  patient_id = 1:1000,
  age = sample(18:90, 1000, replace = TRUE),
  sex = sample(c("Male", "Female"), 1000, replace = TRUE),
  height = rnorm(1000, mean = 170, sd = 10)
)

# Simulating weight and BMI
data$weight <- 0.3 * data$height + rnorm(1000, mean = 19, sd = 5)
data$bmi <- data$weight / (data$height / 100)^2

# Disease status: More likely in older individuals and those with higher BMI
data$disease_status <- rbinom(1000, 1, prob = plogis(0.05 * (data$age - 50) + 0.1 * (data$bmi - 25)))

# Biomarker: Higher levels in those with disease
data$biomarker <- rnorm(1000, mean = 5, sd = 1) + 2 * data$disease_status

# Medication usage: More common in those with disease
data$on_medication <- rbinom(1000, 1, prob = ifelse(data$disease_status == 1, 0.7, 0.1))

# View first rows
head(data)
  patient_id age    sex   height   weight      bmi disease_status biomarker
1          1  48 Female 148.2445 65.98587 30.02573              1  7.463864
2          2  68 Female 178.2761 74.95921 23.58517              1  6.262941
3          3  31 Female 157.2832 58.00624 23.44824              0  3.633938
4          4  84   Male 170.7195 73.53580 25.23090              1  8.449282
5          5  59 Female 158.1606 72.18717 28.85781              1  6.065618
6          6  67   Male 171.3138 76.33345 26.00938              1  7.133792
  on_medication
1             0
2             1
3             0
4             0
5             1
6             0

Data manipulation with dplyr

The dplyr package is a powerful tool for data manipulation in R. It provides a set of functions that make it easy to filter, arrange, group, and summarize data. Some of the most commonly used functions in dplyr are: - filter(): to filter rows based on a condition - arrange(): to reorder rows - select(): to select columns - mutate(): to create new columns.

data %>%
  head()
  patient_id age    sex   height   weight      bmi disease_status biomarker
1          1  48 Female 148.2445 65.98587 30.02573              1  7.463864
2          2  68 Female 178.2761 74.95921 23.58517              1  6.262941
3          3  31 Female 157.2832 58.00624 23.44824              0  3.633938
4          4  84   Male 170.7195 73.53580 25.23090              1  8.449282
5          5  59 Female 158.1606 72.18717 28.85781              1  6.065618
6          6  67   Male 171.3138 76.33345 26.00938              1  7.133792
  on_medication
1             0
2             1
3             0
4             0
5             1
6             0

Filtering data

The filter() function is used to filter rows based on a condition. For example, to filter rows where the age column is greater than 60, you can use the following code:

older_age_data = data %>%
  filter(age > 60)
head(older_age_data, n = 10)
   patient_id age    sex   height   weight      bmi disease_status biomarker
1           2  68 Female 178.2761 74.95921 23.58517              1  6.262941
2           4  84   Male 170.7195 73.53580 25.23090              1  8.449282
3           6  67   Male 171.3138 76.33345 26.00938              1  7.133792
4          10  86   Male 164.4221 69.03427 25.53550              1  8.584147
5          11  74 Female 166.8154 72.20822 25.94862              1  6.387301
6          13  89   Male 164.2035 77.98983 28.92498              1  7.648540
7          27  77   Male 167.4946 58.32532 20.79004              1  7.583616
8          28  70 Female 173.9534 72.19051 23.85690              0  5.726190
9          30  70   Male 171.4386 69.27874 23.57126              1  6.294323
10         34  86 Female 186.1114 74.16929 21.41303              1  6.800364
   on_medication
1              1
2              0
3              0
4              0
5              1
6              0
7              1
8              0
9              0
10             1

Arranging data

The arrange() function is used to reorder rows based on a variable. For example, to arrange the data by increasing age, you can use the following code:

age_increasing_data = data %>%
  arrange(age) 
head(age_increasing_data, n = 10)
   patient_id age    sex   height   weight      bmi disease_status biomarker
1         104  18 Female 169.2076 73.18860 25.56252              0  6.226862
2         166  18   Male 165.5840 73.67305 26.87025              1  7.603067
3         435  18   Male 173.0596 72.76448 24.29561              0  5.749559
4         481  18 Female 160.1326 69.12269 26.95635              0  4.550437
5         715  18 Female 181.0531 76.28598 23.27196              0  5.196493
6         740  18 Female 167.4844 56.63041 20.18835              0  4.460689
7         879  18   Male 167.6281 59.05507 21.01665              1  8.742190
8         989  18   Male 179.3585 75.66011 23.51923              0  2.761182
9         206  19 Female 162.9964 67.13434 25.26905              0  5.478242
10        208  19   Male 156.8160 60.44886 24.58143              0  5.044170
   on_medication
1              0
2              0
3              0
4              1
5              0
6              0
7              0
8              0
9              0
10             0

Selecting variables

The select() function is used to select columns from a data frame. For example, to select only the sex and disease_status columns, you can use the following code:

selected_data <- data %>%
  select(sex, disease_status)
head(selected_data)
     sex disease_status
1 Female              1
2 Female              1
3 Female              0
4   Male              1
5 Female              1
6   Male              1

This is useful if you want to go on to analyze a smaller subset of data.

table(selected_data)
        disease_status
sex        0   1
  Female 234 252
  Male   225 289

Adding variables

The mutate() function is used to create new columns based on existing columns. For example, you might want to create a new variable that categorizes patients based on their biomarker level.

biomarker_data <- data %>%
  mutate(biomarker_category = ifelse(biomarker > 5, "High", "Low"))

Advanced - Transmute

The transmute() function works like mutate() but returns only the newly created columns. For example, you might want to create a dataset with only the patient ID and a risk score calculated from age and BMI.

risk_data <- data %>%
  transmute(
    patient_id,
    risk_score = 0.05 * age + 0.1 * bmi
  )
head(risk_data)
  patient_id risk_score
1          1   5.402573
2          2   5.758517
3          3   3.894824
4          4   6.723090
5          5   5.835781
6          6   5.950938

Advanced - case_when

The case_when() function is a more flexible version of ifelse(). It allows you to specify multiple conditions and corresponding values. For example, you might want to create a new variable that categorizes patients based on their BMI.

data <- data %>%
  mutate(bmi_category = case_when(
    bmi < 18.5 ~ "Underweight",
    bmi >= 18.5 & bmi < 25 ~ "Normal",
    bmi >= 25 & bmi < 30 ~ "Overweight",
    bmi >= 30 ~ "Obese"
  ))
head(data, n = 10)
   patient_id age    sex   height   weight      bmi disease_status biomarker
1           1  48 Female 148.2445 65.98587 30.02573              1  7.463864
2           2  68 Female 178.2761 74.95921 23.58517              1  6.262941
3           3  31 Female 157.2832 58.00624 23.44824              0  3.633938
4           4  84   Male 170.7195 73.53580 25.23090              1  8.449282
5           5  59 Female 158.1606 72.18717 28.85781              1  6.065618
6           6  67   Male 171.3138 76.33345 26.00938              1  7.133792
7           7  60   Male 168.5015 70.16094 24.71087              1  7.941038
8           8  31 Female 184.1604 75.25100 22.18807              0  5.458139
9           9  42   Male 169.0342 69.31874 24.26060              0  4.538404
10         10  86   Male 164.4221 69.03427 25.53550              1  8.584147
   on_medication bmi_category
1              0        Obese
2              1       Normal
3              0       Normal
4              0   Overweight
5              1   Overweight
6              0   Overweight
7              0       Normal
8              0       Normal
9              0       Normal
10             0   Overweight

Plotting with ggplot2

The ggplot2 package is a powerful tool for creating plots in R. It provides a flexible and intuitive syntax for creating a wide variety of plots, including scatter plots, bar plots, line plots, and more.

The basic idea of ggplot2 is to create a plot in layers. You start by creating a new plot using the ggplot() function, and then add layers to the plot using functions like geom_point(), geom_line(), geom_bar(), and so on.

Below we will initiate our first plot!

library(ggplot2)
ggplot(data, aes(x = weight, y = height))

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Obviously this isn’t useful yet, but we will now add layers on top of our canvas to create a plot!

Scatter plots

The ggplot() function is used to create a new plot, and the geom_point() function is used to add points to the plot.

ggplot(data, aes(x = weight, y = height)) + geom_point()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

We can also customize the plot by changing the color and transparency of the points within the geom_point() function!

ggplot(data, aes(x = weight, y = height)) + geom_point(color = "green4", alpha = 0.5)

Version Author Date
5fe30de DrThomasOneil 2025-02-20

We can also add titles and labels to the plot using the labs() function.

ggplot(data, aes(x = weight, y = height)) + geom_point(color = "green4", alpha = 0.5) +
  labs(title = "Scatter Plot of Weight vs Height",
       x = "Weight (kg)",
       y = "Height (cm)")

Version Author Date
5fe30de DrThomasOneil 2025-02-20

We can also change the theme of the plot using the theme_bw() function!

ggplot(data, aes(x = weight, y = height)) + geom_point(color = "green4", alpha = 0.5) +
  labs(title = "Scatter Plot of Weight vs Height",
       x = "Weight (kg)",
       y = "Height (cm)") +
  theme_bw()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Box plots

ggplot(data, aes(x = factor(disease_status), y = bmi, fill = factor(disease_status))) + geom_boxplot()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

The current set up of using 0 and 1 to represent disease status is not very informative. Let’s change the labels to “No Disease” and “Disease” using the factor() function. We will make use of mutate() to create a new column with the new labels.

box_plot_data <- data %>%
  select(disease_status, bmi) %>%
  mutate(disease_status = factor(disease_status, levels = c(0, 1), labels = c("No Disease", "Disease")))
ggplot(box_plot_data, aes(x = disease_status, y = bmi, fill = disease_status)) + geom_boxplot() 

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Line plots

For illustrative purposes, you can simulate a trend over age by summarizing the average biomarker level per age group. Here we use two new functions group_by() and summarize() from the dplyr package to calculate the average biomarker level per age group.
Simply, the group_by() function is used to group the data by a variable, and the summarize() function is used to calculate summary statistics for each group.

age_trend <- data %>%
  group_by(age) %>%
  summarize(avg_biomarker = mean(biomarker))

ggplot(age_trend, aes(x = age, y = avg_biomarker)) +
  geom_line(color = "purple", size = 1) +
  labs(title = "Average Biomarker Level by Age",
       x = "Age",
       y = "Average Biomarker Level") +
  theme_bw()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
generated.

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Bar plots

data %>%
  mutate(bmi_category = case_when(
    bmi < 18.5 ~ "Underweight",
    bmi >= 18.5 & bmi < 25 ~ "Normal",
    bmi >= 25 & bmi < 30 ~ "Overweight",
    bmi >= 30 ~ "Obese"
  )) %>%
ggplot(aes(x = bmi_category, fill = factor(on_medication))) +
  geom_bar(position = "fill") +
  scale_fill_manual(values = c("lightgrey", "dodgerblue"),
                    labels = c("Not on Medication", "On Medication")) +
  labs(title = "Proportion of Medication Use by BMI Category",
       x = "\nBMI Category",
       y = "Proportion",
       fill = "Medication") +
  theme_minimal()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Histograms

ggplot(data, aes(x = biomarker)) +
  geom_histogram(binwidth = 0.5, fill = "cornflowerblue", color = "black") +
  labs(title = "Histogram of Biomarker Levels",
       x = "Biomarker Level",
       y = "Frequency") +
  theme_minimal()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Advanced plotting

ggplot2 has a lot of advanced features that allow you to create more complex and customized plots.

Plotting multiple layers

You can plot multiple datasets on the same plot by adding multiple geom_ layers. For example, let’s plot the average biomarker level by age group and overlay the individual biomarker levels. (This doesn’t make a ton of sense but just shows you how to overlay multiple layers!)

ggplot(age_trend, aes(x = age, y = avg_biomarker)) +
  geom_line(color = "purple", size = 1) +
  geom_point(data = data, aes(x = age, y = biomarker), color = "steelblue", alpha = 0.5) +
  labs(title = "Average Biomarker Level by Age with Individual Data Points",
       x = "Age",
       y = "Biomarker Level") +
  theme_bw()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Plotting with facets

Faceting is a powerful technique for creating multiple plots based on the levels of a categorical variable. For instance, we can compare biomarker distributions between patient sexes.

ggplot(data, aes(x = biomarker, fill = factor(disease_status))) +
  geom_histogram(binwidth = 0.5, color = "black", alpha = 0.7) +
  facet_wrap(.~ sex) +
  scale_fill_manual(values = c("lightblue", "tomato"),
                    labels = c("No Disease", "Disease")) +
  labs(title = "Biomarker Distribution by Disease Status and Sex",
       x = "Biomarker Level",
       y = "Count",
       fill = "Disease Status") +
  theme_bw()

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Plotting with statistics

You can overlay statistical summaries (such as regression lines) using geom_smooth(). For example, let’s add a regression line to the scatter plot of weight versus height.

ggplot(data, aes(x = weight, y = height)) +
  geom_point(color = "steelblue", alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE, color = "darkred") +
  labs(title = "Scatter Plot of Weight vs Height with Regression Line",
       x = "Weight (kg)",
       y = "Height (cm)") +
  theme_minimal()
`geom_smooth()` using formula = 'y ~ x'

Version Author Date
5fe30de DrThomasOneil 2025-02-20

We can also perform basic statistical tests between groups. For example, we can compare the average BMI between patients with and without disease using the stat_compare_means() function from the ggpubr package.

# Uncomment the following line to install the package if you haven't already!
# install.packages("ggpubr")
library(ggpubr)
box_plot_data <- data %>%
  select(disease_status, bmi) %>%
  mutate(disease_status = factor(disease_status, levels = c(0, 1), labels = c("No Disease", "Disease")))
ggplot(box_plot_data, aes(x = disease_status, y = bmi, fill = disease_status)) + geom_boxplot() + theme_bw() + stat_compare_means(method = "t.test")

Version Author Date
5fe30de DrThomasOneil 2025-02-20

Conclusion

In this tutorial, we learned how to load data into R, manipulate data using the dplyr package, and create plots using the ggplot2 package. We also learned how to customize plots and create more advanced plots. These skills are essential for any data analyst or scientist working with R.


sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS Sonoma 14.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Australia/Sydney
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggpubr_0.6.0    readxl_1.4.5    ggplot2_3.5.1   dplyr_1.1.4    
[5] workflowr_1.7.1

loaded via a namespace (and not attached):
 [1] tidyr_1.3.1       sass_0.4.9        generics_0.1.3    rstatix_0.7.2    
 [5] lattice_0.22-6    stringi_1.8.4     digest_0.6.37     magrittr_2.0.3   
 [9] evaluate_1.0.3    grid_4.4.0        fastmap_1.2.0     Matrix_1.7-3     
[13] cellranger_1.1.0  rprojroot_2.0.4   jsonlite_1.9.1    processx_3.8.6   
[17] whisker_0.4.1     backports_1.5.0   Formula_1.2-5     ps_1.9.0         
[21] promises_1.3.2    httr_1.4.7        mgcv_1.9-1        purrr_1.0.4      
[25] scales_1.3.0      jquerylib_0.1.4   abind_1.4-8       cli_3.6.4        
[29] rlang_1.1.5       crayon_1.5.3      munsell_0.5.1     splines_4.4.0    
[33] withr_3.0.2       cachem_1.1.0      yaml_2.3.10       tools_4.4.0      
[37] ggsignif_0.6.4    colorspace_2.1-1  httpuv_1.6.15     broom_1.0.7      
[41] vctrs_0.6.5       R6_2.6.1          lifecycle_1.0.4   git2r_0.35.0     
[45] stringr_1.5.1     car_3.1-3         fs_1.6.5          pkgconfig_2.0.3  
[49] callr_3.7.6       pillar_1.10.1     bslib_0.9.0       later_1.4.1      
[53] gtable_0.3.6      glue_1.8.0        Rcpp_1.0.14       xfun_0.51        
[57] tibble_3.2.1      tidyselect_1.2.1  rstudioapi_0.17.1 knitr_1.50       
[61] farver_2.1.2      nlme_3.1-167      htmltools_0.5.8.1 carData_3.0-5    
[65] rmarkdown_2.29    labeling_0.4.3    compiler_4.4.0    getPass_0.2-4    
LS0tCnRpdGxlOiAiVGlkeXZlcnNlIDE6IERhdGEgUGxvdHRpbmciCmF1dGhvcjogIkhhcnJ5IFJvYmVydHNvbiIKZGF0ZTogIjIwMjUtMDItMTAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZmlnX2NhcHRpb246IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIGNzczogIAogICAgICAtIGh0dHBzOi8vdXNlLmZvbnRhd2Vzb21lLmNvbS9yZWxlYXNlcy92NS4wLjYvY3NzL2FsbC5jc3MKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VCkKYGBgCgojIyBEYXRhIG1hbmlwdWxhdGlvbiBhbmQgcGxvdHRpbmcKCkRhdGEgbWFuaXB1bGF0aW9uIGFuZCBwbG90dGluZyBhcmUgdHdvIG9mIHRoZSBtb3N0IGltcG9ydGFudCB0YXNrcyBpbgpkYXRhIGFuYWx5c2lzLiBJbiB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIGxlYXJuIGhvdyB0byBtYW5pcHVsYXRlIGRhdGEKYW5kIGNyZWF0ZSBwbG90cyB1c2luZyB0aGUgYGRwbHlyYCBhbmQgYGdncGxvdDJgIHBhY2thZ2VzIGluIFIuIEJ1dApmaXJzdCwgbGV0J3MgbGVhcm4gaG93IHRvIGxvYWQgZGF0YSBpbnRvIFIuCgo8d2ltcj4gCgojIyBTZXR1cHsudGFic2V0IC50YWJzZXQtZmFkZX0KCiMjIyBQYWNrYWdlcwoKQmVsb3cgaXMgdGhlIGxpc3Qgb2YgcGFja2FnZXMgdGhhdCB3ZSB3aWxsIGJlIHVzaW5nIGluIHRoaXMgdHV0b3JpYWwuClJlbWVtYmVyLCBwYWNrYWdlcyBuZWVkIHRvIGJlIGluc3RhbGxlZCBvbmx5IG9uY2UsIGJ1dCB0aGV5IG5lZWQgdG8gYmUKbG9hZGVkIGV2ZXJ5IHRpbWUgeW91IHN0YXJ0IGEgbmV3IFIgc2Vzc2lvbi4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQojIFVuY29tbWVudCB0aGUgZm9sbG93aW5nIGxpbmUgdG8gaW5zdGFsbCB0aGUgcGFja2FnZSBpZiB5b3UgaGF2ZW4ndCBhbHJlYWR5ISAKIyBpbnN0YWxsLnBhY2thZ2VzKGMoImRwbHlyIiwgImdncGxvdDIiLCAicmVhZHhsIikpCgojIExvYWQgdGhlIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShyZWFkeGwpCmBgYAo8d2ltcj4KCiMjIyBDcmVhdGUgYSBuZXcgcHJvamVjdAoKU2V0IHRoZSBkaXJlY3RvcnkgdG8gYSBjb21tb24gZm9sZGVyIHlvdSdsbCBjb250aW51ZSB0byB1c2UgZm9yIHRoZXNlIHdvcmtzaG9wcy4gCkZyb20gaGVyZSwgeW91J2xsIGJlIGFibGUgdG8gY3JlYXRlIHN1YmZvbGRlcnMgKippbiBSKiogZm9yIGFsbCBuZXcgd29ya3Nob3BzLiAKCmBgYHtyLCBldmFsPUZ9CiMgU2V0IHlvdXIgd29ya2luZyBkaXJlY3RvcnkKa25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAiL1VzZXJzL3Rob21hc29uZWlsL0Rlc2t0b3AvYW5hbHlzaXMtdXNlci1ncm91cCIpCgojIENyZWF0ZSBhIG5ldyBmb2xkZXIgZm9yIHRvZGF5cyB3b3Jrc2hvcApkaXIuY3JlYXRlKCIyMDI1MDIxMF9UaWR5dmVyc2UiKQpgYGAKPHdpbXI+CgojIyMgRG93bmxvYWQgdGhlIHJlbGV2YW50IGRhdGEKCmBgYHtyLCBldmFsPUZ9CmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gIi9Vc2Vycy90aG9tYXNvbmVpbC9EZXNrdG9wL2FuYWx5c2lzLXVzZXItZ3JvdXAvMjAyNTAyMTBfVGlkeXZlcnNlIikKI0Rvd25sb2FkIGZpbGVzCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9EclRob21hc09uZWlsL2FuYWx5c2lzLXVzZXItZ3JvdXAvcmVmcy9oZWFkcy9tYWluL2RvY3MvYXNzZXRzLzIwMjUwMl9pbnRyb1Nlcmllcy9kYXRhLnJkcyIsICJkYXRhLnJkcyIsIG1ldGhvZCA9ICJjdXJsIikKZG93bmxvYWQuZmlsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0RyVGhvbWFzT25laWwvYW5hbHlzaXMtdXNlci1ncm91cC9yZWZzL2hlYWRzL21haW4vZG9jcy9hc3NldHMvMjAyNTAyX2ludHJvU2VyaWVzL2RhdGEuY3N2IiwgImRhdGEuY3N2IiwgbWV0aG9kID0gImN1cmwiKQpkb3dubG9hZC5maWxlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vRHJUaG9tYXNPbmVpbC9hbmFseXNpcy11c2VyLWdyb3VwL3JlZnMvaGVhZHMvbWFpbi9kb2NzL2Fzc2V0cy8yMDI1MDJfaW50cm9TZXJpZXMvZGF0YS50c3YiLCAiZGF0YS50c3YiLCBtZXRob2QgPSAiY3VybCIpCmRvd25sb2FkLmZpbGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9EclRob21hc09uZWlsL2FuYWx5c2lzLXVzZXItZ3JvdXAvcmVmcy9oZWFkcy9tYWluL2RvY3MvYXNzZXRzLzIwMjUwMl9pbnRyb1Nlcmllcy9kYXRhLnhsc3giLCAiZGF0YS54bHN4IiwgbWV0aG9kID0gImN1cmwiKQpgYGAKCklmIHRoZXNlIGRvd25sb2FkcyBkb250IHdvcmssIHJ1biB0aGlzIGluIHlvdXIgY29uc29sZToKCmBgYHtyfQpzZXQuc2VlZCgxMjMpIAoKZGF0YSA8LSBkYXRhLmZyYW1lKAogIHBhdGllbnRfaWQgPSAxOjEwMDAsCiAgYWdlID0gc2FtcGxlKDE4OjkwLCAxMDAwLCByZXBsYWNlID0gVFJVRSksCiAgc2V4ID0gc2FtcGxlKGMoIk1hbGUiLCAiRmVtYWxlIiksIDEwMDAsIHJlcGxhY2UgPSBUUlVFKSwKICBoZWlnaHQgPSBybm9ybSgxMDAwLCBtZWFuID0gMTcwLCBzZCA9IDEwKQopCgpkYXRhJHdlaWdodCA8LSAwLjMgKiBkYXRhJGhlaWdodCArIHJub3JtKDEwMDAsIG1lYW4gPSAxOSwgc2QgPSA1KQpkYXRhJGJtaSA8LSBkYXRhJHdlaWdodCAvIChkYXRhJGhlaWdodCAvIDEwMCleMgoKZGF0YSRkaXNlYXNlX3N0YXR1cyA8LSByYmlub20oMTAwMCwgMSwgcHJvYiA9IHBsb2dpcygwLjA1ICogKGRhdGEkYWdlIC0gNTApICsgMC4xICogKGRhdGEkYm1pIC0gMjUpKSkKCmRhdGEkYmlvbWFya2VyIDwtIHJub3JtKDEwMDAsIG1lYW4gPSA1LCBzZCA9IDEpICsgMiAqIGRhdGEkZGlzZWFzZV9zdGF0dXMKCmRhdGEkb25fbWVkaWNhdGlvbiA8LSByYmlub20oMTAwMCwgMSwgcHJvYiA9IGlmZWxzZShkYXRhJGRpc2Vhc2Vfc3RhdHVzID09IDEsIDAuNywgMC4xKSkKYGBgCgoKPHdpbXI+CgojIyBMb2FkaW5nIGRhdGEgaW50byBSIHsudGFic2V0fQoKVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBsb2FkIGRhdGEgaW50byBSLiBUaGlzIHdpbGwgbGFyZ2VseSBkZXBlbmQgb24KdGhlIGZvcm1hdCBvZiB0aGUgZGF0YSB5b3UgYXJlIHdvcmtpbmcgd2l0aC4gU29tZSBvZiB0aGUgbW9zdCBjb21tb24Kd2F5cyB0byBsb2FkIGRhdGEgaW50byBSIGFyZToKCi0gICBMb2FkaW5nIGNvbW1hLXNlcGVyYXRlZCBmaWxlcyAoLmNzdikKCi0gICBMb2FkaW5nIHRhYi1kZWxpbWl0ZWQgZmlsZXMgKC50c3YpCgotICAgTG9hZGluZyBFeGNlbCBmaWxlcyAoLnhzbHgpCgotICAgTG9hZGluZyBSRFMgZmlsZXMgKFIncyBuYXRpdmUgZmlsZSBmb3JtYXQpCgoKIyMjIExvYWRpbmcgY29tbWEtc2VwZXJhdGVkIGZpbGVzICguY3N2KQoKT25lIGNvbW1vbiB3YXkgaXMgdG8gdXNlIHRoZSBgcmVhZC5jc3YoKWAgZnVuY3Rpb24gdG8gcmVhZCBhIENTViBmaWxlLgpGb3IgZXhhbXBsZSwgdG8gcmVhZCBhIGZpbGUgbmFtZWQgYGRhdGEuY3N2YCwgeW91IGNhbiB1c2UgdGhlIGZvbGxvd2luZwpjb2RlOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGF0YSA8LSByZWFkLmNzdigiZGF0YS5jc3YiKQpoZWFkKGRhdGEpCmBgYAo8d2ltcj4KCiMjIyBMb2FkaW5nIHRhYi1kZWxpbWl0ZWQgZmlsZXMgKC50c3YpCgpBbm90aGVyIHdheSB0byBsb2FkIGRhdGEgaW50byBSIGlzIHRvIHVzZSB0aGUgYHJlYWQudGFibGUoKWAgZnVuY3Rpb24uClRoaXMgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gcmVhZCB0YWItZGVsaW1pdGVkIGZpbGVzLCBhcyB3ZWxsIGFzIGZpbGVzCndpdGggb3RoZXIgZGVsaW1pdGVycy4gRm9yIGV4YW1wbGUsIHRvIHJlYWQgYSB0YWItZGVsaW1pdGVkIGZpbGUgbmFtZWQKYGRhdGEudHh0YCwgeW91IGNhbiB1c2UgdGhlIGZvbGxvd2luZyBjb2RlLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGF0YSA8LSByZWFkLnRhYmxlKCJkYXRhLnR4dCIsIHNlcCA9ICJcdCIsIGhlYWRlciA9IFRSVUUpIAojIHNlcCA9ICJcdCIgc3BlY2lmaWVzIHRoYXQgdGhlIGZpbGUgaXMgdGFiLWRlbGltaXRlZC4gCiMgaGVhZGVyID0gVFJVRSBzcGVjaWZpZXMgdGhhdCB0aGUgZmlyc3Qgcm93IGNvbnRhaW5zIGNvbHVtbiBuYW1lcy4gCmhlYWQoZGF0YSkKYGBgCgo8d2ltcj4KCiMjIyBMb2FkaW5nIEV4Y2VsIGZpbGVzICgueHNseCkKCkZpbmFsbHksIGEgbG90IG9mIGNvbGxhYm9yYXRvcnMgd2lsbCBvZnRlbiBnaXZlIHVzIGRhdGEgaW4gdGhlIGZvcm0gb2YKYW4gRXhjZWwgZmlsZS4gVG8gcmVhZCBhbiBFeGNlbCBmaWxlIGludG8gUiwgeW91IGNhbiB1c2UgdGhlIGByZWFkeGxgCnBhY2thZ2UuIEZpcnN0LCB5b3UgbmVlZCB0byBpbnN0YWxsIHRoZSBwYWNrYWdlIHVzaW5nIHRoZSBmb2xsb3dpbmcKY29kZToKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMgVW5jb21tZW50IHRoZSBmb2xsb3dpbmcgbGluZSB0byBpbnN0YWxsIHRoZSBwYWNrYWdlIGlmIHlvdSBoYXZlbid0IGFscmVhZHkhIAojIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCmxpYnJhcnkocmVhZHhsKQpkYXRhIDwtIHJlYWR4bDo6cmVhZF9leGNlbCgiZGF0YS54bHN4IikKaGVhZChkYXRhKQpgYGAKCjx3aW1yPgoKIyMjIExvYWRpbmcgUkRTIGZpbGVzCgpSRFMgZmlsZXMgYXJlIFIncyBuYXRpdmUgZmlsZSBmb3JtYXQuIFlvdSBjYW4gc2F2ZSBhbiBSIG9iamVjdCB0byBhbiBSRFMKZmlsZSB1c2luZyB0aGUgYHNhdmVSRFMoKWAgZnVuY3Rpb24sIGFuZCB0aGVuIGxvYWQgaXQgYmFjayBpbnRvIFIgdXNpbmcKdGhlIGByZWFkUkRTKClgIGZ1bmN0aW9uLiBGb3IgZXhhbXBsZSwgdG8gc2F2ZSBhbiBvYmplY3QgbmFtZWQgYGRhdGFgIHRvCmEgZmlsZSBuYW1lZCBgZGF0YS5yZHNgLCB5b3UgY2FuIHVzZSB0aGUgZm9sbG93aW5nIGNvZGU6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojIExldCdzIGZpcnN0IHNhdmUgdGhlIGRhdGEgYXMgYW4gUkRTIGZpbGUuIApzYXZlUkRTKGRhdGEsICJkYXRhLnJkcyIpIAoKZGF0YSA8LSByZWFkUkRTKCJkYXRhLnJkcyIpICMgVG8gbG9hZCB0aGUgZGF0YSBiYWNrIGludG8gUgpoZWFkKGRhdGEpCmBgYAoKPHdpbXI+CgojIyBTaW11bGF0aW5nIGRhdGEgaW4gUgoKRm9yIHRvZGF5cyB0dXRvcmlhbCwgd2Ugd2lsbCBiZSBzaW11bGF0aW5nIG91ciBvd24gcGF0aWVudCBjb2hvcnQgZGF0YS4KV2Ugd2lsbCBjcmVhdGUgYSBkYXRhIGZyYW1lIHdpdGggMTAwMCByb3dzIGFuZCA1IGNvbHVtbnMuIFRoZSBjb2x1bW5zCndpbGwgYmUgbmFtZWQgYHBhdGllbnRfaWRgLCBgYWdlYCwgYHNleGAsIGB3ZWlnaHRgLCBhbmQgYGhlaWdodGAuCgpXZSB3aWxsIG1ha2UgdXNlIG9mIHR3byBmdW5jdGlvbnMgdG8gZ2VuZXJhdGUgdGhlIGRhdGE6IGBzYW1wbGUoKWAgYW5kCmBybm9ybSgpYC4KCi0gICBUaGUgYHNhbXBsZSgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGdlbmVyYXRlIHJhbmRvbSBzYW1wbGVzIGZyb20gYQogICAgdmVjdG9yLgoKLSAgIFRoZSBgcm5vcm0oKWAgZnVuY3Rpb24gaXMgdXNlZCB0byBnZW5lcmF0ZSByYW5kb20gbnVtYmVycyBmcm9tIGEKICAgIG5vcm1hbCBkaXN0cmlidXRpb24uCgpgYGB7ciBzaW11bGF0aW5nIGRhdGF9CnNldC5zZWVkKDEyMykgCgojIFNpbXVsYXRpbmcgZGF0YXNldApkYXRhIDwtIGRhdGEuZnJhbWUoCiAgcGF0aWVudF9pZCA9IDE6MTAwMCwKICBhZ2UgPSBzYW1wbGUoMTg6OTAsIDEwMDAsIHJlcGxhY2UgPSBUUlVFKSwKICBzZXggPSBzYW1wbGUoYygiTWFsZSIsICJGZW1hbGUiKSwgMTAwMCwgcmVwbGFjZSA9IFRSVUUpLAogIGhlaWdodCA9IHJub3JtKDEwMDAsIG1lYW4gPSAxNzAsIHNkID0gMTApCikKCiMgU2ltdWxhdGluZyB3ZWlnaHQgYW5kIEJNSQpkYXRhJHdlaWdodCA8LSAwLjMgKiBkYXRhJGhlaWdodCArIHJub3JtKDEwMDAsIG1lYW4gPSAxOSwgc2QgPSA1KQpkYXRhJGJtaSA8LSBkYXRhJHdlaWdodCAvIChkYXRhJGhlaWdodCAvIDEwMCleMgoKIyBEaXNlYXNlIHN0YXR1czogTW9yZSBsaWtlbHkgaW4gb2xkZXIgaW5kaXZpZHVhbHMgYW5kIHRob3NlIHdpdGggaGlnaGVyIEJNSQpkYXRhJGRpc2Vhc2Vfc3RhdHVzIDwtIHJiaW5vbSgxMDAwLCAxLCBwcm9iID0gcGxvZ2lzKDAuMDUgKiAoZGF0YSRhZ2UgLSA1MCkgKyAwLjEgKiAoZGF0YSRibWkgLSAyNSkpKQoKIyBCaW9tYXJrZXI6IEhpZ2hlciBsZXZlbHMgaW4gdGhvc2Ugd2l0aCBkaXNlYXNlCmRhdGEkYmlvbWFya2VyIDwtIHJub3JtKDEwMDAsIG1lYW4gPSA1LCBzZCA9IDEpICsgMiAqIGRhdGEkZGlzZWFzZV9zdGF0dXMKCiMgTWVkaWNhdGlvbiB1c2FnZTogTW9yZSBjb21tb24gaW4gdGhvc2Ugd2l0aCBkaXNlYXNlCmRhdGEkb25fbWVkaWNhdGlvbiA8LSByYmlub20oMTAwMCwgMSwgcHJvYiA9IGlmZWxzZShkYXRhJGRpc2Vhc2Vfc3RhdHVzID09IDEsIDAuNywgMC4xKSkKCiMgVmlldyBmaXJzdCByb3dzCmhlYWQoZGF0YSkKYGBgCgoKPHdpbXI+CgojIyBEYXRhIG1hbmlwdWxhdGlvbiB3aXRoIGRwbHlyIHsudGFic2V0fQoKVGhlIGBkcGx5cmAgcGFja2FnZSBpcyBhIHBvd2VyZnVsIHRvb2wgZm9yIGRhdGEgbWFuaXB1bGF0aW9uIGluIFIuIEl0CnByb3ZpZGVzIGEgc2V0IG9mIGZ1bmN0aW9ucyB0aGF0IG1ha2UgaXQgZWFzeSB0byBmaWx0ZXIsIGFycmFuZ2UsIGdyb3VwLAphbmQgc3VtbWFyaXplIGRhdGEuIFNvbWUgb2YgdGhlIG1vc3QgY29tbW9ubHkgdXNlZCBmdW5jdGlvbnMgaW4gYGRwbHlyYAphcmU6IC0gYGZpbHRlcigpYDogdG8gZmlsdGVyIHJvd3MgYmFzZWQgb24gYSBjb25kaXRpb24gLSBgYXJyYW5nZSgpYDogdG8KcmVvcmRlciByb3dzIC0gYHNlbGVjdCgpYDogdG8gc2VsZWN0IGNvbHVtbnMgLSBgbXV0YXRlKClgOiB0byBjcmVhdGUgbmV3CmNvbHVtbnMuCgpgYGB7cn0KZGF0YSAlPiUKICBoZWFkKCkKYGBgCgojIyMgRmlsdGVyaW5nIGRhdGEKClRoZSBgZmlsdGVyKClgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gZmlsdGVyIHJvd3MgYmFzZWQgb24gYSBjb25kaXRpb24uIEZvcgpleGFtcGxlLCB0byBmaWx0ZXIgcm93cyB3aGVyZSB0aGUgYGFnZWAgY29sdW1uIGlzIGdyZWF0ZXIgdGhhbiA2MCwgeW91CmNhbiB1c2UgdGhlIGZvbGxvd2luZyBjb2RlOgoKYGBge3J9Cm9sZGVyX2FnZV9kYXRhID0gZGF0YSAlPiUKICBmaWx0ZXIoYWdlID4gNjApCmhlYWQob2xkZXJfYWdlX2RhdGEsIG4gPSAxMCkKYGBgCgo8d2ltcj4KCiMjIyBBcnJhbmdpbmcgZGF0YQoKVGhlIGBhcnJhbmdlKClgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gcmVvcmRlciByb3dzIGJhc2VkIG9uIGEgdmFyaWFibGUuIEZvciBleGFtcGxlLCB0byBhcnJhbmdlIHRoZSBkYXRhIGJ5IGluY3JlYXNpbmcgYWdlLCB5b3UgY2FuIHVzZSB0aGUgZm9sbG93aW5nIGNvZGU6CgpgYGB7cn0KYWdlX2luY3JlYXNpbmdfZGF0YSA9IGRhdGEgJT4lCiAgYXJyYW5nZShhZ2UpIApoZWFkKGFnZV9pbmNyZWFzaW5nX2RhdGEsIG4gPSAxMCkKYGBgCjx3aW1yPgoKIyMjIFNlbGVjdGluZyB2YXJpYWJsZXMKClRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gc2VsZWN0IGNvbHVtbnMgZnJvbSBhIGRhdGEgZnJhbWUuIEZvciBleGFtcGxlLCB0byBzZWxlY3Qgb25seSB0aGUgYHNleGAgYW5kIGBkaXNlYXNlX3N0YXR1c2AgY29sdW1ucywgeW91IGNhbiB1c2UgdGhlIGZvbGxvd2luZyBjb2RlOgoKYGBge3J9CnNlbGVjdGVkX2RhdGEgPC0gZGF0YSAlPiUKICBzZWxlY3Qoc2V4LCBkaXNlYXNlX3N0YXR1cykKaGVhZChzZWxlY3RlZF9kYXRhKQpgYGAKClRoaXMgaXMgdXNlZnVsIGlmIHlvdSB3YW50IHRvIGdvIG9uIHRvIGFuYWx5emUgYSBzbWFsbGVyIHN1YnNldCBvZiBkYXRhLgoKYGBge3J9CnRhYmxlKHNlbGVjdGVkX2RhdGEpCmBgYAo8d2ltcj4KCiMjIyBBZGRpbmcgdmFyaWFibGVzCgpUaGUgYG11dGF0ZSgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGNyZWF0ZSBuZXcgY29sdW1ucyBiYXNlZCBvbiBleGlzdGluZyBjb2x1bW5zLiBGb3IgZXhhbXBsZSwgeW91IG1pZ2h0IHdhbnQgdG8gY3JlYXRlIGEgbmV3IHZhcmlhYmxlIHRoYXQgY2F0ZWdvcml6ZXMgcGF0aWVudHMgYmFzZWQgb24gdGhlaXIgYmlvbWFya2VyIGxldmVsLgoKYGBge3J9CmJpb21hcmtlcl9kYXRhIDwtIGRhdGEgJT4lCiAgbXV0YXRlKGJpb21hcmtlcl9jYXRlZ29yeSA9IGlmZWxzZShiaW9tYXJrZXIgPiA1LCAiSGlnaCIsICJMb3ciKSkKYGBgCgo8d2ltcj4KCiMjIyBBZHZhbmNlZCAtIFRyYW5zbXV0ZQoKVGhlIGB0cmFuc211dGUoKWAgZnVuY3Rpb24gd29ya3MgbGlrZSBgbXV0YXRlKClgIGJ1dCByZXR1cm5zIG9ubHkgdGhlIG5ld2x5IGNyZWF0ZWQgY29sdW1ucy4gRm9yIGV4YW1wbGUsIHlvdSBtaWdodCB3YW50IHRvIGNyZWF0ZSBhIGRhdGFzZXQgd2l0aCBvbmx5IHRoZSBwYXRpZW50IElEIGFuZCBhIHJpc2sgc2NvcmUgY2FsY3VsYXRlZCBmcm9tIGFnZSBhbmQgQk1JLgoKYGBge3J9CnJpc2tfZGF0YSA8LSBkYXRhICU+JQogIHRyYW5zbXV0ZSgKICAgIHBhdGllbnRfaWQsCiAgICByaXNrX3Njb3JlID0gMC4wNSAqIGFnZSArIDAuMSAqIGJtaQogICkKaGVhZChyaXNrX2RhdGEpCmBgYAo8d2ltcj4KCiMjIyBBZHZhbmNlZCAtIGNhc2Vfd2hlbgoKVGhlIGBjYXNlX3doZW4oKWAgZnVuY3Rpb24gaXMgYSBtb3JlIGZsZXhpYmxlIHZlcnNpb24gb2YgYGlmZWxzZSgpYC4gSXQgYWxsb3dzIHlvdSB0byBzcGVjaWZ5IG11bHRpcGxlIGNvbmRpdGlvbnMgYW5kIGNvcnJlc3BvbmRpbmcgdmFsdWVzLiBGb3IgZXhhbXBsZSwgeW91IG1pZ2h0IHdhbnQgdG8gY3JlYXRlIGEgbmV3IHZhcmlhYmxlIHRoYXQgY2F0ZWdvcml6ZXMgcGF0aWVudHMgYmFzZWQgb24gdGhlaXIgQk1JLgoKYGBge3J9CmRhdGEgPC0gZGF0YSAlPiUKICBtdXRhdGUoYm1pX2NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgYm1pIDwgMTguNSB+ICJVbmRlcndlaWdodCIsCiAgICBibWkgPj0gMTguNSAmIGJtaSA8IDI1IH4gIk5vcm1hbCIsCiAgICBibWkgPj0gMjUgJiBibWkgPCAzMCB+ICJPdmVyd2VpZ2h0IiwKICAgIGJtaSA+PSAzMCB+ICJPYmVzZSIKICApKQpoZWFkKGRhdGEsIG4gPSAxMCkKYGBgCgo8d2ltcj4KCiMjIFBsb3R0aW5nIHdpdGggZ2dwbG90MiB7LnRhYnNldH0KClRoZSBgZ2dwbG90MmAgcGFja2FnZSBpcyBhIHBvd2VyZnVsIHRvb2wgZm9yIGNyZWF0aW5nIHBsb3RzIGluIFIuIEl0CnByb3ZpZGVzIGEgZmxleGlibGUgYW5kIGludHVpdGl2ZSBzeW50YXggZm9yIGNyZWF0aW5nIGEgd2lkZSB2YXJpZXR5IG9mCnBsb3RzLCBpbmNsdWRpbmcgc2NhdHRlciBwbG90cywgYmFyIHBsb3RzLCBsaW5lIHBsb3RzLCBhbmQgbW9yZS4KIApUaGUgYmFzaWMgaWRlYSBvZiBgZ2dwbG90MmAgaXMgdG8gY3JlYXRlIGEgcGxvdCBpbiBsYXllcnMuIFlvdSBzdGFydCBieSBjcmVhdGluZyBhIG5ldyBwbG90IHVzaW5nIHRoZSBgZ2dwbG90KClgIGZ1bmN0aW9uLCBhbmQgdGhlbiBhZGQgbGF5ZXJzIHRvIHRoZSBwbG90IHVzaW5nIGZ1bmN0aW9ucyBsaWtlIGBnZW9tX3BvaW50KClgLCBgZ2VvbV9saW5lKClgLCBgZ2VvbV9iYXIoKWAsIGFuZCBzbyBvbi4KCkJlbG93IHdlIHdpbGwgaW5pdGlhdGUgb3VyIGZpcnN0IHBsb3QhCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSB3ZWlnaHQsIHkgPSBoZWlnaHQpKQpgYGAKCk9idmlvdXNseSB0aGlzIGlzbid0IHVzZWZ1bCB5ZXQsIGJ1dCB3ZSB3aWxsIG5vdyBhZGQgbGF5ZXJzIG9uIHRvcCBvZiBvdXIgY2FudmFzIHRvIGNyZWF0ZSBhIHBsb3QhCgojIyMgU2NhdHRlciBwbG90cwoKVGhlIGBnZ3Bsb3QoKWAgZnVuY3Rpb24gaXMgdXNlZCB0byBjcmVhdGUgYSBuZXcgcGxvdCwgYW5kIHRoZSBgZ2VvbV9wb2ludCgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGFkZCBwb2ludHMgdG8gdGhlIHBsb3QuIApgYGB7cn0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gd2VpZ2h0LCB5ID0gaGVpZ2h0KSkgKyBnZW9tX3BvaW50KCkKYGBgCgpXZSBjYW4gYWxzbyBjdXN0b21pemUgdGhlIHBsb3QgYnkgY2hhbmdpbmcgdGhlIGNvbG9yIGFuZCB0cmFuc3BhcmVuY3kgb2YgdGhlIHBvaW50cyB3aXRoaW4gdGhlIGBnZW9tX3BvaW50KClgIGZ1bmN0aW9uIQoKYGBge3J9CmdncGxvdChkYXRhLCBhZXMoeCA9IHdlaWdodCwgeSA9IGhlaWdodCkpICsgZ2VvbV9wb2ludChjb2xvciA9ICJncmVlbjQiLCBhbHBoYSA9IDAuNSkKYGBgCgpXZSBjYW4gYWxzbyBhZGQgdGl0bGVzIGFuZCBsYWJlbHMgdG8gdGhlIHBsb3QgdXNpbmcgdGhlIGBsYWJzKClgIGZ1bmN0aW9uLgoKYGBge3J9CmdncGxvdChkYXRhLCBhZXMoeCA9IHdlaWdodCwgeSA9IGhlaWdodCkpICsgZ2VvbV9wb2ludChjb2xvciA9ICJncmVlbjQiLCBhbHBoYSA9IDAuNSkgKwogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIFdlaWdodCB2cyBIZWlnaHQiLAogICAgICAgeCA9ICJXZWlnaHQgKGtnKSIsCiAgICAgICB5ID0gIkhlaWdodCAoY20pIikKYGBgCgpXZSBjYW4gYWxzbyBjaGFuZ2UgdGhlIHRoZW1lIG9mIHRoZSBwbG90IHVzaW5nIHRoZSBgdGhlbWVfYncoKWAgZnVuY3Rpb24hIAoKYGBge3J9CmdncGxvdChkYXRhLCBhZXMoeCA9IHdlaWdodCwgeSA9IGhlaWdodCkpICsgZ2VvbV9wb2ludChjb2xvciA9ICJncmVlbjQiLCBhbHBoYSA9IDAuNSkgKwogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIFdlaWdodCB2cyBIZWlnaHQiLAogICAgICAgeCA9ICJXZWlnaHQgKGtnKSIsCiAgICAgICB5ID0gIkhlaWdodCAoY20pIikgKwogIHRoZW1lX2J3KCkKYGBgCgo8d2ltcj4KCiMjIyBCb3ggcGxvdHMKCmBgYHtyfQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBmYWN0b3IoZGlzZWFzZV9zdGF0dXMpLCB5ID0gYm1pLCBmaWxsID0gZmFjdG9yKGRpc2Vhc2Vfc3RhdHVzKSkpICsgZ2VvbV9ib3hwbG90KCkKYGBgCgpUaGUgY3VycmVudCBzZXQgdXAgb2YgdXNpbmcgMCBhbmQgMSB0byByZXByZXNlbnQgZGlzZWFzZSBzdGF0dXMgaXMgbm90IHZlcnkgaW5mb3JtYXRpdmUuIExldCdzIGNoYW5nZSB0aGUgbGFiZWxzIHRvICJObyBEaXNlYXNlIiBhbmQgIkRpc2Vhc2UiIHVzaW5nIHRoZSBgZmFjdG9yKClgIGZ1bmN0aW9uLiBXZSB3aWxsIG1ha2UgdXNlIG9mIGBtdXRhdGUoKWAgdG8gY3JlYXRlIGEgbmV3IGNvbHVtbiB3aXRoIHRoZSBuZXcgbGFiZWxzLgoKYGBge3J9CmJveF9wbG90X2RhdGEgPC0gZGF0YSAlPiUKICBzZWxlY3QoZGlzZWFzZV9zdGF0dXMsIGJtaSkgJT4lCiAgbXV0YXRlKGRpc2Vhc2Vfc3RhdHVzID0gZmFjdG9yKGRpc2Vhc2Vfc3RhdHVzLCBsZXZlbHMgPSBjKDAsIDEpLCBsYWJlbHMgPSBjKCJObyBEaXNlYXNlIiwgIkRpc2Vhc2UiKSkpCmdncGxvdChib3hfcGxvdF9kYXRhLCBhZXMoeCA9IGRpc2Vhc2Vfc3RhdHVzLCB5ID0gYm1pLCBmaWxsID0gZGlzZWFzZV9zdGF0dXMpKSArIGdlb21fYm94cGxvdCgpIApgYGAKCjx3aW1yPgoKIyMjIExpbmUgcGxvdHMKCkZvciBpbGx1c3RyYXRpdmUgcHVycG9zZXMsIHlvdSBjYW4gc2ltdWxhdGUgYSB0cmVuZCBvdmVyIGFnZSBieSBzdW1tYXJpemluZyB0aGUgYXZlcmFnZSBiaW9tYXJrZXIgbGV2ZWwgcGVyIGFnZSBncm91cC4gSGVyZSB3ZSB1c2UgdHdvIG5ldyBmdW5jdGlvbnMgYGdyb3VwX2J5KClgIGFuZCBgc3VtbWFyaXplKClgIGZyb20gdGhlIGBkcGx5cmAgcGFja2FnZSB0byBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgYmlvbWFya2VyIGxldmVsIHBlciBhZ2UgZ3JvdXAuICAgClNpbXBseSwgdGhlIGBncm91cF9ieSgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGdyb3VwIHRoZSBkYXRhIGJ5IGEgdmFyaWFibGUsIGFuZCB0aGUgYHN1bW1hcml6ZSgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGNhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIGVhY2ggZ3JvdXAuCgpgYGB7cn0KYWdlX3RyZW5kIDwtIGRhdGEgJT4lCiAgZ3JvdXBfYnkoYWdlKSAlPiUKICBzdW1tYXJpemUoYXZnX2Jpb21hcmtlciA9IG1lYW4oYmlvbWFya2VyKSkKCmdncGxvdChhZ2VfdHJlbmQsIGFlcyh4ID0gYWdlLCB5ID0gYXZnX2Jpb21hcmtlcikpICsKICBnZW9tX2xpbmUoY29sb3IgPSAicHVycGxlIiwgc2l6ZSA9IDEpICsKICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgQmlvbWFya2VyIExldmVsIGJ5IEFnZSIsCiAgICAgICB4ID0gIkFnZSIsCiAgICAgICB5ID0gIkF2ZXJhZ2UgQmlvbWFya2VyIExldmVsIikgKwogIHRoZW1lX2J3KCkKYGBgCgo8d2ltcj4KCiMjIyBCYXIgcGxvdHMKCmBgYHtyfQpkYXRhICU+JQogIG11dGF0ZShibWlfY2F0ZWdvcnkgPSBjYXNlX3doZW4oCiAgICBibWkgPCAxOC41IH4gIlVuZGVyd2VpZ2h0IiwKICAgIGJtaSA+PSAxOC41ICYgYm1pIDwgMjUgfiAiTm9ybWFsIiwKICAgIGJtaSA+PSAyNSAmIGJtaSA8IDMwIH4gIk92ZXJ3ZWlnaHQiLAogICAgYm1pID49IDMwIH4gIk9iZXNlIgogICkpICU+JQpnZ3Bsb3QoYWVzKHggPSBibWlfY2F0ZWdvcnksIGZpbGwgPSBmYWN0b3Iob25fbWVkaWNhdGlvbikpKSArCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJsaWdodGdyZXkiLCAiZG9kZ2VyYmx1ZSIpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk5vdCBvbiBNZWRpY2F0aW9uIiwgIk9uIE1lZGljYXRpb24iKSkgKwogIGxhYnModGl0bGUgPSAiUHJvcG9ydGlvbiBvZiBNZWRpY2F0aW9uIFVzZSBieSBCTUkgQ2F0ZWdvcnkiLAogICAgICAgeCA9ICJcbkJNSSBDYXRlZ29yeSIsCiAgICAgICB5ID0gIlByb3BvcnRpb24iLAogICAgICAgZmlsbCA9ICJNZWRpY2F0aW9uIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCjx3aW1yPgoKIyMjIEhpc3RvZ3JhbXMKCmBgYHtyfQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSBiaW9tYXJrZXIpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjUsIGZpbGwgPSAiY29ybmZsb3dlcmJsdWUiLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBCaW9tYXJrZXIgTGV2ZWxzIiwKICAgICAgIHggPSAiQmlvbWFya2VyIExldmVsIiwKICAgICAgIHkgPSAiRnJlcXVlbmN5IikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCjx3aW1yPgoKIyMgQWR2YW5jZWQgcGxvdHRpbmcgey50YWJzZXR9CgpgZ2dwbG90MmAgaGFzIGEgbG90IG9mIGFkdmFuY2VkIGZlYXR1cmVzIHRoYXQgYWxsb3cgeW91IHRvIGNyZWF0ZSBtb3JlIGNvbXBsZXggYW5kIGN1c3RvbWl6ZWQgcGxvdHMuIAoKIyMjIFBsb3R0aW5nIG11bHRpcGxlIGxheWVycwoKWW91IGNhbiBwbG90IG11bHRpcGxlIGRhdGFzZXRzIG9uIHRoZSBzYW1lIHBsb3QgYnkgYWRkaW5nIG11bHRpcGxlIGBnZW9tX2AgbGF5ZXJzLiBGb3IgZXhhbXBsZSwgbGV04oCZcyBwbG90IHRoZSBhdmVyYWdlIGJpb21hcmtlciBsZXZlbCBieSBhZ2UgZ3JvdXAgYW5kIG92ZXJsYXkgdGhlIGluZGl2aWR1YWwgYmlvbWFya2VyIGxldmVscy4gKFRoaXMgZG9lc24ndCBtYWtlIGEgdG9uIG9mIHNlbnNlIGJ1dCBqdXN0IHNob3dzIHlvdSBob3cgdG8gb3ZlcmxheSBtdWx0aXBsZSBsYXllcnMhKQoKYGBge3J9CmdncGxvdChhZ2VfdHJlbmQsIGFlcyh4ID0gYWdlLCB5ID0gYXZnX2Jpb21hcmtlcikpICsKICBnZW9tX2xpbmUoY29sb3IgPSAicHVycGxlIiwgc2l6ZSA9IDEpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkYXRhLCBhZXMoeCA9IGFnZSwgeSA9IGJpb21hcmtlciksIGNvbG9yID0gInN0ZWVsYmx1ZSIsIGFscGhhID0gMC41KSArCiAgbGFicyh0aXRsZSA9ICJBdmVyYWdlIEJpb21hcmtlciBMZXZlbCBieSBBZ2Ugd2l0aCBJbmRpdmlkdWFsIERhdGEgUG9pbnRzIiwKICAgICAgIHggPSAiQWdlIiwKICAgICAgIHkgPSAiQmlvbWFya2VyIExldmVsIikgKwogIHRoZW1lX2J3KCkKYGBgCgo8d2ltcj4KCiMjIyBQbG90dGluZyB3aXRoIGZhY2V0cwoKRmFjZXRpbmcgaXMgYSBwb3dlcmZ1bCB0ZWNobmlxdWUgZm9yIGNyZWF0aW5nIG11bHRpcGxlIHBsb3RzIGJhc2VkIG9uIHRoZSBsZXZlbHMgb2YgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZS4gRm9yIGluc3RhbmNlLCB3ZSBjYW4gY29tcGFyZSBiaW9tYXJrZXIgZGlzdHJpYnV0aW9ucyBiZXR3ZWVuIHBhdGllbnQgc2V4ZXMuCgpgYGB7cn0KZ2dwbG90KGRhdGEsIGFlcyh4ID0gYmlvbWFya2VyLCBmaWxsID0gZmFjdG9yKGRpc2Vhc2Vfc3RhdHVzKSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSwgY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuNykgKwogIGZhY2V0X3dyYXAoLn4gc2V4KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygibGlnaHRibHVlIiwgInRvbWF0byIpLAogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk5vIERpc2Vhc2UiLCAiRGlzZWFzZSIpKSArCiAgbGFicyh0aXRsZSA9ICJCaW9tYXJrZXIgRGlzdHJpYnV0aW9uIGJ5IERpc2Vhc2UgU3RhdHVzIGFuZCBTZXgiLAogICAgICAgeCA9ICJCaW9tYXJrZXIgTGV2ZWwiLAogICAgICAgeSA9ICJDb3VudCIsCiAgICAgICBmaWxsID0gIkRpc2Vhc2UgU3RhdHVzIikgKwogIHRoZW1lX2J3KCkKYGBgCgo8d2ltcj4KCiMjIyBQbG90dGluZyB3aXRoIHN0YXRpc3RpY3MKCllvdSBjYW4gb3ZlcmxheSBzdGF0aXN0aWNhbCBzdW1tYXJpZXMgKHN1Y2ggYXMgcmVncmVzc2lvbiBsaW5lcykgdXNpbmcgYGdlb21fc21vb3RoKClgLiBGb3IgZXhhbXBsZSwgbGV04oCZcyBhZGQgYSByZWdyZXNzaW9uIGxpbmUgdG8gdGhlIHNjYXR0ZXIgcGxvdCBvZiB3ZWlnaHQgdmVyc3VzIGhlaWdodC4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSwgYWVzKHggPSB3ZWlnaHQsIHkgPSBoZWlnaHQpKSArCiAgZ2VvbV9wb2ludChjb2xvciA9ICJzdGVlbGJsdWUiLCBhbHBoYSA9IDAuNikgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG9yID0gImRhcmtyZWQiKSArCiAgbGFicyh0aXRsZSA9ICJTY2F0dGVyIFBsb3Qgb2YgV2VpZ2h0IHZzIEhlaWdodCB3aXRoIFJlZ3Jlc3Npb24gTGluZSIsCiAgICAgICB4ID0gIldlaWdodCAoa2cpIiwKICAgICAgIHkgPSAiSGVpZ2h0IChjbSkiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKV2UgY2FuIGFsc28gcGVyZm9ybSBiYXNpYyBzdGF0aXN0aWNhbCB0ZXN0cyBiZXR3ZWVuIGdyb3Vwcy4gRm9yIGV4YW1wbGUsIHdlIGNhbiBjb21wYXJlIHRoZSBhdmVyYWdlIEJNSSBiZXR3ZWVuIHBhdGllbnRzIHdpdGggYW5kIHdpdGhvdXQgZGlzZWFzZSB1c2luZyB0aGUgYHN0YXRfY29tcGFyZV9tZWFucygpYCBmdW5jdGlvbiBmcm9tIHRoZSBgZ2dwdWJyYCBwYWNrYWdlLgoKYGBge3J9CiMgVW5jb21tZW50IHRoZSBmb2xsb3dpbmcgbGluZSB0byBpbnN0YWxsIHRoZSBwYWNrYWdlIGlmIHlvdSBoYXZlbid0IGFscmVhZHkhCiMgaW5zdGFsbC5wYWNrYWdlcygiZ2dwdWJyIikKbGlicmFyeShnZ3B1YnIpCmJveF9wbG90X2RhdGEgPC0gZGF0YSAlPiUKICBzZWxlY3QoZGlzZWFzZV9zdGF0dXMsIGJtaSkgJT4lCiAgbXV0YXRlKGRpc2Vhc2Vfc3RhdHVzID0gZmFjdG9yKGRpc2Vhc2Vfc3RhdHVzLCBsZXZlbHMgPSBjKDAsIDEpLCBsYWJlbHMgPSBjKCJObyBEaXNlYXNlIiwgIkRpc2Vhc2UiKSkpCmdncGxvdChib3hfcGxvdF9kYXRhLCBhZXMoeCA9IGRpc2Vhc2Vfc3RhdHVzLCB5ID0gYm1pLCBmaWxsID0gZGlzZWFzZV9zdGF0dXMpKSArIGdlb21fYm94cGxvdCgpICsgdGhlbWVfYncoKSArIHN0YXRfY29tcGFyZV9tZWFucyhtZXRob2QgPSAidC50ZXN0IikKYGBgCgo8d2ltcj4KCiMjIENvbmNsdXNpb24KCkluIHRoaXMgdHV0b3JpYWwsIHdlIGxlYXJuZWQgaG93IHRvIGxvYWQgZGF0YSBpbnRvIFIsIG1hbmlwdWxhdGUgZGF0YQp1c2luZyB0aGUgYGRwbHlyYCBwYWNrYWdlLCBhbmQgY3JlYXRlIHBsb3RzIHVzaW5nIHRoZSBgZ2dwbG90MmAgcGFja2FnZS4KV2UgYWxzbyBsZWFybmVkIGhvdyB0byBjdXN0b21pemUgcGxvdHMgYW5kIGNyZWF0ZSBtb3JlIGFkdmFuY2VkIHBsb3RzLgpUaGVzZSBza2lsbHMgYXJlIGVzc2VudGlhbCBmb3IgYW55IGRhdGEgYW5hbHlzdCBvciBzY2llbnRpc3Qgd29ya2luZwp3aXRoIFIuCg==