file_name <- 'E:/Downloads/business1.json'
business<-jsonlite::stream_in(textConnection(readLines(file_name, n=300000)), flatten = TRUE)
Found 1 records...
Imported 1 records. Simplifying...
file_name <- 'E:/Downloads/review1.json'
review<-jsonlite::stream_in(textConnection(readLines(file_name, n=280984)), flatten = TRUE)
Found 1 records...
Imported 1 records. Simplifying...
##check data types
glimpse(business)
Rows: 50,000
Columns: 58
$ business_id <chr> "K0i8UwxEYFv8mqHl7jAkrg", "o7cEZ~
$ name <chr> "Any Lab Test Now Glendale", "Ca~
$ address <chr> "18205 N 51st Ave, Ste 143", "10~
$ city <chr> "AZ", "Montréal", "Scarborough",~
$ state <chr> "AZ", "QC", "ON", "OH", "OH", "N~
$ postal_code <chr> "85308", "H2Z 1J6", "M1V 4S4", "~
$ latitude <dbl> 33.6523, 45.5085, 43.8230, 41.15~
$ longitude <dbl> -112.1683, -73.5603, -79.3064, -~
$ stars <dbl> 4.0, 4.0, 3.0, 2.0, 4.5, 2.0, 4.~
$ review_count <int> 4, 7, 87, 4, 82, 74, 5, 698, 13,~
$ is_open <int> 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1,~
$ categories <chr> "Diagnostic Services, Laboratory~
$ attributes.BusinessAcceptsCreditCards <chr> "True", NA, NA, NA, "True", "Tru~
$ attributes.BikeParking <chr> NA, NA, "True", "False", "True",~
$ attributes.GoodForKids <chr> NA, NA, "True", NA, "True", NA, ~
$ attributes.BusinessParking <chr> NA, NA, "{'garage': False, 'stre~
$ attributes.RestaurantsPriceRange2 <chr> NA, NA, "1", "2", "1", NA, NA, N~
$ attributes.WiFi <chr> NA, NA, "u'no'", NA, "u'no'", NA~
$ attributes.RestaurantsAttire <chr> NA, NA, "u'casual'", NA, "u'casu~
$ attributes.RestaurantsTakeOut <chr> NA, NA, "True", NA, "True", NA, ~
$ attributes.NoiseLevel <chr> NA, NA, "u'average'", NA, "'aver~
$ attributes.RestaurantsReservations <chr> NA, NA, "False", NA, "False", NA~
$ attributes.RestaurantsGoodForGroups <chr> NA, NA, "False", NA, "False", NA~
$ attributes.HasTV <chr> NA, NA, "True", NA, "True", NA, ~
$ attributes.Alcohol <chr> NA, NA, "u'none'", NA, "'beer_an~
$ attributes.RestaurantsDelivery <chr> NA, NA, "False", NA, "False", NA~
$ attributes.OutdoorSeating <chr> NA, NA, "False", NA, "False", NA~
$ attributes.Caters <chr> NA, NA, "False", NA, "True", NA,~
$ attributes.Ambience <chr> NA, NA, "{'romantic': False, 'in~
$ attributes.RestaurantsTableService <chr> NA, NA, NA, NA, "False", NA, NA,~
$ attributes.GoodForMeal <chr> NA, NA, NA, NA, "{'dessert': Fal~
$ attributes.ByAppointmentOnly <chr> NA, NA, NA, NA, NA, "False", NA,~
$ attributes.AcceptsInsurance <chr> NA, NA, NA, NA, NA, "True", NA, ~
$ attributes.BusinessAcceptsBitcoin <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.DogsAllowed <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.HappyHour <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.WheelchairAccessible <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.DriveThru <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.GoodForDancing <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.CoatCheck <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Music <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Corkage <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.BYOBCorkage <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.HairSpecializesIn <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.BestNights <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Smoking <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.BYOB <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.AgesAllowed <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.DietaryRestrictions <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.RestaurantsCounterService <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.Open24Hours <chr> NA, NA, NA, NA, NA, NA, NA, NA, ~
$ hours.Monday <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Tuesday <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Wednesday <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Thursday <chr> "8:0-18:0", NA, "11:0-22:0", NA,~
$ hours.Friday <chr> "8:0-18:0", NA, "11:0-0:0", NA, ~
$ hours.Saturday <chr> "9:0-14:0", NA, "11:0-0:0", NA, ~
$ hours.Sunday <chr> NA, NA, "12:0-21:0", NA, NA, NA,~
### Let's examine the structure of certain columns
### We will ignore anything with "hours" or "attribute" it's the name
business %>%
select(-starts_with("hours"), -starts_with("attribute"))
### Let's count how many restaurants there are
### We will detect the term "Restaurant" using the str_detect function then retain (using "filter")only the rows that have the term restaurant.
business %>%
select(-starts_with("hours"), -starts_with("attribute")) %>%
filter(str_detect(categories, "Restaurant"))
### Let's examine rows in the Categories that have the term restaurant
### Show the full list of restaurants in the categories column
business %>%
select(categories) %>%
filter(str_detect(categories, "Restaurant"))
#*Lets separate the items in the categories list and put each item on a separate row
#we will then display the name of each restaurant and their categories in separate rows
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=str_split(categories, ",")) %>% ## Split the elements where there is a comma.This will create a list
unnest(categories)%>%## Place each list element onto a separate row
select(name, categories)
###*We would like to group and count the number of different categories associated with restaurants. Notice that there is a space before some of the categories
###This may be problematic if we try to group and count the observations.
###Let's remove all unnecessary spaces
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=str_split(categories, ",")) %>%
unnest(categories)%>%
select(name, categories) %>%
mutate(categories=str_trim(categories))%>% ## create a new variable called categories and remove whitespace from start and end of categories
mutate(categories=str_squish(categories)) ##create a new variable called categories and reduce repeated whitespace inside a string.
### *Let's count the number of different categories associated with restaurants and arrange them from most to least
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=str_split(categories, ",")) %>%
unnest(categories)%>%
select(name, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
count(categories)%>% ## count the unique values in the categories column
arrange (desc(n)) ##sort (in descending order) a data frame by the number of items is each category
###Let's see which are the most popular types of categories in each state or province
###We will filter out the words "Restaurant" and "Food" since those are likely to be common for all the establishments
business %>%
select(-starts_with("hours"), -starts_with("attribute")) %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=strsplit(categories, ",")) %>%
unnest(categories)%>%
select(name, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
filter (!categories %in% c("Restaurants", "Food")) %>% ## filter out multiple categories i.e. Restaurants and Food
count(categories)%>%
arrange(desc(n))
### Now let's answer the following questions
##1. Show the number of different categories other than Restaurants and food, in each state/province ?
business %>%
## filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=strsplit(categories, ",")) %>%
unnest(categories)%>%
select(state, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
filter (!categories %in% c("Restaurants", "Food")) %>% ##filter out multiple categories
group_by(state,categories)%>%
count(categories)%>%
arrange(state,desc(n))
##2. How many establishments are there in each state that have the word Restaurants as one of their categories?
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=strsplit(categories, ",")) %>%
unnest(categories)%>%
select(state, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
filter(categories =="Restaurants") %>%
group_by(state,categories)%>%
count(categories)%>%
arrange(desc(n))
#let's pivot the categories column and create a new wider dataset called business_wider that has categories as columns i.e dummyy variables from the categories column
business_wide<-business%>%
mutate(categories = strsplit(categories, ", ")) %>%
unnest(categories) %>%
arrange(categories) %>%
pivot_wider(names_from = categories,
names_prefix = "categories_",
names_repair = "universal",
values_from = categories,
values_fill = 0,
values_fn = length)
New names:
* `categories_& Probates` -> categories_..Probates
* `categories_3D Printing` -> categories_3D.Printing
* `categories_Acai Bowls` -> categories_Acai.Bowls
* `categories_Acne Treatment` -> categories_Acne.Treatment
* `categories_Active Life` -> categories_Active.Life
* ...
business_wide%>%
filter(categories_Restaurants==1)%>%
select(name, state )%>%
group_by(state)%>%
count(state)%>%
arrange(desc(n))
#3.How many records are there for each state/Province
business_wide%>%
select(name, state )%>%
group_by(state)%>%
count(state)%>%
arrange(desc(n))
#4. How many establishment are open
business_wide%>%
select(is_open) %>%
count(is_open)%>%
group_by(is_open)%>%
arrange(desc(n))
## 5. How many establishments are open and how many are closed in each state. Sort ascending by state and whether or not they are open
business_wide %>%
select(state, is_open) %>%
group_by(state, is_open)%>%
count(is_open)%>%
arrange(state,desc(is_open))
## 6. Show the top 10 states in terms of median star review scores. Organize them in descending order
business %>%
type_convert(cols(stars = col_double()))%>% ##convert the stars column to a double
select(state,stars) %>%
group_by(state)%>%
summarize(Stars=median(stars))%>%
arrange(desc(Stars))%>%
head(10)
##7. Show the bottom 5 states in terms of median star review scores. Also show the median number of review scores that they have received
business_wide %>%
type_convert(cols(stars = col_double(), review_count = col_integer()))%>% ##convert the stars column to a double
select(state,review_count, stars) %>%
group_by(state)%>%
summarize(Median_Stars=median(stars), Number_of_Reviews=median(review_count))%>%
arrange(Median_Stars)%>%
head(5)
##8. Show the establishments with the most number of 5 star reviews (top 5)
review %>%
filter(stars == 5) %>%
group_by(business_id) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
head(5)%>%
inner_join(business_wide)
Joining, by = "business_id"
### 9. Which 5 business appears the most number of times in the data set. Order the businesses by the number of time they appear
business_wide %>%
group_by(name)%>%
summarise(Count = n()) %>%
arrange(desc(Count))%>%
head(5)
## Let's analyze Starbucks given that it the most popular company in the dataset
### we will create a new tibble called StarbucksJoined_tbl that has all the Starbucks business data and their reviews
starbucksbusiness=business %>%
filter(name=="Starbucks")
StarbucksJoined_tbl <-tibble( inner_join(starbucksbusiness,review))
Joining, by = c("business_id", "stars")
#10. Show the number of Starbucks in each State
StarbucksJoined_tbl%>%
group_by(state)%>%
summarise(Count = n()) %>%
arrange(desc(Count))
## 11. What is the number and proportion of visitors to Yelp's site who find the reviews for Starbucks useful
StarbucksJoined_tbl %>%
group_by(useful) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
head(10)
## 11. What is the number and percentage of visitors to Yelp's site who find the reviews for Starbucks funny
StarbucksJoined_tbl %>%
group_by(funny) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
head(10)
## 11. What is the number and percentage of visitors to Yelp's site who find the reviews for Starbucks useful
StarbucksJoined_tbl %>%
group_by(cool) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
head(10)
###Let's analyze the most negative reviews and the most positive reviews. What are customers from each group saying
### to identify the most negative and positive reviews we have to 1)assign sentiment scores to each review 2)sort the reviews scores
###1) assigning review scores and store the results in a data frame called CustomerSentiment
install.packages("textdata")
install.packages("textcat")
install.packages("tidytext")
library(textdata)
library(textcat)
library(tidytext)
get_sentiments("afinn")
CustomerSentiment<-StarbucksJoined_tbl %>%
## filter(textcat(text) == "english") %>% # considering only English text. Omit this line if you want faster processing.
unnest_tokens(word, text) %>% ##Split a text column into words/tokens
anti_join(stop_words)%>% #remove stopwords
inner_join(get_sentiments("afinn"), by = "word") %>% # join the StarbucksJoined_tbl with the afinn lexicon which has 2 columns titled word and value
group_by(review_id) %>%
summarize(sentiment = mean(value),words = n())
Joining, by = "word"
###2) sort the reviews scores and display desired columns(i.e. address,city, date,sentiment,text)
#### Most Negative reviews
CustomerSentiment%>%
arrange(desc(sentiment)) %>%
top_n(-10, sentiment) %>% ## get the lower sentiment scores
inner_join(StarbucksJoined_tbl, by = "review_id") %>%
select(address,city, date,sentiment,text)
#### Most Positive reviews
CustomerSentiment%>%
arrange(desc(sentiment)) %>%
top_n(10, sentiment) %>% ## get the highest sentiment scores
inner_join(StarbucksJoined_tbl, by = "review_id") %>%
select(address,city, date,sentiment,text)
library(lubridate)
### Let's add a formatted Date Column to StarbucksJoined_tbl
## We will use Lubridate reformat the date column ( which is currently stored as chr) and create new columns to multiple columns:
### date, month,day, year,hour
StarbucksDateFormatted<-StarbucksJoined_tbl%>%
mutate(date_formatted = as_date(date),
month_formatted=month(date),
day_formatted=day(date),
year_formatted=year(date),
hour_formatted=hour(date))
### Now lets create some plots for Starbucks
###1. Show the number of reviews for Starbucks over time(years) using a line chart
StarbucksDateFormatted%>%
select(year_formatted)%>%
group_by (year_formatted)%>%
summarise(NumberofReviews = n())%>%
ggplot(aes (x=year_formatted,y=NumberofReviews)) +
geom_line()

###2. Show the number of reviews by State using a column/bar chart
StarbucksDateFormatted%>%
select(state)%>%
group_by(state)%>%
ggplot(aes (x=state)) +
geom_bar()

###3. Create a donut chart that Shows the proportion of businesses that are open vs those that are closed
business %>%
group_by(is_open) %>%
summarise(Count = n())%>%
mutate(is_open=as.factor(is_open), percentage = round(Count/sum(Count)*100,2), LabelPosition = cumsum(percentage)-.1*percentage) %>%
###plot the pie chart with geom_bar() and then convert the bar into pie with the coord_polar() function. to make a donut plot we must specify the x = 2 in aes() and add the xlim() as code
ggplot(
aes(x = 2, y = percentage, fill = is_open))+
geom_bar(stat = "identity")+
coord_polar("y" ) +
geom_text(aes(y = LabelPosition, label = paste(percentage,"%", sep = "")), col = "white") +
theme_void() +
scale_fill_brewer(palette = "Dark2")+
xlim(.2,2.5)

##4. Create a column chart that shows the median number of words used in Starbucks reviews each month
StarbucksDateFormatted %>%
mutate(NumberOfWords=str_count(text, boundary("word"))) %>% ## counts the number of words since boundary is set to word
mutate(MonthName=month(ymd(date_formatted), label = TRUE)) %>%
group_by(MonthName) %>%
summarize(NumberOfWords=median(NumberOfWords)) %>%
ggplot(aes (x=MonthName, y=NumberOfWords)) +
geom_col() +
coord_flip()

### 5. Create a chart that shows the relationship between funny and useful reviews of Starbucks.
StarbucksDateFormatted%>%
ggplot(aes(x=funny, y=useful)) +
geom_point()+
geom_smooth(method=lm)
`geom_smooth()` using formula 'y ~ x'

### 6. Create a wordcloud to show the words that are used the most frequently in reviews
library(wordcloud)
createWordCloud = function(x)
{
StarbucksDateFormatted %>%
unnest_tokens(word, text) %>%
filter(!word %in% stop_words$word, !word %in% 'starbucks') %>%
### filter(!word = 'starbucks') %>%
count(word,sort = TRUE) %>%
ungroup() %>%
head(30) %>%
with(wordcloud(word, n, max.words = 30,colors=brewer.pal(8, "Dark2")))
}
createWordCloud(review)

### 7.Sentiment Analysis : The Positive and negative words associate with Starbucks reviews
positiveWordsBarGraph <- function(SC) {
contributions <- SC %>%
unnest_tokens(word, text) %>%
count(word,sort = TRUE) %>%
ungroup() %>%
inner_join(get_sentiments("afinn"), by = "word") %>%
group_by(word) %>%
summarize(contribution = sum(value), n=n())
contributions %>%
top_n(20, abs(contribution)) %>%
mutate(word = reorder(word, contribution)) %>%
head(20) %>%
ggplot(aes(word, contribution, fill = contribution > 0)) +
geom_col(show.legend = FALSE) +
coord_flip() + theme_bw()
}
positiveWordsBarGraph(StarbucksJoined_tbl)

###8. Show all the Starbucks locations on a map
### we will use the starbucksbusiness data set that we created earlier because it does not have repeating reviews each record is unique
library(leaflet)
### this function will color each location based on the star rating it has
pal <- colorFactor(c("purple", "red", "orange", "black","blue"),
domain = unique(starbucksbusiness$stars))
### this draws the map
map <- leaflet(starbucksbusiness) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
color = ~pal(starbucksbusiness$stars),
stroke = FALSE, fillOpacity = 0.5,
lat = starbucksbusiness$latitude,
lng = starbucksbusiness$longitude,
clusterOptions = markerClusterOptions(),
popup = as.character(starbucksbusiness$address))
map
#Let's join the business and review tables so that we can create a dashboard
business_reviews <- inner_join(business,review)
Joining, by = c("business_id", "stars")
LS0tDQp0aXRsZTogIkV4cGxvcmluZyB0aGUgWWVscCBEYXRhc2V0Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQpgYGB7cn0NCmZpbGVfbmFtZSA8LSAnRTovRG93bmxvYWRzL2J1c2luZXNzMS5qc29uJw0KYnVzaW5lc3M8LWpzb25saXRlOjpzdHJlYW1faW4odGV4dENvbm5lY3Rpb24ocmVhZExpbmVzKGZpbGVfbmFtZSwgbj0zMDAwMDApKSwgZmxhdHRlbiA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQpmaWxlX25hbWUgPC0gJ0U6L0Rvd25sb2Fkcy9yZXZpZXcxLmpzb24nDQpyZXZpZXc8LWpzb25saXRlOjpzdHJlYW1faW4odGV4dENvbm5lY3Rpb24ocmVhZExpbmVzKGZpbGVfbmFtZSwgbj0yODA5ODQpKSwgZmxhdHRlbiA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQojI2NoZWNrIGRhdGEgdHlwZXMNCmdsaW1wc2UoYnVzaW5lc3MpDQpgYGANCg0KYGBge3J9DQojIyMgTGV0J3MgZXhhbWluZSB0aGUgc3RydWN0dXJlIG9mIGNlcnRhaW4gY29sdW1ucw0KIyMjIFdlIHdpbGwgaWdub3JlIGFueXRoaW5nIHdpdGggImhvdXJzIiBvciAiYXR0cmlidXRlIiBpdCdzIHRoZSBuYW1lDQpidXNpbmVzcyAlPiUNCnNlbGVjdCgtc3RhcnRzX3dpdGgoImhvdXJzIiksIC1zdGFydHNfd2l0aCgiYXR0cmlidXRlIikpDQpgYGANCg0KYGBge3J9DQojIyMgTGV0J3MgY291bnQgaG93IG1hbnkgcmVzdGF1cmFudHMgdGhlcmUgYXJlDQojIyMgV2Ugd2lsbCBkZXRlY3QgdGhlIHRlcm0gIlJlc3RhdXJhbnQiIHVzaW5nIHRoZSBzdHJfZGV0ZWN0IGZ1bmN0aW9uIHRoZW4gcmV0YWluICh1c2luZyAiZmlsdGVyIilvbmx5IHRoZSByb3dzIHRoYXQgaGF2ZSB0aGUgdGVybSByZXN0YXVyYW50Lg0KYnVzaW5lc3MgJT4lDQogc2VsZWN0KC1zdGFydHNfd2l0aCgiaG91cnMiKSwgLXN0YXJ0c193aXRoKCJhdHRyaWJ1dGUiKSkgJT4lDQogZmlsdGVyKHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIlJlc3RhdXJhbnQiKSkNCmBgYA0KYGBge3J9DQojIyMgTGV0J3MgZXhhbWluZSByb3dzIGluIHRoZSBDYXRlZ29yaWVzIHRoYXQgaGF2ZSB0aGUgdGVybSByZXN0YXVyYW50IA0KIyMjIFNob3cgdGhlIGZ1bGwgbGlzdCBvZiByZXN0YXVyYW50cyBpbiB0aGUgY2F0ZWdvcmllcyBjb2x1bW4NCmJ1c2luZXNzICU+JQ0KIHNlbGVjdChjYXRlZ29yaWVzKSAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKQ0KYGBgDQpgYGB7cn0NCiMqTGV0cyBzZXBhcmF0ZSB0aGUgaXRlbXMgaW4gdGhlIGNhdGVnb3JpZXMgbGlzdCBhbmQgcHV0IGVhY2ggaXRlbSBvbiBhIHNlcGFyYXRlIHJvdw0KI3dlIHdpbGwgdGhlbiBkaXNwbGF5IHRoZSBuYW1lIG9mIGVhY2ggcmVzdGF1cmFudCBhbmQgdGhlaXIgY2F0ZWdvcmllcyBpbiBzZXBhcmF0ZSByb3dzDQpidXNpbmVzcyAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfc3BsaXQoY2F0ZWdvcmllcywgIiwiKSkgJT4lICMjIFNwbGl0IHRoZSBlbGVtZW50cyB3aGVyZSB0aGVyZSBpcyBhIGNvbW1hLlRoaXMgd2lsbCBjcmVhdGUgYSBsaXN0DQogdW5uZXN0KGNhdGVnb3JpZXMpJT4lIyMgUGxhY2UgZWFjaCBsaXN0IGVsZW1lbnQgb250byBhIHNlcGFyYXRlIHJvdw0KIHNlbGVjdChuYW1lLCBjYXRlZ29yaWVzKQ0KYGBgDQpgYGB7cn0NCiMjIypXZSB3b3VsZCBsaWtlIHRvIGdyb3VwIGFuZCBjb3VudCB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCBjYXRlZ29yaWVzIGFzc29jaWF0ZWQgd2l0aCByZXN0YXVyYW50cy4gTm90aWNlIHRoYXQgdGhlcmUgaXMgYSBzcGFjZSBiZWZvcmUgc29tZSBvZiB0aGUgY2F0ZWdvcmllcw0KIyMjVGhpcyBtYXkgYmUgcHJvYmxlbWF0aWMgaWYgd2UgdHJ5IHRvIGdyb3VwIGFuZCBjb3VudCB0aGUgb2JzZXJ2YXRpb25zLg0KIyMjTGV0J3MgcmVtb3ZlIGFsbCB1bm5lY2Vzc2FyeSBzcGFjZXMNCmJ1c2luZXNzICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl9zcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3QobmFtZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oY2F0ZWdvcmllcykpJT4lICMjIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgY2F0ZWdvcmllcyBhbmQgcmVtb3ZlIHdoaXRlc3BhY2UgZnJvbSBzdGFydCBhbmQgZW5kIG9mIGNhdGVnb3JpZXMNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfc3F1aXNoKGNhdGVnb3JpZXMpKSAjI2NyZWF0ZSBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgY2F0ZWdvcmllcyBhbmQgcmVkdWNlIHJlcGVhdGVkIHdoaXRlc3BhY2UgaW5zaWRlIGEgc3RyaW5nLg0KYGBgDQpgYGB7cn0NCiMjIyAqTGV0J3MgY291bnQgdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgY2F0ZWdvcmllcyBhc3NvY2lhdGVkIHdpdGggcmVzdGF1cmFudHMgYW5kIGFycmFuZ2UgdGhlbSBmcm9tIG1vc3QgdG8gbGVhc3QNCmJ1c2luZXNzICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl9zcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3QobmFtZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KIGNvdW50KGNhdGVnb3JpZXMpJT4lICMjIGNvdW50IHRoZSB1bmlxdWUgdmFsdWVzIGluIHRoZSBjYXRlZ29yaWVzIGNvbHVtbg0KIGFycmFuZ2UgKGRlc2MobikpICMjc29ydCAoaW4gZGVzY2VuZGluZyBvcmRlcikgYSBkYXRhIGZyYW1lIGJ5IHRoZSBudW1iZXIgb2YgaXRlbXMgaXMgZWFjaCBjYXRlZ29yeQ0KYGBgDQoNCmBgYHtyfQ0KIyMjTGV0J3Mgc2VlIHdoaWNoIGFyZSB0aGUgbW9zdCBwb3B1bGFyIHR5cGVzIG9mIGNhdGVnb3JpZXMgaW4gZWFjaCBzdGF0ZSBvciBwcm92aW5jZQ0KIyMjV2Ugd2lsbCBmaWx0ZXIgb3V0IHRoZSB3b3JkcyAiUmVzdGF1cmFudCIgYW5kICJGb29kIiBzaW5jZSB0aG9zZSBhcmUgbGlrZWx5IHRvIGJlIGNvbW1vbiBmb3IgYWxsIHRoZSBlc3RhYmxpc2htZW50cw0KYnVzaW5lc3MgJT4lIA0KICBzZWxlY3QoLXN0YXJ0c193aXRoKCJob3VycyIpLCAtc3RhcnRzX3dpdGgoImF0dHJpYnV0ZSIpKSAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJzcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3QobmFtZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KZmlsdGVyICghY2F0ZWdvcmllcyAlaW4lIGMoIlJlc3RhdXJhbnRzIiwgIkZvb2QiKSkgJT4lICMjIGZpbHRlciBvdXQgbXVsdGlwbGUgY2F0ZWdvcmllcyBpLmUuIFJlc3RhdXJhbnRzIGFuZCBGb29kDQogY291bnQoY2F0ZWdvcmllcyklPiUNCiBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KYGBge3J9DQojIyMgTm93IGxldCdzIGFuc3dlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9ucw0KDQojIzEuIFNob3cgdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgY2F0ZWdvcmllcyBvdGhlciB0aGFuIFJlc3RhdXJhbnRzIGFuZCBmb29kLCBpbiBlYWNoIHN0YXRlL3Byb3ZpbmNlID8NCg0KDQpidXNpbmVzcyAlPiUNCiMjIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cnNwbGl0KGNhdGVnb3JpZXMsICIsIikpICU+JQ0KIHVubmVzdChjYXRlZ29yaWVzKSU+JQ0KIHNlbGVjdChzdGF0ZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KIGZpbHRlciAoIWNhdGVnb3JpZXMgJWluJSBjKCJSZXN0YXVyYW50cyIsICJGb29kIikpICU+JSAjI2ZpbHRlciBvdXQgbXVsdGlwbGUgY2F0ZWdvcmllcw0KIGdyb3VwX2J5KHN0YXRlLGNhdGVnb3JpZXMpJT4lDQogY291bnQoY2F0ZWdvcmllcyklPiUNCiBhcnJhbmdlKHN0YXRlLGRlc2MobikpDQpgYGANCmBgYHtyfQ0KIyMyLiBIb3cgbWFueSBlc3RhYmxpc2htZW50cyBhcmUgdGhlcmUgaW4gZWFjaCBzdGF0ZSB0aGF0IGhhdmUgdGhlIHdvcmQgUmVzdGF1cmFudHMgYXMgb25lIG9mIHRoZWlyIGNhdGVnb3JpZXM/DQpidXNpbmVzcyAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJzcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUNCiBzZWxlY3Qoc3RhdGUsIGNhdGVnb3JpZXMpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl90cmltKHN0cl9zcXVpc2goY2F0ZWdvcmllcykpKSAlPiUNCiBmaWx0ZXIoY2F0ZWdvcmllcyA9PSJSZXN0YXVyYW50cyIpICU+JQ0KIGdyb3VwX2J5KHN0YXRlLGNhdGVnb3JpZXMpJT4lDQogY291bnQoY2F0ZWdvcmllcyklPiUNCiBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KYGBge3J9DQoNCiNsZXQncyBwaXZvdCB0aGUgY2F0ZWdvcmllcyBjb2x1bW4gYW5kIGNyZWF0ZSBhIG5ldyB3aWRlciBkYXRhc2V0IGNhbGxlZCBidXNpbmVzc193aWRlciB0aGF0IGhhcyBjYXRlZ29yaWVzIGFzIGNvbHVtbnMgaS5lIGR1bW15eSB2YXJpYWJsZXMgZnJvbSB0aGUgY2F0ZWdvcmllcyBjb2x1bW4NCg0KYnVzaW5lc3Nfd2lkZTwtYnVzaW5lc3MlPiUNCiAgICAgICBtdXRhdGUoY2F0ZWdvcmllcyA9IHN0cnNwbGl0KGNhdGVnb3JpZXMsICIsICIpKSAlPiUNCiAgICAgdW5uZXN0KGNhdGVnb3JpZXMpICU+JSANCiAgICAgYXJyYW5nZShjYXRlZ29yaWVzKSAlPiUgIA0KICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gY2F0ZWdvcmllcywNCiAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJjYXRlZ29yaWVzXyIsDQogICAgICAgICAgICBuYW1lc19yZXBhaXIgPSAidW5pdmVyc2FsIiwgDQogICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IGNhdGVnb3JpZXMsIA0KICAgICAgICAgICAgdmFsdWVzX2ZpbGwgPSAwLCANCiAgICAgICAgICAgIHZhbHVlc19mbiA9IGxlbmd0aCkNCiAgDQpgYGANCg0KYGBge3J9DQojIzIuIEhvdyBtYW55IGVzdGFibGlzaG1lbnRzIGFyZSB0aGVyZSBpbiBlYWNoIHN0YXRlIHRoYXQgaGF2ZSB0aGUgd29yZCBSZXN0YXVyYW50cyBhcyBvbmUgb2YgdGhlaXIgY2F0ZWdvcmllcz8NCg0KYnVzaW5lc3Nfd2lkZSU+JQ0KICBmaWx0ZXIoY2F0ZWdvcmllc19SZXN0YXVyYW50cz09MSklPiUNCiAgc2VsZWN0KG5hbWUsIHN0YXRlICklPiUNCmdyb3VwX2J5KHN0YXRlKSU+JQ0KY291bnQoc3RhdGUpJT4lDQphcnJhbmdlKGRlc2MobikpDQpgYGANCmBgYHtyfQ0KIzMuSG93IG1hbnkgcmVjb3JkcyBhcmUgdGhlcmUgZm9yIGVhY2ggc3RhdGUvUHJvdmluY2UNCmJ1c2luZXNzX3dpZGUlPiUNCiAgc2VsZWN0KG5hbWUsIHN0YXRlICklPiUNCmdyb3VwX2J5KHN0YXRlKSU+JQ0KY291bnQoc3RhdGUpJT4lDQphcnJhbmdlKGRlc2MobikpDQpgYGANCmBgYHtyfQ0KIzQuIEhvdyBtYW55IGVzdGFibGlzaG1lbnQgYXJlIG9wZW4NCiAgYnVzaW5lc3Nfd2lkZSU+JQ0KICBzZWxlY3QoaXNfb3BlbikgJT4lDQogIGNvdW50KGlzX29wZW4pJT4lDQogIGdyb3VwX2J5KGlzX29wZW4pJT4lDQogIGFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KYGBge3J9DQojIyA1LiBIb3cgbWFueSBlc3RhYmxpc2htZW50cyBhcmUgb3BlbiBhbmQgaG93IG1hbnkgYXJlIGNsb3NlZCBpbiBlYWNoIHN0YXRlLiBTb3J0IGFzY2VuZGluZyBieSBzdGF0ZSBhbmQgd2hldGhlciBvciBub3QgdGhleSBhcmUgb3Blbg0KDQpidXNpbmVzc193aWRlICU+JQ0KICAgc2VsZWN0KHN0YXRlLCBpc19vcGVuKSAlPiUNCiAgZ3JvdXBfYnkoc3RhdGUsIGlzX29wZW4pJT4lDQogIGNvdW50KGlzX29wZW4pJT4lDQogIGFycmFuZ2Uoc3RhdGUsZGVzYyhpc19vcGVuKSkNCmBgYA0KYGBge3J9DQojIyA2LiBTaG93IHRoZSB0b3AgMTAgc3RhdGVzIGluIHRlcm1zIG9mIG1lZGlhbiBzdGFyIHJldmlldyBzY29yZXMuIE9yZ2FuaXplIHRoZW0gaW4gZGVzY2VuZGluZyBvcmRlcg0KYnVzaW5lc3Nfd2lkZSAlPiUNCiAgdHlwZV9jb252ZXJ0KGNvbHMoc3RhcnMgPSBjb2xfZG91YmxlKCkpKSU+JSAjI2NvbnZlcnQgdGhlIHN0YXJzIGNvbHVtbiB0byBhIGRvdWJsZQ0KICBzZWxlY3Qoc3RhdGUsc3RhcnMpICU+JQ0KICBncm91cF9ieShzdGF0ZSklPiUNCiAgc3VtbWFyaXplKFN0YXJzPW1lZGlhbihzdGFycykpJT4lDQogIGFycmFuZ2UoZGVzYyhTdGFycykpJT4lDQogIGhlYWQoMTApDQpgYGANCg0KYGBge3J9DQojIzcuIFNob3cgdGhlIGJvdHRvbSA1IHN0YXRlcyBpbiB0ZXJtcyBvZiBtZWRpYW4gc3RhciByZXZpZXcgc2NvcmVzLiBBbHNvIHNob3cgdGhlIG1lZGlhbiBudW1iZXIgb2YgcmV2aWV3IHNjb3JlcyB0aGF0IHRoZXkgaGF2ZSByZWNlaXZlZA0KYnVzaW5lc3Nfd2lkZSAlPiUNCiAgdHlwZV9jb252ZXJ0KGNvbHMoc3RhcnMgPSBjb2xfZG91YmxlKCksIHJldmlld19jb3VudCA9IGNvbF9pbnRlZ2VyKCkpKSU+JSAjI2NvbnZlcnQgdGhlIHN0YXJzIGNvbHVtbiB0byBhIGRvdWJsZQ0KICAgc2VsZWN0KHN0YXRlLHJldmlld19jb3VudCwgc3RhcnMpICU+JQ0KICBncm91cF9ieShzdGF0ZSklPiUNCiAgc3VtbWFyaXplKE1lZGlhbl9TdGFycz1tZWRpYW4oc3RhcnMpLCBOdW1iZXJfb2ZfUmV2aWV3cz1tZWRpYW4ocmV2aWV3X2NvdW50KSklPiUNCiAgYXJyYW5nZShNZWRpYW5fU3RhcnMpJT4lDQogIGhlYWQoNSkNCmBgYA0KYGBge3J9DQojIzguIFNob3cgdGhlIGVzdGFibGlzaG1lbnRzIHdpdGggdGhlIG1vc3QgbnVtYmVyIG9mIDUgc3RhciByZXZpZXdzICh0b3AgNSkNCnJldmlldyAlPiUNCiAgZmlsdGVyKHN0YXJzID09IDUpICU+JQ0KICBncm91cF9ieShidXNpbmVzc19pZCkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBoZWFkKDUpJT4lDQppbm5lcl9qb2luKGJ1c2luZXNzX3dpZGUpDQoNCmBgYA0KDQpgYGB7cn0NCiMjIyA5LiBXaGljaCA1IGJ1c2luZXNzIGFwcGVhcnMgdGhlIG1vc3QgbnVtYmVyIG9mIHRpbWVzIGluIHRoZSBkYXRhIHNldC4gT3JkZXIgdGhlIGJ1c2luZXNzZXMgYnkgdGhlIG51bWJlciBvZiB0aW1lIHRoZXkgYXBwZWFyDQpidXNpbmVzc193aWRlICU+JQ0KICBncm91cF9ieShuYW1lKSU+JQ0KICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JQ0KICBhcnJhbmdlKGRlc2MoQ291bnQpKSU+JQ0KICBoZWFkKDUpDQpgYGANCmBgYHtyfQ0KIyMgTGV0J3MgYW5hbHl6ZSBTdGFyYnVja3MgIGdpdmVuIHRoYXQgaXQgdGhlIG1vc3QgcG9wdWxhciBjb21wYW55IGluIHRoZSBkYXRhc2V0DQojIyMgd2Ugd2lsbCBjcmVhdGUgYSBuZXcgdGliYmxlIGNhbGxlZCBTdGFyYnVja3NKb2luZWRfdGJsIHRoYXQgaGFzIGFsbCB0aGUgU3RhcmJ1Y2tzIGJ1c2luZXNzIGRhdGEgYW5kIHRoZWlyIHJldmlld3MNCnN0YXJidWNrc2J1c2luZXNzPWJ1c2luZXNzICU+JQ0KICBmaWx0ZXIobmFtZT09IlN0YXJidWNrcyIpDQpTdGFyYnVja3NKb2luZWRfdGJsIDwtdGliYmxlKCBpbm5lcl9qb2luKHN0YXJidWNrc2J1c2luZXNzLHJldmlldykpDQpgYGANCmBgYHtyfQ0KIzEwLiBTaG93IHRoZSBudW1iZXIgb2YgU3RhcmJ1Y2tzIGluIGVhY2ggU3RhdGUNClN0YXJidWNrc0pvaW5lZF90YmwlPiUNCiAgZ3JvdXBfYnkoc3RhdGUpJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpDQpgYGANCg0KYGBge3J9DQojIyAxMS4gV2hhdCBpcyB0aGUgbnVtYmVyIGFuZCBwZXJjZW50YWdlICBvZiB2aXNpdG9ycyB0byBZZWxwJ3Mgc2l0ZSB3aG8gZmluZCB0aGUgcmV2aWV3cyBmb3IgU3RhcmJ1Y2tzIHVzZWZ1bCANClN0YXJidWNrc0pvaW5lZF90YmwgJT4lDQogIGdyb3VwX2J5KHVzZWZ1bCkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpKSAlPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjIDExLiBXaGF0IGlzIHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2UgIG9mIHZpc2l0b3JzIHRvIFllbHAncyBzaXRlIHdobyBmaW5kIHRoZSByZXZpZXdzIGZvciBTdGFyYnVja3MgZnVubnkgDQpTdGFyYnVja3NKb2luZWRfdGJsICU+JQ0KICBncm91cF9ieShmdW5ueSkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpKSAlPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjIDExLiBXaGF0IGlzIHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2UgIG9mIHZpc2l0b3JzIHRvIFllbHAncyBzaXRlIHdobyBmaW5kIHRoZSByZXZpZXdzIGZvciBTdGFyYnVja3MgdXNlZnVsIA0KU3RhcmJ1Y2tzSm9pbmVkX3RibCAlPiUNCiAgZ3JvdXBfYnkoY29vbCkgJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpICU+JQ0KICBtdXRhdGUoUGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpKSAlPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjI0xldCdzIGFuYWx5emUgdGhlIG1vc3QgbmVnYXRpdmUgcmV2aWV3cyBhbmQgdGhlIG1vc3QgcG9zaXRpdmUgcmV2aWV3cy4gV2hhdCBhcmUgY3VzdG9tZXJzIGZyb20gZWFjaCBncm91cCBzYXlpbmcNCiMjIyB0byBpZGVudGlmeSB0aGUgbW9zdCBuZWdhdGl2ZSBhbmQgcG9zaXRpdmUgcmV2aWV3cyB3ZSBoYXZlIHRvIDEpYXNzaWduIHNlbnRpbWVudCBzY29yZXMgdG8gZWFjaCByZXZpZXcgMilzb3J0IHRoZSByZXZpZXdzIHNjb3Jlcw0KIyMjMSkgYXNzaWduaW5nIHJldmlldyBzY29yZXMgYW5kIHN0b3JlIHRoZSByZXN1bHRzIGluIGEgZGF0YSBmcmFtZSBjYWxsZWQgQ3VzdG9tZXJTZW50aW1lbnQNCmluc3RhbGwucGFja2FnZXMoInRleHRkYXRhIikNCmluc3RhbGwucGFja2FnZXMoInRleHRjYXQiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXRleHQiKQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmxpYnJhcnkodGV4dGNhdCkNCmxpYnJhcnkodGlkeXRleHQpDQoNCmBgYA0KDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJhZmlubiIpDQpgYGANCg0KDQpgYGB7cn0NCkN1c3RvbWVyU2VudGltZW50PC1TdGFyYnVja3NKb2luZWRfdGJsICU+JQ0KICAjIyBmaWx0ZXIodGV4dGNhdCh0ZXh0KSA9PSAiZW5nbGlzaCIpICU+JSAjIGNvbnNpZGVyaW5nIG9ubHkgRW5nbGlzaCB0ZXh0LiBPbWl0IHRoaXMgbGluZSBpZiB5b3Ugd2FudCBmYXN0ZXIgcHJvY2Vzc2luZy4NCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgICMjU3BsaXQgYSB0ZXh0IGNvbHVtbiBpbnRvIHdvcmRzL3Rva2Vucw0KICAgYW50aV9qb2luKHN0b3Bfd29yZHMpJT4lICAjcmVtb3ZlIHN0b3B3b3Jkcw0KICAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSwgYnkgPSAid29yZCIpICU+JSAjIGpvaW4gdGhlIFN0YXJidWNrc0pvaW5lZF90Ymwgd2l0aCB0aGUgYWZpbm4gbGV4aWNvbiB3aGljaCBoYXMgMiBjb2x1bW5zIHRpdGxlZCB3b3JkIGFuZCB2YWx1ZSANCiAgIGdyb3VwX2J5KHJldmlld19pZCkgJT4lDQogIHN1bW1hcml6ZShzZW50aW1lbnQgPSBtZWFuKHZhbHVlKSx3b3JkcyA9IG4oKSkgJT4lDQogIGZpbHRlcih3b3JkcyA+PSA1KSAjIHdlIHdpbGwgZXhjbHVkZSByZXZpZXdzIHdpdGggbGVzcyB0aGFuIDUgd29yZHMNCmBgYA0KYGBge3J9DQojIyMyKSBzb3J0IHRoZSByZXZpZXdzIHNjb3JlcyBhbmQgZGlzcGxheSBkZXNpcmVkIGNvbHVtbnMoaS5lLiBhZGRyZXNzLGNpdHksIGRhdGUsc2VudGltZW50LHRleHQpDQojIyMjIE1vc3QgTmVnYXRpdmUgcmV2aWV3cw0KQ3VzdG9tZXJTZW50aW1lbnQlPiUNCiAgYXJyYW5nZShkZXNjKHNlbnRpbWVudCkpICU+JQ0KICB0b3BfbigtMTAsIHNlbnRpbWVudCkgJT4lICAjIyBnZXQgdGhlIGxvd2VyIHNlbnRpbWVudCBzY29yZXMNCiAgaW5uZXJfam9pbihTdGFyYnVja3NKb2luZWRfdGJsLCBieSA9ICJyZXZpZXdfaWQiKSAlPiUNCiAgc2VsZWN0KGFkZHJlc3MsY2l0eSwgZGF0ZSxzZW50aW1lbnQsdGV4dCkNCmBgYA0KDQpgYGB7cn0NCiMjIyMgTW9zdCBQb3NpdGl2ZSByZXZpZXdzDQpDdXN0b21lclNlbnRpbWVudCU+JQ0KICBhcnJhbmdlKGRlc2Moc2VudGltZW50KSkgJT4lDQogIHRvcF9uKDEwLCBzZW50aW1lbnQpICU+JSAjIyBnZXQgdGhlIGhpZ2hlc3Qgc2VudGltZW50IHNjb3Jlcw0KICBpbm5lcl9qb2luKFN0YXJidWNrc0pvaW5lZF90YmwsIGJ5ID0gInJldmlld19pZCIpICU+JQ0KICBzZWxlY3QoYWRkcmVzcyxjaXR5LCBkYXRlLHNlbnRpbWVudCx0ZXh0KQ0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShsdWJyaWRhdGUpDQpgYGANCg0KYGBge3J9DQojIyMgTGV0J3MgYWRkIGEgZm9ybWF0dGVkIERhdGUgQ29sdW1uIHRvIFN0YXJidWNrc0pvaW5lZF90YmwNCiMjIFdlIHdpbGwgdXNlIEx1YnJpZGF0ZSByZWZvcm1hdCB0aGUgZGF0ZSBjb2x1bW4gKCB3aGljaCBpcyBjdXJyZW50bHkgc3RvcmVkIGFzIGNocikgYW5kIGNyZWF0ZSBuZXcgY29sdW1ucyB0byBtdWx0aXBsZSBjb2x1bW5zOg0KIyMjIGRhdGUsIG1vbnRoLGRheSwgeWVhcixob3VyDQpTdGFyYnVja3NEYXRlRm9ybWF0dGVkPC1TdGFyYnVja3NKb2luZWRfdGJsJT4lDQogbXV0YXRlKGRhdGVfZm9ybWF0dGVkID0gYXNfZGF0ZShkYXRlKSwNCiBtb250aF9mb3JtYXR0ZWQ9bW9udGgoZGF0ZSksDQogZGF5X2Zvcm1hdHRlZD1kYXkoZGF0ZSksDQogeWVhcl9mb3JtYXR0ZWQ9eWVhcihkYXRlKSwNCiBob3VyX2Zvcm1hdHRlZD1ob3VyKGRhdGUpKQ0KYGBgDQoNCmBgYHtyfQ0KIyMjIE5vdyBsZXRzIGNyZWF0ZSBzb21lIHBsb3RzIGZvciBTdGFyYnVja3MNCg0KIyMjMS4gU2hvdyB0aGUgbnVtYmVyIG9mIHJldmlld3MgZm9yIFN0YXJidWNrcyBvdmVyIHRpbWUoeWVhcnMpIHVzaW5nIGEgbGluZSBjaGFydA0KU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCU+JQ0KICBzZWxlY3QoeWVhcl9mb3JtYXR0ZWQpJT4lDQogIGdyb3VwX2J5ICh5ZWFyX2Zvcm1hdHRlZCklPiUNCiAgc3VtbWFyaXNlKE51bWJlcm9mUmV2aWV3cyA9IG4oKSklPiUNCiAgZ2dwbG90KGFlcyAoeD15ZWFyX2Zvcm1hdHRlZCx5PU51bWJlcm9mUmV2aWV3cykpICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KDQpgYGB7cn0NCiMjIzIuIFNob3cgdGhlIG51bWJlciBvZiByZXZpZXdzIGJ5IFN0YXRlIHVzaW5nIGEgY29sdW1uL2JhciBjaGFydA0KU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCU+JQ0KICBzZWxlY3Qoc3RhdGUpJT4lDQogIGdyb3VwX2J5KHN0YXRlKSU+JQ0KICBnZ3Bsb3QoYWVzICh4PXN0YXRlKSkgKw0KICBnZW9tX2JhcigpDQpgYGANCg0KYGBge3J9DQojIyMzLiBDcmVhdGUgYSBkb251dCBjaGFydCB0aGF0IFNob3dzIHRoZSBwcm9wb3J0aW9uIG9mIGJ1c2luZXNzZXMgdGhhdCBhcmUgb3BlbiB2cyB0aG9zZSB0aGF0IGFyZSBjbG9zZWQNCmJ1c2luZXNzICU+JQ0KIGdyb3VwX2J5KGlzX29wZW4pICU+JQ0KIHN1bW1hcmlzZShDb3VudCA9IG4oKSklPiUNCiBtdXRhdGUoaXNfb3Blbj1hcy5mYWN0b3IoaXNfb3BlbiksIHBlcmNlbnRhZ2UgPSByb3VuZChDb3VudC9zdW0oQ291bnQpKjEwMCwyKSwgTGFiZWxQb3NpdGlvbiA9IGN1bXN1bShwZXJjZW50YWdlKS0uMSpwZXJjZW50YWdlKSAlPiUNCiAjIyNwbG90IHRoZSBwaWUgY2hhcnQgd2l0aCBnZW9tX2JhcigpIGFuZCB0aGVuIGNvbnZlcnQgdGhlIGJhciBpbnRvIHBpZSB3aXRoIHRoZSBjb29yZF9wb2xhcigpIGZ1bmN0aW9uLiB0byBtYWtlIGEgZG9udXQgcGxvdCB3ZSBtdXN0IHNwZWNpZnkgdGhlIHggPSAyIGluIGFlcygpIGFuZCBhZGQgdGhlIHhsaW0oKSBhcyBjb2RlIA0KIGdncGxvdCgNCiBhZXMoeCA9IDIsIHkgPSBwZXJjZW50YWdlLCBmaWxsID0gaXNfb3BlbikpKw0KIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSsNCiBjb29yZF9wb2xhcigieSIgKSArDQogZ2VvbV90ZXh0KGFlcyh5ID0gTGFiZWxQb3NpdGlvbiwgbGFiZWwgPSBwYXN0ZShwZXJjZW50YWdlLCIlIiwgc2VwID0gIiIpKSwgY29sID0gIndoaXRlIikgKw0KIHRoZW1lX3ZvaWQoKSArDQogc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpKw0KIHhsaW0oLjIsMi41KQ0KYGBgDQoNCmBgYHtyfQ0KIyM0LiBDcmVhdGUgYSBjb2x1bW4gY2hhcnQgdGhhdCBzaG93cyB0aGUgbWVkaWFuIG51bWJlciBvZiB3b3JkcyB1c2VkIGluIFN0YXJidWNrcyByZXZpZXdzIGVhY2ggbW9udGgNClN0YXJidWNrc0RhdGVGb3JtYXR0ZWQgJT4lDQogIG11dGF0ZShOdW1iZXJPZldvcmRzPXN0cl9jb3VudCh0ZXh0LCBib3VuZGFyeSgid29yZCIpKSkgJT4lICMjIGNvdW50cyB0aGUgbnVtYmVyIG9mIHdvcmRzIHNpbmNlIGJvdW5kYXJ5IGlzIHNldCB0byB3b3JkDQogIG11dGF0ZShNb250aE5hbWU9bW9udGgoeW1kKGRhdGVfZm9ybWF0dGVkKSwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KE1vbnRoTmFtZSkgJT4lDQogIHN1bW1hcml6ZShOdW1iZXJPZldvcmRzPW1lZGlhbihOdW1iZXJPZldvcmRzKSkgJT4lDQogIGdncGxvdChhZXMgKHg9TW9udGhOYW1lLCB5PU51bWJlck9mV29yZHMpKSArDQogIGdlb21fY29sKCkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KYGBge3J9DQojIyMgNS4gQ3JlYXRlIGEgY2hhcnQgdGhhdCBzaG93cyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZnVubnkgYW5kIHVzZWZ1bCByZXZpZXdzIG9mIFN0YXJidWNrcy4NClN0YXJidWNrc0RhdGVGb3JtYXR0ZWQlPiUNCiAgZ2dwbG90KGFlcyh4PWZ1bm55LCB5PXVzZWZ1bCkpICsgDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPWxtKQ0KYGBgDQoNCg0KYGBge3J9DQojIyMgNi4gQ3JlYXRlIGEgd29yZGNsb3VkIHRvIHNob3cgdGhlIHdvcmRzIHRoYXQgYXJlIHVzZWQgdGhlIG1vc3QgZnJlcXVlbnRseSBpbiByZXZpZXdzDQoNCmxpYnJhcnkod29yZGNsb3VkKQ0KYGBgDQoNCmBgYHtyfQ0KY3JlYXRlV29yZENsb3VkID0gZnVuY3Rpb24oeCkNCnsNCiAgU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCAlPiUNCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQ0KICAgIA0KICAgIGZpbHRlcighd29yZCAlaW4lIHN0b3Bfd29yZHMkd29yZCwgIXdvcmQgJWluJSAnc3RhcmJ1Y2tzJykgJT4lDQogICAgIyMjIGZpbHRlcighd29yZCA9ICdzdGFyYnVja3MnKSAlPiUNCiAgICBjb3VudCh3b3JkLHNvcnQgPSBUUlVFKSAlPiUNCiAgICB1bmdyb3VwKCkgJT4lDQogICAgaGVhZCgzMCkgJT4lDQogICAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMzAsY29sb3JzPWJyZXdlci5wYWwoOCwgIkRhcmsyIikpKQ0KfQ0KDQpjcmVhdGVXb3JkQ2xvdWQocmV2aWV3KQ0KYGBgDQoNCmBgYHtyfQ0KIyMjIDcuU2VudGltZW50IEFuYWx5c2lzIDogVGhlIFBvc2l0aXZlIGFuZCBuZWdhdGl2ZSB3b3JkcyBhc3NvY2lhdGUgd2l0aCBTdGFyYnVja3MgcmV2aWV3cw0KDQpwb3NpdGl2ZVdvcmRzQmFyR3JhcGggPC0gZnVuY3Rpb24oU0MpIHsNCiAgY29udHJpYnV0aW9ucyA8LSBTQyAlPiUNCiAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQ0KICAgIGNvdW50KHdvcmQsc29ydCA9IFRSVUUpICU+JQ0KICAgIHVuZ3JvdXAoKSAlPiUNCiAgICANCiAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpLCBieSA9ICJ3b3JkIikgJT4lDQogICAgZ3JvdXBfYnkod29yZCkgJT4lDQogICAgc3VtbWFyaXplKGNvbnRyaWJ1dGlvbiA9IHN1bSh2YWx1ZSksIG49bigpKQ0KICANCiAgY29udHJpYnV0aW9ucyAlPiUNCiAgICB0b3BfbigyMCwgYWJzKGNvbnRyaWJ1dGlvbikpICU+JQ0KICAgIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBjb250cmlidXRpb24pKSAlPiUNCiAgICBoZWFkKDIwKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHdvcmQsIGNvbnRyaWJ1dGlvbiwgZmlsbCA9IGNvbnRyaWJ1dGlvbiA+IDApKSArDQogICAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICAgIGNvb3JkX2ZsaXAoKSArIHRoZW1lX2J3KCkNCn0NCg0KcG9zaXRpdmVXb3Jkc0JhckdyYXBoKFN0YXJidWNrc0pvaW5lZF90YmwpDQpgYGANCg0KYGBge3J9DQojIyM4LiBTaG93IGFsbCB0aGUgU3RhcmJ1Y2tzIGxvY2F0aW9ucyBvbiBhIG1hcA0KIyMjIHdlIHdpbGwgdXNlIHRoZSBzdGFyYnVja3NidXNpbmVzcyBkYXRhIHNldCB0aGF0IHdlIGNyZWF0ZWQgZWFybGllciBiZWNhdXNlIGl0IGRvZXMgbm90IGhhdmUgcmVwZWF0aW5nIHJldmlld3MgZWFjaCByZWNvcmQgaXMgdW5pcXVlDQpsaWJyYXJ5KGxlYWZsZXQpDQojIyMgdGhpcyBmdW5jdGlvbiB3aWxsIGNvbG9yIGVhY2ggbG9jYXRpb24gYmFzZWQgb24gdGhlIHN0YXIgcmF0aW5nIGl0IGhhcw0KcGFsIDwtIGNvbG9yRmFjdG9yKGMoInB1cnBsZSIsICJyZWQiLCAib3JhbmdlIiwgImJsYWNrIiwiYmx1ZSIpLA0KICAgICAgICAgICAgICAgICAgIGRvbWFpbiA9IHVuaXF1ZShzdGFyYnVja3NidXNpbmVzcyRzdGFycykpDQojIyMgdGhpcyBkcmF3cyB0aGUgbWFwDQptYXAgPC0gbGVhZmxldChzdGFyYnVja3NidXNpbmVzcykgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMoIkNhcnRvREIuUG9zaXRyb24iKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycygNCiAgICBjb2xvciA9IH5wYWwoc3RhcmJ1Y2tzYnVzaW5lc3Mkc3RhcnMpLA0KICAgIHN0cm9rZSA9IEZBTFNFLCBmaWxsT3BhY2l0eSA9IDAuNSwNCiAgICBsYXQgPSBzdGFyYnVja3NidXNpbmVzcyRsYXRpdHVkZSwNCiAgICBsbmcgPSBzdGFyYnVja3NidXNpbmVzcyRsb25naXR1ZGUsDQogICAgY2x1c3Rlck9wdGlvbnMgPSBtYXJrZXJDbHVzdGVyT3B0aW9ucygpLA0KICAgIHBvcHVwID0gYXMuY2hhcmFjdGVyKHN0YXJidWNrc2J1c2luZXNzJGFkZHJlc3MpKQ0KbWFwDQpgYGANCg0KDQpgYGB7cn0NCiNMZXQncyBqb2luIHRoZSBidXNpbmVzcyBhbmQgcmV2aWV3IHRhYmxlcyBzbyB0aGF0IHdlIGNhbiBjcmVhdGUgYSBkYXNoYm9hcmQNCmJ1c2luZXNzX3Jldmlld3MgPC0gaW5uZXJfam9pbihidXNpbmVzcyxyZXZpZXcpDQpgYGANCg0KDQo=