library(tidyverse)
library(bslib)
library(htmltools)
library(conflicted)

source("setup/conflicted.R")
source("setup/knit_engines_simple.R")
source("metadata/loris/loris_schedule.R")

Intro

The purpose of a metadata file is to organize the potential independent or predictor variables for your analysis into a single table with one row per SampleID. Then, when you produce a set of potential dependent or outcome values, you organize those into a similar structure with the SampleIDs organized rowwise to streamline the process of matching predictor variables to outcome variables by SampleID. It’s good practice to keep as much of your information in one tidy table as possible so that you can keep pulling from that source to further filter, wrangle and analyze without losing track of different versions and datasets over time. That means you should brainstorm as many possible predictor variables you might use in downstream analysis as possible and organize them into one tidy table where each SampleID is matched to a value for every variable. You will end up ignoring most of these variables as you construct individual tests and visuals later, so consider this simply a rough draft of your information of interest. I am going to do this with the Pygmy Loris dataset for this tutorial. You may have a very different set of variables to organize for your own project.

How to Organize Tidy Metadata

Your goal is to match every variable to some indexing column(s) that you can reliably match to each of your experimental “observations” or “samples.” In this example, I am working with data from our Loris Microbiome study, where we are going to consider each day of sample collection for each subject one “observation”. That means I want to construct a metadata table where every row represents one day and one subject, and every column contains measurements of other variables for that subject on that day. This makes it easy for us to then match any sample ID to one row of the metadata table and relate those variables to our microbiome profiles.

Other Cases

In other cases, you may find yourself organizing your metadata into broader units. For example, if we are not interested in a time dimension but are comparing variables across different locations, we may organize our metadata into one row per location. Or if we are comparing something across species, we would have one row per species.

Previous Scripts

If you want to match your samples to the metadata now, you should use the SampleInventory script to organize your sample IDs by date.

Still, you can really put your metadata together at any stage, as long as you know how you will need to match your sample inventory to these variables later, so it is not really necessary to complete any of the previous scripts before this.

External Files for Metadata

You may be importing metadata organized in many different ways. In this case, I am providing an example R script and tsv table to use in this case.

R Script
# metadata/loris/loris/loris_schedule.R

keeper_notes <- list(
  list(date = ymd("2024-01-22"), tag = "food" , subject = list("culi") , note = "removing cauliflower and broccoli from diet rotation"),
  list(date = ymd("2024-02-20"), tag = "food" , subject = list("culi") , note = "removing tomato from diet rotation"),
  list(date = ymd("2024-03-21"), tag = "repro" , subject = list("culi")             , note = "Culi whistling and actively attempting to get to Warble"),
  list(date = ymd("2024-04-04"), tag = "repro" , subject = list("culi", "warble"), note = "Observed several breeding events"),
  list(date = ymd("2023-11-16"), tag = "health", subject = list("culi")             , note = "had what appeared to be dried blood around his anus and tail"),
  list(date = ymd("2023-12-17"), tag = "health", subject = list("culi")             , note = "coprophagy observed"),
  list(date = ymd("2024-01-21"), tag = "health", subject = list("culi")             , note = "coprophagy observed"),
  list(date = ymd("2024-02-10"), tag = "health", subject = list("culi")             , note = "left biscuit and some gum"),
  list(date = ymd("2024-04-01"), tag = "repro" , subject = list("warble")           , note = "swollen vulva"),
Nutrition Table
# metadata/loris/nutrition.tsv

"diet_name" "class" "item"  "fed"   "units" "relative"  "relative_to"
"Baseline"  "Foods" "Biscuit rotation"  13200   "mg"    0.252390057361377   "diet_total"
"Baseline"  "Foods" "Invertebrate, misc"    10000   "mg"    0.191204588910134   "diet_total"
"Baseline"  "Foods" "Mazuri enrich gum arabic (5b35)"   12000   "mg"    0.229445506692161   "diet_total"
"Baseline"  "Foods" "Protein rotation"  2100    "mg"    0.0401529636711281  "diet_total"
"Baseline"  "Foods" "Seasonal vegetables"   15000   "mg"    0.286806883365201   "diet_total"
"Biscuit elimination"   "Foods" "Biscuit rotation"  0   "mg"    0   "diet_total"
"Biscuit elimination"   "Foods" "Invertebrate, misc"    18000   "mg"    0.257142857142857   "diet_total"
"Biscuit elimination"   "Foods" "Mazuri enrich gum arabic (5b35)"   17000   "mg"    0.242857142857143   "diet_total"
"Biscuit elimination"   "Foods" "Protein rotation"  5000    "mg"    0.0714285714285714  "diet_total"
Bristol Table
# metadata/loris/bristols.tsv

date    subject bristol_min bristol_max bristol_mean
10/26/23    culi    3   5   4
10/26/23    warble  1   1   1
10/27/23    culi    3   6   4.5
10/27/23    warble  1   1   1
10/28/23    culi    3   3   3
10/28/23    warble  1   1   1
10/29/23    culi    3   3   3
10/29/23    warble  1   1   1
10/30/23    culi    3   3   3

Script

Organizing Dates

The toughest variables to match and wrange in R are often dates, especially when you are dealing with both states (start and end dates for intervals) and events (single dates). For studies like this one based around daily sample collection, I find it easiest to start by populating a blank dataframe with one row per sample collection day. Then I match date-based variables to this dataframe and join my SampleIDs to it.

Once we combine all the pieces together, we will have a metadata table with one row per day for each subject. Then we can match every sample to subject and day using the same table.

collection_start  <- ymd("2023-10-26")
collection_end    <- ymd("2024-12-14")
collection_date  <- seq.Date(from = collection_start, 
                             to   = collection_end, 
                             by   = "day")
subject          <- c("warble", "culi")
subjects_dates <- expand_grid(subject, collection_date)

collection <- tibble(collection_date = collection_date) %>%
  mutate(study_day  = row_number(),
         day_week   = wday(collection_date, label = TRUE),
         month      = month(collection_date, label = TRUE),
         study_week = if_else(day_week == "Thu", (study_day + 6)/7, NA),
         study_month = consecutive_id(month)
         ) %>%
  fill(study_week) %>%
  select(
    collection_date,
    study_month,
    study_week,
    study_day
  ) %>%
  right_join(subjects_dates, by = "collection_date")

collection

Construct Variables

Health

Diet

We have only two subjects in this dataset: Culi and Warble. Culi underwent a series of diet trials that we organized into start and end dates, while Warble maintained a baseline diet throughout our study.

diet_trials[[1]]$begin <- floor_date(collection_start, "month")

nutrition_wide <- nutrition %>%
  mutate(diet = fct_recode(as.factor(diet_name), !!!diet_factors)) %>%
  relocate(diet) %>%
  select(-diet_name) %>%
  filter(diet != "Other")
nutrition_wide
ends   <- diet_trials %>% discard_at(1) %>%
  map(\(x) list_assign(x, end = x$begin - day(1))) %>%
  map(\(x) keep_at(x, "end")) %>%
  list_flatten() %>%
  list(., list(end = (ceiling_date(collection_end, "month") - days(1)))) %>%
  list_flatten()
diets <- map2(
  diet_trials, 
  ends, 
  \(x, y) list_assign(x, end = y, collection_date = seq(x$begin, y))) %>%
  enframe(name = NULL)  %>%
  unnest_wider(value) %>%
  select(diet, collection_date) %>%
  unnest_longer(collection_date) %>%
  mutate(subject = "culi") %>%
  bind_rows(diets_warble)
diets

Supplements

ends   <- supplements %>% discard_at(1) %>%
  map(\(x) list(x$begin - day(1))) %>%
  list_flatten()

meds <-  supplements %>% discard_at(21) %>%
  map2(
    ends, 
    \(x, y) list_assign(x, end = y, collection_date = seq(x$begin, y))) %>%
  map_depth(1, \(x) keep(x, \(y) all(y > 0))) %>%
  discard(\(x) all(length(x) < 4)) %>%
  enframe(name= NULL) %>%
  unnest_wider(value) %>%
  pivot_longer(
    c("probiotic", "steroid", "fiber", "antibiotic", "antidiarrheal"),
    names_to       = "supplement",
    values_to      = "dose",
    values_drop_na = T
    ) %>%
  select(collection_date, supplement, dose) %>%
  arrange(supplement, dose) %>%
  left_join(supplement_details, by = "supplement") %>%
  rowwise() %>%
  mutate(dose_rel = dose/dose_high) %>%
  ungroup() %>%
  unnest_longer(collection_date)
meds
meds_wide <- meds %>%
  select(collection_date, supplement, dose_rel) %>%
  pivot_wider(
    names_from  = "supplement", 
    values_from = "dose_rel", 
    values_fill = 0) %>%
  mutate(subject = "culi") %>%
  bind_rows(meds_warble)
meds_wide

Diet + Meds

nutrition_meds <- collection %>%
  select(collection_date, subject) %>%
  left_join(diets, by = join_by(collection_date, subject)) %>%
  left_join(meds_wide, by = join_by(collection_date, subject)) %>%
  mutate(across(where(is.numeric), ~replace_na(., 0))) %>%
  left_join(nutrition_wide, by = "diet")
nutrition_meds

Bristol Scores

bristols_warble <- collection %>%
  filter(subject == "warble") %>%
  mutate(bristol = 1)

bristols <- read_csv(
  "metadata/loris/originals/hdz_nutritionData_2025-1-30.csv",
  col_types = cols_only(
    Date = col_date(format = "%m/%d/%y"),
    "Fecal Score for Culi" = col_character()
  )
) %>%
  rename(bristol = "Fecal Score for Culi", collection_date = Date) %>%
  separate_longer_delim(bristol, ",") %>%
  mutate(bristol = as.integer(as.character(str_remove_all(bristol, "[^\\d]")))) %>%
  filter(!is.na(bristol) & bristol != 0) %>%
  group_by(collection_date) %>%
  reframe(bristol = mean(bristol)) %>%
  ungroup() %>%
  right_join(filter(collection, subject == "culi"), by = join_by(collection_date)) %>%
  arrange(collection_date) %>%
  mutate(mean_weekly  = mean(bristol, na.rm = TRUE), .by = "study_week") %>%
  rowwise() %>%
  mutate(bristol = replace_na(bristol, mean_weekly)) %>%
  ungroup() %>%
  bind_rows(bristols_warble) %>%
  select(collection_date, subject, bristol) %>%
  arrange(collection_date)

Social/Repro

dates_pair <- select(collection, collection_date, subject) %>%
  left_join(warble_cycles, by = join_by(collection_date == date)) %>%
  left_join(access_dates, by = join_by(collection_date == date)) %>%
  mutate(warb_cycle   = replace_na(warb_status, "anestrus"), 
         pair_access  = replace_na(pair_access, "n"), 
         .keep = "unused") %>%
  left_join(keeper_notes, by = join_by(collection_date == date, subject)) %>%
  left_join(holdings, by = join_by(collection_date, subject)) %>%
  select(collection_date, subject, warb_cycle, pair_access, holding, keeper_note)
dates_pair

Complete Metadata Table

metadata <- collection %>%
  left_join(nutrition_meds, by = join_by(collection_date, subject)) %>%
  left_join(bristols      , by = join_by(collection_date, subject)) %>%
  left_join(dates_pair    , by = join_by(collection_date, subject)) %>%
  relocate(
    bristol,
    holding, 
    warb_cycle, 
    keeper_note,
    .after = steroid
  )

save(metadata, file = "metadata/loris/metadata.RData")

write_csv(metadata, "metadata/loris/metadata.csv")

metadata

Timeline Option for Plotting

I’ve also found that it is helpful to store a tibble with rows organized by date intervals for events or states that might be plotted on a timeline or timeseries graph for context. I will bring in the same data with different shapes and export this as a tibble for later.

source("metadata/loris/loris_timeline.R")

cat(
  read_lines("metadata/loris/loris_timeline.R", n_max = 20),
  sep = "\n"
)
keeper_notes <- list(
  list(date = ymd("2024-01-22"), tag = "food" , subject = list("culi") , note = "removing cauliflower and broccoli from diet rotation"),
  list(date = ymd("2024-02-20"), tag = "food" , subject = list("culi") , note = "removing tomato from diet rotation"),
  list(date = ymd("2024-03-21"), tag = "repro" , subject = list("culi")             , note = "Culi whistling and actively attempting to get to Warble"),
  list(date = ymd("2024-04-04"), tag = "repro" , subject = list("culi", "warble"), note = "Observed several breeding events"),
  list(date = ymd("2023-11-16"), tag = "health", subject = list("culi")             , note = "had what appeared to be dried blood around his anus and tail"),
  list(date = ymd("2023-12-17"), tag = "health", subject = list("culi")             , note = "coprophagy observed"),
  list(date = ymd("2024-01-21"), tag = "health", subject = list("culi")             , note = "coprophagy observed"),
  list(date = ymd("2024-02-10"), tag = "health", subject = list("culi")             , note = "left biscuit and some gum"),
  list(date = ymd("2024-04-01"), tag = "repro" , subject = list("warble")           , note = "swollen vulva"),
  list(date = ymd("2024-04-02"), tag = "repro" , subject = list("culi")             , note = "swollen testicles"),
  list(date = ymd("2024-04-03"), tag = "repro" , subject = list("culi", "warble"), note = "Keeper Zach entered the facility in the morning to clean and found Warble and Culi sleeping together in a hammock in room 3"),
  list(date = ymd("2024-04-03"), tag = "repro" , subject = list("culi", "warble"), note = "Observed breeding event"),
  list(date = ymd("2024-04-04"), tag = "repro" , subject = list("warble")           , note = "Observed whistling"),
  list(date = ymd("2024-04-04"), tag = "repro" , subject = list("culi", "warble"), note = "JM and AMR found Warble and Culi sleeping together in a hammock in room 3"),
  list(date = ymd("2024-04-04"), tag = "repro" , subject = list("warble")         , note = "swollen vulva"),
  list(date = ymd("2024-04-06"), tag = "repro" , subject = list("warble")         , note = "pregnancy suspected"),
  list(date = ymd("2024-04-14"), tag = "health", subject = list("culi")             , note = "weight = 451 g"),
  list(date = ymd("2024-05-13"), tag = "health", subject = list("warble")           , note = "weight = 467 g"),
  list(date = ymd("2024-06-17"), tag = "health", subject = list("culi")             , note = "left pieces of food in room 5"),
meta_timeline <- diet_trials %>%
  bind_rows(access_pair) %>%
  bind_rows(holdings) %>%
  bind_rows(repro_dates) %>%
  bind_rows(supplements) %>%
  bind_rows(keeper_notes) %>%
  arrange(start, end)

save(meta_timeline, file = "metadata/loris/meta_timeline.RData")
write_csv(meta_timeline, "metadata/loris/meta_timeline.csv")

meta_timeline

Compile with Samples (Optional)

If you would like to bind this to sample data to create the sample table now…

load("../read_processing/samples/loris/inventories/compilation_seq_records.RData")

compilation_seq_records <- compilation_seq_records %>%
  mutate(subj_conf = if_else(subject %in% c("culi", "warble"), "yes", "no")) %>%
  mutate(subject = str_remove_all(subject, "[^\\w+]"))

compilation_metadata <- metadata %>%
  nest(
    health = c(
      bristol,
      warb_cycle,
      keeper_note
    ),
    treatments = c(
      diet,
      antibiotic,
      antidiarrheal,
      fiber,
      probiotic,
      steroid
    ),
    nutrition = c(
      Total_total:Vitamins_lycopene
    ),
    environment = c(
      holding,
      pair_access
    )
  ) %>%
  right_join(compilation_seq_records, 
             by = join_by(subject, collection_date)) %>%
  select(
    study_month,
    study_week,
    collection_date,
    subject,
    study_day,
    sampleID,
    extracted,
    sequenced,
    extracts,
    libraries,
    filtered_samples,
    health,
    treatments,
    nutrition,
    environment
  ) %>%
  arrange(collection_date) %>%
  group_by(collection_date) %>%
  fill(study_month, study_week, study_day) %>%
  ungroup() %>%
  mutate(study_day  = if_else(
    is.na(study_day) & !is.na(collection_date), 
    lag(study_day) + 1, 
    study_day)) %>%
  mutate(study_week = if_else(
    is.na(study_week) & lag(study_week) == lead(study_week), 
    lag(study_week), 
    study_week
    )
    ) %>%
  group_by(study_week) %>%
  fill(study_month) %>%
  ungroup()

save(compilation_metadata, file = "metadata/loris/compilation_metadata.RData")
loris_sample_table <- compilation_metadata %>%
  filter(sequenced > 0) %>%
  select(study_month:sampleID, filtered_samples:environment) %>%
  mutate(obs_id = as.character(str_glue(
    "{str_sub(subject, 1L, 4L)}{study_day}"
    ))) %>%
  unnest(filtered_samples) %>%
  select(
    obs_id,
    alias,
    subject,
    study_month,
    study_week,
    collection_date,
    study_day,
    seqrun,
    read_count,
    treatments,
    health,
    environment,
    nutrition
  ) %>%
  unnest(treatments, health, environment, nutrition) %>%
  distinct()

write_csv(loris_sample_table, "data/loris/microbiome_sample_table.csv")
sample_table <- loris_sample_table %>%
  column_to_rownames("alias")

save(sample_table, file = "data/loris/sample_table.RData")
sample_table

Next Steps

Now you should proceed to a Bioinformatics or Statistical Workflow to Begin Merging Metadata with Results for Analysis.

LS0tCnRpdGxlOiAiQ29uc3RydWN0aW5nIE1ldGFkYXRhIEZpbGVzIgphdXRob3I6ICJBbGljaWEgTS4gUmljaCwgUGguRC4iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZToKICAgICAgYnNsaWI6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogICAgY3NzOiBqb3VybmFsLmNzcwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICAgICAgICAgICAgICAgICAKLS0tCgpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgY29tbWVudD0iIn0KCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGJzbGliKQpsaWJyYXJ5KGh0bWx0b29scykKbGlicmFyeShjb25mbGljdGVkKQoKc291cmNlKCJzZXR1cC9jb25mbGljdGVkLlIiKQpzb3VyY2UoInNldHVwL2tuaXRfZW5naW5lc19zaW1wbGUuUiIpCnNvdXJjZSgibWV0YWRhdGEvbG9yaXMvbG9yaXNfc2NoZWR1bGUuUiIpCmBgYAoKIyBJbnRybwoKVGhlIHB1cnBvc2Ugb2YgYSBtZXRhZGF0YSBmaWxlIGlzIHRvIG9yZ2FuaXplIHRoZSBwb3RlbnRpYWwgaW5kZXBlbmRlbnQgb3IgcHJlZGljdG9yIHZhcmlhYmxlcyBmb3IgeW91ciBhbmFseXNpcyBpbnRvIGEgc2luZ2xlIHRhYmxlIHdpdGggb25lIHJvdyBwZXIgU2FtcGxlSUQuIFRoZW4sIHdoZW4geW91IHByb2R1Y2UgYSBzZXQgb2YgcG90ZW50aWFsIGRlcGVuZGVudCBvciBvdXRjb21lIHZhbHVlcywgeW91IG9yZ2FuaXplIHRob3NlIGludG8gYSBzaW1pbGFyIHN0cnVjdHVyZSB3aXRoIHRoZSBTYW1wbGVJRHMgb3JnYW5pemVkIHJvd3dpc2UgdG8gc3RyZWFtbGluZSB0aGUgcHJvY2VzcyBvZiBtYXRjaGluZyBwcmVkaWN0b3IgdmFyaWFibGVzIHRvIG91dGNvbWUgdmFyaWFibGVzIGJ5IFNhbXBsZUlELiBJdCdzIGdvb2QgcHJhY3RpY2UgdG8ga2VlcCBhcyBtdWNoIG9mIHlvdXIgaW5mb3JtYXRpb24gaW4gb25lIHRpZHkgdGFibGUgYXMgcG9zc2libGUgc28gdGhhdCB5b3UgY2FuIGtlZXAgcHVsbGluZyBmcm9tIHRoYXQgc291cmNlIHRvIGZ1cnRoZXIgZmlsdGVyLCB3cmFuZ2xlIGFuZCBhbmFseXplIHdpdGhvdXQgbG9zaW5nIHRyYWNrIG9mIGRpZmZlcmVudCB2ZXJzaW9ucyBhbmQgZGF0YXNldHMgb3ZlciB0aW1lLiBUaGF0IG1lYW5zIHlvdSBzaG91bGQgYnJhaW5zdG9ybSBhcyBtYW55IHBvc3NpYmxlIHByZWRpY3RvciB2YXJpYWJsZXMgeW91IG1pZ2h0IHVzZSBpbiBkb3duc3RyZWFtIGFuYWx5c2lzIGFzIHBvc3NpYmxlIGFuZCBvcmdhbml6ZSB0aGVtIGludG8gb25lIHRpZHkgdGFibGUgd2hlcmUgZWFjaCBTYW1wbGVJRCBpcyBtYXRjaGVkIHRvIGEgdmFsdWUgZm9yIGV2ZXJ5IHZhcmlhYmxlLiBZb3Ugd2lsbCBlbmQgdXAgaWdub3JpbmcgbW9zdCBvZiB0aGVzZSB2YXJpYWJsZXMgYXMgeW91IGNvbnN0cnVjdCBpbmRpdmlkdWFsIHRlc3RzIGFuZCB2aXN1YWxzIGxhdGVyLCBzbyBjb25zaWRlciB0aGlzIHNpbXBseSBhIHJvdWdoIGRyYWZ0IG9mIHlvdXIgaW5mb3JtYXRpb24gb2YgaW50ZXJlc3QuIEkgYW0gZ29pbmcgdG8gZG8gdGhpcyB3aXRoIHRoZSBQeWdteSBMb3JpcyBkYXRhc2V0IGZvciB0aGlzIHR1dG9yaWFsLiBZb3UgbWF5IGhhdmUgYSB2ZXJ5IGRpZmZlcmVudCBzZXQgb2YgdmFyaWFibGVzIHRvIG9yZ2FuaXplIGZvciB5b3VyIG93biBwcm9qZWN0LgoKIyMgSG93IHRvIE9yZ2FuaXplIFRpZHkgTWV0YWRhdGEKCllvdXIgZ29hbCBpcyB0byBtYXRjaCBldmVyeSB2YXJpYWJsZSB0byBzb21lIGluZGV4aW5nIGNvbHVtbihzKSB0aGF0IHlvdSBjYW4gcmVsaWFibHkgbWF0Y2ggdG8gZWFjaCBvZiB5b3VyIGV4cGVyaW1lbnRhbCAib2JzZXJ2YXRpb25zIiBvciAic2FtcGxlcy4iIEluIHRoaXMgZXhhbXBsZSwgSSBhbSB3b3JraW5nIHdpdGggZGF0YSBmcm9tIG91ciBMb3JpcyBNaWNyb2Jpb21lIHN0dWR5LCB3aGVyZSB3ZSBhcmUgZ29pbmcgdG8gY29uc2lkZXIgZWFjaCBkYXkgb2Ygc2FtcGxlIGNvbGxlY3Rpb24gZm9yIGVhY2ggc3ViamVjdCBvbmUgIm9ic2VydmF0aW9uIi4gVGhhdCBtZWFucyBJIHdhbnQgdG8gY29uc3RydWN0IGEgbWV0YWRhdGEgdGFibGUgd2hlcmUgZXZlcnkgcm93IHJlcHJlc2VudHMgb25lIGRheSBhbmQgb25lIHN1YmplY3QsIGFuZCBldmVyeSBjb2x1bW4gY29udGFpbnMgbWVhc3VyZW1lbnRzIG9mIG90aGVyIHZhcmlhYmxlcyBmb3IgdGhhdCBzdWJqZWN0IG9uIHRoYXQgZGF5LiBUaGlzIG1ha2VzIGl0IGVhc3kgZm9yIHVzIHRvIHRoZW4gbWF0Y2ggYW55IHNhbXBsZSBJRCB0byBvbmUgcm93IG9mIHRoZSBtZXRhZGF0YSB0YWJsZSBhbmQgcmVsYXRlIHRob3NlIHZhcmlhYmxlcyB0byBvdXIgbWljcm9iaW9tZSBwcm9maWxlcy4KCiMjIyBPdGhlciBDYXNlcwoKSW4gb3RoZXIgY2FzZXMsIHlvdSBtYXkgZmluZCB5b3Vyc2VsZiBvcmdhbml6aW5nIHlvdXIgbWV0YWRhdGEgaW50byBicm9hZGVyIHVuaXRzLiBGb3IgZXhhbXBsZSwgaWYgd2UgYXJlIG5vdCBpbnRlcmVzdGVkIGluIGEgdGltZSBkaW1lbnNpb24gYnV0IGFyZSBjb21wYXJpbmcgdmFyaWFibGVzIGFjcm9zcyBkaWZmZXJlbnQgbG9jYXRpb25zLCB3ZSBtYXkgb3JnYW5pemUgb3VyIG1ldGFkYXRhIGludG8gb25lIHJvdyBwZXIgbG9jYXRpb24uIE9yIGlmIHdlIGFyZSBjb21wYXJpbmcgc29tZXRoaW5nIGFjcm9zcyBzcGVjaWVzLCB3ZSB3b3VsZCBoYXZlIG9uZSByb3cgcGVyIHNwZWNpZXMuCgojIyBQcmV2aW91cyBTY3JpcHRzCgpJZiB5b3Ugd2FudCB0byBtYXRjaCB5b3VyIHNhbXBsZXMgdG8gdGhlIG1ldGFkYXRhIG5vdywgeW91IHNob3VsZCB1c2UgdGhlIFtgU2FtcGxlSW52ZW50b3J5YF0oaHR0cHM6Ly9yaWNoLW1vbGVjdWxhci1oZWFsdGgtbGFiLmdpdGh1Yi5pby9yZWFkX3Byb2Nlc3NpbmcvU2FtcGxlSW52ZW50b3J5Lmh0bWwpIHNjcmlwdCB0byBvcmdhbml6ZSB5b3VyIHNhbXBsZSBJRHMgYnkgZGF0ZS4gIAogIApTdGlsbCwgeW91IGNhbiByZWFsbHkgcHV0IHlvdXIgbWV0YWRhdGEgdG9nZXRoZXIgYXQgYW55IHN0YWdlLCBhcyBsb25nIGFzIHlvdSBrbm93IGhvdyB5b3Ugd2lsbCBuZWVkIHRvIG1hdGNoIHlvdXIgc2FtcGxlIGludmVudG9yeSB0byB0aGVzZSB2YXJpYWJsZXMgbGF0ZXIsIHNvIGl0IGlzIG5vdCByZWFsbHkgbmVjZXNzYXJ5IHRvIGNvbXBsZXRlIGFueSBvZiB0aGUgcHJldmlvdXMgc2NyaXB0cyBiZWZvcmUgdGhpcy4KCiMjIyBFeHRlcm5hbCBGaWxlcyBmb3IgTWV0YWRhdGEKCllvdSBtYXkgYmUgaW1wb3J0aW5nIG1ldGFkYXRhIG9yZ2FuaXplZCBpbiBtYW55IGRpZmZlcmVudCB3YXlzLiBJbiB0aGlzIGNhc2UsIEkgYW0gcHJvdmlkaW5nIGFuIGV4YW1wbGUgUiBzY3JpcHQgYW5kIHRzdiB0YWJsZSB0byB1c2UgaW4gdGhpcyBjYXNlLgoKPGRldGFpbHM+CjxzdW1tYXJ5PgoqKlIgU2NyaXB0KioKPC9zdW1tYXJ5PgpgYGB7ciwgZWNobyA9IEZBTFNFfQpjYXQoCiAgIiMgbWV0YWRhdGEvbG9yaXMvbG9yaXMvbG9yaXNfc2NoZWR1bGUuUlxuIiwKICByZWFkX2xpbmVzKCJtZXRhZGF0YS9sb3Jpcy9sb3Jpc19zY2hlZHVsZS5SIiwKICAgICAgICAgICAgIG5fbWF4ID0gMTAKICAgICAgICAgICAgICksIAogIHNlcCA9ICJcbiIpCgpgYGAKPC9kZXRhaWxzPgoKPGRldGFpbHM+CjxzdW1tYXJ5PgoqKk51dHJpdGlvbiBUYWJsZSoqCjwvc3VtbWFyeT4KYGBge3IsIGVjaG8gPSBGQUxTRX0KY2F0KAogICIjIG1ldGFkYXRhL2xvcmlzL251dHJpdGlvbi50c3ZcbiIsCiAgcmVhZF9saW5lcygibWV0YWRhdGEvbG9yaXMvbnV0cml0aW9uLnRzdiIsIAogIG5fbWF4ID0gMTApLAogIHNlcCA9ICJcbiIpCgpgYGAKPC9kZXRhaWxzPgoKPGRldGFpbHM+CjxzdW1tYXJ5PgoqKkJyaXN0b2wgVGFibGUqKgo8L3N1bW1hcnk+CmBgYHtyLCBlY2hvID0gRkFMU0V9CmNhdCgKICAiIyBtZXRhZGF0YS9sb3Jpcy9icmlzdG9scy50c3ZcbiIsCiAgcmVhZF9saW5lcygibWV0YWRhdGEvbG9yaXMvYnJpc3RvbHMudHN2IiwgCiAgbl9tYXggPSAxMCksCiAgc2VwID0gIlxuIikKCmBgYAo8L2RldGFpbHM+CgoKIyBTY3JpcHQKCiMjIE9yZ2FuaXppbmcgRGF0ZXMKClRoZSB0b3VnaGVzdCB2YXJpYWJsZXMgdG8gbWF0Y2ggYW5kIHdyYW5nZSBpbiBSIGFyZSBvZnRlbiBkYXRlcywgZXNwZWNpYWxseSB3aGVuIHlvdSBhcmUgZGVhbGluZyB3aXRoIGJvdGggc3RhdGVzIChzdGFydCBhbmQgZW5kIGRhdGVzIGZvciBpbnRlcnZhbHMpIGFuZCBldmVudHMgKHNpbmdsZSBkYXRlcykuIEZvciBzdHVkaWVzIGxpa2UgdGhpcyBvbmUgYmFzZWQgYXJvdW5kIGRhaWx5IHNhbXBsZSBjb2xsZWN0aW9uLCBJIGZpbmQgaXQgZWFzaWVzdCB0byBzdGFydCBieSBwb3B1bGF0aW5nIGEgYmxhbmsgZGF0YWZyYW1lIHdpdGggb25lIHJvdyBwZXIgc2FtcGxlIGNvbGxlY3Rpb24gZGF5LiBUaGVuIEkgbWF0Y2ggZGF0ZS1iYXNlZCB2YXJpYWJsZXMgdG8gdGhpcyBkYXRhZnJhbWUgYW5kIGpvaW4gbXkgU2FtcGxlSURzIHRvIGl0LiAgCiAgCk9uY2Ugd2UgY29tYmluZSBhbGwgdGhlIHBpZWNlcyB0b2dldGhlciwgd2Ugd2lsbCBoYXZlIGEgbWV0YWRhdGEgdGFibGUgd2l0aCBvbmUgcm93IHBlciBkYXkgZm9yIGVhY2ggc3ViamVjdC4gVGhlbiB3ZSBjYW4gbWF0Y2ggZXZlcnkgc2FtcGxlIHRvIHN1YmplY3QgYW5kIGRheSB1c2luZyB0aGUgc2FtZSB0YWJsZS4KCmBgYHtyfQpjb2xsZWN0aW9uX3N0YXJ0ICA8LSB5bWQoIjIwMjMtMTAtMjYiKQpjb2xsZWN0aW9uX2VuZCAgICA8LSB5bWQoIjIwMjQtMTItMTQiKQpjb2xsZWN0aW9uX2RhdGUgIDwtIHNlcS5EYXRlKGZyb20gPSBjb2xsZWN0aW9uX3N0YXJ0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0byAgID0gY29sbGVjdGlvbl9lbmQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ICAgPSAiZGF5IikKc3ViamVjdCAgICAgICAgICA8LSBjKCJ3YXJibGUiLCAiY3VsaSIpCmBgYAoKCmBgYHtyfQpzdWJqZWN0c19kYXRlcyA8LSBleHBhbmRfZ3JpZChzdWJqZWN0LCBjb2xsZWN0aW9uX2RhdGUpCgpjb2xsZWN0aW9uIDwtIHRpYmJsZShjb2xsZWN0aW9uX2RhdGUgPSBjb2xsZWN0aW9uX2RhdGUpICU+JQogIG11dGF0ZShzdHVkeV9kYXkgID0gcm93X251bWJlcigpLAogICAgICAgICBkYXlfd2VlayAgID0gd2RheShjb2xsZWN0aW9uX2RhdGUsIGxhYmVsID0gVFJVRSksCiAgICAgICAgIG1vbnRoICAgICAgPSBtb250aChjb2xsZWN0aW9uX2RhdGUsIGxhYmVsID0gVFJVRSksCiAgICAgICAgIHN0dWR5X3dlZWsgPSBpZl9lbHNlKGRheV93ZWVrID09ICJUaHUiLCAoc3R1ZHlfZGF5ICsgNikvNywgTkEpLAogICAgICAgICBzdHVkeV9tb250aCA9IGNvbnNlY3V0aXZlX2lkKG1vbnRoKQogICAgICAgICApICU+JQogIGZpbGwoc3R1ZHlfd2VlaykgJT4lCiAgc2VsZWN0KAogICAgY29sbGVjdGlvbl9kYXRlLAogICAgc3R1ZHlfbW9udGgsCiAgICBzdHVkeV93ZWVrLAogICAgc3R1ZHlfZGF5CiAgKSAlPiUKICByaWdodF9qb2luKHN1YmplY3RzX2RhdGVzLCBieSA9ICJjb2xsZWN0aW9uX2RhdGUiKQoKY29sbGVjdGlvbgpgYGAKCiMjIENvbnN0cnVjdCBWYXJpYWJsZXMKCiMjIyBIZWFsdGgKCiMjIyMgRGlldAoKV2UgaGF2ZSBvbmx5IHR3byBzdWJqZWN0cyBpbiB0aGlzIGRhdGFzZXQ6IEN1bGkgYW5kIFdhcmJsZS4gQ3VsaSB1bmRlcndlbnQgYSBzZXJpZXMgb2YgZGlldCB0cmlhbHMgdGhhdCB3ZSBvcmdhbml6ZWQgaW50byBzdGFydCBhbmQgZW5kIGRhdGVzLCB3aGlsZSBXYXJibGUgbWFpbnRhaW5lZCBhIGJhc2VsaW5lIGRpZXQgdGhyb3VnaG91dCBvdXIgc3R1ZHkuICAKCmBgYHtyfQpkaWV0X3RyaWFsc1tbMV1dJGJlZ2luIDwtIGZsb29yX2RhdGUoY29sbGVjdGlvbl9zdGFydCwgIm1vbnRoIikKCm51dHJpdGlvbl93aWRlIDwtIG51dHJpdGlvbiAlPiUKICBtdXRhdGUoZGlldCA9IGZjdF9yZWNvZGUoYXMuZmFjdG9yKGRpZXRfbmFtZSksICEhIWRpZXRfZmFjdG9ycykpICU+JQogIHJlbG9jYXRlKGRpZXQpICU+JQogIHNlbGVjdCgtZGlldF9uYW1lKSAlPiUKICBmaWx0ZXIoZGlldCAhPSAiT3RoZXIiKQpudXRyaXRpb25fd2lkZQplbmRzICAgPC0gZGlldF90cmlhbHMgJT4lIGRpc2NhcmRfYXQoMSkgJT4lCiAgbWFwKFwoeCkgbGlzdF9hc3NpZ24oeCwgZW5kID0geCRiZWdpbiAtIGRheSgxKSkpICU+JQogIG1hcChcKHgpIGtlZXBfYXQoeCwgImVuZCIpKSAlPiUKICBsaXN0X2ZsYXR0ZW4oKSAlPiUKICBsaXN0KC4sIGxpc3QoZW5kID0gKGNlaWxpbmdfZGF0ZShjb2xsZWN0aW9uX2VuZCwgIm1vbnRoIikgLSBkYXlzKDEpKSkpICU+JQogIGxpc3RfZmxhdHRlbigpCmRpZXRzIDwtIG1hcDIoCiAgZGlldF90cmlhbHMsIAogIGVuZHMsIAogIFwoeCwgeSkgbGlzdF9hc3NpZ24oeCwgZW5kID0geSwgY29sbGVjdGlvbl9kYXRlID0gc2VxKHgkYmVnaW4sIHkpKSkgJT4lCiAgZW5mcmFtZShuYW1lID0gTlVMTCkgICU+JQogIHVubmVzdF93aWRlcih2YWx1ZSkgJT4lCiAgc2VsZWN0KGRpZXQsIGNvbGxlY3Rpb25fZGF0ZSkgJT4lCiAgdW5uZXN0X2xvbmdlcihjb2xsZWN0aW9uX2RhdGUpICU+JQogIG11dGF0ZShzdWJqZWN0ID0gImN1bGkiKSAlPiUKICBiaW5kX3Jvd3MoZGlldHNfd2FyYmxlKQpkaWV0cwpgYGAKCiMjIyMgU3VwcGxlbWVudHMKCgpgYGB7cn0KZW5kcyAgIDwtIHN1cHBsZW1lbnRzICU+JSBkaXNjYXJkX2F0KDEpICU+JQogIG1hcChcKHgpIGxpc3QoeCRiZWdpbiAtIGRheSgxKSkpICU+JQogIGxpc3RfZmxhdHRlbigpCgptZWRzIDwtICBzdXBwbGVtZW50cyAlPiUgZGlzY2FyZF9hdCgyMSkgJT4lCiAgbWFwMigKICAgIGVuZHMsIAogICAgXCh4LCB5KSBsaXN0X2Fzc2lnbih4LCBlbmQgPSB5LCBjb2xsZWN0aW9uX2RhdGUgPSBzZXEoeCRiZWdpbiwgeSkpKSAlPiUKICBtYXBfZGVwdGgoMSwgXCh4KSBrZWVwKHgsIFwoeSkgYWxsKHkgPiAwKSkpICU+JQogIGRpc2NhcmQoXCh4KSBhbGwobGVuZ3RoKHgpIDwgNCkpICU+JQogIGVuZnJhbWUobmFtZT0gTlVMTCkgJT4lCiAgdW5uZXN0X3dpZGVyKHZhbHVlKSAlPiUKICBwaXZvdF9sb25nZXIoCiAgICBjKCJwcm9iaW90aWMiLCAic3Rlcm9pZCIsICJmaWJlciIsICJhbnRpYmlvdGljIiwgImFudGlkaWFycmhlYWwiKSwKICAgIG5hbWVzX3RvICAgICAgID0gInN1cHBsZW1lbnQiLAogICAgdmFsdWVzX3RvICAgICAgPSAiZG9zZSIsCiAgICB2YWx1ZXNfZHJvcF9uYSA9IFQKICAgICkgJT4lCiAgc2VsZWN0KGNvbGxlY3Rpb25fZGF0ZSwgc3VwcGxlbWVudCwgZG9zZSkgJT4lCiAgYXJyYW5nZShzdXBwbGVtZW50LCBkb3NlKSAlPiUKICBsZWZ0X2pvaW4oc3VwcGxlbWVudF9kZXRhaWxzLCBieSA9ICJzdXBwbGVtZW50IikgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShkb3NlX3JlbCA9IGRvc2UvZG9zZV9oaWdoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgdW5uZXN0X2xvbmdlcihjb2xsZWN0aW9uX2RhdGUpCm1lZHMKbWVkc193aWRlIDwtIG1lZHMgJT4lCiAgc2VsZWN0KGNvbGxlY3Rpb25fZGF0ZSwgc3VwcGxlbWVudCwgZG9zZV9yZWwpICU+JQogIHBpdm90X3dpZGVyKAogICAgbmFtZXNfZnJvbSAgPSAic3VwcGxlbWVudCIsIAogICAgdmFsdWVzX2Zyb20gPSAiZG9zZV9yZWwiLCAKICAgIHZhbHVlc19maWxsID0gMCkgJT4lCiAgbXV0YXRlKHN1YmplY3QgPSAiY3VsaSIpICU+JQogIGJpbmRfcm93cyhtZWRzX3dhcmJsZSkKbWVkc193aWRlCmBgYAoKCiMjIyMgRGlldCArIE1lZHMKCmBgYHtyfQpudXRyaXRpb25fbWVkcyA8LSBjb2xsZWN0aW9uICU+JQogIHNlbGVjdChjb2xsZWN0aW9uX2RhdGUsIHN1YmplY3QpICU+JQogIGxlZnRfam9pbihkaWV0cywgYnkgPSBqb2luX2J5KGNvbGxlY3Rpb25fZGF0ZSwgc3ViamVjdCkpICU+JQogIGxlZnRfam9pbihtZWRzX3dpZGUsIGJ5ID0gam9pbl9ieShjb2xsZWN0aW9uX2RhdGUsIHN1YmplY3QpKSAlPiUKICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB+cmVwbGFjZV9uYSguLCAwKSkpICU+JQogIGxlZnRfam9pbihudXRyaXRpb25fd2lkZSwgYnkgPSAiZGlldCIpCm51dHJpdGlvbl9tZWRzCmBgYAoKIyMjIyBCcmlzdG9sIFNjb3JlcwoKYGBge3J9CmJyaXN0b2xzX3dhcmJsZSA8LSBjb2xsZWN0aW9uICU+JQogIGZpbHRlcihzdWJqZWN0ID09ICJ3YXJibGUiKSAlPiUKICBtdXRhdGUoYnJpc3RvbCA9IDEpCgpicmlzdG9scyA8LSByZWFkX2NzdigKICAibWV0YWRhdGEvbG9yaXMvb3JpZ2luYWxzL2hkel9udXRyaXRpb25EYXRhXzIwMjUtMS0zMC5jc3YiLAogIGNvbF90eXBlcyA9IGNvbHNfb25seSgKICAgIERhdGUgPSBjb2xfZGF0ZShmb3JtYXQgPSAiJW0vJWQvJXkiKSwKICAgICJGZWNhbCBTY29yZSBmb3IgQ3VsaSIgPSBjb2xfY2hhcmFjdGVyKCkKICApCikgJT4lCiAgcmVuYW1lKGJyaXN0b2wgPSAiRmVjYWwgU2NvcmUgZm9yIEN1bGkiLCBjb2xsZWN0aW9uX2RhdGUgPSBEYXRlKSAlPiUKICBzZXBhcmF0ZV9sb25nZXJfZGVsaW0oYnJpc3RvbCwgIiwiKSAlPiUKICBtdXRhdGUoYnJpc3RvbCA9IGFzLmludGVnZXIoYXMuY2hhcmFjdGVyKHN0cl9yZW1vdmVfYWxsKGJyaXN0b2wsICJbXlxcZF0iKSkpKSAlPiUKICBmaWx0ZXIoIWlzLm5hKGJyaXN0b2wpICYgYnJpc3RvbCAhPSAwKSAlPiUKICBncm91cF9ieShjb2xsZWN0aW9uX2RhdGUpICU+JQogIHJlZnJhbWUoYnJpc3RvbCA9IG1lYW4oYnJpc3RvbCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICByaWdodF9qb2luKGZpbHRlcihjb2xsZWN0aW9uLCBzdWJqZWN0ID09ICJjdWxpIiksIGJ5ID0gam9pbl9ieShjb2xsZWN0aW9uX2RhdGUpKSAlPiUKICBhcnJhbmdlKGNvbGxlY3Rpb25fZGF0ZSkgJT4lCiAgbXV0YXRlKG1lYW5fd2Vla2x5ICA9IG1lYW4oYnJpc3RvbCwgbmEucm0gPSBUUlVFKSwgLmJ5ID0gInN0dWR5X3dlZWsiKSAlPiUKICByb3d3aXNlKCkgJT4lCiAgbXV0YXRlKGJyaXN0b2wgPSByZXBsYWNlX25hKGJyaXN0b2wsIG1lYW5fd2Vla2x5KSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGJpbmRfcm93cyhicmlzdG9sc193YXJibGUpICU+JQogIHNlbGVjdChjb2xsZWN0aW9uX2RhdGUsIHN1YmplY3QsIGJyaXN0b2wpICU+JQogIGFycmFuZ2UoY29sbGVjdGlvbl9kYXRlKQpgYGAKCiMjIyBTb2NpYWwvUmVwcm8KCmBgYHtyfQpkYXRlc19wYWlyIDwtIHNlbGVjdChjb2xsZWN0aW9uLCBjb2xsZWN0aW9uX2RhdGUsIHN1YmplY3QpICU+JQogIGxlZnRfam9pbih3YXJibGVfY3ljbGVzLCBieSA9IGpvaW5fYnkoY29sbGVjdGlvbl9kYXRlID09IGRhdGUpKSAlPiUKICBsZWZ0X2pvaW4oYWNjZXNzX2RhdGVzLCBieSA9IGpvaW5fYnkoY29sbGVjdGlvbl9kYXRlID09IGRhdGUpKSAlPiUKICBtdXRhdGUod2FyYl9jeWNsZSAgID0gcmVwbGFjZV9uYSh3YXJiX3N0YXR1cywgImFuZXN0cnVzIiksIAogICAgICAgICBwYWlyX2FjY2VzcyAgPSByZXBsYWNlX25hKHBhaXJfYWNjZXNzLCAibiIpLCAKICAgICAgICAgLmtlZXAgPSAidW51c2VkIikgJT4lCiAgbGVmdF9qb2luKGtlZXBlcl9ub3RlcywgYnkgPSBqb2luX2J5KGNvbGxlY3Rpb25fZGF0ZSA9PSBkYXRlLCBzdWJqZWN0KSkgJT4lCiAgbGVmdF9qb2luKGhvbGRpbmdzLCBieSA9IGpvaW5fYnkoY29sbGVjdGlvbl9kYXRlLCBzdWJqZWN0KSkgJT4lCiAgc2VsZWN0KGNvbGxlY3Rpb25fZGF0ZSwgc3ViamVjdCwgd2FyYl9jeWNsZSwgcGFpcl9hY2Nlc3MsIGhvbGRpbmcsIGtlZXBlcl9ub3RlKQpkYXRlc19wYWlyCmBgYAoKIyMgQ29tcGxldGUgTWV0YWRhdGEgVGFibGUKCmBgYHtyfQptZXRhZGF0YSA8LSBjb2xsZWN0aW9uICU+JQogIGxlZnRfam9pbihudXRyaXRpb25fbWVkcywgYnkgPSBqb2luX2J5KGNvbGxlY3Rpb25fZGF0ZSwgc3ViamVjdCkpICU+JQogIGxlZnRfam9pbihicmlzdG9scyAgICAgICwgYnkgPSBqb2luX2J5KGNvbGxlY3Rpb25fZGF0ZSwgc3ViamVjdCkpICU+JQogIGxlZnRfam9pbihkYXRlc19wYWlyICAgICwgYnkgPSBqb2luX2J5KGNvbGxlY3Rpb25fZGF0ZSwgc3ViamVjdCkpICU+JQogIHJlbG9jYXRlKAogICAgYnJpc3RvbCwKICAgIGhvbGRpbmcsIAogICAgd2FyYl9jeWNsZSwgCiAgICBrZWVwZXJfbm90ZSwKICAgIC5hZnRlciA9IHN0ZXJvaWQKICApCgpzYXZlKG1ldGFkYXRhLCBmaWxlID0gIm1ldGFkYXRhL2xvcmlzL21ldGFkYXRhLlJEYXRhIikKCndyaXRlX2NzdihtZXRhZGF0YSwgIm1ldGFkYXRhL2xvcmlzL21ldGFkYXRhLmNzdiIpCgptZXRhZGF0YQpgYGAKCiMjIFRpbWVsaW5lIE9wdGlvbiBmb3IgUGxvdHRpbmcKCkkndmUgYWxzbyBmb3VuZCB0aGF0IGl0IGlzIGhlbHBmdWwgdG8gc3RvcmUgYSB0aWJibGUgd2l0aCByb3dzIG9yZ2FuaXplZCBieSBkYXRlIGludGVydmFscyBmb3IgZXZlbnRzIG9yIHN0YXRlcyB0aGF0IG1pZ2h0IGJlIHBsb3R0ZWQgb24gYSB0aW1lbGluZSBvciB0aW1lc2VyaWVzIGdyYXBoIGZvciBjb250ZXh0LiBJIHdpbGwgYnJpbmcgaW4gdGhlIHNhbWUgZGF0YSB3aXRoIGRpZmZlcmVudCBzaGFwZXMgYW5kIGV4cG9ydCB0aGlzIGFzIGEgdGliYmxlIGZvciBsYXRlci4KCmBgYHtyfQpzb3VyY2UoIm1ldGFkYXRhL2xvcmlzL2xvcmlzX3RpbWVsaW5lLlIiKQoKY2F0KAogIHJlYWRfbGluZXMoIm1ldGFkYXRhL2xvcmlzL2xvcmlzX3RpbWVsaW5lLlIiLCBuX21heCA9IDIwKSwKICBzZXAgPSAiXG4iCikKYGBgCgoKYGBge3J9Cm1ldGFfdGltZWxpbmUgPC0gZGlldF90cmlhbHMgJT4lCiAgYmluZF9yb3dzKGFjY2Vzc19wYWlyKSAlPiUKICBiaW5kX3Jvd3MoaG9sZGluZ3MpICU+JQogIGJpbmRfcm93cyhyZXByb19kYXRlcykgJT4lCiAgYmluZF9yb3dzKHN1cHBsZW1lbnRzKSAlPiUKICBiaW5kX3Jvd3Moa2VlcGVyX25vdGVzKSAlPiUKICBhcnJhbmdlKHN0YXJ0LCBlbmQpCgpzYXZlKG1ldGFfdGltZWxpbmUsIGZpbGUgPSAibWV0YWRhdGEvbG9yaXMvbWV0YV90aW1lbGluZS5SRGF0YSIpCndyaXRlX2NzdihtZXRhX3RpbWVsaW5lLCAibWV0YWRhdGEvbG9yaXMvbWV0YV90aW1lbGluZS5jc3YiKQoKbWV0YV90aW1lbGluZQpgYGAKCgojIyBDb21waWxlIHdpdGggU2FtcGxlcyAoT3B0aW9uYWwpCgpJZiB5b3Ugd291bGQgbGlrZSB0byBiaW5kIHRoaXMgdG8gc2FtcGxlIGRhdGEgdG8gY3JlYXRlIHRoZSBzYW1wbGUgdGFibGUgbm93Li4uCgpgYGB7cn0KbG9hZCgiLi4vcmVhZF9wcm9jZXNzaW5nL3NhbXBsZXMvbG9yaXMvaW52ZW50b3JpZXMvY29tcGlsYXRpb25fc2VxX3JlY29yZHMuUkRhdGEiKQoKY29tcGlsYXRpb25fc2VxX3JlY29yZHMgPC0gY29tcGlsYXRpb25fc2VxX3JlY29yZHMgJT4lCiAgbXV0YXRlKHN1YmpfY29uZiA9IGlmX2Vsc2Uoc3ViamVjdCAlaW4lIGMoImN1bGkiLCAid2FyYmxlIiksICJ5ZXMiLCAibm8iKSkgJT4lCiAgbXV0YXRlKHN1YmplY3QgPSBzdHJfcmVtb3ZlX2FsbChzdWJqZWN0LCAiW15cXHcrXSIpKQoKY29tcGlsYXRpb25fbWV0YWRhdGEgPC0gbWV0YWRhdGEgJT4lCiAgbmVzdCgKICAgIGhlYWx0aCA9IGMoCiAgICAgIGJyaXN0b2wsCiAgICAgIHdhcmJfY3ljbGUsCiAgICAgIGtlZXBlcl9ub3RlCiAgICApLAogICAgdHJlYXRtZW50cyA9IGMoCiAgICAgIGRpZXQsCiAgICAgIGFudGliaW90aWMsCiAgICAgIGFudGlkaWFycmhlYWwsCiAgICAgIGZpYmVyLAogICAgICBwcm9iaW90aWMsCiAgICAgIHN0ZXJvaWQKICAgICksCiAgICBudXRyaXRpb24gPSBjKAogICAgICBUb3RhbF90b3RhbDpWaXRhbWluc19seWNvcGVuZQogICAgKSwKICAgIGVudmlyb25tZW50ID0gYygKICAgICAgaG9sZGluZywKICAgICAgcGFpcl9hY2Nlc3MKICAgICkKICApICU+JQogIHJpZ2h0X2pvaW4oY29tcGlsYXRpb25fc2VxX3JlY29yZHMsIAogICAgICAgICAgICAgYnkgPSBqb2luX2J5KHN1YmplY3QsIGNvbGxlY3Rpb25fZGF0ZSkpICU+JQogIHNlbGVjdCgKICAgIHN0dWR5X21vbnRoLAogICAgc3R1ZHlfd2VlaywKICAgIGNvbGxlY3Rpb25fZGF0ZSwKICAgIHN1YmplY3QsCiAgICBzdHVkeV9kYXksCiAgICBzYW1wbGVJRCwKICAgIGV4dHJhY3RlZCwKICAgIHNlcXVlbmNlZCwKICAgIGV4dHJhY3RzLAogICAgbGlicmFyaWVzLAogICAgZmlsdGVyZWRfc2FtcGxlcywKICAgIGhlYWx0aCwKICAgIHRyZWF0bWVudHMsCiAgICBudXRyaXRpb24sCiAgICBlbnZpcm9ubWVudAogICkgJT4lCiAgYXJyYW5nZShjb2xsZWN0aW9uX2RhdGUpICU+JQogIGdyb3VwX2J5KGNvbGxlY3Rpb25fZGF0ZSkgJT4lCiAgZmlsbChzdHVkeV9tb250aCwgc3R1ZHlfd2Vlaywgc3R1ZHlfZGF5KSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHN0dWR5X2RheSAgPSBpZl9lbHNlKAogICAgaXMubmEoc3R1ZHlfZGF5KSAmICFpcy5uYShjb2xsZWN0aW9uX2RhdGUpLCAKICAgIGxhZyhzdHVkeV9kYXkpICsgMSwgCiAgICBzdHVkeV9kYXkpKSAlPiUKICBtdXRhdGUoc3R1ZHlfd2VlayA9IGlmX2Vsc2UoCiAgICBpcy5uYShzdHVkeV93ZWVrKSAmIGxhZyhzdHVkeV93ZWVrKSA9PSBsZWFkKHN0dWR5X3dlZWspLCAKICAgIGxhZyhzdHVkeV93ZWVrKSwgCiAgICBzdHVkeV93ZWVrCiAgICApCiAgICApICU+JQogIGdyb3VwX2J5KHN0dWR5X3dlZWspICU+JQogIGZpbGwoc3R1ZHlfbW9udGgpICU+JQogIHVuZ3JvdXAoKQoKc2F2ZShjb21waWxhdGlvbl9tZXRhZGF0YSwgZmlsZSA9ICJtZXRhZGF0YS9sb3Jpcy9jb21waWxhdGlvbl9tZXRhZGF0YS5SRGF0YSIpCmBgYAoKYGBge3J9CmxvcmlzX3NhbXBsZV90YWJsZSA8LSBjb21waWxhdGlvbl9tZXRhZGF0YSAlPiUKICBmaWx0ZXIoc2VxdWVuY2VkID4gMCkgJT4lCiAgc2VsZWN0KHN0dWR5X21vbnRoOnNhbXBsZUlELCBmaWx0ZXJlZF9zYW1wbGVzOmVudmlyb25tZW50KSAlPiUKICBtdXRhdGUob2JzX2lkID0gYXMuY2hhcmFjdGVyKHN0cl9nbHVlKAogICAgIntzdHJfc3ViKHN1YmplY3QsIDFMLCA0TCl9e3N0dWR5X2RheX0iCiAgICApKSkgJT4lCiAgdW5uZXN0KGZpbHRlcmVkX3NhbXBsZXMpICU+JQogIHNlbGVjdCgKICAgIG9ic19pZCwKICAgIGFsaWFzLAogICAgc3ViamVjdCwKICAgIHN0dWR5X21vbnRoLAogICAgc3R1ZHlfd2VlaywKICAgIGNvbGxlY3Rpb25fZGF0ZSwKICAgIHN0dWR5X2RheSwKICAgIHNlcXJ1biwKICAgIHJlYWRfY291bnQsCiAgICB0cmVhdG1lbnRzLAogICAgaGVhbHRoLAogICAgZW52aXJvbm1lbnQsCiAgICBudXRyaXRpb24KICApICU+JQogIHVubmVzdCh0cmVhdG1lbnRzLCBoZWFsdGgsIGVudmlyb25tZW50LCBudXRyaXRpb24pICU+JQogIGRpc3RpbmN0KCkKCndyaXRlX2Nzdihsb3Jpc19zYW1wbGVfdGFibGUsICJkYXRhL2xvcmlzL21pY3JvYmlvbWVfc2FtcGxlX3RhYmxlLmNzdiIpCmBgYAoKCmBgYHtyfQpzYW1wbGVfdGFibGUgPC0gbG9yaXNfc2FtcGxlX3RhYmxlICU+JQogIGNvbHVtbl90b19yb3duYW1lcygiYWxpYXMiKQoKc2F2ZShzYW1wbGVfdGFibGUsIGZpbGUgPSAiZGF0YS9sb3Jpcy9zYW1wbGVfdGFibGUuUkRhdGEiKQpzYW1wbGVfdGFibGUKYGBgCgojIE5leHQgU3RlcHMKCj5Ob3cgeW91IHNob3VsZCBwcm9jZWVkIHRvIGEgQmlvaW5mb3JtYXRpY3Mgb3IgU3RhdGlzdGljYWwgV29ya2Zsb3cgdG8gQmVnaW4gTWVyZ2luZyBNZXRhZGF0YSB3aXRoIFJlc3VsdHMgZm9yIEFuYWx5c2lzLgoKCgoKCg==