In this notebook, I will focus on feature engineering and data-preprocessing. Feature engineering is defined as “the process of using domain knowledge to extract features from raw data via data mining techniques.” I will be adding some new features (spray angle, batter pitcher matchup, and shifting information), but a majority of my focus will be to get the data ready for modeling. Modeling will take place in the next notebook. Additionally, thanks to the baseballr package I will be adding venue and temperature variables.
Once finalized, I will be using the following features as inputs into my model: launch angle, exit velocity, spray_angle, batter handedness, pitcher handedness, the batter pitcher matchup by handedness, temperature, whether the game was played inside or not, pitch type, the count, where the pitch was thrown (using Baseball Savant’s zone feature), what type of shift the infield and outfield is in, and a combination of the stadium and the handedess of the batter.
I think all 14 of these features contributes to both: accurately classifying the outcome of a batted and gives the batter/pitcher credit for the batted ball outcome. For example, a 3-0 four-seam fastball middle-middle is a lot easier to hit for a home run than a 0-2 slider thrown down and away. Including those features will allow the model to give the proper credit for each batted ball.
Feature Engineering
Adding the pitcher’s name.
# player look up by mlbam key
chadwick_reduced <- chadwick_player_lu_table %>% dplyr::select(key_mlbam, name_last, name_first)
# remove NA to make df smaller
chadwick_reduced <- chadwick_reduced[!is.na(chadwick_reduced$key_mlbam),]
# getting each pitcher's name
batted_ball_processed <- merge(x = batted_ball_processed, y = chadwick_reduced, by.x = "pitcher", by.y = "key_mlbam")
batted_ball_processed %<>%
mutate(batted_ball_processed, pitcher_name = paste(name_first, name_last, sep = " ")) %>%
dplyr::select(-c(name_last, name_first, pitcher))
Adding what team the batter is. Will be used for metric validation later.
batted_ball_processed %<>%
mutate(batter_team = ifelse(inning_topbot == "Top", away_team, home_team))
Next, I’m going to change the pitch type groupings. I’m considering a knuckle curve to be a regular curveball, a splitter to be a change-up, and a sinker to be a two-seam fastball. Lastly, I’m grouping together knuckle balls, screwballs, forkballs, eephus, etc. into one over-arching ‘other’ category.
batted_ball_processed$pitch_type[batted_ball_processed$pitch_type == 'KC'] <- 'CU'
batted_ball_processed$pitch_type[batted_ball_processed$pitch_type == 'FS'] <- 'CH'
batted_ball_processed$pitch_type[batted_ball_processed$pitch_type == 'SI'] <- 'FT'
batted_ball_processed %<>%
mutate(pitch_type = case_when(
pitch_type %in% c("KN", "EP", "FO", NA, "SC", "UN") ~ "Other",
pitch_type == "FF" ~ "FF",
pitch_type == "SL" ~ "SL",
pitch_type == "CH" ~ "CH",
pitch_type == "FT" ~ "FT",
pitch_type == "CU" ~ "CU",
pitch_type == "FC" ~ "FC"
))
Here, I’m combining strikes and balls into one count variable.
batted_ball_processed %<>%
filter(balls != 4) %>% # for some reason there are two instances of a count with 4 balls
mutate(count = paste0(balls, "-", strikes)) %>%
dplyr::select(-c(balls, strikes))
Models tend to prefer numeric categories when possible, so I’m one-hot encoding pitcher handedness and batter stance. There will still be a column for each variable, but now a “1” will correspond to a RHP and a “0” to a LHP. Same thing for batter stance.
batted_ball_processed %<>%
mutate(stand_r = ifelse(stand == "R", 1, 0)) %>%
mutate(p_throws_r = ifelse(p_throws == "R", 1, 0)) %>%
dplyr::select(-c(stand, p_throws))
Now, I’m moving on to more ‘feature engineering.’ I’m adding variables for: the spray angle, the batter pitcher matchup (LvL, RvR, LvR, and RvL), and shifting data combined with batter stance. Outside of where the ball is hit, it reasons that the most important factor of whether a ball will be a hit or not (and what type of hit) is where the fielders are positioned. Unfortunately, there is no publicly available data on the pitch-by-pitch level of where the fielders are exactly positioned. Luckily, Baseball Savant provides some general information on a pitch-by-pitch basis. That information come’s from the if_fielding_alignment and of_fielding_alignment variables. For the infielders, there are three different types of shifts: standard, strategic, and infield shift. Infield shift is the shift that first comes to mind: three infielders are positioned on the same side of second base. Strategic shift is defined as a “catch-call for positioning that is neither ‘standard,’ nor ’three infielders to one side of second base.” For the outfielders, there are are also three types of shifts: standard, strategic, and 4th outfielder. Like infielding shifting, strategic shifting for outfielders is when there are not four outfielders and the alignment is not standard. I’m combinding the shifting information with the batter handedness. Obviously, an infield shift for a left handed batter has the infielders in a way different position than if the batter was right handed.
batted_ball_processed %<>%
mutate(spray_angle = round((atan((hc_x-125.42)/(198.27-hc_y))*180/pi*.75),1))
batted_ball_processed %<>%
mutate(batter_pitcher_matchup = ifelse(p_throws_r == 1 & stand_r == 1, "r.vs.r", #pithcer.hand_batter.hand
ifelse(p_throws_r == 1 & stand_r == 0, "r.vs.l",
ifelse(p_throws_r == 0 & stand_r == 1, "l.vs.r", "l.vs.l"))))
batted_ball_processed %<>%
mutate(batter_if_shift = case_when(
stand_r == 1 & if_fielding_alignment == "Standard" ~ "r_batter.no_shift",
stand_r == 1 & if_fielding_alignment == "Strategic" ~ "r_batter.strategic_shift",
stand_r == 1 & if_fielding_alignment == "Infield shift" ~ "r_batter.shift",
stand_r == 0 & if_fielding_alignment == "Standard" ~ "l_batter.no_shift",
stand_r == 0 & if_fielding_alignment == "Strategic" ~ "l_batter.strategic_shift",
stand_r == 0 & if_fielding_alignment == "Infield shift" ~ "l_batter.shift",
)) %>%
mutate(batter_of_shift = case_when(
stand_r == 1 & of_fielding_alignment == "Standard" ~ "r_batter.no_shift",
stand_r == 1 & of_fielding_alignment == "Strategic" ~ "r_batter.strategic_shift",
stand_r == 1 & of_fielding_alignment == "4th outfielder" ~ "r_batter.4th_outfielder",
stand_r == 0 & of_fielding_alignment == "Standard" ~ "l_batter.no_shift",
stand_r == 0 & of_fielding_alignment == "Strategic" ~ "l_batter.strategic_shift",
stand_r == 0 & of_fielding_alignment == "4th outfielder" ~ "l_batter.4th_outfielder",
)) %>%
dplyr::select(-c(if_fielding_alignment, of_fielding_alignment))
Lastly, I’m adding three new variables. The first is temperature. Baseball prospectus has shown how influential temperature is on the outcome of a batted ball. The best way I’ve been able to think about the affect of the temperature is comparing Wrigley field in April with the wind blowing in vs in August with the wind blowing out. In August, it feels like every ball hit in the air is going to leave the yard, while in April it’s a surprise if any ball does. The temperature variable is only the temperature at the start of the game, so it’s not perfect, but it does a pretty good job. Similarily to temperature, I’m adding a variable that reflects whether the game is inside a doom or not. While there aren’t many indoor games, I think this may be a good proxy for controlling other weather variables, like wind. The last variable is the venue of the game combined with batter handedness. I originally saw this in Baseball Prospectus’ DRC+ breakdown and thought if it was good enough for them, it’s good enough for me. As Jonathan Judge says “[an indicator for stadium and batter handedness] accounts for the reality that stadiums tend to play differently for each batter side, and allows us to park-adjust by handedness for each event.”
game_info <- baseballr::get_game_info_sup_petti()
batted_ball_processed %<>%
left_join(game_info %>% dplyr::select(c("game_pk", "venue_name", "temperature", "other_weather")),
by = c("game_pk")) %>%
mutate(is_dome = case_when(other_weather == "Roof Closed" | other_weather == "Dome" ~ 1,
TRUE ~ 0)) %>%
mutate(trim_venue = gsub("\\s+", "", venue_name),
venue_batter.stand = ifelse(stand_r == 1, paste0(trim_venue, "_", "r"), paste0(trim_venue, "_", "l"))) %>%
dplyr::select(-c(other_weather, trim_venue, venue_name)) %>%
filter(venue_batter.stand != "BB&TBallpark_r", venue_batter.stand != "HiramBithornStadium_l", # no statcast info from these parks
venue_batter.stand != "EstadiodeBeisbolMonterrey_r", venue_batter.stand != "EstadiodeBeisbolMonterrey_l")
# cleaning up stadium re-naming
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'U.S.CellularField_l'] <- 'GuaranteedRateField_l'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'U.S.CellularField_r'] <- 'GuaranteedRateField_r'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'O.coColiseum_l'] <- 'OaklandColiseum_l'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'O.coColiseum_r'] <- 'OaklandColiseum_r'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'AngelStadiumofAnaheim_l'] <- 'AngelStadium_l'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'AngelStadiumofAnaheim_r'] <- 'AngelStadium_r'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'OraclePark_l'] <- 'AT&TPark_l'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'OraclePark_r'] <- 'AT&TPark_r'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'T-MobilePark_l'] <- 'SafecoField_l'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'T-MobilePark_r'] <- 'SafecoField_r'
# for some reason 2016-06-12 Giants vs Dodgers had a null stadium, but it happened at AT&T Park (https://www.baseball-reference.com/boxes/SFN/SFN201606120.shtml)
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'NA_l'] <- 'AT&TPark_l'
batted_ball_processed$venue_batter.stand[batted_ball_processed$venue_batter.stand == 'NA_r'] <- 'AT&TPark_r'
There are 16 unique batted balls events in this dataset. However, for the purposes of modeling, I’m going to condense that down into five categories: single, double, triple, home run, and out. This outcome variable is going to be my response variable. My model is going to try and predict the outcome of each batted ball and assign it to one of these five classes.
batted_ball_processed %<>%
mutate(outcome = case_when(
events == "single" ~ "single",
events == "double" ~ "double",
events == "triple" ~ "triple",
events == "home_run" ~ "home_run",
TRUE ~ "out"
)) %>%
dplyr::select(-c(events))
XGBoost is my model of choice and it only handles numeric features. For that reason, I’m encoding my outcome variable as a number 0-4 representing the five original outcomes. (For some reason, I believe, XGBoost requires the response variable to be zero-indexed, so one of the classes has to be 0.)
batted_ball_processed %<>%
mutate(outcome_ec = case_when(
outcome == "out" ~ 0,
outcome == "single" ~ 1,
outcome == "double" ~ 2,
outcome == "triple" ~ 3,
outcome == "home_run" ~ 4
)) %>%
dplyr::select(-c(outcome))
The last step is changing some of my variables into ‘dummy variables.’ Like I did above to batter and pitcher handedness, I’m going to create a new column for each non-numeric variable. Take the ‘count’ variable, for example. Because XGBoost only works with numeric variables, I can’t input a column with characters ‘0-0’ or ‘3-2’, so now there will be one column that signifies if the count is ‘0-0’ and another column for ‘0-1’, and so on for all 12 count states. I’ll be following that same procedure for: pitch type, zone, batter pitcher matchup, batter infield shift, batter outfield shift, and venue batter handedness.
batted_ball_processed %<>%
mutate(pitch_type = as.factor(pitch_type),
zone = as.factor(zone),
game_year = as.factor(game_year),
count = as.factor(count),
batter_pitcher_matchup = as.factor(batter_pitcher_matchup),
batter_if_shift = as.factor(batter_if_shift),
batter_of_shift = as.factor(batter_of_shift),
venue_batter.stand = as.factor(venue_batter.stand))
batted_ball_processed <- fastDummies::dummy_cols(batted_ball_processed, select_columns = c("pitch_type", "zone", "count",
"batter_pitcher_matchup", "batter_if_shift",
"batter_of_shift", "venue_batter.stand"))
head(batted_ball_processed)
write.csv(batted_ball_processed, "batted_ball_processed.csv")
LS0tCnRpdGxlOiAiQ3JlYXRpbmcgUUMrIFBhcnQgMiAtIEZlYXR1cmUgRW5naW5lZXJpbmcgYW5kIFNlbGVjdGlvbiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSW4gdGhpcyBub3RlYm9vaywgSSB3aWxsIGZvY3VzIG9uIGZlYXR1cmUgZW5naW5lZXJpbmcgYW5kIGRhdGEtcHJlcHJvY2Vzc2luZy4gRmVhdHVyZSBlbmdpbmVlcmluZyBpcyBkZWZpbmVkIGFzICJ0aGUgcHJvY2VzcyBvZiB1c2luZyBkb21haW4ga25vd2xlZGdlIHRvIGV4dHJhY3QgZmVhdHVyZXMgZnJvbSByYXcgZGF0YSB2aWEgZGF0YSBtaW5pbmcgdGVjaG5pcXVlcy4iIEkgd2lsbCBiZSBhZGRpbmcgc29tZSBuZXcgZmVhdHVyZXMgKHNwcmF5IGFuZ2xlLCBiYXR0ZXIgcGl0Y2hlciBtYXRjaHVwLCBhbmQgc2hpZnRpbmcgaW5mb3JtYXRpb24pLCBidXQgYSBtYWpvcml0eSBvZiBteSBmb2N1cyB3aWxsIGJlIHRvIGdldCB0aGUgZGF0YSByZWFkeSBmb3IgbW9kZWxpbmcuIE1vZGVsaW5nIHdpbGwgdGFrZSBwbGFjZSBpbiB0aGUgbmV4dCBub3RlYm9vay4gQWRkaXRpb25hbGx5LCB0aGFua3MgdG8gdGhlIGJhc2ViYWxsciBwYWNrYWdlIEkgd2lsbCBiZSBhZGRpbmcgdmVudWUgYW5kIHRlbXBlcmF0dXJlIHZhcmlhYmxlcy4KCk9uY2UgZmluYWxpemVkLCBJIHdpbGwgYmUgdXNpbmcgdGhlIGZvbGxvd2luZyBmZWF0dXJlcyBhcyBpbnB1dHMgaW50byBteSBtb2RlbDogbGF1bmNoIGFuZ2xlLCBleGl0IHZlbG9jaXR5LCBzcHJheV9hbmdsZSwgYmF0dGVyIGhhbmRlZG5lc3MsIHBpdGNoZXIgaGFuZGVkbmVzcywgdGhlIGJhdHRlciBwaXRjaGVyIG1hdGNodXAgYnkgaGFuZGVkbmVzcywgdGVtcGVyYXR1cmUsIHdoZXRoZXIgdGhlIGdhbWUgd2FzIHBsYXllZCBpbnNpZGUgb3Igbm90LCBwaXRjaCB0eXBlLCB0aGUgY291bnQsIHdoZXJlIHRoZSBwaXRjaCB3YXMgdGhyb3duICh1c2luZyBCYXNlYmFsbCBTYXZhbnQncyB6b25lIGZlYXR1cmUpLCB3aGF0IHR5cGUgb2Ygc2hpZnQgdGhlIGluZmllbGQgYW5kIG91dGZpZWxkIGlzIGluLCBhbmQgYSBjb21iaW5hdGlvbiBvZiB0aGUgc3RhZGl1bSBhbmQgdGhlIGhhbmRlZGVzcyBvZiB0aGUgYmF0dGVyLiAgCgpJIHRoaW5rIGFsbCAxNCBvZiB0aGVzZSBmZWF0dXJlcyBjb250cmlidXRlcyB0byBib3RoOiBhY2N1cmF0ZWx5IGNsYXNzaWZ5aW5nIHRoZSBvdXRjb21lIG9mIGEgYmF0dGVkIGFuZCBnaXZlcyB0aGUgYmF0dGVyL3BpdGNoZXIgY3JlZGl0IGZvciB0aGUgYmF0dGVkIGJhbGwgb3V0Y29tZS4gRm9yIGV4YW1wbGUsIGEgMy0wIGZvdXItc2VhbSBmYXN0YmFsbCBtaWRkbGUtbWlkZGxlIGlzIGEgbG90IGVhc2llciB0byBoaXQgZm9yIGEgaG9tZSBydW4gdGhhbiBhIDAtMiBzbGlkZXIgdGhyb3duIGRvd24gYW5kIGF3YXkuIEluY2x1ZGluZyB0aG9zZSBmZWF0dXJlcyB3aWxsIGFsbG93IHRoZSBtb2RlbCB0byBnaXZlIHRoZSBwcm9wZXIgY3JlZGl0IGZvciBlYWNoIGJhdHRlZCBiYWxsLiAKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShiYXNlYmFsbHIpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGFybSkKbGlicmFyeShibG1lKQpsaWJyYXJ5KGxtZTQpCmxpYnJhcnkoeGdib29zdCkKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KGZhc3REdW1taWVzKQpgYGAKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmJhdHRlZF9iYWxsX2NsZWFuZWQgPC0gcmVhZF9jc3YoImJhdHRlZF9iYWxsX2NsZWFuZWQuY3N2IikKYGBgCgoKIyBGZWF0dXJlIEVuZ2luZWVyaW5nCgpBZGRpbmcgdGhlIHBpdGNoZXIncyBuYW1lLiAKYGBge3J9CiMgcGxheWVyIGxvb2sgdXAgYnkgbWxiYW0ga2V5CmNoYWR3aWNrX3JlZHVjZWQgPC0gY2hhZHdpY2tfcGxheWVyX2x1X3RhYmxlICU+JSBkcGx5cjo6c2VsZWN0KGtleV9tbGJhbSwgbmFtZV9sYXN0LCBuYW1lX2ZpcnN0KQojIHJlbW92ZSBOQSB0byBtYWtlIGRmIHNtYWxsZXIKY2hhZHdpY2tfcmVkdWNlZCA8LSBjaGFkd2lja19yZWR1Y2VkWyFpcy5uYShjaGFkd2lja19yZWR1Y2VkJGtleV9tbGJhbSksXQojIGdldHRpbmcgZWFjaCBwaXRjaGVyJ3MgbmFtZQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgPC0gbWVyZ2UoeCA9IGJhdHRlZF9iYWxsX3Byb2Nlc3NlZCwgeSA9IGNoYWR3aWNrX3JlZHVjZWQsIGJ5LnggPSAicGl0Y2hlciIsIGJ5LnkgPSAia2V5X21sYmFtIikKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkICU8PiUKICBtdXRhdGUoYmF0dGVkX2JhbGxfcHJvY2Vzc2VkLCBwaXRjaGVyX25hbWUgPSBwYXN0ZShuYW1lX2ZpcnN0LCBuYW1lX2xhc3QsIHNlcCA9ICIgIikpICU+JQogIGRwbHlyOjpzZWxlY3QoLWMobmFtZV9sYXN0LCBuYW1lX2ZpcnN0LCBwaXRjaGVyKSkKYGBgCgoKCgpBZGRpbmcgd2hhdCB0ZWFtIHRoZSBiYXR0ZXIgaXMuIFdpbGwgYmUgdXNlZCBmb3IgbWV0cmljIHZhbGlkYXRpb24gbGF0ZXIuCmBgYHtyfQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgJTw+JQogIG11dGF0ZShiYXR0ZXJfdGVhbSA9IGlmZWxzZShpbm5pbmdfdG9wYm90ID09ICJUb3AiLCBhd2F5X3RlYW0sIGhvbWVfdGVhbSkpCmBgYAoKCk5leHQsIEknbSBnb2luZyB0byBjaGFuZ2UgdGhlIHBpdGNoIHR5cGUgZ3JvdXBpbmdzLiBJJ20gY29uc2lkZXJpbmcgYSBrbnVja2xlIGN1cnZlIHRvIGJlIGEgcmVndWxhciBjdXJ2ZWJhbGwsIGEgc3BsaXR0ZXIgdG8gYmUgYSBjaGFuZ2UtdXAsIGFuZCBhIHNpbmtlciB0byBiZSBhIHR3by1zZWFtIGZhc3RiYWxsLiBMYXN0bHksIEknbSBncm91cGluZyB0b2dldGhlciBrbnVja2xlIGJhbGxzLCBzY3Jld2JhbGxzLCBmb3JrYmFsbHMsIGVlcGh1cywgZXRjLiBpbnRvIG9uZSBvdmVyLWFyY2hpbmcgJ290aGVyJyBjYXRlZ29yeS4gCmBgYHtyfQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkcGl0Y2hfdHlwZVtiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkcGl0Y2hfdHlwZSA9PSAnS0MnXSA8LSAnQ1UnCmJhdHRlZF9iYWxsX3Byb2Nlc3NlZCRwaXRjaF90eXBlW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCRwaXRjaF90eXBlID09ICdGUyddIDwtICdDSCcKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHBpdGNoX3R5cGVbYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHBpdGNoX3R5cGUgPT0gJ1NJJ10gPC0gJ0ZUJwoKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkICU8PiUKICBtdXRhdGUocGl0Y2hfdHlwZSA9IGNhc2Vfd2hlbigKICAgIHBpdGNoX3R5cGUgJWluJSBjKCJLTiIsICJFUCIsICJGTyIsIE5BLCAiU0MiLCAiVU4iKSB+ICJPdGhlciIsCiAgICBwaXRjaF90eXBlID09ICJGRiIgfiAiRkYiLAogICAgcGl0Y2hfdHlwZSA9PSAiU0wiIH4gIlNMIiwgCiAgICBwaXRjaF90eXBlID09ICJDSCIgfiAiQ0giLCAKICAgIHBpdGNoX3R5cGUgPT0gIkZUIiB+ICJGVCIsCiAgICBwaXRjaF90eXBlID09ICJDVSIgfiAiQ1UiLCAKICAgIHBpdGNoX3R5cGUgPT0gIkZDIiB+ICJGQyIKICApKQpgYGAKCkhlcmUsIEknbSBjb21iaW5pbmcgc3RyaWtlcyBhbmQgYmFsbHMgaW50byBvbmUgY291bnQgdmFyaWFibGUuIApgYGB7cn0KYmF0dGVkX2JhbGxfcHJvY2Vzc2VkICU8PiUKICBmaWx0ZXIoYmFsbHMgIT0gNCkgJT4lICMgZm9yIHNvbWUgcmVhc29uIHRoZXJlIGFyZSB0d28gaW5zdGFuY2VzIG9mIGEgY291bnQgd2l0aCA0IGJhbGxzCiAgbXV0YXRlKGNvdW50ID0gcGFzdGUwKGJhbGxzLCAiLSIsIHN0cmlrZXMpKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1jKGJhbGxzLCBzdHJpa2VzKSkKYGBgCgpNb2RlbHMgdGVuZCB0byBwcmVmZXIgbnVtZXJpYyBjYXRlZ29yaWVzIHdoZW4gcG9zc2libGUsIHNvIEknbSBvbmUtaG90IGVuY29kaW5nIHBpdGNoZXIgaGFuZGVkbmVzcyBhbmQgYmF0dGVyIHN0YW5jZS4gVGhlcmUgd2lsbCBzdGlsbCBiZSBhIGNvbHVtbiBmb3IgZWFjaCB2YXJpYWJsZSwgYnV0IG5vdyBhICIxIiB3aWxsIGNvcnJlc3BvbmQgdG8gYSBSSFAgYW5kIGEgIjAiIHRvIGEgTEhQLiBTYW1lIHRoaW5nIGZvciBiYXR0ZXIgc3RhbmNlLgpgYGB7cn0KYmF0dGVkX2JhbGxfcHJvY2Vzc2VkICU8PiUKICBtdXRhdGUoc3RhbmRfciA9IGlmZWxzZShzdGFuZCA9PSAiUiIsIDEsIDApKSAlPiUKICBtdXRhdGUocF90aHJvd3NfciA9IGlmZWxzZShwX3Rocm93cyA9PSAiUiIsIDEsIDApKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1jKHN0YW5kLCBwX3Rocm93cykpCmBgYAoKTm93LCBJJ20gbW92aW5nIG9uIHRvIG1vcmUgJ2ZlYXR1cmUgZW5naW5lZXJpbmcuJyBJJ20gYWRkaW5nIHZhcmlhYmxlcyBmb3I6IHRoZSBzcHJheSBhbmdsZSwgdGhlIGJhdHRlciBwaXRjaGVyIG1hdGNodXAgKEx2TCwgUnZSLCBMdlIsIGFuZCBSdkwpLCBhbmQgc2hpZnRpbmcgZGF0YSBjb21iaW5lZCB3aXRoIGJhdHRlciBzdGFuY2UuIE91dHNpZGUgb2Ygd2hlcmUgdGhlIGJhbGwgaXMgaGl0LCBpdCByZWFzb25zIHRoYXQgdGhlIG1vc3QgaW1wb3J0YW50IGZhY3RvciBvZiB3aGV0aGVyIGEgYmFsbCB3aWxsIGJlIGEgaGl0IG9yIG5vdCAoYW5kIHdoYXQgdHlwZSBvZiBoaXQpIGlzIHdoZXJlIHRoZSBmaWVsZGVycyBhcmUgcG9zaXRpb25lZC4gVW5mb3J0dW5hdGVseSwgdGhlcmUgaXMgbm8gcHVibGljbHkgYXZhaWxhYmxlIGRhdGEgb24gdGhlIHBpdGNoLWJ5LXBpdGNoIGxldmVsIG9mIHdoZXJlIHRoZSBmaWVsZGVycyBhcmUgZXhhY3RseSBwb3NpdGlvbmVkLiBMdWNraWx5LCBCYXNlYmFsbCBTYXZhbnQgcHJvdmlkZXMgc29tZSBnZW5lcmFsIGluZm9ybWF0aW9uIG9uIGEgcGl0Y2gtYnktcGl0Y2ggYmFzaXMuIFRoYXQgaW5mb3JtYXRpb24gY29tZSdzIGZyb20gdGhlIGlmX2ZpZWxkaW5nX2FsaWdubWVudCBhbmQgb2ZfZmllbGRpbmdfYWxpZ25tZW50IHZhcmlhYmxlcy4gRm9yIHRoZSBpbmZpZWxkZXJzLCB0aGVyZSBhcmUgdGhyZWUgZGlmZmVyZW50IHR5cGVzIG9mIHNoaWZ0czogc3RhbmRhcmQsIHN0cmF0ZWdpYywgYW5kIGluZmllbGQgc2hpZnQuIEluZmllbGQgc2hpZnQgaXMgdGhlIHNoaWZ0IHRoYXQgZmlyc3QgY29tZXMgdG8gbWluZDogdGhyZWUgaW5maWVsZGVycyBhcmUgcG9zaXRpb25lZCBvbiB0aGUgc2FtZSBzaWRlIG9mIHNlY29uZCBiYXNlLiBTdHJhdGVnaWMgc2hpZnQgaXMgZGVmaW5lZCBhcyBhICJjYXRjaC1jYWxsIGZvciBwb3NpdGlvbmluZyB0aGF0IGlzIG5laXRoZXIgJ3N0YW5kYXJkLCcgbm9yICd0aHJlZSBpbmZpZWxkZXJzIHRvIG9uZSBzaWRlIG9mIHNlY29uZCBiYXNlLiIgRm9yIHRoZSBvdXRmaWVsZGVycywgdGhlcmUgYXJlIGFyZSBhbHNvIHRocmVlIHR5cGVzIG9mIHNoaWZ0czogc3RhbmRhcmQsIHN0cmF0ZWdpYywgYW5kIDR0aCBvdXRmaWVsZGVyLiBMaWtlIGluZmllbGRpbmcgc2hpZnRpbmcsIHN0cmF0ZWdpYyBzaGlmdGluZyBmb3Igb3V0ZmllbGRlcnMgaXMgd2hlbiB0aGVyZSBhcmUgbm90IGZvdXIgb3V0ZmllbGRlcnMgYW5kIHRoZSBhbGlnbm1lbnQgaXMgbm90IHN0YW5kYXJkLiBJJ20gY29tYmluZGluZyB0aGUgc2hpZnRpbmcgaW5mb3JtYXRpb24gd2l0aCB0aGUgYmF0dGVyIGhhbmRlZG5lc3MuIE9idmlvdXNseSwgYW4gaW5maWVsZCBzaGlmdCBmb3IgYSBsZWZ0IGhhbmRlZCBiYXR0ZXIgaGFzIHRoZSBpbmZpZWxkZXJzIGluIGEgd2F5IGRpZmZlcmVudCBwb3NpdGlvbiB0aGFuIGlmIHRoZSBiYXR0ZXIgd2FzIHJpZ2h0IGhhbmRlZC4KCmBgYHtyfQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgJTw+JQogIG11dGF0ZShzcHJheV9hbmdsZSA9IHJvdW5kKChhdGFuKChoY194LTEyNS40MikvKDE5OC4yNy1oY195KSkqMTgwL3BpKi43NSksMSkpCgpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgJTw+JQogIG11dGF0ZShiYXR0ZXJfcGl0Y2hlcl9tYXRjaHVwID0gaWZlbHNlKHBfdGhyb3dzX3IgPT0gMSAmIHN0YW5kX3IgPT0gMSwgInIudnMuciIsICNwaXRoY2VyLmhhbmRfYmF0dGVyLmhhbmQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UocF90aHJvd3NfciA9PSAxICYgc3RhbmRfciA9PSAwLCAici52cy5sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHBfdGhyb3dzX3IgPT0gMCAmIHN0YW5kX3IgPT0gMSwgImwudnMuciIsICJsLnZzLmwiKSkpKQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgJTw+JQogIG11dGF0ZShiYXR0ZXJfaWZfc2hpZnQgPSBjYXNlX3doZW4oCiAgICBzdGFuZF9yID09IDEgJiBpZl9maWVsZGluZ19hbGlnbm1lbnQgPT0gIlN0YW5kYXJkIiB+ICJyX2JhdHRlci5ub19zaGlmdCIsCiAgICBzdGFuZF9yID09IDEgJiBpZl9maWVsZGluZ19hbGlnbm1lbnQgPT0gIlN0cmF0ZWdpYyIgfiAicl9iYXR0ZXIuc3RyYXRlZ2ljX3NoaWZ0IiwKICAgIHN0YW5kX3IgPT0gMSAmIGlmX2ZpZWxkaW5nX2FsaWdubWVudCA9PSAiSW5maWVsZCBzaGlmdCIgfiAicl9iYXR0ZXIuc2hpZnQiLAogICAgc3RhbmRfciA9PSAwICYgaWZfZmllbGRpbmdfYWxpZ25tZW50ID09ICJTdGFuZGFyZCIgfiAibF9iYXR0ZXIubm9fc2hpZnQiLAogICAgc3RhbmRfciA9PSAwICYgaWZfZmllbGRpbmdfYWxpZ25tZW50ID09ICJTdHJhdGVnaWMiIH4gImxfYmF0dGVyLnN0cmF0ZWdpY19zaGlmdCIsCiAgICBzdGFuZF9yID09IDAgJiBpZl9maWVsZGluZ19hbGlnbm1lbnQgPT0gIkluZmllbGQgc2hpZnQiIH4gImxfYmF0dGVyLnNoaWZ0IiwKICApKSAlPiUKICBtdXRhdGUoYmF0dGVyX29mX3NoaWZ0ID0gY2FzZV93aGVuKAogICAgc3RhbmRfciA9PSAxICYgb2ZfZmllbGRpbmdfYWxpZ25tZW50ID09ICJTdGFuZGFyZCIgfiAicl9iYXR0ZXIubm9fc2hpZnQiLAogICAgc3RhbmRfciA9PSAxICYgb2ZfZmllbGRpbmdfYWxpZ25tZW50ID09ICJTdHJhdGVnaWMiIH4gInJfYmF0dGVyLnN0cmF0ZWdpY19zaGlmdCIsCiAgICBzdGFuZF9yID09IDEgJiBvZl9maWVsZGluZ19hbGlnbm1lbnQgPT0gIjR0aCBvdXRmaWVsZGVyIiB+ICJyX2JhdHRlci40dGhfb3V0ZmllbGRlciIsCiAgICBzdGFuZF9yID09IDAgJiBvZl9maWVsZGluZ19hbGlnbm1lbnQgPT0gIlN0YW5kYXJkIiB+ICJsX2JhdHRlci5ub19zaGlmdCIsCiAgICBzdGFuZF9yID09IDAgJiBvZl9maWVsZGluZ19hbGlnbm1lbnQgPT0gIlN0cmF0ZWdpYyIgfiAibF9iYXR0ZXIuc3RyYXRlZ2ljX3NoaWZ0IiwKICAgIHN0YW5kX3IgPT0gMCAmIG9mX2ZpZWxkaW5nX2FsaWdubWVudCA9PSAiNHRoIG91dGZpZWxkZXIiIH4gImxfYmF0dGVyLjR0aF9vdXRmaWVsZGVyIiwKICApKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1jKGlmX2ZpZWxkaW5nX2FsaWdubWVudCwgb2ZfZmllbGRpbmdfYWxpZ25tZW50KSkKYGBgCgpMYXN0bHksIEknbSBhZGRpbmcgdGhyZWUgbmV3IHZhcmlhYmxlcy4gVGhlIGZpcnN0IGlzIHRlbXBlcmF0dXJlLiBCYXNlYmFsbCBwcm9zcGVjdHVzIGhhcyBzaG93biBob3cgaW5mbHVlbnRpYWwgdGVtcGVyYXR1cmUgaXMgb24gdGhlIG91dGNvbWUgb2YgYSBiYXR0ZWQgYmFsbC4gVGhlIGJlc3Qgd2F5IEkndmUgYmVlbiBhYmxlIHRvIHRoaW5rIGFib3V0IHRoZSBhZmZlY3Qgb2YgdGhlIHRlbXBlcmF0dXJlIGlzIGNvbXBhcmluZyBXcmlnbGV5IGZpZWxkIGluIEFwcmlsIHdpdGggdGhlIHdpbmQgYmxvd2luZyBpbiB2cyBpbiBBdWd1c3Qgd2l0aCB0aGUgd2luZCBibG93aW5nIG91dC4gSW4gQXVndXN0LCBpdCBmZWVscyBsaWtlIGV2ZXJ5IGJhbGwgaGl0IGluIHRoZSBhaXIgaXMgZ29pbmcgdG8gbGVhdmUgdGhlIHlhcmQsIHdoaWxlIGluIEFwcmlsIGl0J3MgYSBzdXJwcmlzZSBpZiBhbnkgYmFsbCBkb2VzLiBUaGUgdGVtcGVyYXR1cmUgdmFyaWFibGUgaXMgb25seSB0aGUgdGVtcGVyYXR1cmUgYXQgdGhlIHN0YXJ0IG9mIHRoZSBnYW1lLCBzbyBpdCdzIG5vdCBwZXJmZWN0LCBidXQgaXQgZG9lcyBhIHByZXR0eSBnb29kIGpvYi4gU2ltaWxhcmlseSB0byB0ZW1wZXJhdHVyZSwgSSdtIGFkZGluZyBhIHZhcmlhYmxlIHRoYXQgcmVmbGVjdHMgd2hldGhlciB0aGUgZ2FtZSBpcyBpbnNpZGUgYSBkb29tIG9yIG5vdC4gV2hpbGUgdGhlcmUgYXJlbid0IG1hbnkgaW5kb29yIGdhbWVzLCBJIHRoaW5rIHRoaXMgbWF5IGJlIGEgZ29vZCBwcm94eSBmb3IgY29udHJvbGxpbmcgb3RoZXIgd2VhdGhlciB2YXJpYWJsZXMsIGxpa2Ugd2luZC4gVGhlIGxhc3QgdmFyaWFibGUgaXMgdGhlIHZlbnVlIG9mIHRoZSBnYW1lIGNvbWJpbmVkIHdpdGggYmF0dGVyIGhhbmRlZG5lc3MuIEkgb3JpZ2luYWxseSBzYXcgdGhpcyBpbiBCYXNlYmFsbCBQcm9zcGVjdHVzJyBEUkMrIGJyZWFrZG93biBhbmQgdGhvdWdodCBpZiBpdCB3YXMgZ29vZCBlbm91Z2ggZm9yIHRoZW0sIGl0J3MgZ29vZCBlbm91Z2ggZm9yIG1lLiBBcyBKb25hdGhhbiBKdWRnZSBzYXlzICJbYW4gaW5kaWNhdG9yIGZvciBzdGFkaXVtIGFuZCBiYXR0ZXIgaGFuZGVkbmVzc10gYWNjb3VudHMgZm9yIHRoZSByZWFsaXR5IHRoYXQgc3RhZGl1bXMgdGVuZCB0byBwbGF5IGRpZmZlcmVudGx5IGZvciBlYWNoIGJhdHRlciBzaWRlLCBhbmQgYWxsb3dzIHVzIHRvIHBhcmstYWRqdXN0IGJ5IGhhbmRlZG5lc3MgZm9yIGVhY2ggZXZlbnQuIgpgYGB7cn0KZ2FtZV9pbmZvIDwtIGJhc2ViYWxscjo6Z2V0X2dhbWVfaW5mb19zdXBfcGV0dGkoKQoKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkICU8PiUKICBsZWZ0X2pvaW4oZ2FtZV9pbmZvICU+JSBkcGx5cjo6c2VsZWN0KGMoImdhbWVfcGsiLCAidmVudWVfbmFtZSIsICJ0ZW1wZXJhdHVyZSIsICJvdGhlcl93ZWF0aGVyIikpLAogICAgICAgICAgICBieSA9IGMoImdhbWVfcGsiKSkgJT4lCiAgbXV0YXRlKGlzX2RvbWUgPSBjYXNlX3doZW4ob3RoZXJfd2VhdGhlciA9PSAiUm9vZiBDbG9zZWQiIHwgb3RoZXJfd2VhdGhlciA9PSAiRG9tZSIgfiAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAwKSkgJT4lCiAgbXV0YXRlKHRyaW1fdmVudWUgPSBnc3ViKCJcXHMrIiwgIiIsIHZlbnVlX25hbWUpLAogICAgICAgICB2ZW51ZV9iYXR0ZXIuc3RhbmQgPSBpZmVsc2Uoc3RhbmRfciA9PSAxLCBwYXN0ZTAodHJpbV92ZW51ZSwgIl8iLCAiciIpLCBwYXN0ZTAodHJpbV92ZW51ZSwgIl8iLCAibCIpKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtYyhvdGhlcl93ZWF0aGVyLCB0cmltX3ZlbnVlLCB2ZW51ZV9uYW1lKSkgJT4lCiAgZmlsdGVyKHZlbnVlX2JhdHRlci5zdGFuZCAhPSAiQkImVEJhbGxwYXJrX3IiLCB2ZW51ZV9iYXR0ZXIuc3RhbmQgIT0gIkhpcmFtQml0aG9yblN0YWRpdW1fbCIsICMgbm8gc3RhdGNhc3QgaW5mbyBmcm9tIHRoZXNlIHBhcmtzCiAgICAgICAgIHZlbnVlX2JhdHRlci5zdGFuZCAhPSAiRXN0YWRpb2RlQmVpc2JvbE1vbnRlcnJleV9yIiwgdmVudWVfYmF0dGVyLnN0YW5kICE9ICJFc3RhZGlvZGVCZWlzYm9sTW9udGVycmV5X2wiKQoKIyBjbGVhbmluZyB1cCBzdGFkaXVtIHJlLW5hbWluZwpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmQgPT0gJ1UuUy5DZWxsdWxhckZpZWxkX2wnXSA8LSAnR3VhcmFudGVlZFJhdGVGaWVsZF9sJwpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmQgPT0gJ1UuUy5DZWxsdWxhckZpZWxkX3InXSA8LSAnR3VhcmFudGVlZFJhdGVGaWVsZF9yJwoKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHZlbnVlX2JhdHRlci5zdGFuZFtiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kID09ICdPLmNvQ29saXNldW1fbCddIDwtICdPYWtsYW5kQ29saXNldW1fbCcKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHZlbnVlX2JhdHRlci5zdGFuZFtiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kID09ICdPLmNvQ29saXNldW1fciddIDwtICdPYWtsYW5kQ29saXNldW1fcicKCmJhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmRbYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHZlbnVlX2JhdHRlci5zdGFuZCA9PSAnQW5nZWxTdGFkaXVtb2ZBbmFoZWltX2wnXSA8LSAnQW5nZWxTdGFkaXVtX2wnCmJhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmRbYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHZlbnVlX2JhdHRlci5zdGFuZCA9PSAnQW5nZWxTdGFkaXVtb2ZBbmFoZWltX3InXSA8LSAnQW5nZWxTdGFkaXVtX3InCgpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmQgPT0gJ09yYWNsZVBhcmtfbCddIDwtICdBVCZUUGFya19sJwpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmQgPT0gJ09yYWNsZVBhcmtfciddIDwtICdBVCZUUGFya19yJwoKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHZlbnVlX2JhdHRlci5zdGFuZFtiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kID09ICdULU1vYmlsZVBhcmtfbCddIDwtICdTYWZlY29GaWVsZF9sJwpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmQgPT0gJ1QtTW9iaWxlUGFya19yJ10gPC0gJ1NhZmVjb0ZpZWxkX3InCgojIGZvciBzb21lIHJlYXNvbiAyMDE2LTA2LTEyIEdpYW50cyB2cyBEb2RnZXJzIGhhZCBhIG51bGwgc3RhZGl1bSwgYnV0IGl0IGhhcHBlbmVkIGF0IEFUJlQgUGFyayAoaHR0cHM6Ly93d3cuYmFzZWJhbGwtcmVmZXJlbmNlLmNvbS9ib3hlcy9TRk4vU0ZOMjAxNjA2MTIwLnNodG1sKQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kW2JhdHRlZF9iYWxsX3Byb2Nlc3NlZCR2ZW51ZV9iYXR0ZXIuc3RhbmQgPT0gJ05BX2wnXSA8LSAnQVQmVFBhcmtfbCcKYmF0dGVkX2JhbGxfcHJvY2Vzc2VkJHZlbnVlX2JhdHRlci5zdGFuZFtiYXR0ZWRfYmFsbF9wcm9jZXNzZWQkdmVudWVfYmF0dGVyLnN0YW5kID09ICdOQV9yJ10gPC0gJ0FUJlRQYXJrX3InCmBgYAoKClRoZXJlIGFyZSAxNiB1bmlxdWUgYmF0dGVkIGJhbGxzIGV2ZW50cyBpbiB0aGlzIGRhdGFzZXQuIEhvd2V2ZXIsIGZvciB0aGUgcHVycG9zZXMgb2YgbW9kZWxpbmcsIEknbSBnb2luZyB0byBjb25kZW5zZSB0aGF0IGRvd24gaW50byBmaXZlIGNhdGVnb3JpZXM6IHNpbmdsZSwgZG91YmxlLCB0cmlwbGUsIGhvbWUgcnVuLCBhbmQgb3V0LiBUaGlzIG91dGNvbWUgdmFyaWFibGUgaXMgZ29pbmcgdG8gYmUgbXkgcmVzcG9uc2UgdmFyaWFibGUuIE15IG1vZGVsIGlzIGdvaW5nIHRvIHRyeSBhbmQgcHJlZGljdCB0aGUgb3V0Y29tZSBvZiBlYWNoIGJhdHRlZCBiYWxsIGFuZCBhc3NpZ24gaXQgdG8gb25lIG9mIHRoZXNlIGZpdmUgY2xhc3Nlcy4gCmBgYHtyfQpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgJTw+JQogIG11dGF0ZShvdXRjb21lID0gY2FzZV93aGVuKAogICAgZXZlbnRzID09ICJzaW5nbGUiIH4gInNpbmdsZSIsCiAgICBldmVudHMgPT0gImRvdWJsZSIgfiAiZG91YmxlIiwgCiAgICBldmVudHMgPT0gInRyaXBsZSIgfiAidHJpcGxlIiwgCiAgICBldmVudHMgPT0gImhvbWVfcnVuIiB+ICJob21lX3J1biIsIAogICAgVFJVRSB+ICJvdXQiCiAgKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtYyhldmVudHMpKQpgYGAKClhHQm9vc3QgaXMgbXkgbW9kZWwgb2YgY2hvaWNlIGFuZCBpdCBvbmx5IGhhbmRsZXMgbnVtZXJpYyBmZWF0dXJlcy4gRm9yIHRoYXQgcmVhc29uLCBJJ20gZW5jb2RpbmcgbXkgb3V0Y29tZSB2YXJpYWJsZSBhcyBhIG51bWJlciAwLTQgcmVwcmVzZW50aW5nIHRoZSBmaXZlIG9yaWdpbmFsIG91dGNvbWVzLiAoRm9yIHNvbWUgcmVhc29uLCBJIGJlbGlldmUsIFhHQm9vc3QgcmVxdWlyZXMgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIHRvIGJlIHplcm8taW5kZXhlZCwgc28gb25lIG9mIHRoZSBjbGFzc2VzIGhhcyB0byBiZSAwLikKYGBge3J9CmJhdHRlZF9iYWxsX3Byb2Nlc3NlZCAlPD4lCiAgbXV0YXRlKG91dGNvbWVfZWMgPSBjYXNlX3doZW4oCiAgICBvdXRjb21lID09ICJvdXQiIH4gMCwKICAgIG91dGNvbWUgPT0gInNpbmdsZSIgfiAxLAogICAgb3V0Y29tZSA9PSAiZG91YmxlIiB+IDIsCiAgICBvdXRjb21lID09ICJ0cmlwbGUiIH4gMywKICAgIG91dGNvbWUgPT0gImhvbWVfcnVuIiB+IDQKICApKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1jKG91dGNvbWUpKQpgYGAKClRoZSBsYXN0IHN0ZXAgaXMgY2hhbmdpbmcgc29tZSBvZiBteSB2YXJpYWJsZXMgaW50byAnZHVtbXkgdmFyaWFibGVzLicgTGlrZSBJIGRpZCBhYm92ZSB0byBiYXR0ZXIgYW5kIHBpdGNoZXIgaGFuZGVkbmVzcywgSSdtIGdvaW5nIHRvIGNyZWF0ZSBhIG5ldyBjb2x1bW4gZm9yIGVhY2ggbm9uLW51bWVyaWMgdmFyaWFibGUuIFRha2UgdGhlICdjb3VudCcgdmFyaWFibGUsIGZvciBleGFtcGxlLiBCZWNhdXNlIFhHQm9vc3Qgb25seSB3b3JrcyB3aXRoIG51bWVyaWMgdmFyaWFibGVzLCBJIGNhbid0IGlucHV0IGEgY29sdW1uIHdpdGggY2hhcmFjdGVycyAnMC0wJyBvciAnMy0yJywgc28gbm93IHRoZXJlIHdpbGwgYmUgb25lIGNvbHVtbiB0aGF0IHNpZ25pZmllcyBpZiB0aGUgY291bnQgaXMgJzAtMCcgYW5kIGFub3RoZXIgY29sdW1uIGZvciAnMC0xJywgYW5kIHNvIG9uIGZvciBhbGwgMTIgY291bnQgc3RhdGVzLiBJJ2xsIGJlIGZvbGxvd2luZyB0aGF0IHNhbWUgcHJvY2VkdXJlIGZvcjogcGl0Y2ggdHlwZSwgem9uZSwgYmF0dGVyIHBpdGNoZXIgbWF0Y2h1cCwgYmF0dGVyIGluZmllbGQgc2hpZnQsIGJhdHRlciBvdXRmaWVsZCBzaGlmdCwgYW5kIHZlbnVlIGJhdHRlciBoYW5kZWRuZXNzLiAKYGBge3J9CmJhdHRlZF9iYWxsX3Byb2Nlc3NlZCAlPD4lCiAgbXV0YXRlKHBpdGNoX3R5cGUgPSBhcy5mYWN0b3IocGl0Y2hfdHlwZSksIAogICAgICAgICB6b25lID0gYXMuZmFjdG9yKHpvbmUpLCAKICAgICAgICAgZ2FtZV95ZWFyID0gYXMuZmFjdG9yKGdhbWVfeWVhciksCiAgICAgICAgIGNvdW50ID0gYXMuZmFjdG9yKGNvdW50KSwKICAgICAgICAgYmF0dGVyX3BpdGNoZXJfbWF0Y2h1cCA9IGFzLmZhY3RvcihiYXR0ZXJfcGl0Y2hlcl9tYXRjaHVwKSwKICAgICAgICAgYmF0dGVyX2lmX3NoaWZ0ID0gYXMuZmFjdG9yKGJhdHRlcl9pZl9zaGlmdCksCiAgICAgICAgIGJhdHRlcl9vZl9zaGlmdCA9IGFzLmZhY3RvcihiYXR0ZXJfb2Zfc2hpZnQpLAogICAgICAgICB2ZW51ZV9iYXR0ZXIuc3RhbmQgPSBhcy5mYWN0b3IodmVudWVfYmF0dGVyLnN0YW5kKSkgCgpiYXR0ZWRfYmFsbF9wcm9jZXNzZWQgPC0gZmFzdER1bW1pZXM6OmR1bW15X2NvbHMoYmF0dGVkX2JhbGxfcHJvY2Vzc2VkLCBzZWxlY3RfY29sdW1ucyA9IGMoInBpdGNoX3R5cGUiLCAiem9uZSIsICJjb3VudCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJiYXR0ZXJfcGl0Y2hlcl9tYXRjaHVwIiwgImJhdHRlcl9pZl9zaGlmdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJiYXR0ZXJfb2Zfc2hpZnQiLCAidmVudWVfYmF0dGVyLnN0YW5kIikpCmhlYWQoYmF0dGVkX2JhbGxfcHJvY2Vzc2VkKQpgYGAKCmBgYHtyfQp3cml0ZS5jc3YoYmF0dGVkX2JhbGxfcHJvY2Vzc2VkLCAiYmF0dGVkX2JhbGxfcHJvY2Vzc2VkLmNzdiIpCmBgYA==