# Set up code
# Use if kdict package not installed, run 'devtools::install_github("fauxneticien/kdict")'
library(kdict)
library(DT)
diagnostics <- list()
# Helpers
preview_df <- function(df, ...) {
df %>%
ungroup() %>%
slice(1:params$preview_rows) %>%
mutate_if(is.numeric, funs(round(., digits = params$round_digits))) %>%
datatable(rownames = FALSE, ...)
}
show_diag_df <- function(diagnostics_df, ...) {
diagnostics_df %>%
mutate_if(is.numeric, funs(round(., digits = params$round_digits))) %>%
datatable(rownames = FALSE, ...)
}
About
This R Markdown document steps through pre-processing the raw annotations (e.g. b001_file01_ns.TextGrid
) and formant (e.g. b001_file01.formants.forest.csv
) data, located in the default path of ../../data/raw
relative to the root of the project (this Rmd file should be in src/data
).
Code blocks are hidden by default, so press the Code
buttons on the top right to see each processing block, or ‘Show all code’ on the top right of this document.
Note. Semantically-meaningful word-seperators are used in variable names. Unprocessed data columns have .
in their name (e.g. vowel.text
), while derived/transformed data have _
in their name (e.g. vowel_num
).
By default, 5 first 5 rows from various steps of the data processing are showed (and numbers are rounded to 2 decimal places). Change this by adjusting the preview_rows
and round_digits
parameters at the top of the R Markdown file.
At the end of each step of the data transformation step, there are various diagnostics, which report commonly-occuring data entry errors by annotators.
Annotations (raw data)
Read in all TextGrid data from ../../data/raw
into a data frame
In particular, we read in the ipa
and vowels
tiers, and place them into a list of two data frames, called kdicts_dfs
.
kdict_dfs <- read_kdict_tiers(
kdict_path = params$kdict_path,
tier_names = c("ipa", "vowels")
) %>%
split(.$tier_name) %>%
map(~ select(., -tier_name) %>% mutate(annotator = str_to_lower(annotator)))
Data from ipa
tier
Since each file generally has two repetitions of a word, this data frame’s unit of observation is at the repetition level (i.e. not word level), and we name the columns accordingly (e.g. using rep.txt
and not word.text
).
kdict_dfs$ipa <- kdict_dfs$ipa %>%
group_by(source_file, annotator) %>%
rename(rep.xmin = xmin, rep.xmax = xmax, rep.text = text) %>%
filter(nzchar(str_remove_all(rep.text, "\\s"))) %>%
mutate(rep_num = 1:n()) %>%
ungroup()
kdict_dfs$ipa %>%
preview_df(caption = "Word repetition annotation data from the 'ipa' tiers")
Data from vowels
tier
kdict_dfs$vowels <-
kdict_dfs$vowels %>%
rename(vowel.xmin = xmin, vowel.xmax = xmax, vowel.text = text)
kdict_dfs$vowels %>%
filter(nzchar(vowel.text)) %>%
preview_df(caption = "Vowel annotations data from the 'vowel' tiers")
Resulting joined data
We join the two data frames by their common source_file
, and keep only the resulting rows where the vowel’s midpoint is within the repetition’s time range see mutate(xmid = vowel.xmin + (vowel.xmax - vowel.xmin) / 2)
and filter(xmid >= rep.xmin, xmid <= rep.xmax)
in the code below.
vowels_reps_df <-
kdict_dfs$vowels %>%
filter(str_detect(vowel.text, get_vowels_regex())) %>%
mutate(xmid = vowel.xmin + (vowel.xmax - vowel.xmin) / 2) %>%
left_join(
y = kdict_dfs$ipa,
by = c("annotator", "source_file")
) %>%
filter(xmid >= rep.xmin, xmid <= rep.xmax) %>%
select(-xmid)
vowels_reps_df %>%
preview_df(caption = "Joined data betwen 'vowel' and 'ipa' tiers")
Diagnostics
Mismatched repetition counts
The data frame below displays the files for which annotators have differing number of repetition-level transcriptions within the source_file
.
diagnostics$word_counts_mismatched <-
kdict_dfs$ipa %>%
group_by(source_file, annotator) %>%
summarise(reps_total = max(rep_num)) %>%
group_by(source_file) %>%
filter(n_distinct(reps_total) > 1) %>%
spread(annotator, reps_total)
diagnostics$word_counts_mismatched %>%
show_diag_df(caption = "Mismatched number of repetitions on 'ipa' tier within each file")
Non-vocalic labels
The following data displays counts on the columns of non-vocalic labels on the vowels
tier within the each source_file
. These labels are not analysed, so one should make sure no false negatives in the columns (i.e. vowel labels that should be analysed but have been mistakenly caught by this filter).
diagnostics$unanalysed_vowel_tier_labels <-
anti_join(
x = kdict_dfs$vowels,
y = vowels_reps_df
) %>%
filter(!is.na(vowel.text)) %>%
# mutate(text = str_remove_all(text, ":|\\?|'|#|ˈ|ː")) %>%
group_by(source_file, annotator, vowel.text) %>%
tally() %>%
spread(key = vowel.text, value = n)
diagnostics$unanalysed_vowel_tier_labels %>%
show_diag_df(caption = "Non-vocalic labels on the 'vowels' tiers")
Orphaned transcriptions
The data frame below displays repetition-level transcriptions for which no vowels have been assigned. Likely only the ipa
tier in the source_file
has been completed, and not the vowels
tier as well.
diagnostics$orphaned_reps <-
kdict_dfs$ipa %>%
anti_join(select(vowels_reps_df, source_file, rep_num, annotator)) %>%
filter(str_detect(rep.text, get_vowels_regex()))
diagnostics$orphaned_reps %>%
show_diag_df(caption = "Orphaned transcriptions on 'ipa' tier, unmatched by any interval on its 'vowel' tier")
Vowelless transcriptions
The data frame below displays likely incomplete repetition-level transcriptions, in which no vowels were detected.
diagnostics$novowel_ipa <-
kdict_dfs$ipa %>%
filter(!str_detect(rep.text, get_vowels_regex()), nzchar(str_remove_all(rep.text, "\\s")))
diagnostics$novowel_ipa %>%
show_diag_df(caption = "Vowelless transcriptions")
Annotations (derived data)
Consonantal context
We derive consonantal context (const_ctx
) by stripping both vowel.text
and rep.text
of various diacritics; the diacritic-stripped version of these columns are called base_vowel
and base_transcription
, respectively.
We then derive, for each annotator and word repetition, the consonantal contexts of all vowels on the vowels
tier by matching them to the vowels present on the ipa
tier.
Of course, if they cannot be exactly matched, the const_ctx
returned will be NA
for the whole repetition (such mismatches are given in a diagnostics data frame at the end of this section).
vowels_contexts_df <-
vowels_reps_df %>%
mutate(
base_transcription = remove_diacritics(rep.text) %>% str_remove_all(":|\\?|'|#|ˈ|ː| "),
base_vowel = remove_diacritics(vowel.text) %>% str_remove_all(":|\\?|'|#|ˈ|ː|j|ɹ|ɺ|w| "),
rep_vowels = future_map_chr(base_transcription, ~ str_extract_all(., get_vowels_regex()) %>% unlist(use.names = FALSE) %>% paste0(collapse = ""))
) %>%
group_by(source_file, annotator, rep.xmin, rep.xmax, base_transcription) %>%
nest() %>%
mutate(
base_vowel = map(data, ~ .$base_vowel),
const_ctx = future_map2(base_transcription, base_vowel, ~ get_cons_context(.x, .y))
) %>%
select(-base_vowel) %>%
unnest()
vowels_contexts_df %>%
select(base_vowel, base_transcription, const_ctx, vowel.xmin, vowel.xmax, rep_num, annotator, source_file) %>%
preview_df(caption = "Consonantal context for vowel dervied from repetition-level transcription data")
Diagnostics
Mismatched boundary markers
Initial- and final-vowels are required to be marked by #
on, respectively, the left- and right-edge of intervals on the vowels
tier. The following table displays vowels on the vowels
tier for which the derived consonantal context was either initial or final, but ‘#’ had not been detected as present in the interval(s) on the vowels
tier.
diagnostics$mismatched_edges <-
vowels_contexts_df %>%
filter(str_detect(const_ctx, "#"), !str_detect(vowel.text, "#"))
diagnostics$mismatched_edges %>%
select(source_file, annotator, rep_num, vowel.xmin, vowel.xmax, const_ctx, vowel.text, base_transcription) %>%
show_diag_df()
Mismatched vowels
The following data frame displays repetitions where where ipa
tier’s vowels and vowels
tier’s vowels are mismatched within the given annotator’s source_file
.
diagnostics$ipa_vowels_mismatched <-
vowels_contexts_df %>%
rename(ipa_tier_vowels = rep_vowels) %>%
group_by(source_file, annotator, rep_num, base_transcription, ipa_tier_vowels) %>%
summarise(vowels_tier_vowels = paste0(base_vowel, collapse = "")) %>%
ungroup %>%
filter(vowels_tier_vowels != ipa_tier_vowels)
diagnostics$ipa_vowels_mismatched %>%
show_diag_df(caption = "Word repetitions where 'ipa' and 'vowels' tiers' vowels are mismatched")
Underivable consonantal context
The data frame below displays vowels for which consonantal context could not be derived. Troubleshooting for these observations will probably require a bit of detective work.
diagnostics$const_ctx_na <-
vowels_contexts_df %>%
filter(is.na(const_ctx))
diagnostics$const_ctx_na %>%
select(source_file, annotator, rep_num, vowel.xmin, vowel.xmax, base_vowel, base_transcription, const_ctx) %>%
show_diag_df()
Medial vowels, pre-processed
Excluding any vowels in the diagnostic tables above, we know that within the remaining data that a) vowels and ipa tiers’ vowel labels are matched and, therefore, b) we have a dervied cosonantal context.
Thus, we can exclude all initial and final vowels (i.e. where #
detected in const_ctx
), and then further exclude repetitions for which annotators disagree on the number of medial vowels.
Finally, we also exclude vowel transcriptions not having at least 2 different annotators.
vowels_med_raw <-
vowels_contexts_df %>%
filter(!str_detect(const_ctx, "#"), nchar(base_vowel) == 1) %>% # Keep only medial monophthongs
reduce(.x = diagnostics, .f = anti_join, .init = .) # Discard vowels with any matches in any of the diagnostics data frames
vowels_med_preprocessed <-
vowels_med_raw %>%
group_by(source_file, rep_num, annotator) %>%
nest() %>%
mutate(n_vowels = map_int(data, nrow)) %>% # find number of vowels per word rep per annotator
group_by(source_file, rep_num) %>%
filter(n_distinct(n_vowels) == 1) %>% # keep word rep iff all annotator agree on num of vowels
select(-n_vowels) %>%
unnest() %>%
group_by(source_file, rep_num, annotator) %>% # given all annotators agree, only now give sequential ids
mutate(vowel_num = 1:n()) %>% # to vowel tokens within word repetitions (e.g. rep 2 vowel 1)
group_by(source_file, rep_num, vowel_num) %>%
filter(dplyr::n_distinct(annotator) >= 2) %>% # keep only vowel tokens with more than 1 annotator
ungroup() %>%
select(
source_file, rep_num, vowel_num, base_vowel, const_ctx, base_transcription, annotator,
vowel.text, vowel.xmin, vowel.xmax, rep.text, rep.xmin, rep.xmax
)
vowels_med_preprocessed %>%
select(source_file, rep_num, vowel_num, base_vowel, const_ctx, annotator, vowel.xmin, vowel.xmax) %>%
preview_df(caption = "Preprocessed medial vowel annotations with at least 2 transcribers per vowel")
Write data (not run by default)
Write vowels_med_analysis.csv
To have data (over)written, change the write_csvs
parameter.
readr::write_csv(
x = vowels_med_analysis,
path = file.path(params$output_path, "vowels_med_analysis.csv")
)
Write diagnostics/*.csv
diags_dir <- file.path(params$output_path, "diagnostics")
if(!dir.exists(diags_dir)) { dir.create(diags_dir) }
diagnostics %>%
iwalk(~ readr::write_csv(x = .x, path = file.path(diags_dir, paste0(.y, ".csv")), na = ""))
LS0tCnRpdGxlOiAiUHJlcHJvY2VzcyBhbm5vdGF0aW9ucyBhbmQgZm9ybWFudCBkYXRhIgphdXRob3I6ICJOYXkgU2FuIgpkYXRlOiAnR2VuZXJhdGVkIGByIGZvcm1hdChTeXMudGltZSgpLCAiJUYgJVIgVVRDJXoiKWAnCm91dHB1dDoKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCnBhcmFtczoKICAgIGtkaWN0X3BhdGg6ICIuLi8uLi9kYXRhL3JhdyIKICAgIHByZXZpZXdfcm93czogNQogICAgcm91bmRfZGlnaXRzOiAyCiAgICB3cml0ZV9jc3ZzOiBmYWxzZQogICAgb3V0cHV0X3BhdGg6ICIuLi8uLi9kYXRhL3Byb2Nlc3NlZCIKLS0tCgpgYGB7ciBTZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBTZXQgdXAgY29kZQogICAgCiMgVXNlIGlmIGtkaWN0IHBhY2thZ2Ugbm90IGluc3RhbGxlZCwgcnVuICdkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImZhdXhuZXRpY2llbi9rZGljdCIpJwpsaWJyYXJ5KGtkaWN0KQpsaWJyYXJ5KERUKQogICAgCmRpYWdub3N0aWNzIDwtIGxpc3QoKQogICAgCiMgSGVscGVycwpwcmV2aWV3X2RmIDwtIGZ1bmN0aW9uKGRmLCAuLi4pIHsKICAgIGRmICU+JQogICAgdW5ncm91cCgpICU+JSAKICAgIHNsaWNlKDE6cGFyYW1zJHByZXZpZXdfcm93cykgJT4lCiAgICAgICAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bnMocm91bmQoLiwgZGlnaXRzID0gcGFyYW1zJHJvdW5kX2RpZ2l0cykpKSAlPiUgCiAgICAgICAgZGF0YXRhYmxlKHJvd25hbWVzID0gRkFMU0UsIC4uLikKfQogICAgCnNob3dfZGlhZ19kZiA8LSBmdW5jdGlvbihkaWFnbm9zdGljc19kZiwgLi4uKSB7CiAgICBkaWFnbm9zdGljc19kZiAlPiUgCiAgICAgICAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bnMocm91bmQoLiwgZGlnaXRzID0gcGFyYW1zJHJvdW5kX2RpZ2l0cykpKSAlPiUKICAgICAgICBkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSwgLi4uKQp9CmBgYAoKIyBBYm91dAoKVGhpcyBSIE1hcmtkb3duIGRvY3VtZW50IHN0ZXBzIHRocm91Z2ggcHJlLXByb2Nlc3NpbmcgdGhlIHJhdyBhbm5vdGF0aW9ucyAoZS5nLiBgYjAwMV9maWxlMDFfbnMuVGV4dEdyaWRgKSBhbmQgZm9ybWFudCAoZS5nLiBgYjAwMV9maWxlMDEuZm9ybWFudHMuZm9yZXN0LmNzdmApIGRhdGEsIGxvY2F0ZWQgaW4gdGhlIGRlZmF1bHQgcGF0aCBvZiBgYHIgcGFyYW1zJGtkaWN0X3BhdGhgYCByZWxhdGl2ZSB0byB0aGUgcm9vdCBvZiB0aGUgcHJvamVjdCAodGhpcyBSbWQgZmlsZSBzaG91bGQgYmUgaW4gYHNyYy9kYXRhYCkuCgpDb2RlIGJsb2NrcyBhcmUgaGlkZGVuIGJ5IGRlZmF1bHQsIHNvIHByZXNzIHRoZSBgQ29kZWAgYnV0dG9ucyBvbiB0aGUgdG9wIHJpZ2h0IHRvIHNlZSBlYWNoIHByb2Nlc3NpbmcgYmxvY2ssIG9yICdTaG93IGFsbCBjb2RlJyBvbiB0aGUgdG9wIHJpZ2h0IG9mIHRoaXMgZG9jdW1lbnQuCgoqKio8c3BhbiBzdHlsZT0iY29sb3I6cmVkIj5Ob3RlPC9zcGFuPioqKi4gU2VtYW50aWNhbGx5LW1lYW5pbmdmdWwgd29yZC1zZXBlcmF0b3JzIGFyZSB1c2VkIGluIHZhcmlhYmxlIG5hbWVzLiBVbnByb2Nlc3NlZCBkYXRhIGNvbHVtbnMgaGF2ZSBgLmAgaW4gdGhlaXIgbmFtZSAoZS5nLiBgdm93ZWwudGV4dGApLCB3aGlsZSBkZXJpdmVkL3RyYW5zZm9ybWVkIGRhdGEgaGF2ZSBgX2AgaW4gdGhlaXIgbmFtZSAoZS5nLiBgdm93ZWxfbnVtYCkuCgpCeSBkZWZhdWx0LCBgciBwYXJhbXMkcHJldmlld19yb3dzYCBmaXJzdCA1IHJvd3MgZnJvbSB2YXJpb3VzIHN0ZXBzIG9mIHRoZSBkYXRhIHByb2Nlc3NpbmcgYXJlIHNob3dlZCAoYW5kIG51bWJlcnMgYXJlIHJvdW5kZWQgdG8gMiBkZWNpbWFsIHBsYWNlcykuIENoYW5nZSB0aGlzIGJ5IGFkanVzdGluZyB0aGUgYHByZXZpZXdfcm93c2AgYW5kIGByb3VuZF9kaWdpdHNgIHBhcmFtZXRlcnMgYXQgdGhlIHRvcCBvZiB0aGUgUiBNYXJrZG93biBmaWxlLgoKQXQgdGhlIGVuZCBvZiBlYWNoIHN0ZXAgb2YgdGhlIGRhdGEgdHJhbnNmb3JtYXRpb24gc3RlcCwgdGhlcmUgYXJlIHZhcmlvdXMgZGlhZ25vc3RpY3MsIHdoaWNoIHJlcG9ydCBjb21tb25seS1vY2N1cmluZyBkYXRhIGVudHJ5IGVycm9ycyBieSBhbm5vdGF0b3JzLiAKCiMgQW5ub3RhdGlvbnMgKHJhdyBkYXRhKQoKIyMgUmVhZCBpbiBhbGwgVGV4dEdyaWQgZGF0YSBmcm9tIGBgciBwYXJhbXMka2RpY3RfcGF0aGBgIGludG8gYSBkYXRhIGZyYW1lIHsudGFic2V0fQoKSW4gcGFydGljdWxhciwgd2UgcmVhZCBpbiB0aGUgYGlwYWAgYW5kIGB2b3dlbHNgIHRpZXJzLCBhbmQgcGxhY2UgdGhlbSBpbnRvIGEgbGlzdCBvZiB0d28gZGF0YSBmcmFtZXMsIGNhbGxlZCBga2RpY3RzX2Rmc2AuCgpgYGB7ciBNYWtlIGtkaWN0X2RmcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Ka2RpY3RfZGZzIDwtIHJlYWRfa2RpY3RfdGllcnMoCiAgICAgICAga2RpY3RfcGF0aCA9IHBhcmFtcyRrZGljdF9wYXRoLAogICAgICAgIHRpZXJfbmFtZXMgPSBjKCJpcGEiLCAidm93ZWxzIikKICAgICkgJT4lCiAgICBzcGxpdCguJHRpZXJfbmFtZSkgJT4lCiAgICBtYXAofiBzZWxlY3QoLiwgLXRpZXJfbmFtZSkgJT4lIG11dGF0ZShhbm5vdGF0b3IgPSBzdHJfdG9fbG93ZXIoYW5ub3RhdG9yKSkpCmBgYAoKIyMjIERhdGEgZnJvbSBgaXBhYCB0aWVyCgpTaW5jZSBlYWNoIGZpbGUgZ2VuZXJhbGx5IGhhcyB0d28gcmVwZXRpdGlvbnMgb2YgYSB3b3JkLCB0aGlzIGRhdGEgZnJhbWUncyB1bml0IG9mIG9ic2VydmF0aW9uIGlzIGF0IHRoZSByZXBldGl0aW9uIGxldmVsIChpLmUuIG5vdCB3b3JkIGxldmVsKSwgYW5kIHdlIG5hbWUgdGhlIGNvbHVtbnMgYWNjb3JkaW5nbHkgKGUuZy4gdXNpbmcgYHJlcC50eHRgIGFuZCBub3QgYHdvcmQudGV4dGApLgoKYGBge3IgUHJldmlldyBpcGEgZGF0YSBmcmFtZX0Ka2RpY3RfZGZzJGlwYSA8LSBrZGljdF9kZnMkaXBhICU+JQogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIGFubm90YXRvcikgJT4lCiAgICByZW5hbWUocmVwLnhtaW4gPSB4bWluLCByZXAueG1heCA9IHhtYXgsIHJlcC50ZXh0ID0gdGV4dCkgJT4lCiAgICBmaWx0ZXIobnpjaGFyKHN0cl9yZW1vdmVfYWxsKHJlcC50ZXh0LCAiXFxzIikpKSAlPiUKICAgIG11dGF0ZShyZXBfbnVtID0gMTpuKCkpICU+JQogICAgdW5ncm91cCgpCiAgICAKa2RpY3RfZGZzJGlwYSAlPiUKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJXb3JkIHJlcGV0aXRpb24gYW5ub3RhdGlvbiBkYXRhIGZyb20gdGhlICdpcGEnIHRpZXJzIikKYGBgCgojIyMgRGF0YSBmcm9tIGB2b3dlbHNgIHRpZXIKCmBgYHtyIFByZXZpZXcgdm93ZWwgZGF0YSBmcmFtZX0Ka2RpY3RfZGZzJHZvd2VscyA8LQogICAga2RpY3RfZGZzJHZvd2VscyAlPiUKICAgIHJlbmFtZSh2b3dlbC54bWluID0geG1pbiwgdm93ZWwueG1heCA9IHhtYXgsIHZvd2VsLnRleHQgPSB0ZXh0KQoKa2RpY3RfZGZzJHZvd2VscyAlPiUKICAgIGZpbHRlcihuemNoYXIodm93ZWwudGV4dCkpICU+JSAKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJWb3dlbCBhbm5vdGF0aW9ucyBkYXRhIGZyb20gdGhlICd2b3dlbCcgdGllcnMiKQpgYGAKCiMjIyBSZXN1bHRpbmcgam9pbmVkIGRhdGEKCldlIGpvaW4gdGhlIHR3byBkYXRhIGZyYW1lcyBieSB0aGVpciBjb21tb24gYHNvdXJjZV9maWxlYCwgYW5kIGtlZXAgb25seSB0aGUgcmVzdWx0aW5nIHJvd3Mgd2hlcmUgdGhlIHZvd2VsJ3MgbWlkcG9pbnQgaXMgd2l0aGluIHRoZSByZXBldGl0aW9uJ3MgdGltZSByYW5nZSBzZWUgYG11dGF0ZSh4bWlkID0gdm93ZWwueG1pbiArICh2b3dlbC54bWF4IC0gdm93ZWwueG1pbikgLyAyKWAgYW5kIGBmaWx0ZXIoeG1pZCA+PSByZXAueG1pbiwgeG1pZCA8PSByZXAueG1heClgIGluIHRoZSBjb2RlIGJlbG93LgoKYGBge3IgTWFrZSB2b3dlbF9yZXBzX2RmIGFuZCBwcmV2aWV3IHJlc3VsdH0Kdm93ZWxzX3JlcHNfZGYgPC0KICAgIGtkaWN0X2RmcyR2b3dlbHMgJT4lCiAgICBmaWx0ZXIoc3RyX2RldGVjdCh2b3dlbC50ZXh0LCBnZXRfdm93ZWxzX3JlZ2V4KCkpKSAlPiUgCiAgICBtdXRhdGUoeG1pZCA9IHZvd2VsLnhtaW4gKyAodm93ZWwueG1heCAtIHZvd2VsLnhtaW4pIC8gMikgJT4lIAogICAgbGVmdF9qb2luKAogICAgICAgIHkgID0ga2RpY3RfZGZzJGlwYSwKICAgICAgICBieSA9IGMoImFubm90YXRvciIsICJzb3VyY2VfZmlsZSIpIAogICAgKSAlPiUKICAgIGZpbHRlcih4bWlkID49IHJlcC54bWluLCB4bWlkIDw9IHJlcC54bWF4KSAlPiUKICAgIHNlbGVjdCgteG1pZCkKICAgIAp2b3dlbHNfcmVwc19kZiAlPiUKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJKb2luZWQgZGF0YSBiZXR3ZW4gJ3Zvd2VsJyBhbmQgJ2lwYScgdGllcnMiKQpgYGAKCgojIyBEaWFnbm9zdGljcyB7LnRhYnNldH0KCiMjIyBNaXNtYXRjaGVkIHJlcGV0aXRpb24gY291bnRzCgpUaGUgZGF0YSBmcmFtZSBiZWxvdyBkaXNwbGF5cyB0aGUgZmlsZXMgZm9yIHdoaWNoIGFubm90YXRvcnMgaGF2ZSBkaWZmZXJpbmcgbnVtYmVyIG9mIHJlcGV0aXRpb24tbGV2ZWwgdHJhbnNjcmlwdGlvbnMgd2l0aGluIHRoZSBgc291cmNlX2ZpbGVgLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyR3b3JkX2NvdW50c19taXNtYXRjaGVkfQpkaWFnbm9zdGljcyR3b3JkX2NvdW50c19taXNtYXRjaGVkIDwtCiAgICBrZGljdF9kZnMkaXBhICU+JQogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIGFubm90YXRvcikgJT4lCiAgICBzdW1tYXJpc2UocmVwc190b3RhbCA9IG1heChyZXBfbnVtKSkgJT4lCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSkgJT4lCiAgICBmaWx0ZXIobl9kaXN0aW5jdChyZXBzX3RvdGFsKSA+IDEpICU+JQogICAgc3ByZWFkKGFubm90YXRvciwgcmVwc190b3RhbCkKICAgIApkaWFnbm9zdGljcyR3b3JkX2NvdW50c19taXNtYXRjaGVkICU+JQogICAgc2hvd19kaWFnX2RmKGNhcHRpb24gPSAiTWlzbWF0Y2hlZCBudW1iZXIgb2YgcmVwZXRpdGlvbnMgb24gJ2lwYScgdGllciB3aXRoaW4gZWFjaCBmaWxlIikKYGBgCgoKIyMjIE5vbi12b2NhbGljIGxhYmVscwoKVGhlIGZvbGxvd2luZyBkYXRhIGRpc3BsYXlzIGNvdW50cyBvbiB0aGUgY29sdW1ucyBvZiBub24tdm9jYWxpYyBsYWJlbHMgb24gdGhlIGB2b3dlbHNgIHRpZXIgd2l0aGluIHRoZSBlYWNoIGBzb3VyY2VfZmlsZWAuIFRoZXNlIGxhYmVscyBhcmUgbm90IGFuYWx5c2VkLCBzbyBvbmUgc2hvdWxkIG1ha2Ugc3VyZSBubyBmYWxzZSBuZWdhdGl2ZXMgaW4gdGhlIGNvbHVtbnMgKGkuZS4gdm93ZWwgbGFiZWxzIHRoYXQgc2hvdWxkIGJlIGFuYWx5c2VkIGJ1dCBoYXZlIGJlZW4gbWlzdGFrZW5seSBjYXVnaHQgYnkgdGhpcyBmaWx0ZXIpLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyR1bmFuYWx5c2VkX3Zvd2VsX3RpZXJfbGFiZWxzLCBtZXNzYWdlPUZBTFNFfQpkaWFnbm9zdGljcyR1bmFuYWx5c2VkX3Zvd2VsX3RpZXJfbGFiZWxzIDwtCiAgICBhbnRpX2pvaW4oCiAgICAgICAgeCA9IGtkaWN0X2RmcyR2b3dlbHMsCiAgICAgICAgeSA9IHZvd2Vsc19yZXBzX2RmCiAgICApICU+JSAKICAgIGZpbHRlcighaXMubmEodm93ZWwudGV4dCkpICU+JQogICAgIyBtdXRhdGUodGV4dCA9IHN0cl9yZW1vdmVfYWxsKHRleHQsICI6fFxcP3wnfCN8y4h8y5AiKSkgJT4lIAogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIGFubm90YXRvciwgdm93ZWwudGV4dCkgJT4lIAogICAgdGFsbHkoKSAlPiUKICAgIHNwcmVhZChrZXkgPSB2b3dlbC50ZXh0LCB2YWx1ZSA9IG4pCiAgICAKZGlhZ25vc3RpY3MkdW5hbmFseXNlZF92b3dlbF90aWVyX2xhYmVscyAlPiUKICAgIHNob3dfZGlhZ19kZihjYXB0aW9uID0gIk5vbi12b2NhbGljIGxhYmVscyBvbiB0aGUgJ3Zvd2VscycgdGllcnMiKQpgYGAKCgojIyMgT3JwaGFuZWQgdHJhbnNjcmlwdGlvbnMKClRoZSBkYXRhIGZyYW1lIGJlbG93IGRpc3BsYXlzIHJlcGV0aXRpb24tbGV2ZWwgdHJhbnNjcmlwdGlvbnMgZm9yIHdoaWNoIG5vIHZvd2VscyBoYXZlIGJlZW4gYXNzaWduZWQuIExpa2VseSBvbmx5IHRoZSBgaXBhYCB0aWVyIGluIHRoZSBgc291cmNlX2ZpbGVgIGhhcyBiZWVuIGNvbXBsZXRlZCwgYW5kIG5vdCB0aGUgYHZvd2Vsc2AgdGllciBhcyB3ZWxsLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyRvcnBoYW5lZF9yZXBzLCBtZXNzYWdlPUZBTFNFfQpkaWFnbm9zdGljcyRvcnBoYW5lZF9yZXBzIDwtIAogICAga2RpY3RfZGZzJGlwYSAlPiUKICAgIGFudGlfam9pbihzZWxlY3Qodm93ZWxzX3JlcHNfZGYsIHNvdXJjZV9maWxlLCByZXBfbnVtLCBhbm5vdGF0b3IpKSAlPiUgCiAgICBmaWx0ZXIoc3RyX2RldGVjdChyZXAudGV4dCwgZ2V0X3Zvd2Vsc19yZWdleCgpKSkKICAgIApkaWFnbm9zdGljcyRvcnBoYW5lZF9yZXBzICU+JQogICAgc2hvd19kaWFnX2RmKGNhcHRpb24gPSAiT3JwaGFuZWQgdHJhbnNjcmlwdGlvbnMgb24gJ2lwYScgdGllciwgdW5tYXRjaGVkIGJ5IGFueSBpbnRlcnZhbCBvbiBpdHMgJ3Zvd2VsJyB0aWVyIikKYGBgCgojIyMgVm93ZWxsZXNzIHRyYW5zY3JpcHRpb25zCgpUaGUgZGF0YSBmcmFtZSBiZWxvdyBkaXNwbGF5cyBsaWtlbHkgaW5jb21wbGV0ZSByZXBldGl0aW9uLWxldmVsIHRyYW5zY3JpcHRpb25zLCBpbiB3aGljaCBubyB2b3dlbHMgd2VyZSBkZXRlY3RlZC4KCmBgYHtyIE1ha2UgZGlhZ25vc3RpY3Mkbm92b3dlbF9pcGF9CmRpYWdub3N0aWNzJG5vdm93ZWxfaXBhIDwtCiAgICBrZGljdF9kZnMkaXBhICU+JQogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KHJlcC50ZXh0LCBnZXRfdm93ZWxzX3JlZ2V4KCkpLCBuemNoYXIoc3RyX3JlbW92ZV9hbGwocmVwLnRleHQsICJcXHMiKSkpCiAgICAKZGlhZ25vc3RpY3Mkbm92b3dlbF9pcGEgJT4lCiAgICBzaG93X2RpYWdfZGYoY2FwdGlvbiA9ICJWb3dlbGxlc3MgdHJhbnNjcmlwdGlvbnMiKQpgYGAKCiMgQW5ub3RhdGlvbnMgKGRlcml2ZWQgZGF0YSkKCiMjIENvbnNvbmFudGFsIGNvbnRleHQKCldlIGRlcml2ZSBjb25zb25hbnRhbCBjb250ZXh0IChgY29uc3RfY3R4YCkgYnkgc3RyaXBwaW5nIGJvdGggYHZvd2VsLnRleHRgIGFuZCBgcmVwLnRleHRgIG9mIHZhcmlvdXMgZGlhY3JpdGljczsgdGhlIGRpYWNyaXRpYy1zdHJpcHBlZCB2ZXJzaW9uIG9mIHRoZXNlIGNvbHVtbnMgYXJlIGNhbGxlZCBgYmFzZV92b3dlbGAgYW5kIGBiYXNlX3RyYW5zY3JpcHRpb25gLCByZXNwZWN0aXZlbHkuCgpXZSB0aGVuIGRlcml2ZSwgZm9yIGVhY2ggYW5ub3RhdG9yIGFuZCB3b3JkIHJlcGV0aXRpb24sIHRoZSBjb25zb25hbnRhbCBjb250ZXh0cyBvZiBhbGwgdm93ZWxzIG9uIHRoZSBgdm93ZWxzYCB0aWVyIGJ5IG1hdGNoaW5nIHRoZW0gdG8gdGhlIHZvd2VscyBwcmVzZW50IG9uIHRoZSBgaXBhYCB0aWVyLgoKT2YgY291cnNlLCBpZiB0aGV5IGNhbm5vdCBiZSBleGFjdGx5IG1hdGNoZWQsIHRoZSBgY29uc3RfY3R4YCByZXR1cm5lZCB3aWxsIGJlIGBOQWAgZm9yIHRoZSAqKndob2xlIHJlcGV0aXRpb24qKiAoc3VjaCBtaXNtYXRjaGVzIGFyZSBnaXZlbiBpbiBhIGRpYWdub3N0aWNzIGRhdGEgZnJhbWUgYXQgdGhlIGVuZCBvZiB0aGlzIHNlY3Rpb24pLgoKYGBge3IgTWFrZSB2b3dlbHNfY29udGV4dHNfZGZ9CnZvd2Vsc19jb250ZXh0c19kZiA8LQogICAgdm93ZWxzX3JlcHNfZGYgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgYmFzZV90cmFuc2NyaXB0aW9uID0gcmVtb3ZlX2RpYWNyaXRpY3MocmVwLnRleHQpICU+JSBzdHJfcmVtb3ZlX2FsbCgiOnxcXD98J3wjfMuIfMuQfCAiKSwKICAgICAgICBiYXNlX3Zvd2VsICAgICAgICAgPSByZW1vdmVfZGlhY3JpdGljcyh2b3dlbC50ZXh0KSAlPiUgc3RyX3JlbW92ZV9hbGwoIjp8XFw/fCd8I3zLiHzLkHxqfMm5fMm6fHd8ICIpLAogICAgICAgIHJlcF92b3dlbHMgICAgICAgICA9IGZ1dHVyZV9tYXBfY2hyKGJhc2VfdHJhbnNjcmlwdGlvbiwgfiBzdHJfZXh0cmFjdF9hbGwoLiwgZ2V0X3Zvd2Vsc19yZWdleCgpKSAlPiUgdW5saXN0KHVzZS5uYW1lcyA9IEZBTFNFKSAlPiUgcGFzdGUwKGNvbGxhcHNlID0gIiIpKQogICAgKSAlPiUKICAgIGdyb3VwX2J5KHNvdXJjZV9maWxlLCBhbm5vdGF0b3IsIHJlcC54bWluLCByZXAueG1heCwgYmFzZV90cmFuc2NyaXB0aW9uKSAlPiUKICAgIG5lc3QoKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICBiYXNlX3Zvd2VsID0gbWFwKGRhdGEsIH4gLiRiYXNlX3Zvd2VsKSwKICAgICAgICBjb25zdF9jdHggPSBmdXR1cmVfbWFwMihiYXNlX3RyYW5zY3JpcHRpb24sIGJhc2Vfdm93ZWwsIH4gZ2V0X2NvbnNfY29udGV4dCgueCwgLnkpKQogICAgKSAlPiUKICAgIHNlbGVjdCgtYmFzZV92b3dlbCkgJT4lCiAgICB1bm5lc3QoKQogICAgCnZvd2Vsc19jb250ZXh0c19kZiAlPiUKICAgIHNlbGVjdChiYXNlX3Zvd2VsLCBiYXNlX3RyYW5zY3JpcHRpb24sIGNvbnN0X2N0eCwgdm93ZWwueG1pbiwgdm93ZWwueG1heCwgcmVwX251bSwgYW5ub3RhdG9yLCBzb3VyY2VfZmlsZSkgJT4lCiAgICBwcmV2aWV3X2RmKGNhcHRpb24gPSAiQ29uc29uYW50YWwgY29udGV4dCBmb3Igdm93ZWwgZGVydmllZCBmcm9tIHJlcGV0aXRpb24tbGV2ZWwgdHJhbnNjcmlwdGlvbiBkYXRhIikKYGBgCgojIyBEaWFnbm9zdGljcyB7LnRhYnNldH0KCiMjIyBNaXNtYXRjaGVkIGJvdW5kYXJ5IG1hcmtlcnMKCkluaXRpYWwtIGFuZCBmaW5hbC12b3dlbHMgYXJlIHJlcXVpcmVkIHRvIGJlIG1hcmtlZCBieSBgI2Agb24sIHJlc3BlY3RpdmVseSwgdGhlIGxlZnQtIGFuZCByaWdodC1lZGdlIG9mIGludGVydmFscyBvbiB0aGUgYHZvd2Vsc2AgdGllci4KVGhlIGZvbGxvd2luZyB0YWJsZSBkaXNwbGF5cyB2b3dlbHMgb24gdGhlIGB2b3dlbHNgIHRpZXIgZm9yIHdoaWNoIHRoZSBkZXJpdmVkIGNvbnNvbmFudGFsIGNvbnRleHQgd2FzIGVpdGhlciBpbml0aWFsIG9yIGZpbmFsLCBidXQgJyMnIGhhZCBub3QgYmVlbiBkZXRlY3RlZCBhcyBwcmVzZW50IGluIHRoZSBpbnRlcnZhbChzKSBvbiB0aGUgYHZvd2Vsc2AgdGllci4KCmBgYHtyIE1ha2UgZGlhZ25vc3RpY3MkbWlzbWF0Y2hlZF9lZGdlc30KZGlhZ25vc3RpY3MkbWlzbWF0Y2hlZF9lZGdlcyA8LQogICAgdm93ZWxzX2NvbnRleHRzX2RmICU+JQogICAgZmlsdGVyKHN0cl9kZXRlY3QoY29uc3RfY3R4LCAiIyIpLCAhc3RyX2RldGVjdCh2b3dlbC50ZXh0LCAiIyIpKQoKZGlhZ25vc3RpY3MkbWlzbWF0Y2hlZF9lZGdlcyAlPiUKICAgIHNlbGVjdChzb3VyY2VfZmlsZSwgYW5ub3RhdG9yLCByZXBfbnVtLCB2b3dlbC54bWluLCB2b3dlbC54bWF4LCBjb25zdF9jdHgsIHZvd2VsLnRleHQsIGJhc2VfdHJhbnNjcmlwdGlvbikgJT4lIAogICAgc2hvd19kaWFnX2RmKCkKYGBgCgoKIyMjIE1pc21hdGNoZWQgdm93ZWxzCgpUaGUgZm9sbG93aW5nIGRhdGEgZnJhbWUgZGlzcGxheXMgcmVwZXRpdGlvbnMgd2hlcmUgd2hlcmUgYGlwYWAgdGllcidzIHZvd2VscyBhbmQgYHZvd2Vsc2AgdGllcidzIHZvd2VscyBhcmUgbWlzbWF0Y2hlZCB3aXRoaW4gdGhlIGdpdmVuIGFubm90YXRvcidzIGBzb3VyY2VfZmlsZWAuCgpgYGB7ciBNYWtlIGRpYWdub3N0aWNzJGlwYV92b3dlbHNfbWlzbWF0Y2hlZH0KZGlhZ25vc3RpY3MkaXBhX3Zvd2Vsc19taXNtYXRjaGVkIDwtCiAgICB2b3dlbHNfY29udGV4dHNfZGYgJT4lIAogICAgcmVuYW1lKGlwYV90aWVyX3Zvd2VscyA9IHJlcF92b3dlbHMpICU+JSAKICAgIGdyb3VwX2J5KHNvdXJjZV9maWxlLCBhbm5vdGF0b3IsIHJlcF9udW0sIGJhc2VfdHJhbnNjcmlwdGlvbiwgaXBhX3RpZXJfdm93ZWxzKSAlPiUKICAgIHN1bW1hcmlzZSh2b3dlbHNfdGllcl92b3dlbHMgPSBwYXN0ZTAoYmFzZV92b3dlbCwgY29sbGFwc2UgPSAiIikpICU+JQogICAgdW5ncm91cCAlPiUgCiAgICBmaWx0ZXIodm93ZWxzX3RpZXJfdm93ZWxzICE9IGlwYV90aWVyX3Zvd2VscykKCmRpYWdub3N0aWNzJGlwYV92b3dlbHNfbWlzbWF0Y2hlZCAlPiUKICAgIHNob3dfZGlhZ19kZihjYXB0aW9uID0gIldvcmQgcmVwZXRpdGlvbnMgd2hlcmUgJ2lwYScgYW5kICd2b3dlbHMnIHRpZXJzJyB2b3dlbHMgYXJlIG1pc21hdGNoZWQiKQpgYGAKCgojIyMgVW5kZXJpdmFibGUgY29uc29uYW50YWwgY29udGV4dAoKVGhlIGRhdGEgZnJhbWUgYmVsb3cgZGlzcGxheXMgdm93ZWxzIGZvciB3aGljaCBjb25zb25hbnRhbCBjb250ZXh0IGNvdWxkIG5vdCBiZSBkZXJpdmVkLiAKVHJvdWJsZXNob290aW5nIGZvciB0aGVzZSBvYnNlcnZhdGlvbnMgd2lsbCBwcm9iYWJseSByZXF1aXJlIGEgYml0IG9mIGRldGVjdGl2ZSB3b3JrLgoKYGBge3IgTWFrZSBkaWFnbm9zdGljcyRjb25zdF9jdHhfbmF9CmRpYWdub3N0aWNzJGNvbnN0X2N0eF9uYSA8LQogICAgdm93ZWxzX2NvbnRleHRzX2RmICU+JQogICAgZmlsdGVyKGlzLm5hKGNvbnN0X2N0eCkpCiAgICAKZGlhZ25vc3RpY3MkY29uc3RfY3R4X25hICU+JQogICAgc2VsZWN0KHNvdXJjZV9maWxlLCBhbm5vdGF0b3IsIHJlcF9udW0sIHZvd2VsLnhtaW4sIHZvd2VsLnhtYXgsIGJhc2Vfdm93ZWwsIGJhc2VfdHJhbnNjcmlwdGlvbiwgY29uc3RfY3R4KSAlPiUgCiAgICBzaG93X2RpYWdfZGYoKQpgYGAKCiMgTWVkaWFsIHZvd2VscywgcHJlLXByb2Nlc3NlZAoKRXhjbHVkaW5nIGFueSB2b3dlbHMgaW4gdGhlIGRpYWdub3N0aWMgdGFibGVzIGFib3ZlLCB3ZSBrbm93IHRoYXQgd2l0aGluIHRoZSByZW1haW5pbmcgZGF0YSB0aGF0IGEpIHZvd2VscyBhbmQgaXBhIHRpZXJzJyB2b3dlbCBsYWJlbHMgYXJlIG1hdGNoZWQgYW5kLCB0aGVyZWZvcmUsIGIpIHdlIGhhdmUgYSBkZXJ2aWVkIGNvc29uYW50YWwgY29udGV4dC4KClRodXMsIHdlIGNhbiBleGNsdWRlIGFsbCBpbml0aWFsIGFuZCBmaW5hbCB2b3dlbHMgKGkuZS4gd2hlcmUgYCNgIGRldGVjdGVkIGluIGBjb25zdF9jdHhgKSwgYW5kIHRoZW4gZnVydGhlciBleGNsdWRlIHJlcGV0aXRpb25zIGZvciB3aGljaCBhbm5vdGF0b3JzIGRpc2FncmVlIG9uIHRoZSBudW1iZXIgb2YgbWVkaWFsIHZvd2Vscy4KCkZpbmFsbHksIHdlIGFsc28gZXhjbHVkZSB2b3dlbCB0cmFuc2NyaXB0aW9ucyBub3QgaGF2aW5nIGF0IGxlYXN0IDIgZGlmZmVyZW50IGFubm90YXRvcnMuCgpgYGB7ciBNYWtlIHZvd2Vsc19tZWRfcHJlcHJvY2Vzc2VkLCBtZXNzYWdlPUZBTFNFfQp2b3dlbHNfbWVkX3JhdyA8LQogICAgdm93ZWxzX2NvbnRleHRzX2RmICU+JQogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KGNvbnN0X2N0eCwgIiMiKSwgbmNoYXIoYmFzZV92b3dlbCkgPT0gMSkgJT4lICAgIyBLZWVwIG9ubHkgbWVkaWFsIG1vbm9waHRob25ncwogICAgcmVkdWNlKC54ID0gZGlhZ25vc3RpY3MsIC5mID0gYW50aV9qb2luLCAuaW5pdCA9IC4pICAgICAgICAgICAgICAgIyBEaXNjYXJkIHZvd2VscyB3aXRoIGFueSBtYXRjaGVzIGluIGFueSBvZiB0aGUgZGlhZ25vc3RpY3MgZGF0YSBmcmFtZXMKICAgIAp2b3dlbHNfbWVkX3ByZXByb2Nlc3NlZCA8LQogICAgdm93ZWxzX21lZF9yYXcgJT4lIAogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIHJlcF9udW0sIGFubm90YXRvcikgJT4lCiAgICBuZXN0KCkgJT4lCiAgICBtdXRhdGUobl92b3dlbHMgPSBtYXBfaW50KGRhdGEsIG5yb3cpKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICMgZmluZCBudW1iZXIgb2Ygdm93ZWxzIHBlciB3b3JkIHJlcCBwZXIgYW5ub3RhdG9yCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSwgcmVwX251bSkgJT4lCiAgICBmaWx0ZXIobl9kaXN0aW5jdChuX3Zvd2VscykgPT0gMSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICMga2VlcCB3b3JkIHJlcCBpZmYgYWxsIGFubm90YXRvciBhZ3JlZSBvbiBudW0gb2Ygdm93ZWxzCiAgICBzZWxlY3QoLW5fdm93ZWxzKSAlPiUKICAgIHVubmVzdCgpICU+JQogICAgCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yKSAlPiUgICAgICAgICAgICAgICAgICAgICMgZ2l2ZW4gYWxsIGFubm90YXRvcnMgYWdyZWUsIG9ubHkgbm93IGdpdmUgc2VxdWVudGlhbCBpZHMKICAgIG11dGF0ZSh2b3dlbF9udW0gPSAxOm4oKSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyAgIHRvIHZvd2VsIHRva2VucyB3aXRoaW4gd29yZCByZXBldGl0aW9ucyAoZS5nLiByZXAgMiB2b3dlbCAxKQoKICAgIGdyb3VwX2J5KHNvdXJjZV9maWxlLCByZXBfbnVtLCB2b3dlbF9udW0pICU+JQogICAgZmlsdGVyKGRwbHlyOjpuX2Rpc3RpbmN0KGFubm90YXRvcikgPj0gMikgJT4lICAgICAgICAgICAgICAgICAgICAjIGtlZXAgb25seSB2b3dlbCB0b2tlbnMgd2l0aCBtb3JlIHRoYW4gMSBhbm5vdGF0b3IKICAgIHVuZ3JvdXAoKSAlPiUKCiAgICBzZWxlY3QoCiAgICAgICAgc291cmNlX2ZpbGUsIHJlcF9udW0sIHZvd2VsX251bSwgYmFzZV92b3dlbCwgY29uc3RfY3R4LCBiYXNlX3RyYW5zY3JpcHRpb24sIGFubm90YXRvciwKICAgICAgICB2b3dlbC50ZXh0LCB2b3dlbC54bWluLCB2b3dlbC54bWF4LCByZXAudGV4dCwgcmVwLnhtaW4sIHJlcC54bWF4CiAgICApCiAgICAKdm93ZWxzX21lZF9wcmVwcm9jZXNzZWQgJT4lCiAgICBzZWxlY3Qoc291cmNlX2ZpbGUsIHJlcF9udW0sIHZvd2VsX251bSwgYmFzZV92b3dlbCwgY29uc3RfY3R4LCBhbm5vdGF0b3IsIHZvd2VsLnhtaW4sIHZvd2VsLnhtYXgpICU+JSAKICAgIHByZXZpZXdfZGYoY2FwdGlvbiA9ICJQcmVwcm9jZXNzZWQgbWVkaWFsIHZvd2VsIGFubm90YXRpb25zIHdpdGggYXQgbGVhc3QgMiB0cmFuc2NyaWJlcnMgcGVyIHZvd2VsIikKYGBgCgojIEpvaW4gY29ycmVzcG9uZGluZyBtaWQtcG9pbnQgZm9ybWFudCB2YWx1ZXMgZm9yIHByZS1wcm9jZXNzZWQgbWVkaWFsIHZvd2VscwoKIyMgUmVhZCBpbiBmb3JtYW50IGRhdGEgYW5kIHJlbW92ZSBvdXRsaWVycyB7LnRhYnNldH0KCmBgYHtyIEZvcm1hbnRzIGhlbHBlcnMsIGluY2x1ZGU9RkFMU0V9CnJlbW92ZV9vdXRsaWVyX2Zvcm1hbnRzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgICBuX2Zvcm1hbnRzICA8LSAyCiAgICBmb3Jlc3RfY29scyA8LSBwYXN0ZTAoImZvcmVzdC5mIiwgMTpuX2Zvcm1hbnRzKQogICAgcHJhYXRfY29scyAgPC0gcGFzdGUwKCJwcmFhdC5mIiwgMTpuX2Zvcm1hbnRzKQogICAgCiAgICBkZiAlPiUKICAgICAgICAjIFVuZG8gYW55IGN1cnJlbnQgZ3JvdXBpbmcgZm9yIHBlcmZvcm1pbmcgcm93LXdpc2UgY2FsY3VsYXRpb25zCiAgICAgICAgdW5ncm91cCAlPiUKICAgICAgICAKICAgICAgICBzZXBhcmF0ZShmb3Jlc3QsIGZvcmVzdF9jb2xzKSAlPiUgCiAgICAgICAgc2VwYXJhdGUocHJhYXQsIHByYWF0X2NvbHMsIHNlcCA9ICItIikgJT4lCiAgICAgICAgbXV0YXRlX2F0KHZhcnMobWF0Y2hlcygiZm9yZXN0fHByYWF0IikpLCBmdW5zKHN1cHByZXNzV2FybmluZ3MoYXMubnVtZXJpYyguKSkpKSAlPiUKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgICMgWi1ub3JtYWxpc2UgYWxsIEYxIGFuZCBGMiBtZWFzdXJlcywgYW5kIGRpZmZzCiAgICAgICAgICAgIGZvcmVzdC5mMV96ID0gc2NhbGUoZm9yZXN0LmYxKSwKICAgICAgICAgICAgZm9yZXN0LmYyX3ogPSBzY2FsZShmb3Jlc3QuZjIpLAogICAgICAgICAgICBwcmFhdC5mMV96ICA9IHNjYWxlKHByYWF0LmYxKSwKICAgICAgICAgICAgcHJhYXQuZjJfeiAgPSBzY2FsZShwcmFhdC5mMiksCiAgICAgICAgICAgICMgRGlmZmVyZW5jZSBpbiBIeiBiZXR3ZWVuIEZvcmVzdCBhbmQgUHJhYXQgdHJhY2tlcnMKICAgICAgICAgICAgZjFfZGlmZl96ICAgICA9IHNjYWxlKGZvcmVzdC5mMSAtIHByYWF0LmYxKSwKICAgICAgICAgICAgZjJfZGlmZl96ICAgICA9IHNjYWxlKGZvcmVzdC5mMiAtIHByYWF0LmYyKQogICAgICAgICkgJT4lIAogICAgICAgIGZpbHRlcigKICAgICAgICAgICAgIyBLZWVwIG9ubHkgbWVhc3VyZXMgYm90aCB3aXRoaW4gMyBzdGFuZGFyZCBkZXZpYXRpb25zIG9mCiAgICAgICAgICAgICMgd2l0aGluLXRyYWNrZXIgbWVhbiBhbmQgYmV0d2Vlbi10cmFja2VyIGRpZmZlcmVuY2UgbWVhbgogICAgICAgICAgICBiZXR3ZWVuKGZvcmVzdC5mMV96LCAtMywgMyksIGJldHdlZW4oZm9yZXN0LmYyX3osIC0zLCAzKSwKICAgICAgICAgICAgYmV0d2VlbihwcmFhdC5mMV96LCAtMywgMyksIGJldHdlZW4ocHJhYXQuZjJfeiwgLTMsIDMpLAogICAgICAgICAgICBiZXR3ZWVuKGYxX2RpZmZfeiwgLTMsIDMpLCBiZXR3ZWVuKGYyX2RpZmZfeiwgLTMsIDMpCiAgICAgICAgKSAKfQpgYGAKCgojIyMgUmF3IGRhdGEKClJlYWQgaW4gaW4gcmF3IGRhdGEgZm9yIGVhY2ggZm9ybWF0IHRyYWNrZXIgKGZvcmVzdCwgcHJhYXQpIGZyb20gYWxsIGBmb3JtYW50cy4qLmNzdmAgZmlsZXMgdW5kZXIgdGhlIGByIHBhcmFtcyRrZGljdF9wYXRoYCBkaXJlY3RvcnkuCgpgYGB7ciBNYWtlIGtkaWN0X2Zvcm1hbnRzfQprZGljdF9mb3JtYW50cyA8LSByZWFkX2tkaWN0X2Zvcm1hbnRzKGtkaWN0X3BhdGggPSBwYXJhbXMka2RpY3RfcGF0aCkKICAgIAprZGljdF9mb3JtYW50cyAlPiUKICAgIHNhbXBsZV9uKDEwKSAlPiUKICAgIHByZXZpZXdfZGYoKQpgYGAKCiMjIyBTZWxlY3QgbWlkLXBvaW50IGZvcm1hbnRzLCB0aGVuIHJlbW92ZSBvdXRsaWVycwoKV2UgZGVmaW5lIG91dGxpZXJzIGhlcmUgYXMgYW55IGRhdHVtIG91dHNpZGUgMyBzdGFuZGFyZCBkZXZpYXRpb25zIG9mIHRoZSByZXNwZWN0aXZlIG1lYW4uCldlIHRodXMgZGVyaXZlIGFuZCBmaWx0ZXIgYmFzZWQgb24gei1zY29yZXMgZm9yOgoKLSBhKSB3aXRoaW4tdHJhY2tlciB2YXJpYW5jZSwgYW5kCi0gYikgZGlmZmVyZW5jZSBpbiBtZWFzdXJlbWVudCBiZXR3ZWVuIHRoZSBGb3Jlc3QgYW5kIFByYWF0IGZvcm1hbnQgdHJhY2tlcnMKCgpgYGB7ciBNYWtlIGZvcm1hbnRzX3ByZXByb2Nlc3NlZH0KZm9ybWFudHNfcHJlcHJvY2Vzc2VkIDwtCiAgICB2b3dlbHNfbWVkX3ByZXByb2Nlc3NlZCAlPiUKICAgIHNlbGVjdChzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yLCB2b3dlbF9udW0sIHZvd2VsLnhtaW4sIHZvd2VsLnhtYXgpICU+JQogICAgbGVmdF9qb2luKGtkaWN0X2Zvcm1hbnRzLCBieSA9ICJzb3VyY2VfZmlsZSIpICU+JQogICAgZmlsdGVyKHRpbWUgPj0gdm93ZWwueG1pbiwgdGltZSA8PSB2b3dlbC54bWF4KSAlPiUKCiAgICBncm91cF9ieShzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yLCB2b3dlbF9udW0sIHRyYWNrZXIpICU+JQogICAgZmlsdGVyKHJvd19udW1iZXIoKSA9PSBjZWlsaW5nKG4oKS8yKSkgJT4lICMga2VlcCBvbmx5IG1pZC1wb2ludCBtZWFzdXJlCiAgICB1bmdyb3VwKCkgJT4lCgogICAgdW5pdGUoZm9ybWFudHMsIGYxLCBmMiwgc2VwID0gIi0iKSAlPiUKICAgIHNlbGVjdChzb3VyY2VfZmlsZSwgcmVwX251bSwgYW5ub3RhdG9yLCB2b3dlbF9udW0sIHRyYWNrZXIsIGZvcm1hbnRzKSAlPiUKICAgIHNwcmVhZCh0cmFja2VyLCBmb3JtYW50cykgJT4lCiAgICByZW1vdmVfb3V0bGllcl9mb3JtYW50cygpCiAgICAKZm9ybWFudHNfcHJlcHJvY2Vzc2VkICU+JQogICAgcHJldmlld19kZihjYXB0aW9uID0gIkZvcm1hbnQgdmFsdWVzIGZpbHRlcmVkIG9uIGJlaW5nIHdpdGhpbiArLy0gMyBTRCBvZiByZXNwZWN0aXZlIG1lYW5zIGZvciB7Zm9yZXN0LHByYWF0fS5mezEsMn1feiBhbmQgZnsxLDJ9X2RpZmZfeiIpCmBgYAoKIyBBbmFseXNpcyBkYXRhIGZyYW1lIGZvciBtZWRpYWwgdm93ZWxzCgpgYGB7ciBNYWtlIHZvd2Vsc19tZWRfYW5hbHlzaXMsIG1lc3NhZ2U9RkFMU0V9CnZvd2Vsc19tZWRfYW5hbHlzaXMgPC0gCiAgICBsZWZ0X2pvaW4oCiAgICAgICAgeCA9IHZvd2Vsc19tZWRfcHJlcHJvY2Vzc2VkLAogICAgICAgIHkgPSBzZWxlY3QoZm9ybWFudHNfcHJlcHJvY2Vzc2VkLCAtY29udGFpbnMoImRpZmYiKSkKICAgICkgJT4lCiAgICBmaWx0ZXJfYWxsKGFsbF92YXJzKCFpcy5uYSguKSkpICU+JQogICAgZ3JvdXBfYnkoc291cmNlX2ZpbGUsIHJlcF9udW0sIHZvd2VsX251bSkKICAgIAptZWRfYW5hbHlzaXNfY292ZXJhZ2UgPC0KICAgIGAvYCgKICAgICAgICBucm93KHZvd2Vsc19tZWRfYW5hbHlzaXMpLAogICAgICAgIG5yb3codm93ZWxzX21lZF9yYXcpCiAgICApICU+JSAgICAgCiAgICBgKmAoMTAwKSAlPiUgCiAgICByb3VuZChkaWdpdHMgPSAwKSAKICAgIAp2b3dlbHNfbWVkX2FuYWx5c2lzICU+JQogICAgdW5ncm91cCgpICU+JSAKICAgIHNhbXBsZV9uKDEwKSAlPiUgCiAgICBzZWxlY3Qoc291cmNlX2ZpbGU6Y29uc3RfY3R4LCBhbm5vdGF0b3IsIGZvcmVzdC5mMTpwcmFhdC5mMl96KQogICAgCmBgYAoKIyMgQ292ZXJhZ2Ugb2YgcHJlLXByb2Nlc3NlZCBkYXRhc2V0OiBgciBtZWRfYW5hbHlzaXNfY292ZXJhZ2VgJQoKVGhlIGFuYWx5c2lzIGRhdGEgaW4gYHZvd2Vsc19tZWRfYW5hbHlzaXNgIHJldGFpbnMgYHIgbnJvdyh2b3dlbHNfbWVkX2FuYWx5c2lzKWAgcm93cyBvdXQgb2YgYHIgbnJvdyh2b3dlbHNfbWVkX3JhdylgIGluIGB2b3dlbHNfbWVkX3Jhd2AuCgojIFdyaXRlIGRhdGEgKG5vdCBydW4gYnkgZGVmYXVsdCkgCgojIyBXcml0ZSBgdm93ZWxzX21lZF9hbmFseXNpcy5jc3ZgCgpUbyBoYXZlIGRhdGEgKG92ZXIpd3JpdHRlbiwgY2hhbmdlIHRoZSBgd3JpdGVfY3N2c2AgcGFyYW1ldGVyLgoKYGBge3IgV3JpdGUgdm93ZWxzX21lZF9hbmFseXNpcywgZXZhbCA9IHBhcmFtcyR3cml0ZV9jc3ZzfQpyZWFkcjo6d3JpdGVfY3N2KAogICAgeCAgICA9IHZvd2Vsc19tZWRfYW5hbHlzaXMsCiAgICBwYXRoID0gZmlsZS5wYXRoKHBhcmFtcyRvdXRwdXRfcGF0aCwgInZvd2Vsc19tZWRfYW5hbHlzaXMuY3N2IikKKQpgYGAKCiMjIFdyaXRlIGBkaWFnbm9zdGljcy8qLmNzdmAKCmBgYHtyIFdyaXRlIGRpYWdub3N0aWNzLCBldmFsID0gcGFyYW1zJHdyaXRlX2NzdnN9CmRpYWdzX2RpciA8LSBmaWxlLnBhdGgocGFyYW1zJG91dHB1dF9wYXRoLCAiZGlhZ25vc3RpY3MiKQogICAgCmlmKCFkaXIuZXhpc3RzKGRpYWdzX2RpcikpIHsgZGlyLmNyZWF0ZShkaWFnc19kaXIpIH0KCmRpYWdub3N0aWNzICU+JQogICAgaXdhbGsofiByZWFkcjo6d3JpdGVfY3N2KHggPSAueCwgcGF0aCA9IGZpbGUucGF0aChkaWFnc19kaXIsIHBhc3RlMCgueSwgIi5jc3YiKSksIG5hID0gIiIpKQpgYGAKCgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgpoMSwgaDIsIGgzLCBoNCwgaDUgeyBmb250LXNpemU6MS4xZW0gfQo8L3N0eWxlPgo=