Putting regular expressions to use

Data wrangling

There are a few functions in R that use regular expressions: regexpr, gregexpr, regmatches, sub, gsub.

Briefly we will perform a basic data wrangling exercise. Allison Parrish created a data set that gathers all of the poems in Project Gutenberg into one json file, which can be found on github. But suppose we do not want to work with json, and we just want a plain text file of all of the poems in Project Gutenberg? That could be useful. We would then use regular expressions to strip out the json and render a plain text file.

setwd("~/Desktop") # make sure your notebook file and all other files are saved on your Desktop
gutenberg.poetry.v <- scan(file="./gutenburg-poetry/gutenberg-poetry-v001-sample500k.ndjson", what="character", sep="\n", encoding = "UTF-8") # you may want to use the smaller file "gutenberg-poetry-v001-sample10k.ndjson" with 10k lines to test
poetry.strip.s.v <- gsub('\\{"s": "', " ", gutenberg.poetry.v)
poetry.strip.s.v
gutenberg.poems.plain.v <- gsub(', "gid": "\\d+"\\}', " ", poetry.strip.s.v)
gutenberg.poems.plain.v[1:10] # show the first ten lines just to see if it worked
write.table(gutenberg.poems.plain.v, "gutenberg-poems.txt", row.names=F)

Now you have a plain text file with a numbered list of lines of poetry. Now you can upload this file into Voyant or run it through AntConc for basic text analysis results.

Cleaning up Dickens

If you have not already, download the text file of Dickens’s Great Expectations, or copy the file from our github corpus, onto your working directory and scan the text.

dickens.v <- scan("great-expectations.txt", what="character", sep="\n", encoding = "UTF-8")

You have now loaded Great Expectations into a variable called dickens.v.

With the text loaded, you can now run quick statistical operations, such as the number of lines and word frequencies.

length(dickens.v) # this finds the number of lines in the book

dickens.lower.v <- tolower(dickens.v) # this makes the whole text lowercased, and each sentence is now in a list

dickens.words <- strsplit(dickens.lower.v, "\\W") # strsplit is very important: it takes each sentence in the lowercased words vector and puts each word in a list by finding non-words, i.e., word boundaries
# each list item (word) corresponds to an element of the book's sentences that has been split. In the simplest case, x is a single character string, and strsplit outputs a one-item list.

class(dickens.words) # the class function tells you the data structure of your variable

dickens.words.v <- unlist(dickens.words)

class(dickens.words.v)

dickens.words.v[1:20] # find the first 20 ten words in Great Expectations

Did you notice the “\W” in the strsplit argument? What is that again? Regex! Notice that in R you need to use another backslash to indicate a character escape.

Also, did you notice the blank result on the 10th word? This requires a little clean-up step.

not.blanks.v <- which(dickens.words.v!="")

dickens.words.v <- dickens.words.v[not.blanks.v]

Extra white spaces often cause problems for text analysis.

dickens.words.v[1:20]

Voila! We might want to examine how many times the third result “father” occurs (the fourth word result, and one that will probably be an important word in this book).

length(dickens.words.v[which(dickens.words.v=="father")])

Or produce a list of all unique words.

unique(sort(dickens.words.v, decreasing = FALSE))[1:50]

Here we find another problem: we find in our unique word list some odd non-words such as “0037m.” We should strip those out.

Exercise

Create a regular expression to remove those non-words in dickens.words.v? Remember that you use two backslashes (//) for character escape. For more information on using regex in R, RStudio has a helpful cheat sheet.

Now let’s re-run that not.blanks vector to strip out the blank you just added.

not.blanks.v <- which(dickens.words.clean.v!="")

dickens.words.clean.v <- dickens.words.clean.v[not.blanks.v]

unique(sort(dickens.words.clean.v, decreasing = FALSE))[1:50]

Returning to basic functions, now that we have done some more clean-up: how many unique words are in the book?

length(unique(dickens.words.clean.v))

Divide this by the amount of words in the whole book to calculate vocabulary density ratios.

unique.words <- length(unique(dickens.words.clean.v))

total.words <- length(dickens.words.clean.v)

unique.words/total.words 
# you could do this quicker this way: 
# length(unique(dickens.words.v))/length(dickens.words.v) 
# BUT it's good to get into the practice of storing results in variables

That’s actually a fairly small density number, 5.7% (Moby-Dick by comparison is about 8%).

The other important data structures are tables and data frames. These are probably the most useful for sophisticated analyses, because it renders the data in a table that is very similar to a spreadsheet. It is important to input your data in an Excel or Google docs spreadsheet and then export that data into a comma separated value (.csv) or tab separated value (.tsv) file. Many of the tidytext operations work with data frames, as we’ll see later.

Flow control: For-loops and conditionals in R

Flow control involves stochastic simulation, or repetitive operations or pattern recognition—two of the more important reasons why we use programming languages. The most common form of stochastic simulation is the for() loop. This is a logical command with the following syntax

for (name in seq) {[enter commands]}

This sets a variable called name (any thing you choose to assign) equal to each of the elements of the sequence (any sequence of values), which is usually a vector. Each of these iterates over the command as many times as is necessary.

for (i in letters[1:10]){
  cat(i, ", which is followed by \n")
}

What this literally means is: create a variable called i as an index for the loop. The first value of i is a (the first value of letters, after the in), and R executes the function within the loop (taking the instructions within the curly brackets). The code above just prints i and the text “, which is followed by” with a new line (signified by the regex “”). When the closing bracket is reached, i moves onto the next value (the second letter). When the loop reaches the last value of the sequence (the tenth of the letters), it is completed.

Another simple example is the Fibonacci sequence. A for() loop can automatically generate the first 20 Fibonacci numbers.

Fibonacci <- numeric(20) # creates a vector called Fibonacci that consists of 20 numeric vectors

Fibonacci[1] <- Fibonacci[2] <- 1 # defines the first and second elements as a value of 1. This is important b/c the first two Fibonacci numbers are 1, and the next (3rd) number is gained by adding the first two

for (i in 3:20) Fibonacci[i] <- Fibonacci[i - 2] + Fibonacci[i - 1] # says for each instance of the 3rd through 20th Fibonacci numbers, take the first element - 2 and add that to the next element - 1
Fibonacci

There is another important component to flow control: the conditional. In programming this takes the form of if() statements.

Syntax

if (condition) {commands when TRUE}

if (condition) {commands when TRUE} else {commands when FALSE}

We will not have time to go into details regarding these operations, but it is important to recognize them when you are reading or modifying someone else’s code.

Now, using what we know about regular expressions and flow control, let’s have look at a for() loop that Matthew Jockers uses in Chapter 4 of his Text Analysis for Students of Literature. It’s a fairly complicated but useful way of breaking up a novel text into chapters for analysis. Let’s use it to process the Dickens novel.

length(chapter.freqs.l)[1]
[1] 58

Suppose I wanted to get all relative frequencies of the word “father” in each chapter.

father.freqs <- lapply(chapter.freqs.l, '[', 'father')

father.freqs

You could also use variations of the which function to identify the chapters with the highest and lowest frequencies.

which.max(father.freqs)

which.min(father.freqs)

Exercise

Create a vector that confines your results to only the paragraphs with dialogue.

dialogue.v <- grep('"(.*?)"', novel.lines.v) # grep is another regex function

novel.lines.v[dialogue.v][1:20] 
# check your work by finding all the dialogue lines in novel.lines.v

Bonus Exercise

Modify the for loop in Jockers to find word frequencies only of content with dialogue.

dialogue.chapter.raws.l <- list()
dialogue.chapter.freqs.l <- list()

for(i in 1:length(chap.positions.v)){
    if(i != length(chap.positions.v)){
chapter.title <- novel.lines.v[chap.positions.v[i]]
start <- chap.positions.v[i]+1
end <- chap.positions.v[i+1]-1
chapter.lines.v <- novel.lines.v[start:end]
dialogue.lines.v <- grep('"(.*?)"', chapter.lines.v, value = TRUE) # here is the grep again, pruning the chapter.lines vector into lines with dialogue
chapter.words.v <- tolower(paste(dialogue.lines.v, collapse=" ")) 
chapter.words.l <- strsplit(chapter.words.v, "\\W")
chapter.word.v <- unlist(chapter.words.l)
chapter.word.v <- chapter.word.v[which(chapter.word.v!="")] 
chapter.freqs.t <- table(chapter.word.v) 
dialogue.chapter.raws.l[[chapter.title]] <- chapter.freqs.t 
chapter.freqs.t.rel <- 100*(chapter.freqs.t/sum(chapter.freqs.t)) 
dialogue.chapter.freqs.l[[chapter.title]] <- chapter.freqs.t.rel
    } 
}

dialogue.chapter.freqs.l[1]

Visualising the data with plot

This gives us a general impression of vocabulary density on a chapter-by-chapter basis. Let’s now return to the previous word search of “father.” Suppose we wanted to visualise that frequency of “father” alongside a similar concept, “son.”

We need to introduce a new function, lapply. The lapply function is similar to a for loop, in that it iterates over the elements in a data structure, but it is specifically designed for dealing with lists. It also requires a list as a second argument, and the name of some other function.

chapter.freqs.l[[1]]["father"]
   father 
0.2695418 
chapter.freqs.l[[10]]["son"]
       son 
0.03901678 

The above is just an example: The word “father” appears with 27% relative frequency (that is, 27 times for every 100 words in the chapter) in the first chapter, and the word “son” appear with a 4% relative frequency in the 10th chapter. Now let’s create vectors that store the relative frequencies for each chapter.

Instead of just printing out the values held in this new list, you can capture the results into a single matrix using the rbind function. The do.call functions binds the contents of each list item into rows; this effectively activate the rbind function across the list of “father” and “son” results, respectively.

Let’s look at one of these matrices.

sons.m
                      <NA>
Chapter I               NA
Chapter II              NA
Chapter III             NA
Chapter IV              NA
Chapter V               NA
Chapter VI              NA
Chapter VII             NA
Chapter VIII            NA
Chapter IX      0.03696858
Chapter X       0.03901678
Chapter XI              NA
Chapter XII             NA
Chapter XIII            NA
Chapter XIV             NA
Chapter XV              NA
Chapter XVI             NA
Chapter XVII            NA
Chapter XVIII   0.01953507
Chapter XIX             NA
Chapter XX              NA
Chapter XXI             NA
Chapter XXII    0.03972195
Chapter XXIII   0.03092146
Chapter XXIV            NA
Chapter XXV     0.06563833
Chapter XXVI            NA
Chapter XXVII           NA
Chapter XXVIII          NA
Chapter XXIX            NA
Chapter XXX     0.08818342
Chapter XXXI            NA
Chapter XXXII           NA
Chapter XXXIII          NA
Chapter XXXIV           NA
Chapter XXXV            NA
Chapter XXXVI           NA
Chapter XXXVII  0.27864855
Chapter XXXVIII         NA
Chapter XXXIX   0.04004806
Chapter XL              NA
Chapter XLI             NA
Chapter XLII            NA
Chapter XLIII           NA
Chapter XLIV    0.03423485
Chapter XLV             NA
Chapter XLVI    0.03289474
Chapter XLVII           NA
Chapter XLVIII          NA
Chapter XLIX            NA
Chapter L               NA
Chapter LI              NA
Chapter LII             NA
Chapter LIII            NA
Chapter LIV             NA
Chapter LV      0.03460208
Chapter LVI             NA
Chapter LVII            NA
Chapter LVIII           NA

Compare it to the other matrix of “father” results.

fathers.m
                    father
Chapter I       0.26954178
Chapter II              NA
Chapter III             NA
Chapter IV              NA
Chapter V               NA
Chapter VI              NA
Chapter VII     0.14702279
Chapter VIII            NA
Chapter IX      0.03696858
Chapter X               NA
Chapter XI              NA
Chapter XII             NA
Chapter XIII            NA
Chapter XIV             NA
Chapter XV              NA
Chapter XVI             NA
Chapter XVII            NA
Chapter XVIII           NA
Chapter XIX             NA
Chapter XX      0.06211180
Chapter XXI     0.11055832
Chapter XXII    0.37735849
Chapter XXIII   0.03092146
Chapter XXIV            NA
Chapter XXV             NA
Chapter XXVI            NA
Chapter XXVII   0.06510417
Chapter XXVIII          NA
Chapter XXIX    0.02010454
Chapter XXX     0.26455026
Chapter XXXI            NA
Chapter XXXII           NA
Chapter XXXIII          NA
Chapter XXXIV   0.04230118
Chapter XXXV            NA
Chapter XXXVI           NA
Chapter XXXVII  0.03483107
Chapter XXXVIII         NA
Chapter XXXIX   0.02002403
Chapter XL              NA
Chapter XLI             NA
Chapter XLII            NA
Chapter XLIII           NA
Chapter XLIV            NA
Chapter XLV             NA
Chapter XLVI    0.13157895
Chapter XLVII           NA
Chapter XLVIII          NA
Chapter XLIX            NA
Chapter L       0.13071895
Chapter LI      0.29791460
Chapter LII             NA
Chapter LIII    0.01871608
Chapter LIV             NA
Chapter LV      0.03460208
Chapter LVI             NA
Chapter LVII            NA
Chapter LVIII   0.03205128

Next we create vectors for each search term; the following extracts the father and son values into two new vectors, and uses the cbind functions to combine these vectors into a new, two-column matrix consisting of 58 rows and 2 columns.

dim(fathers.sons.m)
[1] 58  2

Now we can visualise these two word searches.

LS0tCnRpdGxlOiAnTGVjdHVyZSA4OiBJbnRyb2R1Y3Rpb24gdG8gUiwgUGFydCAyLCAxOSBhbmQgMjMgU2VwdGVtYmVyIDIwMTknCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKLS0tCgojIyMgUHV0dGluZyByZWd1bGFyIGV4cHJlc3Npb25zIHRvIHVzZQoKIyMjIyBEYXRhIHdyYW5nbGluZwoKVGhlcmUgYXJlIGEgZmV3IGZ1bmN0aW9ucyBpbiBSIHRoYXQgdXNlIHJlZ3VsYXIgZXhwcmVzc2lvbnM6IGByZWdleHByYCwgYGdyZWdleHByYCwgYHJlZ21hdGNoZXNgLCBgc3ViYCwgYGdzdWJgLgoKQnJpZWZseSB3ZSB3aWxsIHBlcmZvcm0gYSBiYXNpYyBkYXRhIHdyYW5nbGluZyBleGVyY2lzZS4gQWxsaXNvbiBQYXJyaXNoIGNyZWF0ZWQgYSBkYXRhIHNldCB0aGF0IGdhdGhlcnMgYWxsIG9mIHRoZSBwb2VtcyBpbiBQcm9qZWN0IEd1dGVuYmVyZyBpbnRvIG9uZSBqc29uIGZpbGUsIHdoaWNoIGNhbiBiZSBmb3VuZCBvbiBbZ2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vYXBhcnJpc2gvZ3V0ZW5iZXJnLXBvZXRyeS1jb3JwdXMpLiBCdXQgc3VwcG9zZSB3ZSBkbyBub3Qgd2FudCB0byB3b3JrIHdpdGgganNvbiwgYW5kIHdlIGp1c3Qgd2FudCBhIHBsYWluIHRleHQgZmlsZSBvZiBhbGwgb2YgdGhlIHBvZW1zIGluIFByb2plY3QgR3V0ZW5iZXJnPyBUaGF0IGNvdWxkIGJlIHVzZWZ1bC4gV2Ugd291bGQgdGhlbiB1c2UgcmVndWxhciBleHByZXNzaW9ucyB0byBzdHJpcCBvdXQgdGhlIGpzb24gYW5kIHJlbmRlciBhIHBsYWluIHRleHQgZmlsZS4KCmBgYHtyfSAKc2V0d2QoIn4vRGVza3RvcCIpICMgbWFrZSBzdXJlIHlvdXIgbm90ZWJvb2sgZmlsZSBhbmQgYWxsIG90aGVyIGZpbGVzIGFyZSBzYXZlZCBvbiB5b3VyIERlc2t0b3AKZ3V0ZW5iZXJnLnBvZXRyeS52IDwtIHNjYW4oZmlsZT0iLi9ndXRlbmJ1cmctcG9ldHJ5L2d1dGVuYmVyZy1wb2V0cnktdjAwMS1zYW1wbGU1MDBrLm5kanNvbiIsIHdoYXQ9ImNoYXJhY3RlciIsIHNlcD0iXG4iLCBlbmNvZGluZyA9ICJVVEYtOCIpICMgeW91IG1heSB3YW50IHRvIHVzZSB0aGUgc21hbGxlciBmaWxlICJndXRlbmJlcmctcG9ldHJ5LXYwMDEtc2FtcGxlMTBrLm5kanNvbiIgd2l0aCAxMGsgbGluZXMgdG8gdGVzdApwb2V0cnkuc3RyaXAucy52IDwtIGdzdWIoJ1xceyJzIjogIicsICIgIiwgZ3V0ZW5iZXJnLnBvZXRyeS52KQpwb2V0cnkuc3RyaXAucy52Cmd1dGVuYmVyZy5wb2Vtcy5wbGFpbi52IDwtIGdzdWIoJywgImdpZCI6ICJcXGQrIlxcfScsICIgIiwgcG9ldHJ5LnN0cmlwLnMudikKZ3V0ZW5iZXJnLnBvZW1zLnBsYWluLnZbMToxMF0gIyBzaG93IHRoZSBmaXJzdCB0ZW4gbGluZXMganVzdCB0byBzZWUgaWYgaXQgd29ya2VkCndyaXRlLnRhYmxlKGd1dGVuYmVyZy5wb2Vtcy5wbGFpbi52LCAiZ3V0ZW5iZXJnLXBvZW1zLnR4dCIsIHJvdy5uYW1lcz1GKQpgYGAKCk5vdyB5b3UgaGF2ZSBhIHBsYWluIHRleHQgZmlsZSB3aXRoIGEgbnVtYmVyZWQgbGlzdCBvZiBsaW5lcyBvZiBwb2V0cnkuIE5vdyB5b3UgY2FuIHVwbG9hZCB0aGlzIGZpbGUgaW50byBWb3lhbnQgb3IgcnVuIGl0IHRocm91Z2ggQW50Q29uYyBmb3IgYmFzaWMgdGV4dCBhbmFseXNpcyByZXN1bHRzLgoKIyMjIyBDbGVhbmluZyB1cCBEaWNrZW5zCgpJZiB5b3UgaGF2ZSBub3QgYWxyZWFkeSwgZG93bmxvYWQgdGhlIHRleHQgZmlsZSBvZiBEaWNrZW5zJ3MgWypHcmVhdCBFeHBlY3RhdGlvbnMqXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zL3FqaTl1ZWI0NmFqYWl0OS9kaWNrZW5zX2dyZWF0LWV4cGVjdGF0aW9ucy50eHQ/ZGw9MCksIG9yIGNvcHkgdGhlIGZpbGUgZnJvbSBvdXIgZ2l0aHViIGNvcnB1cywgb250byB5b3VyIHdvcmtpbmcgZGlyZWN0b3J5IGFuZCBzY2FuIHRoZSB0ZXh0LgoKYGBge3J9CmRpY2tlbnMudiA8LSBzY2FuKCJncmVhdC1leHBlY3RhdGlvbnMudHh0Iiwgd2hhdD0iY2hhcmFjdGVyIiwgc2VwPSJcbiIsIGVuY29kaW5nID0gIlVURi04IikKYGBgCllvdSBoYXZlIG5vdyBsb2FkZWQgKkdyZWF0IEV4cGVjdGF0aW9ucyogaW50byBhIHZhcmlhYmxlIGNhbGxlZCBgZGlja2Vucy52YC4KCldpdGggdGhlIHRleHQgbG9hZGVkLCB5b3UgY2FuIG5vdyBydW4gcXVpY2sgc3RhdGlzdGljYWwgb3BlcmF0aW9ucywgc3VjaCBhcyB0aGUgbnVtYmVyIG9mIGxpbmVzIGFuZCB3b3JkIGZyZXF1ZW5jaWVzLgoKYGBge3J9Cmxlbmd0aChkaWNrZW5zLnYpICMgdGhpcyBmaW5kcyB0aGUgbnVtYmVyIG9mIGxpbmVzIGluIHRoZSBib29rCgpkaWNrZW5zLmxvd2VyLnYgPC0gdG9sb3dlcihkaWNrZW5zLnYpICMgdGhpcyBtYWtlcyB0aGUgd2hvbGUgdGV4dCBsb3dlcmNhc2VkLCBhbmQgZWFjaCBzZW50ZW5jZSBpcyBub3cgaW4gYSBsaXN0CgpkaWNrZW5zLndvcmRzIDwtIHN0cnNwbGl0KGRpY2tlbnMubG93ZXIudiwgIlxcVyIpICMgc3Ryc3BsaXQgaXMgdmVyeSBpbXBvcnRhbnQ6IGl0IHRha2VzIGVhY2ggc2VudGVuY2UgaW4gdGhlIGxvd2VyY2FzZWQgd29yZHMgdmVjdG9yIGFuZCBwdXRzIGVhY2ggd29yZCBpbiBhIGxpc3QgYnkgZmluZGluZyBub24td29yZHMsIGkuZS4sIHdvcmQgYm91bmRhcmllcwojIGVhY2ggbGlzdCBpdGVtICh3b3JkKSBjb3JyZXNwb25kcyB0byBhbiBlbGVtZW50IG9mIHRoZSBib29rJ3Mgc2VudGVuY2VzIHRoYXQgaGFzIGJlZW4gc3BsaXQuIEluIHRoZSBzaW1wbGVzdCBjYXNlLCB4IGlzIGEgc2luZ2xlIGNoYXJhY3RlciBzdHJpbmcsIGFuZCBzdHJzcGxpdCBvdXRwdXRzIGEgb25lLWl0ZW0gbGlzdC4KCmNsYXNzKGRpY2tlbnMud29yZHMpICMgdGhlIGNsYXNzIGZ1bmN0aW9uIHRlbGxzIHlvdSB0aGUgZGF0YSBzdHJ1Y3R1cmUgb2YgeW91ciB2YXJpYWJsZQoKZGlja2Vucy53b3Jkcy52IDwtIHVubGlzdChkaWNrZW5zLndvcmRzKQoKY2xhc3MoZGlja2Vucy53b3Jkcy52KQoKZGlja2Vucy53b3Jkcy52WzE6MjBdICMgZmluZCB0aGUgZmlyc3QgMjAgdGVuIHdvcmRzIGluIEdyZWF0IEV4cGVjdGF0aW9ucwpgYGAKCkRpZCB5b3Ugbm90aWNlIHRoZSAiXFxXIiBpbiB0aGUgYHN0cnNwbGl0YCBhcmd1bWVudD8gV2hhdCBpcyB0aGF0IGFnYWluPyBSZWdleCEgTm90aWNlIHRoYXQgaW4gUiB5b3UgbmVlZCB0byB1c2UgYW5vdGhlciBiYWNrc2xhc2ggdG8gaW5kaWNhdGUgYSBjaGFyYWN0ZXIgZXNjYXBlLgoKQWxzbywgZGlkIHlvdSBub3RpY2UgdGhlIGJsYW5rIHJlc3VsdCBvbiB0aGUgMTB0aCB3b3JkPyBUaGlzIHJlcXVpcmVzIGEgbGl0dGxlIGNsZWFuLXVwIHN0ZXAuCgpgYGB7cn0Kbm90LmJsYW5rcy52IDwtIHdoaWNoKGRpY2tlbnMud29yZHMudiE9IiIpCgpkaWNrZW5zLndvcmRzLnYgPC0gZGlja2Vucy53b3Jkcy52W25vdC5ibGFua3Mudl0KYGBgCgpFeHRyYSB3aGl0ZSBzcGFjZXMgb2Z0ZW4gY2F1c2UgcHJvYmxlbXMgZm9yIHRleHQgYW5hbHlzaXMuCgpgYGB7cn0KZGlja2Vucy53b3Jkcy52WzE6MjBdCmBgYAoKClZvaWxhISBXZSBtaWdodCB3YW50IHRvIGV4YW1pbmUgaG93IG1hbnkgdGltZXMgdGhlIHRoaXJkIHJlc3VsdCAiZmF0aGVyIiBvY2N1cnMgKHRoZSBmb3VydGggd29yZCByZXN1bHQsIGFuZCBvbmUgdGhhdCB3aWxsIHByb2JhYmx5IGJlIGFuIGltcG9ydGFudCB3b3JkIGluIHRoaXMgYm9vaykuCgpgYGB7cn0KbGVuZ3RoKGRpY2tlbnMud29yZHMudlt3aGljaChkaWNrZW5zLndvcmRzLnY9PSJmYXRoZXIiKV0pCmBgYAoKT3IgcHJvZHVjZSBhIGxpc3Qgb2YgYWxsIHVuaXF1ZSB3b3Jkcy4KCmBgYHtyfQp1bmlxdWUoc29ydChkaWNrZW5zLndvcmRzLnYsIGRlY3JlYXNpbmcgPSBGQUxTRSkpWzE6NTBdCmBgYAoKSGVyZSB3ZSBmaW5kIGFub3RoZXIgcHJvYmxlbTogd2UgZmluZCBpbiBvdXIgdW5pcXVlIHdvcmQgbGlzdCBzb21lIG9kZCBub24td29yZHMgc3VjaCBhcyAiMDAzN20uIiBXZSBzaG91bGQgc3RyaXAgdGhvc2Ugb3V0LgoKIyMgRXhlcmNpc2UKCkNyZWF0ZSBhIHJlZ3VsYXIgZXhwcmVzc2lvbiB0byByZW1vdmUgdGhvc2Ugbm9uLXdvcmRzIGluIGBkaWNrZW5zLndvcmRzLnZgPyBSZW1lbWJlciB0aGF0IHlvdSB1c2UgdHdvIGJhY2tzbGFzaGVzICgvLykgZm9yIGNoYXJhY3RlciBlc2NhcGUuIEZvciBtb3JlIGluZm9ybWF0aW9uIG9uIHVzaW5nIHJlZ2V4IGluIFIsIFJTdHVkaW8gaGFzIGEgaGVscGZ1bCBbY2hlYXQgc2hlZXRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE2LzA5L1JlZ0V4Q2hlYXRzaGVldC5wZGYpLgoKYGBge3J9CgpgYGAKCk5vdyBsZXQncyByZS1ydW4gdGhhdCBub3QuYmxhbmtzIHZlY3RvciB0byBzdHJpcCBvdXQgdGhlIGJsYW5rIHlvdSBqdXN0IGFkZGVkLiAKCmBgYHtyfQpub3QuYmxhbmtzLnYgPC0gd2hpY2goZGlja2Vucy53b3Jkcy5jbGVhbi52IT0iIikKCmRpY2tlbnMud29yZHMuY2xlYW4udiA8LSBkaWNrZW5zLndvcmRzLmNsZWFuLnZbbm90LmJsYW5rcy52XQoKdW5pcXVlKHNvcnQoZGlja2Vucy53b3Jkcy5jbGVhbi52LCBkZWNyZWFzaW5nID0gRkFMU0UpKVsxOjUwXQpgYGAKClJldHVybmluZyB0byBiYXNpYyBmdW5jdGlvbnMsIG5vdyB0aGF0IHdlIGhhdmUgZG9uZSBzb21lIG1vcmUgY2xlYW4tdXA6IGhvdyBtYW55IHVuaXF1ZSB3b3JkcyBhcmUgaW4gdGhlIGJvb2s/CgpgYGB7cn0KbGVuZ3RoKHVuaXF1ZShkaWNrZW5zLndvcmRzLmNsZWFuLnYpKQpgYGAKCkRpdmlkZSB0aGlzIGJ5IHRoZSBhbW91bnQgb2Ygd29yZHMgaW4gdGhlIHdob2xlIGJvb2sgdG8gY2FsY3VsYXRlIHZvY2FidWxhcnkgZGVuc2l0eSByYXRpb3MuCgpgYGB7cn0KdW5pcXVlLndvcmRzIDwtIGxlbmd0aCh1bmlxdWUoZGlja2Vucy53b3Jkcy5jbGVhbi52KSkKCnRvdGFsLndvcmRzIDwtIGxlbmd0aChkaWNrZW5zLndvcmRzLmNsZWFuLnYpCgp1bmlxdWUud29yZHMvdG90YWwud29yZHMgCiMgeW91IGNvdWxkIGRvIHRoaXMgcXVpY2tlciB0aGlzIHdheTogCiMgbGVuZ3RoKHVuaXF1ZShkaWNrZW5zLndvcmRzLnYpKS9sZW5ndGgoZGlja2Vucy53b3Jkcy52KSAKIyBCVVQgaXQncyBnb29kIHRvIGdldCBpbnRvIHRoZSBwcmFjdGljZSBvZiBzdG9yaW5nIHJlc3VsdHMgaW4gdmFyaWFibGVzCmBgYApUaGF0J3MgYWN0dWFsbHkgYSBmYWlybHkgc21hbGwgZGVuc2l0eSBudW1iZXIsIDUuNyUgKCpNb2J5LURpY2sqIGJ5IGNvbXBhcmlzb24gaXMgYWJvdXQgOCUpLgoKVGhlIG90aGVyIGltcG9ydGFudCBkYXRhIHN0cnVjdHVyZXMgYXJlIHRhYmxlcyBhbmQgZGF0YSBmcmFtZXMuIFRoZXNlIGFyZSBwcm9iYWJseSB0aGUgbW9zdCB1c2VmdWwgZm9yIHNvcGhpc3RpY2F0ZWQgYW5hbHlzZXMsIGJlY2F1c2UgaXQgcmVuZGVycyB0aGUgZGF0YSBpbiBhIHRhYmxlIHRoYXQgaXMgdmVyeSBzaW1pbGFyIHRvIGEgc3ByZWFkc2hlZXQuIEl0IGlzIGltcG9ydGFudCB0byBpbnB1dCB5b3VyIGRhdGEgaW4gYW4gRXhjZWwgb3IgR29vZ2xlIGRvY3Mgc3ByZWFkc2hlZXQgYW5kIHRoZW4gZXhwb3J0IHRoYXQgZGF0YSBpbnRvIGEgY29tbWEgc2VwYXJhdGVkIHZhbHVlICguY3N2KSBvciB0YWIgc2VwYXJhdGVkIHZhbHVlICgudHN2KSBmaWxlLiBNYW55IG9mIHRoZSB0aWR5dGV4dCBvcGVyYXRpb25zIHdvcmsgd2l0aCBkYXRhIGZyYW1lcywgYXMgd2UnbGwgc2VlIGxhdGVyLgoKIyMjIEZsb3cgY29udHJvbDogRm9yLWxvb3BzIGFuZCBjb25kaXRpb25hbHMgaW4gUgoKRmxvdyBjb250cm9sIGludm9sdmVzICoqc3RvY2hhc3RpYyBzaW11bGF0aW9uKiosIG9yIHJlcGV0aXRpdmUgb3BlcmF0aW9ucyBvciBwYXR0ZXJuIHJlY29nbml0aW9uLS0tdHdvIG9mIHRoZSBtb3JlIGltcG9ydGFudCByZWFzb25zIHdoeSB3ZSB1c2UgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzLiBUaGUgbW9zdCBjb21tb24gZm9ybSBvZiBzdG9jaGFzdGljIHNpbXVsYXRpb24gaXMgdGhlIGZvcigpIGxvb3AuIFRoaXMgaXMgYSBsb2dpY2FsIGNvbW1hbmQgd2l0aCB0aGUgZm9sbG93aW5nIHN5bnRheAoKZm9yIChgbmFtZWAgaW4gYHNlcWApIHtbZW50ZXIgY29tbWFuZHNdfQoKVGhpcyBzZXRzIGEgdmFyaWFibGUgY2FsbGVkIGBuYW1lYCAoYW55IHRoaW5nIHlvdSBjaG9vc2UgdG8gYXNzaWduKSBlcXVhbCB0byBlYWNoIG9mIHRoZSBlbGVtZW50cyBvZiB0aGUgc2VxdWVuY2UgKGFueSBzZXF1ZW5jZSBvZiB2YWx1ZXMpLCB3aGljaCBpcyB1c3VhbGx5IGEgdmVjdG9yLiBFYWNoIG9mIHRoZXNlIGl0ZXJhdGVzIG92ZXIgdGhlIGNvbW1hbmQgYXMgbWFueSB0aW1lcyBhcyBpcyBuZWNlc3NhcnkuIAoKYGBge3J9CmZvciAoaSBpbiBsZXR0ZXJzWzE6MTBdKXsKICBjYXQoaSwgIiwgd2hpY2ggaXMgZm9sbG93ZWQgYnkgXG4iKQp9CmBgYAoKCldoYXQgdGhpcyBsaXRlcmFsbHkgbWVhbnMgaXM6IGNyZWF0ZSBhIHZhcmlhYmxlIGNhbGxlZCBgaWAgYXMgYW4gaW5kZXggZm9yIHRoZSBsb29wLiBUaGUgZmlyc3QgdmFsdWUgb2YgYGlgIGlzIGBhYCAodGhlIGZpcnN0IHZhbHVlIG9mIGBsZXR0ZXJzYCwgYWZ0ZXIgdGhlIGBpbmApLCBhbmQgUiBleGVjdXRlcyB0aGUgZnVuY3Rpb24gd2l0aGluIHRoZSBsb29wICh0YWtpbmcgdGhlIGluc3RydWN0aW9ucyB3aXRoaW4gdGhlIGN1cmx5IGJyYWNrZXRzKS4gVGhlIGNvZGUgYWJvdmUganVzdCBwcmludHMgYGlgIGFuZCB0aGUgdGV4dCAiLCB3aGljaCBpcyBmb2xsb3dlZCBieSIgd2l0aCBhIG5ldyBsaW5lIChzaWduaWZpZWQgYnkgdGhlIHJlZ2V4ICJcbiIpLiBXaGVuIHRoZSBjbG9zaW5nIGJyYWNrZXQgaXMgcmVhY2hlZCwgYGlgIG1vdmVzIG9udG8gdGhlIG5leHQgdmFsdWUgKHRoZSBzZWNvbmQgbGV0dGVyKS4gV2hlbiB0aGUgbG9vcCByZWFjaGVzIHRoZSBsYXN0IHZhbHVlIG9mIHRoZSBzZXF1ZW5jZSAodGhlIHRlbnRoIG9mIHRoZSBgbGV0dGVyc2ApLCBpdCBpcyBjb21wbGV0ZWQuCgpBbm90aGVyIHNpbXBsZSBleGFtcGxlIGlzIHRoZSBGaWJvbmFjY2kgc2VxdWVuY2UuIEEgZm9yKCkgbG9vcCBjYW4gYXV0b21hdGljYWxseSBnZW5lcmF0ZSB0aGUgZmlyc3QgMjAgRmlib25hY2NpIG51bWJlcnMuCgpgYGB7cn0KRmlib25hY2NpIDwtIG51bWVyaWMoMjApICMgY3JlYXRlcyBhIHZlY3RvciBjYWxsZWQgRmlib25hY2NpIHRoYXQgY29uc2lzdHMgb2YgMjAgbnVtZXJpYyB2ZWN0b3JzCgpGaWJvbmFjY2lbMV0gPC0gRmlib25hY2NpWzJdIDwtIDEgIyBkZWZpbmVzIHRoZSBmaXJzdCBhbmQgc2Vjb25kIGVsZW1lbnRzIGFzIGEgdmFsdWUgb2YgMS4gVGhpcyBpcyBpbXBvcnRhbnQgYi9jIHRoZSBmaXJzdCB0d28gRmlib25hY2NpIG51bWJlcnMgYXJlIDEsIGFuZCB0aGUgbmV4dCAoM3JkKSBudW1iZXIgaXMgZ2FpbmVkIGJ5IGFkZGluZyB0aGUgZmlyc3QgdHdvCgpmb3IgKGkgaW4gMzoyMCkgRmlib25hY2NpW2ldIDwtIEZpYm9uYWNjaVtpIC0gMl0gKyBGaWJvbmFjY2lbaSAtIDFdICMgc2F5cyBmb3IgZWFjaCBpbnN0YW5jZSBvZiB0aGUgM3JkIHRocm91Z2ggMjB0aCBGaWJvbmFjY2kgbnVtYmVycywgdGFrZSB0aGUgZmlyc3QgZWxlbWVudCAtIDIgYW5kIGFkZCB0aGF0IHRvIHRoZSBuZXh0IGVsZW1lbnQgLSAxCkZpYm9uYWNjaQpgYGAKClRoZXJlIGlzIGFub3RoZXIgaW1wb3J0YW50IGNvbXBvbmVudCB0byBmbG93IGNvbnRyb2w6IHRoZSBjb25kaXRpb25hbC4gSW4gcHJvZ3JhbW1pbmcgdGhpcyB0YWtlcyB0aGUgZm9ybSBvZiBgaWYoKWAgc3RhdGVtZW50cy4KCioqU3ludGF4KioKCmBpZiAoY29uZGl0aW9uKSB7Y29tbWFuZHMgd2hlbiBUUlVFfWAKCmBpZiAoY29uZGl0aW9uKSB7Y29tbWFuZHMgd2hlbiBUUlVFfSBlbHNlIHtjb21tYW5kcyB3aGVuIEZBTFNFfWAKCldlIHdpbGwgbm90IGhhdmUgdGltZSB0byBnbyBpbnRvIGRldGFpbHMgcmVnYXJkaW5nIHRoZXNlIG9wZXJhdGlvbnMsIGJ1dCBpdCBpcyBpbXBvcnRhbnQgdG8gcmVjb2duaXplIHRoZW0gd2hlbiB5b3UgYXJlIHJlYWRpbmcgb3IgbW9kaWZ5aW5nIHNvbWVvbmUgZWxzZSdzIGNvZGUuCgpOb3csIHVzaW5nIHdoYXQgd2Uga25vdyBhYm91dCByZWd1bGFyIGV4cHJlc3Npb25zIGFuZCBmbG93IGNvbnRyb2wsIGxldCdzIGhhdmUgbG9vayBhdCBhIGZvcigpIGxvb3AgdGhhdCBNYXR0aGV3IEpvY2tlcnMgdXNlcyBpbiBDaGFwdGVyIDQgb2YgaGlzICpUZXh0IEFuYWx5c2lzIGZvciBTdHVkZW50cyBvZiBMaXRlcmF0dXJlKi4gSXQncyBhIGZhaXJseSBjb21wbGljYXRlZCBidXQgdXNlZnVsIHdheSBvZiBicmVha2luZyB1cCBhIG5vdmVsIHRleHQgaW50byBjaGFwdGVycyBmb3IgYW5hbHlzaXMuIExldCdzIHVzZSBpdCB0byBwcm9jZXNzIHRoZSBEaWNrZW5zIG5vdmVsLiAKCmBgYHtyfQp0ZXh0LnYgPC0gc2NhbigiZGlja2Vuc19ncmVhdC1leHBlY3RhdGlvbnMudHh0Iiwgd2hhdD0iY2hhcmFjdGVyIiwgc2VwPSJcbiIsIGVuY29kaW5nID0gIlVURi04IikKbm90LmJsYW5rcy52IDwtIHdoaWNoKHRleHQudiE9IiIpCmNsZWFuLnRleHQudiA8LSB0ZXh0LnZbbm90LmJsYW5rcy52XQoKc3RhcnQudiA8LSB3aGljaChjbGVhbi50ZXh0LnYgPT0gIkNoYXB0ZXIgSSIpCmVuZC52IDwtIHdoaWNoKGNsZWFuLnRleHQudiA9PSAiVEhFIEVORCIpCm5vdmVsLmxpbmVzLnYgPC0gY2xlYW4udGV4dC52W3N0YXJ0LnY6ZW5kLnZdCmNoYXAucG9zaXRpb25zLnYgPC0gZ3JlcCgiXkNoYXB0ZXIgXFx3Iiwgbm92ZWwubGluZXMudikKCm5vdmVsLmxpbmVzLnZbY2hhcC5wb3NpdGlvbnMudl0KCmNoYXB0ZXIucmF3cy5sIDwtIGxpc3QoKQpjaGFwdGVyLmZyZXFzLmwgPC0gbGlzdCgpCgojIHRoZSBmb2xsb3dpbmcgZm9yIGxvb3Agc3RhcnRzIGJ5IGl0ZXJhdGluZyBvdmVyIGVhY2ggaXRlbSBpbiBjaGFwLnBvc2l0aW9ucy52Cgpmb3IoaSBpbiAxOmxlbmd0aChjaGFwLnBvc2l0aW9ucy52KSl7CiAgIyBpbiB0aGlzIGlmIHN0YXRlbWVudDogaWYgdGhlIHZhbHVlIG9mIGkgaXMgbm90IGVxdWFsIHRvIHRoZSBsZW5ndGggb2YgdGhlIHZlY3Rvciwga2VlcCBpdGVyYXRpbmcgb3ZlciB0aGUgdmVjdG9yCiAgICBpZihpICE9IGxlbmd0aChjaGFwLnBvc2l0aW9ucy52KSl7CmNoYXB0ZXIudGl0bGUgPC0gbm92ZWwubGluZXMudltjaGFwLnBvc2l0aW9ucy52W2ldXSAjIHRoaXMgdmFyaWFibGUgY2FwdHVyZXMgdGhlIGNoYXB0ZXIgdGl0bGUgaW4gbm92ZWwubGluZXMudiB0aGF0IGlzIGluZGljYXRlZCBieSB0aGUgdmFsdWUgaGVsZCBpbiB0aGUgY2hhcC5wb3NpdGlvbnMudi4gSWYgdGhpcyBpcyBjb25mdXNpbmcsIHRyeSB0aGlzOiBJbiB5b3VyIGNvbnNvbGUsIHNldCBpIHRvIDEgYnkgcnVubmluZyBpIDwtIDEuIFRoZW4gcnVuIG5vdmVsLmxpbmVzLnZbY2hhcC5wb3NpdGlvbnMudltpXV0Kc3RhcnQgPC0gY2hhcC5wb3NpdGlvbnMudltpXSsxICMgaSsxIGdpdmVzIG1lIHRoZSBwb3NpdGlvbiBvZiB0aGUgZmlyc3QgbGluZSBvZiB0aGUgY2hhcHRlciB0ZXh0ICh0aGUgZmlyc3QgcGFyYWdyYXBoIGFmdGVyIHRoZSBjaCB0aXRsZSwgaW4gb3RoZXIgd29yZHMpCmVuZCA8LSBjaGFwLnBvc2l0aW9ucy52W2krMV0tMSAjIHJ1biB0aGVzZSBsaW5lcyBpbiB0aGUgY29uc29sZTogaSA8LSAxLCB0aGVuIGNoYXAucG9zaXRpb25zLnZbaSsxXS4gSW5zdGVhZCBvZiBhZGRpbmcgMSB0byB0aGUgdmFsdWUgc3RvcmVkIGluIHRoZSBpdGggcG9zaXRpb24gb2YgY2hhcC5wb3NpdGlvbnMudiwgaXQgYWRkcyAxIHRvIGkgYXMgYW4gaW5kZXguIEluc3RlYWQgb2YgZXh0cmFjdGluZyB0aGUgdmFsdWUgb2YgdGhlIGl0aCBpdGVtIGluIHRoZSB2ZWN0b3IsIHRoZSBwcm9ncmFtIGlkZW50aWZpZXMgdGhlIHZhbHVlIG9mIHRoZSBpdGVtIGluIHRoZSBuZXh0IHBvc2l0aW9uIGJleW9uZCBpIGluIHRoZSB2ZWN0b3IuIFRoaXMgbGluZSByZXR1cm5zIHRoZSBuZXh0IGl0ZW0gaW4gdGhlIHZlY3RvciwgYW5kIHRoZSB2YWx1ZSBoZWxkIGluIHRoYXQgc3BvdCBpcyB0aGUgcG9zaXRpb24gZm9yIHRoZSBzdGFydCBvZiBhIG5ldyBjaGFwdGVyLiBUaGlzIGVuc3VyZXMgdGhlIHByb2Nlc3Npbmcgb2YgdGhlIG5leHQgY2hhcHRlci4gVG8gaWdub3JlIHRoZSB3b3JkcyBpbiB0aGUgY2hhcHRlciBoZWFkaW5nLCB5b3Ugc3VidHJhY3QgMSBmcm9tIHRoYXQgdmFsdWUgaW4gb3JkZXIgdG8gZ2V0IHRoZSBsaW5lIG51bWJlciBpbiBub3ZlbC5saW5lcy52IHRoYXQgY29tZXMganVzdCBiZWZvcmUgdGhlIHN0YXJ0IG9mIGEgbmV3IGNoYXB0ZXIuCmNoYXB0ZXIubGluZXMudiA8LSBub3ZlbC5saW5lcy52W3N0YXJ0OmVuZF0gIyBoYXZpbmcgZGVmaW5lZCBzdGFydCBhbmQgZW5kIHBvaW50cyBvZiBlYWNoIGNoYXB0ZXIsIHlvdSBleHRyYWN0IHRoZSBsaW5lcwpjaGFwdGVyLndvcmRzLnYgPC0gdG9sb3dlcihwYXN0ZShjaGFwdGVyLmxpbmVzLnYsIGNvbGxhcHNlPSIgIikpICMgcGFzdGVzIGNoYXB0ZXIgbGluZXMgaW50byBhIHNpbmdsZSBibG9jayBvZiB0ZXh0LCBhbmQgbG93ZXJjYXNlcyBlYWNoIHdvcmQKY2hhcHRlci53b3Jkcy5sIDwtIHN0cnNwbGl0KGNoYXB0ZXIud29yZHMudiwgIlxcVyIpICMgc3BsaXQgYWxsIHdvcmRzIGluIGVhY2ggY2hhcHRlciBpbnRvIGEgdmVjdG9yIG9mIHdvcmRzCmNoYXB0ZXIud29yZC52IDwtIHVubGlzdChjaGFwdGVyLndvcmRzLmwpCmNoYXB0ZXIud29yZC52IDwtIGNoYXB0ZXIud29yZC52W3doaWNoKGNoYXB0ZXIud29yZC52IT0iIildIApjaGFwdGVyLmZyZXFzLnQgPC0gdGFibGUoY2hhcHRlci53b3JkLnYpICMgdGFidWxhdGVzIHZlY3RvciBvZiB3b3JkcyBpbnRvIGEgZnJlcXVlbmN5IGNvdW50IG9mIGVhY2ggd29yZCB0eXBlCmNoYXB0ZXIucmF3cy5sW1tjaGFwdGVyLnRpdGxlXV0gPC0gY2hhcHRlci5mcmVxcy50ICMgaGVyZSB5b3UgZHVtcCB0aGUgdGFibGUgb2YgcmF3IGZyZXF1ZW5jeSBjb3VudHMgaW50byB0aGUgZW1wdHkgbGlzdCB0aGF0IHdhcyBjcmVhdGVkIGJlZm9yZSBlbnRlcmluZyB0aGUgbG9vcC4gVGhlIGRvdWJsZSBicmFja2V0cyBhc3NpZ24gYSBsYWJlbCB0byB0aGUgbGlzdCBpdGVtOyBoZXJlIGVhY2ggaXRlbSBpbiB0aGUgbGlzdCBpcyBuYW1lZCB3aXRoIHRoZSBjaGFwdGVyIGhlYWRpbmcgZXh0cmFjdGVkIGEgZmV3IGxpbmVzIGFib3ZlCmNoYXB0ZXIuZnJlcXMudC5yZWwgPC0gMTAwKihjaGFwdGVyLmZyZXFzLnQvc3VtKGNoYXB0ZXIuZnJlcXMudCkpICMgY29udmVydHMgdGhlIHJhdyBjb3VudHMgdG8gcmVsYXRpdmUgZnJlcXVlbmNpZXMgYmFzZWQgb24gdGhlIG51bWJlciBvZiB3b3JkcyBpbiB0aGUgY2hhcHRlcgpjaGFwdGVyLmZyZXFzLmxbW2NoYXB0ZXIudGl0bGVdXSA8LSBjaGFwdGVyLmZyZXFzLnQucmVsCiAgICB9IAp9CgpjaGFwdGVyLmZyZXFzLmxbMV0KCmxlbmd0aChjaGFwdGVyLmZyZXFzLmwpWzFdCmBgYAoKU3VwcG9zZSBJIHdhbnRlZCB0byBnZXQgYWxsIHJlbGF0aXZlIGZyZXF1ZW5jaWVzIG9mIHRoZSB3b3JkICJmYXRoZXIiIGluIGVhY2ggY2hhcHRlci4KCmBgYHtyfQpmYXRoZXIuZnJlcXMgPC0gbGFwcGx5KGNoYXB0ZXIuZnJlcXMubCwgJ1snLCAnZmF0aGVyJykKCmZhdGhlci5mcmVxcwpgYGAKCllvdSBjb3VsZCBhbHNvIHVzZSB2YXJpYXRpb25zIG9mIHRoZSBgd2hpY2hgIGZ1bmN0aW9uIHRvIGlkZW50aWZ5IHRoZSBjaGFwdGVycyB3aXRoIHRoZSBoaWdoZXN0IGFuZCBsb3dlc3QgZnJlcXVlbmNpZXMuCgpgYGB7cn0Kd2hpY2gubWF4KGZhdGhlci5mcmVxcykKCndoaWNoLm1pbihmYXRoZXIuZnJlcXMpCmBgYAoKIyMjIEV4ZXJjaXNlCgpDcmVhdGUgYSB2ZWN0b3IgdGhhdCBjb25maW5lcyB5b3VyIHJlc3VsdHMgdG8gb25seSB0aGUgcGFyYWdyYXBocyB3aXRoIGRpYWxvZ3VlLgoKYGBge3J9CmRpYWxvZ3VlLnYgPC0gZ3JlcCgnIiguKj8pIicsIG5vdmVsLmxpbmVzLnYpICMgZ3JlcCBpcyBhbm90aGVyIHJlZ2V4IGZ1bmN0aW9uCgpub3ZlbC5saW5lcy52W2RpYWxvZ3VlLnZdWzE6MjBdIAojIGNoZWNrIHlvdXIgd29yayBieSBmaW5kaW5nIGFsbCB0aGUgZGlhbG9ndWUgbGluZXMgaW4gbm92ZWwubGluZXMudgpgYGAKCiMjIyBCb251cyBFeGVyY2lzZQoKTW9kaWZ5IHRoZSBmb3IgbG9vcCBpbiBKb2NrZXJzIHRvIGZpbmQgd29yZCBmcmVxdWVuY2llcyBvbmx5IG9mIGNvbnRlbnQgd2l0aCBkaWFsb2d1ZS4KCmBgYHtyfQpkaWFsb2d1ZS5jaGFwdGVyLnJhd3MubCA8LSBsaXN0KCkKZGlhbG9ndWUuY2hhcHRlci5mcmVxcy5sIDwtIGxpc3QoKQoKZm9yKGkgaW4gMTpsZW5ndGgoY2hhcC5wb3NpdGlvbnMudikpewogICAgaWYoaSAhPSBsZW5ndGgoY2hhcC5wb3NpdGlvbnMudikpewpjaGFwdGVyLnRpdGxlIDwtIG5vdmVsLmxpbmVzLnZbY2hhcC5wb3NpdGlvbnMudltpXV0Kc3RhcnQgPC0gY2hhcC5wb3NpdGlvbnMudltpXSsxCmVuZCA8LSBjaGFwLnBvc2l0aW9ucy52W2krMV0tMQpjaGFwdGVyLmxpbmVzLnYgPC0gbm92ZWwubGluZXMudltzdGFydDplbmRdCmRpYWxvZ3VlLmxpbmVzLnYgPC0gZ3JlcCgnIiguKj8pIicsIGNoYXB0ZXIubGluZXMudiwgdmFsdWUgPSBUUlVFKSAjIGhlcmUgaXMgdGhlIGdyZXAgYWdhaW4sIHBydW5pbmcgdGhlIGNoYXB0ZXIubGluZXMgdmVjdG9yIGludG8gbGluZXMgd2l0aCBkaWFsb2d1ZQpjaGFwdGVyLndvcmRzLnYgPC0gdG9sb3dlcihwYXN0ZShkaWFsb2d1ZS5saW5lcy52LCBjb2xsYXBzZT0iICIpKSAKY2hhcHRlci53b3Jkcy5sIDwtIHN0cnNwbGl0KGNoYXB0ZXIud29yZHMudiwgIlxcVyIpCmNoYXB0ZXIud29yZC52IDwtIHVubGlzdChjaGFwdGVyLndvcmRzLmwpCmNoYXB0ZXIud29yZC52IDwtIGNoYXB0ZXIud29yZC52W3doaWNoKGNoYXB0ZXIud29yZC52IT0iIildIApjaGFwdGVyLmZyZXFzLnQgPC0gdGFibGUoY2hhcHRlci53b3JkLnYpIApkaWFsb2d1ZS5jaGFwdGVyLnJhd3MubFtbY2hhcHRlci50aXRsZV1dIDwtIGNoYXB0ZXIuZnJlcXMudCAKY2hhcHRlci5mcmVxcy50LnJlbCA8LSAxMDAqKGNoYXB0ZXIuZnJlcXMudC9zdW0oY2hhcHRlci5mcmVxcy50KSkgCmRpYWxvZ3VlLmNoYXB0ZXIuZnJlcXMubFtbY2hhcHRlci50aXRsZV1dIDwtIGNoYXB0ZXIuZnJlcXMudC5yZWwKICAgIH0gCn0KCmRpYWxvZ3VlLmNoYXB0ZXIuZnJlcXMubFsxXQpgYGAKCiMjIyBWaXN1YWxpc2luZyB0aGUgZGF0YSB3aXRoIGBwbG90YAoKYGBge3J9CiMgdG8gZXh0cmFjdCB0aGUgZnJlcXVlbmN5IGRhdGEgZnJvbSBhbGwgb2YgdGhlIGNoYXB0ZXJzIGF0IG9uY2UKbGFwcGx5KGNoYXB0ZXIucmF3cy5sLG1lYW4pCiMgcHV0dGluZyByZXN1bHRzIGludG8gYSBtYXRyaWMgb2JqZWN0Cm1lYW4ud29yZC51c2UubSA8LSBkby5jYWxsKHJiaW5kLCBsYXBwbHkoY2hhcHRlci5yYXdzLmwsbWVhbikpCmRpbShtZWFuLndvcmQudXNlLm0pCiMgdGhpcyByZXBvcnRzIDcwMyByb3dzIGluIDEgY29sdW1uLCBidXQgdGhlcmUncyBtb3JlIGluZm8gaW4gdGhlIG1hdHJpeAoKcGxvdChtZWFuLndvcmQudXNlLm0sIHR5cGUgPSAiaCIsIG1haW4gPSAiTWVhbiB3b3JkIHVzYWdlIHBhdHRlcm5zIGluIGVhY2ggY2hhcHRlciBvZiBEaWNrZW5zJ3MgR3JlYXQgRXhwZWN0YXRpb25zIiwgeWxhYiA9ICJtZWFuIHdvcmQgdXNlIiwgeGxhYiA9ICJFYWNoIGNoYXB0ZXIiKQpgYGAKCmBgYHtyfQojIHVzaW5nIHNjYWxlIHRvIG1ldGhvZCBoYXMgdGhlIGVmZmVjdCBvZiBzdWItIHRyYWN0aW5nIGF3YXkgdGhlIGV4cGVjdGVkIHZhbHVlIAojIChleHBlY3RlZCBhcyBjYWxjdWxhdGVkIGJ5IHRoZSBvdmVyYWxsIG1lYW4pIGFuZCB0aGVuIHNob3dpbmcgb25seSB0aGUgZGV2aWF0aW9ucyBmcm9tIHRoZSBtZWFuCnNjYWxlKG1lYW4ud29yZC51c2UubSkKcGxvdChzY2FsZShtZWFuLndvcmQudXNlLm0pLCB0eXBlID0gImgiLCBtYWluID0gIlNjYWxlZCBtZWFuIHdvcmQgdXNhZ2UgcGF0dGVybnMgaW4gZWFjaCBjaGFwdGVyIG9mIERpY2tlbnMncyBHcmVhdCBFeHBlY3RhdGlvbnMiLCB5bGFiID0gIm1lYW4gd29yZCB1c2UiLCB4bGFiID0gIkVhY2ggY2hhcHRlciIpIApgYGAKClRoaXMgZ2l2ZXMgdXMgYSBnZW5lcmFsIGltcHJlc3Npb24gb2Ygdm9jYWJ1bGFyeSBkZW5zaXR5IG9uIGEgY2hhcHRlci1ieS1jaGFwdGVyIGJhc2lzLiBMZXQncyBub3cgcmV0dXJuIHRvIHRoZSBwcmV2aW91cyB3b3JkIHNlYXJjaCBvZiAiZmF0aGVyLiIgU3VwcG9zZSB3ZSB3YW50ZWQgdG8gdmlzdWFsaXNlIHRoYXQgZnJlcXVlbmN5IG9mICJmYXRoZXIiIGFsb25nc2lkZSBhIHNpbWlsYXIgY29uY2VwdCwgInNvbi4iCgpXZSBuZWVkIHRvIGludHJvZHVjZSBhIG5ldyBmdW5jdGlvbiwgYGxhcHBseWAuIFRoZSBgbGFwcGx5YCBmdW5jdGlvbiBpcyBzaW1pbGFyIHRvIGEgZm9yIGxvb3AsIGluIHRoYXQgaXQgaXRlcmF0ZXMgb3ZlciB0aGUgZWxlbWVudHMgaW4gYSBkYXRhIHN0cnVjdHVyZSwgYnV0IGl0IGlzIHNwZWNpZmljYWxseSBkZXNpZ25lZCBmb3IgZGVhbGluZyB3aXRoIGxpc3RzLiBJdCBhbHNvIHJlcXVpcmVzIGEgbGlzdCBhcyBhIHNlY29uZCBhcmd1bWVudCwgYW5kIHRoZSBuYW1lIG9mIHNvbWUgb3RoZXIgZnVuY3Rpb24uCgpgYGB7cn0KY2hhcHRlci5mcmVxcy5sW1sxXV1bImZhdGhlciJdCmBgYAoKYGBge3J9CmNoYXB0ZXIuZnJlcXMubFtbMTBdXVsic29uIl0KYGBgCgpUaGUgYWJvdmUgaXMganVzdCBhbiBleGFtcGxlOiBUaGUgd29yZCAiZmF0aGVyIiBhcHBlYXJzIHdpdGggMjclIHJlbGF0aXZlIGZyZXF1ZW5jeSAodGhhdCBpcywgMjcgdGltZXMgZm9yIGV2ZXJ5IDEwMCB3b3JkcyBpbiB0aGUgY2hhcHRlcikgaW4gdGhlIGZpcnN0IGNoYXB0ZXIsIGFuZCB0aGUgd29yZCAic29uIiBhcHBlYXIgd2l0aCBhIDQlIHJlbGF0aXZlIGZyZXF1ZW5jeSBpbiB0aGUgMTB0aCBjaGFwdGVyLiBOb3cgbGV0J3MgY3JlYXRlIHZlY3RvcnMgdGhhdCBzdG9yZSB0aGUgcmVsYXRpdmUgZnJlcXVlbmNpZXMgZm9yIGVhY2ggY2hhcHRlci4KCmBgYHtyfQpmYXRoZXJzLmwgPC0gbGFwcGx5KGNoYXB0ZXIuZnJlcXMubCwgJ1snLCAnZmF0aGVyJykKc29ucy5sIDwtIGxhcHBseShjaGFwdGVyLmZyZXFzLmwsICdbJywgJ3NvbicpCmBgYAoKSW5zdGVhZCBvZiBqdXN0IHByaW50aW5nIG91dCB0aGUgdmFsdWVzIGhlbGQgaW4gdGhpcyBuZXcgbGlzdCwgeW91IGNhbiBjYXB0dXJlIHRoZSByZXN1bHRzIGludG8gYSBzaW5nbGUgbWF0cml4IHVzaW5nIHRoZSBgcmJpbmRgIGZ1bmN0aW9uLiBUaGUgYGRvLmNhbGxgIGZ1bmN0aW9ucyBiaW5kcyB0aGUgY29udGVudHMgb2YgZWFjaCBsaXN0IGl0ZW0gaW50byByb3dzOyB0aGlzIGVmZmVjdGl2ZWx5IGFjdGl2YXRlIHRoZSBgcmJpbmRgIGZ1bmN0aW9uIGFjcm9zcyB0aGUgbGlzdCBvZiAiZmF0aGVyIiBhbmQgInNvbiIgcmVzdWx0cywgcmVzcGVjdGl2ZWx5LgoKYGBge3J9CmZhdGhlcnMubSA8LSBkby5jYWxsKHJiaW5kLCBmYXRoZXJzLmwpCnNvbnMubSA8LSBkby5jYWxsKHJiaW5kLCBzb25zLmwpCmBgYAoKTGV0J3MgbG9vayBhdCBvbmUgb2YgdGhlc2UgbWF0cmljZXMuCgpgYGB7cn0Kc29ucy5tCmBgYAoKQ29tcGFyZSBpdCB0byB0aGUgb3RoZXIgbWF0cml4IG9mICJmYXRoZXIiIHJlc3VsdHMuCgpgYGB7cn0KZmF0aGVycy5tCmBgYAoKTmV4dCB3ZSBjcmVhdGUgdmVjdG9ycyBmb3IgZWFjaCBzZWFyY2ggdGVybTsgdGhlIGZvbGxvd2luZyBleHRyYWN0cyB0aGUgZmF0aGVyIGFuZCBzb24gdmFsdWVzIGludG8gdHdvIG5ldyB2ZWN0b3JzLCBhbmQgdXNlcyB0aGUgYGNiaW5kYCBmdW5jdGlvbnMgdG8gY29tYmluZSB0aGVzZSB2ZWN0b3JzIGludG8gYSBuZXcsIHR3by1jb2x1bW4gbWF0cml4IGNvbnNpc3Rpbmcgb2YgNTggcm93cyBhbmQgMiBjb2x1bW5zLgoKYGBge3J9CmZhdGhlcnMudiA8LSBmYXRoZXJzLm1bLDFdCnNvbnMudiA8LSBzb25zLm1bLDFdCgpmYXRoZXJzLnNvbnMubSA8LSBjYmluZChmYXRoZXJzLnYsIHNvbnMudikKCmRpbShmYXRoZXJzLnNvbnMubSkKYGBgCgpOb3cgd2UgY2FuIHZpc3VhbGlzZSB0aGVzZSB0d28gd29yZCBzZWFyY2hlcy4KCmBgYHtyfQpjb2xuYW1lcyhmYXRoZXJzLnNvbnMubSkgPC0gYygiZmF0aGVyIiwgInNvbiIpCgpiYXJwbG90KGZhdGhlcnMuc29ucy5tLCBiZXNpZGU9VCwgY29sPSJncmV5IiwgeWxhYiA9ICJyZWxhdGl2ZSB3b3JkIGZyZXF1ZW5jeSIpCgpgYGA=