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:

  1. Interview: Interview number 1 to 30.
  2. Question: {Q1, Q2, …, Q8}
  3. Speaker: R = Researcher, S01 to S30 = Subjects
  4. 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=