# loading libraries
library(DBI)
library(RPostgres)
library(ggplot2)
library(forcats)
library(stringi)
library(kableExtra)
library(RColorBrewer)
library(treemapify)
library(knitr)
library(tidyverse)
library(tidygeocoder)
library(formatR)

Dataset

The Global Superstore dataset contains data of transactions, each row represents transaction item of products sold around the world by Superstore, also contains a customer information, shipping and product returns.

Metadata

The dataset contains the following tables:

Table Orders

Variable name Data type Description
Row ID Integer Unique ID for each row.
Order ID String Unique Order ID for each Customer.
Order Date Date Order Date of the product.
Ship Date Date Shipping Date of the Product.
Ship Mode String Shipping Mode specified by the Customer.
Customer ID String Unique ID to identify each Customer.
Customer Name String Name of the Customer.
Segment String The segment where the Customer belongs.
City String City of residence of of the Customer.
State String State of residence of the Customer.
Country String Country of residence of the Customer.
Postal Code String Postal Code of residence of the Customer.
Market String Market name.
Region String Region where the Customer belong.
Product ID String Unique ID of the Product.
Category String Category of the product ordered.
Sub-Category String Sub-Category of the product ordered.
Product Name String Name of the Product
Sales Decimal Sales of the Product.
Quantity Integer Quantity of the Product.
Discount Decimal Discount provided.
Profit Decimal Profit/Loss incurred.
Shipping Cost Decimal Shipping cost.
Order Priority String Order priority.

Table Returns

Variable name Data type Description
Order ID String Unique Order ID for each Customer.
Returned Bool The flag, indicates that the order was returned.
Market String Market name.

Data loading

# DB connection
con <- dbConnect(
  RPostgres::Postgres(), dbname = "superstore_global",
  host = "localhost", port = 5432, user = "postgres",
  password = ""
)

For analysis for used PostgreSQL database.

-- NOTE: you need to execute the code below on DB side (R Markdown chunk eval=FALSE).

-- Create a new database
CREATE DATABASE superstore_global;
-- schema for raw data
CREATE SCHEMA raw_data;
-- create table Orders for raw data
CREATE TABLE raw_data.superstore_orders
(
    row_id         VARCHAR(5),
    order_id       VARCHAR(15),
    order_date     VARCHAR(10),
    ship_date      VARCHAR(10),
    ship_mode      VARCHAR(14),
    customer_id    VARCHAR(8),
    customer_name  VARCHAR(22),
    segment        VARCHAR(11),
    city           VARCHAR(35),
    state          VARCHAR(36),
    country        VARCHAR(32),
    postal_code    VARCHAR(5),
    market         VARCHAR(6),
    region         VARCHAR(14),
    product_id     VARCHAR(16),
    category       VARCHAR(15),
    sub_category   VARCHAR(11),
    product_name   VARCHAR(127),
    sales          VARCHAR(10),
    quantity       VARCHAR(2),
    discount       VARCHAR(5),
    profit         VARCHAR(21),
    shipping_cost  VARCHAR(8),
    order_priority VARCHAR(8)
);
-- NOTE: you need to execute the code below on DB side (R Markdown chunk eval=FALSE).

-- create table Returns for raw data
CREATE TABLE raw_data.superstore_returns
(
    returned VARCHAR(3),
    order_id VARCHAR(15),
    market   VARCHAR(13)
);

Prepare data in Excel. Save each sheet as CSV file: + Sheet Orders into data/Global Superstore_orders.csv. + Sheet Returns into data/Global Superstore_returns.csv.

library(readxl)
library(fs)

file_name <- "Global Superstore (raw data).xls"
sheets <- readxl::excel_sheets(paste0("./data/", file_name))
CSVs <- c()
for (sheet in sheets[1:2]) {
  df <- readxl::read_xls(
    paste0("./data/", file_name),
    col_names = TRUE, sheet = sheet
  )
  csv_file <- paste0(
    getwd(), "/data/", "Global Superstore_", sheet,
    ".csv"
  )
  CSVs <- append(CSVs, csv_file)
  write.csv(df, csv_file, row.names = FALSE)
}

orders_csv <- CSVs[1]
returns_csv <- CSVs[2]

Now load data as-is into database. Loading raw data.

-- orders
COPY raw_data.superstore_orders FROM ?orders_csv DELIMITER ',' CSV HEADER;
-- returns
COPY raw_data.superstore_returns FROM ?returns_csv DELIMITER ',' CSV HEADER;

Data cleansing

Create schema analysis for cleaned data.

-- schema for data cleansing
CREATE SCHEMA analysis;

Create tables for cleaned data.

CREATE TABLE analysis.orders
(
    row_id         INTEGER,
    order_id       VARCHAR(15),
    order_date     DATE,
    ship_date      DATE,
    ship_mode      VARCHAR(14),
    customer_id    VARCHAR(8),
    customer_name  VARCHAR(22),
    segment        VARCHAR(11),
    city           VARCHAR(35),
    state          VARCHAR(36),
    country        VARCHAR(32),
    postal_code    VARCHAR(5),
    market         VARCHAR(6),
    region         VARCHAR(14),
    product_id     VARCHAR(16),
    category       VARCHAR(15),
    sub_category   VARCHAR(11),
    product_name   VARCHAR(127),
    sales          NUMERIC(9, 2),
    quantity       SMALLINT,
    discount       NUMERIC(4, 2),
    profit         NUMERIC(9, 2),
    shipping_cost  NUMERIC(9, 2),
    order_priority VARCHAR(8)
);
CREATE TABLE analysis.returns
(
    returned BOOLEAN,
    order_id VARCHAR(15),
    market   VARCHAR(13)
);

Convert data types.

-- Orders table
INSERT INTO analysis.orders
SELECT CAST(row_id AS INTEGER)                                 AS row_id,
       order_id,
       TO_DATE(order_date, 'YYYY-MM-DD')                       AS order_date,
       TO_DATE(ship_date, 'YYYY-MM-DD')                        AS ship_date,
       ship_mode,
       customer_id,
       customer_name,
       segment,
       city,
       state,
       country,
       postal_code,
       market,
       region,
       product_id,
       category,
       sub_category,
       product_name,
       CAST(REPLACE(sales, ',', '.') AS NUMERIC(9, 2))         AS sales,
       CAST(quantity AS SMALLINT)                              AS quantity,
       CAST(REPLACE(discount, ',', '.') AS NUMERIC(4, 2))      AS discount,
       CAST(REPLACE(profit, ',', '.') AS NUMERIC(9, 2))        AS profit,
       CAST(REPLACE(shipping_cost, ',', '.') AS NUMERIC(9, 2)) AS shipping_cost,
       order_priority
FROM raw_data.superstore_orders;
-- Returns table
INSERT INTO analysis.returns
SELECT CAST(returned AS BOOLEAN) AS returned,
       order_id,
       market
FROM raw_data.superstore_returns;

Check Returns table.


SELECT orders.market, returns.market
FROM
(SELECT DISTINCT market FROM analysis.orders ORDER BY 1) AS orders
FULL JOIN
(SELECT DISTINCT market FROM analysis.returns ORDER BY 1) AS returns
ON orders.market = returns.market;
# print result
check_returns

Fix market name in Returns table.


UPDATE analysis.returns
SET market = 'US'
WHERE market = 'United States';

Duplication Data

Check duplicated pairs of order_id and market in returns table.


SELECT order_id, market, count(*) FROM analysis.returns GROUP BY 1, 2 HAVING count(*) > 1;
# print result
duplidated_order_id

-- duplicated data
SELECT
    row_id,
    order_id,
    order_date,
    ship_date,
    ship_mode,
    customer_id,
    customer_name,
    segment,
    city,
    state,
    country,
    postal_code,
    market,
    region,
    product_id,
    category,
    sub_category,
    product_name,
    sales,
    quantity,
    discount,
    profit,
    shipping_cost,
    order_priority, count(*)
FROM analysis.orders
GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24
having count(*) > 1;
# print results
orders_full_duplicates

Check duplicates without row_id.


SELECT
    order_id,
    order_date,
    ship_date,
    ship_mode,
    customer_id,
    customer_name,
    segment,
    city,
    state,
    country,
    postal_code,
    market,
    region,
    product_id,
    category,
    sub_category,
    product_name,
    sales,
    quantity,
    discount,
    profit,
    shipping_cost,
    order_priority, count(*)
FROM analysis.orders
GROUP BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23
having count(*) > 1;
# print results
orders_full_duplicates

Missing Values


select count(1) FILTER ( WHERE row_id is NULL ) AS row_id,
       count(1) FILTER ( WHERE order_id IS NULL ) AS order_id,
       count(1) FILTER ( WHERE order_date IS NULL ) AS order_date,
       count(1) FILTER ( WHERE ship_date IS NULL ) AS ship_date,
       count(1) FILTER ( WHERE ship_mode IS NULL ) AS ship_mode,
       count(1) FILTER ( WHERE customer_id IS NULL ) AS customer_id,
       count(1) FILTER ( WHERE customer_name IS NULL ) AS customer_name,
       count(1) FILTER ( WHERE segment IS NULL ) AS segment,
       count(1) FILTER ( WHERE city IS NULL ) AS city,
       count(1) FILTER ( WHERE state IS NULL ) AS state,
       count(1) FILTER ( WHERE country IS NULL ) AS country,
       count(1) FILTER ( WHERE postal_code IS NULL ) AS postal_code,
       count(1) FILTER ( WHERE market IS NULL ) AS market,
       count(1) FILTER ( WHERE region IS NULL ) AS region,
       count(1) FILTER ( WHERE product_id IS NULL ) AS product_id,
       count(1) FILTER ( WHERE category IS NULL ) AS category,
       count(1) FILTER ( WHERE sub_category IS NULL ) AS sub_category,
       count(1) FILTER ( WHERE product_name IS NULL ) AS product_name,
       count(1) FILTER ( WHERE sales IS NULL ) AS sales,
       count(1) FILTER ( WHERE quantity IS NULL ) AS quantity,
       count(1) FILTER ( WHERE discount IS NULL ) AS discount,
       count(1) FILTER ( WHERE profit IS NULL ) AS profit,
       count(1) FILTER ( WHERE shipping_cost IS NULL ) AS shipping_cost,
       count(1) FILTER ( WHERE order_priority IS NULL ) AS order_priority
FROM analysis.orders;
# print results
missing_values

Preparation for analysis

Make materialized view.


-- materialized view
CREATE MATERIALIZED VIEW IF NOT EXISTS analysis.all_global_orders AS
    SELECT  o.*, CASE
                    WHEN r.returned IS NULL THEN FALSE
                    ELSE r.returned
                 END AS returned
    FROM analysis.orders o
        LEFT JOIN analysis.returns r ON o.order_id = r.order_id AND
                                        o.market = r.market;

-- refresh the view to full it with data
REFRESH MATERIALIZED VIEW analysis.all_global_orders;

Analysis

# setup plots theme
theme_set(theme_light())
theme_update(
  axis.title = element_text(face = "bold"),
  plot.title = element_text(face = "bold"),
  panel.grid.major = element_line(color = "darkgray", linetype = "dotted"),
  panel.grid.minor = element_blank(), panel.background = element_blank(),
  panel.border = element_rect(color = "darkgray"),
  plot.margin = unit(
    c(0.5, 1, 1, 1),
    "lines"
  )
)

Total Revenue and Gross Margin


SELECT DATE_PART('year', order_date)::INT                     AS year,
       market,
       round(SUM(sales), 2)                                   AS sales,
       round(SUM(profit) FILTER (WHERE NOT returned), 2)      AS profit, -- exclude returns
       round(SUM(profit) FILTER (WHERE NOT returned) / SUM(sales), 2) AS margin
FROM analysis.all_global_orders
GROUP BY 1, 2
ORDER BY year, sales desc;
    
# print result
revenue_margin
scale <- mean(revenue_margin$sales)
offset <- mean(revenue_margin$sales)

ggplot(revenue_margin) +
  geom_bar(
    aes(x = market, y = sales, fill = factor(year)),
    stat = "identity", position = position_dodge2(width = 0.9)
  ) +
  geom_bar(
    aes(x = market, y = profit, fill = "Profit"),
    stat = "identity", position = position_dodge2(width = 0.9)
  ) +
  geom_line(
    aes(
      x = market, y = offset + margin * scale,
      group = market, color = "Gross Margin"
    ),
    position = position_dodge2(width = 0.9)
  ) +
  geom_point(
    aes(
      x = market, y = offset + margin * scale,
      color = "Gross Margin"
    ),
    position = position_dodge2(width = 0.9),
    shape = 15
  ) +
  geom_text(
    aes(
      x = market, y = offset + margin * scale,
      label = scales::percent(margin, accuracy = 1L)
    ),
    vjust = -1.2, position = position_dodge2(width = 0.9),
    size = 2
  ) +
  geom_vline(
    xintercept = seq(
      1.5, length(unique(revenue_margin$market)),
      1
    ),
    linetype = "dotted", color = "gray"
  ) +
  scale_y_continuous(
    labels = scales::label_number(scale = 0.001, suffix = "K", accuracy = 1L),
    limits = c(0, 1300000),
    breaks = seq(0, 1300000, 1e+05)
  ) +
  scale_color_manual(name = "", values = c(`Gross Margin` = "black")) +
  scale_fill_manual(
    name = "Sales", values = c(
      Profit = "#003f5c", `2011` = "#58508d",
      `2012` = "#bc5090", `2013` = "#ff6361",
      `2014` = "#ffa600"
    )
  ) +
  labs(
    title = "Total Revenue and Gross Margin", y = "Sales & Profit, $",
    x = "Market"
  ) +
  theme(
    panel.grid.major = element_blank(), legend.position = "top"
  )

Total Gross & Net Revenue and YoY Profit Growth


SELECT year:: INT AS year,
       round(sales, 2) AS sales,
       round(returned_sales, 2) AS returned_sales,
       round(profit, 2) AS profit,
       round((profit - lag(profit, 1) OVER (ORDER BY year))/lag(profit, 1) OVER (ORDER BY year), 2) AS yoy_profit_growth
FROM
(SELECT
      DATE_PART('year', order_date) AS year,
      sum(sales) FILTER ( WHERE NOT returned ) AS sales,
      sum(sales) FILTER ( WHERE returned ) AS returned_sales,
      sum(profit) FILTER ( WHERE NOT returned ) AS profit -- exclude returns
FROM
(SELECT distinct order_id,
       first_value(order_date) over(PARTITION BY order_id) AS order_date,
       sum(sales) over(PARTITION BY order_id) AS sales,
       sum(profit) over(PARTITION BY order_id) AS profit ,
       first_value(returned) over(PARTITION BY order_id) AS returned
FROM analysis.all_global_orders) a
GROUP BY 1
ORDER BY 1) a
ORDER BY year;
    
# print results
yoy_profit_growth
scale <- max(yoy_profit_growth$sales)
offset <- mean(yoy_profit_growth$sales)

ggplot(yoy_profit_growth) +
  geom_bar(
    aes(x = year, y = sales, fill = "Gross Revenue"),
    stat = "identity", width = 0.5
  ) +
  geom_bar(
    aes(
      x = year, y = sales - returned_sales, fill = "Net Revenue"
    ),
    stat = "identity", width = 0.45
  ) +
  geom_bar(
    aes(x = year, y = profit, fill = "Profit"),
    stat = "identity", width = 0.45
  ) +
  geom_line(
    aes(
      x = year, y = yoy_profit_growth * scale,
      group = 1, color = "YoY Profit Growth"
    )
  ) +
  geom_point(
    aes(
      x = year, y = yoy_profit_growth * scale,
      color = "YoY Profit Growth"
    ),
    shape = 15
  ) +
  geom_text(
    aes(
      x = year, y = yoy_profit_growth * scale,
      label = scales::percent(yoy_profit_growth, accuracy = 1L)
    ),
    vjust = -1.2, size = 4
  ) +
  scale_color_manual(
    name = "", values = c(`YoY Profit Growth` = "black")
  ) +
  scale_fill_manual(
    name = "", values = c(
      Profit = "#003f5c", `Gross Revenue` = "#bc5090",
      `Net Revenue` = "#ffa600"
    )
  ) +
  scale_y_continuous(
    labels = scales::label_dollar(scale = 1e-06, suffix = "M", accuracy = 0.1),
    limits = c(0, 4500000),
    breaks = seq(0, 4500000, 5e+05)
  ) +
  labs(
    title = "Total Gross & Net Revenue and YoY Profit Growth",
    subtitle = "Trend lines represent year over year profit growth witch demonstrates positive growth\nthree years in a row.",
    y = "", x = ""
  ) +
  theme(
    legend.position = "top", legend.title = element_blank()
  )

Quarterly Net Revenue growth


-- Trend of quarterly revenue growth
SELECT *,
       (row_number() OVER (ORDER BY year, quarter))::INT AS n,
       concat('Q', quarter, '''', substring(cast(year as TEXT), 3, 4)) AS label,
       round((sales - lag(sales) OVER (ORDER BY year, quarter))/lag(sales) OVER (ORDER BY year, quarter), 2) AS growth
FROM
(SELECT year,
       quarter,
       sum(sales)  FILTER (WHERE NOT returned) AS sales,
       sum(sales) AS gross_sales
    FROM
      (SELECT *,
          extract(quarter from order_date) as quarter,
          extract(year from order_date) as year
        FROM analysis.all_global_orders) a
        GROUP BY 1, 2
        ORDER BY 1, 2) b
ORDER BY 1, 2;
# print results
quarterly_revenue
reorder_value <- function(x, value, decreasing) {
  a <- array(value)
  names(a) <- x
  if (decreasing) {
    factor(x, levels = names(sort(a, decreasing = TRUE)))
  } else {
    factor(x, levels = names(sort(a)))
  }
}

scale <- max(quarterly_revenue$sales)
ggplot(
  quarterly_revenue, aes(x = reorder_value(label, n, FALSE))
) +
  geom_bar(
    aes(y = sales, fill = "Net Revenue"),
    stat = "identity"
  ) +
  geom_text(
    aes(
      y = sales, label = scales::dollar(
        sales, scale = 1e-06, suffix = "M",
        accuracy = 0.1
      )
    ),
    vjust = -1.3, size = 3
  ) +
  geom_line(
    aes(
      y = growth * scale, group = 1, color = "Q/Q Growth"
    ),
    size = 1
  ) +
  scale_fill_manual(name = "", values = c(`Net Revenue` = "#003f5c")) +
  scale_color_manual(name = "", values = c(`Q/Q Growth` = "#ffa600")) +
  geom_vline(
    xintercept = seq(4.5, 16, 4),
    linetype = "dotted"
  ) +
  scale_y_continuous(
    labels = scales::label_dollar(scale = 1e-06, suffix = "M", accuracy = 0.1),
    limits = c(-9e+05, 1600000),
    breaks = seq(0, 1600000, 250000),
    sec.axis = sec_axis(
      trans = ~./scale, labels = scales::label_percent(),
      breaks = seq(-0.6, 1, 0.2)
    )
  ) +
  labs(
    title = "Q/Q Net Revenue growth", subtitle = "Trend line of the growth represents potentionaly of dropping of the revenue\ngrowth dynamic in Q1'15.",
    y = "", x = ""
  ) +
  theme(
    legend.position = "top", legend.title = element_blank()
  )

Total number of orders and loss orders


SELECT market,
             year,
             sum(sales) as sales,
             COUNT(DISTINCT order_id)::INT                               AS total_orders,
             (COUNT(DISTINCT order_id) FILTER ( WHERE profit < 0 ))::INT AS loss_orders
      FROM (SELECT market,
                   EXTRACT(YEAR FROM order_date) AS year,
                   order_id,
                   SUM(sales)                    AS sales,
                   SUM(profit)                   AS profit
            FROM analysis.all_global_orders
            GROUP BY 1, 2, 3) a
      GROUP BY 1, 2
      ORDER BY 2, 3 desc
# print results
orders

Visualize data for 2014 year.

ggplot(orders[orders$year == 2014, ]) +
  geom_col(
    aes(
      reorder(market, sales, decreasing = TRUE),
      sales, fill = "Revenue"
    ),
    position = "dodge2", show.legend = TRUE, alpha = 0.9
  ) +
  geom_segment(
    aes(
      x = reorder(market, sales),
      y = 0, xend = reorder(market, sales),
      yend = total_orders * 1e+06/max(orders[orders$year == 2014, ]$total_orders),
      color = "Total Orders"
    ),
    linetype = "solid"
  ) +
  geom_point(
    aes(
      reorder(market, sales, decreasing = TRUE),
      total_orders * 1e+06/max(orders[orders$year == 2014, ]$total_orders)
    ),
    shape = 95, size = 3, color = "black"
  ) +
  geom_col(
    aes(
      reorder(market, sales, decreasing = TRUE),
      loss_orders * 1e+06/max(orders[orders$year == 2014, ]$total_orders),
      fill = "Loss Orders"
    ),
    width = 0.1
  ) +
  scale_fill_manual(
    name = "", values = c(Revenue = "#ececec", `Loss Orders` = "red")
  ) +
  scale_color_manual(name = "", values = c(`Total Orders` = "black")) +
  theme(
    axis.text.x = element_text(color = "gray12", size = 12),
    legend.position = "top", panel.background = element_rect(fill = "white", color = "white")
  ) +
  scale_y_continuous(
    labels = scales::label_number(accuracy = 0.01, scale = 1e-06, suffix = "M"),
    limits = c(
      0, max(orders[orders$year == 2014, ]$sales) *
        1.1
    ),
    expand = c(0, 0),
    breaks = seq(
      0, max(orders[orders$year == 2014, ]$sales) *
        1.1, 250000
    ),
    sec.axis = sec_axis(
      trans = ~./1e+06 * max(orders[orders$year == 2014, ]$total_orders),
      name = "Number of Orders", breaks = seq(0, 2000, 250)
    )
  ) +
  labs(
    title = "Number of loss orders for 2014", y = "Revenue, $",
    x = "Market"
  ) +
  guides(
    color = guide_legend(override.aes = list(fill = "white")),
    linetype = guide_legend(override.aes = list(fill = "white"))
  )

Customer Lifetime Value (CLV)


SELECT market,
       segment,
       year,
       round(CLV, 2) AS CLV,
       round((CLV - lag(CLV) OVER (PARTITION BY market, segment ORDER BY year))/lag(CLV) OVER (PARTITION BY market, segment ORDER BY year), 2) AS growth
FROM
(SELECT a.market,
       a.segment,
       a.year,
       a.customer_value * b.CLS AS CLV
  FROM (SELECT a.market,
               a.segment,
               a.year,
               a.sales,
               b.unique_customers,
               a.sales / b.unique_customers AS customer_value
        FROM (SELECT market,
                     segment,
                     EXTRACT(YEAR FROM order_date) AS year,
                     SUM(sales)                    AS sales
              FROM analysis.all_global_orders
              GROUP BY 1, 2, 3
              ORDER BY 1, 2, 3) a
                 LEFT JOIN
             (SELECT market,
                     segment,
                     EXTRACT(YEAR FROM order_date) AS year,
                     COUNT(DISTINCT customer_id)   AS unique_customers
              FROM analysis.all_global_orders
              GROUP BY 1, 2, 3
              ORDER BY 1, 2, 3) b ON a.market = b.market AND a.segment = b.segment AND a.year = b.year) a
           INNER JOIN
       (SELECT market,
               segment,
               year,
               DATE_PART('year', SUM(days))::decimal / COUNT(DISTINCT customer_id) AS CLS
        FROM (SELECT DISTINCT market,
                              segment,
                              year,
                              customer_id,
                              MIN(order_date) OVER (PARTITION BY customer_id)       AS first_date,
                              MAX(order_date) OVER (PARTITION BY year, customer_id) AS last_date,
                              AGE(MAX(order_date) OVER (PARTITION BY year, customer_id),
                                  MIN(order_date) OVER (PARTITION BY customer_id))  AS days
  
              FROM (SELECT *,
                           EXTRACT(YEAR FROM order_date) AS year
                    FROM analysis.all_global_orders) a) b
        GROUP BY 1, 2, 3) b ON
                   a.market = b.market AND
                   a.segment = b.segment AND
                   a.year = b.year) a;
# print result
CLV
ggplot(CLV) +
  geom_bar(
    aes(x = year, y = clv, fill = segment),
    stat = "identity", position = position_dodge()
  ) +
  geom_line(
    aes(
      x = year, y = growth * mean(
        CLV[CLV$market == market & CLV$segment ==
          segment, "clv"]
      ),
      color = "YoY Growth"
    )
  ) +
  geom_point(
    aes(
      x = year, y = growth * mean(
        CLV[CLV$market == market & CLV$segment ==
          segment, "clv"]
      ),
      color = "YoY Growth"
    ),
    size = 1
  ) +
  geom_text(
    aes(
      x = year, y = growth * mean(
        CLV[CLV$market == market & CLV$segment ==
          segment, "clv"]
      ),
      label = scales::percent(growth, accuracy = 1L),
      color = "YoY Growth"
    ),
    vjust = -1, size = 2
  ) +
  labs(
    title = "Customer Lifetime Value", x = "",
    y = "Customer Lifetime Value, $"
  ) +
  scale_fill_manual(
    name = "", values = c(
      Consumer = "#ff6361", Corporate = "#bc5090",
      `Home Office` = "#ffa600"
    )
  ) +
  scale_color_manual(name = "", values = c(`YoY Growth` = "black")) +
  facet_grid(market ~ segment, scales = "free_y") +
  theme(
    legend.position = "top", legend.title = element_blank(),
    strip.background = element_blank(), strip.text = element_text(color = "black", face = "bold")
  )

Quarterly Customer Retention Rate


WITH cte_orders AS (
SELECT DISTINCT
       order_id,
       customer_id,
       order_date,
       market,
       segment,
       min(order_date) OVER (PARTITION BY customer_id, market) AS first_date
FROM
    analysis.all_global_orders
ORDER BY customer_id, market, order_date),
    cte_orders_split AS (
SELECT *,
       extract(YEAR FROM order_date) AS order_year,
       extract(QUARTER FROM order_date) AS order_q,
       extract(YEAR FROM first_date) AS first_year,
       extract(QUARTER FROM first_date) AS first_q
FROM cte_orders)

SELECT year, quarter,
       market, segment,
       uniq_customers::INT,
       new_customers::INT,
       n::INT,
       CONCAT('Q', quarter, '''', SUBSTRING(TEXT(year), 3, 2))               AS qy,
       round((uniq_customers-coalesce(new_customers, 0))::decimal/lag(uniq_customers) OVER (PARTITION BY market, segment ORDER BY n), 2) AS ret_rate
FROM
(SELECT order_year AS year,
       order_q AS quarter,
       a.market,
       a.segment,
       a.uniq_customers,
       b.new_customers,
       row_number() OVER (PARTITION BY a.market, a.segment ORDER BY a.order_year, a.order_q) n
FROM
(SELECT order_year,
       order_q,
       market,
       segment,
       count(DISTINCT customer_id) uniq_customers
FROM cte_orders_split
GROUP BY 1, 2, 3, 4) a
LEFT JOIN
(SELECT first_year,
       first_q,
       market,
       segment,
       count(DISTINCT customer_id) new_customers
FROM cte_orders_split
GROUP BY 1, 2, 3, 4) b ON a.order_year = b.first_year AND
                          a.order_q = b.first_q AND
                          a.market = b.market AND
                          a.segment = b.segment
) a;
# prinit results
customer_retention
ggplot(customer_retention) +
  geom_line(
    aes(
      x = reorder(qy, n),
      y = ret_rate, group = 1, color = segment
    ),
    width = 2, show.legend = FALSE
  ) +
  scale_y_continuous(labels = scales::label_percent()) +
  geom_vline(
    aes(xintercept = 4.5),
    linetype = "dotted"
  ) +
  geom_vline(
    aes(xintercept = 8.5),
    linetype = "dotted"
  ) +
  geom_vline(
    aes(xintercept = 12.5),
    linetype = "dotted"
  ) +
  annotate(
    geom = "text", label = 2011, x = 2.5, y = 0.1,
    color = "lightgray"
  ) +
  annotate(
    geom = "text", label = 2012, x = 6.5, y = 0.1,
    color = "lightgray"
  ) +
  annotate(
    geom = "text", label = 2013, x = 10.5, y = 0.1,
    color = "lightgray"
  ) +
  annotate(
    geom = "text", label = 2014, x = 14.5, y = 0.1,
    color = "lightgray"
  ) +
  scale_x_discrete(
    labels = rep(
      paste0("Q", 1:4),
      4
    )
  ) +
  facet_grid(market ~ segment) +
  theme(
    legend.position = "top", legend.title = element_blank(),
    strip.background = element_blank(), strip.text = element_text(color = "black", face = "bold")
  ) +
  labs(
    title = "Quarterly Customer Retention Rate",
    x = "", y = "Retention Rate"
  )

ARPPU and Average check value


SELECT (date_trunc('month', order_date))::date AS last_month_day,
       count(DISTINCT customer_id)::INT AS uniq_customers,
       count(DISTINCT order_id)::INT AS count_orders,
       round(sum(sales)/count(DISTINCT customer_id), 2) AS arppu,
       round(sum(sales)/count(DISTINCT order_id), 2) AS avg_check
FROM analysis.all_global_orders -- from materialized view
GROUP BY 1
ORDER BY 1;
# print results
arppu_check
ggplot(arppu_check) +
  geom_bar(
    aes(x = last_month_day, y = arppu, fill = "ARPPU"),
    stat = "identity"
  ) +
  geom_line(
    aes(
      x = last_month_day, y = avg_check, color = "Avg. check"
    ),
    size = 1
  ) +
  geom_point(
    aes(
      x = last_month_day, y = avg_check, color = "Avg. check"
    ),
    shape = 18, size = 3
  ) +
  geom_smooth(
    aes(x = last_month_day, y = avg_check),
    method = "lm", color = "red", linetype = "dashed",
    se = FALSE
  ) +
  geom_smooth(
    aes(x = last_month_day, y = arppu),
    method = "lm", color = "blue", linetype = "dashed",
    se = FALSE
  ) +
  labs(
    title = "Average revenue per paying user (ARPPU) and average check value\nfrom 2011 to 2014",
    y = "ARPPU & Avg.check value, $", x = ""
  ) +
  scale_fill_manual(name = "", values = c(ARPPU = "#003f5c")) +
  scale_color_manual(name = "", values = c(`Avg. check` = "#ffa600")) +
  theme(legend.position = "top")

Average Basket Value and size


-- avg basket value and size
WITH order_cost AS (
    SELECT market,
       (date_trunc('month', order_date))::date AS last_month_day,
       segment,
       order_id,
       round(sum(sales), 2) as sales,
       round(sum(quantity), 2) as quantity
    FROM analysis.all_global_orders
    GROUP BY 1, 2, 3, 4
    ORDER BY 2, 1, 3, 4
    )


select *,
       round(min(basket_value) over (partition by market) / max(basket_size) over (partition by market), 2) AS scale
       from
(SELECT
    market,
    last_month_day,
    round(avg(sales), 2) AS basket_value,
    round(avg(quantity), 0) AS basket_size
    FROM order_cost
GROUP BY 1, 2) a;
# print results
basket_value
ggplot(basket_value) +
  geom_line(
    aes(
      x = last_month_day, y = basket_value, group = market
    )
  ) +
  geom_segment(
    aes(
      x = last_month_day, xend = last_month_day,
      y = 0, yend = basket_size * scale
    ),
    color = "red"
  ) +
  geom_point(
    aes(
      x = last_month_day, y = basket_size * scale,
      size = ifelse(
        basket_size <= 3, 2, basket_size -
          2
      )
    ),
    shape = 21, color = "red", fill = "white",
    show.legend = FALSE
  ) +
  geom_text(
    aes(
      x = last_month_day, y = basket_size * scale,
      label = basket_size, size = ifelse(
        basket_size <= 3, 2, basket_size -
          2
      )
    ),
    color = "black", show.legend = FALSE
  ) +
  geom_smooth(
    aes(x = last_month_day, y = basket_value),
    method = "lm", se = FALSE, size = 1
  ) +
  facet_grid(market ~ ., scales = "free_y") +
  theme(
    legend.position = "top", legend.title = element_blank(),
    strip.background = element_blank(), strip.text = element_text(color = "black", face = "bold")
  ) +
  labs(
    title = "Average basket value and size", x = "",
    y = "Basket Value, $"
  )

Return Rate


-- return rate
SELECT extract(year from order_date) AS year,
       round(sum(sales), 2) AS total_sales,
       round(sum(sales) FILTER (WHERE returned), 2) AS returned_sales,
       count(distinct order_id)::INT AS count_orders,
       (count(distinct order_id) FILTER (WHERE returned))::INT AS returned_orders,
       round((count(distinct order_id) FILTER (WHERE returned))::decimal/count(distinct order_id), 3) as return_rate
FROM analysis.all_global_orders
GROUP BY 1;
# print results
returns
ggplot(returns) +
  geom_bar(
    aes(x = year, y = total_sales, fill = "Sales"),
    stat = "identity", position = position_dodge(width = 0.8)
  ) +
  geom_bar(
    aes(
      x = year, y = returned_sales, fill = "Returned Sales"
    ),
    stat = "identity", position = position_dodge(width = 0.8)
  ) +
  geom_point(
    aes(
      x = year, y = return_rate * total_sales,
      color = "Return Rate"
    )
  ) +
  geom_text(
    aes(
      x = year, y = return_rate * total_sales,
      label = scales::percent(return_rate, accuracy = 0.1)
    ),
    vjust = -1.5
  ) +
  labs(title = "Return rate", y = "Sales, $", x = "") +
  scale_y_continuous(
    labels = scales::label_number(scale = 1e-06, suffix = "M")
  ) +
  scale_fill_manual(
    name = "", values = c(Sales = "#dbe5da", `Returned Sales` = "#bbcbbc")
  ) +
  scale_color_manual(name = "", values = c(`Return Rate` = "black")) +
  theme(legend.position = "top")

What most significant sub-categories?


WITH cte_sales AS(
SELECT extract(year from order_date) as year,
       sub_category,
       segment,
       sum(sales) as sales,
       sum(profit) as profit
from analysis.all_global_orders
group by 1, 2, 3)

select *,
       round(sum(sales/total_sales) over(partition by year, segment order by sales desc rows BETWEEN
            unbounded preceding and current row), 2) as cum_percent
       from
(select year, sub_category, segment,
       sales, sum(sales) over (PARTITION BY year, segment) as total_sales
from cte_sales
order by 1, 2, 3, 4 desc) a;
# print results
pareto_sales
for (segment in unique(pareto_sales$segment)) {
  scale <- max(
    pareto_sales[pareto_sales$year == 2014 & pareto_sales$segment ==
      segment, ]$sales
  )
  x_intercept <- approx(
    pareto_sales[pareto_sales$year == 2014 & pareto_sales$segment ==
      segment, ]$cum_percent * scale, fct_reorder(
      pareto_sales[pareto_sales$year == 2014 &
        pareto_sales$segment == segment, ]$sub_category,
      -pareto_sales[pareto_sales$year == 2014 &
        pareto_sales$segment == segment, ]$sales
    ),
    0.8 * scale
  )$y

  g <- ggplot(
    pareto_sales[pareto_sales$year == 2014 & pareto_sales$segment ==
      segment, ]
  ) +
    geom_area(
      aes(
        x = fct_reorder(sub_category, -sales),
        y = cum_percent * scale, group = 1
      ),
      fill = "#ececec"
    ) +
    geom_bar(
      aes(
        x = fct_reorder(sub_category, -sales),
        y = sales
      ),
      stat = "identity", fill = "#bbcbbc"
    ) +
    geom_line(
      aes(
        x = fct_reorder(sub_category, -sales),
        y = cum_percent * scale, group = 1
      )
    ) +
    geom_point(
      aes(
        x = fct_reorder(sub_category, -sales),
        y = cum_percent * scale
      )
    ) +
    geom_text(
      aes(
        x = fct_reorder(sub_category, -sales),
        y = cum_percent * scale, label = scales::percent(cum_percent, accuracy = 0.1)
      ),
      size = 2, vjust = -1.5
    ) +
    scale_y_continuous(
      sec.axis = sec_axis(
        trans = ~./scale, labels = scales::label_percent(),
        breaks = seq(0, 1, 0.2)
      ),
      breaks = seq(0, scale, 25000),
      limits = c(0, scale * 1.1),
      labels = scales::label_number(scale = 0.001, suffix = "K")
    ) +
    scale_x_discrete(guide = guide_axis(angle = 45)) +
    geom_segment(
      x = 0, xend = Inf, y = 0.8 * scale, yend = 0.8 *
        scale, color = "blue", size = 0.25,
      linetype = "dashed"
    ) +
    geom_segment(
      x = x_intercept, xend = x_intercept, y = -Inf,
      yend = 0.8 * scale, color = "blue", size = 0.25,
      linetype = "dashed"
    ) +
    labs(
      title = paste0(
        "Sales by sub-category in ", segment,
        " customer segment for 2014"
      ),
      x = "Sub-Category", y = "Sales, $", subtitle = paste0(
        "80% of the total revenue comes from most significant sub-categories\nsuch as ",
        stri_replace_last(
          paste(
          as.character(
            fct_reorder(
            pareto_sales[pareto_sales$year ==
              2014 & pareto_sales$segment ==
              segment, ]$sub_category,
            -pareto_sales[pareto_sales$year ==
              2014 & pareto_sales$segment ==
              segment, ]$sales
          )[seq(
            1, round(8.27, 0),
            1
          )]
          ),
          collapse = ", "
        ),
          fixed = ",", " &"
        )
      )
    )
  print(g)
}

Map


SELECT market,
       region,
       country,
       sum(sales) AS sales
FROM analysis.all_global_orders
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3;
# print results
map
# add geocodes
world <- map_data("world")
mapp <- map %>%
  geocode(
    country = country, method = "osm", lat = latitude,
    long = longitude
  )

centroids <- mapp %>%
  group_by(market) %>%
  summarise(
    longitude = mean(longitude, na.rm = TRUE),
    latitude = mean(latitude, na.rm = TRUE),
    sales = sum(sales, na.rm = TRUE)
  )

ggplot() + geom_map(
  data = world, map = world, aes(long, lat, map_id = region),
  color = "darkgray", fill = "#ececec", size = 0.1
) +
  geom_point(
    data = centroids, aes(
      x = longitude, y = latitude, size = sales,
      color = market
    ),
    show.legend = FALSE, alpha = 0.75
  ) +
  scale_size(range = c(1, 20)) +
  geom_text(
    data = centroids, aes(
      x = longitude, y = latitude, label = scales::dollar(
        sales, scale = 1e-06, suffix = "M",
        accuracy = 0.1
      )
    ),
    size = 2, vjust = 1.5
  ) +
  geom_text(
    data = centroids, aes(x = longitude, y = latitude, label = market),
    size = 2, vjust = -0.5, fontface = "bold"
  ) +
  labs(
    title = "Total revenue per market from 2011 to 2014",
    x = "", y = ""
  ) +
  theme(
    axis.line = element_blank(), axis.text.x = element_blank(),
    axis.text.y = element_blank(), axis.ticks = element_blank(),
    axis.title.x = element_blank(), axis.title.y = element_blank(),
    legend.position = "none", panel.background = element_blank(),
    panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
    plot.background = element_blank()
  )

Top 10 countries by Sales Volume


select * from
(select *,
       round((sales - lag(sales) over (PARTITION BY market, country  ORDER BY year))/
       lag(sales) over (PARTITION BY market, country  ORDER BY  year), 2) as YoY_Growth_Sales,
       round((profit - lag(profit) over (PARTITION BY market, country  ORDER BY year))/
       lag(profit) over (PARTITION BY market, country  ORDER BY  year), 2) as YoY_Growth_Profit
from
(select market,
       country,
       extract(year from order_date) as year,
       sum(sales) as sales,
       sum(profit) as profit
from analysis.all_global_orders
group by 1, 2, 3) a) a
where year = 2014 ORDER BY sales desc limit 10;
# print results
top_sales_countries
top_sales_countries %>%
  mutate_at(
    c("yoy_growth_sales", "yoy_growth_profit"),
    ~cell_spec(
      scales::percent(., accuracy = 0.1),
      "html", color = case_when(
        . < 0 ~ "red", . >= 0 ~ "green", is.na(.) ~
          "lightgray", TRUE ~ "black"
      )
    )
  ) %>%
  mutate_at(
    c("sales", "profit"),
    ~scales::dollar(., accuracy = 0.01)
  ) %>%
  relocate(yoy_growth_sales, .after = sales) %>%
  select(-year) %>%
  kbl(
    col.names = c(
      "Market", "Country", "Total", "YoY Growth",
      "Total", "YoY Growth"
    ),
    caption = "Top 10 countries by Sales Volume for 2014",
    escape = F, align = c("llrrrr")
  ) %>%
  row_spec(0, align = "c") %>%
  kable_styling(
    bootstrap_options = c("striped", "bordered"),
    full_width = TRUE
  ) %>%
  add_header_above(
    header = c(" ", " ", Sales = 2, Profit = 2),
    bold = TRUE, border_left = TRUE, border_right = TRUE
  )
Top 10 countries by Sales Volume for 2014
Sales
Profit
Market Country Total YoY Growth Total YoY Growth
US United States $733,946.97 21.0% $93,507.97 14.0%
APAC Australia $314,733.41 17.0% $32,030.37 11.0%
EU France $308,437.39 34.0% $35,142.08 8.0%
APAC China $218,979.31 12.0% $46,793.98 5.0%
EU Germany $216,537.21 47.0% $35,956.07 33.0%
APAC India $205,032.00 35.0% $48,807.66 48.0%
LATAM Mexico $195,479.76 23.0% $31,330.88 12.0%
EU United Kingdom $194,005.10 56.0% $36,755.55 33.0%
APAC Indonesia $145,334.84 40.0% $10,527.21 1 124.0%
LATAM Brazil $119,772.15 29.0% $15,422.79 681.0%
countries_coords <- top_sales_countries %>%
  geocode(
    country = country, method = "osm", lat = latitude,
    long = longitude
  )

ggplot() + geom_map(
  data = world, map = world, aes(long, lat, map_id = region),
  color = "darkgray", fill = "#ececec", size = 0.1
) +
  geom_point(
    data = countries_coords, aes(
      x = longitude, y = latitude, size = sales,
      color = market, alpha = 0.5
    ),
    show.legend = FALSE
  ) +
  scale_size(range = c(1, 20)) +
  geom_text(
    data = countries_coords, aes(
      x = longitude, y = latitude, label = scales::dollar(
        sales, scale = 0.001, suffix = "K",
        accuracy = 0.1
      )
    ),
    size = 2, vjust = 1.5
  ) +
  geom_text(
    data = countries_coords, aes(x = longitude, y = latitude, label = country),
    size = 1, vjust = -0.5, fontface = "bold"
  ) +
  labs(
    title = "Top 10 countries by Sales Volume for 2014",
    x = "", y = ""
  ) +
  theme(
    axis.line = element_blank(), axis.text.x = element_blank(),
    axis.text.y = element_blank(), axis.ticks = element_blank(),
    axis.title.x = element_blank(), axis.title.y = element_blank(),
    legend.position = "none", panel.background = element_blank(),
    panel.grid.major = element_blank(), panel.grid.minor = element_blank(),
    plot.background = element_blank()
  )

Sales analysis heat map by day and month


SELECT *,
       count_orders::numeric / (SUM(count_orders) OVER ()) AS proc
FROM (SELECT month,
             n_month,
             wod,
             n_wod,
             COUNT(DISTINCT order_id)::INT count_orders
      FROM (SELECT DISTINCT order_id,
                EXTRACT(MONTH FROM FIRST_VALUE(order_date) OVER (PARTITION BY order_id))  AS n_month,
                TO_CHAR(FIRST_VALUE(order_date) OVER (PARTITION BY order_id), 'Month')    AS month,
                EXTRACT(ISODOW FROM FIRST_VALUE(order_date) OVER (PARTITION BY order_id)) AS n_wod,
                TO_CHAR(FIRST_VALUE(order_date) OVER (PARTITION BY order_id), 'Day')      AS wod
            FROM analysis.all_global_orders
            ORDER BY n_month, n_wod) a
      GROUP BY month, n_month, wod, n_wod) a
ORDER BY n_month, n_wod;
# print results
heat
heat %>%
  mutate(
    wod = as.factor(wod),
    month = as.factor(month)
  ) %>%
  mutate(
    wod = fct_reorder(wod, n_wod),
    month = fct_reorder(month, -n_month)
  ) %>%
  ggplot(aes(x = wod, y = month, fill = proc)) +
  geom_tile(show.legend = FALSE) +
  scale_fill_gradient(high = "red", low = "white", na.value = "white") +
  scale_x_discrete(position = "top") +
  labs(
    title = "Sales heat map by day of week and month from 2011 to 2014",
    x = "Weekday", y = "Month"
  )

Top 5 returnable sub-categories by merket


SELECT a.market,
       a.new_sub_category AS sub_category,
       SUM(a.perc)        AS perc
FROM (SELECT a.market,
             a.perc,
             CASE
                 WHEN RANK() OVER (PARTITION BY market ORDER BY perc DESC) <= 5 THEN sub_category
                 ELSE 'Other'
                 END AS new_sub_category
      FROM (SELECT DISTINCT market,
                            sub_category,
                            ROUND(COUNT(*) OVER (PARTITION BY market, sub_category) /
                                  COUNT(*) OVER (PARTITION BY market)::numeric, 3) AS perc
            FROM analysis.all_global_orders
            WHERE returned
            ORDER BY market, perc DESC) a) a
GROUP BY market, sub_category
ORDER BY market, perc DESC;
# print results
returnable_sub_category
for (market in unique(returnable_sub_category$market)) {
  print(
    returnable_sub_category[returnable_sub_category$market ==
      market, ] %>%
      ggplot(aes(x = "", y = perc, fill = sub_category)) +
      geom_col() + geom_text(
      aes(label = scales::percent(perc)),
      size = 3, position = position_stack(vjust = 0.5)
    ) +
      coord_polar(theta = "y") +
      labs(
        title = paste(
          "Top 5 returnable sub-categories in",
          market, "market"
        ),
        x = "", y = "", fill = "Sub-Category"
      ) +
      theme(
        axis.text = element_blank(), axis.ticks = element_blank(),
        panel.grid = element_blank(), panel.grid.major = element_blank(),
        panel.border = element_blank()
      )
  )

}

What are number of total and new customers?


WITH cte_timespot AS (SELECT m.market, a.year, a.quarter
                      FROM (SELECT DISTINCT market FROM analysis.all_global_orders) m
                               CROSS JOIN
                           (SELECT b.year, a.quarter
                            FROM (SELECT UNNEST(ARRAY [1, 2, 3, 4]) AS quarter) a
                                     CROSS JOIN
                                     (SELECT UNNEST(ARRAY [2011, 2012, 2013, 2014]) AS year) b
                            ORDER BY 1, 2) a
                      ORDER BY 1, 2, 3),
     cte_unique_customers AS (SELECT market,
                                     EXTRACT(YEAR FROM order_date)    AS year,
                                     EXTRACT(QUARTER FROM order_date) AS quarter,
                                     COUNT(DISTINCT customer_id)      AS unique_customers
                              FROM analysis.all_global_orders
                              GROUP BY 1, 2, 3),
     cte_new_customers AS (SELECT market,
                                  year,
                                  quarter,
                                  COUNT(DISTINCT customer_id) AS new_customers
                           FROM (SELECT DISTINCT market,
                                                 customer_id,
                                                 FIRST_VALUE(EXTRACT(YEAR FROM order_date))
                                                 OVER (PARTITION BY market, customer_id ORDER BY EXTRACT(YEAR FROM order_date))                                   AS year,
                                                 FIRST_VALUE(EXTRACT(QUARTER FROM order_date))
                                                 OVER (PARTITION BY market, customer_id ORDER BY EXTRACT(YEAR FROM order_date), EXTRACT(QUARTER FROM order_date)) AS quarter
                                 FROM analysis.all_global_orders) a
                           GROUP BY market, year, quarter)
SELECT a.market,
       a.year,
       a.quarter,
       a.unique_customers::INT,
       COALESCE(b.new_customers, 0)::INT AS new_customers,
       CONCAT('Q', a.quarter, '''', SUBSTRING(TEXT(a.year), 3, 2))               AS qy,
       ROW_NUMBER() OVER (PARTITION BY a.market ORDER BY a.year, a.quarter)::INT AS n
FROM cte_timespot t
         LEFT JOIN
     cte_unique_customers a ON t.market = a.market AND t.year = a.year AND t.quarter = a.quarter
         LEFT JOIN
     cte_new_customers b ON a.market = b.market AND a.year = b.year AND a.quarter = b.quarter;
# print results
new_total_customers
for (market in unique(new_total_customers$market)) {
  plot <- ggplot(
    new_total_customers[new_total_customers$market ==
      market, ]
  ) +
    geom_bar(
      aes(
        x = fct_reorder(qy, n),
        y = unique_customers, fill = "Unique Customers"
      ),
      stat = "identity"
    ) +
    geom_line(
      aes(
        x = fct_reorder(qy, n),
        y = new_customers, group = 1, color = "New Customers"
      ),
      size = 1
    ) +
    geom_point(
      aes(
        x = fct_reorder(qy, n),
        y = new_customers, color = "New Customers"
      ),
      shape = 18, size = 3
    ) +
    geom_text(
      aes(
        x = fct_reorder(qy, n),
        y = new_customers, label = scales::number(new_customers)
      ),
      size = 3, vjust = -1.2
    ) +
    geom_vline(
      xintercept = seq(4.5, 12.5, 4),
      linetype = "dotted", color = "gray"
    ) +
    scale_fill_manual(
      name = "", values = c(`Unique Customers` = "#e5bc8b")
    ) +
    scale_color_manual(
      name = "", values = c(`New Customers` = "#003f5c")
    ) +
    scale_y_continuous(
      limits = c(
        0, max(
          new_total_customers[new_total_customers$market ==
          market, ]$unique_customers
        ) *
          1.1
      )
    ) +
    labs(
      title = paste(
        "Total unique and new customers in",
        market, "market"
      ),
      x = "", y = "Number of customers"
    ) +
    theme(
      legend.position = "top", panel.grid.major = element_blank()
    )

  print(plot)
}

Percentage of new business revenue generation


WITH cte_all_orders AS (SELECT market,
                               order_id,
                               order_date,
                               customer_id,
                               SUM(sales) AS sales
                        FROM analysis.all_global_orders
                        GROUP BY 1, 2, 3, 4)
SELECT *,
       ROW_NUMBER() OVER (PARTITION BY market ORDER BY year, quarter) AS n
FROM (SELECT market,
             year,
             quarter,

             CONCAT('Q', quarter, '''', SUBSTRING(TEXT(year), 3, 2))                           AS qy,
             COALESCE(SUM(sales) FILTER (WHERE year = first_year), 0)                          AS new_sales,
             SUM(sales)                                                                        AS total_sales,
             ROUND((COALESCE(SUM(sales) FILTER (WHERE year = first_year), 0)) / SUM(sales), 2) AS proc_new_business
      FROM (SELECT *,
                   EXTRACT(YEAR FROM order_date)                               AS year,
                   EXTRACT(QUARTER FROM order_date)                            AS quarter,
                   FIRST_VALUE(EXTRACT(YEAR FROM order_date))
                   OVER (PARTITION BY market, customer_id ORDER BY order_date) AS first_year
            FROM cte_all_orders) a
      GROUP BY market, year, quarter, qy) a;
# print results
new_business
for (market in unique(new_business$market)) {
  scale <- max(
    new_business[new_business$market == market,
      ]$total_sales
  )
  hi <- round(
    scale * 1.1/10^floor(log10(scale * 1.1)),
    0
  ) *
    10^floor(log10(scale * 1.1))
  scale <- scale * 0.8
  plot <- new_business[new_business$market == market,
    ] %>%
    ggplot() + geom_bar(
    aes(
      x = fct_reorder(qy, n),
      y = total_sales, fill = "Total Revenue"
    ),
    stat = "identity"
  ) +
    geom_bar(
      aes(
        x = fct_reorder(qy, n),
        y = new_sales, fill = "New Revenue"
      ),
      stat = "identity"
    ) +
    geom_line(
      aes(
        x = fct_reorder(qy, n),
        y = proc_new_business * scale, group = 1,
        color = "New Business"
      )
    ) +
    geom_point(
      aes(
        x = fct_reorder(qy, n),
        y = proc_new_business * scale, color = "New Business"
      ),
      shape = 18, size = 3
    ) +
    geom_text(
      aes(
        x = fct_reorder(qy, n),
        y = proc_new_business * scale, label = scales::percent(proc_new_business, accuracy = 0.1)
      ),
      shape = 18, size = 2.5, vjust = -1
    ) +
    geom_vline(
      xintercept = seq(4.5, 12.5, 4),
      linetype = "dotted", color = "gray"
    ) +
    scale_y_continuous(
      limits = c(0, hi),
      breaks = seq(0, hi, by = hi/5),
      labels = scales::number(
        seq(0, hi, by = hi/5),
        scale = 10^-(floor(log10(hi)) -
          floor(log10(hi))%%3),
        suffix = ifelse(
          floor(log10(hi)) -
          floor(log10(hi))%%3 >
          3, "M", "K"
        )
      )
    ) +
    labs(
      title = paste(
        "Percentage of new business revenue generation in",
        market, "market"
      ),
      x = "", y = "Revenue, $"
    ) +
    theme(
      legend.position = "top", legend.title = element_blank(),
      axis.title.x = element_blank(), panel.grid.major = element_blank()
    ) +
    scale_fill_manual(
      name = "", values = c(
        `Total Revenue` = "#dbe5da", `New Revenue` = "#bbcbbc"
      )
    ) +
    scale_color_manual(
      name = "", values = c(`New Business` = "#5e8765")
    )

  print(plot)
}

# Disconnect DB connection
dbDisconnect(conn = con)
LS0tCnRpdGxlOiAiU3VwZXJzdG9yZSBHbG9iYWwgU2FsZXMgQW5hbHlzaXMiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0aGVtZTogdW5pdGVkICAKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgc2VsZl9jb250YWluZWQ6IG5vCi0tLQoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQoKa25pdHI6Om9wdHNfY2h1bmskc2V0KG91dC53aWR0aCA9ICIxMDAlIiwgZWNobyA9IFRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J21hcmt1cCcsIHRpZHk9VFJVRSwgdGlkeS5vcHRzPWxpc3QoYXJyb3c9VFJVRSwgaW5kZW50PTIsIHdpZHRoLmN1dG9mZj01MCwgYXJncy5uZXdsaW5lID0gVFJVRSkpCgpgYGAKCgpgYGB7cn0KCiMgbG9hZGluZyBsaWJyYXJpZXMKbGlicmFyeShEQkkpCmxpYnJhcnkoUlBvc3RncmVzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZm9yY2F0cykKbGlicmFyeShzdHJpbmdpKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KHRyZWVtYXBpZnkpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHlnZW9jb2RlcikKbGlicmFyeShmb3JtYXRSKQoKYGBgCgoKIyBEYXRhc2V0CgpUaGUgW0dsb2JhbCBTdXBlcnN0b3JlIGRhdGFzZXRdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZGF0YXNldHMvc2hla3BhdWwvZ2xvYmFsLXN1cGVyc3RvcmUpIGNvbnRhaW5zIGRhdGEgb2YgdHJhbnNhY3Rpb25zLCBlYWNoIHJvdyByZXByZXNlbnRzIHRyYW5zYWN0aW9uIGl0ZW0gb2YgcHJvZHVjdHMgc29sZCBhcm91bmQgdGhlIHdvcmxkIGJ5IFN1cGVyc3RvcmUsIGFsc28gY29udGFpbnMgYSBjdXN0b21lciBpbmZvcm1hdGlvbiwgc2hpcHBpbmcgYW5kIHByb2R1Y3QgcmV0dXJucy4KCiMjIE1ldGFkYXRhCgpUaGUgZGF0YXNldCBjb250YWlucyB0aGUgZm9sbG93aW5nIHRhYmxlczoKCiMjIyBUYWJsZSAqKk9yZGVycyoqCgp8IFZhcmlhYmxlIG5hbWUgfCBEYXRhIHR5cGUgfCBEZXNjcmlwdGlvbiB8CnwtLS18LS0tfC0tLXwKfCBSb3cgSUQgfCBgSW50ZWdlcmAgfCBVbmlxdWUgSUQgZm9yIGVhY2ggcm93LiB8CnwgT3JkZXIgSUQgfCBgU3RyaW5nYCB8IFVuaXF1ZSBPcmRlciBJRCBmb3IgZWFjaCBDdXN0b21lci4gfAp8IE9yZGVyIERhdGUgfCBgRGF0ZWAgfCBPcmRlciBEYXRlIG9mIHRoZSBwcm9kdWN0LiB8CQp8IFNoaXAgRGF0ZSB8IGBEYXRlYCB8IFNoaXBwaW5nIERhdGUgb2YgdGhlIFByb2R1Y3QuIHwKfCBTaGlwIE1vZGUgfCBgU3RyaW5nYCB8IFNoaXBwaW5nIE1vZGUgc3BlY2lmaWVkIGJ5IHRoZSBDdXN0b21lci4gfAp8IEN1c3RvbWVyIElEIHwgYFN0cmluZ2AgfCBVbmlxdWUgSUQgdG8gaWRlbnRpZnkgZWFjaCBDdXN0b21lci4gfAp8IEN1c3RvbWVyIE5hbWUJfCBgU3RyaW5nYCB8ICBOYW1lIG9mIHRoZSBDdXN0b21lci58CnwgU2VnbWVudCB8IGBTdHJpbmdgIHwgIFRoZSBzZWdtZW50IHdoZXJlIHRoZSBDdXN0b21lciBiZWxvbmdzLiB8CnwgQ2l0eSB8IGBTdHJpbmdgIHwgQ2l0eSBvZiByZXNpZGVuY2Ugb2Ygb2YgdGhlIEN1c3RvbWVyLiB8CnwgU3RhdGUgfCBgU3RyaW5nYCB8IFN0YXRlIG9mIHJlc2lkZW5jZSBvZiB0aGUgQ3VzdG9tZXIuIHwKfCBDb3VudHJ5IHwgYFN0cmluZ2AgfCBDb3VudHJ5IG9mIHJlc2lkZW5jZSBvZiB0aGUgQ3VzdG9tZXIuIHwJCnwgUG9zdGFsIENvZGUgfCBgU3RyaW5nYCB8IFBvc3RhbCBDb2RlIG9mIHJlc2lkZW5jZSBvZiB0aGUgQ3VzdG9tZXIuIHwJCnwgTWFya2V0IHwgYFN0cmluZ2AgfCBNYXJrZXQgbmFtZS4gfAkKfCBSZWdpb24gfCBgU3RyaW5nYCB8IFJlZ2lvbiB3aGVyZSB0aGUgQ3VzdG9tZXIgYmVsb25nLiB8CnwgUHJvZHVjdCBJRCB8IGBTdHJpbmdgIHwgVW5pcXVlIElEIG9mIHRoZSBQcm9kdWN0LiB8CnwgQ2F0ZWdvcnkgfCBgU3RyaW5nYCB8IENhdGVnb3J5IG9mIHRoZSBwcm9kdWN0IG9yZGVyZWQuIHwKfCBTdWItQ2F0ZWdvcnkgfCBgU3RyaW5nYCB8IFN1Yi1DYXRlZ29yeSBvZiB0aGUgcHJvZHVjdCBvcmRlcmVkLiB8CnwgUHJvZHVjdCBOYW1lIHwgYFN0cmluZ2AgfCBOYW1lIG9mIHRoZSBQcm9kdWN0IHwKfCBTYWxlcyB8IGBEZWNpbWFsYCB8IFNhbGVzIG9mIHRoZSBQcm9kdWN0LiB8CnwgUXVhbnRpdHkgfCBgSW50ZWdlcmAgfCBRdWFudGl0eSBvZiB0aGUgUHJvZHVjdC4gfAp8IERpc2NvdW50IHwgYERlY2ltYWxgIHwgRGlzY291bnQgcHJvdmlkZWQuIHwKfCBQcm9maXQgfCBgRGVjaW1hbGAgfCBQcm9maXQvTG9zcyBpbmN1cnJlZC4gfAp8IFNoaXBwaW5nIENvc3QgfCBgRGVjaW1hbGAgfCBTaGlwcGluZyBjb3N0LiB8CnwgT3JkZXIgUHJpb3JpdHkgfCBgU3RyaW5nYCB8IE9yZGVyIHByaW9yaXR5LiB8CgoKIyMjIFRhYmxlICoqUmV0dXJucyoqCgp8IFZhcmlhYmxlIG5hbWUgfCBEYXRhIHR5cGUgfCBEZXNjcmlwdGlvbiB8CnwgLS0tIHwgLS0tIHwgLS0tIHwKfCBPcmRlciBJRCB8IGBTdHJpbmdgIHwgVW5pcXVlIE9yZGVyIElEIGZvciBlYWNoIEN1c3RvbWVyLiB8CnwgUmV0dXJuZWQgfCBgQm9vbGAgfCBUaGUgZmxhZywgaW5kaWNhdGVzIHRoYXQgdGhlIG9yZGVyIHdhcyByZXR1cm5lZC4gfAp8IE1hcmtldCB8IGBTdHJpbmdgIHwgTWFya2V0IG5hbWUuIHwKCgojIERhdGEgbG9hZGluZwoKYGBge3J9CgojIERCIGNvbm5lY3Rpb24KY29uIDwtIGRiQ29ubmVjdCgKICBSUG9zdGdyZXM6OlBvc3RncmVzKCksCiAgZGJuYW1lID0gInN1cGVyc3RvcmVfZ2xvYmFsIiwKICBob3N0ID0gImxvY2FsaG9zdCIsCiAgcG9ydCA9IDU0MzIsCiAgdXNlciA9ICJwb3N0Z3JlcyIsCiAgcGFzc3dvcmQgPSAiIgopCgpgYGAKCkZvciBhbmFseXNpcyBmb3IgdXNlZCBQb3N0Z3JlU1FMIGRhdGFiYXNlLgoKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIGV2YWw9RkFMU0UsIGluY2x1ZGU9VFJVRX0KLS0gTk9URTogeW91IG5lZWQgdG8gZXhlY3V0ZSB0aGUgY29kZSBiZWxvdyBvbiBEQiBzaWRlIChSIE1hcmtkb3duIGNodW5rIGV2YWw9RkFMU0UpLgoKLS0gQ3JlYXRlIGEgbmV3IGRhdGFiYXNlCkNSRUFURSBEQVRBQkFTRSBzdXBlcnN0b3JlX2dsb2JhbDsKCmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWNvbiwgaW5jbHVkZT1GQUxTRX0KCi0tIHByZXBhcmUgZm9yIG1hcmtkb3duIGdlbmVyYXRpb24KRE8gJCQKQkVHSU4KICAgIElGIChTRUxFQ1QgY291bnQoKikKICAgICAgICAgIEZST00gaW5mb3JtYXRpb25fc2NoZW1hLnNjaGVtYXRhIFdIRVJFIHNjaGVtYV9uYW1lID0gJ3Jhd19kYXRhJykgPD4gMCBUSEVOCiAgICAgICAgRFJPUCBTQ0hFTUEgcmF3X2RhdGEgQ0FTQ0FERTsKICAgIEVORCBJRjsKRU5EICQkOwoKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQotLSBzY2hlbWEgZm9yIHJhdyBkYXRhCkNSRUFURSBTQ0hFTUEgcmF3X2RhdGE7CgpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb259Ci0tIGNyZWF0ZSB0YWJsZSBPcmRlcnMgZm9yIHJhdyBkYXRhCkNSRUFURSBUQUJMRSByYXdfZGF0YS5zdXBlcnN0b3JlX29yZGVycwooCiAgICByb3dfaWQgICAgICAgICBWQVJDSEFSKDUpLAogICAgb3JkZXJfaWQgICAgICAgVkFSQ0hBUigxNSksCiAgICBvcmRlcl9kYXRlICAgICBWQVJDSEFSKDEwKSwKICAgIHNoaXBfZGF0ZSAgICAgIFZBUkNIQVIoMTApLAogICAgc2hpcF9tb2RlICAgICAgVkFSQ0hBUigxNCksCiAgICBjdXN0b21lcl9pZCAgICBWQVJDSEFSKDgpLAogICAgY3VzdG9tZXJfbmFtZSAgVkFSQ0hBUigyMiksCiAgICBzZWdtZW50ICAgICAgICBWQVJDSEFSKDExKSwKICAgIGNpdHkgICAgICAgICAgIFZBUkNIQVIoMzUpLAogICAgc3RhdGUgICAgICAgICAgVkFSQ0hBUigzNiksCiAgICBjb3VudHJ5ICAgICAgICBWQVJDSEFSKDMyKSwKICAgIHBvc3RhbF9jb2RlICAgIFZBUkNIQVIoNSksCiAgICBtYXJrZXQgICAgICAgICBWQVJDSEFSKDYpLAogICAgcmVnaW9uICAgICAgICAgVkFSQ0hBUigxNCksCiAgICBwcm9kdWN0X2lkICAgICBWQVJDSEFSKDE2KSwKICAgIGNhdGVnb3J5ICAgICAgIFZBUkNIQVIoMTUpLAogICAgc3ViX2NhdGVnb3J5ICAgVkFSQ0hBUigxMSksCiAgICBwcm9kdWN0X25hbWUgICBWQVJDSEFSKDEyNyksCiAgICBzYWxlcyAgICAgICAgICBWQVJDSEFSKDEwKSwKICAgIHF1YW50aXR5ICAgICAgIFZBUkNIQVIoMiksCiAgICBkaXNjb3VudCAgICAgICBWQVJDSEFSKDUpLAogICAgcHJvZml0ICAgICAgICAgVkFSQ0hBUigyMSksCiAgICBzaGlwcGluZ19jb3N0ICBWQVJDSEFSKDgpLAogICAgb3JkZXJfcHJpb3JpdHkgVkFSQ0hBUig4KQopOwoKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQotLSBOT1RFOiB5b3UgbmVlZCB0byBleGVjdXRlIHRoZSBjb2RlIGJlbG93IG9uIERCIHNpZGUgKFIgTWFya2Rvd24gY2h1bmsgZXZhbD1GQUxTRSkuCgotLSBjcmVhdGUgdGFibGUgUmV0dXJucyBmb3IgcmF3IGRhdGEKQ1JFQVRFIFRBQkxFIHJhd19kYXRhLnN1cGVyc3RvcmVfcmV0dXJucwooCiAgICByZXR1cm5lZCBWQVJDSEFSKDMpLAogICAgb3JkZXJfaWQgVkFSQ0hBUigxNSksCiAgICBtYXJrZXQgICBWQVJDSEFSKDEzKQopOwoKYGBgCgpQcmVwYXJlIGRhdGEgaW4gRXhjZWwuIFNhdmUgZWFjaCBzaGVldCBhcyAqQ1NWKiBmaWxlOgogICsgU2hlZXQgKk9yZGVycyogaW50byAqZGF0YS9HbG9iYWwgU3VwZXJzdG9yZV9vcmRlcnMuY3N2Ki4KICArIFNoZWV0ICpSZXR1cm5zKiBpbnRvICpkYXRhL0dsb2JhbCBTdXBlcnN0b3JlX3JldHVybnMuY3N2Ki4KICAKYGBge3J9CgpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShmcykKCmZpbGVfbmFtZSA8LSAiR2xvYmFsIFN1cGVyc3RvcmUgKHJhdyBkYXRhKS54bHMiCnNoZWV0cyA8LSByZWFkeGw6OmV4Y2VsX3NoZWV0cyhwYXN0ZTAoIi4vZGF0YS8iLCBmaWxlX25hbWUpKQpDU1ZzIDwtIGMoKQpmb3IgKHNoZWV0IGluIHNoZWV0c1sxOjJdKSB7CiAgICBkZiA8LSByZWFkeGw6OnJlYWRfeGxzKHBhc3RlMCgiLi9kYXRhLyIsIGZpbGVfbmFtZSksIGNvbF9uYW1lcyA9IFRSVUUsIHNoZWV0ID0gc2hlZXQpCiAgICBjc3ZfZmlsZSA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9kYXRhLyIsICJHbG9iYWwgU3VwZXJzdG9yZV8iLCBzaGVldCAsIi5jc3YiKQogICAgQ1NWcyA8LSBhcHBlbmQoQ1NWcywgY3N2X2ZpbGUpCiAgICB3cml0ZS5jc3YoZGYsIGNzdl9maWxlLCByb3cubmFtZXM9RkFMU0UpCn0KCm9yZGVyc19jc3YgPC0gQ1NWc1sxXQpyZXR1cm5zX2NzdiA8LSBDU1ZzWzJdCiAgICAgICAgICAgICAgICAgICAgCmBgYAoKCk5vdyBsb2FkIGRhdGEgKiphcy1pcyoqIGludG8gZGF0YWJhc2UuCkxvYWRpbmcgcmF3IGRhdGEuCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29ufQotLSBvcmRlcnMKQ09QWSByYXdfZGF0YS5zdXBlcnN0b3JlX29yZGVycyBGUk9NID9vcmRlcnNfY3N2IERFTElNSVRFUiAnLCcgQ1NWIEhFQURFUjsKCmBgYAoKYGBge3NxbCBjb25uZWN0aW9uPWNvbn0KLS0gcmV0dXJucwpDT1BZIHJhd19kYXRhLnN1cGVyc3RvcmVfcmV0dXJucyBGUk9NID9yZXR1cm5zX2NzdiBERUxJTUlURVIgJywnIENTViBIRUFERVI7CgpgYGAKCgojIERhdGEgY2xlYW5zaW5nCgoKQ3JlYXRlIHNjaGVtYSAqYW5hbHlzaXMqIGZvciBjbGVhbmVkIGRhdGEuCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBpbmNsdWRlPUZBTFNFfQoKLS0gcHJlcGFyZSBmb3IgbWFya2Rvd24gZ2VuZXJhdGlvbgpETyAkJApCRUdJTgogICAgSUYgKFNFTEVDVCBjb3VudCgqKQogICAgICAgICAgRlJPTSBpbmZvcm1hdGlvbl9zY2hlbWEuc2NoZW1hdGEgV0hFUkUgc2NoZW1hX25hbWUgPSAnYW5hbHlzaXMnKSA8PiAwIFRIRU4KICAgICAgICBEUk9QIFNDSEVNQSBhbmFseXNpcyBDQVNDQURFOwogICAgRU5EIElGOwpFTkQgJCQ7CgpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb259Ci0tIHNjaGVtYSBmb3IgZGF0YSBjbGVhbnNpbmcKQ1JFQVRFIFNDSEVNQSBhbmFseXNpczsKCmBgYAoKQ3JlYXRlIHRhYmxlcyBmb3IgY2xlYW5lZCBkYXRhLgoKYGBge3NxbCBjb25uZWN0aW9uPWNvbn0KQ1JFQVRFIFRBQkxFIGFuYWx5c2lzLm9yZGVycwooCiAgICByb3dfaWQgICAgICAgICBJTlRFR0VSLAogICAgb3JkZXJfaWQgICAgICAgVkFSQ0hBUigxNSksCiAgICBvcmRlcl9kYXRlICAgICBEQVRFLAogICAgc2hpcF9kYXRlICAgICAgREFURSwKICAgIHNoaXBfbW9kZSAgICAgIFZBUkNIQVIoMTQpLAogICAgY3VzdG9tZXJfaWQgICAgVkFSQ0hBUig4KSwKICAgIGN1c3RvbWVyX25hbWUgIFZBUkNIQVIoMjIpLAogICAgc2VnbWVudCAgICAgICAgVkFSQ0hBUigxMSksCiAgICBjaXR5ICAgICAgICAgICBWQVJDSEFSKDM1KSwKICAgIHN0YXRlICAgICAgICAgIFZBUkNIQVIoMzYpLAogICAgY291bnRyeSAgICAgICAgVkFSQ0hBUigzMiksCiAgICBwb3N0YWxfY29kZSAgICBWQVJDSEFSKDUpLAogICAgbWFya2V0ICAgICAgICAgVkFSQ0hBUig2KSwKICAgIHJlZ2lvbiAgICAgICAgIFZBUkNIQVIoMTQpLAogICAgcHJvZHVjdF9pZCAgICAgVkFSQ0hBUigxNiksCiAgICBjYXRlZ29yeSAgICAgICBWQVJDSEFSKDE1KSwKICAgIHN1Yl9jYXRlZ29yeSAgIFZBUkNIQVIoMTEpLAogICAgcHJvZHVjdF9uYW1lICAgVkFSQ0hBUigxMjcpLAogICAgc2FsZXMgICAgICAgICAgTlVNRVJJQyg5LCAyKSwKICAgIHF1YW50aXR5ICAgICAgIFNNQUxMSU5ULAogICAgZGlzY291bnQgICAgICAgTlVNRVJJQyg0LCAyKSwKICAgIHByb2ZpdCAgICAgICAgIE5VTUVSSUMoOSwgMiksCiAgICBzaGlwcGluZ19jb3N0ICBOVU1FUklDKDksIDIpLAogICAgb3JkZXJfcHJpb3JpdHkgVkFSQ0hBUig4KQopOwoKYGBgCgpgYGAge3NxbCBjb25uZWN0aW9uPWNvbn0KQ1JFQVRFIFRBQkxFIGFuYWx5c2lzLnJldHVybnMKKAogICAgcmV0dXJuZWQgQk9PTEVBTiwKICAgIG9yZGVyX2lkIFZBUkNIQVIoMTUpLAogICAgbWFya2V0ICAgVkFSQ0hBUigxMykKKTsKCgpgYGAKCkNvbnZlcnQgZGF0YSB0eXBlcy4KCmBgYHtzcWwgY29ubmVjdGlvbj1jb259Ci0tIE9yZGVycyB0YWJsZQpJTlNFUlQgSU5UTyBhbmFseXNpcy5vcmRlcnMKU0VMRUNUIENBU1Qocm93X2lkIEFTIElOVEVHRVIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVMgcm93X2lkLAogICAgICAgb3JkZXJfaWQsCiAgICAgICBUT19EQVRFKG9yZGVyX2RhdGUsICdZWVlZLU1NLUREJykgICAgICAgICAgICAgICAgICAgICAgIEFTIG9yZGVyX2RhdGUsCiAgICAgICBUT19EQVRFKHNoaXBfZGF0ZSwgJ1lZWVktTU0tREQnKSAgICAgICAgICAgICAgICAgICAgICAgIEFTIHNoaXBfZGF0ZSwKICAgICAgIHNoaXBfbW9kZSwKICAgICAgIGN1c3RvbWVyX2lkLAogICAgICAgY3VzdG9tZXJfbmFtZSwKICAgICAgIHNlZ21lbnQsCiAgICAgICBjaXR5LAogICAgICAgc3RhdGUsCiAgICAgICBjb3VudHJ5LAogICAgICAgcG9zdGFsX2NvZGUsCiAgICAgICBtYXJrZXQsCiAgICAgICByZWdpb24sCiAgICAgICBwcm9kdWN0X2lkLAogICAgICAgY2F0ZWdvcnksCiAgICAgICBzdWJfY2F0ZWdvcnksCiAgICAgICBwcm9kdWN0X25hbWUsCiAgICAgICBDQVNUKFJFUExBQ0Uoc2FsZXMsICcsJywgJy4nKSBBUyBOVU1FUklDKDksIDIpKSAgICAgICAgIEFTIHNhbGVzLAogICAgICAgQ0FTVChxdWFudGl0eSBBUyBTTUFMTElOVCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBUyBxdWFudGl0eSwKICAgICAgIENBU1QoUkVQTEFDRShkaXNjb3VudCwgJywnLCAnLicpIEFTIE5VTUVSSUMoNCwgMikpICAgICAgQVMgZGlzY291bnQsCiAgICAgICBDQVNUKFJFUExBQ0UocHJvZml0LCAnLCcsICcuJykgQVMgTlVNRVJJQyg5LCAyKSkgICAgICAgIEFTIHByb2ZpdCwKICAgICAgIENBU1QoUkVQTEFDRShzaGlwcGluZ19jb3N0LCAnLCcsICcuJykgQVMgTlVNRVJJQyg5LCAyKSkgQVMgc2hpcHBpbmdfY29zdCwKICAgICAgIG9yZGVyX3ByaW9yaXR5CkZST00gcmF3X2RhdGEuc3VwZXJzdG9yZV9vcmRlcnM7CgpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb259Ci0tIFJldHVybnMgdGFibGUKSU5TRVJUIElOVE8gYW5hbHlzaXMucmV0dXJucwpTRUxFQ1QgQ0FTVChyZXR1cm5lZCBBUyBCT09MRUFOKSBBUyByZXR1cm5lZCwKICAgICAgIG9yZGVyX2lkLAogICAgICAgbWFya2V0CkZST00gcmF3X2RhdGEuc3VwZXJzdG9yZV9yZXR1cm5zOwoKYGBgCgpDaGVjayAqUmV0dXJucyogdGFibGUuCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJjaGVja19yZXR1cm5zIn0KClNFTEVDVCBvcmRlcnMubWFya2V0LCByZXR1cm5zLm1hcmtldApGUk9NCihTRUxFQ1QgRElTVElOQ1QgbWFya2V0IEZST00gYW5hbHlzaXMub3JkZXJzIE9SREVSIEJZIDEpIEFTIG9yZGVycwpGVUxMIEpPSU4KKFNFTEVDVCBESVNUSU5DVCBtYXJrZXQgRlJPTSBhbmFseXNpcy5yZXR1cm5zIE9SREVSIEJZIDEpIEFTIHJldHVybnMKT04gb3JkZXJzLm1hcmtldCA9IHJldHVybnMubWFya2V0OwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHQKY2hlY2tfcmV0dXJucwoKYGBgCgpGaXggbWFya2V0IG5hbWUgaW4gKlJldHVybnMqIHRhYmxlLgoKYGBge3NxbCBjb25uZWN0aW9uPWNvbn0KClVQREFURSBhbmFseXNpcy5yZXR1cm5zClNFVCBtYXJrZXQgPSAnVVMnCldIRVJFIG1hcmtldCA9ICdVbml0ZWQgU3RhdGVzJzsKCmBgYAoKIyMgRHVwbGljYXRpb24gRGF0YQoKQ2hlY2sgZHVwbGljYXRlZCBwYWlycyBvZiAqb3JkZXJfaWQqIGFuZCAqbWFya2V0KiBpbiAqcmV0dXJucyogdGFibGUuCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJkdXBsaWRhdGVkX29yZGVyX2lkIn0KClNFTEVDVCBvcmRlcl9pZCwgbWFya2V0LCBjb3VudCgqKSBGUk9NIGFuYWx5c2lzLnJldHVybnMgR1JPVVAgQlkgMSwgMiBIQVZJTkcgY291bnQoKikgPiAxOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHQKZHVwbGlkYXRlZF9vcmRlcl9pZAoKYGBgCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJvcmRlcnNfZnVsbF9kdXBsaWNhdGVzIn0KCi0tIGR1cGxpY2F0ZWQgZGF0YQpTRUxFQ1QKICAgIHJvd19pZCwKICAgIG9yZGVyX2lkLAogICAgb3JkZXJfZGF0ZSwKICAgIHNoaXBfZGF0ZSwKICAgIHNoaXBfbW9kZSwKICAgIGN1c3RvbWVyX2lkLAogICAgY3VzdG9tZXJfbmFtZSwKICAgIHNlZ21lbnQsCiAgICBjaXR5LAogICAgc3RhdGUsCiAgICBjb3VudHJ5LAogICAgcG9zdGFsX2NvZGUsCiAgICBtYXJrZXQsCiAgICByZWdpb24sCiAgICBwcm9kdWN0X2lkLAogICAgY2F0ZWdvcnksCiAgICBzdWJfY2F0ZWdvcnksCiAgICBwcm9kdWN0X25hbWUsCiAgICBzYWxlcywKICAgIHF1YW50aXR5LAogICAgZGlzY291bnQsCiAgICBwcm9maXQsCiAgICBzaGlwcGluZ19jb3N0LAogICAgb3JkZXJfcHJpb3JpdHksIGNvdW50KCopCkZST00gYW5hbHlzaXMub3JkZXJzCkdST1VQIEJZIDEsMiwzLDQsNSw2LDcsOCw5LDEwLDExLDEyLDEzLDE0LDE1LDE2LDE3LDE4LDE5LDIwLDIxLDIyLDIzLDI0CmhhdmluZyBjb3VudCgqKSA+IDE7CgpgYGAKCmBgYHtyfQojIHByaW50IHJlc3VsdHMKb3JkZXJzX2Z1bGxfZHVwbGljYXRlcwoKYGBgCgpDaGVjayBkdXBsaWNhdGVzIHdpdGhvdXQgKnJvd19pZCouCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJvcmRlcnNfZnVsbF9kdXBsaWNhdGVzIn0KClNFTEVDVAogICAgb3JkZXJfaWQsCiAgICBvcmRlcl9kYXRlLAogICAgc2hpcF9kYXRlLAogICAgc2hpcF9tb2RlLAogICAgY3VzdG9tZXJfaWQsCiAgICBjdXN0b21lcl9uYW1lLAogICAgc2VnbWVudCwKICAgIGNpdHksCiAgICBzdGF0ZSwKICAgIGNvdW50cnksCiAgICBwb3N0YWxfY29kZSwKICAgIG1hcmtldCwKICAgIHJlZ2lvbiwKICAgIHByb2R1Y3RfaWQsCiAgICBjYXRlZ29yeSwKICAgIHN1Yl9jYXRlZ29yeSwKICAgIHByb2R1Y3RfbmFtZSwKICAgIHNhbGVzLAogICAgcXVhbnRpdHksCiAgICBkaXNjb3VudCwKICAgIHByb2ZpdCwKICAgIHNoaXBwaW5nX2Nvc3QsCiAgICBvcmRlcl9wcmlvcml0eSwgY291bnQoKikKRlJPTSBhbmFseXNpcy5vcmRlcnMKR1JPVVAgQlkgMSwyLDMsNCw1LDYsNyw4LDksMTAsMTEsMTIsMTMsMTQsMTUsMTYsMTcsMTgsMTksMjAsMjEsMjIsMjMKaGF2aW5nIGNvdW50KCopID4gMTsKCmBgYAoKYGBge3J9CiMgcHJpbnQgcmVzdWx0cwpvcmRlcnNfZnVsbF9kdXBsaWNhdGVzCgpgYGAKCiMjIE1pc3NpbmcgVmFsdWVzCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9Im1pc3NpbmdfdmFsdWVzIn0KCnNlbGVjdCBjb3VudCgxKSBGSUxURVIgKCBXSEVSRSByb3dfaWQgaXMgTlVMTCApIEFTIHJvd19pZCwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIG9yZGVyX2lkIElTIE5VTEwgKSBBUyBvcmRlcl9pZCwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIG9yZGVyX2RhdGUgSVMgTlVMTCApIEFTIG9yZGVyX2RhdGUsCiAgICAgICBjb3VudCgxKSBGSUxURVIgKCBXSEVSRSBzaGlwX2RhdGUgSVMgTlVMTCApIEFTIHNoaXBfZGF0ZSwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIHNoaXBfbW9kZSBJUyBOVUxMICkgQVMgc2hpcF9tb2RlLAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgY3VzdG9tZXJfaWQgSVMgTlVMTCApIEFTIGN1c3RvbWVyX2lkLAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgY3VzdG9tZXJfbmFtZSBJUyBOVUxMICkgQVMgY3VzdG9tZXJfbmFtZSwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIHNlZ21lbnQgSVMgTlVMTCApIEFTIHNlZ21lbnQsCiAgICAgICBjb3VudCgxKSBGSUxURVIgKCBXSEVSRSBjaXR5IElTIE5VTEwgKSBBUyBjaXR5LAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgc3RhdGUgSVMgTlVMTCApIEFTIHN0YXRlLAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgY291bnRyeSBJUyBOVUxMICkgQVMgY291bnRyeSwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIHBvc3RhbF9jb2RlIElTIE5VTEwgKSBBUyBwb3N0YWxfY29kZSwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIG1hcmtldCBJUyBOVUxMICkgQVMgbWFya2V0LAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgcmVnaW9uIElTIE5VTEwgKSBBUyByZWdpb24sCiAgICAgICBjb3VudCgxKSBGSUxURVIgKCBXSEVSRSBwcm9kdWN0X2lkIElTIE5VTEwgKSBBUyBwcm9kdWN0X2lkLAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgY2F0ZWdvcnkgSVMgTlVMTCApIEFTIGNhdGVnb3J5LAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgc3ViX2NhdGVnb3J5IElTIE5VTEwgKSBBUyBzdWJfY2F0ZWdvcnksCiAgICAgICBjb3VudCgxKSBGSUxURVIgKCBXSEVSRSBwcm9kdWN0X25hbWUgSVMgTlVMTCApIEFTIHByb2R1Y3RfbmFtZSwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIHNhbGVzIElTIE5VTEwgKSBBUyBzYWxlcywKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIHF1YW50aXR5IElTIE5VTEwgKSBBUyBxdWFudGl0eSwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIGRpc2NvdW50IElTIE5VTEwgKSBBUyBkaXNjb3VudCwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIHByb2ZpdCBJUyBOVUxMICkgQVMgcHJvZml0LAogICAgICAgY291bnQoMSkgRklMVEVSICggV0hFUkUgc2hpcHBpbmdfY29zdCBJUyBOVUxMICkgQVMgc2hpcHBpbmdfY29zdCwKICAgICAgIGNvdW50KDEpIEZJTFRFUiAoIFdIRVJFIG9yZGVyX3ByaW9yaXR5IElTIE5VTEwgKSBBUyBvcmRlcl9wcmlvcml0eQpGUk9NIGFuYWx5c2lzLm9yZGVyczsKCmBgYAoKYGBge3J9CiMgcHJpbnQgcmVzdWx0cwptaXNzaW5nX3ZhbHVlcwoKYGBgCgojIFByZXBhcmF0aW9uIGZvciBhbmFseXNpcwoKTWFrZSBtYXRlcmlhbGl6ZWQgdmlldy4KCmBgYHtzcWwgY29ubmVjdGlvbj1jb259CgotLSBtYXRlcmlhbGl6ZWQgdmlldwpDUkVBVEUgTUFURVJJQUxJWkVEIFZJRVcgSUYgTk9UIEVYSVNUUyBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycyBBUwogICAgU0VMRUNUICBvLiosIENBU0UKICAgICAgICAgICAgICAgICAgICBXSEVOIHIucmV0dXJuZWQgSVMgTlVMTCBUSEVOIEZBTFNFCiAgICAgICAgICAgICAgICAgICAgRUxTRSByLnJldHVybmVkCiAgICAgICAgICAgICAgICAgRU5EIEFTIHJldHVybmVkCiAgICBGUk9NIGFuYWx5c2lzLm9yZGVycyBvCiAgICAgICAgTEVGVCBKT0lOIGFuYWx5c2lzLnJldHVybnMgciBPTiBvLm9yZGVyX2lkID0gci5vcmRlcl9pZCBBTkQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG8ubWFya2V0ID0gci5tYXJrZXQ7CgpgYGAKCmBgYHtzcWwgY29ubmVjdGlvbj1jb259CgotLSByZWZyZXNoIHRoZSB2aWV3IHRvIGZ1bGwgaXQgd2l0aCBkYXRhClJFRlJFU0ggTUFURVJJQUxJWkVEIFZJRVcgYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnM7CgpgYGAKCgojIEFuYWx5c2lzCgoKYGBge3J9CgojIHNldHVwIHBsb3RzIHRoZW1lCnRoZW1lX3NldCh0aGVtZV9saWdodCgpKQp0aGVtZV91cGRhdGUoYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwgCiAgICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJkYXJrZ3JheSIsIGxpbmV0eXBlID0gImRvdHRlZCIpLAogICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICBwYW5lbC5ib3JkZXI9IGVsZW1lbnRfcmVjdChjb2xvcj0iZGFya2dyYXkiKSwKICAgICAgICAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKC41LCAxLCAxLCAxKSwgImxpbmVzIikpCgpgYGAKCgojIyBUb3RhbCBSZXZlbnVlIGFuZCBHcm9zcyBNYXJnaW4KCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9InJldmVudWVfbWFyZ2luIn0KClNFTEVDVCBEQVRFX1BBUlQoJ3llYXInLCBvcmRlcl9kYXRlKTo6SU5UICAgICAgICAgICAgICAgICAgICAgQVMgeWVhciwKICAgICAgIG1hcmtldCwKICAgICAgIHJvdW5kKFNVTShzYWxlcyksIDIpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBUyBzYWxlcywKICAgICAgIHJvdW5kKFNVTShwcm9maXQpIEZJTFRFUiAoV0hFUkUgTk9UIHJldHVybmVkKSwgMikgICAgICBBUyBwcm9maXQsIC0tIGV4Y2x1ZGUgcmV0dXJucwogICAgICAgcm91bmQoU1VNKHByb2ZpdCkgRklMVEVSIChXSEVSRSBOT1QgcmV0dXJuZWQpIC8gU1VNKHNhbGVzKSwgMikgQVMgbWFyZ2luCkZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMKR1JPVVAgQlkgMSwgMgpPUkRFUiBCWSB5ZWFyLCBzYWxlcyBkZXNjOwogICAgCmBgYAoKYGBge3J9CgojIHByaW50IHJlc3VsdApyZXZlbnVlX21hcmdpbgoKYGBgCgoKYGBge3Igd2FybmluZz1GQUxTRX0KCnNjYWxlIDwtIG1lYW4ocmV2ZW51ZV9tYXJnaW4kc2FsZXMpCm9mZnNldCA8LSBtZWFuKHJldmVudWVfbWFyZ2luJHNhbGVzKQogIApnZ3Bsb3QocmV2ZW51ZV9tYXJnaW4pICsKICBnZW9tX2JhcihhZXMoeD1tYXJrZXQsIHk9c2FsZXMsIGZpbGw9ZmFjdG9yKHllYXIpKSwgc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlMih3aWR0aCA9IDAuOSkpICsKICBnZW9tX2JhcihhZXMoeD1tYXJrZXQsIHk9cHJvZml0LCBmaWxsPSJQcm9maXQiKSwgc3RhdD0iaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlMih3aWR0aCA9IDAuOSkpICsKICBnZW9tX2xpbmUoYWVzKHg9bWFya2V0LCB5PW9mZnNldCArIG1hcmdpbipzY2FsZSwgZ3JvdXA9bWFya2V0LCBjb2xvcj0iR3Jvc3MgTWFyZ2luIiksIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UyKHdpZHRoID0gMC45KSkgKwogIGdlb21fcG9pbnQoYWVzKHg9bWFya2V0LCB5PW9mZnNldCArIG1hcmdpbipzY2FsZSwgY29sb3I9Ikdyb3NzIE1hcmdpbiIpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlMih3aWR0aCA9IDAuOSksIHNoYXBlPTE1KSArCiAgZ2VvbV90ZXh0KGFlcyh4PW1hcmtldCwgeT1vZmZzZXQgKyBtYXJnaW4qc2NhbGUsIGxhYmVsPXNjYWxlczo6cGVyY2VudChtYXJnaW4sIGFjY3VyYWN5ID0gMUwpKSwgdmp1c3Q9LTEuMiwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZTIod2lkdGggPSAwLjkpLCBzaXplPTIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzZXEoMS41LCBsZW5ndGgodW5pcXVlKHJldmVudWVfbWFyZ2luJG1hcmtldCkpLCAxKSwgbGluZXR5cGU9ImRvdHRlZCIsIGNvbG9yPSJncmF5IikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZSA9IDFlLTMsIHN1ZmZpeCA9ICJLIiwgYWNjdXJhY3kgPSAxTCksCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTMwMGUzKSwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIDEzMDBlMywgMTAwZTMpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIkdyb3NzIE1hcmdpbiI9ImJsYWNrIikpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSJTYWxlcyIsIHZhbHVlcyA9IGMoIlByb2ZpdCI9IiMwMDNmNWMiLCAiMjAxMSI9IiM1ODUwOGQiLCAiMjAxMiI9IiNiYzUwOTAiLCAiMjAxMyI9IiNmZjYzNjEiLCAiMjAxNCI9IiNmZmE2MDAiKSkgKwogIGxhYnModGl0bGU9IlRvdGFsIFJldmVudWUgYW5kIEdyb3NzIE1hcmdpbiIsCiAgICAgICB5ID0gIlNhbGVzICYgUHJvZml0LCAkIiwKICAgICAgIHggPSAiTWFya2V0IikgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCgpgYGAKCgojIyBUb3RhbCBHcm9zcyAmIE5ldCBSZXZlbnVlIGFuZCBZb1kgUHJvZml0IEdyb3d0aAoKYGBge3NxbCBjb25uZWN0aW9uPWNvbiwgb3V0cHV0LnZhcj0ieW95X3Byb2ZpdF9ncm93dGgifQoKU0VMRUNUIHllYXI6OiBJTlQgQVMgeWVhciwKICAgICAgIHJvdW5kKHNhbGVzLCAyKSBBUyBzYWxlcywKICAgICAgIHJvdW5kKHJldHVybmVkX3NhbGVzLCAyKSBBUyByZXR1cm5lZF9zYWxlcywKICAgICAgIHJvdW5kKHByb2ZpdCwgMikgQVMgcHJvZml0LAogICAgICAgcm91bmQoKHByb2ZpdCAtIGxhZyhwcm9maXQsIDEpIE9WRVIgKE9SREVSIEJZIHllYXIpKS9sYWcocHJvZml0LCAxKSBPVkVSIChPUkRFUiBCWSB5ZWFyKSwgMikgQVMgeW95X3Byb2ZpdF9ncm93dGgKRlJPTQooU0VMRUNUCiAgICAgIERBVEVfUEFSVCgneWVhcicsIG9yZGVyX2RhdGUpIEFTIHllYXIsCiAgICAgIHN1bShzYWxlcykgRklMVEVSICggV0hFUkUgTk9UIHJldHVybmVkICkgQVMgc2FsZXMsCiAgICAgIHN1bShzYWxlcykgRklMVEVSICggV0hFUkUgcmV0dXJuZWQgKSBBUyByZXR1cm5lZF9zYWxlcywKICAgICAgc3VtKHByb2ZpdCkgRklMVEVSICggV0hFUkUgTk9UIHJldHVybmVkICkgQVMgcHJvZml0IC0tIGV4Y2x1ZGUgcmV0dXJucwpGUk9NCihTRUxFQ1QgZGlzdGluY3Qgb3JkZXJfaWQsCiAgICAgICBmaXJzdF92YWx1ZShvcmRlcl9kYXRlKSBvdmVyKFBBUlRJVElPTiBCWSBvcmRlcl9pZCkgQVMgb3JkZXJfZGF0ZSwKICAgICAgIHN1bShzYWxlcykgb3ZlcihQQVJUSVRJT04gQlkgb3JkZXJfaWQpIEFTIHNhbGVzLAogICAgICAgc3VtKHByb2ZpdCkgb3ZlcihQQVJUSVRJT04gQlkgb3JkZXJfaWQpIEFTIHByb2ZpdCAsCiAgICAgICBmaXJzdF92YWx1ZShyZXR1cm5lZCkgb3ZlcihQQVJUSVRJT04gQlkgb3JkZXJfaWQpIEFTIHJldHVybmVkCkZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMpIGEKR1JPVVAgQlkgMQpPUkRFUiBCWSAxKSBhCk9SREVSIEJZIHllYXI7CiAgICAKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCnlveV9wcm9maXRfZ3Jvd3RoCgpgYGAKCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQoKc2NhbGUgPC0gbWF4KHlveV9wcm9maXRfZ3Jvd3RoJHNhbGVzKQpvZmZzZXQgPC0gbWVhbih5b3lfcHJvZml0X2dyb3d0aCRzYWxlcykKICAKZ2dwbG90KHlveV9wcm9maXRfZ3Jvd3RoKSArCiAgZ2VvbV9iYXIoYWVzKHg9eWVhciwgeSA9IHNhbGVzLCBmaWxsPSJHcm9zcyBSZXZlbnVlIiksIHN0YXQ9ImlkZW50aXR5Iiwgd2lkdGg9MC41KSArCiAgZ2VvbV9iYXIoYWVzKHg9eWVhciwgeSA9IHNhbGVzIC0gcmV0dXJuZWRfc2FsZXMsIGZpbGw9Ik5ldCBSZXZlbnVlIiksIHN0YXQ9ImlkZW50aXR5Iiwgd2lkdGg9MC40NSkgKwogIGdlb21fYmFyKGFlcyh4PXllYXIsIHkgPSBwcm9maXQsIGZpbGw9IlByb2ZpdCIpLCBzdGF0PSJpZGVudGl0eSIsIHdpZHRoPTAuNDUpICsKICBnZW9tX2xpbmUoYWVzKHg9eWVhciwgeT15b3lfcHJvZml0X2dyb3d0aCpzY2FsZSwgZ3JvdXA9MSwgY29sb3I9IllvWSBQcm9maXQgR3Jvd3RoIikpICsKICBnZW9tX3BvaW50KGFlcyh4PXllYXIsIHk9eW95X3Byb2ZpdF9ncm93dGgqc2NhbGUsIGNvbG9yPSJZb1kgUHJvZml0IEdyb3d0aCIpLCBzaGFwZT0xNSkgKwogIGdlb21fdGV4dChhZXMoeD15ZWFyLCB5PXlveV9wcm9maXRfZ3Jvd3RoKnNjYWxlLCBsYWJlbD1zY2FsZXM6OnBlcmNlbnQoeW95X3Byb2ZpdF9ncm93dGgsIGFjY3VyYWN5ID0gMUwpKSwgdmp1c3Q9LTEuMiwgc2l6ZT00KSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIllvWSBQcm9maXQgR3Jvd3RoIj0iYmxhY2siKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIlByb2ZpdCI9IiMwMDNmNWMiLCAiR3Jvc3MgUmV2ZW51ZSI9IiNiYzUwOTAiLCAiTmV0IFJldmVudWUiPSIjZmZhNjAwIikpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9kb2xsYXIoc2NhbGUgPSAxZS02LCBzdWZmaXggPSAiTSIsIGFjY3VyYWN5ID0gMC4xKSwKICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA0LjVlNiksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCA0LjVlNiwgNTAwZTMpKSArCiAgbGFicyh0aXRsZT0iVG90YWwgR3Jvc3MgJiBOZXQgUmV2ZW51ZSBhbmQgWW9ZIFByb2ZpdCBHcm93dGgiLAogICAgICAgc3VidGl0bGUgPSAiVHJlbmQgbGluZXMgcmVwcmVzZW50IHllYXIgb3ZlciB5ZWFyIHByb2ZpdCBncm93dGggd2l0Y2ggZGVtb25zdHJhdGVzIHBvc2l0aXZlIGdyb3d0aFxudGhyZWUgeWVhcnMgaW4gYSByb3cuIiwKICAgICAgIHkgPSAiIiwKICAgICAgIHggPSAiIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKCmBgYAoKCiMjIFF1YXJ0ZXJseSBOZXQgUmV2ZW51ZSBncm93dGgKCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJxdWFydGVybHlfcmV2ZW51ZSJ9CgotLSBUcmVuZCBvZiBxdWFydGVybHkgcmV2ZW51ZSBncm93dGgKU0VMRUNUICosCiAgICAgICAocm93X251bWJlcigpIE9WRVIgKE9SREVSIEJZIHllYXIsIHF1YXJ0ZXIpKTo6SU5UIEFTIG4sCiAgICAgICBjb25jYXQoJ1EnLCBxdWFydGVyLCAnJycnLCBzdWJzdHJpbmcoY2FzdCh5ZWFyIGFzIFRFWFQpLCAzLCA0KSkgQVMgbGFiZWwsCiAgICAgICByb3VuZCgoc2FsZXMgLSBsYWcoc2FsZXMpIE9WRVIgKE9SREVSIEJZIHllYXIsIHF1YXJ0ZXIpKS9sYWcoc2FsZXMpIE9WRVIgKE9SREVSIEJZIHllYXIsIHF1YXJ0ZXIpLCAyKSBBUyBncm93dGgKRlJPTQooU0VMRUNUIHllYXIsCiAgICAgICBxdWFydGVyLAogICAgICAgc3VtKHNhbGVzKSAgRklMVEVSIChXSEVSRSBOT1QgcmV0dXJuZWQpIEFTIHNhbGVzLAogICAgICAgc3VtKHNhbGVzKSBBUyBncm9zc19zYWxlcwogICAgRlJPTQogICAgICAoU0VMRUNUICosCiAgICAgICAgICBleHRyYWN0KHF1YXJ0ZXIgZnJvbSBvcmRlcl9kYXRlKSBhcyBxdWFydGVyLAogICAgICAgICAgZXh0cmFjdCh5ZWFyIGZyb20gb3JkZXJfZGF0ZSkgYXMgeWVhcgogICAgICAgIEZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMpIGEKICAgICAgICBHUk9VUCBCWSAxLCAyCiAgICAgICAgT1JERVIgQlkgMSwgMikgYgpPUkRFUiBCWSAxLCAyOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCnF1YXJ0ZXJseV9yZXZlbnVlCgpgYGAKCgpgYGB7cn0KCnJlb3JkZXJfdmFsdWUgPC0gZnVuY3Rpb24oeCwgdmFsdWUsIGRlY3JlYXNpbmcpIHsKICBhIDwtIGFycmF5KHZhbHVlKQogIG5hbWVzKGEpIDwtIHgKICBpZihkZWNyZWFzaW5nKXsKICAgIGZhY3Rvcih4LCBsZXZlbHMgPSBuYW1lcyhzb3J0KGEsIGRlY3JlYXNpbmcgPSBUUlVFKSkpCiAgfWVsc2V7CiAgICBmYWN0b3IoeCwgbGV2ZWxzID0gbmFtZXMoc29ydChhKSkpCiAgfQp9CgpzY2FsZSA8LSBtYXgocXVhcnRlcmx5X3JldmVudWUkc2FsZXMpCmdncGxvdChxdWFydGVybHlfcmV2ZW51ZSwgYWVzKHg9cmVvcmRlcl92YWx1ZShsYWJlbCwgbiwgRkFMU0UpKSkgKwogIGdlb21fYmFyKGFlcyh5PXNhbGVzLCBmaWxsPSJOZXQgUmV2ZW51ZSIpLCBzdGF0PSJpZGVudGl0eSIpICsKICBnZW9tX3RleHQoYWVzKHk9c2FsZXMsIGxhYmVsPXNjYWxlczo6ZG9sbGFyKHNhbGVzLCBzY2FsZSA9IDFlLTYsIHN1ZmZpeCA9ICdNJywgYWNjdXJhY3kgPSAwLjEpKSwgdmp1c3Q9LTEuMywgc2l6ZT0zKSArCiAgZ2VvbV9saW5lKGFlcyh5PWdyb3d0aCpzY2FsZSwgZ3JvdXA9MSwgY29sb3I9IlEvUSBHcm93dGgiKSwgc2l6ZT0xKSArCiAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwgdmFsdWVzPWMoIk5ldCBSZXZlbnVlIj0iIzAwM2Y1YyIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIlEvUSBHcm93dGgiPSIjZmZhNjAwIikpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzZXEoNC41LCAxNiwgNCksIGxpbmV0eXBlPSJkb3R0ZWQiKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfZG9sbGFyKHNjYWxlID0gMWUtNiwgc3VmZml4ID0gIk0iLCBhY2N1cmFjeSA9IDAuMSksCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoLTkwMGUzLCAxLjZlNiksCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCAxLjZlNiwgMjUwZTMpLAogICAgICAgICAgICAgICAgICAgICBzZWMuYXhpcyA9IHNlY19heGlzKHRyYW5zID0gfi4vc2NhbGUsIGxhYmVscyA9IHNjYWxlczo6bGFiZWxfcGVyY2VudCgpLCBicmVha3MgPSBzZXEoLTAuNiwgMSwgMC4yKSkpICsKICBsYWJzKHRpdGxlPSJRL1EgTmV0IFJldmVudWUgZ3Jvd3RoIiwKICAgICAgIHN1YnRpdGxlID0gIlRyZW5kIGxpbmUgb2YgdGhlIGdyb3d0aCByZXByZXNlbnRzIHBvdGVudGlvbmFseSBvZiBkcm9wcGluZyBvZiB0aGUgcmV2ZW51ZVxuZ3Jvd3RoIGR5bmFtaWMgaW4gUTEnMTUuIiwKICAgICAgIHkgPSAiIiwKICAgICAgIHggPSAiIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKCmBgYAoKCiMjIFByb2ZpdCB0cmVuZHMKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9Im1vbnRobHlfcHJvZml0In0KClNFTEVDVCAqLAogICAgICAgcm91bmQoKHByb2ZpdCAtIGxhZyhwcm9maXQpIE9WRVIgKE9SREVSIEJZIGxhc3RfbW9udGhfZGF5KSkvbGFnKHByb2ZpdCkgT1ZFUiAoT1JERVIgQlkgbGFzdF9tb250aF9kYXkpLCAyKSBhcyBncm93dGgKRlJPTQooU0VMRUNUCiAgICAgICAoZGF0ZV90cnVuYygnbW9udGgnLCBvcmRlcl9kYXRlKSArIGludGVydmFsICcxIG1vbnRoIC0gMSBkYXknKTo6ZGF0ZSBBUyBsYXN0X21vbnRoX2RheSwKICAgICAgIHN1bShwcm9maXQpICBGSUxURVIgKFdIRVJFIE5PVCByZXR1cm5lZCkgQVMgcHJvZml0CkZST00KYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMKR1JPVVAgQlkgMSkgYTsKCmBgYAoKYGBge3J9CiMgcHJpbnQgcmVzdWx0cwptb250aGx5X3Byb2ZpdAoKYGBgCgoKYGBge3IgfQoKc2NhbGUgPC0gbWF4KG1vbnRobHlfcHJvZml0JHByb2ZpdCkKZ2dwbG90KG1vbnRobHlfcHJvZml0KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3I9ImdyYXkiLCBzaXplPTAuMjUpICsKICBnZW9tX2xpbmUoYWVzKHg9bGFzdF9tb250aF9kYXksIHk9cHJvZml0LCBjb2xvcj0iUHJvZml0IiksIHNpemU9MSkgKwogIGdlb21fbGluZShhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1ncm93dGgqc2NhbGUsIGNvbG9yPSJZb1kgR3Jvd3RoIiksIHNpemU9MSkgKwogIGdlb21fc21vb3RoKGFlcyh4PWxhc3RfbW9udGhfZGF5LCB5PXByb2ZpdCksIG1ldGhvZD0ibG0iLCBzZSA9IEZBTFNFLCBsaW5ldHlwZT0iZGFzaGVkIiwgY29sb3I9ImJsdWUiLCBzaXplPTAuNzUpICsKICBnZW9tX3Ntb290aChhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1ncm93dGgqc2NhbGUpLCBtZXRob2Q9ImxtIiwgc2UgPSBGQUxTRSwgbGluZXR5cGU9ImRhc2hlZCIsIGNvbG9yPSJyZWQiLCBzaXplPTAuNzUpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoc2VjLmF4aXMgPSBzZWNfYXhpcyh0cmFucyA9IH4uL3NjYWxlLCBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX3BlcmNlbnQoKSksCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoLTUwZTMsIDE1MGUzKSwKICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKC01MGUzLCAxNTBlMywgMjVlMyksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHNjYWxlID0gMWUtMywgc3VmZml4ID0gIksiKSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLCB2YWx1ZXMgPSBjKCJQcm9maXQiPSIjMDAzZjVjIiwgIllvWSBHcm93dGgiPSIjZmZhNjAwIikpICsKICBsYWJzKHRpdGxlPSJQcm9maXQgdHJlbmRzIiwgeD0iIiwgeT0iUHJvZml0LCAkIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKYGBgCgoKIyMgVG90YWwgbnVtYmVyIG9mIG9yZGVycyBhbmQgbG9zcyBvcmRlcnMKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9Im9yZGVycyJ9CgpTRUxFQ1QgbWFya2V0LAogICAgICAgICAgICAgeWVhciwKICAgICAgICAgICAgIHN1bShzYWxlcykgYXMgc2FsZXMsCiAgICAgICAgICAgICBDT1VOVChESVNUSU5DVCBvcmRlcl9pZCk6OklOVCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBUyB0b3RhbF9vcmRlcnMsCiAgICAgICAgICAgICAoQ09VTlQoRElTVElOQ1Qgb3JkZXJfaWQpIEZJTFRFUiAoIFdIRVJFIHByb2ZpdCA8IDAgKSk6OklOVCBBUyBsb3NzX29yZGVycwogICAgICBGUk9NIChTRUxFQ1QgbWFya2V0LAogICAgICAgICAgICAgICAgICAgRVhUUkFDVChZRUFSIEZST00gb3JkZXJfZGF0ZSkgQVMgeWVhciwKICAgICAgICAgICAgICAgICAgIG9yZGVyX2lkLAogICAgICAgICAgICAgICAgICAgU1VNKHNhbGVzKSAgICAgICAgICAgICAgICAgICAgQVMgc2FsZXMsCiAgICAgICAgICAgICAgICAgICBTVU0ocHJvZml0KSAgICAgICAgICAgICAgICAgICBBUyBwcm9maXQKICAgICAgICAgICAgRlJPTSBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycwogICAgICAgICAgICBHUk9VUCBCWSAxLCAyLCAzKSBhCiAgICAgIEdST1VQIEJZIDEsIDIKICAgICAgT1JERVIgQlkgMiwgMyBkZXNjCgpgYGAKCmBgYHtyfQojIHByaW50IHJlc3VsdHMKb3JkZXJzCgpgYGAKClZpc3VhbGl6ZSBkYXRhIGZvciAyMDE0IHllYXIuCgpgYGAge3J9CmdncGxvdChvcmRlcnNbb3JkZXJzJHllYXIgPT0gMjAxNCwgXSkgKwogIGdlb21fY29sKGFlcyhyZW9yZGVyKG1hcmtldCwgc2FsZXMsIGRlY3JlYXNpbmcgPSBUUlVFKSwgc2FsZXMsIGZpbGw9IlJldmVudWUiKSwgcG9zaXRpb24gPSAiZG9kZ2UyIiwKICAgIHNob3cubGVnZW5kID0gVFJVRSwKICAgIGFscGhhID0gLjkpICsKICBnZW9tX3NlZ21lbnQoCiAgICBhZXMoCiAgICAgIHggPSByZW9yZGVyKG1hcmtldCwgc2FsZXMpLAogICAgICB5ID0gMCwKICAgICAgeGVuZCA9IHJlb3JkZXIobWFya2V0LCBzYWxlcyksCiAgICAgIHllbmQgPSB0b3RhbF9vcmRlcnMqMTAwMGUzL21heChvcmRlcnNbb3JkZXJzJHllYXIgPT0gMjAxNCwgXSR0b3RhbF9vcmRlcnMpLAogICAgICBjb2xvcj0iVG90YWwgT3JkZXJzIgogICAgKSwKICAgIGxpbmV0eXBlID0gInNvbGlkIgogICkgKwogIGdlb21fcG9pbnQoYWVzKHJlb3JkZXIobWFya2V0LCBzYWxlcywgZGVjcmVhc2luZyA9IFRSVUUpLCB0b3RhbF9vcmRlcnMqMTAwMGUzL21heChvcmRlcnNbb3JkZXJzJHllYXIgPT0gMjAxNCwgXSR0b3RhbF9vcmRlcnMpKSwgc2hhcGU9OTUsIHNpemU9MywgY29sb3I9ImJsYWNrIikgICsKICBnZW9tX2NvbChhZXMocmVvcmRlcihtYXJrZXQsIHNhbGVzLCBkZWNyZWFzaW5nID0gVFJVRSksIGxvc3Nfb3JkZXJzKjEwMDBlMy9tYXgob3JkZXJzW29yZGVycyR5ZWFyID09IDIwMTQsIF0kdG90YWxfb3JkZXJzKSwgZmlsbD0iTG9zcyBPcmRlcnMiKSwgd2lkdGg9MC4xKSAgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIlJldmVudWUiPSIjZWNlY2VjIiwgIkxvc3MgT3JkZXJzIj0icmVkIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwgdmFsdWVzID0gYygiVG90YWwgT3JkZXJzIj0iYmxhY2siKSkgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSAiZ3JheTEyIiwgc2l6ZSA9IDEyKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIiwgY29sb3IgPSAid2hpdGUiKSkgKwogIHNjYWxlX3lfY29udGludW91cygKICAgIGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKGFjY3VyYWN5ID0gMC4wMSwgc2NhbGUgPSAxZS02LCBzdWZmaXggPSAiTSIpLAogICAgbGltaXRzID0gYygwLCBtYXgob3JkZXJzW29yZGVycyR5ZWFyID09IDIwMTQsIF0kc2FsZXMpKjEuMSksCiAgICBleHBhbmQgPSBjKDAsIDApLAogICAgYnJlYWtzID0gc2VxKDAsIG1heChvcmRlcnNbb3JkZXJzJHllYXIgPT0gMjAxNCwgXSRzYWxlcykqMS4xLCAyNTBlMyksCiAgICBzZWMuYXhpcyA9IHNlY19heGlzKHRyYW5zID0gfi4vMTAwMGUzKm1heChvcmRlcnNbb3JkZXJzJHllYXIgPT0gMjAxNCwgXSR0b3RhbF9vcmRlcnMpLCBuYW1lID0gIk51bWJlciBvZiBPcmRlcnMiLCBicmVha3MgPSBzZXEoMCwgMjAwMCwgMjUwKSkKICApICsgCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgbG9zcyBvcmRlcnMgZm9yIDIwMTQiLCB5PSJSZXZlbnVlLCAkIiwgeD0iTWFya2V0IikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGZpbGwgPSAid2hpdGUiKSksCiAgbGluZXR5cGUgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChmaWxsID0gIndoaXRlIikpKQoKCmBgYAoKCiMjIEN1c3RvbWVyIExpZmV0aW1lIFZhbHVlIChDTFYpCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJDTFYifQoKU0VMRUNUIG1hcmtldCwKICAgICAgIHNlZ21lbnQsCiAgICAgICB5ZWFyLAogICAgICAgcm91bmQoQ0xWLCAyKSBBUyBDTFYsCiAgICAgICByb3VuZCgoQ0xWIC0gbGFnKENMVikgT1ZFUiAoUEFSVElUSU9OIEJZIG1hcmtldCwgc2VnbWVudCBPUkRFUiBCWSB5ZWFyKSkvbGFnKENMVikgT1ZFUiAoUEFSVElUSU9OIEJZIG1hcmtldCwgc2VnbWVudCBPUkRFUiBCWSB5ZWFyKSwgMikgQVMgZ3Jvd3RoCkZST00KKFNFTEVDVCBhLm1hcmtldCwKICAgICAgIGEuc2VnbWVudCwKICAgICAgIGEueWVhciwKICAgICAgIGEuY3VzdG9tZXJfdmFsdWUgKiBiLkNMUyBBUyBDTFYKICBGUk9NIChTRUxFQ1QgYS5tYXJrZXQsCiAgICAgICAgICAgICAgIGEuc2VnbWVudCwKICAgICAgICAgICAgICAgYS55ZWFyLAogICAgICAgICAgICAgICBhLnNhbGVzLAogICAgICAgICAgICAgICBiLnVuaXF1ZV9jdXN0b21lcnMsCiAgICAgICAgICAgICAgIGEuc2FsZXMgLyBiLnVuaXF1ZV9jdXN0b21lcnMgQVMgY3VzdG9tZXJfdmFsdWUKICAgICAgICBGUk9NIChTRUxFQ1QgbWFya2V0LAogICAgICAgICAgICAgICAgICAgICBzZWdtZW50LAogICAgICAgICAgICAgICAgICAgICBFWFRSQUNUKFlFQVIgRlJPTSBvcmRlcl9kYXRlKSBBUyB5ZWFyLAogICAgICAgICAgICAgICAgICAgICBTVU0oc2FsZXMpICAgICAgICAgICAgICAgICAgICBBUyBzYWxlcwogICAgICAgICAgICAgIEZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMKICAgICAgICAgICAgICBHUk9VUCBCWSAxLCAyLCAzCiAgICAgICAgICAgICAgT1JERVIgQlkgMSwgMiwgMykgYQogICAgICAgICAgICAgICAgIExFRlQgSk9JTgogICAgICAgICAgICAgKFNFTEVDVCBtYXJrZXQsCiAgICAgICAgICAgICAgICAgICAgIHNlZ21lbnQsCiAgICAgICAgICAgICAgICAgICAgIEVYVFJBQ1QoWUVBUiBGUk9NIG9yZGVyX2RhdGUpIEFTIHllYXIsCiAgICAgICAgICAgICAgICAgICAgIENPVU5UKERJU1RJTkNUIGN1c3RvbWVyX2lkKSAgIEFTIHVuaXF1ZV9jdXN0b21lcnMKICAgICAgICAgICAgICBGUk9NIGFuYWx5c2lzLmFsbF9nbG9iYWxfb3JkZXJzCiAgICAgICAgICAgICAgR1JPVVAgQlkgMSwgMiwgMwogICAgICAgICAgICAgIE9SREVSIEJZIDEsIDIsIDMpIGIgT04gYS5tYXJrZXQgPSBiLm1hcmtldCBBTkQgYS5zZWdtZW50ID0gYi5zZWdtZW50IEFORCBhLnllYXIgPSBiLnllYXIpIGEKICAgICAgICAgICBJTk5FUiBKT0lOCiAgICAgICAoU0VMRUNUIG1hcmtldCwKICAgICAgICAgICAgICAgc2VnbWVudCwKICAgICAgICAgICAgICAgeWVhciwKICAgICAgICAgICAgICAgREFURV9QQVJUKCd5ZWFyJywgU1VNKGRheXMpKTo6ZGVjaW1hbCAvIENPVU5UKERJU1RJTkNUIGN1c3RvbWVyX2lkKSBBUyBDTFMKICAgICAgICBGUk9NIChTRUxFQ1QgRElTVElOQ1QgbWFya2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWdtZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjdXN0b21lcl9pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUlOKG9yZGVyX2RhdGUpIE9WRVIgKFBBUlRJVElPTiBCWSBjdXN0b21lcl9pZCkgICAgICAgQVMgZmlyc3RfZGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTUFYKG9yZGVyX2RhdGUpIE9WRVIgKFBBUlRJVElPTiBCWSB5ZWFyLCBjdXN0b21lcl9pZCkgQVMgbGFzdF9kYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBR0UoTUFYKG9yZGVyX2RhdGUpIE9WRVIgKFBBUlRJVElPTiBCWSB5ZWFyLCBjdXN0b21lcl9pZCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNSU4ob3JkZXJfZGF0ZSkgT1ZFUiAoUEFSVElUSU9OIEJZIGN1c3RvbWVyX2lkKSkgIEFTIGRheXMKICAKICAgICAgICAgICAgICBGUk9NIChTRUxFQ1QgKiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgRVhUUkFDVChZRUFSIEZST00gb3JkZXJfZGF0ZSkgQVMgeWVhcgogICAgICAgICAgICAgICAgICAgIEZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMpIGEpIGIKICAgICAgICBHUk9VUCBCWSAxLCAyLCAzKSBiIE9OCiAgICAgICAgICAgICAgICAgICBhLm1hcmtldCA9IGIubWFya2V0IEFORAogICAgICAgICAgICAgICAgICAgYS5zZWdtZW50ID0gYi5zZWdtZW50IEFORAogICAgICAgICAgICAgICAgICAgYS55ZWFyID0gYi55ZWFyKSBhOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHQKQ0xWCgpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD0xMH0KCgpnZ3Bsb3QoQ0xWKSArCiAgZ2VvbV9iYXIoYWVzKHg9eWVhciwgeT1jbHYsIGZpbGw9c2VnbWVudCksIHN0YXQgPSAiaWRlbnRpdHkiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKCkpICsKICBnZW9tX2xpbmUoYWVzKHg9eWVhciwgeT1ncm93dGgqbWVhbihDTFZbQ0xWJG1hcmtldCA9PSBtYXJrZXQgJiBDTFYkc2VnbWVudCA9PSBzZWdtZW50LCAiY2x2Il0pLCBjb2xvcj0iWW9ZIEdyb3d0aCIpKSArCiAgZ2VvbV9wb2ludChhZXMoeD15ZWFyLCB5PWdyb3d0aCptZWFuKENMVltDTFYkbWFya2V0ID09IG1hcmtldCAmIENMViRzZWdtZW50ID09IHNlZ21lbnQsICJjbHYiXSksIGNvbG9yPSJZb1kgR3Jvd3RoIiksIHNpemU9MSkgKwogIGdlb21fdGV4dChhZXMoeD15ZWFyLCB5PWdyb3d0aCptZWFuKENMVltDTFYkbWFya2V0ID09IG1hcmtldCAmIENMViRzZWdtZW50ID09IHNlZ21lbnQsICJjbHYiXSksIGxhYmVsPXNjYWxlczo6cGVyY2VudChncm93dGgsIGFjY3VyYWN5ID0gMUwpLCBjb2xvcj0iWW9ZIEdyb3d0aCIpLCB2anVzdD0tMSwgc2l6ZT0yKSArCiAgbGFicyh0aXRsZSA9ICJDdXN0b21lciBMaWZldGltZSBWYWx1ZSIsIHg9IiIsIHk9IkN1c3RvbWVyIExpZmV0aW1lIFZhbHVlLCAkIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIkNvbnN1bWVyIj0iI2ZmNjM2MSIsICJDb3Jwb3JhdGUiPSIjYmM1MDkwIiwgIkhvbWUgT2ZmaWNlIj0iI2ZmYTYwMCIpKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIllvWSBHcm93dGgiPSJibGFjayIpKSArCiAgZmFjZXRfZ3JpZChtYXJrZXR+c2VnbWVudCwgc2NhbGVzPSdmcmVlX3knKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJibGFjayIsIGZhY2UgPSAiYm9sZCIpKQoKCmBgYAoKCgojIyBRdWFydGVybHkgQ3VzdG9tZXIgUmV0ZW50aW9uIFJhdGUKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9ImN1c3RvbWVyX3JldGVudGlvbiJ9CgpXSVRIIGN0ZV9vcmRlcnMgQVMgKApTRUxFQ1QgRElTVElOQ1QKICAgICAgIG9yZGVyX2lkLAogICAgICAgY3VzdG9tZXJfaWQsCiAgICAgICBvcmRlcl9kYXRlLAogICAgICAgbWFya2V0LAogICAgICAgc2VnbWVudCwKICAgICAgIG1pbihvcmRlcl9kYXRlKSBPVkVSIChQQVJUSVRJT04gQlkgY3VzdG9tZXJfaWQsIG1hcmtldCkgQVMgZmlyc3RfZGF0ZQpGUk9NCiAgICBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycwpPUkRFUiBCWSBjdXN0b21lcl9pZCwgbWFya2V0LCBvcmRlcl9kYXRlKSwKICAgIGN0ZV9vcmRlcnNfc3BsaXQgQVMgKApTRUxFQ1QgKiwKICAgICAgIGV4dHJhY3QoWUVBUiBGUk9NIG9yZGVyX2RhdGUpIEFTIG9yZGVyX3llYXIsCiAgICAgICBleHRyYWN0KFFVQVJURVIgRlJPTSBvcmRlcl9kYXRlKSBBUyBvcmRlcl9xLAogICAgICAgZXh0cmFjdChZRUFSIEZST00gZmlyc3RfZGF0ZSkgQVMgZmlyc3RfeWVhciwKICAgICAgIGV4dHJhY3QoUVVBUlRFUiBGUk9NIGZpcnN0X2RhdGUpIEFTIGZpcnN0X3EKRlJPTSBjdGVfb3JkZXJzKQoKU0VMRUNUIHllYXIsIHF1YXJ0ZXIsCiAgICAgICBtYXJrZXQsIHNlZ21lbnQsCiAgICAgICB1bmlxX2N1c3RvbWVyczo6SU5ULAogICAgICAgbmV3X2N1c3RvbWVyczo6SU5ULAogICAgICAgbjo6SU5ULAogICAgICAgQ09OQ0FUKCdRJywgcXVhcnRlciwgJycnJywgU1VCU1RSSU5HKFRFWFQoeWVhciksIDMsIDIpKSAgICAgICAgICAgICAgIEFTIHF5LAogICAgICAgcm91bmQoKHVuaXFfY3VzdG9tZXJzLWNvYWxlc2NlKG5ld19jdXN0b21lcnMsIDApKTo6ZGVjaW1hbC9sYWcodW5pcV9jdXN0b21lcnMpIE9WRVIgKFBBUlRJVElPTiBCWSBtYXJrZXQsIHNlZ21lbnQgT1JERVIgQlkgbiksIDIpIEFTIHJldF9yYXRlCkZST00KKFNFTEVDVCBvcmRlcl95ZWFyIEFTIHllYXIsCiAgICAgICBvcmRlcl9xIEFTIHF1YXJ0ZXIsCiAgICAgICBhLm1hcmtldCwKICAgICAgIGEuc2VnbWVudCwKICAgICAgIGEudW5pcV9jdXN0b21lcnMsCiAgICAgICBiLm5ld19jdXN0b21lcnMsCiAgICAgICByb3dfbnVtYmVyKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGEubWFya2V0LCBhLnNlZ21lbnQgT1JERVIgQlkgYS5vcmRlcl95ZWFyLCBhLm9yZGVyX3EpIG4KRlJPTQooU0VMRUNUIG9yZGVyX3llYXIsCiAgICAgICBvcmRlcl9xLAogICAgICAgbWFya2V0LAogICAgICAgc2VnbWVudCwKICAgICAgIGNvdW50KERJU1RJTkNUIGN1c3RvbWVyX2lkKSB1bmlxX2N1c3RvbWVycwpGUk9NIGN0ZV9vcmRlcnNfc3BsaXQKR1JPVVAgQlkgMSwgMiwgMywgNCkgYQpMRUZUIEpPSU4KKFNFTEVDVCBmaXJzdF95ZWFyLAogICAgICAgZmlyc3RfcSwKICAgICAgIG1hcmtldCwKICAgICAgIHNlZ21lbnQsCiAgICAgICBjb3VudChESVNUSU5DVCBjdXN0b21lcl9pZCkgbmV3X2N1c3RvbWVycwpGUk9NIGN0ZV9vcmRlcnNfc3BsaXQKR1JPVVAgQlkgMSwgMiwgMywgNCkgYiBPTiBhLm9yZGVyX3llYXIgPSBiLmZpcnN0X3llYXIgQU5ECiAgICAgICAgICAgICAgICAgICAgICAgICAgYS5vcmRlcl9xID0gYi5maXJzdF9xIEFORAogICAgICAgICAgICAgICAgICAgICAgICAgIGEubWFya2V0ID0gYi5tYXJrZXQgQU5ECiAgICAgICAgICAgICAgICAgICAgICAgICAgYS5zZWdtZW50ID0gYi5zZWdtZW50CikgYTsKCmBgYAoKYGBge3J9CiMgcHJpbml0IHJlc3VsdHMKY3VzdG9tZXJfcmV0ZW50aW9uCgpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD0xMH0KCmdncGxvdChjdXN0b21lcl9yZXRlbnRpb24pICsKICBnZW9tX2xpbmUoYWVzKHg9cmVvcmRlcihxeSwgbiksIHkgPSByZXRfcmF0ZSwgZ3JvdXA9MSwgY29sb3I9c2VnbWVudCksIHdpZHRoPTIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9wZXJjZW50KCkpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PTQuNSksIGxpbmV0eXBlID0gImRvdHRlZCIpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PTguNSksIGxpbmV0eXBlID0gImRvdHRlZCIpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PTEyLjUpLCBsaW5ldHlwZSA9ICJkb3R0ZWQiKSArCiAgYW5ub3RhdGUoZ2VvbSA9ICJ0ZXh0IiwgbGFiZWw9IDIwMTEsIHg9Mi41LCB5PTAuMSwgY29sb3I9ImxpZ2h0Z3JheSIpICsKICBhbm5vdGF0ZShnZW9tID0gInRleHQiLCBsYWJlbD0gMjAxMiwgeD02LjUsIHk9MC4xLCBjb2xvcj0ibGlnaHRncmF5IikgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsPSAyMDEzLCB4PTEwLjUsIHk9MC4xLCBjb2xvcj0ibGlnaHRncmF5IikgKwogIGFubm90YXRlKGdlb20gPSAidGV4dCIsIGxhYmVsPSAyMDE0LCB4PTE0LjUsIHk9MC4xLCBjb2xvcj0ibGlnaHRncmF5IikgKwogIHNjYWxlX3hfZGlzY3JldGUobGFiZWxzPXJlcChwYXN0ZTAoIlEiLCAxOjQpLCA0KSkgKwogIGZhY2V0X2dyaWQobWFya2V0fnNlZ21lbnQpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIiwKICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImJsYWNrIiwgZmFjZSA9ICJib2xkIikpKwogIGxhYnModGl0bGU9IlF1YXJ0ZXJseSBDdXN0b21lciBSZXRlbnRpb24gUmF0ZSIsIHg9IiIsIHk9IlJldGVudGlvbiBSYXRlIikKCmBgYAoKIyMgQVJQUFUgYW5kIEF2ZXJhZ2UgY2hlY2sgdmFsdWUKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9ImFycHB1X2NoZWNrIn0KClNFTEVDVCAoZGF0ZV90cnVuYygnbW9udGgnLCBvcmRlcl9kYXRlKSk6OmRhdGUgQVMgbGFzdF9tb250aF9kYXksCiAgICAgICBjb3VudChESVNUSU5DVCBjdXN0b21lcl9pZCk6OklOVCBBUyB1bmlxX2N1c3RvbWVycywKICAgICAgIGNvdW50KERJU1RJTkNUIG9yZGVyX2lkKTo6SU5UIEFTIGNvdW50X29yZGVycywKICAgICAgIHJvdW5kKHN1bShzYWxlcykvY291bnQoRElTVElOQ1QgY3VzdG9tZXJfaWQpLCAyKSBBUyBhcnBwdSwKICAgICAgIHJvdW5kKHN1bShzYWxlcykvY291bnQoRElTVElOQ1Qgb3JkZXJfaWQpLCAyKSBBUyBhdmdfY2hlY2sKRlJPTSBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycyAtLSBmcm9tIG1hdGVyaWFsaXplZCB2aWV3CkdST1VQIEJZIDEKT1JERVIgQlkgMTsKCmBgYAoKYGBge3J9CiMgcHJpbnQgcmVzdWx0cwphcnBwdV9jaGVjawoKYGBgCgoKYGBge3J9CgpnZ3Bsb3QoYXJwcHVfY2hlY2spICsKICBnZW9tX2JhcihhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1hcnBwdSwgZmlsbD0iQVJQUFUiKSwgc3RhdD0iaWRlbnRpdHkiKSArCiAgZ2VvbV9saW5lKGFlcyh4PWxhc3RfbW9udGhfZGF5LCB5PWF2Z19jaGVjaywgY29sb3I9IkF2Zy4gY2hlY2siKSwgc2l6ZT0xKSArCiAgZ2VvbV9wb2ludChhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1hdmdfY2hlY2ssIGNvbG9yPSJBdmcuIGNoZWNrIiksIHNoYXBlPTE4LCBzaXplPTMpICsKICBnZW9tX3Ntb290aChhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1hdmdfY2hlY2spLCBtZXRob2Q9ImxtIiwgY29sb3I9InJlZCIsIGxpbmV0eXBlPSJkYXNoZWQiLCBzZT1GQUxTRSkgKwogIGdlb21fc21vb3RoKGFlcyh4PWxhc3RfbW9udGhfZGF5LCB5PWFycHB1KSwgbWV0aG9kPSJsbSIsIGNvbG9yPSJibHVlIiwgbGluZXR5cGU9ImRhc2hlZCIsIHNlPUZBTFNFKSArCiAgbGFicyh0aXRsZT0iQXZlcmFnZSByZXZlbnVlIHBlciBwYXlpbmcgdXNlciAoQVJQUFUpIGFuZCBhdmVyYWdlIGNoZWNrIHZhbHVlXG5mcm9tIDIwMTEgdG8gMjAxNCIsCiAgICAgICB5PSJBUlBQVSAmIEF2Zy5jaGVjayB2YWx1ZSwgJCIsIHg9IiIpICsKICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLCB2YWx1ZXM9YygiQVJQUFUiPSIjMDAzZjVjIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwgdmFsdWVzPWMoIkF2Zy4gY2hlY2siPSIjZmZhNjAwIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKCmBgYAoKCiMjIEF2ZXJhZ2UgQmFza2V0IFZhbHVlIGFuZCBzaXplCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJiYXNrZXRfdmFsdWUifQoKLS0gYXZnIGJhc2tldCB2YWx1ZSBhbmQgc2l6ZQpXSVRIIG9yZGVyX2Nvc3QgQVMgKAogICAgU0VMRUNUIG1hcmtldCwKICAgICAgIChkYXRlX3RydW5jKCdtb250aCcsIG9yZGVyX2RhdGUpKTo6ZGF0ZSBBUyBsYXN0X21vbnRoX2RheSwKICAgICAgIHNlZ21lbnQsCiAgICAgICBvcmRlcl9pZCwKICAgICAgIHJvdW5kKHN1bShzYWxlcyksIDIpIGFzIHNhbGVzLAogICAgICAgcm91bmQoc3VtKHF1YW50aXR5KSwgMikgYXMgcXVhbnRpdHkKICAgIEZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMKICAgIEdST1VQIEJZIDEsIDIsIDMsIDQKICAgIE9SREVSIEJZIDIsIDEsIDMsIDQKICAgICkKCgpzZWxlY3QgKiwKICAgICAgIHJvdW5kKG1pbihiYXNrZXRfdmFsdWUpIG92ZXIgKHBhcnRpdGlvbiBieSBtYXJrZXQpIC8gbWF4KGJhc2tldF9zaXplKSBvdmVyIChwYXJ0aXRpb24gYnkgbWFya2V0KSwgMikgQVMgc2NhbGUKICAgICAgIGZyb20KKFNFTEVDVAogICAgbWFya2V0LAogICAgbGFzdF9tb250aF9kYXksCiAgICByb3VuZChhdmcoc2FsZXMpLCAyKSBBUyBiYXNrZXRfdmFsdWUsCiAgICByb3VuZChhdmcocXVhbnRpdHkpLCAwKSBBUyBiYXNrZXRfc2l6ZQogICAgRlJPTSBvcmRlcl9jb3N0CkdST1VQIEJZIDEsIDIpIGE7CgpgYGAKCmBgYHtyfQojIHByaW50IHJlc3VsdHMKYmFza2V0X3ZhbHVlCgpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTEwfQoKZ2dwbG90KGJhc2tldF92YWx1ZSkgKwogIGdlb21fbGluZShhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1iYXNrZXRfdmFsdWUsIGdyb3VwPW1hcmtldCkpICsKICBnZW9tX3NlZ21lbnQoIGFlcyh4PWxhc3RfbW9udGhfZGF5LCB4ZW5kPWxhc3RfbW9udGhfZGF5LCB5PTAsIHllbmQ9YmFza2V0X3NpemUqc2NhbGUpLCBjb2xvcj0icmVkIikgKwogIGdlb21fcG9pbnQoYWVzKHg9bGFzdF9tb250aF9kYXksIHk9YmFza2V0X3NpemUqc2NhbGUsIHNpemU9aWZlbHNlKGJhc2tldF9zaXplIDw9IDMsIDIsIGJhc2tldF9zaXplIC0gMikpLCBzaGFwZT0yMSwgY29sb3I9InJlZCIsIGZpbGw9IndoaXRlIiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fdGV4dChhZXMoeD1sYXN0X21vbnRoX2RheSwgeT1iYXNrZXRfc2l6ZSpzY2FsZSwgbGFiZWw9YmFza2V0X3NpemUsIHNpemU9aWZlbHNlKGJhc2tldF9zaXplIDw9IDMsIDIsIGJhc2tldF9zaXplIC0gMikpLCBjb2xvcj0iYmxhY2siLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9zbW9vdGgoYWVzKHg9bGFzdF9tb250aF9kYXksIHk9YmFza2V0X3ZhbHVlKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgc2l6ZT0xKSArCiAgZmFjZXRfZ3JpZChtYXJrZXR+Liwgc2NhbGVzID0gImZyZWVfeSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsCiAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAiYmxhY2siLCBmYWNlID0gImJvbGQiKSkgKwogIGxhYnModGl0bGU9IkF2ZXJhZ2UgYmFza2V0IHZhbHVlIGFuZCBzaXplIiwgeD0iIiwgeT0iQmFza2V0IFZhbHVlLCAkIikKCmBgYAoKCiMjIFJldHVybiBSYXRlCgoKYGBge3NxbCBjb25uZWN0aW9uPWNvbiwgb3V0cHV0LnZhcj0icmV0dXJucyJ9CgotLSByZXR1cm4gcmF0ZQpTRUxFQ1QgZXh0cmFjdCh5ZWFyIGZyb20gb3JkZXJfZGF0ZSkgQVMgeWVhciwKICAgICAgIHJvdW5kKHN1bShzYWxlcyksIDIpIEFTIHRvdGFsX3NhbGVzLAogICAgICAgcm91bmQoc3VtKHNhbGVzKSBGSUxURVIgKFdIRVJFIHJldHVybmVkKSwgMikgQVMgcmV0dXJuZWRfc2FsZXMsCiAgICAgICBjb3VudChkaXN0aW5jdCBvcmRlcl9pZCk6OklOVCBBUyBjb3VudF9vcmRlcnMsCiAgICAgICAoY291bnQoZGlzdGluY3Qgb3JkZXJfaWQpIEZJTFRFUiAoV0hFUkUgcmV0dXJuZWQpKTo6SU5UIEFTIHJldHVybmVkX29yZGVycywKICAgICAgIHJvdW5kKChjb3VudChkaXN0aW5jdCBvcmRlcl9pZCkgRklMVEVSIChXSEVSRSByZXR1cm5lZCkpOjpkZWNpbWFsL2NvdW50KGRpc3RpbmN0IG9yZGVyX2lkKSwgMykgYXMgcmV0dXJuX3JhdGUKRlJPTSBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycwpHUk9VUCBCWSAxOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCnJldHVybnMKCmBgYAoKCmBgYHtyfQpnZ3Bsb3QocmV0dXJucykgKwogIGdlb21fYmFyKGFlcyh4PXllYXIsIHk9dG90YWxfc2FsZXMsIGZpbGw9IlNhbGVzIiksIHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9MC44KSkgKwogIGdlb21fYmFyKGFlcyh4PXllYXIsIHk9cmV0dXJuZWRfc2FsZXMsIGZpbGw9IlJldHVybmVkIFNhbGVzIiksIHN0YXQ9ImlkZW50aXR5IiwgcG9zaXRpb249cG9zaXRpb25fZG9kZ2Uod2lkdGg9MC44KSkgKwogIGdlb21fcG9pbnQoYWVzKHg9eWVhciwgeT1yZXR1cm5fcmF0ZSp0b3RhbF9zYWxlcywgY29sb3I9IlJldHVybiBSYXRlIikpICsKICBnZW9tX3RleHQoYWVzKHg9eWVhciwgeT1yZXR1cm5fcmF0ZSp0b3RhbF9zYWxlcywgbGFiZWw9c2NhbGVzOjpwZXJjZW50KHJldHVybl9yYXRlLCBhY2N1cmFjeSA9IDAuMSkpLCB2anVzdD0tMS41KSArCiAgbGFicyh0aXRsZT0iUmV0dXJuIHJhdGUiLCB5PSJTYWxlcywgJCIsIHg9IiIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGU9MWUtNiwgc3VmZml4ID0gIk0iKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKG5hbWU9IiIsIHZhbHVlcz1jKCJTYWxlcyI9IiNkYmU1ZGEiLCAiUmV0dXJuZWQgU2FsZXMiPSIjYmJjYmJjIikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwobmFtZT0iIiwgdmFsdWVzPWMoIlJldHVybiBSYXRlIj0iYmxhY2siKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0idG9wIikKYGBgCgoKIyMgV2hhdCBtb3N0IHNpZ25pZmljYW50IHN1Yi1jYXRlZ29yaWVzPwoKYGBge3NxbCBjb25uZWN0aW9uPWNvbiwgb3V0cHV0LnZhcj0icGFyZXRvX3NhbGVzIn0KCldJVEggY3RlX3NhbGVzIEFTKApTRUxFQ1QgZXh0cmFjdCh5ZWFyIGZyb20gb3JkZXJfZGF0ZSkgYXMgeWVhciwKICAgICAgIHN1Yl9jYXRlZ29yeSwKICAgICAgIHNlZ21lbnQsCiAgICAgICBzdW0oc2FsZXMpIGFzIHNhbGVzLAogICAgICAgc3VtKHByb2ZpdCkgYXMgcHJvZml0CmZyb20gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMKZ3JvdXAgYnkgMSwgMiwgMykKCnNlbGVjdCAqLAogICAgICAgcm91bmQoc3VtKHNhbGVzL3RvdGFsX3NhbGVzKSBvdmVyKHBhcnRpdGlvbiBieSB5ZWFyLCBzZWdtZW50IG9yZGVyIGJ5IHNhbGVzIGRlc2Mgcm93cyBCRVRXRUVOCiAgICAgICAgICAgIHVuYm91bmRlZCBwcmVjZWRpbmcgYW5kIGN1cnJlbnQgcm93KSwgMikgYXMgY3VtX3BlcmNlbnQKICAgICAgIGZyb20KKHNlbGVjdCB5ZWFyLCBzdWJfY2F0ZWdvcnksIHNlZ21lbnQsCiAgICAgICBzYWxlcywgc3VtKHNhbGVzKSBvdmVyIChQQVJUSVRJT04gQlkgeWVhciwgc2VnbWVudCkgYXMgdG90YWxfc2FsZXMKZnJvbSBjdGVfc2FsZXMKb3JkZXIgYnkgMSwgMiwgMywgNCBkZXNjKSBhOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCnBhcmV0b19zYWxlcwoKYGBgCgoKYGBge3J9CgoKZm9yIChzZWdtZW50IGluIHVuaXF1ZShwYXJldG9fc2FsZXMkc2VnbWVudCkpIHsKICBzY2FsZSA8LSBtYXgocGFyZXRvX3NhbGVzW3BhcmV0b19zYWxlcyR5ZWFyID09IDIwMTQgJiBwYXJldG9fc2FsZXMkc2VnbWVudD09c2VnbWVudCxdJHNhbGVzKQogIHhfaW50ZXJjZXB0IDwtIGFwcHJveCgKICAgIHBhcmV0b19zYWxlc1twYXJldG9fc2FsZXMkeWVhciA9PSAyMDE0ICYgcGFyZXRvX3NhbGVzJHNlZ21lbnQ9PXNlZ21lbnQsXSRjdW1fcGVyY2VudCpzY2FsZSwKICAgIGZjdF9yZW9yZGVyKHBhcmV0b19zYWxlc1twYXJldG9fc2FsZXMkeWVhciA9PSAyMDE0ICYgcGFyZXRvX3NhbGVzJHNlZ21lbnQ9PXNlZ21lbnQsXSRzdWJfY2F0ZWdvcnksIC1wYXJldG9fc2FsZXNbcGFyZXRvX3NhbGVzJHllYXIgPT0gMjAxNCAmIHBhcmV0b19zYWxlcyRzZWdtZW50PT1zZWdtZW50LF0kc2FsZXMpLCAKICAgIDAuOCpzY2FsZSkkeQogIAogIGcgPC0gZ2dwbG90KHBhcmV0b19zYWxlc1twYXJldG9fc2FsZXMkeWVhciA9PSAyMDE0ICYgcGFyZXRvX3NhbGVzJHNlZ21lbnQ9PXNlZ21lbnQsXSkgKwogICAgZ2VvbV9hcmVhKGFlcyh4PWZjdF9yZW9yZGVyKHN1Yl9jYXRlZ29yeSwgLXNhbGVzKSwgeT1jdW1fcGVyY2VudCpzY2FsZSwgZ3JvdXA9MSksIGZpbGw9IiNlY2VjZWMiKSArCiAgICBnZW9tX2JhcihhZXMoeD1mY3RfcmVvcmRlcihzdWJfY2F0ZWdvcnksIC1zYWxlcyksIHk9c2FsZXMpLCBzdGF0PSJpZGVudGl0eSIsIGZpbGw9IiNiYmNiYmMiKSArCiAgICBnZW9tX2xpbmUoYWVzKHg9ZmN0X3Jlb3JkZXIoc3ViX2NhdGVnb3J5LCAtc2FsZXMpLCB5PWN1bV9wZXJjZW50KnNjYWxlLCBncm91cD0xKSkgKwogICAgZ2VvbV9wb2ludChhZXMoeD1mY3RfcmVvcmRlcihzdWJfY2F0ZWdvcnksIC1zYWxlcyksIHk9Y3VtX3BlcmNlbnQqc2NhbGUpKSArCiAgICBnZW9tX3RleHQoYWVzKHg9ZmN0X3Jlb3JkZXIoc3ViX2NhdGVnb3J5LCAtc2FsZXMpLCB5PWN1bV9wZXJjZW50KnNjYWxlLCAKICAgICAgICAgICAgICAgICAgbGFiZWw9c2NhbGVzOjpwZXJjZW50KGN1bV9wZXJjZW50LCBhY2N1cmFjeSA9IDAuMSkpLAogICAgICAgICAgICAgIHNpemU9Miwgdmp1c3Q9LTEuNSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKHNlYy5heGlzID0gc2VjX2F4aXModHJhbnMgPSB+Li9zY2FsZSwgbGFiZWxzID0gc2NhbGVzOjpsYWJlbF9wZXJjZW50KCksIGJyZWFrcyA9IHNlcSgwLCAxLCAwLjIpKSwgYnJlYWtzID0gc2VxKDAsIHNjYWxlLCAyNWUzKSwgbGltaXRzPWMoMCwgc2NhbGUqMS4xKSwgbGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGUgPSAxZS0zLCBzdWZmaXggPSAiSyIpKSArCiAgICAgc2NhbGVfeF9kaXNjcmV0ZShndWlkZSA9IGd1aWRlX2F4aXMoYW5nbGUgPSA0NSkpICsKICAgIGdlb21fc2VnbWVudCh4ID0gMCwgeGVuZCA9IEluZiwgeSA9IDAuOCpzY2FsZSwgeWVuZCA9IDAuOCpzY2FsZSwgY29sb3IgPSAiYmx1ZSIsIHNpemU9MC4yNSwKICAgICAgICAgICAgICAgICBsaW5ldHlwZT0iZGFzaGVkIikgKwogICAgZ2VvbV9zZWdtZW50KHggPSB4X2ludGVyY2VwdCwKICAgICAgIHhlbmQgPSB4X2ludGVyY2VwdCwgeSA9IC1JbmYsIHllbmQgPSAwLjgqc2NhbGUsIGNvbG9yID0gImJsdWUiLCBzaXplPTAuMjUsCiAgICAgICAgICAgICAgICAgbGluZXR5cGU9ImRhc2hlZCIpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZTAoIlNhbGVzIGJ5IHN1Yi1jYXRlZ29yeSBpbiAiLCBzZWdtZW50LCIgY3VzdG9tZXIgc2VnbWVudCBmb3IgMjAxNCIpLCB4PSJTdWItQ2F0ZWdvcnkiLCB5PSJTYWxlcywgJCIsIHN1YnRpdGxlID0gcGFzdGUwKCI4MCUgb2YgdGhlIHRvdGFsIHJldmVudWUgY29tZXMgZnJvbSBtb3N0IHNpZ25pZmljYW50IHN1Yi1jYXRlZ29yaWVzXG5zdWNoIGFzICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpX3JlcGxhY2VfbGFzdChwYXN0ZShhcy5jaGFyYWN0ZXIoZmN0X3Jlb3JkZXIocGFyZXRvX3NhbGVzW3BhcmV0b19zYWxlcyR5ZWFyID09IDIwMTQgJiBwYXJldG9fc2FsZXMkc2VnbWVudD09c2VnbWVudCxdJHN1Yl9jYXRlZ29yeSwgLXBhcmV0b19zYWxlc1twYXJldG9fc2FsZXMkeWVhciA9PSAyMDE0ICYgcGFyZXRvX3NhbGVzJHNlZ21lbnQ9PXNlZ21lbnQsXSRzYWxlcylbc2VxKDEsIHJvdW5kKDguMjcsIDApLCAxKV0pLCBjb2xsYXBzZSA9ICcsICcpLCBmaXhlZCA9ICcsJywgJyAmJykpKSAKICBwcmludChnKQp9CgpgYGAKCiMjIE1hcAoKYGBge3NxbCBjb25uZWN0aW9uPWNvbiwgb3V0cHV0LnZhcj0ibWFwIn0KClNFTEVDVCBtYXJrZXQsCiAgICAgICByZWdpb24sCiAgICAgICBjb3VudHJ5LAogICAgICAgc3VtKHNhbGVzKSBBUyBzYWxlcwpGUk9NIGFuYWx5c2lzLmFsbF9nbG9iYWxfb3JkZXJzCkdST1VQIEJZIDEsIDIsIDMKT1JERVIgQlkgMSwgMiwgMzsKCmBgYAoKYGBge3J9CiMgcHJpbnQgcmVzdWx0cwptYXAKCmBgYAoKCmBgYHtyfQoKIyBhZGQgZ2VvY29kZXMKd29ybGQgPC0gbWFwX2RhdGEoIndvcmxkIikKbWFwcCA8LSBtYXAgJT4lIGdlb2NvZGUoY291bnRyeSA9IGNvdW50cnksIG1ldGhvZCA9ICdvc20nLCBsYXQgPSBsYXRpdHVkZSAsIGxvbmcgPSBsb25naXR1ZGUpCgpjZW50cm9pZHMgPC0gbWFwcCAlPiUKICBncm91cF9ieShtYXJrZXQpICU+JQogIHN1bW1hcmlzZShsb25naXR1ZGU9bWVhbihsb25naXR1ZGUsIG5hLnJtID0gVFJVRSksIGxhdGl0dWRlPW1lYW4obGF0aXR1ZGUsIG5hLnJtID0gVFJVRSksIHNhbGVzPSBzdW0oc2FsZXMsIG5hLnJtID0gVFJVRSkpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9tYXAoCiAgICBkYXRhID0gd29ybGQsIG1hcCA9IHdvcmxkLAogICAgYWVzKGxvbmcsIGxhdCwgbWFwX2lkID0gcmVnaW9uKSwKICAgIGNvbG9yID0gImRhcmtncmF5IiwgZmlsbCA9ICIjZWNlY2VjIiwgc2l6ZSA9IDAuMQogICkgKwogIGdlb21fcG9pbnQoZGF0YT1jZW50cm9pZHMsIGFlcyh4PWxvbmdpdHVkZSwgeT1sYXRpdHVkZSwgc2l6ZT1zYWxlcywgY29sb3I9bWFya2V0KSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgYWxwaGE9MC43NSkgKwogIHNjYWxlX3NpemUocmFuZ2UgPSBjKDEsIDIwKSkgKwogIGdlb21fdGV4dChkYXRhPWNlbnRyb2lkcywgYWVzKHg9bG9uZ2l0dWRlLCB5PWxhdGl0dWRlLCBsYWJlbD1zY2FsZXM6OmRvbGxhcihzYWxlcywgc2NhbGUgPSAxZS02LCBzdWZmaXggPSAiTSIsIGFjY3VyYWN5ID0gMC4xKSksIHNpemU9Miwgdmp1c3Q9MS41KSArCiAgZ2VvbV90ZXh0KGRhdGE9Y2VudHJvaWRzLCBhZXMoeD1sb25naXR1ZGUsIHk9bGF0aXR1ZGUsIGxhYmVsPW1hcmtldCksIHNpemU9Miwgdmp1c3Q9LTAuNSwgZm9udGZhY2U9ImJvbGQiKSArCiAgbGFicyh0aXRsZT0iVG90YWwgcmV2ZW51ZSBwZXIgbWFya2V0IGZyb20gMjAxMSB0byAyMDE0IiwgeD0iIiwgeT0iIikgKwogIHRoZW1lKGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCksCiAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgcGxvdC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSkKCmBgYAoKCiMjIFRvcCAxMCBjb3VudHJpZXMgYnkgU2FsZXMgVm9sdW1lCgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJ0b3Bfc2FsZXNfY291bnRyaWVzIn0KCnNlbGVjdCAqIGZyb20KKHNlbGVjdCAqLAogICAgICAgcm91bmQoKHNhbGVzIC0gbGFnKHNhbGVzKSBvdmVyIChQQVJUSVRJT04gQlkgbWFya2V0LCBjb3VudHJ5ICBPUkRFUiBCWSB5ZWFyKSkvCiAgICAgICBsYWcoc2FsZXMpIG92ZXIgKFBBUlRJVElPTiBCWSBtYXJrZXQsIGNvdW50cnkgIE9SREVSIEJZICB5ZWFyKSwgMikgYXMgWW9ZX0dyb3d0aF9TYWxlcywKICAgICAgIHJvdW5kKChwcm9maXQgLSBsYWcocHJvZml0KSBvdmVyIChQQVJUSVRJT04gQlkgbWFya2V0LCBjb3VudHJ5ICBPUkRFUiBCWSB5ZWFyKSkvCiAgICAgICBsYWcocHJvZml0KSBvdmVyIChQQVJUSVRJT04gQlkgbWFya2V0LCBjb3VudHJ5ICBPUkRFUiBCWSAgeWVhciksIDIpIGFzIFlvWV9Hcm93dGhfUHJvZml0CmZyb20KKHNlbGVjdCBtYXJrZXQsCiAgICAgICBjb3VudHJ5LAogICAgICAgZXh0cmFjdCh5ZWFyIGZyb20gb3JkZXJfZGF0ZSkgYXMgeWVhciwKICAgICAgIHN1bShzYWxlcykgYXMgc2FsZXMsCiAgICAgICBzdW0ocHJvZml0KSBhcyBwcm9maXQKZnJvbSBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycwpncm91cCBieSAxLCAyLCAzKSBhKSBhCndoZXJlIHllYXIgPSAyMDE0IE9SREVSIEJZIHNhbGVzIGRlc2MgbGltaXQgMTA7CgpgYGAKCmBgYHtyfQojIHByaW50IHJlc3VsdHMKdG9wX3NhbGVzX2NvdW50cmllcwoKYGBgCgoKYGBge3J9Cgp0b3Bfc2FsZXNfY291bnRyaWVzICU+JQogIG11dGF0ZV9hdChjKCJ5b3lfZ3Jvd3RoX3NhbGVzIiwgInlveV9ncm93dGhfcHJvZml0IiksIH5jZWxsX3NwZWMoc2NhbGVzOjpwZXJjZW50KC4sCiAgICBhY2N1cmFjeSA9IDAuMSksICJodG1sIiwgY29sb3IgPSBjYXNlX3doZW4oLiA8IDAgfiAicmVkIiwgLiA+PSAwIH4gImdyZWVuIiwKICAgIGlzLm5hKC4pIH4gImxpZ2h0Z3JheSIsIFRSVUUgfiAiYmxhY2siKSkpICU+JQogIG11dGF0ZV9hdChjKCJzYWxlcyIsICJwcm9maXQiKSwgfnNjYWxlczo6ZG9sbGFyKC4sIGFjY3VyYWN5ID0gMC4wMSkpICU+JQogIHJlbG9jYXRlKHlveV9ncm93dGhfc2FsZXMsIC5hZnRlciA9IHNhbGVzKSAlPiUKICBzZWxlY3QoLXllYXIpICU+JQogIGtibChjb2wubmFtZXMgPSBjKCJNYXJrZXQiLCAiQ291bnRyeSIsICJUb3RhbCIsICJZb1kgR3Jvd3RoIiwgIlRvdGFsIiwgIllvWSBHcm93dGgiKSwKICAgIGNhcHRpb24gPSAiVG9wIDEwIGNvdW50cmllcyBieSBTYWxlcyBWb2x1bWUgZm9yIDIwMTQiLCBlc2NhcGUgPSBGLAogICAgYWxpZ24gPSBjKCJsbHJycnIiKSkgJT4lCiAgcm93X3NwZWMoMCwgYWxpZ24gPSAiYyIpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImJvcmRlcmVkIiksIGZ1bGxfd2lkdGggPSBUUlVFKSAlPiUKICBhZGRfaGVhZGVyX2Fib3ZlKGhlYWRlciA9IGMoIiAiLCAiICIsIFNhbGVzID0gMiwgUHJvZml0ID0gMiksIGJvbGQgPSBUUlVFLCBib3JkZXJfbGVmdCA9IFRSVUUsCiAgICBib3JkZXJfcmlnaHQgPSBUUlVFKQoKYGBgCgpgYGB7cn0KCmNvdW50cmllc19jb29yZHMgPC0gdG9wX3NhbGVzX2NvdW50cmllcyAlPiUKICBnZW9jb2RlKGNvdW50cnkgPSBjb3VudHJ5LCBtZXRob2QgPSAnb3NtJywgbGF0ID0gbGF0aXR1ZGUgLCBsb25nID0gbG9uZ2l0dWRlKQoKZ2dwbG90KCkgKwogIGdlb21fbWFwKAogICAgZGF0YSA9IHdvcmxkLCBtYXAgPSB3b3JsZCwKICAgIGFlcyhsb25nLCBsYXQsIG1hcF9pZCA9IHJlZ2lvbiksCiAgICBjb2xvciA9ICJkYXJrZ3JheSIsIGZpbGwgPSAiI2VjZWNlYyIsIHNpemUgPSAwLjEKICApICsKICBnZW9tX3BvaW50KGRhdGE9Y291bnRyaWVzX2Nvb3JkcywgYWVzKHg9bG9uZ2l0dWRlLCB5PWxhdGl0dWRlLCBzaXplPXNhbGVzLCBjb2xvcj1tYXJrZXQsIGFscGhhPTAuNSksIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzY2FsZV9zaXplKHJhbmdlID0gYygxLCAyMCkpICsKICBnZW9tX3RleHQoZGF0YT1jb3VudHJpZXNfY29vcmRzLCBhZXMoeD1sb25naXR1ZGUsIHk9bGF0aXR1ZGUsIGxhYmVsPXNjYWxlczo6ZG9sbGFyKHNhbGVzLCBzY2FsZSA9IDFlLTMsIHN1ZmZpeCA9ICJLIiwgYWNjdXJhY3kgPSAwLjEpKSwgc2l6ZT0yLCB2anVzdD0xLjUpICsKICBnZW9tX3RleHQoZGF0YT1jb3VudHJpZXNfY29vcmRzLCBhZXMoeD1sb25naXR1ZGUsIHk9bGF0aXR1ZGUsIGxhYmVsPWNvdW50cnkpLCBzaXplPTEsIHZqdXN0PS0wLjUsIGZvbnRmYWNlPSJib2xkIikgKwogIGxhYnModGl0bGU9IlRvcCAxMCBjb3VudHJpZXMgYnkgU2FsZXMgVm9sdW1lIGZvciAyMDE0IiwgeD0iIiwgeT0iIikgKwogIHRoZW1lKGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCksCiAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uPSJub25lIiwKICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgcGxvdC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSkKCmBgYAoKCiMjIFNhbGVzIGFuYWx5c2lzIGhlYXQgbWFwIGJ5IGRheSBhbmQgbW9udGgKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9ImhlYXQifQoKU0VMRUNUICosCiAgICAgICBjb3VudF9vcmRlcnM6Om51bWVyaWMgLyAoU1VNKGNvdW50X29yZGVycykgT1ZFUiAoKSkgQVMgcHJvYwpGUk9NIChTRUxFQ1QgbW9udGgsCiAgICAgICAgICAgICBuX21vbnRoLAogICAgICAgICAgICAgd29kLAogICAgICAgICAgICAgbl93b2QsCiAgICAgICAgICAgICBDT1VOVChESVNUSU5DVCBvcmRlcl9pZCk6OklOVCBjb3VudF9vcmRlcnMKICAgICAgRlJPTSAoU0VMRUNUIERJU1RJTkNUIG9yZGVyX2lkLAogICAgICAgICAgICAgICAgRVhUUkFDVChNT05USCBGUk9NIEZJUlNUX1ZBTFVFKG9yZGVyX2RhdGUpIE9WRVIgKFBBUlRJVElPTiBCWSBvcmRlcl9pZCkpICBBUyBuX21vbnRoLAogICAgICAgICAgICAgICAgVE9fQ0hBUihGSVJTVF9WQUxVRShvcmRlcl9kYXRlKSBPVkVSIChQQVJUSVRJT04gQlkgb3JkZXJfaWQpLCAnTW9udGgnKSAgICBBUyBtb250aCwKICAgICAgICAgICAgICAgIEVYVFJBQ1QoSVNPRE9XIEZST00gRklSU1RfVkFMVUUob3JkZXJfZGF0ZSkgT1ZFUiAoUEFSVElUSU9OIEJZIG9yZGVyX2lkKSkgQVMgbl93b2QsCiAgICAgICAgICAgICAgICBUT19DSEFSKEZJUlNUX1ZBTFVFKG9yZGVyX2RhdGUpIE9WRVIgKFBBUlRJVElPTiBCWSBvcmRlcl9pZCksICdEYXknKSAgICAgIEFTIHdvZAogICAgICAgICAgICBGUk9NIGFuYWx5c2lzLmFsbF9nbG9iYWxfb3JkZXJzCiAgICAgICAgICAgIE9SREVSIEJZIG5fbW9udGgsIG5fd29kKSBhCiAgICAgIEdST1VQIEJZIG1vbnRoLCBuX21vbnRoLCB3b2QsIG5fd29kKSBhCk9SREVSIEJZIG5fbW9udGgsIG5fd29kOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCmhlYXQKCmBgYAoKCmBgYHtyfQoKaGVhdCAlPiUKICBtdXRhdGUod29kPWFzLmZhY3Rvcih3b2QpLCBtb250aD1hcy5mYWN0b3IobW9udGgpKSAlPiUKICBtdXRhdGUod29kPWZjdF9yZW9yZGVyKHdvZCwgbl93b2QpLCBtb250aD1mY3RfcmVvcmRlcihtb250aCwgLW5fbW9udGgpKSAlPiUKZ2dwbG90KGFlcyh4ID0gd29kLCB5ID0gbW9udGgsIGZpbGwgPSBwcm9jKSkgKyAKICBnZW9tX3RpbGUoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQoaGlnaCA9ICJyZWQiLCBsb3cgPSAid2hpdGUiLCBuYS52YWx1ZSA9ICJ3aGl0ZSIpICsKICBzY2FsZV94X2Rpc2NyZXRlKHBvc2l0aW9uID0gInRvcCIpICsKICBsYWJzKHRpdGxlPSJTYWxlcyBoZWF0IG1hcCBieSBkYXkgb2Ygd2VlayBhbmQgbW9udGggZnJvbSAyMDExIHRvIDIwMTQiLCB4PSJXZWVrZGF5IiwgeT0iTW9udGgiKQoKYGBgCgoKIyMgVG9wIDUgcmV0dXJuYWJsZSBzdWItY2F0ZWdvcmllcyBieSBtZXJrZXQKCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9InJldHVybmFibGVfc3ViX2NhdGVnb3J5In0KClNFTEVDVCBhLm1hcmtldCwKICAgICAgIGEubmV3X3N1Yl9jYXRlZ29yeSBBUyBzdWJfY2F0ZWdvcnksCiAgICAgICBTVU0oYS5wZXJjKSAgICAgICAgQVMgcGVyYwpGUk9NIChTRUxFQ1QgYS5tYXJrZXQsCiAgICAgICAgICAgICBhLnBlcmMsCiAgICAgICAgICAgICBDQVNFCiAgICAgICAgICAgICAgICAgV0hFTiBSQU5LKCkgT1ZFUiAoUEFSVElUSU9OIEJZIG1hcmtldCBPUkRFUiBCWSBwZXJjIERFU0MpIDw9IDUgVEhFTiBzdWJfY2F0ZWdvcnkKICAgICAgICAgICAgICAgICBFTFNFICdPdGhlcicKICAgICAgICAgICAgICAgICBFTkQgQVMgbmV3X3N1Yl9jYXRlZ29yeQogICAgICBGUk9NIChTRUxFQ1QgRElTVElOQ1QgbWFya2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ViX2NhdGVnb3J5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgUk9VTkQoQ09VTlQoKikgT1ZFUiAoUEFSVElUSU9OIEJZIG1hcmtldCwgc3ViX2NhdGVnb3J5KSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDT1VOVCgqKSBPVkVSIChQQVJUSVRJT04gQlkgbWFya2V0KTo6bnVtZXJpYywgMykgQVMgcGVyYwogICAgICAgICAgICBGUk9NIGFuYWx5c2lzLmFsbF9nbG9iYWxfb3JkZXJzCiAgICAgICAgICAgIFdIRVJFIHJldHVybmVkCiAgICAgICAgICAgIE9SREVSIEJZIG1hcmtldCwgcGVyYyBERVNDKSBhKSBhCkdST1VQIEJZIG1hcmtldCwgc3ViX2NhdGVnb3J5Ck9SREVSIEJZIG1hcmtldCwgcGVyYyBERVNDOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCnJldHVybmFibGVfc3ViX2NhdGVnb3J5CgpgYGAKCgpgYGB7cn0KCmZvciAobWFya2V0IGluIHVuaXF1ZShyZXR1cm5hYmxlX3N1Yl9jYXRlZ29yeSRtYXJrZXQpKSB7CiAgcHJpbnQocmV0dXJuYWJsZV9zdWJfY2F0ZWdvcnlbcmV0dXJuYWJsZV9zdWJfY2F0ZWdvcnkkbWFya2V0ID09IG1hcmtldCxdICU+JQogIGdncGxvdChhZXMoeCA9ICIiLCB5ID0gcGVyYywgZmlsbCA9IHN1Yl9jYXRlZ29yeSkpICsKICBnZW9tX2NvbCgpICsgCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc2NhbGVzOjpwZXJjZW50KHBlcmMpKSwgc2l6ZT0zLCBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSkgKwogIGNvb3JkX3BvbGFyKHRoZXRhID0gInkiKSArCiAgICBsYWJzKHRpdGxlPXBhc3RlKCJUb3AgNSByZXR1cm5hYmxlIHN1Yi1jYXRlZ29yaWVzIGluIiwgbWFya2V0LCAibWFya2V0IiksIHg9IiIsIHk9IiIsIGZpbGw9IlN1Yi1DYXRlZ29yeSIpICsKICAgIHRoZW1lKAogIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpCiAgKSAKCiAgfQoKYGBgCgojIyBXaGF0IGFyZSBudW1iZXIgb2YgdG90YWwgYW5kIG5ldyBjdXN0b21lcnM/CgpgYGB7c3FsIGNvbm5lY3Rpb249Y29uLCBvdXRwdXQudmFyPSJuZXdfdG90YWxfY3VzdG9tZXJzIn0KCldJVEggY3RlX3RpbWVzcG90IEFTIChTRUxFQ1QgbS5tYXJrZXQsIGEueWVhciwgYS5xdWFydGVyCiAgICAgICAgICAgICAgICAgICAgICBGUk9NIChTRUxFQ1QgRElTVElOQ1QgbWFya2V0IEZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMpIG0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENST1NTIEpPSU4KICAgICAgICAgICAgICAgICAgICAgICAgICAgKFNFTEVDVCBiLnllYXIsIGEucXVhcnRlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAoU0VMRUNUIFVOTkVTVChBUlJBWSBbMSwgMiwgMywgNF0pIEFTIHF1YXJ0ZXIpIGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENST1NTIEpPSU4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChTRUxFQ1QgVU5ORVNUKEFSUkFZIFsyMDExLCAyMDEyLCAyMDEzLCAyMDE0XSkgQVMgeWVhcikgYgogICAgICAgICAgICAgICAgICAgICAgICAgICAgT1JERVIgQlkgMSwgMikgYQogICAgICAgICAgICAgICAgICAgICAgT1JERVIgQlkgMSwgMiwgMyksCiAgICAgY3RlX3VuaXF1ZV9jdXN0b21lcnMgQVMgKFNFTEVDVCBtYXJrZXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBFWFRSQUNUKFlFQVIgRlJPTSBvcmRlcl9kYXRlKSAgICBBUyB5ZWFyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRVhUUkFDVChRVUFSVEVSIEZST00gb3JkZXJfZGF0ZSkgQVMgcXVhcnRlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENPVU5UKERJU1RJTkNUIGN1c3RvbWVyX2lkKSAgICAgIEFTIHVuaXF1ZV9jdXN0b21lcnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSBhbmFseXNpcy5hbGxfZ2xvYmFsX29yZGVycwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHUk9VUCBCWSAxLCAyLCAzKSwKICAgICBjdGVfbmV3X2N1c3RvbWVycyBBUyAoU0VMRUNUIG1hcmtldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWFydGVyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ09VTlQoRElTVElOQ1QgY3VzdG9tZXJfaWQpIEFTIG5ld19jdXN0b21lcnMKICAgICAgICAgICAgICAgICAgICAgICAgICAgRlJPTSAoU0VMRUNUIERJU1RJTkNUIG1hcmtldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGN1c3RvbWVyX2lkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRklSU1RfVkFMVUUoRVhUUkFDVChZRUFSIEZST00gb3JkZXJfZGF0ZSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPVkVSIChQQVJUSVRJT04gQlkgbWFya2V0LCBjdXN0b21lcl9pZCBPUkRFUiBCWSBFWFRSQUNUKFlFQVIgRlJPTSBvcmRlcl9kYXRlKSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFTIHllYXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGSVJTVF9WQUxVRShFWFRSQUNUKFFVQVJURVIgRlJPTSBvcmRlcl9kYXRlKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9WRVIgKFBBUlRJVElPTiBCWSBtYXJrZXQsIGN1c3RvbWVyX2lkIE9SREVSIEJZIEVYVFJBQ1QoWUVBUiBGUk9NIG9yZGVyX2RhdGUpLCBFWFRSQUNUKFFVQVJURVIgRlJPTSBvcmRlcl9kYXRlKSkgQVMgcXVhcnRlcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIGFuYWx5c2lzLmFsbF9nbG9iYWxfb3JkZXJzKSBhCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIG1hcmtldCwgeWVhciwgcXVhcnRlcikKU0VMRUNUIGEubWFya2V0LAogICAgICAgYS55ZWFyLAogICAgICAgYS5xdWFydGVyLAogICAgICAgYS51bmlxdWVfY3VzdG9tZXJzOjpJTlQsCiAgICAgICBDT0FMRVNDRShiLm5ld19jdXN0b21lcnMsIDApOjpJTlQgQVMgbmV3X2N1c3RvbWVycywKICAgICAgIENPTkNBVCgnUScsIGEucXVhcnRlciwgJycnJywgU1VCU1RSSU5HKFRFWFQoYS55ZWFyKSwgMywgMikpICAgICAgICAgICAgICAgQVMgcXksCiAgICAgICBST1dfTlVNQkVSKCkgT1ZFUiAoUEFSVElUSU9OIEJZIGEubWFya2V0IE9SREVSIEJZIGEueWVhciwgYS5xdWFydGVyKTo6SU5UIEFTIG4KRlJPTSBjdGVfdGltZXNwb3QgdAogICAgICAgICBMRUZUIEpPSU4KICAgICBjdGVfdW5pcXVlX2N1c3RvbWVycyBhIE9OIHQubWFya2V0ID0gYS5tYXJrZXQgQU5EIHQueWVhciA9IGEueWVhciBBTkQgdC5xdWFydGVyID0gYS5xdWFydGVyCiAgICAgICAgIExFRlQgSk9JTgogICAgIGN0ZV9uZXdfY3VzdG9tZXJzIGIgT04gYS5tYXJrZXQgPSBiLm1hcmtldCBBTkQgYS55ZWFyID0gYi55ZWFyIEFORCBhLnF1YXJ0ZXIgPSBiLnF1YXJ0ZXI7CgpgYGAKCmBgYHtyfQojIHByaW50IHJlc3VsdHMKbmV3X3RvdGFsX2N1c3RvbWVycwoKYGBgCgoKYGBge3IgbWVzc2FnZT1UUlVFLCB3YXJuaW5nPVRSVUV9Cgpmb3IgKG1hcmtldCBpbiB1bmlxdWUobmV3X3RvdGFsX2N1c3RvbWVycyRtYXJrZXQpKSB7CiAgcGxvdCA8LSBnZ3Bsb3QobmV3X3RvdGFsX2N1c3RvbWVyc1tuZXdfdG90YWxfY3VzdG9tZXJzJG1hcmtldD09bWFya2V0LF0pICsKICAgIGdlb21fYmFyKGFlcyh4PWZjdF9yZW9yZGVyKHF5LCBuKSwgeT11bmlxdWVfY3VzdG9tZXJzLCBmaWxsPSJVbmlxdWUgQ3VzdG9tZXJzIiksIHN0YXQ9ImlkZW50aXR5IikgKwogICAgZ2VvbV9saW5lKGFlcyh4PWZjdF9yZW9yZGVyKHF5LCBuKSwgeT1uZXdfY3VzdG9tZXJzLCBncm91cD0xLCBjb2xvcj0iTmV3IEN1c3RvbWVycyIpLCBzaXplPTEpICsgCiAgICBnZW9tX3BvaW50KGFlcyh4PWZjdF9yZW9yZGVyKHF5LCBuKSwgeT1uZXdfY3VzdG9tZXJzLCBjb2xvcj0iTmV3IEN1c3RvbWVycyIpLCBzaGFwZT0xOCwgc2l6ZT0zKSArCiAgICBnZW9tX3RleHQoYWVzKHg9ZmN0X3Jlb3JkZXIocXksIG4pLCB5PW5ld19jdXN0b21lcnMsIGxhYmVsPXNjYWxlczo6bnVtYmVyKG5ld19jdXN0b21lcnMpKSwgc2l6ZT0zLCB2anVzdD0tMS4yKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBzZXEoNC41LCAxMi41LCA0KSwgbGluZXR5cGU9ImRvdHRlZCIsIGNvbG9yPSJncmF5IikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwobmFtZT0iIiwgdmFsdWVzPWMoIlVuaXF1ZSBDdXN0b21lcnMiPSIjZTViYzhiIikpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLCB2YWx1ZXM9YygiTmV3IEN1c3RvbWVycyI9IiMwMDNmNWMiKSkgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgbWF4KG5ld190b3RhbF9jdXN0b21lcnNbbmV3X3RvdGFsX2N1c3RvbWVycyRtYXJrZXQ9PW1hcmtldCxdJHVuaXF1ZV9jdXN0b21lcnMpKjEuMSkpICsKICAgIGxhYnModGl0bGU9cGFzdGUoJ1RvdGFsIHVuaXF1ZSBhbmQgbmV3IGN1c3RvbWVycyBpbicsIG1hcmtldCwgJ21hcmtldCcpLCB4PSIiLCB5PSJOdW1iZXIgb2YgY3VzdG9tZXJzIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCkpCiAgICAKICBwcmludChwbG90KQp9CgpgYGAKCgojIyBQZXJjZW50YWdlIG9mIG5ldyBidXNpbmVzcyByZXZlbnVlIGdlbmVyYXRpb24KCmBgYHtzcWwgY29ubmVjdGlvbj1jb24sIG91dHB1dC52YXI9Im5ld19idXNpbmVzcyJ9CgpXSVRIIGN0ZV9hbGxfb3JkZXJzIEFTIChTRUxFQ1QgbWFya2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlcl9kYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3VzdG9tZXJfaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTVU0oc2FsZXMpIEFTIHNhbGVzCiAgICAgICAgICAgICAgICAgICAgICAgIEZST00gYW5hbHlzaXMuYWxsX2dsb2JhbF9vcmRlcnMKICAgICAgICAgICAgICAgICAgICAgICAgR1JPVVAgQlkgMSwgMiwgMywgNCkKU0VMRUNUICosCiAgICAgICBST1dfTlVNQkVSKCkgT1ZFUiAoUEFSVElUSU9OIEJZIG1hcmtldCBPUkRFUiBCWSB5ZWFyLCBxdWFydGVyKSBBUyBuCkZST00gKFNFTEVDVCBtYXJrZXQsCiAgICAgICAgICAgICB5ZWFyLAogICAgICAgICAgICAgcXVhcnRlciwKCiAgICAgICAgICAgICBDT05DQVQoJ1EnLCBxdWFydGVyLCAnJycnLCBTVUJTVFJJTkcoVEVYVCh5ZWFyKSwgMywgMikpICAgICAgICAgICAgICAgICAgICAgICAgICAgQVMgcXksCiAgICAgICAgICAgICBDT0FMRVNDRShTVU0oc2FsZXMpIEZJTFRFUiAoV0hFUkUgeWVhciA9IGZpcnN0X3llYXIpLCAwKSAgICAgICAgICAgICAgICAgICAgICAgICAgQVMgbmV3X3NhbGVzLAogICAgICAgICAgICAgU1VNKHNhbGVzKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFTIHRvdGFsX3NhbGVzLAogICAgICAgICAgICAgUk9VTkQoKENPQUxFU0NFKFNVTShzYWxlcykgRklMVEVSIChXSEVSRSB5ZWFyID0gZmlyc3RfeWVhciksIDApKSAvIFNVTShzYWxlcyksIDIpIEFTIHByb2NfbmV3X2J1c2luZXNzCiAgICAgIEZST00gKFNFTEVDVCAqLAogICAgICAgICAgICAgICAgICAgRVhUUkFDVChZRUFSIEZST00gb3JkZXJfZGF0ZSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQVMgeWVhciwKICAgICAgICAgICAgICAgICAgIEVYVFJBQ1QoUVVBUlRFUiBGUk9NIG9yZGVyX2RhdGUpICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFTIHF1YXJ0ZXIsCiAgICAgICAgICAgICAgICAgICBGSVJTVF9WQUxVRShFWFRSQUNUKFlFQVIgRlJPTSBvcmRlcl9kYXRlKSkKICAgICAgICAgICAgICAgICAgIE9WRVIgKFBBUlRJVElPTiBCWSBtYXJrZXQsIGN1c3RvbWVyX2lkIE9SREVSIEJZIG9yZGVyX2RhdGUpIEFTIGZpcnN0X3llYXIKICAgICAgICAgICAgRlJPTSBjdGVfYWxsX29yZGVycykgYQogICAgICBHUk9VUCBCWSBtYXJrZXQsIHllYXIsIHF1YXJ0ZXIsIHF5KSBhOwoKYGBgCgpgYGB7cn0KIyBwcmludCByZXN1bHRzCm5ld19idXNpbmVzcwpgYGAKCmBgYHtyfQoKZm9yIChtYXJrZXQgaW4gdW5pcXVlKG5ld19idXNpbmVzcyRtYXJrZXQpKSB7CiAgc2NhbGUgPC0gbWF4KG5ld19idXNpbmVzc1tuZXdfYnVzaW5lc3MkbWFya2V0ID09IG1hcmtldCxdJHRvdGFsX3NhbGVzKQogIGhpIDwtIHJvdW5kKHNjYWxlKjEuMSAvIDEwXmZsb29yKGxvZzEwKHNjYWxlKjEuMSkpLCAwKSoxMF5mbG9vcihsb2cxMChzY2FsZSoxLjEpKQogIHNjYWxlIDwtIHNjYWxlICogMC44CiAgcGxvdCA8LSBuZXdfYnVzaW5lc3NbbmV3X2J1c2luZXNzJG1hcmtldCA9PSBtYXJrZXQsXSAlPiUKICAgICAgICAgIGdncGxvdCgpICsgCiAgICAgICAgICBnZW9tX2JhcihhZXMoeD1mY3RfcmVvcmRlcihxeSwgbiksIHk9dG90YWxfc2FsZXMsIGZpbGw9IlRvdGFsIFJldmVudWUiKSwgc3RhdD0iaWRlbnRpdHkiKSsKICAgICAgICAgIGdlb21fYmFyKGFlcyh4PWZjdF9yZW9yZGVyKHF5LCBuKSwgeT1uZXdfc2FsZXMsIGZpbGw9Ik5ldyBSZXZlbnVlIiksIHN0YXQ9ImlkZW50aXR5IikrCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHg9ZmN0X3Jlb3JkZXIocXksIG4pLCB5PXByb2NfbmV3X2J1c2luZXNzKnNjYWxlLCBncm91cD0xLCBjb2xvcj0iTmV3IEJ1c2luZXNzIikpICsKICAgICAgICAgIGdlb21fcG9pbnQoYWVzKHg9ZmN0X3Jlb3JkZXIocXksIG4pLCB5PXByb2NfbmV3X2J1c2luZXNzKnNjYWxlLCBjb2xvcj0iTmV3IEJ1c2luZXNzIiksIHNoYXBlPTE4LCBzaXplPTMpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMoeD1mY3RfcmVvcmRlcihxeSwgbiksIHk9cHJvY19uZXdfYnVzaW5lc3Mqc2NhbGUsIGxhYmVsPXNjYWxlczo6cGVyY2VudChwcm9jX25ld19idXNpbmVzcywgYWNjdXJhY3kgPSAwLjEpKSwgc2hhcGU9MTgsIHNpemU9Mi41LCB2anVzdD0tMSkgKwogICAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gc2VxKDQuNSwgMTIuNSwgNCksIGxpbmV0eXBlPSJkb3R0ZWQiLCBjb2xvcj0iZ3JheSIpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGhpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIGhpLCBieSA9IGhpLzUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6bnVtYmVyKHNlcSgwLCBoaSwgYnkgPSBoaS81KSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSAxMF4tKGZsb29yKGxvZzEwKGhpKSkgLSBmbG9vcihsb2cxMChoaSkpICUlIDMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWZmaXggPSBpZmVsc2UoZmxvb3IobG9nMTAoaGkpKSAtIGZsb29yKGxvZzEwKGhpKSkgJSUgMyA+IDMsICJNIiwgIksiKSkpICsKICAgICAgICAgIGxhYnModGl0bGU9cGFzdGUoIlBlcmNlbnRhZ2Ugb2YgbmV3IGJ1c2luZXNzIHJldmVudWUgZ2VuZXJhdGlvbiBpbiIsIG1hcmtldCwgJ21hcmtldCcpLAogICAgICAgICAgICAgICB4PSIiLCB5PSJSZXZlbnVlLCAkIikgKwogICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCiAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKSArCiAgICBzY2FsZV9maWxsX21hbnVhbChuYW1lPSIiLCB2YWx1ZXMgPSBjKCJUb3RhbCBSZXZlbnVlIj0iI2RiZTVkYSIsICJOZXcgUmV2ZW51ZSI9IiNiYmNiYmMiKSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoIk5ldyBCdXNpbmVzcyI9IiM1ZTg3NjUiKSkKICAgIAogIHByaW50KHBsb3QpCn0KCgpgYGAKCgpgYGB7cn0KCiMgRGlzY29ubmVjdCBEQiBjb25uZWN0aW9uCmRiRGlzY29ubmVjdChjb25uID0gY29uKQoKYGBgCgo=