├── .gitignore
├── R
├── asciicast-experimentation.R
├── brooke-reprex-2.R
├── brooke-reprex.R
├── dplyr-filter-hint.R
├── fresh-start-acitivity-prompt.R
├── fruit.csv
├── fruit_avg.R
├── fruit_avg_assertion.R
├── fruit_avg_browser.R
├── fruit_avg_demo_code.R
├── fruit_avg_fixed.R
├── fruit_avg_test.R
├── hadley-minimal-reprex.R
├── knitr-error-wrap.R
├── object-of-type-closure-REDUX.R
├── object-of-type-closure-is-not-subsettable.R
├── reprex-praise-examples.R
└── theme-explainer-code.R
├── README.md
├── bryan-title-abstract.txt
├── debugging-jenny-bryan.pdf
├── debugging-title-400.jpg
├── debugging-title-600.jpg
├── debugging-title.jpeg
├── debugging.Rproj
├── how-to
├── highlight-custom-theme.md
├── highlight-theme-explainer.key
├── highlight-theme-explainer
│ ├── highlight-theme-explainer.001.png
│ └── highlight-theme-explainer.002.png
└── keynote-theme-development.md
└── resources
└── kuper.theme
/.gitignore:
--------------------------------------------------------------------------------
1 | .Rproj.user
2 | .Rhistory
3 | .RData
4 | key
5 | kth
6 |
--------------------------------------------------------------------------------
/R/asciicast-experimentation.R:
--------------------------------------------------------------------------------
1 | library(asciicast)
2 | library(here)
3 |
4 | cast <- record(
5 | here("R", "object-of-type-closure-is-not-subsettable.R"),
6 | typing_speed = 1/10,
7 | empty_wait = 1.5,
8 | end_wait = 8
9 | )
10 | play(cast)
11 |
12 | write_svg(cast, "foo.svg", window = TRUE)
13 |
--------------------------------------------------------------------------------
/R/brooke-reprex-2.R:
--------------------------------------------------------------------------------
1 | # setup ----
2 |
3 | options(width = 37)
4 | source("R/knitr-error-wrap.R")
5 |
6 | # reprex that resulted from Brooke's original wild-caught puzzle
7 | dat <- data.frame(`..1` = 1)
8 |
9 | dplyr::mutate_all(dat, as.character)
10 |
--------------------------------------------------------------------------------
/R/brooke-reprex.R:
--------------------------------------------------------------------------------
1 | #+ eval = FALSE
2 | fs::dir_info(path = raw_data_path, # list xls[s] files
3 | regexp = "[.]xls[x]?$") %>%
4 |
5 | dplyr::mutate(sheets = purrr::map( # create list-col of
6 | path, ~ readxl::excel_sheets)) %>% # worksheets
7 |
8 | tidyr::unnest(sheets) %>% # get one row per worksheet
9 |
10 | dplyr::mutate(data = purrr::map2( # read data into a list-col
11 | path, sheets, # of data frames
12 | ~ readxl::read_excel(.x, .y) %>% # call `as.character()`
13 | dplyr::mutate_all(as.character) # on each column
14 | ))
15 | #> New names:
16 | #> * `` -> `..2`
17 | #> * `` -> `..3`
18 | #> * `` -> `..4`
19 | #> * `` -> `..5`
20 | #> * `` -> `..6`
21 | #> Error: the ... list does not contain 3 elements
22 |
23 | # https://github.com/tidyverse/dplyr/issues/4094
24 |
25 | #+ after, eval = FALSE
26 | df <- data.frame(`..1` = 1)
27 |
28 | dplyr::mutate_all(df, as.character)
29 | #> Error in mutate_impl(.data, dots) :
30 | #> Evaluation error: ..1 used in an incorrect context, no ... to look in.
31 |
--------------------------------------------------------------------------------
/R/dplyr-filter-hint.R:
--------------------------------------------------------------------------------
1 | #+ message = FALSE, warning = FALSE
2 | library(dplyr)
3 |
4 | filter(iris, Species = "setosa")
5 |
--------------------------------------------------------------------------------
/R/fresh-start-acitivity-prompt.R:
--------------------------------------------------------------------------------
1 | library(dplyr)
2 |
3 | summary <- head
4 |
5 | options(stringsAsFactors = FALSE)
6 |
7 | Sys.setenv(LANGUAGE = "fr")
8 |
9 | x <- 1:5
10 |
11 | attach(iris)
12 |
--------------------------------------------------------------------------------
/R/fruit.csv:
--------------------------------------------------------------------------------
1 | blackberry, blueberry, peach, plum
2 | calories, 4, 1, 59, 30
3 | weight, 9, 2, 150, 78
4 | yumminess, 6, 8, 10, 5
5 |
--------------------------------------------------------------------------------
/R/fruit_avg.R:
--------------------------------------------------------------------------------
1 | fruit_avg <- function(dat, pattern) {
2 | cols <- grep(pattern, names(dat))
3 | mini_dat <- dat[ , cols]
4 | message("Found ", ncol(mini_dat), " fruits!")
5 | rowMeans(mini_dat)
6 | }
7 |
--------------------------------------------------------------------------------
/R/fruit_avg_assertion.R:
--------------------------------------------------------------------------------
1 | dat <- read.csv("R/fruit.csv", strip.white = TRUE)
2 |
3 | all(vapply(dat, is.numeric, logical(1)))
4 |
5 | #+ eval = FALSE
6 | dat <- read.csv("fruit.csv")
7 |
8 | if (!all(vapply(dat, is.numeric, logical(1)))) {
9 | stop("All columns of `dat` must be numeric")
10 | }
11 |
12 | fruit_avg(dat, pattern = "berry")
13 |
--------------------------------------------------------------------------------
/R/fruit_avg_browser.R:
--------------------------------------------------------------------------------
1 | fruit_avg <- function(dat, pattern) {
2 | browser()
3 | cols <- grep(pattern, names(dat))
4 | mini_dat <- dat[ , cols]
5 | message("Found ", ncol(mini_dat), " fruits!")
6 | rowMeans(mini_dat)
7 | }
8 |
--------------------------------------------------------------------------------
/R/fruit_avg_demo_code.R:
--------------------------------------------------------------------------------
1 | # setup ----
2 |
3 | options(width = 50)
4 | options(error = rlang::entrace)
5 |
6 | source("R/knitr-error-wrap.R")
7 | dat <- read.csv("R/fruit.csv", strip.white = TRUE)
8 | source("R/fruit_avg.R")
9 |
10 | # first look at dat and fruit_avg(); see error ----
11 | # series of 3 code slides
12 | # in Keynote, position based on "black" (error) example
13 | dat
14 |
15 | fruit_avg(dat, pattern = "berry")
16 |
17 | dat
18 |
19 | fruit_avg(dat, pattern = "melon")
20 |
21 | dat
22 |
23 | fruit_avg(dat, pattern = "black")
24 |
25 | # What just happened? ways to see the call stack ----
26 | # series of 3 slides: base, rlang, RStudio
27 | # in Keynote, position based on rlang example
28 |
29 |
30 | fruit_avg(dat, pattern = "black")
31 |
32 | traceback()
33 |
34 |
35 |
36 | fruit_avg(dat, pattern = "black")
37 |
38 | rlang::last_trace()
39 |
40 |
41 |
42 | # Rstudio's Debug > On Error > Error Inspector
43 | fruit_avg(dat, pattern = "black")
44 | # screenshot!
45 | options(error = rlang::entrace)
46 |
47 |
48 |
49 | # How to see the state of the world(s) at the moment of failure ----
50 | options(error = recover)
51 |
52 | fruit_avg(dat, "black")
53 | # interactive recover() work is beyond the reach of reprex
54 | # have to fake these snippets by running, copy/pasting,
55 | # tweaking text color in Keynote
56 |
57 | # use a screenshot to convey what goes on in RStudio environment pane
58 |
59 |
60 |
61 | # show power of browser() ----
62 |
63 | # I recorded this with shift + command + 5, record selected part of screen
64 | source("R/fruit_avg_browser.R")
65 | fruit_avg(dat, "black")
66 | # inspect mini_dat, add `drop = FALSE` on-the-fly, see success
67 |
68 | # mention debug() ----
69 | debug(fruit_avg)
70 | fruit_avg(dat, "black")
71 |
72 | # source a version of fruit_avg() that has `drop = FALSE`
73 | source("R/fruit_avg_fixed.R")
74 |
75 | fruit_avg(dat, "black")
76 |
--------------------------------------------------------------------------------
/R/fruit_avg_fixed.R:
--------------------------------------------------------------------------------
1 | fruit_avg <- function(dat, pattern) {
2 | cols <- grep(pattern, names(dat))
3 | mini_dat <- dat[ , cols, drop = FALSE]
4 | message("Found ", ncol(mini_dat), " fruits!")
5 | rowMeans(mini_dat)
6 | }
7 |
--------------------------------------------------------------------------------
/R/fruit_avg_test.R:
--------------------------------------------------------------------------------
1 | # setup ----
2 |
3 | options(width = 50)
4 | options(error = rlang::entrace)
5 |
6 | source("R/knitr-error-wrap.R")
7 | source("R/fruit_avg_fixed.R")
8 |
9 | library(testthat)
10 |
11 | #+ eval = FALSE
12 | # https://github.com/OWNER/REPO/issues/666
13 | test_that("fruit_avg() works for 0, 1, >=2 matches", {
14 | dat <- data.frame(a = 1:2, ab = 3:4, row.names = c("one", "two"))
15 | expect_equal(fruit_avg(dat, "abc"), c(one = NaN, two = NaN))
16 | expect_equal(fruit_avg(dat, "ab"), c(one = 3, two = 4))
17 | expect_equal(fruit_avg(dat, "a"), c(one = 2, two = 3))
18 | })
19 |
--------------------------------------------------------------------------------
/R/hadley-minimal-reprex.R:
--------------------------------------------------------------------------------
1 | # more minimal reprex NOT advice user:tidyverse user:r-lib user:hadley involves:hadley is:issue
2 | # https://github.com/search?o=desc&q=more+minimal+reprex+NOT+advice+user%3Atidyverse+user%3Ar-lib+user%3Ahadley+involves%3Ahadley+is%3Aissue&s=updated&type=Issues
3 | # sorted by "recently updated"
4 | # yielded 208 issues on 2020-01-13
5 | # ggplot2 issues very difficult to count lines for, so mostly ignored them
6 |
7 | # manually processed, very unscientific!
8 | # selected OP's reprex, then Hadley's and counted lines like so:
9 | # length(clipr::read_clip())
10 |
11 | # https://docs.google.com/spreadsheets/d/1zjVdn6L9torH4PR9OqSBFNA8U6YxYsQM-Fj0mJmqF2Q/edit#gid=0
12 |
13 | library(googlesheets4)
14 | library(tidyverse)
15 |
16 | sheets_deauth()
17 |
18 | ssid <- "1zjVdn6L9torH4PR9OqSBFNA8U6YxYsQM-Fj0mJmqF2Q"
19 | df <- sheets_read(ssid)
20 |
21 | lims <- c(0, 83)
22 | p <- df %>%
23 | ggplot(aes(x = them, y = hadley)) +
24 | geom_point() +
25 | geom_abline(slope = 1, intercept = 0) +
26 | coord_fixed(ratio = 1, xlim = lims, ylim = lims)
27 | p
28 | ggsave(here::here("img", "hadley-minimal-reprex.png"), p)
29 |
--------------------------------------------------------------------------------
/R/knitr-error-wrap.R:
--------------------------------------------------------------------------------
1 | # various Hadley tricks
2 |
3 | # Make error messages closer to base R
4 | # https://github.com/hadley/adv-r/blob/a54903729dfa79fa79af5776218e6bc781e98c3d/common.R#L45-L64
5 | registerS3method("wrap", "error", envir = asNamespace("knitr"),
6 | function(x, options) {
7 | msg <- conditionMessage(x)
8 |
9 | call <- conditionCall(x)
10 | if (is.null(call)) {
11 | msg <- paste0("Error: ", msg)
12 | } else {
13 | msg <- paste0("Error in ", deparse(call)[[1]], ": ", msg)
14 | }
15 |
16 | msg <- error_wrap(msg)
17 | knitr:::msg_wrap(msg, "error", options)
18 | }
19 | )
20 |
21 | error_wrap <- function(x, width = getOption("width")) {
22 | lines <- strsplit(x, "\n", fixed = TRUE)[[1]]
23 | paste(strwrap(lines, width = width), collapse = "\n")
24 | }
25 |
26 | ruler <- function(width = getOption("width")) {
27 | x <- seq_len(width)
28 | y <- dplyr::case_when(
29 | x %% 10 == 0 ~ as.character((x %/% 10) %% 10),
30 | x %% 5 == 0 ~ "+",
31 | TRUE ~ "-"
32 | )
33 | cat(y, "\n", sep = "")
34 | cat(x %% 10, "\n", sep = "")
35 | }
36 |
--------------------------------------------------------------------------------
/R/object-of-type-closure-REDUX.R:
--------------------------------------------------------------------------------
1 | # setup ----
2 |
3 | options(width = 53)
4 | options(error = rlang::entrace)
5 |
6 | source("R/knitr-error-wrap.R")
7 |
8 | dat <- data.frame(x = 1, y = 2)
9 |
10 | df$x
11 |
12 | rlang::abort("object of type 'function' is not subsettable")
13 |
14 | rlang::abort("You can not subset `df`, a function, with `$`")
15 |
16 | #> Error: Can't subset a function.
17 | #> Have you forgotten to define a variable named `df`?
18 |
--------------------------------------------------------------------------------
/R/object-of-type-closure-is-not-subsettable.R:
--------------------------------------------------------------------------------
1 | dat <- data.frame(x = 1, y = 2)
2 |
3 | df$x
4 |
5 | cat("Perhaps you wanted `dat$x`?\n")
6 |
--------------------------------------------------------------------------------
/R/reprex-praise-examples.R:
--------------------------------------------------------------------------------
1 | template <- "${EXCLAMATION} - your reprex is ${adjective}!"
2 | praise(template)
3 |
4 | #+ include = FALSE
5 | rm(template)
6 |
7 | #+ include = TRUE
8 | library(praise)
9 | praise(template)
10 |
11 | library(praise)
12 | template <- "${EXCLAMATION} - your reprex is ${adjective}!"
13 | praise(template)
14 |
--------------------------------------------------------------------------------
/R/theme-explainer-code.R:
--------------------------------------------------------------------------------
1 | x <- rnorm(5)
2 |
3 | print(x)
4 |
5 | # I use this file to explore / explain highlight themes
6 | cat("Hello World\n")
7 |
8 | fruit_avg <- function(dat, pattern) {
9 | cols <- grep(pattern, names(dat))
10 | mini_dat <- dat[ , cols]
11 | message("Found ", ncol(mini_dat), " fruits!")
12 | rowMeans(mini_dat)
13 | }
14 |
15 | # more regular comment
16 | if (TRUE) {
17 | "true"
18 | } else {
19 | "false"
20 | }
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Object of type 'closure' is not subsettable
2 |
3 | Talk developed for [rstudio::conf 2020](https://rstudio.com/conference/), January 27 - 30, 2020 in San Francisco
4 | by Jenny Bryan
5 | [jennybryan.org](https://jennybryan.org)
6 | Twitter: [@jennyBryan](https://twitter.com/JennyBryan)
7 | GitHub: [@jennybc](https://github.com/jennybc)
8 |
9 | > Your first "object of type 'closure' is not subsettable" error message is a big milestone for an R user. Congratulations, if there was any lingering doubt, you now know that you are officially programming! Programming involves considerably more troubleshooting and debugging than many of us expected (or signed up for). The ability to solve your own problems is an incredibly powerful stealth skill that is worth cultivating with intention. This talk will help you nurture your inner problem solver, covering both general debugging methods and specific ways to implement them in the R ecosystem.
10 |
11 | ## Link to this repo
12 |
13 | [rstd.io/debugging](https://rstd.io/debugging) is a shortlink to HERE
14 |
15 | ## Slides
16 |
17 |
18 |
19 | Slides [on SpeakerDeck](https://speakerdeck.com/jennybc/object-of-type-closure-is-not-subsettable)
20 |
21 | Slides [as PDF file](debugging-jenny-bryan.pdf) here in this repo
22 |
23 | ## Video
24 |
25 | The original rstudio::conf talk was live-streamed and recorded. Video is available here:
26 |
27 |
28 |
29 | ## Credits and resources
30 |
31 | Annotated and hyperlink-y list of resources mentioned in the slides, in roughly the same order.
32 |
33 | ---
34 |
35 | Restart R, often, and don't save/reload `.Rdata`.
36 |
37 | This recommendation is explored more thoroughly in [Save source, not the workspace](https://rstats.wtf/save-source.html). This mindset works best as part of a more holistic "project-oriented workflow", which is described in adjacent sections of [What They Forgot to Teach You About R](https://rstats.wtf/index.html).
38 |
39 | ---
40 |
41 | > One of the most useful things I’ve learned from hanging out with (much) better programmers: don’t wring hands and speculate. Work a small example that reveals, confirms, or eliminates something.
42 |
43 | I'm paraphrasing my own tweet:
44 |
45 | ---
46 |
47 | The reprex package:
48 |
49 | RStudio webinar about reprex: [Help me help you: Creating reproducible examples with reprex](https://reprex.tidyverse.org/articles/articles/learn-reprex.html)
50 |
51 | ---
52 |
53 | The REPREX section features several illustrations by [Christine Kuper](https://christinekuper.com).
54 |
55 | We gratefully acknowledge [Michelle Rial](https://www.michellerial.com) and her charts for an inspiring visual style.
56 |
57 | ---
58 |
59 | The example of how a wild-caught puzzle evolves into a good reprex was kindly donated by [Brooke Watson Madubuonwu](https://twitter.com/brookLYNevery1).
60 |
61 | Story concluded in [tidyverse/dplyr#4094](https://github.com/tidyverse/dplyr/issues/4094).
62 |
63 | ---
64 |
65 | GitHub search for Hadley's "slightly more minimal reprexes": [`more minimal reprex NOT advice user:tidyverse user:r-lib user:hadley involves:hadley is:issue`] [`more minimal reprex NOT advice user:tidyverse user:r-lib involves:hadley is:issue`](https://github.com/search?o=desc&q=more+minimal+reprex+NOT+advice+user%3Atidyverse+user%3Ar-lib+involves%3Ahadley+is%3Aissue&s=updated&type=Issues)
66 |
67 | ---
68 |
69 | Presentation of a rather intimidating R message was inspired by a Far Side comic from Gary Larson. You know ... the one about Ginger the dog and "blah blah".
70 |
71 | The Far Side very recently got a real online home: . However, last time I checked, this particular comic isn't there (yet). Hopefully it will appear one day and I can link to it.
72 |
73 | ---
74 |
75 | All the code snippets from the `fruit_avg()` debugging example are in [R/](R).
76 |
77 | ---
78 |
79 | [Debugging with RStudio](https://support.rstudio.com/hc/en-us/articles/205612627-Debugging-with-RStudio)
80 |
81 | rlang's functions for [Errors, conditions, and backtraces](https://rlang.r-lib.org/reference/index.html#section-errors-conditions-and-backtraces)
82 |
83 | ---
84 |
85 | Video of a tiny room hidden behind an electrical outlet is from Mozu Studios:
86 |
87 | *
88 | *
89 |
90 | ---
91 |
92 | > The major difference between a thing that might go wrong and a thing that cannot possibly go wrong is that when a thing that cannot possibly go wrong goes wrong it usually turns out to be impossible to get at and repair.
93 |
94 | Quote attributed to [Douglas Adams](https://www.brainyquote.com/quotes/douglas_adams_124773), but I am not sure of the original source.
95 |
96 | ---
97 |
98 | The tidyverse and r-lib packages have an *aspirational* and developing style guide for error messages:
99 |
100 |
101 |
102 | ---
103 |
104 | Static code snippets prepared with the [reprex package](https://reprex.tidyverse.org), using [`venue = "rtf"`](https://reprex.tidyverse.org/articles/articles/rtf.html), which relies on [highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php).
105 |
106 | GIFs of code prepared with [r-lib/asciicast](https://github.com/r-lib/asciicast) plus [asciinema/asciicast2gif](https://github.com/asciinema/asciicast2gif).
107 |
108 | ---
109 |
110 | This talk benefitted greatly from:
111 |
112 | * Regular discussions with the rest of the tidyverse / r-lib team at RStudio
113 | - [tidyverse org members](https://github.com/orgs/tidyverse/people)
114 | - [r-lib org members](https://github.com/orgs/r-lib/people)
115 | * Visual design and art from Christine Kuper
116 | -
117 | * Lightning talk on debugging the IDE given internally by RStudio engineer Jonathan McPherson
118 |
119 | ---
120 |
121 | Image credits
122 |
123 | Fret: https://unsplash.com/photos/OsC8HauR0e0
124 | Do same thing again: https://unsplash.com/photos/uxUUENpp01I
125 | Diver: https://unsplash.com/photos/wVvxjiLJr-g
126 | Ocean horizon background: https://unsplash.com/photos/sYzFIusQp3Q
127 | Calm sea background: https://unsplash.com/photos/IZ01rjX0XQA
128 | Coral reef background: https://unsplash.com/photos/T1Wru10gKhg
129 | Seaweed background: https://unsplash.com/photos/nAkC-KS444M
130 | Orchid: https://unsplash.com/photos/Ug6z9PCwr58
131 | Corn field: https://unsplash.com/photos/nCQXxsSg3oo
132 | On/off key: https://unsplash.com/photos/cw_uvISXkCI
133 | Sunlight under water background: https://unsplash.com/photos/K785Da4A_JA
134 | Garnishing with sauce: https://unsplash.com/photos/YaiY50wzWzI
135 | Death certificate (modified): Public Domain, https://commons.wikimedia.org/w/index.php?curid=214170
136 | Sunlight under water background: https://unsplash.com/photos/K785Da4A_JA
137 | Washing pot: https://unsplash.com/photos/-VhH4S1Lur8
138 | The Night King: https://cnet4.cbsistatic.com/img/vugy5MvUVBvwcJf0JvKIBd1RwJE=/1200x675/2019/04/22/2b2fee8d-111a-4d19-ae83-4e61899cfd47/1nightking.jpg (probably copyright HBO)
139 | Autopsy painting by Rembrandt: https://www.mauritshuis.nl/en/explore/the-collection/artworks/the-anatomy-lesson-of-dr-nicolaes-tulp-146/detailgegevens/ Public Domain, https://commons.wikimedia.org/w/index.php?curid=64281722
140 | Gray cube abstract wallpaper vector art https://unsplash.com/photos/1CVy8JStf3A
141 |
--------------------------------------------------------------------------------
/bryan-title-abstract.txt:
--------------------------------------------------------------------------------
1 | Title:
2 | Object of type 'closure' is not subsettable
3 |
4 | Speaker:
5 | Jennifer Bryan
6 |
7 | Abstract:
8 | Your first "object of type 'closure' is not subsettable" error message is a big milestone for an R user. Congratulations, if there was any lingering doubt, you now know that you are officially programming! Programming involves considerably more troubleshooting and debugging than many of us expected (or signed up for). The ability to solve your own problems is an incredibly powerful stealth skill that is worth cultivating with intention. This talk will help you nurture your inner problem solver, covering both general debugging methods and specific ways to implement them in the R ecosystem.
9 |
--------------------------------------------------------------------------------
/debugging-jenny-bryan.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/debugging-jenny-bryan.pdf
--------------------------------------------------------------------------------
/debugging-title-400.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/debugging-title-400.jpg
--------------------------------------------------------------------------------
/debugging-title-600.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/debugging-title-600.jpg
--------------------------------------------------------------------------------
/debugging-title.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/debugging-title.jpeg
--------------------------------------------------------------------------------
/debugging.Rproj:
--------------------------------------------------------------------------------
1 | Version: 1.0
2 |
3 | RestoreWorkspace: No
4 | SaveWorkspace: No
5 | AlwaysSaveHistory: Default
6 |
7 | EnableCodeIndexing: Yes
8 | UseSpacesForTab: Yes
9 | NumSpacesForTab: 2
10 | Encoding: UTF-8
11 |
12 | RnwWeave: Sweave
13 | LaTeX: pdfLaTeX
14 |
15 | AutoAppendNewline: Yes
16 | StripTrailingWhitespace: Yes
17 |
18 | BuildType: Package
19 | PackageUseDevtools: Yes
20 | PackageInstallArgs: --no-multiarch --with-keep.source
21 | PackageRoxygenize: rd,collate,namespace
22 |
--------------------------------------------------------------------------------
/how-to/highlight-custom-theme.md:
--------------------------------------------------------------------------------
1 | ## How to use a custom theme with highlight (and reprex)
2 |
3 | `reprex::reprex_rtf()` and `reprex:::prex_rtf()` shell out to [highlight](http://www.andre-simon.de/doku/highlight/en/highlight.php) to produce rendered, syntax-highlighted R snippets as RTF. Normally I just choose one of the themes that ship with highlight, such as Darkbone or Solarized Light, for dark and light slides, respectively. But for this talk I was curious to see if I could create a custom theme that is more consistent with the Keynote theme.
4 |
5 | 1. Find your existing themes. On my current system they are at `/usr/local/Cellar/highlight/3.47/share/highlight/themes`.
6 | 1. Copy one of these files, such as `darkbone.theme`, to `kuper.theme`, where "kuper" is whatever you plan to call this custom theme. Modify (or not), in order to conduct a pilot study. For example, you might want to make at least one color obvious and garish, to easily confirm things are working.
7 | 1. Specify the custom theme file via `--config-file` with a test `.R` file (such as a commented R file obtained via `reprex_r(input = "abc.R", outfile = NA)`):
8 |
9 | highlight abc_reprex_rendered.R --out-format rtf --no-trailing-nl \
10 | --encoding=UTF-8 --config-file kuper.theme > \
11 | abc_reprex_rendered.rtf
12 | 1. Method 1 for making the custom theme available through `--style` on the command line: move the theme to `~/.highlight/themes/kuper.theme`.
13 | 1. Method 2: store theme in the relevant project, but symlink it to `~/.highlight/themes/kuper.theme`.
14 | 1. Method 3: symlink `kuper.theme` into the main themes directory.
15 | 1. Regardless of method, you should now be able to specify the custom theme:
16 |
17 | highlight abc_reprex_rendered.R --out-format rtf --no-trailing-nl \
18 | --encoding=UTF-8 --style kuper > abc_reprex_rendered.rtf
19 | 1. To use the custom theme with reprex, specify it the usual way via the `reprex.highlight.hl_style` option:
20 |
21 | options(reprex.highlight.hl_style = "kuper")
22 |
23 | ## Details of a highlight theme
24 |
25 | A theme file looks like this
26 |
27 | ```
28 | Description = "custom theme for blah blah"
29 |
30 | Default = { Colour="#000000" }
31 | Canvas = { Colour="#ffffff" }
32 | Number = { Colour="#000000" }
33 | Escape = { Colour="#bd8d8b" }
34 | String = { Colour="#bd8d8b" }
35 | StringPreProc = { Colour="#bd8d8b" }
36 | BlockComment = { Colour="#ac2020", Italic=true }
37 | PreProcessor = { Colour="#000000" }
38 | LineNum = { Colour="#555555" }
39 | Operator = { Colour="#000000" }
40 | LineComment = BlockComment
41 |
42 | Keywords = {
43 | { Colour= "#9c20ee", Bold=true },
44 | { Colour= "#208920" },
45 | { Colour= "#0000ff" },
46 | { Colour= "#000000" },
47 | }
48 | ```
49 |
50 | Here's a visual look at Darkbone:
51 |
52 |
53 |
54 | And same for "kuper", the custom theme for this talk:
55 |
56 |
57 |
58 | Explanation:
59 |
60 | * **Default** is used for variable names, package names, and function
61 | arguments.
62 | * **Canvas** doesn't matter for reprex usage but indicates whether theme is
63 | designed for a dark or light background. It controls the background of a
64 | resulting `.rtf` file.
65 | * **Number** and **String** are for numbers and strings and are often the
66 | same.
67 | * **Escape** is for escaped characters, like the `\n` in a string.
68 | * **LineComment** is for comments and, in the case of a rendered reprex,
69 | R output.
70 | * **Operator** is for `(`, the assignment operator, `::`, etc.
71 | * I haven't fiddled with BlockComment, StringPreProc, PreProcessor,
72 | LineNum.
73 | * You can set one color to another, previous one, by name. Example:
74 | `LineNum = BlockComment`.
75 | * Keyword group 1: Things like `function()`, `if()`, and `for()`.
76 | * Keyword group 2: Special constants like `NULL`, `NA`, and `TRUE`.
77 | * Keyword group 3: *isn't actually defined in `r.lang`, so I don't know*
78 | * Keyword group 4: Function names.
79 |
80 | ## `r.lang`
81 |
82 | Found at `/usr/local/Cellar/highlight/3.47/share/highlight/langDefs/r.lang`.
83 |
84 | ```
85 | Description="R"
86 |
87 | Identifiers=[[ [a-zA-Z_\.][\w]* ]]
88 |
89 | Keywords={
90 | { Id=1,
91 | List={"if", "else", "repeat", "while", "function", "for", "in", "next", "break", "ifelse", "switch"},
92 | },
93 | { Id=2,
94 | List={"NULL", "NA", "Inf", "NaN", "TRUE", "T", "FALSE", "F"},
95 | },
96 | { Id=4,
97 | Regex=[[([\w+_\.]+)\s*\(]],
98 | }
99 | }
100 |
101 | Strings={
102 | Delimiter=[["|']],
103 | }
104 |
105 | Comments={
106 | { Block=false,
107 | Delimiter= { [[#]] },
108 | },
109 | }
110 |
111 | Operators=[[\(|\)|\[|\]|\{|\}|\,|\;|\:|\&|<|>|\!|\=|\/|\*|\%|\+|\-|\~|\|]]
112 |
113 | EnableIndentation=true
114 | ```
115 |
116 | ## Official docs
117 |
118 |
119 |
--------------------------------------------------------------------------------
/how-to/highlight-theme-explainer.key:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/how-to/highlight-theme-explainer.key
--------------------------------------------------------------------------------
/how-to/highlight-theme-explainer/highlight-theme-explainer.001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/how-to/highlight-theme-explainer/highlight-theme-explainer.001.png
--------------------------------------------------------------------------------
/how-to/highlight-theme-explainer/highlight-theme-explainer.002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jennybc/debugging/2740b1f83382ade3f0a6687a9cc106b6065fcc60/how-to/highlight-theme-explainer/highlight-theme-explainer.002.png
--------------------------------------------------------------------------------
/how-to/keynote-theme-development.md:
--------------------------------------------------------------------------------
1 | Notes on how Christine and I collaborated on the Keynote deck for my rstudio::conf 2020 debugging talk.
2 |
3 | Christine's responsibility:
4 |
5 | * Create a Keynote theme.
6 | * Make specific slides where art or visual design is critical.
7 |
8 | Christine's work product is **the template deck**:
9 |
10 | * Its theme is The Theme. Keynote themes can't really be managed as first-class objects. A theme that is evolving really has to have a companion/host deck that embeds the theme.
11 | * The template deck also has slides:
12 | - Good examples of key slide types.
13 | - The specific slides mentioned earlier, where there's special art.
14 |
15 | Christine uses Keynote/iCloud collaboration features to share the template deck with Jenny. Various aspects are annoying (but it basically works):
16 |
17 | * To modify the theme, Christine must un-share, do her thing, re-share.
18 | * A shared deck has to stay under a certain size limit. So, we could never collaborate this way on the real talk deck.
19 | * The sharing seems buggy / laggy and we often had to re-share, wait, or temporarily share with "anyone with a link".
20 | * You can look at -- and even edit -- a shared Keynote file on iCloud. But this interface is much less capable than the actual Keynote app. For example, you can't see nonstandard fonts or presenter notes. Only useful for taking a quick glance at something.
21 |
22 | Jenny's work product is **the talk deck**. She regularly integrates new versions of the template deck into the talk deck:
23 |
24 | * Open the current template deck. Literally, open its link, which causes the shared Keynote iCloud deck to open in the local Keynote app.
25 | * *File > Duplicate* then save a local copy of the template deck, with same filename pattern as `.kth` files (below).
26 | * *File > Save Theme*. Use the theme name for version control. Save in a `kth/` directory of the talk's project. Example of how this directory looks after ingesting several versions of the theme (Christine incremented her file name once):
27 |
28 | 20 Jan 08:50 01-20-1_RStudio-Debugging-Template.kth
29 | 21 Jan 11:24 01-21-1_RStudio-Debugging-Template-v2.kth
30 | 23 Jan 09:49 01-23-1_RStudio-Debugging-Template-v2.kth
31 | 23 Jan 10:03 01-23-2_RStudio-Debugging-Template-v2.kth
32 |
33 | * Double click on the most recent `.kth` theme file in Finder to cause Keynote to offer to add the theme to the theme choose. Say yes.
34 | * Exactly once, at the start, Jenny creates a new deck with this theme. This is how the talk deck is initially created. Every slide is created *de novo*, based on talk outline or draft slides made earlier, with a different theme. Do not copy / paste slides from elsewhere, so you don't contaminate this deck with foreign master slides. Be very careful with any form of copy/paste to avoid bringing in foreign fonts or styles.
35 | * Everytime Christine "releases" a new theme deck (an informal event), create a copy of the talk deck. Call it "debugging-bleeding-edge" or similar. Keep the main deck "debugging" safe from the theme experimentation.
36 | * Use *Document > Document* to change the theme of "debugging-bleeding-edge" to the theme saved from the most-recent theme deck.
37 | - If all hell breaks loose, tell Christine about problems and abandon the experiment.
38 | - Usually it applies (fairly) cleanly. Click through the slides, compare to current "debugging" deck. Re-apply any style tweaks that were lost/goofed up. Provide Christine with feedback if we can prevent that on the next iteration.
39 | - If any featured art slides have changed or been added, copy the whole slide from template deck to talk deck.
40 | * Once "debugging-bleeding-edge" is sorted out ... rename "debugging" to "debugging-backup-DATE" or similar. Rename "debugging-bleeding-edge" to "debugging". Lather, rinse, repeat, *ad nauseum*.
41 | - This workflow strongly encourages postponing all *ad hoc* style tweaks to the last possible moment, to avoid having to re-apply them every time the theme updates. Just record the need in presenter notes and implement in a late polishing pass.
42 |
--------------------------------------------------------------------------------
/resources/kuper.theme:
--------------------------------------------------------------------------------
1 | Description = "custom theme for rstudio conf talk"
2 |
3 | Default = { Colour="#A6A6A6" }
4 | Canvas = { Colour="#000000" }
5 | Number = { Colour="#FFB500" }
6 | Escape = { Colour="#808080" }
7 | String = { Colour="#FFB500" }
8 | BlockComment = { Colour="#606080" }
9 | StringPreProc = String
10 | LineComment = BlockComment
11 | Operator = { Colour="#CCCCCC" }
12 | LineNum = BlockComment
13 | PreProcessor = Default
14 | Interpolation = { Colour="#CFA5DB" }
15 |
16 | Keywords = {
17 | { Colour="#34B5E2" },
18 | { Colour="#E0F6FF" },
19 | { Colour="#7ff0a6" },
20 | { Colour="#9fbfaf" },
21 | }
22 |
--------------------------------------------------------------------------------