Data exploration with the dplyr, tidyr and stringr libraries. - Column subsetting - Filtering of rows - Boolean operators, Boolean algebra, de Morgan’s laws - Creating new columns (1x Challenge) - Missing values - Manipulating text (3x Challenge) - Aggregating data (1x Challenge) - Pivot tables, data in long and wide format - Merging tables

Useful resources:
- dplyr cheatsheet
- tidyr cheatsheet
- stringr cheatsheet
- ggplot2 cheatsheet
- A. Kassambara - Guide to Create Beautiful Graphics in R.

The data comes from https://flixgem.com/ (dataset version as of March 12, 2021). The data contains information on 9425 movies and series available on Netlix.

Data exploration with dplyr and tidyr libraries

Subset of columns

dane  #takes the dane data frame 
  select(Title, Runtime, IMDb.Score, Release.Date) %>% #to choose only the columns 'Title,' 'Runtime,' 'IMDb.Score,' and 'Release.Date.' 
  head(5) #to display the first 5 rows of the resulting data frame
dane %>% #takes the dane data frame 
  select(-Netflix.Link, -IMDb.Link, -Image, -Poster, -TMDb.Trailer)%>% #to choose only the columns'Netflix.Link,' 'IMDb.Link,' 'Image,' 'Poster,' and 'TMDb.Trailer'
  head(5) #to display the first 5 rows of the resulting data frame
dane %>% #takes the dane data frame 
  select(1:10)%>% #to choose only the columns from the 1st to the 10th column 
  head(5) #to display the first 5 rows of the resulting data frame
dane %>% #takes the dane data frame 
  select(Title:Runtime)%>% #to choose only the columns from 'Title' to 'Runtime'
  head(5) #to display the first 5 rows of the resulting data frame
dane %>% #takes the dane data frame
  select(starts_with('IMDb'))%>% #to select columns that start with the string 'IMDb'
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  select(ends_with('Score'))%>% #to select columns that end with the 'Score'
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  select(contains('Date'))%>% #to select columns that contain the word 'Date'
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  select(matches('^[a-z]{5,6}$')) %>% #to select columns whose names consist of 5 to 6 lowercase letters
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  select(-matches('\\.'))%>% #to select columns which do not contain a period ('.') in their names
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  select(IMDb.Score)%>% #to select the 'IMDb.Score' column
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  pull(IMDb.Score)%>% #to extract the 'IMDb.Score' column
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  pull(IMDb.Score, Title)%>% #to extract the 'IMDb.Score' and 'Title' columns
  head(10) #displays the first 10 rows of the resulting data frame

Row filtering

dane %>% #takes the dane data frame
  filter(Series.or.Movie == "Series")%>% #to filter the dane data frame to select only rows where the 'Series.or.Movie' column has the value "Series." 
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  filter(IMDb.Score > 8)%>% #to select only rows where the 'IMDb.Score' column has a value greater than 8
  head(10) #displays the first 10 rows of the resulting data frame

Boolean operators, Boolean algebra, de Morgan’s laws

dane %>% #takes the dane data frame
  filter(IMDb.Score >= 8 & Series.or.Movie == 'Series')%>% #to select only trows where the 'IMDb.Score' is greater than or equal to 8 and where the 'Series.or.Movie' is equal to 'Series', it retrieves rows with a high IMDb score that are specifically categorized as 'Series'.
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  filter(IMDb.Score >= 9 | IMDb.Votes < 1000)%>% #selects rows that meet either of the two conditions: rows where the 'IMDb.Score' is greater than or equal to 9, rows where the 'IMDb.Votes' is less than 1000.
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  filter(!(IMDb.Score >= 9 | IMDb.Votes < 1000))%>% #selects rows that do not meet either of the following conditions:rows where the 'IMDb.Score' is greater than or equal to 9, rows where the 'IMDb.Votes' is less than 1000
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  filter(!(IMDb.Score >= 9) & !(IMDb.Votes < 1000))%>% #selects rows that do not meet both of the following conditions: eows where the 'IMDb.Score' is greater than or equal to 9, rows where the 'IMDb.Votes' is less than 1000 
  head(10) #displays the first 10 rows of the resulting data frame

Create new columns

dane %>% #takes the dane data frame
  mutate(score_category = if_else(IMDb.Score >= 5, 'Good', 'Poor')) %>% #to add a new column called 'score_category' to the data frame. The values in this column are assigned based on a condition: If the 'IMDb.Score' is greater than or equal to 5, it's labeled as 'Good'; otherwise, it's labeled as 'Poor'.
  select(Title, IMDb.Score, score_category)%>% #to choose and display only the 'Title,' 'IMDb.Score,' and 'score_category' columns from the data frame.
  head(10) #displays the first 10 rows of the resulting data frame
dane %>% #takes the dane data frame
  transmute( #to create new variables
    Release = Release.Date %>% as.Date(format = '%m/%d/%y') #to take the 'Release.Date' column and converts it into a specified date format.
    ,Netflix.Release = Netflix.Release.Date %>% as.Date(format = '%m/%d/%y') #to take the 'Netflix.Release'column and converts it into a specified date format.
  )

Challenge 1.

CHALLENGE 1: What is the oldest Woody Allen film available on Netflix?

# your code goes here
dane %>%
filter(Director == "Woody Allen") %>%
  mutate(Release = Release.Date %>% as.Date(format = '%m/%d/%Y')) %>%
  mutate(Old = min_rank(Release)) %>%
  filter(Old == 1) %>%
  select(Director, Release, Old, Title)
##      Director    Release Old
## 1 Woody Allen 1972-08-06   1
##                                                                   Title
## 1 Everything You Always Wanted to Know About Sex But Were Afraid to Ask
dane %>% #takes the dane data frame
  mutate(score_category = case_when(
    IMDb.Score <= 2 ~ 'Very Poor'
    ,IMDb.Score <= 4 ~ 'Poor'
    ,IMDb.Score <= 6 ~ 'Medium'
    ,IMDb.Score <= 8 ~ 'Good'
    ,IMDb.Score <= 10 ~ 'Very Good'
    )) %>% #categorizes 'IMDb.Score' into different categories 'Very Poor,' 'Poor,' 'Medium,' 'Good,' and 'Very Good' based on the score ranges
  select(Title, IMDb.Score, score_category)%>% 
  head(10) #selects and displays the 'Title,' 'IMDb.Score,' and 'score_category' columns for the first 10 rows of the modified data frame
dane %>% #takes the dane data frame
  mutate(avg_score = mean(c(IMDb.Score * 10
                            ,Hidden.Gem.Score * 10
                            ,Rotten.Tomatoes.Score
                            ,Metacritic.Score)
                          ,na.rm = TRUE) %>% #calculates the average score from a combination of different score columns
           round(2)) %>% #The resulting average score is then rounded to two decimal places
  select(Title, avg_score)%>% 
  head(10) #selects and displays the 'Title' and 'avg_score' columns for the first 10 rows of the modified data frame
dane %>%  #takes the dane data frame
  rowwise() %>% #ensures that the mean() function calculates the average for each row rather than across all rows
  mutate(avg_score = mean(c(IMDb.Score * 10
                            ,Hidden.Gem.Score * 10
                            ,Rotten.Tomatoes.Score
                            ,Metacritic.Score)
                          ,na.rm = TRUE) %>% #calculates the average score from a combination of different score columns
           round(2)) %>% #The resulting average score is then rounded to two decimal places
  select(Title, avg_score)%>% 
  head(10) #selects and displays the 'Title' and 'avg_score' columns for the first 10 rows of the modified data frame
dane %>% #takes the dane data frame
  mutate(Popularity = if_else(IMDb.Votes > quantile(IMDb.Votes, 0.90, na.rm = TRUE), 'High', 'Not High')) %>% #to add a new 'Popularity' column based on the condition that checks if 'IMDb.Votes' is greater than the 90th percentile of 'IMDb.Votes'
  relocate(Popularity, .after = Title) #to move the 'Popularity' column immediately after the 'Title' column in the data frame
dane %>% #takes the dane data frame
  rename(
    Tytul = Title
    ,Gatunek = Genre
  ) #to rename the 'Title' column to 'Tytul' and the 'Genre' column to 'Gatunek' in the data frame

Missing Values

dane %>% #takes the dane data frame
  sapply(function(x) is.na(x) %>% sum()) #returns a count of missing values for each column in the data frame
dane %>% #takes the dane data frame
  drop_na(Hidden.Gem.Score) #to remove rows where the 'Hidden.Gem.Score' column has missing values (NAs). It keeps only the rows with non-missing values in the specified column.
dane %>% #takes the dane data frame
  mutate(Hidden.Gem.Score = replace_na(Hidden.Gem.Score, median(Hidden.Gem.Score, na.rm = TRUE))) %>% #to replace missing values in the 'Hidden.Gem.Score' column with the median of that column
  sapply(function(x) is.na(x) %>% sum()) #to count the number of missing values in each column
dane %>% #takes the dane data frame
  replace_na(list(Hidden.Gem.Score = median(dane$Hidden.Gem.Score, na.rm = TRUE))) %>% #to replace missing values in the 'Hidden.Gem.Score' column with the median of the 'Hidden.Gem.Score' column from the entire dane data frame. The na.rm = TRUE argument ensures that the median is calculated without considering missing values.
  sapply(function(x) is.na(x) %>% sum()) #to count the number of missing values in each column after this replacement

Text manipulation

gatunki = dane$Genre %>%
  paste0(collapse = ', ') %>% #takes the 'Genre' column from the data frame and combines all genre values into a single comma-separated string.
  str_extract_all('[A-Za-z]+') %>% #to extract words (probably genres) from the concatenated string
  unlist() %>% #to convert the list of extracted genres into a vector
  table() %>% #to create a frequency table of genre counts
  as.data.frame() #to convert the table to a data frame

gatunki %>%
  arrange(-Freq) #arranges the data frame 'gatunki' in order based on the frequency ('Freq') column. This provides a list of genres sorted by their popularity, with the most frequent genres listed first.
dane %>% #takes the dane data frame
  mutate(poland_available = str_detect(Country.Availability, 'Poland')) %>% #to create a new column 'poland_available' that checks if the 'Country.Availability' column contains the string 'Poland'. If it does, it sets the value to TRUE.
  filter(poland_available == TRUE) %>% #to select rows where 'poland_available' is TRUE, meaning the movie is available in Poland
  pull(Title)%>% #extracts the 'Title' column from the filtered data frame
  head(10) #to display the first 10 movie titles that are available in Poland based on the 'Country.Availability' column
dane %>% #takes the dane data frame
  unite( 
    col = 'Scores'
    ,c('Hidden.Gem.Score', 'IMDb.Score', 'Rotten.Tomatoes.Score', 'Metacritic.Score') #to combine the values from the columns 'Hidden.Gem.Score,' 'IMDb.Score,' 'Rotten.Tomatoes.Score,' and 'Metacritic.Score' into a single new column called 'Scores'.
    ,sep = ', '
  ) %>% #to specify that the values from these columns should be separated by a comma and a space (', ')
  select(Title, Scores)%>% #to choose and display only the 'Title' and 'Scores' columns from the modified data frame 
  head(10) #to display the first 10 rows of the modified data frame

Challenge 2.

CHALLENGE 2: What are the three highest rated comedies available in Polish?

# your code goes here
dane %>%
  filter(Genre =="Comedy" & str_detect(Country.Availability, "Poland")) %>%
  mutate(Ranking = min_rank(-IMDb.Score)) %>%
  filter(Ranking <= 3) %>%
  arrange(Ranking) %>%
  select(Ranking, Title, IMDb.Score, Country.Availability) 
##   Ranking                            Title IMDb.Score
## 1       1                   No Longer kids        9.0
## 2       2                         Innocent        8.9
## 3       3 Aunty Donnas Big Ol House of Fun        8.8
## 4       3      Monty Pythons Flying Circus        8.8
## 5       3                   Dave Chappelle        8.8
## 6       3                       Still Game        8.8
##                                                                                                                                                                                                                                                                                                                Country.Availability
## 1                              United Kingdom,Russia,Lithuania,Canada,Czech Republic,Iceland,South Africa,India,Australia,Portugal,Hungary,Switzerland,Japan,Mexico,Belgium,Germany,Sweden,Greece,Hong Kong,Singapore,Thailand,Spain,France,Argentina,United States,Malaysia,Brazil,Netherlands,Israel,Italy,Poland,Turkey,Colombia
## 2 Romania,Belgium,Portugal,France,Australia,Japan,Singapore,Netherlands,Russia,Poland,Sweden,India,Slovakia,Germany,Lithuania,Czech Republic,Brazil,Israel,Spain,United Kingdom,Canada,Switzerland,Hong Kong,Argentina,United States,South Africa,Mexico,Greece,South Korea,Iceland,Italy,Thailand,Hungary,Turkey,Malaysia,Colombia
## 3 Lithuania,Australia,United Kingdom,Poland,Brazil,United States,India,Russia,Netherlands,Germany,Hong Kong,Japan,South Korea,Mexico,South Africa,France,Iceland,Thailand,Spain,Singapore,Greece,Argentina,Czech Republic,Israel,Portugal,Switzerland,Hungary,Slovakia,Canada,Italy,Sweden,Belgium,Turkey,Malaysia,Colombia,Romania
## 4 Hong Kong,Australia,Singapore,India,Netherlands,Sweden,Russia,Czech Republic,Slovakia,Israel,Lithuania,Argentina,Brazil,United Kingdom,Canada,Germany,France,Spain,Poland,Japan,Romania,Mexico,South Korea,Belgium,Greece,United States,Switzerland,Portugal,South Africa,Iceland,Italy,Thailand,Hungary,Turkey,Malaysia,Colombia
## 5 Russia,Lithuania,Germany,France,Iceland,Netherlands,India,Spain,Sweden,Belgium,Switzerland,Australia,United Kingdom,Brazil,Argentina,Mexico,Canada,United States,Israel,Hong Kong,Singapore,Portugal,Poland,Romania,Greece,South Africa,Czech Republic,Slovakia,Japan,Italy,Thailand,Hungary,South Korea,Turkey,Malaysia,Colombia
## 6             United Kingdom,Australia,Canada,United States,Lithuania,Czech Republic,Romania,Greece,India,South Africa,Singapore,Iceland,Slovakia,Thailand,Hungary,Russia,Japan,Poland,Hong Kong,Spain,Mexico,Switzerland,Belgium,Portugal,France,Argentina,Germany,Sweden,Turkey,Malaysia,Brazil,Italy,Israel,Netherlands,Colombia

Challenge 3.

CHALLENGE 3: For 2019 and 2020 productions, what is the average time between release and appearance on Netflix?

# your code goes here
dane %>%
  mutate(Release = Release.Date %>% as.Date(format = '%m/%d/%Y')
    ,Netflix.Release = Netflix.Release.Date %>% as.Date(format = '%m/%d/%Y')) %>%
  filter(year(Release) %in% c(2019,2020)) %>%
  mutate(DIFF = Netflix.Release - Release) %>%
  select(Title, Release, Netflix.Release, DIFF) %>%
  summarise(avg = mean(DIFF))
##             avg
## 1 107.0268 days

Challenge 4.

CHALLENGE 4: What are the most popular tags for productions available in Polish?

# your code goes here
dane %>%
  filter(str_detect(Country.Availability, "Poland")) %>%
  select(Tags) %>%
  separate_rows(Tags, sep = ",") %>%
  count(Tags) %>%
  mutate(popular = min_rank(-n), ) %>%
  arrange(popular)
## # A tibble: 769 × 3
##    Tags                     n popular
##    <chr>                <int>   <int>
##  1 Dramas                 813       1
##  2 Comedies               731       2
##  3 TV Dramas              450       3
##  4 TV Programmes          402       4
##  5 Documentaries          394       5
##  6 US Movies              362       6
##  7 Action & Adventure     338       7
##  8 International Dramas   303       8
##  9 TV Comedies            275       9
## 10 US TV Shows            230      10
## # ℹ 759 more rows

Data aggregation

dane %>% #takes the dane data frame
  group_by(Series.or.Movie) %>% #to group the data by the 'Series.or.Movie' column
  summarize( #to calculate various summary statistics
    count = n() #Counts the number of observations
    ,avg_imdb_score = mean(IMDb.Score, na.rm = TRUE) %>% round(2) #Calculates the average 'IMDb.Score' within each group while rounding to two decimal places
    ,avg_imdb_votes = mean(IMDb.Votes, na.rm = TRUE) %>% round(0) #Calculates the average 'IMDb.Votes' within each group and rounds it to the nearest whole number
    ,sum_awards = sum(Awards.Received, na.rm = TRUE) #Sums the 'Awards.Received' within each group
  )
dane %>% #takes the dane data frame
  group_by(Series.or.Movie, Runtime) %>% #to group by two variables: 'Series.or.Movie' and 'Runtime'
  summarize(n = n()) %>% #calculates the number of observations (count) in each group and assigns it to the variable 'n'
  arrange(-n) #to sort in order based on the count ('n')

Challenge 5.

CHALLENGE 5: What are the average ratings of films produced in each decade (i.e., 1960s, 1970s, 1980s, 1990s, etc.)?

# your code goes here
dane %>%
    mutate(Release = Release.Date %>% as.Date(format = '%m/%d/%Y')
    ,Netflix.Release = Netflix.Release.Date %>% as.Date(format = '%m/%d/%Y')) %>%
  mutate(Decade = 10 * (year(Release) %/% 10)) %>%
  select(Title, Release, Decade, IMDb.Score) %>%
  drop_na(Decade, IMDb.Score) %>%
  group_by(Decade) %>%
  summarise(avg = mean(IMDb.Score))
## # A tibble: 10 × 2
##    Decade   avg
##     <dbl> <dbl>
##  1   1930  7.46
##  2   1940  7.43
##  3   1950  7.37
##  4   1960  7.46
##  5   1970  7.33
##  6   1980  7.11
##  7   1990  6.88
##  8   2000  6.85
##  9   2010  6.94
## 10   2020  7.04

Pivot tables, long and wide format data

dane_pivot = dane %>%
  select(Title, ends_with('Score')) #selects the 'Title' column and all columns that end with 'Score' from the dane data frame, creating a new data frame named 'dane_pivot' with these selected columns
dane_pivot = dane_pivot %>% 
  pivot_longer( #reshapes the data from a wide format to a long format by
    cols = 2:5 #Columns 2 through 5 (which contain the various scores) are being melted into a single column
    ,names_to = 'Attribute'#The column that will store the names of the melted variables is named 'Attribute'
    ,values_to = 'Value'#The column that will store the values of the melted variables is named 'Value'
  )
dane_pivot = dane_pivot %>%
  pivot_wider( #reshapes the data back from the long format to a wide format by
    id_cols = 1 #The 'Title' column is specified as the identifier column that remains unchanged
    ,names_from = 'Attribute'#The 'Attribute' column from the long format becomes the new column names in the wide format
    ,values_from = 'Value' #The 'Value' column from the long format becomes the values for the new columns in the wide format
  ) #Result: to convert the original wide format data frame into a long format, and then back into a wide format, while keeping the 'Title' column as the identifier. The result is a data frame where each score type (e.g., 'Hidden.Gem.Score,' 'IMDb.Score,' etc.) is in its own column.

Joining tables

oceny_metacritic = dane %>%
  select(Title, Metacritic.Score) %>% #selects the 'Title' and 'Metacritic.Score' columns from the dane data fram
  .[1:100,] %>% #filters the data to retain only the first 100 rows
  drop_na() #removes rows with missing values in the 'Metacritic.Score' column
oceny_rotten_tomatoes = dane %>%
  select(Title, Rotten.Tomatoes.Score) %>% #selects the 'Title' and 'Rotten.Tomatoes.Score' columns from dane data frame
  .[1:100,] %>% #filters the data to retain only the first 100 rows
  drop_na() #removes rows with missing values (NAs) in the 'Rotten.Tomatoes.Score' column
oceny_metacritic %>%
  left_join(oceny_rotten_tomatoes, by = c('Title' = 'Title')) #to perform a left join between 'oceny_metacritic' and 'oceny_rotten_tomatoes' on the 'Title' column. 
#The result is a merged data frame that combines information from both 'oceny_metacritic' and 'oceny_rotten_tomatoes,' where rows with matching 'Title' values are joined together. 
LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQgMi4iDQphdXRob3I6ICJUZWFtIG1lbWJlcnM6IE1hcnRhIFN6Y3plcnNrYSwgT3NrYXIgUmFiYcW8ecWEc2tpIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIGhpZ2hsaWdodDogdGV4dG1hdGUNCiAgICBmb250c2l6ZTogMTBwdA0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogNA0KICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBmYWxzZQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQpEYXRhIGV4cGxvcmF0aW9uIHdpdGggdGhlICpkcGx5ciosICp0aWR5ciogYW5kICpzdHJpbmdyKiBsaWJyYXJpZXMuIC0NCkNvbHVtbiBzdWJzZXR0aW5nIC0gRmlsdGVyaW5nIG9mIHJvd3MgLSBCb29sZWFuIG9wZXJhdG9ycywgQm9vbGVhbg0KYWxnZWJyYSwgZGUgTW9yZ2FuJ3MgbGF3cyAtIENyZWF0aW5nIG5ldyBjb2x1bW5zICgxeCBDaGFsbGVuZ2UpIC0NCk1pc3NpbmcgdmFsdWVzIC0gTWFuaXB1bGF0aW5nIHRleHQgKDN4IENoYWxsZW5nZSkgLSBBZ2dyZWdhdGluZyBkYXRhICgxeA0KQ2hhbGxlbmdlKSAtIFBpdm90IHRhYmxlcywgZGF0YSBpbiBsb25nIGFuZCB3aWRlIGZvcm1hdCAtIE1lcmdpbmcgdGFibGVzDQoNClVzZWZ1bCByZXNvdXJjZXM6XA0KLSBbZHBseXINCmNoZWF0c2hlZXRdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vZGF0YS10cmFuc2Zvcm1hdGlvbi5wZGYpXA0KLSBbdGlkeXINCmNoZWF0c2hlZXRdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vdGlkeXIucGRmKVwNCi0gW3N0cmluZ3INCmNoZWF0c2hlZXRdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yc3R1ZGlvL2NoZWF0c2hlZXRzL21haW4vc3RyaW5ncy5wZGYpXA0KLSBbZ2dwbG90Mg0KY2hlYXRzaGVldF0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvbWFpbi9kYXRhLXZpc3VhbGl6YXRpb24ucGRmKVwNCi0gW0EuIEthc3NhbWJhcmEgLSBHdWlkZSB0byBDcmVhdGUgQmVhdXRpZnVsIEdyYXBoaWNzIGluDQpSXShodHRwOi8vd3d3LnN0aGRhLmNvbS9lbmdsaXNoL2Rvd25sb2FkLzMtZWJvb2tzLzUtZ3VpZGUtdG8tY3JlYXRlLWJlYXV0aWZ1bC1ncmFwaGljcy1pbi1yLWJvb2svKS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmlmKCFyZXF1aXJlKCd0aWR5dmVyc2UnKSkgaW5zdGFsbC5wYWNrYWdlcygndGlkeXZlcnNlJykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNClRoZSBkYXRhIGNvbWVzIGZyb20gPGh0dHBzOi8vZmxpeGdlbS5jb20vPiAoZGF0YXNldCB2ZXJzaW9uIGFzIG9mIE1hcmNoDQoxMiwgMjAyMSkuIFRoZSBkYXRhIGNvbnRhaW5zIGluZm9ybWF0aW9uIG9uIDk0MjUgbW92aWVzIGFuZCBzZXJpZXMNCmF2YWlsYWJsZSBvbiBOZXRsaXguDQoNCmBgYHtyIGxvYWQtZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkocmVhZHIpDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpkb3dubG9hZC5maWxlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20va2ZsaXNpa293c2tpL2RzL21hc3Rlci9uZXRmbGl4LWRhdGFzZXQuY3N2P3Jhdz10cnVlIiwgZGVzdGZpbGUgPSJkYW5lLmNzdiIsbW9kZT0id2IiKQ0KZGFuZTwtcmVhZC5jc3YoZmlsZT0iZGFuZS5jc3YiLGVuY29kaW5nID0iVVRGLTgiLGhlYWRlcj1UUlVFLHNlcCA9ICIsIikNCmF0dGFjaChkYW5lKQ0KYGBgDQoNCiMjIERhdGEgZXhwbG9yYXRpb24gd2l0aCBkcGx5ciBhbmQgdGlkeXIgbGlicmFyaWVzDQoNCiMjIyBTdWJzZXQgb2YgY29sdW1ucw0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZSANCiAgc2VsZWN0KFRpdGxlLCBSdW50aW1lLCBJTURiLlNjb3JlLCBSZWxlYXNlLkRhdGUpICU+JSAjdG8gY2hvb3NlIG9ubHkgdGhlIGNvbHVtbnMgJ1RpdGxlLCcgJ1J1bnRpbWUsJyAnSU1EYi5TY29yZSwnIGFuZCAnUmVsZWFzZS5EYXRlLicgDQogIGhlYWQoNSkgI3RvIGRpc3BsYXkgdGhlIGZpcnN0IDUgcm93cyBvZiB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lIA0KICBzZWxlY3QoLU5ldGZsaXguTGluaywgLUlNRGIuTGluaywgLUltYWdlLCAtUG9zdGVyLCAtVE1EYi5UcmFpbGVyKSU+JSAjdG8gY2hvb3NlIG9ubHkgdGhlIGNvbHVtbnMnTmV0ZmxpeC5MaW5rLCcgJ0lNRGIuTGluaywnICdJbWFnZSwnICdQb3N0ZXIsJyBhbmQgJ1RNRGIuVHJhaWxlcicNCiAgaGVhZCg1KSAjdG8gZGlzcGxheSB0aGUgZmlyc3QgNSByb3dzIG9mIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUgDQogIHNlbGVjdCgxOjEwKSU+JSAjdG8gY2hvb3NlIG9ubHkgdGhlIGNvbHVtbnMgZnJvbSB0aGUgMXN0IHRvIHRoZSAxMHRoIGNvbHVtbiANCiAgaGVhZCg1KSAjdG8gZGlzcGxheSB0aGUgZmlyc3QgNSByb3dzIG9mIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUgDQogIHNlbGVjdChUaXRsZTpSdW50aW1lKSU+JSAjdG8gY2hvb3NlIG9ubHkgdGhlIGNvbHVtbnMgZnJvbSAnVGl0bGUnIHRvICdSdW50aW1lJw0KICBoZWFkKDUpICN0byBkaXNwbGF5IHRoZSBmaXJzdCA1IHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBzZWxlY3Qoc3RhcnRzX3dpdGgoJ0lNRGInKSklPiUgI3RvIHNlbGVjdCBjb2x1bW5zIHRoYXQgc3RhcnQgd2l0aCB0aGUgc3RyaW5nICdJTURiJw0KICBoZWFkKDEwKSAjZGlzcGxheXMgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBzZWxlY3QoZW5kc193aXRoKCdTY29yZScpKSU+JSAjdG8gc2VsZWN0IGNvbHVtbnMgdGhhdCBlbmQgd2l0aCB0aGUgJ1Njb3JlJw0KICBoZWFkKDEwKSAjZGlzcGxheXMgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBzZWxlY3QoY29udGFpbnMoJ0RhdGUnKSklPiUgI3RvIHNlbGVjdCBjb2x1bW5zIHRoYXQgY29udGFpbiB0aGUgd29yZCAnRGF0ZScNCiAgaGVhZCgxMCkgI2Rpc3BsYXlzIHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUNCiAgc2VsZWN0KG1hdGNoZXMoJ15bYS16XXs1LDZ9JCcpKSAlPiUgI3RvIHNlbGVjdCBjb2x1bW5zIHdob3NlIG5hbWVzIGNvbnNpc3Qgb2YgNSB0byA2IGxvd2VyY2FzZSBsZXR0ZXJzDQogIGhlYWQoMTApICNkaXNwbGF5cyB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIHNlbGVjdCgtbWF0Y2hlcygnXFwuJykpJT4lICN0byBzZWxlY3QgY29sdW1ucyB3aGljaCBkbyBub3QgY29udGFpbiBhIHBlcmlvZCAoJy4nKSBpbiB0aGVpciBuYW1lcw0KICBoZWFkKDEwKSAjZGlzcGxheXMgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBzZWxlY3QoSU1EYi5TY29yZSklPiUgI3RvIHNlbGVjdCB0aGUgJ0lNRGIuU2NvcmUnIGNvbHVtbg0KICBoZWFkKDEwKSAjZGlzcGxheXMgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQoNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIHB1bGwoSU1EYi5TY29yZSklPiUgI3RvIGV4dHJhY3QgdGhlICdJTURiLlNjb3JlJyBjb2x1bW4NCiAgaGVhZCgxMCkgI2Rpc3BsYXlzIHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUNCiAgcHVsbChJTURiLlNjb3JlLCBUaXRsZSklPiUgI3RvIGV4dHJhY3QgdGhlICdJTURiLlNjb3JlJyBhbmQgJ1RpdGxlJyBjb2x1bW5zDQogIGhlYWQoMTApICNkaXNwbGF5cyB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUNCmBgYA0KDQojIyMgUm93IGZpbHRlcmluZw0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIGZpbHRlcihTZXJpZXMub3IuTW92aWUgPT0gIlNlcmllcyIpJT4lICN0byBmaWx0ZXIgdGhlIGRhbmUgZGF0YSBmcmFtZSB0byBzZWxlY3Qgb25seSByb3dzIHdoZXJlIHRoZSAnU2VyaWVzLm9yLk1vdmllJyBjb2x1bW4gaGFzIHRoZSB2YWx1ZSAiU2VyaWVzLiIgDQogIGhlYWQoMTApICNkaXNwbGF5cyB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIGZpbHRlcihJTURiLlNjb3JlID4gOCklPiUgI3RvIHNlbGVjdCBvbmx5IHJvd3Mgd2hlcmUgdGhlICdJTURiLlNjb3JlJyBjb2x1bW4gaGFzIGEgdmFsdWUgZ3JlYXRlciB0aGFuIDgNCiAgaGVhZCgxMCkgI2Rpc3BsYXlzIHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZQ0KYGBgDQoNCiMjIyBCb29sZWFuIG9wZXJhdG9ycywgQm9vbGVhbiBhbGdlYnJhLCBkZSBNb3JnYW4ncyBsYXdzDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIGZpbHRlcihJTURiLlNjb3JlID49IDggJiBTZXJpZXMub3IuTW92aWUgPT0gJ1NlcmllcycpJT4lICN0byBzZWxlY3Qgb25seSB0cm93cyB3aGVyZSB0aGUgJ0lNRGIuU2NvcmUnIGlzIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byA4IGFuZCB3aGVyZSB0aGUgJ1Nlcmllcy5vci5Nb3ZpZScgaXMgZXF1YWwgdG8gJ1NlcmllcycsIGl0IHJldHJpZXZlcyByb3dzIHdpdGggYSBoaWdoIElNRGIgc2NvcmUgdGhhdCBhcmUgc3BlY2lmaWNhbGx5IGNhdGVnb3JpemVkIGFzICdTZXJpZXMnLg0KICBoZWFkKDEwKSAjZGlzcGxheXMgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBmaWx0ZXIoSU1EYi5TY29yZSA+PSA5IHwgSU1EYi5Wb3RlcyA8IDEwMDApJT4lICNzZWxlY3RzIHJvd3MgdGhhdCBtZWV0IGVpdGhlciBvZiB0aGUgdHdvIGNvbmRpdGlvbnM6IHJvd3Mgd2hlcmUgdGhlICdJTURiLlNjb3JlJyBpcyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gOSwgcm93cyB3aGVyZSB0aGUgJ0lNRGIuVm90ZXMnIGlzIGxlc3MgdGhhbiAxMDAwLg0KICBoZWFkKDEwKSAjZGlzcGxheXMgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIHJlc3VsdGluZyBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBmaWx0ZXIoIShJTURiLlNjb3JlID49IDkgfCBJTURiLlZvdGVzIDwgMTAwMCkpJT4lICNzZWxlY3RzIHJvd3MgdGhhdCBkbyBub3QgbWVldCBlaXRoZXIgb2YgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOnJvd3Mgd2hlcmUgdGhlICdJTURiLlNjb3JlJyBpcyBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gOSwgcm93cyB3aGVyZSB0aGUgJ0lNRGIuVm90ZXMnIGlzIGxlc3MgdGhhbiAxMDAwDQogIGhlYWQoMTApICNkaXNwbGF5cyB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIGZpbHRlcighKElNRGIuU2NvcmUgPj0gOSkgJiAhKElNRGIuVm90ZXMgPCAxMDAwKSklPiUgI3NlbGVjdHMgcm93cyB0aGF0IGRvIG5vdCBtZWV0IGJvdGggb2YgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOiBlb3dzIHdoZXJlIHRoZSAnSU1EYi5TY29yZScgaXMgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDksIHJvd3Mgd2hlcmUgdGhlICdJTURiLlZvdGVzJyBpcyBsZXNzIHRoYW4gMTAwMCANCiAgaGVhZCgxMCkgI2Rpc3BsYXlzIHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSByZXN1bHRpbmcgZGF0YSBmcmFtZQ0KYGBgDQoNCiMjIyBDcmVhdGUgbmV3IGNvbHVtbnMNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBtdXRhdGUoc2NvcmVfY2F0ZWdvcnkgPSBpZl9lbHNlKElNRGIuU2NvcmUgPj0gNSwgJ0dvb2QnLCAnUG9vcicpKSAlPiUgI3RvIGFkZCBhIG5ldyBjb2x1bW4gY2FsbGVkICdzY29yZV9jYXRlZ29yeScgdG8gdGhlIGRhdGEgZnJhbWUuIFRoZSB2YWx1ZXMgaW4gdGhpcyBjb2x1bW4gYXJlIGFzc2lnbmVkIGJhc2VkIG9uIGEgY29uZGl0aW9uOiBJZiB0aGUgJ0lNRGIuU2NvcmUnIGlzIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byA1LCBpdCdzIGxhYmVsZWQgYXMgJ0dvb2QnOyBvdGhlcndpc2UsIGl0J3MgbGFiZWxlZCBhcyAnUG9vcicuDQogIHNlbGVjdChUaXRsZSwgSU1EYi5TY29yZSwgc2NvcmVfY2F0ZWdvcnkpJT4lICN0byBjaG9vc2UgYW5kIGRpc3BsYXkgb25seSB0aGUgJ1RpdGxlLCcgJ0lNRGIuU2NvcmUsJyBhbmQgJ3Njb3JlX2NhdGVnb3J5JyBjb2x1bW5zIGZyb20gdGhlIGRhdGEgZnJhbWUuDQogIGhlYWQoMTApICNkaXNwbGF5cyB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUNCg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUNCiAgdHJhbnNtdXRlKCAjdG8gY3JlYXRlIG5ldyB2YXJpYWJsZXMNCiAgICBSZWxlYXNlID0gUmVsZWFzZS5EYXRlICU+JSBhcy5EYXRlKGZvcm1hdCA9ICclbS8lZC8leScpICN0byB0YWtlIHRoZSAnUmVsZWFzZS5EYXRlJyBjb2x1bW4gYW5kIGNvbnZlcnRzIGl0IGludG8gYSBzcGVjaWZpZWQgZGF0ZSBmb3JtYXQuDQogICAgLE5ldGZsaXguUmVsZWFzZSA9IE5ldGZsaXguUmVsZWFzZS5EYXRlICU+JSBhcy5EYXRlKGZvcm1hdCA9ICclbS8lZC8leScpICN0byB0YWtlIHRoZSAnTmV0ZmxpeC5SZWxlYXNlJ2NvbHVtbiBhbmQgY29udmVydHMgaXQgaW50byBhIHNwZWNpZmllZCBkYXRlIGZvcm1hdC4NCiAgKQ0KYGBgDQoNCiMjIyMgQ2hhbGxlbmdlIDEuDQoNCioqQ0hBTExFTkdFIDE6KiogV2hhdCBpcyB0aGUgb2xkZXN0IFdvb2R5IEFsbGVuIGZpbG0gYXZhaWxhYmxlIG9uDQpOZXRmbGl4Pw0KDQpgYGB7ciBjaGFsbGVuZ2UxLCBlY2hvPVRSVUV9DQojIHlvdXIgY29kZSBnb2VzIGhlcmUNCmRhbmUgJT4lDQpmaWx0ZXIoRGlyZWN0b3IgPT0gIldvb2R5IEFsbGVuIikgJT4lDQogIG11dGF0ZShSZWxlYXNlID0gUmVsZWFzZS5EYXRlICU+JSBhcy5EYXRlKGZvcm1hdCA9ICclbS8lZC8lWScpKSAlPiUNCiAgbXV0YXRlKE9sZCA9IG1pbl9yYW5rKFJlbGVhc2UpKSAlPiUNCiAgZmlsdGVyKE9sZCA9PSAxKSAlPiUNCiAgc2VsZWN0KERpcmVjdG9yLCBSZWxlYXNlLCBPbGQsIFRpdGxlKQ0KYGBgDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIG11dGF0ZShzY29yZV9jYXRlZ29yeSA9IGNhc2Vfd2hlbigNCiAgICBJTURiLlNjb3JlIDw9IDIgfiAnVmVyeSBQb29yJw0KICAgICxJTURiLlNjb3JlIDw9IDQgfiAnUG9vcicNCiAgICAsSU1EYi5TY29yZSA8PSA2IH4gJ01lZGl1bScNCiAgICAsSU1EYi5TY29yZSA8PSA4IH4gJ0dvb2QnDQogICAgLElNRGIuU2NvcmUgPD0gMTAgfiAnVmVyeSBHb29kJw0KICAgICkpICU+JSAjY2F0ZWdvcml6ZXMgJ0lNRGIuU2NvcmUnIGludG8gZGlmZmVyZW50IGNhdGVnb3JpZXMgJ1ZlcnkgUG9vciwnICdQb29yLCcgJ01lZGl1bSwnICdHb29kLCcgYW5kICdWZXJ5IEdvb2QnIGJhc2VkIG9uIHRoZSBzY29yZSByYW5nZXMNCiAgc2VsZWN0KFRpdGxlLCBJTURiLlNjb3JlLCBzY29yZV9jYXRlZ29yeSklPiUgDQogIGhlYWQoMTApICNzZWxlY3RzIGFuZCBkaXNwbGF5cyB0aGUgJ1RpdGxlLCcgJ0lNRGIuU2NvcmUsJyBhbmQgJ3Njb3JlX2NhdGVnb3J5JyBjb2x1bW5zIGZvciB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgbW9kaWZpZWQgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUNCiAgbXV0YXRlKGF2Z19zY29yZSA9IG1lYW4oYyhJTURiLlNjb3JlICogMTANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAsSGlkZGVuLkdlbS5TY29yZSAqIDEwDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLFJvdHRlbi5Ub21hdG9lcy5TY29yZQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICxNZXRhY3JpdGljLlNjb3JlKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAsbmEucm0gPSBUUlVFKSAlPiUgI2NhbGN1bGF0ZXMgdGhlIGF2ZXJhZ2Ugc2NvcmUgZnJvbSBhIGNvbWJpbmF0aW9uIG9mIGRpZmZlcmVudCBzY29yZSBjb2x1bW5zDQogICAgICAgICAgIHJvdW5kKDIpKSAlPiUgI1RoZSByZXN1bHRpbmcgYXZlcmFnZSBzY29yZSBpcyB0aGVuIHJvdW5kZWQgdG8gdHdvIGRlY2ltYWwgcGxhY2VzDQogIHNlbGVjdChUaXRsZSwgYXZnX3Njb3JlKSU+JSANCiAgaGVhZCgxMCkgI3NlbGVjdHMgYW5kIGRpc3BsYXlzIHRoZSAnVGl0bGUnIGFuZCAnYXZnX3Njb3JlJyBjb2x1bW5zIGZvciB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgbW9kaWZpZWQgZGF0YSBmcmFtZQ0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIHJvd3dpc2UoKSAlPiUgI2Vuc3VyZXMgdGhhdCB0aGUgbWVhbigpIGZ1bmN0aW9uIGNhbGN1bGF0ZXMgdGhlIGF2ZXJhZ2UgZm9yIGVhY2ggcm93IHJhdGhlciB0aGFuIGFjcm9zcyBhbGwgcm93cw0KICBtdXRhdGUoYXZnX3Njb3JlID0gbWVhbihjKElNRGIuU2NvcmUgKiAxMA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICxIaWRkZW4uR2VtLlNjb3JlICogMTANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAsUm90dGVuLlRvbWF0b2VzLlNjb3JlDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLE1ldGFjcml0aWMuU2NvcmUpDQogICAgICAgICAgICAgICAgICAgICAgICAgICxuYS5ybSA9IFRSVUUpICU+JSAjY2FsY3VsYXRlcyB0aGUgYXZlcmFnZSBzY29yZSBmcm9tIGEgY29tYmluYXRpb24gb2YgZGlmZmVyZW50IHNjb3JlIGNvbHVtbnMNCiAgICAgICAgICAgcm91bmQoMikpICU+JSAjVGhlIHJlc3VsdGluZyBhdmVyYWdlIHNjb3JlIGlzIHRoZW4gcm91bmRlZCB0byB0d28gZGVjaW1hbCBwbGFjZXMNCiAgc2VsZWN0KFRpdGxlLCBhdmdfc2NvcmUpJT4lIA0KICBoZWFkKDEwKSAjc2VsZWN0cyBhbmQgZGlzcGxheXMgdGhlICdUaXRsZScgYW5kICdhdmdfc2NvcmUnIGNvbHVtbnMgZm9yIHRoZSBmaXJzdCAxMCByb3dzIG9mIHRoZSBtb2RpZmllZCBkYXRhIGZyYW1lDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBtdXRhdGUoUG9wdWxhcml0eSA9IGlmX2Vsc2UoSU1EYi5Wb3RlcyA+IHF1YW50aWxlKElNRGIuVm90ZXMsIDAuOTAsIG5hLnJtID0gVFJVRSksICdIaWdoJywgJ05vdCBIaWdoJykpICU+JSAjdG8gYWRkIGEgbmV3ICdQb3B1bGFyaXR5JyBjb2x1bW4gYmFzZWQgb24gdGhlIGNvbmRpdGlvbiB0aGF0IGNoZWNrcyBpZiAnSU1EYi5Wb3RlcycgaXMgZ3JlYXRlciB0aGFuIHRoZSA5MHRoIHBlcmNlbnRpbGUgb2YgJ0lNRGIuVm90ZXMnDQogIHJlbG9jYXRlKFBvcHVsYXJpdHksIC5hZnRlciA9IFRpdGxlKSAjdG8gbW92ZSB0aGUgJ1BvcHVsYXJpdHknIGNvbHVtbiBpbW1lZGlhdGVseSBhZnRlciB0aGUgJ1RpdGxlJyBjb2x1bW4gaW4gdGhlIGRhdGEgZnJhbWUNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIHJlbmFtZSgNCiAgICBUeXR1bCA9IFRpdGxlDQogICAgLEdhdHVuZWsgPSBHZW5yZQ0KICApICN0byByZW5hbWUgdGhlICdUaXRsZScgY29sdW1uIHRvICdUeXR1bCcgYW5kIHRoZSAnR2VucmUnIGNvbHVtbiB0byAnR2F0dW5laycgaW4gdGhlIGRhdGEgZnJhbWUNCmBgYA0KIyMjIE1pc3NpbmcgVmFsdWVzDQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBzYXBwbHkoZnVuY3Rpb24oeCkgaXMubmEoeCkgJT4lIHN1bSgpKSAjcmV0dXJucyBhIGNvdW50IG9mIG1pc3NpbmcgdmFsdWVzIGZvciBlYWNoIGNvbHVtbiBpbiB0aGUgZGF0YSBmcmFtZQ0KDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBkcm9wX25hKEhpZGRlbi5HZW0uU2NvcmUpICN0byByZW1vdmUgcm93cyB3aGVyZSB0aGUgJ0hpZGRlbi5HZW0uU2NvcmUnIGNvbHVtbiBoYXMgbWlzc2luZyB2YWx1ZXMgKE5BcykuIEl0IGtlZXBzIG9ubHkgdGhlIHJvd3Mgd2l0aCBub24tbWlzc2luZyB2YWx1ZXMgaW4gdGhlIHNwZWNpZmllZCBjb2x1bW4uDQoNCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIG11dGF0ZShIaWRkZW4uR2VtLlNjb3JlID0gcmVwbGFjZV9uYShIaWRkZW4uR2VtLlNjb3JlLCBtZWRpYW4oSGlkZGVuLkdlbS5TY29yZSwgbmEucm0gPSBUUlVFKSkpICU+JSAjdG8gcmVwbGFjZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgJ0hpZGRlbi5HZW0uU2NvcmUnIGNvbHVtbiB3aXRoIHRoZSBtZWRpYW4gb2YgdGhhdCBjb2x1bW4NCiAgc2FwcGx5KGZ1bmN0aW9uKHgpIGlzLm5hKHgpICU+JSBzdW0oKSkgI3RvIGNvdW50IHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgaW4gZWFjaCBjb2x1bW4NCg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUNCiAgcmVwbGFjZV9uYShsaXN0KEhpZGRlbi5HZW0uU2NvcmUgPSBtZWRpYW4oZGFuZSRIaWRkZW4uR2VtLlNjb3JlLCBuYS5ybSA9IFRSVUUpKSkgJT4lICN0byByZXBsYWNlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSAnSGlkZGVuLkdlbS5TY29yZScgY29sdW1uIHdpdGggdGhlIG1lZGlhbiBvZiB0aGUgJ0hpZGRlbi5HZW0uU2NvcmUnIGNvbHVtbiBmcm9tIHRoZSBlbnRpcmUgZGFuZSBkYXRhIGZyYW1lLiBUaGUgbmEucm0gPSBUUlVFIGFyZ3VtZW50IGVuc3VyZXMgdGhhdCB0aGUgbWVkaWFuIGlzIGNhbGN1bGF0ZWQgd2l0aG91dCBjb25zaWRlcmluZyBtaXNzaW5nIHZhbHVlcy4NCiAgc2FwcGx5KGZ1bmN0aW9uKHgpIGlzLm5hKHgpICU+JSBzdW0oKSkgI3RvIGNvdW50IHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgaW4gZWFjaCBjb2x1bW4gYWZ0ZXIgdGhpcyByZXBsYWNlbWVudA0KYGBgDQoNCiMjIyBUZXh0IG1hbmlwdWxhdGlvbg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmdhdHVua2kgPSBkYW5lJEdlbnJlICU+JQ0KICBwYXN0ZTAoY29sbGFwc2UgPSAnLCAnKSAlPiUgI3Rha2VzIHRoZSAnR2VucmUnIGNvbHVtbiBmcm9tIHRoZSBkYXRhIGZyYW1lIGFuZCBjb21iaW5lcyBhbGwgZ2VucmUgdmFsdWVzIGludG8gYSBzaW5nbGUgY29tbWEtc2VwYXJhdGVkIHN0cmluZy4NCiAgc3RyX2V4dHJhY3RfYWxsKCdbQS1aYS16XSsnKSAlPiUgI3RvIGV4dHJhY3Qgd29yZHMgKHByb2JhYmx5IGdlbnJlcykgZnJvbSB0aGUgY29uY2F0ZW5hdGVkIHN0cmluZw0KICB1bmxpc3QoKSAlPiUgI3RvIGNvbnZlcnQgdGhlIGxpc3Qgb2YgZXh0cmFjdGVkIGdlbnJlcyBpbnRvIGEgdmVjdG9yDQogIHRhYmxlKCkgJT4lICN0byBjcmVhdGUgYSBmcmVxdWVuY3kgdGFibGUgb2YgZ2VucmUgY291bnRzDQogIGFzLmRhdGEuZnJhbWUoKSAjdG8gY29udmVydCB0aGUgdGFibGUgdG8gYSBkYXRhIGZyYW1lDQoNCmdhdHVua2kgJT4lDQogIGFycmFuZ2UoLUZyZXEpICNhcnJhbmdlcyB0aGUgZGF0YSBmcmFtZSAnZ2F0dW5raScgaW4gb3JkZXIgYmFzZWQgb24gdGhlIGZyZXF1ZW5jeSAoJ0ZyZXEnKSBjb2x1bW4uIFRoaXMgcHJvdmlkZXMgYSBsaXN0IG9mIGdlbnJlcyBzb3J0ZWQgYnkgdGhlaXIgcG9wdWxhcml0eSwgd2l0aCB0aGUgbW9zdCBmcmVxdWVudCBnZW5yZXMgbGlzdGVkIGZpcnN0Lg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KDQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBtdXRhdGUocG9sYW5kX2F2YWlsYWJsZSA9IHN0cl9kZXRlY3QoQ291bnRyeS5BdmFpbGFiaWxpdHksICdQb2xhbmQnKSkgJT4lICN0byBjcmVhdGUgYSBuZXcgY29sdW1uICdwb2xhbmRfYXZhaWxhYmxlJyB0aGF0IGNoZWNrcyBpZiB0aGUgJ0NvdW50cnkuQXZhaWxhYmlsaXR5JyBjb2x1bW4gY29udGFpbnMgdGhlIHN0cmluZyAnUG9sYW5kJy4gSWYgaXQgZG9lcywgaXQgc2V0cyB0aGUgdmFsdWUgdG8gVFJVRS4NCiAgZmlsdGVyKHBvbGFuZF9hdmFpbGFibGUgPT0gVFJVRSkgJT4lICN0byBzZWxlY3Qgcm93cyB3aGVyZSAncG9sYW5kX2F2YWlsYWJsZScgaXMgVFJVRSwgbWVhbmluZyB0aGUgbW92aWUgaXMgYXZhaWxhYmxlIGluIFBvbGFuZA0KICBwdWxsKFRpdGxlKSU+JSAjZXh0cmFjdHMgdGhlICdUaXRsZScgY29sdW1uIGZyb20gdGhlIGZpbHRlcmVkIGRhdGEgZnJhbWUNCiAgaGVhZCgxMCkgI3RvIGRpc3BsYXkgdGhlIGZpcnN0IDEwIG1vdmllIHRpdGxlcyB0aGF0IGFyZSBhdmFpbGFibGUgaW4gUG9sYW5kIGJhc2VkIG9uIHRoZSAnQ291bnRyeS5BdmFpbGFiaWxpdHknIGNvbHVtbg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZSAlPiUgI3Rha2VzIHRoZSBkYW5lIGRhdGEgZnJhbWUNCiAgdW5pdGUoIA0KICAgIGNvbCA9ICdTY29yZXMnDQogICAgLGMoJ0hpZGRlbi5HZW0uU2NvcmUnLCAnSU1EYi5TY29yZScsICdSb3R0ZW4uVG9tYXRvZXMuU2NvcmUnLCAnTWV0YWNyaXRpYy5TY29yZScpICN0byBjb21iaW5lIHRoZSB2YWx1ZXMgZnJvbSB0aGUgY29sdW1ucyAnSGlkZGVuLkdlbS5TY29yZSwnICdJTURiLlNjb3JlLCcgJ1JvdHRlbi5Ub21hdG9lcy5TY29yZSwnIGFuZCAnTWV0YWNyaXRpYy5TY29yZScgaW50byBhIHNpbmdsZSBuZXcgY29sdW1uIGNhbGxlZCAnU2NvcmVzJy4NCiAgICAsc2VwID0gJywgJw0KICApICU+JSAjdG8gc3BlY2lmeSB0aGF0IHRoZSB2YWx1ZXMgZnJvbSB0aGVzZSBjb2x1bW5zIHNob3VsZCBiZSBzZXBhcmF0ZWQgYnkgYSBjb21tYSBhbmQgYSBzcGFjZSAoJywgJykNCiAgc2VsZWN0KFRpdGxlLCBTY29yZXMpJT4lICN0byBjaG9vc2UgYW5kIGRpc3BsYXkgb25seSB0aGUgJ1RpdGxlJyBhbmQgJ1Njb3JlcycgY29sdW1ucyBmcm9tIHRoZSBtb2RpZmllZCBkYXRhIGZyYW1lIA0KICBoZWFkKDEwKSAjdG8gZGlzcGxheSB0aGUgZmlyc3QgMTAgcm93cyBvZiB0aGUgbW9kaWZpZWQgZGF0YSBmcmFtZQ0KYGBgDQoNCiMjIyMgQ2hhbGxlbmdlIDIuDQoNCioqQ0hBTExFTkdFIDI6KiogV2hhdCBhcmUgdGhlIHRocmVlIGhpZ2hlc3QgcmF0ZWQgY29tZWRpZXMgYXZhaWxhYmxlIGluDQpQb2xpc2g/DQoNCmBgYHtyIGNoYWxsZW5nZTIsIGVjaG89VFJVRX0NCiMgeW91ciBjb2RlIGdvZXMgaGVyZQ0KZGFuZSAlPiUNCiAgZmlsdGVyKEdlbnJlID09IkNvbWVkeSIgJiBzdHJfZGV0ZWN0KENvdW50cnkuQXZhaWxhYmlsaXR5LCAiUG9sYW5kIikpICU+JQ0KICBtdXRhdGUoUmFua2luZyA9IG1pbl9yYW5rKC1JTURiLlNjb3JlKSkgJT4lDQogIGZpbHRlcihSYW5raW5nIDw9IDMpICU+JQ0KICBhcnJhbmdlKFJhbmtpbmcpICU+JQ0KICBzZWxlY3QoUmFua2luZywgVGl0bGUsIElNRGIuU2NvcmUsIENvdW50cnkuQXZhaWxhYmlsaXR5KSANCiAgDQpgYGANCg0KIyMjIyBDaGFsbGVuZ2UgMy4NCg0KKipDSEFMTEVOR0UgMzoqKiBGb3IgMjAxOSBhbmQgMjAyMCBwcm9kdWN0aW9ucywgd2hhdCBpcyB0aGUgYXZlcmFnZSB0aW1lDQpiZXR3ZWVuIHJlbGVhc2UgYW5kIGFwcGVhcmFuY2Ugb24gTmV0ZmxpeD8NCg0KYGBge3IgY2hhbGxlbmdlMywgZWNobz1UUlVFfQ0KIyB5b3VyIGNvZGUgZ29lcyBoZXJlDQpkYW5lICU+JQ0KICBtdXRhdGUoUmVsZWFzZSA9IFJlbGVhc2UuRGF0ZSAlPiUgYXMuRGF0ZShmb3JtYXQgPSAnJW0vJWQvJVknKQ0KICAgICxOZXRmbGl4LlJlbGVhc2UgPSBOZXRmbGl4LlJlbGVhc2UuRGF0ZSAlPiUgYXMuRGF0ZShmb3JtYXQgPSAnJW0vJWQvJVknKSkgJT4lDQogIGZpbHRlcih5ZWFyKFJlbGVhc2UpICVpbiUgYygyMDE5LDIwMjApKSAlPiUNCiAgbXV0YXRlKERJRkYgPSBOZXRmbGl4LlJlbGVhc2UgLSBSZWxlYXNlKSAlPiUNCiAgc2VsZWN0KFRpdGxlLCBSZWxlYXNlLCBOZXRmbGl4LlJlbGVhc2UsIERJRkYpICU+JQ0KICBzdW1tYXJpc2UoYXZnID0gbWVhbihESUZGKSkNCmBgYA0KDQojIyMjIENoYWxsZW5nZSA0Lg0KDQoqKkNIQUxMRU5HRSA0OioqIFdoYXQgYXJlIHRoZSBtb3N0IHBvcHVsYXIgdGFncyBmb3IgcHJvZHVjdGlvbnMNCmF2YWlsYWJsZSBpbiBQb2xpc2g/DQoNCmBgYHtyIGNoYWxsZW5nZTQsIGVjaG89VFJVRX0NCiMgeW91ciBjb2RlIGdvZXMgaGVyZQ0KZGFuZSAlPiUNCiAgZmlsdGVyKHN0cl9kZXRlY3QoQ291bnRyeS5BdmFpbGFiaWxpdHksICJQb2xhbmQiKSkgJT4lDQogIHNlbGVjdChUYWdzKSAlPiUNCiAgc2VwYXJhdGVfcm93cyhUYWdzLCBzZXAgPSAiLCIpICU+JQ0KICBjb3VudChUYWdzKSAlPiUNCiAgbXV0YXRlKHBvcHVsYXIgPSBtaW5fcmFuaygtbiksICkgJT4lDQogIGFycmFuZ2UocG9wdWxhcikNCmBgYA0KDQojIyMgRGF0YSBhZ2dyZWdhdGlvbg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmRhbmUgJT4lICN0YWtlcyB0aGUgZGFuZSBkYXRhIGZyYW1lDQogIGdyb3VwX2J5KFNlcmllcy5vci5Nb3ZpZSkgJT4lICN0byBncm91cCB0aGUgZGF0YSBieSB0aGUgJ1Nlcmllcy5vci5Nb3ZpZScgY29sdW1uDQogIHN1bW1hcml6ZSggI3RvIGNhbGN1bGF0ZSB2YXJpb3VzIHN1bW1hcnkgc3RhdGlzdGljcw0KICAgIGNvdW50ID0gbigpICNDb3VudHMgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMNCiAgICAsYXZnX2ltZGJfc2NvcmUgPSBtZWFuKElNRGIuU2NvcmUsIG5hLnJtID0gVFJVRSkgJT4lIHJvdW5kKDIpICNDYWxjdWxhdGVzIHRoZSBhdmVyYWdlICdJTURiLlNjb3JlJyB3aXRoaW4gZWFjaCBncm91cCB3aGlsZSByb3VuZGluZyB0byB0d28gZGVjaW1hbCBwbGFjZXMNCiAgICAsYXZnX2ltZGJfdm90ZXMgPSBtZWFuKElNRGIuVm90ZXMsIG5hLnJtID0gVFJVRSkgJT4lIHJvdW5kKDApICNDYWxjdWxhdGVzIHRoZSBhdmVyYWdlICdJTURiLlZvdGVzJyB3aXRoaW4gZWFjaCBncm91cCBhbmQgcm91bmRzIGl0IHRvIHRoZSBuZWFyZXN0IHdob2xlIG51bWJlcg0KICAgICxzdW1fYXdhcmRzID0gc3VtKEF3YXJkcy5SZWNlaXZlZCwgbmEucm0gPSBUUlVFKSAjU3VtcyB0aGUgJ0F3YXJkcy5SZWNlaXZlZCcgd2l0aGluIGVhY2ggZ3JvdXANCiAgKQ0KDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkYW5lICU+JSAjdGFrZXMgdGhlIGRhbmUgZGF0YSBmcmFtZQ0KICBncm91cF9ieShTZXJpZXMub3IuTW92aWUsIFJ1bnRpbWUpICU+JSAjdG8gZ3JvdXAgYnkgdHdvIHZhcmlhYmxlczogJ1Nlcmllcy5vci5Nb3ZpZScgYW5kICdSdW50aW1lJw0KICBzdW1tYXJpemUobiA9IG4oKSkgJT4lICNjYWxjdWxhdGVzIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIChjb3VudCkgaW4gZWFjaCBncm91cCBhbmQgYXNzaWducyBpdCB0byB0aGUgdmFyaWFibGUgJ24nDQogIGFycmFuZ2UoLW4pICN0byBzb3J0IGluIG9yZGVyIGJhc2VkIG9uIHRoZSBjb3VudCAoJ24nKQ0KYGBgDQoNCiMjIyMgQ2hhbGxlbmdlIDUuDQoNCioqQ0hBTExFTkdFIDU6KiogV2hhdCBhcmUgdGhlIGF2ZXJhZ2UgcmF0aW5ncyBvZiBmaWxtcyBwcm9kdWNlZCBpbiBlYWNoDQpkZWNhZGUgKGkuZS4sIDE5NjBzLCAxOTcwcywgMTk4MHMsIDE5OTBzLCBldGMuKT8NCg0KYGBge3IgY2hhbGxlbmdlNSwgZWNobz1UUlVFfQ0KIyB5b3VyIGNvZGUgZ29lcyBoZXJlDQpkYW5lICU+JQ0KICAgIG11dGF0ZShSZWxlYXNlID0gUmVsZWFzZS5EYXRlICU+JSBhcy5EYXRlKGZvcm1hdCA9ICclbS8lZC8lWScpDQogICAgLE5ldGZsaXguUmVsZWFzZSA9IE5ldGZsaXguUmVsZWFzZS5EYXRlICU+JSBhcy5EYXRlKGZvcm1hdCA9ICclbS8lZC8lWScpKSAlPiUNCiAgbXV0YXRlKERlY2FkZSA9IDEwICogKHllYXIoUmVsZWFzZSkgJS8lIDEwKSkgJT4lDQogIHNlbGVjdChUaXRsZSwgUmVsZWFzZSwgRGVjYWRlLCBJTURiLlNjb3JlKSAlPiUNCiAgZHJvcF9uYShEZWNhZGUsIElNRGIuU2NvcmUpICU+JQ0KICBncm91cF9ieShEZWNhZGUpICU+JQ0KICBzdW1tYXJpc2UoYXZnID0gbWVhbihJTURiLlNjb3JlKSkNCmBgYA0KDQojIyMgUGl2b3QgdGFibGVzLCBsb25nIGFuZCB3aWRlIGZvcm1hdCBkYXRhDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZV9waXZvdCA9IGRhbmUgJT4lDQogIHNlbGVjdChUaXRsZSwgZW5kc193aXRoKCdTY29yZScpKSAjc2VsZWN0cyB0aGUgJ1RpdGxlJyBjb2x1bW4gYW5kIGFsbCBjb2x1bW5zIHRoYXQgZW5kIHdpdGggJ1Njb3JlJyBmcm9tIHRoZSBkYW5lIGRhdGEgZnJhbWUsIGNyZWF0aW5nIGEgbmV3IGRhdGEgZnJhbWUgbmFtZWQgJ2RhbmVfcGl2b3QnIHdpdGggdGhlc2Ugc2VsZWN0ZWQgY29sdW1ucw0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZV9waXZvdCA9IGRhbmVfcGl2b3QgJT4lIA0KICBwaXZvdF9sb25nZXIoICNyZXNoYXBlcyB0aGUgZGF0YSBmcm9tIGEgd2lkZSBmb3JtYXQgdG8gYSBsb25nIGZvcm1hdCBieQ0KICAgIGNvbHMgPSAyOjUgI0NvbHVtbnMgMiB0aHJvdWdoIDUgKHdoaWNoIGNvbnRhaW4gdGhlIHZhcmlvdXMgc2NvcmVzKSBhcmUgYmVpbmcgbWVsdGVkIGludG8gYSBzaW5nbGUgY29sdW1uDQogICAgLG5hbWVzX3RvID0gJ0F0dHJpYnV0ZScjVGhlIGNvbHVtbiB0aGF0IHdpbGwgc3RvcmUgdGhlIG5hbWVzIG9mIHRoZSBtZWx0ZWQgdmFyaWFibGVzIGlzIG5hbWVkICdBdHRyaWJ1dGUnDQogICAgLHZhbHVlc190byA9ICdWYWx1ZScjVGhlIGNvbHVtbiB0aGF0IHdpbGwgc3RvcmUgdGhlIHZhbHVlcyBvZiB0aGUgbWVsdGVkIHZhcmlhYmxlcyBpcyBuYW1lZCAnVmFsdWUnDQogICkNCg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KZGFuZV9waXZvdCA9IGRhbmVfcGl2b3QgJT4lDQogIHBpdm90X3dpZGVyKCAjcmVzaGFwZXMgdGhlIGRhdGEgYmFjayBmcm9tIHRoZSBsb25nIGZvcm1hdCB0byBhIHdpZGUgZm9ybWF0IGJ5DQogICAgaWRfY29scyA9IDEgI1RoZSAnVGl0bGUnIGNvbHVtbiBpcyBzcGVjaWZpZWQgYXMgdGhlIGlkZW50aWZpZXIgY29sdW1uIHRoYXQgcmVtYWlucyB1bmNoYW5nZWQNCiAgICAsbmFtZXNfZnJvbSA9ICdBdHRyaWJ1dGUnI1RoZSAnQXR0cmlidXRlJyBjb2x1bW4gZnJvbSB0aGUgbG9uZyBmb3JtYXQgYmVjb21lcyB0aGUgbmV3IGNvbHVtbiBuYW1lcyBpbiB0aGUgd2lkZSBmb3JtYXQNCiAgICAsdmFsdWVzX2Zyb20gPSAnVmFsdWUnICNUaGUgJ1ZhbHVlJyBjb2x1bW4gZnJvbSB0aGUgbG9uZyBmb3JtYXQgYmVjb21lcyB0aGUgdmFsdWVzIGZvciB0aGUgbmV3IGNvbHVtbnMgaW4gdGhlIHdpZGUgZm9ybWF0DQogICkgI1Jlc3VsdDogdG8gY29udmVydCB0aGUgb3JpZ2luYWwgd2lkZSBmb3JtYXQgZGF0YSBmcmFtZSBpbnRvIGEgbG9uZyBmb3JtYXQsIGFuZCB0aGVuIGJhY2sgaW50byBhIHdpZGUgZm9ybWF0LCB3aGlsZSBrZWVwaW5nIHRoZSAnVGl0bGUnIGNvbHVtbiBhcyB0aGUgaWRlbnRpZmllci4gVGhlIHJlc3VsdCBpcyBhIGRhdGEgZnJhbWUgd2hlcmUgZWFjaCBzY29yZSB0eXBlIChlLmcuLCAnSGlkZGVuLkdlbS5TY29yZSwnICdJTURiLlNjb3JlLCcgZXRjLikgaXMgaW4gaXRzIG93biBjb2x1bW4uDQpgYGANCg0KIyMjICoqSm9pbmluZyB0YWJsZXMqKg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCm9jZW55X21ldGFjcml0aWMgPSBkYW5lICU+JQ0KICBzZWxlY3QoVGl0bGUsIE1ldGFjcml0aWMuU2NvcmUpICU+JSAjc2VsZWN0cyB0aGUgJ1RpdGxlJyBhbmQgJ01ldGFjcml0aWMuU2NvcmUnIGNvbHVtbnMgZnJvbSB0aGUgZGFuZSBkYXRhIGZyYW0NCiAgLlsxOjEwMCxdICU+JSAjZmlsdGVycyB0aGUgZGF0YSB0byByZXRhaW4gb25seSB0aGUgZmlyc3QgMTAwIHJvd3MNCiAgZHJvcF9uYSgpICNyZW1vdmVzIHJvd3Mgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgJ01ldGFjcml0aWMuU2NvcmUnIGNvbHVtbg0KYGBgDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0Kb2Nlbnlfcm90dGVuX3RvbWF0b2VzID0gZGFuZSAlPiUNCiAgc2VsZWN0KFRpdGxlLCBSb3R0ZW4uVG9tYXRvZXMuU2NvcmUpICU+JSAjc2VsZWN0cyB0aGUgJ1RpdGxlJyBhbmQgJ1JvdHRlbi5Ub21hdG9lcy5TY29yZScgY29sdW1ucyBmcm9tIGRhbmUgZGF0YSBmcmFtZQ0KICAuWzE6MTAwLF0gJT4lICNmaWx0ZXJzIHRoZSBkYXRhIHRvIHJldGFpbiBvbmx5IHRoZSBmaXJzdCAxMDAgcm93cw0KICBkcm9wX25hKCkgI3JlbW92ZXMgcm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzIChOQXMpIGluIHRoZSAnUm90dGVuLlRvbWF0b2VzLlNjb3JlJyBjb2x1bW4NCmBgYA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCm9jZW55X21ldGFjcml0aWMgJT4lDQogIGxlZnRfam9pbihvY2VueV9yb3R0ZW5fdG9tYXRvZXMsIGJ5ID0gYygnVGl0bGUnID0gJ1RpdGxlJykpICN0byBwZXJmb3JtIGEgbGVmdCBqb2luIGJldHdlZW4gJ29jZW55X21ldGFjcml0aWMnIGFuZCAnb2Nlbnlfcm90dGVuX3RvbWF0b2VzJyBvbiB0aGUgJ1RpdGxlJyBjb2x1bW4uIA0KI1RoZSByZXN1bHQgaXMgYSBtZXJnZWQgZGF0YSBmcmFtZSB0aGF0IGNvbWJpbmVzIGluZm9ybWF0aW9uIGZyb20gYm90aCAnb2NlbnlfbWV0YWNyaXRpYycgYW5kICdvY2VueV9yb3R0ZW5fdG9tYXRvZXMsJyB3aGVyZSByb3dzIHdpdGggbWF0Y2hpbmcgJ1RpdGxlJyB2YWx1ZXMgYXJlIGpvaW5lZCB0b2dldGhlci4gDQpgYGA=