- Part 0. Proposal
- Part I. Work out functionality π§ β
- Return chunk names
- Code from chunks to files
- Part II. Packaging and documentation π§ β
- Appendix: Reports, Environment
Proposing the {knitrExtra} package! π¦
The goal of {knitrExtra} is to make some of my favorite functionality a little more accessible and usable interactively (in RStudio, Iβm pretty much piggy backing on Kelly Bodwinβs vision and work on this - led the way as to how-to w/ rstudio API).
Without the package, we live in the effort-ful world that follows π:
# grabbing code from a chunk:
knitr::knit_code$get("chunk_code_get_static") |> as.vector()
# getting the names of chunks:
knitr::knit_code$get() |> names()
# sending code from a chunk to a stand alone file
knitr::knit_code$get("chunk_code_get_static") |>
as.vector() |>
writeLines("R/chunk_code_get_static.R")
And importantly, we canβt access chunk names from within a live .Rmd, or the code from chunks in the document we are working on. But this kind of interactivity can be useful.
With the {knitrExtra} package, weβll live in a different world (π¦ π¦ π¦) where the task is a snap π«° and interactivity is provided (from within RStudio IDE - Borrowing from Kelly Bodwinβs approach in flair):
Proposed API:
library(knitrExtra)
knitrExtra::chunk_code_get("chunk_code_get_static")
knitrExtra::chunk_names_get()
knitrExtra::chunk_to_r("chunk_code_get_static")
First, a helper function from the lightparser
parse_current_rmd <- function(){
ed <- rstudioapi::getSourceEditorContext()
source <- ed$contents
tmp <- tempfile()
writeLines(source, tmp)
lightparser::split_to_tbl(tmp)
}
parse_current_rmd()
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
#> # A tibble: 98 Γ 8
#> type label params text code heading heading_level section
#> <chr> <chr> <list> <nam> <lis> <chr> <dbl> <chr>
#> 1 yaml <NA> <named list> <lgl> <lgl> <NA> NA <NA>
#> 2 inline <NA> <lgl [1]> <chr> <lgl> <NA> NA <NA>
#> 3 block unnamed-chunk⦠<named list> <lgl> <chr> <NA> NA <NA>
#> 4 inline <NA> <lgl [1]> <chr> <lgl> <NA> NA <NA>
#> 5 heading <NA> <lgl [1]> <chr> <lgl> "Part β¦ 1 "Part β¦
#> 6 inline <NA> <lgl [1]> <chr> <lgl> <NA> NA "Part β¦
#> 7 heading <NA> <lgl [1]> <chr> <lgl> "grabbβ¦ 1 "grabbβ¦
#> 8 inline <NA> <lgl [1]> <chr> <lgl> <NA> NA "grabbβ¦
#> 9 heading <NA> <lgl [1]> <chr> <lgl> "gettiβ¦ 1 "gettiβ¦
#> 10 inline <NA> <lgl [1]> <chr> <lgl> <NA> NA "gettiβ¦
#> # βΉ 88 more rows
#' Title
#'
#' @param chunk_name a character string with the name of the chunk of interest
#'
#' @return a vector of the code contained in the referenced chunk
#' @export
#'
#' @examples
chunk_code_get <- function(chunk_name = "chunk_code_get"){
rmd_df <- parse_current_rmd()
chunk_info <- subset(rmd_df, rmd_df$label == chunk_name)
chunk_info[,"code"][[1]][[1]] |> as.vector()
}
If we knit our document weβll see that these functions work
chunk_code_get("chunk_code_get")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
#> [1] "#' Title"
#> [2] "#'"
#> [3] "#' @param chunk_name a character string with the name of the chunk of interest"
#> [4] "#'"
#> [5] "#' @return a vector of the code contained in the referenced chunk"
#> [6] "#' @export "
#> [7] "#'"
#> [8] "#' @examples"
#> [9] "chunk_code_get <- function(chunk_name = \"chunk_code_get\"){"
#> [10] " "
#> [11] " rmd_df <- parse_current_rmd()"
#> [12] " "
#> [13] " chunk_info <- subset(rmd_df, rmd_df$label == chunk_name) "
#> [14] " "
#> [15] " chunk_info[,\"code\"][[1]][[1]] |> as.vector()"
#> [16] " "
#> [17] "}"
First we just alias knitr::all_label() to a function thatβs named more in line with others in this package.
#' Title
#'
#' @return
#' @export
#'
#' @examples
chunk_names_get <- function(){
rmd_df <- parse_current_rmd()
chunks_info <- subset(rmd_df, !is.na(rmd_df$label)) |> as.data.frame()
chunks_info[,"label"]
}
chunk_names_get()
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
#> [1] "unnamed-chunk-1" "parse_current_rmd"
#> [3] "unnamed-chunk-2" "chunk_code_get"
#> [5] "unnamed-chunk-3" "chunk_names_get"
#> [7] "unnamed-chunk-4" "chunk_to_dir"
#> [9] "unnamed-chunk-5" "chunk_variants_to_dir"
#> [11] "unnamed-chunk-6" "unnamed-chunk-7"
#> [13] "unnamed-chunk-8" "unnamed-chunk-9"
#> [15] "unnamed-chunk-10" "test_calc_times_two_works"
#> [17] "unnamed-chunk-11" "unnamed-chunk-12"
#> [19] "unnamed-chunk-13" "unnamed-chunk-14"
#> [21] "unnamed-chunk-15" "unnamed-chunk-16"
It is nice to be able to grab code from chunks and send them to files
for the purpose of building packages from a single file like a readme.
chunk_to_dir
exists for this purpose. The defaults are that you are
sending code from a package readme to an .R file in the R package
folder.
#' Title
#'
#' @param chunk_name
#' @param dir
#' @param extension
#'
#' @return
#' @export
#'
#' @examples
chunk_to_dir <- function (chunk_name, dir = "R/", extension = ".R")
{
for (i in 1:length(chunk_name)) {
writeLines(
paste(chunk_code_get(chunk_name = chunk_name[i]),
collapse = "\n"),
con = paste0(dir, "/", chunk_name[i], extension))
}
}
chunk_to_r <- function(chunk_name){
chunk_to_dir(chunk_name = chunk_name)
}
chunk_to_tests_testthat <- function (chunk_name)
{
chunk_to_dir(chunk_name = chunk_name, dir = "tests/testthat/")
}
chunk_to_dir("chunk_to_dir")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
Finally, functionality (and the implementation) that Iβm uncertain about
is chunk_variants_to_dir()
This is an interesting meta programming
solution, perhaps.
chunk_variants_to_dir <- function (chunk_name, chunk_name_suffix = "_variants",
file_name = NULL,
dir = "R/", replace1, replacements1, replace2 = NULL, replacements2 = NULL,
replace3 = NULL, replacements3 = NULL, replace4 = NULL, replacements4 = NULL) {
template <- chunk_code_get(chunk_name)
script_contents <- c()
if (is.null(file_name)) {
file_name <- paste0(chunk_name, chunk_name_suffix, ".R")
}
for (i in 1:length(replacements1)) {
template_mod <- stringr::str_replace_all(template, replace1,
replacements1[i])
if (!is.null(replace2)) {
template_mod <- stringr::str_replace_all(template_mod,
replace2, replacements2[i])
}
if (!is.null(replace3)) {
template_mod <- stringr::str_replace_all(template_mod,
replace3, replacements3[i])
}
if (!is.null(replace4)) {
template_mod <- stringr::str_replace_all(template_mod,
replace4, replacements4[i])
}
script_contents <- c(script_contents, template_mod)
}
writeLines(script_contents, paste0(dir, file_name))
}
# devtools::create(".") # Bit 1. 1X
### Bit 2a: dependencies to functions using '::' syntax to pkg functions
usethis::use_package("rstudioapi") # Bit 2b: document dependencies
usethis::use_package("stringr") # Bit 2b: document dependencies
usethis::use_dev_package(package = "lightparser", remote = "ThinkR-open/lightparser")
chunk_names_get()
chunk_to_r("parse_current_rmd")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
chunk_to_r("chunk_code_get")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
chunk_to_r("chunk_names_get")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
chunk_to_r("chunk_to_dir")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
chunk_to_r("chunk_variants_to_dir")
#> It seems you are currently knitting a Rmd/Qmd file. The parsing of the file will be done in a new R session.
# Bit 3: send code chunk with function to R folder
devtools::check(pkg = ".") # Bit 4: check that package is minimally viable; document's as a pre-step
devtools::install(pkg = ".", upgrade = "never") # Bit 5: install package locally
usethis::use_lifecycle_badge("experimental") # Bit 6: add lifecycle badge
# Bit 7 (below): Write traditional readme
# Bit 8: Compile readme
# Bit 9: Push to githup
# Bit 10: listen and iterate
The goal of the {xxxx} package is to β¦
Install package with:
remotes::install_github("GithubCoolUser/mypacakge")
Once functions are exported you can remove go to two colons, and when things are are really finalized, then go without colons (and rearrange your readmeβ¦)
library(knitrExtra) ##<< change to your package name here
knitrExtra::chunk_names_get()
knitrExtra::chunk_code_get("chunk_to_dir")
knitrExtra:::parse_current_rmd()
getNamespaceExports("knitrExtra")
Bit A. Added a description and author information in the DESCRIPTION file π§ β
Bit B. Added roxygen skeleton? π§ β
Bit C. Chosen a license? π§ β
usethis::use_mit_license()
Bit D. Settle on examples. Put them in the roxygen skeleton and readme. π§ β
Bit E. Written formal tests of functions and save to test that folders π§ β
That would look like thisβ¦
library(testthat)
test_that("calc times 2 works", {
expect_equal(times_two(4), 8)
expect_equal(times_two(5), 10)
})
readme2pkg::chunk_to_tests_testthat("test_calc_times_two_works")
devtools::check(pkg = ".")
readLines("DESCRIPTION")
Here I just want to print the packages and the versions
all <- sessionInfo() |> print() |> capture.output()
all[11:17]
#> [1] ""
#> [2] "attached base packages:"
#> [3] "[1] stats graphics grDevices utils datasets methods base "
#> [4] ""
#> [5] "loaded via a namespace (and not attached):"
#> [6] " [1] lightparser_0.0.1 ps_1.7.2 fansi_1.0.5 utf8_1.2.3 "
#> [7] " [5] digest_0.6.31 R6_2.5.1 lifecycle_1.0.3 magrittr_2.0.3 "
devtools::check(pkg = ".")
fs::dir_tree(recurse = T)
#> .
#> βββ DESCRIPTION
#> βββ NAMESPACE
#> βββ R
#> β βββ chunk_code_get.R
#> β βββ chunk_names_get.R
#> β βββ chunk_to_dir.R
#> β βββ chunk_variants_to_dir.R
#> β βββ parse_current_rmd.R
#> βββ README.Rmd
#> βββ README.md
#> βββ knitrExtra.Rproj
#> βββ man
#> β βββ chunk_code_get.Rd
#> β βββ chunk_names_get.Rd
#> β βββ chunk_to_dir.Rd
#> βββ readme2pkg.template.Rproj