Brief

This report aims to investigate the Swiftkey Dataset. Dataset has four language set. We will examine the English data. The English data has three files named en_US.blogs.txt, en_US.news.txt, en_US.twitter.txt. Sentences from blogs, news and tweets were gathered in the data. We will explore the files, calculate the basic statistical measurements and hopefully make you have basic ideas about the data.

Downloading The Data

data_url <- "https://d396qusza40orc.cloudfront.net/dsscapstone/dataset/Coursera-SwiftKey.zip"
download.file(data_url, destfile ="./swiftkey.zip")

unzip("./swiftkey.zip", exdir= getwd())
dir()
[1] "Coursera-SwiftKey.zip"    "de_DE"                    "en_US"                    "fi_FI"                   
[5] "Milestone Report.Rproj"   "Report Milestone.nb.html" "Report Milestone.RMD"     "ru_RU"                   

We have de_DE, en_US, fi_FI, ru_RU folders. In en_US folder we have three files: en_US.blogs.txt, en_US.news.txt, en_US.twitter.txt. We will create different folders to deploy csv files:

dir.create("blogs")
dir.create("news")
dir.create("twitter")
dir("./en_US/")
[1] "blogs"             "en_US.blogs.txt"   "en_US.news.txt"    "en_US.twitter.txt" "news"              "twitter"          

Now let’s load packages we need to use.

library(tm)
library(ggplot2)
library(readr)
library(stringi)
library(SnowballC)
library(wordcloud)
library(dplyr)
library(rbokeh)

Twitter Data

The txt files is large to work.Let’s have a look at the twitter.txt file:

file_name <- './en_US/en_US.twitter.txt' 
connection <- file(file_name)
twitter_lines <- readLines(connection)
size <- format(object.size(twitter_lines), units="Kb")
line_count <- length(twitter_lines)
total_words <- sum(stri_count_words(twitter_lines))
close(connection)
print(paste("Size of twitter.txt:",size))
[1] "Size of twitter.txt: 326645.2 Kb"
print(paste("Lines in twitter.txt:", line_count))
[1] "Lines in twitter.txt: 2360148"
print(paste("Word Counts in twitter.txt:", total_words))
[1] "Word Counts in twitter.txt: 30217823"

We have 2,360,148 lines and 30,217,823 words in file. Let’s have a look at some samples:

writeLines(sample_lines)
I stand alone. Not behind a teammate. Not with someone else to blame for my failures. For in my sport all my success is up to me.
iWillPark couldn't be more excited for the ! Even if parking at Candlestick is a headache, the success makes it well worth it!
RT : It happens. I always jump around to another script if that happens. But they say that's looked down upon
trying to be more selective with my moves.... looks like things are paying off
MAMBA ALMOST READY!!!!

Tweets can contaion extra punctuations, emojis which can cause different characters than ASCII. So we need to clean them first. After that we will show how many times a word is used in our corpus. But to find frequencies of all words we need to create VCorpus object and convert it into TermDocumentMatrix object and create a matrix with it. And finally make a data.frame of matrix so we can calculate the frequencies. This will require too much memory which any personal computer can’t handle. Because we have 2,360,148 lines and 30,217,823 words. So we will have a different approach.

Algorithm

  1. Read 5000 lines of the file.
  2. Create VCorpus object with it.
  3. Clean the corpus.
  4. Create a TermDocumentMatrix.
  5. Create a matrix.
  6. Sort the words by their frequencies.
  7. Create a data frame.
  8. Write the data frame into a csv file in twitter
  9. Clear the memory so we can read another 5000 lines and reiterate.

For the memory cleaning we need to create a function for the reading and writing. Because we can’t clear the memory inside of a loop. And we will create a function to clear the memory. We have 2,360,148 lines so we need to reiterate 472 times. 472*5000=2,360,000 and the last 148 lines will be read in outside of the loop.

We will create extra functions to clean the data first:

remove_internet_chars <- function(x){
  x <- gsub("[^ ]{1,}@[^ ]{1,}"," ",x)
  x <- gsub(" @[^ ]{1,}"," ", x)
  x <- gsub("#[^ ]{1,}"," ",x)
  x <- gsub("[^ ]{1,}://[^ ]{1,}"," ",x)
  x
}
remove_symbols <- function(x){
  x <- gsub("['??????]","'",x)
  x <- gsub("^a-z']"," ", x)
  x <- gsub("'{2,}", " '", x)
  x <- gsub("' ", " ", x)
  x <- gsub(" '"," ", x)
  x <- gsub("^'","",x)
  x <- gsub("'$","",x)
  x <- gsub("[^\x01-\x7F]+","",x)
  x
}
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072 * 10000)
corpustocsv <- function(start, end){
for(i in start:start){
cleaner()
file_name <- './en_US/en_US.twitter.txt' 
connection <- file(file_name)
twitter_lines <- read_lines(connection, skip = i*5000, n_max = 5000)

smallcorpus <- VCorpus(VectorSource(twitter_lines))

smallcorpus <- tm_map(smallcorpus, removePunctuation)
smallcorpus <- tm_map(smallcorpus, content_transformer(tolower))
smallcorpus <- tm_map(smallcorpus, removeNumbers)
smallcorpus <- tm_map(smallcorpus, removeWords, stopwords("english"))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_internet_chars))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_symbols))
smallcorpus <- tm_map(smallcorpus, stripWhitespace)
smallcorpus <- tm_map(smallcorpus, PlainTextDocument)

dtmCorpus <- TermDocumentMatrix(smallcorpus)
set.seed(100)
corpusMatrix <- as.matrix(dtmCorpus)
sortedMAtrix <- sort(rowSums(corpusMatrix), decreasing = TRUE)
dfCorpus <- data.frame(word= names(sortedMAtrix), freq = sortedMAtrix)
write.csv(dfCorpus,paste0(paste0(paste0(paste0("./en_US/twitter/tvit.",i*5000),"."),(i+1)*5000),".csv"))
  }
}

cleaner <- function(n=1) {for (i in 1:n) gc()}

for(i in 0:471){
  cleaner()
  corpustocsv(start = i, end = i+1)
}
cleaner()


#Last Part
file_name <- './en_US/en_US.twitter.txt' 
connection <- file(file_name)
twitter_lines <- read_lines(connection, skip = 2360000, n_max = 148)

smallcorpus <- VCorpus(VectorSource(twitter_lines))

smallcorpus <- tm_map(smallcorpus, removePunctuation)
smallcorpus <- tm_map(smallcorpus, content_transformer(tolower))
smallcorpus <- tm_map(smallcorpus, removeNumbers)
smallcorpus <- tm_map(smallcorpus, removeWords, stopwords("english"))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_internet_chars))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_symbols))
smallcorpus <- tm_map(smallcorpus, stripWhitespace)
smallcorpus <- tm_map(smallcorpus, PlainTextDocument)

dtmCorpus <- TermDocumentMatrix(smallcorpus)
set.seed(100)
corpusMatrix <- as.matrix(dtmCorpus)
sortedMAtrix <- sort(rowSums(corpusMatrix), decreasing = TRUE)
dfCorpus <- data.frame(word= names(sortedMAtrix), freq = sortedMAtrix)
write.csv(dfCorpus,paste0(paste0(paste0(paste0("./en_US/twitter/tvit.",2360000),"."),2360148),".csv"))
cleaner()

Now we have different csv files. Let’s look at one of the csv files.

So we have two columns: words and freq (frequencies). But keep in mind that every csv hasn’t the same words. So when we are merging these csv files we will consider it.

Merging the CSV files

Algorithm: 1. Create a list contains list of the csv files. 2. Iterate the list and read file in order. 3. Drop the row names. 4. Merge the csv data frame with previous data frame. 5. Some words can be missing in previous data frame so they will appear as NA values, convert them to 0. 6. Drop created columns because of merging.

directory <- dir("./en_US/twitter/")
csv_files <- directory[grepl(".csv",directory)]
for(i in 1:length(csv_files)){
  if(i == 1){
  df1 <- read.csv(paste0("./en_US/twitter/",csv_files[i]))
  df1 <- df1[-c(1)] 
  }
  else{
  df2 <- read.csv(paste0("./en_US/twitter/",csv_files[i]))
  df2 <- df2[-c(1)]
  df1 <- df1 %>% full_join(df2, by="word")
  df1[is.na(df1)] = 0 
  df1$freq <- df1[,c(2)]+ df1[,c(3)]
  df1 <- df1[-c(2,3)]
  }
}
df1 <- df1[order(-df1$freq),]

Now we have words and frequencies in new data frame (df1).

How many words do we have?

dim(df1)
[1] 477021      2

477021 words are gathered. But we didn’t clean the corpus for the typos and mistakenly merged words in tweets.

We can do more cleaning and create a smaller data or we can ignore the words which have less than 3 frequency.

dim(sub_df1)
[1] 73023     2

Now let’s make some plotting about our twitter data:

And let’s look at the ratio of the words in corpus:

News Data

We will have similar approach for the news data.

file_name <- './en_US/en_US.news.txt' 
connection <- file(file_name)
twitter_lines <- readLines(connection)
size <- format(object.size(twitter_lines), units="Kb")
line_count <- length(twitter_lines)
total_words <- sum(stri_count_words(twitter_lines))
close(connection)
print(paste("Size of news.txt:",size))
[1] "Size of news.txt: 20243.6 Kb"
print(paste("Lines in news.txt:", line_count))
[1] "Lines in news.txt: 77259"
print(paste("Word Counts in news.txt:", total_words))
[1] "Word Counts in news.txt: 2693899"
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072 * 10000)
corpustocsv <- function(start, end){
for(i in start:start){
cleaner()
file_name <- './en_US/en_US.news.txt' 
connection <- file(file_name)
news_line <- read_lines(connection,skip = i*5000, n_max = 5000)

smallcorpus <- VCorpus(VectorSource(news_line))

smallcorpus <- tm_map(smallcorpus, removePunctuation)
smallcorpus <- tm_map(smallcorpus, content_transformer(tolower))
smallcorpus <- tm_map(smallcorpus, removeNumbers)
smallcorpus <- tm_map(smallcorpus, removeWords, stopwords("english"))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_internet_chars))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_symbols))
smallcorpus <- tm_map(smallcorpus, stripWhitespace)
smallcorpus <- tm_map(smallcorpus, PlainTextDocument)

dtmCorpus <- TermDocumentMatrix(smallcorpus)
set.seed(100)
corpusMatrix <- as.matrix(dtmCorpus)
sortedMAtrix <- sort(rowSums(corpusMatrix), decreasing = TRUE)
dfCorpus <- data.frame(word= names(sortedMAtrix), freq = sortedMAtrix)
write.csv(dfCorpus,paste0(paste0(paste0(paste0("./en_US/news/news.",i*5000),"."),(i+1)*5000),".csv"))
  }
}

for(i in 0:14){
  cleaner()
  corpustocsv(start = i, end = i+1)
}
cleaner()

#Last Part

file_name <- './en_US/en_US.news.txt' 
connection <- file(file_name)
news_line <- read_lines(connection,skip = 75000, n_max = 2259)

smallcorpus <- VCorpus(VectorSource(news_line))

smallcorpus <- tm_map(smallcorpus, removePunctuation)
smallcorpus <- tm_map(smallcorpus, content_transformer(tolower))
smallcorpus <- tm_map(smallcorpus, removeNumbers)
smallcorpus <- tm_map(smallcorpus, removeWords, stopwords("english"))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_internet_chars))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_symbols))
smallcorpus <- tm_map(smallcorpus, stripWhitespace)
smallcorpus <- tm_map(smallcorpus, PlainTextDocument)

dtmCorpus <- TermDocumentMatrix(smallcorpus)
set.seed(100)
corpusMatrix <- as.matrix(dtmCorpus)
sortedMAtrix <- sort(rowSums(corpusMatrix), decreasing = TRUE)
dfCorpus <- data.frame(word= names(sortedMAtrix), freq = sortedMAtrix)
write.csv(dfCorpus,paste0(paste0(paste0(paste0("./en_US/news/news.",75000),"."),77259),".csv"))

Since we created the csv files for news data, we will merge them.

Have a look at the df_news data:

And word cloud of the data:

Bar chart of the top 20 words used in news data.

Blogs

file_name <- './en_US/en_US.blogs.txt' 
connection <- file(file_name)
blog_lines <- readLines(connection)
size <- format(object.size(blog_lines), units="Kb")
line_count <- length(blog_lines)
total_words <- sum(stri_count_words(blog_lines))
close(connection)
print(paste("Size of blogs.txt:",size))
[1] "Size of blogs.txt: 261483 Kb"
print(paste("Lines in blogs.txt:", line_count))
[1] "Lines in blogs.txt: 899288"
print(paste("Word Counts in blogs.txt:", total_words))
[1] "Word Counts in blogs.txt: 38154268"
Sys.setenv("VROOM_CONNECTION_SIZE" = 131072 * 10000)
corpustocsv <- function(start, end){
for(i in start:start){
cleaner()
file_name <- './en_US/en_US.blogs.txt' 
connection <- file(file_name)
news_line <- read_lines(connection,skip = i*5000, n_max = 5000)

smallcorpus <- VCorpus(VectorSource(news_line))

smallcorpus <- tm_map(smallcorpus, removePunctuation)
smallcorpus <- tm_map(smallcorpus, content_transformer(tolower))
smallcorpus <- tm_map(smallcorpus, removeNumbers)
smallcorpus <- tm_map(smallcorpus, removeWords, stopwords("english"))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_internet_chars))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_symbols))
smallcorpus <- tm_map(smallcorpus, stripWhitespace)
smallcorpus <- tm_map(smallcorpus, PlainTextDocument)

dtmCorpus <- TermDocumentMatrix(smallcorpus)
set.seed(100)
corpusMatrix <- as.matrix(dtmCorpus)
sortedMAtrix <- sort(rowSums(corpusMatrix), decreasing = TRUE)
dfCorpus <- data.frame(word= names(sortedMAtrix), freq = sortedMAtrix)
write.csv(dfCorpus,paste0(paste0(paste0(paste0("./en_US/blogs/blogs.",i*5000),"."),(i+1)*5000),".csv"))
  }
}

for(i in 0:178){
  cleaner()
  corpustocsv(start = i, end = i+1)
}
cleaner()

#Last Part

file_name <- './en_US/en_US.blogs.txt' 
connection <- file(file_name)
news_line <- read_lines(connection,skip = 895000, n_max = 4288)

smallcorpus <- VCorpus(VectorSource(news_line))

smallcorpus <- tm_map(smallcorpus, removePunctuation)
smallcorpus <- tm_map(smallcorpus, content_transformer(tolower))
smallcorpus <- tm_map(smallcorpus, removeNumbers)
smallcorpus <- tm_map(smallcorpus, removeWords, stopwords("english"))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_internet_chars))
smallcorpus <- tm_map(smallcorpus, content_transformer(remove_symbols))
smallcorpus <- tm_map(smallcorpus, stripWhitespace)
smallcorpus <- tm_map(smallcorpus, PlainTextDocument)

dtmCorpus <- TermDocumentMatrix(smallcorpus)
set.seed(100)
corpusMatrix <- as.matrix(dtmCorpus)
sortedMAtrix <- sort(rowSums(corpusMatrix), decreasing = TRUE)
dfCorpus <- data.frame(word= names(sortedMAtrix), freq = sortedMAtrix)
write.csv(dfCorpus,paste0(paste0(paste0(paste0("./en_US/blogs/blogs.",75000),"."),77259),".csv"))

Now merging the csv files for blogs:

directory <- dir("./en_US/blogs/")
csv_files <- directory[grepl(".csv",directory)]
for(i in 1:length(csv_files)){
  if(i == 1){
  df_blogs <- read.csv(paste0("./en_US/blogs/",csv_files[i]))
  df_blogs <- df_blogs[-c(1)] 
  }
  else{
  df2 <- read.csv(paste0("./en_US/blogs/",csv_files[i]))
  df2 <- df2[-c(1)]
  df_blogs <- df_blogs %>% full_join(df2, by="word")
  df_blogs[is.na(df_blogs)] = 0 
  df_blogs$freq <- df_blogs[,c(2)]+ df_blogs[,c(3)]
  df_blogs <- df_blogs[-c(2,3)]
  }
}
df_blogs <- df_blogs[order(-df_blogs$freq),]

Have a look at df_blogs:

Wordcloud of df_blogs:

And the bar chart of the most used words in blogs data.

LS0tDQp0aXRsZTogIk1pbGVzdG9uZSBSZXBvcnQgb2YgVGhlIFN3aWZ0a2V5IERhdGFzZXQiDQphdXRob3I6ICJNZWhtZXQgxLBMxLBLIg0KZGF0ZTogIjI0LzA3LzIwMjIiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyMgQnJpZWYNClRoaXMgcmVwb3J0IGFpbXMgdG8gaW52ZXN0aWdhdGUgdGhlIFN3aWZ0a2V5IERhdGFzZXQuIERhdGFzZXQgaGFzIGZvdXIgbGFuZ3VhZ2Ugc2V0LiBXZSB3aWxsIGV4YW1pbmUgdGhlIEVuZ2xpc2ggZGF0YS4gVGhlIEVuZ2xpc2ggZGF0YSBoYXMgdGhyZWUgZmlsZXMgbmFtZWQgZW5fVVMuYmxvZ3MudHh0LCBlbl9VUy5uZXdzLnR4dCwgZW5fVVMudHdpdHRlci50eHQuIFNlbnRlbmNlcyBmcm9tIGJsb2dzLCBuZXdzIGFuZCB0d2VldHMgd2VyZSBnYXRoZXJlZCBpbiB0aGUgZGF0YS4gV2Ugd2lsbCBleHBsb3JlIHRoZSBmaWxlcywgY2FsY3VsYXRlIHRoZSBiYXNpYyBzdGF0aXN0aWNhbCBtZWFzdXJlbWVudHMgYW5kIGhvcGVmdWxseSBtYWtlIHlvdSBoYXZlIGJhc2ljIGlkZWFzIGFib3V0IHRoZSBkYXRhLg0KDQojIyMgRG93bmxvYWRpbmcgVGhlIERhdGENCg0KYGBge3J9DQpkYXRhX3VybCA8LSAiaHR0cHM6Ly9kMzk2cXVzemE0MG9yYy5jbG91ZGZyb250Lm5ldC9kc3NjYXBzdG9uZS9kYXRhc2V0L0NvdXJzZXJhLVN3aWZ0S2V5LnppcCINCmRvd25sb2FkLmZpbGUoZGF0YV91cmwsIGRlc3RmaWxlID0iLi9zd2lmdGtleS56aXAiKQ0KDQp1bnppcCgiLi9zd2lmdGtleS56aXAiLCBleGRpcj0gZ2V0d2QoKSkNCmBgYA0KYGBge3J9DQpkaXIoKQ0KYGBgDQpXZSBoYXZlIGRlX0RFLCBlbl9VUywgZmlfRkksIHJ1X1JVIGZvbGRlcnMuIEluIGVuX1VTIGZvbGRlciB3ZSBoYXZlIHRocmVlIGZpbGVzOiBlbl9VUy5ibG9ncy50eHQsIGVuX1VTLm5ld3MudHh0LCBlbl9VUy50d2l0dGVyLnR4dC4gV2Ugd2lsbCBjcmVhdGUgZGlmZmVyZW50IGZvbGRlcnMgdG8gZGVwbG95IGNzdiBmaWxlczoNCmBgYHtyfQ0KZGlyLmNyZWF0ZSgiYmxvZ3MiKQ0KZGlyLmNyZWF0ZSgibmV3cyIpDQpkaXIuY3JlYXRlKCJ0d2l0dGVyIikNCmBgYA0KDQpgYGB7cn0NCmRpcigiLi9lbl9VUy8iKQ0KYGBgDQpOb3cgbGV0J3MgbG9hZCBwYWNrYWdlcyB3ZSBuZWVkIHRvIHVzZS4NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRtKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoc3RyaW5naSkNCmxpYnJhcnkoU25vd2JhbGxDKQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyYm9rZWgpDQpgYGANCg0KIyBUd2l0dGVyIERhdGENCg0KVGhlIHR4dCBmaWxlcyBpcyBsYXJnZSB0byB3b3JrLkxldCdzIGhhdmUgYSBsb29rIGF0IHRoZSB0d2l0dGVyLnR4dCBmaWxlOg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFfQ0KZmlsZV9uYW1lIDwtICcuL2VuX1VTL2VuX1VTLnR3aXR0ZXIudHh0JyANCmNvbm5lY3Rpb24gPC0gZmlsZShmaWxlX25hbWUpDQp0d2l0dGVyX2xpbmVzIDwtIHJlYWRMaW5lcyhjb25uZWN0aW9uKQ0Kc2l6ZSA8LSBmb3JtYXQob2JqZWN0LnNpemUodHdpdHRlcl9saW5lcyksIHVuaXRzPSJLYiIpDQpsaW5lX2NvdW50IDwtIGxlbmd0aCh0d2l0dGVyX2xpbmVzKQ0KdG90YWxfd29yZHMgPC0gc3VtKHN0cmlfY291bnRfd29yZHModHdpdHRlcl9saW5lcykpDQpjbG9zZShjb25uZWN0aW9uKQ0KcHJpbnQocGFzdGUoIlNpemUgb2YgdHdpdHRlci50eHQ6IixzaXplKSkNCnByaW50KHBhc3RlKCJMaW5lcyBpbiB0d2l0dGVyLnR4dDoiLCBsaW5lX2NvdW50KSkNCnByaW50KHBhc3RlKCJXb3JkIENvdW50cyBpbiB0d2l0dGVyLnR4dDoiLCB0b3RhbF93b3JkcykpDQpgYGANCldlIGhhdmUgMiwzNjAsMTQ4IGxpbmVzIGFuZCAzMCwyMTcsODIzIHdvcmRzIGluIGZpbGUuIExldCdzIGhhdmUgYSBsb29rIGF0IHNvbWUgc2FtcGxlczoNCmBgYHtyfQ0KY29ubmVjdGlvbiA8LSBmaWxlKGZpbGVfbmFtZSkNCnNhbXBsZV9saW5lcyA8LSByZWFkX2xpbmVzKGNvbm5lY3Rpb24sc2tpcCA9IDE1MjAsIG5fbWF4ID0gNSkNCndyaXRlTGluZXMoc2FtcGxlX2xpbmVzKQ0KYGBgDQpUd2VldHMgY2FuIGNvbnRhaW9uIGV4dHJhIHB1bmN0dWF0aW9ucywgZW1vamlzIHdoaWNoIGNhbiBjYXVzZSBkaWZmZXJlbnQgY2hhcmFjdGVycyB0aGFuIEFTQ0lJLiBTbyB3ZSBuZWVkIHRvIGNsZWFuIHRoZW0gZmlyc3QuIEFmdGVyIHRoYXQgd2Ugd2lsbCBzaG93IGhvdyBtYW55IHRpbWVzIGEgd29yZCBpcyB1c2VkIGluIG91ciBjb3JwdXMuIEJ1dCB0byBmaW5kIGZyZXF1ZW5jaWVzIG9mIGFsbCB3b3JkcyB3ZSBuZWVkIHRvIGNyZWF0ZSBWQ29ycHVzIG9iamVjdCBhbmQgY29udmVydCBpdCBpbnRvICBUZXJtRG9jdW1lbnRNYXRyaXggb2JqZWN0IGFuZCBjcmVhdGUgYSBtYXRyaXggd2l0aCBpdC4gQW5kIGZpbmFsbHkgbWFrZSBhIGRhdGEuZnJhbWUgb2YgbWF0cml4IHNvIHdlIGNhbiBjYWxjdWxhdGUgdGhlIGZyZXF1ZW5jaWVzLiAgVGhpcyB3aWxsIHJlcXVpcmUgdG9vIG11Y2ggbWVtb3J5IHdoaWNoIGFueSBwZXJzb25hbCBjb21wdXRlciBjYW4ndCBoYW5kbGUuIEJlY2F1c2Ugd2UgaGF2ZSAyLDM2MCwxNDggbGluZXMgYW5kIDMwLDIxNyw4MjMgd29yZHMuIFNvIHdlIHdpbGwgaGF2ZSBhIGRpZmZlcmVudCBhcHByb2FjaC4gDQoNCiMjIEFsZ29yaXRobQ0KMS4gUmVhZCA1MDAwIGxpbmVzIG9mIHRoZSBmaWxlLg0KMi4gQ3JlYXRlIFZDb3JwdXMgb2JqZWN0IHdpdGggaXQuDQozLiBDbGVhbiB0aGUgY29ycHVzLg0KNC4gQ3JlYXRlIGEgVGVybURvY3VtZW50TWF0cml4Lg0KNS4gQ3JlYXRlIGEgbWF0cml4Lg0KNi4gU29ydCB0aGUgd29yZHMgYnkgdGhlaXIgZnJlcXVlbmNpZXMuDQo3LiBDcmVhdGUgYSBkYXRhIGZyYW1lLg0KOC4gV3JpdGUgdGhlIGRhdGEgZnJhbWUgaW50byBhIGNzdiBmaWxlIGluIHR3aXR0ZXINCjkuIENsZWFyIHRoZSBtZW1vcnkgc28gd2UgY2FuIHJlYWQgYW5vdGhlciA1MDAwIGxpbmVzIGFuZCByZWl0ZXJhdGUuDQoNCkZvciB0aGUgbWVtb3J5IGNsZWFuaW5nIHdlIG5lZWQgdG8gY3JlYXRlIGEgZnVuY3Rpb24gZm9yIHRoZSByZWFkaW5nIGFuZCB3cml0aW5nLiBCZWNhdXNlIHdlIGNhbid0IGNsZWFyIHRoZSBtZW1vcnkgaW5zaWRlIG9mIGEgbG9vcC4gDQpBbmQgd2Ugd2lsbCBjcmVhdGUgYSBmdW5jdGlvbiB0byBjbGVhciB0aGUgbWVtb3J5Lg0KV2UgaGF2ZSAyLDM2MCwxNDggbGluZXMgc28gd2UgbmVlZCB0byByZWl0ZXJhdGUgNDcyIHRpbWVzLiA0NzIqNTAwMD0yLDM2MCwwMDAgYW5kIHRoZSBsYXN0IDE0OCBsaW5lcyB3aWxsIGJlIHJlYWQgaW4gb3V0c2lkZSBvZiB0aGUgbG9vcC4NCg0KV2Ugd2lsbCBjcmVhdGUgZXh0cmEgZnVuY3Rpb25zIHRvIGNsZWFuIHRoZSBkYXRhIGZpcnN0Og0KYGBge3J9DQpyZW1vdmVfaW50ZXJuZXRfY2hhcnMgPC0gZnVuY3Rpb24oeCl7DQogIHggPC0gZ3N1YigiW14gXXsxLH1AW14gXXsxLH0iLCIgIix4KQ0KICB4IDwtIGdzdWIoIiBAW14gXXsxLH0iLCIgIiwgeCkNCiAgeCA8LSBnc3ViKCIjW14gXXsxLH0iLCIgIix4KQ0KICB4IDwtIGdzdWIoIlteIF17MSx9Oi8vW14gXXsxLH0iLCIgIix4KQ0KICB4DQp9DQpyZW1vdmVfc3ltYm9scyA8LSBmdW5jdGlvbih4KXsNCiAgeCA8LSBnc3ViKCJbJz8/Pz8/P10iLCInIix4KQ0KICB4IDwtIGdzdWIoIl5hLXonXSIsIiAiLCB4KQ0KICB4IDwtIGdzdWIoIid7Mix9IiwgIiAnIiwgeCkNCiAgeCA8LSBnc3ViKCInICIsICIgIiwgeCkNCiAgeCA8LSBnc3ViKCIgJyIsIiAiLCB4KQ0KICB4IDwtIGdzdWIoIl4nIiwiIix4KQ0KICB4IDwtIGdzdWIoIickIiwiIix4KQ0KICB4IDwtIGdzdWIoIlteXHgwMS1ceDdGXSsiLCIiLHgpDQogIHgNCn0NCmBgYA0KDQpgYGB7cn0NClN5cy5zZXRlbnYoIlZST09NX0NPTk5FQ1RJT05fU0laRSIgPSAxMzEwNzIgKiAxMDAwMCkNCmNvcnB1c3RvY3N2IDwtIGZ1bmN0aW9uKHN0YXJ0LCBlbmQpew0KZm9yKGkgaW4gc3RhcnQ6c3RhcnQpew0KY2xlYW5lcigpDQpmaWxlX25hbWUgPC0gJy4vZW5fVVMvZW5fVVMudHdpdHRlci50eHQnIA0KY29ubmVjdGlvbiA8LSBmaWxlKGZpbGVfbmFtZSkNCnR3aXR0ZXJfbGluZXMgPC0gcmVhZF9saW5lcyhjb25uZWN0aW9uLCBza2lwID0gaSo1MDAwLCBuX21heCA9IDUwMDApDQoNCnNtYWxsY29ycHVzIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKHR3aXR0ZXJfbGluZXMpKQ0KDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVOdW1iZXJzKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlX2ludGVybmV0X2NoYXJzKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfc3ltYm9scykpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHN0cmlwV2hpdGVzcGFjZSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgUGxhaW5UZXh0RG9jdW1lbnQpDQoNCmR0bUNvcnB1cyA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoc21hbGxjb3JwdXMpDQpzZXQuc2VlZCgxMDApDQpjb3JwdXNNYXRyaXggPC0gYXMubWF0cml4KGR0bUNvcnB1cykNCnNvcnRlZE1BdHJpeCA8LSBzb3J0KHJvd1N1bXMoY29ycHVzTWF0cml4KSwgZGVjcmVhc2luZyA9IFRSVUUpDQpkZkNvcnB1cyA8LSBkYXRhLmZyYW1lKHdvcmQ9IG5hbWVzKHNvcnRlZE1BdHJpeCksIGZyZXEgPSBzb3J0ZWRNQXRyaXgpDQp3cml0ZS5jc3YoZGZDb3JwdXMscGFzdGUwKHBhc3RlMChwYXN0ZTAocGFzdGUwKCIuL2VuX1VTL3R3aXR0ZXIvdHZpdC4iLGkqNTAwMCksIi4iKSwoaSsxKSo1MDAwKSwiLmNzdiIpKQ0KICB9DQp9DQoNCmNsZWFuZXIgPC0gZnVuY3Rpb24obj0xKSB7Zm9yIChpIGluIDE6bikgZ2MoKX0NCg0KZm9yKGkgaW4gMDo0NzEpew0KICBjbGVhbmVyKCkNCiAgY29ycHVzdG9jc3Yoc3RhcnQgPSBpLCBlbmQgPSBpKzEpDQp9DQpjbGVhbmVyKCkNCg0KDQojTGFzdCBQYXJ0DQpmaWxlX25hbWUgPC0gJy4vZW5fVVMvZW5fVVMudHdpdHRlci50eHQnIA0KY29ubmVjdGlvbiA8LSBmaWxlKGZpbGVfbmFtZSkNCnR3aXR0ZXJfbGluZXMgPC0gcmVhZF9saW5lcyhjb25uZWN0aW9uLCBza2lwID0gMjM2MDAwMCwgbl9tYXggPSAxNDgpDQoNCnNtYWxsY29ycHVzIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKHR3aXR0ZXJfbGluZXMpKQ0KDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVOdW1iZXJzKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlX2ludGVybmV0X2NoYXJzKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfc3ltYm9scykpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHN0cmlwV2hpdGVzcGFjZSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgUGxhaW5UZXh0RG9jdW1lbnQpDQoNCmR0bUNvcnB1cyA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoc21hbGxjb3JwdXMpDQpzZXQuc2VlZCgxMDApDQpjb3JwdXNNYXRyaXggPC0gYXMubWF0cml4KGR0bUNvcnB1cykNCnNvcnRlZE1BdHJpeCA8LSBzb3J0KHJvd1N1bXMoY29ycHVzTWF0cml4KSwgZGVjcmVhc2luZyA9IFRSVUUpDQpkZkNvcnB1cyA8LSBkYXRhLmZyYW1lKHdvcmQ9IG5hbWVzKHNvcnRlZE1BdHJpeCksIGZyZXEgPSBzb3J0ZWRNQXRyaXgpDQp3cml0ZS5jc3YoZGZDb3JwdXMscGFzdGUwKHBhc3RlMChwYXN0ZTAocGFzdGUwKCIuL2VuX1VTL3R3aXR0ZXIvdHZpdC4iLDIzNjAwMDApLCIuIiksMjM2MDE0OCksIi5jc3YiKSkNCmNsZWFuZXIoKQ0KYGBgDQoNCk5vdyB3ZSBoYXZlIGRpZmZlcmVudCBjc3YgZmlsZXMuIExldCdzIGxvb2sgYXQgb25lIG9mIHRoZSBjc3YgZmlsZXMuDQoNCmBgYHtyfQ0Kc2FtcGxlX2RhdGEgPC0gcmVhZC5jc3YoIi4vZW5fVVMvdHdpdHRlci90dml0LjAuNTAwMC5jc3YiKQ0KaGVhZChzYW1wbGVfZGF0YSkNCmBgYA0KU28gd2UgaGF2ZSB0d28gY29sdW1uczogd29yZHMgYW5kIGZyZXEgKGZyZXF1ZW5jaWVzKS4gQnV0IGtlZXAgaW4gbWluZCB0aGF0IGV2ZXJ5IGNzdiBoYXNuJ3QgdGhlIHNhbWUgd29yZHMuIFNvIHdoZW4gd2UgYXJlIG1lcmdpbmcgdGhlc2UgY3N2IGZpbGVzIHdlIHdpbGwgY29uc2lkZXIgaXQuDQoNCiMjIE1lcmdpbmcgdGhlIENTViBmaWxlcw0KQWxnb3JpdGhtOg0KMS4gQ3JlYXRlIGEgbGlzdCBjb250YWlucyBsaXN0IG9mIHRoZSBjc3YgZmlsZXMuDQoyLiBJdGVyYXRlIHRoZSBsaXN0IGFuZCByZWFkIGZpbGUgaW4gb3JkZXIuDQozLiBEcm9wIHRoZSByb3cgbmFtZXMuDQo0LiBNZXJnZSB0aGUgY3N2IGRhdGEgZnJhbWUgd2l0aCBwcmV2aW91cyBkYXRhIGZyYW1lLg0KNS4gU29tZSB3b3JkcyBjYW4gYmUgbWlzc2luZyBpbiBwcmV2aW91cyBkYXRhIGZyYW1lIHNvIHRoZXkgd2lsbCBhcHBlYXIgYXMgTkEgdmFsdWVzLCBjb252ZXJ0IHRoZW0gdG8gMC4NCjYuIERyb3AgY3JlYXRlZCBjb2x1bW5zIGJlY2F1c2Ugb2YgbWVyZ2luZy4NCg0KYGBge3J9DQpkaXJlY3RvcnkgPC0gZGlyKCIuL2VuX1VTL3R3aXR0ZXIvIikNCmNzdl9maWxlcyA8LSBkaXJlY3RvcnlbZ3JlcGwoIi5jc3YiLGRpcmVjdG9yeSldDQpmb3IoaSBpbiAxOmxlbmd0aChjc3ZfZmlsZXMpKXsNCiAgaWYoaSA9PSAxKXsNCiAgZGYxIDwtIHJlYWQuY3N2KHBhc3RlMCgiLi9lbl9VUy90d2l0dGVyLyIsY3N2X2ZpbGVzW2ldKSkNCiAgZGYxIDwtIGRmMVstYygxKV0gDQogIH0NCiAgZWxzZXsNCiAgZGYyIDwtIHJlYWQuY3N2KHBhc3RlMCgiLi9lbl9VUy90d2l0dGVyLyIsY3N2X2ZpbGVzW2ldKSkNCiAgZGYyIDwtIGRmMlstYygxKV0NCiAgZGYxIDwtIGRmMSAlPiUgZnVsbF9qb2luKGRmMiwgYnk9IndvcmQiKQ0KICBkZjFbaXMubmEoZGYxKV0gPSAwIA0KICBkZjEkZnJlcSA8LSBkZjFbLGMoMildKyBkZjFbLGMoMyldDQogIGRmMSA8LSBkZjFbLWMoMiwzKV0NCiAgfQ0KfQ0KZGYxIDwtIGRmMVtvcmRlcigtZGYxJGZyZXEpLF0NCmBgYA0KDQpOb3cgd2UgaGF2ZSB3b3JkcyBhbmQgZnJlcXVlbmNpZXMgaW4gbmV3IGRhdGEgZnJhbWUgKGRmMSkuDQoNCmBgYHtyfQ0KaGVhZChkZjEsIDEwKQ0KYGBgDQpIb3cgbWFueSB3b3JkcyBkbyB3ZSBoYXZlPw0KYGBge3J9DQpkaW0oZGYxKQ0KYGBgDQo0NzcwMjEgd29yZHMgYXJlIGdhdGhlcmVkLiBCdXQgd2UgZGlkbid0IGNsZWFuIHRoZSBjb3JwdXMgZm9yIHRoZSB0eXBvcyBhbmQgbWlzdGFrZW5seSBtZXJnZWQgd29yZHMgaW4gdHdlZXRzLiANCmBgYHtyfQ0KdGFpbChkZjEsMTApDQpgYGANCldlIGNhbiBkbyBtb3JlIGNsZWFuaW5nIGFuZCBjcmVhdGUgYSBzbWFsbGVyIGRhdGEgb3Igd2UgY2FuIGlnbm9yZSB0aGUgd29yZHMgd2hpY2ggaGF2ZSBsZXNzIHRoYW4gMyBmcmVxdWVuY3kuDQoNCmBgYHtyfQ0Kc3ViX2RmMSA8LSBkZjElPiUgc3Vic2V0KGZyZXEgPiA0KQ0KZGltKHN1Yl9kZjEpDQpgYGANCk5vdyBsZXQncyBtYWtlIHNvbWUgcGxvdHRpbmcgYWJvdXQgb3VyIHR3aXR0ZXIgZGF0YToNCmBgYHtyfQ0Kd29yZGNsb3VkKHdvcmRzPWRmMSR3b3JkLCBmcmVxID1kZjEkZnJlcSwgbWluLmZyZXEgPTEsIG1heC53b3Jkcz01MCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuMzUsIGNvbG9ycz0gYnJld2VyLnBhbCg4LCJEYXJrMiIpKQ0KYGBgDQpBbmQgbGV0J3MgbG9vayBhdCB0aGUgcmF0aW8gb2YgdGhlIHdvcmRzIGluIGNvcnB1czoNCg0KYGBge3J9DQpmaWd1cmUodGl0bGUgPSJUb3AgMjAgV29yZHMgVGhhdCBIYXZlIE1vc3QgRnJlcXVlbmNpZXMgSW4gVHdpdHRlciBEYXRhIiwgbGVnZW5kX2xvY2F0aW9uID0gIk5vbmUiLCB3aWR0aCA9IDc1MCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgbHlfYmFyKGRhdGEgPSBzdWJfZGYxWzE6MjAsXSwgd29yZCwgZnJlcSwgY29sb3I9d29yZCwgaG92ZXI9RikgJT4lIHlfYXhpcyhudW1iZXJfZm9ybWF0dGVyID0gIm51bWVyYWwiKQ0KYGBgDQoNCiMgTmV3cyBEYXRhDQoNCldlIHdpbGwgaGF2ZSBzaW1pbGFyIGFwcHJvYWNoIGZvciB0aGUgbmV3cyBkYXRhLg0KYGBge3Igd2FybmluZz1GQUxTRX0NCmZpbGVfbmFtZSA8LSAnLi9lbl9VUy9lbl9VUy5uZXdzLnR4dCcgDQpjb25uZWN0aW9uIDwtIGZpbGUoZmlsZV9uYW1lKQ0KdHdpdHRlcl9saW5lcyA8LSByZWFkTGluZXMoY29ubmVjdGlvbikNCnNpemUgPC0gZm9ybWF0KG9iamVjdC5zaXplKHR3aXR0ZXJfbGluZXMpLCB1bml0cz0iS2IiKQ0KbGluZV9jb3VudCA8LSBsZW5ndGgodHdpdHRlcl9saW5lcykNCnRvdGFsX3dvcmRzIDwtIHN1bShzdHJpX2NvdW50X3dvcmRzKHR3aXR0ZXJfbGluZXMpKQ0KY2xvc2UoY29ubmVjdGlvbikNCnByaW50KHBhc3RlKCJTaXplIG9mIG5ld3MudHh0OiIsc2l6ZSkpDQpwcmludChwYXN0ZSgiTGluZXMgaW4gbmV3cy50eHQ6IiwgbGluZV9jb3VudCkpDQpwcmludChwYXN0ZSgiV29yZCBDb3VudHMgaW4gbmV3cy50eHQ6IiwgdG90YWxfd29yZHMpKQ0KYGBgDQpgYGB7cn0NClN5cy5zZXRlbnYoIlZST09NX0NPTk5FQ1RJT05fU0laRSIgPSAxMzEwNzIgKiAxMDAwMCkNCmNvcnB1c3RvY3N2IDwtIGZ1bmN0aW9uKHN0YXJ0LCBlbmQpew0KZm9yKGkgaW4gc3RhcnQ6c3RhcnQpew0KY2xlYW5lcigpDQpmaWxlX25hbWUgPC0gJy4vZW5fVVMvZW5fVVMubmV3cy50eHQnIA0KY29ubmVjdGlvbiA8LSBmaWxlKGZpbGVfbmFtZSkNCm5ld3NfbGluZSA8LSByZWFkX2xpbmVzKGNvbm5lY3Rpb24sc2tpcCA9IGkqNTAwMCwgbl9tYXggPSA1MDAwKQ0KDQpzbWFsbGNvcnB1cyA8LSBWQ29ycHVzKFZlY3RvclNvdXJjZShuZXdzX2xpbmUpKQ0KDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVOdW1iZXJzKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlX2ludGVybmV0X2NoYXJzKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfc3ltYm9scykpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHN0cmlwV2hpdGVzcGFjZSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgUGxhaW5UZXh0RG9jdW1lbnQpDQoNCmR0bUNvcnB1cyA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoc21hbGxjb3JwdXMpDQpzZXQuc2VlZCgxMDApDQpjb3JwdXNNYXRyaXggPC0gYXMubWF0cml4KGR0bUNvcnB1cykNCnNvcnRlZE1BdHJpeCA8LSBzb3J0KHJvd1N1bXMoY29ycHVzTWF0cml4KSwgZGVjcmVhc2luZyA9IFRSVUUpDQpkZkNvcnB1cyA8LSBkYXRhLmZyYW1lKHdvcmQ9IG5hbWVzKHNvcnRlZE1BdHJpeCksIGZyZXEgPSBzb3J0ZWRNQXRyaXgpDQp3cml0ZS5jc3YoZGZDb3JwdXMscGFzdGUwKHBhc3RlMChwYXN0ZTAocGFzdGUwKCIuL2VuX1VTL25ld3MvbmV3cy4iLGkqNTAwMCksIi4iKSwoaSsxKSo1MDAwKSwiLmNzdiIpKQ0KICB9DQp9DQoNCmZvcihpIGluIDA6MTQpew0KICBjbGVhbmVyKCkNCiAgY29ycHVzdG9jc3Yoc3RhcnQgPSBpLCBlbmQgPSBpKzEpDQp9DQpjbGVhbmVyKCkNCg0KI0xhc3QgUGFydA0KDQpmaWxlX25hbWUgPC0gJy4vZW5fVVMvZW5fVVMubmV3cy50eHQnIA0KY29ubmVjdGlvbiA8LSBmaWxlKGZpbGVfbmFtZSkNCm5ld3NfbGluZSA8LSByZWFkX2xpbmVzKGNvbm5lY3Rpb24sc2tpcCA9IDc1MDAwLCBuX21heCA9IDIyNTkpDQoNCnNtYWxsY29ycHVzIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKG5ld3NfbGluZSkpDQoNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZU51bWJlcnMpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfaW50ZXJuZXRfY2hhcnMpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHJlbW92ZV9zeW1ib2xzKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgc3RyaXBXaGl0ZXNwYWNlKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBQbGFpblRleHREb2N1bWVudCkNCg0KZHRtQ29ycHVzIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChzbWFsbGNvcnB1cykNCnNldC5zZWVkKDEwMCkNCmNvcnB1c01hdHJpeCA8LSBhcy5tYXRyaXgoZHRtQ29ycHVzKQ0Kc29ydGVkTUF0cml4IDwtIHNvcnQocm93U3Vtcyhjb3JwdXNNYXRyaXgpLCBkZWNyZWFzaW5nID0gVFJVRSkNCmRmQ29ycHVzIDwtIGRhdGEuZnJhbWUod29yZD0gbmFtZXMoc29ydGVkTUF0cml4KSwgZnJlcSA9IHNvcnRlZE1BdHJpeCkNCndyaXRlLmNzdihkZkNvcnB1cyxwYXN0ZTAocGFzdGUwKHBhc3RlMChwYXN0ZTAoIi4vZW5fVVMvbmV3cy9uZXdzLiIsNzUwMDApLCIuIiksNzcyNTkpLCIuY3N2IikpDQpgYGANCg0KU2luY2Ugd2UgY3JlYXRlZCB0aGUgY3N2IGZpbGVzIGZvciBuZXdzIGRhdGEsIHdlIHdpbGwgbWVyZ2UgdGhlbS4NCg0KYGBge3J9DQpkaXJlY3RvcnkgPC0gZGlyKCIuL2VuX1VTL25ld3MvIikNCmNzdl9maWxlcyA8LSBkaXJlY3RvcnlbZ3JlcGwoIi5jc3YiLGRpcmVjdG9yeSldDQpmb3IoaSBpbiAxOmxlbmd0aChjc3ZfZmlsZXMpKXsNCiAgaWYoaSA9PSAxKXsNCiAgZGZfbmV3cyA8LSByZWFkLmNzdihwYXN0ZTAoIi4vZW5fVVMvbmV3cy8iLGNzdl9maWxlc1tpXSkpDQogIGRmX25ld3MgPC0gZGZfbmV3c1stYygxKV0gDQogIH0NCiAgZWxzZXsNCiAgZGYyIDwtIHJlYWQuY3N2KHBhc3RlMCgiLi9lbl9VUy9uZXdzLyIsY3N2X2ZpbGVzW2ldKSkNCiAgZGYyIDwtIGRmMlstYygxKV0NCiAgZGZfbmV3cyA8LSBkZl9uZXdzICU+JSBmdWxsX2pvaW4oZGYyLCBieT0id29yZCIpDQogIGRmX25ld3NbaXMubmEoZGZfbmV3cyldID0gMCANCiAgZGZfbmV3cyRmcmVxIDwtIGRmX25ld3NbLGMoMildKyBkZl9uZXdzWyxjKDMpXQ0KICBkZl9uZXdzIDwtIGRmX25ld3NbLWMoMiwzKV0NCiAgfQ0KfQ0KZGZfbmV3cyA8LSBkZl9uZXdzW29yZGVyKC1kZl9uZXdzJGZyZXEpLF0NCmBgYA0KDQpIYXZlIGEgbG9vayBhdCB0aGUgZGZfbmV3cyBkYXRhOg0KYGBge3J9DQpoZWFkKGRmX25ld3MpDQpgYGANCg0KQW5kIHdvcmQgY2xvdWQgb2YgdGhlIGRhdGE6DQoNCmBgYHtyfQ0Kd29yZGNsb3VkKHdvcmRzPWRmX25ld3Mkd29yZCwgZnJlcSA9ZGZfbmV3cyRmcmVxLCBzY2FsZSA9Yyg4LC41KSwgbWluLmZyZXEgPTEsIG1heC53b3Jkcz01MCwgcmFuZG9tLm9yZGVyPUZBTFNFLCByb3QucGVyPTAuMzUsIGNvbG9ycz0gYnJld2VyLnBhbCg4LCJTZXQxIikpDQoNCmBgYA0KQmFyIGNoYXJ0IG9mIHRoZSB0b3AgMjAgd29yZHMgdXNlZCBpbiBuZXdzIGRhdGEuDQpgYGB7cn0NCmZpZ3VyZSh0aXRsZSA9IlRvcCAyMCBXb3JkcyBUaGF0IEhhdmUgTW9zdCBGcmVxdWVuY2llcyBJbiBOZXdzIERhdGEiLCBsZWdlbmRfbG9jYXRpb24gPSAiTm9uZSIsIHdpZHRoID0gNzUwLCBoZWlnaHQgPSA0MDApICU+JQ0KICBseV9iYXIoZGF0YSA9IGRmX25ld3NbMToyMCxdLCB3b3JkLCBmcmVxLCBjb2xvcj13b3JkLCBob3Zlcj1GKSAlPiUgeV9heGlzKG51bWJlcl9mb3JtYXR0ZXIgPSAibnVtZXJhbCIpDQpgYGANCiMgQmxvZ3MNCmBgYHtyfQ0KZmlsZV9uYW1lIDwtICcuL2VuX1VTL2VuX1VTLmJsb2dzLnR4dCcgDQpjb25uZWN0aW9uIDwtIGZpbGUoZmlsZV9uYW1lKQ0KYmxvZ19saW5lcyA8LSByZWFkTGluZXMoY29ubmVjdGlvbikNCnNpemUgPC0gZm9ybWF0KG9iamVjdC5zaXplKGJsb2dfbGluZXMpLCB1bml0cz0iS2IiKQ0KbGluZV9jb3VudCA8LSBsZW5ndGgoYmxvZ19saW5lcykNCnRvdGFsX3dvcmRzIDwtIHN1bShzdHJpX2NvdW50X3dvcmRzKGJsb2dfbGluZXMpKQ0KY2xvc2UoY29ubmVjdGlvbikNCnByaW50KHBhc3RlKCJTaXplIG9mIGJsb2dzLnR4dDoiLHNpemUpKQ0KcHJpbnQocGFzdGUoIkxpbmVzIGluIGJsb2dzLnR4dDoiLCBsaW5lX2NvdW50KSkNCnByaW50KHBhc3RlKCJXb3JkIENvdW50cyBpbiBibG9ncy50eHQ6IiwgdG90YWxfd29yZHMpKQ0KYGBgDQpgYGB7cn0NClN5cy5zZXRlbnYoIlZST09NX0NPTk5FQ1RJT05fU0laRSIgPSAxMzEwNzIgKiAxMDAwMCkNCmNvcnB1c3RvY3N2IDwtIGZ1bmN0aW9uKHN0YXJ0LCBlbmQpew0KZm9yKGkgaW4gc3RhcnQ6c3RhcnQpew0KY2xlYW5lcigpDQpmaWxlX25hbWUgPC0gJy4vZW5fVVMvZW5fVVMuYmxvZ3MudHh0JyANCmNvbm5lY3Rpb24gPC0gZmlsZShmaWxlX25hbWUpDQpuZXdzX2xpbmUgPC0gcmVhZF9saW5lcyhjb25uZWN0aW9uLHNraXAgPSBpKjUwMDAsIG5fbWF4ID0gNTAwMCkNCg0Kc21hbGxjb3JwdXMgPC0gVkNvcnB1cyhWZWN0b3JTb3VyY2UobmV3c19saW5lKSkNCg0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgcmVtb3ZlTnVtYmVycykNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHJlbW92ZV9pbnRlcm5ldF9jaGFycykpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIocmVtb3ZlX3N5bWJvbHMpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBzdHJpcFdoaXRlc3BhY2UpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIFBsYWluVGV4dERvY3VtZW50KQ0KDQpkdG1Db3JwdXMgPC0gVGVybURvY3VtZW50TWF0cml4KHNtYWxsY29ycHVzKQ0Kc2V0LnNlZWQoMTAwKQ0KY29ycHVzTWF0cml4IDwtIGFzLm1hdHJpeChkdG1Db3JwdXMpDQpzb3J0ZWRNQXRyaXggPC0gc29ydChyb3dTdW1zKGNvcnB1c01hdHJpeCksIGRlY3JlYXNpbmcgPSBUUlVFKQ0KZGZDb3JwdXMgPC0gZGF0YS5mcmFtZSh3b3JkPSBuYW1lcyhzb3J0ZWRNQXRyaXgpLCBmcmVxID0gc29ydGVkTUF0cml4KQ0Kd3JpdGUuY3N2KGRmQ29ycHVzLHBhc3RlMChwYXN0ZTAocGFzdGUwKHBhc3RlMCgiLi9lbl9VUy9ibG9ncy9ibG9ncy4iLGkqNTAwMCksIi4iKSwoaSsxKSo1MDAwKSwiLmNzdiIpKQ0KICB9DQp9DQoNCmZvcihpIGluIDA6MTc4KXsNCiAgY2xlYW5lcigpDQogIGNvcnB1c3RvY3N2KHN0YXJ0ID0gaSwgZW5kID0gaSsxKQ0KfQ0KY2xlYW5lcigpDQoNCiNMYXN0IFBhcnQNCg0KZmlsZV9uYW1lIDwtICcuL2VuX1VTL2VuX1VTLmJsb2dzLnR4dCcgDQpjb25uZWN0aW9uIDwtIGZpbGUoZmlsZV9uYW1lKQ0KbmV3c19saW5lIDwtIHJlYWRfbGluZXMoY29ubmVjdGlvbixza2lwID0gODk1MDAwLCBuX21heCA9IDQyODgpDQoNCnNtYWxsY29ycHVzIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKG5ld3NfbGluZSkpDQoNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZU51bWJlcnMpDQpzbWFsbGNvcnB1cyA8LSB0bV9tYXAoc21hbGxjb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihyZW1vdmVfaW50ZXJuZXRfY2hhcnMpKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHJlbW92ZV9zeW1ib2xzKSkNCnNtYWxsY29ycHVzIDwtIHRtX21hcChzbWFsbGNvcnB1cywgc3RyaXBXaGl0ZXNwYWNlKQ0Kc21hbGxjb3JwdXMgPC0gdG1fbWFwKHNtYWxsY29ycHVzLCBQbGFpblRleHREb2N1bWVudCkNCg0KZHRtQ29ycHVzIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChzbWFsbGNvcnB1cykNCnNldC5zZWVkKDEwMCkNCmNvcnB1c01hdHJpeCA8LSBhcy5tYXRyaXgoZHRtQ29ycHVzKQ0Kc29ydGVkTUF0cml4IDwtIHNvcnQocm93U3Vtcyhjb3JwdXNNYXRyaXgpLCBkZWNyZWFzaW5nID0gVFJVRSkNCmRmQ29ycHVzIDwtIGRhdGEuZnJhbWUod29yZD0gbmFtZXMoc29ydGVkTUF0cml4KSwgZnJlcSA9IHNvcnRlZE1BdHJpeCkNCndyaXRlLmNzdihkZkNvcnB1cyxwYXN0ZTAocGFzdGUwKHBhc3RlMChwYXN0ZTAoIi4vZW5fVVMvYmxvZ3MvYmxvZ3MuIiw4OTUwMDApLCIuIiksODk5Mjg4KSwiLmNzdiIpKQ0KDQpgYGANCg0KTm93IG1lcmdpbmcgdGhlIGNzdiBmaWxlcyBmb3IgYmxvZ3M6DQoNCmBgYHtyfQ0KZGlyZWN0b3J5IDwtIGRpcigiLi9lbl9VUy9ibG9ncy8iKQ0KY3N2X2ZpbGVzIDwtIGRpcmVjdG9yeVtncmVwbCgiLmNzdiIsZGlyZWN0b3J5KV0NCmZvcihpIGluIDE6bGVuZ3RoKGNzdl9maWxlcykpew0KICBpZihpID09IDEpew0KICBkZl9ibG9ncyA8LSByZWFkLmNzdihwYXN0ZTAoIi4vZW5fVVMvYmxvZ3MvIixjc3ZfZmlsZXNbaV0pKQ0KICBkZl9ibG9ncyA8LSBkZl9ibG9nc1stYygxKV0gDQogIH0NCiAgZWxzZXsNCiAgZGYyIDwtIHJlYWQuY3N2KHBhc3RlMCgiLi9lbl9VUy9ibG9ncy8iLGNzdl9maWxlc1tpXSkpDQogIGRmMiA8LSBkZjJbLWMoMSldDQogIGRmX2Jsb2dzIDwtIGRmX2Jsb2dzICU+JSBmdWxsX2pvaW4oZGYyLCBieT0id29yZCIpDQogIGRmX2Jsb2dzW2lzLm5hKGRmX2Jsb2dzKV0gPSAwIA0KICBkZl9ibG9ncyRmcmVxIDwtIGRmX2Jsb2dzWyxjKDIpXSsgZGZfYmxvZ3NbLGMoMyldDQogIGRmX2Jsb2dzIDwtIGRmX2Jsb2dzWy1jKDIsMyldDQogIH0NCn0NCmRmX2Jsb2dzIDwtIGRmX2Jsb2dzW29yZGVyKC1kZl9ibG9ncyRmcmVxKSxdDQpgYGANCg0KSGF2ZSBhIGxvb2sgYXQgZGZfYmxvZ3M6DQoNCmBgYHtyfQ0KaGVhZChkZl9ibG9ncykNCmBgYA0KDQpXb3JkY2xvdWQgb2YgZGZfYmxvZ3M6DQpgYGB7cn0NCndvcmRjbG91ZCh3b3Jkcz1kZl9ibG9ncyR3b3JkLCBmcmVxID1kZl9ibG9ncyRmcmVxLCBtaW4uZnJlcSA9MSwgbWF4LndvcmRzPTUwLCByYW5kb20ub3JkZXI9RkFMU0UsIHJvdC5wZXI9MC4zNSwgY29sb3JzPSBicmV3ZXIucGFsKDgsIlNldDIiKSkNCmBgYA0KQW5kIHRoZSBiYXIgY2hhcnQgb2YgdGhlIG1vc3QgdXNlZCB3b3JkcyBpbiBibG9ncyBkYXRhLg0KDQpgYGB7cn0NCmZpZ3VyZSh0aXRsZSA9IlRvcCAyMCBXb3JkcyBUaGF0IEhhdmUgTW9zdCBGcmVxdWVuY2llcyBJbiBCbG9ncyBEYXRhIiwgbGVnZW5kX2xvY2F0aW9uID0gIk5vbmUiLCB3aWR0aCA9IDc1MCwgaGVpZ2h0ID0gNDAwKSAlPiUNCiAgbHlfYmFyKGRhdGEgPSBkZl9ibG9nc1sxOjIwLF0sIHdvcmQsIGZyZXEsIGNvbG9yPXdvcmQsIGhvdmVyPUYpICU+JSB5X2F4aXMobnVtYmVyX2Zvcm1hdHRlciA9ICJudW1lcmFsIikNCmBgYA0KDQo=