### Load standardpackages
library(tidyverse) # Collection of all the good stuff like dplyr, ggplot2 ect.
library(magrittr) # For extra-piping operators (eg. %<>%)
library(tidytext)

This session

This session, we will

  1. Review NLP workflows and data structures in R
  2. Explore different type of DTM matrix type vector representations of text.
  3. Add different types of dimensionality reduction techniques to the repertoir.
  4. HAve a peak into word-embeddings
  5. Add some goddies on top

Refresher:

Bag of words model

  • In order for a computer to understand text we need to somehow find a useful representation.
  • If you need to compare different texts e.g. articles, you will probably go for keywords. These keywords may come from a keyword-list with for example 200 different keywords
  • In that case you could represent each document with a (sparse) vector with 1 for “keyword present” and 0 for “keyword absent”
  • We can also get a bit more sophoistocated and count the number of times a word from our dictionary occurs.
  • For a corpus of documents that would give us a document-term matrix.

example

Let’s try creating a bag of words model from our initial example.

text <- tibble(id = c(1:6),
               text = c('A text about cats.',
                        'A text about dogs.',
                        'And another text about a dog.',
                        'Why always writing about cats and dogs, always dogs?',
                        'There are too little text about cats but to many about dogs',
                        'Cats, cats, cats! I love cats soo much. Cats are way better than dogs'))
text_tidy <- text %>% 
  unnest_tokens(word, text, token = 'words') %>% 
  count(id, word)

The document-term matrix (DTM)

  • The simplest form of vector representation of text is a ddocument-term matrix
  • How to we get a document-term matrix now?
  • We could do it by hand, with well-known dplyr syntax (Note: only works when you have one row per unique document-word pair)
text_tidy %>%
  pivot_wider(names_from = word, values_from = n, values_fill = 0)
  • We could also use cast_dtm() to create a DTM in the format of the tm package.
text_dtm <- text_tidy %>%
  cast_dtm(id, word, n)
text_dtm 
<<DocumentTermMatrix (documents: 6, terms: 25)>>
Non-/sparse entries: 42/108
Sparsity           : 72%
Maximal term length: 7
Weighting          : term frequency (tf)
  • We can simply convert ig to a tibble. Since there exists no direct transfer function, we have to first transform it to a matrix.
  • Notice how we recover the rownames
text_dtm %>% as.matrix() %>% as_tibble(rownames = 'id') 
  • Sidenote: We can also tidy the DTM again to a tidy token-dataframe.
text_dtm %>% tidy()
  • We also can directly use a similar function to cast a sparse matrix (which we for sure then also could transform to a tibble again)
text_tidy %>% cast_sparse(row = id, column = word, value = n)
6 x 25 sparse Matrix of class "dgCMatrix"
                                                   
1 1 1 1 1 . . . . . . . . . . . . . . . . . . . . .
2 1 1 . 1 1 . . . . . . . . . . . . . . . . . . . .
3 1 1 . 1 . 1 1 1 . . . . . . . . . . . . . . . . .
4 . 1 1 . 2 1 . . 2 1 1 . . . . . . . . . . . . . .
5 . 2 1 1 1 . . . . . . 1 1 1 1 1 1 1 . . . . . . .
6 . . 5 . 1 . . . . . . 1 . . . . . . 1 1 1 1 1 1 1
  • Finally, we could just apply a text recipe here
library(recipes)
library(textrecipes)

TF-IDF - Term Frequency - Inverse Document Frequency

  • A token is important for a document if appears very often
  • A token becomes less important for comparison across a corpus if it appears all over the place in the corpus
  • Cat in a corpus of websites talking about cats is not that important

\[w_{i,j} = tf_{i,j}*log(\frac{N}{df_i})\]

  • \(w_{i,j}\) = the TF-IDF score for a term i in a document j
  • \(tf_{i,j}\) = number of occurence of term i in document j
  • \(N\) = number of documents in the corpus
  • \(df_i\) = number of documents with term i
# TFIDF weights
text_tidy %<>%
  bind_tf_idf(term = word,
              document = id,
              n = n)
  • We obviously could also cast a tf_idf weighted dtm…
text_tidy %>%
  select(id, word, tf_idf) %>%
  pivot_wider(names_from = word, values_from = tf_idf, values_fill = 0)
  • btw: this is equivalent to just running a textrecipe like that:
text %>%
  recipe(~.) %>% 
  step_tokenize(text, token = 'words') %>% # tokenize
  step_tfidf(text) %>% # TFIDF weighting
  prep() %>% juice()
  • Sidenote, when we use a POS engine such as spacyr for tokenization, we can also add recipes for lematization, filter for POS etc.
text %>%
  recipe(~.) %>% 
  step_tokenize(text, engine = "spacyr") %>%
  step_pos_filter(text, keep_tags = "NOUN") %>%
  step_lemma(text) %>%
  step_tf(text) %>%
  prep() %>%
  juice()
  • A last reminder on the powerful pairwise_xx() functions from the widyr package
  • For instance, pairwise similarities/distances
library(widyr)
text_tidy %>% pairwise_dist(id, word, tf_idf, method = "manhattan") %>%
  mutate(similarity = 1 - (distance / max(distance)) ) %>%
  select(-distance) %>%
  arrange(desc(similarity))

Dimensionality reduction techniques

rm(list=ls())
  • Ok, lets get first some more interesting data. We will work with the CORDIS project descriptions of EU Horizon 2020 projects again.
text <- read_csv('https://github.com/SDS-AAU/SDS-master/raw/master/M2/data/cordis-h2020reports.gz')
colnames(text) <- colnames(text) %>% str_to_lower()
text %<>%
  select(-x1) %>%
  rename(id = projectid) %>%
  relocate(id) %>%
  filter(language == 'en') %>%
  drop_na(id)
  • Lets create a tidy tokenlist
text_tidy <- text %>%
  rename(text = summary) %>%
  select(id, text) %>%
  unnest_tokens(word, text, token = "words")
  • some preprocessing
# preprocessing
text_tidy %<>%
  filter(str_length(word) > 2 ) %>% # Remove words with less than  3 characters
  filter(!(word %in% c('project', 'research'))) %>%
  anti_join(stop_words, by = 'word') 
  • We can also ad bigrams
text_tidy %<>%
  unnest_tokens(word, word, token = 'ngrams', n = 2, n_min = 1) %>%
  group_by(word) %>% filter(n() > 25) %>% ungroup() 
text_tidy %>%
  count(word, sort = TRUE)
  • Lets finish this up and also add TF-IDF weights
text_tidy %<>%
  count(id, word) %>%
  bind_tf_idf(term = word,
              document = id,
              n = n) %>%
  select(-tf, -idf)
  • Is there a big difference?
text_tidy %>%
  count(word, wt = tf_idf, sort = TRUE)
  • And finally, lets get a DTM dataframe
text_dtm <- text_tidy %>%
  select(id, word, n) %>%
  pivot_wider(names_from = word, values_from = n, values_fill = 0)
  • And, just in case, a TFIDF weighted version
  • We could also prepare a recipe which doe pretty much the same…
recipe_base <- text %>%
  rename(text = summary) %>%
  select(id, text) %>%
  # BAse recipe starts
  recipe(~.) %>% 
  update_role(id, new_role = "id") %>% # Update role of ID
  step_tokenize(text, token = 'words') %>% # tokenize
  step_stopwords(text, keep = FALSE) %>% # remove stopwords
  step_untokenize(text) %>% # Here we now have to first untokenize
  step_tokenize(text, token = "ngrams", options = list(n = 1, n_min = 1)) %>% # and tokenize again
  step_tokenfilter(text, min_times = 25) 
  • Sidenote

  • Here, we can further preprocess to do whatever we would like, such as obtaining a dtm

recipe_base %>% 
  step_tf(text) %>% 
  prep() %>% 
  juice() %>% 
  head(100)
text_pca <- text_dtm %>% 
  column_to_rownames('id') %>% 
  prcomp(center = TRUE, scale. = TRUE, rank. = 10)
text_pca %>% glimpse()
List of 5
 $ sdev    : num [1:499] 3.58 3.11 2.97 2.85 2.71 ...
 $ rotation: num [1:608, 1:10] 0.01761 0.00292 0.07104 -0.03197 0.01753 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:608] "aim" "allowing" "based" "blood" ...
  .. ..$ : chr [1:10] "PC1" "PC2" "PC3" "PC4" ...
 $ center  : Named num [1:608] 0.2265 0.0541 0.6733 0.0701 0.1543 ...
  ..- attr(*, "names")= chr [1:608] "aim" "allowing" "based" "blood" ...
 $ scale   : Named num [1:608] 0.537 0.235 1.049 0.445 0.856 ...
  ..- attr(*, "names")= chr [1:608] "aim" "allowing" "based" "blood" ...
 $ x       : num [1:499, 1:10] -3.259 -0.996 -1.711 -1.379 -1.575 ...
  ..- attr(*, "dimnames")=List of 2
  .. ..$ : chr [1:499] "115844" "633197" "633249" "633261" ...
  .. ..$ : chr [1:10] "PC1" "PC2" "PC3" "PC4" ...
 - attr(*, "class")= chr "prcomp"
text_pca[['x']] %>%
  head()
              PC1        PC2       PC3        PC4        PC5        PC6        PC7        PC8        PC9
115844 -3.2588756 -0.8478672  1.286494 -0.3304838  0.6253670 -0.3161002  0.1642597 -2.2037321 -0.1871126
633197 -0.9960611  4.4346346 -1.054370 -2.9036039 -1.4704782 -0.9094432 -1.6293613 -1.6208713 -0.2130936
633249 -1.7111795  3.7095798 -2.546628 -2.6489614 -2.1026976 -0.7091236  0.6661537 -0.1671077  0.3804010
633261 -1.3789058  4.1268532 -2.175831 -4.1895254 -0.8737219 -1.0295514 -1.1417048 -1.2886798 -1.7668852
633382 -1.5749243  4.2602715 -3.418563 -3.7036367 -1.1608198 -1.0926355 -1.1411842  0.2951679 -0.2694360
633571  1.2576733  1.6711741 -2.251064 -0.9706029 -1.5562738  0.6804761 -0.2523918 -0.2671309  0.9906243
             PC10
115844  0.8770474
633197  2.4375617
633249  1.2127379
633261  2.6082576
633382  1.6113388
633571 -3.8692253
text_pca %>% tidy()
  • Again, alternatively with a recipe…
recipe_pca <- recipe_base %>% # tokenize
  step_tfidf(text, prefix = '') %>% # TFIDF weighting
  step_pca(all_predictors(), num_comp = 10) %>% # PCA
  prep() 
recipe_pca %>% juice()
  • Some plotting
recipe_pca %>% juice() %>%
  ggplot(aes(x = PC01, y = PC02)) +
  geom_point() 

  • we can also use the tidy results of the recipe to do some more analytics
recipe_pca %>%
  tidy(7) %>%
  filter(component %in% paste0("PC", 1:4)) %>%
  group_by(component) %>%
    arrange(desc(value)) %>%
    slice(c(1:2, (n()-2):n())) %>%
  ungroup() %>%
  mutate(component = fct_inorder(component)) %>%
  ggplot(aes(value, terms, fill = terms)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~component, nrow = 1) +
  labs(y = NULL)

  • Note: Also check further for further dimensionlity reduction steps:
    • tep_kpca():
    • step_ica()
    • step_isomap()
    • step_nnmf()

Topic Models: Latent-Dirichlet-Allocation (LDA)

  • While we already did it somewhat ‘on-the-fly’, here a more formal introduction to LDA
  • In contrast to dimnesionality reduction techiques mostly aiming at preprocessing data or easing visualization, LDA more aims at EDA and interpretation
  • It is a generative approach to identify topics (clusters) within the word-usage in documents.
    • Topics are represented as a probability distribution over the words in the vocabulary. Hhigh probability words can be used to charactrize the topic.
    • Documents are represented as a mixture of topics.

alt text

library(topicmodels)
text_dtm <- text_tidy %>%
  cast_dtm(document = id, term = word, value = n)
text_lda <- text_dtm %>% 
  LDA(k = 6, method = "Gibbs",
      control = list(seed = 1337))
  • \(\beta\) is an output of the LDA model, indicating the propability that a word occurs in a certain topic.
  • Therefore, loking at the top probability words of a topic often gives us a good intuition regarding its properties.
# LDA output is defined for tidy(), so we can easily extract it
lda_beta <- text_lda %>% 
  tidy(matrix = "beta") 
lda_beta %>%
  # slice
  group_by(topic) %>%
  arrange(topic, desc(beta)) %>%
  slice(1:10) %>%
  ungroup() %>%
  # visualize
  mutate(term = reorder_within(term, beta, topic)) %>%
  group_by(topic, term) %>%    
  arrange(desc(beta)) %>%  
  ungroup() %>%
  ggplot(aes(term, beta, fill = as.factor(topic))) +
  geom_col(show.legend = FALSE) +
  coord_flip() +
  scale_x_reordered() +
  labs(title = "Top 10 terms in each LDA topic",
       x = NULL, y = expression(beta)) +
  facet_wrap(~ topic, ncol = 3, scales = "free")

  • Documents are represented as a mix of topics. This association of a document to a topic is captured by \(\gamma\)
lda_gamma <- text_lda %>% 
  tidy(matrix = "gamma")
lda_gamma %>%
  group_by(topic) %>%
    arrange(desc(gamma)) %>% 
    slice(1:10) %>%
  ungroup() %>%
  left_join(text %>% select(id, projectacronym) %>% mutate(id = id %>% as.character()), by = c('document' = 'id'))
  • Note that an LDA can also be performed via a recipe:
recipe_lda <- recipe_base %>% # tokenize
  step_untokenize(text) %>% # Is a bit silly, needs the full text vectors instead of tokens....
  step_lda(text, num_topics = 6) %>% # LDA
  prep() 
recipe_lda %>% juice() %>% 
  head(100)
  • As a bonus, a great way to interactively visualize LDA’s.
  • It’s a bit cumbersome in R, though…
library(LDAvis)
# A bit of a lenghty function....
topicmodels_json_ldavis <- function(fitted, doc_dtm, method = "PCA"){
  require(topicmodels); require(dplyr); require(LDAvis)
  
  # Find required quantities
  phi <- posterior(text_lda)$terms %>% as.matrix() # Topic-term distribution
  theta <- posterior(fitted)$topics %>% as.matrix() # Document-topic matrix
  
  text_tidy <- doc_dtm %>% tidy()
  vocab <- colnames(phi)
  doc_length <- tibble(document = rownames(theta)) %>% left_join(text_tidy %>% count(document, wt = count), by = 'document')
  tf <- tibble(term = vocab) %>% left_join(text_tidy %>% count(term, wt = count), by = "term") 
  
  if(method == "PCA"){mds <- jsPCA}
  if(method == "TSNE"){library(tsne); mds <- function(x){tsne(svd(x)$u)} }
  
  # Convert to json
  json_lda <- LDAvis::createJSON(phi = phi, theta = theta, vocab = vocab, doc.length = doc_length %>% pull(n), term.frequency = tf %>% pull(n),
                                 reorder.topics = FALSE, mds.method = mds,plot.opts = list(xlab = "Dim.1", ylab = "Dim.2")) 
  return(json_lda)
}
library(LDAvis)
json_lda <- topicmodels_json_ldavis(fitted = text_lda, 
                                    doc_dtm = text_dtm, 
                                    method = "TSNE")

# json_lda %>% serVis() # For direct output
# json_lda %>% serVis(out.dir = 'LDAviz') # For saving the html

Didnt really figure out how to embedd the resulting plot, but the outcome can be seen here

Embeddings (Bonus)

  • One last thing we did not venture in yet, are embeddings

  • I will not go into details here, just see it as a peak of what’s to come in further sessions.

  • The idee of word embedding is (in a nutshell) that

  • There are packages on how to train own embeddings such as text2vec, but we will for now not bother with that.

  • The only thing we will do for now is to load pretrained embeddings (GloVe, cf. Pennington et al, 2014)

library(textdata)

glove6b <- embedding_glove6b(dimensions = 100)
glove6b
  • La voila, a large pretrained embedding model for around 400k of the most common words.
  • We for now loaded the smallest of these embedding models, there exist way bigger ones.
  • Lets join it with our tidy tokenlist
word_embeddings <- text_tidy %>%
  inner_join(glove6b, by = c('word' = 'token'))
word_embeddings %>% head()
  • We could now create average document embeddings by taking the mean over all dimensions
  • We could also (even better) weight that by then word’s tfidf score.
  • These embddings could now be used for instance for some clustering or SML exercise
  • I guess you can already see how to use these embeddings in an SML model.
library(uwot) # for UMAP
embeddings_umap <- doc_embeddings  %>% 
  column_to_rownames("id") %>%
  umap(n_neighbors = 15, 
       metric = "cosine", 
       min_dist = 0.01, 
       scale = TRUE,
       verbose = TRUE, 
       n_threads = 8) 
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
embeddings_umap %<>% as.data.frame()
embeddings_umap  %>% 
  ggplot(aes(x = V1, y = V2)) + 
  geom_point(shape = 21, alpha = 0.5) 

  • Ok, we see a rather clear seperation of documents.
  • Just for fun, lets add a density based clustering (very good for spatial clustering) on top (even though we already see the results)
library(dbscan)
  • Do the hirarchical density based clustering
embeddings_hdbscan <- embeddings_umap %>% as.matrix() %>% hdbscan(minPts = 15)
  • Plot it
embeddings_umap %>% 
  bind_cols(cluster = embeddings_hdbscan$cluster %>% as.factor(), 
            prob = embeddings_hdbscan$membership_prob) %>%
  ggplot(aes(x = V1, y = V2, col = cluster)) + 
  geom_point(aes(alpha = prob), shape = 21) 

  • Note: We can also assigne the embeddings via a recipe
  • Unfortunately, we can not do a TFIDF weighting here ‘out-of-the-box’, but have to work with average embeddings instead.
recipe_embedding <- recipe_base %>% # tokenize
  step_word_embeddings(text, embeddings = glove6b, aggregation = 'mean')
recipe_embedding %>% prep() %>% juice() %>% 
  head(100)
  • Same goes for UMAP, which can be accessd in recipes via the the package embed pckage.
  • However,embed is a bit heavy in terms of dependencies, since it uses keras and tensorflow, a deep learning framewok, in the backgroubnd, and is in need to install another mini-conda enviroment.
  • If you have no experience with keras and tensorflow so far, I suggest you wait with this one until later sessions when we properly introduce it.
library(embed)
Error: package or namespace load failed for ‘embed’:
 .onLoad failed in loadNamespace() for 'tensorflow', details:
  call: py_module_import(module, convert = convert)
  error: ModuleNotFoundError: No module named 'tensorflow'
recipe_umap <- recipe_embedding %>%
  step_umap(starts_with('w_embed'), n_neighbors = 15) 
recipe_umap %>% prep() %>% juice() %>% 
  head(100)
  • So, that’s all I have for now

Summary

  • There are many ways to convert text data into a vector representation.
  • These range from simple and weighted bags-of-words, to topic models, over different types of dimensionality reduction to finally word and document embeddings.
  • All of them are useful, depending on the purpose.

Endnotes

Packages & Ecosystem

  • textrecipes: Text preprocessing recipes
  • embed: Extra embedding recipes
  • topicmodels: LDA topicmodelling in R
  • LDAvis: A bit clunky but awesome interactive LDA visualizations
  • text2vec: Package vor vector space modelling (aka embeddings & other vectorizations) of textdata
  • textdata: Useful datasets for text, such as GloVe embeddings, sentiment lexica etc.
  • uwot: UMAP for R

References

CHapters:

  • Julia Silge and David Robinson (2020). Text Mining with R: A Tidy Approach, O’Reilly. Online available here
  • Emil Hvidfeldt and Julia Silge (2020). Supervised Machine Learning for Text Analysis in R, online available here

Articles: * Blei, David M., Andrew Y. Ng, and Michael I. Jordan. “Latent dirichlet allocation.” Journal of machine Learning research 3, no. Jan (2003): 993-1022. * Jeffrey Pennington, Richard Socher, and Christopher D Manning. Glove: Global vectors for word representation. In Conference on Empirical Methods on Natural Language Processing (EMNLP), pages 1532–1543, 2014

Further sources

Session Info

sessionInfo()
R version 4.0.2 (2020-06-22)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Catalina 10.15.7

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] dbscan_1.1-5       uwot_0.1.8         Matrix_1.2-18      textdata_0.4.1     tsne_0.1-3        
 [6] LDAvis_0.3.2       topicmodels_0.2-11 widyr_0.1.3        textrecipes_0.3.0  recipes_0.1.13    
[11] tidytext_0.2.6     magrittr_1.5       forcats_0.5.0      stringr_1.4.0      dplyr_1.0.2       
[16] purrr_0.3.4        readr_1.4.0        tidyr_1.1.2        tibble_3.0.4       ggplot2_3.3.2     
[21] tidyverse_1.3.0    knitr_1.30        

loaded via a namespace (and not attached):
  [1] colorspace_1.4-1     ellipsis_0.3.1       class_7.3-17         modeltools_0.2-23    rsconnect_0.8.16    
  [6] rprojroot_1.3-2      base64enc_0.1-3      fs_1.5.0             rstudioapi_0.11      farver_2.0.3        
 [11] textfeatures_0.3.3   SnowballC_0.7.0      RSpectra_0.16-0      prodlim_2019.11.13   fansi_0.4.1         
 [16] lubridate_1.7.9      xml2_1.3.2           codetools_0.2-16     splines_4.0.2        rsparse_0.4.0       
 [21] zeallot_0.1.0        pkgload_1.1.0        mlapi_0.1.0          jsonlite_1.7.1       RhpcBLASctl_0.20-137
 [26] broom_0.7.1          servr_0.19           dbplyr_1.4.4         tfruns_1.4           compiler_4.0.2      
 [31] httr_1.4.2           backports_1.1.10     assertthat_0.2.1     cli_2.1.0            later_1.1.0.1       
 [36] tools_4.0.2          NLP_0.2-0            gtable_0.3.0         glue_1.4.2           reshape2_1.4.4      
 [41] rappdirs_0.3.1       float_0.2-4          Rcpp_1.0.5           slam_0.1-47          cellranger_1.1.0    
 [46] vctrs_0.3.4          RJSONIO_1.3-1.4      timeDate_3043.102    gower_0.2.2          xfun_0.19           
 [51] stopwords_2.0        testthat_3.0.0       rvest_0.3.6          mime_0.9             lifecycle_0.2.0     
 [56] pacman_0.5.1         MASS_7.3-53          scales_1.1.1         ipred_0.9-9          lgr_0.4.1           
 [61] promises_1.1.1       hms_0.5.3            parallel_4.0.2       yaml_2.2.1           curl_4.3            
 [66] reticulate_1.18      rpart_4.1-15         stringi_1.5.3        tokenizers_0.2.1     desc_1.2.0          
 [71] lava_1.6.8           rlang_0.4.8          pkgconfig_2.0.3      lattice_0.20-41      labeling_0.4.2      
 [76] tidyselect_1.1.0     RcppAnnoy_0.0.16     plyr_1.8.6           R6_2.5.0             text2vec_0.6        
 [81] generics_0.1.0       DBI_1.1.0            whisker_0.4          pillar_1.4.6         haven_2.3.1         
 [86] withr_2.3.0          survival_3.2-7       nnet_7.3-14          janeaustenr_0.1.5    modelr_0.1.8        
 [91] crayon_1.3.4         usethis_1.6.3        grid_4.0.2           readxl_1.3.1         data.table_1.13.0   
 [96] blob_1.2.1           reprex_0.3.0         digest_0.6.27        tm_0.7-7             httpuv_1.5.4        
[101] spacyr_1.2.1         stats4_4.0.2         munsell_0.5.0       
LS0tCnRpdGxlOiAnKFNvbWV3aGF0KSBhZHZhbmNlZCBOTFA6IHRleHQgdmVjdG9yaXphdGlvbicKYXV0aG9yOiAiRGFuaWVsIFMuIEhhaW4gKGRzaEBidXNpbmVzcy5hYXUuZGspIgpkYXRlOiAiVXBkYXRlZCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgdGhlbWU6IGZsYXRseQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIyMgR2VuZXJpYyBwcmVhbWJsZQpybShsaXN0PWxzKCkpClN5cy5zZXRlbnYoTEFORyA9ICJlbiIpICMgRm9yIGVuZ2xpc2ggbGFuZ3VhZ2UKb3B0aW9ucyhzY2lwZW4gPSA1KSAjIFRvIGRlYWN0aXZhdGUgYW5ub3lpbmcgc2NpZW50aWZpYyBudW1iZXIgbm90YXRpb24KCiMjIyBLbml0ciBvcHRpb25zCmxpYnJhcnkoa25pdHIpICMgRm9yIGRpc3BsYXkgb2YgdGhlIG1hcmtkb3duCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLAogICAgICAgICAgICAgICAgICAgICBjb21tZW50PUZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduPSJjZW50ZXIiCiAgICAgICAgICAgICAgICAgICAgICkKYGBgCgpgYGB7cn0KIyMjIExvYWQgc3RhbmRhcmRwYWNrYWdlcwpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBDb2xsZWN0aW9uIG9mIGFsbCB0aGUgZ29vZCBzdHVmZiBsaWtlIGRwbHlyLCBnZ3Bsb3QyIGVjdC4KbGlicmFyeShtYWdyaXR0cikgIyBGb3IgZXh0cmEtcGlwaW5nIG9wZXJhdG9ycyAoZWcuICU8PiUpCmBgYAoKYGBge3J9CmxpYnJhcnkodGlkeXRleHQpCmBgYAoKIyBUaGlzIHNlc3Npb24KClRoaXMgc2Vzc2lvbiwgd2Ugd2lsbAoKMS4gUmV2aWV3IE5MUCB3b3JrZmxvd3MgYW5kIGRhdGEgc3RydWN0dXJlcyBpbiBSCjIuIEV4cGxvcmUgZGlmZmVyZW50IHR5cGUgb2YgRFRNIG1hdHJpeCB0eXBlIHZlY3RvciByZXByZXNlbnRhdGlvbnMgb2YgdGV4dC4KMy4gQWRkIGRpZmZlcmVudCB0eXBlcyBvZiBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlcyB0byB0aGUgcmVwZXJ0b2lyLgo2LiBIQXZlIGEgcGVhayBpbnRvIHdvcmQtZW1iZWRkaW5ncwo1LiBBZGQgc29tZSBnb2RkaWVzIG9uIHRvcAoKIyBSZWZyZXNoZXI6CgohW10oaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyLzAwX21lZGlhL25scF90aWR5d29ya2Zsb3cucG5nKQoKCiMgQmFnIG9mIHdvcmRzIG1vZGVsCgoqIEluIG9yZGVyIGZvciBhIGNvbXB1dGVyIHRvIHVuZGVyc3RhbmQgdGV4dCB3ZSBuZWVkIHRvIHNvbWVob3cgZmluZCBhIHVzZWZ1bCByZXByZXNlbnRhdGlvbi4KKiBJZiB5b3UgbmVlZCB0byBjb21wYXJlIGRpZmZlcmVudCB0ZXh0cyBlLmcuIGFydGljbGVzLCB5b3Ugd2lsbCBwcm9iYWJseSBnbyBmb3Iga2V5d29yZHMuIFRoZXNlIGtleXdvcmRzIG1heSBjb21lIGZyb20gYSBrZXl3b3JkLWxpc3Qgd2l0aCBmb3IgZXhhbXBsZSAyMDAgZGlmZmVyZW50IGtleXdvcmRzCiogSW4gdGhhdCBjYXNlIHlvdSBjb3VsZCByZXByZXNlbnQgZWFjaCBkb2N1bWVudCB3aXRoIGEgKHNwYXJzZSkgdmVjdG9yIHdpdGggMSBmb3IgImtleXdvcmQgcHJlc2VudCIgYW5kIDAgZm9yICJrZXl3b3JkIGFic2VudCIKKiBXZSBjYW4gYWxzbyBnZXQgYSBiaXQgbW9yZSBzb3Bob2lzdG9jYXRlZCBhbmQgY291bnQgdGhlIG51bWJlciBvZiB0aW1lcyBhIHdvcmQgZnJvbSBvdXIgZGljdGlvbmFyeSBvY2N1cnMuCiogRm9yIGEgY29ycHVzIG9mIGRvY3VtZW50cyB0aGF0IHdvdWxkIGdpdmUgdXMgYSBkb2N1bWVudC10ZXJtIG1hdHJpeC4KCiFbZXhhbXBsZV0oaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9DMVVNcy5wbmcpCgpMZXQncyB0cnkgY3JlYXRpbmcgYSBiYWcgb2Ygd29yZHMgbW9kZWwgZnJvbSBvdXIgaW5pdGlhbCBleGFtcGxlLgoKYGBge3J9CnRleHQgPC0gdGliYmxlKGlkID0gYygxOjYpLAogICAgICAgICAgICAgICB0ZXh0ID0gYygnQSB0ZXh0IGFib3V0IGNhdHMuJywKICAgICAgICAgICAgICAgICAgICAgICAgJ0EgdGV4dCBhYm91dCBkb2dzLicsCiAgICAgICAgICAgICAgICAgICAgICAgICdBbmQgYW5vdGhlciB0ZXh0IGFib3V0IGEgZG9nLicsCiAgICAgICAgICAgICAgICAgICAgICAgICdXaHkgYWx3YXlzIHdyaXRpbmcgYWJvdXQgY2F0cyBhbmQgZG9ncywgYWx3YXlzIGRvZ3M/JywKICAgICAgICAgICAgICAgICAgICAgICAgJ1RoZXJlIGFyZSB0b28gbGl0dGxlIHRleHQgYWJvdXQgY2F0cyBidXQgdG8gbWFueSBhYm91dCBkb2dzJywKICAgICAgICAgICAgICAgICAgICAgICAgJ0NhdHMsIGNhdHMsIGNhdHMhIEkgbG92ZSBjYXRzIHNvbyBtdWNoLiBDYXRzIGFyZSB3YXkgYmV0dGVyIHRoYW4gZG9ncycpKQpgYGAKCmBgYHtyfQp0ZXh0X3RpZHkgPC0gdGV4dCAlPiUgCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0LCB0b2tlbiA9ICd3b3JkcycpICU+JSAKICBjb3VudChpZCwgd29yZCkKYGBgCgoKIyMgVGhlIGRvY3VtZW50LXRlcm0gbWF0cml4IChEVE0pCgoqIFRoZSBzaW1wbGVzdCBmb3JtIG9mIHZlY3RvciByZXByZXNlbnRhdGlvbiBvZiB0ZXh0IGlzIGEgZGRvY3VtZW50LXRlcm0gbWF0cml4CiogSG93IHRvIHdlIGdldCBhIGRvY3VtZW50LXRlcm0gbWF0cml4IG5vdz8KKiBXZSBjb3VsZCBkbyBpdCBieSBoYW5kLCB3aXRoIHdlbGwta25vd24gYGRwbHlyYCBzeW50YXggKE5vdGU6IG9ubHkgd29ya3Mgd2hlbiB5b3UgaGF2ZSBvbmUgcm93IHBlciB1bmlxdWUgZG9jdW1lbnQtd29yZCBwYWlyKQoKYGBge3J9CnRleHRfdGlkeSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gd29yZCwgdmFsdWVzX2Zyb20gPSBuLCB2YWx1ZXNfZmlsbCA9IDApCmBgYAoKKiBXZSBjb3VsZCBhbHNvIHVzZSBgY2FzdF9kdG0oKWAgdG8gY3JlYXRlIGEgRFRNIGluIHRoZSBmb3JtYXQgb2YgdGhlIGB0bWAgcGFja2FnZS4KCmBgYHtyfQp0ZXh0X2R0bSA8LSB0ZXh0X3RpZHkgJT4lCiAgY2FzdF9kdG0oaWQsIHdvcmQsIG4pCmBgYAoKYGBge3J9CnRleHRfZHRtIApgYGAKCiogV2UgY2FuIHNpbXBseSBjb252ZXJ0IGlnIHRvIGEgdGliYmxlLiBTaW5jZSB0aGVyZSBleGlzdHMgbm8gZGlyZWN0IHRyYW5zZmVyIGZ1bmN0aW9uLCB3ZSBoYXZlIHRvIGZpcnN0IHRyYW5zZm9ybSBpdCB0byBhIG1hdHJpeC4KKiBOb3RpY2UgaG93IHdlIHJlY292ZXIgdGhlIHJvd25hbWVzCgpgYGB7cn0KdGV4dF9kdG0gJT4lIGFzLm1hdHJpeCgpICU+JSBhc190aWJibGUocm93bmFtZXMgPSAnaWQnKSAKYGBgCgoqIFNpZGVub3RlOiBXZSBjYW4gYWxzbyB0aWR5IHRoZSBEVE0gYWdhaW4gdG8gYSB0aWR5IHRva2VuLWRhdGFmcmFtZS4KCmBgYHtyfQp0ZXh0X2R0bSAlPiUgdGlkeSgpCmBgYAoqIFdlIGFsc28gY2FuIGRpcmVjdGx5IHVzZSBhIHNpbWlsYXIgZnVuY3Rpb24gdG8gY2FzdCBhIHNwYXJzZSBtYXRyaXggKHdoaWNoIHdlIGZvciBzdXJlIHRoZW4gYWxzbyBjb3VsZCB0cmFuc2Zvcm0gdG8gYSB0aWJibGUgYWdhaW4pCgpgYGB7cn0KdGV4dF90aWR5ICU+JSBjYXN0X3NwYXJzZShyb3cgPSBpZCwgY29sdW1uID0gd29yZCwgdmFsdWUgPSBuKQpgYGAKCiogRmluYWxseSwgd2UgY291bGQganVzdCBhcHBseSBhIHRleHQgcmVjaXBlIGhlcmUKCmBgYHtyfQpsaWJyYXJ5KHJlY2lwZXMpCmxpYnJhcnkodGV4dHJlY2lwZXMpCmBgYAoKYGBge3J9CnRleHQgJT4lCiAgcmVjaXBlKH4uKSAlPiUgCiAgc3RlcF90b2tlbml6ZSh0ZXh0LCB0b2tlbiA9ICd3b3JkcycpICU+JSAjIHRva2VuaXplCiAgc3RlcF90Zih0ZXh0KSAlPiUgIyBURklERiB3ZWlnaHRpbmcKICBwcmVwKCkgJT4lIGp1aWNlKCkKYGBgCgoKIyMgVEYtSURGIC0gVGVybSBGcmVxdWVuY3kgLSBJbnZlcnNlIERvY3VtZW50IEZyZXF1ZW5jeQoKKiBBIHRva2VuIGlzIGltcG9ydGFudCBmb3IgYSBkb2N1bWVudCBpZiBhcHBlYXJzIHZlcnkgb2Z0ZW4KKiBBIHRva2VuIGJlY29tZXMgbGVzcyBpbXBvcnRhbnQgZm9yIGNvbXBhcmlzb24gYWNyb3NzIGEgY29ycHVzIGlmIGl0IGFwcGVhcnMgYWxsIG92ZXIgdGhlIHBsYWNlIGluIHRoZSBjb3JwdXMKKiAqQ2F0KiBpbiBhIGNvcnB1cyBvZiB3ZWJzaXRlcyB0YWxraW5nIGFib3V0IGNhdHMgaXMgbm90IHRoYXQgaW1wb3J0YW50CgokJHdfe2ksan0gPSB0Zl97aSxqfSpsb2coXGZyYWN7Tn17ZGZfaX0pJCQKCi0gJHdfe2ksan0kID0gdGhlIFRGLUlERiBzY29yZSBmb3IgYSB0ZXJtIGkgaW4gYSBkb2N1bWVudCBqCi0gJHRmX3tpLGp9JCA9IG51bWJlciBvZiBvY2N1cmVuY2Ugb2YgdGVybSBpIGluIGRvY3VtZW50IGoKLSAkTiQgPSBudW1iZXIgb2YgZG9jdW1lbnRzIGluIHRoZSBjb3JwdXMKLSAkZGZfaSQgPSBudW1iZXIgb2YgZG9jdW1lbnRzIHdpdGggdGVybSBpCgpgYGB7cn0KIyBURklERiB3ZWlnaHRzCnRleHRfdGlkeSAlPD4lCiAgYmluZF90Zl9pZGYodGVybSA9IHdvcmQsCiAgICAgICAgICAgICAgZG9jdW1lbnQgPSBpZCwKICAgICAgICAgICAgICBuID0gbikKYGBgCgoqIFdlIG9idmlvdXNseSBjb3VsZCBhbHNvIGNhc3QgYSB0Zl9pZGYgd2VpZ2h0ZWQgZHRtLi4uCgpgYGB7cn0KdGV4dF90aWR5ICU+JQogIHNlbGVjdChpZCwgd29yZCwgdGZfaWRmKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gd29yZCwgdmFsdWVzX2Zyb20gPSB0Zl9pZGYsIHZhbHVlc19maWxsID0gMCkKYGBgCgoqIGJ0dzogdGhpcyBpcyBlcXVpdmFsZW50IHRvIGp1c3QgcnVubmluZyBhIHRleHRyZWNpcGUgbGlrZSB0aGF0OgoKYGBge3J9CnRleHQgJT4lCiAgcmVjaXBlKH4uKSAlPiUgCiAgc3RlcF90b2tlbml6ZSh0ZXh0LCB0b2tlbiA9ICd3b3JkcycpICU+JSAjIHRva2VuaXplCiAgc3RlcF90ZmlkZih0ZXh0KSAlPiUgIyBURklERiB3ZWlnaHRpbmcKICBwcmVwKCkgJT4lIGp1aWNlKCkKYGBgCgoqIFNpZGVub3RlLCB3aGVuIHdlIHVzZSBhIFBPUyBlbmdpbmUgc3VjaCBhcyBgc3BhY3lyYCBmb3IgdG9rZW5pemF0aW9uLCB3ZSBjYW4gYWxzbyBhZGQgcmVjaXBlcyBmb3IgbGVtYXRpemF0aW9uLCBmaWx0ZXIgZm9yIFBPUyBldGMuCgpgYGB7cn0KdGV4dCAlPiUKICByZWNpcGUofi4pICU+JSAKICBzdGVwX3Rva2VuaXplKHRleHQsIGVuZ2luZSA9ICJzcGFjeXIiKSAlPiUKICBzdGVwX3Bvc19maWx0ZXIodGV4dCwga2VlcF90YWdzID0gIk5PVU4iKSAlPiUKICBzdGVwX2xlbW1hKHRleHQpICU+JQogIHN0ZXBfdGYodGV4dCkgJT4lCiAgcHJlcCgpICU+JQogIGp1aWNlKCkKYGBgCgoqIEEgbGFzdCByZW1pbmRlciBvbiB0aGUgcG93ZXJmdWwgYHBhaXJ3aXNlX3h4KClgIGZ1bmN0aW9ucyBmcm9tIHRoZSBgd2lkeXJgIHBhY2thZ2UKKiBGb3IgaW5zdGFuY2UsIHBhaXJ3aXNlIHNpbWlsYXJpdGllcy9kaXN0YW5jZXMKCmBgYHtyfQpsaWJyYXJ5KHdpZHlyKQpgYGAKCmBgYHtyfQp0ZXh0X3RpZHkgJT4lIHBhaXJ3aXNlX2Rpc3QoaWQsIHdvcmQsIHRmX2lkZiwgbWV0aG9kID0gIm1hbmhhdHRhbiIpICU+JQogIG11dGF0ZShzaW1pbGFyaXR5ID0gMSAtIChkaXN0YW5jZSAvIG1heChkaXN0YW5jZSkpICkgJT4lCiAgc2VsZWN0KC1kaXN0YW5jZSkgJT4lCiAgYXJyYW5nZShkZXNjKHNpbWlsYXJpdHkpKQpgYGAKCgojIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiB0ZWNobmlxdWVzCgpgYGB7cn0Kcm0obGlzdD1scygpKQpgYGAKCiogT2ssIGxldHMgZ2V0IGZpcnN0IHNvbWUgbW9yZSBpbnRlcmVzdGluZyBkYXRhLiBXZSB3aWxsIHdvcmsgd2l0aCB0aGUgQ09SRElTIHByb2plY3QgZGVzY3JpcHRpb25zIG9mIEVVIEhvcml6b24gMjAyMCBwcm9qZWN0cyBhZ2Fpbi4KCmBgYHtyfQp0ZXh0IDwtIHJlYWRfY3N2KCdodHRwczovL2dpdGh1Yi5jb20vU0RTLUFBVS9TRFMtbWFzdGVyL3Jhdy9tYXN0ZXIvTTIvZGF0YS9jb3JkaXMtaDIwMjByZXBvcnRzLmd6JykKYGBgCgpgYGB7cn0KY29sbmFtZXModGV4dCkgPC0gY29sbmFtZXModGV4dCkgJT4lIHN0cl90b19sb3dlcigpCnRleHQgJTw+JQogIHNlbGVjdCgteDEpICU+JQogIHJlbmFtZShpZCA9IHByb2plY3RpZCkgJT4lCiAgcmVsb2NhdGUoaWQpICU+JQogIGZpbHRlcihsYW5ndWFnZSA9PSAnZW4nKSAlPiUKICBkcm9wX25hKGlkKQpgYGAKCiogTGV0cyBjcmVhdGUgYSB0aWR5IHRva2VubGlzdAoKYGBge3J9CnRleHRfdGlkeSA8LSB0ZXh0ICU+JQogIHJlbmFtZSh0ZXh0ID0gc3VtbWFyeSkgJT4lCiAgc2VsZWN0KGlkLCB0ZXh0KSAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQsIHRva2VuID0gIndvcmRzIikKYGBgCgoqIHNvbWUgcHJlcHJvY2Vzc2luZwoKYGBge3J9CiMgcHJlcHJvY2Vzc2luZwp0ZXh0X3RpZHkgJTw+JQogIGZpbHRlcihzdHJfbGVuZ3RoKHdvcmQpID4gMiApICU+JSAjIFJlbW92ZSB3b3JkcyB3aXRoIGxlc3MgdGhhbiAgMyBjaGFyYWN0ZXJzCiAgZmlsdGVyKCEod29yZCAlaW4lIGMoJ3Byb2plY3QnLCAncmVzZWFyY2gnKSkpICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICd3b3JkJykgCmBgYAoKKiBXZSBjYW4gYWxzbyBhZCBiaWdyYW1zCgpgYGB7cn0KdGV4dF90aWR5ICU8PiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHdvcmQsIHRva2VuID0gJ25ncmFtcycsIG4gPSAyLCBuX21pbiA9IDEpICU+JQogIGdyb3VwX2J5KHdvcmQpICU+JSBmaWx0ZXIobigpID4gMjUpICU+JSB1bmdyb3VwKCkgCmBgYAoKYGBge3J9CnRleHRfdGlkeSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKYGBgCgoqIExldHMgZmluaXNoIHRoaXMgdXAgYW5kIGFsc28gYWRkIFRGLUlERiB3ZWlnaHRzCgpgYGB7cn0KdGV4dF90aWR5ICU8PiUKICBjb3VudChpZCwgd29yZCkgJT4lCiAgYmluZF90Zl9pZGYodGVybSA9IHdvcmQsCiAgICAgICAgICAgICAgZG9jdW1lbnQgPSBpZCwKICAgICAgICAgICAgICBuID0gbikgJT4lCiAgc2VsZWN0KC10ZiwgLWlkZikKYGBgCgoqIElzIHRoZXJlIGEgYmlnIGRpZmZlcmVuY2U/CgpgYGB7cn0KdGV4dF90aWR5ICU+JQogIGNvdW50KHdvcmQsIHd0ID0gdGZfaWRmLCBzb3J0ID0gVFJVRSkKYGBgCgoqIEFuZCBmaW5hbGx5LCBsZXRzIGdldCBhIERUTSBkYXRhZnJhbWUgCgpgYGB7cn0KdGV4dF9kdG0gPC0gdGV4dF90aWR5ICU+JQogIHNlbGVjdChpZCwgd29yZCwgbikgJT4lCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHdvcmQsIHZhbHVlc19mcm9tID0gbiwgdmFsdWVzX2ZpbGwgPSAwKQpgYGAKCiogQW5kLCBqdXN0IGluIGNhc2UsIGEgVEZJREYgd2VpZ2h0ZWQgdmVyc2lvbgoKYGBge3IsIGluY2x1ZGU9RkFMU0V9CnRleHRfZHRtX3RmX2lkZiA8LSB0ZXh0X3RpZHkgJT4lCiAgc2VsZWN0KGlkLCB3b3JkLCB0Zl9pZGYpICU+JQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB3b3JkLCB2YWx1ZXNfZnJvbSA9IHRmX2lkZiwgdmFsdWVzX2ZpbGwgPSAwKQpgYGAKCiogV2UgY291bGQgYWxzbyBwcmVwYXJlIGEgcmVjaXBlIHdoaWNoIGRvZSBwcmV0dHkgbXVjaCB0aGUgc2FtZS4uLgoKYGBge3J9CnJlY2lwZV9iYXNlIDwtIHRleHQgJT4lCiAgcmVuYW1lKHRleHQgPSBzdW1tYXJ5KSAlPiUKICBzZWxlY3QoaWQsIHRleHQpICU+JQogICMgQkFzZSByZWNpcGUgc3RhcnRzCiAgcmVjaXBlKH4uKSAlPiUgCiAgdXBkYXRlX3JvbGUoaWQsIG5ld19yb2xlID0gImlkIikgJT4lICMgVXBkYXRlIHJvbGUgb2YgSUQKICBzdGVwX3Rva2VuaXplKHRleHQsIHRva2VuID0gJ3dvcmRzJykgJT4lICMgdG9rZW5pemUKICBzdGVwX3N0b3B3b3Jkcyh0ZXh0LCBrZWVwID0gRkFMU0UpICU+JSAjIHJlbW92ZSBzdG9wd29yZHMKICBzdGVwX3VudG9rZW5pemUodGV4dCkgJT4lICMgSGVyZSB3ZSBub3cgaGF2ZSB0byBmaXJzdCB1bnRva2VuaXplCiAgc3RlcF90b2tlbml6ZSh0ZXh0LCB0b2tlbiA9ICJuZ3JhbXMiLCBvcHRpb25zID0gbGlzdChuID0gMSwgbl9taW4gPSAxKSkgJT4lICMgYW5kIHRva2VuaXplIGFnYWluCiAgc3RlcF90b2tlbmZpbHRlcih0ZXh0LCBtaW5fdGltZXMgPSAyNSkgCmBgYAoKKiBTaWRlbm90ZQoKKiBIZXJlLCB3ZSBjYW4gZnVydGhlciBwcmVwcm9jZXNzIHRvIGRvIHdoYXRldmVyIHdlIHdvdWxkIGxpa2UsIHN1Y2ggYXMgb2J0YWluaW5nIGEgZHRtCgpgYGB7cn0KcmVjaXBlX2Jhc2UgJT4lIAogIHN0ZXBfdGYodGV4dCkgJT4lIAogIHByZXAoKSAlPiUgCiAganVpY2UoKSAlPiUgCiAgaGVhZCgxMDApCmBgYAoKYGBge3J9CnRleHRfcGNhIDwtIHRleHRfZHRtICU+JSAKICBjb2x1bW5fdG9fcm93bmFtZXMoJ2lkJykgJT4lIAogIHByY29tcChjZW50ZXIgPSBUUlVFLCBzY2FsZS4gPSBUUlVFLCByYW5rLiA9IDEwKQpgYGAKCmBgYHtyfQp0ZXh0X3BjYSAlPiUgZ2xpbXBzZSgpCmBgYAoKYGBge3J9CnRleHRfcGNhW1sneCddXSAlPiUKICBoZWFkKCkKYGBgCgoKYGBge3J9CnRleHRfcGNhICU+JSB0aWR5KCkKYGBgCgoKKiBBZ2FpbiwgYWx0ZXJuYXRpdmVseSB3aXRoIGEgcmVjaXBlLi4uCgpgYGB7cn0KcmVjaXBlX3BjYSA8LSByZWNpcGVfYmFzZSAlPiUgIyB0b2tlbml6ZQogIHN0ZXBfdGZpZGYodGV4dCwgcHJlZml4ID0gJycpICU+JSAjIFRGSURGIHdlaWdodGluZwogIHN0ZXBfcGNhKGFsbF9wcmVkaWN0b3JzKCksIG51bV9jb21wID0gMTApICU+JSAjIFBDQQogIHByZXAoKSAKYGBgCgpgYGB7cn0KcmVjaXBlX3BjYSAlPiUganVpY2UoKQpgYGAKKiBTb21lIHBsb3R0aW5nCgpgYGB7cn0KcmVjaXBlX3BjYSAlPiUganVpY2UoKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBQQzAxLCB5ID0gUEMwMikpICsKICBnZW9tX3BvaW50KCkgCmBgYAoqIHdlIGNhbiBhbHNvIHVzZSB0aGUgdGlkeSByZXN1bHRzIG9mIHRoZSByZWNpcGUgdG8gZG8gc29tZSBtb3JlIGFuYWx5dGljcwoKYGBge3J9CnJlY2lwZV9wY2EgJT4lCiAgdGlkeSg3KSAlPiUKICBmaWx0ZXIoY29tcG9uZW50ICVpbiUgcGFzdGUwKCJQQyIsIDE6NCkpICU+JQogIGdyb3VwX2J5KGNvbXBvbmVudCkgJT4lCiAgICBhcnJhbmdlKGRlc2ModmFsdWUpKSAlPiUKICAgIHNsaWNlKGMoMToyLCAobigpLTIpOm4oKSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoY29tcG9uZW50ID0gZmN0X2lub3JkZXIoY29tcG9uZW50KSkgJT4lCiAgZ2dwbG90KGFlcyh2YWx1ZSwgdGVybXMsIGZpbGwgPSB0ZXJtcykpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+Y29tcG9uZW50LCBucm93ID0gMSkgKwogIGxhYnMoeSA9IE5VTEwpCmBgYAoKKiAqKk5vdGUqKjogQWxzbyBjaGVjayBmdXJ0aGVyIGZvciBmdXJ0aGVyIGRpbWVuc2lvbmxpdHkgcmVkdWN0aW9uIHN0ZXBzOgogICAqIHRlcF9rcGNhKCk6CiAgICogc3RlcF9pY2EoKQogICAqIHN0ZXBfaXNvbWFwKCkKICAgKiBzdGVwX25ubWYoKQogICAKCiMgVG9waWMgTW9kZWxzOiBMYXRlbnQtRGlyaWNobGV0LUFsbG9jYXRpb24gKExEQSkKCiogV2hpbGUgd2UgYWxyZWFkeSBkaWQgaXQgc29tZXdoYXQgJ29uLXRoZS1mbHknLCBoZXJlIGEgbW9yZSBmb3JtYWwgaW50cm9kdWN0aW9uIHRvIExEQQoqIEluIGNvbnRyYXN0IHRvIGRpbW5lc2lvbmFsaXR5IHJlZHVjdGlvbiB0ZWNoaXF1ZXMgbW9zdGx5IGFpbWluZyBhdCBwcmVwcm9jZXNzaW5nIGRhdGEgb3IgZWFzaW5nIHZpc3VhbGl6YXRpb24sIExEQSBtb3JlIGFpbXMgYXQgRURBIGFuZCBpbnRlcnByZXRhdGlvbgoqIEl0IGlzIGEgZ2VuZXJhdGl2ZSBhcHByb2FjaCB0byBpZGVudGlmeSB0b3BpY3MgKGNsdXN0ZXJzKSB3aXRoaW4gdGhlIHdvcmQtdXNhZ2UgaW4gZG9jdW1lbnRzLgogICAqIFRvcGljcyBhcmUgcmVwcmVzZW50ZWQgYXMgYSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gb3ZlciB0aGUgd29yZHMgaW4gdGhlIHZvY2FidWxhcnkuIEhoaWdoIHByb2JhYmlsaXR5IHdvcmRzIGNhbiBiZSB1c2VkIHRvIGNoYXJhY3RyaXplIHRoZSB0b3BpYy4KICAgKiBEb2N1bWVudHMgYXJlIHJlcHJlc2VudGVkIGFzIGEgbWl4dHVyZSBvZiB0b3BpY3MuCgohW2FsdCB0ZXh0XShodHRwczovL21pcm8ubWVkaXVtLmNvbS9tYXgvMTYwMC8xKnBab19JY3hXMUdWdUgydlFLZG9JTVEuanBlZykKCmBgYHtyfQpsaWJyYXJ5KHRvcGljbW9kZWxzKQpgYGAKCgpgYGB7cn0KdGV4dF9kdG0gPC0gdGV4dF90aWR5ICU+JQogIGNhc3RfZHRtKGRvY3VtZW50ID0gaWQsIHRlcm0gPSB3b3JkLCB2YWx1ZSA9IG4pCmBgYAoKYGBge3J9CnRleHRfbGRhIDwtIHRleHRfZHRtICU+JSAKICBMREEoayA9IDYsIG1ldGhvZCA9ICJHaWJicyIsCiAgICAgIGNvbnRyb2wgPSBsaXN0KHNlZWQgPSAxMzM3KSkKYGBgCgoKKiAkXGJldGEkIGlzIGFuIG91dHB1dCBvZiB0aGUgTERBIG1vZGVsLCBpbmRpY2F0aW5nIHRoZSBwcm9wYWJpbGl0eSB0aGF0IGEgd29yZCBvY2N1cnMgaW4gYSBjZXJ0YWluIHRvcGljLgoqIFRoZXJlZm9yZSwgbG9raW5nIGF0IHRoZSB0b3AgcHJvYmFiaWxpdHkgd29yZHMgb2YgYSB0b3BpYyBvZnRlbiBnaXZlcyB1cyBhIGdvb2QgaW50dWl0aW9uIHJlZ2FyZGluZyBpdHMgcHJvcGVydGllcy4KCmBgYHtyfQojIExEQSBvdXRwdXQgaXMgZGVmaW5lZCBmb3IgdGlkeSgpLCBzbyB3ZSBjYW4gZWFzaWx5IGV4dHJhY3QgaXQKbGRhX2JldGEgPC0gdGV4dF9sZGEgJT4lIAogIHRpZHkobWF0cml4ID0gImJldGEiKSAKYGBgCgpgYGB7cn0KbGRhX2JldGEgJT4lCiAgIyBzbGljZQogIGdyb3VwX2J5KHRvcGljKSAlPiUKICBhcnJhbmdlKHRvcGljLCBkZXNjKGJldGEpKSAlPiUKICBzbGljZSgxOjEwKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgIyB2aXN1YWxpemUKICBtdXRhdGUodGVybSA9IHJlb3JkZXJfd2l0aGluKHRlcm0sIGJldGEsIHRvcGljKSkgJT4lCiAgZ3JvdXBfYnkodG9waWMsIHRlcm0pICU+JSAgICAKICBhcnJhbmdlKGRlc2MoYmV0YSkpICU+JSAgCiAgdW5ncm91cCgpICU+JQogIGdncGxvdChhZXModGVybSwgYmV0YSwgZmlsbCA9IGFzLmZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeF9yZW9yZGVyZWQoKSArCiAgbGFicyh0aXRsZSA9ICJUb3AgMTAgdGVybXMgaW4gZWFjaCBMREEgdG9waWMiLAogICAgICAgeCA9IE5VTEwsIHkgPSBleHByZXNzaW9uKGJldGEpKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBuY29sID0gMywgc2NhbGVzID0gImZyZWUiKQpgYGAKCiogRG9jdW1lbnRzIGFyZSByZXByZXNlbnRlZCBhcyBhIG1peCBvZiB0b3BpY3MuIFRoaXMgYXNzb2NpYXRpb24gb2YgYSBkb2N1bWVudCB0byBhIHRvcGljIGlzIGNhcHR1cmVkIGJ5ICRcZ2FtbWEkCgpgYGB7cn0KbGRhX2dhbW1hIDwtIHRleHRfbGRhICU+JSAKICB0aWR5KG1hdHJpeCA9ICJnYW1tYSIpCmBgYAoKCmBgYHtyfQpsZGFfZ2FtbWEgJT4lCiAgZ3JvdXBfYnkodG9waWMpICU+JQogICAgYXJyYW5nZShkZXNjKGdhbW1hKSkgJT4lIAogICAgc2xpY2UoMToxMCkgJT4lCiAgdW5ncm91cCgpICU+JQogIGxlZnRfam9pbih0ZXh0ICU+JSBzZWxlY3QoaWQsIHByb2plY3RhY3JvbnltKSAlPiUgbXV0YXRlKGlkID0gaWQgJT4lIGFzLmNoYXJhY3RlcigpKSwgYnkgPSBjKCdkb2N1bWVudCcgPSAnaWQnKSkKYGBgCgoKCiogTm90ZSB0aGF0IGFuIExEQSBjYW4gYWxzbyBiZSBwZXJmb3JtZWQgdmlhIGEgcmVjaXBlOgoKYGBge3J9CnJlY2lwZV9sZGEgPC0gcmVjaXBlX2Jhc2UgJT4lICMgdG9rZW5pemUKICBzdGVwX3VudG9rZW5pemUodGV4dCkgJT4lICMgSXMgYSBiaXQgc2lsbHksIG5lZWRzIHRoZSBmdWxsIHRleHQgdmVjdG9ycyBpbnN0ZWFkIG9mIHRva2Vucy4uLi4KICBzdGVwX2xkYSh0ZXh0LCBudW1fdG9waWNzID0gNikgJT4lICMgTERBCiAgcHJlcCgpIApgYGAKCmBgYHtyfQpyZWNpcGVfbGRhICU+JSBqdWljZSgpICU+JSAKICBoZWFkKDEwMCkKYGBgCgoqIEFzIGEgYm9udXMsIGEgZ3JlYXQgd2F5IHRvIGludGVyYWN0aXZlbHkgdmlzdWFsaXplIExEQSdzLgoqIEl0J3MgYSBiaXQgY3VtYmVyc29tZSBpbiBSLCB0aG91Z2guLi4KCmBgYHtyfQpsaWJyYXJ5KExEQXZpcykKYGBgCgoKYGBge3J9CiMgQSBiaXQgb2YgYSBsZW5naHR5IGZ1bmN0aW9uLi4uLgp0b3BpY21vZGVsc19qc29uX2xkYXZpcyA8LSBmdW5jdGlvbihmaXR0ZWQsIGRvY19kdG0sIG1ldGhvZCA9ICJQQ0EiKXsKICByZXF1aXJlKHRvcGljbW9kZWxzKTsgcmVxdWlyZShkcGx5cik7IHJlcXVpcmUoTERBdmlzKQogIAogICMgRmluZCByZXF1aXJlZCBxdWFudGl0aWVzCiAgcGhpIDwtIHBvc3Rlcmlvcih0ZXh0X2xkYSkkdGVybXMgJT4lIGFzLm1hdHJpeCgpICMgVG9waWMtdGVybSBkaXN0cmlidXRpb24KICB0aGV0YSA8LSBwb3N0ZXJpb3IoZml0dGVkKSR0b3BpY3MgJT4lIGFzLm1hdHJpeCgpICMgRG9jdW1lbnQtdG9waWMgbWF0cml4CiAgCiAgdGV4dF90aWR5IDwtIGRvY19kdG0gJT4lIHRpZHkoKQogIHZvY2FiIDwtIGNvbG5hbWVzKHBoaSkKICBkb2NfbGVuZ3RoIDwtIHRpYmJsZShkb2N1bWVudCA9IHJvd25hbWVzKHRoZXRhKSkgJT4lIGxlZnRfam9pbih0ZXh0X3RpZHkgJT4lIGNvdW50KGRvY3VtZW50LCB3dCA9IGNvdW50KSwgYnkgPSAnZG9jdW1lbnQnKQogIHRmIDwtIHRpYmJsZSh0ZXJtID0gdm9jYWIpICU+JSBsZWZ0X2pvaW4odGV4dF90aWR5ICU+JSBjb3VudCh0ZXJtLCB3dCA9IGNvdW50KSwgYnkgPSAidGVybSIpIAogIAogIGlmKG1ldGhvZCA9PSAiUENBIil7bWRzIDwtIGpzUENBfQogIGlmKG1ldGhvZCA9PSAiVFNORSIpe2xpYnJhcnkodHNuZSk7IG1kcyA8LSBmdW5jdGlvbih4KXt0c25lKHN2ZCh4KSR1KX0gfQogIAogICMgQ29udmVydCB0byBqc29uCiAganNvbl9sZGEgPC0gTERBdmlzOjpjcmVhdGVKU09OKHBoaSA9IHBoaSwgdGhldGEgPSB0aGV0YSwgdm9jYWIgPSB2b2NhYiwgZG9jLmxlbmd0aCA9IGRvY19sZW5ndGggJT4lIHB1bGwobiksIHRlcm0uZnJlcXVlbmN5ID0gdGYgJT4lIHB1bGwobiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlb3JkZXIudG9waWNzID0gRkFMU0UsIG1kcy5tZXRob2QgPSBtZHMscGxvdC5vcHRzID0gbGlzdCh4bGFiID0gIkRpbS4xIiwgeWxhYiA9ICJEaW0uMiIpKSAKICByZXR1cm4oanNvbl9sZGEpCn0KYGBgCgoKYGBge3J9CmxpYnJhcnkoTERBdmlzKQpqc29uX2xkYSA8LSB0b3BpY21vZGVsc19qc29uX2xkYXZpcyhmaXR0ZWQgPSB0ZXh0X2xkYSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRvY19kdG0gPSB0ZXh0X2R0bSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJUU05FIikKCmpzb25fbGRhICU+JSBzZXJWaXMoKSAjIEZvciBkaXJlY3Qgb3V0cHV0CiMganNvbl9sZGEgJT4lIHNlclZpcyhvdXQuZGlyID0gJ0xEQXZpeicpICMgRm9yIHNhdmluZyB0aGUgaHRtbApgYGAKCgo8aWZyYW1lIHdpZHRoPSIxMjAwIiBoZWlnaHQ9IjE1MDAiIHNyYz0iaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyL00yL25vdGVib29rcy9MREF2aXovaW5kZXguaHRtbCIgZnJhbWVib3JkZXI9IjAiPjwvaWZyYW1lPiAKCkRpZG50IHJlYWxseSBmaWd1cmUgb3V0IGhvdyB0byBlbWJlZGQgdGhlIHJlc3VsdGluZyBwbG90LCBidXQgdGhlIG91dGNvbWUgY2FuIGJlIHNlZW4gW2hlcmVdKGh0dHBzOi8vc2RzLWFhdS5naXRodWIuaW8vU0RTLW1hc3Rlci9NMi9ub3RlYm9va3MvTERBdml6L2luZGV4Lmh0bWwpCgoKIyBFbWJlZGRpbmdzIChCb251cykKCiogT25lIGxhc3QgdGhpbmcgd2UgZGlkIG5vdCB2ZW50dXJlIGluIHlldCwgYXJlIGVtYmVkZGluZ3MKKiBJIHdpbGwgbm90IGdvIGludG8gZGV0YWlscyBoZXJlLCBqdXN0IHNlZSBpdCBhcyBhIHBlYWsgb2Ygd2hhdCdzIHRvIGNvbWUgaW4gZnVydGhlciBzZXNzaW9ucy4KKiBUaGUgaWRlZSBvZiB3b3JkIGVtYmVkZGluZyBpcyAoaW4gYSBudXRzaGVsbCkgdGhhdAoKCiogVGhlcmUgYXJlIHBhY2thZ2VzIG9uIGhvdyB0byB0cmFpbiBvd24gZW1iZWRkaW5ncyBzdWNoIGFzIFtgdGV4dDJ2ZWNgXShodHRwOi8vdGV4dDJ2ZWMub3JnLyksIGJ1dCB3ZSB3aWxsIGZvciBub3cgbm90IGJvdGhlciB3aXRoIHRoYXQuCiogVGhlIG9ubHkgdGhpbmcgd2Ugd2lsbCBkbyBmb3Igbm93IGlzIHRvIGxvYWQgcHJldHJhaW5lZCBlbWJlZGRpbmdzIChHbG9WZSwgY2YuIFBlbm5pbmd0b24gZXQgYWwsIDIwMTQpCgoKYGBge3J9CmxpYnJhcnkodGV4dGRhdGEpCgpnbG92ZTZiIDwtIGVtYmVkZGluZ19nbG92ZTZiKGRpbWVuc2lvbnMgPSAxMDApCmdsb3ZlNmIKYGBgCgoKKiBMYSB2b2lsYSwgYSBsYXJnZSBwcmV0cmFpbmVkIGVtYmVkZGluZyBtb2RlbCBmb3IgYXJvdW5kIDQwMGsgb2YgdGhlIG1vc3QgY29tbW9uIHdvcmRzLiAKKiBXZSBmb3Igbm93IGxvYWRlZCB0aGUgc21hbGxlc3Qgb2YgdGhlc2UgZW1iZWRkaW5nIG1vZGVscywgdGhlcmUgZXhpc3Qgd2F5IGJpZ2dlciBvbmVzLgoqIExldHMgam9pbiBpdCB3aXRoIG91ciB0aWR5IHRva2VubGlzdAoKYGBge3J9CndvcmRfZW1iZWRkaW5ncyA8LSB0ZXh0X3RpZHkgJT4lCiAgaW5uZXJfam9pbihnbG92ZTZiLCBieSA9IGMoJ3dvcmQnID0gJ3Rva2VuJykpCmBgYAoKYGBge3J9CndvcmRfZW1iZWRkaW5ncyAlPiUgaGVhZCgpCmBgYAoKKiBXZSBjb3VsZCBub3cgY3JlYXRlIGF2ZXJhZ2UgZG9jdW1lbnQgZW1iZWRkaW5ncyBieSB0YWtpbmcgdGhlIG1lYW4gb3ZlciBhbGwgZGltZW5zaW9ucwoqIFdlIGNvdWxkIGFsc28gKGV2ZW4gYmV0dGVyKSB3ZWlnaHQgdGhhdCBieSB0aGVuIHdvcmQncyB0ZmlkZiBzY29yZS4KCmBgYHtyfQpkb2NfZW1iZWRkaW5ncyA8LSB3b3JkX2VtYmVkZGluZ3MgJT4lCiAgZ3JvdXBfYnkoaWQpICU+JQogIHN1bW1hcmlzZShhY3Jvc3Moc3RhcnRzX3dpdGgoImQiKSwgfm1lYW4oLnggLyB0Zl9pZGYsIG5hLnJtID0gVFJVRSkpKQpgYGAKCiogVGhlc2UgZW1iZGRpbmdzIGNvdWxkIG5vdyBiZSB1c2VkIGZvciBpbnN0YW5jZSBmb3Igc29tZSBjbHVzdGVyaW5nIG9yIFNNTCBleGVyY2lzZQoqIEkgZ3Vlc3MgeW91IGNhbiBhbHJlYWR5IHNlZSBob3cgdG8gdXNlIHRoZXNlIGVtYmVkZGluZ3MgaW4gYW4gU01MIG1vZGVsLgoKYGBge3J9CmxpYnJhcnkodXdvdCkgIyBmb3IgVU1BUApgYGAKCgpgYGB7cn0KZW1iZWRkaW5nc191bWFwIDwtIGRvY19lbWJlZGRpbmdzICAlPiUgCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJpZCIpICU+JQogIHVtYXAobl9uZWlnaGJvcnMgPSAxNSwgCiAgICAgICBtZXRyaWMgPSAiY29zaW5lIiwgCiAgICAgICBtaW5fZGlzdCA9IDAuMDEsIAogICAgICAgc2NhbGUgPSBUUlVFLAogICAgICAgdmVyYm9zZSA9IFRSVUUsIAogICAgICAgbl90aHJlYWRzID0gOCkgCmBgYAoKYGBge3J9CmVtYmVkZGluZ3NfdW1hcCAlPD4lIGFzLmRhdGEuZnJhbWUoKQpgYGAKCgpgYGB7cn0KZW1iZWRkaW5nc191bWFwICAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gVjEsIHkgPSBWMikpICsgCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxLCBhbHBoYSA9IDAuNSkgCmBgYAoKKiBPaywgd2Ugc2VlIGEgcmF0aGVyIGNsZWFyIHNlcGVyYXRpb24gb2YgZG9jdW1lbnRzLgoqIEp1c3QgZm9yIGZ1biwgbGV0cyBhZGQgYSBkZW5zaXR5IGJhc2VkIGNsdXN0ZXJpbmcgKHZlcnkgZ29vZCBmb3Igc3BhdGlhbCBjbHVzdGVyaW5nKSBvbiB0b3AgKGV2ZW4gdGhvdWdoIHdlIGFscmVhZHkgc2VlIHRoZSByZXN1bHRzKQoKYGBge3J9CmxpYnJhcnkoZGJzY2FuKQpgYGAKCiogRG8gdGhlIGhpcmFyY2hpY2FsIGRlbnNpdHkgYmFzZWQgY2x1c3RlcmluZwogICAgICAgCmBgYHtyfQplbWJlZGRpbmdzX2hkYnNjYW4gPC0gZW1iZWRkaW5nc191bWFwICU+JSBhcy5tYXRyaXgoKSAlPiUgaGRic2NhbihtaW5QdHMgPSAxNSkKYGBgCgoqIFBsb3QgaXQKCmBgYHtyfQplbWJlZGRpbmdzX3VtYXAgJT4lIAogIGJpbmRfY29scyhjbHVzdGVyID0gZW1iZWRkaW5nc19oZGJzY2FuJGNsdXN0ZXIgJT4lIGFzLmZhY3RvcigpLCAKICAgICAgICAgICAgcHJvYiA9IGVtYmVkZGluZ3NfaGRic2NhbiRtZW1iZXJzaGlwX3Byb2IpICU+JQogIGdncGxvdChhZXMoeCA9IFYxLCB5ID0gVjIsIGNvbCA9IGNsdXN0ZXIpKSArIAogIGdlb21fcG9pbnQoYWVzKGFscGhhID0gcHJvYiksIHNoYXBlID0gMjEpIApgYGAKCiogTm90ZTogV2UgY2FuIGFsc28gYXNzaWduZSB0aGUgZW1iZWRkaW5ncyB2aWEgYSByZWNpcGUKKiBVbmZvcnR1bmF0ZWx5LCB3ZSBjYW4gbm90IGRvIGEgVEZJREYgd2VpZ2h0aW5nIGhlcmUgJ291dC1vZi10aGUtYm94JywgYnV0IGhhdmUgdG8gd29yayB3aXRoIGF2ZXJhZ2UgZW1iZWRkaW5ncyBpbnN0ZWFkLgoKCmBgYHtyfQpyZWNpcGVfZW1iZWRkaW5nIDwtIHJlY2lwZV9iYXNlICU+JSAjIHRva2VuaXplCiAgc3RlcF93b3JkX2VtYmVkZGluZ3ModGV4dCwgZW1iZWRkaW5ncyA9IGdsb3ZlNmIsIGFnZ3JlZ2F0aW9uID0gJ21lYW4nKQpgYGAKCmBgYHtyfQpyZWNpcGVfZW1iZWRkaW5nICU+JSBwcmVwKCkgJT4lIGp1aWNlKCkgJT4lIAogIGhlYWQoMTAwKQpgYGAKCgoqIFNhbWUgZ29lcyBmb3IgVU1BUCwgd2hpY2ggY2FuIGJlIGFjY2Vzc2QgaW4gcmVjaXBlcyB2aWEgdGhlIHRoZSBwYWNrYWdlIGBlbWJlZGAgcGNrYWdlLgoqIEhvd2V2ZXIsYGVtYmVkYCBpcyBhIGJpdCBoZWF2eSBpbiB0ZXJtcyBvZiBkZXBlbmRlbmNpZXMsIHNpbmNlIGl0IHVzZXMgYGtlcmFzYCBhbmQgYHRlbnNvcmZsb3dgLCBhIGRlZXAgbGVhcm5pbmcgZnJhbWV3b2ssIGluIHRoZSBiYWNrZ3JvdWJuZCwgYW5kIGlzIGluIG5lZWQgdG8gaW5zdGFsbCBhbm90aGVyIG1pbmktY29uZGEgZW52aXJvbWVudC4gCiogSWYgeW91IGhhdmUgbm8gZXhwZXJpZW5jZSB3aXRoIGBrZXJhc2AgYW5kIGB0ZW5zb3JmbG93YCBzbyBmYXIsIEkgc3VnZ2VzdCB5b3Ugd2FpdCB3aXRoIHRoaXMgb25lIHVudGlsIGxhdGVyIHNlc3Npb25zIHdoZW4gd2UgcHJvcGVybHkgaW50cm9kdWNlIGl0LgoKYGBge3J9CmxpYnJhcnkoZW1iZWQpCmBgYAoKYGBge3J9CnJlY2lwZV91bWFwIDwtIHJlY2lwZV9lbWJlZGRpbmcgJT4lCiAgc3RlcF91bWFwKHN0YXJ0c193aXRoKCd3X2VtYmVkJyksIG5fbmVpZ2hib3JzID0gMTUpIApgYGAKCmBgYHtyfQpyZWNpcGVfdW1hcCAlPiUgcHJlcCgpICU+JSBqdWljZSgpICU+JSAKICBoZWFkKDEwMCkKYGBgCgoqIFNvLCB0aGF0J3MgYWxsIEkgaGF2ZSBmb3Igbm93CgojIFN1bW1hcnkKCiogVGhlcmUgYXJlIG1hbnkgd2F5cyB0byBjb252ZXJ0IHRleHQgZGF0YSBpbnRvIGEgdmVjdG9yIHJlcHJlc2VudGF0aW9uLgoqIFRoZXNlIHJhbmdlIGZyb20gc2ltcGxlIGFuZCB3ZWlnaHRlZCBiYWdzLW9mLXdvcmRzLCB0byB0b3BpYyBtb2RlbHMsIG92ZXIgZGlmZmVyZW50IHR5cGVzIG9mIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiB0byBmaW5hbGx5IHdvcmQgYW5kIGRvY3VtZW50IGVtYmVkZGluZ3MuCiogQWxsIG9mIHRoZW0gYXJlIHVzZWZ1bCwgZGVwZW5kaW5nIG9uIHRoZSBwdXJwb3NlLgoKIyBFbmRub3RlcwoKIyMjIFBhY2thZ2VzICYgRWNvc3lzdGVtCgoqIFtgdGV4dHJlY2lwZXNgXShodHRwczovL3RleHRyZWNpcGVzLnRpZHltb2RlbHMub3JnLyk6IFRleHQgcHJlcHJvY2Vzc2luZyByZWNpcGVzCiogW2BlbWJlZGBdKGh0dHBzOi8vZW1iZWQudGlkeW1vZGVscy5vcmcvKTogRXh0cmEgZW1iZWRkaW5nIHJlY2lwZXMKKiBbYHRvcGljbW9kZWxzYF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RvcGljbW9kZWxzL3ZpZ25ldHRlcy90b3BpY21vZGVscy5wZGYpOiBMREEgdG9waWNtb2RlbGxpbmcgaW4gUgoqIFtgTERBdmlzYF0oaHR0cHM6Ly9naXRodWIuY29tL2Nwc2lldmVydC9MREF2aXopOiBBIGJpdCBjbHVua3kgYnV0IGF3ZXNvbWUgaW50ZXJhY3RpdmUgTERBIHZpc3VhbGl6YXRpb25zCiogW2B0ZXh0MnZlY2BdKGh0dHA6Ly90ZXh0MnZlYy5vcmcvKTogUGFja2FnZSB2b3IgdmVjdG9yIHNwYWNlIG1vZGVsbGluZyAoYWthIGVtYmVkZGluZ3MgJiBvdGhlciB2ZWN0b3JpemF0aW9ucykgb2YgdGV4dGRhdGEKKiBbYHRleHRkYXRhYF0oaHR0cHM6Ly9naXRodWIuY29tL0VtaWxIdml0ZmVsZHQvdGV4dGRhdGEpOiBVc2VmdWwgZGF0YXNldHMgZm9yIHRleHQsIHN1Y2ggYXMgR2xvVmUgZW1iZWRkaW5ncywgc2VudGltZW50IGxleGljYSBldGMuCiogW2B1d290YF0oaHR0cHM6Ly9naXRodWIuY29tL2psbWVsdmlsbGUvdXdvdCk6IFVNQVAgZm9yIFIKCiMjIyBSZWZlcmVuY2VzIAoKQ0hhcHRlcnM6CgoqIEp1bGlhIFNpbGdlIGFuZCBEYXZpZCBSb2JpbnNvbiAoMjAyMCkuIFRleHQgTWluaW5nIHdpdGggUjogQSBUaWR5IEFwcHJvYWNoLCBP4oCZUmVpbGx5LiBPbmxpbmUgYXZhaWxhYmxlIFtoZXJlXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vKQogICAqIFtDaGFwdGVyIDZdKGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90b3BpY21vZGVsaW5nLmh0bWwpOiB4eHgKKiBFbWlsIEh2aWRmZWxkdCBhbmQgSnVsaWEgU2lsZ2UgKDIwMjApLiBTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFRleHQgQW5hbHlzaXMgaW4gUiwgb25saW5lIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9zbWx0YXIuY29tLykKICAgKiBbQ2hhcHRlciA1XShodHRwczovL3NtbHRhci5jb20vZW1iZWRkaW5ncy5odG1sKTogV29yZCBFbWJlZGRpbmdzCgoKQXJ0aWNsZXM6CiogQmxlaSwgRGF2aWQgTS4sIEFuZHJldyBZLiBOZywgYW5kIE1pY2hhZWwgSS4gSm9yZGFuLiAiTGF0ZW50IGRpcmljaGxldCBhbGxvY2F0aW9uLiIgSm91cm5hbCBvZiBtYWNoaW5lIExlYXJuaW5nIHJlc2VhcmNoIDMsIG5vLiBKYW4gKDIwMDMpOiA5OTMtMTAyMi4KKiBKZWZmcmV5IFBlbm5pbmd0b24sIFJpY2hhcmQgU29jaGVyLCBhbmQgQ2hyaXN0b3BoZXIgRCBNYW5uaW5nLiBHbG92ZTogR2xvYmFsIHZlY3RvcnMgZm9yIHdvcmQgcmVwcmVzZW50YXRpb24uIEluIENvbmZlcmVuY2Ugb24gRW1waXJpY2FsIE1ldGhvZHMgb24gTmF0dXJhbCBMYW5ndWFnZSBQcm9jZXNzaW5nIChFTU5MUCksIHBhZ2VzIDE1MzLigJMxNTQzLCAyMDE0CgojIyMgRnVydGhlciBzb3VyY2VzCgoqIFtKdWxpYSBTaWxnZSdzIEJsb2ddKGh0dHBzOi8vanVsaWFzaWxnZS5jb20vKTogRnVsbCBvZiBncmVhdCBleGFtcGxlcyBvZiBwcmVkaWN0aXZlIG1vZGVsaW5nLCBOTFAsIGFuZCB0aGUgY29tYmluYXRpb24gZm8gYm90aCwgdXNpbmcgdGlkeSBlY29zeXN0ZW1zCiogW0VtaWwgSHZpdGZlbGR0J3MgQmxvZ10oaHR0cHM6Ly93d3cuaHZpdGZlbGR0Lm1lLyk6IExpa2V3aXNlLCBmdWxsIG9mIGdyZWF0IGV4YW1wbGVzIG9mIGFwcGxpZWQgdGlkeSBNTCAmIE5MUCBpbiAKCiMjIyBTZXNzaW9uIEluZm8KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoKCgoKCgo=