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=