I’m a sucker for a tactical blog post, and I’m a sucker for solid data visualization advice. As such, I was double-suckered by Lea Pica’s recent post on the Search Discovery blog: How to Build a Brain-Friendly Bar Chart in Domo. The end result of her chart makeover was this:
For giggles, I thought it would be illuminating to start in the exact same spot – with the default – and then build to the exact same result… but using R. Our stakeholders never care what tool was used to build a visualization, so, in my mind, any visualization platform worth its salt can build exactly what I want it to. And, WWLD (What Would Lea Do) is a pretty solid best practice.
I didn’t nail it, but I came pretty close (and we’ll get to some rationalization as to the lingering differences at the end of this exercise):

With luck, this will illustrate both “the basics” of visualization with ggplot2, as well as some of the hoops that have to be jumped through – and where those hoops are located – to tweak the visuals. I’ll try to also call out the things that are very much one-time operations, as this will undoubtedly seem pretty brutal if every chart required every one of these steps.
The Data
We’ll start with the raw data (I’ll need this loaded into the file I’m building this with, anyway, so might as well show it to you!). I’ve created a simple data frame called channel_data with the following values:
| Social Media |
67 |
| Display |
180 |
| Email |
648 |
| Organic Search |
937 |
| Paid Search |
1305 |
Already, we can spot two things that may require tweaking in our final plot:
- The “.” in the column heading; we’re working with code, and we don’t really want spaces floating around until the very last minute (the underlying data actually does have a space in it, but that gets converted to “.” when outputting here). We’re not going to worry about that (for now; we’ll get there!).
- There are no comma-delimiters in the numbers! Again, that’s because this data is “just the data” – there is no formatting. I certainly could force the output above to have commas, but, like spaces, those are something we’ll worry about later – even if I showed these with commas in the table, I’d still have to do the same thing in my final chart.
As it turns out (see Lea’s final chart) we won’t actually need to get the “.” removed. But we will have to deal with adding comma delimiters!
The Default Chart
Lea started with the default Domo bar chart:
That’s where we’ll start with R, too. Below is the bare-bones ggplot2 code and the resulting output:
# Normally, the entire expression below would be assigned to an object, but we're
# going bare bones here.
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip()

EEK!
For whatever reason, this is the “default.” Almost no one will want this to be the final product (obviously). This is actually because this basic treatment is the default for all possible visualizations with ggplot(), and there are some cases where things like horizontal and vertical grids would make sense. But, I’m not going to defend it.
And, while this post is not meant to be a full-blown tutorial on building data visualizations with ggplot2, it seemed worthwile to at least provide a quick breakdown of this initial plot:
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales))
This is always the first call, as it is the call to the ggplot() function. Everthing else after that is just “adding” to that base call. The two arguments in this function:
data = channel_data – this is really just the overall data set to be used; it could have a bunch of other stuff that never gets plotted, but it’s the set or superset of data to be visualized
mapping = aes(x = Marketing.Channel, y = Sales) – this is one of the most confusing aspects of ggplot2. It’s the mapping of which values in the data to use for what aspects of the plot…by default. The aes() is for “aesthetic,” which is a horribly misleading descriptor of what this is. I’m not going to go into any more detail here, other than to say that, if you’re wondering why the y is set to Sales, when that’s actually the value on the x axis, then read on – it should become clear when you get to coord_flip() in a couple of paragraphs. Mainly, just know that these “mappings” can be used multiple times in a single visualization. We’ll start to see how this works as we get farther along.
geom_bar(stat = "identity")
The ggplot() function doesn’t actually say how to visualize anything. We have to add one or more “geoms” in order to do that. We’ll be adding a few more by the time we’re done. In this case, we’re doing a bar chart (we use the same geom_bar() regardless of whether it’s horizontal or vertical). And, since the data is already aggregated (as opposed to having a data set where each row is an order with an associated channel and sales amount), we have to use the stat = "identity" argument. That’s confusing, too… although it becomes second nature pretty quickly.
coord_flip()
Did you catch how I said we’d use geom_bar() for both horizontal and vertical bar charts? Well, the default is that it’s a vertical bar chart, so we have to add an instruction to “flip the coordinates” to make it horizontal. And, that’s why our “aesthetic” seemed to be backwards – specifying Sales for y and Marketing.Channel for x, even though they’re the opposite of that in the actual chart.
Domo starts out wayyyyyy ahead of this default, no doubt!
Enter…Themes!
The first thing we want to do is get the ickiness of the gray background and gridlines and tickmarks and such stripped away. We’ll need to do more than that, but that will do a lot of the cleanup for us. Themes are like CSS in HTML – not only do they provide a ton of control over the look and feel of the visualization, but they actually “cascade” in a way: if you set one attribute of a theme in one place, you can easily override it later as the visualization is being built.
There are some default themes that are part of the ggplot2 package. So, let’s start by looking at our plot using theme_light(). All we have to do is “add” the theme to the plot that we’re building up.
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip() +
theme_light() # Add a theme

Hmmmm. We still have gridlines and ticks and a border and axis labels. So, we actually need to “add” some overrides to that theme. This is where things start to look a bit cumbersome, but we’ll cover the reusability of these shortly.
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip() +
theme_light() +
# Tweak the theme a bit with additional theme() settings
theme(panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank())

It’s getting better! Hopefully, most of those theme() arguments are fairly self-explanatory. In most cases, we were removing both the x and y aspects of the plot, but, for the axis text, we only wanted to remove the x-axis text, so we specified a .x for axis.text.
But, one other change we want to do with the theme: let’s change the font (for all theme-controlled elements) and adjust the text size and color for the y-axis text. We’ll define the font family as a text argument (so it will apply to all text-based elements in the theme, unless overridden), and then specify the size and color for axis.text.y:
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip() +
theme_light() +
theme(text = element_text(family = "Open Sans"), # DOMO APPEARS TO USE 'OPEN SANS'
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10")) # SET THE AXIS TEXT SIZE AND COLOR

Voila! We’re getting closer. Now, before we reveal that we’re going to double the length of this code to actually get to our final result, it’s worth a quick little diversion on themes.
First off, just like saving a chart style in Excel (but more powerful, actually), you can define an entire theme as a centralized function – in the code, in a separate file that gets referenced, or even in a package you build that you distribute to others in your organization. I’m going to just set this theme as its own function right above my plot for now. The output will be identical, but, hopefully, you’ll see how you can then reuse that same theme time and again. And, we’re going to have some other fun with it, too.
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip() +
my_theme()

So, now, imagine that you were doing multiple plots. Each one of them could simply include a call to my_theme() – even if they are not horizontal bar charts! – and get the same styling. And, if you tweak the definition in just that one function and rebuild your charts, the change will be reflected in all of them (remember how I said themes are like CSS?)!
And (and this is handy), remember that we can still override specific aspects of the chart. If, for instance, I wanted just one instance of the chart to have bold, red, larger text for the axis text (which would be silly), I could keep my function the same, but then add that override:
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip() +
my_theme() +
theme(axis.text.y = element_text(size = 13, color = "red", face = "bold")) # THE 'OVERRIDE' LINE

That’s yucky, so we won’t keep that, but, hopefully, you get the idea. I’m also using base colors here, but I could also use RGB hex colors or defined palettes.
If you happen to be a Stephen Few, Edward Tufte, or FiveThirtyEight.com junky, you can actually install the ggthemes package and get predefined themes – put to use just like I used my_theme() here – to match their specific styles. That is cool!
I’m digressing. It’s what I do.
Let’s get back on track to the chart improvements we needed to make.
Get the Text Closer to the Axis
The text is so far from the axis not because it’s the default spacing, but, rather, by default, the x-axis and y-axis don’t cross at zero. So, we have do a little goofiness to make that happen (I don’t have to keep repeating the theme definition, but I want each block to show the “full set of code needed”):
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity") +
coord_flip() +
scale_y_continuous(expand = c(0, 0)) + # Set the axes to cross at 0
my_theme()

How about lightening up that gray (we could also turn it to a neutral blue, but Lea proposed gray with blue to highlight, so that’s what we’re going to go with)? This isn’t something we do in the theme because we could have multiple geoms in play (and we will be adding some!), and the theme is universally applied. So, rather, we need to define that at the geom level. We would define that inside the aesthetic if we actually had multiple data series and we wanted each series to be a different color. That’s actually one way to do something we’re going to do later (but it’s not how we’re going to do it). But, for now, we can just do that as its own argument in geom_bar(). While we’re at it, let’s increase the space between the bars a bit.
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity",
fill = "gray90", # ADD THE FILL COLOR
width = 0.75) + # ADJUST THE BAR WIDTH
coord_flip() +
scale_y_continuous(expand = c(0, 0)) +
my_theme()

Neat, huh?
Add Data Labels
Since we removed our x-axis, we have no sense of scale! We need to add labels onto the chart. This is where ggplot really diverges from Excel or Domo or Tableau. We don’t just say “show labels.” Rather, we use a different geom that we “add” to the plot. We use geom_text(), which requires three mapping (“aesthetic”) values:
- The x value – this matches what we used for
geom_bar() and what we’d set as the default in the ggplot() function!
- The y value (where to place the text) – OMG! This matches the default, too!
- The actual text to display – Uh-oh. We haven’t defined that yet. But…we just want to display the value of y.
Let’s do the basics and then work on the fine-tuning.
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(channel_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = Sales)) + # x AND y INHERITED. WE JUST NEED TO SPECIFY "label"
coord_flip() +
scale_y_continuous(expand = c(0, 0)) +
my_theme()

Oh, boy. That’s the basic label, but, clearly, it has some problems:
- The largest value is truncated
- The values are centered on the end
- There’s no comma in the (truncated) value
- There’s no percentage
- The font isn’t right
These are all solvable! But, this initial pass was a good place to start, because, if you understand how we got to this, then you’ve got a great grasp on how ggplot() and geoms and aes() mappings work!
Where to begin? Ultimately, we want our labels to display cleanly, so let’s start by getting the complete labels. For this, we’re actually going to go back to our data set and add a column with labels that are both the properly formatted number and have the percentage. This will be a minor digression where I’m not actually going to re-draw the plot.
What we want to do is calculate the “percent of total” for each row in our data frame, and then combine that with the actual value. This gets into dplyr-land, so I’m not going to describe too much of what is going on exactly. At this point, we’re starting to add to our data, which I could do to the original channel_data data frame. But, it’s a small data frame, so I’m going to copy it to a new one that I start munging for the final tweaks to the plot. Lea’s chart goes to two decimal places (except for the 41.6%, which is 41.60%, so the zero gets dropped). I actually think this sort of chart is probably best served with no decimal places. But, since the exercise is to replicate… I’m going to split the difference and just go with a single decimal place (that’s what the "1.f%%" below does):
final_data <- channel_data %>%
# Calculate -- and format -- the percentage for each channel
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
# Combine that percentage with the actual value
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
# Remove the "just the percentage" column
select(-bar_percentage)
# Display the table
kable(final_data, align=c('l','r','r'))
| Social Media |
67 |
67, 2.1% |
| Display |
180 |
180, 5.7% |
| Email |
648 |
648, 20.7% |
| Organic Search |
937 |
937, 29.9% |
| Paid Search |
1305 |
1,305, 41.6% |
Got it?
Now, thinking back to our geom_text() setup, we realize why we needed to have a y (position…which is really the x position in the final plot thanks to coord_flip()) that is different from label. We’re still going to have layout issues, but let’s make a subtle tweak to at least see more robust labels appearing. And, heck, why not go ahead and switch those to use Open Sans at the same time?
# Add in the supplemental data that we need
final_data <- channel_data %>%
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
select(-bar_percentage)
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(final_data, aes(x = Marketing.Channel, y = Sales)) + # Switched from `channel_data` to `final_data`
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = bar_label), # Changed this from `Sales` to `bar_label`
family = "Open Sans", # Specified a font
size = 3.5) + # Specified a font size
coord_flip() +
scale_y_continuous(expand = c(0, 0)) +
my_theme()

We’re getting there!
But, of course, we want the labels outside the end of the bar. By default the text is centered. We want to change it to be left-justified, which we can do by adding hjust = 0 as an argument. hjust and vjust can be confusing, and, in different situations they are / are not recommended. In its simplest application (like here) hjust gets set between 0 and 1, where 0 is left-justified and 1 is right-justified. But, you’re not constrained to setting a value between 0 and 1, which can deliver surprising (but sometimes desired) results. You can also “nudge” text, which provides a lot of control over the layout. but, we don’t need to do that here.
# Add in the supplemental data that we need
final_data <- channel_data %>%
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
select(-bar_percentage)
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(final_data, aes(x = Marketing.Channel, y = Sales)) + # Switched from `channel_data` to `final_data`
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = bar_label),
family = "Open Sans",
size = 3.5,
hjust = 0) + # Change the "horizontal justification" to be left-justified
coord_flip() +
scale_y_continuous(expand = c(0, 0)) +
my_theme()

Now, we’ve got that pesky clipping to deal with. Frankly, ggplot2 starts to get annoying at this point. There are various ways to address this. One option is to use hjust = "inward" instead of hjust = 0, which would put the labels for the longer bars on the inside of the bars. But, if we want them all to be on the outside, there are various clunky approaches. The one used below is to just make the (hidden) x-axis 30% longer than the maximum value to “make room” for the label.
# Add in the supplemental data that we need
final_data <- channel_data %>%
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
select(-bar_percentage)
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(final_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = bar_label),
family = "Open Sans",
size = 3.5,
hjust = 0) +
coord_flip() +
scale_y_continuous(expand = c(0, 0),
limits = c(0, max(final_data$Sales) * 1.3)) + # Make the x-axis 30% longer than the max
my_theme()

The spacing of the Paid Search label is kind of cramped, isn’t it? It’s easy to head down into a rabbit hole trying to figure out how to address this (without whacking the other labels). That can definitely happen with R data visualizations – there is enormous control… except when there is something that seems like it should be simple, and it’s not. I’m going to say we can live with this spacing glitch and move on.
Add Some Color
What’s next? Let’s go ahead and finish the chart area by highlighting the Paid Search bar. Or, let’s say we want to highlight the largest bar (both would be done the same way with the same bit of code tweaking). Just as with adding labels, this is where the “grammar of graphics” (which ggplot2 is built on) really diverges from other platforms conceptually. To highlight one bar, we actually want to put another geom_bar() on top of the initial one. This new plot will have the actual value of the largest bar, and will then be 0 (or could be NA) for the other channels. That means we need to add a new column to our data:
final_data <- channel_data %>%
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
select(-bar_percentage) %>%
mutate(highlight_bar = ifelse(Sales == max(.$Sales), Sales, 0)) # Add a new column
# Quick look at the updated data
kable(final_data, align = c("l", "r", "r", "r"))
| Social Media |
67 |
67, 2.1% |
0 |
| Display |
180 |
180, 5.7% |
0 |
| Email |
648 |
648, 20.7% |
0 |
| Organic Search |
937 |
937, 29.9% |
0 |
| Paid Search |
1305 |
1,305, 41.6% |
1305 |
# And now back to our usual plotting, but with an extra geom_bar()
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
ggplot(final_data, aes(x = Marketing.Channel, y = Sales)) +
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = bar_label),
family = "Open Sans",
size = 3.5,
hjust = 0) +
geom_bar(stat = "identity", # Additional geom_bar()
mapping = aes(y = highlight_bar), # Uses the highlight_bar data
fill = "#90c4e4", # Hex color -- matching what Lea/Domo had
width = 0.75) +
coord_flip() +
scale_y_continuous(expand = c(0, 0),
limits = c(0, max(final_data$Sales) * 1.3)) + # Make the x-axis 30% longer than the max
my_theme()

We’re getting closer!
Final Touches
That’s pretty close on the chart itself. But, what about the title? Lea’s post has a few different versions here, but I’m going to go for matching this one:
Let’s start by adding the title and subtitle. There are two aspects of this:
Specifying the content for those, which we do by adding ggtitle() to our plot (and, since the subtitle has a calculated value in it, we do a little work to figure out what that is – I actually like this, because it shows much more flexibility as to what can be dynamically put in a title than, say, Excel provides).
Defining the theme for the title and subtitle.
In addition to making those additions, let’s also add some white space to the left of the plot using plot.margin in the theme:
# Munge the data
final_data <- channel_data %>%
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
select(-bar_percentage) %>%
mutate(highlight_bar = ifelse(Sales == max(.$Sales), Sales, 0))
# Define the theme
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
plot.title = element_text(size = 13, color = "gray30"), # Set up the title style
plot.subtitle = element_text(size = 11, color = "black"), # Set up the subtitle style
plot.margin = unit(c(0.5,0,0,2.5), "cm"), # Add white space at the top and left
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
# Calculate the total sales and make it nicely formatted. This could be embedded
# in the ggtitle() call, but it gets a little messy
total_sales <- paste0("Total Sales: ",
format(sum(final_data$Sales), big.mark = ","))
# Create the actual plot
ggplot(final_data, aes(x = Marketing.Channel, y = Sales)) +
ggtitle("What Is Our Count of Sales per Marketing Channel?", # Add the title and subtitle
subtitle = total_sales) + # to the plot
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = bar_label),
family = "Open Sans",
size = 3.5,
hjust = 0) +
geom_bar(stat = "identity",
mapping = aes(y = highlight_bar),
fill = "#90c4e4",
width = 0.75) +
coord_flip() +
scale_y_continuous(expand = c(0, 0),
limits = c(0, max(final_data$Sales) * 1.3)) +
my_theme()

That’s close… except the title is aligned with the axis (and left-justified) rather than with the entire left of the plot. It’s actually something of a royal pain to fully left-justify the title. It involves using “grobs” which, in my experience to date, are every bit as hacky as the term connotes! But, if you want to explore that option, you can read about it here.
And, this isn’t showing the total sales as a BIG, BOLD number. That, too, could be done with some grobbing, but I actually am pretty happy with it how it is.
Add Interactivity!
Perhaps you’ve been thinking, “Well, that’s all well and good. But, in Domo, I’ve got a chart that has some interactivity! I can mouse over things (which is one reason, perhaps, that their default chart does not have labels displayed). All you’ve done is generate a static chart!”
As one of my favorite professors used to say: “Maybe so.”
It’s actually super easy to add interactivity to the chart we’ve been building. There are two steps to this:
- Eaaarrrrrly on, I said we would normally assign the actual
ggplot() plot to an object and then just display the object. We actually need to do that now. Because…
- We’re going to use a package (of which there are several) that adds interactivity to
ggplot() objects
# Munge the data
final_data <- channel_data %>%
mutate(bar_percentage = sprintf("%.1f%%", 100*(Sales / sum(.$Sales)))) %>%
mutate(bar_label = paste0(format(Sales, big.mark = ","), ", ", bar_percentage)) %>%
select(-bar_percentage) %>%
mutate(highlight_bar = ifelse(Sales == max(.$Sales), Sales, 0))
# Define the theme
my_theme <- function(){
theme_light() +
theme(text = element_text(family = "Open Sans"),
plot.title = element_text(size = 13, color = "gray30"),
plot.subtitle = element_text(size = 11, color = "black"),
plot.margin = unit(c(0.5,0,0,2.5), "cm"),
panel.grid = element_blank(),
panel.border = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_text(size = 9, color = "gray10"))
}
# Calculate the total sales and make it nicely formatted.
total_sales <- paste0("Total Sales: ",
format(sum(final_data$Sales), big.mark = ","))
# Create the actual plot. Note the subtle change that the whole plot definition is now
# being *assigned* to `my_plot`
my_plot <- ggplot(final_data, aes(x = Marketing.Channel, y = Sales)) +
ggtitle("What Is Our Count of Sales per Marketing Channel?",
subtitle = total_sales) +
geom_bar(stat = "identity",
fill = "gray90",
width = 0.75) +
geom_text(aes(label = bar_label),
family = "Open Sans",
size = 3.5,
hjust = 0) +
geom_bar(stat = "identity",
mapping = aes(y = highlight_bar),
fill = "#90c4e4",
width = 0.75) +
coord_flip() +
scale_y_continuous(expand = c(0, 0),
limits = c(0, max(final_data$Sales) * 1.3)) +
my_theme()
# Load the library (normally we would do this at the very beginning) that we're going to use
# to add interactivity.
library(plotly)
# Plot the result
ggplotly(my_plot, width = 750, height = 350)
We now have an interactive chart! Mouse over the bars! Click around on some of the options in the menu at the top right (these are…pretty useless in a chart as simple as this, but illustrative nonetheless). Unfortunately, we also lost our data labels, the channel names got a bit crowded up to their bars, and the title isn’t left-justified. There are some settings for ggplotly() that you can fiddle with to make up for information that gets lost or dropped, but this was the best I was able to do here. So, I’d probably just stick with the static chart.
Takeaways
How did we do? The end result (the static one, at least), seems pretty darn close to the original. The main difference is the layout of the subtitle with the total sales figure. And, that’s not an insurmountable tweak if that was, truly, exactly what I wanted.
Summarizing the approach with Domo: start with a pretty good chart, and then tweak/massage it a bit, within the constraints of the platform (the “three font sizes” seems pretty limiting)
Summarizing the approach with ggplot2 in R: I’m going to put this in a list form:
- Start with a fairly hideous rendering of just the core data
- Use a theme (that can be reused across many visualizations) to adjust which elements are displayed and how
- Layer on additional “geoms” to add additional information to the plot. Some of these require creating derivatives (or “mutations” in
dplyr-speak) of the original data
Fiddle and experiment along the way.
The code – and the underlying “grammar of graphics” paradigm that ggplot uses – can seem intimidating. It is! And, for a simple horizontal bar chart, it can seem unnecessarily unwieldy. But, hopefully, it’s also apparent that there is a lot of power in this text-based definition of a chart.
LS0tCnRpdGxlOiAiSG93IHRvIEJ1aWxkIGEgQnJhaW4tRnJpZW5kbHkgQmFyIENoYXJ0IGluIFIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIGRhdGFfc2V0dXAsIGVjaG89RkFMU0V9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkodGlkeXZlcnNlKQoKY2hhbm5lbF9kYXRhIDwtIGRhdGEuZnJhbWUoYE1hcmtldGluZyBDaGFubmVsYCA9IGMoIlNvY2lhbCBNZWRpYSIsICJEaXNwbGF5IiwgIkVtYWlsIiwgIk9yZ2FuaWMgU2VhcmNoIiwgIlBhaWQgU2VhcmNoIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFNhbGVzID0gYyg2NywgMTgwLCA2NDgsIDkzNywgMTMwNSkpCgpjaGFubmVsX2RhdGEkTWFya2V0aW5nLkNoYW5uZWwgPC0gZmFjdG9yKGNoYW5uZWxfZGF0YSRNYXJrZXRpbmcuQ2hhbm5lbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjaGFubmVsX2RhdGEkTWFya2V0aW5nLkNoYW5uZWwpCmBgYAoKSSdtIGEgc3Vja2VyIGZvciBhIHRhY3RpY2FsIGJsb2cgcG9zdCwgYW5kIEknbSBhIHN1Y2tlciBmb3Igc29saWQgZGF0YSB2aXN1YWxpemF0aW9uIGFkdmljZS4gQXMgc3VjaCwgSSB3YXMgZG91YmxlLXN1Y2tlcmVkIGJ5IExlYSBQaWNhJ3MgcmVjZW50IHBvc3Qgb24gdGhlIFNlYXJjaCBEaXNjb3ZlcnkgYmxvZzogW0hvdyB0byBCdWlsZCBhIEJyYWluLUZyaWVuZGx5IEJhciBDaGFydCBpbiBEb21vXShodHRwczovL3d3dy5zZWFyY2hkaXNjb3ZlcnkuY29tL2Jsb2cvZG9tby1iYXItY2hhcnQvKS4gVGhlIGVuZCByZXN1bHQgb2YgaGVyIGNoYXJ0IG1ha2VvdmVyIHdhcyB0aGlzOgoKIVtdKGltYWdlcy9sZWFwaWNhMTMucG5nKQoKJm5ic3A7PGJyPgpGb3IgZ2lnZ2xlcywgSSB0aG91Z2h0IGl0IHdvdWxkIGJlIGlsbHVtaW5hdGluZyB0byBzdGFydCBpbiB0aGUgZXhhY3Qgc2FtZSBzcG90IC0tIHdpdGggdGhlIGRlZmF1bHQgLS0gYW5kIHRoZW4gYnVpbGQgdG8gdGhlIGV4YWN0IHNhbWUgcmVzdWx0Li4uIGJ1dCB1c2luZyBSLiBPdXIgc3Rha2Vob2xkZXJzIG5ldmVyIGNhcmUgX3doYXQgdG9vbF8gd2FzIHVzZWQgdG8gYnVpbGQgYSB2aXN1YWxpemF0aW9uLCBzbywgaW4gbXkgbWluZCwgYW55IHZpc3VhbGl6YXRpb24gcGxhdGZvcm0gd29ydGggaXRzIHNhbHQgY2FuIGJ1aWxkIGV4YWN0bHkgd2hhdCBJIHdhbnQgaXQgdG8uIEFuZCwgV1dMRCAoV2hhdCBXb3VsZCBMZWEgRG8pIGlzIGEgcHJldHR5IHNvbGlkIGJlc3QgcHJhY3RpY2UuIAoKSSBkaWRuJ3QgbmFpbCBpdCwgYnV0IEkgY2FtZSBwcmV0dHkgY2xvc2UgKGFuZCB3ZSdsbCBnZXQgdG8gc29tZSByYXRpb25hbGl6YXRpb24gYXMgdG8gdGhlIGxpbmdlcmluZyBkaWZmZXJlbmNlcyBhdCB0aGUgZW5kIG9mIHRoaXMgZXhlcmNpc2UpOgoKCmBgYHtyLCBmaWcud2lkdGg9Ny45LCBmaWcuaGVpZ2h0PTMuNywgZWNobz1GQUxTRX0KIyBNdW5nZSB0aGUgZGF0YQpmaW5hbF9kYXRhIDwtIGNoYW5uZWxfZGF0YSAlPiUgCiAgbXV0YXRlKGJhcl9wZXJjZW50YWdlID0gc3ByaW50ZigiJS4xZiUlIiwgMTAwKihTYWxlcyAvIHN1bSguJFNhbGVzKSkpKSAlPiUgCiAgbXV0YXRlKGJhcl9sYWJlbCA9IHBhc3RlMChmb3JtYXQoU2FsZXMsIGJpZy5tYXJrID0gIiwiKSwgIiwgIiwgYmFyX3BlcmNlbnRhZ2UpKSAlPiUgCiAgc2VsZWN0KC1iYXJfcGVyY2VudGFnZSkgJT4lCiAgbXV0YXRlKGhpZ2hsaWdodF9iYXIgPSBpZmVsc2UoU2FsZXMgPT0gbWF4KC4kU2FsZXMpLCBTYWxlcywgMCkpCgojIERlZmluZSB0aGUgdGhlbWUKbXlfdGhlbWUgPC0gZnVuY3Rpb24oKXsKICB0aGVtZV9saWdodCgpICsKICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIk9wZW4gU2FucyIpLCAgCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMywgY29sb3IgPSAiZ3JheTMwIiksICAgICMgU2V0IHVwIHRoZSB0aXRsZSBzdHlsZQogICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEsIGNvbG9yID0gImJsYWNrIiksICMgU2V0IHVwIHRoZSBzdWJ0aXRsZSBzdHlsZQogICAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMC41LDAsMCwyLjUpLCAiY20iKSwKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCiMgQ2FsY3VsYXRlIHRoZSB0b3RhbCBzYWxlcyBhbmQgbWFrZSBpdCBuaWNlbHkgZm9ybWF0dGVkLiBUaGlzIGNvdWxkIGJlIGVtYmVkZGVkCiMgaW4gdGhlIGdndGl0bGUoKSBjYWxsLCBidXQgaXQgZ2V0cyBhIGxpdHRsZSBtZXNzeQp0b3RhbF9zYWxlcyA8LSBwYXN0ZTAoIlRvdGFsIFNhbGVzOiAiLAogICAgICAgICAgICAgICAgICAgICAgZm9ybWF0KHN1bShmaW5hbF9kYXRhJFNhbGVzKSwgYmlnLm1hcmsgPSAiLCIpKQoKIyBDcmVhdGUgdGhlIGFjdHVhbCBwbG90CmdncGxvdChmaW5hbF9kYXRhLCBhZXMoeCA9IE1hcmtldGluZy5DaGFubmVsLCB5ID0gU2FsZXMpKSArCiAgZ2d0aXRsZSgiV2hhdCBJcyBPdXIgQ291bnQgb2YgU2FsZXMgcGVyIE1hcmtldGluZyBDaGFubmVsPyIsICAjIEFkZCB0aGUgdGl0bGUgYW5kIHN1YnRpdGxlCiAgICAgICAgICBzdWJ0aXRsZSA9IHRvdGFsX3NhbGVzKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRvIHRoZSBwbG90CiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIGZpbGwgPSAiZ3JheTkwIiwgICAgIAogICAgICAgICAgIHdpZHRoID0gMC43NSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBiYXJfbGFiZWwpLAogICAgICAgICAgICBmYW1pbHkgPSAiT3BlbiBTYW5zIiwKICAgICAgICAgICAgc2l6ZSA9IDMuNSwgICAgICAgICAgICAgICAgIAogICAgICAgICAgICBoanVzdCA9IDApICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsCiAgICAgICAgICAgbWFwcGluZyA9IGFlcyh5ID0gaGlnaGxpZ2h0X2JhciksCiAgICAgICAgICAgZmlsbCA9ICIjOTBjNGU0IiwKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIG1heChmaW5hbF9kYXRhJFNhbGVzKSAqIDEuMykpICsKICBteV90aGVtZSgpCmBgYAoKV2l0aCBsdWNrLCB0aGlzIHdpbGwgaWxsdXN0cmF0ZSBib3RoICJ0aGUgYmFzaWNzIiBvZiB2aXN1YWxpemF0aW9uIHdpdGggYGdncGxvdDJgLCBhcyB3ZWxsIGFzIHNvbWUgb2YgdGhlIGhvb3BzIHRoYXQgaGF2ZSB0byBiZSBqdW1wZWQgdGhyb3VnaCAtLSBhbmQgd2hlcmUgdGhvc2UgaG9vcHMgYXJlIGxvY2F0ZWQgLS0gdG8gdHdlYWsgdGhlIHZpc3VhbHMuIEknbGwgdHJ5IHRvIGFsc28gY2FsbCBvdXQgdGhlIHRoaW5ncyB0aGF0IGFyZSB2ZXJ5IG11Y2ggX29uZS10aW1lXyBvcGVyYXRpb25zLCBhcyB0aGlzIHdpbGwgdW5kb3VidGVkbHkgc2VlbSBwcmV0dHkgYnJ1dGFsIGlmIGV2ZXJ5IGNoYXJ0IHJlcXVpcmVkIGV2ZXJ5IG9uZSBvZiB0aGVzZSBzdGVwcy4KCiMjIyBUaGUgRGF0YQoKV2UnbGwgc3RhcnQgd2l0aCB0aGUgcmF3IGRhdGEgKEknbGwgbmVlZCB0aGlzIGxvYWRlZCBpbnRvIHRoZSBmaWxlIEknbSBidWlsZGluZyB0aGlzIHdpdGgsIGFueXdheSwgc28gbWlnaHQgYXMgd2VsbCBzaG93IGl0IHRvIHlvdSEpLiBJJ3ZlIGNyZWF0ZWQgYSBzaW1wbGUgZGF0YSBmcmFtZSBjYWxsZWQgYGNoYW5uZWxfZGF0YWAgd2l0aCB0aGUgZm9sbG93aW5nIHZhbHVlczoKCmBgYHtyIHNob3dfZGF0YSwgZWNobz1GQUxTRX0Ka2FibGUoY2hhbm5lbF9kYXRhKQpgYGAKCkFscmVhZHksIHdlIGNhbiBzcG90IHR3byB0aGluZ3MgdGhhdCBtYXkgcmVxdWlyZSB0d2Vha2luZyBpbiBvdXIgZmluYWwgcGxvdDoKCiogVGhlICIuIiBpbiB0aGUgY29sdW1uIGhlYWRpbmc7IHdlJ3JlIHdvcmtpbmcgd2l0aCBjb2RlLCBhbmQgd2UgZG9uJ3QgcmVhbGx5IHdhbnQgc3BhY2VzIGZsb2F0aW5nIGFyb3VuZCB1bnRpbCB0aGUgdmVyeSBsYXN0IG1pbnV0ZSAodGhlIHVuZGVybHlpbmcgZGF0YSBhY3R1YWxseSBfZG9lc18gaGF2ZSBhIHNwYWNlIGluIGl0LCBidXQgdGhhdCBnZXRzIGNvbnZlcnRlZCB0byAiLiIgd2hlbiBvdXRwdXR0aW5nIGhlcmUpLiBXZSdyZSBub3QgZ29pbmcgdG8gd29ycnkgYWJvdXQgdGhhdCAoZm9yIG5vdzsgd2UnbGwgZ2V0IHRoZXJlISkuCiogVGhlcmUgYXJlIG5vIGNvbW1hLWRlbGltaXRlcnMgaW4gdGhlIG51bWJlcnMhIEFnYWluLCB0aGF0J3MgYmVjYXVzZSB0aGlzIGRhdGEgaXMgImp1c3QgdGhlIGRhdGEiIC0tIHRoZXJlIGlzIG5vIGZvcm1hdHRpbmcuIEkgY2VydGFpbmx5IGNvdWxkIGZvcmNlIHRoZSBvdXRwdXQgYWJvdmUgdG8gaGF2ZSBjb21tYXMsIGJ1dCwgbGlrZSBzcGFjZXMsIHRob3NlIGFyZSBzb21ldGhpbmcgd2UnbGwgd29ycnkgYWJvdXQgbGF0ZXIgLS0gZXZlbiBpZiBJIHNob3dlZCB0aGVzZSB3aXRoIGNvbW1hcyBpbiB0aGUgdGFibGUsIEknZCBzdGlsbCBoYXZlIHRvIGRvIHRoZSBzYW1lIHRoaW5nIGluIG15IGZpbmFsIGNoYXJ0LgoKQXMgaXQgdHVybnMgb3V0IChzZWUgTGVhJ3MgZmluYWwgY2hhcnQpIHdlIHdvbid0IGFjdHVhbGx5IG5lZWQgdG8gZ2V0IHRoZSAiLiIgcmVtb3ZlZC4gQnV0IHdlIHdpbGwgaGF2ZSB0byBkZWFsIHdpdGggYWRkaW5nIGNvbW1hIGRlbGltaXRlcnMhCgojIyMgVGhlIERlZmF1bHQgQ2hhcnQKCkxlYSBzdGFydGVkIHdpdGggdGhlIGRlZmF1bHQgRG9tbyBiYXIgY2hhcnQ6CgohW10oaW1hZ2VzL2xlYXBpY2ExLnBuZykKCiZuYnNwOzxicj4KVGhhdCdzIHdoZXJlIHdlJ2xsIHN0YXJ0IHdpdGggUiwgdG9vLiBCZWxvdyBpcyB0aGUgYmFyZS1ib25lcyBgZ2dwbG90MmAgY29kZSBhbmQgdGhlIHJlc3VsdGluZyBvdXRwdXQ6CgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CiMgTm9ybWFsbHksIHRoZSBlbnRpcmUgZXhwcmVzc2lvbiBiZWxvdyB3b3VsZCBiZSBhc3NpZ25lZCB0byBhbiBvYmplY3QsIGJ1dCB3ZSdyZQojIGdvaW5nIGJhcmUgYm9uZXMgaGVyZS4KZ2dwbG90KGNoYW5uZWxfZGF0YSwgYWVzKHggPSBNYXJrZXRpbmcuQ2hhbm5lbCwgeSA9IFNhbGVzKSkgKwogICAgICAgICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogICAgICAgICBjb29yZF9mbGlwKCkKYGBgCgoqKkVFSyEqKgoKRm9yIHdoYXRldmVyIHJlYXNvbiwgdGhpcyBpcyB0aGUgImRlZmF1bHQuIiBBbG1vc3Qgbm8gb25lIHdpbGwgd2FudCB0aGlzIHRvIGJlIHRoZSBmaW5hbCBwcm9kdWN0IChvYnZpb3VzbHkpLiBUaGlzIGlzIGFjdHVhbGx5IGJlY2F1c2UgdGhpcyBiYXNpYyB0cmVhdG1lbnQgaXMgdGhlIGRlZmF1bHQgZm9yIF9hbGxfIHBvc3NpYmxlIHZpc3VhbGl6YXRpb25zIHdpdGggYGdncGxvdCgpYCwgYW5kIHRoZXJlIGFyZSBzb21lIGNhc2VzIHdoZXJlIHRoaW5ncyBsaWtlIGhvcml6b250YWwgYW5kIHZlcnRpY2FsIGdyaWRzIHdvdWxkIG1ha2Ugc2Vuc2UuIEJ1dCwgSSdtIG5vdCBnb2luZyB0byBkZWZlbmQgaXQuCgpBbmQsIHdoaWxlIHRoaXMgcG9zdCBpcyBub3QgbWVhbnQgdG8gYmUgYSBmdWxsLWJsb3duIHR1dG9yaWFsIG9uIGJ1aWxkaW5nIGRhdGEgdmlzdWFsaXphdGlvbnMgd2l0aCBgZ2dwbG90MmAsIGl0IHNlZW1lZCB3b3J0aHdpbGUgdG8gYXQgbGVhc3QgcHJvdmlkZSBhIHF1aWNrIGJyZWFrZG93biBvZiB0aGlzIGluaXRpYWwgcGxvdDoKCi0tLS0tLQoKKipgZ2dwbG90KGNoYW5uZWxfZGF0YSwgYWVzKHggPSBNYXJrZXRpbmcuQ2hhbm5lbCwgeSA9IFNhbGVzKSlgKioKClRoaXMgaXMgYWx3YXlzIHRoZSBmaXJzdCBjYWxsLCBhcyBpdCBpcyB0aGUgY2FsbCB0byB0aGUgYGdncGxvdCgpYCBmdW5jdGlvbi4gRXZlcnRoaW5nIGVsc2UgYWZ0ZXIgdGhhdCBpcyBqdXN0ICJhZGRpbmciIHRvIHRoYXQgYmFzZSBjYWxsLiBUaGUgdHdvIGFyZ3VtZW50cyBpbiB0aGlzIGZ1bmN0aW9uOgoKKiBgZGF0YSA9IGNoYW5uZWxfZGF0YWAgLS0gdGhpcyBpcyByZWFsbHkganVzdCB0aGUgb3ZlcmFsbCBkYXRhIHNldCB0byBiZSB1c2VkOyBpdCBjb3VsZCBoYXZlIGEgYnVuY2ggb2Ygb3RoZXIgc3R1ZmYgdGhhdCBuZXZlciBnZXRzIHBsb3R0ZWQsIGJ1dCBpdCdzIHRoZSBzZXQgX29yXyBzdXBlcnNldCBvZiBkYXRhIHRvIGJlIHZpc3VhbGl6ZWQKKiBgbWFwcGluZyA9IGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcylgIC0tIHRoaXMgaXMgb25lIG9mIHRoZSBtb3N0IGNvbmZ1c2luZyBhc3BlY3RzIG9mIGBnZ3Bsb3QyYC4gSXQncyB0aGUgbWFwcGluZyBvZiB3aGljaCB2YWx1ZXMgaW4gdGhlIGRhdGEgdG8gdXNlIGZvciB3aGF0IGFzcGVjdHMgb2YgdGhlIHBsb3QuLi5fYnkgZGVmYXVsdF8uIFRoZSBgYWVzKClgIGlzIGZvciAiYWVzdGhldGljLCIgd2hpY2ggaXMgYSBob3JyaWJseSBtaXNsZWFkaW5nIGRlc2NyaXB0b3Igb2Ygd2hhdCB0aGlzIGlzLiBJJ20gbm90IGdvaW5nIHRvIGdvIGludG8gYW55IG1vcmUgZGV0YWlsIGhlcmUsIG90aGVyIHRoYW4gdG8gc2F5IHRoYXQsIGlmIHlvdSdyZSB3b25kZXJpbmcgd2h5IHRoZSBfKip5KipfIGlzIHNldCB0byBgU2FsZXNgLCB3aGVuIHRoYXQncyBhY3R1YWxseSB0aGUgdmFsdWUgb24gdGhlIF8qKngqKl8gYXhpcywgdGhlbiByZWFkIG9uIC0tIGl0IHNob3VsZCBiZWNvbWUgY2xlYXIgd2hlbiB5b3UgZ2V0IHRvIGBjb29yZF9mbGlwKClgIGluIGEgY291cGxlIG9mIHBhcmFncmFwaHMuIE1haW5seSwganVzdCBrbm93IHRoYXQgdGhlc2UgIm1hcHBpbmdzIiBjYW4gYmUgdXNlZCBtdWx0aXBsZSB0aW1lcyBpbiBhIHNpbmdsZSB2aXN1YWxpemF0aW9uLiBXZSdsbCBzdGFydCB0byBzZWUgaG93IHRoaXMgd29ya3MgYXMgd2UgZ2V0IGZhcnRoZXIgYWxvbmcuCgotLS0tLS0KCioqYGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKWAqKgoKVGhlIGBnZ3Bsb3QoKWAgZnVuY3Rpb24gZG9lc24ndCBhY3R1YWxseSBzYXkgX2hvd18gdG8gdmlzdWFsaXplIGFueXRoaW5nLiBXZSBoYXZlIHRvIGFkZCBvbmUgb3IgbW9yZSAiZ2VvbXMiIGluIG9yZGVyIHRvIGRvIHRoYXQuIFdlJ2xsIGJlIGFkZGluZyBhIGZldyBtb3JlIGJ5IHRoZSB0aW1lIHdlJ3JlIGRvbmUuIEluIHRoaXMgY2FzZSwgd2UncmUgZG9pbmcgYSBiYXIgY2hhcnQgKHdlIHVzZSB0aGUgc2FtZSBgZ2VvbV9iYXIoKWAgcmVnYXJkbGVzcyBvZiB3aGV0aGVyIGl0J3MgaG9yaXpvbnRhbCBvciB2ZXJ0aWNhbCkuIEFuZCwgc2luY2UgdGhlIGRhdGEgaXMgYWxyZWFkeSBhZ2dyZWdhdGVkIChhcyBvcHBvc2VkIHRvIGhhdmluZyBhIGRhdGEgc2V0IHdoZXJlIGVhY2ggcm93IGlzIGFuIG9yZGVyIHdpdGggYW4gYXNzb2NpYXRlZCBjaGFubmVsIGFuZCBzYWxlcyBhbW91bnQpLCB3ZSBoYXZlIHRvIHVzZSB0aGUgYHN0YXQgPSAiaWRlbnRpdHkiYCBhcmd1bWVudC4gVGhhdCdzIGNvbmZ1c2luZywgdG9vLi4uIGFsdGhvdWdoIGl0IGJlY29tZXMgc2Vjb25kIG5hdHVyZSBwcmV0dHkgcXVpY2tseS4KCi0tLS0tLQoKKipgY29vcmRfZmxpcCgpYCoqCgpEaWQgeW91IGNhdGNoIGhvdyBJIHNhaWQgd2UnZCB1c2UgYGdlb21fYmFyKClgIGZvciBib3RoIGhvcml6b250YWwgYW5kIHZlcnRpY2FsIGJhciBjaGFydHM/IFdlbGwsIHRoZSBkZWZhdWx0IGlzIHRoYXQgaXQncyBhIHZlcnRpY2FsIGJhciBjaGFydCwgc28gd2UgaGF2ZSB0byBhZGQgYW4gaW5zdHJ1Y3Rpb24gdG8gImZsaXAgdGhlIGNvb3JkaW5hdGVzIiB0byBtYWtlIGl0IGhvcml6b250YWwuIEFuZCwgdGhhdCdzIHdoeSBvdXIgImFlc3RoZXRpYyIgc2VlbWVkIHRvIGJlIGJhY2t3YXJkcyAtLSBzcGVjaWZ5aW5nIGBTYWxlc2AgZm9yIGB5YCBhbmQgYE1hcmtldGluZy5DaGFubmVsYCBmb3IgYHhgLCBldmVuIHRob3VnaCB0aGV5J3JlIHRoZSBvcHBvc2l0ZSBvZiB0aGF0IGluIHRoZSBhY3R1YWwgY2hhcnQuCgpEb21vIHN0YXJ0cyBvdXQgd2F5eXl5eXkgYWhlYWQgb2YgdGhpcyBkZWZhdWx0LCBubyBkb3VidCEKCiMjIyBFbnRlci4uLlRoZW1lcyEKClRoZSBmaXJzdCB0aGluZyB3ZSB3YW50IHRvIGRvIGlzIGdldCB0aGUgaWNraW5lc3Mgb2YgdGhlIGdyYXkgYmFja2dyb3VuZCBhbmQgZ3JpZGxpbmVzIGFuZCB0aWNrbWFya3MgYW5kIHN1Y2ggc3RyaXBwZWQgYXdheS4gV2UnbGwgbmVlZCB0byBkbyBtb3JlIHRoYW4gdGhhdCwgYnV0IHRoYXQgd2lsbCBkbyBhIGxvdCBvZiB0aGUgY2xlYW51cCBmb3IgdXMuIFRoZW1lcyBhcmUgbGlrZSBDU1MgaW4gSFRNTCAtLSBub3Qgb25seSBkbyB0aGV5IHByb3ZpZGUgYSB0b24gb2YgY29udHJvbCBvdmVyIHRoZSBsb29rIGFuZCBmZWVsIG9mIHRoZSB2aXN1YWxpemF0aW9uLCBidXQgdGhleSBhY3R1YWxseSAiY2FzY2FkZSIgaW4gYSB3YXk6IGlmIHlvdSBzZXQgb25lIGF0dHJpYnV0ZSBvZiBhIHRoZW1lIGluIG9uZSBwbGFjZSwgeW91IGNhbiBlYXNpbHkgb3ZlcnJpZGUgaXQgbGF0ZXIgYXMgdGhlIHZpc3VhbGl6YXRpb24gaXMgYmVpbmcgYnVpbHQuCgpUaGVyZSBhcmUgc29tZSBkZWZhdWx0IHRoZW1lcyB0aGF0IGFyZSBwYXJ0IG9mIHRoZSBgZ2dwbG90MmAgcGFja2FnZS4gU28sIGxldCdzIHN0YXJ0IGJ5IGxvb2tpbmcgYXQgb3VyIHBsb3QgdXNpbmcgYHRoZW1lX2xpZ2h0KClgLiBBbGwgd2UgaGF2ZSB0byBkbyBpcyAiYWRkIiB0aGUgdGhlbWUgdG8gdGhlIHBsb3QgdGhhdCB3ZSdyZSBidWlsZGluZyB1cC4KCmBgYHtyLCBmaWcud2lkdGg9Ny45LCBmaWcuaGVpZ2h0PTMuN30KZ2dwbG90KGNoYW5uZWxfZGF0YSwgYWVzKHggPSBNYXJrZXRpbmcuQ2hhbm5lbCwgeSA9IFNhbGVzKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZV9saWdodCgpICAgIyBBZGQgYSB0aGVtZQpgYGAKCkhtbW1tLiBXZSBzdGlsbCBoYXZlIGdyaWRsaW5lcyBhbmQgdGlja3MgYW5kIGEgYm9yZGVyIGFuZCBheGlzIGxhYmVscy4gU28sIHdlIGFjdHVhbGx5IG5lZWQgdG8gImFkZCIgc29tZSBvdmVycmlkZXMgdG8gdGhhdCB0aGVtZS4gVGhpcyBpcyB3aGVyZSB0aGluZ3Mgc3RhcnQgdG8gbG9vayBhIGJpdCBjdW1iZXJzb21lLCBidXQgd2UnbGwgY292ZXIgdGhlIHJldXNhYmlsaXR5IG9mIHRoZXNlIHNob3J0bHkuCgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CmdncGxvdChjaGFubmVsX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgIyBUd2VhayB0aGUgdGhlbWUgYSBiaXQgd2l0aCBhZGRpdGlvbmFsIHRoZW1lKCkgc2V0dGluZ3MKICB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKSXQncyBnZXR0aW5nIGJldHRlciEgSG9wZWZ1bGx5LCBtb3N0IG9mIHRob3NlIGB0aGVtZSgpYCBhcmd1bWVudHMgYXJlIGZhaXJseSBzZWxmLWV4cGxhbmF0b3J5LiBJbiBtb3N0IGNhc2VzLCB3ZSB3ZXJlIHJlbW92aW5nIGJvdGggdGhlICoqeCoqIF9hbmRfICoqeSoqIGFzcGVjdHMgb2YgdGhlIHBsb3QsIGJ1dCwgZm9yIHRoZSBheGlzIHRleHQsIHdlIG9ubHkgd2FudGVkIHRvIHJlbW92ZSB0aGUgKip4LWF4aXMqKiB0ZXh0LCBzbyB3ZSBzcGVjaWZpZWQgYSBgLnhgIGZvciBgYXhpcy50ZXh0YC4gCgpCdXQsIG9uZSBvdGhlciBjaGFuZ2Ugd2Ugd2FudCB0byBkbyB3aXRoIHRoZSB0aGVtZTogbGV0J3MgY2hhbmdlIHRoZSBmb250IChmb3IgYWxsIHRoZW1lLWNvbnRyb2xsZWQgZWxlbWVudHMpIGFuZCBhZGp1c3QgdGhlIHRleHQgc2l6ZSBhbmQgY29sb3IgZm9yIHRoZSB5LWF4aXMgdGV4dC4gV2UnbGwgZGVmaW5lIHRoZSBmb250IGZhbWlseSBhcyBhIGB0ZXh0YCBhcmd1bWVudCAoc28gaXQgd2lsbCBhcHBseSB0byBhbGwgdGV4dC1iYXNlZCBlbGVtZW50cyBpbiB0aGUgdGhlbWUsIHVubGVzcyBvdmVycmlkZGVuKSwgYW5kIHRoZW4gc3BlY2lmeSB0aGUgc2l6ZSBhbmQgY29sb3IgZm9yIGBheGlzLnRleHQueWA6CgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CmdncGxvdChjaGFubmVsX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgdGhlbWVfbGlnaHQoKSArCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiT3BlbiBTYW5zIiksICAgICAgICAgICAgICAgIyBET01PIEFQUEVBUlMgVE8gVVNFICdPUEVOIFNBTlMnCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBjb2xvciA9ICJncmF5MTAiKSkgICMgU0VUIFRIRSBBWElTIFRFWFQgU0laRSBBTkQgQ09MT1IKYGBgCgpWb2lsYSEgV2UncmUgZ2V0dGluZyBjbG9zZXIuIE5vdywgYmVmb3JlIHdlIHJldmVhbCB0aGF0IHdlJ3JlIGdvaW5nIHRvIGRvdWJsZSB0aGUgbGVuZ3RoIG9mIHRoaXMgY29kZSB0byBhY3R1YWxseSBnZXQgdG8gb3VyIGZpbmFsIHJlc3VsdCwgaXQncyB3b3J0aCBhIHF1aWNrIGxpdHRsZSBkaXZlcnNpb24gb24gdGhlbWVzLgoKRmlyc3Qgb2ZmLCBqdXN0IGxpa2Ugc2F2aW5nIGEgY2hhcnQgc3R5bGUgaW4gRXhjZWwgKGJ1dCBtb3JlIHBvd2VyZnVsLCBhY3R1YWxseSksIHlvdSBjYW4gZGVmaW5lIGFuIGVudGlyZSB0aGVtZSBhcyBhIGNlbnRyYWxpemVkIGZ1bmN0aW9uIC0tIGluIHRoZSBjb2RlLCBpbiBhIHNlcGFyYXRlIGZpbGUgdGhhdCBnZXRzIHJlZmVyZW5jZWQsIG9yIGV2ZW4gaW4gYSBwYWNrYWdlIHlvdSBidWlsZCB0aGF0IHlvdSBkaXN0cmlidXRlIHRvIG90aGVycyBpbiB5b3VyIG9yZ2FuaXphdGlvbi4gSSdtIGdvaW5nIHRvIGp1c3Qgc2V0IHRoaXMgdGhlbWUgYXMgaXRzIG93biBmdW5jdGlvbiByaWdodCBhYm92ZSBteSBwbG90IGZvciBub3cuIFRoZSBvdXRwdXQgd2lsbCBiZSBpZGVudGljYWwsIGJ1dCwgaG9wZWZ1bGx5LCB5b3UnbGwgc2VlIGhvdyB5b3UgY2FuIHRoZW4gcmV1c2UgdGhhdCBzYW1lIHRoZW1lIHRpbWUgYW5kIGFnYWluLiBBbmQsIHdlJ3JlIGdvaW5nIHRvIGhhdmUgc29tZSBvdGhlciBmdW4gd2l0aCBpdCwgdG9vLgoKYGBge3IsIGZpZy53aWR0aD03LjksIGZpZy5oZWlnaHQ9My43fQpteV90aGVtZSA8LSBmdW5jdGlvbigpewogIHRoZW1lX2xpZ2h0KCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiT3BlbiBTYW5zIiksICAKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCmdncGxvdChjaGFubmVsX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbXlfdGhlbWUoKQpgYGAKClNvLCBub3csIGltYWdpbmUgdGhhdCB5b3Ugd2VyZSBkb2luZyBtdWx0aXBsZSBwbG90cy4gRWFjaCBvbmUgb2YgdGhlbSBjb3VsZCBzaW1wbHkgaW5jbHVkZSBhIGNhbGwgdG8gYG15X3RoZW1lKClgIC0tIGV2ZW4gaWYgdGhleSBhcmUgbm90IGhvcml6b250YWwgYmFyIGNoYXJ0cyEgLS0gYW5kIGdldCB0aGUgc2FtZSBzdHlsaW5nLiBBbmQsIGlmIHlvdSB0d2VhayB0aGUgZGVmaW5pdGlvbiBpbiBqdXN0IHRoYXQgb25lIGZ1bmN0aW9uIGFuZCByZWJ1aWxkIHlvdXIgY2hhcnRzLCB0aGUgY2hhbmdlIHdpbGwgYmUgcmVmbGVjdGVkIGluIGFsbCBvZiB0aGVtIChyZW1lbWJlciBob3cgSSBzYWlkIHRoZW1lcyBhcmUgbGlrZSBDU1M/KSEKCkFuZCAoYW5kIHRoaXMgaXMgaGFuZHkpLCByZW1lbWJlciB0aGF0IHdlIGNhbiBzdGlsbCBvdmVycmlkZSBzcGVjaWZpYyBhc3BlY3RzIG9mIHRoZSBjaGFydC4gSWYsIGZvciBpbnN0YW5jZSwgSSB3YW50ZWQganVzdCBvbmUgaW5zdGFuY2Ugb2YgdGhlIGNoYXJ0IHRvIGhhdmUgYm9sZCwgcmVkLCBsYXJnZXIgdGV4dCBmb3IgdGhlIGF4aXMgdGV4dCAod2hpY2ggd291bGQgYmUgc2lsbHkpLCBJIGNvdWxkIGtlZXAgbXkgZnVuY3Rpb24gdGhlIHNhbWUsIGJ1dCB0aGVuIGFkZCB0aGF0IG92ZXJyaWRlOgoKYGBge3IsIGZpZy53aWR0aD03LjksIGZpZy5oZWlnaHQ9My43fQpteV90aGVtZSA8LSBmdW5jdGlvbigpewogIHRoZW1lX2xpZ2h0KCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiT3BlbiBTYW5zIiksICAKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCmdncGxvdChjaGFubmVsX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgbXlfdGhlbWUoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEzLCBjb2xvciA9ICJyZWQiLCBmYWNlID0gImJvbGQiKSkgICAjIFRIRSAnT1ZFUlJJREUnIExJTkUKYGBgCgpUaGF0J3MgeXVja3ksIHNvIHdlIHdvbid0IGtlZXAgdGhhdCwgYnV0LCBob3BlZnVsbHksIHlvdSBnZXQgdGhlIGlkZWEuIEknbSBhbHNvIHVzaW5nIGJhc2UgY29sb3JzIGhlcmUsIGJ1dCBJIGNvdWxkIGFsc28gdXNlIFJHQiBoZXggY29sb3JzIG9yIGRlZmluZWQgcGFsZXR0ZXMuIAoKX0lmIHlvdSBoYXBwZW4gdG8gYmUgYSBTdGVwaGVuIEZldywgRWR3YXJkIFR1ZnRlLCBvciBGaXZlVGhpcnR5RWlnaHQuY29tIGp1bmt5LCB5b3UgY2FuIGFjdHVhbGx5IGluc3RhbGwgdGhlIGBnZ3RoZW1lc2AgcGFja2FnZSBhbmQgZ2V0IHByZWRlZmluZWQgdGhlbWVzIC0tIHB1dCB0byB1c2UganVzdCBsaWtlIEkgdXNlZCBgbXlfdGhlbWUoKWAgaGVyZSAtLSB0byBtYXRjaCB0aGVpciBzcGVjaWZpYyBzdHlsZXMuIFRoYXQgaXMgY29vbCFfCgpJJ20gZGlncmVzc2luZy4gSXQncyB3aGF0IEkgZG8uCgpMZXQncyBnZXQgYmFjayBvbiB0cmFjayB0byB0aGUgY2hhcnQgaW1wcm92ZW1lbnRzIHdlIG5lZWRlZCB0byBtYWtlLgoKIyMjIEdldCB0aGUgVGV4dCBDbG9zZXIgdG8gdGhlIEF4aXMKClRoZSB0ZXh0IGlzIHNvIGZhciBmcm9tIHRoZSBheGlzIG5vdCBiZWNhdXNlIGl0J3MgdGhlIGRlZmF1bHQgc3BhY2luZywgYnV0LCByYXRoZXIsIGJ5IGRlZmF1bHQsIHRoZSAqKngtYXhpcyoqIGFuZCAqKnktYXhpcyoqIGRvbid0IGNyb3NzIGF0IHplcm8uIFNvLCB3ZSBoYXZlIGRvIGEgbGl0dGxlIGdvb2ZpbmVzcyB0byBtYWtlIHRoYXQgaGFwcGVuIChJIGRvbid0IF9oYXZlXyB0byBrZWVwIHJlcGVhdGluZyB0aGUgdGhlbWUgZGVmaW5pdGlvbiwgYnV0IEkgd2FudCBlYWNoIGJsb2NrIHRvIHNob3cgdGhlICJmdWxsIHNldCBvZiBjb2RlIG5lZWRlZCIpOgoKYGBge3IsIGZpZy53aWR0aD03LjksIGZpZy5oZWlnaHQ9My43fQpteV90aGVtZSA8LSBmdW5jdGlvbigpewogIHRoZW1lX2xpZ2h0KCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiT3BlbiBTYW5zIiksICAKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCmdncGxvdChjaGFubmVsX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKwogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsgICMgU2V0IHRoZSBheGVzIHRvIGNyb3NzIGF0IDAKbXlfdGhlbWUoKQpgYGAKCkhvdyBhYm91dCBsaWdodGVuaW5nIHVwIHRoYXQgZ3JheSAod2UgY291bGQgYWxzbyB0dXJuIGl0IHRvIGEgbmV1dHJhbCBibHVlLCBidXQgTGVhIHByb3Bvc2VkIGdyYXkgd2l0aCBibHVlIHRvIGhpZ2hsaWdodCwgc28gdGhhdCdzIHdoYXQgd2UncmUgZ29pbmcgdG8gZ28gd2l0aCk/IFRoaXMgaXNuJ3Qgc29tZXRoaW5nIHdlIGRvIGluIHRoZSB0aGVtZSBiZWNhdXNlIHdlIGNvdWxkIGhhdmUgbXVsdGlwbGUgZ2VvbXMgaW4gcGxheSAoYW5kIHdlIHdpbGwgYmUgYWRkaW5nIHNvbWUhKSwgYW5kIHRoZSB0aGVtZSBpcyB1bml2ZXJzYWxseSBhcHBsaWVkLiBTbywgcmF0aGVyLCB3ZSBuZWVkIHRvIGRlZmluZSB0aGF0IGF0IHRoZSBnZW9tIGxldmVsLiBXZSB3b3VsZCBkZWZpbmUgdGhhdCBfaW5zaWRlXyB0aGUgYWVzdGhldGljIGlmIHdlIGFjdHVhbGx5IGhhZCBtdWx0aXBsZSBkYXRhIHNlcmllcyBhbmQgd2Ugd2FudGVkIGVhY2ggc2VyaWVzIHRvIGJlIGEgZGlmZmVyZW50IGNvbG9yLiBUaGF0J3MgYWN0dWFsbHkgb25lIHdheSB0byBkbyBzb21ldGhpbmcgd2UncmUgZ29pbmcgdG8gZG8gbGF0ZXIgKGJ1dCBpdCdzIG5vdCBob3cgd2UncmUgZ29pbmcgdG8gZG8gaXQpLiBCdXQsIGZvciBub3csIHdlIGNhbiBqdXN0IGRvIHRoYXQgYXMgaXRzIG93biBhcmd1bWVudCBpbiBgZ2VvbV9iYXIoKWAuIFdoaWxlIHdlJ3JlIGF0IGl0LCBsZXQncyBpbmNyZWFzZSB0aGUgc3BhY2UgYmV0d2VlbiB0aGUgYmFycyBhIGJpdC4KCmBgYHtyLCBmaWcud2lkdGg9Ny45LCBmaWcuaGVpZ2h0PTMuN30KbXlfdGhlbWUgPC0gZnVuY3Rpb24oKXsKICB0aGVtZV9saWdodCgpICsKICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIk9wZW4gU2FucyIpLCAgCiAgICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBjb2xvciA9ICJncmF5MTAiKSkKICB9CgpnZ3Bsb3QoY2hhbm5lbF9kYXRhLCBhZXMoeCA9IE1hcmtldGluZy5DaGFubmVsLCB5ID0gU2FsZXMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIGZpbGwgPSAiZ3JheTkwIiwgICAgICMgQUREIFRIRSBGSUxMIENPTE9SCiAgICAgICAgICAgd2lkdGggPSAwLjc1KSArICAgICAgIyBBREpVU1QgVEhFIEJBUiBXSURUSAogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsKbXlfdGhlbWUoKQpgYGAKCk5lYXQsIGh1aD8KCiMjIyBBZGQgRGF0YSBMYWJlbHMKClNpbmNlIHdlIHJlbW92ZWQgb3VyIHgtYXhpcywgd2UgaGF2ZSBubyBzZW5zZSBvZiBzY2FsZSEgV2UgbmVlZCB0byBhZGQgbGFiZWxzIG9udG8gdGhlIGNoYXJ0LiBUaGlzIGlzIHdoZXJlIGBnZ3Bsb3RgIHJlYWxseSBkaXZlcmdlcyBmcm9tIEV4Y2VsIG9yIERvbW8gb3IgVGFibGVhdS4gV2UgZG9uJ3QganVzdCBzYXkgInNob3cgbGFiZWxzLiIgUmF0aGVyLCB3ZSB1c2UgYSBfZGlmZmVyZW50IGdlb21fIHRoYXQgd2UgImFkZCIgdG8gdGhlIHBsb3QuIFdlIHVzZSBgZ2VvbV90ZXh0KClgLCB3aGljaCByZXF1aXJlcyB0aHJlZSBtYXBwaW5nICgiYWVzdGhldGljIikgdmFsdWVzOgoKKiBUaGUgKip4KiogdmFsdWUgLS0gdGhpcyBtYXRjaGVzIHdoYXQgd2UgdXNlZCBmb3IgYGdlb21fYmFyKClgIGFuZCB3aGF0IHdlJ2Qgc2V0IGFzIHRoZSBkZWZhdWx0IGluIHRoZSBgZ2dwbG90KClgIGZ1bmN0aW9uIQoqIFRoZSAqKnkqKiB2YWx1ZSAod2hlcmUgdG8gcGxhY2UgdGhlIHRleHQpIC0tIE9NRyEgVGhpcyBtYXRjaGVzIHRoZSBkZWZhdWx0LCB0b28hCiogVGhlIGFjdHVhbCAqKnRleHQgdG8gZGlzcGxheSoqIC0tIFVoLW9oLiBXZSBoYXZlbid0IGRlZmluZWQgdGhhdCB5ZXQuIEJ1dC4uLndlIGp1c3Qgd2FudCB0byBkaXNwbGF5IHRoZSAqKnZhbHVlKiogb2YgeS4KCkxldCdzIGRvIHRoZSBiYXNpY3MgYW5kIHRoZW4gd29yayBvbiB0aGUgZmluZS10dW5pbmcuCgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9Cm15X3RoZW1lIDwtIGZ1bmN0aW9uKCl7CiAgdGhlbWVfbGlnaHQoKSArCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJPcGVuIFNhbnMiKSwgIAogICAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgY29sb3IgPSAiZ3JheTEwIikpCiAgfQoKZ2dwbG90KGNoYW5uZWxfZGF0YSwgYWVzKHggPSBNYXJrZXRpbmcuQ2hhbm5lbCwgeSA9IFNhbGVzKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBmaWxsID0gImdyYXk5MCIsICAgICAKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gU2FsZXMpKSArICAjIHggQU5EIHkgSU5IRVJJVEVELiBXRSBKVVNUIE5FRUQgVE8gU1BFQ0lGWSAibGFiZWwiCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwpteV90aGVtZSgpCmBgYAoKT2gsIGJveS4gVGhhdCdzIHRoZSBiYXNpYyBsYWJlbCwgYnV0LCBjbGVhcmx5LCBpdCBoYXMgc29tZSBwcm9ibGVtczoKCiogVGhlIGxhcmdlc3QgdmFsdWUgaXMgdHJ1bmNhdGVkCiogVGhlIHZhbHVlcyBhcmUgY2VudGVyZWQgb24gdGhlIGVuZAoqIFRoZXJlJ3Mgbm8gY29tbWEgaW4gdGhlICh0cnVuY2F0ZWQpIHZhbHVlCiogVGhlcmUncyBubyBwZXJjZW50YWdlCiogVGhlIGZvbnQgaXNuJ3QgcmlnaHQKClRoZXNlIGFyZSBhbGwgc29sdmFibGUhIEJ1dCwgdGhpcyBpbml0aWFsIHBhc3Mgd2FzIGEgZ29vZCBwbGFjZSB0byBzdGFydCwgYmVjYXVzZSwgaWYgeW91IHVuZGVyc3RhbmQgaG93IHdlIGdvdCB0byB0aGlzLCB0aGVuIHlvdSd2ZSBnb3QgYSBncmVhdCBncmFzcCBvbiBob3cgYGdncGxvdCgpYCBhbmQgYGdlb21zYCBhbmQgYGFlcygpYCBtYXBwaW5ncyB3b3JrIQoKV2hlcmUgdG8gYmVnaW4/IFVsdGltYXRlbHksIHdlIHdhbnQgb3VyIGxhYmVscyB0byBfZGlzcGxheV8gY2xlYW5seSwgc28gbGV0J3Mgc3RhcnQgYnkgZ2V0dGluZyB0aGUgX2NvbXBsZXRlIGxhYmVsc18uIEZvciB0aGlzLCB3ZSdyZSBhY3R1YWxseSBnb2luZyB0byBnbyBiYWNrIHRvIG91ciBkYXRhIHNldCBhbmQgYWRkIGEgY29sdW1uIHdpdGggbGFiZWxzIHRoYXQgYXJlIGJvdGggdGhlIHByb3Blcmx5IGZvcm1hdHRlZCBudW1iZXIgX2FuZF8gaGF2ZSB0aGUgcGVyY2VudGFnZS4gVGhpcyB3aWxsIGJlIGEgbWlub3IgZGlncmVzc2lvbiB3aGVyZSBJJ20gbm90IGFjdHVhbGx5IGdvaW5nIHRvIHJlLWRyYXcgdGhlIHBsb3QuIAoKV2hhdCB3ZSB3YW50IHRvIGRvIGlzIGNhbGN1bGF0ZSB0aGUgInBlcmNlbnQgb2YgdG90YWwiIGZvciBlYWNoIHJvdyBpbiBvdXIgZGF0YSBmcmFtZSwgYW5kIHRoZW4gY29tYmluZSB0aGF0IHdpdGggdGhlIGFjdHVhbCB2YWx1ZS4gVGhpcyBnZXRzIGludG8gYGRwbHlyYC1sYW5kLCBzbyBJJ20gbm90IGdvaW5nIHRvIGRlc2NyaWJlIHRvbyBtdWNoIG9mIHdoYXQgaXMgZ29pbmcgb24gZXhhY3RseS4gQXQgdGhpcyBwb2ludCwgd2UncmUgc3RhcnRpbmcgdG8gYWRkIHRvIG91ciBkYXRhLCB3aGljaCBJIF9jb3VsZF8gZG8gdG8gdGhlIG9yaWdpbmFsIGBjaGFubmVsX2RhdGFgIGRhdGEgZnJhbWUuIEJ1dCwgaXQncyBhIHNtYWxsIGRhdGEgZnJhbWUsIHNvIEknbSBnb2luZyB0byBjb3B5IGl0IHRvIGEgbmV3IG9uZSB0aGF0IEkgc3RhcnQgbXVuZ2luZyBmb3IgdGhlIGZpbmFsIHR3ZWFrcyB0byB0aGUgcGxvdC4gTGVhJ3MgY2hhcnQgZ29lcyB0byB0d28gZGVjaW1hbCBwbGFjZXMgKGV4Y2VwdCBmb3IgdGhlIDQxLjYlLCB3aGljaCBpcyA0MS42MCUsIHNvIHRoZSB6ZXJvIGdldHMgZHJvcHBlZCkuIEkgYWN0dWFsbHkgdGhpbmsgdGhpcyBzb3J0IG9mIGNoYXJ0IGlzIHByb2JhYmx5IGJlc3Qgc2VydmVkIHdpdGggX25vXyBkZWNpbWFsIHBsYWNlcy4gQnV0LCBzaW5jZSB0aGUgZXhlcmNpc2UgaXMgdG8gcmVwbGljYXRlLi4uIEknbSBnb2luZyB0byBzcGxpdCB0aGUgZGlmZmVyZW5jZSBhbmQganVzdCBnbyB3aXRoIGEgc2luZ2xlIGRlY2ltYWwgcGxhY2UgKHRoYXQncyB3aGF0IHRoZSBgIjEuZiUlImAgYmVsb3cgZG9lcyk6CgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CmZpbmFsX2RhdGEgPC0gY2hhbm5lbF9kYXRhICU+JSAKICAjIENhbGN1bGF0ZSAtLSBhbmQgZm9ybWF0IC0tIHRoZSBwZXJjZW50YWdlIGZvciBlYWNoIGNoYW5uZWwKICBtdXRhdGUoYmFyX3BlcmNlbnRhZ2UgPSBzcHJpbnRmKCIlLjFmJSUiLCAxMDAqKFNhbGVzIC8gc3VtKC4kU2FsZXMpKSkpICU+JSAKICAjIENvbWJpbmUgdGhhdCBwZXJjZW50YWdlIHdpdGggdGhlIGFjdHVhbCB2YWx1ZQogIG11dGF0ZShiYXJfbGFiZWwgPSBwYXN0ZTAoZm9ybWF0KFNhbGVzLCBiaWcubWFyayA9ICIsIiksICIsICIsIGJhcl9wZXJjZW50YWdlKSkgJT4lIAogICMgUmVtb3ZlIHRoZSAianVzdCB0aGUgcGVyY2VudGFnZSIgY29sdW1uCiAgc2VsZWN0KC1iYXJfcGVyY2VudGFnZSkKCiMgRGlzcGxheSB0aGUgdGFibGUKa2FibGUoZmluYWxfZGF0YSwgYWxpZ249YygnbCcsJ3InLCdyJykpCmBgYAoKR290IGl0PwoKTm93LCB0aGlua2luZyBiYWNrIHRvIG91ciBgZ2VvbV90ZXh0KClgIHNldHVwLCB3ZSByZWFsaXplIHdoeSB3ZSBuZWVkZWQgdG8gaGF2ZSBhICp5KiAocG9zaXRpb24uLi53aGljaCBpcyByZWFsbHkgdGhlICp4KiBwb3NpdGlvbiBpbiB0aGUgZmluYWwgcGxvdCB0aGFua3MgdG8gYGNvb3JkX2ZsaXAoKWApIHRoYXQgaXMgZGlmZmVyZW50IGZyb20gYGxhYmVsYC4gV2UncmUgc3RpbGwgZ29pbmcgdG8gaGF2ZSBsYXlvdXQgaXNzdWVzLCBidXQgbGV0J3MgbWFrZSBhIHN1YnRsZSB0d2VhayB0byBhdCBsZWFzdCBzZWUgbW9yZSByb2J1c3QgbGFiZWxzIGFwcGVhcmluZy4gQW5kLCBoZWNrLCB3aHkgbm90IGdvIGFoZWFkIGFuZCBzd2l0Y2ggdGhvc2UgdG8gdXNlIGBPcGVuIFNhbnNgIGF0IHRoZSBzYW1lIHRpbWU/CgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CiMgQWRkIGluIHRoZSBzdXBwbGVtZW50YWwgZGF0YSB0aGF0IHdlIG5lZWQKZmluYWxfZGF0YSA8LSBjaGFubmVsX2RhdGEgJT4lIAogIG11dGF0ZShiYXJfcGVyY2VudGFnZSA9IHNwcmludGYoIiUuMWYlJSIsIDEwMCooU2FsZXMgLyBzdW0oLiRTYWxlcykpKSkgJT4lIAogIG11dGF0ZShiYXJfbGFiZWwgPSBwYXN0ZTAoZm9ybWF0KFNhbGVzLCBiaWcubWFyayA9ICIsIiksICIsICIsIGJhcl9wZXJjZW50YWdlKSkgJT4lIAogIHNlbGVjdCgtYmFyX3BlcmNlbnRhZ2UpCgpteV90aGVtZSA8LSBmdW5jdGlvbigpewogIHRoZW1lX2xpZ2h0KCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiT3BlbiBTYW5zIiksICAKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCmdncGxvdChmaW5hbF9kYXRhLCBhZXMoeCA9IE1hcmtldGluZy5DaGFubmVsLCB5ID0gU2FsZXMpKSArICAjIFNXSVRDSEVEIEZST00gYGNoYW5uZWxfZGF0YWAgVE8gYGZpbmFsX2RhdGFgCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIGZpbGwgPSAiZ3JheTkwIiwgICAgIAogICAgICAgICAgIHdpZHRoID0gMC43NSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBiYXJfbGFiZWwpLCAgICAgIyBDSEFOR0VEIFRISVMgRlJPTSBgU2FsZXNgIFRPIGBiYXJfbGFiZWxgCiAgICAgICAgICAgIGZhbWlseSA9ICJPcGVuIFNhbnMiLCAgICAgICAjIFNQRUNJRklFRCBBIEZPTlQKICAgICAgICAgICAgc2l6ZSA9IDMuNSkgKyAgICAgICAgICAgICAgICMgU1BFQ0lGSUVEIEEgRk9OVCBTSVpFCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKwogIG15X3RoZW1lKCkKYGBgCgpXZSdyZSBnZXR0aW5nIHRoZXJlIQoKQnV0LCBvZiBjb3Vyc2UsIHdlIHdhbnQgdGhlIGxhYmVscyBfb3V0c2lkZV8gdGhlIGVuZCBvZiB0aGUgYmFyLiBCeSBkZWZhdWx0IHRoZSB0ZXh0IGlzIGNlbnRlcmVkLiBXZSB3YW50IHRvIGNoYW5nZSBpdCB0byBiZSBsZWZ0LWp1c3RpZmllZCwgd2hpY2ggd2UgY2FuIGRvIGJ5IGFkZGluZyBgaGp1c3QgPSAwYCBhcyBhbiBhcmd1bWVudC4gYGhqdXN0YCBhbmQgYHZqdXN0YCBjYW4gYmUgY29uZnVzaW5nLCBhbmQsIGluIGRpZmZlcmVudCBzaXR1YXRpb25zIHRoZXkgYXJlIC8gYXJlIG5vdCByZWNvbW1lbmRlZC4gSW4gaXRzIHNpbXBsZXN0IGFwcGxpY2F0aW9uIChsaWtlIGhlcmUpIGBoanVzdGAgZ2V0cyBzZXQgYmV0d2VlbiAwIGFuZCAxLCB3aGVyZSAwIGlzIGxlZnQtanVzdGlmaWVkIGFuZCAxIGlzIHJpZ2h0LWp1c3RpZmllZC4gQnV0LCB5b3UncmUgbm90IGNvbnN0cmFpbmVkIHRvIHNldHRpbmcgYSB2YWx1ZSBiZXR3ZWVuIDAgYW5kIDEsIHdoaWNoIGNhbiBkZWxpdmVyIHN1cnByaXNpbmcgKGJ1dCBzb21ldGltZXMgZGVzaXJlZCkgcmVzdWx0cy4gWW91IGNhbiBhbHNvICJudWRnZSIgdGV4dCwgd2hpY2ggcHJvdmlkZXMgYSBfbG90XyBvZiBjb250cm9sIG92ZXIgdGhlIGxheW91dC4gYnV0LCB3ZSBkb24ndCBuZWVkIHRvIGRvIHRoYXQgaGVyZS4KCmBgYHtyLCBmaWcud2lkdGg9Ny45LCBmaWcuaGVpZ2h0PTMuN30KIyBBZGQgaW4gdGhlIHN1cHBsZW1lbnRhbCBkYXRhIHRoYXQgd2UgbmVlZApmaW5hbF9kYXRhIDwtIGNoYW5uZWxfZGF0YSAlPiUgCiAgbXV0YXRlKGJhcl9wZXJjZW50YWdlID0gc3ByaW50ZigiJS4xZiUlIiwgMTAwKihTYWxlcyAvIHN1bSguJFNhbGVzKSkpKSAlPiUgCiAgbXV0YXRlKGJhcl9sYWJlbCA9IHBhc3RlMChmb3JtYXQoU2FsZXMsIGJpZy5tYXJrID0gIiwiKSwgIiwgIiwgYmFyX3BlcmNlbnRhZ2UpKSAlPiUgCiAgc2VsZWN0KC1iYXJfcGVyY2VudGFnZSkKCm15X3RoZW1lIDwtIGZ1bmN0aW9uKCl7CiAgdGhlbWVfbGlnaHQoKSArCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJPcGVuIFNhbnMiKSwgIAogICAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgY29sb3IgPSAiZ3JheTEwIikpCiAgfQoKZ2dwbG90KGZpbmFsX2RhdGEsIGFlcyh4ID0gTWFya2V0aW5nLkNoYW5uZWwsIHkgPSBTYWxlcykpICsgIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBmaWxsID0gImdyYXk5MCIsICAgICAKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gYmFyX2xhYmVsKSwKICAgICAgICAgICAgZmFtaWx5ID0gIk9wZW4gU2FucyIsCiAgICAgICAgICAgIHNpemUgPSAzLjUsICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgaGp1c3QgPSAwKSArICAgICAgICAjIENIQU5HRSBUSEUgIkhPUklaT05UQUwgSlVTVElGSUNBVElPTiIgVE8gQkUgTEVGVC1KVVNUSUZJRUQKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArCiAgbXlfdGhlbWUoKQpgYGAKCk5vdywgd2UndmUgZ290IHRoYXQgcGVza3kgY2xpcHBpbmcgdG8gZGVhbCB3aXRoLiBGcmFua2x5LCBgZ2dwbG90MmAgc3RhcnRzIHRvIGdldCBhbm5veWluZyBhdCB0aGlzIHBvaW50LiBUaGVyZSBhcmUgdmFyaW91cyB3YXlzIHRvIGFkZHJlc3MgdGhpcy4gT25lIG9wdGlvbiBpcyB0byB1c2UgYGhqdXN0ID0gImlud2FyZCJgIGluc3RlYWQgb2YgYGhqdXN0ID0gMGAsIHdoaWNoIHdvdWxkIHB1dCB0aGUgbGFiZWxzIGZvciB0aGUgbG9uZ2VyIGJhcnMgb24gdGhlIGluc2lkZSBvZiB0aGUgYmFycy4gQnV0LCBpZiB3ZSB3YW50IHRoZW0gYWxsIHRvIGJlIG9uIHRoZSBvdXRzaWRlLCB0aGVyZSBhcmUgdmFyaW91cyBjbHVua3kgYXBwcm9hY2hlcy4gVGhlIG9uZSB1c2VkIGJlbG93IGlzIHRvIGp1c3QgbWFrZSB0aGUgKGhpZGRlbikgeC1heGlzIDMwJSBsb25nZXIgdGhhbiB0aGUgbWF4aW11bSB2YWx1ZSB0byAibWFrZSByb29tIiBmb3IgdGhlIGxhYmVsLgoKYGBge3IsIGZpZy53aWR0aD03LjksIGZpZy5oZWlnaHQ9My43fQojIEFkZCBpbiB0aGUgc3VwcGxlbWVudGFsIGRhdGEgdGhhdCB3ZSBuZWVkCmZpbmFsX2RhdGEgPC0gY2hhbm5lbF9kYXRhICU+JSAKICBtdXRhdGUoYmFyX3BlcmNlbnRhZ2UgPSBzcHJpbnRmKCIlLjFmJSUiLCAxMDAqKFNhbGVzIC8gc3VtKC4kU2FsZXMpKSkpICU+JSAKICBtdXRhdGUoYmFyX2xhYmVsID0gcGFzdGUwKGZvcm1hdChTYWxlcywgYmlnLm1hcmsgPSAiLCIpLCAiLCAiLCBiYXJfcGVyY2VudGFnZSkpICU+JSAKICBzZWxlY3QoLWJhcl9wZXJjZW50YWdlKQoKbXlfdGhlbWUgPC0gZnVuY3Rpb24oKXsKICB0aGVtZV9saWdodCgpICsKICAgIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIk9wZW4gU2FucyIpLCAgCiAgICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBjb2xvciA9ICJncmF5MTAiKSkKICB9CgpnZ3Bsb3QoZmluYWxfZGF0YSwgYWVzKHggPSBNYXJrZXRpbmcuQ2hhbm5lbCwgeSA9IFNhbGVzKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBmaWxsID0gImdyYXk5MCIsICAgICAKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gYmFyX2xhYmVsKSwKICAgICAgICAgICAgZmFtaWx5ID0gIk9wZW4gU2FucyIsCiAgICAgICAgICAgIHNpemUgPSAzLjUsICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgaGp1c3QgPSAwKSArIAogIGNvb3JkX2ZsaXAoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgbWF4KGZpbmFsX2RhdGEkU2FsZXMpICogMS4zKSkgKyAgIyBNQUtFIFRIRSBYLUFYSVMgMzAlIExBUkdFUiBUSEFOIFRIRSBNQVggVkFMVUUKICBteV90aGVtZSgpCmBgYAoKVGhlIHNwYWNpbmcgb2YgdGhlICoqUGFpZCBTZWFyY2gqKiBsYWJlbCBpcyBraW5kIG9mIGNyYW1wZWQsIGlzbid0IGl0PyBJdCdzIGVhc3kgdG8gaGVhZCBkb3duIGludG8gYSByYWJiaXQgaG9sZSB0cnlpbmcgdG8gZmlndXJlIG91dCBob3cgdG8gYWRkcmVzcyB0aGlzICh3aXRob3V0IHdoYWNraW5nIHRoZSBvdGhlciBsYWJlbHMpLiBUaGF0IGNhbiBkZWZpbml0ZWx5IGhhcHBlbiB3aXRoIFIgZGF0YSB2aXN1YWxpemF0aW9ucyAtLSB0aGVyZSBpcyBfZW5vcm1vdXMgY29udHJvbF8uLi4gZXhjZXB0IHdoZW4gdGhlcmUgaXMgc29tZXRoaW5nIHRoYXQgc2VlbXMgbGlrZSBpdCBzaG91bGQgYmUgc2ltcGxlLCBhbmQgaXQncyBub3QuIEknbSBnb2luZyB0byBzYXkgd2UgY2FuIGxpdmUgd2l0aCB0aGlzIHNwYWNpbmcgZ2xpdGNoIGFuZCBtb3ZlIG9uLgoKIyMjIEFkZCBTb21lIENvbG9yCgpXaGF0J3MgbmV4dD8gTGV0J3MgZ28gYWhlYWQgYW5kIGZpbmlzaCB0aGUgY2hhcnQgYXJlYSBieSBoaWdobGlnaHRpbmcgdGhlICoqUGFpZCBTZWFyY2gqKiBiYXIuIE9yLCBsZXQncyBzYXkgd2Ugd2FudCB0byBoaWdobGlnaHQgdGhlIF9sYXJnZXN0XyBiYXIgKGJvdGggd291bGQgYmUgZG9uZSB0aGUgc2FtZSB3YXkgd2l0aCB0aGUgc2FtZSBiaXQgb2YgY29kZSB0d2Vha2luZykuIEp1c3QgYXMgd2l0aCBhZGRpbmcgbGFiZWxzLCB0aGlzIGlzIHdoZXJlIHRoZSAiZ3JhbW1hciBvZiBncmFwaGljcyIgKHdoaWNoIGBnZ3Bsb3QyYCBpcyBidWlsdCBvbikgcmVhbGx5IGRpdmVyZ2VzIGZyb20gb3RoZXIgcGxhdGZvcm1zIGNvbmNlcHR1YWxseS4gVG8gaGlnaGxpZ2h0IG9uZSBiYXIsIHdlIGFjdHVhbGx5IHdhbnQgdG8gcHV0IGFub3RoZXIgYGdlb21fYmFyKClgIF9vbiB0b3Agb2ZfIHRoZSBpbml0aWFsIG9uZS4gVGhpcyBuZXcgcGxvdCB3aWxsIGhhdmUgdGhlIGFjdHVhbCB2YWx1ZSBvZiB0aGUgbGFyZ2VzdCBiYXIsIGFuZCB3aWxsIHRoZW4gYmUgYDBgIChvciBjb3VsZCBiZSBgTkFgKSBmb3IgdGhlIG90aGVyIGNoYW5uZWxzLiBUaGF0IG1lYW5zIHdlIG5lZWQgdG8gYWRkIGEgbmV3IGNvbHVtbiB0byBvdXIgZGF0YToKCmBgYHtyLCBmaWcud2lkdGg9Ny45LCBmaWcuaGVpZ2h0PTMuN30KZmluYWxfZGF0YSA8LSBjaGFubmVsX2RhdGEgJT4lIAogIG11dGF0ZShiYXJfcGVyY2VudGFnZSA9IHNwcmludGYoIiUuMWYlJSIsIDEwMCooU2FsZXMgLyBzdW0oLiRTYWxlcykpKSkgJT4lIAogIG11dGF0ZShiYXJfbGFiZWwgPSBwYXN0ZTAoZm9ybWF0KFNhbGVzLCBiaWcubWFyayA9ICIsIiksICIsICIsIGJhcl9wZXJjZW50YWdlKSkgJT4lIAogIHNlbGVjdCgtYmFyX3BlcmNlbnRhZ2UpICU+JQogIG11dGF0ZShoaWdobGlnaHRfYmFyID0gaWZlbHNlKFNhbGVzID09IG1heCguJFNhbGVzKSwgU2FsZXMsIDApKSAgIyBBREQgQSBORVcgQ09MVU1OCgojIFF1aWNrIGxvb2sgYXQgdGhlIHVwZGF0ZWQgZGF0YQprYWJsZShmaW5hbF9kYXRhLCBhbGlnbiA9IGMoImwiLCAiciIsICJyIiwgInIiKSkKCiMgQW5kIG5vdyBiYWNrIHRvIG91ciB1c3VhbCBwbG90dGluZywgYnV0IHdpdGggYW4gZXh0cmEgZ2VvbV9iYXIoKQpteV90aGVtZSA8LSBmdW5jdGlvbigpewogIHRoZW1lX2xpZ2h0KCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiT3BlbiBTYW5zIiksICAKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCmdncGxvdChmaW5hbF9kYXRhLCBhZXMoeCA9IE1hcmtldGluZy5DaGFubmVsLCB5ID0gU2FsZXMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIGZpbGwgPSAiZ3JheTkwIiwgICAgIAogICAgICAgICAgIHdpZHRoID0gMC43NSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBiYXJfbGFiZWwpLAogICAgICAgICAgICBmYW1pbHkgPSAiT3BlbiBTYW5zIiwKICAgICAgICAgICAgc2l6ZSA9IDMuNSwgICAgICAgICAgICAgICAgIAogICAgICAgICAgICBoanVzdCA9IDApICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsICAgICAgICAgICAgICAgICAgICMgQURESVRJT05BTCBnZW9tX2JhcigpCiAgICAgICAgICAgbWFwcGluZyA9IGFlcyh5ID0gaGlnaGxpZ2h0X2JhciksICAgICMgVVNFUyBUSEUgaGlnaGxpZ2h0X2JhciBEQVRBCiAgICAgICAgICAgZmlsbCA9ICIjOTBjNGU0IiwgICAgICAgICAgICAgICAgICAgICMgSEVYIENPTE9SIC0gTUFUQ0hJTkcgV0hBVCBMRUEvRE9NTyBIQUQKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIG1heChmaW5hbF9kYXRhJFNhbGVzKSAqIDEuMykpICsgICMgTWFrZSB0aGUgeC1heGlzIDMwJSBsb25nZXIgdGhhbiB0aGUgbWF4CiAgbXlfdGhlbWUoKQoKYGBgCgpXZSdyZSBnZXR0aW5nIGNsb3NlciEKCiMjIyBGaW5hbCBUb3VjaGVzCgpUaGF0J3MgcHJldHR5IGNsb3NlIG9uIHRoZSBjaGFydCBpdHNlbGYuIEJ1dCwgd2hhdCBhYm91dCB0aGUgdGl0bGU/IExlYSdzIHBvc3QgaGFzIGEgZmV3IGRpZmZlcmVudCB2ZXJzaW9ucyBoZXJlLCBidXQgSSdtIGdvaW5nIHRvIGdvIGZvciBtYXRjaGluZyB0aGlzIG9uZToKCiFbXShpbWFnZXMvbGVhcGljYTEzLnBuZykKCiZuYnNwOzxicj4KTGV0J3Mgc3RhcnQgYnkgYWRkaW5nIHRoZSB0aXRsZSBhbmQgc3VidGl0bGUuIFRoZXJlIGFyZSB0d28gYXNwZWN0cyBvZiB0aGlzOgoKMS4gU3BlY2lmeWluZyB0aGUgX2NvbnRlbnRfIGZvciB0aG9zZSwgd2hpY2ggd2UgZG8gYnkgYWRkaW5nIGBnZ3RpdGxlKClgIHRvIG91ciBwbG90IChhbmQsIHNpbmNlIHRoZSBzdWJ0aXRsZSBoYXMgYSBjYWxjdWxhdGVkIHZhbHVlIGluIGl0LCB3ZSBkbyBhIGxpdHRsZSB3b3JrIHRvIGZpZ3VyZSBvdXQgd2hhdCB0aGF0IGlzIC0tIEkgYWN0dWFsbHkgbGlrZSB0aGlzLCBiZWNhdXNlIGl0IHNob3dzIF9tdWNoXyBtb3JlIGZsZXhpYmlsaXR5IGFzIHRvIHdoYXQgX2Nhbl8gYmUgZHluYW1pY2FsbHkgcHV0IGluIGEgdGl0bGUgdGhhbiwgc2F5LCBFeGNlbCBwcm92aWRlcykuCgoyLiBEZWZpbmluZyB0aGUgX3RoZW1lXyBmb3IgdGhlIHRpdGxlIGFuZCBzdWJ0aXRsZS4KCkluIGFkZGl0aW9uIHRvIG1ha2luZyB0aG9zZSBhZGRpdGlvbnMsIGxldCdzIGFsc28gYWRkIHNvbWUgd2hpdGUgc3BhY2UgdG8gdGhlIGxlZnQgb2YgdGhlIHBsb3QgdXNpbmcgYHBsb3QubWFyZ2luYCBpbiB0aGUgdGhlbWU6CgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CiMgTXVuZ2UgdGhlIGRhdGEKZmluYWxfZGF0YSA8LSBjaGFubmVsX2RhdGEgJT4lIAogIG11dGF0ZShiYXJfcGVyY2VudGFnZSA9IHNwcmludGYoIiUuMWYlJSIsIDEwMCooU2FsZXMgLyBzdW0oLiRTYWxlcykpKSkgJT4lIAogIG11dGF0ZShiYXJfbGFiZWwgPSBwYXN0ZTAoZm9ybWF0KFNhbGVzLCBiaWcubWFyayA9ICIsIiksICIsICIsIGJhcl9wZXJjZW50YWdlKSkgJT4lIAogIHNlbGVjdCgtYmFyX3BlcmNlbnRhZ2UpICU+JQogIG11dGF0ZShoaWdobGlnaHRfYmFyID0gaWZlbHNlKFNhbGVzID09IG1heCguJFNhbGVzKSwgU2FsZXMsIDApKQoKIyBEZWZpbmUgdGhlIHRoZW1lCm15X3RoZW1lIDwtIGZ1bmN0aW9uKCl7CiAgdGhlbWVfbGlnaHQoKSArCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJPcGVuIFNhbnMiKSwgIAogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGNvbG9yID0gImdyYXkzMCIpLCAgICMgU0VUIFVQIFRIRSBUSVRMRSBTVFlMRQogICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEsIGNvbG9yID0gImJsYWNrIiksICMgU0VUIFVQIFRIRSBTVUJUSVRMRSBTVFlMRQogICAgICAgICAgcGxvdC5tYXJnaW4gPSB1bml0KGMoMC41LDAsMCwyLjUpLCAiY20iKSwgICAgICAgICAgICAgICAgICMgQUREIFdISVRFIFNQQUNFIEFUIFRIRSBUT1AgQU5EIExFRlQKICAgICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksIGNvbG9yID0gImdyYXkxMCIpKQogIH0KCiMgQ2FsY3VsYXRlIHRoZSB0b3RhbCBzYWxlcyBhbmQgbWFrZSBpdCBuaWNlbHkgZm9ybWF0dGVkLiBUaGlzIGNvdWxkIGJlIGVtYmVkZGVkCiMgaW4gdGhlIGdndGl0bGUoKSBjYWxsLCBidXQgaXQgZ2V0cyBhIGxpdHRsZSBtZXNzeQp0b3RhbF9zYWxlcyA8LSBwYXN0ZTAoIlRvdGFsIFNhbGVzOiAiLAogICAgICAgICAgICAgICAgICAgICAgZm9ybWF0KHN1bShmaW5hbF9kYXRhJFNhbGVzKSwgYmlnLm1hcmsgPSAiLCIpKQoKIyBDcmVhdGUgdGhlIGFjdHVhbCBwbG90CmdncGxvdChmaW5hbF9kYXRhLCBhZXMoeCA9IE1hcmtldGluZy5DaGFubmVsLCB5ID0gU2FsZXMpKSArCiAgZ2d0aXRsZSgiV2hhdCBJcyBPdXIgQ291bnQgb2YgU2FsZXMgcGVyIE1hcmtldGluZyBDaGFubmVsPyIsICAjIEFERCBUSEUgVElUTEUgQU5EIFNVQlRJVExFCiAgICAgICAgICBzdWJ0aXRsZSA9IHRvdGFsX3NhbGVzKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFRPIFRIRSBQTE9UCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIGZpbGwgPSAiZ3JheTkwIiwgICAgIAogICAgICAgICAgIHdpZHRoID0gMC43NSkgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSBiYXJfbGFiZWwpLAogICAgICAgICAgICBmYW1pbHkgPSAiT3BlbiBTYW5zIiwKICAgICAgICAgICAgc2l6ZSA9IDMuNSwgICAgICAgICAgICAgICAgIAogICAgICAgICAgICBoanVzdCA9IDApICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsCiAgICAgICAgICAgbWFwcGluZyA9IGFlcyh5ID0gaGlnaGxpZ2h0X2JhciksCiAgICAgICAgICAgZmlsbCA9ICIjOTBjNGU0IiwKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLAogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIG1heChmaW5hbF9kYXRhJFNhbGVzKSAqIDEuMykpICsKICBteV90aGVtZSgpCmBgYAoKVGhhdCdzIGNsb3NlLi4uIGV4Y2VwdCB0aGUgdGl0bGUgaXMgYWxpZ25lZCB3aXRoIHRoZSBheGlzIChhbmQgbGVmdC1qdXN0aWZpZWQpIHJhdGhlciB0aGFuIHdpdGggdGhlIGVudGlyZSBsZWZ0IG9mIHRoZSBwbG90LiBJdCdzIGFjdHVhbGx5IHNvbWV0aGluZyBvZiBhIHJveWFsIHBhaW4gdG8gZnVsbHkgbGVmdC1qdXN0aWZ5IHRoZSB0aXRsZS4gSXQgaW52b2x2ZXMgdXNpbmcgImdyb2JzIiB3aGljaCwgaW4gbXkgZXhwZXJpZW5jZSB0byBkYXRlLCBhcmUgZXZlcnkgYml0IGFzIGhhY2t5IGFzIHRoZSB0ZXJtIGNvbm5vdGVzISBCdXQsIGlmIHlvdSB3YW50IHRvIGV4cGxvcmUgdGhhdCBvcHRpb24sIHlvdSBjYW4gcmVhZCBhYm91dCBpdCBbaGVyZV0oaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjU0MDExMTEvbGVmdC1hZGp1c3QtdGl0bGUtaW4tZ2dwbG90Mi1vci1hYnNvbHV0ZS1wb3NpdGlvbi1mb3ItZ2d0aXRsZSkuCgpBbmQsIHRoaXMgaXNuJ3Qgc2hvd2luZyB0aGUgdG90YWwgc2FsZXMgYXMgYSBCSUcsICoqQk9MRCoqIG51bWJlci4gVGhhdCwgdG9vLCBjb3VsZCBiZSBkb25lIHdpdGggc29tZSBncm9iYmluZywgYnV0IEkgYWN0dWFsbHkgYW0gcHJldHR5IGhhcHB5IHdpdGggaXQgaG93IGl0IGlzLgoKIyMjIEFkZCBJbnRlcmFjdGl2aXR5IQoKUGVyaGFwcyB5b3UndmUgYmVlbiB0aGlua2luZywgIldlbGwsIHRoYXQncyBhbGwgd2VsbCBhbmQgZ29vZC4gQnV0LCBpbiBEb21vLCBJJ3ZlIGdvdCBhIGNoYXJ0IHRoYXQgaGFzIHNvbWUgaW50ZXJhY3Rpdml0eSEgSSBjYW4gbW91c2Ugb3ZlciB0aGluZ3MgKHdoaWNoIGlzIG9uZSByZWFzb24sIHBlcmhhcHMsIHRoYXQgdGhlaXIgZGVmYXVsdCBjaGFydCBkb2VzIG5vdCBoYXZlIGxhYmVscyBkaXNwbGF5ZWQpLiBBbGwgeW91J3ZlIGRvbmUgaXMgZ2VuZXJhdGUgYSBzdGF0aWMgY2hhcnQhIgoKQXMgb25lIG9mIG15IGZhdm9yaXRlIHByb2Zlc3NvcnMgdXNlZCB0byBzYXk6ICJNYXliZSBzby4iCgpJdCdzIGFjdHVhbGx5IF9zdXBlcl8gZWFzeSB0byBhZGQgaW50ZXJhY3Rpdml0eSB0byB0aGUgY2hhcnQgd2UndmUgYmVlbiBidWlsZGluZy4gVGhlcmUgYXJlIHR3byBzdGVwcyB0byB0aGlzOgoKKiBFYWFhcnJycnJseSBvbiwgSSBzYWlkIHdlIHdvdWxkIG5vcm1hbGx5IGFzc2lnbiB0aGUgYWN0dWFsIGBnZ3Bsb3QoKWAgcGxvdCB0byBhbiBvYmplY3QgYW5kIHRoZW4ganVzdCBkaXNwbGF5IHRoZSBvYmplY3QuIFdlIGFjdHVhbGx5IG5lZWQgdG8gZG8gdGhhdCBub3cuIEJlY2F1c2UuLi4KKiBXZSdyZSBnb2luZyB0byB1c2UgYSBwYWNrYWdlIChvZiB3aGljaCB0aGVyZSBhcmUgc2V2ZXJhbCkgdGhhdCBhZGRzIGludGVyYWN0aXZpdHkgdG8gYGdncGxvdCgpYCBvYmplY3RzCgpgYGB7ciwgZmlnLndpZHRoPTcuOSwgZmlnLmhlaWdodD0zLjd9CiMgTXVuZ2UgdGhlIGRhdGEKZmluYWxfZGF0YSA8LSBjaGFubmVsX2RhdGEgJT4lIAogIG11dGF0ZShiYXJfcGVyY2VudGFnZSA9IHNwcmludGYoIiUuMWYlJSIsIDEwMCooU2FsZXMgLyBzdW0oLiRTYWxlcykpKSkgJT4lIAogIG11dGF0ZShiYXJfbGFiZWwgPSBwYXN0ZTAoZm9ybWF0KFNhbGVzLCBiaWcubWFyayA9ICIsIiksICIsICIsIGJhcl9wZXJjZW50YWdlKSkgJT4lIAogIHNlbGVjdCgtYmFyX3BlcmNlbnRhZ2UpICU+JQogIG11dGF0ZShoaWdobGlnaHRfYmFyID0gaWZlbHNlKFNhbGVzID09IG1heCguJFNhbGVzKSwgU2FsZXMsIDApKQoKIyBEZWZpbmUgdGhlIHRoZW1lCm15X3RoZW1lIDwtIGZ1bmN0aW9uKCl7CiAgdGhlbWVfbGlnaHQoKSArCiAgICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJPcGVuIFNhbnMiKSwgIAogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMsIGNvbG9yID0gImdyYXkzMCIpLCAgIAogICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTEsIGNvbG9yID0gImJsYWNrIiksCiAgICAgICAgICBwbG90Lm1hcmdpbiA9IHVuaXQoYygwLjUsMCwwLDIuNSksICJjbSIpLAogICAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgY29sb3IgPSAiZ3JheTEwIikpCn0KCiMgQ2FsY3VsYXRlIHRoZSB0b3RhbCBzYWxlcyBhbmQgbWFrZSBpdCBuaWNlbHkgZm9ybWF0dGVkLiAKdG90YWxfc2FsZXMgPC0gcGFzdGUwKCJUb3RhbCBTYWxlczogIiwKICAgICAgICAgICAgICAgICAgICAgIGZvcm1hdChzdW0oZmluYWxfZGF0YSRTYWxlcyksIGJpZy5tYXJrID0gIiwiKSkKCiMgQ3JlYXRlIHRoZSBhY3R1YWwgcGxvdC4gTm90ZSB0aGUgc3VidGxlIGNoYW5nZSB0aGF0IHRoZSB3aG9sZSBwbG90IGRlZmluaXRpb24gaXMgbm93CiMgYmVpbmcgKmFzc2lnbmVkKiB0byBgbXlfcGxvdGAKbXlfcGxvdCA8LSBnZ3Bsb3QoZmluYWxfZGF0YSwgYWVzKHggPSBNYXJrZXRpbmcuQ2hhbm5lbCwgeSA9IFNhbGVzKSkgKwogIGdndGl0bGUoIldoYXQgSXMgT3VyIENvdW50IG9mIFNhbGVzIHBlciBNYXJrZXRpbmcgQ2hhbm5lbD8iLCAgCiAgICAgICAgICBzdWJ0aXRsZSA9IHRvdGFsX3NhbGVzKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBmaWxsID0gImdyYXk5MCIsICAgICAKICAgICAgICAgICB3aWR0aCA9IDAuNzUpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gYmFyX2xhYmVsKSwKICAgICAgICAgICAgZmFtaWx5ID0gIk9wZW4gU2FucyIsCiAgICAgICAgICAgIHNpemUgPSAzLjUsICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgaGp1c3QgPSAwKSArIAogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLAogICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeSA9IGhpZ2hsaWdodF9iYXIpLAogICAgICAgICAgIGZpbGwgPSAiIzkwYzRlNCIsCiAgICAgICAgICAgd2lkdGggPSAwLjc1KSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCBtYXgoZmluYWxfZGF0YSRTYWxlcykgKiAxLjMpKSArCiAgbXlfdGhlbWUoKQoKIyBMb2FkIHRoZSBsaWJyYXJ5IChub3JtYWxseSB3ZSB3b3VsZCBkbyB0aGlzIGF0IHRoZSB2ZXJ5IGJlZ2lubmluZykgdGhhdCB3ZSdyZSBnb2luZyB0byB1c2UKIyB0byBhZGQgaW50ZXJhY3Rpdml0eS4KbGlicmFyeShwbG90bHkpCgojIFBsb3QgdGhlIHJlc3VsdApnZ3Bsb3RseShteV9wbG90LCB3aWR0aCA9IDc1MCwgaGVpZ2h0ID0gMzUwKQpgYGAKCldlIG5vdyBoYXZlIGFuIGludGVyYWN0aXZlIGNoYXJ0ISBNb3VzZSBvdmVyIHRoZSBiYXJzISBDbGljayBhcm91bmQgb24gc29tZSBvZiB0aGUgb3B0aW9ucyBpbiB0aGUgbWVudSBhdCB0aGUgdG9wIHJpZ2h0ICh0aGVzZSBhcmUuLi5wcmV0dHkgdXNlbGVzcyBpbiBhIGNoYXJ0IGFzIHNpbXBsZSBhcyB0aGlzLCBidXQgaWxsdXN0cmF0aXZlIG5vbmV0aGVsZXNzKS4gVW5mb3J0dW5hdGVseSwgd2UgYWxzbyBsb3N0IG91ciBkYXRhIGxhYmVscywgdGhlIGNoYW5uZWwgbmFtZXMgZ290IGEgYml0IGNyb3dkZWQgdXAgdG8gdGhlaXIgYmFycywgYW5kIHRoZSB0aXRsZSBpc24ndCBsZWZ0LWp1c3RpZmllZC4gVGhlcmUgYXJlIF9zb21lXyBzZXR0aW5ncyBmb3IgYGdncGxvdGx5KClgIHRoYXQgeW91IGNhbiBmaWRkbGUgd2l0aCB0byBtYWtlIHVwIGZvciBpbmZvcm1hdGlvbiB0aGF0IGdldHMgbG9zdCBvciBkcm9wcGVkLCBidXQgdGhpcyB3YXMgdGhlIGJlc3QgSSB3YXMgYWJsZSB0byBkbyBoZXJlLiBTbywgSSdkIHByb2JhYmx5IGp1c3Qgc3RpY2sgd2l0aCB0aGUgc3RhdGljIGNoYXJ0LgoKIyMjIFRha2Vhd2F5cwoKSG93IGRpZCB3ZSBkbz8gVGhlIGVuZCByZXN1bHQgKHRoZSBzdGF0aWMgb25lLCBhdCBsZWFzdCksIHNlZW1zIF9wcmV0dHkgZGFybiBjbG9zZV8gdG8gdGhlIG9yaWdpbmFsLiBUaGUgbWFpbiBkaWZmZXJlbmNlIGlzIHRoZSBsYXlvdXQgb2YgdGhlIHN1YnRpdGxlIHdpdGggdGhlIHRvdGFsIHNhbGVzIGZpZ3VyZS4gQW5kLCB0aGF0J3Mgbm90IGFuIGluc3VybW91bnRhYmxlIHR3ZWFrIF9pZl8gdGhhdCB3YXMsIHRydWx5LCBleGFjdGx5IHdoYXQgSSB3YW50ZWQuIAoKKipTdW1tYXJpemluZyB0aGUgYXBwcm9hY2ggd2l0aCBEb21vOioqIHN0YXJ0IHdpdGggYSBwcmV0dHkgZ29vZCBjaGFydCwgYW5kIHRoZW4gdHdlYWsvbWFzc2FnZSBpdCBhIGJpdCwgd2l0aGluIHRoZSBjb25zdHJhaW50cyBvZiB0aGUgcGxhdGZvcm0gKHRoZSAidGhyZWUgZm9udCBzaXplcyIgc2VlbXMgcHJldHR5IGxpbWl0aW5nKQoKKipTdW1tYXJpemluZyB0aGUgYXBwcm9hY2ggd2l0aCBgZ2dwbG90MmAgaW4gUjoqKiBJJ20gZ29pbmcgdG8gcHV0IHRoaXMgaW4gYSBsaXN0IGZvcm06CgoqIFN0YXJ0IHdpdGggYSBmYWlybHkgaGlkZW91cyByZW5kZXJpbmcgb2YganVzdCB0aGUgY29yZSBkYXRhCiogVXNlIGEgdGhlbWUgKHRoYXQgY2FuIGJlIHJldXNlZCBhY3Jvc3MgbWFueSB2aXN1YWxpemF0aW9ucykgdG8gYWRqdXN0IHdoaWNoIGVsZW1lbnRzIGFyZSBkaXNwbGF5ZWQgYW5kIGhvdwoqIExheWVyIG9uIGFkZGl0aW9uYWwgImdlb21zIiB0byBhZGQgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiB0byB0aGUgcGxvdC4gU29tZSBvZiB0aGVzZSByZXF1aXJlIGNyZWF0aW5nIGRlcml2YXRpdmVzIChvciAibXV0YXRpb25zIiBpbiBgZHBseXJgLXNwZWFrKSBvZiB0aGUgb3JpZ2luYWwgZGF0YQoKRmlkZGxlIGFuZCBleHBlcmltZW50IGFsb25nIHRoZSB3YXkuCgpUaGUgY29kZSAtLSBhbmQgdGhlIHVuZGVybHlpbmcgImdyYW1tYXIgb2YgZ3JhcGhpY3MiIHBhcmFkaWdtIHRoYXQgYGdncGxvdGAgdXNlcyAtLSBjYW4gc2VlbSBpbnRpbWlkYXRpbmcuIEl0IGlzISBBbmQsIGZvciBhIHNpbXBsZSBob3Jpem9udGFsIGJhciBjaGFydCwgaXQgY2FuIHNlZW0gdW5uZWNlc3NhcmlseSB1bndpZWxkeS4gQnV0LCBob3BlZnVsbHksIGl0J3MgYWxzbyBhcHBhcmVudCB0aGF0IHRoZXJlIGlzIF9hIGxvdF8gb2YgcG93ZXIgaW4gdGhpcyB0ZXh0LWJhc2VkIGRlZmluaXRpb24gb2YgYSBjaGFydC4KCgoK