The “example” part of the lab is a little longer than usual. The good news is that it’s pretty similar to the analysis in MDSR, and should provide a lot of template code for the “on your own” portion.
We’re barely scratching the surface of text mining. If you want to learn more, I recommend Tidy Text Mining with R.
Part 1: Example
library(tidyverse)
library(tidytext)
library(gutenbergr)
lab12_theme = theme_minimal(base_family = "Optima", base_size = 12)
Today, I’m going to use novels by the Bronte sisters to walk through a few examples of basic text analysis.
Load Data
We’re going to use the gutenbergr package to download some books directly from Project Gutenberg. In order to download the right books, we need to find their IDs, which come from the URLs at their website. For instance, Jane Eyre is book 1260. We’ll store these books as `*_raw* and then clean them up later.
# 767 - Agnes Grey by Anne Bronte
# 1260 - Jane Eyre: An Autobiography by Charlotte Brontë
# 768 - Wuthering Heights by Emily Brontë
bronte_raw <- gutenberg_download(c(767, 1260, 768), mirror = "http://mirrors.xmission.com/gutenberg/", meta_fields = "title")
(You may not need the mirror argument, but the default mirror was down yesterday when I was writing this lab.)
If you don’t want to re-download the books every time you knit (you don’t), we can use an RMarkdown trick. Put the actual code for getting the books in a chunk with eval=FALSE on it and run it manually in RStudio when you want to get the data. Then you can write the downloaded data as a CSV file, and then load it invisibly from the CSV file when you knit:
I first download data from Project Gutenberg:
```{r get-book, eval=FALSE}
books_raw <- gutenberg_download(...)
write_csv(books_raw, "data/books_raw.csv")
```
```{r load-book-data-real, include=FALSE}
books_raw <- read_csv("data/books_raw.csv")
```
Clean Data
The gutenbergr package downloads data in an already tidy form, with a column for the book ID, a column for the book title (which we told it to get through meta_fields = "title"), and a column for the text. BUT, each book usually starts with a little messiness. Here, we can see that Agnes Grey starts with the title page and publisher information:
head(bronte_raw)
## # A tibble: 6 x 3
## gutenberg_id text title
## <dbl> <chr> <chr>
## 1 767 Agnes Grey Agnes Grey
## 2 767 A NOVEL, Agnes Grey
## 3 767 <NA> Agnes Grey
## 4 767 by ACTON BELL. Agnes Grey
## 5 767 <NA> Agnes Grey
## 6 767 LONDON: Agnes Grey
For this lab, we’re just going to ignore this, but in the “real world”, we’d want to figure out a systematic way to remove any text that is not a part of the book. (For example, we could create a new column indicating whether each row in our dataset is part of the book or not, and then filter() based on that column)
Tokenize and count
The first step is to tokenize this data: the unnest_tokens function does this for us automatically. (The first argument is what you want the new variable to be named, the second argument is the variable we want to tokenize)
bronte_words <- bronte_raw %>%
drop_na(text) %>%
unnest_tokens(word, text)
head(bronte_words)
## # A tibble: 6 x 3
## gutenberg_id title word
## <dbl> <chr> <chr>
## 1 767 Agnes Grey agnes
## 2 767 Agnes Grey grey
## 3 767 Agnes Grey a
## 4 767 Agnes Grey novel
## 5 767 Agnes Grey by
## 6 767 Agnes Grey acton
Next, we remove stop words, count all of the words in each book and keep the top 15, and then sort them (to make the output more readable). I also include a step to make the variable an ordered factor, which is helpful for plotting.
top_words_bronte <- bronte_words %>%
# Remove stop words
anti_join(stop_words) %>%
# Count all the words in each book
count(title, word, sort = TRUE) %>%
# Keep top 15 in each book
group_by(title) %>%
top_n(15) %>%
ungroup() %>%
# Make the words an ordered factor so they plot in order
mutate(word = fct_inorder(word))
top_words_bronte
## # A tibble: 46 x 3
## title word n
## <chr> <fct> <int>
## 1 Wuthering Heights heathcliff 421
## 2 Wuthering Heights linton 346
## 3 Jane Eyre: An Autobiography jane 341
## 4 Wuthering Heights catherine 336
## 5 Jane Eyre: An Autobiography rochester 317
## 6 Jane Eyre: An Autobiography sir 316
## 7 Jane Eyre: An Autobiography miss 310
## 8 Jane Eyre: An Autobiography time 244
## 9 Jane Eyre: An Autobiography day 232
## 10 Jane Eyre: An Autobiography looked 221
## # … with 36 more rows
Finally, we plot this processed data:
ggplot(top_words_bronte, aes(y = fct_rev(word), x = n, fill = title)) +
geom_col() +
guides(fill = FALSE) +
labs(y = "Count", x = NULL,
title = "15 most frequent words in Bronte Novels") +
facet_wrap(vars(title), scales = "free_y") +
lab12_theme +
scale_fill_viridis_d(option = "C", end = .75)

(Sharp-eyed readers will notice that the words aren’t actually in perfect order! That’s because some common words are repeated across the plays, like “miss” and “time”. However, each category in a factor can only have one possible position in the order. You can fix this with the reorder_within() function in tidytext—see Julia Silge’s tutorial here for how to use it.)
tf-idf
Now let’s try a fancy counting method (tf-idf):
bronte_words_filtered = bronte_words %>%
# Remove stop words
anti_join(stop_words) %>%
# Count all the words in each book
count(title, word, sort = TRUE)
# Add the tf-idf values to the counts
bronte_tf_idf <- bronte_words_filtered %>%
bind_tf_idf(word, title, n)
# Get the top 10 uniquest words
bronte_tf_idf_plot <- bronte_tf_idf %>%
arrange(desc(tf_idf)) %>%
group_by(title) %>%
top_n(10) %>%
ungroup() %>%
mutate(word = fct_inorder(word))
ggplot(bronte_tf_idf_plot,
aes(y = fct_rev(word), x = tf_idf, fill = title)) +
geom_col() +
guides(fill = FALSE) +
labs(x = "tf-idf", y = NULL) +
facet_wrap(~ title, scales = "free") +
lab12_theme +
scale_fill_viridis_d(option = "C", end = .75)

These tf-idfs are very small compared to the example from the MDSR book on arXiv papers – this is because these three books are all much more similar to one another than the arXiv abstracts. Since the Bronte sisters are from the same era and wrote in a similar style, all tf-idf did was essentially remove non-names. Oh well!
Sentiment Analysis
A note of caution: like we saw in class, sentiment analyses can be rife with bias. They’re fun to make, and the tidytext package makes it extremely easy to do, but take results with a grain of salt.
The first step is to join the sentiment dictionary to our dataset with inner_join: this only keeps words that show up in both the dataset and the sentiment dictionary.
bronte_sentiment <- bronte_words_filtered %>%
inner_join(get_sentiments("bing"))
head(bronte_sentiment)
## # A tibble: 6 x 4
## title word n sentiment
## <chr> <chr> <int> <chr>
## 1 Jane Eyre: An Autobiography miss 310 negative
## 2 Wuthering Heights master 185 positive
## 3 Agnes Grey miss 182 negative
## 4 Jane Eyre: An Autobiography love 151 positive
## 5 Wuthering Heights miss 129 negative
## 6 Jane Eyre: An Autobiography strange 97 negative
Next, we count up each sentiment by title, and then create a new variable that is positive is sentiment is positive and negative otherwise (try running the intermediate data on your own if this doesn’t make sense). This gives as an “overall positive” and “overall negative” number for each book, which we can plot:
bronte_sentiment_by_title <- bronte_sentiment %>%
count(title, sentiment) %>%
mutate(
n_pos_neg = ifelse(sentiment == "positive", n, -n)
)
ggplot(bronte_sentiment_by_title, aes(x = sentiment, y = n_pos_neg, fill = sentiment)) +
geom_col(position = position_dodge()) +
facet_wrap(vars(title)) +
lab12_theme +
scale_fill_viridis_d(option = "C", end = .75)

We could standardize these by book length to better compare across books, but we’ll do something more interesting, which is to look at total sentiment by chapter.
First, we have to get creative in defining the “Chapter” variable - I’ve included all code to do so with comments below. The general idea is that we check if each row is equal to “CHAPTER X”, record a 1 if yes and 0 if no, and then take the cumulative sum of that new variable to get a chapter number for each line. I also did each book separately, rather than figure out a nicer way to start over at 0. (Note that this will likely be slightly different for different books!)
wuthering_heights <- bronte_raw %>%
filter(title == "Wuthering Heights") %>%
# Get rid of rows where text is missing
drop_na(text) %>%
# Chapters start with CHAPTER X, so mark if each row is a chapter start
# cumsum() calculates the cumulative sum, so it'll increase every time there's
# a new chapter and automatically make chapter numbers
mutate(chapter_start = str_detect(text, "^CHAPTER"),
chapter_number = cumsum(chapter_start)) %>%
# Get rid of these columns
select(-chapter_start) %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)
jane_eyre = bronte_raw %>%
filter(title == "Jane Eyre: An Autobiography") %>%
# Get rid of rows where text is missing
drop_na(text) %>%
# Chapters start with CHAPTER X, so mark if each row is a chapter start
# cumsum() calculates the cumulative sum, so it'll increase every time there's
# a new chapter and automatically make chapter numbers
mutate(chapter_start = str_detect(text, "^CHAPTER"),
chapter_number = cumsum(chapter_start)) %>%
# Get rid of these columns
select(-chapter_start) %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)
agnes_grey = bronte_raw %>%
filter(title == "Agnes Grey") %>%
# Get rid of rows where text is missing
drop_na(text) %>%
# Chapters start with CHAPTER X, so mark if each row is a chapter start
# cumsum() calculates the cumulative sum, so it'll increase every time there's
# a new chapter and automatically make chapter numbers
mutate(chapter_start = str_detect(text, "^CHAPTER"),
chapter_number = cumsum(chapter_start)) %>%
# Get rid of these columns
select(-chapter_start) %>%
unnest_tokens(word, text) %>%
anti_join(stop_words)
bronte_with_chapters = bind_rows(wuthering_heights, jane_eyre, agnes_grey)
Next, we follow the same steps with the sentiment dictionary as before, but now counting positive and negative sentiments by book and chapter.
bronte_sentiment_by_chapter <- bronte_with_chapters %>%
inner_join(get_sentiments("bing")) %>%
group_by(title, chapter_number) %>%
summarize(
n_pos = sum(sentiment == "positive"),
n_neg = sum(sentiment == "negative")
) %>%
mutate(
sent_diff = n_pos - n_neg
)
ggplot(bronte_sentiment_by_chapter, aes(x = chapter_number, y = sent_diff, fill = sent_diff > 0)) +
geom_col() +
facet_wrap(vars(title)) +
lab12_theme +
scale_fill_viridis_d(option = "C", end = .75) +
guides(fill = FALSE)

Now this is more interesting! We can see that Wuthering Heights is a real bummer throughout, but both Agnes Grey and Jane Eyre end on a positive note.
Bigram Analysis
The last example I’ll walk through is a bigram analysis. An n-gram is a set of n words, and so a bigram is a set of 2 words. Here’s how to use the unnest_tokens function to count bigrams instead of words:
bronte_bigrams <- bronte_raw %>%
drop_na(text) %>%
# n = 2 here means bigrams. We could also make trigrams (n = 3) or any type of n-gram
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
# Split the bigrams into two words so we can remove stopwords
separate(bigram, c("word1", "word2"), sep = " ") %>%
filter(!word1 %in% stop_words$word,
!word2 %in% stop_words$word) %>%
# Put the two word columns back together
unite(bigram, word1, word2, sep = " ")
bronte_bigrams
## # A tibble: 29,103 x 3
## gutenberg_id title bigram
## <dbl> <chr> <chr>
## 1 767 Agnes Grey agnes grey
## 2 767 Agnes Grey acton bell
## 3 767 Agnes Grey NA NA
## 4 767 Agnes Grey thomas cautley
## 5 767 Agnes Grey cautley newby
## 6 767 Agnes Grey newby publisher
## 7 767 Agnes Grey 72 mortimer
## 8 767 Agnes Grey mortimer st
## 9 767 Agnes Grey st cavendish
## 10 767 Agnes Grey cavendish sq
## # … with 29,093 more rows
Next, we follow the same counting steps as before, but adding the extra step of taking out “NA NA” since that appears in our data.
top_bigrams <- bronte_bigrams %>%
filter(bigram != "NA NA") %>%
# Count all the bigrams in each play
count(title, bigram, sort = TRUE) %>%
# Keep top 15 in each play
group_by(title) %>%
top_n(15) %>%
ungroup() %>%
# Make the bigrams an ordered factor so they plot in order
mutate(bigram = fct_inorder(bigram))
ggplot(top_bigrams, aes(y = fct_rev(bigram), x = n, fill = title)) +
geom_col() +
guides(fill = FALSE) +
labs(y = "Count", x = NULL,
title = "15 most frequent bigrams in Bronte novels") +
facet_wrap(vars(title), scales = "free") +
lab12_theme +
scale_fill_viridis_d(option = "C", end = .75) +
guides(fill = FALSE)

Now we can see where all of the “miss”es came from! (I will also point out couldn't bear and i'm tired are top bigrams in Wuthering Heights, which is more evidence to the overall bummer-ness of the book)
Next, let’s try a version of She giggles, he gallops, by restricting the dataset to only include bigrams that start with “he” or “she”:
pronoun_bigrams <- bronte_raw %>%
drop_na(text) %>%
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
# Split the bigrams into two words so we can remove stopwords
separate(bigram, c("word1", "word2"), sep = " ") %>%
# only filtering word 2, since we want "he" and "she" to be able to show up in first word
filter(!word2 %in% stop_words$word) %>%
# Put the two word columns back together
unite(bigram, word1, word2, sep = " ") %>%
filter(bigram != "NA NA") %>%
# Find bigrams that start with he or she and end with a space
filter(str_detect(bigram, "^he ") | str_detect(bigram, "^she ")) %>%
# Count all the bigrams in each play
count(title, bigram, sort = TRUE) %>%
# Keep top 15 in each play
group_by(title) %>%
top_n(15) %>%
ungroup() %>%
# Make the bigrams an ordered factor so they plot in order
mutate(bigram = fct_inorder(bigram))
pronoun_verbs = pronoun_bigrams %>%
separate(bigram, into = c("pronoun", "verb")) %>%
mutate(
n_pos_neg = ifelse(pronoun == "he", n, -n)
) %>%
group_by(title, verb) %>%
summarize(n_male = sum(n_pos_neg))
ggplot(pronoun_verbs, aes(y = fct_rev(verb), x = n_male, fill = n_male > 0)) +
geom_col() +
guides(fill = FALSE) +
labs(y = "Count", x = NULL,
title = "Most Common Verbs used with he vs she pronoun",
subtitle = "Positive = More associated with he") +
facet_wrap(vars(title), scales = "free") +
lab12_theme +
scale_fill_viridis_d(option = "C", end = .75) +
guides(fill = FALSE)

This isn’t super interesting in this case, since most characters just talk to one another in these books, but maybe you will pick books with more interesting verbs!
LS0tCnRpdGxlOiAiTGFiMTI6IFRleHQiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDogCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY3NzOiBzdGF0NDEtbGFiLXRoZW1lLmNzcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwbG90bHkpCmBgYAoKV2VsY29tZSB0byB5b3VyIExBU1QgTEFCIE9GIFRIRSBKLVRFUk0hIAoKVGhlICJleGFtcGxlIiBwYXJ0IG9mIHRoZSBsYWIgaXMgYSBsaXR0bGUgbG9uZ2VyIHRoYW4gdXN1YWwuIFRoZSBnb29kIG5ld3MgaXMgdGhhdCBpdCdzIHByZXR0eSBzaW1pbGFyIHRvIHRoZSBhbmFseXNpcyBpbiBNRFNSLCBhbmQgc2hvdWxkIHByb3ZpZGUgYSBsb3Qgb2YgdGVtcGxhdGUgY29kZSBmb3IgdGhlICJvbiB5b3VyIG93biIgcG9ydGlvbi4gCgpXZSdyZSBiYXJlbHkgc2NyYXRjaGluZyB0aGUgc3VyZmFjZSBvZiB0ZXh0IG1pbmluZy4gSWYgeW91IHdhbnQgdG8gbGVhcm4gbW9yZSwgSSByZWNvbW1lbmQgW1RpZHkgVGV4dCBNaW5pbmcgd2l0aCBSXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vKS4gCgojIFBhcnQgMTogRXhhbXBsZQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KGd1dGVuYmVyZ3IpCgpsYWIxMl90aGVtZSA9IHRoZW1lX21pbmltYWwoYmFzZV9mYW1pbHkgPSAiT3B0aW1hIiwgYmFzZV9zaXplID0gMTIpCmBgYAoKVG9kYXksIEknbSBnb2luZyB0byB1c2Ugbm92ZWxzIGJ5IHRoZSBCcm9udGUgc2lzdGVycyB0byB3YWxrIHRocm91Z2ggYSBmZXcgZXhhbXBsZXMgb2YgYmFzaWMgdGV4dCBhbmFseXNpcy4KCiMjIExvYWQgRGF0YQoKV2XigJlyZSBnb2luZyB0byB1c2UgdGhlIGBndXRlbmJlcmdyYCBwYWNrYWdlIHRvIGRvd25sb2FkIHNvbWUgYm9va3MgZGlyZWN0bHkgZnJvbSBbUHJvamVjdCBHdXRlbmJlcmddKGh0dHA6Ly93d3cuZ3V0ZW5iZXJnLm9yZy8pLiBJbiBvcmRlciB0byBkb3dubG9hZCB0aGUgcmlnaHQgYm9va3MsIHdlIG5lZWQgdG8gZmluZCB0aGVpciBJRHMsIHdoaWNoIGNvbWUgZnJvbSB0aGUgVVJMcyBhdCB0aGVpciB3ZWJzaXRlLiBGb3IgaW5zdGFuY2UsIEphbmUgRXlyZSBpcyBib29rIDEyNjAuIFdl4oCZbGwgc3RvcmUgdGhlc2UgYm9va3MgYXMgYCpfcmF3KiBhbmQgdGhlbiBjbGVhbiB0aGVtIHVwIGxhdGVyLgoKYGBge3IgZG93bmxvYWQsIGV2YWwgPSBGQUxTRX0KIyA3NjcgLSBBZ25lcyBHcmV5IGJ5IEFubmUgQnJvbnRlCiMgMTI2MCAtIEphbmUgRXlyZTogQW4gQXV0b2Jpb2dyYXBoeSBieSBDaGFybG90dGUgQnJvbnTDqwojIDc2OCAtIFd1dGhlcmluZyBIZWlnaHRzIGJ5IEVtaWx5IEJyb250w6sKCmJyb250ZV9yYXcgPC0gZ3V0ZW5iZXJnX2Rvd25sb2FkKGMoNzY3LCAxMjYwLCA3NjgpLCBtaXJyb3IgPSAiaHR0cDovL21pcnJvcnMueG1pc3Npb24uY29tL2d1dGVuYmVyZy8iLCBtZXRhX2ZpZWxkcyA9ICJ0aXRsZSIpCmBgYAoKKFlvdSBtYXkgbm90IG5lZWQgdGhlIGBtaXJyb3JgIGFyZ3VtZW50LCBidXQgdGhlIGRlZmF1bHQgbWlycm9yIHdhcyBkb3duIHllc3RlcmRheSB3aGVuIEkgd2FzIHdyaXRpbmcgdGhpcyBsYWIuKQoKYGBge3Igd3JpdGUtY3N2LCBldmFsID0gRkFMU0UsIGluY2x1ZGUgPSBGQUxTRX0Kd3JpdGVfY3N2KGJyb250ZV9yYXcsICJkYXRhL2Jyb250ZV9yYXcuY3N2IikKYGBgCgpgYGB7ciBsb2FkLWRhdGEsIGluY2x1ZGUgPSBGQUxTRX0KYnJvbnRlX3JhdyA9IHJlYWRfY3N2KCJkYXRhL2Jyb250ZV9yYXcuY3N2IikKYGBgCgpJZiB5b3UgZG9uJ3Qgd2FudCB0byByZS1kb3dubG9hZCB0aGUgYm9va3MgZXZlcnkgdGltZSB5b3Uga25pdCAoeW91IGRvbid0KSwgd2UgY2FuIHVzZSBhbiBSTWFya2Rvd24gdHJpY2suIFB1dCB0aGUgYWN0dWFsIGNvZGUgZm9yIGdldHRpbmcgdGhlIGJvb2tzIGluIGEgY2h1bmsgd2l0aCBgZXZhbD1GQUxTRWAgb24gaXQgYW5kIHJ1biBpdCBtYW51YWxseSBpbiBSU3R1ZGlvIHdoZW4geW91IHdhbnQgdG8gZ2V0IHRoZSBkYXRhLiBUaGVuIHlvdSBjYW4gd3JpdGUgdGhlIGRvd25sb2FkZWQgZGF0YSBhcyBhIENTViBmaWxlLCBhbmQgdGhlbiBsb2FkIGl0IGludmlzaWJseSBmcm9tIHRoZSBDU1YgZmlsZSB3aGVuIHlvdSBrbml0OiAKCmBgYGB0ZXh0CkkgZmlyc3QgZG93bmxvYWQgZGF0YSBmcm9tIFByb2plY3QgR3V0ZW5iZXJnOgpgYGB7ciBnZXQtYm9vaywgZXZhbD1GQUxTRX1gciAnJ2AKYm9va3NfcmF3IDwtIGd1dGVuYmVyZ19kb3dubG9hZCguLi4pCndyaXRlX2Nzdihib29rc19yYXcsICJkYXRhL2Jvb2tzX3Jhdy5jc3YiKQpgYGAKYGBge3IgbG9hZC1ib29rLWRhdGEtcmVhbCwgaW5jbHVkZT1GQUxTRX1gciAnJ2AKYm9va3NfcmF3IDwtIHJlYWRfY3N2KCJkYXRhL2Jvb2tzX3Jhdy5jc3YiKQpgYGAKYGBgYAoKIyMgQ2xlYW4gRGF0YQoKVGhlIGBndXRlbmJlcmdyYCBwYWNrYWdlIGRvd25sb2FkcyBkYXRhIGluIGFuIGFscmVhZHkgdGlkeSBmb3JtLCB3aXRoIGEgY29sdW1uIGZvciB0aGUgYm9vayBJRCwgYSBjb2x1bW4gZm9yIHRoZSBib29rIHRpdGxlICh3aGljaCB3ZSB0b2xkIGl0IHRvIGdldCB0aHJvdWdoIGBtZXRhX2ZpZWxkcyA9ICJ0aXRsZSJgKSwgYW5kIGEgY29sdW1uIGZvciB0aGUgdGV4dC4gQlVULCBlYWNoIGJvb2sgdXN1YWxseSBzdGFydHMgd2l0aCBhIGxpdHRsZSBtZXNzaW5lc3MuIEhlcmUsIHdlIGNhbiBzZWUgdGhhdCBBZ25lcyBHcmV5IHN0YXJ0cyB3aXRoIHRoZSB0aXRsZSBwYWdlIGFuZCBwdWJsaXNoZXIgaW5mb3JtYXRpb246IAoKYGBge3J9CmhlYWQoYnJvbnRlX3JhdykKYGBgCgpGb3IgdGhpcyBsYWIsIHdlJ3JlIGp1c3QgZ29pbmcgdG8gaWdub3JlIHRoaXMsIGJ1dCBpbiB0aGUgInJlYWwgd29ybGQiLCB3ZSdkIHdhbnQgdG8gZmlndXJlIG91dCBhIHN5c3RlbWF0aWMgd2F5IHRvIHJlbW92ZSBhbnkgdGV4dCB0aGF0IGlzIG5vdCBhIHBhcnQgb2YgdGhlIGJvb2suIChGb3IgZXhhbXBsZSwgd2UgY291bGQgY3JlYXRlIGEgbmV3IGNvbHVtbiBpbmRpY2F0aW5nIHdoZXRoZXIgZWFjaCByb3cgaW4gb3VyIGRhdGFzZXQgaXMgcGFydCBvZiB0aGUgYm9vayBvciBub3QsIGFuZCB0aGVuIGBmaWx0ZXIoKWAgYmFzZWQgb24gdGhhdCBjb2x1bW4pCgojIyBUb2tlbml6ZSBhbmQgY291bnQKClRoZSBmaXJzdCBzdGVwIGlzIHRvICp0b2tlbml6ZSogdGhpcyBkYXRhOiB0aGUgYHVubmVzdF90b2tlbnNgIGZ1bmN0aW9uIGRvZXMgdGhpcyBmb3IgdXMgYXV0b21hdGljYWxseS4gKFRoZSBmaXJzdCBhcmd1bWVudCBpcyB3aGF0IHlvdSB3YW50IHRoZSBuZXcgdmFyaWFibGUgdG8gYmUgbmFtZWQsIHRoZSBzZWNvbmQgYXJndW1lbnQgaXMgdGhlIHZhcmlhYmxlIHdlIHdhbnQgdG8gdG9rZW5pemUpCgpgYGB7cn0KYnJvbnRlX3dvcmRzIDwtIGJyb250ZV9yYXcgJT4lIAogIGRyb3BfbmEodGV4dCkgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkKCmhlYWQoYnJvbnRlX3dvcmRzKQpgYGAKCk5leHQsIHdlIHJlbW92ZSBzdG9wIHdvcmRzLCBjb3VudCBhbGwgb2YgdGhlIHdvcmRzIGluIGVhY2ggYm9vayBhbmQga2VlcCB0aGUgdG9wIDE1LCBhbmQgdGhlbiBzb3J0IHRoZW0gKHRvIG1ha2UgdGhlIG91dHB1dCBtb3JlIHJlYWRhYmxlKS4gSSBhbHNvIGluY2x1ZGUgYSBzdGVwIHRvIG1ha2UgdGhlIHZhcmlhYmxlIGFuIG9yZGVyZWQgZmFjdG9yLCB3aGljaCBpcyBoZWxwZnVsIGZvciBwbG90dGluZy4gCgpgYGB7cn0KdG9wX3dvcmRzX2Jyb250ZSA8LSBicm9udGVfd29yZHMgJT4lIAogICMgUmVtb3ZlIHN0b3Agd29yZHMKICBhbnRpX2pvaW4oc3RvcF93b3JkcykgJT4lIAogICMgQ291bnQgYWxsIHRoZSB3b3JkcyBpbiBlYWNoIGJvb2sKICBjb3VudCh0aXRsZSwgd29yZCwgc29ydCA9IFRSVUUpICU+JSAKICAjIEtlZXAgdG9wIDE1IGluIGVhY2ggYm9vawogIGdyb3VwX2J5KHRpdGxlKSAlPiUgCiAgdG9wX24oMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogICMgTWFrZSB0aGUgd29yZHMgYW4gb3JkZXJlZCBmYWN0b3Igc28gdGhleSBwbG90IGluIG9yZGVyCiAgbXV0YXRlKHdvcmQgPSBmY3RfaW5vcmRlcih3b3JkKSkKdG9wX3dvcmRzX2Jyb250ZQpgYGAKCkZpbmFsbHksIHdlIHBsb3QgdGhpcyBwcm9jZXNzZWQgZGF0YTogCgpgYGB7cn0KZ2dwbG90KHRvcF93b3Jkc19icm9udGUsIGFlcyh5ID0gZmN0X3Jldih3b3JkKSwgeCA9IG4sIGZpbGwgPSB0aXRsZSkpICsgCiAgZ2VvbV9jb2woKSArIAogIGd1aWRlcyhmaWxsID0gRkFMU0UpICsKICBsYWJzKHkgPSAiQ291bnQiLCB4ID0gTlVMTCwgCiAgICAgICB0aXRsZSA9ICIxNSBtb3N0IGZyZXF1ZW50IHdvcmRzIGluIEJyb250ZSBOb3ZlbHMiKSArCiAgZmFjZXRfd3JhcCh2YXJzKHRpdGxlKSwgc2NhbGVzID0gImZyZWVfeSIpICsKICBsYWIxMl90aGVtZSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAuNzUpCmBgYAoKKFNoYXJwLWV5ZWQgcmVhZGVycyB3aWxsIG5vdGljZSB0aGF0IHRoZSB3b3JkcyBhcmVu4oCZdCBhY3R1YWxseSBpbiBwZXJmZWN0IG9yZGVyISBUaGF04oCZcyBiZWNhdXNlIHNvbWUgY29tbW9uIHdvcmRzIGFyZSByZXBlYXRlZCBhY3Jvc3MgdGhlIHBsYXlzLCBsaWtlIOKAnG1pc3PigJ0gYW5kIOKAnHRpbWXigJ0uIEhvd2V2ZXIsIGVhY2ggY2F0ZWdvcnkgaW4gYSBmYWN0b3IgY2FuIG9ubHkgaGF2ZSBvbmUgcG9zc2libGUgcG9zaXRpb24gaW4gdGhlIG9yZGVyLiBZb3UgY2FuIGZpeCB0aGlzIHdpdGggdGhlIHJlb3JkZXJfd2l0aGluKCkgZnVuY3Rpb24gaW4gdGlkeXRleHTigJRzZWUgSnVsaWEgU2lsZ2XigJlzIHR1dG9yaWFsIFtoZXJlXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvcmVvcmRlci13aXRoaW4vKSBmb3IgaG93IHRvIHVzZSBpdC4pCgojIyB0Zi1pZGYKCk5vdyBsZXQncyB0cnkgYSAqZmFuY3kqIGNvdW50aW5nIG1ldGhvZCAodGYtaWRmKTogCgpgYGB7cn0KYnJvbnRlX3dvcmRzX2ZpbHRlcmVkID0gYnJvbnRlX3dvcmRzICU+JSAKICAjIFJlbW92ZSBzdG9wIHdvcmRzCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAKICAjIENvdW50IGFsbCB0aGUgd29yZHMgaW4gZWFjaCBib29rCiAgY291bnQodGl0bGUsIHdvcmQsIHNvcnQgPSBUUlVFKSAKCiMgQWRkIHRoZSB0Zi1pZGYgdmFsdWVzIHRvIHRoZSBjb3VudHMKYnJvbnRlX3RmX2lkZiA8LSBicm9udGVfd29yZHNfZmlsdGVyZWQgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIHRpdGxlLCBuKQoKIyBHZXQgdGhlIHRvcCAxMCB1bmlxdWVzdCB3b3Jkcwpicm9udGVfdGZfaWRmX3Bsb3QgPC0gYnJvbnRlX3RmX2lkZiAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICBncm91cF9ieSh0aXRsZSkgJT4lIAogIHRvcF9uKDEwKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUod29yZCA9IGZjdF9pbm9yZGVyKHdvcmQpKQoKZ2dwbG90KGJyb250ZV90Zl9pZGZfcGxvdCwgCiAgICAgICBhZXMoeSA9IGZjdF9yZXYod29yZCksIHggPSB0Zl9pZGYsIGZpbGwgPSB0aXRsZSkpICsKICBnZW9tX2NvbCgpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgbGFicyh4ID0gInRmLWlkZiIsIHkgPSBOVUxMKSArCiAgZmFjZXRfd3JhcCh+IHRpdGxlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBsYWIxMl90aGVtZSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAuNzUpCmBgYAoKVGhlc2UgdGYtaWRmcyBhcmUgdmVyeSBzbWFsbCBjb21wYXJlZCB0byB0aGUgZXhhbXBsZSBmcm9tIHRoZSBNRFNSIGJvb2sgb24gYXJYaXYgcGFwZXJzIC0tIHRoaXMgaXMgYmVjYXVzZSB0aGVzZSB0aHJlZSBib29rcyBhcmUgYWxsICptdWNoIG1vcmUqIHNpbWlsYXIgdG8gb25lIGFub3RoZXIgdGhhbiB0aGUgYXJYaXYgYWJzdHJhY3RzLiBTaW5jZSB0aGUgQnJvbnRlIHNpc3RlcnMgYXJlIGZyb20gdGhlIHNhbWUgZXJhIGFuZCB3cm90ZSBpbiBhIHNpbWlsYXIgc3R5bGUsIGFsbCB0Zi1pZGYgZGlkIHdhcyBlc3NlbnRpYWxseSByZW1vdmUgbm9uLW5hbWVzLiBPaCB3ZWxsISAKCiMjIFNlbnRpbWVudCBBbmFseXNpcwoKKkEgbm90ZSBvZiBjYXV0aW9uKjogbGlrZSB3ZSBzYXcgaW4gY2xhc3MsIHNlbnRpbWVudCBhbmFseXNlcyBjYW4gYmUgcmlmZSB3aXRoIGJpYXMuIFRoZXkncmUgZnVuIHRvIG1ha2UsIGFuZCB0aGUgYHRpZHl0ZXh0YCBwYWNrYWdlIG1ha2VzIGl0IGV4dHJlbWVseSBlYXN5IHRvIGRvLCBidXQgdGFrZSByZXN1bHRzIHdpdGggYSBncmFpbiBvZiBzYWx0LiAKClRoZSBmaXJzdCBzdGVwIGlzIHRvIGpvaW4gdGhlIHNlbnRpbWVudCBkaWN0aW9uYXJ5IHRvIG91ciBkYXRhc2V0IHdpdGggYGlubmVyX2pvaW5gOiB0aGlzIG9ubHkga2VlcHMgd29yZHMgdGhhdCBzaG93IHVwIGluIGJvdGggdGhlIGRhdGFzZXQgYW5kIHRoZSBzZW50aW1lbnQgZGljdGlvbmFyeS4KCmBgYHtyfQpicm9udGVfc2VudGltZW50IDwtIGJyb250ZV93b3Jkc19maWx0ZXJlZCAlPiUgCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKQpoZWFkKGJyb250ZV9zZW50aW1lbnQpCmBgYAoKTmV4dCwgd2UgY291bnQgdXAgZWFjaCBzZW50aW1lbnQgYnkgdGl0bGUsIGFuZCB0aGVuIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSB0aGF0IGlzIHBvc2l0aXZlIGlzIHNlbnRpbWVudCBpcyBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgb3RoZXJ3aXNlICh0cnkgcnVubmluZyB0aGUgaW50ZXJtZWRpYXRlIGRhdGEgb24geW91ciBvd24gaWYgdGhpcyBkb2Vzbid0IG1ha2Ugc2Vuc2UpLiBUaGlzIGdpdmVzIGFzIGFuICJvdmVyYWxsIHBvc2l0aXZlIiBhbmQgIm92ZXJhbGwgbmVnYXRpdmUiIG51bWJlciBmb3IgZWFjaCBib29rLCB3aGljaCB3ZSBjYW4gcGxvdDoKCmBgYHtyfQpicm9udGVfc2VudGltZW50X2J5X3RpdGxlIDwtIGJyb250ZV9zZW50aW1lbnQgJT4lCiAgY291bnQodGl0bGUsIHNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKAogICAgbl9wb3NfbmVnID0gaWZlbHNlKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiLCBuLCAtbikKICApCgpnZ3Bsb3QoYnJvbnRlX3NlbnRpbWVudF9ieV90aXRsZSwgYWVzKHggPSBzZW50aW1lbnQsIHkgPSBuX3Bvc19uZWcsIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9jb2wocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgpKSArCiAgZmFjZXRfd3JhcCh2YXJzKHRpdGxlKSkgKwogIGxhYjEyX3RoZW1lICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiQyIsIGVuZCA9IC43NSkKYGBgCgpXZSBjb3VsZCBzdGFuZGFyZGl6ZSB0aGVzZSBieSBib29rIGxlbmd0aCB0byBiZXR0ZXIgY29tcGFyZSBhY3Jvc3MgYm9va3MsIGJ1dCB3ZSdsbCBkbyBzb21ldGhpbmcgbW9yZSBpbnRlcmVzdGluZywgd2hpY2ggaXMgdG8gbG9vayBhdCB0b3RhbCBzZW50aW1lbnQgYnkgKmNoYXB0ZXIqLiAKCkZpcnN0LCB3ZSBoYXZlIHRvIGdldCBjcmVhdGl2ZSBpbiBkZWZpbmluZyB0aGUgIkNoYXB0ZXIiIHZhcmlhYmxlIC0gSSd2ZSBpbmNsdWRlZCBhbGwgY29kZSB0byBkbyBzbyB3aXRoIGNvbW1lbnRzIGJlbG93LiBUaGUgZ2VuZXJhbCBpZGVhIGlzIHRoYXQgd2UgY2hlY2sgaWYgZWFjaCByb3cgaXMgZXF1YWwgdG8gIkNIQVBURVIgWCIsIHJlY29yZCBhIDEgaWYgeWVzIGFuZCAwIGlmIG5vLCBhbmQgdGhlbiB0YWtlIHRoZSBjdW11bGF0aXZlIHN1bSBvZiB0aGF0IG5ldyB2YXJpYWJsZSB0byBnZXQgYSBjaGFwdGVyIG51bWJlciBmb3IgZWFjaCBsaW5lLiBJIGFsc28gZGlkIGVhY2ggYm9vayBzZXBhcmF0ZWx5LCByYXRoZXIgdGhhbiBmaWd1cmUgb3V0IGEgbmljZXIgd2F5IHRvIHN0YXJ0IG92ZXIgYXQgMC4gKE5vdGUgdGhhdCB0aGlzIHdpbGwgbGlrZWx5IGJlIHNsaWdodGx5IGRpZmZlcmVudCBmb3IgZGlmZmVyZW50IGJvb2tzISkKCmBgYHtyfQp3dXRoZXJpbmdfaGVpZ2h0cyA8LSBicm9udGVfcmF3ICU+JSAKICBmaWx0ZXIodGl0bGUgPT0gIld1dGhlcmluZyBIZWlnaHRzIikgJT4lCiAgIyBHZXQgcmlkIG9mIHJvd3Mgd2hlcmUgdGV4dCBpcyBtaXNzaW5nCiAgZHJvcF9uYSh0ZXh0KSAlPiUgCiAgIyBDaGFwdGVycyBzdGFydCB3aXRoIENIQVBURVIgWCwgc28gbWFyayBpZiBlYWNoIHJvdyBpcyBhIGNoYXB0ZXIgc3RhcnQKICAjIGN1bXN1bSgpIGNhbGN1bGF0ZXMgdGhlIGN1bXVsYXRpdmUgc3VtLCBzbyBpdCdsbCBpbmNyZWFzZSBldmVyeSB0aW1lIHRoZXJlJ3MKICAjIGEgbmV3IGNoYXB0ZXIgYW5kIGF1dG9tYXRpY2FsbHkgbWFrZSBjaGFwdGVyIG51bWJlcnMKICBtdXRhdGUoY2hhcHRlcl9zdGFydCA9IHN0cl9kZXRlY3QodGV4dCwgIl5DSEFQVEVSIiksCiAgICAgICAgIGNoYXB0ZXJfbnVtYmVyID0gY3Vtc3VtKGNoYXB0ZXJfc3RhcnQpKSAlPiUgCiAgIyBHZXQgcmlkIG9mIHRoZXNlIGNvbHVtbnMKICBzZWxlY3QoLWNoYXB0ZXJfc3RhcnQpICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCgpqYW5lX2V5cmUgPSBicm9udGVfcmF3ICU+JSAKICBmaWx0ZXIodGl0bGUgPT0gIkphbmUgRXlyZTogQW4gQXV0b2Jpb2dyYXBoeSIpICU+JQogICMgR2V0IHJpZCBvZiByb3dzIHdoZXJlIHRleHQgaXMgbWlzc2luZwogIGRyb3BfbmEodGV4dCkgJT4lIAogICMgQ2hhcHRlcnMgc3RhcnQgd2l0aCBDSEFQVEVSIFgsIHNvIG1hcmsgaWYgZWFjaCByb3cgaXMgYSBjaGFwdGVyIHN0YXJ0CiAgIyBjdW1zdW0oKSBjYWxjdWxhdGVzIHRoZSBjdW11bGF0aXZlIHN1bSwgc28gaXQnbGwgaW5jcmVhc2UgZXZlcnkgdGltZSB0aGVyZSdzCiAgIyBhIG5ldyBjaGFwdGVyIGFuZCBhdXRvbWF0aWNhbGx5IG1ha2UgY2hhcHRlciBudW1iZXJzCiAgbXV0YXRlKGNoYXB0ZXJfc3RhcnQgPSBzdHJfZGV0ZWN0KHRleHQsICJeQ0hBUFRFUiIpLAogICAgICAgICBjaGFwdGVyX251bWJlciA9IGN1bXN1bShjaGFwdGVyX3N0YXJ0KSkgJT4lIAogICMgR2V0IHJpZCBvZiB0aGVzZSBjb2x1bW5zCiAgc2VsZWN0KC1jaGFwdGVyX3N0YXJ0KSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKYWduZXNfZ3JleSA9IGJyb250ZV9yYXcgJT4lIAogIGZpbHRlcih0aXRsZSA9PSAiQWduZXMgR3JleSIpICU+JQogICMgR2V0IHJpZCBvZiByb3dzIHdoZXJlIHRleHQgaXMgbWlzc2luZwogIGRyb3BfbmEodGV4dCkgJT4lIAogICMgQ2hhcHRlcnMgc3RhcnQgd2l0aCBDSEFQVEVSIFgsIHNvIG1hcmsgaWYgZWFjaCByb3cgaXMgYSBjaGFwdGVyIHN0YXJ0CiAgIyBjdW1zdW0oKSBjYWxjdWxhdGVzIHRoZSBjdW11bGF0aXZlIHN1bSwgc28gaXQnbGwgaW5jcmVhc2UgZXZlcnkgdGltZSB0aGVyZSdzCiAgIyBhIG5ldyBjaGFwdGVyIGFuZCBhdXRvbWF0aWNhbGx5IG1ha2UgY2hhcHRlciBudW1iZXJzCiAgbXV0YXRlKGNoYXB0ZXJfc3RhcnQgPSBzdHJfZGV0ZWN0KHRleHQsICJeQ0hBUFRFUiIpLAogICAgICAgICBjaGFwdGVyX251bWJlciA9IGN1bXN1bShjaGFwdGVyX3N0YXJ0KSkgJT4lIAogICMgR2V0IHJpZCBvZiB0aGVzZSBjb2x1bW5zCiAgc2VsZWN0KC1jaGFwdGVyX3N0YXJ0KSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKYnJvbnRlX3dpdGhfY2hhcHRlcnMgPSBiaW5kX3Jvd3Mod3V0aGVyaW5nX2hlaWdodHMsIGphbmVfZXlyZSwgYWduZXNfZ3JleSkKYGBgCgpOZXh0LCB3ZSBmb2xsb3cgdGhlIHNhbWUgc3RlcHMgd2l0aCB0aGUgc2VudGltZW50IGRpY3Rpb25hcnkgYXMgYmVmb3JlLCBidXQgbm93IGNvdW50aW5nIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzZW50aW1lbnRzIGJ5ICpib29rIGFuZCBjaGFwdGVyKi4gCmBgYHtyfQpicm9udGVfc2VudGltZW50X2J5X2NoYXB0ZXIgPC0gYnJvbnRlX3dpdGhfY2hhcHRlcnMgJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICBncm91cF9ieSh0aXRsZSwgY2hhcHRlcl9udW1iZXIpICU+JQogIHN1bW1hcml6ZSgKICAgIG5fcG9zID0gc3VtKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKSwKICAgIG5fbmVnID0gc3VtKHNlbnRpbWVudCA9PSAibmVnYXRpdmUiKQogICkgJT4lCiAgbXV0YXRlKAogICAgc2VudF9kaWZmID0gbl9wb3MgLSBuX25lZwogICkKCmdncGxvdChicm9udGVfc2VudGltZW50X2J5X2NoYXB0ZXIsIGFlcyh4ID0gY2hhcHRlcl9udW1iZXIsIHkgPSBzZW50X2RpZmYsIGZpbGwgPSBzZW50X2RpZmYgPiAwKSkgKwogIGdlb21fY29sKCkgKyAKICBmYWNldF93cmFwKHZhcnModGl0bGUpKSArCiAgbGFiMTJfdGhlbWUgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJDIiwgZW5kID0gLjc1KSArCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSkKYGBgCgpOb3cgdGhpcyBpcyBtb3JlIGludGVyZXN0aW5nISBXZSBjYW4gc2VlIHRoYXQgKld1dGhlcmluZyBIZWlnaHRzKiBpcyBhIHJlYWwgYnVtbWVyIHRocm91Z2hvdXQsIGJ1dCBib3RoICpBZ25lcyBHcmV5KiBhbmQgKkphbmUgRXlyZSogZW5kIG9uIGEgcG9zaXRpdmUgbm90ZS4gCgojIyBCaWdyYW0gQW5hbHlzaXMKClRoZSBsYXN0IGV4YW1wbGUgSSdsbCB3YWxrIHRocm91Z2ggaXMgYSAqKmJpZ3JhbSBhbmFseXNpcyoqLiBBbiAqbi1ncmFtKiBpcyBhIHNldCBvZiAqbiogd29yZHMsIGFuZCBzbyBhIGJpZ3JhbSBpcyBhIHNldCBvZiAyIHdvcmRzLiBIZXJlJ3MgaG93IHRvIHVzZSB0aGUgYHVubmVzdF90b2tlbnNgIGZ1bmN0aW9uIHRvIGNvdW50IGJpZ3JhbXMgaW5zdGVhZCBvZiB3b3JkczogCgpgYGB7cn0KYnJvbnRlX2JpZ3JhbXMgPC0gYnJvbnRlX3JhdyAlPiUgCiAgZHJvcF9uYSh0ZXh0KSAlPiUgCiAgIyBuID0gMiBoZXJlIG1lYW5zIGJpZ3JhbXMuIFdlIGNvdWxkIGFsc28gbWFrZSB0cmlncmFtcyAobiA9IDMpIG9yIGFueSB0eXBlIG9mIG4tZ3JhbQogIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lIAogICMgU3BsaXQgdGhlIGJpZ3JhbXMgaW50byB0d28gd29yZHMgc28gd2UgY2FuIHJlbW92ZSBzdG9wd29yZHMKICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIikgJT4lIAogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQsCiAgICAgICAgICF3b3JkMiAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lIAogICMgUHV0IHRoZSB0d28gd29yZCBjb2x1bW5zIGJhY2sgdG9nZXRoZXIKICB1bml0ZShiaWdyYW0sIHdvcmQxLCB3b3JkMiwgc2VwID0gIiAiKQpicm9udGVfYmlncmFtcwpgYGAKCk5leHQsIHdlIGZvbGxvdyB0aGUgc2FtZSBjb3VudGluZyBzdGVwcyBhcyBiZWZvcmUsIGJ1dCBhZGRpbmcgdGhlIGV4dHJhIHN0ZXAgb2YgdGFraW5nIG91dCAiTkEgTkEiIHNpbmNlIHRoYXQgYXBwZWFycyBpbiBvdXIgZGF0YS4gCgpgYGB7cn0KdG9wX2JpZ3JhbXMgPC0gYnJvbnRlX2JpZ3JhbXMgJT4lIAogIGZpbHRlcihiaWdyYW0gIT0gIk5BIE5BIikgJT4lCiAgIyBDb3VudCBhbGwgdGhlIGJpZ3JhbXMgaW4gZWFjaCBwbGF5CiAgY291bnQodGl0bGUsIGJpZ3JhbSwgc29ydCA9IFRSVUUpICU+JSAKICAjIEtlZXAgdG9wIDE1IGluIGVhY2ggcGxheQogIGdyb3VwX2J5KHRpdGxlKSAlPiUgCiAgdG9wX24oMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogICMgTWFrZSB0aGUgYmlncmFtcyBhbiBvcmRlcmVkIGZhY3RvciBzbyB0aGV5IHBsb3QgaW4gb3JkZXIKICBtdXRhdGUoYmlncmFtID0gZmN0X2lub3JkZXIoYmlncmFtKSkKCmdncGxvdCh0b3BfYmlncmFtcywgYWVzKHkgPSBmY3RfcmV2KGJpZ3JhbSksIHggPSBuLCBmaWxsID0gdGl0bGUpKSArIAogIGdlb21fY29sKCkgKyAKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgbGFicyh5ID0gIkNvdW50IiwgeCA9IE5VTEwsIAogICAgICAgdGl0bGUgPSAiMTUgbW9zdCBmcmVxdWVudCBiaWdyYW1zIGluIEJyb250ZSBub3ZlbHMiKSArCiAgZmFjZXRfd3JhcCh2YXJzKHRpdGxlKSwgc2NhbGVzID0gImZyZWUiKSArIAogIGxhYjEyX3RoZW1lICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAuNzUpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKQpgYGAKCk5vdyB3ZSBjYW4gc2VlIHdoZXJlIGFsbCBvZiB0aGUgIm1pc3MiZXMgY2FtZSBmcm9tISAoSSB3aWxsIGFsc28gcG9pbnQgb3V0IGBjb3VsZG4ndCBiZWFyYCBhbmQgYGknbSB0aXJlZGAgYXJlIHRvcCBiaWdyYW1zIGluICpXdXRoZXJpbmcgSGVpZ2h0cyosIHdoaWNoIGlzIG1vcmUgZXZpZGVuY2UgdG8gdGhlIG92ZXJhbGwgYnVtbWVyLW5lc3Mgb2YgdGhlIGJvb2spCgpOZXh0LCBsZXQncyB0cnkgYSB2ZXJzaW9uIG9mIFtTaGUgZ2lnZ2xlcywgaGUgZ2FsbG9wc10oaHR0cHM6Ly9wdWRkaW5nLmNvb2wvMjAxNy8wOC9zY3JlZW4tZGlyZWN0aW9uLyksIGJ5IHJlc3RyaWN0aW5nIHRoZSBkYXRhc2V0IHRvIG9ubHkgaW5jbHVkZSBiaWdyYW1zIHRoYXQgc3RhcnQgd2l0aCAiaGUiIG9yICJzaGUiOiAKCmBgYHtyfQpwcm9ub3VuX2JpZ3JhbXMgPC0gYnJvbnRlX3JhdyAlPiUgCiAgZHJvcF9uYSh0ZXh0KSAlPiUgCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUgCiAgIyBTcGxpdCB0aGUgYmlncmFtcyBpbnRvIHR3byB3b3JkcyBzbyB3ZSBjYW4gcmVtb3ZlIHN0b3B3b3JkcwogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUgCiAgIyBvbmx5IGZpbHRlcmluZyB3b3JkIDIsIHNpbmNlIHdlIHdhbnQgImhlIiBhbmQgInNoZSIgdG8gYmUgYWJsZSB0byBzaG93IHVwIGluIGZpcnN0IHdvcmQKICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKSAlPiUgCiAgIyBQdXQgdGhlIHR3byB3b3JkIGNvbHVtbnMgYmFjayB0b2dldGhlcgogIHVuaXRlKGJpZ3JhbSwgd29yZDEsIHdvcmQyLCBzZXAgPSAiICIpICU+JQogIGZpbHRlcihiaWdyYW0gIT0gIk5BIE5BIikgJT4lCiAgIyBGaW5kIGJpZ3JhbXMgdGhhdCBzdGFydCB3aXRoIGhlIG9yIHNoZSBhbmQgZW5kIHdpdGggYSBzcGFjZQogIGZpbHRlcihzdHJfZGV0ZWN0KGJpZ3JhbSwgIl5oZSAiKSB8IHN0cl9kZXRlY3QoYmlncmFtLCAiXnNoZSAiKSkgJT4lCiAgIyBDb3VudCBhbGwgdGhlIGJpZ3JhbXMgaW4gZWFjaCBwbGF5CiAgY291bnQodGl0bGUsIGJpZ3JhbSwgc29ydCA9IFRSVUUpICU+JSAKICAjIEtlZXAgdG9wIDE1IGluIGVhY2ggcGxheQogIGdyb3VwX2J5KHRpdGxlKSAlPiUgCiAgdG9wX24oMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogICMgTWFrZSB0aGUgYmlncmFtcyBhbiBvcmRlcmVkIGZhY3RvciBzbyB0aGV5IHBsb3QgaW4gb3JkZXIKICBtdXRhdGUoYmlncmFtID0gZmN0X2lub3JkZXIoYmlncmFtKSkKCnByb25vdW5fdmVyYnMgPSBwcm9ub3VuX2JpZ3JhbXMgJT4lIAogIHNlcGFyYXRlKGJpZ3JhbSwgaW50byA9IGMoInByb25vdW4iLCAidmVyYiIpKSAlPiUKICBtdXRhdGUoCiAgICBuX3Bvc19uZWcgPSBpZmVsc2UocHJvbm91biA9PSAiaGUiLCBuLCAtbikKICApICU+JQogIGdyb3VwX2J5KHRpdGxlLCB2ZXJiKSAlPiUKICBzdW1tYXJpemUobl9tYWxlID0gc3VtKG5fcG9zX25lZykpCgpnZ3Bsb3QocHJvbm91bl92ZXJicywgYWVzKHkgPSBmY3RfcmV2KHZlcmIpLCB4ID0gbl9tYWxlLCBmaWxsID0gbl9tYWxlID4gMCkpICsgCiAgZ2VvbV9jb2woKSArIAogIGd1aWRlcyhmaWxsID0gRkFMU0UpICsKICBsYWJzKHkgPSAiQ291bnQiLCB4ID0gTlVMTCwgCiAgICAgICB0aXRsZSA9ICJNb3N0IENvbW1vbiBWZXJicyB1c2VkIHdpdGggaGUgdnMgc2hlIHByb25vdW4iLAogICAgICAgc3VidGl0bGUgPSAiUG9zaXRpdmUgPSBNb3JlIGFzc29jaWF0ZWQgd2l0aCBoZSIpICsKICBmYWNldF93cmFwKHZhcnModGl0bGUpLCBzY2FsZXMgPSAiZnJlZSIpICsgCiAgbGFiMTJfdGhlbWUgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiQyIsIGVuZCA9IC43NSkgKwogIGd1aWRlcyhmaWxsID0gRkFMU0UpCmBgYAoKVGhpcyBpc24ndCBzdXBlciBpbnRlcmVzdGluZyBpbiB0aGlzIGNhc2UsIHNpbmNlIG1vc3QgY2hhcmFjdGVycyBqdXN0IHRhbGsgdG8gb25lIGFub3RoZXIgaW4gdGhlc2UgYm9va3MsIGJ1dCBtYXliZSB5b3Ugd2lsbCBwaWNrIGJvb2tzIHdpdGggbW9yZSBpbnRlcmVzdGluZyB2ZXJicyEgCgojIFBhcnQgMjogT24geW91ciBvd24KICAKRG93bmxvYWQgMysgYm9va3MgYnkgc29tZSBhdXRob3Igb24gW1Byb2plY3QgR3V0ZW5iZXJnXShodHRwOi8vd3d3Lmd1dGVuYmVyZy5vcmcvKS4gSmFuZSBBdXN0ZW4sIFZpY3RvciBIdWdvLCBMdWN5IE1hdWQgTW9udGdvbWVyeSwgQXJ0aHVyIENvbmFuIERveWxlLCBNYXJrIFR3YWluLCBIZW5yeSBEYXZpZCBUaG9yZWF1LCBGeW9kb3IgRG9zdG95ZXZza3ksIExlbyBUb2xzdG95LiBBbnlvbmUuIEp1c3QgbWFrZSBzdXJlIGl04oCZcyBhbGwgZnJvbSB0aGUgc2FtZSBhdXRob3IuIElmIHlvdSBhcmUgYWJsZSB0byBmaW5kIGRvd25sb2FkYWJsZSBib29rcyBlbHNld2hlcmUgKGxpa2UgdGhlICpIYXJyeSBQb3R0ZXIqIGV4YW1wbGUgZnJvbSBjbGFzcykgYW5kIHByZWZlciB0byBkbyB0aGF0LCB0aGF0J3MgT0sgdG9vISBZb3UganVzdCBtaWdodCBoYXZlIHRvIGRvIGEgYml0IG1vcmUgc3RhcnQgdXAgd29yayB0byBnZXQgdGhlIGRhdGEgaW50byBhIHRpZHkgZm9ybWF0LiAKCk1ha2UgdGhlc2UgdHdvIHBsb3RzIGFuZCBkZXNjcmliZSB3aGF0IGVhY2ggdGVsbCBhYm91dCB5b3VyIGF1dGhvcuKAmXMgYm9va3M6CgoxLiBUb3AgMTAgbW9zdCBmcmVxdWVudCB3b3JkcyBpbiBlYWNoIGJvb2sgT1IgdG9wIDEwIG1vc3QgdW5pcXVlIHdvcmRzIGluIGVhY2ggYm9vayAoaS5lLiB0Zi1pZGYpCjMuIEFueSBvdGhlciBwbG90IG9mIHlvdXIgY2hvaWNlCgpZb3UgY2FuIGtuaXQgeW91ciAuUm1kIGZpbGUgdG8gYW55IGZpbGUgZm9ybWF0IHlvdSB3aXNoISAK