For approach to analysis see the tidytext book.
Setup
In case the tidyverse is not installed on your computer already (you haven’t used it in R before) you need to install it before loading the library. This you need to do only once as long as you use the same computer or don’t need to update a loaded package. The library() function needs to be used whenever you start from a clean R state.
install.packages("tidyverse")
library(tidyverse)
Ditto for the tidytext package:
install.packages("tidytext")
library(tidytext)
Special purpose packages:
install.packages("wordcloud")
install.packages("igraph")
trying URL 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.5/igraph_1.2.2.tgz'
Content type 'application/x-gzip' length 6872298 bytes (6.6 MB)
==================================================
downloaded 6.6 MB
The downloaded binary packages are in
/var/folders/42/_86yt7ts4w335_0q50tmvsrh0000gp/T//RtmpUxtNQC/downloaded_packages
install.packages("ggraph")
install.packages("widyr")
install.packages("topicmodels")
Data import
We read the data with read_csv, not with the base package read.csv, to create a tibble.
text_df <- read_csv("interviews.csv")
The data come in the format:
- Interview: Interview number 1 to 30.
- Question: {Q1, Q2, …, Q8}
- Speaker: R = Researcher, S01 to S30 = Subjects
- Text: A question from R and the answer from Sxx.
Note that this table is in the ‘long’ format, so we need to filter rows rather than select columns to select variables such as questions and speakers.
Change the data structure by adding a column “Follow-up” that is either True or False with respect to the column “Question”
Data cleaning
Clearly there are the typical stop words that we don’t want to have in counts. Let’s remove them. There’s a data frame that has stop words specified for us, as part of tidytext:
data(stop_words)
We can View(stop_words) to see them, and add our own ones, of course. Here are the first 10 stop words:
head(stop_words)
Let’s get to the words:
words_df <- text_df %>%
unnest_tokens(word, Text)
The key function is unnest_tokens(), which converts text into a column of n-grams (with default value 1, i.e., single words). Since the survey data is in one-word-per-row format, we can remove stop symbols with an anti_join (from dplyr):
words_df <- anti_join(words_df, stop_words)
In general the anti-join command is:
anti_join(a, b, by = "x1") -- all rows in a that do not have a mach in b (on variable x1)
On inspection, there are still a few words in there which are not really English. Let’s remove these as well.
stop_words2 <- tibble(word = c("uh", "ah", "yeah", "um", "uhm", "don't", "it's"))
words_df <- words_df %>%
anti_join(stop_words2)
Joining, by = "word"
To fix ‘don’t’ and “it’s” are not caught this way. The problem is that this is unicode, so not a straight “’”. To wit, when we search for the straight apostrophe sign, there’s only one hit, and it’s not one that is problematic:
words_df %>%
filter(str_detect(word, "'"))
We get more of the real problems by looking for all punctuation characters:
words_df %>%
filter(str_detect(word, "[:punct:]"))
Let’s remove these:
words_df <- words_df %>%
filter(!str_detect(word, "[:punct:]"))
The function str_detect() comes from the stringr package, see help.
We may also want to remove numbers and special characters. First we need a tibble to store these kind of stop symbols under:
stop_symbols <- tibble(word = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "#", "(", ")", "."))
words_df <- words_df %>%
anti_join(stop_symbols)
Word frequencies
A first look at all words:
words_df %>%
count(word, sort = TRUE)
Let’s do a bit of graphing of words with a frequency GT 10.
words_df %>%
count(word, sort = TRUE) %>%
filter(n > 10) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col() +
xlab(NULL) +
coord_flip()

Now we can calculate word frequencies for each person. First, we group by person and count how many times each person used each word. Then we use left_join() to add a column of the total number of words used by each person. Finally, we calculate a frequency for each person and word.
frequency <- words_df %>%
group_by(Speaker) %>%
count(word, sort = TRUE) %>%
left_join(words_df %>%
group_by(Speaker) %>%
summarise(total = n())) %>%
mutate(freq = n/total)
Joining, by = "Speaker"
A left_join joins matching rows from b into a: inner_join(a,b, by = "x").
Let’s look at the most frequent words:
head(frequency)
Not surprisingly, they come all from Researcher because he speaks most. For many analysis it will make sense to filter out Researcher’s text:
frequency %>%
filter(Speaker != "R") %>%
top_n(2)
Selecting by freq
We will need the data other than the researcher’s questions frequently, so let’s create a variable for the two:
words_students <- words_df %>%
filter(Speaker != "R")
And a variable for the Researcher words:
words_researcher <- words_df %>%
filter(Speaker == "R")
Word cloud
library(wordcloud)
Loading required package: RColorBrewer
words_students %>%
count(word) %>%
with(wordcloud(word, n, max.words = 100))

Sentiment analysis
Tidytext comes with three lexicons that contains words for sentiments, overall more than 27,000 words:
head(sentiments)
Let’s use the nrc lexicon and look for joyfull words. First, get the joyful words into a df:
nrc_joy <- get_sentiments("nrc") %>%
filter(sentiment == "joy")
Then look for them in answers For this, we use inner_join, which retains only the matching rows in both sets. The general syntax is inner_join(a, b, by = "x1"), but in the shorter format this reduces to:
words_students %>%
inner_join(nrc_joy) %>%
count(word, sort = TRUE) %>%
top_n(10)
Joining, by = "word"
Selecting by n
The long form would be inner_join(words_q1, nrc_joy, by = "word") but R is able to figure the arguments provided in the short form.
To do the same analysis for the sentiment ‘fear’, simply replace ‘joy’ with ‘fear’ above (and perhaps rename the variable to nrc_fear or such).
Sentiment anaysis on one question
Just for Q1 and all follow-up questions:
words_students %>%
filter(Question == "Q1" | Question == "Q1-F") %>%
inner_join(nrc_joy) %>%
count(word, sort = TRUE)
Joining, by = "word"
Sentiment on all questions
words_students %>%
group_by(Question) %>%
inner_join(nrc_joy) %>%
count(word, sort = FALSE)
We turned sorting on the count off here because we want to have the results grouped by question id. If Sorting was TRUE, the numeric count would be used first, then the question ID.
Sentiment analysis on a sub-set of all questions
Let’s pick Q1, 3 and 7:
words_students %>%
filter(Question == "Q1" | Question == "Q3" | Question == "Q7") %>%
inner_join(nrc_joy) %>%
count(word, sort = TRUE)
Most common positive and negative sentiment words
Using the lexicon bing, which classifies sentiment words as positive or negative, we can find how words costribute to these overall sentiments:
bing_word_counts <- words_students %>%
inner_join(get_sentiments("bing")) %>%
count(word, sentiment, sort = TRUE) %>%
ungroup()
head(bing_word_counts)
Let’s graph this with ggplot2:
bing_word_counts %>%
group_by(sentiment) %>%
top_n(10) %>%
ungroup() %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment",
x = NULL) +
coord_flip()
Selecting by n

The top_n(10) is returning more than 10 rows if there are ties. Will have to write a more complex expression with filter() and min_rank() because that’s what top_n wraps.
Here’s a cool combination of wordclouds with bing words:
library(reshape2)
Attaching package: ‘reshape2’
The following object is masked from ‘package:tidyr’:
smiths
We need reshape2 for the acast function. acast is a complex function used to transform between data frames and matrices. comparison.cloud() needs a matrix as input. For more see http://had.co.nz/reshape/.
words_students %>%
inner_join(get_sentiments("bing")) %>%
count(word, sentiment, sort = TRUE) %>%
acast(word ~ sentiment, value.var = "n", fill = 0) %>%
comparison.cloud(colors = c("gray20", "gray80"),
max.words = 100)
Joining, by = "word"

Word and Document Frequency
Calculating term frequency per interview
Whole interview corpus
Step 1: A tibble word count by interview sorted by count. This has length 1097 observations, less than the full corpus (2617 obs) because we aggregate in each interview: count(x, ..., wt = NULL, sort = FALSE) with x the tbl and ... the variables to group by; wt is for weighting by counting number of rows.
interview_words <- words_df %>%
count(Interview, word, sort = TRUE) %>%
ungroup()
head(interview_words)
So here we have count(words_df, Interview, word, wt = NULL, sort = TRUE.
Step 2: total of words per interview
total_words <- interview_words %>%
group_by(Interview) %>%
summarize(total = sum(n))
total_words
Step 3: Add the total count column to each of the words and their individual frequency
interview_words <- left_join(interview_words, total_words, by = "Interview")
head(interview_words)
Left_join(a, b, by = "x1') joins mathing rows from b to a. The “by” is included here for clarity, but could be omitted because it is the single shared variable in both tbls.
Let’s look at the term frequency:
ggplot(interview_words, aes(n/total, fill = Interview)) +
geom_histogram(show.legend = FALSE) +
facet_wrap(~Interview, ncol = 2, scales = "free_y")

tf_idf For students reponses only
Since we are looking for differences between interviews (students), it probably makes sense to remove the “shared” factor–the Researcher–and look at students’ answers only:
interview_words <- words_students %>%
count(Interview, word, sort = TRUE) %>%
ungroup()
head(interview_words)
The tf_idf value be calcuated based on the table just created like this:
interview_words <- interview_words %>%
bind_tf_idf(word, Interview, n)
head(interview_words)
Words that are very common across all interviews have a low idf term, and so wil the tf_idf. Let’s find the distinct words by sorting on the tf_idf:
interview_words %>%
arrange(desc(tf_idf))
We can plot this result:
interview_words %>%
arrange(desc(tf_idf)) %>%
mutate(word = factor(word, levels = rev(unique(word)))) %>%
group_by(Interview) %>%
top_n(10) %>%
ungroup %>%
ggplot(aes(word, tf_idf, fill = Interview)) +
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
facet_wrap(~Interview, ncol = 2, scales = "free") +
coord_flip()
Selecting by tf_idf

Todo: unpack the mutate line
Idea: Doing the analysis by question should yield a more distinct profile of specific words.
Relationship between words - N-grams
Filter out researcher talk and create bi-grams:
student_bigrams <- text_df %>%
filter(Speaker != "R") %>%
unnest_tokens(bigram, Text, token = "ngrams", n = 2)
student_bigrams
The way to control the token size is to set paramaters of the unnest_token function, see help(unnest_token"). A single word is an ngram of length 1, so two adjacent words can be used as the token unit by unnest_tokens(ngram, txt, token = "ngrams", n = 2).
We’ll copy these into a more gnerally named variable in case we want to redo this analysis on other bigrams:
bigrams <- student_bigrams
Removing stop words
Many of the bi-grams will be stop word phrases, of which we need to get rid off. The pattern is separate/filter/count/unite:
Step 1: separate into two columns
bigrams_separated <- bigrams %>%
separate(bigram, c("word1", "word2"), sep = " ")
Step 2: Filter out stopwords
bigrams_filtered <- bigrams_separated %>%
filter(!word1 %in% stop_words$word) %>%
filter(!word2 %in% stop_words$word)
Much less pairs! Let’s count them:
Step 3: count
bigram_counts <- bigrams_filtered %>%
count(word1, word2, sort = TRUE)
But still lots of non-meaning words, all those ahs and uhs, because this is talk. Let’s filter some more.
bigrams_filtered <- bigrams_filtered %>%
filter(!word1 %in% stop_words2$word) %>%
filter(!word2 %in% stop_words2$word)
Step 4: And let’s produce a united version again:
bigrams_united <- bigrams_filtered %>%
unite(bigram, word1, word2, sep = " ")
head(bigrams_united)
Analysis
Counting because we can:
bigrams_united %>%
count(bigram, sort = TRUE)
Lets look at all phrases with a specific word in position 2
bigrams_filtered %>%
filter(word2 == "assignment") %>%
count(Interview, word1, sort = TRUE)
Improvement: use regex to deal with different forms of the stem:
bigrams_filtered %>%
filter(grepl("assign", word2)) %>%
count(Interview, word1, sort = TRUE)
grepl(pattern, x) is the logic form of grep, returnign a vector of match or not for each element of x.
Sentiment analysis with words in context
For sentiment analysis we need in particular into negations""
bigrams_separated %>%
filter(word1 == "not") %>%
count(word1, word2, sort = TRUE)
Let’s use that for some analysis with the AFINN lexicon, with uses a numeric score to express sentiment direction:
AFINN <- get_sentiments("afinn")
head(AFINN)
not_words <- bigrams_separated %>%
filter(word1 == "not") %>%
inner_join(AFINN, by = c(word2 = "word")) %>%
count(word2, score, sort = TRUE) %>%
ungroup()
not_words
(This needs some unpacking) We see how important it is to watch out for negations when it comes to sentiments. Other negation words deserve attention, too:
negation_words <- c("not", "no", "never", "without")
negated_words <- bigrams_separated %>%
filter(word1 %in% negation_words) %>%
inner_join(AFINN, by = c(word2 = "word")) %>%
count(word1, word2, score, sort = TRUE) %>%
ungroup()
negated_words
This only makes a small difference in the 5 interviews, but with a larger corpus we are bound to see more impact. We can compute and visualise the impact by computing a value score * n:
negated_words %>%
mutate(contribution = n * score) %>%
arrange(desc(abs(contribution))) %>%
head(20) %>%
mutate(word2 = reorder(word2, contribution)) %>%
ggplot(aes(word2, n * score, fill = n * score > 0)) +
geom_col(show.legend = FALSE) +
xlab("Words preceded by \"not\"") +
ylab("Sentiment score * number of occurrences") +
coord_flip()

This also makes it visible that in the first 5 interviews there were no negatve words negated, such as “no doubt”.
Graphing bigram networks
library(igraph)
Attaching package: ‘igraph’
The following objects are masked from ‘package:dplyr’:
as_data_frame, groups, union
The following objects are masked from ‘package:purrr’:
compose, simplify
The following object is masked from ‘package:tidyr’:
crossing
The following object is masked from ‘package:tibble’:
as_data_frame
The following objects are masked from ‘package:stats’:
decompose, spectrum
The following object is masked from ‘package:base’:
union
The dataframe we need for this is bigram_counts with word1, word2 and n as the “weight”. Let’s use only the those with n GEG 2:
bigram_graph <- bigram_counts %>%
filter(n >= 2) %>%
graph_from_data_frame()
In `d' `NA' elements were replaced with string "NA"
bigram_graph
IGRAPH bd166e8 DN-- 23 13 --
+ attr: name (v/c), n (e/n)
+ edges from bd166e8 (vertex names):
[1] wrong ->direction blah ->blah individual ->contributions share ->ideas
[5] feel ->happy individual ->assignment inter ->related participation->mark
[9] pass ->score team ->leader ten ->percent time ->consuming
[13] NA ->NA
For plottigng we use ggraph:
library(ggraph)
set.seed(2017)
ggraph(bigram_graph, layout = "fr") +
geom_edge_link() +
geom_node_point() +
geom_node_text(aes(label = name), vjust = 1, hjust = 1)

Not a particularly connected graph, but the principle is clear.
Correlations among questions
library(widyr)
word_pairs <- words_students %>%
pairwise_count(word, Question, sort = TRUE)
word_pairs
These are pair counts per question. Here’s the same for interviews:
word_pairs <- words_students %>%
pairwise_count(word, Interview, sort = TRUE)
word_pairs
Note that these become rather big tables because so many things go togehter on that scale. We can also look this way for specific combinations of words. Let’s go back to pairs within questions, and further reduce to the first interview:
word_pairs <- words_students %>%
filter(Interview == 1) %>%
pairwise_count(word, Question, sort = TRUE)
word_pairs
Adn lets look for pairs that have “assignment” in them. Note that the level of counting is the Question. One problem is that we have questions with labels Q and Q-F, which in this analysis get counted separately.
word_pairs %>%
filter(item1 == "assignment")
Let’s calculate a correlation between (frequent) words:
word_cors <- words_students %>%
group_by(word) %>%
filter(n() >= 20) %>%
pairwise_cor(word, Question, sort = TRUE)
word_cors
Plots help to get an overview:
word_cors %>%
filter(item1 %in% c("mark", "time", "idea", "assignment")) %>%
group_by(item1) %>%
top_n(6) %>%
ungroup() %>%
mutate(item2 = reorder(item2, correlation)) %>%
ggplot(aes(item2, correlation)) +
geom_bar(stat = "identity") +
facet_wrap(~ item1, scales = "free") +
coord_flip()
Selecting by correlation

And based on correlations we can create a network graph:
set.seed(2016)
word_cors %>%
filter(correlation > .15) %>%
graph_from_data_frame() %>%
ggraph(layout = "fr") +
geom_edge_link(aes(edge_alpha = correlation), show.legend = FALSE) +
geom_node_point(color = "lightblue", size = 5) +
geom_node_text(aes(label = name), repel = TRUE) +
theme_void()

Topic models
In general, TMs classify documents into topics and determine the overlap of a document with a topic. Each topic is made up out of a number of words.
Casting the tidy text df into a document-term matrix
Most text mining packages use DTM format, which is a matrix where:
- each row represents one document (such as a book or article),
- each column represents one term, and
- each value (typically) contains the number of appearances of that term in that document.
Here’s how to turn a tidy text df into a DTM:
library(tm)
students_dtm <- words_students %>%
count(Interview, word) %>%
cast_dtm(Interview, word, n)
Topics across interviews
With this we have a dtm for each of the 5 interviews, students’ contributions only. Based on this we can mine for topics:
library(topicmodels)
# set a seed so that the output of the model is predictable
students_lda <- LDA(students_dtm, k = 2, control = list(seed = 1234))
students_lda
A LDA_VEM topic model with 2 topics.
This object contains a model with just two groups, to make things easier. This model needs to be inspected to gain insights. For instance, we can look at how the words relate to the topics, extracting the beta value from the matrix. We use tidy to turn that column into a tibble topic-term-beta. The betas can be interpreted as probabilities or the term belonging to the topic.
students_topics <- tidy(students_lda, matrix = "beta")
students_topics
Let’s look at the top-10 terms:
students_top_terms <- students_topics %>%
group_by(topic) %>%
top_n(10, beta) %>%
ungroup() %>%
arrange(topic, -beta)
And visualise them:
students_top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip()

Due to the small corpus, and the fact that we have the same questions in all interviews, this leads not to topics which are easily interpreted. A corresponding analysis, kind of a validation question, would look into topics based on the questions.
The alternative view is to look for terms which differ most between topics.
beta_spread <- students_topics %>%
mutate(topic = paste0("topic", topic)) %>%
spread(topic, beta) %>%
filter(topic1 > .001 | topic2 > .001) %>%
mutate(log_ratio = log2(topic2 / topic1))
beta_spread
To do: Plot a graph with the highest (positive and negative) differences in log ratio.
Topics across questions
students_dtm_questions <- words_students %>%
count(Question, word) %>%
cast_dtm(Question, word, n)
students_dtm_questions
We have 16 questions (documents) here because of the follow up questions. Really need to address that in the base model by introducing a column for follow up. In any case, 9 topics should be a good fit to the data.
students_lda_questions <- LDA(students_dtm_questions, k = 9)
students_lda_questions
students_topics_questions <- tidy(students_lda_questions, matrix = "beta")
students_topics_questions
students_top_terms_questions <- students_topics_questions %>%
group_by(topic) %>%
top_n(5, beta) %>%
ungroup() %>%
arrange(topic, -beta)
And visualise them:
students_top_terms_questions %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip()

Something like this could be used, with the overall corpus, to see if the nine questions yielded different answers, of if other semantic similarities show up. Question 5, for intance, really should be about facilitation.
Also, by changing k we can explore goodness of fit perhaps, for alternative models.
Miscalleneous
Stemming
This now also is the point were we also could do some stemming. For this we need this library:
library(SnowballC)
Which gives us a function ‘wordstem’:
words_stemmed <- mutate(words_df, word = wordStem(word))
head(words_stemmed, n=20L)
But we don’t want to use the stemmed words just now because it would intefere with the sentiment analysis. Stemming is useful, but only once we no longer need “real” words, such as for topic modelling and the likes.
Appendices
Lessons learned
To make analysis flexible, use a distinct variable name for the data set the analysis is based on, and bind it at the beginning of the analysis to the specific data set, which you expect will be varied.
LS0tCnRpdGxlOiAiSW50ZXJ2aWV3IEFuYWx5c2lzICg1IHJlc3BvbmRlbnRzKSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCkZvciBhcHByb2FjaCB0byBhbmFseXNpcyBzZWUgW3RoZSB0aWR5dGV4dCBib29rXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vc2VudGltZW50Lmh0bWwpLiAKCgojIyBTZXR1cAoKSW4gY2FzZSB0aGUgdGlkeXZlcnNlIGlzIG5vdCBpbnN0YWxsZWQgb24geW91ciBjb21wdXRlciBhbHJlYWR5ICh5b3UgaGF2ZW4ndCB1c2VkIGl0IGluIFIgYmVmb3JlKSB5b3UgbmVlZCB0byBpbnN0YWxsIGl0IGJlZm9yZSBsb2FkaW5nIHRoZSBsaWJyYXJ5LiBUaGlzIHlvdSBuZWVkIHRvIGRvIG9ubHkgb25jZSBhcyBsb25nIGFzIHlvdSB1c2UgdGhlIHNhbWUgY29tcHV0ZXIgb3IgZG9uJ3QgbmVlZCB0byB1cGRhdGUgYSBsb2FkZWQgcGFja2FnZS4gVGhlIGBsaWJyYXJ5KClgIGZ1bmN0aW9uIG5lZWRzIHRvIGJlIHVzZWQgd2hlbmV2ZXIgeW91IHN0YXJ0IGZyb20gYSBjbGVhbiBSIHN0YXRlLiAKCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikKYGBgCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKRGl0dG8gZm9yIHRoZSB0aWR5dGV4dCBwYWNrYWdlOgoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoInRpZHl0ZXh0IikKYGBgCgpgYGB7cn0KbGlicmFyeSh0aWR5dGV4dCkKYGBgCgpTcGVjaWFsIHB1cnBvc2UgcGFja2FnZXM6CgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIikKYGBgCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygiaWdyYXBoIikKYGBgCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCJnZ3JhcGgiKQpgYGAKCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCJ3aWR5ciIpCmBgYApgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygidG9waWNtb2RlbHMiKQpgYGAKCgojIyBEYXRhIGltcG9ydAoKV2UgcmVhZCB0aGUgZGF0YSB3aXRoIGByZWFkX2NzdmAsIG5vdCB3aXRoIHRoZSBiYXNlIHBhY2thZ2UgYHJlYWQuY3N2YCwgdG8gY3JlYXRlIGEgdGliYmxlLiAKYGBge3IgUmVhZGluZyBmcm9tIGZpbGUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnRleHRfZGYgPC0gcmVhZF9jc3YoImludGVydmlld3MuY3N2IikKYGBgCgpUaGUgZGF0YSBjb21lIGluIHRoZSBmb3JtYXQ6CgoxLiBJbnRlcnZpZXc6IEludGVydmlldyBudW1iZXIgMSB0byAzMC4KMi4gUXVlc3Rpb246IHtRMSwgUTIsIC4uLiwgUTh9CjMuIFNwZWFrZXI6IFIgPSBSZXNlYXJjaGVyLCBTMDEgdG8gUzMwID0gU3ViamVjdHMKNC4gVGV4dDogQSBxdWVzdGlvbiBmcm9tIFIgYW5kIHRoZSBhbnN3ZXIgZnJvbSBTeHguIAoKTm90ZSB0aGF0IHRoaXMgdGFibGUgaXMgaW4gdGhlICdsb25nJyBmb3JtYXQsIHNvIHdlIG5lZWQgdG8gZmlsdGVyIHJvd3MgcmF0aGVyIHRoYW4gc2VsZWN0IGNvbHVtbnMgdG8gc2VsZWN0IHZhcmlhYmxlcyBzdWNoIGFzIHF1ZXN0aW9ucyBhbmQgc3BlYWtlcnMuIAoKPiBDaGFuZ2UgdGhlIGRhdGEgc3RydWN0dXJlIGJ5IGFkZGluZyBhIGNvbHVtbiAiRm9sbG93LXVwIiB0aGF0IGlzIGVpdGhlciBUcnVlIG9yIEZhbHNlIHdpdGggcmVzcGVjdCB0byB0aGUgY29sdW1uICJRdWVzdGlvbiIKCiMjIERhdGEgY2xlYW5pbmcKCkNsZWFybHkgdGhlcmUgYXJlIHRoZSB0eXBpY2FsIHN0b3Agd29yZHMgdGhhdCB3ZSBkb24ndCB3YW50IHRvIGhhdmUgaW4gY291bnRzLiBMZXQncyByZW1vdmUgdGhlbS4gVGhlcmUncyBhIGRhdGEgZnJhbWUgdGhhdCBoYXMgc3RvcCB3b3JkcyBzcGVjaWZpZWQgZm9yIHVzLCBhcyBwYXJ0IG9mIHRpZHl0ZXh0OiAKCmBgYHtyIHN0b3Atd29yZHN9CmRhdGEoc3RvcF93b3JkcykKYGBgCldlIGNhbiBgVmlldyhzdG9wX3dvcmRzKWAgdG8gc2VlIHRoZW0sIGFuZCBhZGQgb3VyIG93biBvbmVzLCBvZiBjb3Vyc2UuIEhlcmUgYXJlIHRoZSBmaXJzdCAxMCBzdG9wIHdvcmRzOgoKYGBge3J9CmhlYWQoc3RvcF93b3JkcykKYGBgCgoKTGV0J3MgZ2V0IHRvIHRoZSB3b3JkczogCgpgYGB7ciBmaXJzdCBjYWxsIHRvIHVubmVzdF90b2tlbigpfQp3b3Jkc19kZiA8LSB0ZXh0X2RmICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgVGV4dCkKYGBgCgpUaGUga2V5IGZ1bmN0aW9uIGlzIHVubmVzdF90b2tlbnMoKSwgd2hpY2ggY29udmVydHMgdGV4dCBpbnRvIGEgY29sdW1uIG9mIG4tZ3JhbXMgKHdpdGggZGVmYXVsdCB2YWx1ZSAxLCBpLmUuLCBzaW5nbGUgd29yZHMpLiBTaW5jZSB0aGUgc3VydmV5IGRhdGEgaXMgaW4gb25lLXdvcmQtcGVyLXJvdyBmb3JtYXQsIHdlIGNhbiByZW1vdmUgc3RvcCBzeW1ib2xzIHdpdGggYW4gYW50aV9qb2luIChmcm9tIGRwbHlyKToKCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kd29yZHNfZGYgPC0gYW50aV9qb2luKHdvcmRzX2RmLCBzdG9wX3dvcmRzKQpgYGAKCkluIGdlbmVyYWwgdGhlIGFudGktam9pbiBjb21tYW5kIGlzOgpgYGAKYW50aV9qb2luKGEsIGIsIGJ5ID0gIngxIikgIC0tIGFsbCByb3dzIGluIGEgdGhhdCBkbyBub3QgaGF2ZSBhIG1hY2ggaW4gYiAob24gdmFyaWFibGUgeDEpCmBgYApPbiBpbnNwZWN0aW9uLCB0aGVyZSBhcmUgc3RpbGwgYSBmZXcgd29yZHMgaW4gdGhlcmUgd2hpY2ggYXJlIG5vdCByZWFsbHkgRW5nbGlzaC4gTGV0J3MgcmVtb3ZlIHRoZXNlIGFzIHdlbGwuIAoKYGBge3J9CnN0b3Bfd29yZHMyIDwtIHRpYmJsZSh3b3JkID0gYygidWgiLCAiYWgiLCAieWVhaCIsICJ1bSIsICJ1aG0iLCAiZG9uJ3QiLCAiaXQncyIpKQp3b3Jkc19kZiA8LSB3b3Jkc19kZiAlPiUKICBhbnRpX2pvaW4oc3RvcF93b3JkczIpCmBgYAoqKlRvIGZpeCoqICdkb24ndCcgYW5kICJpdCdzIiBhcmUgbm90IGNhdWdodCB0aGlzIHdheS4gVGhlIHByb2JsZW0gaXMgdGhhdCB0aGlzIGlzIHVuaWNvZGUsIHNvIG5vdCBhIHN0cmFpZ2h0ICInIi4gVG8gd2l0LCB3aGVuIHdlIHNlYXJjaCBmb3IgdGhlIHN0cmFpZ2h0IGFwb3N0cm9waGUgc2lnbiwgdGhlcmUncyBvbmx5IG9uZSBoaXQsIGFuZCBpdCdzIG5vdCBvbmUgdGhhdCBpcyBwcm9ibGVtYXRpYzogCgpgYGB7cn0Kd29yZHNfZGYgJT4lCiAgZmlsdGVyKHN0cl9kZXRlY3Qod29yZCwgIiciKSkKYGBgCgpXZSBnZXQgbW9yZSBvZiB0aGUgcmVhbCBwcm9ibGVtcyBieSBsb29raW5nIGZvciBhbGwgcHVuY3R1YXRpb24gY2hhcmFjdGVyczoKCmBgYHtyfQp3b3Jkc19kZiAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdCh3b3JkLCAiWzpwdW5jdDpdIikpCmBgYAoKTGV0J3MgcmVtb3ZlIHRoZXNlOiAKCmBgYHtyfQp3b3Jkc19kZiA8LSB3b3Jkc19kZiAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgIls6cHVuY3Q6XSIpKQpgYGAKClRoZSBmdW5jdGlvbiBgc3RyX2RldGVjdCgpYCBjb21lcyBmcm9tIHRoZSBzdHJpbmdyIHBhY2thZ2UsIHNlZSBoZWxwLiAKCldlIG1heSBhbHNvIHdhbnQgdG8gcmVtb3ZlIG51bWJlcnMgYW5kIHNwZWNpYWwgY2hhcmFjdGVycy4gRmlyc3Qgd2UgbmVlZCBhIHRpYmJsZSB0byBzdG9yZSB0aGVzZSBraW5kIG9mIHN0b3Agc3ltYm9scyB1bmRlcjoKCmBgYHtyfQpzdG9wX3N5bWJvbHMgPC0gdGliYmxlKHdvcmQgPSBjKCIxIiwgIjIiLCAiMyIsICI0IiwgIjUiLCAiNiIsICI3IiwgIjgiLCAiOSIsICIwIiwgIiMiLCAiKCIsICIpIiwgIi4iKSkKYGBgCgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kd29yZHNfZGYgPC0gd29yZHNfZGYgJT4lCiAgYW50aV9qb2luKHN0b3Bfc3ltYm9scykKYGBgCgojIyBXb3JkIGZyZXF1ZW5jaWVzCgpBIGZpcnN0IGxvb2sgYXQgYWxsIHdvcmRzOiAKYGBge3J9CndvcmRzX2RmICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpgYGAKCgoKTGV0J3MgZG8gYSBiaXQgb2YgZ3JhcGhpbmcgb2Ygd29yZHMgd2l0aCBhIGZyZXF1ZW5jeSBHVCAxMC4gCgpgYGB7cn0Kd29yZHNfZGYgJT4lCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JQogIGZpbHRlcihuID4gMTApICU+JQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCBuKSkgKwogIGdlb21fY29sKCkgKwogIHhsYWIoTlVMTCkgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCgpOb3cgd2UgY2FuIGNhbGN1bGF0ZSB3b3JkIGZyZXF1ZW5jaWVzIGZvciBlYWNoIHBlcnNvbi4gRmlyc3QsIHdlIGdyb3VwIGJ5IHBlcnNvbiBhbmQgY291bnQgaG93IG1hbnkgdGltZXMgZWFjaCBwZXJzb24gdXNlZCBlYWNoIHdvcmQuIFRoZW4gd2UgdXNlIGxlZnRfam9pbigpIHRvIGFkZCBhIGNvbHVtbiBvZiB0aGUgdG90YWwgbnVtYmVyIG9mIHdvcmRzIHVzZWQgYnkgZWFjaCBwZXJzb24uIEZpbmFsbHksIHdlIGNhbGN1bGF0ZSBhIGZyZXF1ZW5jeSBmb3IgZWFjaCBwZXJzb24gYW5kIHdvcmQuCgpgYGB7ciBGcmVxdWVuY3kgcGVyIHJlc3BvbmRlbnR9CmZyZXF1ZW5jeSA8LSB3b3Jkc19kZiAlPiUgCiAgZ3JvdXBfYnkoU3BlYWtlcikgJT4lIAogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgCiAgbGVmdF9qb2luKHdvcmRzX2RmICU+JSAKICAgICAgICAgICAgICBncm91cF9ieShTcGVha2VyKSAlPiUgCiAgICAgICAgICAgICAgc3VtbWFyaXNlKHRvdGFsID0gbigpKSkgJT4lCiAgbXV0YXRlKGZyZXEgPSBuL3RvdGFsKQpgYGAKQSBsZWZ0X2pvaW4gam9pbnMgbWF0Y2hpbmcgcm93cyBmcm9tIGIgaW50byBhOiBgaW5uZXJfam9pbihhLGIsIGJ5ID0gIngiKWAuIAoKTGV0J3MgbG9vayBhdCB0aGUgbW9zdCBmcmVxdWVudCB3b3JkczoKCmBgYHtyfQpoZWFkKGZyZXF1ZW5jeSkKYGBgCk5vdCBzdXJwcmlzaW5nbHksIHRoZXkgY29tZSBhbGwgZnJvbSBSZXNlYXJjaGVyIGJlY2F1c2UgaGUgc3BlYWtzIG1vc3QuIEZvciBtYW55IGFuYWx5c2lzIGl0IHdpbGwgbWFrZSBzZW5zZSB0byBmaWx0ZXIgb3V0IFJlc2VhcmNoZXIncyB0ZXh0OgoKYGBge3J9CmZyZXF1ZW5jeSAlPiUKICBmaWx0ZXIoU3BlYWtlciAhPSAiUiIpICU+JQogIHRvcF9uKDIpCmBgYAoKV2Ugd2lsbCBuZWVkIHRoZSBkYXRhIG90aGVyIHRoYW4gdGhlIHJlc2VhcmNoZXIncyBxdWVzdGlvbnMgZnJlcXVlbnRseSwgc28gbGV0J3MgY3JlYXRlIGEgdmFyaWFibGUgZm9yIHRoZSB0d286CgpgYGB7cn0Kd29yZHNfc3R1ZGVudHMgPC0gd29yZHNfZGYgJT4lCiAgZmlsdGVyKFNwZWFrZXIgIT0gIlIiKQpgYGAKQW5kIGEgdmFyaWFibGUgZm9yIHRoZSBSZXNlYXJjaGVyIHdvcmRzOgoKYGBge3J9CndvcmRzX3Jlc2VhcmNoZXIgPC0gd29yZHNfZGYgJT4lCiAgZmlsdGVyKFNwZWFrZXIgPT0gIlIiKQpgYGAKCiMjIyBXb3JkIGNsb3VkCgoKYGBge3J9CmxpYnJhcnkod29yZGNsb3VkKQpgYGAKYGBge3J9CndvcmRzX3N0dWRlbnRzICU+JQogIGNvdW50KHdvcmQpICU+JQogIHdpdGgod29yZGNsb3VkKHdvcmQsIG4sIG1heC53b3JkcyA9IDEwMCkpCmBgYAoKCiMjIFNlbnRpbWVudCBhbmFseXNpcyAKClRpZHl0ZXh0IGNvbWVzIHdpdGggdGhyZWUgbGV4aWNvbnMgdGhhdCBjb250YWlucyB3b3JkcyBmb3Igc2VudGltZW50cywgb3ZlcmFsbCBtb3JlIHRoYW4gMjcsMDAwIHdvcmRzOgoKCmhlYWQoc2VudGltZW50cykKCkxldCdzIHVzZSB0aGUgbnJjIGxleGljb24gYW5kIGxvb2sgZm9yIGpveWZ1bGwgd29yZHMuIEZpcnN0LCBnZXQgdGhlIGpveWZ1bCB3b3JkcyBpbnRvIGEgZGY6CgoKYGBge3J9Cm5yY19qb3kgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSAKICBmaWx0ZXIoc2VudGltZW50ID09ICJqb3kiKQpgYGAKClRoZW4gbG9vayBmb3IgdGhlbSBpbiBhbnN3ZXJzIEZvciB0aGlzLCB3ZSB1c2UgaW5uZXJfam9pbiwgd2hpY2ggcmV0YWlucyBvbmx5IHRoZSBtYXRjaGluZyByb3dzIGluIGJvdGggc2V0cy4gVGhlIGdlbmVyYWwgc3ludGF4IGlzIGBpbm5lcl9qb2luKGEsIGIsIGJ5ID0gIngxIilgLCBidXQgaW4gdGhlIHNob3J0ZXIgZm9ybWF0IHRoaXMgcmVkdWNlcyB0bzoKCmBgYHtyfQp3b3Jkc19zdHVkZW50cyAlPiUKICBpbm5lcl9qb2luKG5yY19qb3kpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUKICB0b3BfbigxMCkKYGBgCgpUaGUgbG9uZyBmb3JtIHdvdWxkIGJlIGBpbm5lcl9qb2luKHdvcmRzX3ExLCBucmNfam95LCBieSA9ICJ3b3JkIilgCmJ1dCBSIGlzIGFibGUgdG8gZmlndXJlIHRoZSAgYXJndW1lbnRzIHByb3ZpZGVkIGluIHRoZSBzaG9ydCBmb3JtLgoKVG8gZG8gdGhlIHNhbWUgYW5hbHlzaXMgZm9yIHRoZSBzZW50aW1lbnQgJ2ZlYXInLCBzaW1wbHkgcmVwbGFjZSAnam95JyB3aXRoICdmZWFyJyBhYm92ZSAoYW5kIHBlcmhhcHMgcmVuYW1lIHRoZSB2YXJpYWJsZSB0byBucmNfZmVhciBvciBzdWNoKS4gCgoKIyMjIFNlbnRpbWVudCBhbmF5c2lzIG9uIG9uZSBxdWVzdGlvbgoKSnVzdCBmb3IgUTEgYW5kIGFsbCBmb2xsb3ctdXAgcXVlc3Rpb25zOgoKYGBge3J9CndvcmRzX3N0dWRlbnRzICU+JQogIGZpbHRlcihRdWVzdGlvbiA9PSAiUTEiIHwgUXVlc3Rpb24gPT0gIlExLUYiKSAlPiUKICBpbm5lcl9qb2luKG5yY19qb3kpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpgYGAKCgojIyMgU2VudGltZW50IG9uIGFsbCBxdWVzdGlvbnMKCmBgYHtyfQp3b3Jkc19zdHVkZW50cyAlPiUKICBncm91cF9ieShRdWVzdGlvbikgJT4lCiAgaW5uZXJfam9pbihucmNfam95KSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gRkFMU0UpCmBgYApXZSB0dXJuZWQgc29ydGluZyBvbiB0aGUgY291bnQgb2ZmIGhlcmUgYmVjYXVzZSB3ZSB3YW50IHRvIGhhdmUgdGhlIHJlc3VsdHMgZ3JvdXBlZCBieSBxdWVzdGlvbiBpZC4gSWYgU29ydGluZyB3YXMgVFJVRSwgdGhlIG51bWVyaWMgY291bnQgd291bGQgYmUgdXNlZCBmaXJzdCwgdGhlbiB0aGUgcXVlc3Rpb24gSUQuIAoKIyMjIFNlbnRpbWVudCBhbmFseXNpcyBvbiBhIHN1Yi1zZXQgb2YgYWxsIHF1ZXN0aW9ucwoKTGV0J3MgcGljayBRMSwgMyBhbmQgNzogCgpgYGB7cn0Kd29yZHNfc3R1ZGVudHMgJT4lCiAgZmlsdGVyKFF1ZXN0aW9uID09ICJRMSIgfCBRdWVzdGlvbiA9PSAiUTMiIHwgUXVlc3Rpb24gPT0gIlE3IikgJT4lCiAgaW5uZXJfam9pbihucmNfam95KSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKYGBgCgoKIyMjIE1vc3QgY29tbW9uIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzZW50aW1lbnQgd29yZHMKClVzaW5nIHRoZSBsZXhpY29uIGJpbmcsIHdoaWNoIGNsYXNzaWZpZXMgc2VudGltZW50IHdvcmRzIGFzIHBvc2l0aXZlIG9yIG5lZ2F0aXZlLCB3ZSBjYW4gZmluZCBob3cgd29yZHMgY29zdHJpYnV0ZSB0byB0aGVzZSBvdmVyYWxsIHNlbnRpbWVudHM6CgpgYGB7cn0KYmluZ193b3JkX2NvdW50cyA8LSB3b3Jkc19zdHVkZW50cyAlPiUKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQpoZWFkKGJpbmdfd29yZF9jb3VudHMpCmBgYApMZXQncyBncmFwaCB0aGlzIHdpdGggZ2dwbG90MjogCgpgYGB7cn0KYmluZ193b3JkX2NvdW50cyAlPiUKICBncm91cF9ieShzZW50aW1lbnQpICU+JQogIHRvcF9uKDEwKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofnNlbnRpbWVudCwgc2NhbGVzID0gImZyZWVfeSIpICsKICBsYWJzKHkgPSAiQ29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsCiAgICAgICB4ID0gTlVMTCkgKwogIGNvb3JkX2ZsaXAoKQpgYGAKVGhlIHRvcF9uKDEwKSBpcyByZXR1cm5pbmcgbW9yZSB0aGFuIDEwIHJvd3MgaWYgdGhlcmUgYXJlIHRpZXMuIFdpbGwgaGF2ZSB0byB3cml0ZSBhIG1vcmUgY29tcGxleCBleHByZXNzaW9uIHdpdGggZmlsdGVyKCkgYW5kIG1pbl9yYW5rKCkgYmVjYXVzZSB0aGF0J3Mgd2hhdCB0b3BfbiB3cmFwcy4gCgoKSGVyZSdzIGEgY29vbCBjb21iaW5hdGlvbiBvZiB3b3JkY2xvdWRzIHdpdGggYmluZyB3b3JkczoKCmBgYHtyfQpsaWJyYXJ5KHJlc2hhcGUyKQpgYGAKV2UgbmVlZCByZXNoYXBlMiBmb3IgdGhlIGBhY2FzdGAgZnVuY3Rpb24uIGBhY2FzdGAgaXMgYSBjb21wbGV4IGZ1bmN0aW9uIHVzZWQgdG8gdHJhbnNmb3JtIGJldHdlZW4gZGF0YSBmcmFtZXMgYW5kIG1hdHJpY2VzLiBgY29tcGFyaXNvbi5jbG91ZCgpYCBuZWVkcyBhIG1hdHJpeCBhcyBpbnB1dC4gRm9yIG1vcmUgc2VlIGh0dHA6Ly9oYWQuY28ubnovcmVzaGFwZS8uIAoKYGBge3J9CndvcmRzX3N0dWRlbnRzICU+JQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCJncmF5MjAiLCAiZ3JheTgwIiksCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAxMDApCmBgYAoKIyBXb3JkIGFuZCBEb2N1bWVudCBGcmVxdWVuY3kKCiMjIENhbGN1bGF0aW5nIHRlcm0gZnJlcXVlbmN5IHBlciBpbnRlcnZpZXcKCiMjIyBXaG9sZSBpbnRlcnZpZXcgY29ycHVzCgpTdGVwIDE6IEEgdGliYmxlIHdvcmQgY291bnQgYnkgaW50ZXJ2aWV3IHNvcnRlZCBieSBjb3VudC4gVGhpcyBoYXMgbGVuZ3RoIDEwOTcgb2JzZXJ2YXRpb25zLCBsZXNzIHRoYW4gdGhlIGZ1bGwgY29ycHVzICgyNjE3IG9icykgYmVjYXVzZSB3ZSBhZ2dyZWdhdGUgaW4gZWFjaCBpbnRlcnZpZXc6IApgY291bnQoeCwgLi4uLCB3dCA9IE5VTEwsIHNvcnQgPSBGQUxTRSlgIHdpdGggeCB0aGUgdGJsIGFuZCBgLi4uYCB0aGUgdmFyaWFibGVzIHRvIGdyb3VwIGJ5OyB3dCBpcyBmb3Igd2VpZ2h0aW5nIGJ5IGNvdW50aW5nIG51bWJlciBvZiByb3dzLiAKCmBgYHtyfQppbnRlcnZpZXdfd29yZHMgPC0gd29yZHNfZGYgJT4lCiAgY291bnQoSW50ZXJ2aWV3LCB3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgdW5ncm91cCgpCgpoZWFkKGludGVydmlld193b3JkcykKYGBgClNvIGhlcmUgd2UgaGF2ZSBgY291bnQod29yZHNfZGYsIEludGVydmlldywgd29yZCwgd3QgPSBOVUxMLCBzb3J0ID0gVFJVRWAuIAoKClN0ZXAgMjogdG90YWwgb2Ygd29yZHMgcGVyIGludGVydmlldyAKYGBge3J9CnRvdGFsX3dvcmRzIDwtIGludGVydmlld193b3JkcyAlPiUgCiAgZ3JvdXBfYnkoSW50ZXJ2aWV3KSAlPiUgCiAgc3VtbWFyaXplKHRvdGFsID0gc3VtKG4pKQoKdG90YWxfd29yZHMKYGBgCgpTdGVwIDM6IEFkZCB0aGUgdG90YWwgY291bnQgY29sdW1uIHRvIGVhY2ggb2YgdGhlIHdvcmRzIGFuZCB0aGVpciBpbmRpdmlkdWFsIGZyZXF1ZW5jeQoKYGBge3J9CmludGVydmlld193b3JkcyA8LSBsZWZ0X2pvaW4oaW50ZXJ2aWV3X3dvcmRzLCB0b3RhbF93b3JkcywgYnkgPSAiSW50ZXJ2aWV3IikKaGVhZChpbnRlcnZpZXdfd29yZHMpCmBgYApgTGVmdF9qb2luKGEsIGIsIGJ5ID0gIngxJylgIGpvaW5zIG1hdGhpbmcgcm93cyBmcm9tIGIgdG8gYS4gVGhlICJieSIgaXMgaW5jbHVkZWQgaGVyZSBmb3IgY2xhcml0eSwgYnV0IGNvdWxkIGJlIG9taXR0ZWQgYmVjYXVzZSBpdCBpcyB0aGUgc2luZ2xlIHNoYXJlZCB2YXJpYWJsZSBpbiBib3RoIHRibHMuCgpMZXQncyBsb29rIGF0IHRoZSB0ZXJtIGZyZXF1ZW5jeTogCgpgYGB7cn0KZ2dwbG90KGludGVydmlld193b3JkcywgYWVzKG4vdG90YWwsIGZpbGwgPSBJbnRlcnZpZXcpKSArCiAgZ2VvbV9oaXN0b2dyYW0oc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofkludGVydmlldywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3kiKQpgYGAKCiMjIyB0Zl9pZGYgRm9yIHN0dWRlbnRzIHJlcG9uc2VzIG9ubHkKClNpbmNlIHdlIGFyZSBsb29raW5nIGZvciBkaWZmZXJlbmNlcyBiZXR3ZWVuIGludGVydmlld3MgKHN0dWRlbnRzKSwgaXQgcHJvYmFibHkgbWFrZXMgc2Vuc2UgdG8gcmVtb3ZlIHRoZSAic2hhcmVkIiBmYWN0b3ItLXRoZSBSZXNlYXJjaGVyLS1hbmQgbG9vayBhdCBzdHVkZW50cycgYW5zd2VycyBvbmx5OgoKYGBge3J9CmludGVydmlld193b3JkcyA8LSB3b3Jkc19zdHVkZW50cyAlPiUKICBjb3VudChJbnRlcnZpZXcsIHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUKICB1bmdyb3VwKCkKCmhlYWQoaW50ZXJ2aWV3X3dvcmRzKQpgYGAKClRoZSB0Zl9pZGYgdmFsdWUgYmUgY2FsY3VhdGVkIGJhc2VkIG9uIHRoZSB0YWJsZSBqdXN0IGNyZWF0ZWQgbGlrZSB0aGlzOgoKYGBge3J9CmludGVydmlld193b3JkcyA8LSBpbnRlcnZpZXdfd29yZHMgJT4lCiAgYmluZF90Zl9pZGYod29yZCwgSW50ZXJ2aWV3LCBuKQpoZWFkKGludGVydmlld193b3JkcykKYGBgCldvcmRzIHRoYXQgYXJlIHZlcnkgY29tbW9uIGFjcm9zcyBhbGwgaW50ZXJ2aWV3cyBoYXZlIGEgbG93IGlkZiB0ZXJtLCBhbmQgc28gd2lsIHRoZSB0Zl9pZGYuIExldCdzIGZpbmQgdGhlIGRpc3RpbmN0IHdvcmRzIGJ5IHNvcnRpbmcgb24gdGhlIHRmX2lkZjoKCmBgYHtyfQppbnRlcnZpZXdfd29yZHMgJT4lCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpCmBgYAoKV2UgY2FuIHBsb3QgdGhpcyByZXN1bHQ6CgpgYGB7cn0KaW50ZXJ2aWV3X3dvcmRzICU+JQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUKICBtdXRhdGUod29yZCA9IGZhY3Rvcih3b3JkLCBsZXZlbHMgPSByZXYodW5pcXVlKHdvcmQpKSkpICU+JSAKICBncm91cF9ieShJbnRlcnZpZXcpICU+JSAKICB0b3BfbigxMCkgJT4lIAogIHVuZ3JvdXAgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCB0Zl9pZGYsIGZpbGwgPSBJbnRlcnZpZXcpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGxhYnMoeCA9IE5VTEwsIHkgPSAidGYtaWRmIikgKwogIGZhY2V0X3dyYXAofkludGVydmlldywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKKipUb2RvOioqIHVucGFjayB0aGUgbXV0YXRlIGxpbmUgCgo+IElkZWE6IERvaW5nIHRoZSBhbmFseXNpcyBieSBxdWVzdGlvbiBzaG91bGQgeWllbGQgYSBtb3JlIGRpc3RpbmN0IHByb2ZpbGUgb2Ygc3BlY2lmaWMgd29yZHMuIAoKIyBSZWxhdGlvbnNoaXAgYmV0d2VlbiB3b3JkcyAtIE4tZ3JhbXMKCkZpbHRlciBvdXQgcmVzZWFyY2hlciB0YWxrIGFuZCBjcmVhdGUgYmktZ3JhbXM6CgpgYGB7cn0Kc3R1ZGVudF9iaWdyYW1zIDwtIHRleHRfZGYgJT4lCiAgZmlsdGVyKFNwZWFrZXIgIT0gIlIiKSAlPiUKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgVGV4dCwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpCgpoZWFkKHN0dWRlbnRfYmlncmFtcykKYGBgCgpUaGUgd2F5IHRvIGNvbnRyb2wgdGhlIHRva2VuIHNpemUgaXMgdG8gc2V0IHBhcmFtYXRlcnMgb2YgdGhlIHVubmVzdF90b2tlbiBmdW5jdGlvbiwgc2VlIApgaGVscCh1bm5lc3RfdG9rZW4iKWAuIEEgc2luZ2xlIHdvcmQgaXMgYW4gbmdyYW0gb2YgbGVuZ3RoIDEsIHNvIHR3byBhZGphY2VudCB3b3JkcyBjYW4gYmUgdXNlZCBhcyB0aGUgdG9rZW4gdW5pdCBieSBgdW5uZXN0X3Rva2VucyhuZ3JhbSwgdHh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMilgLiAKCldlJ2xsIGNvcHkgdGhlc2UgaW50byBhIG1vcmUgZ25lcmFsbHkgbmFtZWQgdmFyaWFibGUgaW4gY2FzZSB3ZSB3YW50IHRvIHJlZG8gdGhpcyBhbmFseXNpcyBvbiBvdGhlciBiaWdyYW1zOgoKYGBge3J9CmJpZ3JhbXMgPC0gc3R1ZGVudF9iaWdyYW1zCmBgYAoKIyMjIFJlbW92aW5nIHN0b3Agd29yZHMgCgpNYW55IG9mIHRoZSBiaS1ncmFtcyB3aWxsIGJlIHN0b3Agd29yZCBwaHJhc2VzLCBvZiB3aGljaCB3ZSBuZWVkIHRvIGdldCByaWQgb2ZmLiBUaGUgcGF0dGVybiBpcyBzZXBhcmF0ZS9maWx0ZXIvY291bnQvdW5pdGU6IAoKU3RlcCAxOiBzZXBhcmF0ZSBpbnRvIHR3byBjb2x1bW5zCgoKYGBge3J9CmJpZ3JhbXNfc2VwYXJhdGVkIDwtIGJpZ3JhbXMgJT4lCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpCmBgYAoKU3RlcCAyOiBGaWx0ZXIgb3V0IHN0b3B3b3JkcyAKCmBgYHtyfQpiaWdyYW1zX2ZpbHRlcmVkIDwtIGJpZ3JhbXNfc2VwYXJhdGVkICU+JQogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQogIGZpbHRlcighd29yZDIgJWluJSBzdG9wX3dvcmRzJHdvcmQpCmBgYAoKTXVjaCBsZXNzIHBhaXJzISAgTGV0J3MgY291bnQgdGhlbToKClN0ZXAgMzogY291bnQKCmBgYHtyfQpiaWdyYW1fY291bnRzIDwtIGJpZ3JhbXNfZmlsdGVyZWQgJT4lIAogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpCmBgYAoKQnV0IHN0aWxsIGxvdHMgb2Ygbm9uLW1lYW5pbmcgd29yZHMsIGFsbCB0aG9zZSBhaHMgYW5kIHVocywgYmVjYXVzZSB0aGlzIGlzIHRhbGsuIExldCdzIGZpbHRlciBzb21lIG1vcmUuIAoKYGBge3J9CmJpZ3JhbXNfZmlsdGVyZWQgPC0gYmlncmFtc19maWx0ZXJlZCAlPiUKICBmaWx0ZXIoIXdvcmQxICVpbiUgc3RvcF93b3JkczIkd29yZCkgJT4lCiAgZmlsdGVyKCF3b3JkMiAlaW4lIHN0b3Bfd29yZHMyJHdvcmQpCmBgYAoKU3RlcCA0OiBBbmQgbGV0J3MgcHJvZHVjZSBhIHVuaXRlZCB2ZXJzaW9uIGFnYWluOgoKCmBgYHtyfQpiaWdyYW1zX3VuaXRlZCA8LSBiaWdyYW1zX2ZpbHRlcmVkICU+JQogIHVuaXRlKGJpZ3JhbSwgd29yZDEsIHdvcmQyLCBzZXAgPSAiICIpCgpoZWFkKGJpZ3JhbXNfdW5pdGVkKQpgYGAKCiMjIyBBbmFseXNpcwoKQ291bnRpbmcgYmVjYXVzZSB3ZSBjYW46CiAKCmBgYHtyfQpiaWdyYW1zX3VuaXRlZCAlPiUKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKQpgYGAKCiBMZXRzIGxvb2sgYXQgYWxsIHBocmFzZXMgd2l0aCBhIHNwZWNpZmljIHdvcmQgaW4gcG9zaXRpb24gMgpgYGB7cn0KYmlncmFtc19maWx0ZXJlZCAlPiUKICBmaWx0ZXIod29yZDIgPT0gImFzc2lnbm1lbnQiKSAlPiUKICBjb3VudChJbnRlcnZpZXcsIHdvcmQxLCBzb3J0ID0gVFJVRSkKYGBgCiBJbXByb3ZlbWVudDogdXNlIHJlZ2V4IHRvIGRlYWwgd2l0aCBkaWZmZXJlbnQgZm9ybXMgb2YgdGhlIHN0ZW06IAogCmBgYHtyfQpiaWdyYW1zX2ZpbHRlcmVkICU+JQogIGZpbHRlcihncmVwbCgiYXNzaWduIiwgd29yZDIpKSAlPiUKICBjb3VudChJbnRlcnZpZXcsIHdvcmQxLCBzb3J0ID0gVFJVRSkKYGBgCgpgZ3JlcGwocGF0dGVybiwgeClgIGlzIHRoZSBsb2dpYyBmb3JtIG9mIGBncmVwYCwgcmV0dXJuaWduIGEgdmVjdG9yIG9mIG1hdGNoIG9yIG5vdCBmb3IgZWFjaCBlbGVtZW50IG9mIHguIAoKIyMjIFNlbnRpbWVudCBhbmFseXNpcyB3aXRoIHdvcmRzIGluIGNvbnRleHQKCkZvciBzZW50aW1lbnQgYW5hbHlzaXMgd2UgbmVlZCBpbiBwYXJ0aWN1bGFyIGludG8gbmVnYXRpb25zIiIKCmBgYHtyfQpiaWdyYW1zX3NlcGFyYXRlZCAlPiUKICBmaWx0ZXIod29yZDEgPT0gIm5vdCIpICU+JQogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpCmBgYAoKTGV0J3MgdXNlIHRoYXQgZm9yIHNvbWUgYW5hbHlzaXMgd2l0aCB0aGUgQUZJTk4gbGV4aWNvbiwgd2l0aCB1c2VzIGEgbnVtZXJpYyBzY29yZSB0byBleHByZXNzIHNlbnRpbWVudCBkaXJlY3Rpb246IAoKYGBge3J9CkFGSU5OIDwtIGdldF9zZW50aW1lbnRzKCJhZmlubiIpCmhlYWQoQUZJTk4pCmBgYAoKCmBgYHtyfQpub3Rfd29yZHMgPC0gYmlncmFtc19zZXBhcmF0ZWQgJT4lCiAgZmlsdGVyKHdvcmQxID09ICJub3QiKSAlPiUKICBpbm5lcl9qb2luKEFGSU5OLCBieSA9IGMod29yZDIgPSAid29yZCIpKSAlPiUKICBjb3VudCh3b3JkMiwgc2NvcmUsIHNvcnQgPSBUUlVFKSAlPiUKICB1bmdyb3VwKCkKCm5vdF93b3JkcwpgYGAKCihUaGlzIG5lZWRzIHNvbWUgdW5wYWNraW5nKQpXZSBzZWUgaG93IGltcG9ydGFudCBpdCBpcyB0byB3YXRjaCBvdXQgZm9yIG5lZ2F0aW9ucyB3aGVuIGl0IGNvbWVzIHRvIHNlbnRpbWVudHMuIE90aGVyIG5lZ2F0aW9uIHdvcmRzIGRlc2VydmUgYXR0ZW50aW9uLCB0b286CgpgYGB7cn0KbmVnYXRpb25fd29yZHMgPC0gYygibm90IiwgIm5vIiwgIm5ldmVyIiwgIndpdGhvdXQiKQoKbmVnYXRlZF93b3JkcyA8LSBiaWdyYW1zX3NlcGFyYXRlZCAlPiUKICBmaWx0ZXIod29yZDEgJWluJSBuZWdhdGlvbl93b3JkcykgJT4lCiAgaW5uZXJfam9pbihBRklOTiwgYnkgPSBjKHdvcmQyID0gIndvcmQiKSkgJT4lCiAgY291bnQod29yZDEsIHdvcmQyLCBzY29yZSwgc29ydCA9IFRSVUUpICU+JQogIHVuZ3JvdXAoKQoKbmVnYXRlZF93b3JkcwpgYGAKClRoaXMgb25seSBtYWtlcyBhIHNtYWxsIGRpZmZlcmVuY2UgaW4gdGhlIDUgaW50ZXJ2aWV3cywgYnV0IHdpdGggYSBsYXJnZXIgY29ycHVzIHdlIGFyZSBib3VuZCB0byBzZWUgbW9yZSBpbXBhY3QuIFdlIGNhbiBjb21wdXRlIGFuZCB2aXN1YWxpc2UgdGhlIGltcGFjdCBieSBjb21wdXRpbmcgYSB2YWx1ZSBgc2NvcmUgKiBuYDogCgpgYGB7cn0KbmVnYXRlZF93b3JkcyAlPiUKICBtdXRhdGUoY29udHJpYnV0aW9uID0gbiAqIHNjb3JlKSAlPiUKICBhcnJhbmdlKGRlc2MoYWJzKGNvbnRyaWJ1dGlvbikpKSAlPiUKICBoZWFkKDIwKSAlPiUKICBtdXRhdGUod29yZDIgPSByZW9yZGVyKHdvcmQyLCBjb250cmlidXRpb24pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQyLCBuICogc2NvcmUsIGZpbGwgPSBuICogc2NvcmUgPiAwKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICB4bGFiKCJXb3JkcyBwcmVjZWRlZCBieSBcIm5vdFwiIikgKwogIHlsYWIoIlNlbnRpbWVudCBzY29yZSAqIG51bWJlciBvZiBvY2N1cnJlbmNlcyIpICsKICBjb29yZF9mbGlwKCkKYGBgCgpUaGlzIGFsc28gbWFrZXMgaXQgdmlzaWJsZSB0aGF0IGluIHRoZSBmaXJzdCA1IGludGVydmlld3MgdGhlcmUgd2VyZSBubyBuZWdhdHZlIHdvcmRzIG5lZ2F0ZWQsIHN1Y2ggYXMgIm5vIGRvdWJ0Ii4gCgojIyMgR3JhcGhpbmcgYmlncmFtIG5ldHdvcmtzIAoKYGBge3IgaW5pdGlhbGlzZSBpZ3JhcGh9CmxpYnJhcnkoaWdyYXBoKQpgYGAKClRoZSBkYXRhZnJhbWUgd2UgbmVlZCBmb3IgdGhpcyBpcyBgYmlncmFtX2NvdW50c2Agd2l0aCB3b3JkMSwgd29yZDIgYW5kIG4gYXMgdGhlICJ3ZWlnaHQiLiBMZXQncyB1c2Ugb25seSB0aGUgdGhvc2Ugd2l0aCBuIEdFRyAyOgoKCmBgYHtyfQpiaWdyYW1fZ3JhcGggPC0gYmlncmFtX2NvdW50cyAlPiUKICBmaWx0ZXIobiA+PSAyKSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKQoKYmlncmFtX2dyYXBoCmBgYApGb3IgcGxvdHRpZ25nIHdlIHVzZSBnZ3JhcGg6CgpgYGB7ciBnZ3JwcmFwaCBpbml0YWxpYXNlZH0KbGlicmFyeShnZ3JhcGgpCmBgYAoKYGBge3J9CnNldC5zZWVkKDIwMTcpCgpnZ3JhcGgoYmlncmFtX2dyYXBoLCBsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoKSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCB2anVzdCA9IDEsIGhqdXN0ID0gMSkKYGBgCgpOb3QgYSBwYXJ0aWN1bGFybHkgY29ubmVjdGVkIGdyYXBoLCBidXQgdGhlIHByaW5jaXBsZSBpcyBjbGVhci4gCgojIyMgQ29ycmVsYXRpb25zIGFtb25nIHF1ZXN0aW9ucwoKYGBge3J9CmxpYnJhcnkod2lkeXIpCmBgYAoKYGBge3J9CndvcmRfcGFpcnMgPC0gd29yZHNfc3R1ZGVudHMgJT4lCiAgcGFpcndpc2VfY291bnQod29yZCwgUXVlc3Rpb24sIHNvcnQgPSBUUlVFKQoKd29yZF9wYWlycwpgYGAKVGhlc2UgYXJlIHBhaXIgY291bnRzIHBlciBxdWVzdGlvbi4gSGVyZSdzIHRoZSBzYW1lIGZvciBpbnRlcnZpZXdzOiAKCmBgYHtyfQp3b3JkX3BhaXJzIDwtIHdvcmRzX3N0dWRlbnRzICU+JQogIHBhaXJ3aXNlX2NvdW50KHdvcmQsIEludGVydmlldywgc29ydCA9IFRSVUUpCgp3b3JkX3BhaXJzCmBgYApOb3RlIHRoYXQgdGhlc2UgYmVjb21lIHJhdGhlciBiaWcgdGFibGVzIGJlY2F1c2Ugc28gbWFueSB0aGluZ3MgZ28gdG9nZWh0ZXIgb24gdGhhdCBzY2FsZS4gV2UgY2FuIGFsc28gbG9vayB0aGlzIHdheSBmb3Igc3BlY2lmaWMgY29tYmluYXRpb25zIG9mIHdvcmRzLiBMZXQncyBnbyBiYWNrIHRvIHBhaXJzIHdpdGhpbiBxdWVzdGlvbnMsIGFuZCBmdXJ0aGVyIHJlZHVjZSB0byB0aGUgZmlyc3QgaW50ZXJ2aWV3OgoKYGBge3J9CndvcmRfcGFpcnMgPC0gd29yZHNfc3R1ZGVudHMgJT4lCiAgZmlsdGVyKEludGVydmlldyA9PSAxKSAlPiUKICBwYWlyd2lzZV9jb3VudCh3b3JkLCBRdWVzdGlvbiwgc29ydCA9IFRSVUUpCgp3b3JkX3BhaXJzCmBgYAoKQWRuIGxldHMgbG9vayBmb3IgcGFpcnMgdGhhdCBoYXZlICJhc3NpZ25tZW50IiBpbiB0aGVtLiBOb3RlIHRoYXQgdGhlIGxldmVsIG9mIGNvdW50aW5nIGlzIHRoZSBRdWVzdGlvbi4gT25lIHByb2JsZW0gaXMgdGhhdCB3ZSBoYXZlIHF1ZXN0aW9ucyB3aXRoIGxhYmVscyBRPGk+IGFuZCBRPGk+LUYsIHdoaWNoIGluIHRoaXMgYW5hbHlzaXMgZ2V0IGNvdW50ZWQgc2VwYXJhdGVseS4gCgoKYGBge3J9CndvcmRfcGFpcnMgJT4lCiAgICBmaWx0ZXIoaXRlbTEgPT0gImFzc2lnbm1lbnQiKQpgYGAKCkxldCdzIGNhbGN1bGF0ZSBhIGNvcnJlbGF0aW9uIGJldHdlZW4gKGZyZXF1ZW50KSB3b3JkczoKCmBgYHtyfQp3b3JkX2NvcnMgPC0gd29yZHNfc3R1ZGVudHMgJT4lCiAgZ3JvdXBfYnkod29yZCkgJT4lCiAgZmlsdGVyKG4oKSA+PSAyMCkgJT4lCiAgcGFpcndpc2VfY29yKHdvcmQsIFF1ZXN0aW9uLCBzb3J0ID0gVFJVRSkKCndvcmRfY29ycwpgYGAKClBsb3RzIGhlbHAgdG8gZ2V0IGFuIG92ZXJ2aWV3OiAKCmBgYHtyfQp3b3JkX2NvcnMgJT4lCiAgZmlsdGVyKGl0ZW0xICVpbiUgYygibWFyayIsICJ0aW1lIiwgImlkZWEiLCAiYXNzaWdubWVudCIpKSAlPiUKICBncm91cF9ieShpdGVtMSkgJT4lCiAgdG9wX24oNikgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShpdGVtMiA9IHJlb3JkZXIoaXRlbTIsIGNvcnJlbGF0aW9uKSkgJT4lCiAgZ2dwbG90KGFlcyhpdGVtMiwgY29ycmVsYXRpb24pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBmYWNldF93cmFwKH4gaXRlbTEsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCkFuZCBiYXNlZCBvbiBjb3JyZWxhdGlvbnMgd2UgY2FuIGNyZWF0ZSBhIG5ldHdvcmsgZ3JhcGg6IAoKYGBge3J9CnNldC5zZWVkKDIwMTYpCgp3b3JkX2NvcnMgJT4lCiAgZmlsdGVyKGNvcnJlbGF0aW9uID4gLjE1KSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gY29ycmVsYXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9ub2RlX3BvaW50KGNvbG9yID0gImxpZ2h0Ymx1ZSIsIHNpemUgPSA1KSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSkgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCgojIFRvcGljIG1vZGVscwoKSW4gZ2VuZXJhbCwgVE1zIGNsYXNzaWZ5IGRvY3VtZW50cyBpbnRvIHRvcGljcyBhbmQgZGV0ZXJtaW5lIHRoZSBvdmVybGFwIG9mIGEgZG9jdW1lbnQgd2l0aCBhIHRvcGljLiBFYWNoIHRvcGljIGlzIG1hZGUgdXAgb3V0IG9mIGEgbnVtYmVyIG9mIHdvcmRzLiAKCiMjIENhc3RpbmcgdGhlIHRpZHkgdGV4dCBkZiBpbnRvIGEgZG9jdW1lbnQtdGVybSBtYXRyaXgKCk1vc3QgdGV4dCBtaW5pbmcgcGFja2FnZXMgdXNlICBEVE0gZm9ybWF0LCB3aGljaCBpcyBhIG1hdHJpeCB3aGVyZToKCiogZWFjaCByb3cgcmVwcmVzZW50cyBvbmUgZG9jdW1lbnQgKHN1Y2ggYXMgYSBib29rIG9yIGFydGljbGUpLAoqIGVhY2ggY29sdW1uIHJlcHJlc2VudHMgb25lIHRlcm0sIGFuZAoqIGVhY2ggdmFsdWUgKHR5cGljYWxseSkgY29udGFpbnMgdGhlIG51bWJlciBvZiBhcHBlYXJhbmNlcyBvZiB0aGF0IHRlcm0gaW4gdGhhdCBkb2N1bWVudC4KCkhlcmUncyBob3cgdG8gdHVybiBhIHRpZHkgdGV4dCBkZiBpbnRvIGEgRFRNOgoKYGBge3J9CmxpYnJhcnkodG0pCmBgYAoKCmBgYHtyfQpzdHVkZW50c19kdG0gPC0gd29yZHNfc3R1ZGVudHMgJT4lCiAgICBjb3VudChJbnRlcnZpZXcsIHdvcmQpICU+JQogICAgY2FzdF9kdG0oSW50ZXJ2aWV3LCB3b3JkLCBuKQoKc3R1ZGVudHNfZHRtCmBgYAoKIyMgVG9waWNzIGFjcm9zcyBpbnRlcnZpZXdzCgpXaXRoIHRoaXMgd2UgaGF2ZSBhIGR0bSBmb3IgZWFjaCBvZiB0aGUgNSBpbnRlcnZpZXdzLCBzdHVkZW50cycgY29udHJpYnV0aW9ucyBvbmx5LiBCYXNlZCBvbiB0aGlzIHdlIGNhbiBtaW5lIGZvciB0b3BpY3M6CgpgYGB7cn0KbGlicmFyeSh0b3BpY21vZGVscykKCmBgYAoKYGBge3J9CiMgc2V0IGEgc2VlZCBzbyB0aGF0IHRoZSBvdXRwdXQgb2YgdGhlIG1vZGVsIGlzIHByZWRpY3RhYmxlCnN0dWRlbnRzX2xkYSA8LSBMREEoc3R1ZGVudHNfZHRtLCBrID0gMiwgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDEyMzQpKQpzdHVkZW50c19sZGEKYGBgClRoaXMgb2JqZWN0IGNvbnRhaW5zIGEgbW9kZWwgd2l0aCBqdXN0IHR3byBncm91cHMsIHRvIG1ha2UgdGhpbmdzIGVhc2llci4gVGhpcyBtb2RlbCBuZWVkcyB0byBiZSBpbnNwZWN0ZWQgdG8gZ2FpbiBpbnNpZ2h0cy4gRm9yIGluc3RhbmNlLCB3ZSBjYW4gbG9vayBhdCBob3cgdGhlIHdvcmRzIHJlbGF0ZSB0byB0aGUgdG9waWNzLCBleHRyYWN0aW5nIHRoZSBgYmV0YWAgdmFsdWUgZnJvbSB0aGUgbWF0cml4LiBXZSB1c2UgdGlkeSB0byB0dXJuIHRoYXQgY29sdW1uIGludG8gYSB0aWJibGUgdG9waWMtdGVybS1iZXRhLiBUaGUgYmV0YXMgY2FuIGJlIGludGVycHJldGVkIGFzIHByb2JhYmlsaXRpZXMgb3IgdGhlIHRlcm0gYmVsb25naW5nIHRvIHRoZSB0b3BpYy4gCgpgYGB7cn0Kc3R1ZGVudHNfdG9waWNzIDwtIHRpZHkoc3R1ZGVudHNfbGRhLCBtYXRyaXggPSAiYmV0YSIpCnN0dWRlbnRzX3RvcGljcwpgYGAKTGV0J3MgbG9vayBhdCB0aGUgdG9wLTEwIHRlcm1zOgoKYGBge3J9CnN0dWRlbnRzX3RvcF90ZXJtcyA8LSBzdHVkZW50c190b3BpY3MgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHRvcF9uKDEwLCBiZXRhKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgYXJyYW5nZSh0b3BpYywgLWJldGEpCmBgYAoKQW5kIHZpc3VhbGlzZSB0aGVtOgoKYGBge3J9CnN0dWRlbnRzX3RvcF90ZXJtcyAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXIodGVybSwgYmV0YSkpICU+JQogIGdncGxvdChhZXModGVybSwgYmV0YSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB0b3BpYywgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKRHVlIHRvIHRoZSBzbWFsbCBjb3JwdXMsIGFuZCB0aGUgZmFjdCB0aGF0IHdlIGhhdmUgdGhlIHNhbWUgcXVlc3Rpb25zIGluIGFsbCBpbnRlcnZpZXdzLCB0aGlzIGxlYWRzIG5vdCB0byB0b3BpY3Mgd2hpY2ggYXJlIGVhc2lseSBpbnRlcnByZXRlZC4gQSBjb3JyZXNwb25kaW5nIGFuYWx5c2lzLCBraW5kIG9mIGEgdmFsaWRhdGlvbiBxdWVzdGlvbiwgd291bGQgbG9vayBpbnRvIHRvcGljcyBiYXNlZCBvbiB0aGUgcXVlc3Rpb25zLiAKClRoZSBhbHRlcm5hdGl2ZSB2aWV3IGlzIHRvIGxvb2sgZm9yIHRlcm1zIHdoaWNoIGRpZmZlciBtb3N0IGJldHdlZW4gdG9waWNzLiAKCgpgYGB7cn0KYmV0YV9zcHJlYWQgPC0gc3R1ZGVudHNfdG9waWNzICU+JQogIG11dGF0ZSh0b3BpYyA9IHBhc3RlMCgidG9waWMiLCB0b3BpYykpICU+JQogIHNwcmVhZCh0b3BpYywgYmV0YSkgJT4lCiAgZmlsdGVyKHRvcGljMSA+IC4wMDEgfCB0b3BpYzIgPiAuMDAxKSAlPiUKICBtdXRhdGUobG9nX3JhdGlvID0gbG9nMih0b3BpYzIgLyB0b3BpYzEpKQoKYmV0YV9zcHJlYWQKYGBgCj4gVG8gZG86IFBsb3QgYSBncmFwaCB3aXRoIHRoZSBoaWdoZXN0IChwb3NpdGl2ZSBhbmQgbmVnYXRpdmUpIGRpZmZlcmVuY2VzIGluIGxvZyByYXRpby4gCgojIyBUb3BpY3MgYWNyb3NzIHF1ZXN0aW9ucwoKYGBge3J9CnN0dWRlbnRzX2R0bV9xdWVzdGlvbnMgPC0gd29yZHNfc3R1ZGVudHMgJT4lCiAgICBjb3VudChRdWVzdGlvbiwgd29yZCkgJT4lCiAgICBjYXN0X2R0bShRdWVzdGlvbiwgd29yZCwgbikKCnN0dWRlbnRzX2R0bV9xdWVzdGlvbnMKYGBgCldlIGhhdmUgMTYgcXVlc3Rpb25zIChkb2N1bWVudHMpIGhlcmUgYmVjYXVzZSBvZiB0aGUgZm9sbG93IHVwIHF1ZXN0aW9ucy4gUmVhbGx5IG5lZWQgdG8gYWRkcmVzcyB0aGF0IGluIHRoZSBiYXNlIG1vZGVsIGJ5IGludHJvZHVjaW5nIGEgY29sdW1uIGZvciBmb2xsb3cgdXAuIEluIGFueSBjYXNlLCA5IHRvcGljcyBzaG91bGQgYmUgYSBnb29kIGZpdCB0byB0aGUgZGF0YS4gCgpgYGB7cn0Kc3R1ZGVudHNfbGRhX3F1ZXN0aW9ucyA8LSBMREEoc3R1ZGVudHNfZHRtX3F1ZXN0aW9ucywgayA9IDkpCnN0dWRlbnRzX2xkYV9xdWVzdGlvbnMKYGBgCgpgYGB7cn0Kc3R1ZGVudHNfdG9waWNzX3F1ZXN0aW9ucyA8LSB0aWR5KHN0dWRlbnRzX2xkYV9xdWVzdGlvbnMsIG1hdHJpeCA9ICJiZXRhIikKc3R1ZGVudHNfdG9waWNzX3F1ZXN0aW9ucwpgYGAKCgoKCmBgYHtyfQpzdHVkZW50c190b3BfdGVybXNfcXVlc3Rpb25zIDwtIHN0dWRlbnRzX3RvcGljc19xdWVzdGlvbnMgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogIHRvcF9uKDUsIGJldGEpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBhcnJhbmdlKHRvcGljLCAtYmV0YSkKYGBgCgpBbmQgdmlzdWFsaXNlIHRoZW06CgpgYGB7cn0Kc3R1ZGVudHNfdG9wX3Rlcm1zX3F1ZXN0aW9ucyAlPiUKICBtdXRhdGUodGVybSA9IHJlb3JkZXIodGVybSwgYmV0YSkpICU+JQogIGdncGxvdChhZXModGVybSwgYmV0YSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofiB0b3BpYywgc2NhbGVzID0gImZyZWUiKSArCiAgY29vcmRfZmxpcCgpCmBgYAoKU29tZXRoaW5nIGxpa2UgdGhpcyBjb3VsZCBiZSB1c2VkLCB3aXRoIHRoZSBvdmVyYWxsIGNvcnB1cywgdG8gc2VlIGlmIHRoZSBuaW5lIHF1ZXN0aW9ucyB5aWVsZGVkIGRpZmZlcmVudCBhbnN3ZXJzLCBvZiBpZiBvdGhlciBzZW1hbnRpYyBzaW1pbGFyaXRpZXMgc2hvdyB1cC4gUXVlc3Rpb24gNSwgZm9yIGludGFuY2UsIHJlYWxseSBzaG91bGQgYmUgYWJvdXQgZmFjaWxpdGF0aW9uLiAKCkFsc28sIGJ5IGNoYW5naW5nIGsgd2UgY2FuIGV4cGxvcmUgZ29vZG5lc3Mgb2YgZml0IHBlcmhhcHMsIGZvciBhbHRlcm5hdGl2ZSBtb2RlbHMuIAoKCiMgTWlzY2FsbGVuZW91cwoKCgojIyBTdGVtbWluZwoKVGhpcyBub3cgYWxzbyBpcyB0aGUgcG9pbnQgd2VyZSB3ZSBhbHNvIGNvdWxkIGRvIHNvbWUgc3RlbW1pbmcuIEZvciB0aGlzIHdlIG5lZWQgdGhpcyBsaWJyYXJ5OgpgYGB7cn0KbGlicmFyeShTbm93YmFsbEMpCmBgYApXaGljaCBnaXZlcyB1cyBhIGZ1bmN0aW9uICd3b3Jkc3RlbSc6IAoKYGBge3Igc3RlbW1pbmd9CndvcmRzX3N0ZW1tZWQgPC0gbXV0YXRlKHdvcmRzX2RmLCB3b3JkID0gd29yZFN0ZW0od29yZCkpCmhlYWQod29yZHNfc3RlbW1lZCwgbj0yMEwpCmBgYApCdXQgd2UgZG9uJ3Qgd2FudCB0byB1c2UgdGhlIHN0ZW1tZWQgd29yZHMganVzdCBub3cgIGJlY2F1c2UgaXQgd291bGQgaW50ZWZlcmUgd2l0aCB0aGUgc2VudGltZW50IGFuYWx5c2lzLiBTdGVtbWluZyBpcyB1c2VmdWwsIGJ1dCBvbmx5IG9uY2Ugd2Ugbm8gbG9uZ2VyIG5lZWQgInJlYWwiIHdvcmRzLCBzdWNoIGFzIGZvciB0b3BpYyBtb2RlbGxpbmcgYW5kIHRoZSBsaWtlcy4KCgoKCgojIEFwcGVuZGljZXMKCiMjIExlc3NvbnMgbGVhcm5lZAoKVG8gbWFrZSBhbmFseXNpcyBmbGV4aWJsZSwgdXNlIGEgZGlzdGluY3QgdmFyaWFibGUgbmFtZSBmb3IgdGhlIGRhdGEgc2V0IHRoZSBhbmFseXNpcyBpcyBiYXNlZCBvbiwgYW5kIGJpbmQgaXQgYXQgdGhlIGJlZ2lubmluZyBvZiB0aGUgYW5hbHlzaXMgdG8gdGhlIHNwZWNpZmljIGRhdGEgc2V0LCB3aGljaCB5b3UgZXhwZWN0IHdpbGwgYmUgdmFyaWVkLiAKCgo=