Ansgar Wolsing’s contribution to 30 Day Chart Challenge, 2022 Edition.

Code on GitHub (very slightly modified).

## remotes::install_github("davidsjoberg/ggstream")
library(ggstream)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(tidytext)
library(ggtext)

## download text from Project Gutenberg
if (! file.exists("macbeth.txt")) {
    macbeth_url <- "https://www.gutenberg.org/cache/epub/1129/pg1129.txt"
    download.file(macbeth_url, "macbeth.txt")
}
macbeth_lines <- read_lines("macbeth.txt")

## Lines where the manuscript starts and ends
start_line <- 282
end_line <- 2899
exclude_lines <- 2358:2365

text_df <- tibble(line = macbeth_lines[start_line:end_line]) |>
    mutate(line = str_squish(line),
           line_id = row_number()) |>
    ## remove empty or irrelevant lines
    filter(line != "" | line_id %in% exclude_lines) |>
    filter(!str_detect(line,
                       "SERVICE THAT CHARGES|WITH PERMISSION|COMMERCIALLY")) |>
    ## extract the act and scene
    mutate(act = str_extract(line, "^ACT\\b.+?\\."),
           scene = str_extract(line, "SCENE\\b.+?\\.")) |>
    fill(act, scene, .direction = "down") |>
    ## extract the character name who speaks
    mutate(speaker = str_extract(line, "^[A-Z\\s]+\\."),
           speaker = str_remove(speaker, "\\.$"),
           speaker = ifelse(str_detect(speaker, "^ACT|SCENE\\b"),
                            NA_character_, speaker),
           speaker = str_to_title(speaker)) |>
    ## manage the switch of speakers from scene to scene
    group_by(act, scene) |>
    fill(speaker, .direction = "down") |>
    ungroup() |>
    ## remove lines without a speaker
    filter(!is.na(speaker)) |>
    ## remove speaker names from the lines
    mutate(line = str_remove(line, paste0(speaker, ". ")))

## count the number of words per speaker in each act and scene
word_count_speakers <- text_df |>
    ## recode the witches and murderers into one category
    mutate(speaker_grp = ifelse(str_detect(speaker, "Witch$"),
                                "Three Witches", speaker),
           speaker_grp = ifelse(str_detect(speaker_grp, "Mutherers?$"),
                                "Mutherers", speaker_grp)) |>
    unnest_tokens(word, line, token = "words", drop = TRUE) |>
    count(act, scene, speaker_grp, speaker, name = "word_count")

## identify character with only a few appearances
few_appearances_speakers <- word_count_speakers |>
    group_by(speaker_grp) |>
    summarize(scenes_count = n_distinct(act, scene),
              word_count_total = sum(word_count)) |>
    filter(scenes_count <= 3, word_count_total < 500) |>
    pull(speaker_grp)

## Custom color palette by character affiliation
speaker_grp_levels <- c(
  "Macbeth", "Lady Macbeth",
  "Duncan", "Malcolm", "Macduff", "Ross",
  "Banquo", "Lennox",
  "Three Witches",
  "Other")
color_palette <- paletteer::paletteer_d(
  "palettetown::pidgey")[c(9, 8,
                           2, 5, 4, 6,
                           12, 6,
                           3,
                           11)]

## Annotations
plot_titles <- list(
    title = "Who speaks when in Shakespeare's MACBETH?",
    subtitle = "Distribution of speech share (number of words) per character in
  each scene. Acts are separated with vertical lines.",
  caption = "Project Gutenberg. Visualization: Ansgar Wolsing"
)

## highlight key events - used for text and lines
story_annotations <- tibble(
    x    = c(13.5, 1.2, 7, 5, 14, 22),
    xend = c(19,   1.2, 7, 5, 14, 22),
    y    = c(-5000, -4200, -4500, 5000, 5000, 3000),
    yend = c(-5000,  -200,   400,  800, 1000,  500),
    vjust = c(   0,  0.25,   0.3,  0.85,  0.9,  0.8), ## nolint: spaces_inside
    label = c(
        "Macduff & Malcolm decide to go to war against Macbeth",
        "Three Witches<br>appear",
        "Macbeth kills King Duncan",
        "Lady Macbeth & Macbeth<br>plan the murder of King Duncan",
        "Murder of Banquo reported to Macbeth,<br>Ghost of Banquo appears",
        "Macduff<br>kills<br>Macbeth"))

word_count_speakers |>
    mutate(speaker_grp =
               ifelse(speaker_grp %in% c("All", few_appearances_speakers),
                      "Other", speaker_grp),
           speaker_grp = factor(speaker_grp, levels = speaker_grp_levels)) |>
    count(act, scene, speaker_grp, wt = word_count, name = "word_count") |>
    ## increment counter across act and scene
    group_by(act, scene) |>
    mutate(act_scene_id = cur_group_id()) |>
    ungroup() |>
    ggplot(aes(act_scene_id, word_count, fill = speaker_grp)) +
    ## vertical lines for the acts
    geom_vline(
        data = function(d) filter(d, scene == "SCENE I."),
        aes(xintercept = act_scene_id),
        color = "grey50", size = 0.2, lty = "dotted") +
    geom_stream(type = "mirror", bw = 0.5,  extra_span = 0.1) +
    ## annotations for key events (text + segment)
    geom_textbox(
        data = story_annotations,
        aes(x - 0.08, y, label = label, vjust = vjust),
        inherit.aes = FALSE,
        color = "grey90", family = "Forum", hjust = 0, fill = NA,
        box.size = 0,
        width = unit(3.5, "cm")) +
    geom_segment(
        data = story_annotations,
        aes(x = x, xend = xend, y = y, yend = yend), inherit.aes = FALSE,
        color = "grey90", size = 0.3) +
    ## text labels for the acts
    geom_text(
        data = function(d)
            group_by(d, act) |>
            summarize(x = min(act_scene_id) + n_distinct(scene) / 2),
        aes(x, y = -Inf, label = act), inherit.aes = FALSE,
        vjust = -1, hjust = 0.5, color = "grey60", family = "Forum") +
    scale_fill_manual(values = color_palette) +
    labs(
        title = plot_titles$title,
        subtitle = plot_titles$subtitle,
        caption = plot_titles$caption,
        fill = NULL) +
    theme_void(base_family = "Forum", base_size = 10) +
    theme(
        plot.background = element_rect(color = NA, fill = "grey8"),
        plot.margin = margin(10, 10, 10, 10),
        legend.position = "bottom",
        legend.direction = "horizontal",
        legend.key.height = unit(3, "mm"),
        legend.spacing.y = unit(4, "cm"),
        legend.text = element_text(size = 9.5),
        text = element_text(color = "white"),
        plot.title = element_text(size = 24, family = "Forum"),
        plot.subtitle = element_markdown(),
        plot.caption = element_markdown(hjust = 1))
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

LS0tCnRpdGxlOiAiV2hvIFNwZWFrcyBXaGVuIGluIFNoYWtlc3BlYXJlJ3MgTUFDQkVUSD8iCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogaGlkZQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVkgJUg6JU0nKWAiCi0tLQoKYGBge3IgZ2xvYmFsX29wdGlvbnMsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbGxhcHNlID0gVFJVRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSIsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIikKYGBgCkFuc2dhciBXb2xzaW5nJ3MKW2NvbnRyaWJ1dGlvbl0oaHR0cHM6Ly90d2l0dGVyLmNvbS9fYW5zZ2FyL3N0YXR1cy8xNTEzMDg0NjU5MDEzNTA1MDI4KSB0bwpbMzAgRGF5IENoYXJ0IENoYWxsZW5nZSwgMjAyMiBFZGl0aW9uXShodHRwczovL3R3aXR0ZXIuY29tLzMwRGF5Q2hhcnRDaGFsbCkuCgpbQ29kZSBvbiBHaXRIdWJdKGh0dHBzOi8vZ2l0aHViLmNvbS9ieWRhdGEvMzBEYXlDaGFydENoYWxsZW5nZS9ibG9iL21haW4vMjAyMi8xMC8xMC1leHBlcmltZW50YWwtbWFjYmV0aC5SKSAodmVyeSBzbGlnaHRseSBtb2RpZmllZCkuCgpgYGB7ciwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA4fQojIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiZGF2aWRzam9iZXJnL2dnc3RyZWFtIikKbGlicmFyeShnZ3N0cmVhbSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkoZ2d0ZXh0KQoKIyMgZG93bmxvYWQgdGV4dCBmcm9tIFByb2plY3QgR3V0ZW5iZXJnCmlmICghIGZpbGUuZXhpc3RzKCJtYWNiZXRoLnR4dCIpKSB7CiAgICBtYWNiZXRoX3VybCA8LSAiaHR0cHM6Ly93d3cuZ3V0ZW5iZXJnLm9yZy9jYWNoZS9lcHViLzExMjkvcGcxMTI5LnR4dCIKICAgIGRvd25sb2FkLmZpbGUobWFjYmV0aF91cmwsICJtYWNiZXRoLnR4dCIpCn0KbWFjYmV0aF9saW5lcyA8LSByZWFkX2xpbmVzKCJtYWNiZXRoLnR4dCIpCgojIyBMaW5lcyB3aGVyZSB0aGUgbWFudXNjcmlwdCBzdGFydHMgYW5kIGVuZHMKc3RhcnRfbGluZSA8LSAyODIKZW5kX2xpbmUgPC0gMjg5OQpleGNsdWRlX2xpbmVzIDwtIDIzNTg6MjM2NQoKdGV4dF9kZiA8LSB0aWJibGUobGluZSA9IG1hY2JldGhfbGluZXNbc3RhcnRfbGluZTplbmRfbGluZV0pIHw+CiAgICBtdXRhdGUobGluZSA9IHN0cl9zcXVpc2gobGluZSksCiAgICAgICAgICAgbGluZV9pZCA9IHJvd19udW1iZXIoKSkgfD4KICAgICMjIHJlbW92ZSBlbXB0eSBvciBpcnJlbGV2YW50IGxpbmVzCiAgICBmaWx0ZXIobGluZSAhPSAiIiB8IGxpbmVfaWQgJWluJSBleGNsdWRlX2xpbmVzKSB8PgogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KGxpbmUsCiAgICAgICAgICAgICAgICAgICAgICAgIlNFUlZJQ0UgVEhBVCBDSEFSR0VTfFdJVEggUEVSTUlTU0lPTnxDT01NRVJDSUFMTFkiKSkgfD4KICAgICMjIGV4dHJhY3QgdGhlIGFjdCBhbmQgc2NlbmUKICAgIG11dGF0ZShhY3QgPSBzdHJfZXh0cmFjdChsaW5lLCAiXkFDVFxcYi4rP1xcLiIpLAogICAgICAgICAgIHNjZW5lID0gc3RyX2V4dHJhY3QobGluZSwgIlNDRU5FXFxiLis/XFwuIikpIHw+CiAgICBmaWxsKGFjdCwgc2NlbmUsIC5kaXJlY3Rpb24gPSAiZG93biIpIHw+CiAgICAjIyBleHRyYWN0IHRoZSBjaGFyYWN0ZXIgbmFtZSB3aG8gc3BlYWtzCiAgICBtdXRhdGUoc3BlYWtlciA9IHN0cl9leHRyYWN0KGxpbmUsICJeW0EtWlxcc10rXFwuIiksCiAgICAgICAgICAgc3BlYWtlciA9IHN0cl9yZW1vdmUoc3BlYWtlciwgIlxcLiQiKSwKICAgICAgICAgICBzcGVha2VyID0gaWZlbHNlKHN0cl9kZXRlY3Qoc3BlYWtlciwgIl5BQ1R8U0NFTkVcXGIiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BX2NoYXJhY3Rlcl8sIHNwZWFrZXIpLAogICAgICAgICAgIHNwZWFrZXIgPSBzdHJfdG9fdGl0bGUoc3BlYWtlcikpIHw+CiAgICAjIyBtYW5hZ2UgdGhlIHN3aXRjaCBvZiBzcGVha2VycyBmcm9tIHNjZW5lIHRvIHNjZW5lCiAgICBncm91cF9ieShhY3QsIHNjZW5lKSB8PgogICAgZmlsbChzcGVha2VyLCAuZGlyZWN0aW9uID0gImRvd24iKSB8PgogICAgdW5ncm91cCgpIHw+CiAgICAjIyByZW1vdmUgbGluZXMgd2l0aG91dCBhIHNwZWFrZXIKICAgIGZpbHRlcighaXMubmEoc3BlYWtlcikpIHw+CiAgICAjIyByZW1vdmUgc3BlYWtlciBuYW1lcyBmcm9tIHRoZSBsaW5lcwogICAgbXV0YXRlKGxpbmUgPSBzdHJfcmVtb3ZlKGxpbmUsIHBhc3RlMChzcGVha2VyLCAiLiAiKSkpCgojIyBjb3VudCB0aGUgbnVtYmVyIG9mIHdvcmRzIHBlciBzcGVha2VyIGluIGVhY2ggYWN0IGFuZCBzY2VuZQp3b3JkX2NvdW50X3NwZWFrZXJzIDwtIHRleHRfZGYgfD4KICAgICMjIHJlY29kZSB0aGUgd2l0Y2hlcyBhbmQgbXVyZGVyZXJzIGludG8gb25lIGNhdGVnb3J5CiAgICBtdXRhdGUoc3BlYWtlcl9ncnAgPSBpZmVsc2Uoc3RyX2RldGVjdChzcGVha2VyLCAiV2l0Y2gkIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlRocmVlIFdpdGNoZXMiLCBzcGVha2VyKSwKICAgICAgICAgICBzcGVha2VyX2dycCA9IGlmZWxzZShzdHJfZGV0ZWN0KHNwZWFrZXJfZ3JwLCAiTXV0aGVyZXJzPyQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTXV0aGVyZXJzIiwgc3BlYWtlcl9ncnApKSB8PgogICAgdW5uZXN0X3Rva2Vucyh3b3JkLCBsaW5lLCB0b2tlbiA9ICJ3b3JkcyIsIGRyb3AgPSBUUlVFKSB8PgogICAgY291bnQoYWN0LCBzY2VuZSwgc3BlYWtlcl9ncnAsIHNwZWFrZXIsIG5hbWUgPSAid29yZF9jb3VudCIpCgojIyBpZGVudGlmeSBjaGFyYWN0ZXIgd2l0aCBvbmx5IGEgZmV3IGFwcGVhcmFuY2VzCmZld19hcHBlYXJhbmNlc19zcGVha2VycyA8LSB3b3JkX2NvdW50X3NwZWFrZXJzIHw+CiAgICBncm91cF9ieShzcGVha2VyX2dycCkgfD4KICAgIHN1bW1hcml6ZShzY2VuZXNfY291bnQgPSBuX2Rpc3RpbmN0KGFjdCwgc2NlbmUpLAogICAgICAgICAgICAgIHdvcmRfY291bnRfdG90YWwgPSBzdW0od29yZF9jb3VudCkpIHw+CiAgICBmaWx0ZXIoc2NlbmVzX2NvdW50IDw9IDMsIHdvcmRfY291bnRfdG90YWwgPCA1MDApIHw+CiAgICBwdWxsKHNwZWFrZXJfZ3JwKQoKIyMgQ3VzdG9tIGNvbG9yIHBhbGV0dGUgYnkgY2hhcmFjdGVyIGFmZmlsaWF0aW9uCnNwZWFrZXJfZ3JwX2xldmVscyA8LSBjKAogICJNYWNiZXRoIiwgIkxhZHkgTWFjYmV0aCIsCiAgIkR1bmNhbiIsICJNYWxjb2xtIiwgIk1hY2R1ZmYiLCAiUm9zcyIsCiAgIkJhbnF1byIsICJMZW5ub3giLAogICJUaHJlZSBXaXRjaGVzIiwKICAiT3RoZXIiKQpjb2xvcl9wYWxldHRlIDwtIHBhbGV0dGVlcjo6cGFsZXR0ZWVyX2QoCiAgInBhbGV0dGV0b3duOjpwaWRnZXkiKVtjKDksIDgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIDIsIDUsIDQsIDYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIDEyLCA2LAogICAgICAgICAgICAgICAgICAgICAgICAgICAzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAxMSldCgojIyBBbm5vdGF0aW9ucwpwbG90X3RpdGxlcyA8LSBsaXN0KAogICAgdGl0bGUgPSAiV2hvIHNwZWFrcyB3aGVuIGluIFNoYWtlc3BlYXJlJ3MgTUFDQkVUSD8iLAogICAgc3VidGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIHNwZWVjaCBzaGFyZSAobnVtYmVyIG9mIHdvcmRzKSBwZXIgY2hhcmFjdGVyIGluCiAgZWFjaCBzY2VuZS4gQWN0cyBhcmUgc2VwYXJhdGVkIHdpdGggdmVydGljYWwgbGluZXMuIiwKICBjYXB0aW9uID0gIlByb2plY3QgR3V0ZW5iZXJnLiBWaXN1YWxpemF0aW9uOiBBbnNnYXIgV29sc2luZyIKKQoKIyMgaGlnaGxpZ2h0IGtleSBldmVudHMgLSB1c2VkIGZvciB0ZXh0IGFuZCBsaW5lcwpzdG9yeV9hbm5vdGF0aW9ucyA8LSB0aWJibGUoCiAgICB4ICAgID0gYygxMy41LCAxLjIsIDcsIDUsIDE0LCAyMiksCiAgICB4ZW5kID0gYygxOSwgICAxLjIsIDcsIDUsIDE0LCAyMiksCiAgICB5ICAgID0gYygtNTAwMCwgLTQyMDAsIC00NTAwLCA1MDAwLCA1MDAwLCAzMDAwKSwKICAgIHllbmQgPSBjKC01MDAwLCAgLTIwMCwgICA0MDAsICA4MDAsIDEwMDAsICA1MDApLAogICAgdmp1c3QgPSBjKCAgIDAsICAwLjI1LCAgIDAuMywgIDAuODUsICAwLjksICAwLjgpLCAjIyBub2xpbnQ6IHNwYWNlc19pbnNpZGUKICAgIGxhYmVsID0gYygKICAgICAgICAiTWFjZHVmZiAmIE1hbGNvbG0gZGVjaWRlIHRvIGdvIHRvIHdhciBhZ2FpbnN0IE1hY2JldGgiLAogICAgICAgICJUaHJlZSBXaXRjaGVzPGJyPmFwcGVhciIsCiAgICAgICAgIk1hY2JldGgga2lsbHMgS2luZyBEdW5jYW4iLAogICAgICAgICJMYWR5IE1hY2JldGggJiBNYWNiZXRoPGJyPnBsYW4gdGhlIG11cmRlciBvZiBLaW5nIER1bmNhbiIsCiAgICAgICAgIk11cmRlciBvZiBCYW5xdW8gcmVwb3J0ZWQgdG8gTWFjYmV0aCw8YnI+R2hvc3Qgb2YgQmFucXVvIGFwcGVhcnMiLAogICAgICAgICJNYWNkdWZmPGJyPmtpbGxzPGJyPk1hY2JldGgiKSkKCndvcmRfY291bnRfc3BlYWtlcnMgfD4KICAgIG11dGF0ZShzcGVha2VyX2dycCA9CiAgICAgICAgICAgICAgIGlmZWxzZShzcGVha2VyX2dycCAlaW4lIGMoIkFsbCIsIGZld19hcHBlYXJhbmNlc19zcGVha2VycyksCiAgICAgICAgICAgICAgICAgICAgICAiT3RoZXIiLCBzcGVha2VyX2dycCksCiAgICAgICAgICAgc3BlYWtlcl9ncnAgPSBmYWN0b3Ioc3BlYWtlcl9ncnAsIGxldmVscyA9IHNwZWFrZXJfZ3JwX2xldmVscykpIHw+CiAgICBjb3VudChhY3QsIHNjZW5lLCBzcGVha2VyX2dycCwgd3QgPSB3b3JkX2NvdW50LCBuYW1lID0gIndvcmRfY291bnQiKSB8PgogICAgIyMgaW5jcmVtZW50IGNvdW50ZXIgYWNyb3NzIGFjdCBhbmQgc2NlbmUKICAgIGdyb3VwX2J5KGFjdCwgc2NlbmUpIHw+CiAgICBtdXRhdGUoYWN0X3NjZW5lX2lkID0gY3VyX2dyb3VwX2lkKCkpIHw+CiAgICB1bmdyb3VwKCkgfD4KICAgIGdncGxvdChhZXMoYWN0X3NjZW5lX2lkLCB3b3JkX2NvdW50LCBmaWxsID0gc3BlYWtlcl9ncnApKSArCiAgICAjIyB2ZXJ0aWNhbCBsaW5lcyBmb3IgdGhlIGFjdHMKICAgIGdlb21fdmxpbmUoCiAgICAgICAgZGF0YSA9IGZ1bmN0aW9uKGQpIGZpbHRlcihkLCBzY2VuZSA9PSAiU0NFTkUgSS4iKSwKICAgICAgICBhZXMoeGludGVyY2VwdCA9IGFjdF9zY2VuZV9pZCksCiAgICAgICAgY29sb3IgPSAiZ3JleTUwIiwgc2l6ZSA9IDAuMiwgbHR5ID0gImRvdHRlZCIpICsKICAgIGdlb21fc3RyZWFtKHR5cGUgPSAibWlycm9yIiwgYncgPSAwLjUsICBleHRyYV9zcGFuID0gMC4xKSArCiAgICAjIyBhbm5vdGF0aW9ucyBmb3Iga2V5IGV2ZW50cyAodGV4dCArIHNlZ21lbnQpCiAgICBnZW9tX3RleHRib3goCiAgICAgICAgZGF0YSA9IHN0b3J5X2Fubm90YXRpb25zLAogICAgICAgIGFlcyh4IC0gMC4wOCwgeSwgbGFiZWwgPSBsYWJlbCwgdmp1c3QgPSB2anVzdCksCiAgICAgICAgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgICAgICBjb2xvciA9ICJncmV5OTAiLCBmYW1pbHkgPSAiRm9ydW0iLCBoanVzdCA9IDAsIGZpbGwgPSBOQSwKICAgICAgICBib3guc2l6ZSA9IDAsCiAgICAgICAgd2lkdGggPSB1bml0KDMuNSwgImNtIikpICsKICAgIGdlb21fc2VnbWVudCgKICAgICAgICBkYXRhID0gc3RvcnlfYW5ub3RhdGlvbnMsCiAgICAgICAgYWVzKHggPSB4LCB4ZW5kID0geGVuZCwgeSA9IHksIHllbmQgPSB5ZW5kKSwgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgICAgICBjb2xvciA9ICJncmV5OTAiLCBzaXplID0gMC4zKSArCiAgICAjIyB0ZXh0IGxhYmVscyBmb3IgdGhlIGFjdHMKICAgIGdlb21fdGV4dCgKICAgICAgICBkYXRhID0gZnVuY3Rpb24oZCkKICAgICAgICAgICAgZ3JvdXBfYnkoZCwgYWN0KSB8PgogICAgICAgICAgICBzdW1tYXJpemUoeCA9IG1pbihhY3Rfc2NlbmVfaWQpICsgbl9kaXN0aW5jdChzY2VuZSkgLyAyKSwKICAgICAgICBhZXMoeCwgeSA9IC1JbmYsIGxhYmVsID0gYWN0KSwgaW5oZXJpdC5hZXMgPSBGQUxTRSwKICAgICAgICB2anVzdCA9IC0xLCBoanVzdCA9IDAuNSwgY29sb3IgPSAiZ3JleTYwIiwgZmFtaWx5ID0gIkZvcnVtIikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3JfcGFsZXR0ZSkgKwogICAgbGFicygKICAgICAgICB0aXRsZSA9IHBsb3RfdGl0bGVzJHRpdGxlLAogICAgICAgIHN1YnRpdGxlID0gcGxvdF90aXRsZXMkc3VidGl0bGUsCiAgICAgICAgY2FwdGlvbiA9IHBsb3RfdGl0bGVzJGNhcHRpb24sCiAgICAgICAgZmlsbCA9IE5VTEwpICsKICAgIHRoZW1lX3ZvaWQoYmFzZV9mYW1pbHkgPSAiRm9ydW0iLCBiYXNlX3NpemUgPSAxMCkgKwogICAgdGhlbWUoCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yID0gTkEsIGZpbGwgPSAiZ3JleTgiKSwKICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMTAsIDEwLCAxMCksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwKICAgICAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMywgIm1tIiksCiAgICAgICAgbGVnZW5kLnNwYWNpbmcueSA9IHVuaXQoNCwgImNtIiksCiAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDkuNSksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJ3aGl0ZSIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDI0LCBmYW1pbHkgPSAiRm9ydW0iKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF9tYXJrZG93bigpLAogICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfbWFya2Rvd24oaGp1c3QgPSAxKSkKYGBgCg==