Chapter 11 Strings with stringr

This chapter introduces you to string manipulation in R. But the focus will be on regular expressions, or regexps for short. Regular expressions are useful because strings usually contain unstructured or semi-structured data, and regexps are a concise language for describing patterns in strings. When you first look at a regexp, you’ll think a cat walked across your keyboard, but as your understanding improves, they will soon start to make sense.

Pre requisites

library(tidyverse) library(stringr)

String Basics

You can use double or single quotes. It doesnt matter. Ideal to use double quotes unless you want to create a string that contains multiple double quotes.

string1 <-  "This is a string"
string2 <- 'To put a "quote" inside a string, use singe quotes'
string1
[1] "This is a string"
string2
[1] "To put a \"quote\" inside a string, use singe quotes"

You can also include a literal single or double quote in a string by escapeing it with  

double_quote <-  "\"" # or '"'
single_quote <-  '\'' #  or "'"
double_quote
[1] "\""
single_quote
[1] "'"

To see the raw contents of the string, use writelines()

writeLines(double_quote)
"
writeLines(single_quote)
'

Other special charatacters include for newline and for tab. Also 0b5 sample way of writing non-English characters that works on all platforms.

x <-  "\u00b5"
x
[1] "µ"
writeLines(x)
µ

Multiple strings can be stored in a charactacter vector with c()

c("one","two","three")
[1] "one"   "two"   "three"
c
function (...)  .Primitive("c")

Functions and packages coverered

  • stringr package
  • str_length
  • str_c
  • str_replace_na
  • str_sub
  • str_to_uppser, str_sort, str_to_lower, str_order
  • str_length, str_pad, str_trim, str_sub
  • For regex = str_view, str_view_all
  • regex syntax
  • str_detect
  • str_subset
  • str_count
  • str_extract
  • str_match
  • tidyr::extract
  • str_split
  • str_locate
  • str_sub
  • the stringi package

Ideas

  • mention rex. A package with friendly regular expressions.
  • Use it to match country names? Extract numbers from text?
  • Discuss fuzzy joining and string distance, approximate matching.

Also see

String Length

str_length() tells you the number of characters in a string

library(tidyverse)
package <U+393C><U+3E31>tidyverse<U+393C><U+3E32> was built under R version 3.3.3Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages ------------------------------------------------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
library(stringr)
str_length( c("a","R for data science", NA))
[1]  1 18 NA

Combining Strings

Use str_c() to combine one or more strings. Use the sep= argument to control how they’re separated.
# much like paste0 function (combines strings without spaces in between them)

str_c("x","y")
[1] "xy"
str_c("x","y","z")
[1] "xyz"
str_c("x","y", sep = ", ")
[1] "x, y"

Like most other functions in R, missing values are contagious. If you want them to print as “NA”, use str_replace_na()

x <-  c("abc", NA)
str_c("|-",x,"-|")
[1] "|-abc-|" NA       
str_c("|-",str_replace_na(x),"-|")
[1] "|-abc-|" "|-NA-|" 

Str_c is vectorized and it automatically recylces shorter vectors to the same length as the longest

str_c("prefix-",c("a","b","c"), "-suffix", sep=":")
[1] "prefix-:a:-suffix" "prefix-:b:-suffix" "prefix-:c:-suffix"

Objects of length 0 are dropped.

name <- "Hadley"
time_of_day <-  "morning"
birthday <-  TRUE
str_c("Good",time_of_day,name, if(birthday) " and Happy Birthday", sep = " ")
[1] "Good morning Hadley  and Happy Birthday"

To collapse a vector of strings into a single string, use collapse argument

str_c(c("x","y","z"), collapse = ", ")
[1] "x, y, z"

Subsetting Strings

You can extract parts of a string using str_sub(). It takes start and end arguments that give the position of the substring. Negative numbers ocunt backwards from the end. It won’t fail if the string is too short. It will return as much as possible of the string.

x <-  c("Apple", "Banana", "CaRrots")
str_sub(x, 1, 3)
[1] "App" "Ban" "CaR"
str_sub(x, -3, -1)
[1] "ple" "ana" "ots"
str_sub(x, 1, 7)
[1] "Apple"   "Banana"  "CaRrots"

Using the assignment form of str_sub, we can change the capitalization on the first character of each string in x

x
[1] "Apple"   "Banana"  "CaRrots"
str_sub(x,1,1) <- str_to_lower(str_sub(x,1,1))
x
[1] "apple"   "banana"  "caRrots"

Locales

You can also use str_to_upper() and str_to_title(). However, changing case is more complicated because of different languages . You can set which rules to apply by specifyinga locale

# turkish has two i's: with and without a dot
# it has a different rule for capitalizing them
str_to_upper(c("i","I"), locale="tr")
[1] "I" "I"

Locales also affects sorting. The base R order() and sort() functions sort strings using the current locale. If you want robust behavior across different computers, you want to use str_sort() ans str_order() which take an additional locale argument.

x <- c("apple","eggplant","banana")
str_sort(x, locale = "en")
[1] "apple"    "banana"   "eggplant"
str_sort(x, locale = "haw")
[1] "apple"    "eggplant" "banana"  

Exercises: In code that doesn’t use stringr, you’ll often see paste() and paste0(). What’s the difference between the two functions? What stringr function are they equivalent to? How do the functions differ in their handling of NA?

The function paste seperates strings by spaces by default, while paste0 does not seperate strings with spaces by default.Since str_c does not seperate strings with spaces by default it is closer in behabior to paste0.

paste("foo", "bar")
[1] "foo bar"
#> [1] "foo bar"
paste0("foo", "bar")
[1] "foobar"
#> [1] "foobar"

However, str_c and the paste function handle NA differently. The function str_c propogates NA, if any argument is a missing value, it returns a missing value. This is in line with how the numeric R functions, e.g. sum, mean, handle missing values. However, the paste functions, convert NA to the string “NA” and then treat it as any other character vector.

str_c("foo", NA)
[1] NA
paste("foo", NA)
[1] "foo NA"
paste0("foo", NA)
[1] "fooNA"
In your own words, describe the difference between the sep and collapse arguments to str_c().
The sep argument is the string inserted between argugments to str_c, while collapse is the string used to separate any elements of the character vector into a character vector of length one.

Use str_length() and str_sub() to extract the middle character from a string. What will you do if the string has an even number of characters?
The following function extracts the middle character. If the string has an even number of characters the choice is arbitrary. We choose to select n/2 , because that case works even if the string is only of length one. A more general method would allow the user to select either the floor or ceiling for the middle character of an even string.

x <- c("a", "abc", "abcd", "abcde", "abcdef")
L <- str_length(x)
m <- ceiling(L / 2)
str_sub(x, m, m)
[1] "a" "b" "b" "c" "c"
What does str_wrap() do? When might you want to use it?
The function str_wrap wraps text so that it fits within a certain width. This is useful for wrapping long strings of text to be typeset.

What does str_trim() do? What’s the opposite of str_trim()?
The function str_trim trims the whitespace from a string.

str_trim(" abc ")
[1] "abc"
#> [1] "abc"
str_trim(" abc ", side = "left")
[1] "abc "
#> [1] "abc "
str_trim(" abc ", side = "right")
[1] " abc"
#> [1] " abc"
The opposite of str_trim is str_pad which adds characters to each side.

str_pad("abc", 5, side = "both")
[1] " abc "
#> [1] " abc "
str_pad("abc", 4, side = "right")
[1] "abc "
#> [1] "abc "
str_pad("abc", 4, side = "left")
[1] " abc"
#> [1] " abc"
Write a function that turns (e.g.) a vector c(“a”, “b”, “c”) into the string a, b, and c. Think carefully about what it should do if given a vector of length 0, 1, or 2.

str_commasep <- function(x, sep = ", ", last = ", and ") {
  if (length(x) > 1) {
    str_c(str_c(x[-length(x)], collapse = sep),
                x[length(x)],
                sep = last)
  } else {
    x
  }
}
str_commasep("")
[1] ""
str_commasep("a")
[1] "a"
str_commasep(c("a", "b"))
[1] "a, and b"
str_commasep(c("a", "b", "c"))
[1] "a, b, and c"

Matching Patterns with regular expressions

Regexp are very terse language that allow you to describe patterns in strings. They take a little while to get your head around. To learn regular expressions, we’ll use str_view() and str_view_all(). These functions take a character vector and a regular expression and show you how they match. We’ll start with very simple regular expresions and then gradually get more and more complicated.

basic matches

The simplest patterns match exact strings:

# need to instal htmlwidgets package first
library(tidyverse)
library(stringr)
x <-  c("apple", "banana", "pear")
str_view(x,"an")

. matches any character

str_view(x, ".a.")

to match special characters: use \

# dot
dot <- "\\."
writeLines(dot)
\.

This tells R to look for an explict .

str_view(c("abc", "a.c","bef"),"a\\.c")
str_view(c("abc", "a.c","bef"),".\\..")

To match a literal ,you need four \\

x <- "a\\b"
str_view(x, "\\\\")

Explain why each of these strings don’t match a : “",”\“,”\".
“": This will escape the next character in the R string.
”\“: This will resolve to  in the regular expression, which will escape the next character in the regular expression.
”\": The first two backslashes will resolve to a literal backslash in the regular expression, the third will escape the next character. So in the regular expresion, this will escape some escaped character.

How would you match the sequence “’ ?

x <- c("'\\","a","b")
writeLines(x)
'\
a
b
str_view(x, "\'\\\\")

What patterns will the regular expression ...... match? How would you represent it as a string?
It will match any patterns that are a dot followed by any character, repeated three times.

Anchors

It’s often useful to anchor the regular expression so that it matches fromt he start or end of the string. You can use
^ to match the start of the string
$ to match the end of the string

Mnemonic begin with Power ^ and end with money($)

x <-  c("apple","banana","pear")
str_view(x,"^a")

str_view(x,"a$")

To force a regular expression to only match a complete string, enclose it with ^ and $

x <- c("apple pie", "apple", "apple cake")
str_view(x,"apple")

# will list all three
str_view(x, "^apple$")

# willlist only apple

You can use to match the boundary between words

x <- c("applecrust pie", "apple crust pie", "apple crumble cake")
str_view(x, "\\bcrust\\b")

How would you match the literal string " ^^ “?

str_view(c("$^$", "ab$^$sfas"), "^\\$\\^\\$$")

Given the corpus of common words in stringr::words, create regular expressions that find all words that:
Since this list is long, you might want to use the match=TRUE argument to str_view() to show only the matching or non-matching words.

Start with “y”.

str_view(words,"^y.", match=TRUE)

End with “x”

str_view(words,"x$", match = TRUE)

Are exactly three letters long. (Don’t cheat by using str_length()!)

str_view(words,"^...$", match = TRUE)

Have seven letters or more.

str_view(words,"^.......", match = TRUE)

Character Classes and Alternatives

matches any digit
matches any whitespace
[abc] matches a,b or c
[^abc] matches anything except a, b or c

remember to use \d and \s for those special characters

You can use alternates to pick between one or more alternative patterns. For example, “abc|d..f” will match either “abc” or “deaf” Note that the precendence for | is low, so that abc|xyz matches abc or xyz not abcyz or abxyz. Use parenthesis to make it clear what you want

str_view(c("grey","gray"),"gr(e|a)y")

Exercises:
Create regular expressions to find all words that:

Start with a vowel.

str_view(words,"^[aeiou].", match = TRUE)

That only contain consonants. (Hint: thinking about matching “not”-vowels.)

str_view(stringr::words, "^[^aeiou]+$", match = TRUE)

End with ed, but not with eed.

str_view(stringr::words, "^ed$|[^e]ed$", match = TRUE)

End with ing or ise.

str_view(stringr::words, "ing$|ise$", match = TRUE)

Empirically verify the rule “i before e except after c”.

str_view(stringr::words, "(cei|[^c]ie)", match = TRUE)
str_view(stringr::words, "(cie|[^c]ei)", match = TRUE)

Is “q” always followed by a “u”?

str_view(stringr::words, "qu", match = TRUE)
str_view(stringr::words, "q[^u]", match = TRUE)

Create a regular expression that will match telephone numbers as commonly written in your country. Using what has been covered in R4DS thus far

x <- c("123-456-7890", "1235-2351")
str_view(x, "\\d\\d\\d-\\d\\d\\d-\\d\\d\\d\\d")

Repetition

Controls how many times a pattern matches.
? 0 or 1
+ 1 or more
* 0 or more

x <-  "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"
str_view(x, "CC?")
str_view(x, "CC+")
str_view(x, "CC[LX]+")

You can also specify the number of matches precisely:
{n} exactly n times
{n,} n or more
{,m} at most m
{n,m} between n and m

str_view(x, "C{2}")
str_view(x, "C{2,}")
str_view(x, "C{2,3}")
str_view(x, "C{,2}")
Error in stri_locate_first_regex(string, pattern, opts_regex = opts(pattern)) : 
  Error in {min,max} interval. (U_REGEX_BAD_INTERVAL)

by default regexp will be greedy. They will match the longest string possible. Make them lazy by putting a [?] after them.

str_view(x, 'C{2,3}?')
str_view(x, "C[LX]+?")
Describe the equivalents of ?, +, * in {m,n} form.
The equivalent of ? is {,1}, matching at most 1. The equivalent of + is {1,}, matching 1 or more. There is no direct equivalent of * in {m,n} form since there are no bounds on the matches: it can be 0 up to infinity matches.

Describe in words what these regular expressions match: (read carefully to see if I’m using a regular expression or a string that defines a regular expression.)

^.*$: Any string
“\{.+\}”: Any string with curly braces surrounding at least one character.
--: A date in “%Y-%m-%d” format: four digits followed by a dash, followed by two digits followed by a dash, followed by another two digits followed by a dash.

“\\{4}”: This resolves to the regex \{4}, which is four backslashes.

Create regular expressions to find all words that:
find all words starting with three consonants

str_view(words, '^[^aeiou]{3}?', match = TRUE)

find three or more vowels in a row:

str_view(words, '[aeiou]{3,}?', match = TRUE)

Find Two or more vowel-consonant pairs in a row.

str_view(words, "([aeiou][^aeiou]){2,}", match = TRUE)

Grouping and Backreferences

Parenttheses is a way to disambiguate complex expressions. They also define groups that you can refer to with backreferences , like , etc. For example the following regular expression finds all fruits that have a repeated pair of leters:

str_view(fruit, "(..)\\1", match = TRUE)

Exercises

Describe, in words, what these expressions will match:

(.) : The same character apearing three times in a row. E.g. “aaa”
“(.)(.)\2\1”: A pair of characters followed by the same pair of characters in reversed order. E.g. “abba”.
(..): Any two characters repeated. E.g. “a1a1”.
“(.).\1.\1”: A character followed by any character, the original character, any other character, the original character again. E.g. “abaca”, “b8b.b”.
“(.)(.)(.).*\3\2\1" Three characters followed by zero or more characters of any kind followed by the same three characters but in reverse order. E.g. “abcsgasgddsadgsdgcba” or “abccba” or “abc1cba”.

Construct regular expressions to match words that:

Start and end with the same character. Assuming the word is more than one character and all strings are considered words, ^(.).*$

str_view(words, "^(.).*\\1$", match = TRUE)

Contain one letter repeated in at least three places (e.g. “eleven” contains three “e”s.)

str_view(words, "(.).*\\1.*\\1", match = TRUE)

Tools

Learn about stringr functions that let you :
Determine which strings match a pattern str_view, str_view_all, str_detect() and str_count()
find the position of matches
Extract the content of matches str_extract()
Replaces matches with new values str_replace() and str_replace_all()
Split a string based on a match str_split()

Detect Matches str_detect()

x <- c("apple", "banana", "pear")
str_detect(x,"e")
[1]  TRUE FALSE  TRUE

Since Fales = 0 and True = 1 you can sum() and mean() it:

# how many common words start with t?
sum(str_detect(words, "^t"))
[1] 65
# What proportion of common words end with a vowel?
mean(str_detect(words, "[aeiou]$"))
[1] 0.2765306

A common use of str_detect is to select the elements that match a pattern. You can do this with logical subsetting , or the convenient str_subset() wrapper.

# logical subsetting method
words[str_detect(words, "x$")]
[1] "box" "sex" "six" "tax"
# using str_subset method
str_subset(words, "x$")
[1] "box" "sex" "six" "tax"

Typically your strings will be one colum of a data frame, and you’ll want to use filter instead:

df <-  tibble(word=words, i=seq_along(word))
df %>%
  filter(str_detect(words, "x$"))

A variation on str_detect() is str_count(). rather than a simple yes or no, it tells you how many matches there are in a string.

x <- c("apple","banana","pear")
str_count(x, "a")
[1] 1 3 1
# on the average, how many vowels per word?
mean(str_count(words,"[aeiou]"))
[1] 1.991837

You can also use str_count() with mutate

df %>%
  mutate(
    vowels = str_count(word,"[aeiou]"), 
    consonants = str_count(word, "[^aeiou]")
  )

Note: matches never overlap. For example, in “abababa” how many times will the pattern “aba” match?

str_count("abababa","aba")
[1] 2
str_view_all("abababa","aba")

For each of the following challenges, try solving it by using both a single regular expression, and a combination of multiple str_detect() calls.

Find all words that start or end with x.

str_view(words, "^x|x$", match = TRUE)

words[str_detect(words, "^x|x$")]
[1] "box" "sex" "six" "tax"
start_with_x <- str_detect(words, "^x")
end_with_x <- str_detect(words, "x$")
words[start_with_x | end_with_x]
[1] "box" "sex" "six" "tax"

Find all words that start with a vowel and end with a consonant.

words[str_detect(words,"^[aieou].*[^aeiou]$")]
  [1] "about"       "accept"      "account"     "across"      "act"         "actual"      "add"        
  [8] "address"     "admit"       "affect"      "afford"      "after"       "afternoon"   "again"      
 [15] "against"     "agent"       "air"         "all"         "allow"       "almost"      "along"      
 [22] "already"     "alright"     "although"    "always"      "amount"      "and"         "another"    
 [29] "answer"      "any"         "apart"       "apparent"    "appear"      "apply"       "appoint"    
 [36] "approach"    "arm"         "around"      "art"         "as"          "ask"         "at"         
 [43] "attend"      "authority"   "away"        "awful"       "each"        "early"       "east"       
 [50] "easy"        "eat"         "economy"     "effect"      "egg"         "eight"       "either"     
 [57] "elect"       "electric"    "eleven"      "employ"      "end"         "english"     "enjoy"      
 [64] "enough"      "enter"       "environment" "equal"       "especial"    "even"        "evening"    
 [71] "ever"        "every"       "exact"       "except"      "exist"       "expect"      "explain"    
 [78] "express"     "identify"    "if"          "important"   "in"          "indeed"      "individual" 
 [85] "industry"    "inform"      "instead"     "interest"    "invest"      "it"          "item"       
 [92] "obvious"     "occasion"    "odd"         "of"          "off"         "offer"       "often"      
 [99] "okay"        "old"         "on"          "only"        "open"        "opportunity" "or"         
[106] "order"       "original"    "other"       "ought"       "out"         "over"        "own"        
[113] "under"       "understand"  "union"       "unit"        "university"  "unless"      "until"      
[120] "up"          "upon"        "usual"      

Are there any words that contain at least one of each different vowel?

 
words[str_detect(words, "a") &
        str_detect(words, "e") &
        str_detect(words, "i") &
        str_detect(words, "o") &
        str_detect(words, "u")]
character(0)

What word has the highest number of vowels? What word has the highest proportion of vowels? (Hint: what is the denominator?)

prop_vowels <- str_count(words, "[aeiou]") / str_length(words)
words[which(prop_vowels == max(prop_vowels))]
[1] "a"

Extract Matches

To extract the actual text of a match, use st_extract()

# using harvard sentences which were designed to test voip systems
length(sentences)
[1] 720
head(sentences)
[1] "The birch canoe slid on the smooth planks."  "Glue the sheet to the dark blue background."
[3] "It's easy to tell the depth of a well."      "These days a chicken leg is a rare dish."   
[5] "Rice is often served in round bowls."        "The juice of lemons makes fine punch."      

Imagine we want to find all sentences that contain a color. We first create a vector of color names, and then turn it into a single regular expression.

library(stringr)
colors <- c("red","orange","yellow","green","blue","purple")
color_match <-  str_c(colors, collapse = "|")
color_match
[1] "red|orange|yellow|green|blue|purple"
colour_match2 <- str_c("\\b(", str_c(colors, collapse = "|"), ")\\b")
colour_match2
[1] "\\b(red|orange|yellow|green|blue|purple)\\b"

Now we can select the sentences that contain a color, and then extract the color to figure out which one it is:

has_color <- str_subset(sentences, color_match)
matches <- str_extract(has_color, color_match)
head(matches)
[1] "blue" "blue" "red"  "red"  "red"  "blue"
# but str_extract only extracts the first match.
more <-  sentences[str_count(sentences, color_match)>1]
str_view_all(more, color_match)
more2 <- sentences[str_count(sentences, colour_match2) > 1]
str_view_all(more2, colour_match2, match = TRUE)
str_extract(more, color_match)
[1] "blue"   "green"  "orange"

To get ALL matches, use str_extract_all

str_extract_all(more, color_match)
[[1]]
[1] "blue" "red" 

[[2]]
[1] "green" "red"  

[[3]]
[1] "orange" "red"   

If you use simplify = TRUE, str_extract_all will return a matrix with short matches expanded to the same legnth as the longest.

str_extract_all(more, color_match, simplify=TRUE)
     [,1]     [,2] 
[1,] "blue"   "red"
[2,] "green"  "red"
[3,] "orange" "red"
x <-  c("a","a b","a b c")
str_extract_all(x, "[a-z]", simplify = TRUE)
     [,1] [,2] [,3]
[1,] "a"  ""   ""  
[2,] "a"  "b"  ""  
[3,] "a"  "b"  "c" 

Grouped Matches

You can also use parentheses to extract parts of a complex match. For example, imagine we want to extract nouns from the sentences. As a heuristic, we’ll look for any word that comes after “a” or “the” Defining a word in a regular expression is a little tricky.

noun <- "(a|the) ([^ ]+)"
has_noun <-sentences %>%
  str_subset(noun) %>%
head(10)
has_noun %>%
  str_extract(noun)
 [1] "the smooth" "the sheet"  "the depth"  "a chicken"  "the parked" "the sun"    "the huge"   "the ball"  
 [9] "the woman"  "a helps"   

str_extract() gives us the complete match; str_match() gives us each individual component

has_noun %>%
  str_match(noun)
      [,1]         [,2]  [,3]     
 [1,] "the smooth" "the" "smooth" 
 [2,] "the sheet"  "the" "sheet"  
 [3,] "the depth"  "the" "depth"  
 [4,] "a chicken"  "a"   "chicken"
 [5,] "the parked" "the" "parked" 
 [6,] "the sun"    "the" "sun"    
 [7,] "the huge"   "the" "huge"   
 [8,] "the ball"   "the" "ball"   
 [9,] "the woman"  "the" "woman"  
[10,] "a helps"    "a"   "helps"  

You can also used tidyr::extract()

tibble(sentence = sentences) %>%
  tidyr::extract(
    sentence, c("article", "noun"),"(a|the) ([^ ]+)", remove = FALSE
  )

Like str_extract, if you want ALL matches for each string, you’ll need str_match_all()

Find all words that come after a “number” like “one”, “two”, “three” etc. Pull out both the number and the word.

numword <- "(one|two|three|four|five|six|seven|eight|nine|ten) +(\\S+)"
sentences[str_detect(sentences, numword)] %>%
  str_extract(numword)
 [1] "ten served"    "one over"      "seven books"   "two met"       "two factors"   "one and"      
 [7] "three lists"   "seven is"      "two when"      "one floor."    "ten inches."   "one with"     
[13] "one war"       "one button"    "six minutes."  "ten years"     "one in"        "ten chased"   
[19] "one like"      "two shares"    "two distinct"  "one costs"     "ten two"       "five robins." 
[25] "four kinds"    "one rang"      "ten him."      "three story"   "ten by"        "one wall."    
[31] "three inches"  "ten your"      "six comes"     "one before"    "three batches" "two leaves."  

Find all contractions. Separate out the pieces before and after the apostrophe.

contraction <- "([A-Za-z]+)'([A-Za-z]+)"
sentences %>%
  `[`(str_detect(sentences, contraction)) %>%
  str_extract(contraction)
 [1] "It's"       "man's"      "don't"      "store's"    "workmen's"  "Let's"      "sun's"      "child's"   
 [9] "king's"     "It's"       "don't"      "queen's"    "don't"      "pirate's"   "neighbor's"

Replacing Matches

str_replace() and str_replace_all() allow you to replace matches with new strings. The simplest use is to replace a pattern with a fixed string:

x <- c("apple","pear", "banana")
str_replace(x, "[aeiou]", "-")
[1] "-pple"  "p-ar"   "b-nana"
x <- c("apple","pear", "banana")
str_replace_all(x, "[aeiou]", "-")
[1] "-ppl-"  "p--r"   "b-n-n-"

You can insert backreferences (identified when you use parenthesis) to insert components of the match. In the following code, I flip the order of the second and third words:

sentences %>%
  str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1 \\3 \\2") %>%
  head(5)
[1] "The canoe birch slid on the smooth planks."  "Glue sheet the to the dark blue background."
[3] "It's to easy tell the depth of a well."      "These a days chicken leg is a rare dish."   
[5] "Rice often is served in round bowls."       

Splitting

Use str_split() to split a string up into pieces. For example, we could split sentences into words:

sentences %>%
  head(5) %>%
  str_split(" ")
[[1]]
[1] "The"     "birch"   "canoe"   "slid"    "on"      "the"     "smooth"  "planks."

[[2]]
[1] "Glue"        "the"         "sheet"       "to"          "the"         "dark"        "blue"       
[8] "background."

[[3]]
[1] "It's"  "easy"  "to"    "tell"  "the"   "depth" "of"    "a"     "well."

[[4]]
[1] "These"   "days"    "a"       "chicken" "leg"     "is"      "a"       "rare"    "dish."  

[[5]]
[1] "Rice"   "is"     "often"  "served" "in"     "round"  "bowls."
sentences %>%
  head(5) %>%
  str_split(" ", simplify =TRUE)
     [,1]    [,2]    [,3]    [,4]      [,5]  [,6]    [,7]     [,8]          [,9]   
[1,] "The"   "birch" "canoe" "slid"    "on"  "the"   "smooth" "planks."     ""     
[2,] "Glue"  "the"   "sheet" "to"      "the" "dark"  "blue"   "background." ""     
[3,] "It's"  "easy"  "to"    "tell"    "the" "depth" "of"     "a"           "well."
[4,] "These" "days"  "a"     "chicken" "leg" "is"    "a"      "rare"        "dish."
[5,] "Rice"  "is"    "often" "served"  "in"  "round" "bowls." ""            ""     

Split up a string like “apples, pears, and bananas” into individual components.

x <- c("apples, pears, and bananas")
str_split(x, ", +(and +)?")[[1]]
[1] "apples"  "pears"   "bananas"

Why is it better to split up by boundary(“word”) than " “?
Answer: Splitting by boundary(”word“) splits on punctuation and not just whitespace.

What does splitting with an empty string (“”) do?

str_split("ab. cd|agt", "")[[1]]
 [1] "a" "b" "." " " "c" "d" "|" "a" "g" "t"

Answer: It splits the string into individual characters.

Find Matches

str_locate() and str_locate_all() give you the starting and ending positiong of each match. These are particulary useful when on of the other ufnctions does exactly what you want. You can use str_locate() to find the matching pattern, and str_sub() to extract and/or modify them.

# the regular call
str_locate(fruit, "nana") %>%
  head(10)
      start end
 [1,]    NA  NA
 [2,]    NA  NA
 [3,]    NA  NA
 [4,]     3   6
 [5,]    NA  NA
 [6,]    NA  NA
 [7,]    NA  NA
 [8,]    NA  NA
 [9,]    NA  NA
[10,]    NA  NA

Other Types of Pattern

When you use a pattern that’s a string, it’s automatically wrapped into a call to regex()

# the regular call
str_view(fruit, "nan", match = TRUE)

# Is shorthand for
str_view(fruit, regex("nana"), match = TRUE)

You can use other arguments of regex() to control details of the match like:
ignore_case = TRUE
multiline = TRUE (allows the ^ and $ to match the start and end of each line rather than the start and end of the complete string )
comments =true
dotall = TRUE allows . to match everything, including

bananas <-  c("banana","Banana", "BANANA")
str_view(bananas,"banana")
str_view(bananas,regex("banana", ignore.case= TRUE ))
# multiline example
x <-  "Line 1\nLine 2 \nLine 3"
str_extract_all(x, "^Line") [[1]]
[1] "Line"
str_extract_all(x, regex("^Line", multiline = TRUE)) [[1]]
[1] "Line" "Line" "Line"
# comments = TRUE example
phone <-  regex("
                \\(?    # optiona opening parens
                (\\d{3}) # area code
                [)- ]?  # optional closing parens, dash, or space
                (\\d{3}) # another three numbers
                [ -]?    # optional space or dash
                (\\d{3}) # three more numbers
                ", comments = TRUE)
str_match("514-791-8141", phone)
     [,1]          [,2]  [,3]  [,4] 
[1,] "514-791-814" "514" "791" "814"

There are three other functions you can use nistead of regex()
fixed() matches exactly the specified sequence of bytes. It ignores all special regular expressions and operates at a very low level. This allows you to avoid complex escaping and can be much faster than regular expressions.

# microbenchmark shows that it's about 3x faster for a simple example
# you need to install microbenchmark package
microbenchmark::microbenchmark(
  fixed = str_detect(sentences, fixed("the")),
  regex = str_detect(sentences, "the"),
  times =20
  )
Unit: microseconds
  expr     min     lq     mean   median       uq     max neval
 fixed 323.410 340.05 365.8062 348.0145 364.7970 629.469    20
 regex 849.057 858.16 874.1598 862.9955 870.2485 990.425    20

fixed() is faster but problematic when there are multiple ways of representing the same character. Use coll() instead.

a1 <-  "\u00e1"
a2 <- "a\u0301"
c(a1,a2)
[1] "á" "a´"
# but a1 is not the same as a2
a1==a2
[1] FALSE

Coll() compares strings using standard collation rules. This is useful for doing case-insensitive matching. Note that coll() takes a locale parameter that controls which rules are used for comparing characters.

str_detect(a1, fixed(a2))
[1] FALSE
str_detect(a1, coll(a2))
[1] TRUE

Both fixed() and regex() have ignore_case statement. but only coll() allows you to ick the locale. They always use the default locale. The downside of coll() is speed. You can use boundary() to match boundaries.

x <-  "this is a sentence."
str_view_all(x, boundary("word"))
str_extract_all(x, boundary("word"))
[[1]]
[1] "this"     "is"       "a"        "sentence"

How would you find all strings containing  with regex() vs. with fixed()?

str_subset(c("a\\b", "ab"), "\\\\")
[1] "a\\b"
#> [1] "a\\b"
str_subset(c("a\\b", "ab"), fixed("\\"))
[1] "a\\b"
#> [1] "a\\b"

What are the five most common words in sentences?

library(tidyverse)
package <U+393C><U+3E31>tidyverse<U+393C><U+3E32> was built under R version 3.3.3Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages ------------------------------------------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
str_extract_all(sentences, boundary("word")) %>%
  unlist() %>%
  str_to_lower() %>%
  tibble() %>%
  set_names("word") %>%
  group_by(word) %>%
  count(sort = TRUE) %>%
  head(5)

Other uses of regular expressions

Two other useful functions in base R that also use regular expressions:
apropos() searches allobjects available fromt he global environment. This is useful if you can’t quite remember the name of the function:

apropos("replace")
 [1] "%+replace%"              ".rs.registerReplaceHook" ".rs.replaceBinding"     
 [4] "replace"                 "replace_na"              "setReplaceMethod"       
 [7] "str_replace"             "str_replace_all"         "str_replace_na"         
[10] "theme_replace"          

dir() lists all the files in a directory. The pattern argument takes a regular expression and only returns filenmaes that match the pattern

head(dir(pattern = "\\.Rmd$"))
[1] "chapter1.Rmd"  "chapter10.Rmd" "chapter11.Rmd" "Chapter2.Rmd"  "Chapter3.Rmd"  "Chapter4.Rmd" 

Stringi

stringr is built on top of the stringi package. stingr is useful when you’r learning because it exposes a minimal set of functions, which have been carefully picked to handle the most common string manipulation functions.

Stringi on the other hand is designed to be comperehensive. It contains almost every function you might ever need. Stringi has 234 functions to stringr’s 42.

Find the stringi functions that:

Count the number of words. stri_count_words() Find duplicated strings. stri_duplicated() Generate random text. There are several functions beginning with stri_rand_.
stri_rand_lipsum generates lorem ipsum text,
stri_rand_strings generates random strings,
stri_rand_shuffle randomly shuffles the code points in the text.

How do you control the language that stri_sort() uses for sorting?
Use the locale argument to the opts_collator argument.

LS0tDQp0aXRsZTogIlIgRm9yIERhdGEgU2NpZW5jZSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCjxoMT4gQ2hhcHRlciAxMSBTdHJpbmdzIHdpdGggc3RyaW5nciA8L2gxPg0KVGhpcyBjaGFwdGVyIGludHJvZHVjZXMgeW91IHRvIHN0cmluZyBtYW5pcHVsYXRpb24gaW4gUi4gQnV0IHRoZSBmb2N1cyB3aWxsIGJlIG9uIHJlZ3VsYXIgZXhwcmVzc2lvbnMsIG9yIHJlZ2V4cHMgZm9yIHNob3J0LiBSZWd1bGFyIGV4cHJlc3Npb25zIGFyZSB1c2VmdWwgYmVjYXVzZSBzdHJpbmdzIHVzdWFsbHkgY29udGFpbiB1bnN0cnVjdHVyZWQgb3Igc2VtaS1zdHJ1Y3R1cmVkIGRhdGEsIGFuZCByZWdleHBzIGFyZSBhIGNvbmNpc2UgbGFuZ3VhZ2UgZm9yIGRlc2NyaWJpbmcgcGF0dGVybnMgaW4gc3RyaW5ncy4gV2hlbiB5b3UgZmlyc3QgbG9vayBhdCBhIHJlZ2V4cCwgeW91J2xsIHRoaW5rIGEgY2F0IHdhbGtlZCBhY3Jvc3MgeW91ciBrZXlib2FyZCwgYnV0IGFzIHlvdXIgdW5kZXJzdGFuZGluZyBpbXByb3ZlcywgdGhleSB3aWxsIHNvb24gc3RhcnQgdG8gbWFrZSBzZW5zZS4gDQoNCjxoMj4gUHJlIHJlcXVpc2l0ZXMgPC9oMj4NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzdHJpbmdyKQ0KDQo8aDI+U3RyaW5nIEJhc2ljcyA8L2gyPg0KWW91IGNhbiB1c2UgZG91YmxlIG9yIHNpbmdsZSBxdW90ZXMuIEl0IGRvZXNudCBtYXR0ZXIuIElkZWFsIHRvIHVzZSBkb3VibGUgcXVvdGVzIHVubGVzcyB5b3Ugd2FudCB0byBjcmVhdGUgYSBzdHJpbmcgdGhhdCBjb250YWlucyBtdWx0aXBsZSBkb3VibGUgcXVvdGVzLg0KYGBge3J9DQpzdHJpbmcxIDwtICAiVGhpcyBpcyBhIHN0cmluZyINCnN0cmluZzIgPC0gJ1RvIHB1dCBhICJxdW90ZSIgaW5zaWRlIGEgc3RyaW5nLCB1c2Ugc2luZ2UgcXVvdGVzJw0KDQpzdHJpbmcxDQpzdHJpbmcyDQpgYGANCg0KWW91IGNhbiBhbHNvIGluY2x1ZGUgYSBsaXRlcmFsIHNpbmdsZSBvciBkb3VibGUgcXVvdGUgaW4gYSBzdHJpbmcgYnkgZXNjYXBlaW5nIGl0IHdpdGggXCA8L2JyPg0KDQoNCmBgYHtyfQ0KZG91YmxlX3F1b3RlIDwtICAiXCIiICMgb3IgJyInDQpzaW5nbGVfcXVvdGUgPC0gICdcJycgIyAgb3IgIiciDQpkb3VibGVfcXVvdGUNCnNpbmdsZV9xdW90ZQ0KDQpgYGANClRvIHNlZSB0aGUgcmF3IGNvbnRlbnRzIG9mIHRoZSBzdHJpbmcsIHVzZSB3cml0ZWxpbmVzKCkNCg0KYGBge3J9DQp3cml0ZUxpbmVzKGRvdWJsZV9xdW90ZSkNCndyaXRlTGluZXMoc2luZ2xlX3F1b3RlKQ0KYGBgDQpPdGhlciBzcGVjaWFsIGNoYXJhdGFjdGVycyBpbmNsdWRlIFxuIGZvciBuZXdsaW5lIGFuZCBcdCBmb3IgdGFiLiBBbHNvIFx1MDBiNSBzYW1wbGUgd2F5IG9mIHdyaXRpbmcgbm9uLUVuZ2xpc2ggY2hhcmFjdGVycyB0aGF0IHdvcmtzIG9uIGFsbCBwbGF0Zm9ybXMuIA0KDQpgYGB7cn0NCnggPC0gICJcdTAwYjUiDQp4DQp3cml0ZUxpbmVzKHgpDQpgYGANCg0KTXVsdGlwbGUgc3RyaW5ncyBjYW4gYmUgc3RvcmVkIGluIGEgY2hhcmFjdGFjdGVyIHZlY3RvciB3aXRoIGMoKQ0KDQpgYGB7cn0NCmMoIm9uZSIsInR3byIsInRocmVlIikNCmMNCg0KYGBgDQoNCg0KDQoNCjxkaXYgaWQ9InN0cmluZ3MiIGNsYXNzPSJzZWN0aW9uIGxldmVsMSI+DQo8ZGl2IGlkPSJpbnRyb2R1Y3Rpb24tNCIgY2xhc3M9InNlY3Rpb24gbGV2ZWwyIj4NCjxwPkZ1bmN0aW9ucyBhbmQgcGFja2FnZXMgY292ZXJlcmVkPC9wPg0KPHVsPg0KPGxpPjxzdHJvbmc+c3RyaW5ncjwvc3Ryb25nPiBwYWNrYWdlPC9saT4NCjxsaT48Y29kZT5zdHJfbGVuZ3RoPC9jb2RlPjwvbGk+DQo8bGk+PGNvZGU+c3RyX2M8L2NvZGU+PC9saT4NCjxsaT48Y29kZT5zdHJfcmVwbGFjZV9uYTwvY29kZT48L2xpPg0KPGxpPjxjb2RlPnN0cl9zdWI8L2NvZGU+PC9saT4NCjxsaT48Y29kZT5zdHJfdG9fdXBwc2VyPC9jb2RlPiwgPGNvZGU+c3RyX3NvcnQ8L2NvZGU+LCA8Y29kZT5zdHJfdG9fbG93ZXI8L2NvZGU+LCA8Y29kZT5zdHJfb3JkZXI8L2NvZGU+PC9saT4NCjxsaT48Y29kZT5zdHJfbGVuZ3RoPC9jb2RlPiwgPGNvZGU+c3RyX3BhZDwvY29kZT4sIDxjb2RlPnN0cl90cmltPC9jb2RlPiwgPGNvZGU+c3RyX3N1YjwvY29kZT48L2xpPg0KPGxpPkZvciByZWdleCA9IDxjb2RlPnN0cl92aWV3PC9jb2RlPiwgPGNvZGU+c3RyX3ZpZXdfYWxsPC9jb2RlPjwvbGk+DQo8bGk+cmVnZXggc3ludGF4PC9saT4NCjxsaT48Y29kZT5zdHJfZGV0ZWN0PC9jb2RlPjwvbGk+DQo8bGk+PGNvZGU+c3RyX3N1YnNldDwvY29kZT48L2xpPg0KPGxpPjxjb2RlPnN0cl9jb3VudDwvY29kZT48L2xpPg0KPGxpPjxjb2RlPnN0cl9leHRyYWN0PC9jb2RlPjwvbGk+DQo8bGk+PGNvZGU+c3RyX21hdGNoPC9jb2RlPjwvbGk+DQo8bGk+PGNvZGU+dGlkeXI6OmV4dHJhY3Q8L2NvZGU+PC9saT4NCjxsaT48Y29kZT5zdHJfc3BsaXQ8L2NvZGU+PC9saT4NCjxsaT48Y29kZT5zdHJfbG9jYXRlPC9jb2RlPjwvbGk+DQo8bGk+PGNvZGU+c3RyX3N1YjwvY29kZT48L2xpPg0KPGxpPnRoZSA8c3Ryb25nPnN0cmluZ2k8L3N0cm9uZz4gcGFja2FnZTwvbGk+DQo8L3VsPg0KPHA+SWRlYXM8L3A+DQo8dWw+DQo8bGk+bWVudGlvbiA8YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20va2V2aW51c2hleS9yZXgiPjxjb2RlPnJleDwvY29kZT48L2E+LiBBIHBhY2thZ2Ugd2l0aCBmcmllbmRseSByZWd1bGFyIGV4cHJlc3Npb25zLjwvbGk+DQo8bGk+VXNlIGl0IHRvIG1hdGNoIGNvdW50cnkgbmFtZXM/IEV4dHJhY3QgbnVtYmVycyBmcm9tIHRleHQ/PC9saT4NCjxsaT5EaXNjdXNzIGZ1enp5IGpvaW5pbmcgYW5kIHN0cmluZyBkaXN0YW5jZSwgYXBwcm94aW1hdGUgbWF0Y2hpbmcuPC9saT4NCjwvdWw+DQo8cD5BbHNvIHNlZTwvcD4NCjx1bD4NCjxsaT48YSBocmVmPSJodHRwOi8vc3RhdDU0NS5jb20vYmxvY2swMzJfY2hhcmFjdGVyLWVuY29kaW5nLmh0bWwiPkNoYXJhY3RlciBlbmNvZGluZzwvYT4gU3RhdCA1NDUuIEplbm55IEJyeWFuLjwvbGk+DQo8bGk+PGEgaHJlZj0iaHR0cDovL3N0YXQ1NDUuY29tL2Jsb2NrMDI4X2NoYXJhY3Rlci1kYXRhLmh0bWwiPkNoYXJhY3RlciBkYXRhPC9hPi4gU3RhdCA1NDUuIEplbm55IEJyeWFuLjwvbGk+DQo8bGk+PGEgaHJlZj0iaHR0cDovL3N0YXQ1NDUuY29tL2Jsb2NrMDIyX3JlZ3VsYXItZXhwcmVzc2lvbi5odG1sIj5SZWd1bGFyIGV4cHJlc3Npb24gaW4gUjwvYT4uIFN0YXQgNTQ1LiBKZW5ueSBCcnlhbi48L2xpPg0KPC91bD4NCg0KDQo8aDI+IFN0cmluZyBMZW5ndGggPC9oMj4NCnN0cl9sZW5ndGgoKSB0ZWxscyB5b3UgdGhlIG51bWJlciBvZiBjaGFyYWN0ZXJzIGluIGEgc3RyaW5nIA0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzdHJpbmdyKQ0Kc3RyX2xlbmd0aCggYygiYSIsIlIgZm9yIGRhdGEgc2NpZW5jZSIsIE5BKSkNCg0KYGBgDQoNCg0KPGgyPiBDb21iaW5pbmcgU3RyaW5ncyA8L2gyPg0KVXNlIHN0cl9jKCkgdG8gY29tYmluZSBvbmUgb3IgbW9yZSBzdHJpbmdzLiBVc2UgdGhlIHNlcD0gYXJndW1lbnQgdG8gY29udHJvbCBob3cgdGhleSdyZSBzZXBhcmF0ZWQuPC9icj4NCiMgbXVjaCBsaWtlIHBhc3RlMCBmdW5jdGlvbiAoY29tYmluZXMgc3RyaW5ncyB3aXRob3V0IHNwYWNlcyBpbiBiZXR3ZWVuIHRoZW0pDQoNCmBgYHtyfQ0Kc3RyX2MoIngiLCJ5IikNCnN0cl9jKCJ4IiwieSIsInoiKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyX2MoIngiLCJ5Iiwgc2VwID0gIiwgIikNCmBgYA0KDQpMaWtlIG1vc3Qgb3RoZXIgZnVuY3Rpb25zIGluIFIsIG1pc3NpbmcgdmFsdWVzIGFyZSBjb250YWdpb3VzLiBJZiB5b3Ugd2FudCB0aGVtIHRvIHByaW50IGFzICJOQSIsIHVzZSBzdHJfcmVwbGFjZV9uYSgpDQoNCmBgYHtyfQ0KeCA8LSAgYygiYWJjIiwgTkEpDQpzdHJfYygifC0iLHgsIi18IikNCnN0cl9jKCJ8LSIsc3RyX3JlcGxhY2VfbmEoeCksIi18IikNCg0KYGBgDQoNClN0cl9jIGlzIHZlY3Rvcml6ZWQgYW5kIGl0IGF1dG9tYXRpY2FsbHkgcmVjeWxjZXMgc2hvcnRlciB2ZWN0b3JzIHRvIHRoZSBzYW1lIGxlbmd0aCBhcyB0aGUgbG9uZ2VzdA0KDQpgYGB7cn0NCnN0cl9jKCJwcmVmaXgtIixjKCJhIiwiYiIsImMiKSwgIi1zdWZmaXgiKQ0KYGBgDQoNCk9iamVjdHMgb2YgbGVuZ3RoIDAgYXJlIGRyb3BwZWQuIA0KDQpgYGB7cn0NCm5hbWUgPC0gIkhhZGxleSINCnRpbWVfb2ZfZGF5IDwtICAibW9ybmluZyINCmJpcnRoZGF5IDwtICBUUlVFDQojIGJpcnRoZGF5IDwtICBGQUxTRQ0Kc3RyX2MoIkdvb2QiLHRpbWVfb2ZfZGF5LG5hbWUsIGlmKGJpcnRoZGF5KSAiIGFuZCBIYXBweSBCaXJ0aGRheSIsIHNlcCA9ICIgIikNCg0KDQpgYGANCg0KVG8gY29sbGFwc2UgYSB2ZWN0b3Igb2Ygc3RyaW5ncyBpbnRvIGEgc2luZ2xlIHN0cmluZywgdXNlIGNvbGxhcHNlIGFyZ3VtZW50DQoNCmBgYHtyfQ0Kc3RyX2MoYygieCIsInkiLCJ6IiksIGNvbGxhcHNlID0gIiwgIikNCmBgYA0KDQo8aDI+IFN1YnNldHRpbmcgU3RyaW5ncyA8L2gyPg0KWW91IGNhbiBleHRyYWN0IHBhcnRzIG9mIGEgc3RyaW5nIHVzaW5nIHN0cl9zdWIoKS4gSXQgdGFrZXMgc3RhcnQgYW5kIGVuZCBhcmd1bWVudHMgdGhhdCBnaXZlIHRoZSBwb3NpdGlvbiBvZiB0aGUgc3Vic3RyaW5nLiBOZWdhdGl2ZSBudW1iZXJzIG9jdW50IGJhY2t3YXJkcyBmcm9tIHRoZSBlbmQuIEl0IHdvbid0IGZhaWwgaWYgdGhlIHN0cmluZyBpcyB0b28gc2hvcnQuIEl0IHdpbGwgcmV0dXJuIGFzIG11Y2ggYXMgcG9zc2libGUgb2YgdGhlIHN0cmluZy4NCg0KYGBge3J9DQp4IDwtICBjKCJBcHBsZSIsICJCYW5hbmEiLCAiQ2FScm90cyIpDQpzdHJfc3ViKHgsIDEsIDMpDQpzdHJfc3ViKHgsIC0zLCAtMSkNCnN0cl9zdWIoeCwgMSwgNykNCmBgYA0KDQpVc2luZyB0aGUgYXNzaWdubWVudCBmb3JtIG9mIHN0cl9zdWIsIHdlIGNhbiBjaGFuZ2UgdGhlIGNhcGl0YWxpemF0aW9uIG9uIHRoZSBmaXJzdCBjaGFyYWN0ZXIgb2YgZWFjaCBzdHJpbmcgaW4geA0KDQpgYGB7cn0NCngNCnN0cl9zdWIoeCwxLDEpIDwtIHN0cl90b19sb3dlcihzdHJfc3ViKHgsMSwxKSkNCngNCg0KYGBgDQoNCg0KPGgyPiBMb2NhbGVzIDwvaDI+DQpZb3UgY2FuIGFsc28gdXNlIHN0cl90b191cHBlcigpIGFuZCBzdHJfdG9fdGl0bGUoKS4gSG93ZXZlciwgY2hhbmdpbmcgY2FzZSBpcyBtb3JlIGNvbXBsaWNhdGVkIGJlY2F1c2Ugb2YgZGlmZmVyZW50IGxhbmd1YWdlcyAuIFlvdSBjYW4gc2V0IHdoaWNoIHJ1bGVzIHRvIGFwcGx5IGJ5IHNwZWNpZnlpbmdhIGxvY2FsZQ0KDQpgYGB7cn0NCiMgdHVya2lzaCBoYXMgdHdvIGknczogd2l0aCBhbmQgd2l0aG91dCBhIGRvdA0KIyBpdCBoYXMgYSBkaWZmZXJlbnQgcnVsZSBmb3IgY2FwaXRhbGl6aW5nIHRoZW0NCg0Kc3RyX3RvX3VwcGVyKGMoImkiLCJJIiksIGxvY2FsZSA9ICJ0ciIpDQpgYGANCg0KTG9jYWxlcyBhbHNvIGFmZmVjdHMgc29ydGluZy4gVGhlIGJhc2UgUiBvcmRlcigpIGFuZCBzb3J0KCkgZnVuY3Rpb25zIHNvcnQgc3RyaW5ncyB1c2luZyB0aGUgY3VycmVudCBsb2NhbGUuIElmIHlvdSB3YW50IHJvYnVzdCBiZWhhdmlvciBhY3Jvc3MgZGlmZmVyZW50IGNvbXB1dGVycywgeW91IHdhbnQgdG8gdXNlIHN0cl9zb3J0KCkgYW5zIHN0cl9vcmRlcigpIHdoaWNoIHRha2UgYW4gYWRkaXRpb25hbCBsb2NhbGUgYXJndW1lbnQuDQoNCmBgYHtyfQ0KeCA8LSBjKCJhcHBsZSIsImVnZ3BsYW50IiwiYmFuYW5hIikNCnN0cl9zb3J0KHgsIGxvY2FsZSA9ICJlbiIpDQpzdHJfc29ydCh4LCBsb2NhbGUgPSAiaGF3IikNCmBgYA0KDQpFeGVyY2lzZXM6DQpJbiBjb2RlIHRoYXQgZG9lc24ndCB1c2Ugc3RyaW5nciwgeW91J2xsIG9mdGVuIHNlZSBwYXN0ZSgpIGFuZCBwYXN0ZTAoKS4gV2hhdCdzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBmdW5jdGlvbnM/IFdoYXQgc3RyaW5nciBmdW5jdGlvbiBhcmUgdGhleSBlcXVpdmFsZW50IHRvPyBIb3cgZG8gdGhlIGZ1bmN0aW9ucyBkaWZmZXIgaW4gdGhlaXIgaGFuZGxpbmcgb2YgTkE/IDwvYnI+DQoNClRoZSBmdW5jdGlvbiBwYXN0ZSBzZXBlcmF0ZXMgc3RyaW5ncyBieSBzcGFjZXMgYnkgZGVmYXVsdCwgd2hpbGUgcGFzdGUwIGRvZXMgbm90IHNlcGVyYXRlIHN0cmluZ3Mgd2l0aCBzcGFjZXMgYnkgZGVmYXVsdC5TaW5jZSBzdHJfYyBkb2VzIG5vdCBzZXBlcmF0ZSBzdHJpbmdzIHdpdGggc3BhY2VzIGJ5IGRlZmF1bHQgaXQgaXMgY2xvc2VyIGluIGJlaGFiaW9yIHRvIHBhc3RlMC48YnI+DQoNCmBgYHtyfQ0KcGFzdGUoImZvbyIsICJiYXIiKQ0KIz4gWzFdICJmb28gYmFyIg0KcGFzdGUwKCJmb28iLCAiYmFyIikNCiM+IFsxXSAiZm9vYmFyIg0KYGBgDQoNCg0KSG93ZXZlciwgc3RyX2MgYW5kIHRoZSBwYXN0ZSBmdW5jdGlvbiBoYW5kbGUgTkEgZGlmZmVyZW50bHkuIFRoZSBmdW5jdGlvbiBzdHJfYyBwcm9wb2dhdGVzIE5BLCBpZiBhbnkgYXJndW1lbnQgaXMgYSBtaXNzaW5nIHZhbHVlLCBpdCByZXR1cm5zIGEgbWlzc2luZyB2YWx1ZS4gVGhpcyBpcyBpbiBsaW5lIHdpdGggaG93IHRoZSBudW1lcmljIFIgZnVuY3Rpb25zLCBlLmcuIHN1bSwgbWVhbiwgaGFuZGxlIG1pc3NpbmcgdmFsdWVzLiBIb3dldmVyLCB0aGUgcGFzdGUgZnVuY3Rpb25zLCBjb252ZXJ0IE5BIHRvIHRoZSBzdHJpbmcgIk5BIiBhbmQgdGhlbiB0cmVhdCBpdCBhcyBhbnkgb3RoZXIgY2hhcmFjdGVyIHZlY3Rvci48L2JyPg0KDQpgYGB7cn0NCnN0cl9jKCJmb28iLCBOQSkNCg0KcGFzdGUoImZvbyIsIE5BKQ0KDQpwYXN0ZTAoImZvbyIsIE5BKQ0KDQpgYGANCkluIHlvdXIgb3duIHdvcmRzLCBkZXNjcmliZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSA8Yj5zZXA8L2I+IGFuZCA8Yj5jb2xsYXBzZTwvYj4gYXJndW1lbnRzIHRvIHN0cl9jKCkuIDwvYnI+DQpUaGUgc2VwIGFyZ3VtZW50IGlzIHRoZSBzdHJpbmcgaW5zZXJ0ZWQgYmV0d2VlbiBhcmd1Z21lbnRzIHRvIHN0cl9jLCB3aGlsZSBjb2xsYXBzZSBpcyB0aGUgc3RyaW5nIHVzZWQgdG8gc2VwYXJhdGUgYW55IGVsZW1lbnRzIG9mIHRoZSBjaGFyYWN0ZXIgdmVjdG9yIGludG8gYSBjaGFyYWN0ZXIgdmVjdG9yIG9mIGxlbmd0aCBvbmUuIDwvcD4NCg0KDQpVc2Ugc3RyX2xlbmd0aCgpIGFuZCBzdHJfc3ViKCkgdG8gZXh0cmFjdCB0aGUgbWlkZGxlIGNoYXJhY3RlciBmcm9tIGEgc3RyaW5nLiBXaGF0IHdpbGwgeW91IGRvIGlmIHRoZSBzdHJpbmcgaGFzIGFuIGV2ZW4gbnVtYmVyIG9mIGNoYXJhY3RlcnM/IDwvYnI+DQpUaGUgZm9sbG93aW5nIGZ1bmN0aW9uIGV4dHJhY3RzIHRoZSBtaWRkbGUgY2hhcmFjdGVyLiBJZiB0aGUgc3RyaW5nIGhhcyBhbiBldmVuIG51bWJlciBvZiBjaGFyYWN0ZXJzIHRoZSBjaG9pY2UgaXMgYXJiaXRyYXJ5LiBXZSBjaG9vc2UgdG8gc2VsZWN0ICBuLzIgLCBiZWNhdXNlIHRoYXQgY2FzZSB3b3JrcyBldmVuIGlmIHRoZSBzdHJpbmcgaXMgb25seSBvZiBsZW5ndGggb25lLiBBIG1vcmUgZ2VuZXJhbCBtZXRob2Qgd291bGQgYWxsb3cgdGhlIHVzZXIgdG8gc2VsZWN0IGVpdGhlciB0aGUgZmxvb3Igb3IgY2VpbGluZyBmb3IgdGhlIG1pZGRsZSBjaGFyYWN0ZXIgb2YgYW4gZXZlbiBzdHJpbmcuIDwvcD4NCg0KYGBge3J9DQp4IDwtIGMoImEiLCAiYWJjIiwgImFiY2QiLCAiYWJjZGUiLCAiYWJjZGVmIikNCkwgPC0gc3RyX2xlbmd0aCh4KQ0KbSA8LSBjZWlsaW5nKEwgLyAyKQ0Kc3RyX3N1Yih4LCBtLCBtKQ0KDQpgYGANCg0KV2hhdCBkb2VzIHN0cl93cmFwKCkgZG8/IFdoZW4gbWlnaHQgeW91IHdhbnQgdG8gdXNlIGl0PyA8L2JyPg0KVGhlIGZ1bmN0aW9uIHN0cl93cmFwIHdyYXBzIHRleHQgc28gdGhhdCBpdCBmaXRzIHdpdGhpbiBhIGNlcnRhaW4gd2lkdGguIFRoaXMgaXMgdXNlZnVsIGZvciB3cmFwcGluZyBsb25nIHN0cmluZ3Mgb2YgdGV4dCB0byBiZSB0eXBlc2V0LjwvcD4NCg0KV2hhdCBkb2VzIHN0cl90cmltKCkgZG8/IFdoYXQncyB0aGUgb3Bwb3NpdGUgb2Ygc3RyX3RyaW0oKT8gPC9icj4NClRoZSBmdW5jdGlvbiBzdHJfdHJpbSB0cmltcyB0aGUgd2hpdGVzcGFjZSBmcm9tIGEgc3RyaW5nLg0KDQpgYGB7cn0NCnN0cl90cmltKCIgYWJjICIpDQojPiBbMV0gImFiYyINCnN0cl90cmltKCIgYWJjICIsIHNpZGUgPSAibGVmdCIpDQojPiBbMV0gImFiYyAiDQpzdHJfdHJpbSgiIGFiYyAiLCBzaWRlID0gInJpZ2h0IikNCiM+IFsxXSAiIGFiYyINCmBgYA0KDQpUaGUgb3Bwb3NpdGUgb2Ygc3RyX3RyaW0gaXMgc3RyX3BhZCB3aGljaCBhZGRzIGNoYXJhY3RlcnMgdG8gZWFjaCBzaWRlLjwvcD4NCmBgYHtyfQ0Kc3RyX3BhZCgiYWJjIiwgNSwgc2lkZSA9ICJib3RoIikNCiM+IFsxXSAiIGFiYyAiDQpzdHJfcGFkKCJhYmMiLCA0LCBzaWRlID0gInJpZ2h0IikNCiM+IFsxXSAiYWJjICINCnN0cl9wYWQoImFiYyIsIDQsIHNpZGUgPSAibGVmdCIpDQojPiBbMV0gIiBhYmMiDQpgYGANCg0KV3JpdGUgYSBmdW5jdGlvbiB0aGF0IHR1cm5zIChlLmcuKSBhIHZlY3RvciBjKCJhIiwgImIiLCAiYyIpIGludG8gdGhlIHN0cmluZyBhLCBiLCBhbmQgYy4gVGhpbmsgY2FyZWZ1bGx5IGFib3V0IHdoYXQgaXQgc2hvdWxkIGRvIGlmIGdpdmVuIGEgdmVjdG9yIG9mIGxlbmd0aCAwLCAxLCBvciAyLiA8L3A+DQoNCmBgYHtyfQ0Kc3RyX2NvbW1hc2VwIDwtIGZ1bmN0aW9uKHgsIHNlcCA9ICIsICIsIGxhc3QgPSAiLCBhbmQgIikgew0KICBpZiAobGVuZ3RoKHgpID4gMSkgew0KICAgIHN0cl9jKHN0cl9jKHhbLWxlbmd0aCh4KV0sIGNvbGxhcHNlID0gc2VwKSwNCiAgICAgICAgICAgICAgICB4W2xlbmd0aCh4KV0sDQogICAgICAgICAgICAgICAgc2VwID0gbGFzdCkNCiAgfSBlbHNlIHsNCiAgICB4DQogIH0NCn0NCnN0cl9jb21tYXNlcCgiIikNCnN0cl9jb21tYXNlcCgiYSIpDQpzdHJfY29tbWFzZXAoYygiYSIsICJiIikpDQpzdHJfY29tbWFzZXAoYygiYSIsICJiIiwgImMiKSkNCg0KYGBgDQoNCjxoMj4gTWF0Y2hpbmcgUGF0dGVybnMgd2l0aCByZWd1bGFyIGV4cHJlc3Npb25zIDwvaDI+DQpSZWdleHAgYXJlIHZlcnkgdGVyc2UgbGFuZ3VhZ2UgdGhhdCBhbGxvdyB5b3UgdG8gZGVzY3JpYmUgcGF0dGVybnMgaW4gc3RyaW5ncy4gVGhleSB0YWtlIGEgbGl0dGxlIHdoaWxlIHRvIGdldCB5b3VyIGhlYWQgYXJvdW5kLiBUbyBsZWFybiByZWd1bGFyIGV4cHJlc3Npb25zLCB3ZSdsbCB1c2Ugc3RyX3ZpZXcoKSBhbmQgc3RyX3ZpZXdfYWxsKCkuIFRoZXNlIGZ1bmN0aW9ucyB0YWtlIGEgY2hhcmFjdGVyIHZlY3RvciBhbmQgYSByZWd1bGFyIGV4cHJlc3Npb24gYW5kIHNob3cgeW91IGhvdyB0aGV5IG1hdGNoLiBXZSdsbCBzdGFydCB3aXRoIHZlcnkgc2ltcGxlIHJlZ3VsYXIgZXhwcmVzaW9ucyBhbmQgdGhlbiBncmFkdWFsbHkgZ2V0IG1vcmUgYW5kIG1vcmUgY29tcGxpY2F0ZWQuIA0KDQoNCjxoMz4gYmFzaWMgbWF0Y2hlcyA8L2gzPg0KVGhlIHNpbXBsZXN0IHBhdHRlcm5zIG1hdGNoIGV4YWN0IHN0cmluZ3M6DQoNCmBgYHtyfQ0KIyBuZWVkIHRvIGluc3RhbCBodG1sd2lkZ2V0cyBwYWNrYWdlIGZpcnN0DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc3RyaW5ncikNCnggPC0gIGMoImFwcGxlIiwgImJhbmFuYSIsICJwZWFyIikNCnN0cl92aWV3KHgsImFuIikNCmBgYA0KDQouIG1hdGNoZXMgYW55IGNoYXJhY3Rlcg0KDQpgYGB7cn0NCnN0cl92aWV3KHgsICIuYS4iKQ0KYGBgDQoNCnRvIG1hdGNoIHNwZWNpYWwgY2hhcmFjdGVyczogdXNlIFxcDQoNCmBgYHtyfQ0KIyBkb3QNCmRvdCA8LSAiXFwuIg0Kd3JpdGVMaW5lcyhkb3QpDQpgYGANCg0KVGhpcyB0ZWxscyBSIHRvIGxvb2sgZm9yIGFuIGV4cGxpY3QgLg0KDQpgYGB7cn0NCnN0cl92aWV3KGMoImFiYyIsICJhLmMiLCJiZWYiKSwiYVxcLmMiKQ0KDQoNCmBgYA0KYGBge3J9DQpzdHJfdmlldyhjKCJhYmMiLCAiYS5jIiwiYmVmIiksIi5cXC4uIikNCg0KYGBgDQoNClRvIG1hdGNoIGEgbGl0ZXJhbCBcLHlvdSBuZWVkIGZvdXIgXFxcXA0KDQpgYGB7cn0NCnggPC0gImFcXGIiDQpzdHJfdmlldyh4LCAiXFxcXCIpDQoNCmBgYA0KDQoNCkV4cGxhaW4gd2h5IGVhY2ggb2YgdGhlc2Ugc3RyaW5ncyBkb24ndCBtYXRjaCBhIFw6ICJcIiwgIlxcIiwgIlxcXCIuIDwvYnI+DQoiXCI6IFRoaXMgd2lsbCBlc2NhcGUgdGhlIG5leHQgY2hhcmFjdGVyIGluIHRoZSBSIHN0cmluZy4gPC9icj4NCiJcXCI6IFRoaXMgd2lsbCByZXNvbHZlIHRvIFwgaW4gdGhlIHJlZ3VsYXIgZXhwcmVzc2lvbiwgd2hpY2ggd2lsbCBlc2NhcGUgdGhlIG5leHQgY2hhcmFjdGVyIGluIHRoZSByZWd1bGFyIGV4cHJlc3Npb24uPC9icj4NCiJcXFwiOiBUaGUgZmlyc3QgdHdvIGJhY2tzbGFzaGVzIHdpbGwgcmVzb2x2ZSB0byBhIGxpdGVyYWwgYmFja3NsYXNoIGluIHRoZSByZWd1bGFyIGV4cHJlc3Npb24sIHRoZSB0aGlyZCB3aWxsIGVzY2FwZSB0aGUgbmV4dCBjaGFyYWN0ZXIuIFNvIGluIHRoZSByZWd1bGFyIGV4cHJlc2lvbiwgdGhpcyB3aWxsIGVzY2FwZSBzb21lIGVzY2FwZWQgY2hhcmFjdGVyLjwvYnI+DQoNCg0KSG93IHdvdWxkIHlvdSBtYXRjaCB0aGUgc2VxdWVuY2UgIidcID8NCg0KYGBge3J9DQp4IDwtIGMoIidcXCIsImEiLCJiIikNCndyaXRlTGluZXMoeCkNCnN0cl92aWV3KHgsICJcJ1xcXFwiKQ0KYGBgDQoNCg0KV2hhdCBwYXR0ZXJucyB3aWxsIHRoZSByZWd1bGFyIGV4cHJlc3Npb24gXC4uXC4uXC4uIG1hdGNoPyBIb3cgd291bGQgeW91IHJlcHJlc2VudCBpdCBhcyBhIHN0cmluZz8gPC9icj4NCkl0IHdpbGwgbWF0Y2ggYW55IHBhdHRlcm5zIHRoYXQgYXJlIGEgZG90IGZvbGxvd2VkIGJ5IGFueSBjaGFyYWN0ZXIsIHJlcGVhdGVkIHRocmVlIHRpbWVzLg0KDQoNCjxoMz4gQW5jaG9ycyA8L2gzPg0KSXQncyBvZnRlbiB1c2VmdWwgdG8gYW5jaG9yIHRoZSByZWd1bGFyIGV4cHJlc3Npb24gc28gdGhhdCBpdCBtYXRjaGVzIGZyb210IGhlIHN0YXJ0IG9yIGVuZCBvZiB0aGUgc3RyaW5nLiBZb3UgY2FuIHVzZSA8L2JyPg0KXiB0byBtYXRjaCB0aGUgc3RhcnQgb2YgdGhlIHN0cmluZyA8L2JyPg0KJCB0byBtYXRjaCB0aGUgZW5kIG9mIHRoZSBzdHJpbmcgPC9icj4NCg0KTW5lbW9uaWMgYmVnaW4gd2l0aCBQb3dlciBeIGFuZCBlbmQgd2l0aCBtb25leSgkKQ0KDQpgYGB7cn0NCnggPC0gIGMoImFwcGxlIiwiYmFuYW5hIiwicGVhciIpDQpzdHJfdmlldyh4LCJeYSIpDQpzdHJfdmlldyh4LCJhJCIpDQpgYGANCg0KVG8gZm9yY2UgYSByZWd1bGFyIGV4cHJlc3Npb24gdG8gb25seSBtYXRjaCBhIGNvbXBsZXRlIHN0cmluZywgZW5jbG9zZSBpdCB3aXRoIF4gYW5kICQNCg0KYGBge3J9DQp4IDwtIGMoImFwcGxlIHBpZSIsICJhcHBsZSIsICJhcHBsZSBjYWtlIikNCnN0cl92aWV3KHgsImFwcGxlIikNCiMgd2lsbCBsaXN0IGFsbCB0aHJlZQ0Kc3RyX3ZpZXcoeCwgIl5hcHBsZSQiKQ0KIyB3aWxsbGlzdCBvbmx5IGFwcGxlDQoNCmBgYA0KWW91IGNhbiB1c2UgXGIgdG8gbWF0Y2ggdGhlIGJvdW5kYXJ5IGJldHdlZW4gd29yZHMNCg0KYGBge3J9DQp4IDwtIGMoImFwcGxlY3J1c3QgcGllIiwgImFwcGxlIGNydXN0IHBpZSIsICJhcHBsZSBjcnVtYmxlIGNha2UiKQ0Kc3RyX3ZpZXcoeCwgIlxcYmNydXN0XFxiIikNCg0KYGBgDQoNCg0KSG93IHdvdWxkIHlvdSBtYXRjaCB0aGUgbGl0ZXJhbCBzdHJpbmcgIiBeXiAiPw0KYGBge3J9DQpzdHJfdmlldyhjKCIkXiQiLCAiYWIkXiRzZmFzIiksICJeXFwkXFxeXFwkJCIpDQpgYGANCg0KR2l2ZW4gdGhlIGNvcnB1cyBvZiBjb21tb24gd29yZHMgaW4gc3RyaW5ncjo6d29yZHMsIGNyZWF0ZSByZWd1bGFyIGV4cHJlc3Npb25zIHRoYXQgZmluZCBhbGwgd29yZHMgdGhhdDogPC9icj4NClNpbmNlIHRoaXMgbGlzdCBpcyBsb25nLCB5b3UgbWlnaHQgd2FudCB0byB1c2UgdGhlIDxiPm1hdGNoPVRSVUU8L2I+IGFyZ3VtZW50IHRvIHN0cl92aWV3KCkgdG8gc2hvdyBvbmx5IHRoZSBtYXRjaGluZyBvciBub24tbWF0Y2hpbmcgd29yZHMuPC9icj4NCg0KU3RhcnQgd2l0aCAieSIuPC9icj4NCmBgYHtyfQ0Kc3RyX3ZpZXcod29yZHMsIl55LiIsIG1hdGNoID0gVFJVRSkNCg0KYGBgDQoNCkVuZCB3aXRoICJ4IjwvYnI+DQoNCmBgYHtyfQ0Kc3RyX3ZpZXcod29yZHMsIngkIiwgbWF0Y2ggPSBUUlVFKQ0KDQpgYGANCg0KQXJlIGV4YWN0bHkgdGhyZWUgbGV0dGVycyBsb25nLiAoRG9uJ3QgY2hlYXQgYnkgdXNpbmcgc3RyX2xlbmd0aCgpISk8L2JyPg0KDQpgYGB7cn0NCnN0cl92aWV3KHdvcmRzLCJeLi4uJCIsIG1hdGNoID0gVFJVRSkNCmBgYA0KDQpIYXZlIHNldmVuIGxldHRlcnMgb3IgbW9yZS48L2JyPg0KYGBge3J9DQpzdHJfdmlldyh3b3JkcywiXi4uLi4uLi4iLCBtYXRjaCA9IFRSVUUpDQpgYGANCg0KPGgzPiBDaGFyYWN0ZXIgQ2xhc3NlcyBhbmQgQWx0ZXJuYXRpdmVzIDwvaDM+DQpcZCBtYXRjaGVzIGFueSBkaWdpdCA8L2JyPg0KXHMgbWF0Y2hlcyBhbnkgd2hpdGVzcGFjZSA8L2JyPg0KW2FiY10gbWF0Y2hlcyBhLGIgb3IgYyA8L2JyPg0KW15hYmNdIG1hdGNoZXMgYW55dGhpbmcgZXhjZXB0IGEsIGIgb3IgYyA8L2JyPg0KDQpyZW1lbWJlciB0byB1c2UgXFxkIGFuZCBcXHMgZm9yIHRob3NlIHNwZWNpYWwgY2hhcmFjdGVycyA8L2JyPg0KDQpZb3UgY2FuIHVzZSBhbHRlcm5hdGVzIHRvIHBpY2sgYmV0d2VlbiBvbmUgb3IgbW9yZSBhbHRlcm5hdGl2ZSBwYXR0ZXJucy4gRm9yIGV4YW1wbGUsICJhYmN8ZC4uZiIgd2lsbCBtYXRjaCBlaXRoZXIgImFiYyIgb3IgImRlYWYiIE5vdGUgdGhhdCB0aGUgcHJlY2VuZGVuY2UgZm9yIHwgaXMgbG93LCBzbyB0aGF0IGFiY3x4eXogbWF0Y2hlcyBhYmMgb3IgeHl6IG5vdCBhYmN5eiBvciBhYnh5ei4gVXNlIHBhcmVudGhlc2lzIHRvIG1ha2UgaXQgY2xlYXIgd2hhdCB5b3Ugd2FudA0KDQpgYGB7cn0NCnN0cl92aWV3KGMoImdyZXkiLCJncmF5IiksImdyKGV8YSl5IikNCmBgYA0KDQoNCkV4ZXJjaXNlczogPC9icj4NCkNyZWF0ZSByZWd1bGFyIGV4cHJlc3Npb25zIHRvIGZpbmQgYWxsIHdvcmRzIHRoYXQ6DQoNClN0YXJ0IHdpdGggYSB2b3dlbC4NCg0KYGBge3J9DQpzdHJfdmlldyh3b3JkcywiXlthZWlvdV0uIiwgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNClRoYXQgb25seSBjb250YWluIGNvbnNvbmFudHMuIChIaW50OiB0aGlua2luZyBhYm91dCBtYXRjaGluZyAibm90Ii12b3dlbHMuKQ0KYGBge3J9DQpzdHJfdmlldyhzdHJpbmdyOjp3b3JkcywgIl5bXmFlaW91XSskIiwgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNCkVuZCB3aXRoIGVkLCBidXQgbm90IHdpdGggZWVkLg0KYGBge3J9DQpzdHJfdmlldyhzdHJpbmdyOjp3b3JkcywgIl5lZCR8W15lXWVkJCIsIG1hdGNoID0gVFJVRSkNCmBgYA0KDQpFbmQgd2l0aCBpbmcgb3IgaXNlLg0KYGBge3J9DQpzdHJfdmlldyhzdHJpbmdyOjp3b3JkcywgImluZyR8aXNlJCIsIG1hdGNoID0gVFJVRSkNCiMgc3RyX3ZpZXcoc3RyaW5ncjo6d29yZHMsICJpKG5nfHNlKSQiLCBtYXRjaCA9IFRSVUUpDQpgYGANCg0KDQpFbXBpcmljYWxseSB2ZXJpZnkgdGhlIHJ1bGUgImkgYmVmb3JlIGUgZXhjZXB0IGFmdGVyIGMiLg0KDQpgYGB7cn0NCnN0cl92aWV3KHN0cmluZ3I6OndvcmRzLCAiKGNlaXxbXmNdaWUpIiwgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNCg0KYGBge3J9DQpzdHJfdmlldyhzdHJpbmdyOjp3b3JkcywgIihjaWV8W15jXWVpKSIsIG1hdGNoID0gVFJVRSkNCmBgYA0KSXMgInEiIGFsd2F5cyBmb2xsb3dlZCBieSBhICJ1Ij8NCg0KYGBge3J9DQpzdHJfdmlldyhzdHJpbmdyOjp3b3JkcywgInF1IiwgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyX3ZpZXcoc3RyaW5ncjo6d29yZHMsICJxW151XSIsIG1hdGNoID0gVFJVRSkNCmBgYA0KDQpDcmVhdGUgYSByZWd1bGFyIGV4cHJlc3Npb24gdGhhdCB3aWxsIG1hdGNoIHRlbGVwaG9uZSBudW1iZXJzIGFzIGNvbW1vbmx5IHdyaXR0ZW4gaW4geW91ciBjb3VudHJ5Lg0KVXNpbmcgd2hhdCBoYXMgYmVlbiBjb3ZlcmVkIGluIFI0RFMgdGh1cyBmYXINCg0KDQpgYGB7cn0NCnggPC0gYygiMTIzLTQ1Ni03ODkwIiwgIjEyMzUtMjM1MSIpDQpzdHJfdmlldyh4LCAiXFxkXFxkXFxkLVxcZFxcZFxcZC1cXGRcXGRcXGRcXGQiKQ0KYGBgDQoNCg0KPGgzPiBSZXBldGl0aW9uIDwvaDM+DQpDb250cm9scyBob3cgbWFueSB0aW1lcyBhIHBhdHRlcm4gbWF0Y2hlcy48L2JyPg0KPyAwIG9yIDEgPC9icj4NCisgMSBvciBtb3JlPC9icj4NCiogMCBvciBtb3JlIDwvYnI+DQoNCg0KYGBge3J9DQp4IDwtICAiMTg4OCBpcyB0aGUgbG9uZ2VzdCB5ZWFyIGluIFJvbWFuIG51bWVyYWxzOiBNRENDQ0xYWFhWSUlJIg0Kc3RyX3ZpZXcoeCwgIkNDPyIpDQpgYGANCg0KYGBge3J9DQpzdHJfdmlldyh4LCAiQ0MrIikNCmBgYA0KDQpgYGB7cn0NCnN0cl92aWV3KHgsICJDQ1tMWF0rIikNCmBgYA0KDQpZb3UgY2FuIGFsc28gc3BlY2lmeSB0aGUgbnVtYmVyIG9mIG1hdGNoZXMgcHJlY2lzZWx5OiA8L2JyPg0Ke259IGV4YWN0bHkgbiB0aW1lcyA8L2JyPg0Ke24sfSBuIG9yIG1vcmUgPC9icj4NCnssbX0gYXQgbW9zdCBtIDwvYnI+DQp7bixtfSBiZXR3ZWVuIG4gYW5kIG0gPC9icj4NCg0KYGBge3J9DQpzdHJfdmlldyh4LCAiQ3syfSIpDQpgYGANCg0KYGBge3J9DQpzdHJfdmlldyh4LCAiQ3syLH0iKQ0KYGBgDQpgYGB7cn0NCnN0cl92aWV3KHgsICJDezIsM30iKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyX3ZpZXcoeCwgIkN7LDJ9IikNCmBgYA0KDQpieSBkZWZhdWx0IHJlZ2V4cCB3aWxsIGJlIGdyZWVkeS4gVGhleSB3aWxsIG1hdGNoIHRoZSBsb25nZXN0IHN0cmluZyBwb3NzaWJsZS4gTWFrZSB0aGVtIGxhenkgYnkgcHV0dGluZyBhIFs/XSBhZnRlciB0aGVtLiANCg0KYGBge3J9DQpzdHJfdmlldyh4LCAnQ3syLDN9PycpDQpgYGANCmBgYHtyfQ0Kc3RyX3ZpZXcoeCwgIkNbTFhdKz8iKQ0KYGBgDQpEZXNjcmliZSB0aGUgZXF1aXZhbGVudHMgb2YgPywgKywgKiBpbiB7bSxufSBmb3JtLiA8L2JyPg0KVGhlIGVxdWl2YWxlbnQgb2YgPyBpcyB7LDF9LCBtYXRjaGluZyBhdCBtb3N0IDEuIFRoZSBlcXVpdmFsZW50IG9mICsgaXMgezEsfSwgbWF0Y2hpbmcgMSBvciBtb3JlLiBUaGVyZSBpcyBubyBkaXJlY3QgZXF1aXZhbGVudCBvZiAqIGluIHttLG59IGZvcm0gc2luY2UgdGhlcmUgYXJlIG5vIGJvdW5kcyBvbiB0aGUgbWF0Y2hlczogaXQgY2FuIGJlIDAgdXAgdG8gaW5maW5pdHkgbWF0Y2hlcy4gPC9wPg0KDQoNCkRlc2NyaWJlIGluIHdvcmRzIHdoYXQgdGhlc2UgcmVndWxhciBleHByZXNzaW9ucyBtYXRjaDogKHJlYWQgY2FyZWZ1bGx5IHRvIHNlZSBpZiBJJ20gdXNpbmcgYSByZWd1bGFyIGV4cHJlc3Npb24gb3IgYSBzdHJpbmcgdGhhdCBkZWZpbmVzIGEgcmVndWxhciBleHByZXNzaW9uLikgPC9icj4NCg0KXi4qJDogQW55IHN0cmluZyA8L2JyPg0KIlxcey4rXFx9IjogQW55IHN0cmluZyB3aXRoIGN1cmx5IGJyYWNlcyBzdXJyb3VuZGluZyBhdCBsZWFzdCBvbmUgY2hhcmFjdGVyLiA8L2JyPg0KXGR7NH0tXGR7Mn0tXGR7Mn06IEEgZGF0ZSBpbiAiJVktJW0tJWQiIGZvcm1hdDogZm91ciBkaWdpdHMgZm9sbG93ZWQgYnkgYSBkYXNoLCBmb2xsb3dlZCBieSB0d28gZGlnaXRzIGZvbGxvd2VkIGJ5IGEgZGFzaCwgZm9sbG93ZWQgYnkgYW5vdGhlciB0d28gZGlnaXRzIGZvbGxvd2VkIGJ5IGEgZGFzaC4gPC9wPg0KDQoNCiJcXFxcezR9IjogVGhpcyByZXNvbHZlcyB0byB0aGUgcmVnZXggXFx7NH0sIHdoaWNoIGlzIGZvdXIgYmFja3NsYXNoZXMuIDwvcD4NCg0KDQoNCkNyZWF0ZSByZWd1bGFyIGV4cHJlc3Npb25zIHRvIGZpbmQgYWxsIHdvcmRzIHRoYXQ6IDwvYnI+DQpmaW5kIGFsbCB3b3JkcyBzdGFydGluZyB3aXRoIHRocmVlIGNvbnNvbmFudHMNCmBgYHtyfQ0Kc3RyX3ZpZXcod29yZHMsICdeW15hZWlvdV17M30/JywgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNCmZpbmQgdGhyZWUgb3IgbW9yZSB2b3dlbHMgaW4gYSByb3c6DQpgYGB7cn0NCnN0cl92aWV3KHdvcmRzLCAnW2FlaW91XXszLH0/JywgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNCkZpbmQgVHdvIG9yIG1vcmUgdm93ZWwtY29uc29uYW50IHBhaXJzIGluIGEgcm93Lg0KDQpgYGB7cn0NCnN0cl92aWV3KHdvcmRzLCAiKFthZWlvdV1bXmFlaW91XSl7Mix9IiwgbWF0Y2ggPSBUUlVFKQ0KYGBgDQoNCjxoMz4gR3JvdXBpbmcgYW5kIEJhY2tyZWZlcmVuY2VzIDwvaDM+DQpQYXJlbnR0aGVzZXMgaXMgYSB3YXkgdG8gZGlzYW1iaWd1YXRlIGNvbXBsZXggZXhwcmVzc2lvbnMuIFRoZXkgYWxzbyBkZWZpbmUgZ3JvdXBzIHRoYXQgeW91IGNhbiByZWZlciB0byB3aXRoIGJhY2tyZWZlcmVuY2VzICwgbGlrZSBcMSwgXDIgZXRjLiBGb3IgZXhhbXBsZSB0aGUgZm9sbG93aW5nIHJlZ3VsYXIgZXhwcmVzc2lvbiBmaW5kcyBhbGwgZnJ1aXRzIHRoYXQgaGF2ZSBhIHJlcGVhdGVkIHBhaXIgb2YgbGV0ZXJzOg0KDQpgYGB7cn0NCnN0cl92aWV3KGZydWl0LCAiKC4uKVxcMSIsIG1hdGNoID0gVFJVRSkNCmBgYA0KDQpFeGVyY2lzZXMgPC9icj4NCg0KRGVzY3JpYmUsIGluIHdvcmRzLCB3aGF0IHRoZXNlIGV4cHJlc3Npb25zIHdpbGwgbWF0Y2g6IDwvYnI+DQoNCiguKVwxXDEgOiBUaGUgc2FtZSBjaGFyYWN0ZXIgYXBlYXJpbmcgdGhyZWUgdGltZXMgaW4gYSByb3cuIEUuZy4gImFhYSIgPC9icj4NCiIoLikoLilcXDJcXDEiOiBBIHBhaXIgb2YgY2hhcmFjdGVycyBmb2xsb3dlZCBieSB0aGUgc2FtZSBwYWlyIG9mIGNoYXJhY3RlcnMgaW4gcmV2ZXJzZWQgb3JkZXIuIEUuZy4gImFiYmEiLiA8L2JyPg0KKC4uKVwxOiBBbnkgdHdvIGNoYXJhY3RlcnMgcmVwZWF0ZWQuIEUuZy4gImExYTEiLiA8L2JyPg0KIiguKS5cXDEuXFwxIjogQSBjaGFyYWN0ZXIgZm9sbG93ZWQgYnkgYW55IGNoYXJhY3RlciwgdGhlIG9yaWdpbmFsIGNoYXJhY3RlciwgYW55IG90aGVyIGNoYXJhY3RlciwgdGhlIG9yaWdpbmFsIGNoYXJhY3RlciBhZ2Fpbi4gRS5nLiAiYWJhY2EiLCAiYjhiLmIiLiA8L2JyPg0KIiguKSguKSguKS4qXFwzXFwyXFwxIiBUaHJlZSBjaGFyYWN0ZXJzIGZvbGxvd2VkIGJ5IHplcm8gb3IgbW9yZSBjaGFyYWN0ZXJzIG9mIGFueSBraW5kIGZvbGxvd2VkIGJ5IHRoZSBzYW1lICB0aHJlZSBjaGFyYWN0ZXJzIGJ1dCBpbiByZXZlcnNlIG9yZGVyLiBFLmcuICJhYmNzZ2FzZ2Rkc2FkZ3NkZ2NiYSIgb3IgImFiY2NiYSIgb3IgImFiYzFjYmEiLiA8L2JyPg0KDQoNCkNvbnN0cnVjdCByZWd1bGFyIGV4cHJlc3Npb25zIHRvIG1hdGNoIHdvcmRzIHRoYXQ6PC9icj4NCg0KU3RhcnQgYW5kIGVuZCB3aXRoIHRoZSBzYW1lIGNoYXJhY3Rlci4gQXNzdW1pbmcgdGhlIHdvcmQgaXMgbW9yZSB0aGFuIG9uZSBjaGFyYWN0ZXIgYW5kIGFsbCBzdHJpbmdzIGFyZSBjb25zaWRlcmVkIHdvcmRzLCBeKC4pLipcMSQgPC9icj4NCg0KYGBge3J9DQpzdHJfdmlldyh3b3JkcywgIl4oLikuKlxcMSQiLCBtYXRjaCA9IFRSVUUpDQpgYGANCg0KDQpDb250YWluIG9uZSBsZXR0ZXIgcmVwZWF0ZWQgaW4gYXQgbGVhc3QgdGhyZWUgcGxhY2VzIChlLmcuICJlbGV2ZW4iIGNvbnRhaW5zIHRocmVlICJlInMuKSANCg0KYGBge3J9DQpzdHJfdmlldyh3b3JkcywgIiguKS4qXFwxLipcXDEiLCBtYXRjaCA9IFRSVUUpDQoNCmBgYA0KDQoNCg0KPGgyPiBUb29scyA8L2gyPg0KDQpMZWFybiBhYm91dCBzdHJpbmdyIGZ1bmN0aW9ucyB0aGF0IGxldCB5b3UgOiA8L2JyPg0KRGV0ZXJtaW5lIHdoaWNoIHN0cmluZ3MgbWF0Y2ggYSBwYXR0ZXJuIHN0cl92aWV3LCBzdHJfdmlld19hbGwsIHN0cl9kZXRlY3QoKSBhbmQgc3RyX2NvdW50KCk8L2JyPg0KZmluZCB0aGUgcG9zaXRpb24gb2YgbWF0Y2hlcyA8L2JyPg0KRXh0cmFjdCB0aGUgY29udGVudCBvZiBtYXRjaGVzIHN0cl9leHRyYWN0KCkgPC9icj4NClJlcGxhY2VzIG1hdGNoZXMgd2l0aCBuZXcgdmFsdWVzIHN0cl9yZXBsYWNlKCkgYW5kIHN0cl9yZXBsYWNlX2FsbCgpPC9icj4NClNwbGl0IGEgc3RyaW5nIGJhc2VkIG9uIGEgbWF0Y2ggc3RyX3NwbGl0KCk8L2JyPg0KDQo8aDM+IERldGVjdCBNYXRjaGVzIHN0cl9kZXRlY3QoKSA8L2gzPg0KDQpgYGB7cn0NCnggPC0gYygiYXBwbGUiLCAiYmFuYW5hIiwgInBlYXIiKQ0Kc3RyX2RldGVjdCh4LCJlIikNCmBgYA0KDQpTaW5jZSBGYWxlcyA9IDAgYW5kIFRydWUgPSAxIHlvdSBjYW4gc3VtKCkgYW5kIG1lYW4oKSBpdDoNCg0KYGBge3J9DQojIGhvdyBtYW55IGNvbW1vbiB3b3JkcyBzdGFydCB3aXRoIHQ/DQpzdW0oc3RyX2RldGVjdCh3b3JkcywgIl50IikpDQpgYGANCg0KYGBge3J9DQojIFdoYXQgcHJvcG9ydGlvbiBvZiBjb21tb24gd29yZHMgZW5kIHdpdGggYSB2b3dlbD8NCm1lYW4oc3RyX2RldGVjdCh3b3JkcywgIlthZWlvdV0kIikpDQpgYGANCg0KQSBjb21tb24gdXNlIG9mIHN0cl9kZXRlY3QgaXMgdG8gc2VsZWN0IHRoZSBlbGVtZW50cyB0aGF0IG1hdGNoIGEgcGF0dGVybi4gWW91IGNhbiBkbyB0aGlzIHdpdGggbG9naWNhbCBzdWJzZXR0aW5nICwgb3IgdGhlIGNvbnZlbmllbnQgc3RyX3N1YnNldCgpIHdyYXBwZXIuDQoNCmBgYHtyfQ0KIyBsb2dpY2FsIHN1YnNldHRpbmcgbWV0aG9kDQp3b3Jkc1tzdHJfZGV0ZWN0KHdvcmRzLCAieCQiKV0NCmBgYA0KDQoNCmBgYHtyfQ0KIyB1c2luZyBzdHJfc3Vic2V0IG1ldGhvZA0Kc3RyX3N1YnNldCh3b3JkcywgIngkIikNCmBgYA0KDQpUeXBpY2FsbHkgeW91ciBzdHJpbmdzIHdpbGwgYmUgb25lIGNvbHVtIG9mIGEgZGF0YSBmcmFtZSwgYW5kIHlvdSdsbCB3YW50IHRvIHVzZSBmaWx0ZXIgaW5zdGVhZDoNCg0KYGBge3J9DQpkZiA8LSAgdGliYmxlKHdvcmQ9d29yZHMsIGk9c2VxX2Fsb25nKHdvcmQpKQ0KZGYgJT4lDQogIGZpbHRlcihzdHJfZGV0ZWN0KHdvcmRzLCAieCQiKSkNCmBgYA0KDQpBIHZhcmlhdGlvbiBvbiBzdHJfZGV0ZWN0KCkgaXMgc3RyX2NvdW50KCkuIHJhdGhlciB0aGFuIGEgc2ltcGxlIHllcyBvciBubywgaXQgdGVsbHMgeW91IGhvdyBtYW55IG1hdGNoZXMgdGhlcmUgYXJlIGluIGEgc3RyaW5nLiANCg0KYGBge3J9DQp4IDwtIGMoImFwcGxlIiwiYmFuYW5hIiwicGVhciIpDQpzdHJfY291bnQoeCwgImEiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBvbiB0aGUgYXZlcmFnZSwgaG93IG1hbnkgdm93ZWxzIHBlciB3b3JkPw0KbWVhbihzdHJfY291bnQod29yZHMsIlthZWlvdV0iKSkNCmBgYA0KDQpZb3UgY2FuIGFsc28gdXNlIHN0cl9jb3VudCgpIHdpdGggbXV0YXRlDQoNCmBgYHtyfQ0KZGYgJT4lDQogIG11dGF0ZSgNCiAgICB2b3dlbHMgPSBzdHJfY291bnQod29yZCwiW2FlaW91XSIpLCANCiAgICBjb25zb25hbnRzID0gc3RyX2NvdW50KHdvcmQsICJbXmFlaW91XSIpDQogICkNCmBgYA0KDQoNCk5vdGU6IG1hdGNoZXMgbmV2ZXIgb3ZlcmxhcC4gRm9yIGV4YW1wbGUsIGluICJhYmFiYWJhIiBob3cgbWFueSB0aW1lcyB3aWxsIHRoZSBwYXR0ZXJuICJhYmEiIG1hdGNoPyANCg0KYGBge3J9DQpzdHJfY291bnQoImFiYWJhYmEiLCJhYmEiKQ0Kc3RyX3ZpZXdfYWxsKCJhYmFiYWJhIiwiYWJhIikNCmBgYA0KDQoNCg0KDQpGb3IgZWFjaCBvZiB0aGUgZm9sbG93aW5nIGNoYWxsZW5nZXMsIHRyeSBzb2x2aW5nIGl0IGJ5IHVzaW5nIGJvdGggYSBzaW5nbGUgcmVndWxhciBleHByZXNzaW9uLCBhbmQgYSBjb21iaW5hdGlvbiBvZiBtdWx0aXBsZSBzdHJfZGV0ZWN0KCkgY2FsbHMuIDwvYnI+DQoNCkZpbmQgYWxsIHdvcmRzIHRoYXQgc3RhcnQgb3IgZW5kIHdpdGggeC4NCmBgYHtyfQ0Kc3RyX3ZpZXcod29yZHMsICJeeHx4JCIsIG1hdGNoID0gVFJVRSkNCndvcmRzW3N0cl9kZXRlY3Qod29yZHMsICJeeHx4JCIpXQ0Kc3RhcnRfd2l0aF94IDwtIHN0cl9kZXRlY3Qod29yZHMsICJeeCIpDQplbmRfd2l0aF94IDwtIHN0cl9kZXRlY3Qod29yZHMsICJ4JCIpDQp3b3Jkc1tzdGFydF93aXRoX3ggfCBlbmRfd2l0aF94XQ0KDQpgYGANCg0KRmluZCBhbGwgd29yZHMgdGhhdCBzdGFydCB3aXRoIGEgdm93ZWwgYW5kIGVuZCB3aXRoIGEgY29uc29uYW50Lg0KYGBge3J9DQp3b3Jkc1tzdHJfZGV0ZWN0KHdvcmRzLCJeW2FpZW91XS4qW15hZWlvdV0kIildDQpgYGANCg0KQXJlIHRoZXJlIGFueSB3b3JkcyB0aGF0IGNvbnRhaW4gYXQgbGVhc3Qgb25lIG9mIGVhY2ggZGlmZmVyZW50IHZvd2VsPw0KDQpgYGB7cn0NCiANCndvcmRzW3N0cl9kZXRlY3Qod29yZHMsICJhIikgJg0KICAgICAgICBzdHJfZGV0ZWN0KHdvcmRzLCAiZSIpICYNCiAgICAgICAgc3RyX2RldGVjdCh3b3JkcywgImkiKSAmDQogICAgICAgIHN0cl9kZXRlY3Qod29yZHMsICJvIikgJg0KICAgICAgICBzdHJfZGV0ZWN0KHdvcmRzLCAidSIpXQ0KDQoNCmBgYA0KV2hhdCB3b3JkIGhhcyB0aGUgaGlnaGVzdCBudW1iZXIgb2Ygdm93ZWxzPyBXaGF0IHdvcmQgaGFzIHRoZSBoaWdoZXN0IHByb3BvcnRpb24gb2Ygdm93ZWxzPyAoSGludDogd2hhdCBpcyB0aGUgZGVub21pbmF0b3I/KQ0KDQpgYGB7cn0NCnByb3Bfdm93ZWxzIDwtIHN0cl9jb3VudCh3b3JkcywgIlthZWlvdV0iKSAvIHN0cl9sZW5ndGgod29yZHMpDQp3b3Jkc1t3aGljaChwcm9wX3Zvd2VscyA9PSBtYXgocHJvcF92b3dlbHMpKV0NCmBgYA0KDQoNCg0KPGgyPiBFeHRyYWN0IE1hdGNoZXMgPC9oMj4NClRvIGV4dHJhY3QgdGhlIGFjdHVhbCB0ZXh0IG9mIGEgbWF0Y2gsIHVzZSBzdF9leHRyYWN0KCkNCg0KYGBge3J9DQojIHVzaW5nIGhhcnZhcmQgc2VudGVuY2VzIHdoaWNoIHdlcmUgZGVzaWduZWQgdG8gdGVzdCB2b2lwIHN5c3RlbXMNCmxlbmd0aChzZW50ZW5jZXMpDQpoZWFkKHNlbnRlbmNlcykNCmBgYA0KDQoNCkltYWdpbmUgd2Ugd2FudCB0byBmaW5kIGFsbCBzZW50ZW5jZXMgdGhhdCBjb250YWluIGEgY29sb3IuIFdlIGZpcnN0IGNyZWF0ZSBhIHZlY3RvciBvZiBjb2xvciBuYW1lcywgYW5kIHRoZW4gdHVybiBpdCBpbnRvIGEgc2luZ2xlIHJlZ3VsYXIgZXhwcmVzc2lvbi4gDQoNCmBgYHtyfQ0KbGlicmFyeShzdHJpbmdyKQ0KY29sb3JzIDwtIGMoInJlZCIsIm9yYW5nZSIsInllbGxvdyIsImdyZWVuIiwiYmx1ZSIsInB1cnBsZSIpDQpjb2xvcl9tYXRjaCA8LSAgc3RyX2MoY29sb3JzLCBjb2xsYXBzZSA9ICJ8IikNCmNvbG9yX21hdGNoDQoNCmNvbG91cl9tYXRjaDIgPC0gc3RyX2MoIlxcYigiLCBzdHJfYyhjb2xvcnMsIGNvbGxhcHNlID0gInwiKSwgIilcXGIiKQ0KY29sb3VyX21hdGNoMg0KDQoNCmBgYA0KDQpOb3cgd2UgY2FuIHNlbGVjdCB0aGUgc2VudGVuY2VzIHRoYXQgY29udGFpbiBhIGNvbG9yLCBhbmQgdGhlbiBleHRyYWN0IHRoZSBjb2xvciB0byBmaWd1cmUgb3V0IHdoaWNoIG9uZSBpdCBpczoNCg0KYGBge3J9DQpoYXNfY29sb3IgPC0gc3RyX3N1YnNldChzZW50ZW5jZXMsIGNvbG9yX21hdGNoKQ0KbWF0Y2hlcyA8LSBzdHJfZXh0cmFjdChoYXNfY29sb3IsIGNvbG9yX21hdGNoKQ0KaGVhZChtYXRjaGVzKQ0KYGBgDQoNCmBgYHtyfQ0KIyBidXQgc3RyX2V4dHJhY3Qgb25seSBleHRyYWN0cyB0aGUgZmlyc3QgbWF0Y2guDQptb3JlIDwtICBzZW50ZW5jZXNbc3RyX2NvdW50KHNlbnRlbmNlcywgY29sb3JfbWF0Y2gpID4gIDFdDQpzdHJfdmlld19hbGwobW9yZSwgY29sb3JfbWF0Y2gpDQpgYGANCg0KYGBge3J9DQojIGEgYmV0dGVyIGV4YW1wbGUgdXNpbmcgXFxiIGZvciBjb2xvdXJfbWF0Y2ggdG8gdGFrZSBvdXQgZmxpY2tlUkVEDQoNCm1vcmUyIDwtIHNlbnRlbmNlc1tzdHJfY291bnQoc2VudGVuY2VzLCBjb2xvdXJfbWF0Y2gyKSA+IDFdDQpzdHJfdmlld19hbGwobW9yZTIsIGNvbG91cl9tYXRjaDIsIG1hdGNoID0gVFJVRSkNCg0KYGBgDQoNCg0KYGBge3J9DQpzdHJfZXh0cmFjdChtb3JlLCBjb2xvcl9tYXRjaCkNCmBgYA0KDQpUbyBnZXQgQUxMIG1hdGNoZXMsIHVzZSBzdHJfZXh0cmFjdF9hbGwNCg0KYGBge3J9DQpzdHJfZXh0cmFjdF9hbGwobW9yZSwgY29sb3JfbWF0Y2gpDQpgYGANCg0KSWYgeW91IHVzZSBzaW1wbGlmeSA9IFRSVUUsIHN0cl9leHRyYWN0X2FsbCB3aWxsIHJldHVybiBhIG1hdHJpeCB3aXRoIHNob3J0IG1hdGNoZXMgZXhwYW5kZWQgdG8gdGhlIHNhbWUgbGVnbnRoIGFzIHRoZSBsb25nZXN0Lg0KDQpgYGB7cn0NCnN0cl9leHRyYWN0X2FsbChtb3JlLCBjb2xvcl9tYXRjaCwgc2ltcGxpZnk9VFJVRSkNCmBgYA0KDQpgYGB7cn0NCnggPC0gIGMoImEiLCJhIGIiLCJhIGIgYyIpDQpzdHJfZXh0cmFjdF9hbGwoeCwgIlthLXpdIiwgc2ltcGxpZnkgPSBUUlVFKQ0KDQpgYGANCg0KDQo8aDI+IEdyb3VwZWQgTWF0Y2hlcyA8L2gyPg0KDQpZb3UgY2FuIGFsc28gdXNlIHBhcmVudGhlc2VzIHRvIGV4dHJhY3QgcGFydHMgb2YgYSBjb21wbGV4IG1hdGNoLiAgRm9yIGV4YW1wbGUsIGltYWdpbmUgd2Ugd2FudCB0byBleHRyYWN0IG5vdW5zIGZyb20gdGhlIHNlbnRlbmNlcy4gQXMgYSBoZXVyaXN0aWMsIHdlJ2xsIGxvb2sgZm9yIGFueSB3b3JkIHRoYXQgY29tZXMgYWZ0ZXIgImEiIG9yICJ0aGUiIA0KRGVmaW5pbmcgYSB3b3JkIGluIGEgcmVndWxhciBleHByZXNzaW9uIGlzIGEgbGl0dGxlIHRyaWNreS4NCg0KYGBge3J9DQpub3VuIDwtICIoYXx0aGUpIChbXiBdKykiDQpoYXNfbm91biA8LSBzZW50ZW5jZXMgJT4lDQogIHN0cl9zdWJzZXQobm91bikgJT4lDQpoZWFkKDEwKQ0KDQpoYXNfbm91biAlPiUNCiAgc3RyX2V4dHJhY3Qobm91bikNCg0KYGBgDQoNCg0Kc3RyX2V4dHJhY3QoKSBnaXZlcyB1cyB0aGUgY29tcGxldGUgbWF0Y2g7IA0Kc3RyX21hdGNoKCkgZ2l2ZXMgdXMgZWFjaCBpbmRpdmlkdWFsIGNvbXBvbmVudA0KDQpgYGB7cn0NCmhhc19ub3VuICU+JQ0KICBzdHJfbWF0Y2gobm91bikNCmBgYA0KDQoNCllvdSBjYW4gYWxzbyB1c2VkIHRpZHlyOjpleHRyYWN0KCkNCg0KYGBge3J9DQp0aWJibGUoc2VudGVuY2UgPSBzZW50ZW5jZXMpICU+JQ0KICB0aWR5cjo6ZXh0cmFjdCgNCiAgICBzZW50ZW5jZSwgYygiYXJ0aWNsZSIsICJub3VuIiksIihhfHRoZSkgKFteIF0rKSIsIHJlbW92ZSA9IEZBTFNFDQogICkNCmBgYA0KDQoNCkxpa2Ugc3RyX2V4dHJhY3QsIGlmIHlvdSB3YW50IEFMTCBtYXRjaGVzIGZvciBlYWNoIHN0cmluZywgeW91J2xsIG5lZWQgc3RyX21hdGNoX2FsbCgpDQoNCkZpbmQgYWxsIHdvcmRzIHRoYXQgY29tZSBhZnRlciBhICJudW1iZXIiIGxpa2UgIm9uZSIsICJ0d28iLCAidGhyZWUiIGV0Yy4gUHVsbCBvdXQgYm90aCB0aGUgbnVtYmVyIGFuZCB0aGUgd29yZC4NCg0KDQoNCmBgYHtyfQ0KbnVtd29yZCA8LSAiKG9uZXx0d298dGhyZWV8Zm91cnxmaXZlfHNpeHxzZXZlbnxlaWdodHxuaW5lfHRlbikgKyhcXFMrKSINCnNlbnRlbmNlc1tzdHJfZGV0ZWN0KHNlbnRlbmNlcywgbnVtd29yZCldICU+JQ0KICBzdHJfZXh0cmFjdChudW13b3JkKQ0KYGBgDQoNCg0KRmluZCBhbGwgY29udHJhY3Rpb25zLiBTZXBhcmF0ZSBvdXQgdGhlIHBpZWNlcyBiZWZvcmUgYW5kIGFmdGVyIHRoZSBhcG9zdHJvcGhlLg0KYGBge3J9DQpjb250cmFjdGlvbiA8LSAiKFtBLVphLXpdKyknKFtBLVphLXpdKykiDQpzZW50ZW5jZXMgJT4lDQogIGBbYChzdHJfZGV0ZWN0KHNlbnRlbmNlcywgY29udHJhY3Rpb24pKSAlPiUNCiAgc3RyX2V4dHJhY3QoY29udHJhY3Rpb24pDQoNCmBgYA0KDQo8aDI+IFJlcGxhY2luZyBNYXRjaGVzIDwvaDI+DQoNCnN0cl9yZXBsYWNlKCkgYW5kIHN0cl9yZXBsYWNlX2FsbCgpIGFsbG93IHlvdSB0byByZXBsYWNlIG1hdGNoZXMgd2l0aCBuZXcgc3RyaW5ncy4gVGhlIHNpbXBsZXN0IHVzZSBpcyB0byByZXBsYWNlIGEgcGF0dGVybiB3aXRoIGEgZml4ZWQgc3RyaW5nOg0KDQpgYGB7cn0NCg0KeCA8LSBjKCJhcHBsZSIsInBlYXIiLCAiYmFuYW5hIikNCnN0cl9yZXBsYWNlKHgsICJbYWVpb3VdIiwgIi0iKQ0KYGBgDQoNCg0KYGBge3J9DQp4IDwtIGMoImFwcGxlIiwicGVhciIsICJiYW5hbmEiKQ0Kc3RyX3JlcGxhY2VfYWxsKHgsICJbYWVpb3VdIiwgIi0iKQ0KYGBgDQoNCllvdSBjYW4gaW5zZXJ0IGJhY2tyZWZlcmVuY2VzIChpZGVudGlmaWVkIHdoZW4geW91IHVzZSBwYXJlbnRoZXNpcykgdG8gaW5zZXJ0IGNvbXBvbmVudHMgb2YgdGhlIG1hdGNoLiBJbiB0aGUgZm9sbG93aW5nIGNvZGUsIEkgZmxpcCB0aGUgb3JkZXIgb2YgdGhlIHNlY29uZCBhbmQgdGhpcmQgd29yZHM6DQoNCmBgYHtyfQ0Kc2VudGVuY2VzICU+JQ0KICBzdHJfcmVwbGFjZSgiKFteIF0rKSAoW14gXSspIChbXiBdKykiLCAiXFwxIFxcMyBcXDIiKSAlPiUNCiAgaGVhZCg1KQ0KDQpgYGANCg0KDQo8aDI+IFNwbGl0dGluZyA8L2gyPg0KVXNlIHN0cl9zcGxpdCgpIHRvIHNwbGl0IGEgc3RyaW5nIHVwIGludG8gcGllY2VzLiBGb3IgZXhhbXBsZSwgd2UgY291bGQgc3BsaXQgc2VudGVuY2VzIGludG8gd29yZHM6DQoNCmBgYHtyfQ0Kc2VudGVuY2VzICU+JQ0KICBoZWFkKDUpICU+JQ0KICBzdHJfc3BsaXQoIiAiKQ0KYGBgDQoNCmBgYHtyfQ0Kc2VudGVuY2VzICU+JQ0KICBoZWFkKDUpICU+JQ0KICBzdHJfc3BsaXQoIiAiLCBzaW1wbGlmeSA9VFJVRSkNCg0KYGBgDQoNClNwbGl0IHVwIGEgc3RyaW5nIGxpa2UgImFwcGxlcywgcGVhcnMsIGFuZCBiYW5hbmFzIiBpbnRvIGluZGl2aWR1YWwgY29tcG9uZW50cy4NCg0KYGBge3J9DQp4IDwtIGMoImFwcGxlcywgcGVhcnMsIGFuZCBiYW5hbmFzIikNCnN0cl9zcGxpdCh4LCAiLCArKGFuZCArKT8iKVtbMV1dDQpgYGANCldoeSBpcyBpdCBiZXR0ZXIgdG8gc3BsaXQgdXAgYnkgYm91bmRhcnkoIndvcmQiKSB0aGFuICIgIj8gPC9icj4NCkFuc3dlcjogU3BsaXR0aW5nIGJ5IGJvdW5kYXJ5KCJ3b3JkIikgc3BsaXRzIG9uIHB1bmN0dWF0aW9uIGFuZCBub3QganVzdCB3aGl0ZXNwYWNlLg0KDQoNCldoYXQgZG9lcyBzcGxpdHRpbmcgd2l0aCBhbiBlbXB0eSBzdHJpbmcgKCIiKSBkbz8NCg0KYGBge3J9DQpzdHJfc3BsaXQoImFiLiBjZHxhZ3QiLCAiIilbWzFdXQ0KDQpgYGANCg0KQW5zd2VyOiBJdCBzcGxpdHMgdGhlIHN0cmluZyBpbnRvIGluZGl2aWR1YWwgY2hhcmFjdGVycy4gDQoNCjxoMz4gRmluZCBNYXRjaGVzIDwvaDM+DQoNCnN0cl9sb2NhdGUoKSBhbmQgc3RyX2xvY2F0ZV9hbGwoKSBnaXZlIHlvdSB0aGUgc3RhcnRpbmcgYW5kIGVuZGluZyBwb3NpdGlvbmcgb2YgZWFjaCBtYXRjaC4gVGhlc2UgYXJlIHBhcnRpY3VsYXJ5IHVzZWZ1bCB3aGVuIG9uIG9mIHRoZSBvdGhlciB1Zm5jdGlvbnMgZG9lcyBleGFjdGx5IHdoYXQgeW91IHdhbnQuIFlvdSBjYW4gdXNlIHN0cl9sb2NhdGUoKSB0byBmaW5kIHRoZSBtYXRjaGluZyBwYXR0ZXJuLCBhbmQgc3RyX3N1YigpIHRvIGV4dHJhY3QgYW5kL29yIG1vZGlmeSB0aGVtLiANCg0KYGBge3J9DQojIHRoZSByZWd1bGFyIGNhbGwNCnN0cl9sb2NhdGUoZnJ1aXQsICJuYW5hIikgJT4lDQogIGhlYWQoMTApDQpgYGANCg0KDQo8aDI+IE90aGVyIFR5cGVzIG9mIFBhdHRlcm4gPC9oMj4NCldoZW4geW91IHVzZSBhIHBhdHRlcm4gdGhhdCdzIGEgc3RyaW5nLCBpdCdzIGF1dG9tYXRpY2FsbHkgd3JhcHBlZCBpbnRvIGEgY2FsbCB0byByZWdleCgpDQoNCmBgYHtyfQ0KIyB0aGUgcmVndWxhciBjYWxsDQpzdHJfdmlldyhmcnVpdCwgIm5hbiIsIG1hdGNoID0gVFJVRSkNCiMgSXMgc2hvcnRoYW5kIGZvcg0Kc3RyX3ZpZXcoZnJ1aXQsIHJlZ2V4KCJuYW5hIiksIG1hdGNoID0gVFJVRSkNCmBgYA0KDQoNCllvdSBjYW4gdXNlIG90aGVyIGFyZ3VtZW50cyBvZiByZWdleCgpIHRvIGNvbnRyb2wgZGV0YWlscyBvZiB0aGUgbWF0Y2ggbGlrZTogPC9icj4NCmlnbm9yZV9jYXNlID0gVFJVRSA8L2JyPg0KbXVsdGlsaW5lID0gVFJVRSAoYWxsb3dzIHRoZSBeIGFuZCAkIHRvIG1hdGNoIHRoZSBzdGFydCBhbmQgZW5kIG9mIGVhY2ggbGluZSByYXRoZXIgdGhhbiB0aGUgc3RhcnQgYW5kIGVuZCBvZiB0aGUgY29tcGxldGUgc3RyaW5nICkgPC9icj4NCmNvbW1lbnRzID10cnVlIDwvYnI+DQpkb3RhbGwgPSBUUlVFIGFsbG93cyAuIHRvIG1hdGNoIGV2ZXJ5dGhpbmcsIGluY2x1ZGluZyBcbjwvYnI+DQoNCg0KYGBge3J9DQpiYW5hbmFzIDwtICBjKCJiYW5hbmEiLCJCYW5hbmEiLCAiQkFOQU5BIikNCnN0cl92aWV3KGJhbmFuYXMsImJhbmFuYSIpDQpgYGANCg0KYGBge3J9DQpzdHJfdmlldyhiYW5hbmFzLHJlZ2V4KCJiYW5hbmEiLCBpZ25vcmUuY2FzZSA9IFRSVUUgKSkNCmBgYA0KYGBge3J9DQojIG11bHRpbGluZSBleGFtcGxlDQp4IDwtICAiTGluZSAxXG5MaW5lIDIgXG5MaW5lIDMiDQpzdHJfZXh0cmFjdF9hbGwoeCwgIl5MaW5lIikgW1sxXV0NCnN0cl9leHRyYWN0X2FsbCh4LCByZWdleCgiXkxpbmUiLCBtdWx0aWxpbmUgPSBUUlVFKSkgW1sxXV0NCg0KYGBgDQoNCg0KYGBge3J9DQojIGNvbW1lbnRzID0gVFJVRSBleGFtcGxlDQoNCnBob25lIDwtICByZWdleCgiDQogICAgICAgICAgICAgICAgXFwoPyAgICAjIG9wdGlvbmEgb3BlbmluZyBwYXJlbnMNCiAgICAgICAgICAgICAgICAoXFxkezN9KSAjIGFyZWEgY29kZQ0KICAgICAgICAgICAgICAgIFspLSBdPyAgIyBvcHRpb25hbCBjbG9zaW5nIHBhcmVucywgZGFzaCwgb3Igc3BhY2UNCiAgICAgICAgICAgICAgICAoXFxkezN9KSAjIGFub3RoZXIgdGhyZWUgbnVtYmVycw0KICAgICAgICAgICAgICAgIFsgLV0/ICAgICMgb3B0aW9uYWwgc3BhY2Ugb3IgZGFzaA0KICAgICAgICAgICAgICAgIChcXGR7M30pICMgdGhyZWUgbW9yZSBudW1iZXJzDQogICAgICAgICAgICAgICAgIiwgY29tbWVudHMgPSBUUlVFKQ0KDQpzdHJfbWF0Y2goIjUxNC03OTEtODE0MSIsIHBob25lKQ0KDQpgYGANCg0KVGhlcmUgYXJlIHRocmVlIG90aGVyIGZ1bmN0aW9ucyB5b3UgY2FuIHVzZSBuaXN0ZWFkIG9mIHJlZ2V4KCkgPC9icj4NCmZpeGVkKCkgbWF0Y2hlcyBleGFjdGx5IHRoZSBzcGVjaWZpZWQgc2VxdWVuY2Ugb2YgYnl0ZXMuIEl0IGlnbm9yZXMgYWxsIHNwZWNpYWwgcmVndWxhciBleHByZXNzaW9ucyBhbmQgb3BlcmF0ZXMgYXQgYSB2ZXJ5IGxvdyBsZXZlbC4gVGhpcyBhbGxvd3MgeW91IHRvIGF2b2lkIGNvbXBsZXggZXNjYXBpbmcgYW5kIGNhbiBiZSBtdWNoIGZhc3RlciB0aGFuIHJlZ3VsYXIgZXhwcmVzc2lvbnMuIA0KDQpgYGB7cn0NCiMgbWljcm9iZW5jaG1hcmsgc2hvd3MgdGhhdCBpdCdzIGFib3V0IDN4IGZhc3RlciBmb3IgYSBzaW1wbGUgZXhhbXBsZQ0KIyB5b3UgbmVlZCB0byBpbnN0YWxsIG1pY3JvYmVuY2htYXJrIHBhY2thZ2UNCg0KbWljcm9iZW5jaG1hcms6Om1pY3JvYmVuY2htYXJrKA0KICBmaXhlZCA9IHN0cl9kZXRlY3Qoc2VudGVuY2VzLCBmaXhlZCgidGhlIikpLA0KICByZWdleCA9IHN0cl9kZXRlY3Qoc2VudGVuY2VzLCAidGhlIiksDQogIHRpbWVzID0gMjANCiAgKQ0KYGBgDQoNCmZpeGVkKCkgaXMgZmFzdGVyIGJ1dCBwcm9ibGVtYXRpYyB3aGVuIHRoZXJlIGFyZSBtdWx0aXBsZSB3YXlzIG9mIHJlcHJlc2VudGluZyB0aGUgc2FtZSBjaGFyYWN0ZXIuDQpVc2UgY29sbCgpIGluc3RlYWQuDQoNCmBgYHtyfQ0KYTEgPC0gICJcdTAwZTEiDQphMiA8LSAiYVx1MDMwMSINCmMoYTEsYTIpDQpgYGANCg0KYGBge3J9DQojIGJ1dCBhMSBpcyBub3QgdGhlIHNhbWUgYXMgYTINCg0KYTEgPT0gYTINCg0KYGBgDQoNCg0KQ29sbCgpIGNvbXBhcmVzIHN0cmluZ3MgdXNpbmcgc3RhbmRhcmQgY29sbGF0aW9uIHJ1bGVzLiBUaGlzIGlzIHVzZWZ1bCBmb3IgZG9pbmcgY2FzZS1pbnNlbnNpdGl2ZSBtYXRjaGluZy4gTm90ZSB0aGF0IGNvbGwoKSB0YWtlcyBhIGxvY2FsZSBwYXJhbWV0ZXIgdGhhdCBjb250cm9scyB3aGljaCBydWxlcyBhcmUgdXNlZCBmb3IgY29tcGFyaW5nIGNoYXJhY3RlcnMuDQoNCmBgYHtyfQ0Kc3RyX2RldGVjdChhMSwgZml4ZWQoYTIpKQ0Kc3RyX2RldGVjdChhMSwgY29sbChhMikpDQpgYGANCg0KQm90aCBmaXhlZCgpIGFuZCByZWdleCgpIGhhdmUgaWdub3JlX2Nhc2Ugc3RhdGVtZW50LiBidXQgb25seSBjb2xsKCkgYWxsb3dzIHlvdSB0byBpY2sgdGhlIGxvY2FsZS4gVGhleSBhbHdheXMgdXNlIHRoZSBkZWZhdWx0IGxvY2FsZS4gIFRoZSBkb3duc2lkZSBvZiBjb2xsKCkgaXMgc3BlZWQuIFlvdSBjYW4gdXNlIGJvdW5kYXJ5KCkgdG8gbWF0Y2ggYm91bmRhcmllcy4NCg0KYGBge3J9DQp4IDwtICAidGhpcyBpcyBhIHNlbnRlbmNlLiINCnN0cl92aWV3X2FsbCh4LCBib3VuZGFyeSgid29yZCIpKQ0KDQpgYGANCmBgYHtyfQ0Kc3RyX2V4dHJhY3RfYWxsKHgsIGJvdW5kYXJ5KCJ3b3JkIikpDQoNCmBgYA0KDQoNCkhvdyB3b3VsZCB5b3UgZmluZCBhbGwgc3RyaW5ncyBjb250YWluaW5nIFwgd2l0aCByZWdleCgpIHZzLiB3aXRoIGZpeGVkKCk/DQoNCmBgYHtyfQ0KDQpzdHJfc3Vic2V0KGMoImFcXGIiLCAiYWIiKSwgIlxcXFwiKQ0KIz4gWzFdICJhXFxiIg0Kc3RyX3N1YnNldChjKCJhXFxiIiwgImFiIiksIGZpeGVkKCJcXCIpKQ0KIz4gWzFdICJhXFxiIg0KDQpgYGANCg0KDQpXaGF0IGFyZSB0aGUgZml2ZSBtb3N0IGNvbW1vbiB3b3JkcyBpbiBzZW50ZW5jZXM/DQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpzdHJfZXh0cmFjdF9hbGwoc2VudGVuY2VzLCBib3VuZGFyeSgid29yZCIpKSAlPiUNCiAgdW5saXN0KCkgJT4lDQogIHN0cl90b19sb3dlcigpICU+JQ0KICB0aWJibGUoKSAlPiUNCiAgc2V0X25hbWVzKCJ3b3JkIikgJT4lDQogIGdyb3VwX2J5KHdvcmQpICU+JQ0KICBjb3VudChzb3J0ID0gVFJVRSkgJT4lDQogIGhlYWQoNSkNCg0KYGBgDQoNCjxoMz4gT3RoZXIgdXNlcyBvZiByZWd1bGFyIGV4cHJlc3Npb25zIDwvaDM+DQoNClR3byBvdGhlciB1c2VmdWwgZnVuY3Rpb25zIGluIGJhc2UgUiB0aGF0IGFsc28gdXNlIHJlZ3VsYXIgZXhwcmVzc2lvbnM6IDwvYnI+DQphcHJvcG9zKCkgc2VhcmNoZXMgYWxsb2JqZWN0cyBhdmFpbGFibGUgZnJvbXQgaGUgZ2xvYmFsIGVudmlyb25tZW50LiBUaGlzIGlzIHVzZWZ1bCBpZiB5b3UgY2FuJ3QgcXVpdGUgcmVtZW1iZXIgdGhlIG5hbWUgb2YgdGhlIGZ1bmN0aW9uOg0KDQpgYGB7cn0NCmFwcm9wb3MoInJlcGxhY2UiKQ0KDQpgYGANCmRpcigpIGxpc3RzIGFsbCB0aGUgZmlsZXMgaW4gYSBkaXJlY3RvcnkuICBUaGUgcGF0dGVybiBhcmd1bWVudCB0YWtlcyBhIHJlZ3VsYXIgZXhwcmVzc2lvbiBhbmQgb25seSByZXR1cm5zIGZpbGVubWFlcyB0aGF0IG1hdGNoIHRoZSBwYXR0ZXJuDQoNCmBgYHtyfQ0KaGVhZChkaXIocGF0dGVybiA9ICJcXC5SbWQkIikpDQpgYGANCg0KPGgyPiBTdHJpbmdpIDwvaDI+DQoNCnN0cmluZ3IgaXMgYnVpbHQgb24gdG9wIG9mIHRoZSBzdHJpbmdpIHBhY2thZ2UuIHN0aW5nciBpcyB1c2VmdWwgd2hlbiB5b3UnciBsZWFybmluZyBiZWNhdXNlIGl0IGV4cG9zZXMgYSBtaW5pbWFsIHNldCBvZiBmdW5jdGlvbnMsIHdoaWNoIGhhdmUgYmVlbiBjYXJlZnVsbHkgcGlja2VkIHRvIGhhbmRsZSB0aGUgbW9zdCBjb21tb24gc3RyaW5nIG1hbmlwdWxhdGlvbiBmdW5jdGlvbnMuIDwvcD4NCg0KU3RyaW5naSBvbiB0aGUgb3RoZXIgaGFuZCBpcyBkZXNpZ25lZCB0byBiZSBjb21wZXJlaGVuc2l2ZS4gSXQgY29udGFpbnMgYWxtb3N0IGV2ZXJ5IGZ1bmN0aW9uIHlvdSBtaWdodCBldmVyIG5lZWQuIFN0cmluZ2kgaGFzIDIzNCBmdW5jdGlvbnMgdG8gc3RyaW5ncidzIDQyLiANCjwvcD4NCg0KRmluZCB0aGUgc3RyaW5naSBmdW5jdGlvbnMgdGhhdDoNCg0KQ291bnQgdGhlIG51bWJlciBvZiB3b3Jkcy4gc3RyaV9jb3VudF93b3JkcygpDQpGaW5kIGR1cGxpY2F0ZWQgc3RyaW5ncy4gc3RyaV9kdXBsaWNhdGVkKCkNCkdlbmVyYXRlIHJhbmRvbSB0ZXh0LiBUaGVyZSBhcmUgc2V2ZXJhbCBmdW5jdGlvbnMgYmVnaW5uaW5nIHdpdGggc3RyaV9yYW5kXy4gPC9icj4NCnN0cmlfcmFuZF9saXBzdW0gZ2VuZXJhdGVzIGxvcmVtIGlwc3VtIHRleHQsIDwvYnI+DQpzdHJpX3JhbmRfc3RyaW5ncyBnZW5lcmF0ZXMgcmFuZG9tIHN0cmluZ3MsIDwvYnI+DQpzdHJpX3JhbmRfc2h1ZmZsZSByYW5kb21seSBzaHVmZmxlcyB0aGUgY29kZSBwb2ludHMgaW4gdGhlIHRleHQuPC9icj4NCg0KSG93IGRvIHlvdSBjb250cm9sIHRoZSBsYW5ndWFnZSB0aGF0IHN0cmlfc29ydCgpIHVzZXMgZm9yIHNvcnRpbmc/PC9icj4NClVzZSB0aGUgbG9jYWxlIGFyZ3VtZW50IHRvIHRoZSBvcHRzX2NvbGxhdG9yIGFyZ3VtZW50Lg==