Purpose: A living notebook of short, reliable R
snippets I actually use. Keep it short. Keep it runnable. Update as I
learn.
1. Quick Setup
# Install-once (uncomment as needed)
# install.packages(c("tidyverse", "janitor", "lubridate", "readr", "readxl", "openxlsx",
# "skimr", "here", "fs", "glue", "knitr", "rmarkdown", "ggthemes",
# "patchwork", "gt", "gtExtras", "stringr", "forcats"))
# Load every session
suppressPackageStartupMessages({
library(tidyverse)
library(janitor)
library(lubridate)
library(here)
library(glue)
library(skimr)
library(gt)
})
# Reproducibility
set.seed(42)
Project tip: Use an RStudio Project and
here::here() for paths. Never hard‑code
"C:/Users/...".
2. Reading & Writing Data (fast + safe)
# CSV (robust defaults)
df_csv <- readr::read_csv(here("data", "my_data.csv"))
# Excel (first sheet)
df_xlsx <- readxl::read_excel(here("data", "my_data.xlsx"), sheet = 1)
# Write outputs with timestamps
out_path <- here("output", glue("cleaned_{format(Sys.Date(), '%Y%m%d')}.csv"))
# readr::write_csv(df_csv, out_path)
Gotcha: If you see encoding issues, try
locale = locale(encoding = "UTF-8") in
read_csv().
3. Inspecting Data (what is this?)
# High‑level skim
skimr::skim(df_csv)
# Structure & types
str(df_csv)
# Column names (clean + check)
names(df_csv)
janitor::compare_df_cols(df_csv)
Rule of thumb: If a column should be a date, convert
it immediately with lubridate.
4. Cleaning Columns & Rows
# Consistent names
clean <- df_csv %>%
janitor::clean_names() %>% # snake_case column names
mutate(across(where(is.character), trimws)) # trim leading/trailing spaces
# Remove complete duplicate rows
clean <- distinct(clean)
# Handle blanks as NA
clean <- mutate(clean, across(everything(), ~na_if(.x, "")))
Tip: Use distinct(.keep_all = TRUE) to
de‑dupe by subset of columns.
5. dplyr Cheatsheet (minimal set)
result <- clean %>%
filter(!is.na(id)) %>%
mutate(
date = lubridate::ymd(date),
category = forcats::fct_lump_n(as.factor(category), n = 5)
) %>%
group_by(category) %>%
summarize(
n = n(),
mean_val = mean(value, na.rm = TRUE),
.groups = "drop"
) %>%
arrange(desc(n))
Mnemonic:
Select–Filter–Mutate–Summarize–Arrange covers 80% of
wrangling.
6. Joins (I always mix these up)
# left_join: keep all rows from x, bring matches from y
joined <- df_csv %>% left_join(df_xlsx, by = "id")
# anti_join: rows in x with no match in y (great for QA)
missing_keys <- df_csv %>% anti_join(df_xlsx, by = "id")
QA trick: anti_join() first to see what
won’t match before any heavy processing.
7. Dates & Times (lubridate)
# Parse and standardize
clean_dates <- clean %>%
mutate(
date = ymd(date),
year = year(date),
month = month(date, label = TRUE, abbr = TRUE),
wk = isoweek(date)
)
Tip: If parsing fails, inspect with
parse_date_time(x, orders = c("ymd", "mdy", "dmy")).
8. Strings (stringr)
text_clean <- clean %>%
mutate(
email = str_to_lower(email),
domain = str_extract(email, "@.+$")
)
Regex sanity: Test patterns at https://regex101.com/
before committing.
9. Factors (forcats)
fac <- clean %>%
mutate(
status = fct_relevel(as.factor(status), c("new", "active", "inactive")),
top_cat = fct_lump_n(as.factor(category), n = 6)
)
Plotting tip: Relevel factors to control ggplot
ordering.
10. Plotting (ggplot2: minimal patterns)
# Bar (counts)
clean %>%
ggplot(aes(x = category)) +
geom_bar(fill = "#2E86AB") +
theme_minimal(base_size = 12) +
labs(title = "Counts by Category", x = NULL, y = "Count")
# Line (time series)
clean_dates %>%
group_by(date) %>% summarize(n = n(), .groups = "drop") %>%
ggplot(aes(date, n)) +
geom_line(color = "#7D3C98", linewidth = 0.9) +
theme_minimal(base_size = 12) +
labs(title = "Daily Counts", x = NULL, y = NULL)
Small multiples: Use
+ facet_wrap(~group) when categories are many.
11. Tables (gt quick pattern)
result %>%
gt::gt() %>%
gt::fmt_number(columns = where(is.numeric), decimals = 2) %>%
gt::tab_header(title = md("**Summary by Category**"))
Export: gtsave("table.png") or
gt::gtsave() to PNG/PDF/HTML.
12. Modeling (tidymodels tiny starter)
# install.packages("tidymodels") # once
# library(tidymodels)
# set.seed(42)
# split <- initial_split(clean, prop = 0.8)
# train <- training(split); test <- testing(split)
# rec <- recipe(target ~ ., data = train) %>% step_dummy(all_nominal(), -all_outcomes())
# mod <- linear_reg() %>% set_engine("lm")
# wf <- workflow() %>% add_model(mod) %>% add_recipe(rec)
# fit <- fit(wf, data = train)
# metrics <- predict(fit, test) %>% bind_cols(test) %>% metrics(truth = target, estimate = .pred)
Reality check: Always baseline with a simple model
(e.g., lm) before anything fancy.
13. Debugging & Safety Nets
- Common errors: missing packages, wrong column
names, bad joins, factor levels not set.
- Tactics:
rlang::last_error() to see context
dplyr::glimpse() before/after key steps
stopifnot() for assumptions (e.g., unique keys)
- Use
tryCatch() around fragile I/O
stopifnot(!anyDuplicated(clean$id)) # ids should be unique
14. Reproducible Paths & Projects
- Use RStudio Projects; root paths with
here::here().
- Keep folders:
data/, R/,
output/, figs/, docs/.
- Save session info with outputs.
sessionInfo()
15. Handy Snippets I Reuse
# Percent of total
percent_of_total <- function(x) round(100 * x / sum(x, na.rm = TRUE), 1)
# Not-in operator
`%nin%` <- function(x, y) !(x %in% y)
# Quietly run an expression
quietly <- purrr::quietly
16. Checklist Before You Ship
17. Appendix: swirl (learn by doing)
- Install once:
install.packages("swirl")
- Each session:
library(swirl); swirl()
- Navigate with:
skip(), play() →
nxt(), main(), info(),
bye()
18. Appendix: Keyboard Macros (RStudio)
- Run line/selection: Ctrl/Cmd + Enter
- Run all chunks above: Ctrl + Shift + P
(Windows/Linux) or Cmd + Option + P (macOS)
- Insert chunk: Ctrl + Alt + I / Cmd + Option
+ I
LS0tCnRpdGxlOiAiTWVsYW5pZeKAmXMgUiBHcmFi4oCRQmFnOiBVc2VmdWwgQ29kZSAmIFRpcHMgZm9yIEZ1dHVyZSBNZSIKYXV0aG9yOiAiTWVsYW5pZSBIb2xkZW4iCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVCICVkLCAlWScpYCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKPiAqKlB1cnBvc2U6KiogQSBsaXZpbmcgbm90ZWJvb2sgb2Ygc2hvcnQsIHJlbGlhYmxlIFIgc25pcHBldHMgSSBhY3R1YWxseSB1c2UuIEtlZXAgaXQgc2hvcnQuIEtlZXAgaXQgcnVubmFibGUuIFVwZGF0ZSBhcyBJIGxlYXJuLgoKIyAxLiBRdWljayBTZXR1cAoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgSW5zdGFsbC1vbmNlICh1bmNvbW1lbnQgYXMgbmVlZGVkKQojIGluc3RhbGwucGFja2FnZXMoYygidGlkeXZlcnNlIiwgImphbml0b3IiLCAibHVicmlkYXRlIiwgInJlYWRyIiwgInJlYWR4bCIsICJvcGVueGxzeCIsCiMgICAgICAgICAgICAgICAgICAgICJza2ltciIsICJoZXJlIiwgImZzIiwgImdsdWUiLCAia25pdHIiLCAicm1hcmtkb3duIiwgImdndGhlbWVzIiwKIyAgICAgICAgICAgICAgICAgICAgInBhdGNod29yayIsICJndCIsICJndEV4dHJhcyIsICJzdHJpbmdyIiwgImZvcmNhdHMiKSkKCiMgTG9hZCBldmVyeSBzZXNzaW9uCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeSh0aWR5dmVyc2UpCiAgbGlicmFyeShqYW5pdG9yKQogIGxpYnJhcnkobHVicmlkYXRlKQogIGxpYnJhcnkoaGVyZSkKICBsaWJyYXJ5KGdsdWUpCiAgbGlicmFyeShza2ltcikKICBsaWJyYXJ5KGd0KQp9KQoKIyBSZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoNDIpCmBgYAoKKipQcm9qZWN0IHRpcDoqKiBVc2UgYW4gUlN0dWRpbyBQcm9qZWN0IGFuZCBgaGVyZTo6aGVyZSgpYCBmb3IgcGF0aHMuIE5ldmVyIGhhcmTigJFjb2RlIGAiQzovVXNlcnMvLi4uImAuCgojIDIuIFJlYWRpbmcgJiBXcml0aW5nIERhdGEgKGZhc3QgKyBzYWZlKQoKYGBge3IgaW99CiMgQ1NWIChyb2J1c3QgZGVmYXVsdHMpCmRmX2NzdiA8LSByZWFkcjo6cmVhZF9jc3YoaGVyZSgiZGF0YSIsICJteV9kYXRhLmNzdiIpKQoKIyBFeGNlbCAoZmlyc3Qgc2hlZXQpCmRmX3hsc3ggPC0gcmVhZHhsOjpyZWFkX2V4Y2VsKGhlcmUoImRhdGEiLCAibXlfZGF0YS54bHN4IiksIHNoZWV0ID0gMSkKCiMgV3JpdGUgb3V0cHV0cyB3aXRoIHRpbWVzdGFtcHMKb3V0X3BhdGggPC0gaGVyZSgib3V0cHV0IiwgZ2x1ZSgiY2xlYW5lZF97Zm9ybWF0KFN5cy5EYXRlKCksICclWSVtJWQnKX0uY3N2IikpCiMgcmVhZHI6OndyaXRlX2NzdihkZl9jc3YsIG91dF9wYXRoKQpgYGAKCioqR290Y2hhOioqIElmIHlvdSBzZWUgZW5jb2RpbmcgaXNzdWVzLCB0cnkgYGxvY2FsZSA9IGxvY2FsZShlbmNvZGluZyA9ICJVVEYtOCIpYCBpbiBgcmVhZF9jc3YoKWAuCgojIDMuIEluc3BlY3RpbmcgRGF0YSAod2hhdCBpcyB0aGlzPykKCmBgYHtyIGluc3BlY3R9CiMgSGlnaOKAkWxldmVsIHNraW0Kc2tpbXI6OnNraW0oZGZfY3N2KQoKIyBTdHJ1Y3R1cmUgJiB0eXBlcwpzdHIoZGZfY3N2KQoKIyBDb2x1bW4gbmFtZXMgKGNsZWFuICsgY2hlY2spCm5hbWVzKGRmX2NzdikKamFuaXRvcjo6Y29tcGFyZV9kZl9jb2xzKGRmX2NzdikKYGBgCgoqKlJ1bGUgb2YgdGh1bWI6KiogSWYgYSBjb2x1bW4gc2hvdWxkIGJlIGEgZGF0ZSwgY29udmVydCBpdCAqaW1tZWRpYXRlbHkqIHdpdGggYGx1YnJpZGF0ZWAuCgojIDQuIENsZWFuaW5nIENvbHVtbnMgJiBSb3dzCgpgYGB7ciBjbGVhbmluZ30KIyBDb25zaXN0ZW50IG5hbWVzCmNsZWFuIDwtIGRmX2NzdiAlPiUKICBqYW5pdG9yOjpjbGVhbl9uYW1lcygpICU+JSAgICAgICAgICAgICAgICAgIyBzbmFrZV9jYXNlIGNvbHVtbiBuYW1lcwogIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuY2hhcmFjdGVyKSwgdHJpbXdzKSkgIyB0cmltIGxlYWRpbmcvdHJhaWxpbmcgc3BhY2VzCgojIFJlbW92ZSBjb21wbGV0ZSBkdXBsaWNhdGUgcm93cwpjbGVhbiA8LSBkaXN0aW5jdChjbGVhbikKCiMgSGFuZGxlIGJsYW5rcyBhcyBOQQpjbGVhbiA8LSBtdXRhdGUoY2xlYW4sIGFjcm9zcyhldmVyeXRoaW5nKCksIH5uYV9pZigueCwgIiIpKSkKYGBgCgoqKlRpcDoqKiBVc2UgYGRpc3RpbmN0KC5rZWVwX2FsbCA9IFRSVUUpYCB0byBkZeKAkWR1cGUgYnkgc3Vic2V0IG9mIGNvbHVtbnMuCgojIDUuIGRwbHlyIENoZWF0c2hlZXQgKG1pbmltYWwgc2V0KQoKYGBge3IgZHBseXJ9CnJlc3VsdCA8LSBjbGVhbiAlPiUKICBmaWx0ZXIoIWlzLm5hKGlkKSkgJT4lCiAgbXV0YXRlKAogICAgZGF0ZSA9IGx1YnJpZGF0ZTo6eW1kKGRhdGUpLAogICAgY2F0ZWdvcnkgPSBmb3JjYXRzOjpmY3RfbHVtcF9uKGFzLmZhY3RvcihjYXRlZ29yeSksIG4gPSA1KQogICkgJT4lCiAgZ3JvdXBfYnkoY2F0ZWdvcnkpICU+JQogIHN1bW1hcml6ZSgKICAgIG4gPSBuKCksCiAgICBtZWFuX3ZhbCA9IG1lYW4odmFsdWUsIG5hLnJtID0gVFJVRSksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSAlPiUKICBhcnJhbmdlKGRlc2MobikpCmBgYAoKKipNbmVtb25pYzoqKiAqU2VsZWN04oCTRmlsdGVy4oCTTXV0YXRl4oCTU3VtbWFyaXpl4oCTQXJyYW5nZSogY292ZXJzIDgwJSBvZiB3cmFuZ2xpbmcuCgojIDYuIEpvaW5zIChJIGFsd2F5cyBtaXggdGhlc2UgdXApCgpgYGB7ciBqb2luc30KIyBsZWZ0X2pvaW46IGtlZXAgYWxsIHJvd3MgZnJvbSB4LCBicmluZyBtYXRjaGVzIGZyb20geQpqb2luZWQgPC0gZGZfY3N2ICU+JSBsZWZ0X2pvaW4oZGZfeGxzeCwgYnkgPSAiaWQiKQoKIyBhbnRpX2pvaW46IHJvd3MgaW4geCB3aXRoIG5vIG1hdGNoIGluIHkgKGdyZWF0IGZvciBRQSkKbWlzc2luZ19rZXlzIDwtIGRmX2NzdiAlPiUgYW50aV9qb2luKGRmX3hsc3gsIGJ5ID0gImlkIikKYGBgCgoqKlFBIHRyaWNrOioqIGBhbnRpX2pvaW4oKWAgZmlyc3QgdG8gc2VlIHdoYXQgd29u4oCZdCBtYXRjaCBiZWZvcmUgYW55IGhlYXZ5IHByb2Nlc3NpbmcuCgojIDcuIERhdGVzICYgVGltZXMgKGx1YnJpZGF0ZSkKCmBgYHtyIGRhdGVzfQojIFBhcnNlIGFuZCBzdGFuZGFyZGl6ZQpjbGVhbl9kYXRlcyA8LSBjbGVhbiAlPiUKICBtdXRhdGUoCiAgICBkYXRlID0geW1kKGRhdGUpLAogICAgeWVhciA9IHllYXIoZGF0ZSksCiAgICBtb250aCA9IG1vbnRoKGRhdGUsIGxhYmVsID0gVFJVRSwgYWJiciA9IFRSVUUpLAogICAgd2sgPSBpc293ZWVrKGRhdGUpCiAgKQpgYGAKCioqVGlwOioqIElmIHBhcnNpbmcgZmFpbHMsIGluc3BlY3Qgd2l0aCBgcGFyc2VfZGF0ZV90aW1lKHgsIG9yZGVycyA9IGMoInltZCIsICJtZHkiLCAiZG15IikpYC4KCiMgOC4gU3RyaW5ncyAoc3RyaW5ncikKCmBgYHtyIHN0cmluZ3N9CnRleHRfY2xlYW4gPC0gY2xlYW4gJT4lCiAgbXV0YXRlKAogICAgZW1haWwgPSBzdHJfdG9fbG93ZXIoZW1haWwpLAogICAgZG9tYWluID0gc3RyX2V4dHJhY3QoZW1haWwsICJALiskIikKICApCmBgYAoKKipSZWdleCBzYW5pdHk6KiogVGVzdCBwYXR0ZXJucyBhdCA8aHR0cHM6Ly9yZWdleDEwMS5jb20vPiBiZWZvcmUgY29tbWl0dGluZy4KCiMgOS4gRmFjdG9ycyAoZm9yY2F0cykKCmBgYHtyIGZhY3RvcnN9CmZhYyA8LSBjbGVhbiAlPiUKICBtdXRhdGUoCiAgICBzdGF0dXMgPSBmY3RfcmVsZXZlbChhcy5mYWN0b3Ioc3RhdHVzKSwgYygibmV3IiwgImFjdGl2ZSIsICJpbmFjdGl2ZSIpKSwKICAgIHRvcF9jYXQgPSBmY3RfbHVtcF9uKGFzLmZhY3RvcihjYXRlZ29yeSksIG4gPSA2KQogICkKYGBgCgoqKlBsb3R0aW5nIHRpcDoqKiBSZWxldmVsIGZhY3RvcnMgdG8gY29udHJvbCBnZ3Bsb3Qgb3JkZXJpbmcuCgojIDEwLiBQbG90dGluZyAoZ2dwbG90MjogbWluaW1hbCBwYXR0ZXJucykKCmBgYHtyIHBsb3RzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00fQojIEJhciAoY291bnRzKQpjbGVhbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjYXRlZ29yeSkpICsKICBnZW9tX2JhcihmaWxsID0gIiMyRTg2QUIiKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKwogIGxhYnModGl0bGUgPSAiQ291bnRzIGJ5IENhdGVnb3J5IiwgeCA9IE5VTEwsIHkgPSAiQ291bnQiKQoKIyBMaW5lICh0aW1lIHNlcmllcykKY2xlYW5fZGF0ZXMgJT4lCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lIHN1bW1hcml6ZShuID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBnZ3Bsb3QoYWVzKGRhdGUsIG4pKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gIiM3RDNDOTgiLCBsaW5ld2lkdGggPSAwLjkpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArCiAgbGFicyh0aXRsZSA9ICJEYWlseSBDb3VudHMiLCB4ID0gTlVMTCwgeSA9IE5VTEwpCmBgYAoKKipTbWFsbCBtdWx0aXBsZXM6KiogVXNlIGArIGZhY2V0X3dyYXAofmdyb3VwKWAgd2hlbiBjYXRlZ29yaWVzIGFyZSBtYW55LgoKIyAxMS4gVGFibGVzIChndCBxdWljayBwYXR0ZXJuKQoKYGBge3IgZ3R9CnJlc3VsdCAlPiUKICBndDo6Z3QoKSAlPiUKICBndDo6Zm10X251bWJlcihjb2x1bW5zID0gd2hlcmUoaXMubnVtZXJpYyksIGRlY2ltYWxzID0gMikgJT4lCiAgZ3Q6OnRhYl9oZWFkZXIodGl0bGUgPSBtZCgiKipTdW1tYXJ5IGJ5IENhdGVnb3J5KioiKSkKYGBgCgoqKkV4cG9ydDoqKiBgZ3RzYXZlKCJ0YWJsZS5wbmciKWAgb3IgYGd0OjpndHNhdmUoKWAgdG8gUE5HL1BERi9IVE1MLgoKIyAxMi4gTW9kZWxpbmcgKHRpZHltb2RlbHMgKnRpbnkqIHN0YXJ0ZXIpCgpgYGB7ciBtb2RlbGluZywgbWVzc2FnZT1GQUxTRX0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5bW9kZWxzIikgICMgb25jZQojIGxpYnJhcnkodGlkeW1vZGVscykKIyBzZXQuc2VlZCg0MikKIyBzcGxpdCA8LSBpbml0aWFsX3NwbGl0KGNsZWFuLCBwcm9wID0gMC44KQojIHRyYWluIDwtIHRyYWluaW5nKHNwbGl0KTsgdGVzdCA8LSB0ZXN0aW5nKHNwbGl0KQojIHJlYyA8LSByZWNpcGUodGFyZ2V0IH4gLiwgZGF0YSA9IHRyYWluKSAlPiUgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCkpCiMgbW9kIDwtIGxpbmVhcl9yZWcoKSAlPiUgc2V0X2VuZ2luZSgibG0iKQojIHdmICA8LSB3b3JrZmxvdygpICU+JSBhZGRfbW9kZWwobW9kKSAlPiUgYWRkX3JlY2lwZShyZWMpCiMgZml0IDwtIGZpdCh3ZiwgZGF0YSA9IHRyYWluKQojIG1ldHJpY3MgPC0gcHJlZGljdChmaXQsIHRlc3QpICU+JSBiaW5kX2NvbHModGVzdCkgJT4lIG1ldHJpY3ModHJ1dGggPSB0YXJnZXQsIGVzdGltYXRlID0gLnByZWQpCmBgYAoKKipSZWFsaXR5IGNoZWNrOioqIEFsd2F5cyBiYXNlbGluZSB3aXRoIGEgc2ltcGxlIG1vZGVsIChlLmcuLCBgbG1gKSBiZWZvcmUgYW55dGhpbmcgZmFuY3kuCgojIDEzLiBEZWJ1Z2dpbmcgJiBTYWZldHkgTmV0cwoKLSAgICoqQ29tbW9uIGVycm9yczoqKiBtaXNzaW5nIHBhY2thZ2VzLCB3cm9uZyBjb2x1bW4gbmFtZXMsIGJhZCBqb2lucywgZmFjdG9yIGxldmVscyBub3Qgc2V0LgotICAgKipUYWN0aWNzOioqCiAgICAtICAgYHJsYW5nOjpsYXN0X2Vycm9yKClgIHRvIHNlZSBjb250ZXh0CiAgICAtICAgYGRwbHlyOjpnbGltcHNlKClgIGJlZm9yZS9hZnRlciBrZXkgc3RlcHMKICAgIC0gICBgc3RvcGlmbm90KClgIGZvciBhc3N1bXB0aW9ucyAoZS5nLiwgdW5pcXVlIGtleXMpCiAgICAtICAgVXNlIGB0cnlDYXRjaCgpYCBhcm91bmQgZnJhZ2lsZSBJL08KCmBgYHtyIGd1YXJkcmFpbHN9CnN0b3BpZm5vdCghYW55RHVwbGljYXRlZChjbGVhbiRpZCkpICAjIGlkcyBzaG91bGQgYmUgdW5pcXVlCmBgYAoKIyAxNC4gUmVwcm9kdWNpYmxlIFBhdGhzICYgUHJvamVjdHMKCi0gICBVc2UgUlN0dWRpbyBQcm9qZWN0czsgcm9vdCBwYXRocyB3aXRoIGBoZXJlOjpoZXJlKClgLgotICAgS2VlcCBmb2xkZXJzOiBgZGF0YS9gLCBgUi9gLCBgb3V0cHV0L2AsIGBmaWdzL2AsIGBkb2NzL2AuCi0gICBTYXZlIHNlc3Npb24gaW5mbyB3aXRoIG91dHB1dHMuCgpgYGB7ciBzZXNzaW9uLWluZm99CnNlc3Npb25JbmZvKCkKYGBgCgojIDE1LiBIYW5keSBTbmlwcGV0cyBJIFJldXNlCgpgYGB7ciBzbmlwcGV0c30KIyBQZXJjZW50IG9mIHRvdGFsCnBlcmNlbnRfb2ZfdG90YWwgPC0gZnVuY3Rpb24oeCkgcm91bmQoMTAwICogeCAvIHN1bSh4LCBuYS5ybSA9IFRSVUUpLCAxKQoKIyBOb3QtaW4gb3BlcmF0b3IKYCVuaW4lYCA8LSBmdW5jdGlvbih4LCB5KSAhKHggJWluJSB5KQoKIyBRdWlldGx5IHJ1biBhbiBleHByZXNzaW9uCnF1aWV0bHkgPC0gcHVycnI6OnF1aWV0bHkKYGBgCgojIDE2LiBDaGVja2xpc3QgQmVmb3JlIFlvdSBTaGlwCgotICAgWyBdIENvbHVtbiBuYW1lcyBhcmUgY2xlYW4gJiBjb25zaXN0ZW50Ci0gICBbIF0gRGF0ZXMgcGFyc2VkIGFuZCBpbiBjb3JyZWN0IHRpbWV6b25lL2Zvcm1hdAotICAgWyBdIEpvaW5zIGF1ZGl0ZWQgd2l0aCBgYW50aV9qb2luKClgCi0gICBbIF0gTkFzIGhhbmRsZWQgaW50ZW50aW9uYWxseQotICAgWyBdIEZpZ3VyZXMgaGF2ZSB0aXRsZXMsIGxhYmVscywgdW5pdHMKLSAgIFsgXSBDb2RlIGNodW5rcyBhcmUgZGV0ZXJtaW5pc3RpYyAoc2V0IHNlZWRzKQotICAgWyBdIFNhdmUgYXJ0aWZhY3RzIHdpdGggdmVyc2lvbmVkIGZpbGVuYW1lcwoKIyAxNy4gQXBwZW5kaXg6IHN3aXJsIChsZWFybiBieSBkb2luZykKCi0gICBJbnN0YWxsIG9uY2U6IGBpbnN0YWxsLnBhY2thZ2VzKCJzd2lybCIpYAotICAgRWFjaCBzZXNzaW9uOiBgbGlicmFyeShzd2lybCk7IHN3aXJsKClgCi0gICBOYXZpZ2F0ZSB3aXRoOiBgc2tpcCgpYCwgYHBsYXkoKWAg4oaSIGBueHQoKWAsIGBtYWluKClgLCBgaW5mbygpYCwgYGJ5ZSgpYAoKIyAxOC4gQXBwZW5kaXg6IEtleWJvYXJkIE1hY3JvcyAoUlN0dWRpbykKCi0gICBSdW4gbGluZS9zZWxlY3Rpb246ICoqQ3RybC9DbWQgKyBFbnRlcioqCi0gICBSdW4gYWxsIGNodW5rcyBhYm92ZTogKipDdHJsICsgU2hpZnQgKyBQKiogKFdpbmRvd3MvTGludXgpIG9yICoqQ21kICsgT3B0aW9uICsgUCoqIChtYWNPUykKLSAgIEluc2VydCBjaHVuazogKipDdHJsICsgQWx0ICsgSSoqIC8gKipDbWQgKyBPcHRpb24gKyBJKioKCiMgMTkuIFRvIERvIC8gUGFya2luZyBMb3QKCi0gICBbIF0gQWRkIGEgYHRhcmdldHNgIG9yIGByZW52YCBzZWN0aW9uIHdoZW4gcHJvamVjdHMgZ3JvdwotICAgWyBdIEFkZCB1bml0IHRlc3RzIHdpdGggYHRlc3R0aGF0YCBmb3Iga2V5IGhlbHBlcnMKLSAgIFsgXSBBZGQgYSBzdHlsZSBndWlkZSBkZWNpc2lvbiAobGludHIvc3R5bGVyKQo=