Librairies

library (tidytext)
library(tidyverse)
## -- Attaching packages --------------------------------------------------------------------------------------------------- tidyverse 1.3.0 --
## v ggplot2 3.3.2     v purrr   0.3.4
## v tibble  3.0.3     v dplyr   1.0.2
## v tidyr   1.1.2     v stringr 1.4.0
## v readr   1.3.1     v forcats 0.5.0
## -- Conflicts ------------------------------------------------------------------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
library(kableExtra)
## 
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
## 
##     group_rows
library(janeaustenr)
library(stringr)
library(textdata)
## Warning: package 'textdata' was built under R version 4.0.3

Part 1: Book’s example

The sentiments dataset

get_sentiments("afinn")
## # A tibble: 2,477 x 2
##    word       value
##    <chr>      <dbl>
##  1 abandon       -2
##  2 abandoned     -2
##  3 abandons      -2
##  4 abducted      -2
##  5 abduction     -2
##  6 abductions    -2
##  7 abhor         -3
##  8 abhorred      -3
##  9 abhorrent     -3
## 10 abhors        -3
## # ... with 2,467 more rows
get_sentiments("bing")
## # A tibble: 6,786 x 2
##    word        sentiment
##    <chr>       <chr>    
##  1 2-faces     negative 
##  2 abnormal    negative 
##  3 abolish     negative 
##  4 abominable  negative 
##  5 abominably  negative 
##  6 abominate   negative 
##  7 abomination negative 
##  8 abort       negative 
##  9 aborted     negative 
## 10 aborts      negative 
## # ... with 6,776 more rows
get_sentiments("nrc")
## # A tibble: 13,901 x 2
##    word        sentiment
##    <chr>       <chr>    
##  1 abacus      trust    
##  2 abandon     fear     
##  3 abandon     negative 
##  4 abandon     sadness  
##  5 abandoned   anger    
##  6 abandoned   fear     
##  7 abandoned   negative 
##  8 abandoned   sadness  
##  9 abandonment anger    
## 10 abandonment fear     
## # ... with 13,891 more rows

Sentiment analysis with inner join

library(janeaustenr)
library(dplyr)


tidy_books <- austen_books() %>%
  group_by(book) %>%
  mutate(
    linenumber = row_number(),
    chapter = cumsum(str_detect(text, regex("^chapter [\\divxlc]",
      ignore_case = TRUE
    )))
  ) %>%
  ungroup() %>%
  unnest_tokens(word, text)
nrc_joy <- get_sentiments("nrc") %>%
  filter(sentiment == "joy")

tidy_books %>%
  filter(book == "Emma") %>%
  inner_join(nrc_joy) %>%
  count(word, sort = TRUE)
## Joining, by = "word"
## # A tibble: 303 x 2
##    word        n
##    <chr>   <int>
##  1 good      359
##  2 young     192
##  3 friend    166
##  4 hope      143
##  5 happy     125
##  6 love      117
##  7 deal       92
##  8 found      92
##  9 present    89
## 10 kind       82
## # ... with 293 more rows
library(tidyr)

jane_austen_sentiment <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(book, index = linenumber %/% 80, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
## Joining, by = "word"
library(ggplot2)

ggplot(jane_austen_sentiment, aes(index, sentiment, fill = book)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~book, ncol = 2, scales = "free_x")

Comparing the three sentiment dictionaries

pride_prejudice <- tidy_books %>%
  filter(book == "Pride & Prejudice")

pride_prejudice
## # A tibble: 122,204 x 4
##    book              linenumber chapter word     
##    <fct>                  <int>   <int> <chr>    
##  1 Pride & Prejudice          1       0 pride    
##  2 Pride & Prejudice          1       0 and      
##  3 Pride & Prejudice          1       0 prejudice
##  4 Pride & Prejudice          3       0 by       
##  5 Pride & Prejudice          3       0 jane     
##  6 Pride & Prejudice          3       0 austen   
##  7 Pride & Prejudice          7       1 chapter  
##  8 Pride & Prejudice          7       1 1        
##  9 Pride & Prejudice         10       1 it       
## 10 Pride & Prejudice         10       1 is       
## # ... with 122,194 more rows
afinn <- pride_prejudice %>%
  inner_join(get_sentiments("afinn")) %>%
  group_by(index = linenumber %/% 80) %>%
  summarise(sentiment = sum(value)) %>%
  mutate(method = "AFINN")
## Joining, by = "word"
## `summarise()` ungrouping output (override with `.groups` argument)
bing_and_nrc <- bind_rows(
  pride_prejudice %>%
    inner_join(get_sentiments("bing")) %>%
    mutate(method = "Bing et al."),
  pride_prejudice %>%
    inner_join(get_sentiments("nrc") %>%
      filter(sentiment %in% c(
        "positive",
        "negative"
      ))) %>%
    mutate(method = "NRC")
) %>%
  count(method, index = linenumber %/% 80, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
## Joining, by = "word"
## Joining, by = "word"
bind_rows(
  afinn,
  bing_and_nrc
) %>%
  ggplot(aes(index, sentiment, fill = method)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~method, ncol = 1, scales = "free_y")

get_sentiments("nrc") %>%
  filter(sentiment %in% c(
    "positive",
    "negative"
  )) %>%
  count(sentiment)
## # A tibble: 2 x 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   3324
## 2 positive   2312
get_sentiments("bing") %>%
  count(sentiment)
## # A tibble: 2 x 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   4781
## 2 positive   2005

Most common positive and negative words

bing_word_counts <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
## Joining, by = "word"
bing_word_counts
## # A tibble: 2,585 x 3
##    word     sentiment     n
##    <chr>    <chr>     <int>
##  1 miss     negative   1855
##  2 well     positive   1523
##  3 good     positive   1380
##  4 great    positive    981
##  5 like     positive    725
##  6 better   positive    639
##  7 enough   positive    613
##  8 happy    positive    534
##  9 love     positive    495
## 10 pleasure positive    462
## # ... with 2,575 more rows
bing_word_counts %>%
  group_by(sentiment) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(
    y = "Contribution to sentiment",
    x = NULL
  ) +
  coord_flip()
## Selecting by n

custom_stop_words <- bind_rows(
  tibble(
    word = c("miss"),
    lexicon = c("custom")
  ),
  stop_words
)

custom_stop_words
## # A tibble: 1,150 x 2
##    word        lexicon
##    <chr>       <chr>  
##  1 miss        custom 
##  2 a           SMART  
##  3 a's         SMART  
##  4 able        SMART  
##  5 about       SMART  
##  6 above       SMART  
##  7 according   SMART  
##  8 accordingly SMART  
##  9 across      SMART  
## 10 actually    SMART  
## # ... with 1,140 more rows

Wordclouds

library(wordcloud)
## Warning: package 'wordcloud' was built under R version 4.0.3
## Loading required package: RColorBrewer
tidy_books %>%
  anti_join(stop_words) %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100))
## Joining, by = "word"

library(reshape2)
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
## 
##     smiths
tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(
    colors = c("gray20", "gray80"),
    max.words = 100
  )
## Joining, by = "word"

Source

Silge, J., & Robinson, D. (2017). Text mining with R: A tidy approach. Sebastopol, CA: O’Reilly.

Chapter 2: Sentiment Analysis with Tidy Data

See: www.tidytextmining.com/sentiment.html

Part 2: Sentiment Analysis of “Glasses”, a book written by Henry James.

We are going to analyze “Glasses”, found in gutenbergr package, it is a book written by Henry James. It is a story about a young woman whose only asset is a supremely beautiful face is about to make a society marriage until her fiance discovers that, being virtually bind, she needs thick glasses which ruin her looks.

Source: Glasses

Loading the package and tidying the dataset

library(gutenbergr)
## Warning: package 'gutenbergr' was built under R version 4.0.3
# Download the book id 1187, War of the Classes

mybook <- gutenberg_download(1195)
## Determining mirror for Project Gutenberg from http://www.gutenberg.org/robot/harvest
## Using mirror http://aleph.gutenberg.org
mybook
## # A tibble: 1,803 x 2
##    gutenberg_id text                                                            
##           <int> <chr>                                                           
##  1         1195 "GLASSES"                                                       
##  2         1195 ""                                                              
##  3         1195 ""                                                              
##  4         1195 "CHAPTER I"                                                     
##  5         1195 ""                                                              
##  6         1195 ""                                                              
##  7         1195 "Yes indeed, I say to myself, pen in hand, I can keep hold of t~
##  8         1195 "and let it lead me back to the first impression.  The little s~
##  9         1195 "there, I can touch it from point to point; for the thread, as ~
## 10         1195 "is a row of coloured beads on a string.  None of the beads are~
## # ... with 1,793 more rows
# Restructure to one-token_per-row and remove stop words

mybook_tidy <- mybook %>%
  unnest_tokens(word, text) %>%
  anti_join(stop_words)
## Joining, by = "word"
mybook_tidy
## # A tibble: 5,670 x 2
##    gutenberg_id word      
##           <int> <chr>     
##  1         1195 glasses   
##  2         1195 chapter   
##  3         1195 pen       
##  4         1195 hand      
##  5         1195 hold      
##  6         1195 thread    
##  7         1195 lead      
##  8         1195 impression
##  9         1195 story     
## 10         1195 touch     
## # ... with 5,660 more rows

Net Sentiment analysis accross the book per chapter

# Restructure to one-token_per-row and remove stop words

mybook_chapters <- mybook %>% 
  filter(text != "") %>%
  
  mutate(linenumber = row_number(),
         
         chapter = cumsum(str_detect(text, regex("(Chapter )([\\divxlc])", 
                                                 
            ignore_case =  TRUE
            
            )))
         ) %>%
  
  ungroup()


mybook_chapters
## # A tibble: 1,560 x 4
##    gutenberg_id text                                          linenumber chapter
##           <int> <chr>                                              <int>   <int>
##  1         1195 GLASSES                                                1       0
##  2         1195 CHAPTER I                                              2       1
##  3         1195 Yes indeed, I say to myself, pen in hand, I ~          3       1
##  4         1195 and let it lead me back to the first impress~          4       1
##  5         1195 there, I can touch it from point to point; f~          5       1
##  6         1195 is a row of coloured beads on a string.  Non~          6       1
##  7         1195 least I think they're not: that's exactly wh~          7       1
##  8         1195 finding out.                                           8       1
##  9         1195 I had been all summer working hard in town a~          9       1
## 10         1195 Folkestone for a blow.  Art was long, I felt~         10       1
## # ... with 1,550 more rows

Tidying by tokenizing and using afinn lexicon

# tidying mybook_chapter by tokenizing and using afinn lexicon

mybook_chapters_tidy <- mybook_chapters %>%
  
  unnest_tokens(word, text) %>%
  inner_join(get_sentiments("afinn"))
## Joining, by = "word"

Sentiment analysis accross the book

mybooks_rows_plot <- mybook_chapters_tidy %>%
  inner_join(get_sentiments("bing")) %>%
  count(index = linenumber %/% 20, sentiment) %>%
  spread(sentiment, n, fill = 0) %>%
  mutate(sentiment = positive - negative)
## Joining, by = "word"
ggplot(mybooks_rows_plot, aes(index, sentiment)) +
  geom_col(show.legend = FALSE) +
  
  geom_col(fill = "violet") +
  
   labs(title = "Net Sentiment accross the book") 

We can see that the sentiment accroos the book varies. We are going now to analyze the net sentiment per chapiter and the overall sentiment per chapter. Note that we have 13 chapters in this book.

# Grouping needed variables

mybook_chapters_plot <- mybook_chapters_tidy %>%
  
  select(chapter, value) %>%
  
  group_by(chapter) %>% 
  
  summarize(total_sentiment = sum(value))
## `summarise()` ungrouping output (override with `.groups` argument)
# Plot

mybook_chapters_plot %>%
  
  ggplot(aes(chapter, total_sentiment)) +
  
  geom_col(fill = "red") +
  
   xlab("Index - chapter") +
  
  
   ylab("Net Sentiment") + 
  
  labs(title = "Net Sentiment accross the book per chapter") 

From the graph above we can see that the first 6 chapters have a net positive sentiment with the third one to be the most positive while chapter 7 is the most negative.

Overall sentiment

Let take a look at the overall sentiment in the entire book using bing lexicon:

# Get "bing" lexicon for this analysis

mybook_overall_sentiment <- mybook %>% 
  unnest_tokens(word, text) %>%
  inner_join(get_sentiments("bing")) %>% 
  count(sentiment) %>%
  mutate(total = n / sum(n))
## Joining, by = "word"
# Plot

ggplot(mybook_overall_sentiment) + 
  
  aes(x = sentiment, y = total) + 
  geom_col(fill = "violet") + 
  
  xlab("Sentiment") +
  ylab("Percent") + 
 
  labs(title = "Overall Sentiment") + 
  
  geom_text(aes(label = round(total * 100, 2) , vjust = -.4))

There is almost positive contribution as there is negative one.

Let plot now the most positive and negative words below. We are going to use bing lexicon as well:

Most positive words

mybook %>%
  
  unnest_tokens(word, text) %>%
  inner_join(get_sentiments("bing")) %>% 
  filter(sentiment == "positive") %>%
  count(word, sentiment, sort = TRUE) %>% 
  
  top_n(15) %>%
  mutate(word = reorder(word, n)) %>%
  
  ggplot() + 
  
  aes(x = word, y = n) +
  labs(title = "Most Positive Words") + 
  ylab("Contribution to sentiment") + 
  xlab("Word") +
  geom_col(fill = "blue") +
  
  
  coord_flip()
## Joining, by = "word"
## Selecting by n

Most negative words

mybook %>%
  
  unnest_tokens(word, text) %>%
  inner_join(get_sentiments("bing")) %>% 
  filter(sentiment == "negative") %>%
  count(word, sentiment, sort = TRUE) %>% 
  
  top_n(15) %>%
  mutate(word = reorder(word, n)) %>%
  
  ggplot() + 
  
  aes(x = word, y = n) +
  labs(title = "Most Negative Words") + 
  ylab("Contribution to sentiment") + 
  xlab("Word") +
  geom_col(fill = "red") +
  
  
  coord_flip() 
## Joining, by = "word"
## Selecting by n

Sentiment Analysis with Loughran-MacDonald sentiment lexicon

This lexicon labels words with six possible sentiments important in financial contexts: “negative”, “positive”, “litigious”, “uncertainty”, “constraining”, or “superfluous”.

source: https://emilhvitfeldt.github.io/textdata/reference/lexicon_loughran.html

In this analysis, we are going to explore type of words in “Glasses” are associated to “uncertainty” and “constraining”.

# Get loughran

sentiment <- get_sentiments("loughran")

Words associated to litigious & Superfluous

We chose these words by the fact they are not really common in daily conversation.

  mybook_chapters %>% 
  unnest_tokens(word, text) %>% 
  inner_join(get_sentiments("loughran")) %>%
  filter(sentiment %in% c("litigious", "superfluous")) %>%
  count(word, sentiment, sort = TRUE) %>%
  group_by(sentiment) %>%
  
  top_n(10) %>%
  
  ggplot() + 
  aes(x = reorder(word,desc(n)), y = n) + 
  geom_col(fill = "turquoise") +
  facet_grid(~sentiment, scales = "free_x")  + 
  geom_text(aes(label = n, vjust = -.5)) + 
  labs(title = "Words Associated to litigious & Superfluous") +
  
  facet_wrap(~sentiment, ncol = 1, scales = "free_x") +
  
  
  xlab("Word") + 
  ylab("Count") 
## Joining, by = "word"
## Selecting by n

Although the two words are not common but we found more words related to “litigious” but only one to “superfluous”, that is really interesting.

Positive words

We will use this then compare the words I will get to the ones I will get using nrc lexicon.

  mybook_chapters %>% 
  unnest_tokens(word, text) %>% 
  inner_join(get_sentiments("loughran")) %>%
  filter(sentiment %in% c("positive", "negative")) %>%
  count(word, sentiment, sort = TRUE) %>%
  group_by(sentiment) %>%
  
  top_n(10) %>%
  
  ggplot() + 
  aes(x = reorder(word,desc(n)), y = n) + 
  geom_col(fill = "turquoise") +
  facet_grid(~sentiment, scales = "free_x")  + 
  geom_text(aes(label = n, vjust = -.5)) + 
  labs(title = "Positive words") +
  
  facet_wrap(~sentiment, ncol = 1, scales = "free_x") +
  
  
  xlab("Word") + 
  ylab("Count") 
## Joining, by = "word"
## Selecting by n

Words associated to positive and negative emotions using nrc lexicon

This is to compare how both lexicon classify words

  mybook_chapters %>% 
  unnest_tokens(word, text) %>% 
  inner_join(get_sentiments("nrc")) %>%
  filter(sentiment %in% c("positive", "negative")) %>%
  count(word, sentiment, sort = TRUE) %>%
  group_by(sentiment) %>%
  
  top_n(10) %>%
  
  ggplot() + 
  aes(x = reorder(word,desc(n)), y = n) + 
  geom_col(fill = "turquoise") +
  facet_grid(~sentiment, scales = "free_x")  + 
  geom_text(aes(label = n, vjust = -.5)) + 
  labs(title = "Positive words") +
  
  facet_wrap(~sentiment, ncol = 1, scales = "free_x") +
  
  xlab("Word") + 
  ylab("Count") 
## Joining, by = "word"
## Selecting by n

Findings

When look at on the two last graphs, we can see that the sentiment lexicons don’t classify words in the same way even though the emotion is the same. We can see that for “positive” emotion for example in both loughran and nrc sentiment lexicon, the most frequent words are not the same. Only the word “good” appears in both with the same count. The rest are different. Negative emotion in the other hand, all the words are different. Thus, choosing a sentiment lexicon would depend on specific aspects we want to base our sentiment analysis. We need to ask ourselves several questions on the text we want to analyze prior even starting the analysis or using any sentiment lexicon.

LS0tDQp0aXRsZTogIkRBVEEgNjA3IEFTU0lHTk1FTlQgMTAiDQphdXRob3I6ICJKZXJlZCBBdGFreSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogDQogIG9wZW5pbnRybzo6bGFiX3JlcG9ydDogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50Og0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyMgTGlicmFpcmllcw0KDQpgYGB7cn0NCmxpYnJhcnkgKHRpZHl0ZXh0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGphbmVhdXN0ZW5yKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmBgYA0KDQoNCiMjIFBhcnQgMTogQm9vaydzIGV4YW1wbGUNCg0KDQojIyMgVGhlIHNlbnRpbWVudHMgZGF0YXNldA0KDQpgYGB7cn0NCg0KZ2V0X3NlbnRpbWVudHMoImFmaW5uIikNCg0KYGBgDQoNCmBgYHtyfQ0KDQpnZXRfc2VudGltZW50cygiYmluZyIpDQoNCmBgYA0KDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJucmMiKQ0KDQpgYGANCg0KIyMjIFNlbnRpbWVudCBhbmFseXNpcyB3aXRoIGlubmVyIGpvaW4NCg0KDQpgYGB7cn0NCg0KDQpsaWJyYXJ5KGphbmVhdXN0ZW5yKQ0KbGlicmFyeShkcGx5cikNCg0KDQp0aWR5X2Jvb2tzIDwtIGF1c3Rlbl9ib29rcygpICU+JQ0KICBncm91cF9ieShib29rKSAlPiUNCiAgbXV0YXRlKA0KICAgIGxpbmVudW1iZXIgPSByb3dfbnVtYmVyKCksDQogICAgY2hhcHRlciA9IGN1bXN1bShzdHJfZGV0ZWN0KHRleHQsIHJlZ2V4KCJeY2hhcHRlciBbXFxkaXZ4bGNdIiwNCiAgICAgIGlnbm9yZV9jYXNlID0gVFJVRQ0KICAgICkpKQ0KICApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkNCg0KDQpgYGANCg0KDQpgYGB7cn0NCg0KbnJjX2pveSA8LSBnZXRfc2VudGltZW50cygibnJjIikgJT4lDQogIGZpbHRlcihzZW50aW1lbnQgPT0gImpveSIpDQoNCnRpZHlfYm9va3MgJT4lDQogIGZpbHRlcihib29rID09ICJFbW1hIikgJT4lDQogIGlubmVyX2pvaW4obnJjX2pveSkgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmxpYnJhcnkodGlkeXIpDQoNCmphbmVfYXVzdGVuX3NlbnRpbWVudCA8LSB0aWR5X2Jvb2tzICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBjb3VudChib29rLCBpbmRleCA9IGxpbmVudW1iZXIgJS8lIDgwLCBzZW50aW1lbnQpICU+JQ0KICBzcHJlYWQoc2VudGltZW50LCBuLCBmaWxsID0gMCkgJT4lDQogIG11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlKQ0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KGdncGxvdDIpDQoNCmdncGxvdChqYW5lX2F1c3Rlbl9zZW50aW1lbnQsIGFlcyhpbmRleCwgc2VudGltZW50LCBmaWxsID0gYm9vaykpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5ib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpDQoNCg0KYGBgDQoNCg0KDQojIyMgIENvbXBhcmluZyB0aGUgdGhyZWUgc2VudGltZW50IGRpY3Rpb25hcmllcw0KDQpgYGB7cn0NCg0KcHJpZGVfcHJlanVkaWNlIDwtIHRpZHlfYm9va3MgJT4lDQogIGZpbHRlcihib29rID09ICJQcmlkZSAmIFByZWp1ZGljZSIpDQoNCnByaWRlX3ByZWp1ZGljZQ0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KYWZpbm4gPC0gcHJpZGVfcHJlanVkaWNlICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpKSAlPiUNCiAgZ3JvdXBfYnkoaW5kZXggPSBsaW5lbnVtYmVyICUvJSA4MCkgJT4lDQogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0odmFsdWUpKSAlPiUNCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpDQoNCmJpbmdfYW5kX25yYyA8LSBiaW5kX3Jvd3MoDQogIHByaWRlX3ByZWp1ZGljZSAlPiUNCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICAgIG11dGF0ZShtZXRob2QgPSAiQmluZyBldCBhbC4iKSwNCiAgcHJpZGVfcHJlanVkaWNlICU+JQ0KICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JQ0KICAgICAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoDQogICAgICAgICJwb3NpdGl2ZSIsDQogICAgICAgICJuZWdhdGl2ZSINCiAgICAgICkpKSAlPiUNCiAgICBtdXRhdGUobWV0aG9kID0gIk5SQyIpDQopICU+JQ0KICBjb3VudChtZXRob2QsIGluZGV4ID0gbGluZW51bWJlciAlLyUgODAsIHNlbnRpbWVudCkgJT4lDQogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmJpbmRfcm93cygNCiAgYWZpbm4sDQogIGJpbmdfYW5kX25yYw0KKSAlPiUNCiAgZ2dwbG90KGFlcyhpbmRleCwgc2VudGltZW50LCBmaWxsID0gbWV0aG9kKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofm1ldGhvZCwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3kiKQ0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpnZXRfc2VudGltZW50cygibnJjIikgJT4lDQogIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKA0KICAgICJwb3NpdGl2ZSIsDQogICAgIm5lZ2F0aXZlIg0KICApKSAlPiUNCiAgY291bnQoc2VudGltZW50KQ0KDQpnZXRfc2VudGltZW50cygiYmluZyIpICU+JQ0KICBjb3VudChzZW50aW1lbnQpDQoNCg0KDQpgYGANCg0KDQojIyMgTW9zdCBjb21tb24gcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmRzDQoNCmBgYHtyfQ0KYmluZ193b3JkX2NvdW50cyA8LSB0aWR5X2Jvb2tzICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgdW5ncm91cCgpDQoNCmJpbmdfd29yZF9jb3VudHMNCg0KDQpgYGANCg0KDQpgYGB7cn0NCg0KYmluZ193b3JkX2NvdW50cyAlPiUNCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCiAgdG9wX24oMTApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQogIGdncGxvdChhZXMod29yZCwgbiwgZmlsbCA9IHNlbnRpbWVudCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlX3kiKSArDQogIGxhYnMoDQogICAgeSA9ICJDb250cmlidXRpb24gdG8gc2VudGltZW50IiwNCiAgICB4ID0gTlVMTA0KICApICsNCiAgY29vcmRfZmxpcCgpDQoNCg0KYGBgDQoNCg0KYGBge3J9DQoNCmN1c3RvbV9zdG9wX3dvcmRzIDwtIGJpbmRfcm93cygNCiAgdGliYmxlKA0KICAgIHdvcmQgPSBjKCJtaXNzIiksDQogICAgbGV4aWNvbiA9IGMoImN1c3RvbSIpDQogICksDQogIHN0b3Bfd29yZHMNCikNCg0KY3VzdG9tX3N0b3Bfd29yZHMNCg0KDQpgYGANCg0KDQojIyMgV29yZGNsb3Vkcw0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHdvcmRjbG91ZCkNCg0KdGlkeV9ib29rcyAlPiUNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JQ0KICBjb3VudCh3b3JkKSAlPiUNCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMTAwKSkNCg0KDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQp0aWR5X2Jvb2tzICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lDQogIGNvbXBhcmlzb24uY2xvdWQoDQogICAgY29sb3JzID0gYygiZ3JheTIwIiwgImdyYXk4MCIpLA0KICAgIG1heC53b3JkcyA9IDEwMA0KICApDQoNCg0KDQpgYGANCg0KDQoNCiMjIyBTb3VyY2UNCg0KDQpTaWxnZSwgSi4sICYgUm9iaW5zb24sIEQuICgyMDE3KS4gVGV4dCBtaW5pbmcgd2l0aCBSOiBBIHRpZHkgYXBwcm9hY2guIFNlYmFzdG9wb2wsIENBOiBPJ1JlaWxseS4gDQoNCkNoYXB0ZXIgMjogU2VudGltZW50IEFuYWx5c2lzIHdpdGggVGlkeSBEYXRhDQoNClNlZTogd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS9zZW50aW1lbnQuaHRtbA0KDQoNCiMjIFBhcnQgMjogU2VudGltZW50IEFuYWx5c2lzIG9mICJHbGFzc2VzIiwgYSBib29rIHdyaXR0ZW4gYnkgSGVucnkgSmFtZXMuDQoNCg0KPHN0eWxlPg0KZGl2LmFxdWFtYXJpbmUgeyBiYWNrZ3JvdW5kLWNvbG9yOiM3ZmZmZDQ7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDVweDt9DQo8L3N0eWxlPg0KPGRpdiBjbGFzcyA9ICJhcXVhbWFyaW5lIj4NCg0KV2UgYXJlIGdvaW5nIHRvIGFuYWx5emUgIkdsYXNzZXMiLCBmb3VuZCBpbiBndXRlbmJlcmdyIHBhY2thZ2UsIGl0IGlzIGEgYm9vaw0Kd3JpdHRlbiBieSBIZW5yeSBKYW1lcy4gSXQgaXMgYSBzdG9yeSBhYm91dCBhIHlvdW5nIHdvbWFuIHdob3NlIG9ubHkgYXNzZXQgaXMgYSANCnN1cHJlbWVseSBiZWF1dGlmdWwgZmFjZSBpcyBhYm91dCB0byBtYWtlIGEgc29jaWV0eSBtYXJyaWFnZSB1bnRpbCBoZXINCmZpYW5jZSBkaXNjb3ZlcnMgdGhhdCwgYmVpbmcgdmlydHVhbGx5IGJpbmQsIHNoZSBuZWVkcyB0aGljayBnbGFzc2VzIHdoaWNoIA0KcnVpbiBoZXIgbG9va3MuDQoNClNvdXJjZTogW0dsYXNzZXNdKGh0dHBzOi8vd3d3Lmd1dGVuYmVyZy5vcmcvZmlsZXMvMTE5NS8xMTk1LWgvMTE5NS1oLmh0bSkNCg0KDQo8L2Rpdj4gXGhmaWxsXGJyZWFrDQoNCg0KIyMjIExvYWRpbmcgdGhlIHBhY2thZ2UgYW5kIHRpZHlpbmcgdGhlIGRhdGFzZXQNCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KDQpsaWJyYXJ5KGd1dGVuYmVyZ3IpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBEb3dubG9hZCB0aGUgYm9vayBpZCAxMTg3LCBXYXIgb2YgdGhlIENsYXNzZXMNCg0KbXlib29rIDwtIGd1dGVuYmVyZ19kb3dubG9hZCgxMTk1KQ0KDQpgYGANCg0KYGBge3J9DQoNCm15Ym9vaw0KDQpgYGANCg0KDQoNCmBgYHtyfQ0KDQojIFJlc3RydWN0dXJlIHRvIG9uZS10b2tlbl9wZXItcm93IGFuZCByZW1vdmUgc3RvcCB3b3Jkcw0KDQpteWJvb2tfdGlkeSA8LSBteWJvb2sgJT4lDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lDQogIGFudGlfam9pbihzdG9wX3dvcmRzKQ0KDQpteWJvb2tfdGlkeQ0KDQpgYGANCg0KIyMjIE5ldCBTZW50aW1lbnQgYW5hbHlzaXMgYWNjcm9zcyB0aGUgYm9vayBwZXIgY2hhcHRlcg0KDQpgYGB7cn0NCg0KIyBSZXN0cnVjdHVyZSB0byBvbmUtdG9rZW5fcGVyLXJvdyBhbmQgcmVtb3ZlIHN0b3Agd29yZHMNCg0KbXlib29rX2NoYXB0ZXJzIDwtIG15Ym9vayAlPiUgDQogIGZpbHRlcih0ZXh0ICE9ICIiKSAlPiUNCiAgDQogIG11dGF0ZShsaW5lbnVtYmVyID0gcm93X251bWJlcigpLA0KICAgICAgICAgDQogICAgICAgICBjaGFwdGVyID0gY3Vtc3VtKHN0cl9kZXRlY3QodGV4dCwgcmVnZXgoIihDaGFwdGVyICkoW1xcZGl2eGxjXSkiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gIFRSVUUNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgKSkpDQogICAgICAgICApICU+JQ0KICANCiAgdW5ncm91cCgpDQoNCg0KbXlib29rX2NoYXB0ZXJzDQoNCmBgYA0KDQpUaWR5aW5nIGJ5IHRva2VuaXppbmcgYW5kIHVzaW5nIGFmaW5uIGxleGljb24NCg0KYGBge3J9DQoNCiMgdGlkeWluZyBteWJvb2tfY2hhcHRlciBieSB0b2tlbml6aW5nIGFuZCB1c2luZyBhZmlubiBsZXhpY29uDQoNCm15Ym9va19jaGFwdGVyc190aWR5IDwtIG15Ym9va19jaGFwdGVycyAlPiUNCiAgDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIikpDQoNCg0KYGBgDQojIyMgU2VudGltZW50IGFuYWx5c2lzIGFjY3Jvc3MgdGhlIGJvb2sNCg0KYGBge3J9DQoNCg0KbXlib29rc19yb3dzX3Bsb3QgPC0gbXlib29rX2NoYXB0ZXJzX3RpZHkgJT4lDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lDQogIGNvdW50KGluZGV4ID0gbGluZW51bWJlciAlLyUgMjAsIHNlbnRpbWVudCkgJT4lDQogIHNwcmVhZChzZW50aW1lbnQsIG4sIGZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQoNCg0KZ2dwbG90KG15Ym9va3Nfcm93c19wbG90LCBhZXMoaW5kZXgsIHNlbnRpbWVudCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICANCiAgZ2VvbV9jb2woZmlsbCA9ICJ2aW9sZXQiKSArDQogIA0KICAgbGFicyh0aXRsZSA9ICJOZXQgU2VudGltZW50IGFjY3Jvc3MgdGhlIGJvb2siKSANCg0KDQoNCmBgYA0KDQoNCjxzdHlsZT4NCmRpdi5hcXVhbWFyaW5lIHsgYmFja2dyb3VuZC1jb2xvcjojN2ZmZmQ0OyBib3JkZXItcmFkaXVzOiAxMHB4OyBwYWRkaW5nOiA1cHg7fQ0KPC9zdHlsZT4NCjxkaXYgY2xhc3MgPSAiYXF1YW1hcmluZSI+DQoNCldlIGNhbiBzZWUgdGhhdCB0aGUgc2VudGltZW50IGFjY3Jvb3MgdGhlIGJvb2sgdmFyaWVzLg0KV2UgYXJlIGdvaW5nIG5vdyB0byBhbmFseXplIHRoZSBuZXQgc2VudGltZW50IHBlciBjaGFwaXRlciBhbmQgdGhlIG92ZXJhbGwgc2VudGltZW50DQpwZXIgY2hhcHRlci4gTm90ZSB0aGF0IHdlIGhhdmUgMTMgY2hhcHRlcnMgaW4gdGhpcyBib29rLg0KDQo8L2Rpdj4gXGhmaWxsXGJyZWFrDQoNCmBgYHtyfQ0KDQojIEdyb3VwaW5nIG5lZWRlZCB2YXJpYWJsZXMNCg0KbXlib29rX2NoYXB0ZXJzX3Bsb3QgPC0gbXlib29rX2NoYXB0ZXJzX3RpZHkgJT4lDQogIA0KICBzZWxlY3QoY2hhcHRlciwgdmFsdWUpICU+JQ0KICANCiAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIA0KICANCiAgc3VtbWFyaXplKHRvdGFsX3NlbnRpbWVudCA9IHN1bSh2YWx1ZSkpDQoNCg0KIyBQbG90DQoNCm15Ym9va19jaGFwdGVyc19wbG90ICU+JQ0KICANCiAgZ2dwbG90KGFlcyhjaGFwdGVyLCB0b3RhbF9zZW50aW1lbnQpKSArDQogIA0KICBnZW9tX2NvbChmaWxsID0gInJlZCIpICsNCiAgDQogICB4bGFiKCJJbmRleCAtIGNoYXB0ZXIiKSArDQogIA0KICANCiAgIHlsYWIoIk5ldCBTZW50aW1lbnQiKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJOZXQgU2VudGltZW50IGFjY3Jvc3MgdGhlIGJvb2sgcGVyIGNoYXB0ZXIiKSANCiAgDQoNCmBgYA0KDQo8c3R5bGU+DQpkaXYuYXF1YW1hcmluZSB7IGJhY2tncm91bmQtY29sb3I6IzdmZmZkNDsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogNXB4O30NCjwvc3R5bGU+DQo8ZGl2IGNsYXNzID0gImFxdWFtYXJpbmUiPg0KDQoNCkZyb20gdGhlIGdyYXBoIGFib3ZlIHdlIGNhbiBzZWUgdGhhdCB0aGUgZmlyc3QgNiBjaGFwdGVycyBoYXZlIGEgbmV0IHBvc2l0aXZlIA0Kc2VudGltZW50IHdpdGggdGhlIHRoaXJkIG9uZSB0byBiZSB0aGUgbW9zdCBwb3NpdGl2ZSB3aGlsZSBjaGFwdGVyIDcgaXMgdGhlIA0KbW9zdCBuZWdhdGl2ZS4NCg0KPC9kaXY+IFxoZmlsbFxicmVhaw0KDQojIyMgT3ZlcmFsbCBzZW50aW1lbnQgDQoNCjxzdHlsZT4NCmRpdi5hcXVhbWFyaW5lIHsgYmFja2dyb3VuZC1jb2xvcjojN2ZmZmQ0OyBib3JkZXItcmFkaXVzOiAxMHB4OyBwYWRkaW5nOiA1cHg7fQ0KPC9zdHlsZT4NCjxkaXYgY2xhc3MgPSAiYXF1YW1hcmluZSI+DQoNCkxldCB0YWtlIGEgbG9vayBhdCB0aGUgb3ZlcmFsbCBzZW50aW1lbnQgaW4gdGhlIGVudGlyZSBib29rIHVzaW5nIGJpbmcgbGV4aWNvbjoNCg0KPC9kaXY+IFxoZmlsbFxicmVhaw0KDQoNCmBgYHtyfQ0KDQojIEdldCAiYmluZyIgbGV4aWNvbiBmb3IgdGhpcyBhbmFseXNpcw0KDQpteWJvb2tfb3ZlcmFsbF9zZW50aW1lbnQgPC0gbXlib29rICU+JSANCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUgDQogIGNvdW50KHNlbnRpbWVudCkgJT4lDQogIG11dGF0ZSh0b3RhbCA9IG4gLyBzdW0obikpDQoNCiMgUGxvdA0KDQpnZ3Bsb3QobXlib29rX292ZXJhbGxfc2VudGltZW50KSArIA0KICANCiAgYWVzKHggPSBzZW50aW1lbnQsIHkgPSB0b3RhbCkgKyANCiAgZ2VvbV9jb2woZmlsbCA9ICJ2aW9sZXQiKSArIA0KICANCiAgeGxhYigiU2VudGltZW50IikgKw0KICB5bGFiKCJQZXJjZW50IikgKyANCiANCiAgbGFicyh0aXRsZSA9ICJPdmVyYWxsIFNlbnRpbWVudCIpICsgDQogIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQodG90YWwgKiAxMDAsIDIpICwgdmp1c3QgPSAtLjQpKQ0KDQoNCmBgYA0KDQo8c3R5bGU+DQpkaXYuYXF1YW1hcmluZSB7IGJhY2tncm91bmQtY29sb3I6IzdmZmZkNDsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogNXB4O30NCjwvc3R5bGU+DQo8ZGl2IGNsYXNzID0gImFxdWFtYXJpbmUiPg0KDQpUaGVyZSBpcyBhbG1vc3QgcG9zaXRpdmUgY29udHJpYnV0aW9uIGFzIHRoZXJlIGlzIG5lZ2F0aXZlIG9uZS4NCg0KTGV0IHBsb3Qgbm93IHRoZSBtb3N0IHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyBiZWxvdy4NCldlIGFyZSBnb2luZyB0byB1c2UgYmluZyBsZXhpY29uIGFzIHdlbGw6DQoNCjwvZGl2PiBcaGZpbGxcYnJlYWsNCg0KIyMjIE1vc3QgcG9zaXRpdmUgd29yZHMNCg0KYGBge3J9DQoNCm15Ym9vayAlPiUNCiAgDQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lIA0KICBmaWx0ZXIoc2VudGltZW50ID09ICJwb3NpdGl2ZSIpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUgDQogIA0KICB0b3BfbigxNSkgJT4lDQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lDQogIA0KICBnZ3Bsb3QoKSArIA0KICANCiAgYWVzKHggPSB3b3JkLCB5ID0gbikgKw0KICBsYWJzKHRpdGxlID0gIk1vc3QgUG9zaXRpdmUgV29yZHMiKSArIA0KICB5bGFiKCJDb250cmlidXRpb24gdG8gc2VudGltZW50IikgKyANCiAgeGxhYigiV29yZCIpICsNCiAgZ2VvbV9jb2woZmlsbCA9ICJibHVlIikgKw0KICANCiAgDQogIGNvb3JkX2ZsaXAoKQ0KICANCiAgDQpgYGANCg0KDQojIyMgTW9zdCBuZWdhdGl2ZSB3b3Jkcw0KDQpgYGB7cn0NCg0KbXlib29rICU+JQ0KICANCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUNCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUgDQogIGZpbHRlcihzZW50aW1lbnQgPT0gIm5lZ2F0aXZlIikgJT4lDQogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JSANCiAgDQogIHRvcF9uKDE1KSAlPiUNCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUNCiAgDQogIGdncGxvdCgpICsgDQogIA0KICBhZXMoeCA9IHdvcmQsIHkgPSBuKSArDQogIGxhYnModGl0bGUgPSAiTW9zdCBOZWdhdGl2ZSBXb3JkcyIpICsgDQogIHlsYWIoIkNvbnRyaWJ1dGlvbiB0byBzZW50aW1lbnQiKSArIA0KICB4bGFiKCJXb3JkIikgKw0KICBnZW9tX2NvbChmaWxsID0gInJlZCIpICsNCiAgDQogIA0KICBjb29yZF9mbGlwKCkgDQogIA0KIA0KDQpgYGANCg0KDQoNCiMjIyBTZW50aW1lbnQgQW5hbHlzaXMgd2l0aCBMb3VnaHJhbi1NYWNEb25hbGQgc2VudGltZW50IGxleGljb24NCg0KPHN0eWxlPg0KZGl2LmFxdWFtYXJpbmUgeyBiYWNrZ3JvdW5kLWNvbG9yOiM3ZmZmZDQ7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDVweDt9DQo8L3N0eWxlPg0KPGRpdiBjbGFzcyA9ICJhcXVhbWFyaW5lIj4NCg0KVGhpcyBsZXhpY29uIGxhYmVscyB3b3JkcyB3aXRoIHNpeCBwb3NzaWJsZSBzZW50aW1lbnRzIGltcG9ydGFudCBpbiBmaW5hbmNpYWwgY29udGV4dHM6DQoibmVnYXRpdmUiLCAicG9zaXRpdmUiLCAibGl0aWdpb3VzIiwgInVuY2VydGFpbnR5IiwgImNvbnN0cmFpbmluZyIsIG9yICJzdXBlcmZsdW91cyIuDQoNCnNvdXJjZTogaHR0cHM6Ly9lbWlsaHZpdGZlbGR0LmdpdGh1Yi5pby90ZXh0ZGF0YS9yZWZlcmVuY2UvbGV4aWNvbl9sb3VnaHJhbi5odG1sDQoNCkluIHRoaXMgYW5hbHlzaXMsIHdlIGFyZSBnb2luZyB0byBleHBsb3JlIHR5cGUgb2Ygd29yZHMgaW4gIkdsYXNzZXMiDQphcmUgYXNzb2NpYXRlZCB0byAgInVuY2VydGFpbnR5IiBhbmQgImNvbnN0cmFpbmluZyIuDQoNCjwvZGl2PiBcaGZpbGxcYnJlYWsNCg0KDQpgYGB7cn0NCg0KIyBHZXQgbG91Z2hyYW4NCg0Kc2VudGltZW50IDwtIGdldF9zZW50aW1lbnRzKCJsb3VnaHJhbiIpDQoNCmBgYA0KDQojIyMjIFdvcmRzIGFzc29jaWF0ZWQgdG8gbGl0aWdpb3VzICYgU3VwZXJmbHVvdXMNCg0KPHN0eWxlPg0KZGl2LmFxdWFtYXJpbmUgeyBiYWNrZ3JvdW5kLWNvbG9yOiM3ZmZmZDQ7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDVweDt9DQo8L3N0eWxlPg0KPGRpdiBjbGFzcyA9ICJhcXVhbWFyaW5lIj4NCg0KV2UgY2hvc2UgdGhlc2Ugd29yZHMgYnkgdGhlIGZhY3QgdGhleSBhcmUgbm90IHJlYWxseSBjb21tb24gaW4gZGFpbHkgY29udmVyc2F0aW9uLg0KDQo8L2Rpdj4gXGhmaWxsXGJyZWFrDQoNCmBgYHtyfQ0KDQoNCiAgbXlib29rX2NoYXB0ZXJzICU+JSANCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgDQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImxvdWdocmFuIikpICU+JQ0KICBmaWx0ZXIoc2VudGltZW50ICVpbiUgYygibGl0aWdpb3VzIiwgInN1cGVyZmx1b3VzIikpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCiAgDQogIHRvcF9uKDEwKSAlPiUNCiAgDQogIGdncGxvdCgpICsgDQogIGFlcyh4ID0gcmVvcmRlcih3b3JkLGRlc2MobikpLCB5ID0gbikgKyANCiAgZ2VvbV9jb2woZmlsbCA9ICJ0dXJxdW9pc2UiKSArDQogIGZhY2V0X2dyaWQofnNlbnRpbWVudCwgc2NhbGVzID0gImZyZWVfeCIpICArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gbiwgdmp1c3QgPSAtLjUpKSArIA0KICBsYWJzKHRpdGxlID0gIldvcmRzIEFzc29jaWF0ZWQgdG8gbGl0aWdpb3VzICYgU3VwZXJmbHVvdXMiKSArDQogIA0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV94IikgKw0KICANCiAgDQogIHhsYWIoIldvcmQiKSArIA0KICB5bGFiKCJDb3VudCIpIA0KICANCiAgDQogDQoNCg0KYGBgDQoNCjxzdHlsZT4NCmRpdi5hcXVhbWFyaW5lIHsgYmFja2dyb3VuZC1jb2xvcjojN2ZmZmQ0OyBib3JkZXItcmFkaXVzOiAxMHB4OyBwYWRkaW5nOiA1cHg7fQ0KPC9zdHlsZT4NCjxkaXYgY2xhc3MgPSAiYXF1YW1hcmluZSI+DQoNCkFsdGhvdWdoIHRoZSB0d28gd29yZHMgYXJlIG5vdCBjb21tb24gYnV0IHdlIGZvdW5kIG1vcmUgd29yZHMgcmVsYXRlZCB0byAibGl0aWdpb3VzIg0KYnV0IG9ubHkgb25lIHRvICJzdXBlcmZsdW91cyIsIHRoYXQgaXMgcmVhbGx5IGludGVyZXN0aW5nLiANCg0KPC9kaXY+IFxoZmlsbFxicmVhaw0KDQoNCiMjIyMgUG9zaXRpdmUgd29yZHMNCg0KPHN0eWxlPg0KZGl2LmFxdWFtYXJpbmUgeyBiYWNrZ3JvdW5kLWNvbG9yOiM3ZmZmZDQ7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDVweDt9DQo8L3N0eWxlPg0KPGRpdiBjbGFzcyA9ICJhcXVhbWFyaW5lIj4NCg0KV2Ugd2lsbCB1c2UgdGhpcyB0aGVuIGNvbXBhcmUgdGhlIHdvcmRzIEkgd2lsbCBnZXQgdG8gdGhlIG9uZXMNCkkgd2lsbCBnZXQgdXNpbmcgbnJjIGxleGljb24uDQoNCjwvZGl2PiBcaGZpbGxcYnJlYWsNCg0KYGBge3J9DQoNCg0KICBteWJvb2tfY2hhcHRlcnMgJT4lIA0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JSANCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibG91Z2hyYW4iKSkgJT4lDQogIGZpbHRlcihzZW50aW1lbnQgJWluJSBjKCJwb3NpdGl2ZSIsICJuZWdhdGl2ZSIpKSAlPiUNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lDQogIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lDQogIA0KICB0b3BfbigxMCkgJT4lDQogIA0KICBnZ3Bsb3QoKSArIA0KICBhZXMoeCA9IHJlb3JkZXIod29yZCxkZXNjKG4pKSwgeSA9IG4pICsgDQogIGdlb21fY29sKGZpbGwgPSAidHVycXVvaXNlIikgKw0KICBmYWNldF9ncmlkKH5zZW50aW1lbnQsIHNjYWxlcyA9ICJmcmVlX3giKSAgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4sIHZqdXN0ID0gLS41KSkgKyANCiAgbGFicyh0aXRsZSA9ICJQb3NpdGl2ZSB3b3JkcyIpICsNCiAgDQogIGZhY2V0X3dyYXAofnNlbnRpbWVudCwgbmNvbCA9IDEsIHNjYWxlcyA9ICJmcmVlX3giKSArDQogIA0KICANCiAgeGxhYigiV29yZCIpICsgDQogIHlsYWIoIkNvdW50IikgDQoNCg0KYGBgDQoNCg0KDQojIyMjIFdvcmRzIGFzc29jaWF0ZWQgdG8gcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGVtb3Rpb25zIHVzaW5nIG5yYyBsZXhpY29uDQoNCjxzdHlsZT4NCmRpdi5hcXVhbWFyaW5lIHsgYmFja2dyb3VuZC1jb2xvcjojN2ZmZmQ0OyBib3JkZXItcmFkaXVzOiAxMHB4OyBwYWRkaW5nOiA1cHg7fQ0KPC9zdHlsZT4NCjxkaXYgY2xhc3MgPSAiYXF1YW1hcmluZSI+DQoNClRoaXMgaXMgdG8gY29tcGFyZSBob3cgYm90aCBsZXhpY29uIGNsYXNzaWZ5IHdvcmRzDQoNCjwvZGl2PiBcaGZpbGxcYnJlYWsNCg0KYGBge3J9DQoNCg0KICBteWJvb2tfY2hhcHRlcnMgJT4lIA0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JSANCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIikpICU+JQ0KICBmaWx0ZXIoc2VudGltZW50ICVpbiUgYygicG9zaXRpdmUiLCAibmVnYXRpdmUiKSkgJT4lDQogIGNvdW50KHdvcmQsIHNlbnRpbWVudCwgc29ydCA9IFRSVUUpICU+JQ0KICBncm91cF9ieShzZW50aW1lbnQpICU+JQ0KICANCiAgdG9wX24oMTApICU+JQ0KICANCiAgZ2dwbG90KCkgKyANCiAgYWVzKHggPSByZW9yZGVyKHdvcmQsZGVzYyhuKSksIHkgPSBuKSArIA0KICBnZW9tX2NvbChmaWxsID0gInR1cnF1b2lzZSIpICsNCiAgZmFjZXRfZ3JpZCh+c2VudGltZW50LCBzY2FsZXMgPSAiZnJlZV94IikgICsgDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuLCB2anVzdCA9IC0uNSkpICsgDQogIGxhYnModGl0bGUgPSAiUG9zaXRpdmUgd29yZHMiKSArDQogIA0KICBmYWNldF93cmFwKH5zZW50aW1lbnQsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV94IikgKw0KICANCiAgeGxhYigiV29yZCIpICsgDQogIHlsYWIoIkNvdW50IikgDQoNCg0KYGBgDQoNCiMjIyBGaW5kaW5ncw0KDQo8c3R5bGU+DQpkaXYuYXF1YW1hcmluZSB7IGJhY2tncm91bmQtY29sb3I6IzdmZmZkNDsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogNXB4O30NCjwvc3R5bGU+DQo8ZGl2IGNsYXNzID0gImFxdWFtYXJpbmUiPg0KDQpXaGVuIGxvb2sgYXQgb24gdGhlIHR3byBsYXN0IGdyYXBocywgd2UgY2FuIHNlZSB0aGF0IHRoZSBzZW50aW1lbnQgbGV4aWNvbnMgDQpkb24ndCBjbGFzc2lmeSB3b3JkcyBpbiB0aGUgc2FtZSB3YXkgZXZlbiB0aG91Z2ggdGhlIGVtb3Rpb24gaXMgdGhlIHNhbWUuDQpXZSBjYW4gc2VlIHRoYXQgZm9yICJwb3NpdGl2ZSIgZW1vdGlvbiBmb3IgZXhhbXBsZSBpbiBib3RoIGxvdWdocmFuIGFuZCBucmMgc2VudGltZW50DQpsZXhpY29uLCB0aGUgbW9zdCBmcmVxdWVudCB3b3JkcyBhcmUgbm90IHRoZSBzYW1lLiBPbmx5IHRoZSB3b3JkICJnb29kIg0KYXBwZWFycyBpbiBib3RoIHdpdGggdGhlIHNhbWUgY291bnQuIFRoZSByZXN0IGFyZSBkaWZmZXJlbnQuDQpOZWdhdGl2ZSBlbW90aW9uIGluIHRoZSBvdGhlciBoYW5kLCBhbGwgdGhlIHdvcmRzIGFyZSBkaWZmZXJlbnQuDQpUaHVzLCBjaG9vc2luZyBhIHNlbnRpbWVudCBsZXhpY29uIHdvdWxkIGRlcGVuZCBvbiBzcGVjaWZpYyBhc3BlY3RzIA0Kd2Ugd2FudCB0byBiYXNlIG91ciBzZW50aW1lbnQgYW5hbHlzaXMuIFdlIG5lZWQgdG8gYXNrIG91cnNlbHZlcyBzZXZlcmFsIHF1ZXN0aW9ucw0Kb24gdGhlIHRleHQgd2Ugd2FudCB0byBhbmFseXplIHByaW9yIGV2ZW4gc3RhcnRpbmcgdGhlIGFuYWx5c2lzIG9yIHVzaW5nIGFueQ0Kc2VudGltZW50IGxleGljb24uDQogDQo8L2Rpdj4gXGhmaWxsXGJyZWFrDQoNCg==