1 Basic Setup

# Use pacman to load packages
pacman::p_load(tidyverse, stringr, pander)
# Set working directory to the external drive containing the IRI dataset
volume_dir <- "/Volumes/ADS_235/Academic Dataset External"
base_dir <- ''
data_dir <- paste0(base_dir, "data/")
viz_dir <- paste0(base_dir, "viz/")
dir.create(data_dir, showWarnings = FALSE)
dir.create(viz_dir, showWarnings = FALSE)
panderOptions('big.mark', ',')

2 Data Size Assessment

Before we begin loading data, let’s start by assessing the data size of each of the summary files.

2.1 Tissue Category

We use the shell command find . -name "myfile" | xargs wc - l to recursively find a file with a particular regular expression construction and then perform the line count.

tissue_category <- "factiss"
if (!file.exists(paste0(data_dir, "tissue_wc.RDS"))) {
    
    tissue_wc <- system(paste0("cd '", volume_dir,"';
            find . -name '", tissue_category, "_drug*' -o -name '", tissue_category, "_groc*' | xargs wc -l"), intern = TRUE)
    saveRDS(tissue_wc, paste0(data_dir, "tissue_wc.RDS"))
}

The table below lists the file paths within the /Volumes/ADS_235/Academic Dataset External folder and the number of records.

tissue_wc <- readRDS(paste0(data_dir, "tissue_wc.RDS"))
tissue_weekly_files <- 
    tissue_wc %>%
    stringr::str_trim(side = "left") %>%
    stringr::str_split_fixed(" ", n = 2) %>%
    tibble::as_tibble() %>%
    rename(records = V1, file = V2) %>%
    dplyr::mutate(records = as.integer(records)) %>%
    dplyr::mutate(records_clean = prettyNum(records, big.mark = ",")) %>%
    # Use the fact that the last couplet of 4 digits create the proper ordering
    dplyr::arrange(as.integer(str_replace(str_sub(file, -9,-1), "_",""))) %>%
    dplyr::select(file, records, records_clean)
tissue_weekly_files %>% 
    select(file, records = records_clean) %>%
    pander(justify = c("left","right"))
file records
./Year1/External/factiss/factiss_drug_1114_1165 276,046
./Year1/External/factiss/factiss_groc_1114_1165 1,861,943
./Year2/External/factiss/factiss_drug_1166_1217 262,577
./Year2/External/factiss/factiss_groc_1166_1217 1,816,241
./Year3/External/factiss/factiss_drug_1218_1269 260,883
./Year3/External/factiss/factiss_groc_1218_1269 1,919,860
./Year4/External/factiss/factiss_drug_1270_1321 267,729
./Year4/External/factiss/factiss_groc_1270_1321 1,871,891
./Year5/External/factiss/factiss_drug_1322_1373 313,035
./Year5/External/factiss/factiss_groc_1322_1373 1,941,955
./Year6/External/factiss/factiss_drug_1374_1426 326,100
./Year6/External/factiss/factiss_groc_1374_1426 1,959,222
./Year7/External/factiss/factiss_drug_1427_1478 353,290
./Year7/External/factiss/factiss_groc_1427_1478 1,853,608
./Year8/factiss/factiss_drug_1479_1530 395,891
./Year8/factiss/factiss_groc_1479_1530 1,888,008
./Year9/factiss/factiss_drug_1531_1582 371,024
./Year9/factiss/factiss_groc_1531_1582 1,823,431
./Year10/factiss/factiss_drug_1583_1634 356,381
./Year10/factiss/factiss_groc_1583_1634 1,702,116
./Year11/factiss/factiss_drug_1635_1686 359,005
./Year11/factiss/factiss_groc_1635_1686 1,666,630
total 23,846,866

2.2 Carbonated Beverage Category

We then do the same process for the carbonated beverage category.

carbbev_category <- "carbbev"
if (!file.exists(paste0(data_dir, "carbbev_wc.RDS"))) {
    carbbev_wc <- system(paste0("cd '", volume_dir,"';
                find . -name '", carbbev_category, "_drug*' -o -name '", carbbev_category, "_groc*' | xargs wc -l"), intern = TRUE)
    
    saveRDS(carbbev_wc, paste0(data_dir, "carbbev_wc.RDS"))
}

We note the size difference. However, there are different sub-categories within the carbonated beverage category.

carbbev_wc <- readRDS(paste0(data_dir, "carbbev_wc.RDS"))
carbbev_weekly_files <- 
    carbbev_wc %>%
    stringr::str_trim(side = "left") %>%
    stringr::str_split_fixed(" ", n = 2) %>%
    tibble::as_tibble() %>%
    rename(records = V1, file = V2) %>%
    dplyr::mutate(records = as.integer(records)) %>%
    dplyr::mutate(records_clean = prettyNum(records, big.mark = ",")) %>%
    # Use the fact that the last couplet of 4 digits create the proper ordering
    dplyr::arrange(as.integer(str_replace(str_sub(file, -9,-1), "_",""))) %>%
    dplyr::select(file, records, records_clean)
    
carbbev_weekly_files %>% 
    select(file, records = records_clean) %>%
    pander(justify = c("left","right"))
file records
./Year1/External/carbbev/carbbev_drug_1114_1165 1,076,917
./Year1/External/carbbev/carbbev_groc_1114_1165 16,250,083
./Year2/External/carbbev/carbbev_drug_1166_1217 1,170,728
./Year2/External/carbbev/carbbev_groc_1166_1217 17,010,319
./Year3/External/carbbev/carbbev_drug_1218_1269 1,284,562
./Year3/External/carbbev/carbbev_groc_1218_1269 18,736,690
./Year4/External/carbbev/carbbev_drug_1270_1321 1,317,722
./Year4/External/carbbev/carbbev_groc_1270_1321 18,920,681
./Year5/External/carbbev/carbbev_drug_1322_1373 1,423,878
./Year5/External/carbbev/carbbev_groc_1322_1373 19,393,965
./Year6/External/carbbev/carbbev_drug_1374_1426 1,446,004
./Year6/External/carbbev/carbbev_groc_1374_1426 19,739,033
./Year7/External/carbbev/carbbev_drug_1427_1478 1,457,445
./Year7/External/carbbev/carbbev_groc_1427_1478 18,833,115
./Year8/carbbev/carbbev_drug_1479_1530 1,609,317
./Year8/carbbev/carbbev_groc_1479_1530 19,274,904
./Year9/carbbev/carbbev_drug_1531_1582 1,583,182
./Year9/carbbev/carbbev_groc_1531_1582 19,811,582
./Year10/carbbev/carbbev_drug_1583_1634 1,546,509
./Year10/carbbev/carbbev_groc_1583_1634 19,614,571
./Year11/carbbev/carbbev_drug_1635_1686 1,542,187
./Year11/carbbev/carbbev_groc_1635_1686 19,241,642
total 222,285,036

3 UPC Lookup Table

Next we load the UPC lookup tables. From the documentation’s explanation, we need three as the UPC descriptions were changed between periods.

3.1 Tissue Category

Each UPC lookup table has the same structure (defined in the documentation), so we load the three and combine and eliminate unnecessary columns.

tissue_upc_year_1_to_6 <- 
    readxl::read_excel(paste0(volume_dir, "/parsed stub files/prod_tissue.xls")) %>%
    dplyr::mutate(iri_year = '1-6')
tissue_upc_year_7 <- 
    readxl::read_excel(paste0(volume_dir, "/parsed stub files 2007/prod_factiss.xlsx")) %>%
    dplyr::mutate(iri_year = '7')
tissue_upc_year_8_to_11 <- 
    readxl::read_excel(paste0(volume_dir, "/parsed stub files 2008-2011/prod11_factiss.xlsx")) %>%
    dplyr::mutate(iri_year = '8-11')
tissue_upc <- 
    bind_rows(
     tissue_upc_year_1_to_6, tissue_upc_year_7, tissue_upc_year_8_to_11
    ) %>% 
    rename(
        large_category = L1
        , small_category = L2
        , parent_company = L3
        , vendor = L4
        , brand = L5
        , upc = UPC
    ) %>%
    # These positions are outlined in the documentation
    select(1,2,3,4,5,8, 15:22) %>%
    setNames(tolower(make.names(names(.)))) %>%
    select(iri_year, everything())

Below is a table of the first 20 records.

tissue_upc %>% 
    select(parent_company, vendor, brand) %>%
    distinct() %>%
    arrange(parent_company, vendor, brand) %>%
    head(20)

3.2 Carbonated Beverage Category

carbbev_upc_year_1_to_6 <- 
    readxl::read_excel(paste0(volume_dir, "/parsed stub files/prod_carbbev.xls")) %>%
    dplyr::mutate(iri_year = '1-6')
carbbev_upc_year_7 <- 
    readxl::read_excel(paste0(volume_dir, "/parsed stub files 2007/prod_carbbev.xlsx")) %>%
    dplyr::mutate(iri_year = '7')
carbbev_upc_year_8_to_11 <- 
    readxl::read_excel(paste0(volume_dir, "/parsed stub files 2008-2011/prod11_carbbev.xlsx")) %>%
    dplyr::mutate(iri_year = '8-11')
carbbev_upc <- 
    bind_rows(
     carbbev_upc_year_1_to_6, carbbev_upc_year_7, carbbev_upc_year_8_to_11
    ) %>% 
    rename(
        large_category = L1
        , small_category = L2
        , parent_company = L3
        , vendor = L4
        , brand = L5
        , upc = UPC
    ) %>%
    # These positions are outlined in the documentation
    select(1,2,3,4,5,8, 15:22) %>%
    setNames(tolower(make.names(names(.)))) %>%
    select(iri_year, everything())

There are six sub-categories:

carbbev_upc %>%
    group_by(small_category) %>%
    summarise(`UPC's` = n()) %>%
    arrange(desc(`UPC's`)) %>%
    pandoc.table(justify = c("left","right"))
small_category UPC’s
REGULAR SOFT DRINKS 25,840
LOW CALORIE SOFT DRINKS 7,949
SELTZER/TONIC WATER/CLUB SODA 2,490
PLU SOFT DRINKS 926
PLU - ALL BRANDS SODA 61
SUGAR/CALORIE REDUCED SOFT DRINK 56

Even excluding the categories, there are 1,019 company / vender / brand combinations. Below are the first 20:

carbbev_upc %>% 
    select(parent_company, vendor, brand) %>%
    distinct() %>%
    arrange(parent_company, vendor, brand) %>%
    head(20)

4 Sales Information

To load the sales information we can write a function that will load each file using read_fwf (with the column widths found by reviewing the files). We do not use read_table as this can potentially be unreliable with large datasets (see Hadly’s comment). We also add the year, store type, and ID (row number for reference) as preceding columns.

fn_load_weekly_file <- function(filename) {
    year_num <- as.integer(str_match(filename, "Year([:digit:]+)")[, 2])
    drug_or_groc <- str_match(filename, "drug|groc")[, 1]
    column_widths <- c(7, 5, 3, 3, 6, 6, 6, 9, 5, 2, 2)
    column_names <- c('IRI_KEY','WEEK','SY','GE','VEND','ITEM','UNITS','DOLLARS','F','D','PR')
    column_types <- cols(
                      IRI_KEY = col_character(),
                      WEEK = col_integer(),
                      SY = col_character(),
                      GE = col_character(),
                      VEND = col_character(),
                      ITEM = col_character(),
                      UNITS = col_integer(),
                      DOLLARS = col_double(),
                      `F` = col_character(),
                      D = col_integer(),
                      PR = col_integer()
                    )
    
    read_fwf(paste0(volume_dir, str_replace(filename, ".", "")), 
             fwf_widths(column_widths, column_names), col_types = column_types,
             skip = 1, progress = FALSE) %>%
        mutate(iri_year = year_num, id = row_number(), store_type = drug_or_groc) %>%
        select(iri_year, id, store_type, everything())
}
fn_integrity_check <- function(dt) {
    records <- dt %>% nrow()
    unique_records <- 
        dt %>%
        select(IRI_KEY, WEEK, SY, GE, VEND, ITEM) %>%
        distinct() %>%
        nrow()
    return(data_frame(records_raw = records, unique_records = unique_records))
}

4.1 Tissue Category

We load each of the store datasets and perform data integrity checks. We show that the number of records loaded is one less than we found with the wc -l shell command (due to the header) and that the number of records unique on store / week / upc (SY, GE, VEND, ITEM) is the total number of records loaded.

tissue_files <- tissue_weekly_files$file[-length(tissue_weekly_files$file)]
tissue_files_list <- lapply(tissue_files, fn_load_weekly_file)
tissue_weekly <- bind_rows(tissue_files_list)
lapply(tissue_files_list, fn_integrity_check) %>%
    bind_rows() %>%
    bind_rows(data_frame(records_raw = sum(.$records_raw), unique_records = sum(.$unique_records))) %>%
    bind_cols(tissue_weekly_files) %>%
    mutate(
         n = n()
         , `Pass?` = if_else(file == "total", 
                            if_else(records_raw == (records - n + 1) & unique_records == records_raw, "Yes","No"), 
                            if_else(records_raw == (records - 1) & unique_records == records_raw, "Yes","No"))
    ) %>%
    select(File = file, Records = records_clean, `Records Loaded` = records_raw, 
           `Unique on Store/Week/UPC` = unique_records, `Pass?`) %>%
    pander(split.table = Inf, justify = c('left', 'right','right','right','center'))
File Records Records Loaded Unique on Store/Week/UPC Pass?
./Year1/External/factiss/factiss_drug_1114_1165 276,046 276,045 276,045 Yes
./Year1/External/factiss/factiss_groc_1114_1165 1,861,943 1,861,942 1,861,942 Yes
./Year2/External/factiss/factiss_drug_1166_1217 262,577 262,576 262,576 Yes
./Year2/External/factiss/factiss_groc_1166_1217 1,816,241 1,816,240 1,816,240 Yes
./Year3/External/factiss/factiss_drug_1218_1269 260,883 260,882 260,882 Yes
./Year3/External/factiss/factiss_groc_1218_1269 1,919,860 1,919,859 1,919,859 Yes
./Year4/External/factiss/factiss_drug_1270_1321 267,729 267,728 267,728 Yes
./Year4/External/factiss/factiss_groc_1270_1321 1,871,891 1,871,890 1,871,890 Yes
./Year5/External/factiss/factiss_drug_1322_1373 313,035 313,034 313,034 Yes
./Year5/External/factiss/factiss_groc_1322_1373 1,941,955 1,941,954 1,941,954 Yes
./Year6/External/factiss/factiss_drug_1374_1426 326,100 326,099 326,099 Yes
./Year6/External/factiss/factiss_groc_1374_1426 1,959,222 1,959,221 1,959,221 Yes
./Year7/External/factiss/factiss_drug_1427_1478 353,290 353,289 353,289 Yes
./Year7/External/factiss/factiss_groc_1427_1478 1,853,608 1,853,607 1,853,607 Yes
./Year8/factiss/factiss_drug_1479_1530 395,891 395,890 395,890 Yes
./Year8/factiss/factiss_groc_1479_1530 1,888,008 1,888,007 1,888,007 Yes
./Year9/factiss/factiss_drug_1531_1582 371,024 371,023 371,023 Yes
./Year9/factiss/factiss_groc_1531_1582 1,823,431 1,823,430 1,823,430 Yes
./Year10/factiss/factiss_drug_1583_1634 356,381 356,380 356,380 Yes
./Year10/factiss/factiss_groc_1583_1634 1,702,116 1,702,115 1,702,115 Yes
./Year11/factiss/factiss_drug_1635_1686 359,005 359,004 359,004 Yes
./Year11/factiss/factiss_groc_1635_1686 1,666,630 1,666,629 1,666,629 Yes
total 23,846,866 23,846,844 23,846,844 Yes

We are going to construct a UPC column from the columns SY, GE, VEND, and ITEM in the sales information table. We can show that all tissue UPC’s in the UPC Lookup Table are 3765 characters long by

table(sapply(tissue_upc$upc, nchar)) %>%
    pander()
17
3,765

We create a UPC from the components and properly name the columns.

tissue_weekly2 <- 
    tissue_weekly %>%
        mutate(
            upc = paste(str_pad(SY, width = 2, "left", "0"), str_pad(GE, width = 2, "left", "0"), 
                           str_pad(VEND, width = 5, "left", "0"), str_pad(ITEM, width = 5, "left", "0"), sep = "-")
            , avg_price = DOLLARS / UNITS
        ) %>% 
        rename(feature = `F`, display = D, price_reduction = PR) %>%
        setNames(tolower(make.names(names(.)))) %>%
        select(iri_year, iri_key, week, upc, units, dollars, avg_price, feature, display, price_reduction)

Now, let’s verify again that this dataset is indeed at the store (iri_key) - week - UPC level of detail, with the new UPC column.

data_frame(
    `Record Count` = tissue_weekly2 %>% nrow()
    , `Store - Week - UPC Combinations` = tissue_weekly2 %>% select(iri_key, week, upc) %>% distinct() %>% nrow()
) %>%
    pander()
Record Count Store - Week - UPC Combinations
23,846,844 23,846,844

With that assurance, let’s pull in the details about each UPC. We do this for each year window because the documentation indicated that UPC information (potentially including company) changed between the time windows. We then do a check that no records were dropped in the inner join.

tissue_weekly_1_to_6 <- 
    tissue_weekly2 %>%
        filter(iri_year <= 6) %>%
        inner_join(
            tissue_upc %>%
                filter(iri_year == "1-6") %>%
                select(-iri_year)
            , by = c("upc" = 'upc')
        )
tissue_weekly_7 <- 
    tissue_weekly2 %>%
        filter(iri_year == 7) %>%
        inner_join(
            tissue_upc %>%
                filter(iri_year == "7") %>%
                select(-iri_year)
            , by = c("upc" = 'upc')
        )
tissue_weekly_8_to_11 <- 
    tissue_weekly2 %>%
        filter(iri_year >= 8) %>%
        inner_join(
            tissue_upc %>%
                filter(iri_year == "8-11") %>%
                select(-iri_year)
            , by = c("upc" = 'upc')
        )
data_frame(
    Years = c("1-6", "7", "8-11")
    , `Record Count in Weekly Data` = sapply(list(filter(tissue_weekly2, iri_year <= 6), 
                                                  filter(tissue_weekly2, iri_year == 7), filter(tissue_weekly2, iri_year >= 8)), nrow)
    , `Record Count in Joined Data` = sapply(list(tissue_weekly_1_to_6, tissue_weekly_7, tissue_weekly_8_to_11), nrow)
) %>%
    pander()
Years Record Count in Weekly Data Record Count in Joined Data
1-6 13,077,470 13,077,470
7 2,206,896 2,206,896
8-11 8,562,478 8,562,478

This passes our data integrity check, so we’ll combine the 3 time windows datasets.

tissue_final <- bind_rows(tissue_weekly_1_to_6, tissue_weekly_7, tissue_weekly_8_to_11)
tissue_final %>%
    head(20)

4.2 Carbonated Beverage Category

LS0tCnRpdGxlOiAiRHluYW1pYyBCcmFuZCBFcXVpdHkiCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIiVCICVkLCAlWSAlSDolTSIpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNzczogc3R5bGUuY3NzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IGZsYXRseQogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBmaWdfY2FwdGlvbjogdHJ1ZQotLS0KCiMgQmFzaWMgU2V0dXAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQojIFNldCBvcHRpb25zIGZvciB0aGUgcm1hcmtkb3duIGZpbGUKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcicsIHdpZHRoID0gMTAwKQppbnZpc2libGUoU3lzLnNldGxvY2FsZSgiTENfQUxMIiwgImVuX1VTLlVURi04IikpCm9wdGlvbnMoZGlnaXRzID0gNCwgd2lkdGggPSAyMjApICMgUHJldmVudCBwcmludGluZyBpbiBzY2llbnRpZmljIG5vdGF0aW9uCmBgYAoKYGBge3J9CiMgVXNlIHBhY21hbiB0byBsb2FkIHBhY2thZ2VzCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSwgc3RyaW5nciwgcGFuZGVyKQoKIyBTZXQgd29ya2luZyBkaXJlY3RvcnkgdG8gdGhlIGV4dGVybmFsIGRyaXZlIGNvbnRhaW5pbmcgdGhlIElSSSBkYXRhc2V0CnZvbHVtZV9kaXIgPC0gIi9Wb2x1bWVzL0FEU18yMzUvQWNhZGVtaWMgRGF0YXNldCBFeHRlcm5hbCIKCmJhc2VfZGlyIDwtICcnCmRhdGFfZGlyIDwtIHBhc3RlMChiYXNlX2RpciwgImRhdGEvIikKdml6X2RpciA8LSBwYXN0ZTAoYmFzZV9kaXIsICJ2aXovIikKCmRpci5jcmVhdGUoZGF0YV9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpkaXIuY3JlYXRlKHZpel9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQoKcGFuZGVyT3B0aW9ucygnYmlnLm1hcmsnLCAnLCcpCmBgYAoKIyBEYXRhIFNpemUgQXNzZXNzbWVudAoKQmVmb3JlIHdlIGJlZ2luIGxvYWRpbmcgZGF0YSwgbGV0J3Mgc3RhcnQgYnkgYXNzZXNzaW5nIHRoZSBkYXRhIHNpemUgb2YgZWFjaCBvZiB0aGUgc3VtbWFyeSBmaWxlcy4KCiMjIFRpc3N1ZSBDYXRlZ29yeQoKV2UgdXNlIHRoZSBzaGVsbCBjb21tYW5kIGBmaW5kIC4gLW5hbWUgIm15ZmlsZSIgfCB4YXJncyB3YyAtIGxgIHRvIHJlY3Vyc2l2ZWx5IGZpbmQgYSBmaWxlIHdpdGggYSBwYXJ0aWN1bGFyIHJlZ3VsYXIgZXhwcmVzc2lvbiBjb25zdHJ1Y3Rpb24gYW5kIHRoZW4gcGVyZm9ybSB0aGUgbGluZSBjb3VudC4KCmBgYHtyfQp0aXNzdWVfY2F0ZWdvcnkgPC0gImZhY3Rpc3MiCgppZiAoIWZpbGUuZXhpc3RzKHBhc3RlMChkYXRhX2RpciwgInRpc3N1ZV93Yy5SRFMiKSkpIHsKICAgIAogICAgdGlzc3VlX3djIDwtIHN5c3RlbShwYXN0ZTAoImNkICciLCB2b2x1bWVfZGlyLCInOwogICAgICAgICAgICBmaW5kIC4gLW5hbWUgJyIsIHRpc3N1ZV9jYXRlZ29yeSwgIl9kcnVnKicgLW8gLW5hbWUgJyIsIHRpc3N1ZV9jYXRlZ29yeSwgIl9ncm9jKicgfCB4YXJncyB3YyAtbCIpLCBpbnRlcm4gPSBUUlVFKQoKICAgIHNhdmVSRFModGlzc3VlX3djLCBwYXN0ZTAoZGF0YV9kaXIsICJ0aXNzdWVfd2MuUkRTIikpCn0KYGBgCgpUaGUgdGFibGUgYmVsb3cgbGlzdHMgdGhlIGZpbGUgcGF0aHMgd2l0aGluIHRoZSBgYHIgdm9sdW1lX2RpcmBgIGZvbGRlciBhbmQgdGhlIG51bWJlciBvZiByZWNvcmRzLgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdGlzc3VlX3djIDwtIHJlYWRSRFMocGFzdGUwKGRhdGFfZGlyLCAidGlzc3VlX3djLlJEUyIpKQoKdGlzc3VlX3dlZWtseV9maWxlcyA8LSAKICAgIHRpc3N1ZV93YyAlPiUKICAgIHN0cmluZ3I6OnN0cl90cmltKHNpZGUgPSAibGVmdCIpICU+JQogICAgc3RyaW5ncjo6c3RyX3NwbGl0X2ZpeGVkKCIgIiwgbiA9IDIpICU+JQogICAgdGliYmxlOjphc190aWJibGUoKSAlPiUKICAgIHJlbmFtZShyZWNvcmRzID0gVjEsIGZpbGUgPSBWMikgJT4lCiAgICBkcGx5cjo6bXV0YXRlKHJlY29yZHMgPSBhcy5pbnRlZ2VyKHJlY29yZHMpKSAlPiUKICAgIGRwbHlyOjptdXRhdGUocmVjb3Jkc19jbGVhbiA9IHByZXR0eU51bShyZWNvcmRzLCBiaWcubWFyayA9ICIsIikpICU+JQogICAgIyBVc2UgdGhlIGZhY3QgdGhhdCB0aGUgbGFzdCBjb3VwbGV0IG9mIDQgZGlnaXRzIGNyZWF0ZSB0aGUgcHJvcGVyIG9yZGVyaW5nCiAgICBkcGx5cjo6YXJyYW5nZShhcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cl9zdWIoZmlsZSwgLTksLTEpLCAiXyIsIiIpKSkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGZpbGUsIHJlY29yZHMsIHJlY29yZHNfY2xlYW4pCgp0aXNzdWVfd2Vla2x5X2ZpbGVzICU+JSAKICAgIHNlbGVjdChmaWxlLCByZWNvcmRzID0gcmVjb3Jkc19jbGVhbikgJT4lCiAgICBwYW5kZXIoanVzdGlmeSA9IGMoImxlZnQiLCJyaWdodCIpKQpgYGAKCiMjIENhcmJvbmF0ZWQgQmV2ZXJhZ2UgQ2F0ZWdvcnkKCldlIHRoZW4gZG8gdGhlIHNhbWUgcHJvY2VzcyBmb3IgdGhlIGNhcmJvbmF0ZWQgYmV2ZXJhZ2UgY2F0ZWdvcnkuCgpgYGB7cn0KY2FyYmJldl9jYXRlZ29yeSA8LSAiY2FyYmJldiIKCmlmICghZmlsZS5leGlzdHMocGFzdGUwKGRhdGFfZGlyLCAiY2FyYmJldl93Yy5SRFMiKSkpIHsKCiAgICBjYXJiYmV2X3djIDwtIHN5c3RlbShwYXN0ZTAoImNkICciLCB2b2x1bWVfZGlyLCInOwogICAgICAgICAgICAgICAgZmluZCAuIC1uYW1lICciLCBjYXJiYmV2X2NhdGVnb3J5LCAiX2RydWcqJyAtbyAtbmFtZSAnIiwgY2FyYmJldl9jYXRlZ29yeSwgIl9ncm9jKicgfCB4YXJncyB3YyAtbCIpLCBpbnRlcm4gPSBUUlVFKQogICAgCiAgICBzYXZlUkRTKGNhcmJiZXZfd2MsIHBhc3RlMChkYXRhX2RpciwgImNhcmJiZXZfd2MuUkRTIikpCn0KYGBgCgpXZSBub3RlIHRoZSBzaXplIGRpZmZlcmVuY2UuIEhvd2V2ZXIsIHRoZXJlIGFyZSBkaWZmZXJlbnQgc3ViLWNhdGVnb3JpZXMgd2l0aGluIHRoZSBjYXJib25hdGVkIGJldmVyYWdlIGNhdGVnb3J5LgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KY2FyYmJldl93YyA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwgImNhcmJiZXZfd2MuUkRTIikpCgpjYXJiYmV2X3dlZWtseV9maWxlcyA8LSAKICAgIGNhcmJiZXZfd2MgJT4lCiAgICBzdHJpbmdyOjpzdHJfdHJpbShzaWRlID0gImxlZnQiKSAlPiUKICAgIHN0cmluZ3I6OnN0cl9zcGxpdF9maXhlZCgiICIsIG4gPSAyKSAlPiUKICAgIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgICByZW5hbWUocmVjb3JkcyA9IFYxLCBmaWxlID0gVjIpICU+JQogICAgZHBseXI6Om11dGF0ZShyZWNvcmRzID0gYXMuaW50ZWdlcihyZWNvcmRzKSkgJT4lCiAgICBkcGx5cjo6bXV0YXRlKHJlY29yZHNfY2xlYW4gPSBwcmV0dHlOdW0ocmVjb3JkcywgYmlnLm1hcmsgPSAiLCIpKSAlPiUKICAgICMgVXNlIHRoZSBmYWN0IHRoYXQgdGhlIGxhc3QgY291cGxldCBvZiA0IGRpZ2l0cyBjcmVhdGUgdGhlIHByb3BlciBvcmRlcmluZwogICAgZHBseXI6OmFycmFuZ2UoYXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJfc3ViKGZpbGUsIC05LC0xKSwgIl8iLCIiKSkpICU+JQogICAgZHBseXI6OnNlbGVjdChmaWxlLCByZWNvcmRzLCByZWNvcmRzX2NsZWFuKQogICAgCgpjYXJiYmV2X3dlZWtseV9maWxlcyAlPiUgCiAgICBzZWxlY3QoZmlsZSwgcmVjb3JkcyA9IHJlY29yZHNfY2xlYW4pICU+JQogICAgcGFuZGVyKGp1c3RpZnkgPSBjKCJsZWZ0IiwicmlnaHQiKSkKYGBgCgojIFVQQyBMb29rdXAgVGFibGUKCk5leHQgd2UgbG9hZCB0aGUgVVBDIGxvb2t1cCB0YWJsZXMuIEZyb20gdGhlIGRvY3VtZW50YXRpb24ncyBleHBsYW5hdGlvbiwgd2UgbmVlZCB0aHJlZSBhcyB0aGUgVVBDIGRlc2NyaXB0aW9ucyB3ZXJlIGNoYW5nZWQgYmV0d2VlbiBwZXJpb2RzLgoKIyMgVGlzc3VlIENhdGVnb3J5CgpFYWNoIFVQQyBsb29rdXAgdGFibGUgaGFzIHRoZSBzYW1lIHN0cnVjdHVyZSAoZGVmaW5lZCBpbiB0aGUgZG9jdW1lbnRhdGlvbiksIHNvIHdlIGxvYWQgdGhlIHRocmVlIGFuZCBjb21iaW5lIGFuZCBlbGltaW5hdGUgdW5uZWNlc3NhcnkgY29sdW1ucy4KCmBgYHtyfQp0aXNzdWVfdXBjX3llYXJfMV90b182IDwtIAogICAgcmVhZHhsOjpyZWFkX2V4Y2VsKHBhc3RlMCh2b2x1bWVfZGlyLCAiL3BhcnNlZCBzdHViIGZpbGVzL3Byb2RfdGlzc3VlLnhscyIpKSAlPiUKICAgIGRwbHlyOjptdXRhdGUoaXJpX3llYXIgPSAnMS02JykKCnRpc3N1ZV91cGNfeWVhcl83IDwtIAogICAgcmVhZHhsOjpyZWFkX2V4Y2VsKHBhc3RlMCh2b2x1bWVfZGlyLCAiL3BhcnNlZCBzdHViIGZpbGVzIDIwMDcvcHJvZF9mYWN0aXNzLnhsc3giKSkgJT4lCiAgICBkcGx5cjo6bXV0YXRlKGlyaV95ZWFyID0gJzcnKQoKdGlzc3VlX3VwY195ZWFyXzhfdG9fMTEgPC0gCiAgICByZWFkeGw6OnJlYWRfZXhjZWwocGFzdGUwKHZvbHVtZV9kaXIsICIvcGFyc2VkIHN0dWIgZmlsZXMgMjAwOC0yMDExL3Byb2QxMV9mYWN0aXNzLnhsc3giKSkgJT4lCiAgICBkcGx5cjo6bXV0YXRlKGlyaV95ZWFyID0gJzgtMTEnKQoKdGlzc3VlX3VwYyA8LSAKICAgIGJpbmRfcm93cygKICAgICB0aXNzdWVfdXBjX3llYXJfMV90b182LCB0aXNzdWVfdXBjX3llYXJfNywgdGlzc3VlX3VwY195ZWFyXzhfdG9fMTEKICAgICkgJT4lIAogICAgcmVuYW1lKAogICAgICAgIGxhcmdlX2NhdGVnb3J5ID0gTDEKICAgICAgICAsIHNtYWxsX2NhdGVnb3J5ID0gTDIKICAgICAgICAsIHBhcmVudF9jb21wYW55ID0gTDMKICAgICAgICAsIHZlbmRvciA9IEw0CiAgICAgICAgLCBicmFuZCA9IEw1CiAgICAgICAgLCB1cGMgPSBVUEMKICAgICkgJT4lCiAgICAjIFRoZXNlIHBvc2l0aW9ucyBhcmUgb3V0bGluZWQgaW4gdGhlIGRvY3VtZW50YXRpb24KICAgIHNlbGVjdCgxLDIsMyw0LDUsOCwgMTU6MjIpICU+JQogICAgc2V0TmFtZXModG9sb3dlcihtYWtlLm5hbWVzKG5hbWVzKC4pKSkpICU+JQogICAgc2VsZWN0KGlyaV95ZWFyLCBldmVyeXRoaW5nKCkpCmBgYAoKQmVsb3cgaXMgYSB0YWJsZSBvZiB0aGUgZmlyc3QgMjAgcmVjb3Jkcy4KCmBgYHtyfQp0aXNzdWVfdXBjICU+JSAKICAgIHNlbGVjdChwYXJlbnRfY29tcGFueSwgdmVuZG9yLCBicmFuZCkgJT4lCiAgICBkaXN0aW5jdCgpICU+JQogICAgYXJyYW5nZShwYXJlbnRfY29tcGFueSwgdmVuZG9yLCBicmFuZCkgJT4lCiAgICBoZWFkKDIwKQpgYGAKCiMjIENhcmJvbmF0ZWQgQmV2ZXJhZ2UgQ2F0ZWdvcnkKCmBgYHtyfQpjYXJiYmV2X3VwY195ZWFyXzFfdG9fNiA8LSAKICAgIHJlYWR4bDo6cmVhZF9leGNlbChwYXN0ZTAodm9sdW1lX2RpciwgIi9wYXJzZWQgc3R1YiBmaWxlcy9wcm9kX2NhcmJiZXYueGxzIikpICU+JQogICAgZHBseXI6Om11dGF0ZShpcmlfeWVhciA9ICcxLTYnKQoKY2FyYmJldl91cGNfeWVhcl83IDwtIAogICAgcmVhZHhsOjpyZWFkX2V4Y2VsKHBhc3RlMCh2b2x1bWVfZGlyLCAiL3BhcnNlZCBzdHViIGZpbGVzIDIwMDcvcHJvZF9jYXJiYmV2Lnhsc3giKSkgJT4lCiAgICBkcGx5cjo6bXV0YXRlKGlyaV95ZWFyID0gJzcnKQoKY2FyYmJldl91cGNfeWVhcl84X3RvXzExIDwtIAogICAgcmVhZHhsOjpyZWFkX2V4Y2VsKHBhc3RlMCh2b2x1bWVfZGlyLCAiL3BhcnNlZCBzdHViIGZpbGVzIDIwMDgtMjAxMS9wcm9kMTFfY2FyYmJldi54bHN4IikpICU+JQogICAgZHBseXI6Om11dGF0ZShpcmlfeWVhciA9ICc4LTExJykKCmNhcmJiZXZfdXBjIDwtIAogICAgYmluZF9yb3dzKAogICAgIGNhcmJiZXZfdXBjX3llYXJfMV90b182LCBjYXJiYmV2X3VwY195ZWFyXzcsIGNhcmJiZXZfdXBjX3llYXJfOF90b18xMQogICAgKSAlPiUgCiAgICByZW5hbWUoCiAgICAgICAgbGFyZ2VfY2F0ZWdvcnkgPSBMMQogICAgICAgICwgc21hbGxfY2F0ZWdvcnkgPSBMMgogICAgICAgICwgcGFyZW50X2NvbXBhbnkgPSBMMwogICAgICAgICwgdmVuZG9yID0gTDQKICAgICAgICAsIGJyYW5kID0gTDUKICAgICAgICAsIHVwYyA9IFVQQwogICAgKSAlPiUKICAgICMgVGhlc2UgcG9zaXRpb25zIGFyZSBvdXRsaW5lZCBpbiB0aGUgZG9jdW1lbnRhdGlvbgogICAgc2VsZWN0KDEsMiwzLDQsNSw4LCAxNToyMikgJT4lCiAgICBzZXROYW1lcyh0b2xvd2VyKG1ha2UubmFtZXMobmFtZXMoLikpKSkgJT4lCiAgICBzZWxlY3QoaXJpX3llYXIsIGV2ZXJ5dGhpbmcoKSkKYGBgCgpUaGVyZSBhcmUgc2l4ICpzdWItY2F0ZWdvcmllcyo6CgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQpjYXJiYmV2X3VwYyAlPiUKICAgIGdyb3VwX2J5KHNtYWxsX2NhdGVnb3J5KSAlPiUKICAgIHN1bW1hcmlzZShgVVBDJ3NgID0gbigpKSAlPiUKICAgIGFycmFuZ2UoZGVzYyhgVVBDJ3NgKSkgJT4lCiAgICBwYW5kb2MudGFibGUoanVzdGlmeSA9IGMoImxlZnQiLCJyaWdodCIpKQpgYGAKCkV2ZW4gZXhjbHVkaW5nIHRoZSBjYXRlZ29yaWVzLCB0aGVyZSBhcmUgMSwwMTkgKmNvbXBhbnkgLyB2ZW5kZXIgLyBicmFuZCogY29tYmluYXRpb25zLiBCZWxvdyBhcmUgdGhlIGZpcnN0IDIwOgoKYGBge3J9CmNhcmJiZXZfdXBjICU+JSAKICAgIHNlbGVjdChwYXJlbnRfY29tcGFueSwgdmVuZG9yLCBicmFuZCkgJT4lCiAgICBkaXN0aW5jdCgpICU+JQogICAgYXJyYW5nZShwYXJlbnRfY29tcGFueSwgdmVuZG9yLCBicmFuZCkgJT4lCiAgICBoZWFkKDIwKQpgYGAKCiMgU2FsZXMgSW5mb3JtYXRpb24KClRvIGxvYWQgdGhlIHNhbGVzIGluZm9ybWF0aW9uIHdlIGNhbiB3cml0ZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBsb2FkIGVhY2ggZmlsZSB1c2luZyBgcmVhZF9md2ZgICh3aXRoIHRoZSBjb2x1bW4gd2lkdGhzIGZvdW5kIGJ5IHJldmlld2luZyB0aGUgZmlsZXMpLiBXZSBkbyBub3QgdXNlIGByZWFkX3RhYmxlYCBhcyB0aGlzIGNhbiBwb3RlbnRpYWxseSBiZSB1bnJlbGlhYmxlIHdpdGggbGFyZ2UgZGF0YXNldHMgKHNlZSBbSGFkbHkncyBjb21tZW50XShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL3JlYWRyL2lzc3Vlcy81MTgjaXNzdWVjb21tZW50LTI2ODg3MjUzOCkpLiBXZSBhbHNvIGFkZCB0aGUgeWVhciwgc3RvcmUgdHlwZSwgYW5kIElEIChyb3cgbnVtYmVyIGZvciByZWZlcmVuY2UpIGFzIHByZWNlZGluZyBjb2x1bW5zLgoKYGBge3J9CmZuX2xvYWRfd2Vla2x5X2ZpbGUgPC0gZnVuY3Rpb24oZmlsZW5hbWUpIHsKICAgIHllYXJfbnVtIDwtIGFzLmludGVnZXIoc3RyX21hdGNoKGZpbGVuYW1lLCAiWWVhcihbOmRpZ2l0Ol0rKSIpWywgMl0pCiAgICBkcnVnX29yX2dyb2MgPC0gc3RyX21hdGNoKGZpbGVuYW1lLCAiZHJ1Z3xncm9jIilbLCAxXQogICAgY29sdW1uX3dpZHRocyA8LSBjKDcsIDUsIDMsIDMsIDYsIDYsIDYsIDksIDUsIDIsIDIpCiAgICBjb2x1bW5fbmFtZXMgPC0gYygnSVJJX0tFWScsJ1dFRUsnLCdTWScsJ0dFJywnVkVORCcsJ0lURU0nLCdVTklUUycsJ0RPTExBUlMnLCdGJywnRCcsJ1BSJykKICAgIGNvbHVtbl90eXBlcyA8LSBjb2xzKAogICAgICAgICAgICAgICAgICAgICAgSVJJX0tFWSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgIFdFRUsgPSBjb2xfaW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgU1kgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICBHRSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgIFZFTkQgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgICAgICAgICAgICAgICAgICBJVEVNID0gY29sX2NoYXJhY3RlcigpLAogICAgICAgICAgICAgICAgICAgICAgVU5JVFMgPSBjb2xfaW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgRE9MTEFSUyA9IGNvbF9kb3VibGUoKSwKICAgICAgICAgICAgICAgICAgICAgIGBGYCA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgIEQgPSBjb2xfaW50ZWdlcigpLAogICAgICAgICAgICAgICAgICAgICAgUFIgPSBjb2xfaW50ZWdlcigpCiAgICAgICAgICAgICAgICAgICAgKQogICAgCiAgICByZWFkX2Z3ZihwYXN0ZTAodm9sdW1lX2Rpciwgc3RyX3JlcGxhY2UoZmlsZW5hbWUsICIuIiwgIiIpKSwgCiAgICAgICAgICAgICBmd2Zfd2lkdGhzKGNvbHVtbl93aWR0aHMsIGNvbHVtbl9uYW1lcyksIGNvbF90eXBlcyA9IGNvbHVtbl90eXBlcywKICAgICAgICAgICAgIHNraXAgPSAxLCBwcm9ncmVzcyA9IEZBTFNFKSAlPiUKICAgICAgICBtdXRhdGUoaXJpX3llYXIgPSB5ZWFyX251bSwgaWQgPSByb3dfbnVtYmVyKCksIHN0b3JlX3R5cGUgPSBkcnVnX29yX2dyb2MpICU+JQogICAgICAgIHNlbGVjdChpcmlfeWVhciwgaWQsIHN0b3JlX3R5cGUsIGV2ZXJ5dGhpbmcoKSkKfQoKZm5faW50ZWdyaXR5X2NoZWNrIDwtIGZ1bmN0aW9uKGR0KSB7CiAgICByZWNvcmRzIDwtIGR0ICU+JSBucm93KCkKICAgIHVuaXF1ZV9yZWNvcmRzIDwtIAogICAgICAgIGR0ICU+JQogICAgICAgIHNlbGVjdChJUklfS0VZLCBXRUVLLCBTWSwgR0UsIFZFTkQsIElURU0pICU+JQogICAgICAgIGRpc3RpbmN0KCkgJT4lCiAgICAgICAgbnJvdygpCiAgICByZXR1cm4oZGF0YV9mcmFtZShyZWNvcmRzX3JhdyA9IHJlY29yZHMsIHVuaXF1ZV9yZWNvcmRzID0gdW5pcXVlX3JlY29yZHMpKQp9CmBgYAoKIyMgVGlzc3VlIENhdGVnb3J5CgpXZSBsb2FkIGVhY2ggb2YgdGhlIHN0b3JlIGRhdGFzZXRzIGFuZCBwZXJmb3JtIGRhdGEgaW50ZWdyaXR5IGNoZWNrcy4gV2Ugc2hvdyB0aGF0IHRoZSBudW1iZXIgb2YgcmVjb3JkcyBsb2FkZWQgaXMgb25lIGxlc3MgdGhhbiB3ZSBmb3VuZCB3aXRoIHRoZSBgd2MgLWxgIHNoZWxsIGNvbW1hbmQgKGR1ZSB0byB0aGUgaGVhZGVyKSBhbmQgdGhhdCB0aGUgbnVtYmVyIG9mIHJlY29yZHMgdW5pcXVlIG9uIHN0b3JlIC8gd2VlayAvIHVwYyAoYFNZYCwgYEdFYCwgYFZFTkRgLCBgSVRFTWApIGlzIHRoZSB0b3RhbCBudW1iZXIgb2YgcmVjb3JkcyBsb2FkZWQuIAoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdGlzc3VlX2ZpbGVzIDwtIHRpc3N1ZV93ZWVrbHlfZmlsZXMkZmlsZVstbGVuZ3RoKHRpc3N1ZV93ZWVrbHlfZmlsZXMkZmlsZSldCnRpc3N1ZV9maWxlc19saXN0IDwtIGxhcHBseSh0aXNzdWVfZmlsZXMsIGZuX2xvYWRfd2Vla2x5X2ZpbGUpCnRpc3N1ZV93ZWVrbHkgPC0gYmluZF9yb3dzKHRpc3N1ZV9maWxlc19saXN0KQoKbGFwcGx5KHRpc3N1ZV9maWxlc19saXN0LCBmbl9pbnRlZ3JpdHlfY2hlY2spICU+JQogICAgYmluZF9yb3dzKCkgJT4lCiAgICBiaW5kX3Jvd3MoZGF0YV9mcmFtZShyZWNvcmRzX3JhdyA9IHN1bSguJHJlY29yZHNfcmF3KSwgdW5pcXVlX3JlY29yZHMgPSBzdW0oLiR1bmlxdWVfcmVjb3JkcykpKSAlPiUKICAgIGJpbmRfY29scyh0aXNzdWVfd2Vla2x5X2ZpbGVzKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICAgbiA9IG4oKQogICAgICAgICAsIGBQYXNzP2AgPSBpZl9lbHNlKGZpbGUgPT0gInRvdGFsIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZl9lbHNlKHJlY29yZHNfcmF3ID09IChyZWNvcmRzIC0gbiArIDEpICYgdW5pcXVlX3JlY29yZHMgPT0gcmVjb3Jkc19yYXcsICJZZXMiLCJObyIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmX2Vsc2UocmVjb3Jkc19yYXcgPT0gKHJlY29yZHMgLSAxKSAmIHVuaXF1ZV9yZWNvcmRzID09IHJlY29yZHNfcmF3LCAiWWVzIiwiTm8iKSkKICAgICkgJT4lCiAgICBzZWxlY3QoRmlsZSA9IGZpbGUsIFJlY29yZHMgPSByZWNvcmRzX2NsZWFuLCBgUmVjb3JkcyBMb2FkZWRgID0gcmVjb3Jkc19yYXcsIAogICAgICAgICAgIGBVbmlxdWUgb24gU3RvcmUvV2Vlay9VUENgID0gdW5pcXVlX3JlY29yZHMsIGBQYXNzP2ApICU+JQogICAgcGFuZGVyKHNwbGl0LnRhYmxlID0gSW5mLCBqdXN0aWZ5ID0gYygnbGVmdCcsICdyaWdodCcsJ3JpZ2h0JywncmlnaHQnLCdjZW50ZXInKSkKYGBgCgpXZSBhcmUgZ29pbmcgdG8gY29uc3RydWN0IGEgVVBDIGNvbHVtbiBmcm9tIHRoZSBjb2x1bW5zIFNZLCBHRSwgVkVORCwgYW5kIElURU0gaW4gdGhlIHNhbGVzIGluZm9ybWF0aW9uIHRhYmxlLiBXZSBjYW4gc2hvdyB0aGF0IGFsbCB0aXNzdWUgVVBDJ3MgaW4gdGhlIFVQQyBMb29rdXAgVGFibGUgYXJlICoqYHIgdGFibGUoc2FwcGx5KHRpc3N1ZV91cGMkdXBjLCBuY2hhcikpYCoqIGNoYXJhY3RlcnMgbG9uZyBieQoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdGFibGUoc2FwcGx5KHRpc3N1ZV91cGMkdXBjLCBuY2hhcikpICU+JQogICAgcGFuZGVyKCkKYGBgCgpXZSBjcmVhdGUgYSBVUEMgZnJvbSB0aGUgY29tcG9uZW50cyBhbmQgcHJvcGVybHkgbmFtZSB0aGUgY29sdW1ucy4KCmBgYHtyfQp0aXNzdWVfd2Vla2x5MiA8LSAKICAgIHRpc3N1ZV93ZWVrbHkgJT4lCiAgICAgICAgbXV0YXRlKAogICAgICAgICAgICB1cGMgPSBwYXN0ZShzdHJfcGFkKFNZLCB3aWR0aCA9IDIsICJsZWZ0IiwgIjAiKSwgc3RyX3BhZChHRSwgd2lkdGggPSAyLCAibGVmdCIsICIwIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJfcGFkKFZFTkQsIHdpZHRoID0gNSwgImxlZnQiLCAiMCIpLCBzdHJfcGFkKElURU0sIHdpZHRoID0gNSwgImxlZnQiLCAiMCIpLCBzZXAgPSAiLSIpCiAgICAgICAgICAgICwgYXZnX3ByaWNlID0gRE9MTEFSUyAvIFVOSVRTCiAgICAgICAgKSAlPiUgCiAgICAgICAgcmVuYW1lKGZlYXR1cmUgPSBgRmAsIGRpc3BsYXkgPSBELCBwcmljZV9yZWR1Y3Rpb24gPSBQUikgJT4lCiAgICAgICAgc2V0TmFtZXModG9sb3dlcihtYWtlLm5hbWVzKG5hbWVzKC4pKSkpICU+JQogICAgICAgIHNlbGVjdChpcmlfeWVhciwgaXJpX2tleSwgd2VlaywgdXBjLCB1bml0cywgZG9sbGFycywgYXZnX3ByaWNlLCBmZWF0dXJlLCBkaXNwbGF5LCBwcmljZV9yZWR1Y3Rpb24pCmBgYAoKTm93LCBsZXQncyB2ZXJpZnkgYWdhaW4gdGhhdCB0aGlzIGRhdGFzZXQgaXMgaW5kZWVkIGF0IHRoZSBzdG9yZSAoaXJpX2tleSkgLSB3ZWVrIC0gVVBDIGxldmVsIG9mIGRldGFpbCwgd2l0aCB0aGUgbmV3IFVQQyBjb2x1bW4uCgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQpkYXRhX2ZyYW1lKAogICAgYFJlY29yZCBDb3VudGAgPSB0aXNzdWVfd2Vla2x5MiAlPiUgbnJvdygpCiAgICAsIGBTdG9yZSAtIFdlZWsgLSBVUEMgQ29tYmluYXRpb25zYCA9IHRpc3N1ZV93ZWVrbHkyICU+JSBzZWxlY3QoaXJpX2tleSwgd2VlaywgdXBjKSAlPiUgZGlzdGluY3QoKSAlPiUgbnJvdygpCikgJT4lCiAgICBwYW5kZXIoKQpgYGAKCldpdGggdGhhdCBhc3N1cmFuY2UsIGxldCdzIHB1bGwgaW4gdGhlIGRldGFpbHMgYWJvdXQgZWFjaCBVUEMuIFdlIGRvIHRoaXMgZm9yIGVhY2ggeWVhciB3aW5kb3cgYmVjYXVzZSB0aGUgZG9jdW1lbnRhdGlvbiBpbmRpY2F0ZWQgdGhhdCBVUEMgaW5mb3JtYXRpb24gKHBvdGVudGlhbGx5IGluY2x1ZGluZyBjb21wYW55KSBjaGFuZ2VkIGJldHdlZW4gdGhlIHRpbWUgd2luZG93cy4gV2UgdGhlbiBkbyBhIGNoZWNrIHRoYXQgbm8gcmVjb3JkcyB3ZXJlIGRyb3BwZWQgaW4gdGhlIGBpbm5lciBqb2luYC4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CnRpc3N1ZV93ZWVrbHlfMV90b182IDwtIAogICAgdGlzc3VlX3dlZWtseTIgJT4lCiAgICAgICAgZmlsdGVyKGlyaV95ZWFyIDw9IDYpICU+JQogICAgICAgIGlubmVyX2pvaW4oCiAgICAgICAgICAgIHRpc3N1ZV91cGMgJT4lCiAgICAgICAgICAgICAgICBmaWx0ZXIoaXJpX3llYXIgPT0gIjEtNiIpICU+JQogICAgICAgICAgICAgICAgc2VsZWN0KC1pcmlfeWVhcikKICAgICAgICAgICAgLCBieSA9IGMoInVwYyIgPSAndXBjJykKICAgICAgICApCgp0aXNzdWVfd2Vla2x5XzcgPC0gCiAgICB0aXNzdWVfd2Vla2x5MiAlPiUKICAgICAgICBmaWx0ZXIoaXJpX3llYXIgPT0gNykgJT4lCiAgICAgICAgaW5uZXJfam9pbigKICAgICAgICAgICAgdGlzc3VlX3VwYyAlPiUKICAgICAgICAgICAgICAgIGZpbHRlcihpcmlfeWVhciA9PSAiNyIpICU+JQogICAgICAgICAgICAgICAgc2VsZWN0KC1pcmlfeWVhcikKICAgICAgICAgICAgLCBieSA9IGMoInVwYyIgPSAndXBjJykKICAgICAgICApCgp0aXNzdWVfd2Vla2x5XzhfdG9fMTEgPC0gCiAgICB0aXNzdWVfd2Vla2x5MiAlPiUKICAgICAgICBmaWx0ZXIoaXJpX3llYXIgPj0gOCkgJT4lCiAgICAgICAgaW5uZXJfam9pbigKICAgICAgICAgICAgdGlzc3VlX3VwYyAlPiUKICAgICAgICAgICAgICAgIGZpbHRlcihpcmlfeWVhciA9PSAiOC0xMSIpICU+JQogICAgICAgICAgICAgICAgc2VsZWN0KC1pcmlfeWVhcikKICAgICAgICAgICAgLCBieSA9IGMoInVwYyIgPSAndXBjJykKICAgICAgICApCgpkYXRhX2ZyYW1lKAogICAgWWVhcnMgPSBjKCIxLTYiLCAiNyIsICI4LTExIikKICAgICwgYFJlY29yZCBDb3VudCBpbiBXZWVrbHkgRGF0YWAgPSBzYXBwbHkobGlzdChmaWx0ZXIodGlzc3VlX3dlZWtseTIsIGlyaV95ZWFyIDw9IDYpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIodGlzc3VlX3dlZWtseTIsIGlyaV95ZWFyID09IDcpLCBmaWx0ZXIodGlzc3VlX3dlZWtseTIsIGlyaV95ZWFyID49IDgpKSwgbnJvdykKICAgICwgYFJlY29yZCBDb3VudCBpbiBKb2luZWQgRGF0YWAgPSBzYXBwbHkobGlzdCh0aXNzdWVfd2Vla2x5XzFfdG9fNiwgdGlzc3VlX3dlZWtseV83LCB0aXNzdWVfd2Vla2x5XzhfdG9fMTEpLCBucm93KQopICU+JQogICAgcGFuZGVyKCkKYGBgCgpUaGlzIHBhc3NlcyBvdXIgZGF0YSBpbnRlZ3JpdHkgY2hlY2ssIHNvIHdlJ2xsIGNvbWJpbmUgdGhlIDMgdGltZSB3aW5kb3dzIGRhdGFzZXRzLgoKYGBge3J9CnRpc3N1ZV9maW5hbCA8LSBiaW5kX3Jvd3ModGlzc3VlX3dlZWtseV8xX3RvXzYsIHRpc3N1ZV93ZWVrbHlfNywgdGlzc3VlX3dlZWtseV84X3RvXzExKQoKdGlzc3VlX2ZpbmFsICU+JQogICAgaGVhZCgyMCkKYGBgCgoKIyMgQ2FyYm9uYXRlZCBCZXZlcmFnZSBDYXRlZ29yeQo=