### Load standardpackages
library(tidyverse) # Collection of all the good stuff like dplyr, ggplot2 ect.
library(magrittr) # For extra-piping operators (eg. %<>%)

This session

  • Now, that we have some experience with short texts, let’s try out to work with longer texts.
  • We will be analysing a whole (very long) book.
  • We will continue using more of the tidytext functionality.
  • However, we will also introduce Spacy, a high-level DeepLearning based NLP library that will help us to do complex stuff with not too much code

Exploring ‘Crime and Punishment’

Let’s raise the bar with some Fyodor Dostoevsky

library(tidytext)

Download Data

# We first need to get the book text. It can be conveniently retrieved via the gutenbergr library, linking r to the Gutenberg project
library(gutenbergr)
# check the id of crime and punishment
gutenberg_metadata %>%
  filter(title == "Crime and Punishment")
text_raw <- gutenberg_download(2554)
text_raw %>% glimpse()
Rows: 22,061
Columns: 2
$ gutenberg_id <int> 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 255…
$ text         <chr> "CRIME AND PUNISHMENT", "", "By Fyodor Dostoevsky", "", "", "", "Tran…
# LEts take a look
text_raw %>% head(200)

Preprocessing

  • We see the data is read in by line rather than one cell per book/chapter, or paragraph.
  • We also see all original line breaks are contained, including empty lines
  • The real book starts at line 102
  • The parts and chapters are assigned, info we can probably use.
  • to create IDs for the text chunks, we could use the linenumber
  • Then we could already get rid of empty and chapter lines
text <- text_raw %>%
  select(-gutenberg_id) %>%
  slice(-c(1:102)) %>%
  mutate(line_num = row_number(),# create new variable line_num
         part = cumsum(str_detect(text, regex("^PART [\\divxlc]",
                                                  ignore_case = TRUE)))) %>% # create variable part: Crime and Punishment has 7 parts %>%
         group_by(part) %>%
         mutate(chapter = cumsum(str_detect(text, regex("^CHAPTER [\\divxlc]",
                                                          ignore_case = TRUE)))) %>% # create new variable number of Chapter per part %>%
         ungroup() %>%
  filter(text != "" & !str_detect(text, regex("^[PART|CHAPTER]"))) %>%
  mutate(index = 1:n()) %>%
  relocate(index, line_num, part, chapter, text)
  
text %>% glimpse()
Rows: 16,548
Columns: 5
$ index    <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21…
$ line_num <int> 7, 8, 9, 12, 13, 14, 15, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27, 28, 29, …
$ part     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ chapter  <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1…
$ text     <chr> "On an exceptionally hot evening early in July a young man came out of", …

Cool!

text_tidy <- text %>% unnest_tokens(word, text, token = 'words') %>%
  anti_join(stop_words, by = 'word')
text_tidy %>% head(10)

Stemming

  • We will not use it here a lot, but consider stemming as a possible pre-processing option.
  • Here, words will be reduced to their common word-stem, eg. words like “analysis”, “analyze”, “analyzing” etc. will all be reduced to “analyz”
  • This sometimes makes it easier to work with word token, however, also is sometimes to aggresive and reduces interpretability.
library(SnowballC) # Includes stemming algos
text_tidy %>%
  mutate(stem = wordStem(word)) %>%
  head(10)
text_tidy %>%
  mutate(stem = wordStem(word)) %>%
  count(stem, sort = TRUE) %>%
  head(50)

First exploration

Topwords

# top 10 words used in Crime and Punishment
text_tidy %>%
  count(word, sort = TRUE) %>%
  slice(1:10) %>%
  ggplot(aes(x = fct_reorder(word, n), y = n, fill = word)) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  labs(title = "Crime and Punishment: Top 10 words used", x = NULL) 

  • Unsurprisingly, the word used more often corresponds to the name of the main character, Raskolnikov.
  • We can also use a word cloud:
# People love wordclouds
library(wordcloud)

text_tidy %>%
  count(word) %>%
  with(wordcloud(word, n, 
                 max.words = 50, 
                 color = "blue"))

Sentiment Analysis

  • While interesting, word frequency does not tell us much about the emotions/states of mind present in the novel.

  • For this reason, we will go ahead with a sentiment analysis of “Crime and Punishment”

  • While sounding very comlpex, sentiment analysis is usually done in a rather simple way.

  • There are already predefined sentiment lexica around, linking words ith certain sentiments

  • So we do have to only join our word-token with the corresponding sentiments

  • The most popular dictionaries available are

    1. “bing”: classifies words binary into positive and negative sentiment
    2. “nrc” has the following emotion categories: positive, negative, anger, anticipation, disgust, fear, joy, sadness, surprise, and trust; and
    3. “afinn” corresponds to a sentiment score from -5 (very negative) to 5 (very positive).
# You might need to first install the 'textdata' package for some of the lexica
get_sentiments("bing") %>% 
  head(20)

Lets calculate them all

sentiment_bing <- text_tidy %>%
  inner_join(get_sentiments("bing")) %>%
  count(chapter, index = index %/% 100, sentiment) %>% # index of 100 lines of text
  mutate(lexicon = 'Bing')
  
sentiment_nrc <- text_tidy %>%  
  inner_join(get_sentiments("nrc")) %>%
  count(chapter, index = index %/% 100, sentiment) %>% # index of 100 lines of text
  mutate(lexicon = 'NRC')

sentiment_afinn <- text_tidy %>%  
  select(-line_num, -part) %>%
  inner_join(get_sentiments("afinn")) %>%
  group_by(index = index %/% 100)  %>% # index of 100 lines of text
  summarise(sentiment = sum(value, na.rm = TRUE)) %>%
  mutate(lexicon = 'AFINN')
# Lets join them all together for plotting
sentiment_all <- sentiment_afinn %>%
  bind_rows(sentiment_bing %>%
              pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>% 
              mutate(sentiment = positive - negative) %>%
              select(index, sentiment, lexicon) ) %>%
    bind_rows(sentiment_nrc %>%
              pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>% 
              mutate(sentiment = positive - negative) %>%
              select(index, sentiment, lexicon) ) 
# crime and punishment - 
sentiment_all %>%
  ggplot(aes(x = index, y = sentiment, fill = lexicon)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ lexicon) + 
  labs(title = "Sentiment Analysis: “Crime and Punishment",
       subtitle = 'Using the Bing, NRC, AFINN lexicon') 

  • Since NRS also provides us with options to dive deeper into specific, rather than only positive and negative sentiments.
text_tidy %>%
  inner_join(get_sentiments("nrc")) %>%
  count(word, sentiment, sort = TRUE) %>% 
  filter(sentiment %in% c("joy", "sadness")) %>%
  pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
  as.data.frame() %>% 
  remove_rownames() %>% 
  column_to_rownames("word") %>% 
  comparison.cloud(colors = c("darkgreen", "grey75"), 
                   max.words = 100,
                   title.size = 1.5)

# crime and punishment - 
sentiment_nrc %>%
  ggplot(aes(x = index, y = sentiment, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ chapter) + 
  labs(title = "Sentiment Analysis: “Crime and Punishment",
       subtitle = 'By chapter: Using NRC lexicon') 

Word network

  • So far we’ve considered words as individual units, and considered their relationships to sentiments or to documents.
  • However, many interesting text analyses are based on the relationships between words, whether examining which words tend to follow others immediately, or that tend to co-occur within the same documents.
  • Obviously, we no have to sprinkle in some networks here
  • The easiest way would bee to look t words that frequently occur together.
  • We could do that in a more complicated way, but since we already have the line-nubmer, why not start with creating edges between words in the same line?
  • tidytext had an amazing function pairwise_count for that. However, since it is of more general use, the developers outsourced it into the not-text-specific widyr package, which they also maintain.
library(widyr)
el_words <- text_tidy %>%
  pairwise_count(word, index, sort = TRUE) %>%
  rename(from = item1, to = item2, weight = n)
el_words %>% head()
library(tidygraph)
library(ggraph)
g <- el_words %>%
  filter(weight >= 9) %>%
  as_tbl_graph(directed = FALSE) %>%
  igraph::simplify() %>% as_tbl_graph() 
set.seed(1337)
g %N>%
#  filter(centrality_degree(weight = weight) > 100) %>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(width = weight, edge_alpha = weight)) +
  geom_node_point(aes(size = centrality_degree(weight = weight)), color = "plum4") +
  geom_node_text(aes(label = name,), repel = TRUE) +
  theme_graph() +
  theme(legend.position = 'none') +
  labs(title = 'Co-Word Network Crime and Punishment')

Bigrams and n-grams

  • Often, a meaning is not only unfolded by a single word, but a combination of words, such as in eg. “Machine Learning”, “Entity Recognition” etc.
  • Likewise, we have been using the unnest_tokens function to tokenize by word, or sometimes by sentence, which is useful for the kinds of sentiment and frequency analyses we’ve been doing so far.
  • But we can also use the function to tokenize into consecutive sequences of words, called n-grams.
  • To do so, we just have to supply the token = 'ngrams' argument, and specify how many subsequent words we want to consider.
text_tidy_ngrams <- text %>% 
  unnest_tokens(bigram, text, token = 'ngrams', n = 2) %>%
  na.omit()
text_tidy_ngrams %>% head()
text_tidy_ngrams %>% 
  count(bigram, sort = TRUE) %>%
  head(100)
  • We now also can get rid of stopwords like before
  • However, there we first have to seperate the bigrams again, then get rid of stopwords, and then we can unite them again.
# Seperate them
text_tidy_ngrams %<>%
  separate(bigram, c("word1", "word2"), sep = " ")
text_tidy_ngrams %>% head()
# Get rid of stopwords
text_tidy_ngrams %<>%
  anti_join(stop_words, by = c('word1' = 'word')) %>%
  anti_join(stop_words, by = c('word2' = 'word')) 
# And unite again
text_tidy_ngrams %<>%
  unite(bigram, word1, word2, sep = " ")
# And finally count
text_tidy_ngrams %<>%
  count(index, bigram)
text_tidy_ngrams %>%
  count(bigram, wt = n, sort = TRUE) %>%
  head(50)
  • These bigrams could now be analyzed on their own, or used as token for all the stuff we can do, for instance topic modelling, ML, etc….

Just for fun, a little gender analysis now…

# Define our pronouns
pronouns <- c("he", "she")
# Get our bigram where first word is a pronoun
gender_bigrams <-  text %>% 
  unnest_tokens(bigram, text, token = 'ngrams', n = 2) %>%
  na.omit() %>%
  count(bigram) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>%
  filter(word1 %in% pronouns) %>%
  count(word1, word2, wt = n, sort = TRUE) 

Named Entity Recognition (introducing spacy)

  • In this part of the tutorial, I will introduce Spacy, a high-level DeepLearning based NLP library that will help us to do complex stuff with not too much code and without having do go deep into “old-school-NLP”

{width=600}

  • Here a nice cheat-sheet
  • Spacy is today one of the leading solutions for NLP in industry which goes as far as them hosting a whole conference with the leading NLP experts worldwide in Berlin last summer
  • It is implemented as a Python package, but accessible via the spacyr wrapper.
  • Most documentation will be found for python, but almost all functions afe available in the r version in the same way.
library(spacyr)
# spacy_install() # creates a new conda environment called spacy_condaenv, as long as some version of conda is installed 
spacy_initialize(model = "en_core_web_sm")
NULL
text_example <- c(d1 = "spaCy is great at fast natural language processing. Everybody loves it! 
                  In Denmark and elsewhere.",
                  d2 = "We can also use it in R via the great spacyR wrapper. Daniel Michels does that sometimes")
# process documents and obtain a data.table
text_parsed <- spacy_parse(text_example)
text_parsed
  • Dependency parsing: Detailed parsing of syntactic dependencies is possible with the dependency = TRUE option:
text_example %>% spacy_parse(dependency = TRUE, lemma = FALSE, pos = FALSE)
text_chapter <- text %>%
  group_by(chapter) %>%
  summarise(text = paste(text, collapse = ' ')) %>%
  pivot_wider(names_from = chapter, values_from = text)
text_entities <-text_chapter %>% as.character() %>% spacy_parse(entity = TRUE)
text_entities %>% head(1000)
text_entities %>% head(1000)

Cool or cool?

We can now again create a network, right? this time really a character network!

el_persons <- text_entities %>% 
  entity_consolidate() %>%
  filter(entity_type %>% str_detect('PERSON')) %>%
  unite(chap_sent_id, doc_id, sentence_id, sep = '_') %>%
  pairwise_count(token, chap_sent_id, sort = TRUE) %>%
  rename(from = item1, to = item2, weight = n)
el_persons %>% head(50)
el_persons %>% head(50)
g <- el_persons %>%
  as_tbl_graph(directed = FALSE) %>%
  igraph::simplify() %>% as_tbl_graph() 
set.seed(1337)
g %N>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(width = weight, edge_alpha = weight)) +
  geom_node_point(aes(size = centrality_degree(weight = weight)), color = "plum4") +
  geom_node_text(aes(label = name,), repel = TRUE) +
  theme_graph() +
  theme(legend.position = 'none') +
  labs(title = 'Character Network Crime and Punishment')

set.seed(1337)
g %N>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(width = weight, edge_alpha = weight)) +
  geom_node_point(aes(size = centrality_degree(weight = weight)), color = "plum4") +
  geom_node_text(aes(label = name,), repel = TRUE) +
  theme_graph() +
  theme(legend.position = 'none') +
  labs(title = 'Character Network Crime and Punishment')

Endnotes

Main reference

  • Julia Silge and David Robinson (2020). Text Mining with R: A Tidy Approach, O’Reilly. Online available here
    • Chapter 2: Introduction to sentiment analysis
  • Emil Hvidfeldt and Julia Silge (2020). Supervised Machine Learning for Text Analysis in R, online available here

Packages & Ecosystem

further: * spacyr: R wrapper for spaCy. Also check this tutorial

Suggestions for further study

DataCamp (!Most courses have somewhat outdated ecosystems)

Session Info

sessionInfo()
LS0tCnRpdGxlOiAnTmF0dXJhbC1sYW5ndWFnZS1Qcm9jZXNzaW5nIChSKScKYXV0aG9yOiAiRGFuaWVsIFMuIEhhaW4gKGRzaEBidXNpbmVzcy5hYXUuZGspIgpkYXRlOiAiVXBkYXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgdGhlbWU6IGZsYXRseQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIyMgR2VuZXJpYyBwcmVhbWJsZQpybShsaXN0PWxzKCkpClN5cy5zZXRlbnYoTEFORyA9ICJlbiIpICMgRm9yIGVuZ2xpc2ggbGFuZ3VhZ2UKb3B0aW9ucyhzY2lwZW4gPSA1KSAjIFRvIGRlYWN0aXZhdGUgYW5ub3lpbmcgc2NpZW50aWZpYyBudW1iZXIgbm90YXRpb24KCiMjIyBLbml0ciBvcHRpb25zCmxpYnJhcnkoa25pdHIpICMgRm9yIGRpc3BsYXkgb2YgdGhlIG1hcmtkb3duCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICBjb21tZW50PUZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduPSJjZW50ZXIiCiAgICAgICAgICAgICAgICAgICAgICkKYGBgCgpgYGB7cn0KIyMjIExvYWQgc3RhbmRhcmRwYWNrYWdlcwpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBDb2xsZWN0aW9uIG9mIGFsbCB0aGUgZ29vZCBzdHVmZiBsaWtlIGRwbHlyLCBnZ3Bsb3QyIGVjdC4KbGlicmFyeShtYWdyaXR0cikgIyBGb3IgZXh0cmEtcGlwaW5nIG9wZXJhdG9ycyAoZWcuICU8PiUpCmBgYAoKIyMjIFRoaXMgc2Vzc2lvbgoKKiBOb3csIHRoYXQgd2UgaGF2ZSBzb21lIGV4cGVyaWVuY2Ugd2l0aCBzaG9ydCB0ZXh0cywgbGV0J3MgdHJ5IG91dCB0byB3b3JrIHdpdGggbG9uZ2VyIHRleHRzLiAKKiBXZSB3aWxsIGJlIGFuYWx5c2luZyAgYSB3aG9sZSAodmVyeSBsb25nKSBib29rLiAKKiBXZSB3aWxsIGNvbnRpbnVlIHVzaW5nIG1vcmUgb2YgdGhlIGB0aWR5dGV4dGAgZnVuY3Rpb25hbGl0eS4KKiBIb3dldmVyLCB3ZSB3aWxsIGFsc28gaW50cm9kdWNlIGBTcGFjeWAsIGEgaGlnaC1sZXZlbCBEZWVwTGVhcm5pbmcgYmFzZWQgTkxQIGxpYnJhcnkgdGhhdCB3aWxsIGhlbHAgdXMgdG8gZG8gY29tcGxleCBzdHVmZiB3aXRoIG5vdCB0b28gbXVjaCBjb2RlIAoKIyBFeHBsb3JpbmcgJ0NyaW1lIGFuZCBQdW5pc2htZW50JwoKTGV0J3MgcmFpc2UgdGhlIGJhciB3aXRoIHNvbWUgKipGeW9kb3IgRG9zdG9ldnNreSoqCgohW10oaHR0cHM6Ly9pLnBpbmltZy5jb20vNTY0eC9iYy9lYi85Yy9iY2ViOWNlZjk5YWJiZWQ1MmI5NDA3NjdjOTUzMGJiYy5qcGcpCgpgYGB7cn0KbGlicmFyeSh0aWR5dGV4dCkKYGBgCgojIyBEb3dubG9hZCBEYXRhCgpgYGB7cn0KIyBXZSBmaXJzdCBuZWVkIHRvIGdldCB0aGUgYm9vayB0ZXh0LiBJdCBjYW4gYmUgY29udmVuaWVudGx5IHJldHJpZXZlZCB2aWEgdGhlIGd1dGVuYmVyZ3IgbGlicmFyeSwgbGlua2luZyByIHRvIHRoZSBHdXRlbmJlcmcgcHJvamVjdApsaWJyYXJ5KGd1dGVuYmVyZ3IpCmBgYAoKYGBge3J9CiMgY2hlY2sgdGhlIGlkIG9mIGNyaW1lIGFuZCBwdW5pc2htZW50Cmd1dGVuYmVyZ19tZXRhZGF0YSAlPiUKICBmaWx0ZXIodGl0bGUgPT0gIkNyaW1lIGFuZCBQdW5pc2htZW50IikKYGBgCgpgYGB7cn0KdGV4dF9yYXcgPC0gZ3V0ZW5iZXJnX2Rvd25sb2FkKDI1NTQpCmBgYAoKYGBge3J9CnRleHRfcmF3ICU+JSBnbGltcHNlKCkKYGBgCgpgYGB7cn0KIyBMRXRzIHRha2UgYSBsb29rCnRleHRfcmF3ICU+JSBoZWFkKDIwMCkKYGBgCgojIyBQcmVwcm9jZXNzaW5nCgoqIFdlIHNlZSB0aGUgZGF0YSBpcyByZWFkIGluIGJ5IGxpbmUgcmF0aGVyIHRoYW4gb25lIGNlbGwgcGVyIGJvb2svY2hhcHRlciwgb3IgcGFyYWdyYXBoLgoqIFdlIGFsc28gc2VlIGFsbCBvcmlnaW5hbCBsaW5lIGJyZWFrcyBhcmUgY29udGFpbmVkLCBpbmNsdWRpbmcgZW1wdHkgbGluZXMKKiBUaGUgcmVhbCBib29rIHN0YXJ0cyBhdCBsaW5lIDEwMgoqIFRoZSBwYXJ0cyBhbmQgY2hhcHRlcnMgYXJlIGFzc2lnbmVkLCBpbmZvIHdlIGNhbiBwcm9iYWJseSB1c2UuCiogdG8gY3JlYXRlIElEcyBmb3IgdGhlIHRleHQgY2h1bmtzLCB3ZSBjb3VsZCB1c2UgdGhlIGxpbmVudW1iZXIKKiBUaGVuIHdlIGNvdWxkIGFscmVhZHkgZ2V0IHJpZCBvZiBlbXB0eSBhbmQgY2hhcHRlciBsaW5lcwoKYGBge3J9CnRleHQgPC0gdGV4dF9yYXcgJT4lCiAgc2VsZWN0KC1ndXRlbmJlcmdfaWQpICU+JQogIHNsaWNlKC1jKDE6MTAyKSkgJT4lCiAgbXV0YXRlKGxpbmVfbnVtID0gcm93X251bWJlcigpLCMgY3JlYXRlIG5ldyB2YXJpYWJsZSBsaW5lX251bQogICAgICAgICBwYXJ0ID0gY3Vtc3VtKHN0cl9kZXRlY3QodGV4dCwgcmVnZXgoIl5QQVJUIFtcXGRpdnhsY10iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gVFJVRSkpKSkgJT4lICMgY3JlYXRlIHZhcmlhYmxlIHBhcnQ6IENyaW1lIGFuZCBQdW5pc2htZW50IGhhcyA3IHBhcnRzICU+JQogICAgICAgICBncm91cF9ieShwYXJ0KSAlPiUKICAgICAgICAgbXV0YXRlKGNoYXB0ZXIgPSBjdW1zdW0oc3RyX2RldGVjdCh0ZXh0LCByZWdleCgiXkNIQVBURVIgW1xcZGl2eGxjXSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZ25vcmVfY2FzZSA9IFRSVUUpKSkpICU+JSAjIGNyZWF0ZSBuZXcgdmFyaWFibGUgbnVtYmVyIG9mIENoYXB0ZXIgcGVyIHBhcnQgJT4lCiAgICAgICAgIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIodGV4dCAhPSAiIiAmICFzdHJfZGV0ZWN0KHRleHQsIHJlZ2V4KCJeW1BBUlR8Q0hBUFRFUl0iKSkpICU+JQogIG11dGF0ZShpbmRleCA9IDE6bigpKSAlPiUKICByZWxvY2F0ZShpbmRleCwgbGluZV9udW0sIHBhcnQsIGNoYXB0ZXIsIHRleHQpCiAgCmBgYAoKYGBge3J9CnRleHQgJT4lIGdsaW1wc2UoKQpgYGAKCkNvb2whCgpgYGB7cn0KdGV4dF90aWR5IDwtIHRleHQgJT4lIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCwgdG9rZW4gPSAnd29yZHMnKSAlPiUKICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnkgPSAnd29yZCcpCmBgYAoKYGBge3J9CnRleHRfdGlkeSAlPiUgaGVhZCgxMCkKYGBgCgojIyBTdGVtbWluZwoKKiBXZSB3aWxsIG5vdCB1c2UgaXQgaGVyZSBhIGxvdCwgYnV0IGNvbnNpZGVyIHN0ZW1taW5nIGFzIGEgcG9zc2libGUgcHJlLXByb2Nlc3Npbmcgb3B0aW9uLgoqIEhlcmUsIHdvcmRzIHdpbGwgYmUgcmVkdWNlZCB0byB0aGVpciBjb21tb24gd29yZC1zdGVtLCBlZy4gd29yZHMgbGlrZSAiYW5hbHlzaXMiLCAiYW5hbHl6ZSIsICJhbmFseXppbmciIGV0Yy4gd2lsbCBhbGwgYmUgcmVkdWNlZCB0byAiYW5hbHl6IgoqIFRoaXMgc29tZXRpbWVzIG1ha2VzIGl0IGVhc2llciB0byB3b3JrIHdpdGggd29yZCB0b2tlbiwgaG93ZXZlciwgYWxzbyBpcyBzb21ldGltZXMgdG8gYWdncmVzaXZlIGFuZCByZWR1Y2VzIGludGVycHJldGFiaWxpdHkuCgpgYGB7cn0KbGlicmFyeShTbm93YmFsbEMpICMgSW5jbHVkZXMgc3RlbW1pbmcgYWxnb3MKYGBgCgoKYGBge3J9CnRleHRfdGlkeSAlPiUKICBtdXRhdGUoc3RlbSA9IHdvcmRTdGVtKHdvcmQpKSAlPiUKICBoZWFkKDEwKQpgYGAKCmBgYHtyfQp0ZXh0X3RpZHkgJT4lCiAgbXV0YXRlKHN0ZW0gPSB3b3JkU3RlbSh3b3JkKSkgJT4lCiAgY291bnQoc3RlbSwgc29ydCA9IFRSVUUpICU+JQogIGhlYWQoNTApCmBgYAoKIyBGaXJzdCBleHBsb3JhdGlvbgoKIyMgVG9wd29yZHMKCmBgYHtyfQojIHRvcCAxMCB3b3JkcyB1c2VkIGluIENyaW1lIGFuZCBQdW5pc2htZW50CnRleHRfdGlkeSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lCiAgc2xpY2UoMToxMCkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gZmN0X3Jlb3JkZXIod29yZCwgbiksIHkgPSBuLCBmaWxsID0gd29yZCkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHRpdGxlID0gIkNyaW1lIGFuZCBQdW5pc2htZW50OiBUb3AgMTAgd29yZHMgdXNlZCIsIHggPSBOVUxMKSAKYGBgCgoqIFVuc3VycHJpc2luZ2x5LCB0aGUgd29yZCB1c2VkIG1vcmUgb2Z0ZW4gY29ycmVzcG9uZHMgdG8gdGhlIG5hbWUgb2YgdGhlIG1haW4gY2hhcmFjdGVyLCBSYXNrb2xuaWtvdi4gCiogV2UgY2FuIGFsc28gdXNlIGEgd29yZCBjbG91ZDoKCmBgYHtyfQojIFBlb3BsZSBsb3ZlIHdvcmRjbG91ZHMKbGlicmFyeSh3b3JkY2xvdWQpCgp0ZXh0X3RpZHkgJT4lCiAgY291bnQod29yZCkgJT4lCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgCiAgICAgICAgICAgICAgICAgbWF4LndvcmRzID0gNTAsIAogICAgICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiKSkKYGBgCgojIyBTZW50aW1lbnQgQW5hbHlzaXMKCiogV2hpbGUgaW50ZXJlc3RpbmcsIHdvcmQgZnJlcXVlbmN5IGRvZXMgbm90IHRlbGwgdXMgbXVjaCBhYm91dCB0aGUgZW1vdGlvbnMvc3RhdGVzIG9mIG1pbmQgcHJlc2VudCBpbiB0aGUgbm92ZWwuIAoqIEZvciB0aGlzIHJlYXNvbiwgd2Ugd2lsbCBnbyBhaGVhZCB3aXRoIGEgc2VudGltZW50IGFuYWx5c2lzIG9mIOKAnENyaW1lIGFuZCBQdW5pc2htZW504oCdCgoqIFdoaWxlIHNvdW5kaW5nIHZlcnkgY29tbHBleCwgc2VudGltZW50IGFuYWx5c2lzIGlzIHVzdWFsbHkgZG9uZSBpbiBhIHJhdGhlciBzaW1wbGUgd2F5LgoqIFRoZXJlIGFyZSBhbHJlYWR5IHByZWRlZmluZWQgc2VudGltZW50IGxleGljYSBhcm91bmQsIGxpbmtpbmcgd29yZHMgaXRoIGNlcnRhaW4gc2VudGltZW50cwoqIFNvIHdlIGRvIGhhdmUgdG8gb25seSBqb2luIG91ciB3b3JkLXRva2VuIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgc2VudGltZW50cwoqIFRoZSBtb3N0IHBvcHVsYXIgZGljdGlvbmFyaWVzIGF2YWlsYWJsZSBhcmUKICAgMS4gImJpbmciOiBjbGFzc2lmaWVzIHdvcmRzIGJpbmFyeSBpbnRvIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBzZW50aW1lbnQKICAgMi4g4oCcbnJj4oCdICBoYXMgdGhlIGZvbGxvd2luZyBlbW90aW9uIGNhdGVnb3JpZXM6IHBvc2l0aXZlLCBuZWdhdGl2ZSwgYW5nZXIsIGFudGljaXBhdGlvbiwgZGlzZ3VzdCwgZmVhciwgam95LCBzYWRuZXNzLCBzdXJwcmlzZSwgYW5kIHRydXN0OyBhbmQKICAgMy4g4oCcYWZpbm7igJ0gY29ycmVzcG9uZHMgdG8gYSBzZW50aW1lbnQgc2NvcmUgZnJvbSAtNSAodmVyeSBuZWdhdGl2ZSkgdG8gNSAodmVyeSBwb3NpdGl2ZSkuIAoKCmBgYHtyfQojIFlvdSBtaWdodCBuZWVkIHRvIGZpcnN0IGluc3RhbGwgdGhlICd0ZXh0ZGF0YScgcGFja2FnZSBmb3Igc29tZSBvZiB0aGUgbGV4aWNhCmdldF9zZW50aW1lbnRzKCJiaW5nIikgJT4lIAogIGhlYWQoMjApCmBgYAoKTGV0cyBjYWxjdWxhdGUgdGhlbSBhbGwKCmBgYHtyfQpzZW50aW1lbnRfYmluZyA8LSB0ZXh0X3RpZHkgJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICBjb3VudChjaGFwdGVyLCBpbmRleCA9IGluZGV4ICUvJSAxMDAsIHNlbnRpbWVudCkgJT4lICMgaW5kZXggb2YgMTAwIGxpbmVzIG9mIHRleHQKICBtdXRhdGUobGV4aWNvbiA9ICdCaW5nJykKICAKc2VudGltZW50X25yYyA8LSB0ZXh0X3RpZHkgJT4lICAKICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSkgJT4lCiAgY291bnQoY2hhcHRlciwgaW5kZXggPSBpbmRleCAlLyUgMTAwLCBzZW50aW1lbnQpICU+JSAjIGluZGV4IG9mIDEwMCBsaW5lcyBvZiB0ZXh0CiAgbXV0YXRlKGxleGljb24gPSAnTlJDJykKCnNlbnRpbWVudF9hZmlubiA8LSB0ZXh0X3RpZHkgJT4lICAKICBzZWxlY3QoLWxpbmVfbnVtLCAtcGFydCkgJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lCiAgZ3JvdXBfYnkoaW5kZXggPSBpbmRleCAlLyUgMTAwKSAgJT4lICMgaW5kZXggb2YgMTAwIGxpbmVzIG9mIHRleHQKICBzdW1tYXJpc2Uoc2VudGltZW50ID0gc3VtKHZhbHVlLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBtdXRhdGUobGV4aWNvbiA9ICdBRklOTicpCmBgYAoKCmBgYHtyfQojIExldHMgam9pbiB0aGVtIGFsbCB0b2dldGhlciBmb3IgcGxvdHRpbmcKc2VudGltZW50X2FsbCA8LSBzZW50aW1lbnRfYWZpbm4gJT4lCiAgYmluZF9yb3dzKHNlbnRpbWVudF9iaW5nICU+JQogICAgICAgICAgICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzZW50aW1lbnQsIHZhbHVlc19mcm9tID0gbiwgdmFsdWVzX2ZpbGwgPSAwKSAlPiUgCiAgICAgICAgICAgICAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpICU+JQogICAgICAgICAgICAgIHNlbGVjdChpbmRleCwgc2VudGltZW50LCBsZXhpY29uKSApICU+JQogICAgYmluZF9yb3dzKHNlbnRpbWVudF9ucmMgJT4lCiAgICAgICAgICAgICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNlbnRpbWVudCwgdmFsdWVzX2Zyb20gPSBuLCB2YWx1ZXNfZmlsbCA9IDApICU+JSAKICAgICAgICAgICAgICBtdXRhdGUoc2VudGltZW50ID0gcG9zaXRpdmUgLSBuZWdhdGl2ZSkgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KGluZGV4LCBzZW50aW1lbnQsIGxleGljb24pICkgCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTE1fQojIGNyaW1lIGFuZCBwdW5pc2htZW50IC0gCnNlbnRpbWVudF9hbGwgJT4lCiAgZ2dwbG90KGFlcyh4ID0gaW5kZXgsIHkgPSBzZW50aW1lbnQsIGZpbGwgPSBsZXhpY29uKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBmYWNldF93cmFwKH4gbGV4aWNvbikgKyAKICBsYWJzKHRpdGxlID0gIlNlbnRpbWVudCBBbmFseXNpczog4oCcQ3JpbWUgYW5kIFB1bmlzaG1lbnQiLAogICAgICAgc3VidGl0bGUgPSAnVXNpbmcgdGhlIEJpbmcsIE5SQywgQUZJTk4gbGV4aWNvbicpIApgYGAKCiogU2luY2UgTlJTIGFsc28gcHJvdmlkZXMgdXMgd2l0aCBvcHRpb25zIHRvIGRpdmUgZGVlcGVyIGludG8gc3BlY2lmaWMsIHJhdGhlciB0aGFuIG9ubHkgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHNlbnRpbWVudHMuCgpgYGB7cn0KdGV4dF90aWR5ICU+JQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpKSAlPiUKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoImpveSIsICJzYWRuZXNzIikpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzZW50aW1lbnQsIHZhbHVlc19mcm9tID0gbiwgdmFsdWVzX2ZpbGwgPSAwKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHJlbW92ZV9yb3duYW1lcygpICU+JSAKICBjb2x1bW5fdG9fcm93bmFtZXMoIndvcmQiKSAlPiUgCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCJkYXJrZ3JlZW4iLCAiZ3JleTc1IiksIAogICAgICAgICAgICAgICAgICAgbWF4LndvcmRzID0gMTAwLAogICAgICAgICAgICAgICAgICAgdGl0bGUuc2l6ZSA9IDEuNSkKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTV9CiMgY3JpbWUgYW5kIHB1bmlzaG1lbnQgLSAKc2VudGltZW50X25yYyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBpbmRleCwgeSA9IHNlbnRpbWVudCwgZmlsbCA9IHNlbnRpbWVudCkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IGNoYXB0ZXIpICsgCiAgbGFicyh0aXRsZSA9ICJTZW50aW1lbnQgQW5hbHlzaXM6IOKAnENyaW1lIGFuZCBQdW5pc2htZW50IiwKICAgICAgIHN1YnRpdGxlID0gJ0J5IGNoYXB0ZXI6IFVzaW5nIE5SQyBsZXhpY29uJykgCmBgYAoKCiMjIyBXb3JkIG5ldHdvcmsKCiogU28gZmFyIHdl4oCZdmUgY29uc2lkZXJlZCB3b3JkcyBhcyBpbmRpdmlkdWFsIHVuaXRzLCBhbmQgY29uc2lkZXJlZCB0aGVpciByZWxhdGlvbnNoaXBzIHRvIHNlbnRpbWVudHMgb3IgdG8gZG9jdW1lbnRzLiAKKiBIb3dldmVyLCBtYW55IGludGVyZXN0aW5nIHRleHQgYW5hbHlzZXMgYXJlIGJhc2VkIG9uIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gd29yZHMsIHdoZXRoZXIgZXhhbWluaW5nIHdoaWNoIHdvcmRzIHRlbmQgdG8gZm9sbG93IG90aGVycyBpbW1lZGlhdGVseSwgb3IgdGhhdCB0ZW5kIHRvIGNvLW9jY3VyIHdpdGhpbiB0aGUgc2FtZSBkb2N1bWVudHMuCiogT2J2aW91c2x5LCB3ZSBubyBoYXZlIHRvIHNwcmlua2xlIGluIHNvbWUgbmV0d29ya3MgaGVyZQoqIFRoZSBlYXNpZXN0IHdheSB3b3VsZCBiZWUgdG8gbG9vayB0IHdvcmRzIHRoYXQgZnJlcXVlbnRseSBvY2N1ciB0b2dldGhlci4KKiBXZSBjb3VsZCBkbyB0aGF0IGluIGEgbW9yZSBjb21wbGljYXRlZCB3YXksIGJ1dCBzaW5jZSB3ZSBhbHJlYWR5IGhhdmUgdGhlIGxpbmUtbnVibWVyLCB3aHkgbm90IHN0YXJ0IHdpdGggY3JlYXRpbmcgZWRnZXMgYmV0d2VlbiB3b3JkcyBpbiB0aGUgc2FtZSBsaW5lPwoqIGB0aWR5dGV4dGAgaGFkIGFuIGFtYXppbmcgZnVuY3Rpb24gYHBhaXJ3aXNlX2NvdW50YCBmb3IgdGhhdC4gSG93ZXZlciwgc2luY2UgaXQgaXMgb2YgbW9yZSBnZW5lcmFsIHVzZSwgIHRoZSBkZXZlbG9wZXJzIG91dHNvdXJjZWQgaXQgaW50byB0aGUgbm90LXRleHQtc3BlY2lmaWMgYHdpZHlyYCBwYWNrYWdlLCB3aGljaCB0aGV5IGFsc28gbWFpbnRhaW4uCgpgYGB7cn0KbGlicmFyeSh3aWR5cikKZWxfd29yZHMgPC0gdGV4dF90aWR5ICU+JQogIHBhaXJ3aXNlX2NvdW50KHdvcmQsIGluZGV4LCBzb3J0ID0gVFJVRSkgJT4lCiAgcmVuYW1lKGZyb20gPSBpdGVtMSwgdG8gPSBpdGVtMiwgd2VpZ2h0ID0gbikKYGBgCgpgYGB7cn0KZWxfd29yZHMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmBgYAoKYGBge3J9CmcgPC0gZWxfd29yZHMgJT4lCiAgZmlsdGVyKHdlaWdodCA+PSA5KSAlPiUKICBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGQUxTRSkgJT4lCiAgaWdyYXBoOjpzaW1wbGlmeSgpICU+JSBhc190YmxfZ3JhcGgoKSAKYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0Kc2V0LnNlZWQoMTMzNykKZyAlTj4lCiMgIGZpbHRlcihjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHQgPSB3ZWlnaHQpID4gMTAwKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IHdlaWdodCwgZWRnZV9hbHBoYSA9IHdlaWdodCkpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHQgPSB3ZWlnaHQpKSwgY29sb3IgPSAicGx1bTQiKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSwpLCByZXBlbCA9IFRSVUUpICsKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICsKICBsYWJzKHRpdGxlID0gJ0NvLVdvcmQgTmV0d29yayBDcmltZSBhbmQgUHVuaXNobWVudCcpCmBgYAoKIyMgQmlncmFtcyBhbmQgbi1ncmFtcwoKKiBPZnRlbiwgYSBtZWFuaW5nIGlzIG5vdCBvbmx5IHVuZm9sZGVkIGJ5IGEgc2luZ2xlIHdvcmQsIGJ1dCBhIGNvbWJpbmF0aW9uIG9mIHdvcmRzLCBzdWNoIGFzIGluIGVnLiAiTWFjaGluZSBMZWFybmluZyIsICJFbnRpdHkgUmVjb2duaXRpb24iIGV0Yy4KKiBMaWtld2lzZSwgd2UgaGF2ZSBiZWVuIHVzaW5nIHRoZSBgdW5uZXN0X3Rva2Vuc2AgZnVuY3Rpb24gdG8gdG9rZW5pemUgYnkgd29yZCwgb3Igc29tZXRpbWVzIGJ5IHNlbnRlbmNlLCB3aGljaCBpcyB1c2VmdWwgZm9yIHRoZSBraW5kcyBvZiBzZW50aW1lbnQgYW5kIGZyZXF1ZW5jeSBhbmFseXNlcyB3ZeKAmXZlIGJlZW4gZG9pbmcgc28gZmFyLiAKKiBCdXQgd2UgY2FuIGFsc28gdXNlIHRoZSBmdW5jdGlvbiB0byB0b2tlbml6ZSBpbnRvIGNvbnNlY3V0aXZlIHNlcXVlbmNlcyBvZiB3b3JkcywgY2FsbGVkIG4tZ3JhbXMuIAoqIFRvIGRvIHNvLCB3ZSBqdXN0IGhhdmUgdG8gc3VwcGx5IHRoZSBgdG9rZW4gPSAnbmdyYW1zJ2AgYXJndW1lbnQsIGFuZCBzcGVjaWZ5IGhvdyBtYW55IHN1YnNlcXVlbnQgd29yZHMgd2Ugd2FudCB0byBjb25zaWRlci4KCmBgYHtyfQp0ZXh0X3RpZHlfbmdyYW1zIDwtIHRleHQgJT4lIAogIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICduZ3JhbXMnLCBuID0gMikgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CnRleHRfdGlkeV9uZ3JhbXMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQp0ZXh0X3RpZHlfbmdyYW1zICU+JSAKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKSAlPiUKICBoZWFkKDEwMCkKYGBgIAoKKiBXZSBub3cgYWxzbyBjYW4gZ2V0IHJpZCBvZiBzdG9wd29yZHMgbGlrZSBiZWZvcmUKKiBIb3dldmVyLCB0aGVyZSB3ZSBmaXJzdCBoYXZlIHRvIHNlcGVyYXRlIHRoZSBiaWdyYW1zIGFnYWluLCB0aGVuIGdldCByaWQgb2Ygc3RvcHdvcmRzLCBhbmQgdGhlbiB3ZSBjYW4gdW5pdGUgdGhlbSBhZ2Fpbi4KCmBgYHtyfQojIFNlcGVyYXRlIHRoZW0KdGV4dF90aWR5X25ncmFtcyAlPD4lCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpCmBgYAoKYGBge3J9CnRleHRfdGlkeV9uZ3JhbXMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQojIEdldCByaWQgb2Ygc3RvcHdvcmRzCnRleHRfdGlkeV9uZ3JhbXMgJTw+JQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9IGMoJ3dvcmQxJyA9ICd3b3JkJykpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9IGMoJ3dvcmQyJyA9ICd3b3JkJykpIApgYGAKCmBgYHtyfQojIEFuZCB1bml0ZSBhZ2Fpbgp0ZXh0X3RpZHlfbmdyYW1zICU8PiUKICB1bml0ZShiaWdyYW0sIHdvcmQxLCB3b3JkMiwgc2VwID0gIiAiKQpgYGAKCmBgYHtyfQojIEFuZCBmaW5hbGx5IGNvdW50CnRleHRfdGlkeV9uZ3JhbXMgJTw+JQogIGNvdW50KGluZGV4LCBiaWdyYW0pCmBgYAoKYGBge3J9CnRleHRfdGlkeV9uZ3JhbXMgJT4lCiAgY291bnQoYmlncmFtLCB3dCA9IG4sIHNvcnQgPSBUUlVFKSAlPiUKICBoZWFkKDUwKQpgYGAKCiogVGhlc2UgYmlncmFtcyBjb3VsZCBub3cgYmUgYW5hbHl6ZWQgb24gdGhlaXIgb3duLCBvciB1c2VkIGFzIHRva2VuIGZvciBhbGwgdGhlIHN0dWZmIHdlIGNhbiBkbywgZm9yIGluc3RhbmNlIHRvcGljIG1vZGVsbGluZywgTUwsIGV0Yy4uLi4KCkp1c3QgZm9yIGZ1biwgYSBsaXR0bGUgZ2VuZGVyIGFuYWx5c2lzIG5vdy4uLgoKYGBge3J9CiMgRGVmaW5lIG91ciBwcm9ub3Vucwpwcm9ub3VucyA8LSBjKCJoZSIsICJzaGUiKQpgYGAKCmBgYHtyfQojIEdldCBvdXIgYmlncmFtIHdoZXJlIGZpcnN0IHdvcmQgaXMgYSBwcm9ub3VuCmdlbmRlcl9iaWdyYW1zIDwtICB0ZXh0ICU+JSAKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgdGV4dCwgdG9rZW4gPSAnbmdyYW1zJywgbiA9IDIpICU+JQogIG5hLm9taXQoKSAlPiUKICBjb3VudChiaWdyYW0pICU+JQogIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiKSAlPiUKICBmaWx0ZXIod29yZDEgJWluJSBwcm9ub3VucykgJT4lCiAgY291bnQod29yZDEsIHdvcmQyLCB3dCA9IG4sIHNvcnQgPSBUUlVFKSAKYGBgCgojIyBOYW1lZCBFbnRpdHkgUmVjb2duaXRpb24gKGludHJvZHVjaW5nIHNwYWN5KQoKKiBJbiB0aGlzIHBhcnQgb2YgdGhlIHR1dG9yaWFsLCBJIHdpbGwgaW50cm9kdWNlIFNwYWN5LCBhIGhpZ2gtbGV2ZWwgRGVlcExlYXJuaW5nIGJhc2VkIE5MUCBsaWJyYXJ5IHRoYXQgd2lsbCBoZWxwIHVzIHRvIGRvIGNvbXBsZXggc3R1ZmYgd2l0aCBub3QgdG9vIG11Y2ggY29kZSBhbmQgd2l0aG91dCBoYXZpbmcgZG8gZ28gZGVlcCBpbnRvICJvbGQtc2Nob29sLU5MUCIKCiFbXShodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iLzgvODgvU3BhQ3lfbG9nby5zdmcvMTIwMHB4LVNwYUN5X2xvZ28uc3ZnLnBuZykge3dpZHRoPTYwMH0KCgoqIEhlcmUgYSBuaWNlIFtjaGVhdC1zaGVldF0oaHR0cHM6Ly93d3cuZGF0YWNhbXAuY29tL2NvbW11bml0eS9ibG9nL3NwYWN5LWNoZWF0c2hlZXQpCiogU3BhY3kgaXMgdG9kYXkgb25lIG9mIHRoZSBsZWFkaW5nIHNvbHV0aW9ucyBmb3IgTkxQIGluIGluZHVzdHJ5IHdoaWNoIGdvZXMgYXMgZmFyIGFzIHRoZW0gaG9zdGluZyBhIFt3aG9sZSBjb25mZXJlbmNlXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PWhOUHdSUGc5QnJRJmxpc3Q9UExCbWN1T2JkNUFuNFVDNmp2S18tZVNsNmpDdlAxZ3dYYykgd2l0aCB0aGUgbGVhZGluZyBOTFAgZXhwZXJ0cyB3b3JsZHdpZGUgaW4gQmVybGluIGxhc3Qgc3VtbWVyCiogSXQgaXMgaW1wbGVtZW50ZWQgYXMgYSBQeXRob24gcGFja2FnZSwgYnV0IGFjY2Vzc2libGUgdmlhIHRoZSBgc3BhY3lyYCB3cmFwcGVyLgoqIE1vc3QgZG9jdW1lbnRhdGlvbiB3aWxsIGJlIGZvdW5kIGZvciBweXRob24sIGJ1dCBhbG1vc3QgYWxsIGZ1bmN0aW9ucyBhZmUgYXZhaWxhYmxlIGluIHRoZSByIHZlcnNpb24gaW4gdGhlIHNhbWUgd2F5LgoKYGBge3J9CmxpYnJhcnkoc3BhY3lyKQojIHNwYWN5X2luc3RhbGwoKSAjIGNyZWF0ZXMgYSBuZXcgY29uZGEgZW52aXJvbm1lbnQgY2FsbGVkIHNwYWN5X2NvbmRhZW52LCBhcyBsb25nIGFzIHNvbWUgdmVyc2lvbiBvZiBjb25kYSBpcyBpbnN0YWxsZWQgCmBgYAoKYGBge3J9CnNwYWN5X2luaXRpYWxpemUobW9kZWwgPSAiZW5fY29yZV93ZWJfc20iKQpgYGAKCmBgYHtyfQp0ZXh0X2V4YW1wbGUgPC0gYyhkMSA9ICJzcGFDeSBpcyBncmVhdCBhdCBmYXN0IG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZy4gRXZlcnlib2R5IGxvdmVzIGl0ISAKICAgICAgICAgICAgICAgICAgSW4gRGVubWFyayBhbmQgZWxzZXdoZXJlLiIsCiAgICAgICAgICAgICAgICAgIGQyID0gIldlIGNhbiBhbHNvIHVzZSBpdCBpbiBSIHZpYSB0aGUgZ3JlYXQgc3BhY3lSIHdyYXBwZXIuIERhbmllbCBNaWNoZWxzIGRvZXMgdGhhdCBzb21ldGltZXMiKQpgYGAKCmBgYHtyfQojIHByb2Nlc3MgZG9jdW1lbnRzIGFuZCBvYnRhaW4gYSBkYXRhLnRhYmxlCnRleHRfcGFyc2VkIDwtIHNwYWN5X3BhcnNlKHRleHRfZXhhbXBsZSkKYGBgCgpgYGB7cn0KdGV4dF9wYXJzZWQKYGBgCgoqIERlcGVuZGVuY3kgcGFyc2luZzogRGV0YWlsZWQgcGFyc2luZyBvZiBzeW50YWN0aWMgZGVwZW5kZW5jaWVzIGlzIHBvc3NpYmxlIHdpdGggdGhlIGRlcGVuZGVuY3kgPSBUUlVFIG9wdGlvbjoKCmBgYHtyfQp0ZXh0X2V4YW1wbGUgJT4lIHNwYWN5X3BhcnNlKGRlcGVuZGVuY3kgPSBUUlVFLCBsZW1tYSA9IEZBTFNFLCBwb3MgPSBGQUxTRSkKYGBgCgpgYGB7cn0KdGV4dF9jaGFwdGVyIDwtIHRleHQgJT4lCiAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lCiAgc3VtbWFyaXNlKHRleHQgPSBwYXN0ZSh0ZXh0LCBjb2xsYXBzZSA9ICcgJykpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBjaGFwdGVyLCB2YWx1ZXNfZnJvbSA9IHRleHQpCmBgYAoKYGBge3J9CnRleHRfZW50aXRpZXMgPC10ZXh0X2NoYXB0ZXIgJT4lIGFzLmNoYXJhY3RlcigpICU+JSBzcGFjeV9wYXJzZShlbnRpdHkgPSBUUlVFKQpgYGAKCmBgYHtyfQp0ZXh0X2VudGl0aWVzICU+JSBoZWFkKDEwMDApCmBgYApDb29sIG9yIGNvb2w/CgpXZSBjYW4gbm93IGFnYWluIGNyZWF0ZSBhIG5ldHdvcmssIHJpZ2h0PyB0aGlzIHRpbWUgcmVhbGx5IGEgY2hhcmFjdGVyIG5ldHdvcmshCgpgYGB7cn0KZWxfcGVyc29ucyA8LSB0ZXh0X2VudGl0aWVzICU+JSAKICBlbnRpdHlfY29uc29saWRhdGUoKSAlPiUKICBmaWx0ZXIoZW50aXR5X3R5cGUgJT4lIHN0cl9kZXRlY3QoJ1BFUlNPTicpKSAlPiUKICB1bml0ZShjaGFwX3NlbnRfaWQsIGRvY19pZCwgc2VudGVuY2VfaWQsIHNlcCA9ICdfJykgJT4lCiAgcGFpcndpc2VfY291bnQodG9rZW4sIGNoYXBfc2VudF9pZCwgc29ydCA9IFRSVUUpICU+JQogIHJlbmFtZShmcm9tID0gaXRlbTEsIHRvID0gaXRlbTIsIHdlaWdodCA9IG4pCmBgYAoKYGBge3J9CmVsX3BlcnNvbnMgJT4lIGhlYWQoNTApCmBgYAoKYGBge3J9CmcgPC0gZWxfcGVyc29ucyAlPiUKICBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGQUxTRSkgJT4lCiAgaWdyYXBoOjpzaW1wbGlmeSgpICU+JSBhc190YmxfZ3JhcGgoKSAKYGBgCgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0Kc2V0LnNlZWQoMTMzNykKZyAlTj4lCiAgZ2dyYXBoKGxheW91dCA9ICJmciIpICsKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQsIGVkZ2VfYWxwaGEgPSB3ZWlnaHQpKSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gY2VudHJhbGl0eV9kZWdyZWUod2VpZ2h0ID0gd2VpZ2h0KSksIGNvbG9yID0gInBsdW00IikgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUsKSwgcmVwZWwgPSBUUlVFKSArCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSArCiAgbGFicyh0aXRsZSA9ICdDaGFyYWN0ZXIgTmV0d29yayBDcmltZSBhbmQgUHVuaXNobWVudCcpCmBgYAoKIyBFbmRub3RlcwoKIyMjIE1haW4gcmVmZXJlbmNlCgoKKiBKdWxpYSBTaWxnZSBhbmQgRGF2aWQgUm9iaW5zb24gKDIwMjApLiBUZXh0IE1pbmluZyB3aXRoIFI6IEEgVGlkeSBBcHByb2FjaCwgT+KAmVJlaWxseS4gT25saW5lIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tLykKICAgKiBbQ2hhcHRlciAyXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vc2VudGltZW50Lmh0bWwpOiBJbnRyb2R1Y3Rpb24gdG8gc2VudGltZW50IGFuYWx5c2lzCiogRW1pbCBIdmlkZmVsZHQgYW5kIEp1bGlhIFNpbGdlICgyMDIwKS4gU3VwZXJ2aXNlZCBNYWNoaW5lIExlYXJuaW5nIGZvciBUZXh0IEFuYWx5c2lzIGluIFIsIG9ubGluZSBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vc21sdGFyLmNvbS8pCiAgICogW0NoYXB0ZXIgNF0oaHR0cHM6Ly9zbWx0YXIuY29tL3N0ZW1taW5nLmh0bWwpOiBTdGVtbWluZwoKCiMjIyBQYWNrYWdlcyAmIEVjb3N5c3RlbQoKKiBbYHRpZHl0ZXh0YF0oaHR0cHM6Ly9naXRodWIuY29tL2p1bGlhc2lsZ2UvdGlkeXRleHQpCgpmdXJ0aGVyOiAKKiBbYHNwYWN5cmBdKGh0dHBzOi8vZ2l0aHViLmNvbS9xdWFudGVkYS9zcGFjeXIpOiBSIHdyYXBwZXIgZm9yIHNwYUN5LiBBbHNvIGNoZWNrIFt0aGlzIHR1dG9yaWFsXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc3BhY3lyL3ZpZ25ldHRlcy91c2luZ19zcGFjeXIuaHRtbCkKCiMjIyBTdWdnZXN0aW9ucyBmb3IgZnVydGhlciBzdHVkeQoKRGF0YUNhbXAgKCFNb3N0IGNvdXJzZXMgaGF2ZSBzb21ld2hhdCBvdXRkYXRlZCBlY29zeXN0ZW1zKQoKKiBbSW50cm9kdWN0aW9uIHRvIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyBpbiBSXShodHRwczovL2xlYXJuLmRhdGFjYW1wLmNvbS9jb3Vyc2VzL2ludHJvZHVjdGlvbi10by1uYXR1cmFsLWxhbmd1YWdlLXByb2Nlc3NpbmctaW4tcik6IFNvbWUgcmVmcmVzaGVyIHBsdXMgbW9yZSBhZHZhbmNlZCBhcHBsaWNhdGlvbnMgaW4gdGhlIGVuZC4gCgogCiAKIyMjIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgoKCgoK