Text mining
Data retrieval
Now, let’s move forward to simple text analysis. First, we need to
prepare the data! (as usual)
tokens <- toots %>%
select(id, content) %>% # Keeps only id and text/content of the tweet
unnest_tokens(word, content) # 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 %>%
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") + theme_bw()
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") + theme_bw()
Problem: strange characters remain. We are going to
remove them by converting the text to ASCII format and omit NA
data.
new_stop_words <- c("https", "span", "class", "href", "target", "_blank", "rel", "tag",
"mastodon.social", "ellipsis", "mastodon.online", "mstdn.social", "amp",
"http", "invisible", "03", search_term, tolower(search_term), "d0", "src",
"tags", "mention", "noreferrer", "noopener", "nofollow", "hashtag", "translate",
"www", "url", "die", "der", "und", "a", "p", "br", "1", "2", "01", "02")
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% new_stop_words) # search_term defined above
)
tidy_tokens %>%
count(word, sort = TRUE) %>% # Count words
head(30) %>% # Keep only top words
ggplot(aes(y = reorder(word,n), x = n)) + geom_col() + ylab("Words") + theme_bw()
Perfect!
n-grams
See https://www.tidytextmining.com/ngrams.html
toots %>%
mutate(id = 1:nrow(toots)) %>% # This creates a tweet id
select(id, content, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, content, token = "ngrams", n = 2) %>%
filter(!(bigram %in% c("span a", "a href", "a a", "_blank span", "href https", "span class",
"a p", "p p", "br a", "target _blank", "noreferrer target", "span span",
"hastag rel", "class mention", "rel nofollow", "mention hashtag",
"class invisible", "invisible https", "p a",
"nofollow noopener", "noopener noreferrer", "hashtag rel"))) %>%
group_by(bigram) %>%
count(sort = T) %>%
head(20) %>%
ggplot(aes(y = reorder(bigram, n), x = n)) +
geom_col() + theme_bw() + ylab("bigrams")
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.
toots %>%
mutate(id = row_number()) %>% # This creates a tweet id
select(id, content, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, content, token = "ngrams", n = 2) %>%
mutate(bigram = iconv(bigram, from = "UTF-8", to = "ASCII")) %>%
na.omit() %>%
separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
filter(!(word1 %in% c(new_stop_words, stop_words$word)),
!(word2 %in% c(new_stop_words, stop_words$word))) %>%
group_by(bigram) %>%
count(sort = T) %>%
head(24) %>%
ggplot(aes(y = reorder(bigram, n), x = n)) + geom_col() + ylab("Bi-gram") +
theme_bw()
cloud_data <- toots %>%
mutate(id = row_number()) %>% # This creates a tweet id
select(id, content, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(bigram, content, token = "ngrams", n = 2) %>%
mutate(bigram = iconv(bigram, from = "UTF-8", to = "ASCII")) %>%
na.omit() %>%
separate(bigram, c("word1", "word2"), sep = " ", remove = F) %>%
filter(!(word1 %in% c(new_stop_words, stop_words$word)),
!(word2 %in% c(new_stop_words, stop_words$word))) %>%
group_by(bigram) %>%
count(sort = T) |>
mutate(length = nchar(bigram)) |>
filter(length < 15)
cloud_data
wordcloud(words = cloud_data$bigram,
freq = cloud_data$n, min.freq = 10,
max.words = 35, random.order = FALSE, rot.per = 0.10,
colors = brewer.pal(8, "Dark2"))
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
afinn |> filter(value > 3)
To create a nice visualization, we need to extract the
time of the tweets.
tokens_time <- toots %>%
mutate(id = row_number()) %>% # This creates a tweet id
select(id, content, created_at) %>% # Keeps id, text and date of the tweet
unnest_tokens(word, content) # 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)
Joining with `by = join_by(word)`
sentiment
We then compute the average sentiment, minute-by-minute, or
day-by-day, depending on frequency.
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 %>%
mutate(date = as.Date(created_at)) |>
group_by(date) %>%
#filter(year(date)==2024) |>
summarise(avg_sentiment = mean(value)) %>%
ggplot(aes(x = date, y = avg_sentiment)) + geom_col() + theme_bw()
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))
nrc
We then create the merged dataset.
emotions <- tokens_time %>%
inner_join(nrc) %>% # Merge data with sentiment
mutate(date = as.Date(created_at)) # Create day column
Joining with `by = join_by(word)`Warning: Detected an unexpected many-to-many relationship between `x` and `y`.
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(date, sentiment) %>%
summarise(intensity = n()) %>%
filter(year(date) == 2024) |>
ggplot(aes(x = date, y = intensity, fill = sentiment)) + geom_col() +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
scale_fill_viridis_d(option = "magma", direction = -1) + theme_bw()
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.
ggplotly(g)
This can also be shown in percentage format.
g <- emotions %>%
group_by(date, sentiment) %>%
filter(year(date) == 2024) |>
summarise(intensity = n()) %>%
ggplot(aes(x = date, 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_d(option = "magma", direction = -1) + theme_bw() +
geom_hline(yintercept = 0.5, linetype = 2)
`summarise()` has grouped output by 'date'. You can override using the `.groups` argument.
ggplotly(g)
emotions %>%
mutate(sentiment = if_else(sentiment < "negative", "positive", "negative")) %>%
group_by(date, sentiment) %>%
summarise(intensity = n()) %>%
ggplot(aes(x = date, y = intensity, fill = sentiment)) + geom_col(position = "fill") +
theme_bw() +
theme(axis.text.x = element_text(angle = 80,
size = 10,
hjust = 1)) + xlab("Time") +
geom_hline(yintercept = 0.5) +
scale_fill_manual(values = c("#223333", "#FFBB99"))
`summarise()` has grouped output by 'date'. 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!
# toots_en <- toots %>%
# mutate(language = textcat(content)) %>%
# filter(language == "english") %>%
# dplyr::select(created_at, content)
toots_en <- toots |> filter(language == "en")
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 <- toots_en$content %>%
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.
ggplot(aes(x = date, y = avg_sent)) + geom_col()
Error in `fortify()`:
! `data` must be a <data.frame>, or an object coercible by `fortify()`, not a <uneval> object.
ℹ Did you accidentally pass `aes()` to the `data` argument?
Backtrace:
1. ggplot2::ggplot(aes(x = date, y = avg_sent))
2. ggplot2:::ggplot.default(aes(x = date, y = avg_sent))
4. ggplot2:::fortify.default(data, ...)
LS0tCnRpdGxlOiAiVGhpcmQgcGFydHkgZGF0YSBhbmQgYmFzaWMgdGV4dCBtaW5pbmciCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgojIFRoZSBnZW5lcmFsIGlkZWEKCkRhdGEgdHJhbnNmZXIgaXMgaGlnaGx5IGNvbnRyb2xsZWQuIFRoZSBrZXkgbm90aW9ucyBhcmUgKiphdXRoZW50aWNhdGlvbioqIGFuZCAqKnByb3RvY29sKiouCgojIERvd25sb2FkaW5nIHRvb3RzIHdpdGggKnJ0b290KgoKVGhlcmUgYXJlIHNldmVyYWwgcGFja2FnZXMgdGhhdCBydW4gYW4gaW50ZXJmYWNlIHdpdGggdHdpdHRlcjogKnJ0d2VldCosICpSVHdpdHRlckFQSSosICpzdHJlYW1SKiBhbmQgKnR3aXR0ZVIqLgkKQnV0IHNpbmNlIEF1dGggVjIsIHdlIHdpbGwgbmVlZCAqKlJUd2l0dGVyVjIqKiEgQnV0IHRoaXMgb25seSBydW5zIG9uIFIgdjQuMiEgIApEb2N1bWVudGF0aW9uOiBodHRwczovL2dpdGh1Yi5jb20vTWFlbEt1YmxpL1JUd2l0dGVyVjIuICAgIApSZWNlbnQgcGFja2FnZXMgYXJlIGJldHRlciBiZWNhdXNlIGZpcm1zIHVwZGF0ZSB0aGVpciBBUEkgcG9saWNpZXMgKGFuZCBhY2Nlc3MpLCB0aHVzIG9sZCBwcm90b2NvbHMgc29tZXRpbWVzIGRvIG5vdCB3b3JrISAgIApVbmZvcnR1bmF0ZWx5LCB0aGUgVHdpdHRlciBBUEkgaXMgbm8gbG9uZ2VyIGZyZWUhICAgCkhlbmNlLCBpbiB0aGlzIG5vdGVib29rLCB3ZSB3aWxsIHRlc3QgdGhlIGNvbXBldGl0b3I6IFsqKm1hc3RvZG9uKipdKGh0dHBzOi8vam9pbm1hc3RvZG9uLm9yZy8pISAgClRoZSBwYWNrYWdlIGZvciB0aGlzIHdpbGwgYmUgWyoqcnRvb3QqKl0oaHR0cHM6Ly9zY2hvY2hhc3RpY3MuZ2l0aHViLmlvL3J0b290LykuCgojIyBGaXJzdCB0aGluZ3MgZmlyc3QKCioqRmlyc3QqKiwgdGhlIHBhY2thZ2VzLiBEb3dubG9hZC4uLgoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQppZighcmVxdWlyZShydG9vdCkpe2luc3RhbGwucGFja2FnZXMoInJ0b290Iil9CmBgYAoKLi4uIGFuZCBhY3RpdmF0ZS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJ0b290KQpgYGAKCiMjIEF1dGhlbnRpY2F0aW9uCgoqKlNlY29uZCoqOiBhdXRoZW50aWNhdGlvbgpZb3UgaGF2ZSB0byBjaG9vc2UgYSBwYXJ0aWN1bGFyIGluc3RhbmNlIG9mIHRoZSBuZXR3b3JrLgpQZXJzb25hbGx5LCBJIGFtIHJlZ2lzdGVyZWQgb24gInNjaWVuY2VzLnNvY2lhbCIsIHRoZSBsYXJnZXN0IG9uZSBpcyAibWFzdG9kb24uc29jaWFsIiAoc2VlIGh0dHBzOi8vbWFzdG9kb25zZXJ2ZXJzLm5ldC9zZXJ2ZXJzL3RvcCkKPT4gV3JpdGUgdGhlIGFuc3dlciB3aXRob3V0IHRoZSBxdW90YXRpb24gbWFya3MgYW5kIGNob29zZSBhIHB1YmxpYyB0b2NrZW4KCmBgYHtyfQpydG9vdDo6YXV0aF9zZXR1cCgKICBpbnN0YW5jZSA9ICJtYXN0b2Rvbi5zb2NpYWwiLAogIHR5cGUgPSAicHVibGljIgopCmBgYAoKYGBge3J9CiMgZ2V0X3RpbWVsaW5lX2hhc2h0YWcoaGFzaHRhZyA9ICJyc3RhdHMiLCAKIyAgICAgICAgICAgICAgICAgICAgICBpbnN0YW5jZSA9ICJtYXN0b2Rvbi5zb2NpYWwiLAojICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gMjAwKQpgYGAKCgpBdXRoZW50aWNhdGlvbiBjYW4gYmUgYW4gaW1wb3J0YW50IHBhcnQgb2YgdGhlIHByb2Nlc3MuIEZvciBtb3JlIGluZm8gb24gdGhhdDogIAotIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nb29nbGVzaGVldHMvdmlnbmV0dGVzL21hbmFnaW5nLWF1dGgtdG9rZW5zLmh0bWwgICAKLSBodHRwczovL2h0dHIuci1saWIub3JnL3JlZmVyZW5jZS9pbmRleC5odG1sIChzZWN0aW9uIEF1dGhlbnRpY2F0aW9uKSAgIAotIGh0dHBzOi8vYmxvZy5yLWh1Yi5pby8yMDIxLzAxLzI1L29hdXRoLTIuMC8KCiMjIEV4dHJhY3Rpb24KCklmIG5vIGVycm9yIGFwcGVhcnMsIHdlIGFyZSByZWFkeSB0byBxdWVyeS4gRGVwZW5kaW5nIG9uIHRoZSBudW1iZXIgb2YgcmVxdWVzdGVkIHR3ZWV0cywgdGhpcyBjYW4gdGFrZSBzb21lIHRpbWUuICAKClRoZXJlIGFyZSBkaWZmZXJlbnQgdHlwZXMgb2YgcXVlcmllcyB0aGF0IHRoZSBwYWNrYWdlcyBhbGxvd3MuICAgCkZvciBpbnN0YW5jZSwgYmVsb3cgd2UgdXNlIHRoZSAqKmdldF90aW1lbGluZV9oYXNodGFnKiogZnVuY3Rpb24gdG8gYWNjZXNzIHRvb3RzIHRoYXQgaW5jbHVkZSBvbmUgcGFydGljdWxhciB0ZXJtLCB0aGUgImhhc2h0YWciLiAgCgoKCmBgYHtyfQpzZWFyY2hfdGVybSA8LSAiZWxlY3Rpb24iCnRvb3RzIDwtIGdldF90aW1lbGluZV9oYXNodGFnKGhhc2h0YWcgPSBzZWFyY2hfdGVybSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGluc3RhbmNlID0gIm1hc3RvZG9uLnNvY2lhbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbWl0ID0gMjAwMCkKYGBgCgoKCiMgVGV4dCBtaW5pbmcKCiMjIFJlZmVyZW5jZXMKVGhlIHJlZmVyZW5jZSBib29rIGlzOiBodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20gICAgICAKQSBncmVhdCBpbnRlcmFjdGl2ZSB0dXRvcmlhbDogaHR0cHM6Ly9qdWxpYXNpbGdlLnNoaW55YXBwcy5pby9sZWFybnRpZHl0ZXh0LyAgICAKQW5kIHRoZSBwYWNrYWdlIGlzOiAgCgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9CmlmKCFyZXF1aXJlKHRpZHl0ZXh0KSl7aW5zdGFsbC5wYWNrYWdlcygidGlkeXRleHQiLCByZXBvcyA9ICJodHRwczovL2Nsb3VkLnItcHJvamVjdC5vcmcvIil9CmxpYnJhcnkodGlkeXRleHQpCmBgYAoKKHNlZSBhbHNvOiBodHRwczovL3F1YW50ZWRhLmlvL2luZGV4Lmh0bWwpCgojIyBEYXRhIHJldHJpZXZhbAoKTm93LCBsZXQncyBtb3ZlIGZvcndhcmQgdG8gc2ltcGxlIHRleHQgYW5hbHlzaXMuIEZpcnN0LCB3ZSBuZWVkIHRvIHByZXBhcmUgdGhlIGRhdGEhIChhcyB1c3VhbCkKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zIDwtIHRvb3RzICU+JSAKICAgIHNlbGVjdChpZCwgY29udGVudCkgJT4lICAgICAgICAgICAgICMgS2VlcHMgb25seSBpZCBhbmQgdGV4dC9jb250ZW50IG9mIHRoZSB0d2VldAogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCBjb250ZW50KSAgICAgICAgIyBDcmVhdGVzIHRva2VucyEKdG9rZW5zCmBgYAoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgd29yZCBmcmVxdWVuY2llcy4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JQogICAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCmBgYAoKVGhpcyBpcyBwb2xsdXRlZCBieSBzbWFsbCB3b3Jkcy4gTGV0J3MgZmlsdGVyIHRoYXQgKCpGSVJTVCBNRVRIT0QqKS4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KdG9rZW5zICU+JSBtdXRhdGUobGVuZ3RoID0gbmNoYXIod29yZCkpCmBgYAoKCiMjIERhdGEgZnJlcXVlbmNpZXMKTm93IGxldCdzIG9taXQgdGhlIHNtYWxsIHdvcmRzIChzbWFsbGVyIHRoYW4gNSBjaGFyYWN0ZXJzKS4gICAKKipOT1RFKio6IGFsbCB0aGUgdGhyZXNob2xkcyBiZWxvdyBkZXBlbmQgb24gdGhlIHNhbXBsZSEgCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnRva2VucyAlPiUKICAgIG11dGF0ZShsZW5ndGggPSBuY2hhcih3b3JkKSkgJT4lCiAgICBmaWx0ZXIobGVuZ3RoID4gNCkgJT4lICAgICAgICAgICAgICMgS2VlcCB3b3JkcyB3aXRoIGxlbmd0aCBsYXJnZXIgdGhhbiA0CiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjEpICU+JSAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdG9wIDEyIHdvcmRzCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiV29yZHMiKSArIHRoZW1lX2J3KCkKYGBgCgpBIGJldHRlciB3YXkgdG8gcHJvY2VlZCBpcyB0byByZW1vdmUgInN0b3Agd29yZHMiIGxpa2UgImEiLCAiSSIsICJvZiIsICJ0aGUiLCBldGMgKCpTRUNPTkQgTUVUSE9EKikuCkFsc28sIGl0IHdvdWxkIG1ha2Ugc2Vuc2UgdG8gcmVtb3ZlIHRoZSBzZWFyY2ggaXRlbSBhbmQgImh0dHBzIi4KCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KZGF0YSgic3RvcF93b3JkcyIpCnRpZHlfdG9rZW5zIDwtIHRva2VucyAlPiUgCiAgICBhbnRpX2pvaW4oc3RvcF93b3JkcykgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHVucmVsZXZhbnQgdGVybXMKdGlkeV90b2tlbnMgJT4lCiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICAgICAgICMgQ291bnQgd29yZHMKICAgIGhlYWQoMjApICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBLZWVwIG9ubHkgdG9wIDE1IHdvcmRzCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKHdvcmQsbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiV29yZHMiKSArIHRoZW1lX2J3KCkKYGBgCgoqKlByb2JsZW0qKjogc3RyYW5nZSBjaGFyYWN0ZXJzIHJlbWFpbi4gV2UgYXJlIGdvaW5nIHRvIHJlbW92ZSB0aGVtIGJ5IGNvbnZlcnRpbmcgdGhlIHRleHQgdG8gQVNDSUkgZm9ybWF0IGFuZCBvbWl0ICpOQSogZGF0YS4gCgpgYGB7ciwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9Cm5ld19zdG9wX3dvcmRzIDwtIGMoImh0dHBzIiwgInNwYW4iLCAiY2xhc3MiLCAiaHJlZiIsICJ0YXJnZXQiLCAiX2JsYW5rIiwgInJlbCIsICJ0YWciLAogICAgICAgICAgICAgICAgICAgICJtYXN0b2Rvbi5zb2NpYWwiLCAiZWxsaXBzaXMiLCAibWFzdG9kb24ub25saW5lIiwgIm1zdGRuLnNvY2lhbCIsICJhbXAiLAogICAgICAgICAgICAgICAgICAgICJodHRwIiwgImludmlzaWJsZSIsICIwMyIsIHNlYXJjaF90ZXJtLCB0b2xvd2VyKHNlYXJjaF90ZXJtKSwgImQwIiwgInNyYyIsCiAgICAgICAgICAgICAgICAgICAgInRhZ3MiLCAibWVudGlvbiIsICJub3JlZmVycmVyIiwgIm5vb3BlbmVyIiwgIm5vZm9sbG93IiwgImhhc2h0YWciLCAidHJhbnNsYXRlIiwKICAgICAgICAgICAgICAgICAgICAid3d3IiwgInVybCIsICJkaWUiLCAiZGVyIiwgInVuZCIsICJhIiwgInAiLCAiYnIiLCAiMSIsICIyIiwgIjAxIiwgIjAyIikKdGlkeV90b2tlbnMgPC0gdG9rZW5zICU+JSAKICAgIGFudGlfam9pbihzdG9wX3dvcmRzKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBSZW1vdmUgdW5yZWxldmFudAogICAgbXV0YXRlKHdvcmQgPSBpY29udih3b3JkLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkiKSkgJT4lICMgUHV0IGluIGxhdGluIGZvcm1hdAogICAgbmEub21pdCgpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJlbW92ZSBtaXNzaW5nCiAgICBmaWx0ZXIobmNoYXIod29yZCkgPiAyLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmVtb3ZlIHNtYWxsIHdvcmRzCiAgICAgICAgICAgISh3b3JkICVpbiUgbmV3X3N0b3Bfd29yZHMpICAjIHNlYXJjaF90ZXJtIGRlZmluZWQgYWJvdmUKICAgICkKdGlkeV90b2tlbnMgJT4lCiAgICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkgJT4lICAgICAgICAgIyBDb3VudCB3b3JkcwogICAgaGVhZCgzMCkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICMgS2VlcCBvbmx5IHRvcCB3b3JkcwogICAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcih3b3JkLG4pLCB4ID0gbikpICsgZ2VvbV9jb2woKSArIHlsYWIoIldvcmRzIikgKyB0aGVtZV9idygpCmBgYAoKUGVyZmVjdCEKCiMjIFdvcmQgY2xvdWQKClRoaXMgZGF0YSBjYW4gYWxzbyBiZSBzaG93biB3aXRoIGEgd29yZCBjbG91ZC4gV2Ugc2ltcGx5IHVzZSB0aGUgKndvcmRjbG91ZCogcGFja2FnZTogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3dvcmRjbG91ZC9pbmRleC5odG1sIAoKVGhlIHBhY2thZ2UgKndvcmRjbG91ZDIqIGFkZHMgYSBmZXcgZmVhdHVyZXM6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy93b3JkY2xvdWQyL3ZpZ25ldHRlcy93b3JkY2xvdWQuaHRtbAoKYGBge3IsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGg9OH0KaWYoIXJlcXVpcmUod29yZGNsb3VkKSl7aW5zdGFsbC5wYWNrYWdlcygid29yZGNsb3VkIil9CmxpYnJhcnkod29yZGNsb3VkKQpjbG91ZF9kYXRhIDwtIHRpZHlfdG9rZW5zICU+JSBjb3VudCh3b3JkKQp3b3JkY2xvdWQod29yZHMgPSBjbG91ZF9kYXRhJHdvcmQsIAogICAgICAgICAgZnJlcSA9IGNsb3VkX2RhdGEkbiwgbWluLmZyZXEgPSAxMCwKICAgICAgICAgIG1heC53b3JkcyA9IDgyLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgcm90LnBlciA9IDAuMTUsIAogICAgICAgICAgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKSkgCmBgYAoKIyMgbi1ncmFtcwoKU2VlIGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS9uZ3JhbXMuaHRtbAoKYGBge3IgYmlncmFtcywgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQp0b290cyAlPiUgCiAgICBtdXRhdGUoaWQgPSAxOm5yb3codG9vdHMpKSAlPiUgICAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgY29udGVudCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCBjb250ZW50LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgICBmaWx0ZXIoIShiaWdyYW0gJWluJSBjKCJzcGFuIGEiLCAiYSBocmVmIiwgImEgYSIsICJfYmxhbmsgc3BhbiIsICJocmVmIGh0dHBzIiwgInNwYW4gY2xhc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiYSBwIiwgInAgcCIsICJiciBhIiwgInRhcmdldCBfYmxhbmsiLCAibm9yZWZlcnJlciB0YXJnZXQiLCAic3BhbiBzcGFuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgImhhc3RhZyByZWwiLCAiY2xhc3MgbWVudGlvbiIsICJyZWwgbm9mb2xsb3ciLCAibWVudGlvbiBoYXNodGFnIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgImNsYXNzIGludmlzaWJsZSIsICJpbnZpc2libGUgaHR0cHMiLCAicCBhIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIm5vZm9sbG93IG5vb3BlbmVyIiwgIm5vb3BlbmVyIG5vcmVmZXJyZXIiLCAiaGFzaHRhZyByZWwiKSkpICU+JQogICAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICAgIGNvdW50KHNvcnQgPSBUKSAlPiUKICAgIGhlYWQoMjApICU+JQogICAgZ2dwbG90KGFlcyh5ID0gcmVvcmRlcihiaWdyYW0sIG4pLCB4ID0gbikpICsgCiAgZ2VvbV9jb2woKSArIHRoZW1lX2J3KCkgKyB5bGFiKCJiaWdyYW1zIikKYGBgCgpBZ2Fpbjogc2FtZSBpc3N1ZSB3aXRoIHN0b3Agd29yZHMhIFNvIHdlIG11c3QgcmVtb3ZlIHRoZW0gYWdhaW4uIEJ1dCBpdCdzIG1vcmUgY29tcGxpY2F0ZWQgbm93LgpXZSBjYW4gdXNlIHRoZSAqc2VwYXJhdGUqKCkgZnVuY3Rpb24gdG8gaGVscCB1cy4KCmBgYHtyfQp0b290cyAlPiUgCiAgICBtdXRhdGUoaWQgPSByb3dfbnVtYmVyKCkpICU+JSAgICAgICAgICMgVGhpcyBjcmVhdGVzIGEgdHdlZXQgaWQKICAgIHNlbGVjdChpZCwgY29udGVudCwgY3JlYXRlZF9hdCkgJT4lICAgIyBLZWVwcyBpZCwgdGV4dCBhbmQgZGF0ZSBvZiB0aGUgdHdlZXQKICAgIHVubmVzdF90b2tlbnMoYmlncmFtLCBjb250ZW50LCB0b2tlbiA9ICJuZ3JhbXMiLCBuID0gMikgJT4lCiAgICBtdXRhdGUoYmlncmFtID0gaWNvbnYoYmlncmFtLCBmcm9tID0gIlVURi04IiwgdG8gPSAiQVNDSUkiKSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzZXBhcmF0ZShiaWdyYW0sIGMoIndvcmQxIiwgIndvcmQyIiksIHNlcCA9ICIgIiwgcmVtb3ZlID0gRikgJT4lCiAgICBmaWx0ZXIoISh3b3JkMSAlaW4lIGMobmV3X3N0b3Bfd29yZHMsIHN0b3Bfd29yZHMkd29yZCkpLAogICAgICAgICAgICEod29yZDIgJWluJSBjKG5ld19zdG9wX3dvcmRzLCBzdG9wX3dvcmRzJHdvcmQpKSkgJT4lCiAgICBncm91cF9ieShiaWdyYW0pICU+JQogICAgY291bnQoc29ydCA9IFQpICU+JQogICAgaGVhZCgyNCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHkgPSByZW9yZGVyKGJpZ3JhbSwgbiksIHggPSBuKSkgKyBnZW9tX2NvbCgpICsgeWxhYigiQmktZ3JhbSIpICsKICAgIHRoZW1lX2J3KCkKYGBgCgpgYGB7ciwgZmlnLndpZHRoPTh9CmNsb3VkX2RhdGEgPC0gdG9vdHMgJT4lIAogICAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUgICAgICAgICAjIFRoaXMgY3JlYXRlcyBhIHR3ZWV0IGlkCiAgICBzZWxlY3QoaWQsIGNvbnRlbnQsIGNyZWF0ZWRfYXQpICU+JSAgICMgS2VlcHMgaWQsIHRleHQgYW5kIGRhdGUgb2YgdGhlIHR3ZWV0CiAgICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgY29udGVudCwgdG9rZW4gPSAibmdyYW1zIiwgbiA9IDIpICU+JQogICAgbXV0YXRlKGJpZ3JhbSA9IGljb252KGJpZ3JhbSwgZnJvbSA9ICJVVEYtOCIsIHRvID0gIkFTQ0lJIikpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIsIHJlbW92ZSA9IEYpICU+JQogICAgZmlsdGVyKCEod29yZDEgJWluJSBjKG5ld19zdG9wX3dvcmRzLCBzdG9wX3dvcmRzJHdvcmQpKSwKICAgICAgICAgICAhKHdvcmQyICVpbiUgYyhuZXdfc3RvcF93b3Jkcywgc3RvcF93b3JkcyR3b3JkKSkpICU+JQogICAgZ3JvdXBfYnkoYmlncmFtKSAlPiUKICAgIGNvdW50KHNvcnQgPSBUKSB8PgogIG11dGF0ZShsZW5ndGggPSBuY2hhcihiaWdyYW0pKSB8PgogIGZpbHRlcihsZW5ndGggPCAxNSkKICAKY2xvdWRfZGF0YQp3b3JkY2xvdWQod29yZHMgPSBjbG91ZF9kYXRhJGJpZ3JhbSwgCiAgICAgICAgICBmcmVxID0gY2xvdWRfZGF0YSRuLCBtaW4uZnJlcSA9IDEwLAogICAgICAgICAgbWF4LndvcmRzID0gMzUsIHJhbmRvbS5vcmRlciA9IEZBTFNFLCByb3QucGVyID0gMC4xMCwgCiAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpKSAKYGBgCgoKIyMgU2VudGltZW50CgpUaGlzIHNlY3Rpb24gaXMgaW5zcGlyZWQgZnJvbTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL3NlbnRpbWVudC5odG1sICAgIApTb21ldGltZXMsIHlvdSBtYXkgYmUgYXNrZWQgaW4gdGhlIHByb2Nlc3MgaWYgeW91ICpyZWFsbHkqIHdhbnQgdG8gZG93bmxvYWQgZGF0YSAobGV4aWNvbnMpLiAgCkp1c3Qgc2F5IHllcyBpbiB0aGUgKipjb25zb2xlKiogKHR5cGUgdGhlIGNvcnJlY3QgYW5zd2VyOiBpZiBub3QsIHlvdSB3aWxsIGJlIGJsb2NrZWQvc3RydWNrKS4KCkZpcnN0LCB3ZSBuZWVkIHRvIGxvYWQgc29tZSBzZW50aW1lbnQgbGV4aWNvbi4gQUZJTk4gaXMgb25lIHN1Y2ggc2VudGltZW50IGRhdGFiYXNlLiAKCmBgYHtyfQppZighcmVxdWlyZSh0ZXh0ZGF0YSkpe2luc3RhbGwucGFja2FnZXMoInRleHRkYXRhIiwgcmVwb3MgPSAiaHR0cHM6Ly9jbG91ZC5yLXByb2plY3Qub3JnLyIpfQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KHRleHRkYXRhKQphZmlubiA8LSBnZXRfc2VudGltZW50cygiYWZpbm4iKQphZmlubgphZmlubiB8PiBmaWx0ZXIodmFsdWUgPiAzKQpgYGAKClRvIGNyZWF0ZSBhIG5pY2UgdmlzdWFsaXphdGlvbiwgd2UgbmVlZCB0byBleHRyYWN0IHRoZSAqKnRpbWUqKiBvZiB0aGUgdHdlZXRzLgoKYGBge3J9CnRva2Vuc190aW1lIDwtIHRvb3RzICU+JSAKICAgIG11dGF0ZShpZCA9IHJvd19udW1iZXIoKSkgJT4lICAgICAgICAgIyBUaGlzIGNyZWF0ZXMgYSB0d2VldCBpZAogICAgc2VsZWN0KGlkLCBjb250ZW50LCBjcmVhdGVkX2F0KSAlPiUgICAjIEtlZXBzIGlkLCB0ZXh0IGFuZCBkYXRlIG9mIHRoZSB0d2VldAogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCBjb250ZW50KSAgICAgICAgICAjIENyZWF0ZXMgdG9rZW5zIQp0b2tlbnNfdGltZQpgYGAKCldlIHRoZW4gdXNlICoqaW5uZXJfam9pbioqKCkgdG8gbWVyZ2UgdGhlIHR3byBzZXRzLiBUaGlzIGZ1bmN0aW9uIHJlbW92ZXMgdGhlIGNhc2VzIHdoZW4gYSBtYXRjaCBkb2VzIG5vdCBvY2N1ci4KCmBgYHtyfQpsaWJyYXJ5KGx1YnJpZGF0ZSkKc2VudGltZW50IDwtIHRva2Vuc190aW1lICU+JSAKICAgIGlubmVyX2pvaW4oYWZpbm4pICU+JQogICAgbXV0YXRlKGRheSA9IGRheShjcmVhdGVkX2F0KSwKICAgICAgICAgICBob3VyID0gaG91cihjcmVhdGVkX2F0KSAvIDI0LAogICAgICAgICAgIG1pbnV0ZSA9IG1pbnV0ZShjcmVhdGVkX2F0KSAvIDYwIC8gMjQsCiAgICAgICAgICAgdGltZSA9IGRheSArIGhvdXIgKyBtaW51dGUpCnNlbnRpbWVudApgYGAKCldlIHRoZW4gY29tcHV0ZSB0aGUgYXZlcmFnZSBzZW50aW1lbnQsIG1pbnV0ZS1ieS1taW51dGUsIG9yIGRheS1ieS1kYXksIGRlcGVuZGluZyBvbiBmcmVxdWVuY3kuICAgCk9mIGNvdXJzZSwgYXZlcmFnZSBzZW50aW1lbnQgY2FuIGJlIG1pc2xlYWRpbmcuIEluZGVlZCwgaWYgYSB0ZXh0IGNvbnRhaW5zIHRoZSB0ZXJtcyAiKkknbSBub3QgaGFwcHkqIiwgdGhlbiBvbmx5ICIqaGFwcHkqIiB3aWxsIGJlIHRhZ2dlZCwgd2hpY2ggaXMgdGhlIG9wcG9zaXRlIG9mIHRoZSBpbnRlbmRlZCBtZWFuaW5nLgoKYGBge3J9CnNlbnRpbWVudCAlPiUKICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoY3JlYXRlZF9hdCkpIHw+CiAgICBncm91cF9ieShkYXRlKSAlPiUKICAgICNmaWx0ZXIoeWVhcihkYXRlKT09MjAyNCkgfD4KICAgIHN1bW1hcmlzZShhdmdfc2VudGltZW50ID0gbWVhbih2YWx1ZSkpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gZGF0ZSwgeSA9IGF2Z19zZW50aW1lbnQpKSArIGdlb21fY29sKCkgKyB0aGVtZV9idygpCmBgYAoKCldoYXQgYWJvdXQgZW1vdGlvbnM/IFRoZSAqKk5SQyoqIGxleGljb24gY2F0ZWdvcml6ZXMgKmVtb3Rpb25zKi4gQmVsb3csIHdlIG9yZGVyIGVtb3Rpb25zLiBUaGUgbW9zdCBpbXBvcnRhbnQgaW1wYWN0IGlzIHRoZSBkaWNob3RvbXkgYmV0d2VlbiBwb3NpdGl2ZSAmIG5lZ2F0aXZlIGVtb3Rpb25zLiAKCmBgYHtyLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbnJjIDwtIGdldF9zZW50aW1lbnRzKCJucmMiKQpucmMgPC0gbnJjICU+JQogICAgbXV0YXRlKHNlbnRpbWVudCA9IGFzLmZhY3RvcihzZW50aW1lbnQpLAogICAgICAgICAgIHNlbnRpbWVudCA9IHJlY29kZV9mYWN0b3Ioc2VudGltZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgam95ID0gImpveSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cnVzdCA9ICJ0cnVzdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJwcmlzZSA9ICJzdXJwcmlzZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbnRpY2lwYXRpb24gPSAiYW50aWNpcGF0aW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gInBvc2l0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5lZ2F0aXZlID0gIm5lZ2F0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhZG5lc3MgPSAic2FkbmVzcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbmdlciA9ICJhbmdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmZWFyID0gImZlYXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlndXN0ID0gImRpc2d1c3QiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLm9yZGVyZWQgPSBUKSkKbnJjCmBgYAoKV2UgdGhlbiBjcmVhdGUgdGhlIG1lcmdlZCBkYXRhc2V0LgoKYGBge3J9CmVtb3Rpb25zIDwtIHRva2Vuc190aW1lICU+JSAKICAgIGlubmVyX2pvaW4obnJjKSAlPiUgICAgICAgICAgICAgICAgICAgICAjIE1lcmdlIGRhdGEgd2l0aCBzZW50aW1lbnQKICAgIG11dGF0ZShkYXRlID0gYXMuRGF0ZShjcmVhdGVkX2F0KSkgICAgICAjIENyZWF0ZSBkYXkgY29sdW1uCmVtb3Rpb25zICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTaG93IHRoZSByZXN1bHQKYGBgCgpUaGUgbWVyZ2luZyBoYXMgcmVkdWNlZCB0aGUgc2l6ZSBvZiB0aGUgZGF0YXNldCwgYnV0IHRoZXJlIHN0aWxsIHJlbWFpbnMgZW5vdWdoIHRvIHB1cnN1ZSB0aGUgc3R1ZHkuICAgCkZpbmFsbHksIHdlIG1vdmUgdG8gdGhlIHBpdm90LXRhYmxlIHRoYXQgY291bnRzIGVtb3Rpb25zIGZvciBlYWNoIGRheS4KCmBgYHtyfQpnIDwtIGVtb3Rpb25zICU+JSAKICAgIGdyb3VwX2J5KGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICAgIGZpbHRlcih5ZWFyKGRhdGUpID09IDIwMjQpIHw+CiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gaW50ZW5zaXR5LCBmaWxsID0gc2VudGltZW50KSkgKyBnZW9tX2NvbCgpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJtYWdtYSIsIGRpcmVjdGlvbiA9IC0xKSArIHRoZW1lX2J3KCkKZ2dwbG90bHkoZykKYGBgCgpUaGlzIGNhbiBhbHNvIGJlIHNob3duIGluIHBlcmNlbnRhZ2UgZm9ybWF0LiAKCmBgYHtyfQpnIDwtIGVtb3Rpb25zICU+JSAKICAgIGdyb3VwX2J5KGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgICBmaWx0ZXIoeWVhcihkYXRlKSA9PSAyMDI0KSB8PgogICAgc3VtbWFyaXNlKGludGVuc2l0eSA9IG4oKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gaW50ZW5zaXR5LCBmaWxsID0gc2VudGltZW50KSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJmaWxsIikgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA4MCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpKSArIHhsYWIoIlRpbWUiKSArCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfZChvcHRpb24gPSAibWFnbWEiLCBkaXJlY3Rpb24gPSAtMSkgKyB0aGVtZV9idygpICsgCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gMikKZ2dwbG90bHkoZykKYGBgCgpgYGB7cn0KZW1vdGlvbnMgJT4lIAogICAgbXV0YXRlKHNlbnRpbWVudCA9IGlmX2Vsc2Uoc2VudGltZW50IDwgIm5lZ2F0aXZlIiwgInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSAKICAgIGdyb3VwX2J5KGRhdGUsIHNlbnRpbWVudCkgJT4lCiAgICBzdW1tYXJpc2UoaW50ZW5zaXR5ID0gbigpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBpbnRlbnNpdHksIGZpbGwgPSBzZW50aW1lbnQpKSArIGdlb21fY29sKHBvc2l0aW9uID0gImZpbGwiKSArCiAgICB0aGVtZV9idygpICsgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDgwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gMSkpICsgeGxhYigiVGltZSIpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuNSkgKyAKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMyMjMzMzMiLCAiI0ZGQkI5OSIpKSAKYGBgCgoKCgojIyBBZHZhbmNlZCBzZW50aW1lbnQgCgpUaGUgcHJvYmxlbSB3aXRoIHRoZSBwcmVjZWRpbmcgbWV0aG9kcyBpcyB0aGF0IHRoZXkgZG9uJ3QgdGFrZSBpbnRvIGFjY291bnQgKip2YWxlbmNlIHNoaWZ0ZXJzKiogKGkuZS4sIG5lZ2F0b3JzLCBhbXBsaWZpZXJzIChpbnRlbnNpZmllcnMpLCBkZS1hbXBsaWZpZXJzIChkb3dudG9uZXJzKSwgYW5kIGFkdmVyc2F0aXZlIGNvbmp1bmN0aW9ucykuIElmIGEgdHdlZXQgc2F5cyAqbm90IGhhcHB5KiwgY291bnRpbmcgdGhlIHdvcmQgKmhhcHB5KiBpcyBub3QgYSBnb29kIGlkZWEhIFRoZSBwYWNrYWdlICpzZW50aW1lbnRyKiBpcyBidWlsdCB0byBjaXJjdW12ZW50IHRoZXNlIGlzc3VlczogaGF2ZSBhIGxvb2sgYXQgaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvc2VudGltZW50ciAgCihzZWUgYWxzbzogaHR0cHM6Ly93d3cuc2VudG9tZXRyaWNzLm9yZyBhbmQgdGhlIGJvb2sgKipTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFRleHQgQW5hbHlzaXMgaW4gUioqIGhvc3RlZCBhdCBodHRwczovL3NtbHRhci5jb20pCgpJIGhhdmVuJ3QgdGVzdGVkICoqYXdzLmNvbXByZWhlbmQqKiwgYnV0IGl0IHNlZW1zIHByb21pc2luZzogaHR0cHM6Ly9naXRodWIuY29tL2Nsb3VkeXIvYXdzLmNvbXByZWhlbmQKCmBgYHtyLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KaWYoIXJlcXVpcmUoc2VudGltZW50cikpe2luc3RhbGwucGFja2FnZXMoYygic2VudGltZW50ciIsICJ0ZXh0Y2F0IikpfQpsaWJyYXJ5KHNlbnRpbWVudHIpCmxpYnJhcnkodGV4dGNhdCkKYGBgCgpGaXJzdCwgbGV0J3Mga2VlcCBvbmx5IHRoZSB0d2VldHMgd3JpdHRlbiBpbiBFbmdsaXNoIQoKYGBge3J9CiMgdG9vdHNfZW4gPC0gdG9vdHMgJT4lCiMgICAgIG11dGF0ZShsYW5ndWFnZSA9IHRleHRjYXQoY29udGVudCkpICU+JQojICAgICBmaWx0ZXIobGFuZ3VhZ2UgPT0gImVuZ2xpc2giKSAlPiUKIyAgICAgZHBseXI6OnNlbGVjdChjcmVhdGVkX2F0LCBjb250ZW50KQoKdG9vdHNfZW4gPC0gdG9vdHMgfD4gZmlsdGVyKGxhbmd1YWdlID09ICJlbiIpCmBgYAoKKipOT1RFKio6IHRoZSBjb2RlIGFib3ZlIHdhcyB1c2VkIHRvIHNob3cgdGhlIGZ1bmN0aW9uICp0ZXh0Y2F0KjogdGhlIGxhbmd1YWdlIGlzIGFscmVhZHkgY29kZWQgaW4gdGhlIHR3ZWV0cyB2aWEgdGhlICoqbGFuZyoqIGNvbHVtbi92YXJpYWJsZS4gKGl0IHN1ZmZpY2VzIHRvIGtlZXAgdGhlIGluc3RhbmNlcyBmb3Igd2hpY2ggbGFuZyA9PSAiZW4iKQoKTmV4dCwgd2UgY29tcHV0ZSBhZHZhbmNlZCBzZW50aW1lbnQuIAoKYGBge3J9CnR3ZWV0X3NlbnQgPC0gdG9vdHNfZW4kY29udGVudCAlPiUKICAgIGdldF9zZW50ZW5jZXMoKSAlPiUgICMgSW50ZXJtZWRpYXRlIGZ1bmN0aW9uCiAgICBzZW50aW1lbnQoKSAgICAgICAgICAjIFNlbnRpbWVudCEKdHdlZXRfc2VudApgYGAKCioqTk9URSoqOiBkZXBlbmRpbmcgb24gZnJlcXVlbmN5IGlzc3VlcywgaXQgaXMgYmV0dGVyIHRvIGFuYWx5emUgYXQgZGFpbHkgb3IgaG91cmx5IHNjYWxlcy4gSWYgYSB3b3JkIGlzIHZlcnkgcG9wdWxhciwgdGhlbiwgaGlnaGVyIGZyZXF1ZW5jaWVzIGFyZSBtb3JlIHJlbGV2YW50LiAKCmBgYHtyfQp0b290c19lbiAlPiUKICAgIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICMgVGhpcyBjcmVhdGVzIGEgbmV3IGNvbHVtbiB3aXRoIHJvdyBudW1iZXIKCnRvb3RzX2VuICU+JQogICAgcm93aWRfdG9fY29sdW1uKCJlbGVtZW50X2lkIikgJT4lCiAgICBsZWZ0X2pvaW4odHdlZXRfc2VudCwgYnkgPSAiZWxlbWVudF9pZCIpCgp0b290c19lbiAlPiUKICAgIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICU+JQogICAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICAgIGdyb3VwX2J5KGRhdGUgPSBtYWtlX2RhdGUoZGF5ID0gZGF5KGNyZWF0ZWRfYXQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9udGggPSBtb250aChjcmVhdGVkX2F0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeWVhciA9IHllYXIoY3JlYXRlZF9hdCkpKSAlPiUKICAgIHN1bW1hcmlzZShhdmdfc2VudCA9IG1lYW4oc2VudGltZW50KSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gYXZnX3NlbnQpKSArIGdlb21fY29sKCkgCgp0b290c19lbiAlPiUKICAgIHJvd2lkX3RvX2NvbHVtbigiZWxlbWVudF9pZCIpICU+JQogICAgbGVmdF9qb2luKHR3ZWV0X3NlbnQsIGJ5ID0gImVsZW1lbnRfaWQiKSAlPiUKICAgIGZpbHRlcihzZW50aW1lbnQgIT0gMCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoaG91cihjcmVhdGVkX2F0KSksIHkgPSBzZW50aW1lbnQpKSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKwogICAgZ2VvbV9qaXR0ZXIoc2l6ZSA9IDAuMikgKwogICAgZ2VvbV9ib3hwbG90KGFlcyhjb2xvciA9IGFzLmZhY3Rvcihob3VyKGNyZWF0ZWRfYXQpKSksIGFscGhhID0gMC41KSArCiAgICB0aGVtZV9idygpICsgCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgeGxhYigiaG91ciIpIApgYGAKCgoKIyBSZXNvdXJjZXMKCkJlbG93LCBhIHNob3J0IGxpc3Qgb2YgcmVzb3VyY2VzICh0byBhY2Nlc3MgdGhpcmQtcGFydHkgZGF0YSk6ICAgCgotICoqdGV4dCBtaW5pbmcgd2l0aCBSKiogKG9ubGluZSBib29rKTogaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tICAgICAgCi0gKipCbG9vbWJlcmcqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JibHBhcGkvaW5kZXguaHRtbCAgIAotICoqZ21haWwqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dtYWlsci92aWduZXR0ZXMvZ21haWxyLmh0bWwgICAKLSAqKkdvb2dsZSBNYXBzKio6IGh0dHBzOi8vY3Jhbi5yc3R1ZGlvLmNvbS93ZWIvcGFja2FnZXMvbWFwc2FwaS92aWduZXR0ZXMvaW50cm8uaHRtbCAgCi0gKipHb29nbGUgdHJlbmRzKio6IGh0dHBzOi8vZ2l0aHViLmNvbS9QTWFzc2ljb3R0ZS9ndHJlbmRzUgotICoqR29vZ2xlIEFQSXMqKiAobW9yZSBnZW5lcmFsbHkpOiBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2FyZ2xlL3ZpZ25ldHRlcy9hdXRoLWZyb20td2ViLmh0bWwKLSAqKkZhY2Vib29rIEFQSSoqOiBkZXZlbG9wZXJzLmZhY2Vib29rLmNvbS9hZHMvYmxvZy9wb3N0L3YyLzIwMTgvMDUvMTUvZmFjZWJvb2stcmVhY2gtZnJlcXVlbmN5LWFwaS8gIAoKUG9zc2libHkgZGVwcmVjYXRlZDogIAotICoqRmFjZWJvb2sqKjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL1JmYWNlYm9vay9pbmRleC5odG1sICAgIAotICoqSW5zdGFncmFtKio6IGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9pbnN0YVIvaW5kZXguaHRtbAoKYGBge3J9CgpgYGAK