0. INTRODUCTION

For this independent study, I decided to analyze a new dataset comprising the reviews of App Store users about a popular financial management application, named “Mint.” This application helps users keep track of their income and expenses. It also provides them insights into their credit score, bills and balances. Although there are so many competitors for Mint, it has been able to rise to one of the top financial management apps on the market.


1. PREPARE

1b. Guiding Questions

During this data analysis, I liked to find an answer to three questions:

  1. What topics are mostly discussed in the reviews of a successful application?

  2. What finance-related topics will appear more in these reviews?

  3. How different are the results of topic modeling techniques (stemming, LDA vs STM) from each other?

1c. Set Up

First of all, we load packages needed for this walkthrough.

library(tidyverse)
library(tidytext)
library(SnowballC)
library(topicmodels)
library(stm)
library(ldatuning)
library(knitr)
library(LDAvis)

2. WRANGLE

2a. Import Data From Mint

I pulled 10,000 reviews of Mint application from Apple Store using a library named “app_store_scraper” in python and imported the data into this project.

mint_reviews <- read_csv("data/mint.csv", 
     col_types = cols(userName = col_character(),
                   title = col_character(), 
                   review = col_character()
                   ))
mint_reviews <- select(mint_reviews,userName,title,review)

2b. Cast a Document Term Matrix

Tidying Text

When I looked at different attributes in this dataset, I found out “title” column includes useful information. Hence, I concatenated the content of “review” and “title” columns and put the result into a new column named “combined”. Then, the data is cleaned and tokenized.

mint_reviews$combined = paste(mint_reviews$title, mint_reviews$review, sep=" ")

mint_selected <- mint_reviews %>%
  unnest_tokens(output = word, input = combined) %>%
  anti_join(stop_words, by = "word")

mint_selected <- select(mint_selected,userName, word)
mint_selected <- na.omit(mint_selected)

The most common words in the reviews of Mint are:

mint_selected %>%
  count(word, sort = TRUE)
## # A tibble: 9,691 x 2
##    word         n
##    <chr>    <int>
##  1 app      12661
##  2 mint      5067
##  3 accounts  3572
##  4 it’s      2678
##  5 love      2438
##  6 update    2318
##  7 budget    2313
##  8 account   2270
##  9 time      2061
## 10 credit    1704
## # ... with 9,681 more rows

Looking at the most frequent words, I removed some of them which were not necessary for analysis such as “app” and “mint”.

mint_tidy <- mint_selected %>%
  select(userName, word) %>% 
  filter(!word == "app" & !word == "mint" & !word == "it’s" & !word == "i’ve" & !word == "i’m" & !word == "can’t" & !word == "don’t" & !word == "doesn’t" & !word == "won’t" & !word == "like" & !word == "see" & !word == "one" & !word == "just" & !word == "now" & !word == "use" & !word == "account" & !word == "accounts" & !word == "finances" & !word =="financial")
mint_count <- mint_tidy %>%
  count(word, sort = TRUE)

PS. I realized that there is a massive difference between apostrophe ’ and single-quote ’ in R. It took me some time to find it out, but if you want to filter a word like “I’m”, you have to use an apostrophe; otherwise, it would not work!

Terms like “budget,” “update” and “love” are what we would have expected to see from reviewers. However, the term “time” is not so intuitive and worth a quick look as well. I select 10 random samples of reviews using the sample_n() function for the term “time”.

## # A tibble: 10 x 1
##    combined                                                                     
##    <chr>                                                                        
##  1 "Used to be good This app and its support have gone the way of so many good ~
##  2 "Renewed Interface is not intuitive I think Mint actually updated the interf~
##  3 "Not as intuitive as phone or web app The layout and functionality of the iP~
##  4 "Good and bad I really like the layout and the quick view. The ability to co~
##  5 "Ok Devs, time to update!! Been using Mint for years now. Amazing app, good ~
##  6 "Good, but could be so much better I switched to Mint from Personal Cap a fe~
##  7 "stash i love mint and as advertised on mint, i use stash as well. since sta~
##  8 "Looking for a real update The app is good; it works well most of the time. ~
##  9 "Great idea, poor execution Everyone knows Mint’s basic purpose and it’s a g~
## 10 "Super helpful for tracking expenses I use it all the time"

Creating a Document Term Matrix

We will consider each individual review as a unique “document.” To do this, we can use the attribute “userName,” which is unique for each user. Given that, we would create a document term matrix by counting the number of times each word appears in the review of each user.

mint_dtm <- mint_tidy %>%
  count(userName, word) %>%
  cast_dtm(userName, word, n)

class(mint_dtm)
## [1] "DocumentTermMatrix"    "simple_triplet_matrix"
mint_dtm
## <<DocumentTermMatrix (documents: 9994, terms: 9678)>>
## Non-/sparse entries: 158369/96563563
## Sparsity           : 100%
## Maximal term length: 23
## Weighting          : term frequency (tf)

Processing and Stemming for STM

Let’s go ahead and prepare our reviews for structural topic modeling:

temp <- textProcessor(mint_reviews$combined,
                    metadata = mint_reviews,  
                    lowercase=TRUE, 
                    removestopwords=TRUE, 
                    removenumbers=TRUE,  
                    removepunctuation=TRUE, 
                    wordLengths=c(3,Inf),
                    stem=TRUE,
                    onlycharacter= FALSE, 
                    striphtml=TRUE, 
                    customstopwords=c("app","mint","it’s", "i’ve","i’m","can’t","don’t", "doesn’t","won’t","like","see", "one","just" ,"now","use","account","accounts","finances","financial"))
## Building corpus... 
## Converting to Lower Case... 
## Removing punctuation... 
## Removing stopwords... 
## Remove Custom Stopwords...
## Removing numbers... 
## Stemming... 
## Creating Output...
meta <- temp$meta
vocab <- temp$vocab
docs <- temp$documents

Stemming Tidy Text

Let’s take a look at the original words and the stem that are produced:

stemmed_mint <- mint_reviews %>%
  unnest_tokens(output = word, input = combined) %>%
  anti_join(stop_words, by = "word") %>%
  filter(!word == "app" & !word == "mint" & !word == "it’s" & !word == "i’ve" & !word == "i’m" & !word == "can’t" & !word == "don’t" & !word == "doesn’t" & !word == "won’t" & !word == "like" & !word == "see" & !word == "one" & !word == "just" & !word == "now" & !word == "use" & !word == "account" & !word == "accounts" & !word == "finances" & !word =="financial") %>%
  mutate(stem = wordStem(word))

stemmed_mint
## # A tibble: 179,771 x 5
##    userName  title                       review                     word   stem 
##    <chr>     <chr>                       <chr>                      <chr>  <chr>
##  1 Mere67193 Using for years and love it "Hi there - I have been u~ love   love 
##  2 Mere67193 Using for years and love it "Hi there - I have been u~ budge~ budg~
##  3 Mere67193 Using for years and love it "Hi there - I have been u~ keepi~ keep 
##  4 Mere67193 Using for years and love it "Hi there - I have been u~ track  track
##  5 Mere67193 Using for years and love it "Hi there - I have been u~ credit cred~
##  6 Mere67193 Using for years and love it "Hi there - I have been u~ score  score
##  7 Mere67193 Using for years and love it "Hi there - I have been u~ couple coupl
##  8 Mere67193 Using for years and love it "Hi there - I have been u~ score  score
##  9 Mere67193 Using for years and love it "Hi there - I have been u~ grown  grown
## 10 Mere67193 Using for years and love it "Hi there - I have been u~ 50     50   
## # ... with 179,761 more rows

We can see that words like “budgeting” that occur frequently in our discussions have been reduced to the word stem “budget”.

## <<DocumentTermMatrix (documents: 9994, terms: 6394)>>
## Non-/sparse entries: 152439/63749197
## Sparsity           : 100%
## Maximal term length: 23
## Weighting          : term frequency (tf)
## <<DocumentTermMatrix (documents: 9994, terms: 9678)>>
## Non-/sparse entries: 158369/96563563
## Sparsity           : 100%
## Maximal term length: 23
## Weighting          : term frequency (tf)
## # A tibble: 6,394 x 2
##    stem         n
##    <chr>    <int>
##  1 budget    4017
##  2 updat     3554
##  3 love      2701
##  4 time      2651
##  5 track     2178
##  6 transact  2068
##  7 spend     2047
##  8 bill      2033
##  9 month     1788
## 10 bank      1754
## # ... with 6,384 more rows

Considering the fact that stemmed version 3,000 less rows, it may be more reasonable to use it rather than unstemmed data.

3. MODEL

3a. Fitting a Topic Modeling with LDA

I ran findingK() function and found out 7 is reasonable for number of topics in reviews to keep the semantic coherence high.

# n_distinct(ts_forum_data$forum_name)

mint_lda <- LDA(mint_dtm, 
                  k = 7, 
                  control = list(seed = 588)
                  )

mint_lda
## A LDA_VEM topic model with 7 topics.

3b. Fitting a Structural Topic Model

For using STM, these elements should be extracted.

docs <- temp$documents 
meta <- temp$meta 
vocab <- temp$vocab 

And now use these elements to fit the model using the same number of topics for K that we specified for our LDA topic model.

mint_stm <- stm(documents=docs, 
         data=meta,
         vocab=vocab, 
         K=7,
         max.em.its=25,
         verbose = FALSE)
mint_stm
## A topic model with 7 topics, 10000 documents and a 8025 word dictionary.

Let’s show the first 5 terms in each topic:

plot.STM(mint_stm, n = 10)

3c. Finding K

The FindTopicsNumber Function

I use this function to estimate the most preferable number of topics.

k_metrics <- FindTopicsNumber(
  mint_dtm,
  topics = seq(5, 50, by = 5),
  metrics = "Griffiths2004",
  method = "Gibbs",
  control = list(),
  mc.cores = NA,
  return_models = FALSE,
  verbose = FALSE,
  libpath = NULL
)

FindTopicsNumber_plot(k_metrics)

As we see, k = 35 seems to be the best choice, but based on the results I obtained from findingK() function, 7 is more likely to provide good results. My guess is the same as I do not think there would be a variety of topics in reviews.

The LDAvis Explorer

We can also use toLDAvis() function to generate visualizations for exploring topic and word distributions using LDAvis topic browser:

toLDAvis(mod = mint_stm, docs = docs)
## Loading required namespace: servr

4. EXPLORE

4a. Exploring Beta Values

Let’s take a look at the 5 most likely terms assigned to each topic.

terms(mint_lda, 10)
##       Topic 1        Topic 2        Topic 3      Topic 4        Topic 5   
##  [1,] "transactions" "update"       "love"       "budget"       "love"    
##  [2,] "bank"         "transactions" "time"       "spending"     "budget"  
##  [3,] "budget"       "fix"          "credit"     "transactions" "money"   
##  [4,] "month"        "track"        "bills"      "update"       "easy"    
##  [5,] "time"         "issues"       "update"     "money"        "bills"   
##  [6,] "track"        "time"         "bank"       "track"        "credit"  
##  [7,] "bills"        "budgets"      "budget"     "add"          "add"     
##  [8,] "easy"         "month"        "track"      "change"       "bill"    
##  [9,] "version"      "helpful"      "categories" "card"         "manually"
## [10,] "support"      "bank"         "card"       "information"  "feature" 
##       Topic 6      Topic 7      
##  [1,] "love"       "spending"   
##  [2,] "update"     "money"      
##  [3,] "time"       "credit"     
##  [4,] "track"      "user"       
##  [5,] "budget"     "support"    
##  [6,] "fix"        "months"     
##  [7,] "spending"   "information"
##  [8,] "categories" "track"      
##  [9,] "budgeting"  "month"      
## [10,] "version"    "link"

Now let’s look at this information visually:

tidy_lda <- tidy(mint_lda)
top_terms <- tidy_lda %>%
  group_by(topic) %>%
  slice_max(beta, n = 10, with_ties = FALSE) %>%
  ungroup() %>%
  arrange(topic, -beta)

top_terms %>%
  mutate(term = reorder_within(term, beta, topic)) %>%
  group_by(topic, term) %>%    
  arrange(desc(beta)) %>%  
  ungroup() %>%
  ggplot(aes(beta, term, fill = as.factor(topic))) +
  geom_col(show.legend = FALSE) +
  scale_y_reordered() +
  labs(title = "Top 5 terms in each LDA topic",
       x = expression(beta), y = NULL) +
  facet_wrap(~ topic, ncol = 4, scales = "free")

4b. Exploring Gamma Values

We can combine our beta and gamma values to understand the topic prevalence in our corpus, and which words contribute to each topic.

td_beta <- tidy(mint_lda)
td_gamma <- tidy(mint_lda, matrix = "gamma")
top_terms <- td_beta %>%
  arrange(beta) %>%
  group_by(topic) %>%
  top_n(10, beta) %>%
  arrange(-beta) %>%
  select(topic, term) %>%
  summarise(terms = list(term)) %>%
  mutate(terms = map(terms, paste, collapse = ", ")) %>% 
  unnest()
## Warning: `cols` is now required when using unnest().
## Please use `cols = c(terms)`
gamma_terms <- td_gamma %>%
  group_by(topic) %>%
  summarise(gamma = mean(gamma)) %>%
  arrange(desc(gamma)) %>%
  left_join(top_terms, by = "topic") %>%
  mutate(topic = paste0("Topic ", topic),
         topic = reorder(topic, gamma))

gamma_terms %>%
  select(topic, gamma, terms) %>%
  kable(digits = 3, 
        col.names = c("Topic", "Expected topic proportion", "Top 10 terms"))
Topic Expected topic proportion Top 10 terms
Topic 2 0.143 update, transactions, fix, track, issues, time, budgets, month, helpful, bank
Topic 4 0.143 budget, spending, transactions, update, money, track, add, change, card, information
Topic 7 0.143 spending, money, credit, user, support, months, information, track, month, link
Topic 6 0.143 love, update, time, track, budget, fix, spending, categories, budgeting, version
Topic 3 0.143 love, time, credit, bills, update, bank, budget, track, categories, card
Topic 1 0.143 transactions, bank, budget, month, time, track, bills, easy, version, support
Topic 5 0.143 love, budget, money, easy, bills, credit, add, bill, manually, feature

4c. Discussion

The first thing that came to my mind after looking at the results of LDA and STM was that the topics of LDA have more terms in common, while there is more variety in the words of STM topics. Having this said, I think the terms included in LDA topics could be more easily interpreted compared to STM topics as they were semantically more coherent than STM at least in analyzing this dataset. My conclusion for this comparison is that if we want terms in each topic make more sense aggregately, we should go for LDA. But if our goal is to find a hierarchy of importance in our topics we should choose STM. As an example, topic 2 in the STM model contains basically the most important words in the corpus and topic 4 shows nearly the second group of most important words.

I would like to categorize the most important topics discussed in the reviews as follows:

  1. Tracking budget and expenses: we encounter terms related to this category a lot in the LDA model such as topics 2, 7, and 5. We can also see terms relevant to budget tracking in topic 2 of STM.

  2. Category of transactions: the words related to this topic can be seen in topics 3 and 6 of LDA and topic 2 of STM. There is a feature in Mint that would automatically categorize the type of transaction you make. This has always seemed so useful to me, and I was very curious to see whether I could find something associated with it in topics or not, and interestingly, I could. This feature is especially helpful when users like to plan their expenses in a specific category such as shopping, entertainment, bills, health, etc.

  3. Application features, updates, and issues: this was highly expected to appear in topics as well. Topics 2, 4, 1, and 5 of LDA have terms directly related to the features and issues of Mint. Even though we see the term “fix” in some of these topics, we cannot discover whether users are talking about fixed issues or existing problems based on these models. We can also see some words relevant to this subject in topics 4, 6, and 3 of STM.

The last thing that I like to investigate is the existence of any hidden or apparent pattern in topics that could correlate to the success of the application. By success, I mean that many websites/online magazines such as Forbes, CNBC, and Investopedia have mentioned Mint as the best or one of the best applications for financial management. The interesting thing that I encountered was that in the models of both LDA and STM, almost no negative word can be found. For instance, in topics 4, 7, 3, 1, and 5 of LDA, I could not find a single negative word. I think this backs the high popularity of this application. To support this hypothesis, I should study the pattern of topics for applications with low ratings to see how different they are.

All in all, this was one of the most enjoyable projects I did for the course so far and I will most probably analyze the reviews of more applications in the future.

LS0tDQp0aXRsZTogIldlZWsgNyBJbmRlcGVuZGVudCBBbmFseXNpczogVG9waWMgTW9kZWxpbmciDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogVFJVRQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIyAwLiBJTlRST0RVQ1RJT04NCg0KRm9yIHRoaXMgaW5kZXBlbmRlbnQgc3R1ZHksIEkgZGVjaWRlZCB0byBhbmFseXplIGEgbmV3IGRhdGFzZXQNCmNvbXByaXNpbmcgdGhlIHJldmlld3Mgb2YgQXBwIFN0b3JlIHVzZXJzIGFib3V0IGEgcG9wdWxhciBmaW5hbmNpYWwNCm1hbmFnZW1lbnQgYXBwbGljYXRpb24sIG5hbWVkICJNaW50LiIgVGhpcyBhcHBsaWNhdGlvbiBoZWxwcyB1c2VycyBrZWVwDQp0cmFjayBvZiB0aGVpciBpbmNvbWUgYW5kIGV4cGVuc2VzLiBJdCBhbHNvIHByb3ZpZGVzIHRoZW0gaW5zaWdodHMgaW50bw0KdGhlaXIgY3JlZGl0IHNjb3JlLCBiaWxscyBhbmQgYmFsYW5jZXMuIEFsdGhvdWdoIHRoZXJlIGFyZSBzbyBtYW55DQpjb21wZXRpdG9ycyBmb3IgTWludCwgaXQgaGFzIGJlZW4gYWJsZSB0byByaXNlIHRvIG9uZSBvZiB0aGUgdG9wDQpmaW5hbmNpYWwgbWFuYWdlbWVudCBhcHBzIG9uIHRoZSBtYXJrZXQuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAxLiBQUkVQQVJFDQoNCiMjIyAxYi4gR3VpZGluZyBRdWVzdGlvbnMNCg0KRHVyaW5nIHRoaXMgZGF0YSBhbmFseXNpcywgSSBsaWtlZCB0byBmaW5kIGFuIGFuc3dlciB0byB0aHJlZSBxdWVzdGlvbnM6DQoNCjEuICBXaGF0IHRvcGljcyBhcmUgbW9zdGx5IGRpc2N1c3NlZCBpbiB0aGUgcmV2aWV3cyBvZiBhIHN1Y2Nlc3NmdWwNCiAgICBhcHBsaWNhdGlvbj8NCg0KMi4gIFdoYXQgZmluYW5jZS1yZWxhdGVkIHRvcGljcyB3aWxsIGFwcGVhciBtb3JlIGluIHRoZXNlIHJldmlld3M/DQoNCjMuICBIb3cgZGlmZmVyZW50IGFyZSB0aGUgcmVzdWx0cyBvZiB0b3BpYyBtb2RlbGluZyB0ZWNobmlxdWVzDQogICAgKHN0ZW1taW5nLCBMREEgdnMgU1RNKSBmcm9tIGVhY2ggb3RoZXI/DQoNCiMjIyAxYy4gU2V0IFVwDQoNCkZpcnN0IG9mIGFsbCwgd2UgbG9hZCBwYWNrYWdlcyBuZWVkZWQgZm9yIHRoaXMgd2Fsa3Rocm91Z2guDQoNCmBgYHtyIGxvYWQtcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KFNub3diYWxsQykNCmxpYnJhcnkodG9waWNtb2RlbHMpDQpsaWJyYXJ5KHN0bSkNCmxpYnJhcnkobGRhdHVuaW5nKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoTERBdmlzKQ0KYGBgDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyAyLiBXUkFOR0xFDQoNCiMjIyAyYS4gSW1wb3J0IERhdGEgRnJvbSBNaW50DQoNCkkgcHVsbGVkIDEwLDAwMCByZXZpZXdzIG9mIE1pbnQgYXBwbGljYXRpb24gZnJvbSBBcHBsZSBTdG9yZSB1c2luZyBhDQpsaWJyYXJ5IG5hbWVkICJhcHBfc3RvcmVfc2NyYXBlciIgaW4gcHl0aG9uIGFuZCBpbXBvcnRlZCB0aGUgZGF0YSBpbnRvDQp0aGlzIHByb2plY3QuDQoNCmBgYHtyIHJlYWQtY3N2fQ0KbWludF9yZXZpZXdzIDwtIHJlYWRfY3N2KCJkYXRhL21pbnQuY3N2IiwgDQogICAgIGNvbF90eXBlcyA9IGNvbHModXNlck5hbWUgPSBjb2xfY2hhcmFjdGVyKCksDQogICAgICAgICAgICAgICAgICAgdGl0bGUgPSBjb2xfY2hhcmFjdGVyKCksIA0KICAgICAgICAgICAgICAgICAgIHJldmlldyA9IGNvbF9jaGFyYWN0ZXIoKQ0KICAgICAgICAgICAgICAgICAgICkpDQptaW50X3Jldmlld3MgPC0gc2VsZWN0KG1pbnRfcmV2aWV3cyx1c2VyTmFtZSx0aXRsZSxyZXZpZXcpDQpgYGANCg0KIyMjIDJiLiBDYXN0IGEgRG9jdW1lbnQgVGVybSBNYXRyaXgNCg0KIyMjIyBUaWR5aW5nIFRleHQNCg0KV2hlbiBJIGxvb2tlZCBhdCBkaWZmZXJlbnQgYXR0cmlidXRlcyBpbiB0aGlzIGRhdGFzZXQsIEkgZm91bmQgb3V0DQoidGl0bGUiIGNvbHVtbiBpbmNsdWRlcyB1c2VmdWwgaW5mb3JtYXRpb24uIEhlbmNlLCBJIGNvbmNhdGVuYXRlZCB0aGUNCmNvbnRlbnQgb2YgInJldmlldyIgYW5kICJ0aXRsZSIgY29sdW1ucyBhbmQgcHV0IHRoZSByZXN1bHQgaW50byBhIG5ldw0KY29sdW1uIG5hbWVkICJjb21iaW5lZCIuIFRoZW4sIHRoZSBkYXRhIGlzIGNsZWFuZWQgYW5kIHRva2VuaXplZC4NCg0KYGBge3IgdG9rZW5pemUtZm9ydW1zfQ0KbWludF9yZXZpZXdzJGNvbWJpbmVkID0gcGFzdGUobWludF9yZXZpZXdzJHRpdGxlLCBtaW50X3Jldmlld3MkcmV2aWV3LCBzZXA9IiAiKQ0KDQptaW50X3NlbGVjdGVkIDwtIG1pbnRfcmV2aWV3cyAlPiUNCiAgdW5uZXN0X3Rva2VucyhvdXRwdXQgPSB3b3JkLCBpbnB1dCA9IGNvbWJpbmVkKSAlPiUNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5ID0gIndvcmQiKQ0KDQptaW50X3NlbGVjdGVkIDwtIHNlbGVjdChtaW50X3NlbGVjdGVkLHVzZXJOYW1lLCB3b3JkKQ0KbWludF9zZWxlY3RlZCA8LSBuYS5vbWl0KG1pbnRfc2VsZWN0ZWQpDQpgYGANCg0KVGhlIG1vc3QgY29tbW9uIHdvcmRzIGluIHRoZSByZXZpZXdzIG9mIE1pbnQgYXJlOg0KDQpgYGB7ciBjb3VudC13b3Jkc30NCm1pbnRfc2VsZWN0ZWQgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNCkxvb2tpbmcgYXQgdGhlIG1vc3QgZnJlcXVlbnQgd29yZHMsIEkgcmVtb3ZlZCBzb21lIG9mIHRoZW0gd2hpY2ggd2VyZQ0Kbm90IG5lY2Vzc2FyeSBmb3IgYW5hbHlzaXMgc3VjaCBhcyAiYXBwIiBhbmQgIm1pbnQiLg0KDQpgYGB7cn0NCm1pbnRfdGlkeSA8LSBtaW50X3NlbGVjdGVkICU+JQ0KICBzZWxlY3QodXNlck5hbWUsIHdvcmQpICU+JSANCiAgZmlsdGVyKCF3b3JkID09ICJhcHAiICYgIXdvcmQgPT0gIm1pbnQiICYgIXdvcmQgPT0gIml04oCZcyIgJiAhd29yZCA9PSAiaeKAmXZlIiAmICF3b3JkID09ICJp4oCZbSIgJiAhd29yZCA9PSAiY2Fu4oCZdCIgJiAhd29yZCA9PSAiZG9u4oCZdCIgJiAhd29yZCA9PSAiZG9lc27igJl0IiAmICF3b3JkID09ICJ3b27igJl0IiAmICF3b3JkID09ICJsaWtlIiAmICF3b3JkID09ICJzZWUiICYgIXdvcmQgPT0gIm9uZSIgJiAhd29yZCA9PSAianVzdCIgJiAhd29yZCA9PSAibm93IiAmICF3b3JkID09ICJ1c2UiICYgIXdvcmQgPT0gImFjY291bnQiICYgIXdvcmQgPT0gImFjY291bnRzIiAmICF3b3JkID09ICJmaW5hbmNlcyIgJiAhd29yZCA9PSJmaW5hbmNpYWwiKQ0KYGBgDQoNCmBgYHtyfQ0KbWludF9jb3VudCA8LSBtaW50X3RpZHkgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNCioqUFMuKiogSSByZWFsaXplZCB0aGF0IHRoZXJlIGlzIGEgbWFzc2l2ZSBkaWZmZXJlbmNlIGJldHdlZW4gYXBvc3Ryb3BoZQ0KJyBhbmQgc2luZ2xlLXF1b3RlICcgaW4gUi4gSXQgdG9vayBtZSBzb21lIHRpbWUgdG8gZmluZCBpdCBvdXQsIGJ1dCBpZg0KeW91IHdhbnQgdG8gZmlsdGVyIGEgd29yZCBsaWtlICJJJ20iLCB5b3UgaGF2ZSB0byB1c2UgYW4gYXBvc3Ryb3BoZTsNCm90aGVyd2lzZSwgaXQgd291bGQgbm90IHdvcmshDQoNClRlcm1zIGxpa2UgImJ1ZGdldCwiICJ1cGRhdGUiIGFuZCAibG92ZSIgYXJlIHdoYXQgd2Ugd291bGQgaGF2ZSBleHBlY3RlZA0KdG8gc2VlIGZyb20gcmV2aWV3ZXJzLiBIb3dldmVyLCB0aGUgdGVybSAidGltZSIgaXMgbm90IHNvIGludHVpdGl2ZSBhbmQNCndvcnRoIGEgcXVpY2sgbG9vayBhcyB3ZWxsLiBJIHNlbGVjdCAxMCByYW5kb20gc2FtcGxlcyBvZiByZXZpZXdzIHVzaW5nDQp0aGUgYHNhbXBsZV9uKClgIGZ1bmN0aW9uIGZvciB0aGUgdGVybSAidGltZSIuDQoNCmBgYHtyIGZpbmQtcXVvdGVzLCBlY2hvPUZBTFNFfQ0KbWludF9xdW90ZXMgPC0gbWludF9yZXZpZXdzJT4lDQogIHNlbGVjdChjb21iaW5lZCkgJT4lIA0KICBmaWx0ZXIoZ3JlcGwoJ3RpbWUnLCBjb21iaW5lZCkpDQoNCnNhbXBsZV9uKG1pbnRfcXVvdGVzLDEwKQ0KYGBgDQoNCiMjIyMgQ3JlYXRpbmcgYSBEb2N1bWVudCBUZXJtIE1hdHJpeA0KDQpXZSB3aWxsIGNvbnNpZGVyIGVhY2ggaW5kaXZpZHVhbCByZXZpZXcgYXMgYSB1bmlxdWUgImRvY3VtZW50LiIgVG8gZG8NCnRoaXMsIHdlIGNhbiB1c2UgdGhlIGF0dHJpYnV0ZSAidXNlck5hbWUsIiB3aGljaCBpcyB1bmlxdWUgZm9yIGVhY2gNCnVzZXIuIEdpdmVuIHRoYXQsIHdlIHdvdWxkIGNyZWF0ZSBhIGRvY3VtZW50IHRlcm0gbWF0cml4IGJ5IGNvdW50aW5nIHRoZQ0KbnVtYmVyIG9mIHRpbWVzIGVhY2ggd29yZCBhcHBlYXJzIGluIHRoZSByZXZpZXcgb2YgZWFjaCB1c2VyLg0KDQpgYGB7ciBjYXN0LWR0bX0NCm1pbnRfZHRtIDwtIG1pbnRfdGlkeSAlPiUNCiAgY291bnQodXNlck5hbWUsIHdvcmQpICU+JQ0KICBjYXN0X2R0bSh1c2VyTmFtZSwgd29yZCwgbikNCg0KY2xhc3MobWludF9kdG0pDQptaW50X2R0bQ0KYGBgDQoNCiMjIyMgUHJvY2Vzc2luZyBhbmQgU3RlbW1pbmcgZm9yIFNUTQ0KDQpMZXQncyBnbyBhaGVhZCBhbmQgcHJlcGFyZSBvdXIgcmV2aWV3cyBmb3Igc3RydWN0dXJhbCB0b3BpYyBtb2RlbGluZzoNCg0KYGBge3IgdGV4dFByb2Nlc3Nvcn0NCnRlbXAgPC0gdGV4dFByb2Nlc3NvcihtaW50X3Jldmlld3MkY29tYmluZWQsDQogICAgICAgICAgICAgICAgICAgIG1ldGFkYXRhID0gbWludF9yZXZpZXdzLCAgDQogICAgICAgICAgICAgICAgICAgIGxvd2VyY2FzZT1UUlVFLCANCiAgICAgICAgICAgICAgICAgICAgcmVtb3Zlc3RvcHdvcmRzPVRSVUUsIA0KICAgICAgICAgICAgICAgICAgICByZW1vdmVudW1iZXJzPVRSVUUsICANCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlcHVuY3R1YXRpb249VFJVRSwgDQogICAgICAgICAgICAgICAgICAgIHdvcmRMZW5ndGhzPWMoMyxJbmYpLA0KICAgICAgICAgICAgICAgICAgICBzdGVtPVRSVUUsDQogICAgICAgICAgICAgICAgICAgIG9ubHljaGFyYWN0ZXI9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgICAgc3RyaXBodG1sPVRSVUUsIA0KICAgICAgICAgICAgICAgICAgICBjdXN0b21zdG9wd29yZHM9YygiYXBwIiwibWludCIsIml04oCZcyIsICJp4oCZdmUiLCJp4oCZbSIsImNhbuKAmXQiLCJkb27igJl0IiwgImRvZXNu4oCZdCIsIndvbuKAmXQiLCJsaWtlIiwic2VlIiwgIm9uZSIsImp1c3QiICwibm93IiwidXNlIiwiYWNjb3VudCIsImFjY291bnRzIiwiZmluYW5jZXMiLCJmaW5hbmNpYWwiKSkNCmBgYA0KDQpgYGB7ciBzdG0taW5wdXRzfQ0KbWV0YSA8LSB0ZW1wJG1ldGENCnZvY2FiIDwtIHRlbXAkdm9jYWINCmRvY3MgPC0gdGVtcCRkb2N1bWVudHMNCmBgYA0KDQojIyMjIFN0ZW1taW5nIFRpZHkgVGV4dA0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgb3JpZ2luYWwgd29yZHMgYW5kIHRoZSBzdGVtIHRoYXQgYXJlIHByb2R1Y2VkOg0KDQpgYGB7ciB3b3JkU3RlbX0NCnN0ZW1tZWRfbWludCA8LSBtaW50X3Jldmlld3MgJT4lDQogIHVubmVzdF90b2tlbnMob3V0cHV0ID0gd29yZCwgaW5wdXQgPSBjb21iaW5lZCkgJT4lDQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikgJT4lDQogIGZpbHRlcighd29yZCA9PSAiYXBwIiAmICF3b3JkID09ICJtaW50IiAmICF3b3JkID09ICJpdOKAmXMiICYgIXdvcmQgPT0gImnigJl2ZSIgJiAhd29yZCA9PSAiaeKAmW0iICYgIXdvcmQgPT0gImNhbuKAmXQiICYgIXdvcmQgPT0gImRvbuKAmXQiICYgIXdvcmQgPT0gImRvZXNu4oCZdCIgJiAhd29yZCA9PSAid29u4oCZdCIgJiAhd29yZCA9PSAibGlrZSIgJiAhd29yZCA9PSAic2VlIiAmICF3b3JkID09ICJvbmUiICYgIXdvcmQgPT0gImp1c3QiICYgIXdvcmQgPT0gIm5vdyIgJiAhd29yZCA9PSAidXNlIiAmICF3b3JkID09ICJhY2NvdW50IiAmICF3b3JkID09ICJhY2NvdW50cyIgJiAhd29yZCA9PSAiZmluYW5jZXMiICYgIXdvcmQgPT0iZmluYW5jaWFsIikgJT4lDQogIG11dGF0ZShzdGVtID0gd29yZFN0ZW0od29yZCkpDQoNCnN0ZW1tZWRfbWludA0KYGBgDQoNCldlIGNhbiBzZWUgdGhhdCB3b3JkcyBsaWtlICJidWRnZXRpbmciIHRoYXQgb2NjdXIgZnJlcXVlbnRseSBpbiBvdXINCmRpc2N1c3Npb25zIGhhdmUgYmVlbiByZWR1Y2VkIHRvIHRoZSB3b3JkIHN0ZW0gImJ1ZGdldCIuDQoNCmBgYHtyIHN0ZW0tY291bnRzLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0Kc3RlbW1lZF9kdG0gPC0gbWludF9yZXZpZXdzICU+JQ0KICB1bm5lc3RfdG9rZW5zKG91dHB1dCA9IHdvcmQsIGlucHV0ID0gY29tYmluZWQpICU+JQ0KICBhbnRpX2pvaW4oc3RvcF93b3JkcywgYnkgPSAid29yZCIpICU+JQ0KICBmaWx0ZXIoIXdvcmQgPT0gImFwcCIgJiAhd29yZCA9PSAibWludCIgJiAhd29yZCA9PSAiaXTigJlzIiAmICF3b3JkID09ICJp4oCZdmUiICYgIXdvcmQgPT0gImnigJltIiAmICF3b3JkID09ICJjYW7igJl0IiAmICF3b3JkID09ICJkb27igJl0IiAmICF3b3JkID09ICJkb2VzbuKAmXQiICYgIXdvcmQgPT0gIndvbuKAmXQiICYgIXdvcmQgPT0gImxpa2UiICYgIXdvcmQgPT0gInNlZSIgJiAhd29yZCA9PSAib25lIiAmICF3b3JkID09ICJqdXN0IiAmICF3b3JkID09ICJub3ciICYgIXdvcmQgPT0gInVzZSIgJiAhd29yZCA9PSAiYWNjb3VudCIgJiAhd29yZCA9PSAiYWNjb3VudHMiICYgIXdvcmQgPT0gImZpbmFuY2VzIiAmICF3b3JkID09ImZpbmFuY2lhbCIpICU+JQ0KICBtdXRhdGUoc3RlbSA9IHdvcmRTdGVtKHdvcmQpKSAlPiUNCiAgY291bnQodXNlck5hbWUsIHN0ZW0sIHNvcnQgPSBUUlVFKSAlPiUNCiAgY2FzdF9kdG0odXNlck5hbWUsIHN0ZW0sIG4pDQoNCnN0ZW1tZWRfZHRtDQptaW50X2R0bQ0KDQpzdGVtX2NvdW50cyA8LSBtaW50X3Jldmlld3MgJT4lDQogIHVubmVzdF90b2tlbnMob3V0cHV0ID0gd29yZCwgaW5wdXQgPSBjb21iaW5lZCkgJT4lDQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikgJT4lDQogIGZpbHRlcighd29yZCA9PSAiYXBwIiAmICF3b3JkID09ICJtaW50IiAmICF3b3JkID09ICJpdOKAmXMiICYgIXdvcmQgPT0gImnigJl2ZSIgJiAhd29yZCA9PSAiaeKAmW0iICYgIXdvcmQgPT0gImNhbuKAmXQiICYgIXdvcmQgPT0gImRvbuKAmXQiICYgIXdvcmQgPT0gImRvZXNu4oCZdCIgJiAhd29yZCA9PSAid29u4oCZdCIgJiAhd29yZCA9PSAibGlrZSIgJiAhd29yZCA9PSAic2VlIiAmICF3b3JkID09ICJvbmUiICYgIXdvcmQgPT0gImp1c3QiICYgIXdvcmQgPT0gIm5vdyIgJiAhd29yZCA9PSAidXNlIiAmICF3b3JkID09ICJhY2NvdW50IiAmICF3b3JkID09ICJhY2NvdW50cyIgJiAhd29yZCA9PSAiZmluYW5jZXMiICYgIXdvcmQgPT0iZmluYW5jaWFsIikgJT4lDQogIG11dGF0ZShzdGVtID0gd29yZFN0ZW0od29yZCkpICU+JQ0KICBjb3VudChzdGVtLCBzb3J0ID0gVFJVRSkNCg0Kc3RlbV9jb3VudHMNCmBgYA0KDQpDb25zaWRlcmluZyB0aGUgZmFjdCB0aGF0IHN0ZW1tZWQgdmVyc2lvbiAzLDAwMCBsZXNzIHJvd3MsIGl0IG1heSBiZQ0KbW9yZSByZWFzb25hYmxlIHRvIHVzZSBpdCByYXRoZXIgdGhhbiB1bnN0ZW1tZWQgZGF0YS4NCg0KIyMgMy4gTU9ERUwNCg0KIyMjIDNhLiBGaXR0aW5nIGEgVG9waWMgTW9kZWxpbmcgd2l0aCBMREENCg0KSSByYW4gZmluZGluZ0soKSBmdW5jdGlvbiBhbmQgZm91bmQgb3V0IDcgaXMgcmVhc29uYWJsZSBmb3IgbnVtYmVyIG9mDQp0b3BpY3MgaW4gcmV2aWV3cyB0byBrZWVwIHRoZSBzZW1hbnRpYyBjb2hlcmVuY2UgaGlnaC4NCg0KYGBge3IgTERBfQ0KIyBuX2Rpc3RpbmN0KHRzX2ZvcnVtX2RhdGEkZm9ydW1fbmFtZSkNCg0KbWludF9sZGEgPC0gTERBKG1pbnRfZHRtLCANCiAgICAgICAgICAgICAgICAgIGsgPSA3LCANCiAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSA1ODgpDQogICAgICAgICAgICAgICAgICApDQoNCm1pbnRfbGRhDQpgYGANCg0KIyMjIDNiLiBGaXR0aW5nIGEgU3RydWN0dXJhbCBUb3BpYyBNb2RlbA0KDQpGb3IgdXNpbmcgU1RNLCB0aGVzZSBlbGVtZW50cyBzaG91bGQgYmUgZXh0cmFjdGVkLg0KDQpgYGB7ciBzdG0tZG9jc30NCmRvY3MgPC0gdGVtcCRkb2N1bWVudHMgDQptZXRhIDwtIHRlbXAkbWV0YSANCnZvY2FiIDwtIHRlbXAkdm9jYWIgDQpgYGANCg0KQW5kIG5vdyB1c2UgdGhlc2UgZWxlbWVudHMgdG8gZml0IHRoZSBtb2RlbCB1c2luZyB0aGUgc2FtZSBudW1iZXIgb2YNCnRvcGljcyBmb3IgKksqIHRoYXQgd2Ugc3BlY2lmaWVkIGZvciBvdXIgTERBIHRvcGljIG1vZGVsLg0KDQpgYGB7ciBzdG19DQptaW50X3N0bSA8LSBzdG0oZG9jdW1lbnRzPWRvY3MsIA0KICAgICAgICAgZGF0YT1tZXRhLA0KICAgICAgICAgdm9jYWI9dm9jYWIsIA0KICAgICAgICAgSz03LA0KICAgICAgICAgbWF4LmVtLml0cz0yNSwNCiAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkNCm1pbnRfc3RtDQpgYGANCg0KTGV0J3Mgc2hvdyB0aGUgZmlyc3QgNSB0ZXJtcyBpbiBlYWNoIHRvcGljOg0KDQpgYGB7ciBwbG90LXN0bX0NCnBsb3QuU1RNKG1pbnRfc3RtLCBuID0gMTApDQpgYGANCg0KIyMjIDNjLiBGaW5kaW5nICpLKg0KDQojIyMjIFRoZSBGaW5kVG9waWNzTnVtYmVyIEZ1bmN0aW9uDQoNCkkgdXNlIHRoaXMgZnVuY3Rpb24gdG8gZXN0aW1hdGUgdGhlIG1vc3QgcHJlZmVyYWJsZSBudW1iZXIgb2YgdG9waWNzLg0KDQpgYGB7ciBmaW5kLXRvcGljLCBldmFsPUZBTFNFfQ0Ka19tZXRyaWNzIDwtIEZpbmRUb3BpY3NOdW1iZXIoDQogIG1pbnRfZHRtLA0KICB0b3BpY3MgPSBzZXEoNSwgNTAsIGJ5ID0gNSksDQogIG1ldHJpY3MgPSAiR3JpZmZpdGhzMjAwNCIsDQogIG1ldGhvZCA9ICJHaWJicyIsDQogIGNvbnRyb2wgPSBsaXN0KCksDQogIG1jLmNvcmVzID0gTkEsDQogIHJldHVybl9tb2RlbHMgPSBGQUxTRSwNCiAgdmVyYm9zZSA9IEZBTFNFLA0KICBsaWJwYXRoID0gTlVMTA0KKQ0KDQpGaW5kVG9waWNzTnVtYmVyX3Bsb3Qoa19tZXRyaWNzKQ0KYGBgDQoNCkFzIHdlIHNlZSwgayA9IDM1IHNlZW1zIHRvIGJlIHRoZSBiZXN0IGNob2ljZSwgYnV0IGJhc2VkIG9uIHRoZSByZXN1bHRzDQpJIG9idGFpbmVkIGZyb20gZmluZGluZ0soKSBmdW5jdGlvbiwgNyBpcyBtb3JlIGxpa2VseSB0byBwcm92aWRlIGdvb2QNCnJlc3VsdHMuIE15IGd1ZXNzIGlzIHRoZSBzYW1lIGFzIEkgZG8gbm90IHRoaW5rIHRoZXJlIHdvdWxkIGJlIGEgdmFyaWV0eQ0Kb2YgdG9waWNzIGluIHJldmlld3MuDQoNCiMjIyMgVGhlIExEQXZpcyBFeHBsb3Jlcg0KDQpXZSBjYW4gYWxzbyB1c2UgYHRvTERBdmlzKClgIGZ1bmN0aW9uIHRvIGdlbmVyYXRlIHZpc3VhbGl6YXRpb25zIGZvcg0KZXhwbG9yaW5nIHRvcGljIGFuZCB3b3JkIGRpc3RyaWJ1dGlvbnMgdXNpbmcgYExEQXZpc2AgdG9waWMgYnJvd3NlcjoNCg0KYGBge3IgTERBdmlzfQ0KdG9MREF2aXMobW9kID0gbWludF9zdG0sIGRvY3MgPSBkb2NzKQ0KYGBgDQoNCiMjIDQuIEVYUExPUkUNCg0KIyMjIDRhLiBFeHBsb3JpbmcgQmV0YSBWYWx1ZXMNCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIDUgbW9zdCBsaWtlbHkgdGVybXMgYXNzaWduZWQgdG8gZWFjaCB0b3BpYy4NCg0KYGBge3IgdGVybXN9DQp0ZXJtcyhtaW50X2xkYSwgMTApDQpgYGANCg0KTm93IGxldCdzIGxvb2sgYXQgdGhpcyBpbmZvcm1hdGlvbiB2aXN1YWxseToNCg0KYGBge3IgdG9wX3Rlcm1zfQ0KdGlkeV9sZGEgPC0gdGlkeShtaW50X2xkYSkNCnRvcF90ZXJtcyA8LSB0aWR5X2xkYSAlPiUNCiAgZ3JvdXBfYnkodG9waWMpICU+JQ0KICBzbGljZV9tYXgoYmV0YSwgbiA9IDEwLCB3aXRoX3RpZXMgPSBGQUxTRSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgYXJyYW5nZSh0b3BpYywgLWJldGEpDQoNCnRvcF90ZXJtcyAlPiUNCiAgbXV0YXRlKHRlcm0gPSByZW9yZGVyX3dpdGhpbih0ZXJtLCBiZXRhLCB0b3BpYykpICU+JQ0KICBncm91cF9ieSh0b3BpYywgdGVybSkgJT4lICAgIA0KICBhcnJhbmdlKGRlc2MoYmV0YSkpICU+JSAgDQogIHVuZ3JvdXAoKSAlPiUNCiAgZ2dwbG90KGFlcyhiZXRhLCB0ZXJtLCBmaWxsID0gYXMuZmFjdG9yKHRvcGljKSkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBzY2FsZV95X3Jlb3JkZXJlZCgpICsNCiAgbGFicyh0aXRsZSA9ICJUb3AgNSB0ZXJtcyBpbiBlYWNoIExEQSB0b3BpYyIsDQogICAgICAgeCA9IGV4cHJlc3Npb24oYmV0YSksIHkgPSBOVUxMKSArDQogIGZhY2V0X3dyYXAofiB0b3BpYywgbmNvbCA9IDQsIHNjYWxlcyA9ICJmcmVlIikNCmBgYA0KDQojIyMgNGIuIEV4cGxvcmluZyBHYW1tYSBWYWx1ZXMNCg0KV2UgY2FuIGNvbWJpbmUgb3VyIGJldGEgYW5kIGdhbW1hIHZhbHVlcyB0byB1bmRlcnN0YW5kIHRoZSB0b3BpYw0KcHJldmFsZW5jZSBpbiBvdXIgY29ycHVzLCBhbmQgd2hpY2ggd29yZHMgY29udHJpYnV0ZSB0byBlYWNoIHRvcGljLg0KDQpgYGB7ciBwcmV2YWxlbmNlX3RhYmxlfQ0KdGRfYmV0YSA8LSB0aWR5KG1pbnRfbGRhKQ0KdGRfZ2FtbWEgPC0gdGlkeShtaW50X2xkYSwgbWF0cml4ID0gImdhbW1hIikNCnRvcF90ZXJtcyA8LSB0ZF9iZXRhICU+JQ0KICBhcnJhbmdlKGJldGEpICU+JQ0KICBncm91cF9ieSh0b3BpYykgJT4lDQogIHRvcF9uKDEwLCBiZXRhKSAlPiUNCiAgYXJyYW5nZSgtYmV0YSkgJT4lDQogIHNlbGVjdCh0b3BpYywgdGVybSkgJT4lDQogIHN1bW1hcmlzZSh0ZXJtcyA9IGxpc3QodGVybSkpICU+JQ0KICBtdXRhdGUodGVybXMgPSBtYXAodGVybXMsIHBhc3RlLCBjb2xsYXBzZSA9ICIsICIpKSAlPiUgDQogIHVubmVzdCgpDQoNCmdhbW1hX3Rlcm1zIDwtIHRkX2dhbW1hICU+JQ0KICBncm91cF9ieSh0b3BpYykgJT4lDQogIHN1bW1hcmlzZShnYW1tYSA9IG1lYW4oZ2FtbWEpKSAlPiUNCiAgYXJyYW5nZShkZXNjKGdhbW1hKSkgJT4lDQogIGxlZnRfam9pbih0b3BfdGVybXMsIGJ5ID0gInRvcGljIikgJT4lDQogIG11dGF0ZSh0b3BpYyA9IHBhc3RlMCgiVG9waWMgIiwgdG9waWMpLA0KICAgICAgICAgdG9waWMgPSByZW9yZGVyKHRvcGljLCBnYW1tYSkpDQoNCmdhbW1hX3Rlcm1zICU+JQ0KICBzZWxlY3QodG9waWMsIGdhbW1hLCB0ZXJtcykgJT4lDQogIGthYmxlKGRpZ2l0cyA9IDMsIA0KICAgICAgICBjb2wubmFtZXMgPSBjKCJUb3BpYyIsICJFeHBlY3RlZCB0b3BpYyBwcm9wb3J0aW9uIiwgIlRvcCAxMCB0ZXJtcyIpKQ0KYGBgDQoNCiMjIyA0Yy4gRGlzY3Vzc2lvbg0KDQpUaGUgZmlyc3QgdGhpbmcgdGhhdCBjYW1lIHRvIG15IG1pbmQgYWZ0ZXIgbG9va2luZyBhdCB0aGUgcmVzdWx0cyBvZiBMREENCmFuZCBTVE0gd2FzIHRoYXQgdGhlIHRvcGljcyBvZiBMREEgaGF2ZSBtb3JlIHRlcm1zIGluIGNvbW1vbiwgd2hpbGUNCnRoZXJlIGlzIG1vcmUgdmFyaWV0eSBpbiB0aGUgd29yZHMgb2YgU1RNIHRvcGljcy4gSGF2aW5nIHRoaXMgc2FpZCwgSQ0KdGhpbmsgdGhlIHRlcm1zIGluY2x1ZGVkIGluIExEQSB0b3BpY3MgY291bGQgYmUgbW9yZSBlYXNpbHkgaW50ZXJwcmV0ZWQNCmNvbXBhcmVkIHRvIFNUTSB0b3BpY3MgYXMgdGhleSB3ZXJlIHNlbWFudGljYWxseSBtb3JlIGNvaGVyZW50IHRoYW4gU1RNDQphdCBsZWFzdCBpbiBhbmFseXppbmcgdGhpcyBkYXRhc2V0LiBNeSBjb25jbHVzaW9uIGZvciB0aGlzIGNvbXBhcmlzb24gaXMNCnRoYXQgaWYgd2Ugd2FudCB0ZXJtcyBpbiBlYWNoIHRvcGljIG1ha2UgbW9yZSBzZW5zZSBhZ2dyZWdhdGVseSwgd2UNCnNob3VsZCBnbyBmb3IgTERBLiBCdXQgaWYgb3VyIGdvYWwgaXMgdG8gZmluZCBhIGhpZXJhcmNoeSBvZiBpbXBvcnRhbmNlDQppbiBvdXIgdG9waWNzIHdlIHNob3VsZCBjaG9vc2UgU1RNLiBBcyBhbiBleGFtcGxlLCB0b3BpYyAyIGluIHRoZSBTVE0NCm1vZGVsIGNvbnRhaW5zIGJhc2ljYWxseSB0aGUgbW9zdCBpbXBvcnRhbnQgd29yZHMgaW4gdGhlIGNvcnB1cyBhbmQNCnRvcGljIDQgc2hvd3MgbmVhcmx5IHRoZSBzZWNvbmQgZ3JvdXAgb2YgbW9zdCBpbXBvcnRhbnQgd29yZHMuDQoNCkkgd291bGQgbGlrZSB0byBjYXRlZ29yaXplIHRoZSBtb3N0IGltcG9ydGFudCB0b3BpY3MgZGlzY3Vzc2VkIGluIHRoZQ0KcmV2aWV3cyBhcyBmb2xsb3dzOg0KDQoxLiAgKipUcmFja2luZyBidWRnZXQgYW5kIGV4cGVuc2VzOioqIHdlIGVuY291bnRlciB0ZXJtcyByZWxhdGVkIHRvIHRoaXMNCiAgICBjYXRlZ29yeSBhIGxvdCBpbiB0aGUgTERBIG1vZGVsIHN1Y2ggYXMgdG9waWNzIDIsIDcsIGFuZCA1LiBXZSBjYW4NCiAgICBhbHNvIHNlZSB0ZXJtcyByZWxldmFudCB0byBidWRnZXQgdHJhY2tpbmcgaW4gdG9waWMgMiBvZiBTVE0uDQoNCjIuICAqKkNhdGVnb3J5IG9mIHRyYW5zYWN0aW9uczoqKiB0aGUgd29yZHMgcmVsYXRlZCB0byB0aGlzIHRvcGljIGNhbiBiZQ0KICAgIHNlZW4gaW4gdG9waWNzIDMgYW5kIDYgb2YgTERBIGFuZCB0b3BpYyAyIG9mIFNUTS4gVGhlcmUgaXMgYSBmZWF0dXJlDQogICAgaW4gTWludCB0aGF0IHdvdWxkIGF1dG9tYXRpY2FsbHkgY2F0ZWdvcml6ZSB0aGUgdHlwZSBvZiB0cmFuc2FjdGlvbg0KICAgIHlvdSBtYWtlLiBUaGlzIGhhcyBhbHdheXMgc2VlbWVkIHNvIHVzZWZ1bCB0byBtZSwgYW5kIEkgd2FzIHZlcnkNCiAgICBjdXJpb3VzIHRvIHNlZSB3aGV0aGVyIEkgY291bGQgZmluZCBzb21ldGhpbmcgYXNzb2NpYXRlZCB3aXRoIGl0IGluDQogICAgdG9waWNzIG9yIG5vdCwgYW5kIGludGVyZXN0aW5nbHksIEkgY291bGQuIFRoaXMgZmVhdHVyZSBpcw0KICAgIGVzcGVjaWFsbHkgaGVscGZ1bCB3aGVuIHVzZXJzIGxpa2UgdG8gcGxhbiB0aGVpciBleHBlbnNlcyBpbiBhDQogICAgc3BlY2lmaWMgY2F0ZWdvcnkgc3VjaCBhcyBzaG9wcGluZywgZW50ZXJ0YWlubWVudCwgYmlsbHMsIGhlYWx0aCwNCiAgICBldGMuDQoNCjMuICAqKkFwcGxpY2F0aW9uIGZlYXR1cmVzLCB1cGRhdGVzLCBhbmQgaXNzdWVzOioqIHRoaXMgd2FzIGhpZ2hseQ0KICAgIGV4cGVjdGVkIHRvIGFwcGVhciBpbiB0b3BpY3MgYXMgd2VsbC4gVG9waWNzIDIsIDQsIDEsIGFuZCA1IG9mIExEQQ0KICAgIGhhdmUgdGVybXMgZGlyZWN0bHkgcmVsYXRlZCB0byB0aGUgZmVhdHVyZXMgYW5kIGlzc3VlcyBvZiBNaW50LiBFdmVuDQogICAgdGhvdWdoIHdlIHNlZSB0aGUgdGVybSAiZml4IiBpbiBzb21lIG9mIHRoZXNlIHRvcGljcywgd2UgY2Fubm90DQogICAgZGlzY292ZXIgd2hldGhlciB1c2VycyBhcmUgdGFsa2luZyBhYm91dCBmaXhlZCBpc3N1ZXMgb3IgZXhpc3RpbmcNCiAgICBwcm9ibGVtcyBiYXNlZCBvbiB0aGVzZSBtb2RlbHMuIFdlIGNhbiBhbHNvIHNlZSBzb21lIHdvcmRzIHJlbGV2YW50DQogICAgdG8gdGhpcyBzdWJqZWN0IGluIHRvcGljcyA0LCA2LCBhbmQgMyBvZiBTVE0uDQoNClRoZSBsYXN0IHRoaW5nIHRoYXQgSSBsaWtlIHRvIGludmVzdGlnYXRlIGlzIHRoZSBleGlzdGVuY2Ugb2YgYW55IGhpZGRlbg0Kb3IgYXBwYXJlbnQgcGF0dGVybiBpbiB0b3BpY3MgdGhhdCBjb3VsZCBjb3JyZWxhdGUgdG8gdGhlIHN1Y2Nlc3Mgb2YgdGhlDQphcHBsaWNhdGlvbi4gQnkgc3VjY2VzcywgSSBtZWFuIHRoYXQgbWFueSB3ZWJzaXRlcy9vbmxpbmUgbWFnYXppbmVzIHN1Y2gNCmFzIEZvcmJlcywgQ05CQywgYW5kIEludmVzdG9wZWRpYSBoYXZlIG1lbnRpb25lZCBNaW50IGFzIHRoZSBiZXN0IG9yIG9uZQ0Kb2YgdGhlIGJlc3QgYXBwbGljYXRpb25zIGZvciBmaW5hbmNpYWwgbWFuYWdlbWVudC4gVGhlIGludGVyZXN0aW5nIHRoaW5nDQp0aGF0IEkgZW5jb3VudGVyZWQgd2FzIHRoYXQgaW4gdGhlIG1vZGVscyBvZiBib3RoIExEQSBhbmQgU1RNLCBhbG1vc3Qgbm8NCm5lZ2F0aXZlIHdvcmQgY2FuIGJlIGZvdW5kLiBGb3IgaW5zdGFuY2UsIGluIHRvcGljcyA0LCA3LCAzLCAxLCBhbmQgNSBvZg0KTERBLCBJIGNvdWxkIG5vdCBmaW5kIGEgc2luZ2xlIG5lZ2F0aXZlIHdvcmQuIEkgdGhpbmsgdGhpcyBiYWNrcyB0aGUNCmhpZ2ggcG9wdWxhcml0eSBvZiB0aGlzIGFwcGxpY2F0aW9uLiBUbyBzdXBwb3J0IHRoaXMgaHlwb3RoZXNpcywgSQ0Kc2hvdWxkIHN0dWR5IHRoZSBwYXR0ZXJuIG9mIHRvcGljcyBmb3IgYXBwbGljYXRpb25zIHdpdGggbG93IHJhdGluZ3MgdG8NCnNlZSBob3cgZGlmZmVyZW50IHRoZXkgYXJlLg0KDQpBbGwgaW4gYWxsLCB0aGlzIHdhcyBvbmUgb2YgdGhlIG1vc3QgZW5qb3lhYmxlIHByb2plY3RzIEkgZGlkIGZvciB0aGUNCmNvdXJzZSBzbyBmYXIgYW5kIEkgd2lsbCBtb3N0IHByb2JhYmx5IGFuYWx5emUgdGhlIHJldmlld3Mgb2YgbW9yZQ0KYXBwbGljYXRpb25zIGluIHRoZSBmdXR1cmUuDQo=