Netflix datset text analysis

Introduction

In this example we are going to do some text and sentimental analysis with the Netflix dataset. The dataset has a lot of information so you may do many analysis based on your research question and target variable.

Data source: https://www.kaggle.com/datasets/shivamb/netflix-shows/download?datasetVersionNumber=5

The main libraries we will use are:

library(dplyr)
library(tidytext)
library(ggplot2)
library(ggthemes)
library(stringr)
library(tidyr)

we will work on description variable so we will select only this columns as a start.

library(readr)
netflix_titles <- read_csv("netflix_titles.csv")
Rows: 8807 Columns: 12── Column specification ───────────────────────────────────────────────────
Delimiter: ","
chr (11): show_id, type, title, director, cast, country, date_added, ra...
dbl  (1): release_year
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
View(netflix_titles)

Dealing with stopwords helps a lot, especially when our text information is not clear and official. Below we may see some of the stop-words used in main libraries, but you may also create your own stop-word vector and remove unnecessary words from your text. For more on this refer to the work done here: https://github.com/EGjika/Text-Analysis-NASA-dataset

data("stop_words")
head(stop_words)
# or we may use 
stop_words$word[which(stop_words$word %in% sentiments$word)] %>% head(20)
 [1] "appreciate"    "appropriate"   "available"     "awfully"      
 [5] "best"          "better"        "clearly"       "enough"       
 [9] "like"          "liked"         "reasonably"    "right"        
[13] "sensible"      "sorry"         "thank"         "unfortunately"
[17] "unlikely"      "useful"        "welcome"       "well"         

World cloud

For a start we will use wordcloud to have a better view of our text. We may apply it at “description” or even to understand which cast and director has mostly appeared. ### For description

# Libraries used
library(wordcloud)
library(SnowballC)
#generate word cloud
set.seed(1234)
wordcloud(words = netflix_titles$description, max.words=100, random.order=FALSE, rot.per=0.40, colors=brewer.pal(8, "Dark2"))
Warning: transformation drops documentsWarning: transformation drops documents

For Cast and director

set.seed(1234)
wordcloud(words = netflix_titles$cast, max.words=100, random.order=FALSE, rot.per=0.40, colors=brewer.pal(8, "Dark2"))
Warning: transformation drops documentsWarning: transformation drops documents

set.seed(1234)
wordcloud(words = netflix_titles$director, max.words=100, random.order=FALSE, rot.per=0.40, colors=brewer.pal(8, "Dark2"))
Warning: transformation drops documentsWarning: transformation drops documents

Word Association

We may also use correlation as a statistical measure to demonstrate whether, and how strongly, pairs of variables are related. In this case we are looking at analyzing which words occur most often in association with the most frequently occurring words. This script shows which words are most frequently associated with the terms “good”,“work”,“health”,“love”,“comedy” (corlimit = 0.3 is the lower limit/threshold set. You can set it lower to see more words, or higher to see less). or, even change the list of words based on theri frequency (higher better).

library(tm)
# Build a term-document matrix
Netflx_dtm <- TermDocumentMatrix(netflix_titles$description)
# Find associations 
findAssocs(Netflx_dtm, terms = c("good","work","health","love","comedy"), corlimit = 0.3)           
$good
numeric(0)

$work
numeric(0)

$health
mental 
  0.37 

$love
numeric(0)

$comedy
numeric(0)
# Find associations for words that occur at least 50 times
findAssocs(Netflx_dtm, terms = findFreqTerms(Netflx_dtm, lowfreq = 1000), corlimit = 0.3)
$and
numeric(0)

$her
numeric(0)

$his
numeric(0)

$the
numeric(0)

$from
numeric(0)

$this
documentary 
       0.39 

$their
numeric(0)

$with
numeric(0)

$when
numeric(0)

$`for`
numeric(0)

Tokenization

Here we will start our process of tikenization for our “description” column.

library(dplyr)
tidy_netflix_description <- netflix_titles %>%
select("show_id","description")
head(tidy_netflix_title)

Netflx<-tidy_netflix_description %>%
   unnest_tokens(
    output = word,
    input = description,# our column of interest
    token = 'words',
    drop = FALSE
  ) %>%
  ungroup()
head(Netflx,24)# show just first 24 rows of words
NA

Let’s create a frequency table of words used in “description”:

Netflx<- Netflx %>%
unnest_tokens(word, description) %>%
count(word, sort = TRUE)
Netflx

Now, you can remove the stop words from your data frame:

Netflx <- Netflx %>%
anti_join(stop_words)
Joining, by = "word"
Netflx # after removing stop_words

Next, run the following to plot the words in Netflix Description that appear more than 50 times. You will see in this situation almost nothing to understand so, why not filter based on the frequency. (try to change the number of frequency to other frequencies 3000)


Netflx %>%
filter(n > 50) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col(fill = "darkred") +
theme_fivethirtyeight() +
xlab(NULL) +
ylab("Word Count") +
  coord_flip() +
ggtitle("Word Usage in Netflix description more than 50 times")


Netflx %>%
filter(n > 3000) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n)) +
geom_col(fill = "darkred") +
theme_fivethirtyeight() +
xlab(NULL) +
ylab("Word Count") +
  coord_flip() +
ggtitle("Word Usage in Netflix description more than 50 times")

Why not group by show_id? Let’s do it!

Netflx_s<-tidy_netflix_description %>%
   unnest_tokens(
    output = word,
    input = description,# our column of interest
    token = 'words',
    drop = FALSE
  ) %>%
  ungroup()
# get sentiment via inner join
netflix_sentiment = Netflx_s %>% 
  inner_join(get_sentiments("afinn")) %>% 
  group_by(show_id, description) %>% # group by show_id and description
  dplyr::summarise(sentiment=sum(value)) %>% # value will be the numerical sentiment given and we want to group by show_id
  ungroup()
Joining, by = "word"`summarise()` has grouped output by 'show_id'. You can override using the `.groups` argument.
netflix_sentiment

Next! We may filter those show_id which are in a given range of sentiment.

netflix_sentiment%>%
filter(sentiment >=12) %>%
ggplot(aes(show_id, sentiment)) +
theme_fivethirtyeight() +
geom_col() +
xlab(NULL) +
coord_flip() +
ylab("Word Count") +
ggtitle("Sentiment greater than 12 in Netflix show_id", subtitle = "Sentiment Analysis Using NRC")

Sentiment Lexicons

There are three mostly used lexicons in R:

get_sentiments("bing")
get_sentiments("afinn")
get_sentiments("nrc")

Now we will try BING lexicon on description.

Netflx_bing <- Netflx %>%
inner_join(get_sentiments("bing"))
Joining, by = "word"
Netflx_bing

Library “yarr”

We may create some graphs here . They work better if we have also a time variable we may use. Why not try? (exercise for you)

library(yarrr)
Loading required package: jpeg
Loading required package: BayesFactor
Loading required package: coda
Loading required package: Matrix

Attaching package: ‘Matrix’

The following objects are masked from ‘package:tidyr’:

    expand, pack, unpack

************
Welcome to BayesFactor 0.9.12-4.4. If you have questions, please contact Richard Morey (richarddmorey@gmail.com).

Type BFManual() to open the manual.
************
Loading required package: circlize
========================================
circlize version 0.4.15
CRAN page: https://cran.r-project.org/package=circlize
Github page: https://github.com/jokergoo/circlize
Documentation: https://jokergoo.github.io/circlize_book/book/

If you use it in published research, please cite:
Gu, Z. circlize implements and enhances circular visualization
  in R. Bioinformatics 2014.

This message can be suppressed by:
  suppressPackageStartupMessages(library(circlize))
========================================

yarrr v0.1.5. Citation info at citation('yarrr'). Package guide at yarrr.guide()
Email me at Nathaniel.D.Phillips.is@gmail.com

Attaching package: ‘yarrr’

The following object is masked from ‘package:ggplot2’:

    diamonds
pirateplot(formula =  n ~ word + sentiment, #Formula
   data = Netflx_bing, #Data frame
   xlab = NULL, ylab = "Word Count", #Axis labels
   main = "Lexical Diversity Netflix", #Plot title
   pal = "google", #Color scheme
   point.o = .2, #Points
   avg.line.o = 1, #Turn on the Average/Mean line
   theme = 0, #Theme
   point.pch = 16, #Point `pch` type
   point.cex = 1.5, #Point size
   jitter.val = .1, #Turn on jitter to see the songs better
   cex.lab = .9, cex.names = .7) #Axis label size

To obtain a better view we may filter some of the words with a given number of frequency:

Netflx_bing %>%
  filter(n>=1500) %>%
pirateplot(formula =  n ~ word + sentiment, #Formula
   xlab = NULL, ylab = "Word Count", #Axis labels
   main = "Lexical Diversity Netflix", #Plot title
   pal = "google", #Color scheme
   point.o = .2, #Points
   avg.line.o = 1, #Turn on the Average/Mean line
   theme = 0, #Theme
   point.pch = 16, #Point `pch` type
   point.cex = 1.5, #Point size
   jitter.val = .1, #Turn on jitter to see the songs better
   cex.lab = .9, cex.names = .7) #Axis label size

For more reference: https://www.datacamp.com/tutorial/sentiment-analysis-R

Netflx_nrc <- Netflx %>%
inner_join(get_sentiments("nrc"))
Joining, by = "word"
Netflx_nrc
Netflx_nrc %>%
filter(n > 2000) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill=sentiment)) +
theme_fivethirtyeight() +
geom_col() +
xlab(NULL) +
coord_flip() +
ylab("Word Count") +
ggtitle("Word Usage in Netflix description", subtitle = "Sentiment Analysis Using NRC")

Produce a horizontal bar chart showing positive and negative word usage in Netflix description using the Bing et al. sentiment lexicon.(try to change the value 2000 and observe the words)

Netflx_bing %>%
filter(n > 2000) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill=sentiment)) +
theme_fivethirtyeight() +
geom_col() +
xlab(NULL) +
coord_flip() +
ylab("Word Count") +
ggtitle("Word Usage in Netflix description", subtitle = "Sentiment Analysis Using
Bing et al.")

Netflx_nrc %>%
filter(n > 2000) %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill=sentiment)) +
theme_fivethirtyeight() +
geom_col() +
xlab(NULL) +
coord_flip() +
ylab("Word Count") +
ggtitle("Word Usage in Netflix description", subtitle = "Sentiment Analysis Using nrc")

Try to filter some of the sentiments as in the examples below. I am interested on knowwing which words are related to the sentiment “surprise” and then “love” in teh description of the Netflix Movies or TV Shows.

Net_nrc_surprise <- get_sentiments("nrc") %>%
filter(sentiment == "surprise")
Net_nrc_surprise
Netflx %>%
inner_join(Net_nrc_surprise)
Joining, by = "word"
Net_nrc_love <- get_sentiments("nrc") %>%
filter(sentiment == "love")
Net_nrc_love
Netflx %>%
inner_join(Net_nrc_love)
Joining, by = "word"

Try another word and explore some relationships!! We will see how NRC result shows for some of the sentiments and top words.

Netflx_nrc %>% 
  filter(n>2500) %>% #  filter only those frequency greater than 2000
mutate(word = reorder(word, n)) %>%
count(word,sentiment,sort=TRUE) %>%
group_by(sentiment)%>%
  top_n(n=5) %>% 
ungroup() %>%
  ggplot(aes(x=reorder(word,n),y=n,fill=sentiment)) + 
  geom_col(show.legend = FALSE) + 
  facet_wrap(~sentiment,scales="free") + 
  coord_flip()
Selecting by n

Comparing Sentiment Lexicons

library(stringr)
library(tidyr)

Add more filtering such as “type” because we want more info also on the type of the title if it was a Movie or a TV show.

Netflx_type <- netflix_titles %>%
select("show_id","type","description")
head(Netflx_type)

Netflx_type <-Netflx_type %>%
unnest_tokens(word, description) %>%
anti_join(stop_words)
Joining, by = "word"
Netflx_type
NA

Let’s use AFINN lexicon to observe the sentiment of Movie and TV shows.

Netflx_type_afinn <- Netflx_type %>%
inner_join(get_sentiments("afinn")) %>%
  group_by(index = type) %>%
summarize(sentiment = sum(value)) %>% # try to get also a mean 
mutate(method = "AFINN")
Joining, by = "word"
Netflx_type_afinn
Netflx_type_bing <- Netflx_type %>%
inner_join(get_sentiments("bing")) %>%
  group_by(index = type) %>%
#summarize(sentiment = sum(value)) %>% # try to get also a mean 
mutate(method = "BING")
Joining, by = "word"
Netflx_type_bing
Netflx_bing %>% 
  filter(n>2500) %>% #  filter only those frequency greater than 2500
mutate(word = reorder(word, n)) %>%
count(word,sentiment,sort=TRUE) %>%
group_by(sentiment) %>% 
  top_n(n=10) %>% 
ungroup() %>%
  ggplot(aes(x=reorder(word,n),y=n,fill=sentiment)) + 
  geom_col(show.legend = FALSE) + 
  facet_wrap(~sentiment,scales="free") + 
  coord_flip()
Selecting by n

Let’s do a comparision between sentiments and lexicons for the “type” of the Netflix: TV show or Movie

Netflx_bingnrc <- bind_rows(Netflx_type %>%
inner_join(get_sentiments("bing")) %>%
mutate(method = "Bing et al."),Netflx_type %>%
inner_join(get_sentiments("nrc") %>%
filter(sentiment %in% c("positive","negative"))) %>%
mutate(method = "NRC")) %>%
count(method, index = type, sentiment) %>%
spread(sentiment, n, fill = 0) %>%
mutate(sentiment = positive - negative)
Joining, by = "word"Joining, by = "word"
Netflx_bingnrc
Netflx_bingnrc <- bind_rows(Netflx_type %>%
inner_join(get_sentiments("bing")) %>%
mutate(method = "Bing et al."),Netflx_type %>%
inner_join(get_sentiments("nrc") %>%
filter(sentiment %in% c("positive","negative"))) %>%
mutate(method = "NRC")) %>%
count(method, index = word, sentiment) %>% # try to change index "word" or "type"
spread(sentiment, n, fill = 0) %>%
mutate(sentiment = positive - negative)
Joining, by = "word"Joining, by = "word"
Netflx_bingnrc

How much each word contributes to the overall sentiment of the descriptions .

Netflix_bingcounts <- Netflx_bing %>%
inner_join(get_sentiments("bing")) %>%
count(word, sentiment, sort = TRUE) %>%
ungroup()
Joining, by = c("word", "sentiment")
Netflix_bingcounts
Netflix_bingcounts %>%
 group_by(sentiment) %>%
#top_n(2) %>%
ungroup() %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
coord_flip() +
theme_fivethirtyeight() +
ggtitle("Words' Contribution to Sentiment in Netflix description", subtitle = "Using the Bing et. al Lexicon")

Netflx_nrccounts <- Netflx_nrc %>%
inner_join(get_sentiments("nrc")) %>%
filter(sentiment %in% c("positive","negative")) %>%
count(word, sentiment, sort = TRUE) %>%
ungroup()
Joining, by = c("word", "sentiment")
Netflx_nrccounts
Netflx_nrccounts %>%
group_by(sentiment) %>%
top_n(20) %>%
ungroup() %>%
mutate(word = reorder(word, n)) %>%
ggplot(aes(word, n, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
coord_flip() +
theme_fivethirtyeight() +
ggtitle("Words' Contribution to Sentiment in Netflix", subtitle = "Using the NRC Lexicon")
Selecting by n

More info: https://github.com/EGjika

LS0tDQp0aXRsZTogIk5ldGZsaXggVGV4dCBBbmFseXNpcyINCmF1dGhvcjogIkVyYWxkYSBHamlrYSINCmRhdGU6ICIwMyBEZWNlbWJlciAyMDIyIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQojIE5ldGZsaXggZGF0c2V0IHRleHQgYW5hbHlzaXMgey50YWJzZXR9DQoNCiMjIEludHJvZHVjdGlvbg0KSW4gdGhpcyBleGFtcGxlIHdlIGFyZSBnb2luZyB0byBkbyBzb21lIHRleHQgYW5kIHNlbnRpbWVudGFsIGFuYWx5c2lzIHdpdGggdGhlIE5ldGZsaXggZGF0YXNldC4gVGhlIGRhdGFzZXQgaGFzIGEgbG90IG9mIGluZm9ybWF0aW9uIHNvIHlvdSBtYXkgZG8gbWFueSBhbmFseXNpcyBiYXNlZCBvbiB5b3VyIHJlc2VhcmNoIHF1ZXN0aW9uIGFuZCB0YXJnZXQgdmFyaWFibGUuIA0KDQoqKkRhdGEgc291cmNlOioqIGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvc2hpdmFtYi9uZXRmbGl4LXNob3dzL2Rvd25sb2FkP2RhdGFzZXRWZXJzaW9uTnVtYmVyPTUgDQoNClRoZSBtYWluIGxpYnJhcmllcyB3ZSB3aWxsIHVzZSBhcmU6DQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkodGlkeXIpDQpgYGANCg0Kd2Ugd2lsbCB3b3JrIG9uIGRlc2NyaXB0aW9uIHZhcmlhYmxlIHNvIHdlIHdpbGwgc2VsZWN0IG9ubHkgdGhpcyBjb2x1bW5zIGFzIGEgc3RhcnQuDQpgYGB7cn0NCmxpYnJhcnkocmVhZHIpDQpuZXRmbGl4X3RpdGxlcyA8LSByZWFkX2NzdigibmV0ZmxpeF90aXRsZXMuY3N2IikNClZpZXcobmV0ZmxpeF90aXRsZXMpDQpgYGANCg0KRGVhbGluZyB3aXRoICoqc3RvcHdvcmRzKiogaGVscHMgYSBsb3QsIGVzcGVjaWFsbHkgd2hlbiBvdXIgdGV4dCBpbmZvcm1hdGlvbiBpcyBub3QgY2xlYXIgYW5kIG9mZmljaWFsLiBCZWxvdyB3ZSBtYXkgc2VlIHNvbWUgb2YgdGhlIHN0b3Atd29yZHMgdXNlZCBpbiBtYWluIGxpYnJhcmllcywgYnV0IHlvdSBtYXkgYWxzbyBjcmVhdGUgeW91ciBvd24gc3RvcC13b3JkIHZlY3RvciBhbmQgcmVtb3ZlIHVubmVjZXNzYXJ5IHdvcmRzIGZyb20geW91ciB0ZXh0LiBGb3IgbW9yZSBvbiB0aGlzIHJlZmVyIHRvIHRoZSB3b3JrIGRvbmUgaGVyZTogaHR0cHM6Ly9naXRodWIuY29tL0VHamlrYS9UZXh0LUFuYWx5c2lzLU5BU0EtZGF0YXNldA0KDQpgYGB7cn0NCmRhdGEoInN0b3Bfd29yZHMiKQ0KaGVhZChzdG9wX3dvcmRzKQ0KIyBvciB3ZSBtYXkgdXNlIA0Kc3RvcF93b3JkcyR3b3JkW3doaWNoKHN0b3Bfd29yZHMkd29yZCAlaW4lIHNlbnRpbWVudHMkd29yZCldICU+JSBoZWFkKDIwKQ0KYGBgDQoNCiMjIFdvcmxkIGNsb3VkDQpGb3IgYSBzdGFydCB3ZSB3aWxsIHVzZSB3b3JkY2xvdWQgdG8gaGF2ZSBhIGJldHRlciB2aWV3IG9mIG91ciB0ZXh0LiBXZSBtYXkgYXBwbHkgaXQgYXQgImRlc2NyaXB0aW9uIiBvciBldmVuIHRvIHVuZGVyc3RhbmQgd2hpY2ggY2FzdCBhbmQgZGlyZWN0b3IgaGFzIG1vc3RseSBhcHBlYXJlZC4NCiMjIyBGb3IgZGVzY3JpcHRpb24NCmBgYHtyfQ0KIyBMaWJyYXJpZXMgdXNlZA0KbGlicmFyeSh3b3JkY2xvdWQpDQpsaWJyYXJ5KFNub3diYWxsQykNCiNnZW5lcmF0ZSB3b3JkIGNsb3VkDQpzZXQuc2VlZCgxMjM0KQ0Kd29yZGNsb3VkKHdvcmRzID0gbmV0ZmxpeF90aXRsZXMkZGVzY3JpcHRpb24sIG1heC53b3Jkcz0xMDAsIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjQwLCBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkNCmBgYA0KIyMjIEZvciBDYXN0IGFuZCBkaXJlY3Rvcg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0KQ0Kd29yZGNsb3VkKHdvcmRzID0gbmV0ZmxpeF90aXRsZXMkY2FzdCwgbWF4LndvcmRzPTEwMCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuNDAsIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQ0KDQpzZXQuc2VlZCgxMjM0KQ0Kd29yZGNsb3VkKHdvcmRzID0gbmV0ZmxpeF90aXRsZXMkZGlyZWN0b3IsIG1heC53b3Jkcz0xMDAsIHJhbmRvbS5vcmRlcj1GQUxTRSwgcm90LnBlcj0wLjQwLCBjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkNCmBgYA0KIyMjIFdvcmQgQXNzb2NpYXRpb24NCldlIG1heSBhbHNvIHVzZSBjb3JyZWxhdGlvbiBhcyBhIHN0YXRpc3RpY2FsIG1lYXN1cmUgdG8gZGVtb25zdHJhdGUgd2hldGhlciwgYW5kIGhvdyBzdHJvbmdseSwgcGFpcnMgb2YgdmFyaWFibGVzIGFyZSByZWxhdGVkLiBJbiB0aGlzIGNhc2Ugd2UgYXJlIGxvb2tpbmcgYXQgIGFuYWx5emluZyB3aGljaCB3b3JkcyBvY2N1ciBtb3N0IG9mdGVuIGluIGFzc29jaWF0aW9uIHdpdGggdGhlIG1vc3QgZnJlcXVlbnRseSBvY2N1cnJpbmcgd29yZHMuDQpUaGlzIHNjcmlwdCBzaG93cyB3aGljaCB3b3JkcyBhcmUgbW9zdCBmcmVxdWVudGx5IGFzc29jaWF0ZWQgd2l0aCB0aGUgdGVybXMgImdvb2QiLCJ3b3JrIiwiaGVhbHRoIiwibG92ZSIsImNvbWVkeSIgKGNvcmxpbWl0ID0gMC4zIGlzIHRoZSBsb3dlciBsaW1pdC90aHJlc2hvbGQgc2V0LiBZb3UgY2FuIHNldCBpdCBsb3dlciB0byBzZWUgbW9yZSB3b3Jkcywgb3IgaGlnaGVyIHRvIHNlZSBsZXNzKS4gb3IsIGV2ZW4gY2hhbmdlIHRoZSBsaXN0IG9mIHdvcmRzIGJhc2VkIG9uIHRoZXJpIGZyZXF1ZW5jeSAoaGlnaGVyIGJldHRlcikuDQoNCmBgYHtyfQ0KbGlicmFyeSh0bSkNCiMgQnVpbGQgYSB0ZXJtLWRvY3VtZW50IG1hdHJpeA0KTmV0Zmx4X2R0bSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgobmV0ZmxpeF90aXRsZXMkZGVzY3JpcHRpb24pDQojIEZpbmQgYXNzb2NpYXRpb25zIA0KZmluZEFzc29jcyhOZXRmbHhfZHRtLCB0ZXJtcyA9IGMoImdvb2QiLCJ3b3JrIiwiaGVhbHRoIiwibG92ZSIsImNvbWVkeSIpLCBjb3JsaW1pdCA9IDAuMykJCQkNCg0KYGBgDQoNCmBgYHtyfQ0KIyBGaW5kIGFzc29jaWF0aW9ucyBmb3Igd29yZHMgdGhhdCBvY2N1ciBhdCBsZWFzdCA1MCB0aW1lcw0KZmluZEFzc29jcyhOZXRmbHhfZHRtLCB0ZXJtcyA9IGZpbmRGcmVxVGVybXMoTmV0Zmx4X2R0bSwgbG93ZnJlcSA9IDEwMDApLCBjb3JsaW1pdCA9IDAuMykNCmBgYA0KIyMgVG9rZW5pemF0aW9uDQoNCkhlcmUgd2Ugd2lsbCBzdGFydCBvdXIgcHJvY2VzcyBvZiB0aWtlbml6YXRpb24gZm9yIG91ciAiZGVzY3JpcHRpb24iIGNvbHVtbi4NCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KdGlkeV9uZXRmbGl4X2Rlc2NyaXB0aW9uIDwtIG5ldGZsaXhfdGl0bGVzICU+JQ0Kc2VsZWN0KCJzaG93X2lkIiwiZGVzY3JpcHRpb24iKQ0KaGVhZCh0aWR5X25ldGZsaXhfdGl0bGUpDQoNCk5ldGZseDwtdGlkeV9uZXRmbGl4X2Rlc2NyaXB0aW9uICU+JQ0KICAgdW5uZXN0X3Rva2VucygNCiAgICBvdXRwdXQgPSB3b3JkLA0KICAgIGlucHV0ID0gZGVzY3JpcHRpb24sIyBvdXIgY29sdW1uIG9mIGludGVyZXN0DQogICAgdG9rZW4gPSAnd29yZHMnLA0KICAgIGRyb3AgPSBGQUxTRQ0KICApICU+JQ0KICB1bmdyb3VwKCkNCmhlYWQoTmV0Zmx4LDI0KSMgc2hvdyBqdXN0IGZpcnN0IDI0IHJvd3Mgb2Ygd29yZHMNCiANCmBgYA0KTGV0J3MgY3JlYXRlIGEgZnJlcXVlbmN5IHRhYmxlIG9mIHdvcmRzIHVzZWQgaW4gImRlc2NyaXB0aW9uIjoNCg0KYGBge3J9DQpOZXRmbHg8LSBOZXRmbHggJT4lDQp1bm5lc3RfdG9rZW5zKHdvcmQsIGRlc2NyaXB0aW9uKSAlPiUNCmNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KTmV0Zmx4DQpgYGANCk5vdywgeW91IGNhbiByZW1vdmUgdGhlIHN0b3Agd29yZHMgZnJvbSB5b3VyIGRhdGEgZnJhbWU6DQpgYGB7cn0NCk5ldGZseCA8LSBOZXRmbHggJT4lDQphbnRpX2pvaW4oc3RvcF93b3JkcykNCk5ldGZseCAjIGFmdGVyIHJlbW92aW5nIHN0b3Bfd29yZHMNCmBgYA0KTmV4dCwgcnVuIHRoZSBmb2xsb3dpbmcgdG8gcGxvdCB0aGUgd29yZHMgaW4gTmV0ZmxpeCBEZXNjcmlwdGlvbiB0aGF0IGFwcGVhciBtb3JlIHRoYW4gNTAgdGltZXMuIFlvdSB3aWxsIHNlZSBpbiB0aGlzIHNpdHVhdGlvbiBhbG1vc3Qgbm90aGluZyB0byB1bmRlcnN0YW5kIHNvLCB3aHkgbm90IGZpbHRlciBiYXNlZCBvbiB0aGUgZnJlcXVlbmN5LiANCih0cnkgdG8gY2hhbmdlIHRoZSBudW1iZXIgb2YgZnJlcXVlbmN5IHRvIG90aGVyIGZyZXF1ZW5jaWVzIDMwMDApDQpgYGB7cn0NCg0KTmV0Zmx4ICU+JQ0KZmlsdGVyKG4gPiA1MCkgJT4lDQptdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KZ2dwbG90KGFlcyh3b3JkLCBuKSkgKw0KZ2VvbV9jb2woZmlsbCA9ICJkYXJrcmVkIikgKw0KdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KeGxhYihOVUxMKSArDQp5bGFiKCJXb3JkIENvdW50IikgKw0KICBjb29yZF9mbGlwKCkgKw0KZ2d0aXRsZSgiV29yZCBVc2FnZSBpbiBOZXRmbGl4IGRlc2NyaXB0aW9uIG1vcmUgdGhhbiA1MCB0aW1lcyIpDQoNCk5ldGZseCAlPiUNCmZpbHRlcihuID4gMzAwMCkgJT4lDQptdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KZ2dwbG90KGFlcyh3b3JkLCBuKSkgKw0KZ2VvbV9jb2woZmlsbCA9ICJkYXJrcmVkIikgKw0KdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KeGxhYihOVUxMKSArDQp5bGFiKCJXb3JkIENvdW50IikgKw0KICBjb29yZF9mbGlwKCkgKw0KZ2d0aXRsZSgiV29yZCBVc2FnZSBpbiBOZXRmbGl4IGRlc2NyaXB0aW9uIG1vcmUgdGhhbiA1MCB0aW1lcyIpDQpgYGANCg0KV2h5IG5vdCBncm91cCBieSBzaG93X2lkPyBMZXQncyBkbyBpdCENCmBgYHtyfQ0KTmV0Zmx4X3M8LXRpZHlfbmV0ZmxpeF9kZXNjcmlwdGlvbiAlPiUNCiAgIHVubmVzdF90b2tlbnMoDQogICAgb3V0cHV0ID0gd29yZCwNCiAgICBpbnB1dCA9IGRlc2NyaXB0aW9uLCMgb3VyIGNvbHVtbiBvZiBpbnRlcmVzdA0KICAgIHRva2VuID0gJ3dvcmRzJywNCiAgICBkcm9wID0gRkFMU0UNCiAgKSAlPiUNCiAgdW5ncm91cCgpDQojIGdldCBzZW50aW1lbnQgdmlhIGlubmVyIGpvaW4NCm5ldGZsaXhfc2VudGltZW50ID0gTmV0Zmx4X3MgJT4lIA0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpKSAlPiUgDQogIGdyb3VwX2J5KHNob3dfaWQsIGRlc2NyaXB0aW9uKSAlPiUgIyBncm91cCBieSBzaG93X2lkIGFuZCBkZXNjcmlwdGlvbg0KICBkcGx5cjo6c3VtbWFyaXNlKHNlbnRpbWVudD1zdW0odmFsdWUpKSAlPiUgIyB2YWx1ZSB3aWxsIGJlIHRoZSBudW1lcmljYWwgc2VudGltZW50IGdpdmVuIGFuZCB3ZSB3YW50IHRvIGdyb3VwIGJ5IHNob3dfaWQNCiAgdW5ncm91cCgpDQoNCm5ldGZsaXhfc2VudGltZW50DQpgYGANCk5leHQhDQpXZSBtYXkgZmlsdGVyIHRob3NlIHNob3dfaWQgd2hpY2ggYXJlIGluIGEgZ2l2ZW4gcmFuZ2Ugb2Ygc2VudGltZW50Lg0KYGBge3J9DQpuZXRmbGl4X3NlbnRpbWVudCU+JQ0KZmlsdGVyKHNlbnRpbWVudCA+PTEyKSAlPiUNCmdncGxvdChhZXMoc2hvd19pZCwgc2VudGltZW50KSkgKw0KdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KZ2VvbV9jb2woKSArDQp4bGFiKE5VTEwpICsNCmNvb3JkX2ZsaXAoKSArDQp5bGFiKCJXb3JkIENvdW50IikgKw0KZ2d0aXRsZSgiU2VudGltZW50IGdyZWF0ZXIgdGhhbiAxMiBpbiBOZXRmbGl4IHNob3dfaWQiLCBzdWJ0aXRsZSA9ICJTZW50aW1lbnQgQW5hbHlzaXMgVXNpbmcgTlJDIikNCmBgYA0KDQojIyBTZW50aW1lbnQgTGV4aWNvbnMNClRoZXJlIGFyZSB0aHJlZSBtb3N0bHkgdXNlZCBsZXhpY29ucyBpbiBSOg0KYGBge3J9DQpnZXRfc2VudGltZW50cygiYmluZyIpDQpnZXRfc2VudGltZW50cygiYWZpbm4iKQ0KZ2V0X3NlbnRpbWVudHMoIm5yYyIpDQpgYGANCk5vdyB3ZSB3aWxsIHRyeSBCSU5HIGxleGljb24gb24gZGVzY3JpcHRpb24uDQpgYGB7cn0NCk5ldGZseF9iaW5nIDwtIE5ldGZseCAlPiUNCmlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkNCk5ldGZseF9iaW5nDQpgYGANCiMjIyBMaWJyYXJ5ICJ5YXJyIg0KDQpXZSBtYXkgY3JlYXRlIHNvbWUgZ3JhcGhzIGhlcmUgLiBUaGV5IHdvcmsgYmV0dGVyIGlmIHdlIGhhdmUgYWxzbyBhIHRpbWUgdmFyaWFibGUgd2UgbWF5IHVzZS4gV2h5IG5vdCB0cnk/IChleGVyY2lzZSBmb3IgeW91KQ0KYGBge3J9DQpsaWJyYXJ5KHlhcnJyKQ0KDQpwaXJhdGVwbG90KGZvcm11bGEgPSAgbiB+IHdvcmQgKyBzZW50aW1lbnQsICNGb3JtdWxhDQogICBkYXRhID0gTmV0Zmx4X2JpbmcsICNEYXRhIGZyYW1lDQogICB4bGFiID0gTlVMTCwgeWxhYiA9ICJXb3JkIENvdW50IiwgI0F4aXMgbGFiZWxzDQogICBtYWluID0gIkxleGljYWwgRGl2ZXJzaXR5IE5ldGZsaXgiLCAjUGxvdCB0aXRsZQ0KICAgcGFsID0gImdvb2dsZSIsICNDb2xvciBzY2hlbWUNCiAgIHBvaW50Lm8gPSAuMiwgI1BvaW50cw0KICAgYXZnLmxpbmUubyA9IDEsICNUdXJuIG9uIHRoZSBBdmVyYWdlL01lYW4gbGluZQ0KICAgdGhlbWUgPSAwLCAjVGhlbWUNCiAgIHBvaW50LnBjaCA9IDE2LCAjUG9pbnQgYHBjaGAgdHlwZQ0KICAgcG9pbnQuY2V4ID0gMS41LCAjUG9pbnQgc2l6ZQ0KICAgaml0dGVyLnZhbCA9IC4xLCAjVHVybiBvbiBqaXR0ZXIgdG8gc2VlIHRoZSBzb25ncyBiZXR0ZXINCiAgIGNleC5sYWIgPSAuOSwgY2V4Lm5hbWVzID0gLjcpICNBeGlzIGxhYmVsIHNpemUNCmBgYA0KDQpUbyBvYnRhaW4gYSBiZXR0ZXIgdmlldyB3ZSBtYXkgZmlsdGVyIHNvbWUgb2YgdGhlIHdvcmRzIHdpdGggYSBnaXZlbiBudW1iZXIgb2YgZnJlcXVlbmN5Og0KDQpgYGB7cn0NCk5ldGZseF9iaW5nICU+JQ0KICBmaWx0ZXIobj49MTUwMCkgJT4lDQpwaXJhdGVwbG90KGZvcm11bGEgPSAgbiB+IHdvcmQgKyBzZW50aW1lbnQsICNGb3JtdWxhDQogICB4bGFiID0gTlVMTCwgeWxhYiA9ICJXb3JkIENvdW50IiwgI0F4aXMgbGFiZWxzDQogICBtYWluID0gIkxleGljYWwgRGl2ZXJzaXR5IE5ldGZsaXgiLCAjUGxvdCB0aXRsZQ0KICAgcGFsID0gImdvb2dsZSIsICNDb2xvciBzY2hlbWUNCiAgIHBvaW50Lm8gPSAuMiwgI1BvaW50cw0KICAgYXZnLmxpbmUubyA9IDEsICNUdXJuIG9uIHRoZSBBdmVyYWdlL01lYW4gbGluZQ0KICAgdGhlbWUgPSAwLCAjVGhlbWUNCiAgIHBvaW50LnBjaCA9IDE2LCAjUG9pbnQgYHBjaGAgdHlwZQ0KICAgcG9pbnQuY2V4ID0gMS41LCAjUG9pbnQgc2l6ZQ0KICAgaml0dGVyLnZhbCA9IC4xLCAjVHVybiBvbiBqaXR0ZXIgdG8gc2VlIHRoZSBzb25ncyBiZXR0ZXINCiAgIGNleC5sYWIgPSAuOSwgY2V4Lm5hbWVzID0gLjcpICNBeGlzIGxhYmVsIHNpemUNCmBgYA0KRm9yIG1vcmUgcmVmZXJlbmNlOiBodHRwczovL3d3dy5kYXRhY2FtcC5jb20vdHV0b3JpYWwvc2VudGltZW50LWFuYWx5c2lzLVINCg0KDQpgYGB7cn0NCk5ldGZseF9ucmMgPC0gTmV0Zmx4ICU+JQ0KaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIikpDQpOZXRmbHhfbnJjDQpgYGANCg0KYGBge3J9DQpOZXRmbHhfbnJjICU+JQ0KZmlsdGVyKG4gPiAyMDAwKSAlPiUNCm11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQpnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGw9c2VudGltZW50KSkgKw0KdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KZ2VvbV9jb2woKSArDQp4bGFiKE5VTEwpICsNCmNvb3JkX2ZsaXAoKSArDQp5bGFiKCJXb3JkIENvdW50IikgKw0KZ2d0aXRsZSgiV29yZCBVc2FnZSBpbiBOZXRmbGl4IGRlc2NyaXB0aW9uIiwgc3VidGl0bGUgPSAiU2VudGltZW50IEFuYWx5c2lzIFVzaW5nIE5SQyIpDQpgYGANCg0KUHJvZHVjZSBhIGhvcml6b250YWwgYmFyIGNoYXJ0IHNob3dpbmcgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmQgdXNhZ2UgaW4gTmV0ZmxpeCBkZXNjcmlwdGlvbiB1c2luZyB0aGUgQmluZyBldCBhbC4gc2VudGltZW50IGxleGljb24uKHRyeSB0byBjaGFuZ2UgdGhlIHZhbHVlIDIwMDAgYW5kIG9ic2VydmUgdGhlIHdvcmRzKQ0KYGBge3J9DQpOZXRmbHhfYmluZyAlPiUNCmZpbHRlcihuID4gMjAwMCkgJT4lDQptdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsPXNlbnRpbWVudCkpICsNCnRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsNCmdlb21fY29sKCkgKw0KeGxhYihOVUxMKSArDQpjb29yZF9mbGlwKCkgKw0KeWxhYigiV29yZCBDb3VudCIpICsNCmdndGl0bGUoIldvcmQgVXNhZ2UgaW4gTmV0ZmxpeCBkZXNjcmlwdGlvbiIsIHN1YnRpdGxlID0gIlNlbnRpbWVudCBBbmFseXNpcyBVc2luZw0KQmluZyBldCBhbC4iKQ0KYGBgDQoNCmBgYHtyfQ0KTmV0Zmx4X25yYyAlPiUNCmZpbHRlcihuID4gMjAwMCkgJT4lDQptdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsPXNlbnRpbWVudCkpICsNCnRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsNCmdlb21fY29sKCkgKw0KeGxhYihOVUxMKSArDQpjb29yZF9mbGlwKCkgKw0KeWxhYigiV29yZCBDb3VudCIpICsNCmdndGl0bGUoIldvcmQgVXNhZ2UgaW4gTmV0ZmxpeCBkZXNjcmlwdGlvbiIsIHN1YnRpdGxlID0gIlNlbnRpbWVudCBBbmFseXNpcyBVc2luZyBucmMiKQ0KYGBgDQoNClRyeSB0byBmaWx0ZXIgc29tZSBvZiB0aGUgc2VudGltZW50cyBhcyBpbiB0aGUgZXhhbXBsZXMgYmVsb3cuIA0KSSBhbSBpbnRlcmVzdGVkIG9uIGtub3d3aW5nIHdoaWNoIHdvcmRzIGFyZSByZWxhdGVkIHRvIHRoZSBzZW50aW1lbnQgInN1cnByaXNlIiBhbmQgdGhlbiAibG92ZSIgaW4gdGVoIGRlc2NyaXB0aW9uIG9mIHRoZSBOZXRmbGl4IE1vdmllcyBvciBUViBTaG93cy4NCmBgYHtyfQ0KTmV0X25yY19zdXJwcmlzZSA8LSBnZXRfc2VudGltZW50cygibnJjIikgJT4lDQpmaWx0ZXIoc2VudGltZW50ID09ICJzdXJwcmlzZSIpDQpOZXRfbnJjX3N1cnByaXNlDQpOZXRmbHggJT4lDQppbm5lcl9qb2luKE5ldF9ucmNfc3VycHJpc2UpDQoNCg0KDQpOZXRfbnJjX2xvdmUgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JQ0KZmlsdGVyKHNlbnRpbWVudCA9PSAibG92ZSIpDQpOZXRfbnJjX2xvdmUNCk5ldGZseCAlPiUNCmlubmVyX2pvaW4oTmV0X25yY19sb3ZlKQ0KYGBgDQpUcnkgYW5vdGhlciB3b3JkIGFuZCBleHBsb3JlIHNvbWUgcmVsYXRpb25zaGlwcyEhDQpXZSB3aWxsIHNlZSBob3cgTlJDIHJlc3VsdCBzaG93cyBmb3Igc29tZSBvZiB0aGUgc2VudGltZW50cyBhbmQgdG9wIHdvcmRzLg0KDQpgYGB7cn0NCk5ldGZseF9ucmMgJT4lIA0KICBmaWx0ZXIobj4yNTAwKSAlPiUgIyAgZmlsdGVyIG9ubHkgdGhvc2UgZnJlcXVlbmN5IGdyZWF0ZXIgdGhhbiAyMDAwDQptdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KY291bnQod29yZCxzZW50aW1lbnQsc29ydD1UUlVFKSAlPiUNCmdyb3VwX2J5KHNlbnRpbWVudCklPiUNCiAgdG9wX24obj01KSAlPiUgDQp1bmdyb3VwKCkgJT4lDQogIGdncGxvdChhZXMoeD1yZW9yZGVyKHdvcmQsbikseT1uLGZpbGw9c2VudGltZW50KSkgKyANCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyANCiAgZmFjZXRfd3JhcCh+c2VudGltZW50LHNjYWxlcz0iZnJlZSIpICsgDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCg0KDQoNCiMjIENvbXBhcmluZyBTZW50aW1lbnQgTGV4aWNvbnMNCmBgYHtyfQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeSh0aWR5cikNCmBgYA0KDQpBZGQgbW9yZSBmaWx0ZXJpbmcgc3VjaCBhcyAidHlwZSIgYmVjYXVzZSB3ZSB3YW50IG1vcmUgaW5mbyBhbHNvIG9uIHRoZSB0eXBlIG9mIHRoZSB0aXRsZSBpZiBpdCB3YXMgYSBNb3ZpZSBvciBhIFRWIHNob3cuDQpgYGB7cn0NCk5ldGZseF90eXBlIDwtIG5ldGZsaXhfdGl0bGVzICU+JQ0Kc2VsZWN0KCJzaG93X2lkIiwidHlwZSIsImRlc2NyaXB0aW9uIikNCmhlYWQoTmV0Zmx4X3R5cGUpDQoNCk5ldGZseF90eXBlIDwtTmV0Zmx4X3R5cGUgJT4lDQp1bm5lc3RfdG9rZW5zKHdvcmQsIGRlc2NyaXB0aW9uKSAlPiUNCmFudGlfam9pbihzdG9wX3dvcmRzKQ0KDQpOZXRmbHhfdHlwZQ0KDQpgYGANCkxldCdzIHVzZSBBRklOTiBsZXhpY29uIHRvICBvYnNlcnZlIHRoZSBzZW50aW1lbnQgb2YgTW92aWUgYW5kIFRWIHNob3dzLg0KYGBge3J9DQpOZXRmbHhfdHlwZV9hZmlubiA8LSBOZXRmbHhfdHlwZSAlPiUNCmlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpICU+JQ0KICBncm91cF9ieShpbmRleCA9IHR5cGUpICU+JQ0Kc3VtbWFyaXplKHNlbnRpbWVudCA9IHN1bSh2YWx1ZSkpICU+JSAjIHRyeSB0byBnZXQgYWxzbyBhIG1lYW4gDQptdXRhdGUobWV0aG9kID0gIkFGSU5OIikNCk5ldGZseF90eXBlX2FmaW5uDQpgYGANCg0KYGBge3J9DQpOZXRmbHhfdHlwZV9iaW5nIDwtIE5ldGZseF90eXBlICU+JQ0KaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCiAgZ3JvdXBfYnkoaW5kZXggPSB0eXBlKSAlPiUNCiNzdW1tYXJpemUoc2VudGltZW50ID0gc3VtKHZhbHVlKSkgJT4lICMgdHJ5IHRvIGdldCBhbHNvIGEgbWVhbiANCm11dGF0ZShtZXRob2QgPSAiQklORyIpDQpOZXRmbHhfdHlwZV9iaW5nDQpgYGANCg0KYGBge3J9DQpOZXRmbHhfYmluZyAlPiUgDQogIGZpbHRlcihuPjI1MDApICU+JSAjICBmaWx0ZXIgb25seSB0aG9zZSBmcmVxdWVuY3kgZ3JlYXRlciB0aGFuIDI1MDANCm11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQpjb3VudCh3b3JkLHNlbnRpbWVudCxzb3J0PVRSVUUpICU+JQ0KZ3JvdXBfYnkoc2VudGltZW50KSAlPiUgDQogIHRvcF9uKG49MTApICU+JSANCnVuZ3JvdXAoKSAlPiUNCiAgZ2dwbG90KGFlcyh4PXJlb3JkZXIod29yZCxuKSx5PW4sZmlsbD1zZW50aW1lbnQpKSArIA0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArIA0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsc2NhbGVzPSJmcmVlIikgKyANCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KDQpMZXQncyBkbyBhIGNvbXBhcmlzaW9uIGJldHdlZW4gc2VudGltZW50cyBhbmQgbGV4aWNvbnMgZm9yIHRoZSAidHlwZSIgb2YgdGhlIE5ldGZsaXg6IFRWIHNob3cgb3IgTW92aWUNCg0KYGBge3J9DQpOZXRmbHhfYmluZ25yYyA8LSBiaW5kX3Jvd3MoTmV0Zmx4X3R5cGUgJT4lDQppbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KbXV0YXRlKG1ldGhvZCA9ICJCaW5nIGV0IGFsLiIpLE5ldGZseF90eXBlICU+JQ0KaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIikgJT4lDQpmaWx0ZXIoc2VudGltZW50ICVpbiUgYygicG9zaXRpdmUiLCJuZWdhdGl2ZSIpKSkgJT4lDQptdXRhdGUobWV0aG9kID0gIk5SQyIpKSAlPiUNCmNvdW50KG1ldGhvZCwgaW5kZXggPSB0eXBlLCBzZW50aW1lbnQpICU+JQ0Kc3ByZWFkKHNlbnRpbWVudCwgbiwgZmlsbCA9IDApICU+JQ0KbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQpOZXRmbHhfYmluZ25yYw0KYGBgDQoNCmBgYHtyfQ0KTmV0Zmx4X2JpbmducmMgPC0gYmluZF9yb3dzKE5ldGZseF90eXBlICU+JQ0KaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUNCm11dGF0ZShtZXRob2QgPSAiQmluZyBldCBhbC4iKSxOZXRmbHhfdHlwZSAlPiUNCmlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JQ0KZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwibmVnYXRpdmUiKSkpICU+JQ0KbXV0YXRlKG1ldGhvZCA9ICJOUkMiKSkgJT4lDQpjb3VudChtZXRob2QsIGluZGV4ID0gd29yZCwgc2VudGltZW50KSAlPiUgIyB0cnkgdG8gY2hhbmdlIGluZGV4ICJ3b3JkIiBvciAidHlwZSINCnNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCm11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlKQ0KTmV0Zmx4X2JpbmducmMNCmBgYA0KDQpIb3cgbXVjaCBlYWNoIHdvcmQgY29udHJpYnV0ZXMgdG8gdGhlIG92ZXJhbGwgc2VudGltZW50IG9mIHRoZSBkZXNjcmlwdGlvbnMgLg0KYGBge3J9DQpOZXRmbGl4X2Jpbmdjb3VudHMgPC0gTmV0Zmx4X2JpbmcgJT4lDQppbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQp1bmdyb3VwKCkNCk5ldGZsaXhfYmluZ2NvdW50cw0KYGBgDQoNCmBgYHtyfQ0KTmV0ZmxpeF9iaW5nY291bnRzICU+JQ0KIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lDQojdG9wX24oMikgJT4lDQp1bmdyb3VwKCkgJT4lDQptdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gc2VudGltZW50KSkgKw0KZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KZmFjZXRfd3JhcCh+c2VudGltZW50LCBzY2FsZXMgPSAiZnJlZV95IikgKw0KY29vcmRfZmxpcCgpICsNCnRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsNCmdndGl0bGUoIldvcmRzJyBDb250cmlidXRpb24gdG8gU2VudGltZW50IGluIE5ldGZsaXggZGVzY3JpcHRpb24iLCBzdWJ0aXRsZSA9ICJVc2luZyB0aGUgQmluZyBldC4gYWwgTGV4aWNvbiIpDQpgYGANCg0KYGBge3J9DQpOZXRmbHhfbnJjY291bnRzIDwtIE5ldGZseF9ucmMgJT4lDQppbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSkgJT4lDQpmaWx0ZXIoc2VudGltZW50ICVpbiUgYygicG9zaXRpdmUiLCJuZWdhdGl2ZSIpKSAlPiUNCmNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JQ0KdW5ncm91cCgpDQpOZXRmbHhfbnJjY291bnRzDQpgYGANCg0KYGBge3J9DQpOZXRmbHhfbnJjY291bnRzICU+JQ0KZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCnRvcF9uKDIwKSAlPiUNCnVuZ3JvdXAoKSAlPiUNCm11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQpnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGwgPSBzZW50aW1lbnQpKSArDQpnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQpmYWNldF93cmFwKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQpjb29yZF9mbGlwKCkgKw0KdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKw0KZ2d0aXRsZSgiV29yZHMnIENvbnRyaWJ1dGlvbiB0byBTZW50aW1lbnQgaW4gTmV0ZmxpeCIsIHN1YnRpdGxlID0gIlVzaW5nIHRoZSBOUkMgTGV4aWNvbiIpDQpgYGANCg0KDQpNb3JlIGluZm86IGh0dHBzOi8vZ2l0aHViLmNvbS9FR2ppa2EgDQoNCg0KDQoNCg0K