Text mining
Data retrieval
Now, let’s move forward to simple text analysis. First, we need to prepare the data! (as usual)
tokens <- tweets %>%
mutate(id = row_number()) %>% # This creates a tweet id
select(id, text) %>% # Keeps only id and text of the tweet
unnest_tokens(word, text) # Creates tokens!
tokens
Let’s have a look at word frequencies.
tokens %>%
count(word, sort = TRUE)
This is polluted by small words. Let’s filter that (FIRST METHOD).
tokens %>% mutate(length = nchar(word))
Data frequencies
Now let’s omit the small words (smaller than 5 characters).
NOTE: all the thresholds below depend on the sample!
tokens_2 %>%
mutate(length = nchar(word)) %>%
filter(length > 4) %>% # Keep words with length larger than 4
count(word, sort = TRUE) %>% # Count words
head(21) %>% # Keep only top 12 words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

A better way to proceed is to remove “stop words” like “a”, “I”, “of”, “the”, etc (SECOND METHOD). Also, it would make sense to remove the search item and “https”.
data("stop_words")
tidy_tokens <- tokens %>%
anti_join(stop_words) # Remove unrelevant terms
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(20) %>% # Keep only top 15 words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")

Problem: strange characters remain. We are going to remove them by converting the text to ASCII format and omit NA data.
tidy_tokens <- tokens %>%
anti_join(stop_words) %>% # Remove unrelevant
mutate(word = iconv(word, from = "UTF-8", to = "ASCII")) %>% # Put in latin format
na.omit() %>% # Remove missing
filter(nchar(word) > 2, # Remove small words
!(word %in% c("https", "t.co", search_term)) # search_term defined above
)
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(20) %>% # Keep only top words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words")
Perfect!
n-grams
See https://www.tidytextmining.com/ngrams.html
tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
group_by(bigram) %>%
count(sort = T) %>%
head(20) %>%
ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col()

Again: same issue with stop words! So we must remove them again. But it’s more complicated now. We can use the separate() function to help us.
tweets %>%
mutate(id = row_number()) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, text, token = "ngrams", n = 2) %>%
separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
filter(!(word1 %in% c(stop_words$word, "https", search_term)),
!(word2 %in% c(stop_words$word, "https", search_term))) %>%
group_by(bigram) %>%
count(sort = T) %>%
head(20) %>%
ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col() + ylab("Bi-gram")
Sentiment
This section is inspired from: https://www.tidytextmining.com/sentiment.html
Sometimes, you may be asked in the process if you really want to download data (lexicons).
Just say yes in the console (type the correct answer: if not, you will be blocked/struck).
First, we need to load some sentiment lexicon. AFINN is one such sentiment database.
if(!require(textdata)){install.packages("textdata", repos = "https://cloud.r-project.org/")}
Loading required package: textdata
library(tidytext)
library(textdata)
afinn <- get_sentiments("afinn")
afinn
To create a nice visualization, we need to extract the time of the tweets.
tokens_time <- tweets %>%
mutate(id = 1:nrow(tweets)) %>% # This creates a tweet id
select(id, text, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(word, text) # Creates tokens!
tokens_time
We then use inner_join() to merge the two sets. This function removes the cases when a match does not occur.
library(lubridate)
sentiment <- tokens_time %>%
inner_join(afinn) %>%
mutate(day = day(created_at),
hour = hour(created_at) / 24,
minute = minute(created_at) / 60 / 24,
time = day + hour + minute)
sentiment
We then compute the average sentiment, minute-by-minute.
Of course, average sentiment can be misleading. Indeed, if a text contains the terms “I’m not happy”, then only “happy” will be tagged, which is the opposite of the intended meaning.
sentiment %>%
group_by(time, day, hour, minute) %>%
summarise(avg_sentiment = mean(value)) %>%
mutate(time = make_datetime(year = 2020, month = 4, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = avg_sentiment)) + geom_col()
`summarise()` has grouped output by 'time', 'day', 'hour'. You can override using the `.groups` argument.

There are 24 bars per day, but the y-axis is not optimal…
What about emotions? The NRC lexicon categorizes emotions. Below, we order emotions. The most important impact is the dichotomy between positive & negative emotions.
nrc <- get_sentiments("nrc")
nrc <- nrc %>%
mutate(sentiment = as.factor(sentiment),
sentiment = recode_factor(sentiment,
joy = "joy",
trust = "trust",
surprise = "surprise",
anticipation = "anticipation",
positive = "positive",
negative = "negative",
sadness = "sadness",
anger = "anger",
fear = "fear",
digust = "disgust",
.ordered = T))
We then create the merged dataset.
emotions <- tokens_time %>%
inner_join(nrc) %>% # Merge data with sentiment
mutate(day = day(created_at),
hour = hour(created_at)/24,
minute = minute(created_at)/24/60,
time = day + hour + minute) # Create day column
emotions # Show the result
The merging has reduced the size of the dataset, but there still remains enough to pursue the study.
Finally, we move to the pivot-table that counts emotions for each day.
g <- emotions %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col() +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
scale_fill_viridis(option = "magma", discrete = T, direction = -1)
`summarise()` has grouped output by 'time', 'sentiment', 'day', 'hour'. You can override using the `.groups` argument.
ggplotly(g)
This can also be shown in percentage format.
g <- emotions %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
scale_fill_viridis(option = "magma", discrete = T, direction = -1)
ggplotly(g)
emotions %>%
mutate(sentiment = if_else(sentiment < "negative", "positive", "negative")) %>%
group_by(time, sentiment, day, hour, minute) %>%
summarise(intensity = n()) %>%
mutate(time = make_datetime(year = 2020, month = 10, day = day, hour = hour*24, min = minute*24*60)) %>%
ggplot(aes(x = time, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
scale_fill_manual(values = c("#001144", "#FFDD99"))
`summarise()` has grouped output by 'time', 'sentiment', 'day', 'hour'. You can override using the `.groups` argument.

Advanced sentiment
The problem with the preceding methods is that they don’t take into account valence shifters (i.e., negators, amplifiers (intensifiers), de-amplifiers (downtoners), and adversative conjunctions). If a tweet says not happy, counting the word happy is not a good idea! The package sentimentr is built to circumvent these issues: have a look at https://github.com/trinker/sentimentr
(see also: https://www.sentometrics.org and the book Supervised Machine Learning for Text Analysis in R hosted at https://smltar.com)
I haven’t tested aws.comprehend, but it seems promising: https://github.com/cloudyr/aws.comprehend
if(!require(sentimentr)){install.packages(c("sentimentr", "textcat"))}
library(sentimentr)
library(textcat)
First, let’s keep only the tweets written in English!
tweets_en <- tweets %>%
mutate(language = textcat(text)) %>%
filter(language == "english") %>%
dplyr::select(created_at, text)
NOTE: the code above was used to show the function textcat: the language is already coded in the tweets via the lang column/variable. (it suffices to keep the instances for which lang == “en”)
Next, we compute advanced sentiment.
tweet_sent <- tweets_en$text %>%
get_sentences() %>% # Intermediate function
sentiment() # Sentiment!
tweet_sent
NOTE: depending on frequency issues, it is better to analyze at daily or hourly scales. If a word is very popular, then, higher frequencies are more relevant.

LS0tCnRpdGxlOiAiVGhpcmQgcGFydHkgZGF0YSBhbmQgYmFzaWMgdGV4dCBtaW5pbmciCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIFRoZSBnZW5lcmFsIGlkZWEKCkRhdGEgdHJhbnNmZXIgaXMgaGlnaGx5IGNvbnRyb2xsZWQuIFRoZSBrZXkgbm90aW9ucyBhcmUgKiphdXRoZW50aWNhdGlvbioqIGFuZCAqKnByb3RvY29sKiouCgojIERvd25sb2FkaW5nIHR3ZWV0cyB3aXRoICpydHdlZXQqCgpUaGVyZSBhcmUgc2V2ZXJhbCBwYWNrYWdlcyB0aGF0IHJ1biBhbiBpbnRlcmZhY2Ugd2l0aCB0d2l0dGVyOiAqcnR3ZWV0KiwgKlJUd2l0dGVyQVBJKiwgKnN0cmVhbVIqIGFuZCAqdHdpdHRlUiouCQkKUmVjZW50IHBhY2thZ2VzIGFyZSBiZXR0ZXIgYmVjYXVzZSBmaXJtcyB1cGRhdGUgdGhlaXIgQVBJIHBvbGljaWVzIChhbmQgYWNjZXNzKSwgdGh1cyBvbGQgcHJvdG9jb2xzIHNvbWV0aW1lcyBkbyBub3Qgd29yayEKCiMjIEZpcnN0IHRoaW5ncyBmaXJzdAoqKkZpcnN0KiosIHRoZSBwYWNrYWdlcy4gRG93bmxvYWQuLi4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUocnR3ZWV0KSl7aW5zdGFsbC5wYWNrYWdlcygicnR3ZWV0Iil9CmBgYAoKLi4uIGFuZCBhY3RpdmF0ZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJ0d2VldCkKYGBgCgojIyBBdXRoZW50aWNhdGlvbgoKKipTZWNvbmQqKjogeW91IG5lZWQgeW91ciB0d2l0dGVyIGNyZWRlbnRpYWxzICh5b3UgbmVlZCBhIHR3aXR0ZXIgYWNjb3VudCkuCllvdSBhbHNvIG5lZWQgYSAqKmRldmVsb3BlciBhY2NvdW50Kio6IGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL2FwcGx5LWZvci1hY2Nlc3MgCkxvZ2luIG9uIHR3aXR0ZXIgYW5kIGdvIHRvOiBodHRwczovL2RldmVsb3Blci50d2l0dGVyLmNvbSAKCiFbXSh0d2l0dGVyMS5wbmcpCgpUaGUgbmV4dCBzdGVwIGlzIGNydWNpYWw6IHdlIG5lZWQgdG8gcmV0cmlldmUgaWRlbnRpZmljYXRpb24gY3JlZGVudGlhbHMuICAgCkluIG9yZGVyIHRvIGRvIHRoYXQsIHlvdSBuZWVkIHRvIGNyZWF0ZSBhIFR3aXR0ZXIgYXBwLiBCZWxvdywgeW91IGNhbiBzZWUgbWluZS4gClRvIGNyZWF0ZSBvbmUsIHNpbXBseSBjbGljayBvbiB0aGUgIipDcmVhdGUgYW4gYXBwKiIgIGJ1dHRvbiAob24gdGhlIHJpZ2h0KQoKIVtdKHR3aXR0ZXIyLnBuZykKCklmIHlvdSBjbGljayBvbiB0aGUgImRldGFpbHMiIG9mIGFuIGFwcCwgeW91IGNhbiBzZWUgdGhpczoKCiFbXSh0d2l0dGVyMy5wbmcpCgpUaGUgc2Vjb25kIHRhYiBpcyBjYWxsZWQgIioqS2V5cyBhbmQgdG9rZW5zKioiICRccmlnaHRhcnJvdyQgdGhhdCdzIHdoZXJlIHRoZSBpbmZvIGlzISEhCgohW10odHdpdHRlcjQucG5nKQoKCk5vdyB3ZSBhcmUgcmVhZHkgdG8gcHJvY2VlZC4gVGhlIGxpbmVzIGJlbG93IG9wZW4gdGhlIGNvbm5leGlvbiB3aXRoIHRoZSBBUEkuCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGV2YWwgPSBGQUxTRX0KY29uc3VtZXJfa2V5IDwtICJ5b3VyX2NvbnN1bWVyX2tleSIKY29uc3VtZXJfc2VjcmV0IDwtICJ5b3VfY29uc3VtZXJfc2VjcmV0IgphY2Nlc3NfdG9rZW4gPC0gInlvdXJfYWNjZXNzX3Rva2VuIgphY2Nlc3Nfc2VjcmV0IDwtICJ5b3VyX2FjY2Vzc19zZWNyZXQiCgpjcmVhdGVfdG9rZW4oYXBwID0gInRoZV9uYW1lX29mX3lvdXJfYXBwIiwKICAgICAgICAgICAgIGNvbnN1bWVyX2tleSA9IGNvbnN1bWVyX2tleSwgCiAgICAgICAgICAgICBjb25zdW1lcl9zZWNyZXQgPSBjb25zdW1lcl9zZWNyZXQsIAogICAgICAgICAgICAgYWNjZXNzX3Rva2VuID0gYWNjZXNzX3Rva2VuLCAKICAgICAgICAgICAgIGFjY2Vzc19zZWNyZXQgPSBhY2Nlc3Nfc2VjcmV0CiAgICAgICAgICAgICApCmBgYAoKCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KY29uc3VtZXJfa2V5IDwtICJ4cU1tMTBWd3dsMVhBeDMxQ0F6WU5pcW9pIgpjb25zdW1lcl9zZWNyZXQgPC0gIkRSTGNLanFQTGJpb3oyaTJWRVduV3JBUWpZUlVGQ0w0c1VuTmF6Q3hadVE4QnRUcWNkIgphY2Nlc3NfdG9rZW4gPC0gIjMyNjEwNTI4OTEtT0FxUGpFa1VRcmdrTU9rVXlFdnlXUkRWeGE3NkpGYTllNTJOZE5UIgphY2Nlc3Nfc2VjcmV0IDwtICI0ZjNza2pmbndMYXZPWkdOaHR1ZXRJbEU0Z3N4OENHWEVpMkdNd2FSZkY3bjAiCgpjcmVhdGVfdG9rZW4oYXBwID0gIkJpZyBEb3Vkb3UiLAogICAgICAgICAgICAgY29uc3VtZXJfa2V5ID0gY29uc3VtZXJfa2V5LCAKICAgICAgICAgICAgIGNvbnN1bWVyX3NlY3JldCA9IGNvbnN1bWVyX3NlY3JldCwgCiAgICAgICAgICAgICBhY2Nlc3NfdG9rZW4gPSBhY2Nlc3NfdG9rZW4sIAogICAgICAgICAgICAgYWNjZXNzX3NlY3JldCA9IGFjY2Vzc19zZWNyZXQKICAgICAgICAgICAgICkKYGBgCgpBdXRoZW50aWNhdGlvbiBpcyBhbiBpbXBvcnRhbnQgcGFydCBvZiB0aGUgcHJvY2Vzcy4gRm9yIG1vcmUgaW5mbyBvbiB0aGF0OiAgCi0gaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dvb2dsZXNoZWV0cy92aWduZXR0ZXMvbWFuYWdpbmctYXV0aC10b2tlbnMuaHRtbCAgIAotIGh0dHBzOi8vaHR0ci5yLWxpYi5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWwgKHNlY3Rpb24gQXV0aGVudGljYXRpb24pCgojIyBFeHRyYWN0aW9uCgpJZiBubyBlcnJvciBhcHBlYXJzLCB3ZSBhcmUgcmVhZHkgdG8gcXVlcnkuIERlcGVuZGluZyBvbiB0aGUgbnVtYmVyIG9mIHJlcXVlc3RlZCB0d2VldHMsIHRoaXMgY2FuIHRha2Ugc29tZSB0aW1lLgoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpzZWFyY2hfdGVybSA8LSAiY29uZmluZW1lbnQiCnR3ZWV0cyA8LSBzZWFyY2hfdHdlZXRzKAogIHNlYXJjaF90ZXJtLCAgICAgICAgICAjIFdoYXQgdG8gc2VhcmNoIGZvcgogIG4gPSAyMDAwLCAgICAgICAgICAgICAjIE51bWJlciBvZiB0d2VldHMgdG8gZG93bmxvYWQKICBpbmNsdWRlX3J0cyA9IEZBTFNFICAgIyBFeGNsdWRlIHJlLXR3ZWV0cwopCmBgYApGb3IgbGFyZ2UgcXVlcmllcywgdGhlIHByb2dyZXNzIGJhciBoZWxwcy4gICAKTm90ZSB0aGF0IG1hbnkgb3B0aW9ucyBhcmUgYXZhaWxhYmxlLCBsaWtlOiBleGNsdWRlIHJldHdlZXRzLCBsaW1pdCBzZWFyY2ggdG8gcGFydGljdWxhciBnZW9ncmFwaGljYWwgem9uZXMgKGluc2lkZSByYWRpdXNlcykuCgojIFRleHQgbWluaW5nCgojIyBSZWZlcmVuY2VzClRoZSByZWZlcmVuY2UgYm9vayBpczogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tICAgICAgCkEgZ3JlYXQgaW50ZXJhY3RpdmUgdHV0b3JpYWw6IGh0dHBzOi8vanVsaWFzaWxnZS5zaGlueWFwcHMuaW8vbGVhcm50aWR5dGV4dC8gICAgCkFuZCB0aGUgcGFja2FnZSBpczogIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQppZighcmVxdWlyZSh0aWR5dGV4dCkpe2luc3RhbGwucGFja2FnZXMoInRpZHl0ZXh0IiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpgYGAKIChzZWUgYWxzbzogaHR0cHM6Ly9xdWFudGVkYS5pby9pbmRleC5odG1sKQoKIyMgRGF0YSByZXRyaWV2YWwKCk5vdywgbGV0J3MgbW92ZSBmb3J3YXJkIHRvIHNpbXBsZSB0ZXh0IGFuYWx5c2lzLiBGaXJzdCwgd2UgbmVlZCB0byBwcmVwYXJlIHRoZSBkYXRhISAoYXMgdXN1YWwpCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyA8LSB0d2VldHMgJT4lIAogICAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUgICAgIyBUaGlzIGNyZWF0ZXMgYSB0d2VldCBpZAogICAgc2VsZWN0KGlkLCB0ZXh0KSAlPiUgICAgICAgICAgICAgIyBLZWVwcyBvbmx5IGlkIGFuZCB0ZXh0IG9mIHRoZSB0d2VldAogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAgICAgICAgIyBDcmVhdGVzIHRva2VucyEKdG9rZW5zCmBgYAoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgd29yZCBmcmVxdWVuY2llcy4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCmBgYAoKVGhpcyBpcyBwb2xsdXRlZCBieSBzbWFsbCB3b3Jkcy4gTGV0J3MgZmlsdGVyIHRoYXQgKCpGSVJTVCBNRVRIT0QqKS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JSBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpCmBgYAoKCiMjIERhdGEgZnJlcXVlbmNpZXMKTm93IGxldCdzIG9taXQgdGhlIHNtYWxsIHdvcmRzIChzbWFsbGVyIHRoYW4gNSBjaGFyYWN0ZXJzKS4gICAKKipOT1RFKio6IGFsbCB0aGUgdGhyZXNob2xkcyBiZWxvdyBkZXBlbmQgb24gdGhlIHNhbXBsZSEgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUKICAgIG11dGF0ZShsZW5ndGggPSBuY2hhcih3b3JkKSkgJT4lCiAgICBmaWx0ZXIobGVuZ3RoID4gNCkgJT4lICAgICAgICAgICAgICMgS2VlcCB3b3JkcyB3aXRoIGxlbmd0aCBsYXJnZXIgdGhhbiA0CiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjEpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdG9wIDEyIHdvcmRzCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiV29yZHMiKQpgYGAKCkEgYmV0dGVyIHdheSB0byBwcm9jZWVkIGlzIHRvIHJlbW92ZSAic3RvcCB3b3JkcyIgbGlrZSAiYSIsICJJIiwgIm9mIiwgInRoZSIsIGV0YyAoKlNFQ09ORCBNRVRIT0QqKS4KQWxzbywgaXQgd291bGQgbWFrZSBzZW5zZSB0byByZW1vdmUgdGhlIHNlYXJjaCBpdGVtIGFuZCAiaHR0cHMiLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpkYXRhKCJzdG9wX3dvcmRzIikKdGlkeV90b2tlbnMgPC0gdG9rZW5zICU+JSAKICAgIGFudGlfam9pbihzdG9wX3dvcmRzKSAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgdW5yZWxldmFudCB0ZXJtcwp0aWR5X3Rva2VucyAlPiUKICAgIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgICAgICAgICAgICAgIyBDb3VudCB3b3JkcwogICAgaGVhZCgyMCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEtlZXAgb25seSB0b3AgMTUgd29yZHMKICAgIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKKipQcm9ibGVtKio6IHN0cmFuZ2UgY2hhcmFjdGVycyByZW1haW4uIFdlIGFyZSBnb2luZyB0byByZW1vdmUgdGhlbSBieSBjb252ZXJ0aW5nIHRoZSB0ZXh0IHRvIEFTQ0lJIGZvcm1hdCBhbmQgb21pdCAqTkEqIGRhdGEuIAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQp0aWR5X3Rva2VucyA8LSB0b2tlbnMgJT4lIAogICAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSB1bnJlbGV2YW50CiAgICBtdXRhdGUod29yZCA9IGljb252KHdvcmQsIGZyb20gPSAiVVRGLTgiLCB0byA9ICJBU0NJSSIpKSAlPiUgIyBQdXQgaW4gbGF0aW4gZm9ybWF0CiAgICBuYS5vbWl0KCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIG1pc3NpbmcKICAgIGZpbHRlcihuY2hhcih3b3JkKSA+IDIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgc21hbGwgd29yZHMKICAgICAgICAgICAhKHdvcmQgJWluJSBjKCJodHRwcyIsICJ0LmNvIiwgc2VhcmNoX3Rlcm0pKSAgIyBzZWFyY2hfdGVybSBkZWZpbmVkIGFib3ZlCiAgICApCnRpZHlfdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JSAgICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjApICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAjIEtlZXAgb25seSB0b3Agd29yZHMKICAgIGdncGxvdChhZXMoeSA9IHJlb3JkZXIod29yZCxuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJXb3JkcyIpCmBgYAoKUGVyZmVjdCEKCiMjIFdvcmQgY2xvdWQKClRoaXMgZGF0YSBjYW4gYWxzbyBiZSBzaG93biB3aXRoIGEgd29yZCBjbG91ZC4gV2Ugc2ltcGx5IHVzZSB0aGUgKndvcmRjbG91ZCogcGFja2FnZTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRjbG91ZC9pbmRleC5odG1sIAoKVGhlIHBhY2thZ2UgKndvcmRjbG91ZDIqIGFkZHMgYSBmZXcgZmVhdHVyZXM6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93b3JkY2xvdWQyL3ZpZ25ldHRlcy93b3JkY2xvdWQuaHRtbAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQppZighcmVxdWlyZSh3b3JkY2xvdWQpKXtpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKX0KbGlicmFyeSh3b3JkY2xvdWQpCmNsb3VkX2RhdGEgPC0gdGlkeV90b2tlbnMgJT4lIGNvdW50KHdvcmQpCndvcmRjbG91ZCh3b3JkcyA9IGNsb3VkX2RhdGEkd29yZCwgCiAgICAgICAgICBmcmVxID0gY2xvdWRfZGF0YSRuLCBtaW4uZnJlcSA9IDEwLAogICAgICAgICAgbWF4LndvcmRzID0gMTAwLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgcm90LnBlciA9IDAuMTUsIAogICAgICAgICAgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkKYGBgCgojIyBuLWdyYW1zCgpTZWUgaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL25ncmFtcy5odG1sCgpgYGB7ciBiaWdyYW1zLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CnR3ZWV0cyAlPiUgCiAgICBtdXRhdGUoaWQgPSAxOm5yb3codHdlZXRzKSkgJT4lICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICBjb3VudChzb3J0ID0gVCkgJT4lCiAgaGVhZCgyMCkgJT4lCiAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihiaWdyYW0sIG4pLCB4ID0gbikpICsgZ2VvbV9jb2woKQpgYGAKCkFnYWluOiBzYW1lIGlzc3VlIHdpdGggc3RvcCB3b3JkcyEgU28gd2UgbXVzdCByZW1vdmUgdGhlbSBhZ2Fpbi4gQnV0IGl0J3MgbW9yZSBjb21wbGljYXRlZCBub3cuCldlIGNhbiB1c2UgdGhlICpzZXBhcmF0ZSooKSBmdW5jdGlvbiB0byBoZWxwIHVzLgoKYGBge3J9CnR3ZWV0cyAlPiUgCiAgICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JSAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCB0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgIHNlcGFyYXRlKGJpZ3JhbSwgYygid29yZDEiLCAid29yZDIiKSwgc2VwID0gIiAiLCByZW1vdmUgPSBGKSAlPiUKICBmaWx0ZXIoISh3b3JkMSAlaW4lIGMoc3RvcF93b3JkcyR3b3JkLCAiaHR0cHMiLCBzZWFyY2hfdGVybSkpLAogICAgICAgICAhKHdvcmQyICVpbiUgYyhzdG9wX3dvcmRzJHdvcmQsICJodHRwcyIsIHNlYXJjaF90ZXJtKSkpICU+JQogIGdyb3VwX2J5KGJpZ3JhbSkgJT4lCiAgY291bnQoc29ydCA9IFQpICU+JQogIGhlYWQoMjApICU+JQogIGdncGxvdChhZXMoeSA9IHJlb3JkZXIoYmlncmFtLCBuKSwgeCA9IG4pKSArIGdlb21fY29sKCkgKyB5bGFiKCJCaS1ncmFtIikKYGBgCgoKIyMgU2VudGltZW50CgpUaGlzIHNlY3Rpb24gaXMgaW5zcGlyZWQgZnJvbTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3NlbnRpbWVudC5odG1sICAgIApTb21ldGltZXMsIHlvdSBtYXkgYmUgYXNrZWQgaW4gdGhlIHByb2Nlc3MgaWYgeW91ICpyZWFsbHkqIHdhbnQgdG8gZG93bmxvYWQgZGF0YSAobGV4aWNvbnMpLiAgCkp1c3Qgc2F5IHllcyBpbiB0aGUgKipjb25zb2xlKiogKHR5cGUgdGhlIGNvcnJlY3QgYW5zd2VyOiBpZiBub3QsIHlvdSB3aWxsIGJlIGJsb2NrZWQvc3RydWNrKS4KCkZpcnN0LCB3ZSBuZWVkIHRvIGxvYWQgc29tZSBzZW50aW1lbnQgbGV4aWNvbi4gQUZJTk4gaXMgb25lIHN1Y2ggc2VudGltZW50IGRhdGFiYXNlLiAKCmBgYHtyfQppZighcmVxdWlyZSh0ZXh0ZGF0YSkpe2luc3RhbGwucGFja2FnZXMoInRleHRkYXRhIiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHRleHRkYXRhKQphZmlubiA8LSBnZXRfc2VudGltZW50cygiYWZpbm4iKQphZmlubgpgYGAKClRvIGNyZWF0ZSBhIG5pY2UgdmlzdWFsaXphdGlvbiwgd2UgbmVlZCB0byBleHRyYWN0IHRoZSAqKnRpbWUqKiBvZiB0aGUgdHdlZXRzLgoKYGBge3J9CnRva2Vuc190aW1lIDwtIHR3ZWV0cyAlPiUgCiAgICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JSAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgdGV4dCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgICAgICAgICAgIyBDcmVhdGVzIHRva2VucyEKdG9rZW5zX3RpbWUKYGBgCgpXZSB0aGVuIHVzZSAqKmlubmVyX2pvaW4qKigpIHRvIG1lcmdlIHRoZSB0d28gc2V0cy4gVGhpcyBmdW5jdGlvbiByZW1vdmVzIHRoZSBjYXNlcyB3aGVuIGEgbWF0Y2ggZG9lcyBub3Qgb2NjdXIuCgpgYGB7cn0KbGlicmFyeShsdWJyaWRhdGUpCnNlbnRpbWVudCA8LSB0b2tlbnNfdGltZSAlPiUgCiAgaW5uZXJfam9pbihhZmlubikgJT4lCiAgbXV0YXRlKGRheSA9IGRheShjcmVhdGVkX2F0KSwKICAgICAgICAgaG91ciA9IGhvdXIoY3JlYXRlZF9hdCkgLyAyNCwKICAgICAgICAgbWludXRlID0gbWludXRlKGNyZWF0ZWRfYXQpIC8gNjAgLyAyNCwKICAgICAgICAgdGltZSA9IGRheSArIGhvdXIgKyBtaW51dGUpCnNlbnRpbWVudApgYGAKCldlIHRoZW4gY29tcHV0ZSB0aGUgYXZlcmFnZSBzZW50aW1lbnQsIG1pbnV0ZS1ieS1taW51dGUuICAgCk9mIGNvdXJzZSwgYXZlcmFnZSBzZW50aW1lbnQgY2FuIGJlIG1pc2xlYWRpbmcuIEluZGVlZCwgaWYgYSB0ZXh0IGNvbnRhaW5zIHRoZSB0ZXJtcyAiKkknbSBub3QgaGFwcHkqIiwgdGhlbiBvbmx5ICIqaGFwcHkqIiB3aWxsIGJlIHRhZ2dlZCwgd2hpY2ggaXMgdGhlIG9wcG9zaXRlIG9mIHRoZSBpbnRlbmRlZCBtZWFuaW5nLgoKYGBge3J9CnNlbnRpbWVudCAlPiUKICBncm91cF9ieSh0aW1lLCBkYXksIGhvdXIsIG1pbnV0ZSkgJT4lCiAgc3VtbWFyaXNlKGF2Z19zZW50aW1lbnQgPSBtZWFuKHZhbHVlKSkgJT4lCiAgbXV0YXRlKHRpbWUgPSBtYWtlX2RhdGV0aW1lKHllYXIgPSAyMDIwLCBtb250aCA9IDQsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBhdmdfc2VudGltZW50KSkgKyBnZW9tX2NvbCgpIApgYGAKVGhlcmUgYXJlIDI0IGJhcnMgcGVyIGRheSwgYnV0IHRoZSAqeSotYXhpcyBpcyBub3Qgb3B0aW1hbC4uLiAgCiAKV2hhdCBhYm91dCBlbW90aW9ucz8gVGhlICoqTlJDKiogbGV4aWNvbiBjYXRlZ29yaXplcyAqZW1vdGlvbnMqLiBCZWxvdywgd2Ugb3JkZXIgZW1vdGlvbnMuIFRoZSBtb3N0IGltcG9ydGFudCBpbXBhY3QgaXMgdGhlIGRpY2hvdG9teSBiZXR3ZWVuIHBvc2l0aXZlICYgbmVnYXRpdmUgZW1vdGlvbnMuIAoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpucmMgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpCm5yYyA8LSBucmMgJT4lCiAgbXV0YXRlKHNlbnRpbWVudCA9IGFzLmZhY3RvcihzZW50aW1lbnQpLAogICAgICAgICBzZW50aW1lbnQgPSByZWNvZGVfZmFjdG9yKHNlbnRpbWVudCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqb3kgPSAiam95IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnVzdCA9ICJ0cnVzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VycHJpc2UgPSAic3VycHJpc2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFudGljaXBhdGlvbiA9ICJhbnRpY2lwYXRpb24iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gInBvc2l0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZWdhdGl2ZSA9ICJuZWdhdGl2ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FkbmVzcyA9ICJzYWRuZXNzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmdlciA9ICJhbmdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmVhciA9ICJmZWFyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaWd1c3QgPSAiZGlzZ3VzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLm9yZGVyZWQgPSBUKSkKYGBgCgpXZSB0aGVuIGNyZWF0ZSB0aGUgbWVyZ2VkIGRhdGFzZXQuCgpgYGB7cn0KZW1vdGlvbnMgPC0gdG9rZW5zX3RpbWUgJT4lIAogIGlubmVyX2pvaW4obnJjKSAlPiUgICAgICAgICAgICAgICAgICAjIE1lcmdlIGRhdGEgd2l0aCBzZW50aW1lbnQKICBtdXRhdGUoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpLAogICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KS8yNCwKICAgICAgICAgbWludXRlID0gbWludXRlKGNyZWF0ZWRfYXQpLzI0LzYwLAogICAgICAgICB0aW1lID0gZGF5ICsgaG91ciArIG1pbnV0ZSkgICAjIENyZWF0ZSBkYXkgY29sdW1uCmVtb3Rpb25zICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU2hvdyB0aGUgcmVzdWx0CmBgYAoKVGhlIG1lcmdpbmcgaGFzIHJlZHVjZWQgdGhlIHNpemUgb2YgdGhlIGRhdGFzZXQsIGJ1dCB0aGVyZSBzdGlsbCByZW1haW5zIGVub3VnaCB0byBwdXJzdWUgdGhlIHN0dWR5LiAgIApGaW5hbGx5LCB3ZSBtb3ZlIHRvIHRoZSBwaXZvdC10YWJsZSB0aGF0IGNvdW50cyBlbW90aW9ucyBmb3IgZWFjaCBkYXkuCgpgYGB7cn0KZyA8LSBlbW90aW9ucyAlPiUgCiAgZ3JvdXBfYnkodGltZSwgc2VudGltZW50LCBkYXksIGhvdXIsIG1pbnV0ZSkgJT4lCiAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lCiAgbXV0YXRlKHRpbWUgPSBtYWtlX2RhdGV0aW1lKHllYXIgPSAyMDIxLCBtb250aCA9IDQsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKCJUaW1lIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFQsIGRpcmVjdGlvbiA9IC0xKQpnZ3Bsb3RseShnKQpgYGAKClRoaXMgY2FuIGFsc28gYmUgc2hvd24gaW4gcGVyY2VudGFnZSBmb3JtYXQuIAoKYGBge3J9CmcgPC0gZW1vdGlvbnMgJT4lIAogIGdyb3VwX2J5KHRpbWUsIHNlbnRpbWVudCwgZGF5LCBob3VyLCBtaW51dGUpICU+JQogIHN1bW1hcmlzZShpbnRlbnNpdHkgPSBuKCkpICU+JQogIG11dGF0ZSh0aW1lID0gbWFrZV9kYXRldGltZSh5ZWFyID0gMjAyMCwgbW9udGggPSAxMCwgZGF5ID0gZGF5LCBob3VyID0gaG91cioyNCwgbWluID0gbWludXRlKjI0KjYwKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gdGltZSwgeSA9IGludGVuc2l0eSwgZmlsbCA9IHNlbnRpbWVudCkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZmlsbCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGp1c3QgPSAxKSkgKyB4bGFiKCJUaW1lIikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFQsIGRpcmVjdGlvbiA9IC0xKQpnZ3Bsb3RseShnKQpgYGAKCmBgYHtyfQplbW90aW9ucyAlPiUgCiAgbXV0YXRlKHNlbnRpbWVudCA9IGlmX2Vsc2Uoc2VudGltZW50IDwgIm5lZ2F0aXZlIiwgInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSAKICBncm91cF9ieSh0aW1lLCBzZW50aW1lbnQsIGRheSwgaG91ciwgbWludXRlKSAlPiUKICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICBtdXRhdGUodGltZSA9IG1ha2VfZGF0ZXRpbWUoeWVhciA9IDIwMjAsIG1vbnRoID0gMTAsIGRheSA9IGRheSwgaG91ciA9IGhvdXIqMjQsIG1pbiA9IG1pbnV0ZSoyNCo2MCkpICU+JQogIGdncGxvdChhZXMoeCA9IHRpbWUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjMDAxMTQ0IiwgIiNGRkREOTkiKSkKYGBgCgoKCgojIyBBZHZhbmNlZCBzZW50aW1lbnQgCgpUaGUgcHJvYmxlbSB3aXRoIHRoZSBwcmVjZWRpbmcgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZG9uJ3QgdGFrZSBpbnRvIGFjY291bnQgKip2YWxlbmNlIHNoaWZ0ZXJzKiogKGkuZS4sIG5lZ2F0b3JzLCBhbXBsaWZpZXJzIChpbnRlbnNpZmllcnMpLCBkZS1hbXBsaWZpZXJzIChkb3dudG9uZXJzKSwgYW5kIGFkdmVyc2F0aXZlIGNvbmp1bmN0aW9ucykuIElmIGEgdHdlZXQgc2F5cyAqbm90IGhhcHB5KiwgY291bnRpbmcgdGhlIHdvcmQgKmhhcHB5KiBpcyBub3QgYSBnb29kIGlkZWEhIFRoZSBwYWNrYWdlICpzZW50aW1lbnRyKiBpcyBidWlsdCB0byBjaXJjdW12ZW50IHRoZXNlIGlzc3VlczogaGF2ZSBhIGxvb2sgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvc2VudGltZW50ciAgCihzZWUgYWxzbzogaHR0cHM6Ly93d3cuc2VudG9tZXRyaWNzLm9yZyBhbmQgdGhlIGJvb2sgKipTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFRleHQgQW5hbHlzaXMgaW4gUioqIGhvc3RlZCBhdCBodHRwczovL3NtbHRhci5jb20pCgpJIGhhdmVuJ3QgdGVzdGVkICoqYXdzLmNvbXByZWhlbmQqKiwgYnV0IGl0IHNlZW1zIHByb21pc2luZzogaHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkeXIvYXdzLmNvbXByZWhlbmQKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2VudGltZW50cikpe2luc3RhbGwucGFja2FnZXMoYygic2VudGltZW50ciIsICJ0ZXh0Y2F0IikpfQpsaWJyYXJ5KHNlbnRpbWVudHIpCmxpYnJhcnkodGV4dGNhdCkKYGBgCgpGaXJzdCwgbGV0J3Mga2VlcCBvbmx5IHRoZSB0d2VldHMgd3JpdHRlbiBpbiBFbmdsaXNoIQoKYGBge3J9CnR3ZWV0c19lbiA8LSB0d2VldHMgJT4lCiAgbXV0YXRlKGxhbmd1YWdlID0gdGV4dGNhdCh0ZXh0KSkgJT4lCiAgZmlsdGVyKGxhbmd1YWdlID09ICJlbmdsaXNoIikgJT4lCiAgZHBseXI6OnNlbGVjdChjcmVhdGVkX2F0LCB0ZXh0KQpgYGAKCioqTk9URSoqOiB0aGUgY29kZSBhYm92ZSB3YXMgdXNlZCB0byBzaG93IHRoZSBmdW5jdGlvbiAqdGV4dGNhdCo6IHRoZSBsYW5ndWFnZSBpcyBhbHJlYWR5IGNvZGVkIGluIHRoZSB0d2VldHMgdmlhIHRoZSAqKmxhbmcqKiBjb2x1bW4vdmFyaWFibGUuIChpdCBzdWZmaWNlcyB0byBrZWVwIHRoZSBpbnN0YW5jZXMgZm9yIHdoaWNoIGxhbmcgPT0gImVuIikKCk5leHQsIHdlIGNvbXB1dGUgYWR2YW5jZWQgc2VudGltZW50LiAKCmBgYHtyfQp0d2VldF9zZW50IDwtIHR3ZWV0c19lbiR0ZXh0ICU+JQogIGdldF9zZW50ZW5jZXMoKSAlPiUgICMgSW50ZXJtZWRpYXRlIGZ1bmN0aW9uCiAgc2VudGltZW50KCkgICAgICAgICAgIyBTZW50aW1lbnQhCnR3ZWV0X3NlbnQKYGBgCgoqKk5PVEUqKjogZGVwZW5kaW5nIG9uIGZyZXF1ZW5jeSBpc3N1ZXMsIGl0IGlzIGJldHRlciB0byBhbmFseXplIGF0IGRhaWx5IG9yIGhvdXJseSBzY2FsZXMuIElmIGEgd29yZCBpcyB2ZXJ5IHBvcHVsYXIsIHRoZW4sIGhpZ2hlciBmcmVxdWVuY2llcyBhcmUgbW9yZSByZWxldmFudC4gCgpgYGB7cn0KdHdlZXRzX2VuICU+JQogIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICMgVGhpcyBjcmVhdGVzIGEgbmV3IGNvbHVtbiB3aXRoIHJvdyBudW1iZXIKCnR3ZWV0c19lbiAlPiUKICByb3dpZF90b19jb2x1bW4oImVsZW1lbnRfaWQiKSAlPiUKICBsZWZ0X2pvaW4odHdlZXRfc2VudCwgYnkgPSAiZWxlbWVudF9pZCIpCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICBncm91cF9ieShkYXkgPSBkYXkoY3JlYXRlZF9hdCkpICU+JQogIHN1bW1hcmlzZShhdmdfc2VudCA9IG1lYW4oc2VudGltZW50KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gYXMuZmFjdG9yKGRheSksIHkgPSBhdmdfc2VudCkpICsgZ2VvbV9jb2woKSArIHhsYWIoImRheSIpCgp0d2VldHNfZW4gJT4lCiAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICBmaWx0ZXIoc2VudGltZW50ICE9IDApICU+JQogIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihob3VyKGNyZWF0ZWRfYXQpKSwgeSA9IHNlbnRpbWVudCkpICsgCiAgZ2VvbV9qaXR0ZXIoc2l6ZSA9IDAuMikgKwogIGdlb21fYm94cGxvdChhZXMoY29sb3IgPSBhcy5mYWN0b3IoaG91cihjcmVhdGVkX2F0KSkpLCBhbHBoYSA9IDAuNSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyB4bGFiKCJob3VyIikKYGBgCgoKCiMgUmVzb3VyY2VzCgpCZWxvdywgYSBzaG9ydCBsaXN0IG9mIHJlc291cmNlcyAodG8gYWNjZXNzIHRoaXJkLXBhcnR5IGRhdGEpOiAgIAoKLSAqKnRleHQgbWluaW5nIHdpdGggUioqIChvbmxpbmUgYm9vayk6IGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbSAgICAgIAotICoqQmxvb21iZXJnKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9SYmxwYXBpL2luZGV4Lmh0bWwgICAKLSAqKmdtYWlsKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nbWFpbHIvdmlnbmV0dGVzL2dtYWlsci5odG1sICAgCi0gKipHb29nbGUgTWFwcyoqOiBodHRwczovL2NyYW4ucnN0dWRpby5jb20vd2ViL3BhY2thZ2VzL21hcHNhcGkvdmlnbmV0dGVzL2ludHJvLmh0bWwgIAotICoqR29vZ2xlIHRyZW5kcyoqOiBodHRwczovL2dpdGh1Yi5jb20vUE1hc3NpY290dGUvZ3RyZW5kc1IKLSAqKkdvb2dsZSBBUElzKiogKG1vcmUgZ2VuZXJhbGx5KTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dhcmdsZS92aWduZXR0ZXMvYXV0aC1mcm9tLXdlYi5odG1sCi0gKipGYWNlYm9vayBBUEkqKjogZGV2ZWxvcGVycy5mYWNlYm9vay5jb20vYWRzL2Jsb2cvcG9zdC92Mi8yMDE4LzA1LzE1L2ZhY2Vib29rLXJlYWNoLWZyZXF1ZW5jeS1hcGkvICAKClBvc3NpYmx5IGRlcHJlY2F0ZWQ6ICAKLSAqKkZhY2Vib29rKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9SZmFjZWJvb2svaW5kZXguaHRtbCAgICAKLSAqKkluc3RhZ3JhbSoqOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvaW5zdGFSL2luZGV4Lmh0bWwKCmBgYHtyfQoKYGBgCg==