Google Data Analytics Professional Certificate capstone case study track one Cyclistic bike-share analysis

Prepare data

Set up initial environment by loading packages:

library(tidyverse)
library(magrittr)
library(rmarkdown)

Import files Divvy data files from October 2021-September 2022 and simplify file names:

Oct_2021 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202110-divvy-tripdata.csv")
Nov_2021 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202111-divvy-tripdata.csv")
Dec_2021 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202112-divvy-tripdata.csv")
Jan_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202201-divvy-tripdata.csv")
Feb_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202202-divvy-tripdata.csv")
Mar_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202203-divvy-tripdata.csv")
Apr_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202204-divvy-tripdata.csv")
May_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202205-divvy-tripdata.csv")
June_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202206-divvy-tripdata.csv")
July_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202207-divvy-tripdata.csv")
Aug_2022 <- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202208-divvy-tripdata.csv")
Sept_2022<- read_csv("~/Desktop/FINAL PROJECT/2021-2022 original files/202209-divvy-tripdata.csv")

Check each file for proper import by column and row count (not including header):

dim(Oct_2021)
[1] 631226     13
dim(Nov_2021)
[1] 359978     13
dim(Dec_2021)
[1] 247540     13
dim(Jan_2022)
[1] 103770     13
dim(Feb_2022)
[1] 115609     13
dim(Mar_2022)
[1] 284042     13
dim(Apr_2022)
[1] 371249     13
dim(May_2022)
[1] 634858     13
dim(June_2022)
[1] 769204     13
dim(July_2022)
[1] 823488     13
dim(Aug_2022)
[1] 785932     13
dim(Sept_2022)
[1] 701339     13

Check that all column names are in agreement:

colnames(Oct_2021)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Nov_2021)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Dec_2021)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Jan_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Feb_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Mar_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Apr_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(May_2022) 
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(June_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(July_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Aug_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     
colnames(Sept_2022)
 [1] "ride_id"            "rideable_type"      "started_at"        
 [4] "ended_at"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     

Check a sample of Oct_2021 to get a feel for the data frame:

Note many NAs.

View(Oct_2021)

Process (wrangling, cleaning, and transformation)

Columns in agreement, so bind into fiscal and seasonal quarters:

fiscal_year_2021_2022_1Q <- bind_rows (Oct_2021, Nov_2021, Dec_2021)
fiscal_year_2021_2022_2Q <- bind_rows (Jan_2022, Feb_2022, Mar_2022)
fiscal_year_2021_2022_3Q <- bind_rows (Apr_2022, May_2022, June_2022)
fiscal_year_2021_2022_4Q <- bind_rows (July_2022, Aug_2022, Sept_2022)

Check row and column counts:

dim(fiscal_year_2021_2022_1Q)
[1] 1238744      13
dim(fiscal_year_2021_2022_2Q)
[1] 503421     13
dim(fiscal_year_2021_2022_3Q)
[1] 1775311      13
dim(fiscal_year_2021_2022_4Q)
[1] 2310759      13

Bind all quarters into new data frame entitled “cyclistic_2021_2022”:

cyclistic_2021_2022 <- bind_rows (fiscal_year_2021_2022_1Q,fiscal_year_2021_2022_2Q,fiscal_year_2021_2022_3Q,fiscal_year_2021_2022_4Q)

Check that files bound and check out new data frame:

glimpse(cyclistic_2021_2022)
Rows: 5,828,235
Columns: 13
$ ride_id            <chr> "620BC6107255BF4C", "4471C70731AB2E45", "26CA69D43D15EE14…
$ rideable_type      <chr> "electric_bike", "electric_bike", "electric_bike", "elect…
$ started_at         <dttm> 2021-10-22 12:46:42, 2021-10-21 09:12:37, 2021-10-16 16:…
$ ended_at           <dttm> 2021-10-22 12:49:50, 2021-10-21 09:14:14, 2021-10-16 16:…
$ start_station_name <chr> "Kingsbury St & Kinzie St", NA, NA, NA, NA, NA, NA, NA, N…
$ start_station_id   <chr> "KA1503000043", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ end_station_name   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ end_station_id     <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ start_lat          <dbl> 41.88919, 41.93000, 41.92000, 41.92000, 41.89000, 41.8900…
$ start_lng          <dbl> -87.63850, -87.70000, -87.70000, -87.69000, -87.71000, -8…
$ end_lat            <dbl> 41.89000, 41.93000, 41.94000, 41.92000, 41.89000, 41.9300…
$ end_lng            <dbl> -87.63000, -87.71000, -87.72000, -87.69000, -87.69000, -8…
$ member_casual      <chr> "member", "member", "member", "member", "member", "member…

Check out the variables for the columns rideable_type and member_casual:

table(cyclistic_2021_2022$member_casual)

 casual  member 
2401286 3426949 
table(cyclistic_2021_2022$rideable_type)

 classic_bike   docked_bike electric_bike 
      2740516        192475       2895244 

Rename columns ride_id, started_at, ended_at, rideable_type, and member_casual:

cyclistic_2021_2022 <- cyclistic_2021_2022 %>% 
  rename(trip_id = ride_id
         ,bike_type = rideable_type
         ,start_time = started_at
         ,end_time = ended_at
         ,user_type = member_casual)

Check new column names:

colnames(cyclistic_2021_2022)
 [1] "trip_id"            "bike_type"          "start_time"        
 [4] "end_time"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "user_type"         

Check data frame for missing values:

any(is.na(cyclistic_2021_2022))
[1] TRUE

Set up cleaning environment and perform thorough review of data frame:

library(skimr)
library(janitor)
skim(cyclistic_2021_2022)
── Data Summary ────────────────────────
                           Values             
Name                       cyclistic_2021_2022
Number of rows             5828235            
Number of columns          13                 
_______________________                       
Column type frequency:                        
  character                7                  
  numeric                  4                  
  POSIXct                  2                  
________________________                      
Group variables            None               

Note that there are:

  • 5828235 rows the trip_id is a consistent string length at 16 characters

  • 3 unique types of bikes

  • 1591 unique start stations, which do not match the unique start station ids at 1302

  • 1609 unique end stations, which do not match the unique end station ids at 1309

  • Both of these suggest that the station names are not consistent

  • Also, curious that the number of unique start and end stations is not the same.

  • 895032 start stations and ids each are missing

  • 958227 end stations and ids each are missing

  • 0 missing start latitudes or longitudes

  • 5844 missing end latitudes and longitudes each

  • 2 unique user types no white spaces

Remove rows with missing values and check new row and column count. Create draft of new data frame:

cyclistic_2021_2022_V2 <- cyclistic_2021_2022 %>% drop_na(c(trip_id
,bike_type
,start_time
,end_time
,start_station_name
,start_station_id
,end_station_name
,end_station_id
,start_lat
,start_lng
,end_lat
,end_lng
,user_type))
dim(cyclistic_2021_2022_V2)
[1] 4474141      13

Recheck for duplicate ride_id values:

sum(duplicated(cyclistic_2021_2022_V2$trip_id))
[1] 0

Make sure that column names are unique and consistent:

cyclistic_2021_2022_V2 <- clean_names(cyclistic_2021_2022_V2)

Recheck number of distinct start and end station names:

n_distinct(cyclistic_2021_2022_V2$start_station_name)
[1] 1480
n_distinct(cyclistic_2021_2022_V2$end_station_name)
[1] 1513

Recheck the data frame:

skim(cyclistic_2021_2022_V2)
── Data Summary ────────────────────────
                           Values                
Name                       cyclistic_2021_2022_V2
Number of rows             4474141               
Number of columns          13                    
_______________________                          
Column type frequency:                           
  character                7                     
  numeric                  4                     
  POSIXct                  2                     
________________________                         
Group variables            None                  

Note that there are now:

  • 5828235 rows went to 4474141

  • 1480 unique start stations, which do not match the unique start station ids at 1252

  • 1513 unique end stations, which do not match the unique end station ids at 1266

  • Also, curious that the number of unique start and end stations is not the same.

  • 0 start stations and ids are missing

  • 0 end stations and ids are missing

  • 0 missing start latitudes or longitudes

  • 0 missing end latitudes and longitudes

  • 2 unique user types

Check start and end station names for maintenance and temp stations:

Remove warehouse, repair, and charging stations from start_station_name. Create new draft of data frame:

cyclistic_2021_2022_V3 <- cyclistic_2021_2022_V2[!(cyclistic_2021_2022_V2$start_station_name=="Base - 2132 W Hubbard" | 
cyclistic_2021_2022_V2$start_station_name=="Base - 2132 W Hubbard Warehouse" |  
cyclistic_2021_2022_V2$start_station_name=="Base - 2132 W Hubbard Warehouse" |  
cyclistic_2021_2022_V2$start_station_name=="Hastings WH 2" | 
cyclistic_2021_2022_V2$start_station_name=="DIVVY CASSETTE REPAIR MOBILE STATION" | 
cyclistic_2021_2022_V2$start_station_name=="Throop/Hastings Mobile Station" | 
cyclistic_2021_2022_V2$start_station_name=="Bissell St & Armitage Ave - Charging" | 
cyclistic_2021_2022_V2$start_station_name=="Lincoln Ave & Roscoe St - Charging" | 
cyclistic_2021_2022_V2$start_station_name=="Pawel Bialowas - Test- PBSC charging station" | 
cyclistic_2021_2022_V2$start_station_name=="Wilton Ave & Diversey Pkwy - Charging"),]

Remove warehouse, repair, and charging stations from end_station_name:

cyclistic_2021_2022_V3 <-  cyclistic_2021_2022_V3[!(cyclistic_2021_2022_V3$end_station_name=="Base - 2132 W Hubbard" |
cyclistic_2021_2022_V3$end_station_name=="Base - 2132 W Hubbard Warehouse" |  
cyclistic_2021_2022_V3$end_station_name=="Base - 2132 W Hubbard Warehouse" |  
cyclistic_2021_2022_V3$end_station_name=="Hastings WH 2" |
cyclistic_2021_2022_V3$end_station_name=="DIVVY CASSETTE REPAIR MOBILE STATION" |
cyclistic_2021_2022_V3$end_station_name=="Throop/Hastings Mobile Station" |
cyclistic_2021_2022_V3$end_station_name=="Bissell St & Armitage Ave - Charging" |
cyclistic_2021_2022_V3$end_station_name=="Lincoln Ave & Roscoe St - Charging" |
cyclistic_2021_2022_V3$end_station_name=="Pawel Bialowas - Test- PBSC charging station" |
cyclistic_2021_2022_V3$end_station_name=="Wilton Ave & Diversey Pkwy - Charging"),]

Recheck the data frame:

skim(cyclistic_2021_2022_V3)
── Data Summary ────────────────────────
                           Values                
Name                       cyclistic_2021_2022_V3
Number of rows             4472599               
Number of columns          13                    
_______________________                          
Column type frequency:                           
  character                7                     
  numeric                  4                     
  POSIXct                  2                     
________________________                         
Group variables            None                  

Note that there are now:

  • 5828235 rows became 4474141 became 4472599

  • 1472 unique start stations, which do not match the unique start station ids at 1245

  • 1502 unique end stations, which do not match the unique end station ids at 1258

  • 0 start stations and ids are missing

  • 0 end stations and ids are missing

  • 0 missing start latitudes or longitudes

  • 0 missing end latitudes and longitudes

  • 2 unique user types

Create new columns: date, month, day, year, and day_of_the_week for aggregation:

cyclistic_2021_2022_V3$date <- as.Date(cyclistic_2021_2022_V3$start_time)
cyclistic_2021_2022_V3$month <- format(as.Date(cyclistic_2021_2022_V3$date), "%m")
cyclistic_2021_2022_V3$day <- format(as.Date(cyclistic_2021_2022_V3$date), "%d")
cyclistic_2021_2022_V3$year <- format(as.Date(cyclistic_2021_2022_V3$date), "%Y")
cyclistic_2021_2022_V3$day_of_week <- format(as.Date(cyclistic_2021_2022_V3$date), "%A")
colnames(cyclistic_2021_2022_V3)
 [1] "trip_id"            "bike_type"          "start_time"        
 [4] "end_time"           "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"     "start_lat"         
[10] "start_lng"          "end_lat"            "end_lng"           
[13] "user_type"          "date"               "month"             
[16] "day"                "year"               "day_of_week"       

Check that new columns created properly:

Note to change the manner by which a month name is referred from a number to a name.

View(cyclistic_2021_2022_V3)

Create column for trip duration, check that it was created in difftime:

Note to retype to integer.

cyclistic_2021_2022_V3$trip_duration<- difftime(cyclistic_2021_2022_V3$end_time, cyclistic_2021_2022_V3$start_time)
glimpse(cyclistic_2021_2022_V3)
Rows: 4,472,599
Columns: 19
$ trip_id            <chr> "614B15BC42810184", "ADCC6E3CF9C04688", "6184CC57243AEF3C…
$ bike_type          <chr> "docked_bike", "classic_bike", "docked_bike", "docked_bik…
$ start_time         <dttm> 2021-10-05 10:56:05, 2021-10-06 13:55:33, 2021-10-16 10:…
$ end_time           <dttm> 2021-10-05 11:38:48, 2021-10-06 13:58:16, 2021-10-16 12:…
$ start_station_name <chr> "Michigan Ave & Oak St", "Desplaines St & Kinzie St", "Mi…
$ start_station_id   <chr> "13042", "TA1306000003", "13042", "13042", "KA1503000043"…
$ end_station_name   <chr> "Michigan Ave & Oak St", "Kingsbury St & Kinzie St", "Mic…
$ end_station_id     <chr> "13042", "KA1503000043", "13042", "13042", "TA1306000003"…
$ start_lat          <dbl> 41.90096, 41.88872, 41.90096, 41.90096, 41.88918, 42.0582…
$ start_lng          <dbl> -87.62378, -87.64445, -87.62378, -87.62378, -87.63851, -8…
$ end_lat            <dbl> 41.90096, 41.88918, 41.90096, 41.90096, 41.88872, 42.0582…
$ end_lng            <dbl> -87.62378, -87.63851, -87.62378, -87.62378, -87.64445, -8…
$ user_type          <chr> "casual", "member", "casual", "casual", "member", "member…
$ date               <date> 2021-10-05, 2021-10-06, 2021-10-16, 2021-10-24, 2021-10-…
$ month              <chr> "10", "10", "10", "10", "10", "10", "10", "10", "10", "10…
$ day                <chr> "05", "06", "16", "24", "23", "25", "01", "21", "08", "31…
$ year               <chr> "2021", "2021", "2021", "2021", "2021", "2021", "2021", "…
$ day_of_week        <chr> "Tuesday", "Wednesday", "Saturday", "Sunday", "Saturday",…
$ trip_duration      <drtn> 2563 secs, 163 secs, 6097 secs, 7587 secs, 125 secs, 307…

Change trip_duration data type to integer for aggregation:

Note that trip_duration has trips under 60 seconds long (-7621 seconds) and trips over 86400 seconds long (over 24hrs [2442301 seconds]). Note that these outliers are likely representative of false starts, break downs, or theft and should be removed.

cyclistic_2021_2022_V3$trip_duration<- as.numeric(as.character(cyclistic_2021_2022_V3$trip_duration))
summary(cyclistic_2021_2022_V3)
   trip_id           bike_type           start_time                    
 Length:4472599     Length:4472599     Min.   :2021-10-01 00:00:09.00  
 Class :character   Class :character   1st Qu.:2022-03-05 17:19:39.50  
 Mode  :character   Mode  :character   Median :2022-06-09 21:10:45.00  
                                       Mean   :2022-05-08 21:08:27.41  
                                       3rd Qu.:2022-08-02 08:37:36.00  
                                       Max.   :2022-09-30 23:59:56.00  
    end_time                     start_station_name start_station_id  
 Min.   :2021-10-01 00:03:51.0   Length:4472599     Length:4472599    
 1st Qu.:2022-03-05 17:48:25.5   Class :character   Class :character  
 Median :2022-06-09 21:29:44.0   Mode  :character   Mode  :character  
 Mean   :2022-05-08 21:25:53.9                                        
 3rd Qu.:2022-08-02 08:51:07.5                                        
 Max.   :2022-10-01 14:22:35.0                                        
 end_station_name   end_station_id       start_lat       start_lng     
 Length:4472599     Length:4472599     Min.   :41.65   Min.   :-87.83  
 Class :character   Class :character   1st Qu.:41.88   1st Qu.:-87.66  
 Mode  :character   Mode  :character   Median :41.90   Median :-87.64  
                                       Mean   :41.90   Mean   :-87.64  
                                       3rd Qu.:41.93   3rd Qu.:-87.63  
                                       Max.   :42.06   Max.   :-87.53  
    end_lat         end_lng        user_type              date           
 Min.   :41.65   Min.   :-87.83   Length:4472599     Min.   :2021-10-01  
 1st Qu.:41.88   1st Qu.:-87.66   Class :character   1st Qu.:2022-03-05  
 Median :41.90   Median :-87.64   Mode  :character   Median :2022-06-09  
 Mean   :41.90   Mean   :-87.64                      Mean   :2022-05-08  
 3rd Qu.:41.93   3rd Qu.:-87.63                      3rd Qu.:2022-08-02  
 Max.   :42.06   Max.   :-87.53                      Max.   :2022-09-30  
    month               day                year           day_of_week       
 Length:4472599     Length:4472599     Length:4472599     Length:4472599    
 Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                            
                                                                            
                                                                            
 trip_duration    
 Min.   :  -7621  
 1st Qu.:    370  
 Median :    647  
 Mean   :   1046  
 3rd Qu.:   1160  
 Max.   :2442301  

Count number of trip_duration rows that are equal to or over 60 seconds and equal to or over 86400 seconds:

Note that 74443+269 = 74712.

length(which(cyclistic_2021_2022_V3$trip_duration <= 60))
[1] 74443
length(which(cyclistic_2021_2022_V3$trip_duration >= 86400))
[1] 269

Remove trips with false starts (60) or over 24hrs (86400) and create final version of the data frame:

Note that 4472599-74712 = 4397887.

cyclistic_2021_2022_CLEAN <- cyclistic_2021_2022_V3 %>%
  filter(trip_duration > 60 & trip_duration < 86400)
dim(cyclistic_2021_2022_CLEAN)
[1] 4397887      19

Recheck for outliers:

Note that there were none.

length(which(cyclistic_2021_2022_CLEAN$trip_duration <= 60))
[1] 0
length(which(cyclistic_2021_2022_CLEAN$trip_duration >= 86400))
[1] 0

Create csv file “cyclistic_2021_2022_CLEAN”:

write.csv(cyclistic_2021_2022_CLEAN, “cyclistic_2021_2022_CLEAN.csv”)

Streamline working data frame name:

cyclistic_analysis<- cyclistic_2021_2022_CLEAN

Aggregate, Analyze, and Visualize

Set up environment for more clear aggregation:

library(scales)
library(data.table)
library(formattable)
improvement_formatter <- formatter("span", style = x ~ style(font.weight = "bold", color = ifelse(x > .50, "tomato", ifelse(x < .50, "steelblue", "black")))
                                   , x ~ icontext(ifelse (x > .50, "arrow-up", "arrow-down"), x))

Count rides and their percentage

Count rides and their percentage of total by user type:

user_percent <- cyclistic_analysis %>%
  group_by(user_type) %>%
  summarize(total_rides = n()) %>%
  mutate(percent=percent(total_rides/sum(total_rides),0))
formattable(user_percent,
             align =c("l", "c","r"), 
             list('user_type' = color_tile("seashell2", "seashell3"), 'percent' = improvement_formatter))

Plot percentage of total rides by user type:

ggplot(user_percent, aes(x = "", y = percent, fill = user_type)) +
  geom_col(color = "black") +
  geom_label(aes(label = percent), color = c("white", 1), position = position_stack(vjust = 0.5),show.legend = FALSE) +
  guides(fill = guide_legend(title = "Percentage of Total Rides by User Type")) +
  scale_fill_viridis_d() +
  coord_polar(theta = "y") +
  theme_void()

Count rides by user type by month (total = 4397887):

Note that month references should be changed to names.

table(cyclistic_2021_2022_CLEAN$month)

    01     02     03     04     05     06     07     08     09     10     11     12 
 79015  87606 212719 268413 493923 609513 630474 593928 525033 471244 252093 173926 

Change month reference from integer to name:

Note that names are out of order now that data type is changed to string.

cyclistic_analysis <- cyclistic_2021_2022_CLEAN %>% mutate(month = month.abb[as.numeric(month)])
table(cyclistic_analysis$month)

   Apr    Aug    Dec    Feb    Jan    Jul    Jun    Mar    May    Nov    Oct    Sep 
268413 593928 173926  87606  79015 630474 609513 212719 493923 252093 471244 525033 

Put months in order:

cyclistic_analysis$month <- ordered(cyclistic_analysis$month, levels=c("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug", "Sep", "Oct","Nov", "Dec"))
table(cyclistic_analysis$month)

   Jan    Feb    Mar    Apr    May    Jun    Jul    Aug    Sep    Oct    Nov    Dec 
 79015  87606 212719 268413 493923 609513 630474 593928 525033 471244 252093 173926 

Count rides and their percentage by user type by month:

rm(month_totals)
 rides_by_month<- cyclistic_analysis %>%
  group_by(month, user_type) %>%
  summarize(total_rides = n()) %>%
  mutate(percent = (percent(total_rides/sum(total_rides),0)))
formattable(rides_by_month,
             align =c("l","c","c"), 
           list('month' = color_tile("pink", "lightblue"), 'percent' = improvement_formatter))

Plot ride count by user type by month:

cyclistic_analysis%>%
group_by(user_type, month)%>%
summarize(number_of_rides = n())%>%
arrange(user_type, month)%>%
ggplot(aes(x = month, y = number_of_rides, fill = user_type)) + geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend(title = "User Type"))+
  ggtitle("Ride Count by User by Month")

Percentage rides by user type by month:

cyclistic_analysis%>%
group_by(month, user_type)%>%
summarize(total_rides = n())%>%
mutate(percent = percent(total_rides / sum(total_rides)))%>%
arrange(user_type, month)%>%
ggplot(aes(x = month, y = percent, fill = user_type)) + geom_col(position = "dodge", color="black") + scale_fill_hue(l=40)+
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Percentage Rides by User by Month")

Check total ride count for each user type by day of the week (total = 4397887):

table(cyclistic_2021_2022_CLEAN$day_of_week)

   Friday    Monday  Saturday    Sunday  Thursday   Tuesday Wednesday 
   626214    573444    726464    605710    626459    620765    618831 

Put days of week in order:

cyclistic_analysis$day_of_week <- ordered(cyclistic_analysis$day_of_week, levels=c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"))
table(cyclistic_analysis$day_of_week)

   Sunday    Monday   Tuesday Wednesday  Thursday    Friday  Saturday 
   605710    573444    620765    618831    626459    626214    726464 

Count rides and their percentage by user type by day of the week:

rides_by_day_of_week<- cyclistic_analysis %>%
  group_by(day_of_week, user_type) %>%
  summarize(total_rides = n()) %>%
  mutate(percent = (percent(total_rides/sum(total_rides),0)))
formattable(rides_by_day_of_week,
             align =c("l","c","c"), 
           list('day_of_week' = color_tile("pink", "lightblue"), 'percent' = improvement_formatter))

Plot ride count by user type by day of the week:

cyclistic_analysis%>%
group_by(user_type, day_of_week)%>%
summarize(number_of_rides = n())%>%
arrange(user_type, day_of_week)%>%
ggplot(aes(x = day_of_week, y = number_of_rides, fill = user_type)) + geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Ride Count by User by Day")

Plot percentage of rides by user type by day of the week:

cyclistic_analysis%>%
group_by(day_of_week, user_type)%>%
summarize(total_rides = n())%>%
mutate(percent=formattable::percent(total_rides/sum(total_rides)))%>%
arrange(user_type, day_of_week)%>%
ggplot(aes(x = day_of_week, y = percent, fill = user_type)) + geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Percentage of Rides by User by Day")

Aggregate trip duration by user type and bike type

Aggregate column trip duration:

round(summary(cyclistic_analysis$trip_duration)/60)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1       6      11      18      20    1439 

Aggregate trip duration by user type:

Note that the average duration of a casual user’s ride is roughly twice as long as a member’s ride.

summary_trip_duration <- cyclistic_analysis %>%
  group_by(user_type) %>%
  summarize(average_duration=round((mean(trip_duration))/60),
            median_duration=round(median(trip_duration)/60),
            min_duration=round(min(trip_duration)/60),
            max_duration=round(max(trip_duration)/60))
formattable(summary_trip_duration)

Aggregate trip duration by user by month:

improvement <- formatter("span", style = x ~ style(font.weight = "bold", color = ifelse(x > 20, "tomato", ifelse(x < 20, "steelblue", "black")))
                                   , x ~ icontext(ifelse(x >= 20, "arrow-up", "arrow-down"), x))
by_month <- cyclistic_analysis%>%
group_by(month, user_type)%>%
summarize(average_duration=round((mean(trip_duration))/60))%>%
arrange(month, user_type)
formattable(by_month,
             align =c("l","c","c"), 
           list('month' = color_tile("pink", "lightblue"), 'average_duration' = improvement))

Plot average trip duration by user by month:

cyclistic_analysis%>%
group_by(user_type, month) %>%
summarize(average_trip_duration = mean(trip_duration)/60) %>%
arrange(user_type, month) %>%
ggplot(aes(x = month, y = average_trip_duration, fill = user_type)) + geom_col(position = "stack", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Average Trip Duration by User by Month")

Aggregate trip duration by user by day of the week:

by_day_of_week <- cyclistic_analysis%>%
group_by(day_of_week, user_type)%>%
summarize(average_duration=round((mean(trip_duration))/60))%>%
arrange(day_of_week, user_type)
tibble(by_day_of_week)
formattable(by_day_of_week,
             align =c("l","c","c"), 
           list('day_of_week' = color_tile("pink", "lightblue"), 'average_duration' = improvement))

Plot average trip duration by user by day of the week:

cyclistic_analysis%>%
group_by(user_type, day_of_week) %>%
summarize(average_duration = mean(trip_duration)/60) %>%
arrange(user_type, day_of_week) %>%
ggplot(aes(x = day_of_week, y = average_duration, fill = user_type)) + geom_col(position = "stack", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Average Trip Duration by User by Day")

Total bike type usage by user type

Count rides and their percent of total by bike type:

by_bike_type <- cyclistic_analysis %>%
  group_by(bike_type) %>%
  summarize(total_number = n()) %>%
  mutate(percent=percent(total_number/sum(total_number),0))
formattable(by_bike_type,
             align =c("l","c","r"), 
           list('bike_type' = color_tile("pink", "red"), 'percent' = improvement_formatter))

Plot percentage of use by bike type:

ggplot(by_bike_type, aes(x = "", y = percent, fill = bike_type)) +
  geom_col(color = "black") +
  geom_label(aes(label = percent), color = c("white", 1, 1), position = position_stack(vjust = 0.5),show.legend = FALSE) +
  guides(fill = guide_legend(title = "Percentage of Use of Bike Types")) +
  scale_fill_viridis_d() +
  coord_polar(theta = "y") +
  theme_void()

Count rides and their percentage by bike and user type and aggregate average duration of those rides:

bike_preference <- cyclistic_analysis %>%
  group_by(bike_type, user_type) %>%
  summarize(number_of_rides = n(), average_duration=round((mean(trip_duration))/60))%>%
  mutate(percent=percent(number_of_rides/sum(number_of_rides),0))%>%
  arrange(user_type, bike_type)
formattable(bike_preference,
             align =c("l","c","c"), 
           list('bike_type' = color_tile("pink", "lightblue"), 'percent' = improvement_formatter))

Plot ride count by bike and user type:

bike_preference%>%
ggplot(aes(x = bike_type, y = number_of_rides, fill = user_type)) + geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Ride Count by Bike and User Type")

Plot percentage of rides by bike and user type:

bike_preference%>%
ggplot(aes(x = bike_type, y = percent, fill = user_type)) + geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Percentage of Rides by Bike and User Type")

Plot average duration of ride by bike and user type:

bike_preference%>%
ggplot(aes(x = bike_type, y = average_duration, fill = user_type)) + 
             geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
  guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Average Duration of Rides by Bike and User Type")

Create two pie charts comparatively demonstrating percentage of use of bike type by user type.

Note that electric bikes were chosen roughly 30% of the time for both user types.

Count rides and their percentage by bike type by casual user:
by_casual <- cyclistic_analysis %>%
  filter(user_type == "casual") %>%
  group_by(user_type, bike_type) %>%
  summarize(total_number = n(), percent = n()) %>%
  mutate(percent = percent(percent / sum(percent),0))
formattable(by_casual,
             align =c("l","c","c", "r"), 
           list('bike_type' = color_tile("pink", "lightblue"), 'percent' = improvement_formatter))
Plot percentage of bike type used by casual user:
ggplot(by_casual, aes(x = "", y = percent, fill = bike_type)) +
  geom_col(color = "black") +
  geom_label(aes(label = percent), color = c("white", 1, 1), position = position_stack(vjust = 0.5),show.legend = FALSE) +
  guides(fill = guide_legend(title = "Percentage of Bike Type Use by Casual Users")) +
  scale_fill_viridis_d() +
  coord_polar(theta = "y") +
  theme_void()

Count rides and their percentage by bike type by member user:
by_member <- cyclistic_analysis %>%
  filter(user_type == "member") %>%
  group_by(user_type, bike_type) %>%
  summarize(total_number = n(), percent = n()) %>%
  mutate(percent = percent(percent / sum(percent),0))
formattable(by_casual,
             align =c("l","c","c", "r"), 
           list('bike_type' = color_tile("pink", "lightblue"), 'percent' = improvement_formatter))
Plot percentage of bike type used by member user:
ggplot(by_member, aes(x = "", y = percent, fill = bike_type)) +
  geom_col(color = "black") +
  geom_label(aes(label = percent), color = c("white", 1), position = position_stack(vjust = 0.5),show.legend = FALSE) +
  guides(fill = guide_legend(title = "Percentage of Bike Type Use by Member Users")) +
  scale_fill_viridis_d() +
  coord_polar(theta = "y") +
  theme_void()

Investigate station use by user type

Create new boolean column to calculate round trips based on station ids:

cyclistic_analysis_V2 <- cyclistic_analysis %>%
  mutate(round_trip = start_station_id == end_station_id)

Compare number and percentage of round trips and their average duration by user:

round_trip <- cyclistic_analysis_V2 %>%
  group_by(user_type, round_trip) %>%
 summarize(number_of_rides = n(), average_duration = round(mean(trip_duration)/60))%>%
  mutate(percent_round_trips = percent(number_of_rides / sum(number_of_rides),0))%>%
  arrange(user_type, round_trip)
formattable(round_trip,
             align =c("l","c","c", "c", "r"), 
           list('bike_type' = color_tile("pink", "lightblue"), 'percent_round_trips' = formatter("span", style = x ~ style(font.weight = "bold", color = ifelse(x > .90, "tomato", ifelse(x < .90, "steelblue", "black"))))))

Plot percentage of round trips by user:

round_trip %>%
  ggplot(aes(x = round_trip, y = percent_round_trips, fill = user_type)) +
             geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Percentage of Round Trips by Users")

Compare number and percentage of round trips and their average duration by user and bike type:

round_trip <- cyclistic_analysis_V2 %>%
  group_by(user_type, round_trip, bike_type) %>%
 summarize(number_of_rides = n(), average_duration = round(mean(trip_duration)/60))%>%
  mutate(percent_round_trips = percent(number_of_rides / sum(number_of_rides),0))%>%
  arrange(user_type, round_trip, bike_type)
formattable(round_trip, align =c("l","c","c","c","c","r"), 
           list('percent_round_trips' = formatter("span", style = x ~ style(font.weight = "bold")))) 

Plot percentage of round trips by user and bike type:

round_trip_TRUE <- round_trip %>%
  filter(round_trip == TRUE)
round_trip_TRUE %>%
  ggplot(aes(x = bike_type, y = percent_round_trips, fill = user_type)) +
             geom_col(position = "dodge", color="black") +
  scale_fill_hue(l=40) +
guides(fill = guide_legend (title = "User Type"))+
  ggtitle("Percentage of Round Trips by Bike and User Types")

Investigate most and least used stations of user types.

Calculate most and least used stations of both user types:

most_popular_stations <- cyclistic_analysis_V2 %>%
  group_by(start_station_id, start_station_name) %>%
  summarize(number_of_rides = n(),average_duration = round(mean(trip_duration)/60))%>%
  arrange(desc(number_of_rides))%>%
  head(n = 5)
formattable(most_popular_stations)

Calculate most used station of causal rider:

most_popular_casual <- cyclistic_analysis_V2 %>%
  filter(user_type == "casual") %>%
  group_by(start_station_id, start_station_name) %>%
  summarize(number_of_rides=n(), average_duration=round(mean(trip_duration)/60))%>%
  arrange(desc(number_of_rides))%>%
  head(n = 5)
formattable(most_popular_casual)

Calculate most taken casual trips from the most used start station:

Note that Streeter Dr & Grand Ave is Navy Pier on the Lakefront Trail.

LFT <- cyclistic_analysis_V2 %>%
  filter(user_type == "casual", start_station_name == "Streeter Dr & Grand Ave") %>%
  group_by(start_station_name, end_station_name)%>%
    summarize(number_of_rides = n(),average_duration=round(mean(trip_duration)/60))%>%
arrange(desc(number_of_rides))%>%
  head(n = 5)
formattable(LFT)

Calculate most used station of member rider:

most_popular_member <- cyclistic_analysis_V2 %>%
  filter(user_type == "member") %>%
  group_by(start_station_id, start_station_name) %>%
  summarize(number_of_rides = n(),average_duration=round(mean(trip_duration)/60))%>%
  arrange(desc(number_of_rides))%>%
  head(n = 5)
formattable(most_popular_member)

Calculate most taken member commuter trip from most used start station:

Note that Clinton St & Madison St is Union Station.

Union_Station <- cyclistic_analysis_V2 %>%
  filter(user_type == "member", start_station_name == "Clinton St & Madison St") %>%
  group_by(start_station_name, end_station_name)%>%
    summarize(number_of_rides = n(),average_duration=round(mean(trip_duration)/60))%>%
arrange(desc(number_of_rides))%>%
  head(n = 5)
formattable(Union_Station)

Calculate least used stations of user types:

least_popular <- cyclistic_analysis_V2 %>%
  group_by(start_station_name) %>%
  summarize(number_of_rides = n())%>%
  arrange(number_of_rides)%>%
  head(n = 5)
formattable(least_popular)

Calculate least used stations of casual users:

least_popular_casual <- cyclistic_analysis_V2 %>%
  filter(user_type == "casual") %>%
  group_by(start_station_name) %>%
  summarize(number_of_rides = n())%>%
  arrange(number_of_rides)%>%
  head(n = 5)
formattable(least_popular_casual)

Calculate least used station of member rider:

least_popular_member <- cyclistic_analysis_V2 %>%
  filter(user_type == "casual") %>%
  group_by(start_station_name) %>%
  summarize(number_of_rides = n())%>%
  arrange(number_of_rides)%>%
  head(n = 5)
formattable(least_popular_member)
LS0tCnRpdGxlOiAiQ3ljbGlzdGljcyAyMDIxLTIwMjIgQW5hbHlzaXMgaW4gUiBvbiBSU3R1ZGlvIgphdXRob3I6ICJac29sbmF5IgpkYXRlOiAiMjAyMy0wNy0wOCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6ICAgICAKICAgIHRoZW1lOiBzYW5kc3RvbmUKICBodG1sX2RvY3VtZW50OiAgICAKICAgIHRoZW1lOiBzYW5kc3RvbmUKICAgIGRmX3ByaW50OiBwYWdlZAogIHdvcmRfZG9jdW1lbnQ6ICAgICAKICAgIHRoZW1lOiBzYW5kc3RvbmUKICBwZGZfZG9jdW1lbnQ6ICAgICAKICAgIHRoZW1lOiBzYW5kc3RvbmUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSkKbGlicmFyeShic2xpYikKYGBgCgojIyMjIyBHb29nbGUgRGF0YSBBbmFseXRpY3MgUHJvZmVzc2lvbmFsIENlcnRpZmljYXRlIGNhcHN0b25lIGNhc2Ugc3R1ZHkgdHJhY2sgb25lIEN5Y2xpc3RpYyBiaWtlLXNoYXJlIGFuYWx5c2lzCgojIyBQcmVwYXJlIGRhdGEKCiMjIyMgU2V0IHVwIGluaXRpYWwgZW52aXJvbm1lbnQgYnkgbG9hZGluZyBwYWNrYWdlczoKCmBgYHtyIGluc3RhbGxpbmcgcGFja2FnZXN9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KHJtYXJrZG93bikKYGBgCgojIyMjIEltcG9ydCBmaWxlcyBEaXZ2eSBkYXRhIGZpbGVzIGZyb20gT2N0b2JlciAyMDIxLVNlcHRlbWJlciAyMDIyIGFuZCBzaW1wbGlmeSBmaWxlIG5hbWVzOgoKYGBge3IgaW1wb3J0IGRhdGEgZmlsZXMsIHJlc3VsdHMgPSAiaGlkZSJ9Ck9jdF8yMDIxIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMTEwLWRpdnZ5LXRyaXBkYXRhLmNzdiIpCk5vdl8yMDIxIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMTExLWRpdnZ5LXRyaXBkYXRhLmNzdiIpCkRlY18yMDIxIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMTEyLWRpdnZ5LXRyaXBkYXRhLmNzdiIpCkphbl8yMDIyIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMjAxLWRpdnZ5LXRyaXBkYXRhLmNzdiIpCkZlYl8yMDIyIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMjAyLWRpdnZ5LXRyaXBkYXRhLmNzdiIpCk1hcl8yMDIyIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMjAzLWRpdnZ5LXRyaXBkYXRhLmNzdiIpCkFwcl8yMDIyIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMjA0LWRpdnZ5LXRyaXBkYXRhLmNzdiIpCk1heV8yMDIyIDwtIHJlYWRfY3N2KCJ+L0Rlc2t0b3AvRklOQUwgUFJPSkVDVC8yMDIxLTIwMjIgb3JpZ2luYWwgZmlsZXMvMjAyMjA1LWRpdnZ5LXRyaXBkYXRhLmNzdiIpCkp1bmVfMjAyMiA8LSByZWFkX2Nzdigifi9EZXNrdG9wL0ZJTkFMIFBST0pFQ1QvMjAyMS0yMDIyIG9yaWdpbmFsIGZpbGVzLzIwMjIwNi1kaXZ2eS10cmlwZGF0YS5jc3YiKQpKdWx5XzIwMjIgPC0gcmVhZF9jc3YoIn4vRGVza3RvcC9GSU5BTCBQUk9KRUNULzIwMjEtMjAyMiBvcmlnaW5hbCBmaWxlcy8yMDIyMDctZGl2dnktdHJpcGRhdGEuY3N2IikKQXVnXzIwMjIgPC0gcmVhZF9jc3YoIn4vRGVza3RvcC9GSU5BTCBQUk9KRUNULzIwMjEtMjAyMiBvcmlnaW5hbCBmaWxlcy8yMDIyMDgtZGl2dnktdHJpcGRhdGEuY3N2IikKU2VwdF8yMDIyPC0gcmVhZF9jc3YoIn4vRGVza3RvcC9GSU5BTCBQUk9KRUNULzIwMjEtMjAyMiBvcmlnaW5hbCBmaWxlcy8yMDIyMDktZGl2dnktdHJpcGRhdGEuY3N2IikKYGBgCgojIyMjIENoZWNrIGVhY2ggZmlsZSBmb3IgcHJvcGVyIGltcG9ydCBieSBjb2x1bW4gYW5kIHJvdyBjb3VudCAobm90IGluY2x1ZGluZyBoZWFkZXIpOgoKYGBge3Igcm93IGNvdW50fQpkaW0oT2N0XzIwMjEpCmRpbShOb3ZfMjAyMSkKZGltKERlY18yMDIxKQpkaW0oSmFuXzIwMjIpCmRpbShGZWJfMjAyMikKZGltKE1hcl8yMDIyKQpkaW0oQXByXzIwMjIpCmRpbShNYXlfMjAyMikKZGltKEp1bmVfMjAyMikKZGltKEp1bHlfMjAyMikKZGltKEF1Z18yMDIyKQpkaW0oU2VwdF8yMDIyKQpgYGAKCiMjIyMgQ2hlY2sgdGhhdCBhbGwgY29sdW1uIG5hbWVzIGFyZSBpbiBhZ3JlZW1lbnQ6CgpgYGB7ciBhZ3JlZW1lbnQgb2YgY29sdW1uIG5hbWVzfQpjb2xuYW1lcyhPY3RfMjAyMSkKY29sbmFtZXMoTm92XzIwMjEpCmNvbG5hbWVzKERlY18yMDIxKQpjb2xuYW1lcyhKYW5fMjAyMikKY29sbmFtZXMoRmViXzIwMjIpCmNvbG5hbWVzKE1hcl8yMDIyKQpjb2xuYW1lcyhBcHJfMjAyMikKY29sbmFtZXMoTWF5XzIwMjIpIApjb2xuYW1lcyhKdW5lXzIwMjIpCmNvbG5hbWVzKEp1bHlfMjAyMikKY29sbmFtZXMoQXVnXzIwMjIpCmNvbG5hbWVzKFNlcHRfMjAyMikKYGBgCgojIyMjIENoZWNrIGEgc2FtcGxlIG9mIE9jdF8yMDIxIHRvIGdldCBhIGZlZWwgZm9yIHRoZSBkYXRhIGZyYW1lOgoKTm90ZSBtYW55IE5Bcy4KClZpZXcoT2N0XzIwMjEpCgojIyBQcm9jZXNzICh3cmFuZ2xpbmcsIGNsZWFuaW5nLCBhbmQgdHJhbnNmb3JtYXRpb24pCgojIyMjIENvbHVtbnMgaW4gYWdyZWVtZW50LCBzbyBiaW5kIGludG8gZmlzY2FsIGFuZCBzZWFzb25hbCBxdWFydGVyczoKCmBgYHtyIGJpbmQgaW50byBxdWF0ZXJzfQpmaXNjYWxfeWVhcl8yMDIxXzIwMjJfMVEgPC0gYmluZF9yb3dzIChPY3RfMjAyMSwgTm92XzIwMjEsIERlY18yMDIxKQpmaXNjYWxfeWVhcl8yMDIxXzIwMjJfMlEgPC0gYmluZF9yb3dzIChKYW5fMjAyMiwgRmViXzIwMjIsIE1hcl8yMDIyKQpmaXNjYWxfeWVhcl8yMDIxXzIwMjJfM1EgPC0gYmluZF9yb3dzIChBcHJfMjAyMiwgTWF5XzIwMjIsIEp1bmVfMjAyMikKZmlzY2FsX3llYXJfMjAyMV8yMDIyXzRRIDwtIGJpbmRfcm93cyAoSnVseV8yMDIyLCBBdWdfMjAyMiwgU2VwdF8yMDIyKQpgYGAKCiMjIyMgQ2hlY2sgcm93IGFuZCBjb2x1bW4gY291bnRzOgoKYGBge3IgY2hlY2sgY291bnRzfQpkaW0oZmlzY2FsX3llYXJfMjAyMV8yMDIyXzFRKQpkaW0oZmlzY2FsX3llYXJfMjAyMV8yMDIyXzJRKQpkaW0oZmlzY2FsX3llYXJfMjAyMV8yMDIyXzNRKQpkaW0oZmlzY2FsX3llYXJfMjAyMV8yMDIyXzRRKQpgYGAKCiMjIyMgQmluZCBhbGwgcXVhcnRlcnMgaW50byBuZXcgZGF0YSBmcmFtZSBlbnRpdGxlZCAiY3ljbGlzdGljXzIwMjFfMjAyMiI6CgpgYGB7ciBiaW5kIGludG8gb25lIGRmfQpjeWNsaXN0aWNfMjAyMV8yMDIyIDwtIGJpbmRfcm93cyAoZmlzY2FsX3llYXJfMjAyMV8yMDIyXzFRLGZpc2NhbF95ZWFyXzIwMjFfMjAyMl8yUSxmaXNjYWxfeWVhcl8yMDIxXzIwMjJfM1EsZmlzY2FsX3llYXJfMjAyMV8yMDIyXzRRKQpgYGAKCiMjIyMgQ2hlY2sgdGhhdCBmaWxlcyBib3VuZCBhbmQgY2hlY2sgb3V0IG5ldyBkYXRhIGZyYW1lOgoKYGBge3IgbmV3IGRmfQpnbGltcHNlKGN5Y2xpc3RpY18yMDIxXzIwMjIpCmBgYAoKIyMjIyBDaGVjayBvdXQgdGhlIHZhcmlhYmxlcyBmb3IgdGhlIGNvbHVtbnMgcmlkZWFibGVfdHlwZSBhbmQgbWVtYmVyX2Nhc3VhbDoKCmBgYHtyIGNoZWNrIHNwZWNpZmljc30KdGFibGUoY3ljbGlzdGljXzIwMjFfMjAyMiRtZW1iZXJfY2FzdWFsKQp0YWJsZShjeWNsaXN0aWNfMjAyMV8yMDIyJHJpZGVhYmxlX3R5cGUpCmBgYAoKIyMjIyBSZW5hbWUgY29sdW1ucyByaWRlX2lkLCBzdGFydGVkX2F0LCBlbmRlZF9hdCwgcmlkZWFibGVfdHlwZSwgYW5kIG1lbWJlcl9jYXN1YWw6CgpgYGB7ciByZW5hbWV9CmN5Y2xpc3RpY18yMDIxXzIwMjIgPC0gY3ljbGlzdGljXzIwMjFfMjAyMiAlPiUgCiAgcmVuYW1lKHRyaXBfaWQgPSByaWRlX2lkCiAgICAgICAgICxiaWtlX3R5cGUgPSByaWRlYWJsZV90eXBlCiAgICAgICAgICxzdGFydF90aW1lID0gc3RhcnRlZF9hdAogICAgICAgICAsZW5kX3RpbWUgPSBlbmRlZF9hdAogICAgICAgICAsdXNlcl90eXBlID0gbWVtYmVyX2Nhc3VhbCkKYGBgCgojIyMjIENoZWNrIG5ldyBjb2x1bW4gbmFtZXM6CgpgYGB7ciBjaGVjayBuYW1lc30KY29sbmFtZXMoY3ljbGlzdGljXzIwMjFfMjAyMikKYGBgCgojIyMjIENoZWNrIGRhdGEgZnJhbWUgZm9yIG1pc3NpbmcgdmFsdWVzOgoKYGBge3IgY2hlY2tzIGZvciBOQXN9CmFueShpcy5uYShjeWNsaXN0aWNfMjAyMV8yMDIyKSkKYGBgCgojIyMjIFNldCB1cCBjbGVhbmluZyBlbnZpcm9ubWVudCBhbmQgcGVyZm9ybSB0aG9yb3VnaCByZXZpZXcgb2YgZGF0YSBmcmFtZToKCmBgYHtyIHNldCB1cCBjbGVhbn0KbGlicmFyeShza2ltcikKbGlicmFyeShqYW5pdG9yKQpgYGAKCmBgYHtyIGNoZWNrIGRmfQpza2ltKGN5Y2xpc3RpY18yMDIxXzIwMjIpCmBgYAoKIyMjIyBOb3RlIHRoYXQgdGhlcmUgYXJlOgoKLSAgIDU4MjgyMzUgcm93cyB0aGUgdHJpcF9pZCBpcyBhIGNvbnNpc3RlbnQgc3RyaW5nIGxlbmd0aCBhdCAxNiBjaGFyYWN0ZXJzCgotICAgMyB1bmlxdWUgdHlwZXMgb2YgYmlrZXMKCi0gICAxNTkxIHVuaXF1ZSBzdGFydCBzdGF0aW9ucywgd2hpY2ggZG8gbm90IG1hdGNoIHRoZSB1bmlxdWUgc3RhcnQgc3RhdGlvbiBpZHMgYXQgMTMwMgoKLSAgIDE2MDkgdW5pcXVlIGVuZCBzdGF0aW9ucywgd2hpY2ggZG8gbm90IG1hdGNoIHRoZSB1bmlxdWUgZW5kIHN0YXRpb24gaWRzIGF0IDEzMDkKCi0gICBCb3RoIG9mIHRoZXNlIHN1Z2dlc3QgdGhhdCB0aGUgc3RhdGlvbiBuYW1lcyBhcmUgbm90IGNvbnNpc3RlbnQKCi0gICBBbHNvLCBjdXJpb3VzIHRoYXQgdGhlIG51bWJlciBvZiB1bmlxdWUgc3RhcnQgYW5kIGVuZCBzdGF0aW9ucyBpcyBub3QgdGhlIHNhbWUuCgotICAgODk1MDMyIHN0YXJ0IHN0YXRpb25zIGFuZCBpZHMgZWFjaCBhcmUgbWlzc2luZwoKLSAgIDk1ODIyNyBlbmQgc3RhdGlvbnMgYW5kIGlkcyBlYWNoIGFyZSBtaXNzaW5nCgotICAgMCBtaXNzaW5nIHN0YXJ0IGxhdGl0dWRlcyBvciBsb25naXR1ZGVzCgotICAgNTg0NCBtaXNzaW5nIGVuZCBsYXRpdHVkZXMgYW5kIGxvbmdpdHVkZXMgZWFjaAoKLSAgIDIgdW5pcXVlIHVzZXIgdHlwZXMgbm8gd2hpdGUgc3BhY2VzCgojIyMjIFJlbW92ZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMgYW5kIGNoZWNrIG5ldyByb3cgYW5kIGNvbHVtbiBjb3VudC4gQ3JlYXRlIGRyYWZ0IG9mIG5ldyBkYXRhIGZyYW1lOgoKYGBge3IgZHJvcCBOQXN9CmN5Y2xpc3RpY18yMDIxXzIwMjJfVjIgPC0gY3ljbGlzdGljXzIwMjFfMjAyMiAlPiUgZHJvcF9uYShjKHRyaXBfaWQKLGJpa2VfdHlwZQosc3RhcnRfdGltZQosZW5kX3RpbWUKLHN0YXJ0X3N0YXRpb25fbmFtZQosc3RhcnRfc3RhdGlvbl9pZAosZW5kX3N0YXRpb25fbmFtZQosZW5kX3N0YXRpb25faWQKLHN0YXJ0X2xhdAosc3RhcnRfbG5nCixlbmRfbGF0CixlbmRfbG5nCix1c2VyX3R5cGUpKQpkaW0oY3ljbGlzdGljXzIwMjFfMjAyMl9WMikKYGBgCgojIyMjIFJlY2hlY2sgZm9yIGR1cGxpY2F0ZSByaWRlX2lkIHZhbHVlczoKCmBgYHtyIGNoZWNrIGZvciBkdXBsaWNhdGVzfQpzdW0oZHVwbGljYXRlZChjeWNsaXN0aWNfMjAyMV8yMDIyX1YyJHRyaXBfaWQpKQpgYGAKCiMjIyMgTWFrZSBzdXJlIHRoYXQgY29sdW1uIG5hbWVzIGFyZSB1bmlxdWUgYW5kIGNvbnNpc3RlbnQ6CgpgYGB7ciBjbGVhbiBuYW1lc30KY3ljbGlzdGljXzIwMjFfMjAyMl9WMiA8LSBjbGVhbl9uYW1lcyhjeWNsaXN0aWNfMjAyMV8yMDIyX1YyKQpgYGAKCiMjIyMgUmVjaGVjayBudW1iZXIgb2YgZGlzdGluY3Qgc3RhcnQgYW5kIGVuZCBzdGF0aW9uIG5hbWVzOgoKYGBge3IgZGlzdGluY3QgY291bnRzfQpuX2Rpc3RpbmN0KGN5Y2xpc3RpY18yMDIxXzIwMjJfVjIkc3RhcnRfc3RhdGlvbl9uYW1lKQpuX2Rpc3RpbmN0KGN5Y2xpc3RpY18yMDIxXzIwMjJfVjIkZW5kX3N0YXRpb25fbmFtZSkKYGBgCgojIyMjIFJlY2hlY2sgdGhlIGRhdGEgZnJhbWU6CgpgYGB7ciBza2ltIGRmfQpza2ltKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjIpCmBgYAoKIyMjIyBOb3RlIHRoYXQgdGhlcmUgYXJlIG5vdzoKCi0gICA1ODI4MjM1IHJvd3Mgd2VudCB0byA0NDc0MTQxCgotICAgMTQ4MCB1bmlxdWUgc3RhcnQgc3RhdGlvbnMsIHdoaWNoIGRvIG5vdCBtYXRjaCB0aGUgdW5pcXVlIHN0YXJ0IHN0YXRpb24gaWRzIGF0IDEyNTIKCi0gICAxNTEzIHVuaXF1ZSBlbmQgc3RhdGlvbnMsIHdoaWNoIGRvIG5vdCBtYXRjaCB0aGUgdW5pcXVlIGVuZCBzdGF0aW9uIGlkcyBhdCAxMjY2CgotICAgQWxzbywgY3VyaW91cyB0aGF0IHRoZSBudW1iZXIgb2YgdW5pcXVlIHN0YXJ0IGFuZCBlbmQgc3RhdGlvbnMgaXMgbm90IHRoZSBzYW1lLgoKLSAgIDAgc3RhcnQgc3RhdGlvbnMgYW5kIGlkcyBhcmUgbWlzc2luZwoKLSAgIDAgZW5kIHN0YXRpb25zIGFuZCBpZHMgYXJlIG1pc3NpbmcKCi0gICAwIG1pc3Npbmcgc3RhcnQgbGF0aXR1ZGVzIG9yIGxvbmdpdHVkZXMKCi0gICAwIG1pc3NpbmcgZW5kIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcwoKLSAgIDIgdW5pcXVlIHVzZXIgdHlwZXMKCiMjIyMgQ2hlY2sgc3RhcnQgYW5kIGVuZCBzdGF0aW9uIG5hbWVzIGZvciBtYWludGVuYW5jZSBhbmQgdGVtcCBzdGF0aW9uczoKCmBgYHtyIGxpc3Qgc3RhdGlvbnMsIHJlc3VsdHMgPSAiaGlkZSJ9CnRhYmxlKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjIkc3RhcnRfc3RhdGlvbl9uYW1lKQp0YWJsZShjeWNsaXN0aWNfMjAyMV8yMDIyX1YyJGVuZF9zdGF0aW9uX25hbWUpCmBgYAoKIyMjIyBSZW1vdmUgd2FyZWhvdXNlLCByZXBhaXIsIGFuZCBjaGFyZ2luZyBzdGF0aW9ucyBmcm9tIHN0YXJ0X3N0YXRpb25fbmFtZS4gQ3JlYXRlIG5ldyBkcmFmdCBvZiBkYXRhIGZyYW1lOgoKYGBge3IgcmVtb3ZlIHJvd3N9CmN5Y2xpc3RpY18yMDIxXzIwMjJfVjMgPC0gY3ljbGlzdGljXzIwMjFfMjAyMl9WMlshKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjIkc3RhcnRfc3RhdGlvbl9uYW1lPT0iQmFzZSAtIDIxMzIgVyBIdWJiYXJkIiB8IApjeWNsaXN0aWNfMjAyMV8yMDIyX1YyJHN0YXJ0X3N0YXRpb25fbmFtZT09IkJhc2UgLSAyMTMyIFcgSHViYmFyZCBXYXJlaG91c2UiIHwgIApjeWNsaXN0aWNfMjAyMV8yMDIyX1YyJHN0YXJ0X3N0YXRpb25fbmFtZT09IkJhc2UgLSAyMTMyIFcgSHViYmFyZCBXYXJlaG91c2UiIHwgIApjeWNsaXN0aWNfMjAyMV8yMDIyX1YyJHN0YXJ0X3N0YXRpb25fbmFtZT09Ikhhc3RpbmdzIFdIIDIiIHwgCmN5Y2xpc3RpY18yMDIxXzIwMjJfVjIkc3RhcnRfc3RhdGlvbl9uYW1lPT0iRElWVlkgQ0FTU0VUVEUgUkVQQUlSIE1PQklMRSBTVEFUSU9OIiB8IApjeWNsaXN0aWNfMjAyMV8yMDIyX1YyJHN0YXJ0X3N0YXRpb25fbmFtZT09IlRocm9vcC9IYXN0aW5ncyBNb2JpbGUgU3RhdGlvbiIgfCAKY3ljbGlzdGljXzIwMjFfMjAyMl9WMiRzdGFydF9zdGF0aW9uX25hbWU9PSJCaXNzZWxsIFN0ICYgQXJtaXRhZ2UgQXZlIC0gQ2hhcmdpbmciIHwgCmN5Y2xpc3RpY18yMDIxXzIwMjJfVjIkc3RhcnRfc3RhdGlvbl9uYW1lPT0iTGluY29sbiBBdmUgJiBSb3Njb2UgU3QgLSBDaGFyZ2luZyIgfCAKY3ljbGlzdGljXzIwMjFfMjAyMl9WMiRzdGFydF9zdGF0aW9uX25hbWU9PSJQYXdlbCBCaWFsb3dhcyAtIFRlc3QtIFBCU0MgY2hhcmdpbmcgc3RhdGlvbiIgfCAKY3ljbGlzdGljXzIwMjFfMjAyMl9WMiRzdGFydF9zdGF0aW9uX25hbWU9PSJXaWx0b24gQXZlICYgRGl2ZXJzZXkgUGt3eSAtIENoYXJnaW5nIiksXQpgYGAKCiMjIyMgUmVtb3ZlIHdhcmVob3VzZSwgcmVwYWlyLCBhbmQgY2hhcmdpbmcgc3RhdGlvbnMgZnJvbSBlbmRfc3RhdGlvbl9uYW1lOgoKYGBge3IgcmVtb3ZlIG1vcmUgcm93c30KY3ljbGlzdGljXzIwMjFfMjAyMl9WMyA8LSAgY3ljbGlzdGljXzIwMjFfMjAyMl9WM1shKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZW5kX3N0YXRpb25fbmFtZT09IkJhc2UgLSAyMTMyIFcgSHViYmFyZCIgfApjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJGVuZF9zdGF0aW9uX25hbWU9PSJCYXNlIC0gMjEzMiBXIEh1YmJhcmQgV2FyZWhvdXNlIiB8ICAKY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRlbmRfc3RhdGlvbl9uYW1lPT0iQmFzZSAtIDIxMzIgVyBIdWJiYXJkIFdhcmVob3VzZSIgfCAgCmN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZW5kX3N0YXRpb25fbmFtZT09Ikhhc3RpbmdzIFdIIDIiIHwKY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRlbmRfc3RhdGlvbl9uYW1lPT0iRElWVlkgQ0FTU0VUVEUgUkVQQUlSIE1PQklMRSBTVEFUSU9OIiB8CmN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZW5kX3N0YXRpb25fbmFtZT09IlRocm9vcC9IYXN0aW5ncyBNb2JpbGUgU3RhdGlvbiIgfApjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJGVuZF9zdGF0aW9uX25hbWU9PSJCaXNzZWxsIFN0ICYgQXJtaXRhZ2UgQXZlIC0gQ2hhcmdpbmciIHwKY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRlbmRfc3RhdGlvbl9uYW1lPT0iTGluY29sbiBBdmUgJiBSb3Njb2UgU3QgLSBDaGFyZ2luZyIgfApjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJGVuZF9zdGF0aW9uX25hbWU9PSJQYXdlbCBCaWFsb3dhcyAtIFRlc3QtIFBCU0MgY2hhcmdpbmcgc3RhdGlvbiIgfApjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJGVuZF9zdGF0aW9uX25hbWU9PSJXaWx0b24gQXZlICYgRGl2ZXJzZXkgUGt3eSAtIENoYXJnaW5nIiksXQpgYGAKCiMjIyMgUmVjaGVjayB0aGUgZGF0YSBmcmFtZToKCmBgYHtyIHNraW0gZGYgYWdhaW59CnNraW0oY3ljbGlzdGljXzIwMjFfMjAyMl9WMykKYGBgCgojIyMjIE5vdGUgdGhhdCB0aGVyZSBhcmUgbm93OgoKLSAgIDU4MjgyMzUgcm93cyBiZWNhbWUgNDQ3NDE0MSBiZWNhbWUgNDQ3MjU5OQoKLSAgIDE0NzIgdW5pcXVlIHN0YXJ0IHN0YXRpb25zLCB3aGljaCBkbyBub3QgbWF0Y2ggdGhlIHVuaXF1ZSBzdGFydCBzdGF0aW9uIGlkcyBhdCAxMjQ1CgotICAgMTUwMiB1bmlxdWUgZW5kIHN0YXRpb25zLCB3aGljaCBkbyBub3QgbWF0Y2ggdGhlIHVuaXF1ZSBlbmQgc3RhdGlvbiBpZHMgYXQgMTI1OAoKLSAgIDAgc3RhcnQgc3RhdGlvbnMgYW5kIGlkcyBhcmUgbWlzc2luZwoKLSAgIDAgZW5kIHN0YXRpb25zIGFuZCBpZHMgYXJlIG1pc3NpbmcKCi0gICAwIG1pc3Npbmcgc3RhcnQgbGF0aXR1ZGVzIG9yIGxvbmdpdHVkZXMKCi0gICAwIG1pc3NpbmcgZW5kIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcwoKLSAgIDIgdW5pcXVlIHVzZXIgdHlwZXMKCiMjIyMgQ3JlYXRlIG5ldyBjb2x1bW5zOiBkYXRlLCBtb250aCwgZGF5LCB5ZWFyLCBhbmQgZGF5X29mX3RoZV93ZWVrIGZvciBhZ2dyZWdhdGlvbjoKCmBgYHtyIGNyZWF0ZSBuZXcgY29sdW1uc30KY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRkYXRlIDwtIGFzLkRhdGUoY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRzdGFydF90aW1lKQpjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJG1vbnRoIDwtIGZvcm1hdChhcy5EYXRlKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZGF0ZSksICIlbSIpCmN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZGF5IDwtIGZvcm1hdChhcy5EYXRlKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZGF0ZSksICIlZCIpCmN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkeWVhciA8LSBmb3JtYXQoYXMuRGF0ZShjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJGRhdGUpLCAiJVkiKQpjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJGRheV9vZl93ZWVrIDwtIGZvcm1hdChhcy5EYXRlKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMkZGF0ZSksICIlQSIpCmNvbG5hbWVzKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMpCmBgYAoKIyMjIyBDaGVjayB0aGF0IG5ldyBjb2x1bW5zIGNyZWF0ZWQgcHJvcGVybHk6CgpOb3RlIHRvIGNoYW5nZSB0aGUgbWFubmVyIGJ5IHdoaWNoIGEgbW9udGggbmFtZSBpcyByZWZlcnJlZCBmcm9tIGEgbnVtYmVyIHRvIGEgbmFtZS4KClZpZXcoY3ljbGlzdGljXzIwMjFfMjAyMl9WMykKCiMjIyMgQ3JlYXRlIGNvbHVtbiBmb3IgdHJpcCBkdXJhdGlvbiwgY2hlY2sgdGhhdCBpdCB3YXMgY3JlYXRlZCBpbiBkaWZmdGltZToKCk5vdGUgdG8gcmV0eXBlIHRvIGludGVnZXIuCgpgYGB7cn0KY3ljbGlzdGljXzIwMjFfMjAyMl9WMyR0cmlwX2R1cmF0aW9uPC0gZGlmZnRpbWUoY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRlbmRfdGltZSwgY3ljbGlzdGljXzIwMjFfMjAyMl9WMyRzdGFydF90aW1lKQpnbGltcHNlKGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMpCmBgYAoKIyMjIyBDaGFuZ2UgdHJpcF9kdXJhdGlvbiBkYXRhIHR5cGUgdG8gaW50ZWdlciBmb3IgYWdncmVnYXRpb246CgpOb3RlIHRoYXQgdHJpcF9kdXJhdGlvbiBoYXMgdHJpcHMgdW5kZXIgNjAgc2Vjb25kcyBsb25nICgtNzYyMSBzZWNvbmRzKSBhbmQgdHJpcHMgb3ZlciA4NjQwMCBzZWNvbmRzIGxvbmcgKG92ZXIgMjRocnMgWzI0NDIzMDEgc2Vjb25kc10pLiBOb3RlIHRoYXQgdGhlc2Ugb3V0bGllcnMgYXJlIGxpa2VseSByZXByZXNlbnRhdGl2ZSBvZiBmYWxzZSBzdGFydHMsIGJyZWFrIGRvd25zLCBvciB0aGVmdCBhbmQgc2hvdWxkIGJlIHJlbW92ZWQuCgpgYGB7ciByZWNhc3QgZGF0YSB0eXBlfQpjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJHRyaXBfZHVyYXRpb248LSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJHRyaXBfZHVyYXRpb24pKQpzdW1tYXJ5KGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMpCmBgYAoKIyMjIyBDb3VudCBudW1iZXIgb2YgdHJpcF9kdXJhdGlvbiByb3dzIHRoYXQgYXJlIGVxdWFsIHRvIG9yIG92ZXIgNjAgc2Vjb25kcyBhbmQgZXF1YWwgdG8gb3Igb3ZlciA4NjQwMCBzZWNvbmRzOgoKTm90ZSB0aGF0IDc0NDQzKzI2OSA9IDc0NzEyLgoKYGBge3Igc2NhbiBsZW5ndGh9Cmxlbmd0aCh3aGljaChjeWNsaXN0aWNfMjAyMV8yMDIyX1YzJHRyaXBfZHVyYXRpb24gPD0gNjApKQpsZW5ndGgod2hpY2goY3ljbGlzdGljXzIwMjFfMjAyMl9WMyR0cmlwX2R1cmF0aW9uID49IDg2NDAwKSkKYGBgCgojIyMjIFJlbW92ZSB0cmlwcyB3aXRoIGZhbHNlIHN0YXJ0cyAoNjApIG9yIG92ZXIgMjRocnMgKDg2NDAwKSBhbmQgY3JlYXRlIGZpbmFsIHZlcnNpb24gb2YgdGhlIGRhdGEgZnJhbWU6CgpOb3RlIHRoYXQgNDQ3MjU5OS03NDcxMiA9IDQzOTc4ODcuCgpgYGB7ciBmaWx0ZXIgbGVuZ3RofQpjeWNsaXN0aWNfMjAyMV8yMDIyX0NMRUFOIDwtIGN5Y2xpc3RpY18yMDIxXzIwMjJfVjMgJT4lCiAgZmlsdGVyKHRyaXBfZHVyYXRpb24gPiA2MCAmIHRyaXBfZHVyYXRpb24gPCA4NjQwMCkKZGltKGN5Y2xpc3RpY18yMDIxXzIwMjJfQ0xFQU4pCmBgYAoKIyMjIyBSZWNoZWNrIGZvciBvdXRsaWVyczoKCk5vdGUgdGhhdCB0aGVyZSB3ZXJlIG5vbmUuCgpgYGB7ciByZXNjYW4gbGVuZ3RofQpsZW5ndGgod2hpY2goY3ljbGlzdGljXzIwMjFfMjAyMl9DTEVBTiR0cmlwX2R1cmF0aW9uIDw9IDYwKSkKbGVuZ3RoKHdoaWNoKGN5Y2xpc3RpY18yMDIxXzIwMjJfQ0xFQU4kdHJpcF9kdXJhdGlvbiA+PSA4NjQwMCkpCmBgYAoKIyMjIyBDcmVhdGUgY3N2IGZpbGUgImN5Y2xpc3RpY18yMDIxXzIwMjJfQ0xFQU4iOgoKd3JpdGUuY3N2KGN5Y2xpc3RpY18yMDIxXzIwMjJfQ0xFQU4sICJjeWNsaXN0aWNfMjAyMV8yMDIyX0NMRUFOLmNzdiIpCgojIyMjIFN0cmVhbWxpbmUgd29ya2luZyBkYXRhIGZyYW1lIG5hbWU6CgpgYGB7ciBzdHJlYW1saW5lIGRmIG5hbWV9CmN5Y2xpc3RpY19hbmFseXNpczwtIGN5Y2xpc3RpY18yMDIxXzIwMjJfQ0xFQU4KYGBgCgojIyBBZ2dyZWdhdGUsIEFuYWx5emUsIGFuZCBWaXN1YWxpemUKCiMjIyMgU2V0IHVwIGVudmlyb25tZW50IGZvciBtb3JlIGNsZWFyIGFnZ3JlZ2F0aW9uOgoKYGBge3Igc2V0LXVwIGFnZ3JlZ2F0aW9uIGVudmlyb25tZW50LCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkoc2NhbGVzKQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkoZm9ybWF0dGFibGUpCmBgYAoKYGBge3J9CmltcHJvdmVtZW50X2Zvcm1hdHRlciA8LSBmb3JtYXR0ZXIoInNwYW4iLCBzdHlsZSA9IHggfiBzdHlsZShmb250LndlaWdodCA9ICJib2xkIiwgY29sb3IgPSBpZmVsc2UoeCA+IC41MCwgInRvbWF0byIsIGlmZWxzZSh4IDwgLjUwLCAic3RlZWxibHVlIiwgImJsYWNrIikpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgeCB+IGljb250ZXh0KGlmZWxzZSAoeCA+IC41MCwgImFycm93LXVwIiwgImFycm93LWRvd24iKSwgeCkpCmBgYAoKIyMjIENvdW50IHJpZGVzIGFuZCB0aGVpciBwZXJjZW50YWdlCgojIyMjIENvdW50IHJpZGVzIGFuZCB0aGVpciBwZXJjZW50YWdlIG9mIHRvdGFsIGJ5IHVzZXIgdHlwZToKCmBgYHtyIGNhbGN1bGF0ZSBwZXJjZW50IHRvdGFsIHJpZGVzfQp1c2VyX3BlcmNlbnQgPC0gY3ljbGlzdGljX2FuYWx5c2lzICU+JQogIGdyb3VwX2J5KHVzZXJfdHlwZSkgJT4lCiAgc3VtbWFyaXplKHRvdGFsX3JpZGVzID0gbigpKSAlPiUKICBtdXRhdGUocGVyY2VudD1wZXJjZW50KHRvdGFsX3JpZGVzL3N1bSh0b3RhbF9yaWRlcyksMCkpCmBgYAoKYGBge3J9CmZvcm1hdHRhYmxlKHVzZXJfcGVyY2VudCwKICAgICAgICAgICAgIGFsaWduID1jKCJsIiwgImMiLCJyIiksIAogICAgICAgICAgICAgbGlzdCgndXNlcl90eXBlJyA9IGNvbG9yX3RpbGUoInNlYXNoZWxsMiIsICJzZWFzaGVsbDMiKSwgJ3BlcmNlbnQnID0gaW1wcm92ZW1lbnRfZm9ybWF0dGVyKSkKYGBgCgojIyMjIFBsb3QgcGVyY2VudGFnZSBvZiB0b3RhbCByaWRlcyBieSB1c2VyIHR5cGU6CgpgYGB7ciBwbG90IHBlcmNlbnQgdG90YWwgcmlkZXN9CmdncGxvdCh1c2VyX3BlcmNlbnQsIGFlcyh4ID0gIiIsIHkgPSBwZXJjZW50LCBmaWxsID0gdXNlcl90eXBlKSkgKwogIGdlb21fY29sKGNvbG9yID0gImJsYWNrIikgKwogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gcGVyY2VudCksIGNvbG9yID0gYygid2hpdGUiLCAxKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlBlcmNlbnRhZ2Ugb2YgVG90YWwgUmlkZXMgYnkgVXNlciBUeXBlIikpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpICsKICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCiMjIyMgQ291bnQgcmlkZXMgYnkgdXNlciB0eXBlIGJ5IG1vbnRoICh0b3RhbCA9IDQzOTc4ODcpOgoKTm90ZSB0aGF0IG1vbnRoIHJlZmVyZW5jZXMgc2hvdWxkIGJlIGNoYW5nZWQgdG8gbmFtZXMuCgpgYGB7ciBsaXN0IG1vbnRoIGNvdW50c30KdGFibGUoY3ljbGlzdGljXzIwMjFfMjAyMl9DTEVBTiRtb250aCkKYGBgCgojIyMjIENoYW5nZSBtb250aCByZWZlcmVuY2UgZnJvbSBpbnRlZ2VyIHRvIG5hbWU6CgpOb3RlIHRoYXQgbmFtZXMgYXJlIG91dCBvZiBvcmRlciBub3cgdGhhdCBkYXRhIHR5cGUgaXMgY2hhbmdlZCB0byBzdHJpbmcuCgpgYGB7ciBtdXRhdGUgbW9udGhzfQpjeWNsaXN0aWNfYW5hbHlzaXMgPC0gY3ljbGlzdGljXzIwMjFfMjAyMl9DTEVBTiAlPiUgbXV0YXRlKG1vbnRoID0gbW9udGguYWJiW2FzLm51bWVyaWMobW9udGgpXSkKdGFibGUoY3ljbGlzdGljX2FuYWx5c2lzJG1vbnRoKQpgYGAKCiMjIyMgUHV0IG1vbnRocyBpbiBvcmRlcjoKCmBgYHtyIG9yZGVyIG1vbnRoc30KY3ljbGlzdGljX2FuYWx5c2lzJG1vbnRoIDwtIG9yZGVyZWQoY3ljbGlzdGljX2FuYWx5c2lzJG1vbnRoLCBsZXZlbHM9YygiSmFuIiwiRmViIiwiTWFyIiwiQXByIiwiTWF5IiwiSnVuIiwiSnVsIiwiQXVnIiwgIlNlcCIsICJPY3QiLCJOb3YiLCAiRGVjIikpCnRhYmxlKGN5Y2xpc3RpY19hbmFseXNpcyRtb250aCkKYGBgCgojIyMgQ291bnQgcmlkZXMgYW5kIHRoZWlyIHBlcmNlbnRhZ2UgYnkgdXNlciB0eXBlIGJ5IG1vbnRoOgoKYGBge3J9CnJtKG1vbnRoX3RvdGFscykKYGBgCgpgYGB7ciBjYWxjdWxhdGUgcmlkZSBjb3VudCBieSBtb250aH0KIHJpZGVzX2J5X21vbnRoPC0gY3ljbGlzdGljX2FuYWx5c2lzICU+JQogIGdyb3VwX2J5KG1vbnRoLCB1c2VyX3R5cGUpICU+JQogIHN1bW1hcml6ZSh0b3RhbF9yaWRlcyA9IG4oKSkgJT4lCiAgbXV0YXRlKHBlcmNlbnQgPSAocGVyY2VudCh0b3RhbF9yaWRlcy9zdW0odG90YWxfcmlkZXMpLDApKSkKZm9ybWF0dGFibGUocmlkZXNfYnlfbW9udGgsCiAgICAgICAgICAgICBhbGlnbiA9YygibCIsImMiLCJjIiksIAogICAgICAgICAgIGxpc3QoJ21vbnRoJyA9IGNvbG9yX3RpbGUoInBpbmsiLCAibGlnaHRibHVlIiksICdwZXJjZW50JyA9IGltcHJvdmVtZW50X2Zvcm1hdHRlcikpCmBgYAoKIyMjIyBQbG90IHJpZGUgY291bnQgYnkgdXNlciB0eXBlIGJ5IG1vbnRoOgoKYGBge3IgcGxvdCBjb3VudCBvZiB1c2VyIHJpZGVzfQpjeWNsaXN0aWNfYW5hbHlzaXMlPiUKZ3JvdXBfYnkodXNlcl90eXBlLCBtb250aCklPiUKc3VtbWFyaXplKG51bWJlcl9vZl9yaWRlcyA9IG4oKSklPiUKYXJyYW5nZSh1c2VyX3R5cGUsIG1vbnRoKSU+JQpnZ3Bsb3QoYWVzKHggPSBtb250aCwgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IHVzZXJfdHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmxhY2siKSArCiAgc2NhbGVfZmlsbF9odWUobD00MCkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlVzZXIgVHlwZSIpKSsKICBnZ3RpdGxlKCJSaWRlIENvdW50IGJ5IFVzZXIgYnkgTW9udGgiKQpgYGAKCiMjIyMgUGVyY2VudGFnZSByaWRlcyBieSB1c2VyIHR5cGUgYnkgbW9udGg6CgpgYGB7ciBwbG90IHBlcmNlbnRhZ2Ugb2YgdXNlciByaWRlc30KY3ljbGlzdGljX2FuYWx5c2lzJT4lCmdyb3VwX2J5KG1vbnRoLCB1c2VyX3R5cGUpJT4lCnN1bW1hcml6ZSh0b3RhbF9yaWRlcyA9IG4oKSklPiUKbXV0YXRlKHBlcmNlbnQgPSBwZXJjZW50KHRvdGFsX3JpZGVzIC8gc3VtKHRvdGFsX3JpZGVzKSkpJT4lCmFycmFuZ2UodXNlcl90eXBlLCBtb250aCklPiUKZ2dwbG90KGFlcyh4ID0gbW9udGgsIHkgPSBwZXJjZW50LCBmaWxsID0gdXNlcl90eXBlKSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIGNvbG9yPSJibGFjayIpICsgc2NhbGVfZmlsbF9odWUobD00MCkrCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQgKHRpdGxlID0gIlVzZXIgVHlwZSIpKSsKICBnZ3RpdGxlKCJQZXJjZW50YWdlIFJpZGVzIGJ5IFVzZXIgYnkgTW9udGgiKQpgYGAKCiMjIyMgQ2hlY2sgdG90YWwgcmlkZSBjb3VudCBmb3IgZWFjaCB1c2VyIHR5cGUgYnkgZGF5IG9mIHRoZSB3ZWVrICh0b3RhbCA9IDQzOTc4ODcpOgoKYGBge3IgcmlkZSBjb3VudCBieSBkYXl9CnRhYmxlKGN5Y2xpc3RpY18yMDIxXzIwMjJfQ0xFQU4kZGF5X29mX3dlZWspCmBgYAoKIyMjIyBQdXQgZGF5cyBvZiB3ZWVrIGluIG9yZGVyOgoKYGBge3Igb3JkZXIgZGF5cyBvZiB3ZWVrfQpjeWNsaXN0aWNfYW5hbHlzaXMkZGF5X29mX3dlZWsgPC0gb3JkZXJlZChjeWNsaXN0aWNfYW5hbHlzaXMkZGF5X29mX3dlZWssIGxldmVscz1jKCJTdW5kYXkiLCAiTW9uZGF5IiwgIlR1ZXNkYXkiLCAiV2VkbmVzZGF5IiwgIlRodXJzZGF5IiwgIkZyaWRheSIsICJTYXR1cmRheSIpKQp0YWJsZShjeWNsaXN0aWNfYW5hbHlzaXMkZGF5X29mX3dlZWspCmBgYAoKIyMjIyBDb3VudCByaWRlcyBhbmQgdGhlaXIgcGVyY2VudGFnZSBieSB1c2VyIHR5cGUgYnkgZGF5IG9mIHRoZSB3ZWVrOgoKYGBge3IgY2FsY3VsYXRlIHVzZXIgcmlkZSBjb3VudHMgYW5kIHBlcmNlbnRhZ2V9CnJpZGVzX2J5X2RheV9vZl93ZWVrPC0gY3ljbGlzdGljX2FuYWx5c2lzICU+JQogIGdyb3VwX2J5KGRheV9vZl93ZWVrLCB1c2VyX3R5cGUpICU+JQogIHN1bW1hcml6ZSh0b3RhbF9yaWRlcyA9IG4oKSkgJT4lCiAgbXV0YXRlKHBlcmNlbnQgPSAocGVyY2VudCh0b3RhbF9yaWRlcy9zdW0odG90YWxfcmlkZXMpLDApKSkKZm9ybWF0dGFibGUocmlkZXNfYnlfZGF5X29mX3dlZWssCiAgICAgICAgICAgICBhbGlnbiA9YygibCIsImMiLCJjIiksIAogICAgICAgICAgIGxpc3QoJ2RheV9vZl93ZWVrJyA9IGNvbG9yX3RpbGUoInBpbmsiLCAibGlnaHRibHVlIiksICdwZXJjZW50JyA9IGltcHJvdmVtZW50X2Zvcm1hdHRlcikpCmBgYAoKIyMjIyBQbG90IHJpZGUgY291bnQgYnkgdXNlciB0eXBlIGJ5IGRheSBvZiB0aGUgd2VlazoKCmBgYHtyIHBsb3QgdXNlciByaWRlcyBieSBjb3VudCwgZGF5fQpjeWNsaXN0aWNfYW5hbHlzaXMlPiUKZ3JvdXBfYnkodXNlcl90eXBlLCBkYXlfb2Zfd2VlayklPiUKc3VtbWFyaXplKG51bWJlcl9vZl9yaWRlcyA9IG4oKSklPiUKYXJyYW5nZSh1c2VyX3R5cGUsIGRheV9vZl93ZWVrKSU+JQpnZ3Bsb3QoYWVzKHggPSBkYXlfb2Zfd2VlaywgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IHVzZXJfdHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmxhY2siKSArCiAgc2NhbGVfZmlsbF9odWUobD00MCkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiUmlkZSBDb3VudCBieSBVc2VyIGJ5IERheSIpCmBgYAoKIyMjIyBQbG90IHBlcmNlbnRhZ2Ugb2YgcmlkZXMgYnkgdXNlciB0eXBlIGJ5IGRheSBvZiB0aGUgd2VlazoKCmBgYHtyIHBsb3QgdXNlciByaWRlcyBieSBwZXJjZW50YWdlLCBkYXl9CmN5Y2xpc3RpY19hbmFseXNpcyU+JQpncm91cF9ieShkYXlfb2Zfd2VlaywgdXNlcl90eXBlKSU+JQpzdW1tYXJpemUodG90YWxfcmlkZXMgPSBuKCkpJT4lCm11dGF0ZShwZXJjZW50PWZvcm1hdHRhYmxlOjpwZXJjZW50KHRvdGFsX3JpZGVzL3N1bSh0b3RhbF9yaWRlcykpKSU+JQphcnJhbmdlKHVzZXJfdHlwZSwgZGF5X29mX3dlZWspJT4lCmdncGxvdChhZXMoeCA9IGRheV9vZl93ZWVrLCB5ID0gcGVyY2VudCwgZmlsbCA9IHVzZXJfdHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmxhY2siKSArCiAgc2NhbGVfZmlsbF9odWUobD00MCkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBvZiBSaWRlcyBieSBVc2VyIGJ5IERheSIpCmBgYAoKIyMjIEFnZ3JlZ2F0ZSB0cmlwIGR1cmF0aW9uIGJ5IHVzZXIgdHlwZSBhbmQgYmlrZSB0eXBlCgojIyMjIEFnZ3JlZ2F0ZSBjb2x1bW4gdHJpcCBkdXJhdGlvbjoKCmBgYHtyIGFnZ3JlZ2F0ZSB0cmlwIGR1cmF0aW9uc30Kcm91bmQoc3VtbWFyeShjeWNsaXN0aWNfYW5hbHlzaXMkdHJpcF9kdXJhdGlvbikvNjApCmBgYAoKIyMjIyBBZ2dyZWdhdGUgdHJpcCBkdXJhdGlvbiBieSB1c2VyIHR5cGU6CgpOb3RlIHRoYXQgdGhlIGF2ZXJhZ2UgZHVyYXRpb24gb2YgYSBjYXN1YWwgdXNlcidzIHJpZGUgaXMgcm91Z2hseSB0d2ljZSBhcyBsb25nIGFzIGEgbWVtYmVyJ3MgcmlkZS4KCmBgYHtyIGFnZ3JlZ2F0ZSB0cmlwIGR1cmF0aW9uIGJ5IHVzZXJ9CnN1bW1hcnlfdHJpcF9kdXJhdGlvbiA8LSBjeWNsaXN0aWNfYW5hbHlzaXMgJT4lCiAgZ3JvdXBfYnkodXNlcl90eXBlKSAlPiUKICBzdW1tYXJpemUoYXZlcmFnZV9kdXJhdGlvbj1yb3VuZCgobWVhbih0cmlwX2R1cmF0aW9uKSkvNjApLAogICAgICAgICAgICBtZWRpYW5fZHVyYXRpb249cm91bmQobWVkaWFuKHRyaXBfZHVyYXRpb24pLzYwKSwKICAgICAgICAgICAgbWluX2R1cmF0aW9uPXJvdW5kKG1pbih0cmlwX2R1cmF0aW9uKS82MCksCiAgICAgICAgICAgIG1heF9kdXJhdGlvbj1yb3VuZChtYXgodHJpcF9kdXJhdGlvbikvNjApKQpmb3JtYXR0YWJsZShzdW1tYXJ5X3RyaXBfZHVyYXRpb24pCmBgYAoKIyMjIyBBZ2dyZWdhdGUgdHJpcCBkdXJhdGlvbiBieSB1c2VyIGJ5IG1vbnRoOgoKYGBge3J9CmltcHJvdmVtZW50IDwtIGZvcm1hdHRlcigic3BhbiIsIHN0eWxlID0geCB+IHN0eWxlKGZvbnQud2VpZ2h0ID0gImJvbGQiLCBjb2xvciA9IGlmZWxzZSh4ID4gMjAsICJ0b21hdG8iLCBpZmVsc2UoeCA8IDIwLCAic3RlZWxibHVlIiwgImJsYWNrIikpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgeCB+IGljb250ZXh0KGlmZWxzZSh4ID49IDIwLCAiYXJyb3ctdXAiLCAiYXJyb3ctZG93biIpLCB4KSkKYGBgCgpgYGB7ciBzdW1tYXJpemUgYXZnIHVzZXIgZHVyYXRpb24gYnkgbW9udGh9CmJ5X21vbnRoIDwtIGN5Y2xpc3RpY19hbmFseXNpcyU+JQpncm91cF9ieShtb250aCwgdXNlcl90eXBlKSU+JQpzdW1tYXJpemUoYXZlcmFnZV9kdXJhdGlvbj1yb3VuZCgobWVhbih0cmlwX2R1cmF0aW9uKSkvNjApKSU+JQphcnJhbmdlKG1vbnRoLCB1c2VyX3R5cGUpCmZvcm1hdHRhYmxlKGJ5X21vbnRoLAogICAgICAgICAgICAgYWxpZ24gPWMoImwiLCJjIiwiYyIpLCAKICAgICAgICAgICBsaXN0KCdtb250aCcgPSBjb2xvcl90aWxlKCJwaW5rIiwgImxpZ2h0Ymx1ZSIpLCAnYXZlcmFnZV9kdXJhdGlvbicgPSBpbXByb3ZlbWVudCkpCmBgYAoKIyMjIyBQbG90IGF2ZXJhZ2UgdHJpcCBkdXJhdGlvbiBieSB1c2VyIGJ5IG1vbnRoOgoKYGBge3IgcGxvdCBhdmcgdXNlciBkdXJhdGlvbiBieSBtb250aH0KY3ljbGlzdGljX2FuYWx5c2lzJT4lCmdyb3VwX2J5KHVzZXJfdHlwZSwgbW9udGgpICU+JQpzdW1tYXJpemUoYXZlcmFnZV90cmlwX2R1cmF0aW9uID0gbWVhbih0cmlwX2R1cmF0aW9uKS82MCkgJT4lCmFycmFuZ2UodXNlcl90eXBlLCBtb250aCkgJT4lCmdncGxvdChhZXMoeCA9IG1vbnRoLCB5ID0gYXZlcmFnZV90cmlwX2R1cmF0aW9uLCBmaWxsID0gdXNlcl90eXBlKSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJzdGFjayIsIGNvbG9yPSJibGFjayIpICsKICBzY2FsZV9maWxsX2h1ZShsPTQwKSArCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQgKHRpdGxlID0gIlVzZXIgVHlwZSIpKSsKICBnZ3RpdGxlKCJBdmVyYWdlIFRyaXAgRHVyYXRpb24gYnkgVXNlciBieSBNb250aCIpCmBgYAoKIyMjIyBBZ2dyZWdhdGUgdHJpcCBkdXJhdGlvbiBieSB1c2VyIGJ5IGRheSBvZiB0aGUgd2VlazoKCmBgYHtyIHN1bW1hcml6ZSBhdmcgdXNlciBkdXJhdGlvbiBieSBkYXl9CmJ5X2RheV9vZl93ZWVrIDwtIGN5Y2xpc3RpY19hbmFseXNpcyU+JQpncm91cF9ieShkYXlfb2Zfd2VlaywgdXNlcl90eXBlKSU+JQpzdW1tYXJpemUoYXZlcmFnZV9kdXJhdGlvbj1yb3VuZCgobWVhbih0cmlwX2R1cmF0aW9uKSkvNjApKSU+JQphcnJhbmdlKGRheV9vZl93ZWVrLCB1c2VyX3R5cGUpCnRpYmJsZShieV9kYXlfb2Zfd2VlaykKZm9ybWF0dGFibGUoYnlfZGF5X29mX3dlZWssCiAgICAgICAgICAgICBhbGlnbiA9YygibCIsImMiLCJjIiksIAogICAgICAgICAgIGxpc3QoJ2RheV9vZl93ZWVrJyA9IGNvbG9yX3RpbGUoInBpbmsiLCAibGlnaHRibHVlIiksICdhdmVyYWdlX2R1cmF0aW9uJyA9IGltcHJvdmVtZW50KSkKYGBgCgojIyMjIFBsb3QgYXZlcmFnZSB0cmlwIGR1cmF0aW9uIGJ5IHVzZXIgYnkgZGF5IG9mIHRoZSB3ZWVrOgoKYGBge3IgcGxvdCBhdmcgdXNlciBkdXJhdGlvbiBieSBkYXl9CmN5Y2xpc3RpY19hbmFseXNpcyU+JQpncm91cF9ieSh1c2VyX3R5cGUsIGRheV9vZl93ZWVrKSAlPiUKc3VtbWFyaXplKGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHRyaXBfZHVyYXRpb24pLzYwKSAlPiUKYXJyYW5nZSh1c2VyX3R5cGUsIGRheV9vZl93ZWVrKSAlPiUKZ2dwbG90KGFlcyh4ID0gZGF5X29mX3dlZWssIHkgPSBhdmVyYWdlX2R1cmF0aW9uLCBmaWxsID0gdXNlcl90eXBlKSkgKyBnZW9tX2NvbChwb3NpdGlvbiA9ICJzdGFjayIsIGNvbG9yPSJibGFjayIpICsKICBzY2FsZV9maWxsX2h1ZShsPTQwKSArCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQgKHRpdGxlID0gIlVzZXIgVHlwZSIpKSsKICBnZ3RpdGxlKCJBdmVyYWdlIFRyaXAgRHVyYXRpb24gYnkgVXNlciBieSBEYXkiKQpgYGAKCiMjIyBUb3RhbCBiaWtlIHR5cGUgdXNhZ2UgYnkgdXNlciB0eXBlCgojIyMjIENvdW50IHJpZGVzIGFuZCB0aGVpciBwZXJjZW50IG9mIHRvdGFsIGJ5IGJpa2UgdHlwZToKCmBgYHtyIGNhbGN1bGF0ZSBwZXJjZW50YWdlcyBvZiBiaWtlIHR5cGV9CmJ5X2Jpa2VfdHlwZSA8LSBjeWNsaXN0aWNfYW5hbHlzaXMgJT4lCiAgZ3JvdXBfYnkoYmlrZV90eXBlKSAlPiUKICBzdW1tYXJpemUodG90YWxfbnVtYmVyID0gbigpKSAlPiUKICBtdXRhdGUocGVyY2VudD1wZXJjZW50KHRvdGFsX251bWJlci9zdW0odG90YWxfbnVtYmVyKSwwKSkKZm9ybWF0dGFibGUoYnlfYmlrZV90eXBlLAogICAgICAgICAgICAgYWxpZ24gPWMoImwiLCJjIiwiciIpLCAKICAgICAgICAgICBsaXN0KCdiaWtlX3R5cGUnID0gY29sb3JfdGlsZSgicGluayIsICJyZWQiKSwgJ3BlcmNlbnQnID0gaW1wcm92ZW1lbnRfZm9ybWF0dGVyKSkKYGBgCgojIyMjIFBsb3QgcGVyY2VudGFnZSBvZiB1c2UgYnkgYmlrZSB0eXBlOgoKYGBge3IgcGxvdCBwZXJjZW50YWdlcyBvZiBiaWtlIHR5cGV9CmdncGxvdChieV9iaWtlX3R5cGUsIGFlcyh4ID0gIiIsIHkgPSBwZXJjZW50LCBmaWxsID0gYmlrZV90eXBlKSkgKwogIGdlb21fY29sKGNvbG9yID0gImJsYWNrIikgKwogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gcGVyY2VudCksIGNvbG9yID0gYygid2hpdGUiLCAxLCAxKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlBlcmNlbnRhZ2Ugb2YgVXNlIG9mIEJpa2UgVHlwZXMiKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKwogIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArCiAgdGhlbWVfdm9pZCgpCmBgYAoKIyMjIyBDb3VudCByaWRlcyBhbmQgdGhlaXIgcGVyY2VudGFnZSBieSBiaWtlIGFuZCB1c2VyIHR5cGUgYW5kIGFnZ3JlZ2F0ZSBhdmVyYWdlIGR1cmF0aW9uIG9mIHRob3NlIHJpZGVzOgoKYGBge3IgY2FsY3VsYXRlIHBlcmNlbnRhZ2VzLCBudW0gb2YgcmlkZXMsIGFuZCBkdXJhdGlvbiBieSBiaWtlIHR5cGV9CmJpa2VfcHJlZmVyZW5jZSA8LSBjeWNsaXN0aWNfYW5hbHlzaXMgJT4lCiAgZ3JvdXBfYnkoYmlrZV90eXBlLCB1c2VyX3R5cGUpICU+JQogIHN1bW1hcml6ZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb249cm91bmQoKG1lYW4odHJpcF9kdXJhdGlvbikpLzYwKSklPiUKICBtdXRhdGUocGVyY2VudD1wZXJjZW50KG51bWJlcl9vZl9yaWRlcy9zdW0obnVtYmVyX29mX3JpZGVzKSwwKSklPiUKICBhcnJhbmdlKHVzZXJfdHlwZSwgYmlrZV90eXBlKQpmb3JtYXR0YWJsZShiaWtlX3ByZWZlcmVuY2UsCiAgICAgICAgICAgICBhbGlnbiA9YygibCIsImMiLCJjIiksIAogICAgICAgICAgIGxpc3QoJ2Jpa2VfdHlwZScgPSBjb2xvcl90aWxlKCJwaW5rIiwgImxpZ2h0Ymx1ZSIpLCAncGVyY2VudCcgPSBpbXByb3ZlbWVudF9mb3JtYXR0ZXIpKQpgYGAKCiMjIyMgUGxvdCByaWRlIGNvdW50IGJ5IGJpa2UgYW5kIHVzZXIgdHlwZToKCmBgYHtyIHBsb3QgbnVtYmVyIG9mIHJpZGVzIG9uIGJpa2UgdHlwZXMgdG8gdXNlciB0eXBlc30KYmlrZV9wcmVmZXJlbmNlJT4lCmdncGxvdChhZXMoeCA9IGJpa2VfdHlwZSwgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IHVzZXJfdHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmxhY2siKSArCiAgc2NhbGVfZmlsbF9odWUobD00MCkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiUmlkZSBDb3VudCBieSBCaWtlIGFuZCBVc2VyIFR5cGUiKQpgYGAKCiMjIyMgUGxvdCBwZXJjZW50YWdlIG9mIHJpZGVzIGJ5IGJpa2UgYW5kIHVzZXIgdHlwZToKCmBgYHtyIHBsb3QgcGVyY2VudGFnZSBvZiB1c2Ugb2YgYmlrZSB0eXBlcyBieSB1c2VyIHR5cGVzfQpiaWtlX3ByZWZlcmVuY2UlPiUKZ2dwbG90KGFlcyh4ID0gYmlrZV90eXBlLCB5ID0gcGVyY2VudCwgZmlsbCA9IHVzZXJfdHlwZSkpICsgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmxhY2siKSArCiAgc2NhbGVfZmlsbF9odWUobD00MCkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBvZiBSaWRlcyBieSBCaWtlIGFuZCBVc2VyIFR5cGUiKQpgYGAKCiMjIyMgUGxvdCBhdmVyYWdlIGR1cmF0aW9uIG9mIHJpZGUgYnkgYmlrZSBhbmQgdXNlciB0eXBlOgoKYGBge3IgcGxvdCBhdmVyYWdlIGR1cmF0aW9uIG9mIGJpa2UgdHlwZXMgYnkgdXNlciB0eXBlc30KYmlrZV9wcmVmZXJlbmNlJT4lCmdncGxvdChhZXMoeCA9IGJpa2VfdHlwZSwgeSA9IGF2ZXJhZ2VfZHVyYXRpb24sIGZpbGwgPSB1c2VyX3R5cGUpKSArIAogICAgICAgICAgICAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiLCBjb2xvcj0iYmxhY2siKSArCiAgc2NhbGVfZmlsbF9odWUobD00MCkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiQXZlcmFnZSBEdXJhdGlvbiBvZiBSaWRlcyBieSBCaWtlIGFuZCBVc2VyIFR5cGUiKQpgYGAKCiMjIyMgQ3JlYXRlIHR3byBwaWUgY2hhcnRzIGNvbXBhcmF0aXZlbHkgZGVtb25zdHJhdGluZyBwZXJjZW50YWdlIG9mIHVzZSBvZiBiaWtlIHR5cGUgYnkgdXNlciB0eXBlLgoKTm90ZSB0aGF0IGVsZWN0cmljIGJpa2VzIHdlcmUgY2hvc2VuIHJvdWdobHkgMzAlIG9mIHRoZSB0aW1lIGZvciBib3RoIHVzZXIgdHlwZXMuCgojIyMjIyBDb3VudCByaWRlcyBhbmQgdGhlaXIgcGVyY2VudGFnZSBieSBiaWtlIHR5cGUgYnkgY2FzdWFsIHVzZXI6CgpgYGB7ciBjYWxjdWxhdGUgcGVyY2VudGFnZSBvZiBjYXVzYWwgdXNlcn0KYnlfY2FzdWFsIDwtIGN5Y2xpc3RpY19hbmFseXNpcyAlPiUKICBmaWx0ZXIodXNlcl90eXBlID09ICJjYXN1YWwiKSAlPiUKICBncm91cF9ieSh1c2VyX3R5cGUsIGJpa2VfdHlwZSkgJT4lCiAgc3VtbWFyaXplKHRvdGFsX251bWJlciA9IG4oKSwgcGVyY2VudCA9IG4oKSkgJT4lCiAgbXV0YXRlKHBlcmNlbnQgPSBwZXJjZW50KHBlcmNlbnQgLyBzdW0ocGVyY2VudCksMCkpCmZvcm1hdHRhYmxlKGJ5X2Nhc3VhbCwKICAgICAgICAgICAgIGFsaWduID1jKCJsIiwiYyIsImMiLCAiciIpLCAKICAgICAgICAgICBsaXN0KCdiaWtlX3R5cGUnID0gY29sb3JfdGlsZSgicGluayIsICJsaWdodGJsdWUiKSwgJ3BlcmNlbnQnID0gaW1wcm92ZW1lbnRfZm9ybWF0dGVyKSkKYGBgCgojIyMjIyBQbG90IHBlcmNlbnRhZ2Ugb2YgYmlrZSB0eXBlIHVzZWQgYnkgY2FzdWFsIHVzZXI6CgpgYGB7ciBwbG90IHBlcmNlbnRhZ2Ugb2YgY2F1c2FsIHVzZXJ9CmdncGxvdChieV9jYXN1YWwsIGFlcyh4ID0gIiIsIHkgPSBwZXJjZW50LCBmaWxsID0gYmlrZV90eXBlKSkgKwogIGdlb21fY29sKGNvbG9yID0gImJsYWNrIikgKwogIGdlb21fbGFiZWwoYWVzKGxhYmVsID0gcGVyY2VudCksIGNvbG9yID0gYygid2hpdGUiLCAxLCAxKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlBlcmNlbnRhZ2Ugb2YgQmlrZSBUeXBlIFVzZSBieSBDYXN1YWwgVXNlcnMiKSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKwogIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArCiAgdGhlbWVfdm9pZCgpCmBgYAoKIyMjIyMgQ291bnQgcmlkZXMgYW5kIHRoZWlyIHBlcmNlbnRhZ2UgYnkgYmlrZSB0eXBlIGJ5IG1lbWJlciB1c2VyOgoKYGBge3IgY2FsY3VsYXRlIHBlcmNlbnRhZ2Ugb2YgbWVtYmVyIHVzZXJ9CmJ5X21lbWJlciA8LSBjeWNsaXN0aWNfYW5hbHlzaXMgJT4lCiAgZmlsdGVyKHVzZXJfdHlwZSA9PSAibWVtYmVyIikgJT4lCiAgZ3JvdXBfYnkodXNlcl90eXBlLCBiaWtlX3R5cGUpICU+JQogIHN1bW1hcml6ZSh0b3RhbF9udW1iZXIgPSBuKCksIHBlcmNlbnQgPSBuKCkpICU+JQogIG11dGF0ZShwZXJjZW50ID0gcGVyY2VudChwZXJjZW50IC8gc3VtKHBlcmNlbnQpLDApKQpmb3JtYXR0YWJsZShieV9jYXN1YWwsCiAgICAgICAgICAgICBhbGlnbiA9YygibCIsImMiLCJjIiwgInIiKSwgCiAgICAgICAgICAgbGlzdCgnYmlrZV90eXBlJyA9IGNvbG9yX3RpbGUoInBpbmsiLCAibGlnaHRibHVlIiksICdwZXJjZW50JyA9IGltcHJvdmVtZW50X2Zvcm1hdHRlcikpCmBgYAoKIyMjIyMgUGxvdCBwZXJjZW50YWdlIG9mIGJpa2UgdHlwZSB1c2VkIGJ5IG1lbWJlciB1c2VyOgoKYGBge3IgcGxvdCBwZXJjZW50YWdlIG9mIG1lbWJlciB1c2VyfQpnZ3Bsb3QoYnlfbWVtYmVyLCBhZXMoeCA9ICIiLCB5ID0gcGVyY2VudCwgZmlsbCA9IGJpa2VfdHlwZSkpICsKICBnZW9tX2NvbChjb2xvciA9ICJibGFjayIpICsKICBnZW9tX2xhYmVsKGFlcyhsYWJlbCA9IHBlcmNlbnQpLCBjb2xvciA9IGMoIndoaXRlIiwgMSksIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLHNob3cubGVnZW5kID0gRkFMU0UpICsKICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICJQZXJjZW50YWdlIG9mIEJpa2UgVHlwZSBVc2UgYnkgTWVtYmVyIFVzZXJzIikpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpICsKICBjb29yZF9wb2xhcih0aGV0YSA9ICJ5IikgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCiMjIyBJbnZlc3RpZ2F0ZSBzdGF0aW9uIHVzZSBieSB1c2VyIHR5cGUKCiMjIyMgQ3JlYXRlIG5ldyBib29sZWFuIGNvbHVtbiB0byBjYWxjdWxhdGUgcm91bmQgdHJpcHMgYmFzZWQgb24gc3RhdGlvbiBpZHM6CgpgYGB7ciBjcmVhdGUgY29sdW1uIHJvdW5kdHJpcH0KY3ljbGlzdGljX2FuYWx5c2lzX1YyIDwtIGN5Y2xpc3RpY19hbmFseXNpcyAlPiUKICBtdXRhdGUocm91bmRfdHJpcCA9IHN0YXJ0X3N0YXRpb25faWQgPT0gZW5kX3N0YXRpb25faWQpCmBgYAoKIyMjIyBDb21wYXJlIG51bWJlciBhbmQgcGVyY2VudGFnZSBvZiByb3VuZCB0cmlwcyBhbmQgdGhlaXIgYXZlcmFnZSBkdXJhdGlvbiBieSB1c2VyOgoKYGBge3IgY2FsY3VsYXRlIG51bWJlciBvZiByb3VuZHRyaXBzfQpyb3VuZF90cmlwIDwtIGN5Y2xpc3RpY19hbmFseXNpc19WMiAlPiUKICBncm91cF9ieSh1c2VyX3R5cGUsIHJvdW5kX3RyaXApICU+JQogc3VtbWFyaXplKG51bWJlcl9vZl9yaWRlcyA9IG4oKSwgYXZlcmFnZV9kdXJhdGlvbiA9IHJvdW5kKG1lYW4odHJpcF9kdXJhdGlvbikvNjApKSU+JQogIG11dGF0ZShwZXJjZW50X3JvdW5kX3RyaXBzID0gcGVyY2VudChudW1iZXJfb2ZfcmlkZXMgLyBzdW0obnVtYmVyX29mX3JpZGVzKSwwKSklPiUKICBhcnJhbmdlKHVzZXJfdHlwZSwgcm91bmRfdHJpcCkKZm9ybWF0dGFibGUocm91bmRfdHJpcCwKICAgICAgICAgICAgIGFsaWduID1jKCJsIiwiYyIsImMiLCAiYyIsICJyIiksIAogICAgICAgICAgIGxpc3QoJ2Jpa2VfdHlwZScgPSBjb2xvcl90aWxlKCJwaW5rIiwgImxpZ2h0Ymx1ZSIpLCAncGVyY2VudF9yb3VuZF90cmlwcycgPSBmb3JtYXR0ZXIoInNwYW4iLCBzdHlsZSA9IHggfiBzdHlsZShmb250LndlaWdodCA9ICJib2xkIiwgY29sb3IgPSBpZmVsc2UoeCA+IC45MCwgInRvbWF0byIsIGlmZWxzZSh4IDwgLjkwLCAic3RlZWxibHVlIiwgImJsYWNrIikpKSkpKQpgYGAKCiMjIyMgUGxvdCBwZXJjZW50YWdlIG9mIHJvdW5kIHRyaXBzIGJ5IHVzZXI6CgpgYGB7ciBwbG90IHBlcmNlbnRhZ2UgcnR9CnJvdW5kX3RyaXAgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcm91bmRfdHJpcCwgeSA9IHBlcmNlbnRfcm91bmRfdHJpcHMsIGZpbGwgPSB1c2VyX3R5cGUpKSArCiAgICAgICAgICAgICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIGNvbG9yPSJibGFjayIpICsKICBzY2FsZV9maWxsX2h1ZShsPTQwKSArCmd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBvZiBSb3VuZCBUcmlwcyBieSBVc2VycyIpCmBgYAoKIyMjIyBDb21wYXJlIG51bWJlciBhbmQgcGVyY2VudGFnZSBvZiByb3VuZCB0cmlwcyBhbmQgdGhlaXIgYXZlcmFnZSBkdXJhdGlvbiBieSB1c2VyIGFuZCBiaWtlIHR5cGU6CgpgYGB7ciBhZ2dyZWdhdGUgcm91bmQgdHJpcHN9CnJvdW5kX3RyaXAgPC0gY3ljbGlzdGljX2FuYWx5c2lzX1YyICU+JQogIGdyb3VwX2J5KHVzZXJfdHlwZSwgcm91bmRfdHJpcCwgYmlrZV90eXBlKSAlPiUKIHN1bW1hcml6ZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSByb3VuZChtZWFuKHRyaXBfZHVyYXRpb24pLzYwKSklPiUKICBtdXRhdGUocGVyY2VudF9yb3VuZF90cmlwcyA9IHBlcmNlbnQobnVtYmVyX29mX3JpZGVzIC8gc3VtKG51bWJlcl9vZl9yaWRlcyksMCkpJT4lCiAgYXJyYW5nZSh1c2VyX3R5cGUsIHJvdW5kX3RyaXAsIGJpa2VfdHlwZSkKZm9ybWF0dGFibGUocm91bmRfdHJpcCwgYWxpZ24gPWMoImwiLCJjIiwiYyIsImMiLCJjIiwiciIpLCAKICAgICAgICAgICBsaXN0KCdwZXJjZW50X3JvdW5kX3RyaXBzJyA9IGZvcm1hdHRlcigic3BhbiIsIHN0eWxlID0geCB+IHN0eWxlKGZvbnQud2VpZ2h0ID0gImJvbGQiKSkpKSAKYGBgCgojIyMjIFBsb3QgcGVyY2VudGFnZSBvZiByb3VuZCB0cmlwcyBieSB1c2VyIGFuZCBiaWtlIHR5cGU6CgpgYGB7ciBydHMgYnkgdXNlciBhbmQgYmlrZX0Kcm91bmRfdHJpcF9UUlVFIDwtIHJvdW5kX3RyaXAgJT4lCiAgZmlsdGVyKHJvdW5kX3RyaXAgPT0gVFJVRSkKcm91bmRfdHJpcF9UUlVFICU+JQogIGdncGxvdChhZXMoeCA9IGJpa2VfdHlwZSwgeSA9IHBlcmNlbnRfcm91bmRfdHJpcHMsIGZpbGwgPSB1c2VyX3R5cGUpKSArCiAgICAgICAgICAgICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIsIGNvbG9yPSJibGFjayIpICsKICBzY2FsZV9maWxsX2h1ZShsPTQwKSArCmd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kICh0aXRsZSA9ICJVc2VyIFR5cGUiKSkrCiAgZ2d0aXRsZSgiUGVyY2VudGFnZSBvZiBSb3VuZCBUcmlwcyBieSBCaWtlIGFuZCBVc2VyIFR5cGVzIikKYGBgCgojIyMgSW52ZXN0aWdhdGUgbW9zdCBhbmQgbGVhc3QgdXNlZCBzdGF0aW9ucyBvZiB1c2VyIHR5cGVzLgoKIyMjIyBDYWxjdWxhdGUgbW9zdCBhbmQgbGVhc3QgdXNlZCBzdGF0aW9ucyBvZiBib3RoIHVzZXIgdHlwZXM6CgpgYGB7ciBjYWxjdWxhdGUgbW9zdCB1c2VkIHN0YXRpb259Cm1vc3RfcG9wdWxhcl9zdGF0aW9ucyA8LSBjeWNsaXN0aWNfYW5hbHlzaXNfVjIgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICBzdW1tYXJpemUobnVtYmVyX29mX3JpZGVzID0gbigpLGF2ZXJhZ2VfZHVyYXRpb24gPSByb3VuZChtZWFuKHRyaXBfZHVyYXRpb24pLzYwKSklPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3JpZGVzKSklPiUKICBoZWFkKG4gPSA1KQpmb3JtYXR0YWJsZShtb3N0X3BvcHVsYXJfc3RhdGlvbnMpCmBgYAoKIyMjIyBDYWxjdWxhdGUgbW9zdCB1c2VkIHN0YXRpb24gb2YgY2F1c2FsIHJpZGVyOgoKYGBge3IgY2FsY3VsYXRlIG1vc3QgdXNlZCBzdGF0aW9uIGJ5IGNhc3VhbH0KbW9zdF9wb3B1bGFyX2Nhc3VhbCA8LSBjeWNsaXN0aWNfYW5hbHlzaXNfVjIgJT4lCiAgZmlsdGVyKHVzZXJfdHlwZSA9PSAiY2FzdWFsIikgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICBzdW1tYXJpemUobnVtYmVyX29mX3JpZGVzPW4oKSwgYXZlcmFnZV9kdXJhdGlvbj1yb3VuZChtZWFuKHRyaXBfZHVyYXRpb24pLzYwKSklPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3JpZGVzKSklPiUKICBoZWFkKG4gPSA1KQpmb3JtYXR0YWJsZShtb3N0X3BvcHVsYXJfY2FzdWFsKQpgYGAKCiMjIyMgQ2FsY3VsYXRlIG1vc3QgdGFrZW4gY2FzdWFsIHRyaXBzIGZyb20gdGhlIG1vc3QgdXNlZCBzdGFydCBzdGF0aW9uOgoKTm90ZSB0aGF0IFN0cmVldGVyIERyICYgR3JhbmQgQXZlIGlzIE5hdnkgUGllciBvbiB0aGUgTGFrZWZyb250IFRyYWlsLgoKYGBge3IgY2FsY3VsYXRlIG1vc3QgdXNlZCB0cmlwIGZyb20gbW9zdCB1c2VkIHN0YXRpb24gYnkgY2FzdWFsfQpMRlQgPC0gY3ljbGlzdGljX2FuYWx5c2lzX1YyICU+JQogIGZpbHRlcih1c2VyX3R5cGUgPT0gImNhc3VhbCIsIHN0YXJ0X3N0YXRpb25fbmFtZSA9PSAiU3RyZWV0ZXIgRHIgJiBHcmFuZCBBdmUiKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX25hbWUsIGVuZF9zdGF0aW9uX25hbWUpJT4lCiAgICBzdW1tYXJpemUobnVtYmVyX29mX3JpZGVzID0gbigpLGF2ZXJhZ2VfZHVyYXRpb249cm91bmQobWVhbih0cmlwX2R1cmF0aW9uKS82MCkpJT4lCmFycmFuZ2UoZGVzYyhudW1iZXJfb2ZfcmlkZXMpKSU+JQogIGhlYWQobiA9IDUpCmZvcm1hdHRhYmxlKExGVCkKYGBgCgojIyMjIENhbGN1bGF0ZSBtb3N0IHVzZWQgc3RhdGlvbiBvZiBtZW1iZXIgcmlkZXI6CgpgYGB7ciBjYWxjdWxhdGUgbW9zdCB1c2VkIHN0YXRpb24gYnkgbWVtYmVyfQptb3N0X3BvcHVsYXJfbWVtYmVyIDwtIGN5Y2xpc3RpY19hbmFseXNpc19WMiAlPiUKICBmaWx0ZXIodXNlcl90eXBlID09ICJtZW1iZXIiKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9zdGF0aW9uX25hbWUpICU+JQogIHN1bW1hcml6ZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksYXZlcmFnZV9kdXJhdGlvbj1yb3VuZChtZWFuKHRyaXBfZHVyYXRpb24pLzYwKSklPiUKICBhcnJhbmdlKGRlc2MobnVtYmVyX29mX3JpZGVzKSklPiUKICBoZWFkKG4gPSA1KQpmb3JtYXR0YWJsZShtb3N0X3BvcHVsYXJfbWVtYmVyKQpgYGAKCiMjIyMgQ2FsY3VsYXRlIG1vc3QgdGFrZW4gbWVtYmVyIGNvbW11dGVyIHRyaXAgZnJvbSBtb3N0IHVzZWQgc3RhcnQgc3RhdGlvbjoKCk5vdGUgdGhhdCBDbGludG9uIFN0ICYgTWFkaXNvbiBTdCBpcyBVbmlvbiBTdGF0aW9uLgoKYGBge3IgY2FsY3VsYXRlIG1vc3QgdXNlZCB0cmlwIGZyb20gbW9zdCB1c2VkIHN0YXRpb24gYnkgbWVtYmVyfQpVbmlvbl9TdGF0aW9uIDwtIGN5Y2xpc3RpY19hbmFseXNpc19WMiAlPiUKICBmaWx0ZXIodXNlcl90eXBlID09ICJtZW1iZXIiLCBzdGFydF9zdGF0aW9uX25hbWUgPT0gIkNsaW50b24gU3QgJiBNYWRpc29uIFN0IikgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9uYW1lLCBlbmRfc3RhdGlvbl9uYW1lKSU+JQogICAgc3VtbWFyaXplKG51bWJlcl9vZl9yaWRlcyA9IG4oKSxhdmVyYWdlX2R1cmF0aW9uPXJvdW5kKG1lYW4odHJpcF9kdXJhdGlvbikvNjApKSU+JQphcnJhbmdlKGRlc2MobnVtYmVyX29mX3JpZGVzKSklPiUKICBoZWFkKG4gPSA1KQpmb3JtYXR0YWJsZShVbmlvbl9TdGF0aW9uKQpgYGAKCiMjIyMgQ2FsY3VsYXRlIGxlYXN0IHVzZWQgc3RhdGlvbnMgb2YgdXNlciB0eXBlczoKCmBgYHtyIGNhbGN1bGF0ZSBsZWFzdCB1c2VkIHN0YXRpb259CmxlYXN0X3BvcHVsYXIgPC0gY3ljbGlzdGljX2FuYWx5c2lzX1YyICU+JQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgc3VtbWFyaXplKG51bWJlcl9vZl9yaWRlcyA9IG4oKSklPiUKICBhcnJhbmdlKG51bWJlcl9vZl9yaWRlcyklPiUKICBoZWFkKG4gPSA1KQpmb3JtYXR0YWJsZShsZWFzdF9wb3B1bGFyKQpgYGAKCiMjIyMgQ2FsY3VsYXRlIGxlYXN0IHVzZWQgc3RhdGlvbnMgb2YgY2FzdWFsIHVzZXJzOgoKYGBge3IgY2FsY3VsYXRlIGxlYXN0IHVzZWQgc3RhdGlvbiBieSBjYXN1YWx9CmxlYXN0X3BvcHVsYXJfY2FzdWFsIDwtIGN5Y2xpc3RpY19hbmFseXNpc19WMiAlPiUKICBmaWx0ZXIodXNlcl90eXBlID09ICJjYXN1YWwiKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX25hbWUpICU+JQogIHN1bW1hcml6ZShudW1iZXJfb2ZfcmlkZXMgPSBuKCkpJT4lCiAgYXJyYW5nZShudW1iZXJfb2ZfcmlkZXMpJT4lCiAgaGVhZChuID0gNSkKZm9ybWF0dGFibGUobGVhc3RfcG9wdWxhcl9jYXN1YWwpCmBgYAoKIyMjIyBDYWxjdWxhdGUgbGVhc3QgdXNlZCBzdGF0aW9uIG9mIG1lbWJlciByaWRlcjoKCmBgYHtyIGNhbGN1bGF0ZSBsZWFzdCB1c2VkIHN0YXRpb24gYnkgbWVtYmVyfQpsZWFzdF9wb3B1bGFyX21lbWJlciA8LSBjeWNsaXN0aWNfYW5hbHlzaXNfVjIgJT4lCiAgZmlsdGVyKHVzZXJfdHlwZSA9PSAiY2FzdWFsIikgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICBzdW1tYXJpemUobnVtYmVyX29mX3JpZGVzID0gbigpKSU+JQogIGFycmFuZ2UobnVtYmVyX29mX3JpZGVzKSU+JQogIGhlYWQobiA9IDUpCmZvcm1hdHRhYmxlKGxlYXN0X3BvcHVsYXJfbWVtYmVyKQpgYGAK