Welcome to your LAST LAB OF THE J-TERM!

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!

Part 2: On your own

Download 3+ books by some author on Project Gutenberg. Jane Austen, Victor Hugo, Lucy Maud Montgomery, Arthur Conan Doyle, Mark Twain, Henry David Thoreau, Fyodor Dostoyevsky, Leo Tolstoy. Anyone. Just make sure it’s all from the same author. If you are able to find downloadable books elsewhere (like the Harry Potter example from class) and prefer to do that, that’s OK too! You just might have to do a bit more start up work to get the data into a tidy format.

Make these two plots and describe what each tell about your author’s books:

  1. Top 10 most frequent words in each book OR top 10 most unique words in each book (i.e. tf-idf)
  2. Any other plot of your choice

You can knit your .Rmd file to any file format you wish!

LS0tCnRpdGxlOiAiTGFiMTI6IFRleHQiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDogCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgY3NzOiBzdGF0NDEtbGFiLXRoZW1lLmNzcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShwbG90bHkpCmBgYAoKV2VsY29tZSB0byB5b3VyIExBU1QgTEFCIE9GIFRIRSBKLVRFUk0hIAoKVGhlICJleGFtcGxlIiBwYXJ0IG9mIHRoZSBsYWIgaXMgYSBsaXR0bGUgbG9uZ2VyIHRoYW4gdXN1YWwuIFRoZSBnb29kIG5ld3MgaXMgdGhhdCBpdCdzIHByZXR0eSBzaW1pbGFyIHRvIHRoZSBhbmFseXNpcyBpbiBNRFNSLCBhbmQgc2hvdWxkIHByb3ZpZGUgYSBsb3Qgb2YgdGVtcGxhdGUgY29kZSBmb3IgdGhlICJvbiB5b3VyIG93biIgcG9ydGlvbi4gCgpXZSdyZSBiYXJlbHkgc2NyYXRjaGluZyB0aGUgc3VyZmFjZSBvZiB0ZXh0IG1pbmluZy4gSWYgeW91IHdhbnQgdG8gbGVhcm4gbW9yZSwgSSByZWNvbW1lbmQgW1RpZHkgVGV4dCBNaW5pbmcgd2l0aCBSXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vKS4gCgojIFBhcnQgMTogRXhhbXBsZQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KGd1dGVuYmVyZ3IpCgpsYWIxMl90aGVtZSA9IHRoZW1lX21pbmltYWwoYmFzZV9mYW1pbHkgPSAiT3B0aW1hIiwgYmFzZV9zaXplID0gMTIpCmBgYAoKVG9kYXksIEknbSBnb2luZyB0byB1c2Ugbm92ZWxzIGJ5IHRoZSBCcm9udGUgc2lzdGVycyB0byB3YWxrIHRocm91Z2ggYSBmZXcgZXhhbXBsZXMgb2YgYmFzaWMgdGV4dCBhbmFseXNpcy4KCiMjIExvYWQgRGF0YQoKV2XigJlyZSBnb2luZyB0byB1c2UgdGhlIGBndXRlbmJlcmdyYCBwYWNrYWdlIHRvIGRvd25sb2FkIHNvbWUgYm9va3MgZGlyZWN0bHkgZnJvbSBbUHJvamVjdCBHdXRlbmJlcmddKGh0dHA6Ly93d3cuZ3V0ZW5iZXJnLm9yZy8pLiBJbiBvcmRlciB0byBkb3dubG9hZCB0aGUgcmlnaHQgYm9va3MsIHdlIG5lZWQgdG8gZmluZCB0aGVpciBJRHMsIHdoaWNoIGNvbWUgZnJvbSB0aGUgVVJMcyBhdCB0aGVpciB3ZWJzaXRlLiBGb3IgaW5zdGFuY2UsIEphbmUgRXlyZSBpcyBib29rIDEyNjAuIFdl4oCZbGwgc3RvcmUgdGhlc2UgYm9va3MgYXMgYCpfcmF3KiBhbmQgdGhlbiBjbGVhbiB0aGVtIHVwIGxhdGVyLgoKYGBge3IgZG93bmxvYWQsIGV2YWwgPSBGQUxTRX0KIyA3NjcgLSBBZ25lcyBHcmV5IGJ5IEFubmUgQnJvbnRlCiMgMTI2MCAtIEphbmUgRXlyZTogQW4gQXV0b2Jpb2dyYXBoeSBieSBDaGFybG90dGUgQnJvbnTDqwojIDc2OCAtIFd1dGhlcmluZyBIZWlnaHRzIGJ5IEVtaWx5IEJyb250w6sKCmJyb250ZV9yYXcgPC0gZ3V0ZW5iZXJnX2Rvd25sb2FkKGMoNzY3LCAxMjYwLCA3NjgpLCBtaXJyb3IgPSAiaHR0cDovL21pcnJvcnMueG1pc3Npb24uY29tL2d1dGVuYmVyZy8iLCBtZXRhX2ZpZWxkcyA9ICJ0aXRsZSIpCmBgYAoKKFlvdSBtYXkgbm90IG5lZWQgdGhlIGBtaXJyb3JgIGFyZ3VtZW50LCBidXQgdGhlIGRlZmF1bHQgbWlycm9yIHdhcyBkb3duIHllc3RlcmRheSB3aGVuIEkgd2FzIHdyaXRpbmcgdGhpcyBsYWIuKQoKYGBge3Igd3JpdGUtY3N2LCBldmFsID0gRkFMU0UsIGluY2x1ZGUgPSBGQUxTRX0Kd3JpdGVfY3N2KGJyb250ZV9yYXcsICJkYXRhL2Jyb250ZV9yYXcuY3N2IikKYGBgCgpgYGB7ciBsb2FkLWRhdGEsIGluY2x1ZGUgPSBGQUxTRX0KYnJvbnRlX3JhdyA9IHJlYWRfY3N2KCJkYXRhL2Jyb250ZV9yYXcuY3N2IikKYGBgCgpJZiB5b3UgZG9uJ3Qgd2FudCB0byByZS1kb3dubG9hZCB0aGUgYm9va3MgZXZlcnkgdGltZSB5b3Uga25pdCAoeW91IGRvbid0KSwgd2UgY2FuIHVzZSBhbiBSTWFya2Rvd24gdHJpY2suIFB1dCB0aGUgYWN0dWFsIGNvZGUgZm9yIGdldHRpbmcgdGhlIGJvb2tzIGluIGEgY2h1bmsgd2l0aCBgZXZhbD1GQUxTRWAgb24gaXQgYW5kIHJ1biBpdCBtYW51YWxseSBpbiBSU3R1ZGlvIHdoZW4geW91IHdhbnQgdG8gZ2V0IHRoZSBkYXRhLiBUaGVuIHlvdSBjYW4gd3JpdGUgdGhlIGRvd25sb2FkZWQgZGF0YSBhcyBhIENTViBmaWxlLCBhbmQgdGhlbiBsb2FkIGl0IGludmlzaWJseSBmcm9tIHRoZSBDU1YgZmlsZSB3aGVuIHlvdSBrbml0OiAKCmBgYGB0ZXh0CkkgZmlyc3QgZG93bmxvYWQgZGF0YSBmcm9tIFByb2plY3QgR3V0ZW5iZXJnOgpgYGB7ciBnZXQtYm9vaywgZXZhbD1GQUxTRX1gciAnJ2AKYm9va3NfcmF3IDwtIGd1dGVuYmVyZ19kb3dubG9hZCguLi4pCndyaXRlX2Nzdihib29rc19yYXcsICJkYXRhL2Jvb2tzX3Jhdy5jc3YiKQpgYGAKYGBge3IgbG9hZC1ib29rLWRhdGEtcmVhbCwgaW5jbHVkZT1GQUxTRX1gciAnJ2AKYm9va3NfcmF3IDwtIHJlYWRfY3N2KCJkYXRhL2Jvb2tzX3Jhdy5jc3YiKQpgYGAKYGBgYAoKIyMgQ2xlYW4gRGF0YQoKVGhlIGBndXRlbmJlcmdyYCBwYWNrYWdlIGRvd25sb2FkcyBkYXRhIGluIGFuIGFscmVhZHkgdGlkeSBmb3JtLCB3aXRoIGEgY29sdW1uIGZvciB0aGUgYm9vayBJRCwgYSBjb2x1bW4gZm9yIHRoZSBib29rIHRpdGxlICh3aGljaCB3ZSB0b2xkIGl0IHRvIGdldCB0aHJvdWdoIGBtZXRhX2ZpZWxkcyA9ICJ0aXRsZSJgKSwgYW5kIGEgY29sdW1uIGZvciB0aGUgdGV4dC4gQlVULCBlYWNoIGJvb2sgdXN1YWxseSBzdGFydHMgd2l0aCBhIGxpdHRsZSBtZXNzaW5lc3MuIEhlcmUsIHdlIGNhbiBzZWUgdGhhdCBBZ25lcyBHcmV5IHN0YXJ0cyB3aXRoIHRoZSB0aXRsZSBwYWdlIGFuZCBwdWJsaXNoZXIgaW5mb3JtYXRpb246IAoKYGBge3J9CmhlYWQoYnJvbnRlX3JhdykKYGBgCgpGb3IgdGhpcyBsYWIsIHdlJ3JlIGp1c3QgZ29pbmcgdG8gaWdub3JlIHRoaXMsIGJ1dCBpbiB0aGUgInJlYWwgd29ybGQiLCB3ZSdkIHdhbnQgdG8gZmlndXJlIG91dCBhIHN5c3RlbWF0aWMgd2F5IHRvIHJlbW92ZSBhbnkgdGV4dCB0aGF0IGlzIG5vdCBhIHBhcnQgb2YgdGhlIGJvb2suIChGb3IgZXhhbXBsZSwgd2UgY291bGQgY3JlYXRlIGEgbmV3IGNvbHVtbiBpbmRpY2F0aW5nIHdoZXRoZXIgZWFjaCByb3cgaW4gb3VyIGRhdGFzZXQgaXMgcGFydCBvZiB0aGUgYm9vayBvciBub3QsIGFuZCB0aGVuIGBmaWx0ZXIoKWAgYmFzZWQgb24gdGhhdCBjb2x1bW4pCgojIyBUb2tlbml6ZSBhbmQgY291bnQKClRoZSBmaXJzdCBzdGVwIGlzIHRvICp0b2tlbml6ZSogdGhpcyBkYXRhOiB0aGUgYHVubmVzdF90b2tlbnNgIGZ1bmN0aW9uIGRvZXMgdGhpcyBmb3IgdXMgYXV0b21hdGljYWxseS4gKFRoZSBmaXJzdCBhcmd1bWVudCBpcyB3aGF0IHlvdSB3YW50IHRoZSBuZXcgdmFyaWFibGUgdG8gYmUgbmFtZWQsIHRoZSBzZWNvbmQgYXJndW1lbnQgaXMgdGhlIHZhcmlhYmxlIHdlIHdhbnQgdG8gdG9rZW5pemUpCgpgYGB7cn0KYnJvbnRlX3dvcmRzIDwtIGJyb250ZV9yYXcgJT4lIAogIGRyb3BfbmEodGV4dCkgJT4lIAogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkKCmhlYWQoYnJvbnRlX3dvcmRzKQpgYGAKCk5leHQsIHdlIHJlbW92ZSBzdG9wIHdvcmRzLCBjb3VudCBhbGwgb2YgdGhlIHdvcmRzIGluIGVhY2ggYm9vayBhbmQga2VlcCB0aGUgdG9wIDE1LCBhbmQgdGhlbiBzb3J0IHRoZW0gKHRvIG1ha2UgdGhlIG91dHB1dCBtb3JlIHJlYWRhYmxlKS4gSSBhbHNvIGluY2x1ZGUgYSBzdGVwIHRvIG1ha2UgdGhlIHZhcmlhYmxlIGFuIG9yZGVyZWQgZmFjdG9yLCB3aGljaCBpcyBoZWxwZnVsIGZvciBwbG90dGluZy4gCgpgYGB7cn0KdG9wX3dvcmRzX2Jyb250ZSA8LSBicm9udGVfd29yZHMgJT4lIAogICMgUmVtb3ZlIHN0b3Agd29yZHMKICBhbnRpX2pvaW4oc3RvcF93b3JkcykgJT4lIAogICMgQ291bnQgYWxsIHRoZSB3b3JkcyBpbiBlYWNoIGJvb2sKICBjb3VudCh0aXRsZSwgd29yZCwgc29ydCA9IFRSVUUpICU+JSAKICAjIEtlZXAgdG9wIDE1IGluIGVhY2ggYm9vawogIGdyb3VwX2J5KHRpdGxlKSAlPiUgCiAgdG9wX24oMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogICMgTWFrZSB0aGUgd29yZHMgYW4gb3JkZXJlZCBmYWN0b3Igc28gdGhleSBwbG90IGluIG9yZGVyCiAgbXV0YXRlKHdvcmQgPSBmY3RfaW5vcmRlcih3b3JkKSkKdG9wX3dvcmRzX2Jyb250ZQpgYGAKCkZpbmFsbHksIHdlIHBsb3QgdGhpcyBwcm9jZXNzZWQgZGF0YTogCgpgYGB7cn0KZ2dwbG90KHRvcF93b3Jkc19icm9udGUsIGFlcyh5ID0gZmN0X3Jldih3b3JkKSwgeCA9IG4sIGZpbGwgPSB0aXRsZSkpICsgCiAgZ2VvbV9jb2woKSArIAogIGd1aWRlcyhmaWxsID0gRkFMU0UpICsKICBsYWJzKHkgPSAiQ291bnQiLCB4ID0gTlVMTCwgCiAgICAgICB0aXRsZSA9ICIxNSBtb3N0IGZyZXF1ZW50IHdvcmRzIGluIEJyb250ZSBOb3ZlbHMiKSArCiAgZmFjZXRfd3JhcCh2YXJzKHRpdGxlKSwgc2NhbGVzID0gImZyZWVfeSIpICsKICBsYWIxMl90aGVtZSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAuNzUpCmBgYAoKKFNoYXJwLWV5ZWQgcmVhZGVycyB3aWxsIG5vdGljZSB0aGF0IHRoZSB3b3JkcyBhcmVu4oCZdCBhY3R1YWxseSBpbiBwZXJmZWN0IG9yZGVyISBUaGF04oCZcyBiZWNhdXNlIHNvbWUgY29tbW9uIHdvcmRzIGFyZSByZXBlYXRlZCBhY3Jvc3MgdGhlIHBsYXlzLCBsaWtlIOKAnG1pc3PigJ0gYW5kIOKAnHRpbWXigJ0uIEhvd2V2ZXIsIGVhY2ggY2F0ZWdvcnkgaW4gYSBmYWN0b3IgY2FuIG9ubHkgaGF2ZSBvbmUgcG9zc2libGUgcG9zaXRpb24gaW4gdGhlIG9yZGVyLiBZb3UgY2FuIGZpeCB0aGlzIHdpdGggdGhlIHJlb3JkZXJfd2l0aGluKCkgZnVuY3Rpb24gaW4gdGlkeXRleHTigJRzZWUgSnVsaWEgU2lsZ2XigJlzIHR1dG9yaWFsIFtoZXJlXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvcmVvcmRlci13aXRoaW4vKSBmb3IgaG93IHRvIHVzZSBpdC4pCgojIyB0Zi1pZGYKCk5vdyBsZXQncyB0cnkgYSAqZmFuY3kqIGNvdW50aW5nIG1ldGhvZCAodGYtaWRmKTogCgpgYGB7cn0KYnJvbnRlX3dvcmRzX2ZpbHRlcmVkID0gYnJvbnRlX3dvcmRzICU+JSAKICAjIFJlbW92ZSBzdG9wIHdvcmRzCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAKICAjIENvdW50IGFsbCB0aGUgd29yZHMgaW4gZWFjaCBib29rCiAgY291bnQodGl0bGUsIHdvcmQsIHNvcnQgPSBUUlVFKSAKCiMgQWRkIHRoZSB0Zi1pZGYgdmFsdWVzIHRvIHRoZSBjb3VudHMKYnJvbnRlX3RmX2lkZiA8LSBicm9udGVfd29yZHNfZmlsdGVyZWQgJT4lIAogIGJpbmRfdGZfaWRmKHdvcmQsIHRpdGxlLCBuKQoKIyBHZXQgdGhlIHRvcCAxMCB1bmlxdWVzdCB3b3Jkcwpicm9udGVfdGZfaWRmX3Bsb3QgPC0gYnJvbnRlX3RmX2lkZiAlPiUgCiAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICBncm91cF9ieSh0aXRsZSkgJT4lIAogIHRvcF9uKDEwKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBtdXRhdGUod29yZCA9IGZjdF9pbm9yZGVyKHdvcmQpKQoKZ2dwbG90KGJyb250ZV90Zl9pZGZfcGxvdCwgCiAgICAgICBhZXMoeSA9IGZjdF9yZXYod29yZCksIHggPSB0Zl9pZGYsIGZpbGwgPSB0aXRsZSkpICsKICBnZW9tX2NvbCgpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgbGFicyh4ID0gInRmLWlkZiIsIHkgPSBOVUxMKSArCiAgZmFjZXRfd3JhcCh+IHRpdGxlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBsYWIxMl90aGVtZSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAuNzUpCmBgYAoKVGhlc2UgdGYtaWRmcyBhcmUgdmVyeSBzbWFsbCBjb21wYXJlZCB0byB0aGUgZXhhbXBsZSBmcm9tIHRoZSBNRFNSIGJvb2sgb24gYXJYaXYgcGFwZXJzIC0tIHRoaXMgaXMgYmVjYXVzZSB0aGVzZSB0aHJlZSBib29rcyBhcmUgYWxsICptdWNoIG1vcmUqIHNpbWlsYXIgdG8gb25lIGFub3RoZXIgdGhhbiB0aGUgYXJYaXYgYWJzdHJhY3RzLiBTaW5jZSB0aGUgQnJvbnRlIHNpc3RlcnMgYXJlIGZyb20gdGhlIHNhbWUgZXJhIGFuZCB3cm90ZSBpbiBhIHNpbWlsYXIgc3R5bGUsIGFsbCB0Zi1pZGYgZGlkIHdhcyBlc3NlbnRpYWxseSByZW1vdmUgbm9uLW5hbWVzLiBPaCB3ZWxsISAKCiMjIFNlbnRpbWVudCBBbmFseXNpcwoKKkEgbm90ZSBvZiBjYXV0aW9uKjogbGlrZSB3ZSBzYXcgaW4gY2xhc3MsIHNlbnRpbWVudCBhbmFseXNlcyBjYW4gYmUgcmlmZSB3aXRoIGJpYXMuIFRoZXkncmUgZnVuIHRvIG1ha2UsIGFuZCB0aGUgYHRpZHl0ZXh0YCBwYWNrYWdlIG1ha2VzIGl0IGV4dHJlbWVseSBlYXN5IHRvIGRvLCBidXQgdGFrZSByZXN1bHRzIHdpdGggYSBncmFpbiBvZiBzYWx0LiAKClRoZSBmaXJzdCBzdGVwIGlzIHRvIGpvaW4gdGhlIHNlbnRpbWVudCBkaWN0aW9uYXJ5IHRvIG91ciBkYXRhc2V0IHdpdGggYGlubmVyX2pvaW5gOiB0aGlzIG9ubHkga2VlcHMgd29yZHMgdGhhdCBzaG93IHVwIGluIGJvdGggdGhlIGRhdGFzZXQgYW5kIHRoZSBzZW50aW1lbnQgZGljdGlvbmFyeS4KCmBgYHtyfQpicm9udGVfc2VudGltZW50IDwtIGJyb250ZV93b3Jkc19maWx0ZXJlZCAlPiUgCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKQpoZWFkKGJyb250ZV9zZW50aW1lbnQpCmBgYAoKTmV4dCwgd2UgY291bnQgdXAgZWFjaCBzZW50aW1lbnQgYnkgdGl0bGUsIGFuZCB0aGVuIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSB0aGF0IGlzIHBvc2l0aXZlIGlzIHNlbnRpbWVudCBpcyBwb3NpdGl2ZSBhbmQgbmVnYXRpdmUgb3RoZXJ3aXNlICh0cnkgcnVubmluZyB0aGUgaW50ZXJtZWRpYXRlIGRhdGEgb24geW91ciBvd24gaWYgdGhpcyBkb2Vzbid0IG1ha2Ugc2Vuc2UpLiBUaGlzIGdpdmVzIGFzIGFuICJvdmVyYWxsIHBvc2l0aXZlIiBhbmQgIm92ZXJhbGwgbmVnYXRpdmUiIG51bWJlciBmb3IgZWFjaCBib29rLCB3aGljaCB3ZSBjYW4gcGxvdDoKCmBgYHtyfQpicm9udGVfc2VudGltZW50X2J5X3RpdGxlIDwtIGJyb250ZV9zZW50aW1lbnQgJT4lCiAgY291bnQodGl0bGUsIHNlbnRpbWVudCkgJT4lCiAgbXV0YXRlKAogICAgbl9wb3NfbmVnID0gaWZlbHNlKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiLCBuLCAtbikKICApCgpnZ3Bsb3QoYnJvbnRlX3NlbnRpbWVudF9ieV90aXRsZSwgYWVzKHggPSBzZW50aW1lbnQsIHkgPSBuX3Bvc19uZWcsIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9jb2wocG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgpKSArCiAgZmFjZXRfd3JhcCh2YXJzKHRpdGxlKSkgKwogIGxhYjEyX3RoZW1lICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiQyIsIGVuZCA9IC43NSkKYGBgCgpXZSBjb3VsZCBzdGFuZGFyZGl6ZSB0aGVzZSBieSBib29rIGxlbmd0aCB0byBiZXR0ZXIgY29tcGFyZSBhY3Jvc3MgYm9va3MsIGJ1dCB3ZSdsbCBkbyBzb21ldGhpbmcgbW9yZSBpbnRlcmVzdGluZywgd2hpY2ggaXMgdG8gbG9vayBhdCB0b3RhbCBzZW50aW1lbnQgYnkgKmNoYXB0ZXIqLiAKCkZpcnN0LCB3ZSBoYXZlIHRvIGdldCBjcmVhdGl2ZSBpbiBkZWZpbmluZyB0aGUgIkNoYXB0ZXIiIHZhcmlhYmxlIC0gSSd2ZSBpbmNsdWRlZCBhbGwgY29kZSB0byBkbyBzbyB3aXRoIGNvbW1lbnRzIGJlbG93LiBUaGUgZ2VuZXJhbCBpZGVhIGlzIHRoYXQgd2UgY2hlY2sgaWYgZWFjaCByb3cgaXMgZXF1YWwgdG8gIkNIQVBURVIgWCIsIHJlY29yZCBhIDEgaWYgeWVzIGFuZCAwIGlmIG5vLCBhbmQgdGhlbiB0YWtlIHRoZSBjdW11bGF0aXZlIHN1bSBvZiB0aGF0IG5ldyB2YXJpYWJsZSB0byBnZXQgYSBjaGFwdGVyIG51bWJlciBmb3IgZWFjaCBsaW5lLiBJIGFsc28gZGlkIGVhY2ggYm9vayBzZXBhcmF0ZWx5LCByYXRoZXIgdGhhbiBmaWd1cmUgb3V0IGEgbmljZXIgd2F5IHRvIHN0YXJ0IG92ZXIgYXQgMC4gKE5vdGUgdGhhdCB0aGlzIHdpbGwgbGlrZWx5IGJlIHNsaWdodGx5IGRpZmZlcmVudCBmb3IgZGlmZmVyZW50IGJvb2tzISkKCmBgYHtyfQp3dXRoZXJpbmdfaGVpZ2h0cyA8LSBicm9udGVfcmF3ICU+JSAKICBmaWx0ZXIodGl0bGUgPT0gIld1dGhlcmluZyBIZWlnaHRzIikgJT4lCiAgIyBHZXQgcmlkIG9mIHJvd3Mgd2hlcmUgdGV4dCBpcyBtaXNzaW5nCiAgZHJvcF9uYSh0ZXh0KSAlPiUgCiAgIyBDaGFwdGVycyBzdGFydCB3aXRoIENIQVBURVIgWCwgc28gbWFyayBpZiBlYWNoIHJvdyBpcyBhIGNoYXB0ZXIgc3RhcnQKICAjIGN1bXN1bSgpIGNhbGN1bGF0ZXMgdGhlIGN1bXVsYXRpdmUgc3VtLCBzbyBpdCdsbCBpbmNyZWFzZSBldmVyeSB0aW1lIHRoZXJlJ3MKICAjIGEgbmV3IGNoYXB0ZXIgYW5kIGF1dG9tYXRpY2FsbHkgbWFrZSBjaGFwdGVyIG51bWJlcnMKICBtdXRhdGUoY2hhcHRlcl9zdGFydCA9IHN0cl9kZXRlY3QodGV4dCwgIl5DSEFQVEVSIiksCiAgICAgICAgIGNoYXB0ZXJfbnVtYmVyID0gY3Vtc3VtKGNoYXB0ZXJfc3RhcnQpKSAlPiUgCiAgIyBHZXQgcmlkIG9mIHRoZXNlIGNvbHVtbnMKICBzZWxlY3QoLWNoYXB0ZXJfc3RhcnQpICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpCgpqYW5lX2V5cmUgPSBicm9udGVfcmF3ICU+JSAKICBmaWx0ZXIodGl0bGUgPT0gIkphbmUgRXlyZTogQW4gQXV0b2Jpb2dyYXBoeSIpICU+JQogICMgR2V0IHJpZCBvZiByb3dzIHdoZXJlIHRleHQgaXMgbWlzc2luZwogIGRyb3BfbmEodGV4dCkgJT4lIAogICMgQ2hhcHRlcnMgc3RhcnQgd2l0aCBDSEFQVEVSIFgsIHNvIG1hcmsgaWYgZWFjaCByb3cgaXMgYSBjaGFwdGVyIHN0YXJ0CiAgIyBjdW1zdW0oKSBjYWxjdWxhdGVzIHRoZSBjdW11bGF0aXZlIHN1bSwgc28gaXQnbGwgaW5jcmVhc2UgZXZlcnkgdGltZSB0aGVyZSdzCiAgIyBhIG5ldyBjaGFwdGVyIGFuZCBhdXRvbWF0aWNhbGx5IG1ha2UgY2hhcHRlciBudW1iZXJzCiAgbXV0YXRlKGNoYXB0ZXJfc3RhcnQgPSBzdHJfZGV0ZWN0KHRleHQsICJeQ0hBUFRFUiIpLAogICAgICAgICBjaGFwdGVyX251bWJlciA9IGN1bXN1bShjaGFwdGVyX3N0YXJ0KSkgJT4lIAogICMgR2V0IHJpZCBvZiB0aGVzZSBjb2x1bW5zCiAgc2VsZWN0KC1jaGFwdGVyX3N0YXJ0KSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKYWduZXNfZ3JleSA9IGJyb250ZV9yYXcgJT4lIAogIGZpbHRlcih0aXRsZSA9PSAiQWduZXMgR3JleSIpICU+JQogICMgR2V0IHJpZCBvZiByb3dzIHdoZXJlIHRleHQgaXMgbWlzc2luZwogIGRyb3BfbmEodGV4dCkgJT4lIAogICMgQ2hhcHRlcnMgc3RhcnQgd2l0aCBDSEFQVEVSIFgsIHNvIG1hcmsgaWYgZWFjaCByb3cgaXMgYSBjaGFwdGVyIHN0YXJ0CiAgIyBjdW1zdW0oKSBjYWxjdWxhdGVzIHRoZSBjdW11bGF0aXZlIHN1bSwgc28gaXQnbGwgaW5jcmVhc2UgZXZlcnkgdGltZSB0aGVyZSdzCiAgIyBhIG5ldyBjaGFwdGVyIGFuZCBhdXRvbWF0aWNhbGx5IG1ha2UgY2hhcHRlciBudW1iZXJzCiAgbXV0YXRlKGNoYXB0ZXJfc3RhcnQgPSBzdHJfZGV0ZWN0KHRleHQsICJeQ0hBUFRFUiIpLAogICAgICAgICBjaGFwdGVyX251bWJlciA9IGN1bXN1bShjaGFwdGVyX3N0YXJ0KSkgJT4lIAogICMgR2V0IHJpZCBvZiB0aGVzZSBjb2x1bW5zCiAgc2VsZWN0KC1jaGFwdGVyX3N0YXJ0KSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKYnJvbnRlX3dpdGhfY2hhcHRlcnMgPSBiaW5kX3Jvd3Mod3V0aGVyaW5nX2hlaWdodHMsIGphbmVfZXlyZSwgYWduZXNfZ3JleSkKYGBgCgpOZXh0LCB3ZSBmb2xsb3cgdGhlIHNhbWUgc3RlcHMgd2l0aCB0aGUgc2VudGltZW50IGRpY3Rpb25hcnkgYXMgYmVmb3JlLCBidXQgbm93IGNvdW50aW5nIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzZW50aW1lbnRzIGJ5ICpib29rIGFuZCBjaGFwdGVyKi4gCmBgYHtyfQpicm9udGVfc2VudGltZW50X2J5X2NoYXB0ZXIgPC0gYnJvbnRlX3dpdGhfY2hhcHRlcnMgJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICBncm91cF9ieSh0aXRsZSwgY2hhcHRlcl9udW1iZXIpICU+JQogIHN1bW1hcml6ZSgKICAgIG5fcG9zID0gc3VtKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKSwKICAgIG5fbmVnID0gc3VtKHNlbnRpbWVudCA9PSAibmVnYXRpdmUiKQogICkgJT4lCiAgbXV0YXRlKAogICAgc2VudF9kaWZmID0gbl9wb3MgLSBuX25lZwogICkKCmdncGxvdChicm9udGVfc2VudGltZW50X2J5X2NoYXB0ZXIsIGFlcyh4ID0gY2hhcHRlcl9udW1iZXIsIHkgPSBzZW50X2RpZmYsIGZpbGwgPSBzZW50X2RpZmYgPiAwKSkgKwogIGdlb21fY29sKCkgKyAKICBmYWNldF93cmFwKHZhcnModGl0bGUpKSArCiAgbGFiMTJfdGhlbWUgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJDIiwgZW5kID0gLjc1KSArCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSkKYGBgCgpOb3cgdGhpcyBpcyBtb3JlIGludGVyZXN0aW5nISBXZSBjYW4gc2VlIHRoYXQgKld1dGhlcmluZyBIZWlnaHRzKiBpcyBhIHJlYWwgYnVtbWVyIHRocm91Z2hvdXQsIGJ1dCBib3RoICpBZ25lcyBHcmV5KiBhbmQgKkphbmUgRXlyZSogZW5kIG9uIGEgcG9zaXRpdmUgbm90ZS4gCgojIyBCaWdyYW0gQW5hbHlzaXMKClRoZSBsYXN0IGV4YW1wbGUgSSdsbCB3YWxrIHRocm91Z2ggaXMgYSAqKmJpZ3JhbSBhbmFseXNpcyoqLiBBbiAqbi1ncmFtKiBpcyBhIHNldCBvZiAqbiogd29yZHMsIGFuZCBzbyBhIGJpZ3JhbSBpcyBhIHNldCBvZiAyIHdvcmRzLiBIZXJlJ3MgaG93IHRvIHVzZSB0aGUgYHVubmVzdF90b2tlbnNgIGZ1bmN0aW9uIHRvIGNvdW50IGJpZ3JhbXMgaW5zdGVhZCBvZiB3b3JkczogCgpgYGB7cn0KYnJvbnRlX2JpZ3JhbXMgPC0gYnJvbnRlX3JhdyAlPiUgCiAgZHJvcF9uYSh0ZXh0KSAlPiUgCiAgIyBuID0gMiBoZXJlIG1lYW5zIGJpZ3JhbXMuIFdlIGNvdWxkIGFsc28gbWFrZSB0cmlncmFtcyAobiA9IDMpIG9yIGFueSB0eXBlIG9mIG4tZ3JhbQogIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lIAogICMgU3BsaXQgdGhlIGJpZ3JhbXMgaW50byB0d28gd29yZHMgc28gd2UgY2FuIHJlbW92ZSBzdG9wd29yZHMKICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIikgJT4lIAogIGZpbHRlcighd29yZDEgJWluJSBzdG9wX3dvcmRzJHdvcmQsCiAgICAgICAgICF3b3JkMiAlaW4lIHN0b3Bfd29yZHMkd29yZCkgJT4lIAogICMgUHV0IHRoZSB0d28gd29yZCBjb2x1bW5zIGJhY2sgdG9nZXRoZXIKICB1bml0ZShiaWdyYW0sIHdvcmQxLCB3b3JkMiwgc2VwID0gIiAiKQpicm9udGVfYmlncmFtcwpgYGAKCk5leHQsIHdlIGZvbGxvdyB0aGUgc2FtZSBjb3VudGluZyBzdGVwcyBhcyBiZWZvcmUsIGJ1dCBhZGRpbmcgdGhlIGV4dHJhIHN0ZXAgb2YgdGFraW5nIG91dCAiTkEgTkEiIHNpbmNlIHRoYXQgYXBwZWFycyBpbiBvdXIgZGF0YS4gCgpgYGB7cn0KdG9wX2JpZ3JhbXMgPC0gYnJvbnRlX2JpZ3JhbXMgJT4lIAogIGZpbHRlcihiaWdyYW0gIT0gIk5BIE5BIikgJT4lCiAgIyBDb3VudCBhbGwgdGhlIGJpZ3JhbXMgaW4gZWFjaCBwbGF5CiAgY291bnQodGl0bGUsIGJpZ3JhbSwgc29ydCA9IFRSVUUpICU+JSAKICAjIEtlZXAgdG9wIDE1IGluIGVhY2ggcGxheQogIGdyb3VwX2J5KHRpdGxlKSAlPiUgCiAgdG9wX24oMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogICMgTWFrZSB0aGUgYmlncmFtcyBhbiBvcmRlcmVkIGZhY3RvciBzbyB0aGV5IHBsb3QgaW4gb3JkZXIKICBtdXRhdGUoYmlncmFtID0gZmN0X2lub3JkZXIoYmlncmFtKSkKCmdncGxvdCh0b3BfYmlncmFtcywgYWVzKHkgPSBmY3RfcmV2KGJpZ3JhbSksIHggPSBuLCBmaWxsID0gdGl0bGUpKSArIAogIGdlb21fY29sKCkgKyAKICBndWlkZXMoZmlsbCA9IEZBTFNFKSArCiAgbGFicyh5ID0gIkNvdW50IiwgeCA9IE5VTEwsIAogICAgICAgdGl0bGUgPSAiMTUgbW9zdCBmcmVxdWVudCBiaWdyYW1zIGluIEJyb250ZSBub3ZlbHMiKSArCiAgZmFjZXRfd3JhcCh2YXJzKHRpdGxlKSwgc2NhbGVzID0gImZyZWUiKSArIAogIGxhYjEyX3RoZW1lICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAuNzUpICsKICBndWlkZXMoZmlsbCA9IEZBTFNFKQpgYGAKCk5vdyB3ZSBjYW4gc2VlIHdoZXJlIGFsbCBvZiB0aGUgIm1pc3MiZXMgY2FtZSBmcm9tISAoSSB3aWxsIGFsc28gcG9pbnQgb3V0IGBjb3VsZG4ndCBiZWFyYCBhbmQgYGknbSB0aXJlZGAgYXJlIHRvcCBiaWdyYW1zIGluICpXdXRoZXJpbmcgSGVpZ2h0cyosIHdoaWNoIGlzIG1vcmUgZXZpZGVuY2UgdG8gdGhlIG92ZXJhbGwgYnVtbWVyLW5lc3Mgb2YgdGhlIGJvb2spCgpOZXh0LCBsZXQncyB0cnkgYSB2ZXJzaW9uIG9mIFtTaGUgZ2lnZ2xlcywgaGUgZ2FsbG9wc10oaHR0cHM6Ly9wdWRkaW5nLmNvb2wvMjAxNy8wOC9zY3JlZW4tZGlyZWN0aW9uLyksIGJ5IHJlc3RyaWN0aW5nIHRoZSBkYXRhc2V0IHRvIG9ubHkgaW5jbHVkZSBiaWdyYW1zIHRoYXQgc3RhcnQgd2l0aCAiaGUiIG9yICJzaGUiOiAKCmBgYHtyfQpwcm9ub3VuX2JpZ3JhbXMgPC0gYnJvbnRlX3JhdyAlPiUgCiAgZHJvcF9uYSh0ZXh0KSAlPiUgCiAgdW5uZXN0X3Rva2VucyhiaWdyYW0sIHRleHQsIHRva2VuID0gIm5ncmFtcyIsIG4gPSAyKSAlPiUgCiAgIyBTcGxpdCB0aGUgYmlncmFtcyBpbnRvIHR3byB3b3JkcyBzbyB3ZSBjYW4gcmVtb3ZlIHN0b3B3b3JkcwogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUgCiAgIyBvbmx5IGZpbHRlcmluZyB3b3JkIDIsIHNpbmNlIHdlIHdhbnQgImhlIiBhbmQgInNoZSIgdG8gYmUgYWJsZSB0byBzaG93IHVwIGluIGZpcnN0IHdvcmQKICBmaWx0ZXIoIXdvcmQyICVpbiUgc3RvcF93b3JkcyR3b3JkKSAlPiUgCiAgIyBQdXQgdGhlIHR3byB3b3JkIGNvbHVtbnMgYmFjayB0b2dldGhlcgogIHVuaXRlKGJpZ3JhbSwgd29yZDEsIHdvcmQyLCBzZXAgPSAiICIpICU+JQogIGZpbHRlcihiaWdyYW0gIT0gIk5BIE5BIikgJT4lCiAgIyBGaW5kIGJpZ3JhbXMgdGhhdCBzdGFydCB3aXRoIGhlIG9yIHNoZSBhbmQgZW5kIHdpdGggYSBzcGFjZQogIGZpbHRlcihzdHJfZGV0ZWN0KGJpZ3JhbSwgIl5oZSAiKSB8IHN0cl9kZXRlY3QoYmlncmFtLCAiXnNoZSAiKSkgJT4lCiAgIyBDb3VudCBhbGwgdGhlIGJpZ3JhbXMgaW4gZWFjaCBwbGF5CiAgY291bnQodGl0bGUsIGJpZ3JhbSwgc29ydCA9IFRSVUUpICU+JSAKICAjIEtlZXAgdG9wIDE1IGluIGVhY2ggcGxheQogIGdyb3VwX2J5KHRpdGxlKSAlPiUgCiAgdG9wX24oMTUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogICMgTWFrZSB0aGUgYmlncmFtcyBhbiBvcmRlcmVkIGZhY3RvciBzbyB0aGV5IHBsb3QgaW4gb3JkZXIKICBtdXRhdGUoYmlncmFtID0gZmN0X2lub3JkZXIoYmlncmFtKSkKCnByb25vdW5fdmVyYnMgPSBwcm9ub3VuX2JpZ3JhbXMgJT4lIAogIHNlcGFyYXRlKGJpZ3JhbSwgaW50byA9IGMoInByb25vdW4iLCAidmVyYiIpKSAlPiUKICBtdXRhdGUoCiAgICBuX3Bvc19uZWcgPSBpZmVsc2UocHJvbm91biA9PSAiaGUiLCBuLCAtbikKICApICU+JQogIGdyb3VwX2J5KHRpdGxlLCB2ZXJiKSAlPiUKICBzdW1tYXJpemUobl9tYWxlID0gc3VtKG5fcG9zX25lZykpCgpnZ3Bsb3QocHJvbm91bl92ZXJicywgYWVzKHkgPSBmY3RfcmV2KHZlcmIpLCB4ID0gbl9tYWxlLCBmaWxsID0gbl9tYWxlID4gMCkpICsgCiAgZ2VvbV9jb2woKSArIAogIGd1aWRlcyhmaWxsID0gRkFMU0UpICsKICBsYWJzKHkgPSAiQ291bnQiLCB4ID0gTlVMTCwgCiAgICAgICB0aXRsZSA9ICJNb3N0IENvbW1vbiBWZXJicyB1c2VkIHdpdGggaGUgdnMgc2hlIHByb25vdW4iLAogICAgICAgc3VidGl0bGUgPSAiUG9zaXRpdmUgPSBNb3JlIGFzc29jaWF0ZWQgd2l0aCBoZSIpICsKICBmYWNldF93cmFwKHZhcnModGl0bGUpLCBzY2FsZXMgPSAiZnJlZSIpICsgCiAgbGFiMTJfdGhlbWUgKyAKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAiQyIsIGVuZCA9IC43NSkgKwogIGd1aWRlcyhmaWxsID0gRkFMU0UpCmBgYAoKVGhpcyBpc24ndCBzdXBlciBpbnRlcmVzdGluZyBpbiB0aGlzIGNhc2UsIHNpbmNlIG1vc3QgY2hhcmFjdGVycyBqdXN0IHRhbGsgdG8gb25lIGFub3RoZXIgaW4gdGhlc2UgYm9va3MsIGJ1dCBtYXliZSB5b3Ugd2lsbCBwaWNrIGJvb2tzIHdpdGggbW9yZSBpbnRlcmVzdGluZyB2ZXJicyEgCgojIFBhcnQgMjogT24geW91ciBvd24KICAKRG93bmxvYWQgMysgYm9va3MgYnkgc29tZSBhdXRob3Igb24gW1Byb2plY3QgR3V0ZW5iZXJnXShodHRwOi8vd3d3Lmd1dGVuYmVyZy5vcmcvKS4gSmFuZSBBdXN0ZW4sIFZpY3RvciBIdWdvLCBMdWN5IE1hdWQgTW9udGdvbWVyeSwgQXJ0aHVyIENvbmFuIERveWxlLCBNYXJrIFR3YWluLCBIZW5yeSBEYXZpZCBUaG9yZWF1LCBGeW9kb3IgRG9zdG95ZXZza3ksIExlbyBUb2xzdG95LiBBbnlvbmUuIEp1c3QgbWFrZSBzdXJlIGl04oCZcyBhbGwgZnJvbSB0aGUgc2FtZSBhdXRob3IuIElmIHlvdSBhcmUgYWJsZSB0byBmaW5kIGRvd25sb2FkYWJsZSBib29rcyBlbHNld2hlcmUgKGxpa2UgdGhlICpIYXJyeSBQb3R0ZXIqIGV4YW1wbGUgZnJvbSBjbGFzcykgYW5kIHByZWZlciB0byBkbyB0aGF0LCB0aGF0J3MgT0sgdG9vISBZb3UganVzdCBtaWdodCBoYXZlIHRvIGRvIGEgYml0IG1vcmUgc3RhcnQgdXAgd29yayB0byBnZXQgdGhlIGRhdGEgaW50byBhIHRpZHkgZm9ybWF0LiAKCk1ha2UgdGhlc2UgdHdvIHBsb3RzIGFuZCBkZXNjcmliZSB3aGF0IGVhY2ggdGVsbCBhYm91dCB5b3VyIGF1dGhvcuKAmXMgYm9va3M6CgoxLiBUb3AgMTAgbW9zdCBmcmVxdWVudCB3b3JkcyBpbiBlYWNoIGJvb2sgT1IgdG9wIDEwIG1vc3QgdW5pcXVlIHdvcmRzIGluIGVhY2ggYm9vayAoaS5lLiB0Zi1pZGYpCjMuIEFueSBvdGhlciBwbG90IG9mIHlvdXIgY2hvaWNlCgpZb3UgY2FuIGtuaXQgeW91ciAuUm1kIGZpbGUgdG8gYW55IGZpbGUgZm9ybWF0IHlvdSB3aXNoISAK