This code is accompanying material for this talk for R-Ladies Tunis.

Text data provides an oasis of information for both researchers and non-researchers alike to explore. Natural Language Processing (NLP) methods help make sense of this difficult data type. The talk and code give you a smooth introduction to the quanteda package. I will also showcase how to quickly visualize your text data and cover both supervised and unsupervised approaches in NLP. As part of the code demo, we will use text data from the UN as a working example to give you first insights into the structure of text data and how to work with it.

Knowing terms and concepts

Before we get started, you might want to revise the terms and concepts once more (or use it as a glossary to look them up when needed).

Preparation

In a first step, we load the packages.

## Packages
pkgs <- c(
  "knitr",       # A General-Purpose Package for Dynamic Report Generation in R
  "tidyverse",   # Easily Install and Load the 'Tidyverse'
  "quanteda",    # Quantitative Analysis of Textual Data
  "stm",         # Estimation of the Structural Topic Model
  "stminsights", # A 'Shiny' Application for Inspecting Structural Topic Models
  "LDAvis",      # Interactive Visualization of Topic Models
  "servr",       # A Simple HTTP Server to Serve Static Files or Dynamic Documents
  "topicmodels", # Topic Models
  "kableExtra",  # Construct Complex Table with 'kable' and Pipe Syntax
  "readtext",    # Import and Handling for Plain and Formatted Text Files
  "magrittr",    # A Forward-Pipe Operator for R
  "overviewR",   # Easily Extracting Information About Your Data
  "countrycode", # Convert Country Names and Country Codes
  "wesanderson", # A Wes Anderson Palette Generator
  "tidytext"     # Text Mining using 'dplyr', 'ggplot2', and Other Tidy Tools
)

## Install uninstalled packages
lapply(pkgs[!(pkgs %in% installed.packages())], install.packages)

## Load all packages to library
lapply(pkgs, library, character.only = TRUE)

## Set a theme for the plots:
theme_set(
  theme_minimal() + theme(
    strip.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank()
  )
)

How to deal with text data?

Remember, we will use quanteda in this workshop. Starting with quanteda basically works in 3-4 different steps:

  1. Import the data

  2. Build a corpus

  3. Pre-process your data

  4. Calculate a document-feature matrix (DFM)

And we will walk through each of them now.

  1. Import the data
# Load packages
library(quanteda) # For NLP (Quantitative Analysis of Textual Data)

load("../data/UN-data.RData")

Before we proceed, we check the data and the time and geographical coverage of the data:

head(un_data)
## readtext object consisting of 6 documents and 3 docvars.
## # Description: df[,5] [6 × 5]
##   doc_id          text                 country session  year
## * <chr>           <chr>                <chr>     <int> <int>
## 1 AFG_55_2000.txt "\"On my way \"..."  AFG          55  2000
## 2 AGO_55_2000.txt "\"Allow me t\"..."  AGO          55  2000
## 3 ALB_55_2000.txt "\"Allow me t\"..."  ALB          55  2000
## 4 AND_55_2000.txt "\"Andorra wi\"..."  AND          55  2000
## 5 ARE_55_2000.txt "\"I have the\"..."  ARE          55  2000
## 6 ARG_55_2000.txt "\"In\nthis, m\"..." ARG          55  2000

What we see is that our data set has five variables containing information on doc_id, text, country, session, and year. The text of the speech is stored in the variable text, the doc_id gives us a unique id for each document.

No we turn to the time and geographical coverage:

un_data %>%
  overview_tab(id = country, time = year)
## # A tibble: 197 x 2
## # Groups:   country [197]
##    country time_frame 
##    <chr>   <chr>      
##  1 AFG     2000 - 2018
##  2 AGO     2000 - 2018
##  3 ALB     2000 - 2018
##  4 AND     2000 - 2018
##  5 ARE     2000 - 2018
##  6 ARG     2000 - 2018
##  7 ARM     2000 - 2018
##  8 ATG     2000 - 2018
##  9 AUS     2000 - 2018
## 10 AUT     2000 - 2018
## # … with 187 more rows

To decrease the sample size, I reduced the data set and it now only captures data from 2000 onwards.

  1. Build a corpus
# Build the corpus
mycorpus <- corpus(un_data)

If you’re curious how a corpus looks like, here you go:

head(mycorpus, 2)
## Corpus consisting of 2 documents and 3 docvars.
## AFG_55_2000.txt :
## "On my way to the Assembly Hall, I was informed by the Suprem..."
## 
## AGO_55_2000.txt :
## "Allow me to begin by congratulating you, Sir, on behalf of m..."

It looks like just the text, right? But a corpus is so much more. It also has docvars - basically variables describing it.

head(docvars(mycorpus))
##   country session year
## 1     AFG      55 2000
## 2     AGO      55 2000
## 3     ALB      55 2000
## 4     AND      55 2000
## 5     ARE      55 2000
## 6     ARG      55 2000

And we can even add a docvar:

# Assigns a unique identifier to each text
docvars(mycorpus, "Textno") <-
  sprintf("%02d", 1:ndoc(mycorpus)) 

And again have a look at it:

head(docvars(mycorpus))
##   country session year Textno
## 1     AFG      55 2000     01
## 2     AGO      55 2000     02
## 3     ALB      55 2000     03
## 4     AND      55 2000     04
## 5     ARE      55 2000     05
## 6     ARG      55 2000     06
  1. Text pre-processing (not all steps are always required). This way, we get the tokens.
# Create tokens
token <-
  tokens(
    # Takes the corpus
    mycorpus,
    # Remove numbers
    remove_numbers = TRUE,
    # Remove punctuation
    remove_punct = TRUE,
    # Remove symbols
    remove_symbols = TRUE,
    # Remove URL
    remove_url = TRUE,
    # Split up hyphenated words
    split_hyphens = TRUE,
    # And include the doc vars (we'll need them later)
    include_docvars = TRUE
  )

But how do tokens look like? We can easily check it:

head(token, 2)
## Tokens consisting of 2 documents and 4 docvars.
## AFG_55_2000.txt :
##  [1] "On"       "my"       "way"      "to"       "the"      "Assembly"
##  [7] "Hall"     "I"        "was"      "informed" "by"       "the"     
## [ ... and 2,869 more ]
## 
## AGO_55_2000.txt :
##  [1] "Allow"          "me"             "to"             "begin"         
##  [5] "by"             "congratulating" "you"            "Sir"           
##  [9] "on"             "behalf"         "of"             "my"            
## [ ... and 2,520 more ]

Since the data is generated with OCR, we need to additionally clean this. We do this using the following command:

# Clean tokens created by OCR
token_ungd <- tokens_select(
  token,
  c("[\\d-]", "[[:punct:]]", "^.{1,2}$"),
  selection = "remove",
  valuetype = "regex",
  verbose = TRUE
)

We can now see how this changed our tokens:

head(token_ungd, 2)
## Tokens consisting of 2 documents and 4 docvars.
## AFG_55_2000.txt :
##  [1] "way"      "the"      "Assembly" "Hall"     "was"      "informed"
##  [7] "the"      "Supreme"  "State"    "Council"  "the"      "Islamic" 
## [ ... and 2,318 more ]
## 
## AGO_55_2000.txt :
##  [1] "Allow"          "begin"          "congratulating" "you"           
##  [5] "Sir"            "behalf"         "Government"     "and"           
##  [9] "own"            "behalf"         "your"           "assumption"    
## [ ... and 2,032 more ]
  1. Calculate a document-feature matrix (DFM)

Here, we also stem, remove stop words and lower case words.

mydfm <- dfm(
  # Take the token object
  token_ungd,
  # Lower the words
  tolower = TRUE,
  # Get the stem of the words
  stem = TRUE,
  # Remove stop words
  remove = stopwords("english")
)

Have a look at the DFM:

head(mydfm, 2)
## Document-feature matrix of: 2 documents, 19,265 features (96.2% sparse) and 4 docvars.
##                  features
## docs              way assembl hall inform suprem state council islam
##   AFG_55_2000.txt   1       8    1      2      1    14       9    16
##   AGO_55_2000.txt   5       2    0      0      0     2       5     0
##                  features
## docs              afghanistan self
##   AFG_55_2000.txt          45    1
##   AGO_55_2000.txt           0    2
## [ reached max_nfeat ... 19,255 more features ]

Trim data: remove all the words that appear less than 7.5% of the time and more than 90% of the time

mydfm.trim <-
  dfm_trim(
    mydfm,
    min_docfreq = 0.075,
    # min 7.5%
    max_docfreq = 0.90,
    #  max 90%
    docfreq_type = "prop"
  ) 

And check how your trimmed DFM looks like:

head(mydfm.trim, 2)
## Document-feature matrix of: 2 documents, 1,526 features (67.1% sparse) and 4 docvars.
##                  features
## docs              way hall inform council islam afghanistan self evid act
##   AFG_55_2000.txt   1    1      2       9    16          45    1    1   6
##   AGO_55_2000.txt   5    0      0       5     0           0    2    0   1
##                  features
## docs              aggress
##   AFG_55_2000.txt       1
##   AGO_55_2000.txt       0
## [ reached max_nfeat ... 1,516 more features ]

Visualizing your data

Word clouds

To get a first and easy visualization, we use word clouds:

quanteda::textplot_wordcloud(
  # Load the DFM object
  mydfm,
  # Define the minimum number the words have to occur
  min_count = 3,
  # Define the maximum number the words can occur
  max_words = 500,
  # Define a color
  color = wes_palette("Darjeeling1")
)

Word clouds are an illustrative way to show what you have in your data. You can also use word clouds beyond NLP analysis purposes, e.g., on a website.

Frequency plot

To visualize the frequency of the top 30 features, we use a lollipop plot:

# Inspired here: https://bit.ly/37MCEHg

# Get the 30 top features from the DFM
freq_feature <- topfeatures(mydfm, 30)

# Create a data.frame for ggplot
data <- data.frame(list(
  term = names(freq_feature),
  frequency = unname(freq_feature)
))

# Plot the plot
data %>%
  # Call ggplot
  ggplot() +
  # Add geom_segment (this will give us the lines of the lollipops)
  geom_segment(aes(
    x = reorder(term, frequency),
    xend = reorder(term, frequency),
    y = 0,
    yend = frequency
  ), color = "grey") +
  # Call a point plot with the terms on the x-axis and the frequency on the y-axis
  geom_point(aes(x = reorder(term, frequency), y = frequency)) +
  # Flip the plot
  coord_flip() +
  # Add labels for the axes
  xlab("") +
  ylab("Absolute frequency of the features")

Known categories

Dictionary approach

We use the LexiCoder Policy Agenda dictionary. It captures major topics from the comparative Policy Agenda project and is currently available in Dutch and English.

# Load the dictionary with quanteda's built-in function
dict <- dictionary(file = "../data/policy_agendas_english.lcd")

Using this dictionary, we now generate our DFM:

# Generate the DFM...
mydfm.un <- dfm(mydfm.trim, 
                # Based on country
                groups = "country",
                # And the previously loaded dictionary
                dictionary = dict)

Have a look at the new DFM:

head(mydfm.un)
## Document-feature matrix of: 6 documents, 28 features (35.7% sparse) and 1 docvar.
##      features
## docs  macroeconomics civil_rights healthcare agriculture forestry labour
##   AFG              3           14         13           0        0     12
##   AGO             11            4         10           0        0      7
##   ALB              4           44          7           0        0      3
##   AND              6           12         12           0        0      2
##   ARE             15           11          3           0        0     11
##   ARG            135           13         15           0        0     27
##      features
## docs  immigration education environment energy
##   AFG          16        29           1      0
##   AGO          16         4           9      1
##   ALB          16         9           5      1
##   AND          16        15          13      1
##   ARE          13         1          13      2
##   ARG          12         6          13      5
## [ reached max_nfeat ... 18 more features ]

Before we can turn to the plotting, we need to wrangle the data a bit to bring it in the right order. These are basic tidyverse commands.

un.topics.pa <- 
  # Convert the DFM to a data frame
  convert(mydfm.un, "data.frame") %>%
  # Rename the doc_id to country
  dplyr::rename(country = doc_id) %>%
  # Select relevant variables
  dplyr::select(country, macroeconomics, intl_affairs, defence) %>%
  # Bring the data set in a different order
  tidyr::pivot_longer(macroeconomics:defence, names_to = "topic", values_to = "share") %>%
  # Group by country
  group_by(country) %>%
  dplyr::mutate(
    # Generate the relative share of topics
    share = share / sum(share),
    # Make topic a factor
    topic = haven::as_factor(topic))

Based on this data set, we now generate the plot.

# Generate the plot
un.topics.pa %>%
  # We have country on the x-axis and the share on the y-axis, we color and fill by topic
  ggplot(aes(x = country, y = share, colour = topic, fill = topic)) +
  # Call the `geom_bar`
  geom_bar(stat = "identity") +
  # Define the fill colors and the labels in the legend
  scale_fill_manual(
    values = wes_palette("Darjeeling1"),
    labels = c("Macro-economic", "International affairs", "Defence")
  ) +
  # Same for the colors
  scale_color_manual(
    values = wes_palette("Darjeeling1"),
    labels = c("Macro-economic", "International affairs", "Defence")
  ) +
  # Add a title
  ggtitle("Distribution of PA topics in the UN General Debate corpus") +
  # And add x-axis and y-axis labels
  xlab("") +
  ylab("Topic share (%)") +
  # And last do some tweaking with the theme
  theme(axis.text.x = element_blank(),
        axis.ticks.x = element_blank())

Sentiment analysis

Here, we need to define more stop words (create a manual list of them) to make sure that we do not bias our results. I have a non-exhaustive list here:

# Define stopwords
UNGD_stopwords <-
  c(
    "good bye",
    "good morning",
    "unit",
    "nation",
    "res",
    "general",
    "assemb",
    "mr",
    "greet",
    "thank",
    "congratulat",
    "sir",
    "per",
    "cent",
    "mdgs",
    "soo",
    "han",
    "ó",
    "g",
    "madam",
    "ncds",
    "sdgs",
    "pv",
    "isil",
    "isi",
    "f",
    "fifti",
    "sixtieth",
    "annan",
    "kofi",
    "fifth",
    "fourth",
    "first",
    "second",
    "third",
    "sixth",
    "seventh",
    "eighth",
    "ninth",
    "tenth",
    "seventieth",
    "jeremić",
    "agenda",
    "obama",
    "julian",
    "sergio",
    "mello",
    "septemb",
    "document",
    "plenari",
    "jean",
    "eliasson",
    "anniversari",
    "vieira",
    "haya",
    "rash",
    "treki"
  )

To apply it, we re-start from step 4 creating a DFM.

We know apply it to our function

# Remove self-defined stopwords
mydfm_sentiment <- dfm(
  # Select the token object
  token_ungd,
  # Lower the words
  tolower = TRUE,
  # Stem the words
  stem = TRUE,
  # Remove stop words and self-defined stop words
  remove = c(UNGD_stopwords, stopwords("english"))
)

Trim data: remove all the words that appear less than 7.5% of the time and more than 90% of the time

mydfm.trim <-
  dfm_trim(
    # Select the DFM object
    mydfm_sentiment,
    min_docfreq = 0.075,
    # min 7.5%
    max_docfreq = 0.90,
    # max 90%
    docfreq_type = "prop"
  ) 

And now we get the sentiment :-) There are multiple ways how to do it, we will use one that is built in quanteda. In any case, it is important to check the underlying dictionary (on which basis is it built on?). The most frequently used dictionaries are:

For a more fine-grained approach, there is also the sentimentr package, that my co-author, Dennis Hammerschmidt, and I use in our paper on sentiment at the UNGD.

We will go with the LSD dictionary here as it is already built in quanteda.

# Call a dictionary
dfmat_lsd <-
  dfm(mydfm.trim,
      dictionary =
        data_dictionary_LSD2015[1:2])

We can look at the dictionary first choosing the first 5 documents:

head(dfmat_lsd, 5)
## Document-feature matrix of: 5 documents, 2 features (0.0% sparse) and 4 docvars.
##                  features
## docs              negative positive
##   AFG_55_2000.txt       84       68
##   AGO_55_2000.txt       88       95
##   ALB_55_2000.txt       42      105
##   AND_55_2000.txt       53       63
##   ARE_55_2000.txt       34       81

To better work with the data, we convert it to a data frame:

# Calculate the overall
# share of positive and
# negative words on a scale
data <- convert(dfmat_lsd,
                to = "data.frame")

And have a look at the data:

head(data)
##            doc_id negative positive
## 1 AFG_55_2000.txt       84       68
## 2 AGO_55_2000.txt       88       95
## 3 ALB_55_2000.txt       42      105
## 4 AND_55_2000.txt       53       63
## 5 ARE_55_2000.txt       34       81
## 6 ARG_55_2000.txt       60      141

To get more meaningful results, we do some last tweaks:

data %<>%
  dplyr::mutate(
    # Generate the number of total words
    total_words = positive + negative,
    # Generate the relative frequency
    pos_perc = positive / total_words * 100,
    neg_perc = negative / total_words * 100,
    # Generate the net sentiment
    net_perc = pos_perc - neg_perc
  )

Again, have a look at the data:

head(data)
##            doc_id negative positive total_words pos_perc neg_perc   net_perc
## 1 AFG_55_2000.txt       84       68         152 44.73684 55.26316 -10.526316
## 2 AGO_55_2000.txt       88       95         183 51.91257 48.08743   3.825137
## 3 ALB_55_2000.txt       42      105         147 71.42857 28.57143  42.857143
## 4 AND_55_2000.txt       53       63         116 54.31034 45.68966   8.620690
## 5 ARE_55_2000.txt       34       81         115 70.43478 29.56522  40.869565
## 6 ARG_55_2000.txt       60      141         201 70.14925 29.85075  40.298507
# Generate country code and year
data %<>%
  dplyr::mutate(# Define the country-code (it's all in the document ID)
    ccode = str_sub(doc_id, 1, 3),
    # Define the year (it's also in the document ID)
    year = as.numeric(str_sub(doc_id, 8, 11))) %>%
  # Drop all observations with "EU_" because they are not a single country
  dplyr::filter(ccode != "EU_") %>%
  # Drop the variable doc_id
  dplyr::select(-doc_id)

We first get an overall impression by plotting the average net sentiment by continent over time:

data %>%
  # Generate the continent for each country using the `countrycode()` command
  dplyr::mutate(continent = countrycode(ccode, "iso3c", "continent", custom_match =
                                          c("YUG" = "Europe"))) %>%
  # We group by continent and year to generate the average sentiment by continent 
  # and and year  
  group_by(continent, year) %>%
  dplyr::mutate(avg = mean(net_perc)) %>%
  # We now plot it
  ggplot() +
  # Using a line chart with year on the x-axis, the average sentiment by continent
  # on the y-axis and colored by continent
  geom_line(aes(x = year, y = avg, col = continent)) +
  # Define the colors
  scale_color_manual(name = "", values = wes_palette("Darjeeling1")) +
  # Label the axes
  xlab("Time") +
  ylab("Average net sentiment") 

And now we want to visualize the results in more detail :-)

data %>%
  # Generate the country name for each country using the `countrycode()` command
  dplyr::mutate(countryname = countrycode(ccode, "iso3c", "country.name")) %>%
  # Filter and only select specific countries that we want to compare
  dplyr::filter(countryname %in% c(
    "Germany",
    "France",
    "United Kingdom",
    "Norway",
    "Spain",
    "Sweden"
  )) %>%
  # Now comes the plotting part :-)
  ggplot() +
  # We do a bar plot that has the years on the x-axis and the level of the 
  # net-sentiment on the y-axis
  # We also color it so that all the net-sentiments greater 0 get a 
  # different color
  geom_col(aes(
    x = year,
    y = net_perc,
    fill = (net_perc > 0)
  )) +
  # Here we define the colors as well as the labels and title of the legend
  scale_fill_manual(
    name = "Sentiment",
    labels = c("Negative", "Positive"),
    values = c("#C93312", "#446455")
  ) +
  # Now we add the axes labels
  xlab("Time") +
  ylab("Net sentiment") +
  # And do a facet_wrap by country to get a more meaningful visualization
  facet_wrap(~ countryname)

Unknown categories

Structural topic models

We use the stm package here. Quanteda also has built-in topic models such as LDA.

library(stm) # Estimation of the Structural Topic Model

In a first step, we assign a topic count. Usually the number of topics can be higher – but that obviously comes at a cost. Here, it’s computational power. To make it as fast as possible, we’ll pick 5 topics for our example.

# Assigns the number of topics
topic.count <- 5 
# To make sure that we get the same results, we set a seed
set.seed(68159)

# Convert the trimmed DFM to a STM object
dfm2stm <- convert(mydfm.trim, to = "stm")

# Use this object to estimate the structural topic model
model.stm <- stm(
  # Define the documents
  documents = dfm2stm$documents,
  # Define the words in the corpus
  vocab = dfm2stm$vocab,
  # Define the number of topics
  K = topic.count,
  # The neat thing about STM is that you can use meta data to inform your model 
  # (here we use country and year and rely heavily on the vignette of STM)
  prevalence = ~ country + s(year),
  # Define the data set that contains content variables (remember, this is what is so great about STM!)
  data = dfm2stm$meta,
  # This defines the initialization method. "spectral" is the default and provides a deterministic
  # initialization based on Arora et al. 2014 (it is in particular recommended if the number of
  # documents is large)
  init.type = "Spectral"
)
## Beginning Spectral Initialization 
##   Calculating the gram matrix...
##   Finding anchor words...
##      .....
##   Recovering initialization...
##      ...............
## Initialization complete.
## ....................................................................................................
## Completed E-Step (2 seconds). 
## Completed M-Step. 
## Completing Iteration 1 (approx. per word bound = -6.836) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 2 (approx. per word bound = -6.814, relative change = 3.106e-03) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 3 (approx. per word bound = -6.807, relative change = 1.110e-03) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 4 (approx. per word bound = -6.803, relative change = 6.143e-04) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 5 (approx. per word bound = -6.800, relative change = 4.018e-04) 
## Topic 1: republ, right, democrat, cooper, respect 
##  Topic 2: global, chang, climat, sustain, island 
##  Topic 3: right, terror, region, one, war 
##  Topic 4: global, council, terror, region, right 
##  Topic 5: african, govern, africa, conflict, commit 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 6 (approx. per word bound = -6.798, relative change = 2.762e-04) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 7 (approx. per word bound = -6.797, relative change = 1.994e-04) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 8 (approx. per word bound = -6.796, relative change = 1.510e-04) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 9 (approx. per word bound = -6.795, relative change = 1.173e-04) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 10 (approx. per word bound = -6.794, relative change = 9.379e-05) 
## Topic 1: right, republ, democrat, respect, social 
##  Topic 2: global, chang, climat, sustain, island 
##  Topic 3: right, one, terror, war, palestinian 
##  Topic 4: global, council, region, right, terror 
##  Topic 5: african, govern, africa, conflict, session 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 11 (approx. per word bound = -6.794, relative change = 7.697e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 12 (approx. per word bound = -6.793, relative change = 6.401e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 13 (approx. per word bound = -6.793, relative change = 5.409e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 14 (approx. per word bound = -6.793, relative change = 4.636e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 15 (approx. per word bound = -6.792, relative change = 4.041e-05) 
## Topic 1: right, republ, social, respect, govern 
##  Topic 2: global, chang, climat, sustain, island 
##  Topic 3: right, one, war, terror, palestinian 
##  Topic 4: council, region, global, right, cooper 
##  Topic 5: african, govern, africa, conflict, session 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 16 (approx. per word bound = -6.792, relative change = 3.538e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 17 (approx. per word bound = -6.792, relative change = 3.155e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 18 (approx. per word bound = -6.792, relative change = 2.822e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 19 (approx. per word bound = -6.792, relative change = 2.551e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 20 (approx. per word bound = -6.791, relative change = 2.322e-05) 
## Topic 1: right, social, republ, govern, respect 
##  Topic 2: global, chang, climat, sustain, island 
##  Topic 3: right, one, war, terror, time 
##  Topic 4: region, council, global, right, cooper 
##  Topic 5: african, govern, africa, session, conflict 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 21 (approx. per word bound = -6.791, relative change = 2.114e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 22 (approx. per word bound = -6.791, relative change = 1.942e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 23 (approx. per word bound = -6.791, relative change = 1.799e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 24 (approx. per word bound = -6.791, relative change = 1.706e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 25 (approx. per word bound = -6.791, relative change = 1.585e-05) 
## Topic 1: right, social, govern, republ, respect 
##  Topic 2: global, chang, climat, sustain, island 
##  Topic 3: right, one, war, terror, time 
##  Topic 4: region, council, global, right, cooper 
##  Topic 5: african, govern, africa, session, conflict 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 26 (approx. per word bound = -6.791, relative change = 1.456e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 27 (approx. per word bound = -6.791, relative change = 1.344e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 28 (approx. per word bound = -6.790, relative change = 1.255e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 29 (approx. per word bound = -6.790, relative change = 1.169e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 30 (approx. per word bound = -6.790, relative change = 1.088e-05) 
## Topic 1: right, social, govern, respect, republ 
##  Topic 2: global, chang, climat, sustain, island 
##  Topic 3: right, one, war, terror, time 
##  Topic 4: region, council, right, global, cooper 
##  Topic 5: african, govern, africa, session, conflict 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Completing Iteration 31 (approx. per word bound = -6.790, relative change = 1.015e-05) 
## ....................................................................................................
## Completed E-Step (1 seconds). 
## Completed M-Step. 
## Model Converged

As mentioned during the talk, the structural topic model can also include meta data to estimate the topic. You include them using the argument prevalence. If you want to know more about it works, see this excellent vignette.

There are different ways to visualize the results. We’ll first go with base-plot which gives you a shared estimation of the topic shares.

plot(
  # Takes the STM object
  model.stm,
  # Define the type of plot
  type = "summary",
  # Define font size
  text.cex = 0.5,
  # Label the title
  main = "STM topic shares",
  # And the x-axis
  xlab = "Share estimation"
)

To get more out of it and to learn more about topic 4, we can use findThoughts to get the plain text that is associated with the topic:

findThoughts(
  # Your topic model
  model.stm,
  # The text data that you used 
  # to retrieve passages from
  texts = un_data$text,
  # Number of documents to be displayed here
  n = 1,
  # Topic number you are interested in
  topics = 4
)
## 
##  Topic 4: 
##       At the outset, allow me to express our sincere gratitude 
## for the honour of addressing the General Assembly from 
## this rostrum. I bring greetings from His Excellency 
## Mr. Gurbanguly Berdimuhamedov, President of 
## Turkmenistan, who wishes the General Assembly the 
## best of luck at this session. I congratulate Mr. John 
## William Ashe on his election as President of the General 
## Assembly at its sixty-eighth session and wish him all 
## the best in fulfilling his forthcoming tasks. I would also 
## like to thank Mr. Vuk Jeremi., President of the General 
## Assembly at its sixty-seventh session, for his skill and 
## effectiveness in that post.
## 
## Turkmenistan considers this session to be an 
## important phase in the process of consolidating the 
## efforts of the international community to strengthen 
## universal peace, stability and security by adopting 
## meaningful decisions on sustainable development and 
## to counter emerging challenges and threats. We believe 
## that strict adherence to the principles and norms of the 
## Charter of the United Nations is the main prerequisite 
## for ensuring long-term peace and strategic stability.
## 
## In that belief, Turkmenistan adheres to a steady 
## and resolute policy of peace, good-neighbourliness and 
## the active promotion of peacebuilding processes. As a 
## matter of principle, we reject the use of military force as 
## a tool of foreign policy and international relations. Our 
## country is convinced that solutions based on the use 
## of force are doomed to fail. They neither eliminate the 
## causes of conflicts nor create conditions for adequate 
## responses to the many issues that arise from military 
## action. Therefore, at the heart of Turkmenistan’s policies 
## is the will to resolve any situation by peaceful, political 
## and diplomatic ways and means, which it considers to 
## be the main legitimate resources available within the 
## United Nations. This approach is based on our common 
## goal to establish a world without conflict.
## 
## At the sixty-sixth session of the General Assembly, 
## Turkmenistan’s President launched an initiative aimed 
## at the adoption of a United Nations declaration on 
## prioritizing political and diplomatic ways and means 
## for the resolution of international challenges. Today, 
## the elaboration of such a document has become a top 
## priority. Turkmenistan therefore reaffirms its firm 
## desire to engage in a meaningful discussion on this 
## initiative with all interested Member States. We are 
## convinced that the adoption of such a declaration would 
## help to expand and strengthen the legal basis for the 
## work of the General Assembly, the Security Council 
## and other United Nations entities dealing with issues 
## relating to world peace, stability and security.
## 
## The challenging processes unfolding in today’s 
## world call for a responsible, thoughtful, effective 
## and efficient approach on the part of the United 
## Nations. That is also linked directly to the important 
## challenges of disarmament. By playing an active role 
## in the multilateral dialogue on disarmament issues, my 
## Government is demonstrating its firm commitment to 
## complying with the core international norms regulating 
## the disarmament process and the non-proliferation 
## of weapons of mass destruction through practical 
## action. Following this course of action and taking 
## into consideration the need to energize the discussion 
## and meaningful consideration of disarmament issues, 
## Turkmenistan proposes the convening in 2014 of a high-
## level international meeting on disarmament issues. 
## We are prepared to create all the necessary conditions 
## and to provide the appropriate infrastructure for this 
## meeting in our capital city.
## 
## Nowadays, problems related to strengthening 
## peace and stability and to ensuring the stability of 
## countries and nations are among the most important 
## topics in global politics. Their resolution will depend 
## primarily on the establishment and effective legal 
## and organizational operationalization of international 
## political cooperation. In this context, we advise the 
## General Assembly at this session to embark on the 
## consideration of issues relating to the improvement of 
## various forms of multilateral interaction that could serve 
## as a political platform for finding mutually acceptable 
## decisions on urgent regional and international policy 
## matters.
## 
## It should be noted in that regard that the 
## United Nations fulfils its purpose. For example, the 
## establishment of United Nations preventive diplomacy 
## centres in various regions of the world has become a 
## 
## 
## 
## highly effective form of joint work to strengthen security, 
## prevent conflicts and eliminate their underlying causes. 
## It is well known that the first such centre, the United 
## Nations Regional Centre for Preventive Diplomacy in 
## Central Asia, based in Ashgabat, opened in December 
## 2007. In our view, the experience of creating new 
## mechanisms and institutions aimed at forming a system 
## of international interaction at the global and regional 
## levels must and should be replicated by States Members 
## of the United Nations.
## 
## Taking into account the need to enhance the 
## effectiveness of inter-State contact at the regional 
## level, Turkmenistan has launched a forum of peace 
## and cooperation, aimed at establishing a standing 
## mechanism for political dialogue in Central Asia. 
## We believe that the forum will contribute to the 
## elaboration of consensus-based approaches to finding 
## solutions to the most important issues relating to the 
## present and future development of Central Asia and 
## its neighbouring regions. Moreover, the forum could 
## become the basis for the establishment of a consultative 
## council of the Heads of State of Central Asia. We are 
## convinced that the development of new formats for 
## political interaction among States within the region, 
## coupled with the effective functioning of United 
## Nations regional structures, will provide a reliable 
## foundation and stability for the entire architecture of 
## inter-State relations in Central Asia.
## 
## To a great extent, attaining the goals of 
## comprehensive and universal security will depend on 
## ensuring security in the sphere of energy. Furthermore, 
## the achievement of that goal is one of the most 
## important components of a stable world economy and 
## serves to protect it against distortions and disruptions. 
## In that connection, the development of an international 
## mechanism that provides for a set of guarantees for the 
## global energy supply is a task of paramount importance. 
## It is also necessary to underscore the importance of 
## the joint work and coordinated efforts of all Member 
## States aimed at developing and adopting consolidated 
## approaches to the solution of energy security issues.
## 
## The establishment by the United Nations of a new 
## universal international legal tool kit is a key element 
## of that process. It should, in our view, consist of the 
## following three major elements: a multilateral United 
## Nations document providing the legal basis for 
## relations in the area of the global supply of energy 
## resources; a corresponding United Nations structure 
## that would ensure the implementation of the provisions 
## of the aforementioned document; and an international 
## database designed for the collection and analysis of 
## data on the implementation of international obligations 
## assumed by the participating States.
## 
## It is common knowledge that on 17 May 2013 the 
## General Assembly adopted by consensus resolution 
## 67/263, submitted on the initiative of Turkmenistan’s 
## President, entitled “Reliable and stable transit of energy 
## and its role in ensuring sustainable development and 
## international cooperation”. The importance of that 
## document lies primarily in the fact that it forms the 
## basis for a global energy partnership that takes into 
## account the interests of producer States, transit States 
## and States that are consumers of energy resources.
## 
## In accordance with the letter and the spirit of that 
## resolution, our country proposes to Member States 
## the establishment, during the current session of the 
## Assembly, of an international group of experts for the 
## development of a new mechanism for energy security. 
## To that end, the Government of Turkmenistan proposes 
## to convene an international meeting of experts on 
## that topic in 2014. We are ready to engage in close 
## cooperation with all Member States and the United 
## Nations Secretariat with a view to organizing and 
## holding such a forum.
## 
## Currently the resolution of issues of security and 
## sustainable development depends largely on the level 
## of international cooperation in the important areas of 
## transport and communications. The geo-economic 
## potential of new transport and transit routes in the 
## world is enormously significant. Such routes involve 
## vast spaces and enormous human resources and 
## attract considerable investments. All of that creates 
## opportunities to transform the transport sector into 
## one of the most important factors in sustainable 
## development.
## 
## Turkmenistan is convinced that the twenty-first-
## century transportation architecture provides the 
## framework for a breakthrough in integration, in joining 
## the common efforts of regions and in the pooling of 
## resources and industrial and human potential. It is 
## our firm conviction that the future belongs to such 
## a combined system of transport communication, 
## involving major international and regional maritime, 
## road, railroad and air hubs, their optimal integration 
## and the use of their specific advantages.
## 
## The practical implementation of that idea 
## became the subject of a high-level event on modality, 
## 
## 
## 
## interconnectivity and the post-2015 development 
## programme, which was held in New York on 
## 26 September. It was organized by the Government of 
## Turkmenistan and the International Road Transport 
## Union. The event focused on the search for effective 
## solutions relating to the establishment of modern, 
## diversified and safe transport infrastructure throughout 
## the world.
## 
## We consider it necessary to continue the multilateral 
## dialogue on transport issues that was initiated during 
## the current session of the General Assembly. In that 
## connection, Turkmenistan would like to submit a 
## proposal to host in 2014 in Ashgabat an international 
## conference on the role of transport and transit corridors 
## in ensuring international cooperation, stability and 
## sustainable development.
## 
## With regard to the achievement of the sustainable 
## development goals, we believe that the greatest attention 
## should be focused on promoting the economic interests 
## of States, while maintaining an appropriate ecological 
## balance and preventing harm to the environment. That, 
## in turn, implies the use of cutting-edge environmental 
## technologies and the development of innovative 
## solutions for the preservation of nature. Preserving 
## the significant environmental component of the global 
## economic space has therefore become an integral part 
## of its effectiveness.
## 
## We highly value the efforts undertaken by the 
## Secretary-General, as well as the successive actions 
## of the international community at the United Nations 
## Climate Change Conferences in Copenhagen and 
## Cancun and during the seventeenth Conference of the 
## Parties to the United Nations Framework Convention 
## on Climate Change, held in Durban, which have 
## gradually laid the foundations for the development 
## of comprehensive decisions at the United Nations 
## Conference on Sustainable Development.
## 
## We look forward to the continuation of a constructive 
## international dialogue on that topic during the sixty-
## eighth session of the Assembly. We are convinced that 
## it is necessary to combine our efforts in that area at 
## the international, regional and national levels, and to 
## effectively coordinate the efforts of States with those of 
## the United Nations.
## 
## Taking into account the numerous aspects of the 
## climate change issue, Turkmenistan wishes to state at the 
## current session of the General Assembly that it stands 
## ready to make its contribution to the strengthening of the 
## role of multilateral international mechanisms aimed at 
## preventing the negative consequences of global climate 
## change. In particular, we refer to the need for enhancing 
## the implementation of the provisions of the United 
## Nations Convention to Combat Desertification. In that 
## connection, we are prepared to host in Turkmenistan 
## the Conference of the Parties to the United Nations 
## Convention to Combat Desertification in 2014.
## 
## Furthermore, our country would like to launch an 
## initiative aimed at the establishment of a specialized 
## entity, a subregional centre on technologies relating 
## to climate change in Central Asia and the Caspian Sea 
## basin. We believe that such an entity would help the 
## countries of our regions to substantially strengthen 
## their interaction in the sphere of environmental security 
## and would contribute to the effective coordination of 
## interregional efforts in that field.
## 
## The challenges confronting the community 
## of nations in the area of security and sustainable 
## development cannot be resolved unless we find a 
## solution to the humanitarian issues at the international 
## level. In particular, we are referring to the serious global 
## problem of the fate of refugees and stateless persons. 
## As a permanent member of the Executive Committee of 
## the High Commissioner’s Programme of the Office of 
## the United Nations High Commissioner for Refugees, 
## Turkmenistan has accumulated valuable experience in 
## resolving the issues facing people who were forced to 
## leave their home countries. Together with the Office of 
## the United Nations High Commissioner for Refugees, 
## we propose that all interested parties become familiar 
## with Turkmenistan’s practical work in granting 
## citizenship to refugees and stateless persons.
## 
## In that connection, it would be advisable to work 
## jointly with United Nations humanitarian agencies to 
## develop an appropriate social programme. Moreover, 
## taking into account the outcomes of the International 
## Ministerial Conference of the Organization of Islamic 
## Cooperation on Refugees in the Muslim World, held 
## in Ashgabat in May 2012, we consider it necessary to 
## develop long-term solutions to such issues, on the basis 
## of generally recognized norms of international law. 
## With a view to discussing those issues, we are ready 
## to host in Turkmenistan in 2014 a high-level event in 
## cooperation with the Office of the United Nations High 
## Commissioner for Refugees.
## 
## Today, as Member States actively discuss the 
## role and place of the United Nations in international 
## 
## 
## 
## relations, Turkmenistan declares that constructive and 
## multilateral cooperation with the United Nations is 
## the top priority of its foreign policy strategy. In that 
## connection, we believe that it is precisely the United 
## Nations that is the main and universal international 
## Organization, which adopts decisions concerning 
## the most important issues of global development and 
## comprehensive peace and security. Since its inception, 
## the United Nations has demonstrated its role as the 
## foundation of the entire system of international stability, 
## through mechanisms to ensure justice and to resolve 
## the most complex international problems.
## 
## Similarly, we share the opinion of the Organization 
## today that the issue of providing it with fresh impetus 
## is increasingly relevant, in view of the rapidly changing 
## realities of the modern world. Therefore, Turkmenistan 
## supports a strengthened and expanded role for the 
## United Nations at the global level.
## 
## We are firmly convinced that international 
## law and the provisions of the Charter of the United 
## Nations — based on peace, equal rights and respect for 
## nations, their rights and sovereignty — must remain 
## the foundation of the world order in the twenty-first 
## century.

You can also combine stm with tidytext to generate ggplot2 objects. We follow Julia Silge’s outline here. The plot shows the probabilities with which different terms are associated with the topics.

# Load tidytext
library(tidytext) # Text Mining using 'dplyr', 'ggplot2', and Other Tidy Tools

# Turn the STM object into a data frame. This is necessary so that we can work with it.
td_beta <- tidy(model.stm)

td_beta %>%
  # Group by topic
  group_by(topic) %>%
  # Take the top 10 based on beta
  top_n(10, beta) %>%
  # Ungroup
  ungroup() %>%
  # Generate the variables topic and term
  dplyr::mutate(topic = paste0("Topic ", topic),
                term = reorder_within(term, beta, topic)) %>%
  # And plot it
  ggplot() +
  # Using a bar plot with the terms on the x-axis, the beta on the y-axis, filled by topic
  geom_col(aes(x = term, y = beta, fill = as.factor(topic)),
           alpha = 0.8,
           show.legend = FALSE) +
  # Do a facet_wrap by topic
  facet_wrap(~ topic, scales = "free_y") +
  # And flip the plot
  coord_flip() +
  scale_x_reordered() +
  # Label the x-axis, y-axis, as well as title
  labs(
    y = expression(beta),
    title = "Highest word probabilities for each topic",
    subtitle = "Different words are associated with different topics"
  ) +
  # And finally define the colors
  scale_fill_manual(values = wes_palette("Darjeeling1"))

You could also visualize our results with a perspective plot that allows you to show how different terms are related with each topic. We again use a base plot here.

plot(
  # Access the STM object
  model.stm,
  # Select the type of plot
  type = "perspectives",
  # Select the topics
  topics = c(4, 5),
  # Define the title
  main = "Putting two different topics in perspective")

It is always tricky to find the right number of topics (your k). The stm package comes with a handy function called searchK that allows you to evaluate different topic models and to find your k. We can again plot it using the plot() function.

To further evaluate your topic models, have a look at stminsights, LDAvis (use stminsights::toLDAvis to convert your STM model), and oolong.

Here’s a short code snippet that shows how LDAvis works. Both stminsights and LDAvis call ShinyApps that allow you to interactively access your topic models.

library(LDAvis) # Interactive Visualization of Topic Models

toLDAvis(
  # The topic model
  mod = model.stm, 
  # The documents
  docs = dfm2stm$documents)

The ShinyApp looks like this:

Next steps

These are just the most basic supervised and unsupervised models in NLP that you can use but as you work more and more with textual data, you will see that there is so much more in the field of NLP including document similarity, text generation or even chat bots that you can create using your knowledge and the same simple steps that we have used here.


  1. University of Mannheim, ↩︎

LS0tCnRpdGxlOiAiR2V0dGluZyB0aGUgbW9zdCBvdXQgb2YgdGV4dCIKc3VidGl0bGU6ICJVc2luZyBzdXBlcnZpc2VkIGFuZCB1bnN1cGVydmlzZWQgYXBwcm9hY2hlcyBpbiBOTFAiCmRhdGU6ICIyMDIxLTAxLTMwIgphdXRob3I6CiAgLSBDb3NpbWEgTWV5ZXJeW1VuaXZlcnNpdHkgb2YgTWFubmhlaW0sIGNvc2ltYS5tZXllckB1bmktbWFubmhlaW0uZGVdCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2NfZGVwdGg6IDUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKClRoaXMgY29kZSBpcyBhY2NvbXBhbnlpbmcgbWF0ZXJpYWwgZm9yIFt0aGlzIHRhbGtdKGh0dHBzOi8vY29zaW1hbWV5ZXIucmJpbmQuaW8vc2xpZGVzL25scC1ybGFkaWVzLXR1bmlzL3RhbGsjMSkgZm9yIFItTGFkaWVzIFR1bmlzLiAKClRleHQgZGF0YSBwcm92aWRlcyBhbiBvYXNpcyBvZiBpbmZvcm1hdGlvbiBmb3IgYm90aCByZXNlYXJjaGVycyBhbmQgbm9uLXJlc2VhcmNoZXJzIGFsaWtlIHRvIGV4cGxvcmUuIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyAoTkxQKSBtZXRob2RzIGhlbHAgbWFrZSBzZW5zZSBvZiB0aGlzIGRpZmZpY3VsdCBkYXRhIHR5cGUuIFRoZSB0YWxrIGFuZCBjb2RlIGdpdmUgeW91IGEgc21vb3RoIGludHJvZHVjdGlvbiB0byB0aGUgW3F1YW50ZWRhIHBhY2thZ2VdKGh0dHBzOi8vcXVhbnRlZGEuaW8vKS4gSSB3aWxsIGFsc28gc2hvd2Nhc2UgaG93IHRvIHF1aWNrbHkgdmlzdWFsaXplIHlvdXIgdGV4dCBkYXRhIGFuZCBjb3ZlciBib3RoIHN1cGVydmlzZWQgYW5kIHVuc3VwZXJ2aXNlZCBhcHByb2FjaGVzIGluIE5MUC4gQXMgcGFydCBvZiB0aGUgY29kZSBkZW1vLCB3ZSB3aWxsIHVzZSB0ZXh0IFtkYXRhIGZyb20gdGhlIFVOXShodHRwczovL2RvaS5vcmcvMTAuNzkxMC9EVk4vMFRKWDhZKSBhcyBhIHdvcmtpbmcgZXhhbXBsZSB0byBnaXZlIHlvdSBmaXJzdCBpbnNpZ2h0cyBpbnRvIHRoZSBzdHJ1Y3R1cmUgb2YgdGV4dCBkYXRhIGFuZCBob3cgdG8gd29yayB3aXRoIGl0LgoKIyMgS25vd2luZyB0ZXJtcyBhbmQgY29uY2VwdHMKCkJlZm9yZSB3ZSBnZXQgc3RhcnRlZCwgeW91IG1pZ2h0IHdhbnQgdG8gcmV2aXNlIHRoZSB0ZXJtcyBhbmQgY29uY2VwdHMgb25jZSBtb3JlIChvciB1c2UgaXQgYXMgYSBnbG9zc2FyeSB0byBsb29rIHRoZW0gdXAgd2hlbiBuZWVkZWQpLiAKCiFbXSguLi9maWd1cmVzL3Rlcm1zLnBuZykKCiMjIFByZXBhcmF0aW9uCgpJbiBhIGZpcnN0IHN0ZXAsIHdlIGxvYWQgdGhlIHBhY2thZ2VzLgoKYGBge3IgcGFja2FnZSwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9VFJVRSwgZXZhbD1UUlVFLCBlY2hvPVRSVUV9CiMjIFBhY2thZ2VzCnBrZ3MgPC0gYygKICAia25pdHIiLCAgICAgICAjIEEgR2VuZXJhbC1QdXJwb3NlIFBhY2thZ2UgZm9yIER5bmFtaWMgUmVwb3J0IEdlbmVyYXRpb24gaW4gUgogICJ0aWR5dmVyc2UiLCAgICMgRWFzaWx5IEluc3RhbGwgYW5kIExvYWQgdGhlICdUaWR5dmVyc2UnCiAgInF1YW50ZWRhIiwgICAgIyBRdWFudGl0YXRpdmUgQW5hbHlzaXMgb2YgVGV4dHVhbCBEYXRhCiAgInN0bSIsICAgICAgICAgIyBFc3RpbWF0aW9uIG9mIHRoZSBTdHJ1Y3R1cmFsIFRvcGljIE1vZGVsCiAgInN0bWluc2lnaHRzIiwgIyBBICdTaGlueScgQXBwbGljYXRpb24gZm9yIEluc3BlY3RpbmcgU3RydWN0dXJhbCBUb3BpYyBNb2RlbHMKICAiTERBdmlzIiwgICAgICAjIEludGVyYWN0aXZlIFZpc3VhbGl6YXRpb24gb2YgVG9waWMgTW9kZWxzCiAgInNlcnZyIiwgICAgICAgIyBBIFNpbXBsZSBIVFRQIFNlcnZlciB0byBTZXJ2ZSBTdGF0aWMgRmlsZXMgb3IgRHluYW1pYyBEb2N1bWVudHMKICAidG9waWNtb2RlbHMiLCAjIFRvcGljIE1vZGVscwogICJrYWJsZUV4dHJhIiwgICMgQ29uc3RydWN0IENvbXBsZXggVGFibGUgd2l0aCAna2FibGUnIGFuZCBQaXBlIFN5bnRheAogICJyZWFkdGV4dCIsICAgICMgSW1wb3J0IGFuZCBIYW5kbGluZyBmb3IgUGxhaW4gYW5kIEZvcm1hdHRlZCBUZXh0IEZpbGVzCiAgIm1hZ3JpdHRyIiwgICAgIyBBIEZvcndhcmQtUGlwZSBPcGVyYXRvciBmb3IgUgogICJvdmVydmlld1IiLCAgICMgRWFzaWx5IEV4dHJhY3RpbmcgSW5mb3JtYXRpb24gQWJvdXQgWW91ciBEYXRhCiAgImNvdW50cnljb2RlIiwgIyBDb252ZXJ0IENvdW50cnkgTmFtZXMgYW5kIENvdW50cnkgQ29kZXMKICAid2VzYW5kZXJzb24iLCAjIEEgV2VzIEFuZGVyc29uIFBhbGV0dGUgR2VuZXJhdG9yCiAgInRpZHl0ZXh0IiAgICAgIyBUZXh0IE1pbmluZyB1c2luZyAnZHBseXInLCAnZ2dwbG90MicsIGFuZCBPdGhlciBUaWR5IFRvb2xzCikKCiMjIEluc3RhbGwgdW5pbnN0YWxsZWQgcGFja2FnZXMKbGFwcGx5KHBrZ3NbIShwa2dzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkpXSwgaW5zdGFsbC5wYWNrYWdlcykKCiMjIExvYWQgYWxsIHBhY2thZ2VzIHRvIGxpYnJhcnkKbGFwcGx5KHBrZ3MsIGxpYnJhcnksIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKCiMjIFNldCBhIHRoZW1lIGZvciB0aGUgcGxvdHM6CnRoZW1lX3NldCgKICB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZSgKICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKQogICkKKQpgYGAKCiMjIEhvdyB0byBkZWFsIHdpdGggdGV4dCBkYXRhPwoKUmVtZW1iZXIsIHdlIHdpbGwgdXNlIFtgcXVhbnRlZGFgXShodHRwczovL3F1YW50ZWRhLmlvLykgaW4gdGhpcyB3b3Jrc2hvcC4gU3RhcnRpbmcgd2l0aCBgcXVhbnRlZGFgIGJhc2ljYWxseSB3b3JrcyBpbiAzLTQgZGlmZmVyZW50IHN0ZXBzOgoKMS4gKipJbXBvcnQqKiB0aGUgZGF0YSAKCjIuIEJ1aWxkIGEgKipjb3JwdXMqKgoKMy4gKipQcmUtcHJvY2VzcyB5b3VyIGRhdGEqKgoKNC4gQ2FsY3VsYXRlIGEgKipkb2N1bWVudC1mZWF0dXJlIG1hdHJpeCoqIChERk0pCgpBbmQgd2Ugd2lsbCB3YWxrIHRocm91Z2ggZWFjaCBvZiB0aGVtIG5vdy4KCjEuICoqSW1wb3J0KiogdGhlIGRhdGEgCgpgYGB7ciBkYXRhLWxvYWQsIGVjaG89VFJVRX0KIyBMb2FkIHBhY2thZ2VzCmxpYnJhcnkocXVhbnRlZGEpICMgRm9yIE5MUCAoUXVhbnRpdGF0aXZlIEFuYWx5c2lzIG9mIFRleHR1YWwgRGF0YSkKCmxvYWQoIi4uL2RhdGEvVU4tZGF0YS5SRGF0YSIpCmBgYAoKQmVmb3JlIHdlIHByb2NlZWQsIHdlIGNoZWNrIHRoZSBkYXRhIGFuZCB0aGUgdGltZSBhbmQgZ2VvZ3JhcGhpY2FsIGNvdmVyYWdlIG9mIHRoZSBkYXRhOgoKYGBge3IsIGRhdGEtY2hlY2sgMSwgZWNobz1UUlVFfQpoZWFkKHVuX2RhdGEpCmBgYAoKV2hhdCB3ZSBzZWUgaXMgdGhhdCBvdXIgZGF0YSBzZXQgaGFzIGZpdmUgdmFyaWFibGVzIGNvbnRhaW5pbmcgaW5mb3JtYXRpb24gb24gYGRvY19pZGAsIGB0ZXh0YCwgYGNvdW50cnlgLCBgc2Vzc2lvbmAsIGFuZCBgeWVhcmAuIFRoZSB0ZXh0IG9mIHRoZSBzcGVlY2ggaXMgc3RvcmVkIGluIHRoZSB2YXJpYWJsZSBgdGV4dGAsIHRoZSBgZG9jX2lkYCBnaXZlcyB1cyBhIHVuaXF1ZSBpZCBmb3IgZWFjaCBkb2N1bWVudC4KCk5vIHdlIHR1cm4gdG8gdGhlIHRpbWUgYW5kIGdlb2dyYXBoaWNhbCBjb3ZlcmFnZToKCmBgYHtyLCBkYXRhLWNoZWNrIDIsIGVjaG89VFJVRX0KdW5fZGF0YSAlPiUKICBvdmVydmlld190YWIoaWQgPSBjb3VudHJ5LCB0aW1lID0geWVhcikKYGBgCgpUbyBkZWNyZWFzZSB0aGUgc2FtcGxlIHNpemUsIEkgcmVkdWNlZCB0aGUgZGF0YSBzZXQgYW5kIGl0IG5vdyBvbmx5IGNhcHR1cmVzIGRhdGEgZnJvbSAyMDAwIG9ud2FyZHMuCgoyLiBCdWlsZCBhICoqY29ycHVzKioKCmBgYHtyIGNvcnB1cywgZWNobz1UUlVFfQojIEJ1aWxkIHRoZSBjb3JwdXMKbXljb3JwdXMgPC0gY29ycHVzKHVuX2RhdGEpCmBgYAoKSWYgeW91J3JlIGN1cmlvdXMgaG93IGEgYGNvcnB1c2AgbG9va3MgbGlrZSwgaGVyZSB5b3UgZ286CmBgYHtyIGNvcnB1c192aWV3LCBlY2hvPVRSVUV9CmhlYWQobXljb3JwdXMsIDIpCmBgYAoKSXQgbG9va3MgbGlrZSBqdXN0IHRoZSB0ZXh0LCByaWdodD8gQnV0IGEgY29ycHVzIGlzIHNvIG11Y2ggbW9yZS4gSXQgYWxzbyBoYXMgYGRvY3ZhcnNgIC0gYmFzaWNhbGx5IHZhcmlhYmxlcyBkZXNjcmliaW5nIGl0LiAKCmBgYHtyLCBjb3JwdXNfdmlldzIsIGVjaG89VFJVRX0KaGVhZChkb2N2YXJzKG15Y29ycHVzKSkKYGBgCkFuZCB3ZSBjYW4gZXZlbiBhZGQgYSBgZG9jdmFyYDogCmBgYHtyIGNvcnB1c19kb2N2YXJzLCBlY2hvPVRSVUV9CiMgQXNzaWducyBhIHVuaXF1ZSBpZGVudGlmaWVyIHRvIGVhY2ggdGV4dApkb2N2YXJzKG15Y29ycHVzLCAiVGV4dG5vIikgPC0KICBzcHJpbnRmKCIlMDJkIiwgMTpuZG9jKG15Y29ycHVzKSkgCmBgYAoKQW5kIGFnYWluIGhhdmUgYSBsb29rIGF0IGl0OgpgYGB7ciwgY29ycHVzX3ZpZXczLCBlY2hvPVRSVUV9CmhlYWQoZG9jdmFycyhteWNvcnB1cykpCmBgYAozLiBUZXh0ICoqcHJlLXByb2Nlc3NpbmcqKiAobm90IGFsbCBzdGVwcyBhcmUgYWx3YXlzIHJlcXVpcmVkKS4gVGhpcyB3YXksIHdlIGdldCB0aGUgdG9rZW5zLgoKYGBge3IgcnRva2VucywgZWNobz1UUlVFfQojIENyZWF0ZSB0b2tlbnMKdG9rZW4gPC0KICB0b2tlbnMoCiAgICAjIFRha2VzIHRoZSBjb3JwdXMKICAgIG15Y29ycHVzLAogICAgIyBSZW1vdmUgbnVtYmVycwogICAgcmVtb3ZlX251bWJlcnMgPSBUUlVFLAogICAgIyBSZW1vdmUgcHVuY3R1YXRpb24KICAgIHJlbW92ZV9wdW5jdCA9IFRSVUUsCiAgICAjIFJlbW92ZSBzeW1ib2xzCiAgICByZW1vdmVfc3ltYm9scyA9IFRSVUUsCiAgICAjIFJlbW92ZSBVUkwKICAgIHJlbW92ZV91cmwgPSBUUlVFLAogICAgIyBTcGxpdCB1cCBoeXBoZW5hdGVkIHdvcmRzCiAgICBzcGxpdF9oeXBoZW5zID0gVFJVRSwKICAgICMgQW5kIGluY2x1ZGUgdGhlIGRvYyB2YXJzICh3ZSdsbCBuZWVkIHRoZW0gbGF0ZXIpCiAgICBpbmNsdWRlX2RvY3ZhcnMgPSBUUlVFCiAgKQpgYGAKCkJ1dCBob3cgZG8gYHRva2Vuc2AgbG9vayBsaWtlPyBXZSBjYW4gZWFzaWx5IGNoZWNrIGl0OgoKYGBge3IgdG9rZW5zX3ZpZXcsIGVjaG89VFJVRX0KaGVhZCh0b2tlbiwgMikKYGBgCgpTaW5jZSB0aGUgZGF0YSBpcyBnZW5lcmF0ZWQgd2l0aCBPQ1IsIHdlIG5lZWQgdG8gYWRkaXRpb25hbGx5IGNsZWFuIHRoaXMuIFdlIGRvIHRoaXMgdXNpbmcgdGhlIGZvbGxvd2luZyBjb21tYW5kOgoKYGBge3Igb2NyLWNsZWFuaW5nLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgZWNobz1UUlVFfQojIENsZWFuIHRva2VucyBjcmVhdGVkIGJ5IE9DUgp0b2tlbl91bmdkIDwtIHRva2Vuc19zZWxlY3QoCiAgdG9rZW4sCiAgYygiW1xcZC1dIiwgIltbOnB1bmN0Ol1dIiwgIl4uezEsMn0kIiksCiAgc2VsZWN0aW9uID0gInJlbW92ZSIsCiAgdmFsdWV0eXBlID0gInJlZ2V4IiwKICB2ZXJib3NlID0gVFJVRQopCmBgYAoKV2UgY2FuIG5vdyBzZWUgaG93IHRoaXMgY2hhbmdlZCBvdXIgdG9rZW5zOiAKYGBge3IgdG9rZW5zX3ZpZXcyLCBlY2hvPVRSVUV9CmhlYWQodG9rZW5fdW5nZCwgMikKYGBgCgoKNC4gQ2FsY3VsYXRlIGEgKipkb2N1bWVudC1mZWF0dXJlIG1hdHJpeCoqIChERk0pCgpIZXJlLCB3ZSBhbHNvIHN0ZW0sIHJlbW92ZSBzdG9wIHdvcmRzIGFuZCBsb3dlciBjYXNlIHdvcmRzLgoKYGBge3IgZGZtLCBlY2hvPVRSVUV9Cm15ZGZtIDwtIGRmbSgKICAjIFRha2UgdGhlIHRva2VuIG9iamVjdAogIHRva2VuX3VuZ2QsCiAgIyBMb3dlciB0aGUgd29yZHMKICB0b2xvd2VyID0gVFJVRSwKICAjIEdldCB0aGUgc3RlbSBvZiB0aGUgd29yZHMKICBzdGVtID0gVFJVRSwKICAjIFJlbW92ZSBzdG9wIHdvcmRzCiAgcmVtb3ZlID0gc3RvcHdvcmRzKCJlbmdsaXNoIikKKQpgYGAKCkhhdmUgYSBsb29rIGF0IHRoZSBERk06CgpgYGB7ciBoZWFkLWRmbS1ub3JtYWwsIGVjaG89VFJVRX0KaGVhZChteWRmbSwgMikKYGBgCgpUcmltIGRhdGE6IHJlbW92ZSBhbGwgdGhlIHdvcmRzIHRoYXQgYXBwZWFyIGxlc3MgdGhhbiA3LjUlIG9mIHRoZSB0aW1lIGFuZCBtb3JlIHRoYW4gOTAlIG9mIHRoZSB0aW1lCgpgYGB7ciBkZm0tdHJpbSwgZWNobz1UUlVFfQpteWRmbS50cmltIDwtCiAgZGZtX3RyaW0oCiAgICBteWRmbSwKICAgIG1pbl9kb2NmcmVxID0gMC4wNzUsCiAgICAjIG1pbiA3LjUlCiAgICBtYXhfZG9jZnJlcSA9IDAuOTAsCiAgICAjICBtYXggOTAlCiAgICBkb2NmcmVxX3R5cGUgPSAicHJvcCIKICApIApgYGAKCkFuZCBjaGVjayBob3cgeW91ciB0cmltbWVkIGBERk1gIGxvb2tzIGxpa2U6CmBgYHtyLCBkZm0tdHJpbTIsIGVjaG89VFJVRX0KaGVhZChteWRmbS50cmltLCAyKQpgYGAKCiMjIFZpc3VhbGl6aW5nIHlvdXIgZGF0YQoKIyMjIyBXb3JkIGNsb3VkcwoKVG8gZ2V0IGEgZmlyc3QgYW5kIGVhc3kgdmlzdWFsaXphdGlvbiwgd2UgdXNlIHdvcmQgY2xvdWRzOgoKYGBge3Igd29yZC1jbG91ZCwgZWNobz1UUlVFfQpxdWFudGVkYTo6dGV4dHBsb3Rfd29yZGNsb3VkKAogICMgTG9hZCB0aGUgREZNIG9iamVjdAogIG15ZGZtLAogICMgRGVmaW5lIHRoZSBtaW5pbXVtIG51bWJlciB0aGUgd29yZHMgaGF2ZSB0byBvY2N1cgogIG1pbl9jb3VudCA9IDMsCiAgIyBEZWZpbmUgdGhlIG1heGltdW0gbnVtYmVyIHRoZSB3b3JkcyBjYW4gb2NjdXIKICBtYXhfd29yZHMgPSA1MDAsCiAgIyBEZWZpbmUgYSBjb2xvcgogIGNvbG9yID0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmcxIikKKQpgYGAKCldvcmQgY2xvdWRzIGFyZSBhbiBpbGx1c3RyYXRpdmUgd2F5IHRvIHNob3cgd2hhdCB5b3UgaGF2ZSBpbiB5b3VyIGRhdGEuIFlvdSBjYW4gYWxzbyB1c2Ugd29yZCBjbG91ZHMgYmV5b25kIE5MUCBhbmFseXNpcyBwdXJwb3NlcywgZS5nLiwgb24gYSBbd2Vic2l0ZV0oaHR0cHM6Ly93d3cudW5pLW1hbm5oZWltLmRlL2NvbmZsaWN0LWR5bmFtaWNzL3Bhc3Qtd29ya3Nob3BzL3dvcmtzaG9wLTIwMjAvKS4KCiMjIyMgRnJlcXVlbmN5IHBsb3QKClRvIHZpc3VhbGl6ZSB0aGUgZnJlcXVlbmN5IG9mIHRoZSB0b3AgMzAgZmVhdHVyZXMsIHdlIHVzZSBhIGxvbGxpcG9wIHBsb3Q6CgpgYGB7ciBmcmVxdWVuY3ktcGxvdCwgZWNobz1UUlVFfQojIEluc3BpcmVkIGhlcmU6IGh0dHBzOi8vYml0Lmx5LzM3TUNFSGcKCiMgR2V0IHRoZSAzMCB0b3AgZmVhdHVyZXMgZnJvbSB0aGUgREZNCmZyZXFfZmVhdHVyZSA8LSB0b3BmZWF0dXJlcyhteWRmbSwgMzApCgojIENyZWF0ZSBhIGRhdGEuZnJhbWUgZm9yIGdncGxvdApkYXRhIDwtIGRhdGEuZnJhbWUobGlzdCgKICB0ZXJtID0gbmFtZXMoZnJlcV9mZWF0dXJlKSwKICBmcmVxdWVuY3kgPSB1bm5hbWUoZnJlcV9mZWF0dXJlKQopKQoKIyBQbG90IHRoZSBwbG90CmRhdGEgJT4lCiAgIyBDYWxsIGdncGxvdAogIGdncGxvdCgpICsKICAjIEFkZCBnZW9tX3NlZ21lbnQgKHRoaXMgd2lsbCBnaXZlIHVzIHRoZSBsaW5lcyBvZiB0aGUgbG9sbGlwb3BzKQogIGdlb21fc2VnbWVudChhZXMoCiAgICB4ID0gcmVvcmRlcih0ZXJtLCBmcmVxdWVuY3kpLAogICAgeGVuZCA9IHJlb3JkZXIodGVybSwgZnJlcXVlbmN5KSwKICAgIHkgPSAwLAogICAgeWVuZCA9IGZyZXF1ZW5jeQogICksIGNvbG9yID0gImdyZXkiKSArCiAgIyBDYWxsIGEgcG9pbnQgcGxvdCB3aXRoIHRoZSB0ZXJtcyBvbiB0aGUgeC1heGlzIGFuZCB0aGUgZnJlcXVlbmN5IG9uIHRoZSB5LWF4aXMKICBnZW9tX3BvaW50KGFlcyh4ID0gcmVvcmRlcih0ZXJtLCBmcmVxdWVuY3kpLCB5ID0gZnJlcXVlbmN5KSkgKwogICMgRmxpcCB0aGUgcGxvdAogIGNvb3JkX2ZsaXAoKSArCiAgIyBBZGQgbGFiZWxzIGZvciB0aGUgYXhlcwogIHhsYWIoIiIpICsKICB5bGFiKCJBYnNvbHV0ZSBmcmVxdWVuY3kgb2YgdGhlIGZlYXR1cmVzIikKYGBgCgojIyBLbm93biBjYXRlZ29yaWVzCgojIyMjIERpY3Rpb25hcnkgYXBwcm9hY2gKCldlIHVzZSB0aGUgW0xleGlDb2RlciBQb2xpY3kgQWdlbmRhXShodHRwczovL3d3dy5jb21wYXJhdGl2ZWFnZW5kYXMubmV0KSBkaWN0aW9uYXJ5LiBJdCBbY2FwdHVyZXMgbWFqb3IgdG9waWNzIGZyb20gdGhlIGNvbXBhcmF0aXZlIFBvbGljeSBBZ2VuZGEgcHJvamVjdCBhbmQgaXMgY3VycmVudGx5IGF2YWlsYWJsZSBpbiBEdXRjaCBhbmQgRW5nbGlzaC5dKGh0dHBzOi8vd3d3Lm16ZXMudW5pLW1hbm5oZWltLmRlL3NvY2lhbHNjaWVuY2VkYXRhbGFiL2FydGljbGUvYWR2YW5jaW5nLXRleHQtbWluaW5nLykKCmBgYHtyIGRpYy10b3BpYywgZWNobz1UUlVFfQojIExvYWQgdGhlIGRpY3Rpb25hcnkgd2l0aCBxdWFudGVkYSdzIGJ1aWx0LWluIGZ1bmN0aW9uCmRpY3QgPC0gZGljdGlvbmFyeShmaWxlID0gIi4uL2RhdGEvcG9saWN5X2FnZW5kYXNfZW5nbGlzaC5sY2QiKQpgYGAKClVzaW5nIHRoaXMgZGljdGlvbmFyeSwgd2Ugbm93IGdlbmVyYXRlIG91ciBERk06CgpgYGB7ciBkZm0tdG9waWMsIGVjaG89VFJVRX0KIyBHZW5lcmF0ZSB0aGUgREZNLi4uCm15ZGZtLnVuIDwtIGRmbShteWRmbS50cmltLCAKICAgICAgICAgICAgICAgICMgQmFzZWQgb24gY291bnRyeQogICAgICAgICAgICAgICAgZ3JvdXBzID0gImNvdW50cnkiLAogICAgICAgICAgICAgICAgIyBBbmQgdGhlIHByZXZpb3VzbHkgbG9hZGVkIGRpY3Rpb25hcnkKICAgICAgICAgICAgICAgIGRpY3Rpb25hcnkgPSBkaWN0KQpgYGAKCkhhdmUgYSBsb29rIGF0IHRoZSBuZXcgREZNOgoKYGBge3IgaGVhZC10b3BpYywgZWNobz1UUlVFfQpoZWFkKG15ZGZtLnVuKQpgYGAKCkJlZm9yZSB3ZSBjYW4gdHVybiB0byB0aGUgcGxvdHRpbmcsIHdlIG5lZWQgdG8gd3JhbmdsZSB0aGUgZGF0YSBhIGJpdCB0byBicmluZyBpdCBpbiB0aGUgcmlnaHQgb3JkZXIuIFRoZXNlIGFyZSBiYXNpYyB0aWR5dmVyc2UgY29tbWFuZHMuCgpgYGB7ciB0b3BpYy13cmFuZ2xpbmcsIGVjaG89VFJVRX0KdW4udG9waWNzLnBhIDwtIAogICMgQ29udmVydCB0aGUgREZNIHRvIGEgZGF0YSBmcmFtZQogIGNvbnZlcnQobXlkZm0udW4sICJkYXRhLmZyYW1lIikgJT4lCiAgIyBSZW5hbWUgdGhlIGRvY19pZCB0byBjb3VudHJ5CiAgZHBseXI6OnJlbmFtZShjb3VudHJ5ID0gZG9jX2lkKSAlPiUKICAjIFNlbGVjdCByZWxldmFudCB2YXJpYWJsZXMKICBkcGx5cjo6c2VsZWN0KGNvdW50cnksIG1hY3JvZWNvbm9taWNzLCBpbnRsX2FmZmFpcnMsIGRlZmVuY2UpICU+JQogICMgQnJpbmcgdGhlIGRhdGEgc2V0IGluIGEgZGlmZmVyZW50IG9yZGVyCiAgdGlkeXI6OnBpdm90X2xvbmdlcihtYWNyb2Vjb25vbWljczpkZWZlbmNlLCBuYW1lc190byA9ICJ0b3BpYyIsIHZhbHVlc190byA9ICJzaGFyZSIpICU+JQogICMgR3JvdXAgYnkgY291bnRyeQogIGdyb3VwX2J5KGNvdW50cnkpICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICAjIEdlbmVyYXRlIHRoZSByZWxhdGl2ZSBzaGFyZSBvZiB0b3BpY3MKICAgIHNoYXJlID0gc2hhcmUgLyBzdW0oc2hhcmUpLAogICAgIyBNYWtlIHRvcGljIGEgZmFjdG9yCiAgICB0b3BpYyA9IGhhdmVuOjphc19mYWN0b3IodG9waWMpKQpgYGAKCkJhc2VkIG9uIHRoaXMgZGF0YSBzZXQsIHdlIG5vdyBnZW5lcmF0ZSB0aGUgcGxvdC4KCmBgYHtyIHBsb3QtdG9waWMsIGVjaG89VFJVRX0KIyBHZW5lcmF0ZSB0aGUgcGxvdAp1bi50b3BpY3MucGEgJT4lCiAgIyBXZSBoYXZlIGNvdW50cnkgb24gdGhlIHgtYXhpcyBhbmQgdGhlIHNoYXJlIG9uIHRoZSB5LWF4aXMsIHdlIGNvbG9yIGFuZCBmaWxsIGJ5IHRvcGljCiAgZ2dwbG90KGFlcyh4ID0gY291bnRyeSwgeSA9IHNoYXJlLCBjb2xvdXIgPSB0b3BpYywgZmlsbCA9IHRvcGljKSkgKwogICMgQ2FsbCB0aGUgYGdlb21fYmFyYAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgIyBEZWZpbmUgdGhlIGZpbGwgY29sb3JzIGFuZCB0aGUgbGFiZWxzIGluIHRoZSBsZWdlbmQKICBzY2FsZV9maWxsX21hbnVhbCgKICAgIHZhbHVlcyA9IHdlc19wYWxldHRlKCJEYXJqZWVsaW5nMSIpLAogICAgbGFiZWxzID0gYygiTWFjcm8tZWNvbm9taWMiLCAiSW50ZXJuYXRpb25hbCBhZmZhaXJzIiwgIkRlZmVuY2UiKQogICkgKwogICMgU2FtZSBmb3IgdGhlIGNvbG9ycwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIHZhbHVlcyA9IHdlc19wYWxldHRlKCJEYXJqZWVsaW5nMSIpLAogICAgbGFiZWxzID0gYygiTWFjcm8tZWNvbm9taWMiLCAiSW50ZXJuYXRpb25hbCBhZmZhaXJzIiwgIkRlZmVuY2UiKQogICkgKwogICMgQWRkIGEgdGl0bGUKICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgUEEgdG9waWNzIGluIHRoZSBVTiBHZW5lcmFsIERlYmF0ZSBjb3JwdXMiKSArCiAgIyBBbmQgYWRkIHgtYXhpcyBhbmQgeS1heGlzIGxhYmVscwogIHhsYWIoIiIpICsKICB5bGFiKCJUb3BpYyBzaGFyZSAoJSkiKSArCiAgIyBBbmQgbGFzdCBkbyBzb21lIHR3ZWFraW5nIHdpdGggdGhlIHRoZW1lCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyMgU2VudGltZW50IGFuYWx5c2lzCgpIZXJlLCB3ZSBuZWVkIHRvIGRlZmluZSBtb3JlIHN0b3Agd29yZHMgKGNyZWF0ZSBhIG1hbnVhbCBsaXN0IG9mIHRoZW0pIHRvIG1ha2Ugc3VyZSB0aGF0IHdlIGRvIG5vdCBiaWFzIG91ciByZXN1bHRzLiBJIGhhdmUgYSBub24tZXhoYXVzdGl2ZSBsaXN0IGhlcmU6CgpgYGB7ciBzZW50aW1lbnQtc3RvcHdvcmRzLCBlY2hvPVRSVUV9CiMgRGVmaW5lIHN0b3B3b3JkcwpVTkdEX3N0b3B3b3JkcyA8LQogIGMoCiAgICAiZ29vZCBieWUiLAogICAgImdvb2QgbW9ybmluZyIsCiAgICAidW5pdCIsCiAgICAibmF0aW9uIiwKICAgICJyZXMiLAogICAgImdlbmVyYWwiLAogICAgImFzc2VtYiIsCiAgICAibXIiLAogICAgImdyZWV0IiwKICAgICJ0aGFuayIsCiAgICAiY29uZ3JhdHVsYXQiLAogICAgInNpciIsCiAgICAicGVyIiwKICAgICJjZW50IiwKICAgICJtZGdzIiwKICAgICJzb28iLAogICAgImhhbiIsCiAgICAiw7MiLAogICAgImciLAogICAgIm1hZGFtIiwKICAgICJuY2RzIiwKICAgICJzZGdzIiwKICAgICJwdiIsCiAgICAiaXNpbCIsCiAgICAiaXNpIiwKICAgICJmIiwKICAgICJmaWZ0aSIsCiAgICAic2l4dGlldGgiLAogICAgImFubmFuIiwKICAgICJrb2ZpIiwKICAgICJmaWZ0aCIsCiAgICAiZm91cnRoIiwKICAgICJmaXJzdCIsCiAgICAic2Vjb25kIiwKICAgICJ0aGlyZCIsCiAgICAic2l4dGgiLAogICAgInNldmVudGgiLAogICAgImVpZ2h0aCIsCiAgICAibmludGgiLAogICAgInRlbnRoIiwKICAgICJzZXZlbnRpZXRoIiwKICAgICJqZXJlbWnEhyIsCiAgICAiYWdlbmRhIiwKICAgICJvYmFtYSIsCiAgICAianVsaWFuIiwKICAgICJzZXJnaW8iLAogICAgIm1lbGxvIiwKICAgICJzZXB0ZW1iIiwKICAgICJkb2N1bWVudCIsCiAgICAicGxlbmFyaSIsCiAgICAiamVhbiIsCiAgICAiZWxpYXNzb24iLAogICAgImFubml2ZXJzYXJpIiwKICAgICJ2aWVpcmEiLAogICAgImhheWEiLAogICAgInJhc2giLAogICAgInRyZWtpIgogICkKYGBgCgpUbyBhcHBseSBpdCwgd2UgcmUtc3RhcnQgZnJvbSBzdGVwIDQgY3JlYXRpbmcgYSBERk0uIAoKV2Uga25vdyBhcHBseSBpdCB0byBvdXIgZnVuY3Rpb24KCmBgYHtyIGRmbS1zZW50aW1lbnQsIGVjaG89VFJVRX0KIyBSZW1vdmUgc2VsZi1kZWZpbmVkIHN0b3B3b3JkcwpteWRmbV9zZW50aW1lbnQgPC0gZGZtKAogICMgU2VsZWN0IHRoZSB0b2tlbiBvYmplY3QKICB0b2tlbl91bmdkLAogICMgTG93ZXIgdGhlIHdvcmRzCiAgdG9sb3dlciA9IFRSVUUsCiAgIyBTdGVtIHRoZSB3b3JkcwogIHN0ZW0gPSBUUlVFLAogICMgUmVtb3ZlIHN0b3Agd29yZHMgYW5kIHNlbGYtZGVmaW5lZCBzdG9wIHdvcmRzCiAgcmVtb3ZlID0gYyhVTkdEX3N0b3B3b3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpCikKYGBgCgpUcmltIGRhdGE6IHJlbW92ZSBhbGwgdGhlIHdvcmRzIHRoYXQgYXBwZWFyIGxlc3MgdGhhbiA3LjUlIG9mIHRoZSB0aW1lIGFuZCBtb3JlIHRoYW4gOTAlIG9mIHRoZSB0aW1lCgpgYGB7ciBkZm0tdHJpbS1zZW50aW1lbnQsIGVjaG89VFJVRX0KbXlkZm0udHJpbSA8LQogIGRmbV90cmltKAogICAgIyBTZWxlY3QgdGhlIERGTSBvYmplY3QKICAgIG15ZGZtX3NlbnRpbWVudCwKICAgIG1pbl9kb2NmcmVxID0gMC4wNzUsCiAgICAjIG1pbiA3LjUlCiAgICBtYXhfZG9jZnJlcSA9IDAuOTAsCiAgICAjIG1heCA5MCUKICAgIGRvY2ZyZXFfdHlwZSA9ICJwcm9wIgogICkgCmBgYAoKQW5kIG5vdyB3ZSBnZXQgdGhlIHNlbnRpbWVudCA6LSkKVGhlcmUgYXJlIG11bHRpcGxlIHdheXMgaG93IHRvIGRvIGl0LCB3ZSB3aWxsIHVzZSBvbmUgdGhhdCBpcyBidWlsdCBpbiBxdWFudGVkYS4gSW4gYW55IGNhc2UsIGl0IGlzIGltcG9ydGFudCB0byBjaGVjayB0aGUgdW5kZXJseWluZyBkaWN0aW9uYXJ5IChvbiB3aGljaCBiYXNpcyBpcyBpdCBidWlsdCBvbj8pLiAKVGhlIG1vc3QgZnJlcXVlbnRseSB1c2VkIGRpY3Rpb25hcmllcyBhcmU6CgotIFtOUkNdKGh0dHBzOi8vc2FpZm1vaGFtbWFkLmNvbS9XZWJQYWdlcy9OUkMtRW1vdGlvbi1MZXhpY29uLmh0bSkKLSBbQmluZ10oaHR0cHM6Ly93d3cuY3MudWljLmVkdS9+bGl1Yi9GQlMvc2VudGltZW50LWFuYWx5c2lzLmh0bWwpCi0gW0FGSU5OXShodHRwOi8vd3d3Mi5pbW0uZHR1LmRrL3B1YmRiL3B1YnMvNjAxMC1mdWxsLmh0bWwpCi0gYW5kIFtMU0QgKExleGljb2RlciBTZW50aW1lbnQgRGljdGlvbmFyeSBieSBZb3VuZyBhbmQgU29yb2spXShodHRwOi8vd3d3LnNuc29yb2thLmNvbS9kYXRhLWxleGljb2Rlci8pCgpGb3IgYSBtb3JlIGZpbmUtZ3JhaW5lZCBhcHByb2FjaCwgdGhlcmUgaXMgYWxzbyB0aGUgW2BzZW50aW1lbnRyYCBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vdHJpbmtlci9zZW50aW1lbnRyKSwgdGhhdCBteSBjby1hdXRob3IsIERlbm5pcyBIYW1tZXJzY2htaWR0LCBhbmQgSSB1c2UgaW4gb3VyIFtwYXBlcl0oaHR0cHM6Ly93d3cudGVjdHVtLXNob3AuZGUvdGl0ZWwvY2hpbmFzLXJvbGxlLWluLWVpbmVyLW5ldWVuLXdlbHRvcmRudW5nLWlkLTk3ODY3Lykgb24gc2VudGltZW50IGF0IHRoZSBVTkdELgoKV2Ugd2lsbCBnbyB3aXRoIHRoZSBMU0QgZGljdGlvbmFyeSBoZXJlIGFzIGl0IGlzIGFscmVhZHkgYnVpbHQgaW4gYHF1YW50ZWRhYC4KCmBgYHtyIHNlbnRpbWVudC1kaWN0LCBlY2hvPVRSVUV9CiMgQ2FsbCBhIGRpY3Rpb25hcnkKZGZtYXRfbHNkIDwtCiAgZGZtKG15ZGZtLnRyaW0sCiAgICAgIGRpY3Rpb25hcnkgPQogICAgICAgIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1WzE6Ml0pCmBgYAoKV2UgY2FuIGxvb2sgIGF0IHRoZSBkaWN0aW9uYXJ5IGZpcnN0IGNob29zaW5nIHRoZSBmaXJzdCA1IGRvY3VtZW50czoKCmBgYHtyIHNlbnRpbWVudC1oZWFkLCBlY2hvPVRSVUV9CmhlYWQoZGZtYXRfbHNkLCA1KQpgYGAKCgpUbyBiZXR0ZXIgd29yayB3aXRoIHRoZSBkYXRhLCB3ZSBjb252ZXJ0IGl0IHRvIGEgZGF0YSBmcmFtZToKCmBgYHtyIGNvbnZlcnQtZGZtLCBlY2hvPVRSVUV9CiMgQ2FsY3VsYXRlIHRoZSBvdmVyYWxsCiMgc2hhcmUgb2YgcG9zaXRpdmUgYW5kCiMgbmVnYXRpdmUgd29yZHMgb24gYSBzY2FsZQpkYXRhIDwtIGNvbnZlcnQoZGZtYXRfbHNkLAogICAgICAgICAgICAgICAgdG8gPSAiZGF0YS5mcmFtZSIpCmBgYAoKQW5kIGhhdmUgYSBsb29rIGF0IHRoZSBkYXRhOgpgYGB7ciwgZGF0YV92aWV3X2xzcywgZWNobz1UUlVFfQpoZWFkKGRhdGEpCmBgYAoKVG8gZ2V0IG1vcmUgbWVhbmluZ2Z1bCByZXN1bHRzLCB3ZSBkbyBzb21lIGxhc3QgdHdlYWtzOgoKYGBge3Igc2VudGltZW50LXdyYW5nbGluZywgZWNobz1UUlVFfQpkYXRhICU8PiUKICBkcGx5cjo6bXV0YXRlKAogICAgIyBHZW5lcmF0ZSB0aGUgbnVtYmVyIG9mIHRvdGFsIHdvcmRzCiAgICB0b3RhbF93b3JkcyA9IHBvc2l0aXZlICsgbmVnYXRpdmUsCiAgICAjIEdlbmVyYXRlIHRoZSByZWxhdGl2ZSBmcmVxdWVuY3kKICAgIHBvc19wZXJjID0gcG9zaXRpdmUgLyB0b3RhbF93b3JkcyAqIDEwMCwKICAgIG5lZ19wZXJjID0gbmVnYXRpdmUgLyB0b3RhbF93b3JkcyAqIDEwMCwKICAgICMgR2VuZXJhdGUgdGhlIG5ldCBzZW50aW1lbnQKICAgIG5ldF9wZXJjID0gcG9zX3BlcmMgLSBuZWdfcGVyYwogICkKYGBgCgpBZ2FpbiwgaGF2ZSBhIGxvb2sgYXQgdGhlIGRhdGE6CmBgYHtyIGRhdGFfdmlld19zZW50aW1lbnQsIGVjaG89VFJVRX0KaGVhZChkYXRhKQpgYGAKCgpgYGB7ciBzZW50aW1lbnQtd3JhbmdsaW5nMiwgZWNobz1UUlVFfQojIEdlbmVyYXRlIGNvdW50cnkgY29kZSBhbmQgeWVhcgpkYXRhICU8PiUKICBkcGx5cjo6bXV0YXRlKCMgRGVmaW5lIHRoZSBjb3VudHJ5LWNvZGUgKGl0J3MgYWxsIGluIHRoZSBkb2N1bWVudCBJRCkKICAgIGNjb2RlID0gc3RyX3N1Yihkb2NfaWQsIDEsIDMpLAogICAgIyBEZWZpbmUgdGhlIHllYXIgKGl0J3MgYWxzbyBpbiB0aGUgZG9jdW1lbnQgSUQpCiAgICB5ZWFyID0gYXMubnVtZXJpYyhzdHJfc3ViKGRvY19pZCwgOCwgMTEpKSkgJT4lCiAgIyBEcm9wIGFsbCBvYnNlcnZhdGlvbnMgd2l0aCAiRVVfIiBiZWNhdXNlIHRoZXkgYXJlIG5vdCBhIHNpbmdsZSBjb3VudHJ5CiAgZHBseXI6OmZpbHRlcihjY29kZSAhPSAiRVVfIikgJT4lCiAgIyBEcm9wIHRoZSB2YXJpYWJsZSBkb2NfaWQKICBkcGx5cjo6c2VsZWN0KC1kb2NfaWQpCmBgYAoKV2UgZmlyc3QgZ2V0IGFuIG92ZXJhbGwgaW1wcmVzc2lvbiBieSBwbG90dGluZyB0aGUgYXZlcmFnZSBuZXQgc2VudGltZW50IGJ5IGNvbnRpbmVudCBvdmVyIHRpbWU6CgpgYGB7ciB2aXMtYXZnLXNlbnRpbWVudCwgZWNobz1UUlVFfQpkYXRhICU+JQogICMgR2VuZXJhdGUgdGhlIGNvbnRpbmVudCBmb3IgZWFjaCBjb3VudHJ5IHVzaW5nIHRoZSBgY291bnRyeWNvZGUoKWAgY29tbWFuZAogIGRwbHlyOjptdXRhdGUoY29udGluZW50ID0gY291bnRyeWNvZGUoY2NvZGUsICJpc28zYyIsICJjb250aW5lbnQiLCBjdXN0b21fbWF0Y2ggPQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCJZVUciID0gIkV1cm9wZSIpKSkgJT4lCiAgIyBXZSBncm91cCBieSBjb250aW5lbnQgYW5kIHllYXIgdG8gZ2VuZXJhdGUgdGhlIGF2ZXJhZ2Ugc2VudGltZW50IGJ5IGNvbnRpbmVudCAKICAjIGFuZCBhbmQgeWVhciAgCiAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAlPiUKICBkcGx5cjo6bXV0YXRlKGF2ZyA9IG1lYW4obmV0X3BlcmMpKSAlPiUKICAjIFdlIG5vdyBwbG90IGl0CiAgZ2dwbG90KCkgKwogICMgVXNpbmcgYSBsaW5lIGNoYXJ0IHdpdGggeWVhciBvbiB0aGUgeC1heGlzLCB0aGUgYXZlcmFnZSBzZW50aW1lbnQgYnkgY29udGluZW50CiAgIyBvbiB0aGUgeS1heGlzIGFuZCBjb2xvcmVkIGJ5IGNvbnRpbmVudAogIGdlb21fbGluZShhZXMoeCA9IHllYXIsIHkgPSBhdmcsIGNvbCA9IGNvbnRpbmVudCkpICsKICAjIERlZmluZSB0aGUgY29sb3JzCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmcxIikpICsKICAjIExhYmVsIHRoZSBheGVzCiAgeGxhYigiVGltZSIpICsKICB5bGFiKCJBdmVyYWdlIG5ldCBzZW50aW1lbnQiKSAKYGBgCgpBbmQgbm93IHdlIHdhbnQgdG8gdmlzdWFsaXplIHRoZSByZXN1bHRzIGluIG1vcmUgZGV0YWlsIDotKQoKYGBge3IgdmlzLXNlbnRpbWVudCwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG89VFJVRX0KZGF0YSAlPiUKICAjIEdlbmVyYXRlIHRoZSBjb3VudHJ5IG5hbWUgZm9yIGVhY2ggY291bnRyeSB1c2luZyB0aGUgYGNvdW50cnljb2RlKClgIGNvbW1hbmQKICBkcGx5cjo6bXV0YXRlKGNvdW50cnluYW1lID0gY291bnRyeWNvZGUoY2NvZGUsICJpc28zYyIsICJjb3VudHJ5Lm5hbWUiKSkgJT4lCiAgIyBGaWx0ZXIgYW5kIG9ubHkgc2VsZWN0IHNwZWNpZmljIGNvdW50cmllcyB0aGF0IHdlIHdhbnQgdG8gY29tcGFyZQogIGRwbHlyOjpmaWx0ZXIoY291bnRyeW5hbWUgJWluJSBjKAogICAgIkdlcm1hbnkiLAogICAgIkZyYW5jZSIsCiAgICAiVW5pdGVkIEtpbmdkb20iLAogICAgIk5vcndheSIsCiAgICAiU3BhaW4iLAogICAgIlN3ZWRlbiIKICApKSAlPiUKICAjIE5vdyBjb21lcyB0aGUgcGxvdHRpbmcgcGFydCA6LSkKICBnZ3Bsb3QoKSArCiAgIyBXZSBkbyBhIGJhciBwbG90IHRoYXQgaGFzIHRoZSB5ZWFycyBvbiB0aGUgeC1heGlzIGFuZCB0aGUgbGV2ZWwgb2YgdGhlIAogICMgbmV0LXNlbnRpbWVudCBvbiB0aGUgeS1heGlzCiAgIyBXZSBhbHNvIGNvbG9yIGl0IHNvIHRoYXQgYWxsIHRoZSBuZXQtc2VudGltZW50cyBncmVhdGVyIDAgZ2V0IGEgCiAgIyBkaWZmZXJlbnQgY29sb3IKICBnZW9tX2NvbChhZXMoCiAgICB4ID0geWVhciwKICAgIHkgPSBuZXRfcGVyYywKICAgIGZpbGwgPSAobmV0X3BlcmMgPiAwKQogICkpICsKICAjIEhlcmUgd2UgZGVmaW5lIHRoZSBjb2xvcnMgYXMgd2VsbCBhcyB0aGUgbGFiZWxzIGFuZCB0aXRsZSBvZiB0aGUgbGVnZW5kCiAgc2NhbGVfZmlsbF9tYW51YWwoCiAgICBuYW1lID0gIlNlbnRpbWVudCIsCiAgICBsYWJlbHMgPSBjKCJOZWdhdGl2ZSIsICJQb3NpdGl2ZSIpLAogICAgdmFsdWVzID0gYygiI0M5MzMxMiIsICIjNDQ2NDU1IikKICApICsKICAjIE5vdyB3ZSBhZGQgdGhlIGF4ZXMgbGFiZWxzCiAgeGxhYigiVGltZSIpICsKICB5bGFiKCJOZXQgc2VudGltZW50IikgKwogICMgQW5kIGRvIGEgZmFjZXRfd3JhcCBieSBjb3VudHJ5IHRvIGdldCBhIG1vcmUgbWVhbmluZ2Z1bCB2aXN1YWxpemF0aW9uCiAgZmFjZXRfd3JhcCh+IGNvdW50cnluYW1lKQpgYGAKCgojIyBVbmtub3duIGNhdGVnb3JpZXMKCiMjIyMgU3RydWN0dXJhbCB0b3BpYyBtb2RlbHMKCldlIHVzZSB0aGUgW2BzdG1gIHBhY2thZ2UgaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3N0bS92aWduZXR0ZXMvc3RtVmlnbmV0dGUucGRmKS4gUXVhbnRlZGEgYWxzbyBoYXMgW2J1aWx0LWluIHRvcGljIG1vZGVscyBzdWNoIGFzIExEQV0oaHR0cHM6Ly90dXRvcmlhbHMucXVhbnRlZGEuaW8vbWFjaGluZS1sZWFybmluZy90b3BpY21vZGVsLykuCgpgYGB7ciwgbG9hZC1zdG19CmxpYnJhcnkoc3RtKSAjIEVzdGltYXRpb24gb2YgdGhlIFN0cnVjdHVyYWwgVG9waWMgTW9kZWwKYGBgCgoKSW4gYSBmaXJzdCBzdGVwLCB3ZSBhc3NpZ24gYSB0b3BpYyBjb3VudC4gVXN1YWxseSB0aGUgbnVtYmVyIG9mIHRvcGljcyBjYW4gYmUgaGlnaGVyIC0tIGJ1dCB0aGF0IG9idmlvdXNseSBjb21lcyBhdCBhIGNvc3QuIEhlcmUsIGl0J3MgY29tcHV0YXRpb25hbCBwb3dlci4gVG8gbWFrZSBpdCBhcyBmYXN0IGFzIHBvc3NpYmxlLCB3ZSdsbCBwaWNrIDUgdG9waWNzIGZvciBvdXIgZXhhbXBsZS4KCmBgYHtyIHRvcGljLXN0bSwgZWNobz1UUlVFfQojIEFzc2lnbnMgdGhlIG51bWJlciBvZiB0b3BpY3MKdG9waWMuY291bnQgPC0gNSAKYGBgCgpgYGB7ciBkZm0tdG8tc3RtLCBlY2hvPVRSVUV9CiMgVG8gbWFrZSBzdXJlIHRoYXQgd2UgZ2V0IHRoZSBzYW1lIHJlc3VsdHMsIHdlIHNldCBhIHNlZWQKc2V0LnNlZWQoNjgxNTkpCgojIENvbnZlcnQgdGhlIHRyaW1tZWQgREZNIHRvIGEgU1RNIG9iamVjdApkZm0yc3RtIDwtIGNvbnZlcnQobXlkZm0udHJpbSwgdG8gPSAic3RtIikKCiMgVXNlIHRoaXMgb2JqZWN0IHRvIGVzdGltYXRlIHRoZSBzdHJ1Y3R1cmFsIHRvcGljIG1vZGVsCm1vZGVsLnN0bSA8LSBzdG0oCiAgIyBEZWZpbmUgdGhlIGRvY3VtZW50cwogIGRvY3VtZW50cyA9IGRmbTJzdG0kZG9jdW1lbnRzLAogICMgRGVmaW5lIHRoZSB3b3JkcyBpbiB0aGUgY29ycHVzCiAgdm9jYWIgPSBkZm0yc3RtJHZvY2FiLAogICMgRGVmaW5lIHRoZSBudW1iZXIgb2YgdG9waWNzCiAgSyA9IHRvcGljLmNvdW50LAogICMgVGhlIG5lYXQgdGhpbmcgYWJvdXQgU1RNIGlzIHRoYXQgeW91IGNhbiB1c2UgbWV0YSBkYXRhIHRvIGluZm9ybSB5b3VyIG1vZGVsIAogICMgKGhlcmUgd2UgdXNlIGNvdW50cnkgYW5kIHllYXIgYW5kIHJlbHkgaGVhdmlseSBvbiB0aGUgdmlnbmV0dGUgb2YgU1RNKQogIHByZXZhbGVuY2UgPSB+IGNvdW50cnkgKyBzKHllYXIpLAogICMgRGVmaW5lIHRoZSBkYXRhIHNldCB0aGF0IGNvbnRhaW5zIGNvbnRlbnQgdmFyaWFibGVzIChyZW1lbWJlciwgdGhpcyBpcyB3aGF0IGlzIHNvIGdyZWF0IGFib3V0IFNUTSEpCiAgZGF0YSA9IGRmbTJzdG0kbWV0YSwKICAjIFRoaXMgZGVmaW5lcyB0aGUgaW5pdGlhbGl6YXRpb24gbWV0aG9kLiAic3BlY3RyYWwiIGlzIHRoZSBkZWZhdWx0IGFuZCBwcm92aWRlcyBhIGRldGVybWluaXN0aWMKICAjIGluaXRpYWxpemF0aW9uIGJhc2VkIG9uIEFyb3JhIGV0IGFsLiAyMDE0IChpdCBpcyBpbiBwYXJ0aWN1bGFyIHJlY29tbWVuZGVkIGlmIHRoZSBudW1iZXIgb2YKICAjIGRvY3VtZW50cyBpcyBsYXJnZSkKICBpbml0LnR5cGUgPSAiU3BlY3RyYWwiCikKYGBgCkFzIG1lbnRpb25lZCBkdXJpbmcgdGhlIHRhbGssIHRoZSBzdHJ1Y3R1cmFsIHRvcGljIG1vZGVsIGNhbiBhbHNvIGluY2x1ZGUgbWV0YSBkYXRhIHRvIGVzdGltYXRlIHRoZSB0b3BpYy4gWW91IGluY2x1ZGUgdGhlbSB1c2luZyB0aGUgYXJndW1lbnQgYHByZXZhbGVuY2VgLiBJZiB5b3Ugd2FudCB0byBrbm93IG1vcmUgYWJvdXQgaXQgd29ya3MsIHNlZSBbdGhpcyBleGNlbGxlbnQgdmlnbmV0dGVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zdG0vdmlnbmV0dGVzL3N0bVZpZ25ldHRlLnBkZikuCgpUaGVyZSBhcmUgZGlmZmVyZW50IHdheXMgdG8gdmlzdWFsaXplIHRoZSByZXN1bHRzLiBXZSdsbCBmaXJzdCBnbyB3aXRoIGJhc2UtcGxvdCB3aGljaCBnaXZlcyB5b3UgYSBzaGFyZWQgZXN0aW1hdGlvbiBvZiB0aGUgdG9waWMgc2hhcmVzLgoKYGBge3IgcGxvdC1zdG0sIGVjaG89VFJVRX0KcGxvdCgKICAjIFRha2VzIHRoZSBTVE0gb2JqZWN0CiAgbW9kZWwuc3RtLAogICMgRGVmaW5lIHRoZSB0eXBlIG9mIHBsb3QKICB0eXBlID0gInN1bW1hcnkiLAogICMgRGVmaW5lIGZvbnQgc2l6ZQogIHRleHQuY2V4ID0gMC41LAogICMgTGFiZWwgdGhlIHRpdGxlCiAgbWFpbiA9ICJTVE0gdG9waWMgc2hhcmVzIiwKICAjIEFuZCB0aGUgeC1heGlzCiAgeGxhYiA9ICJTaGFyZSBlc3RpbWF0aW9uIgopCmBgYAoKVG8gZ2V0IG1vcmUgb3V0IG9mIGl0IGFuZCB0byBsZWFybiBtb3JlIGFib3V0IHRvcGljIDQsIHdlIGNhbiB1c2UgYGZpbmRUaG91Z2h0c2AgdG8gZ2V0IHRoZSBwbGFpbiB0ZXh0IHRoYXQgaXMgYXNzb2NpYXRlZCB3aXRoIHRoZSB0b3BpYzoKCmBgYHtyLCBzdG0tcGxhaW4tdGV4dH0KZmluZFRob3VnaHRzKAogICMgWW91ciB0b3BpYyBtb2RlbAogIG1vZGVsLnN0bSwKICAjIFRoZSB0ZXh0IGRhdGEgdGhhdCB5b3UgdXNlZCAKICAjIHRvIHJldHJpZXZlIHBhc3NhZ2VzIGZyb20KICB0ZXh0cyA9IHVuX2RhdGEkdGV4dCwKICAjIE51bWJlciBvZiBkb2N1bWVudHMgdG8gYmUgZGlzcGxheWVkIGhlcmUKICBuID0gMSwKICAjIFRvcGljIG51bWJlciB5b3UgYXJlIGludGVyZXN0ZWQgaW4KICB0b3BpY3MgPSA0CikKYGBgCgpZb3UgY2FuIGFsc28gW2NvbWJpbmUgYHN0bWAgd2l0aCBgdGlkeXRleHRgXShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvc2hlcmxvY2staG9sbWVzLXN0bS8pIHRvIGdlbmVyYXRlIGBnZ3Bsb3QyYCBvYmplY3RzLiBXZSBmb2xsb3cgW0p1bGlhIFNpbGdlJ3Mgb3V0bGluZV0oaHR0cHM6Ly9qdWxpYXNpbGdlLmNvbS9ibG9nL3NoZXJsb2NrLWhvbG1lcy1zdG0vKSBoZXJlLiBUaGUgcGxvdCBzaG93cyB0aGUgcHJvYmFiaWxpdGllcyB3aXRoIHdoaWNoIGRpZmZlcmVudCB0ZXJtcyBhcmUgYXNzb2NpYXRlZCB3aXRoIHRoZSB0b3BpY3MuCgpgYGB7ciBzdG0tdGlkeSwgZWNobz1UUlVFfQojIExvYWQgdGlkeXRleHQKbGlicmFyeSh0aWR5dGV4dCkgIyBUZXh0IE1pbmluZyB1c2luZyAnZHBseXInLCAnZ2dwbG90MicsIGFuZCBPdGhlciBUaWR5IFRvb2xzCgojIFR1cm4gdGhlIFNUTSBvYmplY3QgaW50byBhIGRhdGEgZnJhbWUuIFRoaXMgaXMgbmVjZXNzYXJ5IHNvIHRoYXQgd2UgY2FuIHdvcmsgd2l0aCBpdC4KdGRfYmV0YSA8LSB0aWR5KG1vZGVsLnN0bSkKCnRkX2JldGEgJT4lCiAgIyBHcm91cCBieSB0b3BpYwogIGdyb3VwX2J5KHRvcGljKSAlPiUKICAjIFRha2UgdGhlIHRvcCAxMCBiYXNlZCBvbiBiZXRhCiAgdG9wX24oMTAsIGJldGEpICU+JQogICMgVW5ncm91cAogIHVuZ3JvdXAoKSAlPiUKICAjIEdlbmVyYXRlIHRoZSB2YXJpYWJsZXMgdG9waWMgYW5kIHRlcm0KICBkcGx5cjo6bXV0YXRlKHRvcGljID0gcGFzdGUwKCJUb3BpYyAiLCB0b3BpYyksCiAgICAgICAgICAgICAgICB0ZXJtID0gcmVvcmRlcl93aXRoaW4odGVybSwgYmV0YSwgdG9waWMpKSAlPiUKICAjIEFuZCBwbG90IGl0CiAgZ2dwbG90KCkgKwogICMgVXNpbmcgYSBiYXIgcGxvdCB3aXRoIHRoZSB0ZXJtcyBvbiB0aGUgeC1heGlzLCB0aGUgYmV0YSBvbiB0aGUgeS1heGlzLCBmaWxsZWQgYnkgdG9waWMKICBnZW9tX2NvbChhZXMoeCA9IHRlcm0sIHkgPSBiZXRhLCBmaWxsID0gYXMuZmFjdG9yKHRvcGljKSksCiAgICAgICAgICAgYWxwaGEgPSAwLjgsCiAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICMgRG8gYSBmYWNldF93cmFwIGJ5IHRvcGljCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBzY2FsZXMgPSAiZnJlZV95IikgKwogICMgQW5kIGZsaXAgdGhlIHBsb3QKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3hfcmVvcmRlcmVkKCkgKwogICMgTGFiZWwgdGhlIHgtYXhpcywgeS1heGlzLCBhcyB3ZWxsIGFzIHRpdGxlCiAgbGFicygKICAgIHkgPSBleHByZXNzaW9uKGJldGEpLAogICAgdGl0bGUgPSAiSGlnaGVzdCB3b3JkIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggdG9waWMiLAogICAgc3VidGl0bGUgPSAiRGlmZmVyZW50IHdvcmRzIGFyZSBhc3NvY2lhdGVkIHdpdGggZGlmZmVyZW50IHRvcGljcyIKICApICsKICAjIEFuZCBmaW5hbGx5IGRlZmluZSB0aGUgY29sb3JzCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gd2VzX3BhbGV0dGUoIkRhcmplZWxpbmcxIikpCmBgYAoKWW91IGNvdWxkIGFsc28gdmlzdWFsaXplIG91ciByZXN1bHRzIHdpdGggYSBwZXJzcGVjdGl2ZSBwbG90IHRoYXQgYWxsb3dzIHlvdSB0byBzaG93IGhvdyBkaWZmZXJlbnQgdGVybXMgYXJlIHJlbGF0ZWQgd2l0aCBlYWNoIHRvcGljLiBXZSBhZ2FpbiB1c2UgYSBiYXNlIHBsb3QgaGVyZS4KCmBgYHtyIHN0bS1wZXJzcGVjdGl2ZSwgZWNobz1UUlVFfQpwbG90KAogICMgQWNjZXNzIHRoZSBTVE0gb2JqZWN0CiAgbW9kZWwuc3RtLAogICMgU2VsZWN0IHRoZSB0eXBlIG9mIHBsb3QKICB0eXBlID0gInBlcnNwZWN0aXZlcyIsCiAgIyBTZWxlY3QgdGhlIHRvcGljcwogIHRvcGljcyA9IGMoNCwgNSksCiAgIyBEZWZpbmUgdGhlIHRpdGxlCiAgbWFpbiA9ICJQdXR0aW5nIHR3byBkaWZmZXJlbnQgdG9waWNzIGluIHBlcnNwZWN0aXZlIikKYGBgCgpJdCBpcyBhbHdheXMgdHJpY2t5IHRvIGZpbmQgdGhlIHJpZ2h0IG51bWJlciBvZiB0b3BpY3MgKHlvdXIgYGtgKS4gVGhlIGBzdG1gIHBhY2thZ2UgY29tZXMgd2l0aCBhIGhhbmR5IGZ1bmN0aW9uIGNhbGxlZCBgc2VhcmNoS2AgdGhhdCBhbGxvd3MgeW91IHRvIGV2YWx1YXRlIGRpZmZlcmVudCB0b3BpYyBtb2RlbHMgYW5kIHRvIGZpbmQgeW91ciBga2AuIFdlIGNhbiBhZ2FpbiBwbG90IGl0IHVzaW5nIHRoZSBgcGxvdCgpYCBmdW5jdGlvbi4gCgpUbyBmdXJ0aGVyIGV2YWx1YXRlIHlvdXIgdG9waWMgbW9kZWxzLCBoYXZlIGEgbG9vayBhdCBbYHN0bWluc2lnaHRzYF0oaHR0cHM6Ly9naXRodWIuY29tL2NzY2h3ZW0yZXIvc3RtaW5zaWdodHMpLCBbYExEQXZpc2BdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9MREF2aXMvaW5kZXguaHRtbCkgKHVzZSBgc3RtaW5zaWdodHM6OnRvTERBdmlzYCB0byBjb252ZXJ0IHlvdXIgU1RNIG1vZGVsKSwgYW5kIFtgb29sb25nYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL29vbG9uZy9pbmRleC5odG1sKS4KCkhlcmUncyBhIHNob3J0IGNvZGUgc25pcHBldCB0aGF0IHNob3dzIGhvdyBgTERBdmlzYCB3b3Jrcy4gQm90aCBgc3RtaW5zaWdodHNgIGFuZCBgTERBdmlzYCBjYWxsIFNoaW55QXBwcyB0aGF0IGFsbG93IHlvdSB0byBpbnRlcmFjdGl2ZWx5IGFjY2VzcyB5b3VyIHRvcGljIG1vZGVscy4KCmBgYHtyLCBldmFsPUZBTFNFLCBzdG0tZXZhbHVhdGlvbn0KbGlicmFyeShMREF2aXMpICMgSW50ZXJhY3RpdmUgVmlzdWFsaXphdGlvbiBvZiBUb3BpYyBNb2RlbHMKCnRvTERBdmlzKAogICMgVGhlIHRvcGljIG1vZGVsCiAgbW9kID0gbW9kZWwuc3RtLCAKICAjIFRoZSBkb2N1bWVudHMKICBkb2NzID0gZGZtMnN0bSRkb2N1bWVudHMpCmBgYAoKVGhlIFNoaW55QXBwIGxvb2tzIGxpa2UgdGhpczoKCiFbXSguLi9maWd1cmVzL3N0bS1ldmFsdWF0aW9uLnBuZykKCiMjIE5leHQgc3RlcHMKClRoZXNlIGFyZSBqdXN0IHRoZSBtb3N0IGJhc2ljIHN1cGVydmlzZWQgYW5kIHVuc3VwZXJ2aXNlZCBtb2RlbHMgaW4gTkxQIHRoYXQgeW91IGNhbiB1c2UgYnV0IGFzIHlvdSB3b3JrIG1vcmUgYW5kIG1vcmUgd2l0aCB0ZXh0dWFsIGRhdGEsIHlvdSB3aWxsIHNlZSB0aGF0IHRoZXJlIGlzIHNvIG11Y2ggbW9yZSBpbiB0aGUgZmllbGQgb2YgTkxQIGluY2x1ZGluZyBbZG9jdW1lbnQgc2ltaWxhcml0eV0oaHR0cDovL2Nvc2ltYW1leWVyLnJiaW5kLmlvL3NsaWRlcy9ubHAtcmxhZGllcy90YWxrIzU2KSwgW3RleHQgZ2VuZXJhdGlvbl0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzE5MDYuMDE5NDYpIG9yIGV2ZW4gW2NoYXQgYm90c10oaHR0cDovL2Nvc2ltYW1leWVyLnJiaW5kLmlvL3NsaWRlcy9ubHAtcmxhZGllcy90YWxrIzU3KSB0aGF0IHlvdSBjYW4gY3JlYXRlIHVzaW5nIHlvdXIga25vd2xlZGdlIGFuZCB0aGUgc2FtZSBzaW1wbGUgc3RlcHMgdGhhdCB3ZSBoYXZlIHVzZWQgaGVyZS4KCiMjIE1vcmUgcmVzb3VyY2VzCgotIFF1YW50ZWRhCgogIC0gW0tvaGVpIFdhdGFuYWJlIGFuZCBTdGVmYW4gTcO8bGxlcjogUXVhbnRlZGEgVHV0b3JpYWxzXShodHRwczovL3R1dG9yaWFscy5xdWFudGVkYS5pbykKICAtIFtRdWFudGVkYSBDaGVhdCBTaGVldF0oaHR0cHM6Ly9tdWVsbGVyc3RlZmFuLm5ldC9maWxlcy9xdWFudGVkYS1jaGVhdHNoZWV0LnBkZikKCi0gTW9yZSBvbiB0ZXh0IG1pbmluZyBhbmQgTkxQCiAgLSBbQ29zaW1hIE1leWVyIGFuZCBDb3JuZWxpdXMgUHVzY2htYW5uOiBBZHZhbmNpbmcgVGV4dCBNaW5pbmcgd2l0aCBSIGFuZCBxdWFudGVkYV0oaHR0cHM6Ly93d3cubXplcy51bmktbWFubmhlaW0uZGUvc29jaWFsc2NpZW5jZWRhdGFsYWIvYXJ0aWNsZS9hZHZhbmNpbmctdGV4dC1taW5pbmcvKQogIC0gW0p1c3RpbiBHcmltbWVyIGFuZCBCcmFuZG9uIFN0ZXdhcnQ6IFRleHQgYXMgRGF0YTogVGhlIFByb21pc2UgYW5kIFBpdGZhbGxzIG9mIEF1dG9tYXRpYyBDb250ZW50IEFuYWx5c2lzIE1ldGhvZHMgZm9yIFBvbGl0aWNhbCBUZXh0c10oaHR0cHM6Ly93d3cuY2FtYnJpZGdlLm9yZy9jb3JlL2pvdXJuYWxzL3BvbGl0aWNhbC1hbmFseXNpcy9hcnRpY2xlL3RleHQtYXMtZGF0YS10aGUtcHJvbWlzZS1hbmQtcGl0ZmFsbHMtb2YtYXV0b21hdGljLWNvbnRlbnQtYW5hbHlzaXMtbWV0aG9kcy1mb3ItcG9saXRpY2FsLXRleHRzL0Y3QUFDOEIyOTA5NDQxNjAzRkVCMjVDMTU2NDQ4RjIwKQogIC0gW0RhbiBKdXJhZnNreSBhbmQgSmFtZXMgSC4gTWFydGluOiBTcGVlY2ggYW5kIExhbmd1YWdlIFByb2Nlc3NpbmddKGh0dHBzOi8vd2ViLnN0YW5mb3JkLmVkdS9+anVyYWZza3kvc2xwMy8pCgotIFNlbnRpbWVudCBhbmFseXNpcwogIC0gW3NlbnRpbWVudHJdKGh0dHBzOi8vZ2l0aHViLmNvbS90cmlua2VyL3NlbnRpbWVudHIpCiAgLSBbRGVubmlzIEhhbW1lcnNjaG1pZHQgYW5kIENvc2ltYSBNZXllciAyMDIwOiBNb25leSBNYWtlcyB0aGUgV29ybGQgR28gRnJvd25lZCAtIEFuYWx5emluZyB0aGUgSW1wYWN0IG9mIENoaW5lc2UgRm9yZWlnbiBBaWQgb24gU3RhdGVzJyBTZW50aW1lbnQgVXNpbmcgTmF0dXJhbCBMYW5ndWFnZSBQcm9jZXNzaW5nXShodHRwczovL3d3dy50ZWN0dW0tc2hvcC5kZS90aXRlbC9jaGluYXMtcm9sbGUtaW4tZWluZXItbmV1ZW4td2VsdG9yZG51bmctaWQtOTc4NjcvKSAoZm9yIGFuIGFwcGxpZWQgZXhhbXBsZSBvZiBzZW50aW1lbnQgYW5hbHlzaXMpCgotIE1vZGVsIHZhbGlkYXRpb24KICAtIFtvb2xvbmc6IFZhbGlkYXRpb24gb2YgZGljdGlvbmFyeSBhcHByb2FjaGVzIGFuZCB0b3BpYyBtb2RlbHNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9vb2xvbmcvaW5kZXguaHRtbCkKICAtIFtzdG1pbnNpZ2h0c10oaHR0cHM6Ly9naXRodWIuY29tL2NzY2h3ZW0yZXIvc3RtaW5zaWdodHMpCiAgLSBbTERBdmlzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvTERBdmlzL2luZGV4Lmh0bWwpCiAgCi0gTW9yZSBnZW5lcmFsIHJlc291cmNlCiAgLSBbRGF0YSBTY2llbmNlICYgU29jaWV0eV0oaHR0cHM6Ly9kc3NvYy5naXRodWIuaW8vc2NoZWR1bGUvKQogIC0gW1JlZ0V4IENoZWF0IFNoZWV0XShodHRwczovL3JzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE2LzA5L1JlZ0V4Q2hlYXRzaGVldC5wZGYpCiAgLSBbU3RyaW5nciBDaGVhdCBTaGVldF0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvYmxvYi9tYXN0ZXIvc3RyaW5ncy5wZGYpCgo=