drawing

Seasons 1950-2017





Setup Stage


Loading necessary packages.

library(dplyr)
library(tidyr)
library(ggplot2)
library(RColorBrewer)
library(plotly)
library(kableExtra)


Create additional functions

# Mode average
getmode <- function(v) {
   uniqv <- unique(v)
   uniqv[which.max(tabulate(match(v, uniqv)))]
}


Load the dataset


Dataset taken from: https://www.kaggle.com/drgilermo/nba-players-stats/version/2

If you need to see the glossary and the data tidying process, please visit the first part here.

Loading datasets…

NBA <- read.csv("NBA_TidySet.csv")[,-c(1)]
NBA_Scaled <- read.csv("NBA_Scaled_TidySet.csv")
NBA$Pos <- factor(NBA$Pos, levels = c("C", "PF", "SF", "SG", "PG"))
NBA_Scaled$Pos <- factor(NBA_Scaled$Pos, levels = c("C", "PF", "SF", "SG", "PG"))
PosColorCode <- c("C"="#FF0000", "PF"="#FFA500", "SF"="#DDDD00" ,"SG"="#0000FF", "PG"="#32CD32")


Displaying raw tidy data table

NBA


  • Number of rows: 22345
  • Number of columns: 55
  • Number of players: 3889
  • Number of teams: 68
  • File Size: 7188.1 Kb




Players

In this session, we start by exploring the players. The variables that will be enticing to explore include their positions, height, weight, born and age.


NBA Teams growth


We start by exploring how NBA teams and the number of players has grown over the years. The table will show the detailed numbers.

Team_Player <- NBA %>%
  group_by(Year) %>%
  summarise(nPlayers = n_distinct(Player),
            nTeams = n_distinct(Tm),
            nGames = max(G),
            Players_per_Team = round(nPlayers/nTeams, 2)) 
Team_Player %>%
    kable(escape = FALSE, align='c', caption = "Players, Teams and Games") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(1, bold = T) %>%
    scroll_box(width = "100%", height = "300px")
Players, Teams and Games
Year nPlayers nTeams nGames Players_per_Team
1950 219 17 68 12.88
1951 126 11 69 11.45
1952 112 10 66 11.20
1953 123 10 72 12.30
1954 108 9 72 12.00
1955 96 9 72 10.67
1956 91 8 72 11.38
1957 96 8 72 12.00
1958 98 8 72 12.25
1959 91 8 72 11.38
1960 95 8 75 11.88
1961 92 8 79 11.50
1962 106 9 80 11.78
1963 116 9 80 12.89
1964 111 9 80 12.33
1965 114 9 80 12.67
1966 109 9 80 12.11
1967 121 10 81 12.10
1968 149 12 82 12.42
1969 166 14 82 11.86
1970 167 14 82 11.93
1971 213 17 82 12.53
1972 212 17 82 12.47
1973 210 17 82 12.35
1974 217 17 82 12.76
1975 231 18 82 12.83
1976 233 18 82 12.94
1977 289 22 82 13.14
1978 281 22 82 12.77
1979 274 22 82 12.45
1980 281 22 82 12.77
1981 297 23 82 12.91
1982 309 23 82 13.43
1983 309 23 82 13.43
1984 306 23 82 13.30
1985 314 23 82 13.65
1986 319 23 82 13.87
1987 330 23 82 14.35
1988 328 23 82 14.26
1989 348 25 82 13.92
1990 376 27 82 13.93
1991 383 27 82 14.19
1992 385 27 82 14.26
1993 386 27 82 14.30
1994 399 27 82 14.78
1995 402 27 82 14.89
1996 424 29 82 14.62
1997 437 29 82 15.07
1998 434 29 82 14.97
1999 436 29 50 15.03
2000 435 29 82 15.00
2001 437 29 82 15.07
2002 436 29 82 15.03
2003 425 29 82 14.66
2004 439 29 82 15.14
2005 460 30 82 15.33
2006 454 30 82 15.13
2007 456 30 82 15.20
2008 448 30 82 14.93
2009 442 30 82 14.73
2010 440 30 82 14.67
2011 449 30 82 14.97
2012 475 30 66 15.83
2013 464 30 82 15.47
2014 477 30 82 15.90
2015 489 30 82 16.30
2016 472 30 82 15.73
2017 481 30 82 16.03


Now we see how NBA teams have grown, with this simple plot.

Team_Player %>%
    ggplot() +
    geom_line(aes(Year, nTeams, linetype = "Trend line")) +
    ggtitle("Number of NBA Teams by Year") +
    geom_hline(aes(yintercept = mean(Team_Player$nTeams), linetype = "Average line"),
               col = "red",
               alpha = 0.5) +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    scale_linetype_manual(name = "", values = c(2, 1), guide = guide_legend(reverse = TRUE)) +
    ylab("Number of Teams") +
    theme(legend.position="bottom")


  • The number of NBA teams has grown from 17 in 1950 to 30 in 2017.
  • Lowest number of teams occur during 1956-1961 seasons, with only 8 teams competes.
  • The highest number of teams occur from 2005 until present with 30 teams.




Number of NBA Players


We can plot number of NBA players year by year based on the same table.

Team_Player %>%
    ggplot(aes(Year, nPlayers, fill=nPlayers)) +
    geom_bar(stat = "identity") +
    ggtitle("Number of NBA Players by Year") +
    geom_hline(aes(yintercept = mean(Team_Player$nPlayers), linetype = "Average line"),
               col = "red",
               alpha = 0.5) +
    scale_fill_gradient(low = "green", high = "red") +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    scale_linetype_manual(name = "", values = 2) +
    ylab("Number of Players") +
    theme(legend.position="bottom")


  • The number of NBA players has grown more than doubled, from 219 in 1950 to 481 in 2017.
  • The average number of players compete in NBA regular season is 294.82
  • The least number of players competes in a season is 91 players during 1955-1956 & 1958-1959 season.
  • The most number of players competes in a season is 489 players in 2014-2015 season.




Number of games for each team in a season


Almost all NBA fans know that there are 82 games to play in a season. However, I’d like to see the past history, I wonder if it’s has grown over time, too.

Team_Player %>% 
    ggplot(aes(Year, nGames, fill=nGames)) +
    ggtitle("Number of Games in a Season") +
    geom_bar(stat = "identity") +
    scale_fill_gradient(low = "green", high = "red") +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    ylab("Number of Games") +
    theme(legend.position="bottom")


Since 1967–68 season, NBA expands its regular season to 82 games per team, where it still stands to this date. Except for these notable occurrences:

  • 1998–99 NBA season: number of games is 50, due to a lockout
  • 2011-12 NBA season: number of games is 66, due to another lockout




Position Ratio in the NBA


My next question, is the position ratio always evenly distributed?

NBA %>%
    ggplot(aes(Year, group=Pos, color = Pos, fill = Pos)) +
    geom_density(alpha = 0.5, position = "fill") +
    ggtitle("Position Ratio by Year") +
    scale_color_manual("Pos", values = PosColorCode) +
    scale_fill_manual("Pos", values = PosColorCode) +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    theme(legend.position="bottom")




Height Distribution


We all know basketball players are tall, naturally, I want to know how tall most of them are, and how small we are (average height person) compared to their standard.

HeightMean <- mean(NBA$Height)
HeightSD <- sd(NBA$Height)
NBA %>% ggplot(aes(Height, fill=TRUE)) +
    geom_density() +
    scale_x_continuous(breaks = seq(160, 240, 10)) +
    geom_vline(aes(xintercept = HeightMean, linetype = "Average height of NBA players"),
               col = "red",
               alpha = 0.8) +
    geom_vline(aes(xintercept = 177, linetype = "Average height of American male"),
               col = "blue",
               alpha = 0.8) +
    geom_vline(xintercept = c(seq(HeightMean, 240, HeightSD), seq(HeightMean, 160, -HeightSD)),
               col = "blue",
               alpha = 0.3,
               linetype = 5) +
    scale_linetype_manual(name = "", values = c(1, 1)) +
    guides(fill=FALSE) +
    theme(legend.position="bottom")


  • Average height of NBA players is: 199.6 cm, with standard deviation: 9.3.
  • Average height of American men is 177 cm (source) is shorter by more than two standard deviation away from average NBA players.


Now let’s see the groundcrawlers and the skyscrapers in the NBA.


NBA %>%
    group_by(Height, Player) %>%
    summarise(Pos = getmode(Position),
              YearActive = paste(mean(YearStart), "-", mean(YearEnd)),
              Team = getmode(Tm),
              Games = sum(G),
              PPG = round(sum(PTS)/sum(G), 2)) %>%
    arrange(Height) %>%
    head() %>%
    kable(escape = FALSE, align='c', caption = "Shortest Players") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "white", background = "#777777")
Shortest Players
Height Player Pos YearActive Team Games PPG
160 Muggsy Bogues PG 1988 - 2001 CHH 889 7.71
165 Earl Boykins PG 1999 - 2012 DEN 652 8.88
168 Spud Webb PG 1986 - 1998 ATL 814 9.92
170 Greg Grant PG 1990 - 1996 PHI 274 2.80
170 Keith Jennings PG 1993 - 1995 GSW 164 6.65
170 Monte Towe PG 1976 - 1977 DEN 51 2.55


NBA %>%
    group_by(Height, Player) %>%
    summarise(Pos = getmode(Position),
              YearActive = paste(mean(YearStart), "-", mean(YearEnd)),
              Team = getmode(Tm),
              Games = sum(G),
              PPG = round(sum(PTS)/sum(G), 2)) %>%
    arrange(desc(Height)) %>%
    head(n=8) %>%
    kable(escape = FALSE, align='c', caption = "Tallest Players") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "white", background = "#777777")
Tallest Players
Height Player Pos YearActive Team Games PPG
231 Gheorghe Muresan C 1994 - 2000 WSB 307 9.84
231 Manute Bol C 1986 - 1995 WSB 624 2.56
229 Shawn Bradley C 1994 - 2005 DAL 832 8.12
229 Yao Ming C 2003 - 2011 HOU 486 19.03
226 Chuck Nevitt C 1983 - 1994 HOU 155 1.62
226 Pavel Podkolzin C 2005 - 2006 DAL 6 0.67
226 Sim Bhullar C 2015 - 2015 SAC 3 0.67
226 Slavko Vranes C 2004 - 2004 POR 1 0.00



Height Comparison


Next, I’d like to compare them side-by-side, from Center to Point Guard, and find their averages and ranges.

NBA %>%
    group_by(Pos) %>%
    summarise(MinHeight = min(Height),
              MaxHeight = max(Height),
              MedianHeight = median(Height),
              ModeHeight = getmode(Height),
              MeanHeight = round(mean(`Height`), 2)) %>%
    mutate(Pos = cell_spec(Pos,
                            color = "white",
                            align = "c",
                            background = factor(Pos, c("C", "PF", "SF", "SG", "PG"),
                                                PosColorCode))) %>%
    kable(escape = FALSE, align='c', caption = "Height: Averages and Range by Position") %>%
    kable_styling("striped", full_width = T)
Height: Averages and Range by Position
Pos MinHeight MaxHeight MedianHeight ModeHeight MeanHeight
C 196 231 211 211 210.61
PF 185 224 206 206 204.91
SF 178 213 201 201 200.48
SG 165 211 196 196 194.03
PG 160 211 188 190 187.14


Violin plot not only gives us the averages and ranges, but it also gives us the distribution.

NBA %>%
  ggplot(aes(Pos, Height, color=Pos)) +
  geom_violin() +
  ggtitle("Height distribution by position") +
  stat_summary(fun.y=mean, geom="point", shape=8, size=6) +
  geom_point() +
  geom_hline(aes(yintercept = mean(NBA$Height, na.rm=T), linetype = "Average NBA players"),
             col = "red",
             alpha = 0.5) +
  geom_hline(aes(yintercept = 177, linetype = "Average American male"),
             col = "blue",
             alpha = 0.5) +
  scale_color_manual("Pos", values = PosColorCode) +
  scale_linetype_manual(name = "", values = c(1, 1)) +
  theme(legend.position="bottom")




Height by Years


Exploring the height of NBA players would not feel complete without taking a look at it from the chronological perspective. This might not produce significant insight, but I just can’t resist seeing the plot.

HeightYear <- NBA %>%
    group_by(Year) %>%
    summarise(meanHeight = round(mean(Height, na.rm = T), 1))
NBA %>%
    group_by(Year, Pos) %>%
    summarise(meanHeight = round(mean(Height, na.rm = T), 1)) %>%
    ggplot() +
    geom_line(aes(Year, meanHeight, group=Pos, color=Pos), size = 1.2, alpha = 1) +
    geom_line(aes(Year, meanHeight, linetype = "Average line"),
              data = HeightYear, color = "black", size = 0.8, alpha = 0.5) +
    ggtitle("Height by position by Year") +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    scale_color_manual("Pos", values = PosColorCode) +
    scale_linetype_manual(name = "", values = c(3)) +
    ylab("Height") +
    guides(group=FALSE) +
    theme(legend.position="bottom")




BMI Distribution


Next, let’s explore the BMI (Body Mass Index) of the players.

BMI <- NBA %>%
    group_by(Player) %>%
    filter(!is.na(BMI)) %>%
    mutate(BMIGroup = ifelse(BMI < 18.5, "Underweight",
                               ifelse(BMI >= 18.5 & BMI < 25, "Healthy weight",
                                      ifelse(BMI >= 25 & BMI < 30, "Overweight",
                                             "Obese Class I")))) %>%
    summarise(Pos = getmode(Position),
              Height = getmode(Height),
              Weight = getmode(Weight),
              BMI = getmode(BMI),
              BMIGroup = getmode(BMIGroup),
              Games = sum(G),
              PPG = round(sum(PTS)/Games, 1))
shade <- data.frame(xstart = c(15, 25, 30), xend = c(18.5, 30, 33), col = c("#F00", "#0F0", "#00F"))
BMIclass <- data.frame(X = c(15.7, 20, 26.5, 31), Y = 0.27, label = c("Underweight", "Healthy weight", "Overweight", "Obese"))
BMIMean <- mean(BMI$BMI)
BMISD <- sd(BMI$BMI)
BMI %>% ggplot() +
    geom_rect(data = shade,
              aes(xmin = xstart, xmax = xend, ymin = 0, ymax = Inf, fill = col),
              alpha = 0.3) +
    geom_density(aes(BMI, fill=TRUE)) +
    scale_x_continuous(breaks = seq(15, 33, 1)) +
    geom_vline(aes(xintercept = BMIMean, linetype = "Average line"),
               col = "black",
               alpha = 0.8) +
    geom_vline(xintercept = c(seq(BMIMean, 33, BMISD), seq(BMIMean, 15, -BMISD)),
               col = "blue",
               alpha = 0.3,
               linetype = 5) +
    geom_text(data = BMIclass,
              mapping = aes(x = X, y = Y, label = label),
              size = 3,
              vjust = 0,
              hjust = 0,
              color = "forestgreen") +
    scale_linetype_manual(name = "", values = c(1, 1, 1, 1)) +
    guides(fill=FALSE) +
    theme(legend.position="bottom")


    BMI category based on WHO standard BMI classification.

  • Average BMI: 24.1, with standard deviation: 1.7
  • As expected, most players, 73.3% are in the “Healthy Weight” category.
  • 26.2% players are in “Overweight” category, which is still common in athletes.
  • 15 players(0.4%) are in “Obese Class I” category, Shaquille O’Neal is one of them.
  • Only 1 player falls under the “Underweight” category. As one of the tallest player in the NBA (231 cm), he also the skinniest player in the league history with BMI only 17.1, his name is Manute Bol


Now I’m interested to see the real BIG guys in the NBA.


BMI %>%
    arrange(desc(BMI)) %>%
    filter(BMIGroup == "Obese Class I") %>%
    select(BMI, everything()) %>%
    kable(escape = FALSE, align='c', caption = "Highest BMI (All in Obese Class I group)") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "white", background = "#777777") %>%
    scroll_box(width = "100%", height = "300px")
Highest BMI (All in Obese Class I group)
BMI Player Pos Height Weight BMIGroup Games PPG
31.91 Sim Bhullar C 226 163 Obese Class I 3 0.7
31.56 Thomas Hamilton C 218 150 Obese Class I 33 3.2
31.51 Shaquille O'Neal C 216 147 Obese Class I 1207 23.7
31.45 Dexter Pittman C 211 140 Obese Class I 50 2.3
31.30 Robert Traylor C 203 129 Obese Class I 438 4.8
31.22 Nikola Pekovic C 211 139 Obese Class I 271 12.6
31.11 Jahidi White C 206 132 Obese Class I 334 5.9
31.00 Garret Siler C 211 138 Obese Class I 21 2.1
30.87 Glen Davis PF 206 131 Obese Class I 514 8.0
30.40 Kevin Seraphin C 206 129 Obese Class I 423 5.9
30.33 Elton Brand PF 203 125 Obese Class I 1058 15.9
30.33 Mike Sweetney C 203 125 Obese Class I 233 6.5
30.28 Al Jefferson C 208 131 Obese Class I 879 16.0
30.20 DeJuan Blair PF 201 122 Obese Class I 424 6.8
30.09 Garth Joseph C 218 143 Obese Class I 4 0.5



Chronological Size Growth


Inspired by Hans Rosling’s animated bubble chart from Gapminder, I challenged myself to see if I can make one myself with this dataset. Weight in the x-axis, height in the y-axis, size for BMI, and color represent their position in the field. Don’t forget to click “Play” to see them grows year-by-year.players are in “Overweight” category, which is still common in athletes.

pBubble <- NBA %>%
    filter(!is.na(BMI), !is.na(Weight), !is.na(Height)) %>%
    plot_ly(x = ~Weight,
            y = ~Height,
            size = ~BMI,
            color = ~Pos,
            colors = PosColorCode,
            frame = ~Year,
            text = ~Player, 
            hoverinfo = "text",
            type = 'scatter',
            mode = 'markers') %>% 
    animation_opts(1000, easing = "elastic",
                   redraw = FALSE) %>% 
    animation_button(x = 1,
                     xanchor = "right",
                     y = 0,
                     yanchor = "bottom") %>%
    animation_slider(currentvalue = list(prefix = "Year: ", font = list(color="red")))
htmlwidgets::saveWidget(as.widget(pBubble), "pBubble.html")

Note: If this animated plot (which is the most awesome plot of all), does not appear in your browser, that means I still have technical difficulty with attaching the plot with the ‘iframe’ method. Will come back to this later when the solution emerge



Ability by Position Comparison


Since we now know that height is strongly correlated with their position in the field, naturally I’m interested to see how their abilities differ based on their roles.

To compare multiple variables with different scales, I use normalized (scaled) data I created in part 1: Data Preparation Stage of this series. This makes “0” in the scale is the average of each variable, anything below “0” tells us that the group performance in that particular category is below average, and vice versa.

RadarHeight <- NBA_Scaled %>%
    group_by(Pos) %>%
    summarise(Shoot2P = mean(X2P., na.rm=T),
              Shoot3P = mean(X3P., na.rm=T),
              ShootFT = mean(FT., na.rm=T),
              OffensiveRB = mean(ORB, na.rm=T),
              Assist = mean(AST, na.rm=T),
              DefensiveRB = mean(DRB, na.rm=T),
              Steal = mean(STL, na.rm=T),
              Block = mean(BLK, na.rm=T)) %>%
    select(-Pos)
pRadar <- plot_ly(type = 'scatterpolar',
        fill = 'toself',
        mode = 'lines') %>%
    add_trace(r = as.numeric(as.vector(RadarHeight[1,])),
              theta = as.character(as.vector(colnames(RadarHeight))),
              name = 'C',
              fillcolor = "#FF0000",
              opacity = 0.5) %>%
    add_trace(r = as.numeric(as.vector(RadarHeight[2,])),
              theta = as.character(as.vector(colnames(RadarHeight))),
              name = 'PF',
              fillcolor = "#FFA500",
              opacity = 0.5) %>%
    add_trace(r = as.numeric(as.vector(RadarHeight[3,])),
              theta = as.character(as.vector(colnames(RadarHeight))),
              name = 'SF',
              fillcolor = "#DDDD00",
              opacity = 0.5) %>%
    add_trace(r = as.numeric(as.vector(RadarHeight[4,])),
              theta = as.character(as.vector(colnames(RadarHeight))),
              name = 'SG',
              fillcolor = "#0000FF",
              opacity = 0.5) %>%
    add_trace(r = as.numeric(as.vector(RadarHeight[5,])),
              theta = as.character(as.vector(colnames(RadarHeight))),
              name = 'PG',
              fillcolor = "#32CD32",
              opacity = 0.5) %>%
    layout(polar = list(
        radialaxis = list(
        visible = T,
        range = c(-1, 1))))
RadarHeight

click for interactive plotly graph


How to interpret the plot:

Click on the ‘position legend’ in the upper right of the graph to select/unselect each position, double-click to isolate the trace. You can compare them in any way as you desire.

So, as we can see, the graph does not just confirm the well-known fact that the tall guys (Centers) are the best shot-blockers and the short guys (Point Guards) are the best at passing the ball, we can also measure how far better they perform these task.




Annual Age Range


Same like with height, it would be compelling to see the age range in the NBA year-by-year.

AgeNBA <- NBA %>%
    group_by(Year) %>%
    summarise(Average = round(mean(Age, na.rm = T), 2),
              Max = max(Age, na.rm = T),
              Min = min(Age, na.rm = T))
AgeNBA %>% kable(align = "c", caption = "Age: Average and Range by Year") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(1, bold = T) %>%
    scroll_box(width = "100%", height = "300px")
Age: Average and Range by Year
Year Average Max Min
1950 26.06 36 20
1951 26.26 33 22
1952 26.07 34 21
1953 25.99 35 21
1954 25.81 38 19
1955 26.10 35 21
1956 25.83 34 20
1957 25.87 35 21
1958 26.21 35 22
1959 25.96 33 21
1960 26.06 34 21
1961 26.11 34 22
1962 25.59 34 22
1963 25.83 34 21
1964 26.02 35 21
1965 25.76 33 21
1966 26.19 34 21
1967 25.89 35 21
1968 25.96 36 21
1969 26.08 36 22
1970 26.45 41 21
1971 26.05 37 21
1972 26.17 38 21
1973 26.23 39 21
1974 26.31 37 21
1975 26.35 37 19
1976 26.27 35 18
1977 25.87 36 19
1978 25.88 37 20
1979 25.81 35 21
1980 26.16 36 19
1981 25.87 35 20
1982 26.01 36 20
1983 26.12 37 21
1984 26.12 38 21
1985 26.22 37 21
1986 26.43 38 20
1987 26.36 39 20
1988 26.55 40 21
1989 26.70 41 21
1990 26.72 39 20
1991 26.79 37 21
1992 26.78 38 21
1993 27.00 39 20
1994 27.05 40 20
1995 27.34 41 20
1996 27.39 42 19
1997 27.51 43 18
1998 27.55 40 18
1999 27.51 40 18
2000 27.66 39 19
2001 27.77 39 19
2002 27.22 39 19
2003 27.12 40 19
2004 27.13 41 18
2005 27.05 42 18
2006 26.55 39 18
2007 26.48 44 19
2008 26.84 41 19
2009 26.63 42 19
2010 26.69 39 19
2011 26.71 38 19
2012 26.62 39 19
2013 26.55 40 19
2014 26.44 39 19
2015 26.52 38 19
2016 26.71 39 19
2017 26.38 40 19


And here’s the plot…

AgeNBA %>% ggplot(aes(Year)) +
    geom_line(aes(y = Max, linetype = "Oldest"), color = "red", alpha = 0.5) +
    geom_line(aes(y = Average, linetype = "Average"), color = "black") +
    geom_line(aes(y = Min, linetype = "Youngest"), color = "blue", alpha = 0.5) +
    ggtitle("Age Range by Year") +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    scale_linetype_manual(name = "", values = c(1, 1, 1)) +
    guides(group=FALSE) +
    theme(legend.position="bottom")



Player Distribution by Generation


NBA has been around since 1950, this suggests that many generations have passed since the first NBA season. Would it be nice to see which generation dominated the league each year?

NBA %>%
    group_by(Year, Player) %>%
    filter(!is.na(Born)) %>%
    mutate(Generation = ifelse(Born <= 1921, "The Depression Era",
                          ifelse(Born %in% 1922:1927, "World War II",
                            ifelse(Born %in% 1928:1945, "Post-War Cohort",
                              ifelse(Born %in% 1946:1954, "Baby Boomers",
                                ifelse(Born %in% 1955:1965, "Generation Jones",
                                  ifelse(Born %in% 1966:1976, "Generation X",
                                    ifelse(Born %in% 1977:1995, "Millennials",
                                      "Generation Z")))))))) %>%
    summarise(Generation = getmode(Generation)) %>%
    arrange(Year, Player) %>%
    mutate(Generation = factor(Generation, levels = c("The Depression Era",
                                                     "World War II",
                                                     "Post-War Cohort",
                                                     "Baby Boomers",
                                                     "Generation Jones",
                                                     "Generation X",
                                                     "Millennials",
                                                     "Generation Z"))) %>%
    ggplot(aes(Year, y=..count.., colour=Generation, fill=Generation)) +
    geom_density(alpha=0.55) +
    ggtitle("Generation Count by Years") +
    ylab("Count") +
    scale_x_continuous(breaks = seq(1950, 2017, 10)) +
    theme(legend.position='bottom')

Generation classification based on WJS marketing research


And here’s the oldest generation of the NBA…

Earliest born:

NBA %>%
    group_by(Player, Born) %>%
    summarise(Pos = getmode(Position),
              ActiveYears = paste(getmode(YearStart), "-", getmode(YearEnd)),
              NBASeasons = n_distinct(Year),
              Games = sum(G),
              StartingAge = min(Age),
              FinalAge = max(Age),
              PPG = round(sum(PTS)/sum(G), 1)) %>%
    arrange(Born) %>%
    select(Born, everything()) %>%
    head(9) %>%
    kable(escape = F, align = "c", caption = "Oldest NBA Players") %>%
    column_spec(1, bold = T, color = "white", background = "#777777") %>%
    column_spec(2, bold = T) %>%
    kable_styling("striped", full_width = T)
Oldest NBA Players
Born Player Pos ActiveYears NBASeasons Games StartingAge FinalAge PPG
1914 Charley Shipp SF 1950 - 1950 1 23 36 36 4.7
1915 Chick Reiser SF 1948 - 1950 1 67 35 35 9.0
1916 Mike Novak C 1949 - 1954 2 65 34 38 1.5
1917 Dick Schulz SF 1947 - 1950 1 50 33 33 4.2
1918 Al Cervi PG 1950 - 1953 4 202 32 35 7.9
1918 Bob Carpenter PF 1950 - 1951 2 122 32 33 7.7
1918 Buddy Jeannette SG 1948 - 1950 1 37 32 32 5.2
1918 Ed Sadowski C 1947 - 1950 1 69 32 32 12.6
1918 Gene Englund PF 1950 - 1950 1 46 32 32 7.8


Notice that in the ActiveYears column some players started their career before 1950 and the number of seasons in the NBASeasons column summed up after 1949. This because the NBA formally formed in 1949 by the merger of two rival organizations, the National Basketball League (founded 1937) and the Basketball Association of America (founded 1946). You can dig more into NBA history here.



Career Length in the NBA


My next question is how long do NBA careers last?

This can be answered by summing up the number of seasons each player participated in.

SeasonNBA <- NBA %>%
    group_by(Player, Born) %>%
    summarise(NBASeasons = n_distinct(Year))
SeasonNBA %>%
    ggplot(aes(NBASeasons)) +
    geom_bar(aes(fill = ..count..)) +
    ggtitle("Player Distribution by Number of Seasons") +
    geom_vline(aes(xintercept = mean(NBASeasons, na.rm = T), linetype = "Average line"),
               col = "black",
               alpha = 0.5) +
    scale_fill_gradient(low = "green", high = "red") +
    scale_linetype_manual(name = "", values = 2) +
    xlab("Number of NBA Seasons") +
    ylab("Count") +
    theme(legend.position="bottom")


  • 1068 players, or more than a quarter (27.2%) of all players in the NBA history didn’t survive after their first season.
  • Average career length in the NBA is 5.1 years.
  • Less than half of all NBA players (41.7%) participate in at 5 seasons or more.
  • And only 1 out of 5 players (19.9%), can survive for 10 seasons or more.


Now let’s find out who has been in the NBA the longest…


NBA %>%
    group_by(Player, Born) %>%
    summarise(Pos = getmode(Position),
              Team = getmode(Tm),
              ActiveYears = paste(min(Year), "-", max(Year)),
              RookieAge = min(Age),
              RetirementAge = max(Age),
              Seasons = n_distinct(Year),
              RpG = round(sum(TRB)/sum(G), 1),
              PpG = round(sum(PTS)/sum(G), 1)) %>%
    arrange(desc(Seasons), ActiveYears) %>%
    select(Seasons, everything(), -Born) %>%
    head(17) %>%
    kable(escape = F, align = "c", caption = "Longest Career in the NBA") %>%
    column_spec(1, bold = T, color = "white", background = "#777777") %>%
    column_spec(2, bold = T) %>%
    kable_styling("striped", full_width = T)
Longest Career in the NBA
Seasons Player Pos Team ActiveYears RookieAge RetirementAge RpG PpG
21 Robert Parish C BOS 1977 - 1997 23 43 9.1 14.5
21 Kevin Willis PF ATL 1985 - 2007 22 44 8.4 12.1
21 Kevin Garnett PF MIN 1996 - 2016 19 39 10.0 17.8
20 Kareem Abdul-Jabbar C LAL 1970 - 1989 22 41 11.2 24.6
20 Kobe Bryant SG LAL 1997 - 2016 18 37 5.2 25.0
19 Moses Malone C HOU 1977 - 1995 21 39 12.2 20.6
19 James Edwards C PHO 1978 - 1996 22 40 5.1 12.7
19 John Stockton PG UTA 1985 - 2003 22 40 2.7 13.1
19 Charles Oakley PF NYK 1986 - 2004 22 40 9.5 9.7
19 Karl Malone PF UTA 1986 - 2004 22 40 10.1 25.0
19 Shaquille O'Neal C LAL 1993 - 2011 20 38 10.9 23.7
19 Jason Kidd PG DAL 1995 - 2013 21 39 6.3 12.6
19 Juwan Howard PF WAS 1995 - 2013 21 39 6.1 13.4
19 Tim Duncan C SAS 1998 - 2016 21 39 10.8 19.0
19 Dirk Nowitzki PF DAL 1999 - 2017 20 38 7.8 21.7
19 Paul Pierce SF BOS 1999 - 2017 21 39 5.6 19.7
19 Vince Carter SG TOR 1999 - 2017 22 40 4.6 18.2

The table above listed the players who have a career in the NBA for 19 years or more.

    Few things I noticed in this group are:

  • Most of them are well-known great players, 94.1 % of them have double digits overall points per game, which only about 1 in 5 NBA players can accomplish this feat.
  • What interesting is the Centers are Power Forwards (translated: tall guys) dominated the group with 35.3 % each, which makes up 70.6 % in total.



The Rookies vs the Retirees


Now we take a closer look at the age when they started and finished their career in the NBA.

RRAge <- NBA %>%
    group_by(Player) %>%
    filter(!is.na(Age)) %>%
    summarise(RookieAge = min(Age),
              RetirementAge = max(Age)) %>%
    gather(Parameter, Value, RookieAge:RetirementAge)
RRAge %>%
    ggplot(aes(x=as.factor(Value),fill=Parameter)) + 
    geom_bar(data=filter(RRAge, Parameter == "RetirementAge")) + 
    geom_bar(data=filter(RRAge, Parameter == "RookieAge"), aes(y = ..count.. * (-1))) +
    ggtitle("Rookie Age vs, Retirement Age in the NBA") +
    xlab("Age") +
    ylab("Count") +
    scale_y_continuous(breaks=seq(-1500,1500,500),labels=abs(seq(-1500,1500,500))) +
    scale_fill_brewer(palette = "Set1") + 
    coord_flip() +
    theme(legend.position="bottom")

RRAge <- RRAge %>%
    spread(Parameter, Value)


  • Most of the players (68.4%) start their career in NBA between age 22-24.
  • 2.7% of NBA players starting their career before their 20 years old birthday.
  • Only 50 players (1.3%) starting their career in their 30’s.


NBA %>%
    filter(YearStart >= 1950) %>%
    group_by(Player) %>%
    summarise(RookieAge = min(Age),
              Pos = getmode(Position),
              Team = getmode(Tm),
              RetirementAge = max(Age),
              NBAYears = paste(min(Year), "-", max(Year)),
              NBASeasons = n_distinct(Year),
              Games = sum(G),
              PpG = round(sum(PTS)/Games,2)) %>%
    arrange(desc(RookieAge)) %>%
    select(RookieAge, everything()) %>%
    head(10) %>%
    kable(escape = F, align = "c", caption = "Oldest NBA Rookies") %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "white", background = "#777777") %>%
    kable_styling("striped", full_width = T)
Oldest NBA Rookies
RookieAge Player Pos Team RetirementAge NBAYears NBASeasons Games PpG
36 Charley Shipp SF WAT 36 1950 - 1950 1 23 4.65
35 Pablo Prigioni PG NYK 38 2013 - 2016 4 270 3.50
33 Steve Jones SG POR 33 1976 - 1976 1 64 6.47
32 Al Cervi PG SYR 35 1950 - 1953 4 202 7.88
32 Bob Carpenter PF FTW 33 1950 - 1951 2 122 7.68
32 Byron Beck PF DEN 32 1977 - 1977 1 53 4.72
32 Gene Englund PF BOS 32 1950 - 1950 1 46 7.83
32 Louie Dampier PG SAS 34 1977 - 1979 3 232 6.69
32 Marcelo Huertas PG LAL 33 2016 - 2017 2 76 3.95
32 Mel Daniels C NYN 32 1977 - 1977 1 11 3.55

Bear in mind that these are rookies as in the NBA. Some of them might already have years of experience in another professional basketball league.


NBA %>%
    select(Age, Player, Position, Year, Tm, G, GS, PTS) %>%
    mutate(PPG = round(PTS/G, 2)) %>%
    arrange(desc(Age)) %>%
    head(n=10) %>%
    kable(escape = F, align = 'c', caption = "Oldest NBA Players") %>%
    kable_styling("striped", full_width = T) %>%
    column_spec(2, bold = T) %>%
    column_spec(1, bold = T, color = "white", background = "#777777")
Oldest NBA Players
Age Player Position Year Tm G GS PTS PPG
44 Kevin Willis PF 2007 DAL 5 0 12 2.40
43 Robert Parish C 1997 CHI 43 3 161 3.74
42 Robert Parish C 1996 CHH 74 34 290 3.92
42 Kevin Willis C 2005 ATL 29 5 87 3.00
42 Dikembe Mutombo C 2009 HOU 9 2 16 1.78
41 Bob Cousy PG 1970 CIN 7 NA 5 0.71
41 Kareem Abdul-Jabbar C 1989 LAL 74 74 748 10.11
41 Robert Parish C 1995 CHH 81 4 389 4.80
41 Kevin Willis C 2004 SAS 48 0 164 3.42
41 Dikembe Mutombo C 2008 HOU 39 25 118 3.03



Age vs Chance and Ability


Lastly, I’d like to know how age correlates with their chances and ability in the field.

AgeCor <- NBA_Scaled %>%
    group_by(Age) %>%
    summarise(Games = mean(G, na.rm = T),
              GameStarted = mean(GS, na.rm = T),
              MinutesPlayed = mean(MP, na.rm = T),
              Shooting = mean(TS., na.rm = T),
              ShootAttemps = mean(FGA, na.rm = T),
              Rebound = mean(RpG, na.rm = T),
              Assist = mean(ApG, na.rm = T),
              Steal = mean(SpG, na.rm = T),
              Block = mean(BpG, na.rm = T),
              Turnover = mean(TpG, na.rm = T),
              Points = mean(PpG, na.rm = T)) %>%
    gather(variable, value, -Age) %>%
    filter(!is.na(Age)) %>%
    mutate(variable = factor(variable, levels = c("Games", "GameStarted", "MinutesPlayed", "Shooting", "ShootAttemps", "Rebound", "Assist", "Steal", "Block", "Turnover", "Points")))
AgeCor %>%
    ggplot(aes(Age, variable, fill=value)) +
    geom_tile(color = "grey50") +
    scale_x_continuous(expand = c(0, 0)) +
    scale_fill_gradientn(colors = brewer.pal(9, "Reds")) +
    theme(panel.grid = element_blank()) +
    scale_y_discrete(limits = rev(levels(AgeCor$variable))) +
    ggtitle("Age, Chance and Ability") +
    ylab("Parameter") +
    theme(legend.position="bottom")


It turns out that their peak around 25-30 years old, with some exception in shooting (TS%) and blocks. That is because, as we have seen in the previous table, only a handful of players still active after their 40 and 90% of them are post players who are shot-blockers with a good FG%.




End of Session


LS0tDQp0aXRsZTogJ1Zpc3VhbGl6aW5nIE5CQSBTZWFzb25zIFBhcnQgMTogUGxheWVycycNCmF1dGhvcjogIk51bm5vIE51Z3JvaG8iDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBzdHlsZS5jc3MNCiAgICB0aGVtZTogcGFwZXINCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjc3M6IHN0eWxlLmNzcw0KICAgIHRoZW1lOiBwYXBlcg0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KLS0tDQoNCjxici8+DQo8YnIvPg0KDQo8ZGl2IGNsYXNzPSJoZWFkZXIiPg0KDQo8Y2VudGVyPjxpbWcgc3JjPSJpbWFnZXMvTkJBTG9nb1RyYW5zcC5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjIwMHB4IiBoZWlndGg9IjEwMHB4Ii8+DQoNCioqU2Vhc29ucyAxOTUwLTIwMTcqKg0KDQo8ZGl2IGNsYXNzPSJkcm9wZG93biI+DQoNCjxidXR0b24gY2xhc3M9ImRyb3BidG4iPlBhcnQgMTogUGxheWVyczwvYnV0dG9uPg0KDQo8ZGl2IGNsYXNzPSJkcm9wZG93bi1jb250ZW50Ij4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnMwIj5JbnRyb2R1Y3Rpb24gJiBQcmVwYXJhdGlvbjwvYT4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnMyIj5QYXJ0IDI6IFBvaW50czwvYT4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnMzIj5QYXJ0IDM6IFNob290aW5ncyAoRkcgYW5kIEZUKTwvYT4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnM0Ij5QYXJ0IDQ6IFNob290aW5ncyAoMy1Qb2ludHMgYW5kIE1peGVkKTwvYT4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnM1Ij5QYXJ0IDU6IFNraWxsczwvYT4NCg0KPGEgaHJlZj0iaHR0cHM6Ly9ycHVicy5jb20vbmluamF6emxlL05CQVNlYXNvbnM2Ij5QYXJ0IDY6IFJvbGVzPC9hPg0KDQo8L2Rpdj4NCg0KPC9kaXY+DQoNCjwvY2VudGVyPg0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCi0tLQ0KDQo8YnIvPg0KDQojIFNldHVwIFN0YWdlDQoNCjxici8+DQoNCkxvYWRpbmcgbmVjZXNzYXJ5IHBhY2thZ2VzLg0KDQpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmBgYA0KDQo8YnIvPg0KDQpDcmVhdGUgYWRkaXRpb25hbCBmdW5jdGlvbnMNCg0KYGBge3J9DQojIE1vZGUgYXZlcmFnZQ0KZ2V0bW9kZSA8LSBmdW5jdGlvbih2KSB7DQogICB1bmlxdiA8LSB1bmlxdWUodikNCiAgIHVuaXF2W3doaWNoLm1heCh0YWJ1bGF0ZShtYXRjaCh2LCB1bmlxdikpKV0NCn0NCmBgYA0KDQo8YnIvPg0KDQojIyMjIExvYWQgdGhlIGRhdGFzZXQNCg0KPGJyLz4NCg0KRGF0YXNldCB0YWtlbiBmcm9tOiBodHRwczovL3d3dy5rYWdnbGUuY29tL2RyZ2lsZXJtby9uYmEtcGxheWVycy1zdGF0cy92ZXJzaW9uLzINCg0KSWYgeW91IG5lZWQgdG8gc2VlIHRoZSBnbG9zc2FyeSBhbmQgdGhlIGRhdGEgdGlkeWluZyBwcm9jZXNzLCBwbGVhc2UgdmlzaXQgdGhlIGZpcnN0IHBhcnQgW2hlcmVdKGh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMCkuDQoNCkxvYWRpbmcgZGF0YXNldHMuLi4NCg0KYGBge3J9DQpOQkEgPC0gcmVhZC5jc3YoIk5CQV9UaWR5U2V0LmNzdiIpWywtYygxKV0NCk5CQV9TY2FsZWQgPC0gcmVhZC5jc3YoIk5CQV9TY2FsZWRfVGlkeVNldC5jc3YiKQ0KTkJBJFBvcyA8LSBmYWN0b3IoTkJBJFBvcywgbGV2ZWxzID0gYygiQyIsICJQRiIsICJTRiIsICJTRyIsICJQRyIpKQ0KTkJBX1NjYWxlZCRQb3MgPC0gZmFjdG9yKE5CQV9TY2FsZWQkUG9zLCBsZXZlbHMgPSBjKCJDIiwgIlBGIiwgIlNGIiwgIlNHIiwgIlBHIikpDQpQb3NDb2xvckNvZGUgPC0gYygiQyI9IiNGRjAwMDAiLCAiUEYiPSIjRkZBNTAwIiwgIlNGIj0iI0REREQwMCIgLCJTRyI9IiMwMDAwRkYiLCAiUEciPSIjMzJDRDMyIikNCmBgYA0KDQo8YnIvPg0KDQoqKkRpc3BsYXlpbmcgcmF3IHRpZHkgZGF0YSB0YWJsZSoqDQoNCjxkaXYgY2xhc3M9J21haW50YWJsZSc+DQoNCmBgYHtyIGZpZy53aWR0aD05fQ0KTkJBDQpgYGANCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0iRmFjdCI+DQoNCjx1bCBjbGFzcz0iQ3VzdG9tTGlzdCI+DQoNCjxsaT5OdW1iZXIgb2Ygcm93czogYHIgbnJvdyhOQkEpYDwvbGk+DQo8bGk+TnVtYmVyIG9mIGNvbHVtbnM6IGByIG5jb2woTkJBKWA8L2xpPg0KPGxpPk51bWJlciBvZiBwbGF5ZXJzOiBgciBuX2Rpc3RpbmN0KE5CQSRQbGF5ZXIpYDwvbGk+DQo8bGk+TnVtYmVyIG9mIHRlYW1zOiBgciBuX2Rpc3RpbmN0KE5CQSRUbSlgPC9saT4NCjxsaT5GaWxlIFNpemU6IGByIGZvcm1hdChvYmplY3Quc2l6ZShOQkEpLCB1bml0cyA9ICJLYiIpYDwvbGk+DQoNCjwvdWw+DQoNCjwvZGl2Pg0KDQo8L2Rpdj4NCg0KPGJyLz4NCg0KLS0tDQoNCjxici8+DQoNCiMgUGxheWVycw0KDQpJbiB0aGlzIHNlc3Npb24sIHdlIHN0YXJ0IGJ5IGV4cGxvcmluZyB0aGUgcGxheWVycy4gVGhlIHZhcmlhYmxlcyB0aGF0IHdpbGwgYmUgZW50aWNpbmcgdG8gZXhwbG9yZSBpbmNsdWRlIHRoZWlyIHBvc2l0aW9ucywgaGVpZ2h0LCB3ZWlnaHQsIGJvcm4gYW5kIGFnZS4NCg0KPGJyLz4NCg0KIyMjIE5CQSBUZWFtcyBncm93dGgNCg0KPGRpdiBjbGFzcz0nQm94Jz4NCg0KPGJyLz4NCg0KV2Ugc3RhcnQgYnkgZXhwbG9yaW5nIGhvdyBOQkEgdGVhbXMgYW5kIHRoZSBudW1iZXIgb2YgcGxheWVycyBoYXMgZ3Jvd24gb3ZlciB0aGUgeWVhcnMuIFRoZSB0YWJsZSB3aWxsIHNob3cgdGhlIGRldGFpbGVkIG51bWJlcnMuDQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCg0KYGBge3J9DQpUZWFtX1BsYXllciA8LSBOQkEgJT4lDQogIGdyb3VwX2J5KFllYXIpICU+JQ0KICBzdW1tYXJpc2UoblBsYXllcnMgPSBuX2Rpc3RpbmN0KFBsYXllciksDQogICAgICAgICAgICBuVGVhbXMgPSBuX2Rpc3RpbmN0KFRtKSwNCiAgICAgICAgICAgIG5HYW1lcyA9IG1heChHKSwNCiAgICAgICAgICAgIFBsYXllcnNfcGVyX1RlYW0gPSByb3VuZChuUGxheWVycy9uVGVhbXMsIDIpKSANCg0KVGVhbV9QbGF5ZXIgJT4lDQogICAga2FibGUoZXNjYXBlID0gRkFMU0UsIGFsaWduPSdjJywgY2FwdGlvbiA9ICJQbGF5ZXJzLCBUZWFtcyBhbmQgR2FtZXMiKSAlPiUNCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBUKSAlPiUNCiAgICBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiLCBoZWlnaHQgPSAiMzAwcHgiKQ0KYGBgDQoNCjwvZGl2Pg0KDQo8YnIvPg0KDQpOb3cgd2Ugc2VlIGhvdyBOQkEgdGVhbXMgaGF2ZSBncm93biwgd2l0aCB0aGlzIHNpbXBsZSBwbG90Lg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0PTV9DQpUZWFtX1BsYXllciAlPiUNCiAgICBnZ3Bsb3QoKSArDQogICAgZ2VvbV9saW5lKGFlcyhZZWFyLCBuVGVhbXMsIGxpbmV0eXBlID0gIlRyZW5kIGxpbmUiKSkgKw0KICAgIGdndGl0bGUoIk51bWJlciBvZiBOQkEgVGVhbXMgYnkgWWVhciIpICsNCiAgICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gbWVhbihUZWFtX1BsYXllciRuVGVhbXMpLCBsaW5ldHlwZSA9ICJBdmVyYWdlIGxpbmUiKSwNCiAgICAgICAgICAgICAgIGNvbCA9ICJyZWQiLA0KICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDE5NTAsIDIwMTcsIDEwKSkgKw0KICAgIHNjYWxlX2xpbmV0eXBlX21hbnVhbChuYW1lID0gIiIsIHZhbHVlcyA9IGMoMiwgMSksIGd1aWRlID0gZ3VpZGVfbGVnZW5kKHJldmVyc2UgPSBUUlVFKSkgKw0KICAgIHlsYWIoIk51bWJlciBvZiBUZWFtcyIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0iRmFjdCI+DQoNCjx1bCBjbGFzcz0iQ3VzdG9tTGlzdCI+DQoNCjxsaT5UaGUgbnVtYmVyIG9mIE5CQSB0ZWFtcyBoYXMgZ3Jvd24gZnJvbSAxNyBpbiAxOTUwIHRvIDMwIGluIDIwMTcuPC9saT4NCjxsaT5Mb3dlc3QgbnVtYmVyIG9mIHRlYW1zIG9jY3VyIGR1cmluZyAxOTU2LTE5NjEgc2Vhc29ucywgd2l0aCBvbmx5IDggdGVhbXMgY29tcGV0ZXMuPC9saT4NCjxsaT5UaGUgaGlnaGVzdCBudW1iZXIgb2YgdGVhbXMgb2NjdXIgZnJvbSAyMDA1IHVudGlsIHByZXNlbnQgd2l0aCAzMCB0ZWFtcy48L2xpPg0KICAgIA0KPC91bD4NCg0KPC9kaXY+DQoNCjxici8+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIE51bWJlciBvZiBOQkEgUGxheWVycw0KDQo8ZGl2IGNsYXNzPSdCb3gnPg0KDQo8YnIvPg0KDQpXZSBjYW4gcGxvdCBudW1iZXIgb2YgTkJBIHBsYXllcnMgeWVhciBieSB5ZWFyIGJhc2VkIG9uIHRoZSBzYW1lIHRhYmxlLg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0PTV9DQpUZWFtX1BsYXllciAlPiUNCiAgICBnZ3Bsb3QoYWVzKFllYXIsIG5QbGF5ZXJzLCBmaWxsPW5QbGF5ZXJzKSkgKw0KICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogICAgZ2d0aXRsZSgiTnVtYmVyIG9mIE5CQSBQbGF5ZXJzIGJ5IFllYXIiKSArDQogICAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IG1lYW4oVGVhbV9QbGF5ZXIkblBsYXllcnMpLCBsaW5ldHlwZSA9ICJBdmVyYWdlIGxpbmUiKSwNCiAgICAgICAgICAgICAgIGNvbCA9ICJyZWQiLA0KICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJncmVlbiIsIGhpZ2ggPSAicmVkIikgKw0KICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTk1MCwgMjAxNywgMTApKSArDQogICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gMikgKw0KICAgIHlsYWIoIk51bWJlciBvZiBQbGF5ZXJzIikgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikNCmBgYA0KDQo8YnIvPg0KDQo8ZGl2IGNsYXNzPSJGYWN0Ij4NCg0KPHVsIGNsYXNzPSJDdXN0b21MaXN0Ij4NCg0KPGxpPlRoZSBudW1iZXIgb2YgTkJBIHBsYXllcnMgaGFzIGdyb3duIG1vcmUgdGhhbiBkb3VibGVkLCBmcm9tIDIxOSBpbiAxOTUwIHRvIDQ4MSBpbiAyMDE3LjwvbGk+DQo8bGk+VGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHBsYXllcnMgY29tcGV0ZSBpbiBOQkEgcmVndWxhciBzZWFzb24gaXMgYHIgcm91bmQobWVhbihUZWFtX1BsYXllciRuUGxheWVycyksIDIpYDwvbGk+DQo8bGk+VGhlIGxlYXN0IG51bWJlciBvZiBwbGF5ZXJzIGNvbXBldGVzIGluIGEgc2Vhc29uIGlzIGByIG1pbihUZWFtX1BsYXllciRuUGxheWVycylgIHBsYXllcnMgZHVyaW5nIDE5NTUtMTk1NiAmIDE5NTgtMTk1OSBzZWFzb24uPC9saT4NCjxsaT5UaGUgbW9zdCBudW1iZXIgb2YgcGxheWVycyBjb21wZXRlcyBpbiBhIHNlYXNvbiBpcyBgciBtYXgoVGVhbV9QbGF5ZXIkblBsYXllcnMpYCBwbGF5ZXJzIGluIDIwMTQtMjAxNSBzZWFzb24uPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjxici8+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIE51bWJlciBvZiBnYW1lcyBmb3IgZWFjaCB0ZWFtIGluIGEgc2Vhc29uDQoNCjxkaXYgY2xhc3M9IkJveCI+DQoNCjxici8+DQoNCkFsbW9zdCBhbGwgTkJBIGZhbnMga25vdyB0aGF0IHRoZXJlIGFyZSA4MiBnYW1lcyB0byBwbGF5IGluIGEgc2Vhc29uLiBIb3dldmVyLCBJJ2QgbGlrZSB0byBzZWUgdGhlIHBhc3QgaGlzdG9yeSwgSSB3b25kZXIgaWYgaXQncyBoYXMgZ3Jvd24gb3ZlciB0aW1lLCB0b28uDQoNCmBgYHtyIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQ9NX0NClRlYW1fUGxheWVyICU+JSANCiAgICBnZ3Bsb3QoYWVzKFllYXIsIG5HYW1lcywgZmlsbD1uR2FtZXMpKSArDQogICAgZ2d0aXRsZSgiTnVtYmVyIG9mIEdhbWVzIGluIGEgU2Vhc29uIikgKw0KICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogICAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAiZ3JlZW4iLCBoaWdoID0gInJlZCIpICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDE5NTAsIDIwMTcsIDEwKSkgKw0KICAgIHlsYWIoIk51bWJlciBvZiBHYW1lcyIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0iRmFjdCI+DQoNClNpbmNlIDE5NjfigJM2OCBzZWFzb24sIE5CQSBleHBhbmRzIGl0cyByZWd1bGFyIHNlYXNvbiB0byA4MiBnYW1lcyBwZXIgdGVhbSwgd2hlcmUgaXQgc3RpbGwgc3RhbmRzIHRvIHRoaXMgZGF0ZS4gRXhjZXB0IGZvciB0aGVzZSBub3RhYmxlIG9jY3VycmVuY2VzOg0KDQo8dWwgY2xhc3M9IkN1c3RvbUxpc3QiPg0KDQo8bGk+MTk5OOKAkzk5IE5CQSBzZWFzb246IG51bWJlciBvZiBnYW1lcyBpcyA1MCwgZHVlIHRvIGEgW2xvY2tvdXRdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpLzE5OTglRTIlODAlOTM5OV9OQkFfbG9ja291dCk8L2xpPg0KPGxpPjIwMTEtMTIgTkJBIHNlYXNvbjogbnVtYmVyIG9mIGdhbWVzIGlzIDY2LCBkdWUgdG8gYW5vdGhlciBbbG9ja291dF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvMjAxMV9OQkFfbG9ja291dCk8L2xpPg0KDQo8L3VsPg0KDQo8L2Rpdj4NCg0KPGJyLz4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgUG9zaXRpb24gUmF0aW8gaW4gdGhlIE5CQQ0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpNeSBuZXh0IHF1ZXN0aW9uLCBpcyB0aGUgcG9zaXRpb24gcmF0aW8gYWx3YXlzIGV2ZW5seSBkaXN0cmlidXRlZD8NCg0KYGBge3IgZmlnLndpZHRoID0gOX0NCk5CQSAlPiUNCiAgICBnZ3Bsb3QoYWVzKFllYXIsIGdyb3VwPVBvcywgY29sb3IgPSBQb3MsIGZpbGwgPSBQb3MpKSArDQogICAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC41LCBwb3NpdGlvbiA9ICJmaWxsIikgKw0KICAgIGdndGl0bGUoIlBvc2l0aW9uIFJhdGlvIGJ5IFllYXIiKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKCJQb3MiLCB2YWx1ZXMgPSBQb3NDb2xvckNvZGUpICsNCiAgICBzY2FsZV9maWxsX21hbnVhbCgiUG9zIiwgdmFsdWVzID0gUG9zQ29sb3JDb2RlKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxOTUwLCAyMDE3LCAxMCkpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgSGVpZ2h0IERpc3RyaWJ1dGlvbg0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpXZSBhbGwga25vdyBiYXNrZXRiYWxsIHBsYXllcnMgYXJlIHRhbGwsIG5hdHVyYWxseSwgSSB3YW50IHRvIGtub3cgaG93IHRhbGwgbW9zdCBvZiB0aGVtIGFyZSwgYW5kIGhvdyBzbWFsbCB3ZSBhcmUgKGF2ZXJhZ2UgaGVpZ2h0IHBlcnNvbikgY29tcGFyZWQgdG8gdGhlaXIgc3RhbmRhcmQuDQoNCmBgYHtyIGZpZy53aWR0aCA9IDksIGZpZy5oZWlnaHQ9NX0NCkhlaWdodE1lYW4gPC0gbWVhbihOQkEkSGVpZ2h0KQ0KSGVpZ2h0U0QgPC0gc2QoTkJBJEhlaWdodCkNCg0KTkJBICU+JSBnZ3Bsb3QoYWVzKEhlaWdodCwgZmlsbD1UUlVFKSkgKw0KICAgIGdlb21fZGVuc2l0eSgpICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDE2MCwgMjQwLCAxMCkpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gSGVpZ2h0TWVhbiwgbGluZXR5cGUgPSAiQXZlcmFnZSBoZWlnaHQgb2YgTkJBIHBsYXllcnMiKSwNCiAgICAgICAgICAgICAgIGNvbCA9ICJyZWQiLA0KICAgICAgICAgICAgICAgYWxwaGEgPSAwLjgpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gMTc3LCBsaW5ldHlwZSA9ICJBdmVyYWdlIGhlaWdodCBvZiBBbWVyaWNhbiBtYWxlIiksDQogICAgICAgICAgICAgICBjb2wgPSAiYmx1ZSIsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuOCkgKw0KICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoc2VxKEhlaWdodE1lYW4sIDI0MCwgSGVpZ2h0U0QpLCBzZXEoSGVpZ2h0TWVhbiwgMTYwLCAtSGVpZ2h0U0QpKSwNCiAgICAgICAgICAgICAgIGNvbCA9ICJibHVlIiwNCiAgICAgICAgICAgICAgIGFscGhhID0gMC4zLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSA1KSArDQogICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gYygxLCAxKSkgKw0KICAgIGd1aWRlcyhmaWxsPUZBTFNFKSArDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KYGBgDQoNCjxici8+DQoNCjxkaXYgY2xhc3M9IkZhY3QiPg0KDQo8dWwgY2xhc3M9IkN1c3RvbUxpc3QiPg0KDQo8bGk+QXZlcmFnZSBoZWlnaHQgb2YgTkJBIHBsYXllcnMgaXM6IGByIHJvdW5kKG1lYW4oTkJBJEhlaWdodCwgbmEucm09VCksIDEpYCBjbSwgd2l0aCBzdGFuZGFyZCBkZXZpYXRpb246IGByIHJvdW5kKHNkKE5CQSRIZWlnaHQsIG5hLnJtPVQpLCAxKWAuPC9saT4NCjxsaT5BdmVyYWdlIGhlaWdodCBvZiBBbWVyaWNhbiBtZW4gaXMgMTc3IGNtIChbc291cmNlXShodHRwczovL2hhbGxzLm1kL2F2ZXJhZ2UtaGVpZ2h0LW1lbi1oZWlnaHQtd2VpZ2h0LykpIGlzIHNob3J0ZXIgYnkgbW9yZSB0aGFuIHR3byBzdGFuZGFyZCBkZXZpYXRpb24gYXdheSBmcm9tIGF2ZXJhZ2UgTkJBIHBsYXllcnMuPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjxici8+DQoNCk5vdyBsZXQncyBzZWUgdGhlIGdyb3VuZGNyYXdsZXJzIGFuZCB0aGUgc2t5c2NyYXBlcnMgaW4gdGhlIE5CQS4NCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7cn0NCk5CQSAlPiUNCiAgICBncm91cF9ieShIZWlnaHQsIFBsYXllcikgJT4lDQogICAgc3VtbWFyaXNlKFBvcyA9IGdldG1vZGUoUG9zaXRpb24pLA0KICAgICAgICAgICAgICBZZWFyQWN0aXZlID0gcGFzdGUobWVhbihZZWFyU3RhcnQpLCAiLSIsIG1lYW4oWWVhckVuZCkpLA0KICAgICAgICAgICAgICBUZWFtID0gZ2V0bW9kZShUbSksDQogICAgICAgICAgICAgIEdhbWVzID0gc3VtKEcpLA0KICAgICAgICAgICAgICBQUEcgPSByb3VuZChzdW0oUFRTKS9zdW0oRyksIDIpKSAlPiUNCiAgICBhcnJhbmdlKEhlaWdodCkgJT4lDQogICAgaGVhZCgpICU+JQ0KICAgIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBhbGlnbj0nYycsIGNhcHRpb24gPSAiU2hvcnRlc3QgUGxheWVycyIpICU+JQ0KICAgIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMiwgYm9sZCA9IFQpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBULCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzc3Nzc3NyIpDQpgYGANCg0KPGJyLz4NCg0KYGBge3J9DQpOQkEgJT4lDQogICAgZ3JvdXBfYnkoSGVpZ2h0LCBQbGF5ZXIpICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvc2l0aW9uKSwNCiAgICAgICAgICAgICAgWWVhckFjdGl2ZSA9IHBhc3RlKG1lYW4oWWVhclN0YXJ0KSwgIi0iLCBtZWFuKFllYXJFbmQpKSwNCiAgICAgICAgICAgICAgVGVhbSA9IGdldG1vZGUoVG0pLA0KICAgICAgICAgICAgICBHYW1lcyA9IHN1bShHKSwNCiAgICAgICAgICAgICAgUFBHID0gcm91bmQoc3VtKFBUUykvc3VtKEcpLCAyKSkgJT4lDQogICAgYXJyYW5nZShkZXNjKEhlaWdodCkpICU+JQ0KICAgIGhlYWQobj04KSAlPiUNCiAgICBrYWJsZShlc2NhcGUgPSBGQUxTRSwgYWxpZ249J2MnLCBjYXB0aW9uID0gIlRhbGxlc3QgUGxheWVycyIpICU+JQ0KICAgIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMiwgYm9sZCA9IFQpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBULCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzc3Nzc3NyIpDQpgYGANCg0KPC9kaXY+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIEhlaWdodCBDb21wYXJpc29uDQoNCjxkaXYgY2xhc3M9IkJveCI+DQoNCjxici8+DQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCg0KTmV4dCwgSSdkIGxpa2UgdG8gY29tcGFyZSB0aGVtIHNpZGUtYnktc2lkZSwgZnJvbSBDZW50ZXIgdG8gUG9pbnQgR3VhcmQsIGFuZCBmaW5kIHRoZWlyIGF2ZXJhZ2VzIGFuZCByYW5nZXMuDQoNCmBgYHtyfQ0KTkJBICU+JQ0KICAgIGdyb3VwX2J5KFBvcykgJT4lDQogICAgc3VtbWFyaXNlKE1pbkhlaWdodCA9IG1pbihIZWlnaHQpLA0KICAgICAgICAgICAgICBNYXhIZWlnaHQgPSBtYXgoSGVpZ2h0KSwNCiAgICAgICAgICAgICAgTWVkaWFuSGVpZ2h0ID0gbWVkaWFuKEhlaWdodCksDQogICAgICAgICAgICAgIE1vZGVIZWlnaHQgPSBnZXRtb2RlKEhlaWdodCksDQogICAgICAgICAgICAgIE1lYW5IZWlnaHQgPSByb3VuZChtZWFuKGBIZWlnaHRgKSwgMikpICU+JQ0KICAgIG11dGF0ZShQb3MgPSBjZWxsX3NwZWMoUG9zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbiA9ICJjIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYWNrZ3JvdW5kID0gZmFjdG9yKFBvcywgYygiQyIsICJQRiIsICJTRiIsICJTRyIsICJQRyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUG9zQ29sb3JDb2RlKSkpICU+JQ0KICAgIGthYmxlKGVzY2FwZSA9IEZBTFNFLCBhbGlnbj0nYycsIGNhcHRpb24gPSAiSGVpZ2h0OiBBdmVyYWdlcyBhbmQgUmFuZ2UgYnkgUG9zaXRpb24iKSAlPiUNCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpDQpgYGANCg0KPC9kaXY+DQoNCjxici8+DQoNClZpb2xpbiBwbG90IG5vdCBvbmx5IGdpdmVzIHVzIHRoZSBhdmVyYWdlcyBhbmQgcmFuZ2VzLCBidXQgaXQgYWxzbyBnaXZlcyB1cyB0aGUgZGlzdHJpYnV0aW9uLg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0PTV9DQpOQkEgJT4lDQogIGdncGxvdChhZXMoUG9zLCBIZWlnaHQsIGNvbG9yPVBvcykpICsNCiAgZ2VvbV92aW9saW4oKSArDQogIGdndGl0bGUoIkhlaWdodCBkaXN0cmlidXRpb24gYnkgcG9zaXRpb24iKSArDQogIHN0YXRfc3VtbWFyeShmdW4ueT1tZWFuLCBnZW9tPSJwb2ludCIsIHNoYXBlPTgsIHNpemU9NikgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gbWVhbihOQkEkSGVpZ2h0LCBuYS5ybT1UKSwgbGluZXR5cGUgPSAiQXZlcmFnZSBOQkEgcGxheWVycyIpLA0KICAgICAgICAgICAgIGNvbCA9ICJyZWQiLA0KICAgICAgICAgICAgIGFscGhhID0gMC41KSArDQogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSAxNzcsIGxpbmV0eXBlID0gIkF2ZXJhZ2UgQW1lcmljYW4gbWFsZSIpLA0KICAgICAgICAgICAgIGNvbCA9ICJibHVlIiwNCiAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwoIlBvcyIsIHZhbHVlcyA9IFBvc0NvbG9yQ29kZSkgKw0KICBzY2FsZV9saW5ldHlwZV9tYW51YWwobmFtZSA9ICIiLCB2YWx1ZXMgPSBjKDEsIDEpKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikNCmBgYA0KDQo8YnIvPg0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCiMjIyBIZWlnaHQgYnkgWWVhcnMNCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KRXhwbG9yaW5nIHRoZSBoZWlnaHQgb2YgTkJBIHBsYXllcnMgd291bGQgbm90IGZlZWwgY29tcGxldGUgd2l0aG91dCB0YWtpbmcgYSBsb29rIGF0IGl0IGZyb20gdGhlIGNocm9ub2xvZ2ljYWwgcGVyc3BlY3RpdmUuIFRoaXMgbWlnaHQgbm90IHByb2R1Y2Ugc2lnbmlmaWNhbnQgaW5zaWdodCwgYnV0IEkganVzdCBjYW4ndCByZXNpc3Qgc2VlaW5nIHRoZSBwbG90Lg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0PTV9DQpIZWlnaHRZZWFyIDwtIE5CQSAlPiUNCiAgICBncm91cF9ieShZZWFyKSAlPiUNCiAgICBzdW1tYXJpc2UobWVhbkhlaWdodCA9IHJvdW5kKG1lYW4oSGVpZ2h0LCBuYS5ybSA9IFQpLCAxKSkNCg0KTkJBICU+JQ0KICAgIGdyb3VwX2J5KFllYXIsIFBvcykgJT4lDQogICAgc3VtbWFyaXNlKG1lYW5IZWlnaHQgPSByb3VuZChtZWFuKEhlaWdodCwgbmEucm0gPSBUKSwgMSkpICU+JQ0KICAgIGdncGxvdCgpICsNCiAgICBnZW9tX2xpbmUoYWVzKFllYXIsIG1lYW5IZWlnaHQsIGdyb3VwPVBvcywgY29sb3I9UG9zKSwgc2l6ZSA9IDEuMiwgYWxwaGEgPSAxKSArDQogICAgZ2VvbV9saW5lKGFlcyhZZWFyLCBtZWFuSGVpZ2h0LCBsaW5ldHlwZSA9ICJBdmVyYWdlIGxpbmUiKSwNCiAgICAgICAgICAgICAgZGF0YSA9IEhlaWdodFllYXIsIGNvbG9yID0gImJsYWNrIiwgc2l6ZSA9IDAuOCwgYWxwaGEgPSAwLjUpICsNCiAgICBnZ3RpdGxlKCJIZWlnaHQgYnkgcG9zaXRpb24gYnkgWWVhciIpICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDE5NTAsIDIwMTcsIDEwKSkgKw0KICAgIHNjYWxlX2NvbG9yX21hbnVhbCgiUG9zIiwgdmFsdWVzID0gUG9zQ29sb3JDb2RlKSArDQogICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gYygzKSkgKw0KICAgIHlsYWIoIkhlaWdodCIpICsNCiAgICBndWlkZXMoZ3JvdXA9RkFMU0UpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgQk1JIERpc3RyaWJ1dGlvbg0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpOZXh0LCBsZXQncyBleHBsb3JlIHRoZSBCTUkgKEJvZHkgTWFzcyBJbmRleCkgb2YgdGhlIHBsYXllcnMuDQoNCmBgYHtyIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTV9DQpCTUkgPC0gTkJBICU+JQ0KICAgIGdyb3VwX2J5KFBsYXllcikgJT4lDQogICAgZmlsdGVyKCFpcy5uYShCTUkpKSAlPiUNCiAgICBtdXRhdGUoQk1JR3JvdXAgPSBpZmVsc2UoQk1JIDwgMTguNSwgIlVuZGVyd2VpZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoQk1JID49IDE4LjUgJiBCTUkgPCAyNSwgIkhlYWx0aHkgd2VpZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKEJNSSA+PSAyNSAmIEJNSSA8IDMwLCAiT3ZlcndlaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiT2Jlc2UgQ2xhc3MgSSIpKSkpICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvc2l0aW9uKSwNCiAgICAgICAgICAgICAgSGVpZ2h0ID0gZ2V0bW9kZShIZWlnaHQpLA0KICAgICAgICAgICAgICBXZWlnaHQgPSBnZXRtb2RlKFdlaWdodCksDQogICAgICAgICAgICAgIEJNSSA9IGdldG1vZGUoQk1JKSwNCiAgICAgICAgICAgICAgQk1JR3JvdXAgPSBnZXRtb2RlKEJNSUdyb3VwKSwNCiAgICAgICAgICAgICAgR2FtZXMgPSBzdW0oRyksDQogICAgICAgICAgICAgIFBQRyA9IHJvdW5kKHN1bShQVFMpL0dhbWVzLCAxKSkNCg0Kc2hhZGUgPC0gZGF0YS5mcmFtZSh4c3RhcnQgPSBjKDE1LCAyNSwgMzApLCB4ZW5kID0gYygxOC41LCAzMCwgMzMpLCBjb2wgPSBjKCIjRjAwIiwgIiMwRjAiLCAiIzAwRiIpKQ0KQk1JY2xhc3MgPC0gZGF0YS5mcmFtZShYID0gYygxNS43LCAyMCwgMjYuNSwgMzEpLCBZID0gMC4yNywgbGFiZWwgPSBjKCJVbmRlcndlaWdodCIsICJIZWFsdGh5IHdlaWdodCIsICJPdmVyd2VpZ2h0IiwgIk9iZXNlIikpDQpCTUlNZWFuIDwtIG1lYW4oQk1JJEJNSSkNCkJNSVNEIDwtIHNkKEJNSSRCTUkpDQoNCkJNSSAlPiUgZ2dwbG90KCkgKw0KICAgIGdlb21fcmVjdChkYXRhID0gc2hhZGUsDQogICAgICAgICAgICAgIGFlcyh4bWluID0geHN0YXJ0LCB4bWF4ID0geGVuZCwgeW1pbiA9IDAsIHltYXggPSBJbmYsIGZpbGwgPSBjb2wpLA0KICAgICAgICAgICAgICBhbHBoYSA9IDAuMykgKw0KICAgIGdlb21fZGVuc2l0eShhZXMoQk1JLCBmaWxsPVRSVUUpKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxNSwgMzMsIDEpKSArDQogICAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IEJNSU1lYW4sIGxpbmV0eXBlID0gIkF2ZXJhZ2UgbGluZSIpLA0KICAgICAgICAgICAgICAgY29sID0gImJsYWNrIiwNCiAgICAgICAgICAgICAgIGFscGhhID0gMC44KSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYyhzZXEoQk1JTWVhbiwgMzMsIEJNSVNEKSwgc2VxKEJNSU1lYW4sIDE1LCAtQk1JU0QpKSwNCiAgICAgICAgICAgICAgIGNvbCA9ICJibHVlIiwNCiAgICAgICAgICAgICAgIGFscGhhID0gMC4zLA0KICAgICAgICAgICAgICAgbGluZXR5cGUgPSA1KSArDQogICAgZ2VvbV90ZXh0KGRhdGEgPSBCTUljbGFzcywNCiAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gWCwgeSA9IFksIGxhYmVsID0gbGFiZWwpLA0KICAgICAgICAgICAgICBzaXplID0gMywNCiAgICAgICAgICAgICAgdmp1c3QgPSAwLA0KICAgICAgICAgICAgICBoanVzdCA9IDAsDQogICAgICAgICAgICAgIGNvbG9yID0gImZvcmVzdGdyZWVuIikgKw0KICAgIHNjYWxlX2xpbmV0eXBlX21hbnVhbChuYW1lID0gIiIsIHZhbHVlcyA9IGMoMSwgMSwgMSwgMSkpICsNCiAgICBndWlkZXMoZmlsbD1GQUxTRSkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIikNCmBgYA0KDQo8YnIvPg0KDQo8ZGl2IGNsYXNzPSJGYWN0Ij4NCg0KPHVsIGNsYXNzPSJDdXN0b21MaXN0Ij4NCkJNSSBjYXRlZ29yeSBiYXNlZCBvbiBbV0hPIHN0YW5kYXJkIEJNSSBjbGFzc2lmaWNhdGlvbi5dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0JvZHlfbWFzc19pbmRleCNDYXRlZ29yaWVzKQ0KDQo8bGk+QXZlcmFnZSBCTUk6IGByIHJvdW5kKG1lYW4oQk1JJEJNSSksIDEpYCwgd2l0aCBzdGFuZGFyZCBkZXZpYXRpb246IGByIHJvdW5kKHNkKEJNSSRCTUkpLDEpYDwvbGk+DQo8bGk+QXMgZXhwZWN0ZWQsIG1vc3QgcGxheWVycywgYHIgcm91bmQobWVhbihCTUkkQk1JR3JvdXAgPT0gIkhlYWx0aHkgd2VpZ2h0IikqMTAwLCAxKWAlIGFyZSBpbiB0aGUgIkhlYWx0aHkgV2VpZ2h0IiBjYXRlZ29yeS48L2xpPg0KPGxpPmByIHJvdW5kKG1lYW4oQk1JJEJNSUdyb3VwID09ICJPdmVyd2VpZ2h0IikqMTAwLCAxKWAlIHBsYXllcnMgYXJlIGluICJPdmVyd2VpZ2h0IiBjYXRlZ29yeSwgd2hpY2ggaXMgc3RpbGwgY29tbW9uIGluIGF0aGxldGVzLjwvbGk+DQo8bGk+YHIgbnJvdyhCTUlbQk1JJEJNSUdyb3VwID09ICJPYmVzZSBDbGFzcyBJIixdKWAgcGxheWVycyhgciByb3VuZChtZWFuKEJNSSRCTUlHcm91cCA9PSAiT2Jlc2UgQ2xhc3MgSSIpKjEwMCwgMSlgJSkgYXJlIGluICJPYmVzZSBDbGFzcyBJIiBjYXRlZ29yeSwgU2hhcXVpbGxlIE8nTmVhbCBpcyBvbmUgb2YgdGhlbS48L2xpPg0KPGxpPk9ubHkgMSBwbGF5ZXIgZmFsbHMgdW5kZXIgdGhlICJVbmRlcndlaWdodCIgY2F0ZWdvcnkuIEFzIG9uZSBvZiB0aGUgdGFsbGVzdCBwbGF5ZXIgaW4gdGhlIE5CQSAoMjMxIGNtKSwgaGUgYWxzbyB0aGUgc2tpbm5pZXN0IHBsYXllciBpbiB0aGUgbGVhZ3VlIGhpc3Rvcnkgd2l0aCBCTUkgb25seSAxNy4xLCBoaXMgbmFtZSBpcyBbTWFudXRlIEJvbF0oaHR0cHM6Ly8ya210Y2VudHJhbC5jb20vaW1nL3BsYXllcnMvMTgvZ2VuZXJhdGVkLzgwNDVpMS5wbmcvY2FjaGUtMTUwOTQ2OTcyNi84MDQ1aTEucG5nKTwvbGk+DQoNCjwvdWw+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KDQpOb3cgSSdtIGludGVyZXN0ZWQgdG8gc2VlIHRoZSByZWFsIEJJRyBndXlzIGluIHRoZSBOQkEuDQoNCjxici8+DQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCg0KYGBge3J9DQpCTUkgJT4lDQogICAgYXJyYW5nZShkZXNjKEJNSSkpICU+JQ0KICAgIGZpbHRlcihCTUlHcm91cCA9PSAiT2Jlc2UgQ2xhc3MgSSIpICU+JQ0KICAgIHNlbGVjdChCTUksIGV2ZXJ5dGhpbmcoKSkgJT4lDQogICAga2FibGUoZXNjYXBlID0gRkFMU0UsIGFsaWduPSdjJywgY2FwdGlvbiA9ICJIaWdoZXN0IEJNSSAoQWxsIGluIE9iZXNlIENsYXNzIEkgZ3JvdXApIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCBib2xkID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQsIGNvbG9yID0gIndoaXRlIiwgYmFja2dyb3VuZCA9ICIjNzc3Nzc3IikgJT4lDQogICAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCmBgYA0KDQo8L2Rpdj4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgQ2hyb25vbG9naWNhbCBTaXplIEdyb3d0aA0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpJbnNwaXJlZCBieSBIYW5zIFJvc2xpbmcncyBhbmltYXRlZCBidWJibGUgY2hhcnQgZnJvbSBbR2FwbWluZGVyXShodHRwczovL3d3dy5nYXBtaW5kZXIub3JnL3Rvb2xzLyMkY2hhcnQtdHlwZT1idWJibGVzKSwgSSBjaGFsbGVuZ2VkIG15c2VsZiB0byBzZWUgaWYgSSBjYW4gbWFrZSBvbmUgbXlzZWxmIHdpdGggdGhpcyBkYXRhc2V0LiBXZWlnaHQgaW4gdGhlIHgtYXhpcywgaGVpZ2h0IGluIHRoZSB5LWF4aXMsIHNpemUgZm9yIEJNSSwgYW5kIGNvbG9yIHJlcHJlc2VudCB0aGVpciBwb3NpdGlvbiBpbiB0aGUgZmllbGQuIERvbid0IGZvcmdldCB0byBjbGljayAqKiJQbGF5IioqIHRvIHNlZSB0aGVtIGdyb3dzIHllYXItYnkteWVhci5wbGF5ZXJzIGFyZSBpbiAiT3ZlcndlaWdodCIgY2F0ZWdvcnksIHdoaWNoIGlzIHN0aWxsIGNvbW1vbiBpbiBhdGhsZXRlcy4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgcGxvdGx5PVRSVUV9DQpwQnViYmxlIDwtIE5CQSAlPiUNCiAgICBmaWx0ZXIoIWlzLm5hKEJNSSksICFpcy5uYShXZWlnaHQpLCAhaXMubmEoSGVpZ2h0KSkgJT4lDQogICAgcGxvdF9seSh4ID0gfldlaWdodCwNCiAgICAgICAgICAgIHkgPSB+SGVpZ2h0LA0KICAgICAgICAgICAgc2l6ZSA9IH5CTUksDQogICAgICAgICAgICBjb2xvciA9IH5Qb3MsDQogICAgICAgICAgICBjb2xvcnMgPSBQb3NDb2xvckNvZGUsDQogICAgICAgICAgICBmcmFtZSA9IH5ZZWFyLA0KICAgICAgICAgICAgdGV4dCA9IH5QbGF5ZXIsIA0KICAgICAgICAgICAgaG92ZXJpbmZvID0gInRleHQiLA0KICAgICAgICAgICAgdHlwZSA9ICdzY2F0dGVyJywNCiAgICAgICAgICAgIG1vZGUgPSAnbWFya2VycycpICU+JSANCiAgICBhbmltYXRpb25fb3B0cygxMDAwLCBlYXNpbmcgPSAiZWxhc3RpYyIsDQogICAgICAgICAgICAgICAgICAgcmVkcmF3ID0gRkFMU0UpICU+JSANCiAgICBhbmltYXRpb25fYnV0dG9uKHggPSAxLA0KICAgICAgICAgICAgICAgICAgICAgeGFuY2hvciA9ICJyaWdodCIsDQogICAgICAgICAgICAgICAgICAgICB5ID0gMCwNCiAgICAgICAgICAgICAgICAgICAgIHlhbmNob3IgPSAiYm90dG9tIikgJT4lDQogICAgYW5pbWF0aW9uX3NsaWRlcihjdXJyZW50dmFsdWUgPSBsaXN0KHByZWZpeCA9ICJZZWFyOiAiLCBmb250ID0gbGlzdChjb2xvcj0icmVkIikpKQ0KDQpodG1sd2lkZ2V0czo6c2F2ZVdpZGdldChhcy53aWRnZXQocEJ1YmJsZSksICJwQnViYmxlLmh0bWwiKQ0KYGBgDQoNCjxpZnJhbWUgYWxpZ24gPSAiY2VudGVyIiB3aWR0aD0iODAwIiBoZWlnaHQ9IjYwMCIgZnJhbWVib3JkZXI9IjAiIHNjcm9sbGluZz0ibm8iIHNyYz0icEJ1YmJsZS5odG1sIj48L2lmcmFtZT4NCg0KKipOb3RlOioqICpJZiB0aGlzIGFuaW1hdGVkIHBsb3QgKHdoaWNoIGlzIHRoZSBtb3N0IGF3ZXNvbWUgcGxvdCBvZiBhbGwpLCBkb2VzIG5vdCBhcHBlYXIgaW4geW91ciBicm93c2VyLCB0aGF0IG1lYW5zIEkgc3RpbGwgaGF2ZSB0ZWNobmljYWwgZGlmZmljdWx0eSB3aXRoIGF0dGFjaGluZyB0aGUgcGxvdCB3aXRoIHRoZSAnaWZyYW1lJyBtZXRob2QuIFdpbGwgY29tZSBiYWNrIHRvIHRoaXMgbGF0ZXIgd2hlbiB0aGUgc29sdXRpb24gZW1lcmdlKg0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCiMjIyBBYmlsaXR5IGJ5IFBvc2l0aW9uIENvbXBhcmlzb24NCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KU2luY2Ugd2Ugbm93IGtub3cgdGhhdCBoZWlnaHQgaXMgc3Ryb25nbHkgY29ycmVsYXRlZCB3aXRoIHRoZWlyIHBvc2l0aW9uIGluIHRoZSBmaWVsZCwgbmF0dXJhbGx5IEknbSBpbnRlcmVzdGVkIHRvIHNlZSBob3cgdGhlaXIgYWJpbGl0aWVzIGRpZmZlciBiYXNlZCBvbiB0aGVpciByb2xlcy4NCg0KVG8gY29tcGFyZSBtdWx0aXBsZSB2YXJpYWJsZXMgd2l0aCBkaWZmZXJlbnQgc2NhbGVzLCBJIHVzZSBub3JtYWxpemVkIChzY2FsZWQpIGRhdGEgSSBjcmVhdGVkIGluIFtwYXJ0IDE6IERhdGEgUHJlcGFyYXRpb24gU3RhZ2VdKCkgb2YgdGhpcyBzZXJpZXMuIFRoaXMgbWFrZXMgIjAiIGluIHRoZSBzY2FsZSBpcyB0aGUgYXZlcmFnZSBvZiBlYWNoIHZhcmlhYmxlLCBhbnl0aGluZyBiZWxvdyAiMCIgdGVsbHMgdXMgdGhhdCB0aGUgZ3JvdXAgcGVyZm9ybWFuY2UgaW4gdGhhdCBwYXJ0aWN1bGFyIGNhdGVnb3J5IGlzIGJlbG93IGF2ZXJhZ2UsIGFuZCB2aWNlIHZlcnNhLg0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBwbG90bHk9VFJVRX0NClJhZGFySGVpZ2h0IDwtIE5CQV9TY2FsZWQgJT4lDQogICAgZ3JvdXBfYnkoUG9zKSAlPiUNCiAgICBzdW1tYXJpc2UoU2hvb3QyUCA9IG1lYW4oWDJQLiwgbmEucm09VCksDQogICAgICAgICAgICAgIFNob290M1AgPSBtZWFuKFgzUC4sIG5hLnJtPVQpLA0KICAgICAgICAgICAgICBTaG9vdEZUID0gbWVhbihGVC4sIG5hLnJtPVQpLA0KICAgICAgICAgICAgICBPZmZlbnNpdmVSQiA9IG1lYW4oT1JCLCBuYS5ybT1UKSwNCiAgICAgICAgICAgICAgQXNzaXN0ID0gbWVhbihBU1QsIG5hLnJtPVQpLA0KICAgICAgICAgICAgICBEZWZlbnNpdmVSQiA9IG1lYW4oRFJCLCBuYS5ybT1UKSwNCiAgICAgICAgICAgICAgU3RlYWwgPSBtZWFuKFNUTCwgbmEucm09VCksDQogICAgICAgICAgICAgIEJsb2NrID0gbWVhbihCTEssIG5hLnJtPVQpKSAlPiUNCiAgICBzZWxlY3QoLVBvcykNCg0KcFJhZGFyIDwtIHBsb3RfbHkodHlwZSA9ICdzY2F0dGVycG9sYXInLA0KICAgICAgICBmaWxsID0gJ3Rvc2VsZicsDQogICAgICAgIG1vZGUgPSAnbGluZXMnKSAlPiUNCiAgICBhZGRfdHJhY2UociA9IGFzLm51bWVyaWMoYXMudmVjdG9yKFJhZGFySGVpZ2h0WzEsXSkpLA0KICAgICAgICAgICAgICB0aGV0YSA9IGFzLmNoYXJhY3Rlcihhcy52ZWN0b3IoY29sbmFtZXMoUmFkYXJIZWlnaHQpKSksDQogICAgICAgICAgICAgIG5hbWUgPSAnQycsDQogICAgICAgICAgICAgIGZpbGxjb2xvciA9ICIjRkYwMDAwIiwNCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuNSkgJT4lDQogICAgYWRkX3RyYWNlKHIgPSBhcy5udW1lcmljKGFzLnZlY3RvcihSYWRhckhlaWdodFsyLF0pKSwNCiAgICAgICAgICAgICAgdGhldGEgPSBhcy5jaGFyYWN0ZXIoYXMudmVjdG9yKGNvbG5hbWVzKFJhZGFySGVpZ2h0KSkpLA0KICAgICAgICAgICAgICBuYW1lID0gJ1BGJywNCiAgICAgICAgICAgICAgZmlsbGNvbG9yID0gIiNGRkE1MDAiLA0KICAgICAgICAgICAgICBvcGFjaXR5ID0gMC41KSAlPiUNCiAgICBhZGRfdHJhY2UociA9IGFzLm51bWVyaWMoYXMudmVjdG9yKFJhZGFySGVpZ2h0WzMsXSkpLA0KICAgICAgICAgICAgICB0aGV0YSA9IGFzLmNoYXJhY3Rlcihhcy52ZWN0b3IoY29sbmFtZXMoUmFkYXJIZWlnaHQpKSksDQogICAgICAgICAgICAgIG5hbWUgPSAnU0YnLA0KICAgICAgICAgICAgICBmaWxsY29sb3IgPSAiI0REREQwMCIsDQogICAgICAgICAgICAgIG9wYWNpdHkgPSAwLjUpICU+JQ0KICAgIGFkZF90cmFjZShyID0gYXMubnVtZXJpYyhhcy52ZWN0b3IoUmFkYXJIZWlnaHRbNCxdKSksDQogICAgICAgICAgICAgIHRoZXRhID0gYXMuY2hhcmFjdGVyKGFzLnZlY3Rvcihjb2xuYW1lcyhSYWRhckhlaWdodCkpKSwNCiAgICAgICAgICAgICAgbmFtZSA9ICdTRycsDQogICAgICAgICAgICAgIGZpbGxjb2xvciA9ICIjMDAwMEZGIiwNCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDAuNSkgJT4lDQogICAgYWRkX3RyYWNlKHIgPSBhcy5udW1lcmljKGFzLnZlY3RvcihSYWRhckhlaWdodFs1LF0pKSwNCiAgICAgICAgICAgICAgdGhldGEgPSBhcy5jaGFyYWN0ZXIoYXMudmVjdG9yKGNvbG5hbWVzKFJhZGFySGVpZ2h0KSkpLA0KICAgICAgICAgICAgICBuYW1lID0gJ1BHJywNCiAgICAgICAgICAgICAgZmlsbGNvbG9yID0gIiMzMkNEMzIiLA0KICAgICAgICAgICAgICBvcGFjaXR5ID0gMC41KSAlPiUNCiAgICBsYXlvdXQocG9sYXIgPSBsaXN0KA0KICAgICAgICByYWRpYWxheGlzID0gbGlzdCgNCiAgICAgICAgdmlzaWJsZSA9IFQsDQogICAgICAgIHJhbmdlID0gYygtMSwgMSkpKSkNCmBgYA0KDQo8ZGl2PjxhIGhyZWY9Imh0dHBzOi8vcGxvdC5seS9+TnVuTm9EZUNhYnVuaWMvMS8/c2hhcmVfa2V5PTBmRzhCckZRNjBxSUFzRFJFUWJMNHkiIHRhcmdldD0iX2JsYW5rIiB0aXRsZT0iUmFkYXJIZWlnaHQiIHN0eWxlPSJkaXNwbGF5OiBibG9jazsgdGV4dC1hbGlnbjogY2VudGVyOyI+PGltZyBzcmM9Imh0dHBzOi8vcGxvdC5seS9+TnVuTm9EZUNhYnVuaWMvMS5wbmc/c2hhcmVfa2V5PTBmRzhCckZRNjBxSUFzRFJFUWJMNHkiIGFsdD0iUmFkYXJIZWlnaHQiIHN0eWxlPSJtYXgtd2lkdGg6IDEwMCU7d2lkdGg6IDYwMHB4OyIgIHdpZHRoPSI2MDAiIG9uZXJyb3I9InRoaXMub25lcnJvcj1udWxsO3RoaXMuc3JjPSdodHRwczovL3Bsb3QubHkvNDA0LnBuZyc7IiAvPjwvYT48c2NyaXB0IGRhdGEtcGxvdGx5PSJOdW5Ob0RlQ2FidW5pYzoxIiBzaGFyZWtleS1wbG90bHk9IjBmRzhCckZRNjBxSUFzRFJFUWJMNHkiIHNyYz0iaHR0cHM6Ly9wbG90Lmx5L2VtYmVkLmpzIiBhc3luYz48L3NjcmlwdD48L2Rpdj4NCipjbGljayBmb3IgaW50ZXJhY3RpdmUgcGxvdGx5IGdyYXBoKg0KDQo8YnIvPg0KDQoqKkhvdyB0byBpbnRlcnByZXQgdGhlIHBsb3Q6KioNCg0KQ2xpY2sgb24gdGhlICdwb3NpdGlvbiBsZWdlbmQnIGluIHRoZSB1cHBlciByaWdodCBvZiB0aGUgZ3JhcGggdG8gc2VsZWN0L3Vuc2VsZWN0IGVhY2ggcG9zaXRpb24sIGRvdWJsZS1jbGljayB0byBpc29sYXRlIHRoZSB0cmFjZS4gWW91IGNhbiBjb21wYXJlIHRoZW0gaW4gYW55IHdheSBhcyB5b3UgZGVzaXJlLg0KDQpTbywgYXMgd2UgY2FuIHNlZSwgdGhlIGdyYXBoIGRvZXMgbm90IGp1c3QgY29uZmlybSB0aGUgd2VsbC1rbm93biBmYWN0IHRoYXQgdGhlIHRhbGwgZ3V5cyAoQ2VudGVycykgYXJlIHRoZSBiZXN0IHNob3QtYmxvY2tlcnMgYW5kIHRoZSBzaG9ydCBndXlzIChQb2ludCBHdWFyZHMpIGFyZSB0aGUgYmVzdCBhdCBwYXNzaW5nIHRoZSBiYWxsLCB3ZSBjYW4gYWxzbyBtZWFzdXJlIGhvdyBmYXIgYmV0dGVyIHRoZXkgcGVyZm9ybSB0aGVzZSB0YXNrLg0KDQo8YnIvPg0KDQo8L2Rpdj4NCg0KPGJyLz4NCjxici8+DQoNCiMjIyBBbm51YWwgQWdlIFJhbmdlDQoNCjxkaXYgY2xhc3M9IkJveCI+DQoNCjxici8+DQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCg0KU2FtZSBsaWtlIHdpdGggaGVpZ2h0LCBpdCB3b3VsZCBiZSBjb21wZWxsaW5nIHRvIHNlZSB0aGUgYWdlIHJhbmdlIGluIHRoZSBOQkEgeWVhci1ieS15ZWFyLg0KDQpgYGB7cn0NCkFnZU5CQSA8LSBOQkEgJT4lDQogICAgZ3JvdXBfYnkoWWVhcikgJT4lDQogICAgc3VtbWFyaXNlKEF2ZXJhZ2UgPSByb3VuZChtZWFuKEFnZSwgbmEucm0gPSBUKSwgMiksDQogICAgICAgICAgICAgIE1heCA9IG1heChBZ2UsIG5hLnJtID0gVCksDQogICAgICAgICAgICAgIE1pbiA9IG1pbihBZ2UsIG5hLnJtID0gVCkpDQoNCkFnZU5CQSAlPiUga2FibGUoYWxpZ24gPSAiYyIsIGNhcHRpb24gPSAiQWdlOiBBdmVyYWdlIGFuZCBSYW5nZSBieSBZZWFyIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCkgJT4lDQogICAgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjMwMHB4IikNCmBgYA0KPC9kaXY+DQoNCjxici8+DQoNCkFuZCBoZXJlJ3MgdGhlIHBsb3QuLi4NCg0KYGBge3IgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodD01fQ0KQWdlTkJBICU+JSBnZ3Bsb3QoYWVzKFllYXIpKSArDQogICAgZ2VvbV9saW5lKGFlcyh5ID0gTWF4LCBsaW5ldHlwZSA9ICJPbGRlc3QiKSwgY29sb3IgPSAicmVkIiwgYWxwaGEgPSAwLjUpICsNCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBBdmVyYWdlLCBsaW5ldHlwZSA9ICJBdmVyYWdlIiksIGNvbG9yID0gImJsYWNrIikgKw0KICAgIGdlb21fbGluZShhZXMoeSA9IE1pbiwgbGluZXR5cGUgPSAiWW91bmdlc3QiKSwgY29sb3IgPSAiYmx1ZSIsIGFscGhhID0gMC41KSArDQogICAgZ2d0aXRsZSgiQWdlIFJhbmdlIGJ5IFllYXIiKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxOTUwLCAyMDE3LCAxMCkpICsNCiAgICBzY2FsZV9saW5ldHlwZV9tYW51YWwobmFtZSA9ICIiLCB2YWx1ZXMgPSBjKDEsIDEsIDEpKSArDQogICAgZ3VpZGVzKGdyb3VwPUZBTFNFKSArDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KYGBgDQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIFBsYXllciBEaXN0cmlidXRpb24gYnkgR2VuZXJhdGlvbg0KDQo8ZGl2IGNsYXNzPSJCb3giPg0KDQo8YnIvPg0KDQpOQkEgaGFzIGJlZW4gYXJvdW5kIHNpbmNlIDE5NTAsIHRoaXMgc3VnZ2VzdHMgdGhhdCBtYW55IGdlbmVyYXRpb25zIGhhdmUgcGFzc2VkIHNpbmNlIHRoZSBmaXJzdCBOQkEgc2Vhc29uLiBXb3VsZCBpdCBiZSBuaWNlIHRvIHNlZSB3aGljaCBnZW5lcmF0aW9uIGRvbWluYXRlZCB0aGUgbGVhZ3VlIGVhY2ggeWVhcj8NCg0KYGBge3IgZmlnLndpZHRoID0gOSwgZmlnLmhlaWdodD01fQ0KTkJBICU+JQ0KICAgIGdyb3VwX2J5KFllYXIsIFBsYXllcikgJT4lDQogICAgZmlsdGVyKCFpcy5uYShCb3JuKSkgJT4lDQogICAgbXV0YXRlKEdlbmVyYXRpb24gPSBpZmVsc2UoQm9ybiA8PSAxOTIxLCAiVGhlIERlcHJlc3Npb24gRXJhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKEJvcm4gJWluJSAxOTIyOjE5MjcsICJXb3JsZCBXYXIgSUkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShCb3JuICVpbiUgMTkyODoxOTQ1LCAiUG9zdC1XYXIgQ29ob3J0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShCb3JuICVpbiUgMTk0NjoxOTU0LCAiQmFieSBCb29tZXJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKEJvcm4gJWluJSAxOTU1OjE5NjUsICJHZW5lcmF0aW9uIEpvbmVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoQm9ybiAlaW4lIDE5NjY6MTk3NiwgIkdlbmVyYXRpb24gWCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoQm9ybiAlaW4lIDE5Nzc6MTk5NSwgIk1pbGxlbm5pYWxzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkdlbmVyYXRpb24gWiIpKSkpKSkpKSAlPiUNCiAgICBzdW1tYXJpc2UoR2VuZXJhdGlvbiA9IGdldG1vZGUoR2VuZXJhdGlvbikpICU+JQ0KICAgIGFycmFuZ2UoWWVhciwgUGxheWVyKSAlPiUNCiAgICBtdXRhdGUoR2VuZXJhdGlvbiA9IGZhY3RvcihHZW5lcmF0aW9uLCBsZXZlbHMgPSBjKCJUaGUgRGVwcmVzc2lvbiBFcmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiV29ybGQgV2FyIElJIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBvc3QtV2FyIENvaG9ydCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJCYWJ5IEJvb21lcnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiR2VuZXJhdGlvbiBKb25lcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHZW5lcmF0aW9uIFgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTWlsbGVubmlhbHMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiR2VuZXJhdGlvbiBaIikpKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKFllYXIsIHk9Li5jb3VudC4uLCBjb2xvdXI9R2VuZXJhdGlvbiwgZmlsbD1HZW5lcmF0aW9uKSkgKw0KICAgIGdlb21fZGVuc2l0eShhbHBoYT0wLjU1KSArDQogICAgZ2d0aXRsZSgiR2VuZXJhdGlvbiBDb3VudCBieSBZZWFycyIpICsNCiAgICB5bGFiKCJDb3VudCIpICsNCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDE5NTAsIDIwMTcsIDEwKSkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0nYm90dG9tJykNCmBgYA0KDQo8ZGl2IGNsYXNzPSJmb290bm90ZSI+DQpHZW5lcmF0aW9uIGNsYXNzaWZpY2F0aW9uIGJhc2VkIG9uIFtXSlMgbWFya2V0aW5nIHJlc2VhcmNoXShodHRwOi8vc29jaWFsbWFya2V0aW5nLm9yZy9hcmNoaXZlcy9nZW5lcmF0aW9ucy14eS16LWFuZC10aGUtb3RoZXJzLykNCjwvZGl2Pg0KDQo8YnIvPg0KDQpBbmQgaGVyZSdzIHRoZSBvbGRlc3QgZ2VuZXJhdGlvbiBvZiB0aGUgTkJBLi4uDQoNCiMjIyMgRWFybGllc3QgYm9ybjoNCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7cn0NCk5CQSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIsIEJvcm4pICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvc2l0aW9uKSwNCiAgICAgICAgICAgICAgQWN0aXZlWWVhcnMgPSBwYXN0ZShnZXRtb2RlKFllYXJTdGFydCksICItIiwgZ2V0bW9kZShZZWFyRW5kKSksDQogICAgICAgICAgICAgIE5CQVNlYXNvbnMgPSBuX2Rpc3RpbmN0KFllYXIpLA0KICAgICAgICAgICAgICBHYW1lcyA9IHN1bShHKSwNCiAgICAgICAgICAgICAgU3RhcnRpbmdBZ2UgPSBtaW4oQWdlKSwNCiAgICAgICAgICAgICAgRmluYWxBZ2UgPSBtYXgoQWdlKSwNCiAgICAgICAgICAgICAgUFBHID0gcm91bmQoc3VtKFBUUykvc3VtKEcpLCAxKSkgJT4lDQogICAgYXJyYW5nZShCb3JuKSAlPiUNCiAgICBzZWxlY3QoQm9ybiwgZXZlcnl0aGluZygpKSAlPiUNCiAgICBoZWFkKDkpICU+JQ0KICAgIGthYmxlKGVzY2FwZSA9IEYsIGFsaWduID0gImMiLCBjYXB0aW9uID0gIk9sZGVzdCBOQkEgUGxheWVycyIpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBULCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzc3Nzc3NyIpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDIsIGJvbGQgPSBUKSAlPiUNCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IFQpDQpgYGANCg0KPGJyLz4NCg0KTm90aWNlIHRoYXQgaW4gdGhlIGBBY3RpdmVZZWFyc2AgY29sdW1uIHNvbWUgcGxheWVycyBzdGFydGVkIHRoZWlyIGNhcmVlciBiZWZvcmUgMTk1MCBhbmQgdGhlIG51bWJlciBvZiBzZWFzb25zIGluIHRoZSBgTkJBU2Vhc29uc2AgY29sdW1uIHN1bW1lZCB1cCBhZnRlciAxOTQ5LiBUaGlzIGJlY2F1c2UgdGhlIE5CQSBmb3JtYWxseSBmb3JtZWQgaW4gMTk0OSBieSB0aGUgbWVyZ2VyIG9mIHR3byByaXZhbCBvcmdhbml6YXRpb25zLCB0aGUgKk5hdGlvbmFsIEJhc2tldGJhbGwgTGVhZ3VlKiAoZm91bmRlZCAxOTM3KSBhbmQgdGhlICpCYXNrZXRiYWxsIEFzc29jaWF0aW9uIG9mIEFtZXJpY2EqIChmb3VuZGVkIDE5NDYpLiBZb3UgY2FuIGRpZyBtb3JlIGludG8gTkJBIGhpc3RvcnkgW2hlcmVdKGh0dHBzOi8vd3d3LmJyaXRhbm5pY2EuY29tL3RvcGljL05hdGlvbmFsLUJhc2tldGJhbGwtQXNzb2NpYXRpb24pLg0KDQo8L2Rpdj4NCg0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQojIyMgQ2FyZWVyIExlbmd0aCBpbiB0aGUgTkJBDQoNCjxici8+DQoNCjxkaXYgY2xhc3M9J0JveCc+DQoNCk15IG5leHQgcXVlc3Rpb24gaXMgaG93IGxvbmcgZG8gTkJBIGNhcmVlcnMgbGFzdD8NCg0KVGhpcyBjYW4gYmUgYW5zd2VyZWQgYnkgc3VtbWluZyB1cCB0aGUgbnVtYmVyIG9mIHNlYXNvbnMgZWFjaCBwbGF5ZXIgcGFydGljaXBhdGVkIGluLg0KDQpgYGB7ciBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNSwgd2FybmluZz1GQUxTRX0NClNlYXNvbk5CQSA8LSBOQkEgJT4lDQogICAgZ3JvdXBfYnkoUGxheWVyLCBCb3JuKSAlPiUNCiAgICBzdW1tYXJpc2UoTkJBU2Vhc29ucyA9IG5fZGlzdGluY3QoWWVhcikpDQoNClNlYXNvbk5CQSAlPiUNCiAgICBnZ3Bsb3QoYWVzKE5CQVNlYXNvbnMpKSArDQogICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSAuLmNvdW50Li4pKSArDQogICAgZ2d0aXRsZSgiUGxheWVyIERpc3RyaWJ1dGlvbiBieSBOdW1iZXIgb2YgU2Vhc29ucyIpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbihOQkFTZWFzb25zLCBuYS5ybSA9IFQpLCBsaW5ldHlwZSA9ICJBdmVyYWdlIGxpbmUiKSwNCiAgICAgICAgICAgICAgIGNvbCA9ICJibGFjayIsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKw0KICAgIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gImdyZWVuIiwgaGlnaCA9ICJyZWQiKSArDQogICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKG5hbWUgPSAiIiwgdmFsdWVzID0gMikgKw0KICAgIHhsYWIoIk51bWJlciBvZiBOQkEgU2Vhc29ucyIpICsNCiAgICB5bGFiKCJDb3VudCIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0iRmFjdCI+DQoNCjx1bCBjbGFzcz0iQ3VzdG9tTGlzdCI+DQoNCjxsaT5gciBucm93KFNlYXNvbk5CQVtTZWFzb25OQkEkTkJBU2Vhc29ucyA8PSAxLF0pYCBwbGF5ZXJzLCBvciBtb3JlIHRoYW4gYSBxdWFydGVyIChgciByb3VuZCgobnJvdyhTZWFzb25OQkFbU2Vhc29uTkJBJE5CQVNlYXNvbnMgPD0gMSxdKS9ucm93KFNlYXNvbk5CQSkpKjEwMCwgMSlgJSkgb2YgYWxsIHBsYXllcnMgaW4gdGhlIE5CQSBoaXN0b3J5IGRpZG4ndCBzdXJ2aXZlIGFmdGVyIHRoZWlyIGZpcnN0IHNlYXNvbi48L2xpPg0KPGxpPkF2ZXJhZ2UgY2FyZWVyIGxlbmd0aCBpbiB0aGUgTkJBIGlzIGByIHJvdW5kKG1lYW4oU2Vhc29uTkJBJE5CQVNlYXNvbnMsIG5hLnJtPVQpLDEpYCB5ZWFycy48L2xpPg0KPGxpPkxlc3MgdGhhbiBoYWxmIG9mIGFsbCBOQkEgcGxheWVycyAoYHIgcm91bmQoKG5yb3coU2Vhc29uTkJBW1NlYXNvbk5CQSROQkFTZWFzb25zID49IDUsXSkvbnJvdyhTZWFzb25OQkEpKSoxMDAsIDEpYCUpIHBhcnRpY2lwYXRlIGluIGF0IDUgc2Vhc29ucyBvciBtb3JlLjwvbGk+DQo8bGk+QW5kIG9ubHkgMSBvdXQgb2YgNSBwbGF5ZXJzIChgciByb3VuZCgobnJvdyhTZWFzb25OQkFbU2Vhc29uTkJBJE5CQVNlYXNvbnMgPj0gMTAsXSkvbnJvdyhTZWFzb25OQkEpKSoxMDAsIDEpYCUpLCBjYW4gc3Vydml2ZSBmb3IgMTAgc2Vhc29ucyBvciBtb3JlLjwvbGk+DQoNCjwvdWw+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KDQpOb3cgbGV0J3MgZmluZCBvdXQgd2hvIGhhcyBiZWVuIGluIHRoZSBOQkEgdGhlIGxvbmdlc3QuLi4NCg0KPGJyLz4NCg0KPGRpdiBjbGFzcz0idGFibGUiPg0KDQpgYGB7cn0NCk5CQSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIsIEJvcm4pICU+JQ0KICAgIHN1bW1hcmlzZShQb3MgPSBnZXRtb2RlKFBvc2l0aW9uKSwNCiAgICAgICAgICAgICAgVGVhbSA9IGdldG1vZGUoVG0pLA0KICAgICAgICAgICAgICBBY3RpdmVZZWFycyA9IHBhc3RlKG1pbihZZWFyKSwgIi0iLCBtYXgoWWVhcikpLA0KICAgICAgICAgICAgICBSb29raWVBZ2UgPSBtaW4oQWdlKSwNCiAgICAgICAgICAgICAgUmV0aXJlbWVudEFnZSA9IG1heChBZ2UpLA0KICAgICAgICAgICAgICBTZWFzb25zID0gbl9kaXN0aW5jdChZZWFyKSwNCiAgICAgICAgICAgICAgUnBHID0gcm91bmQoc3VtKFRSQikvc3VtKEcpLCAxKSwNCiAgICAgICAgICAgICAgUHBHID0gcm91bmQoc3VtKFBUUykvc3VtKEcpLCAxKSkgJT4lDQogICAgYXJyYW5nZShkZXNjKFNlYXNvbnMpLCBBY3RpdmVZZWFycykgJT4lDQogICAgc2VsZWN0KFNlYXNvbnMsIGV2ZXJ5dGhpbmcoKSwgLUJvcm4pICU+JQ0KICAgIGhlYWQoMTcpICU+JQ0KICAgIGthYmxlKGVzY2FwZSA9IEYsIGFsaWduID0gImMiLCBjYXB0aW9uID0gIkxvbmdlc3QgQ2FyZWVyIGluIHRoZSBOQkEiKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCwgY29sb3IgPSAid2hpdGUiLCBiYWNrZ3JvdW5kID0gIiM3Nzc3NzciKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCBib2xkID0gVCkgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKQ0KYGBgDQoNClRoZSB0YWJsZSBhYm92ZSBsaXN0ZWQgdGhlIHBsYXllcnMgd2hvIGhhdmUgYSBjYXJlZXIgaW4gdGhlIE5CQSBmb3IgMTkgeWVhcnMgb3IgbW9yZS4NCg0KPGRpdiBjbGFzcz0iRmFjdCI+DQoNCjx1bCBjbGFzcz0iQ3VzdG9tTGlzdCI+DQoNCkZldyB0aGluZ3MgSSBub3RpY2VkIGluIHRoaXMgZ3JvdXAgYXJlOg0KDQo8bGk+TW9zdCBvZiB0aGVtIGFyZSB3ZWxsLWtub3duIGdyZWF0IHBsYXllcnMsIGByIHJvdW5kKDE2LzE3ICogMTAwLCAxKWAgJSBvZiB0aGVtIGhhdmUgZG91YmxlIGRpZ2l0cyBvdmVyYWxsIHBvaW50cyBwZXIgZ2FtZSwgd2hpY2ggb25seSBhYm91dCAxIGluIDUgTkJBIHBsYXllcnMgY2FuIGFjY29tcGxpc2ggdGhpcyBmZWF0LjwvbGk+DQo8bGk+V2hhdCBpbnRlcmVzdGluZyBpcyB0aGUgQ2VudGVycyBhcmUgUG93ZXIgRm9yd2FyZHMgKHRyYW5zbGF0ZWQ6IHRhbGwgZ3V5cykgZG9taW5hdGVkIHRoZSBncm91cCB3aXRoIGByIHJvdW5kKDYvMTcgKiAxMDAsIDEpYCAlIGVhY2gsIHdoaWNoIG1ha2VzIHVwIGByIHJvdW5kKDEyLzE3ICogMTAwLCAxKWAgJSBpbiB0b3RhbC48L2xpPg0KDQo8L3VsPg0KDQo8L2Rpdj4NCg0KPC9kaXY+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIFRoZSBSb29raWVzIHZzIHRoZSBSZXRpcmVlcw0KDQo8ZGl2IGNsYXNzPSdCb3gnPg0KDQo8YnIvPg0KDQpOb3cgd2UgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSBhZ2Ugd2hlbiB0aGV5IHN0YXJ0ZWQgYW5kIGZpbmlzaGVkIHRoZWlyIGNhcmVlciBpbiB0aGUgTkJBLg0KDQpgYGB7ciBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01fQ0KUlJBZ2UgPC0gTkJBICU+JQ0KICAgIGdyb3VwX2J5KFBsYXllcikgJT4lDQogICAgZmlsdGVyKCFpcy5uYShBZ2UpKSAlPiUNCiAgICBzdW1tYXJpc2UoUm9va2llQWdlID0gbWluKEFnZSksDQogICAgICAgICAgICAgIFJldGlyZW1lbnRBZ2UgPSBtYXgoQWdlKSkgJT4lDQogICAgZ2F0aGVyKFBhcmFtZXRlciwgVmFsdWUsIFJvb2tpZUFnZTpSZXRpcmVtZW50QWdlKQ0KDQpSUkFnZSAlPiUNCiAgICBnZ3Bsb3QoYWVzKHg9YXMuZmFjdG9yKFZhbHVlKSxmaWxsPVBhcmFtZXRlcikpICsgDQogICAgZ2VvbV9iYXIoZGF0YT1maWx0ZXIoUlJBZ2UsIFBhcmFtZXRlciA9PSAiUmV0aXJlbWVudEFnZSIpKSArIA0KICAgIGdlb21fYmFyKGRhdGE9ZmlsdGVyKFJSQWdlLCBQYXJhbWV0ZXIgPT0gIlJvb2tpZUFnZSIpLCBhZXMoeSA9IC4uY291bnQuLiAqICgtMSkpKSArDQogICAgZ2d0aXRsZSgiUm9va2llIEFnZSB2cywgUmV0aXJlbWVudCBBZ2UgaW4gdGhlIE5CQSIpICsNCiAgICB4bGFiKCJBZ2UiKSArDQogICAgeWxhYigiQ291bnQiKSArDQogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1zZXEoLTE1MDAsMTUwMCw1MDApLGxhYmVscz1hYnMoc2VxKC0xNTAwLDE1MDAsNTAwKSkpICsNCiAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDEiKSArIA0KICAgIGNvb3JkX2ZsaXAoKSArDQogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KDQpSUkFnZSA8LSBSUkFnZSAlPiUNCiAgICBzcHJlYWQoUGFyYW1ldGVyLCBWYWx1ZSkNCmBgYA0KDQo8YnIvPg0KDQo8ZGl2IGNsYXNzPSJGYWN0Ij4NCg0KPHVsIGNsYXNzPSJDdXN0b21MaXN0Ij4NCg0KPGxpPk1vc3Qgb2YgdGhlIHBsYXllcnMgKGByIHJvdW5kKChucm93KFJSQWdlW1JSQWdlJFJvb2tpZUFnZSA+IDIxICYgUlJBZ2UkUm9va2llQWdlIDwgMjUsXSkvbnJvdyhSUkFnZSkpKjEwMCwgMSlgJSkgc3RhcnQgdGhlaXIgY2FyZWVyIGluIE5CQSBiZXR3ZWVuIGFnZSAyMi0yNC48L2xpPg0KPGxpPmByIHJvdW5kKChucm93KFJSQWdlW1JSQWdlJFJvb2tpZUFnZSA8IDIwLF0pL25yb3coUlJBZ2UpKSoxMDAsIDEpYCUgb2YgTkJBIHBsYXllcnMgc3RhcnRpbmcgdGhlaXIgY2FyZWVyIGJlZm9yZSB0aGVpciAyMCB5ZWFycyBvbGQgYmlydGhkYXkuPC9saT4NCjxsaT5Pbmx5IGByIG5yb3coUlJBZ2VbUlJBZ2UkUm9va2llQWdlID49IDMwLF0pYCBwbGF5ZXJzIChgciByb3VuZCgobnJvdyhSUkFnZVtSUkFnZSRSb29raWVBZ2UgPj0gMzAsXSkvbnJvdyhSUkFnZSkpKjEwMCwgMSlgJSkgc3RhcnRpbmcgdGhlaXIgY2FyZWVyIGluIHRoZWlyIDMwJ3MuPC9saT4NCg0KPC91bD4NCg0KPC9kaXY+DQoNCjxici8+DQoNCjxkaXYgY2xhc3M9InRhYmxlIj4NCg0KYGBge3J9DQpOQkEgJT4lDQogICAgZmlsdGVyKFllYXJTdGFydCA+PSAxOTUwKSAlPiUNCiAgICBncm91cF9ieShQbGF5ZXIpICU+JQ0KICAgIHN1bW1hcmlzZShSb29raWVBZ2UgPSBtaW4oQWdlKSwNCiAgICAgICAgICAgICAgUG9zID0gZ2V0bW9kZShQb3NpdGlvbiksDQogICAgICAgICAgICAgIFRlYW0gPSBnZXRtb2RlKFRtKSwNCiAgICAgICAgICAgICAgUmV0aXJlbWVudEFnZSA9IG1heChBZ2UpLA0KICAgICAgICAgICAgICBOQkFZZWFycyA9IHBhc3RlKG1pbihZZWFyKSwgIi0iLCBtYXgoWWVhcikpLA0KICAgICAgICAgICAgICBOQkFTZWFzb25zID0gbl9kaXN0aW5jdChZZWFyKSwNCiAgICAgICAgICAgICAgR2FtZXMgPSBzdW0oRyksDQogICAgICAgICAgICAgIFBwRyA9IHJvdW5kKHN1bShQVFMpL0dhbWVzLDIpKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoUm9va2llQWdlKSkgJT4lDQogICAgc2VsZWN0KFJvb2tpZUFnZSwgZXZlcnl0aGluZygpKSAlPiUNCiAgICBoZWFkKDEwKSAlPiUNCiAgICBrYWJsZShlc2NhcGUgPSBGLCBhbGlnbiA9ICJjIiwgY2FwdGlvbiA9ICJPbGRlc3QgTkJBIFJvb2tpZXMiKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCBib2xkID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQsIGNvbG9yID0gIndoaXRlIiwgYmFja2dyb3VuZCA9ICIjNzc3Nzc3IikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBUKQ0KYGBgDQoNCkJlYXIgaW4gbWluZCB0aGF0IHRoZXNlIGFyZSByb29raWVzIGFzIGluIHRoZSBOQkEuIFNvbWUgb2YgdGhlbSBtaWdodCBhbHJlYWR5IGhhdmUgeWVhcnMgb2YgZXhwZXJpZW5jZSBpbiBhbm90aGVyIHByb2Zlc3Npb25hbCBiYXNrZXRiYWxsIGxlYWd1ZS4NCg0KPGJyLz4NCg0KYGBge3J9DQpOQkEgJT4lDQogICAgc2VsZWN0KEFnZSwgUGxheWVyLCBQb3NpdGlvbiwgWWVhciwgVG0sIEcsIEdTLCBQVFMpICU+JQ0KICAgIG11dGF0ZShQUEcgPSByb3VuZChQVFMvRywgMikpICU+JQ0KICAgIGFycmFuZ2UoZGVzYyhBZ2UpKSAlPiUNCiAgICBoZWFkKG49MTApICU+JQ0KICAgIGthYmxlKGVzY2FwZSA9IEYsIGFsaWduID0gJ2MnLCBjYXB0aW9uID0gIk9sZGVzdCBOQkEgUGxheWVycyIpICU+JQ0KICAgIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gVCkgJT4lDQogICAgY29sdW1uX3NwZWMoMiwgYm9sZCA9IFQpICU+JQ0KICAgIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBULCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzc3Nzc3NyIpDQpgYGANCg0KPC9kaXY+DQoNCjwvZGl2Pg0KDQo8YnIvPg0KPGJyLz4NCg0KIyMjIEFnZSB2cyBDaGFuY2UgYW5kIEFiaWxpdHkNCg0KPGRpdiBjbGFzcz0iQm94Ij4NCg0KPGJyLz4NCg0KTGFzdGx5LCBJJ2QgbGlrZSB0byBrbm93IGhvdyBhZ2UgY29ycmVsYXRlcyB3aXRoIHRoZWlyIGNoYW5jZXMgYW5kIGFiaWxpdHkgaW4gdGhlIGZpZWxkLg0KDQpgYGB7ciBmaWcud2lkdGggPSA5fQ0KQWdlQ29yIDwtIE5CQV9TY2FsZWQgJT4lDQogICAgZ3JvdXBfYnkoQWdlKSAlPiUNCiAgICBzdW1tYXJpc2UoR2FtZXMgPSBtZWFuKEcsIG5hLnJtID0gVCksDQogICAgICAgICAgICAgIEdhbWVTdGFydGVkID0gbWVhbihHUywgbmEucm0gPSBUKSwNCiAgICAgICAgICAgICAgTWludXRlc1BsYXllZCA9IG1lYW4oTVAsIG5hLnJtID0gVCksDQogICAgICAgICAgICAgIFNob290aW5nID0gbWVhbihUUy4sIG5hLnJtID0gVCksDQogICAgICAgICAgICAgIFNob290QXR0ZW1wcyA9IG1lYW4oRkdBLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBSZWJvdW5kID0gbWVhbihScEcsIG5hLnJtID0gVCksDQogICAgICAgICAgICAgIEFzc2lzdCA9IG1lYW4oQXBHLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBTdGVhbCA9IG1lYW4oU3BHLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBCbG9jayA9IG1lYW4oQnBHLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBUdXJub3ZlciA9IG1lYW4oVHBHLCBuYS5ybSA9IFQpLA0KICAgICAgICAgICAgICBQb2ludHMgPSBtZWFuKFBwRywgbmEucm0gPSBUKSkgJT4lDQogICAgZ2F0aGVyKHZhcmlhYmxlLCB2YWx1ZSwgLUFnZSkgJT4lDQogICAgZmlsdGVyKCFpcy5uYShBZ2UpKSAlPiUNCiAgICBtdXRhdGUodmFyaWFibGUgPSBmYWN0b3IodmFyaWFibGUsIGxldmVscyA9IGMoIkdhbWVzIiwgIkdhbWVTdGFydGVkIiwgIk1pbnV0ZXNQbGF5ZWQiLCAiU2hvb3RpbmciLCAiU2hvb3RBdHRlbXBzIiwgIlJlYm91bmQiLCAiQXNzaXN0IiwgIlN0ZWFsIiwgIkJsb2NrIiwgIlR1cm5vdmVyIiwgIlBvaW50cyIpKSkNCg0KQWdlQ29yICU+JQ0KICAgIGdncGxvdChhZXMoQWdlLCB2YXJpYWJsZSwgZmlsbD12YWx1ZSkpICsNCiAgICBnZW9tX3RpbGUoY29sb3IgPSAiZ3JleTUwIikgKw0KICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArDQogICAgc2NhbGVfZmlsbF9ncmFkaWVudG4oY29sb3JzID0gYnJld2VyLnBhbCg5LCAiUmVkcyIpKSArDQogICAgdGhlbWUocGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSkgKw0KICAgIHNjYWxlX3lfZGlzY3JldGUobGltaXRzID0gcmV2KGxldmVscyhBZ2VDb3IkdmFyaWFibGUpKSkgKw0KICAgIGdndGl0bGUoIkFnZSwgQ2hhbmNlIGFuZCBBYmlsaXR5IikgKw0KICAgIHlsYWIoIlBhcmFtZXRlciIpICsNCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIpDQpgYGANCg0KPGJyLz4NCg0KSXQgdHVybnMgb3V0IHRoYXQgdGhlaXIgcGVhayBhcm91bmQgMjUtMzAgeWVhcnMgb2xkLCB3aXRoIHNvbWUgZXhjZXB0aW9uIGluIHNob290aW5nIChUUyUpIGFuZCBibG9ja3MuIFRoYXQgaXMgYmVjYXVzZSwgYXMgd2UgaGF2ZSBzZWVuIGluIHRoZSBwcmV2aW91cyB0YWJsZSwgb25seSBhIGhhbmRmdWwgb2YgcGxheWVycyBzdGlsbCBhY3RpdmUgYWZ0ZXIgdGhlaXIgNDAgYW5kIDkwJSBvZiB0aGVtIGFyZSBwb3N0IHBsYXllcnMgd2hvIGFyZSBzaG90LWJsb2NrZXJzIHdpdGggYSBnb29kIEZHJS4gDQoNCjwvZGl2Pg0KIA0KPC9kaXY+DQoNCjxici8+DQo8YnIvPg0KDQotLS0NCg0KPGRpdiBjbGFzcz0icm93Ij4NCg0KIDxkaXYgY2xhc3M9ImNvbHVtbiBsZWZ0Ij4NCiA8YSBocmVmPSJodHRwczovL3JwdWJzLmNvbS9uaW5qYXp6bGUvTkJBU2Vhc29uczAiIHRhcmdldD0iX2JsYW5rIj4NCjxidXR0b24gY2xhc3M9ImxlZnRidG4iPjwvYnV0dG9uPg0KIDwvYT4NCiA8L2Rpdj4NCiANCiA8ZGl2IGNsYXNzPSJjb2x1bW4gbWlkZGxlIj4NCg0KRW5kIG9mIFNlc3Npb24NCg0KICA8L2Rpdj4NCiAgDQogIDxkaXYgY2xhc3M9ImNvbHVtbiByaWdodCI+DQogIDxhIGhyZWY9Imh0dHBzOi8vcnB1YnMuY29tL25pbmphenpsZS9OQkFTZWFzb25zMiIgdGFyZ2V0PSJfYmxhbmsiPg0KPGJ1dHRvbiBjbGFzcz0icmlnaHRidG4iPjwvYnV0dG9uPg0KICA8L2E+DQogIDwvZGl2Pg0KICANCjwvZGl2Pg0KDQotLS0=