--- title: "Motivation" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Motivation} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` How can `sinew` help you? `sinew` automates tasks that are part of `R` package documentation and maintance in order to help developers consistently create robust [roxygen2](https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.html) documentation and pass `R CMD check --as-cran`. ## Quick Turnaraound Two common scenarios arise in package development - You start a new project and open a file to develop your idea. Many functions later at the end of the day you look up and you have created a mess of a file. ```{r, echo = FALSE} if(file.exists('../reference/figures/Guy-Tangled-in-Lights.jpg')){ knitr::include_graphics('../reference/figures/Guy-Tangled-in-Lights.jpg', dpi = 100) }else{ knitr::include_graphics('../man/figures/Guy-Tangled-in-Lights.jpg', dpi = 100) } ``` - You recieve a mammoth 10,000 line uncommented file to decipher for QC - good luck. `sinew` can help turn the around that headache into a `CRAN` ready package in a few short steps 1. Create a package project in `RStudio`. 2. Place the file in the `data-raw` folder. 3. Run `sinew::untangle` on the large script with the destination directory `pkg/R`. This will separate the core functions in the body into single function files (named as the function) and keep the body in `body.R` 4. Run `sinew::pretty_namespace` to append namespaces in the function scripts. 5. Run `sinew::makeOxyFile` to populate the Roxygen2 skeleton. 6. Run `sinew::make_import` to populate the `Imports` field of the `DESCRIPTION` file. This should get you far enough to make the impossible problem of understanding what is in that file to a manageable task, with the added benefit of producing a new package ready for distribution. ## Reproducible Example Lets use a reproducible example - The goal is to convert raw script into a package. The script includes two functions `yy` and `zz` and some general script that uses them To start we create a package with [usethis](https://github.com/r-lib/usethis) ```{r} library(sinew) ``` ```{r} pkg_dir <- file.path(tempdir(),'pkg') usethis::create_package(path = pkg_dir, open = FALSE) withr::with_dir(pkg_dir, usethis::use_data_raw(open = FALSE)) withr::with_dir(pkg_dir, usethis::use_mit_license(copyright_holder = "John Doe")) withr::with_dir(pkg_dir, usethis::use_roxygen_md()) ``` ```{r} withr::with_dir(pkg_dir,fs::dir_tree()) ``` Lets create a temporary file that will contain the script. ```{r} example_file <- tempfile(fileext = '.R',tmpdir = file.path(pkg_dir, 'data-raw')) example_lines <- "#some comment yy <- function(a=4){ head(runif(10),a) # a comment } zz <- function(v=10,a=8){ head(runif(v),a) } yy(6) zz(30,3) " cat(example_lines,file = example_file) ``` ## Untangling Files One of the first tasks for new developers is to move from long scripts that are intertwined with functions and body code into single function files in a R subdirectory and a clean body script that is easier to read. This task is probably a non-starter if you have more than a few hundered lines of code. This is where `sinew::untangle` can save you time. `sinew::untangle` will separate the long script into single function files in a subdirectory and keep the body script intact. ```{r} pkg_dir_R <- file.path(pkg_dir,'R') sinew::untangle(file = example_file, dir.out = pkg_dir_R, dir.body = file.path(pkg_dir, 'data-raw')) ``` As we can see we got three new files. - `body.R` in the data-raw directory - `yy.R` in the R subdirectory - `zz.R` in the `R` subdirectory ```{r} withr::with_dir(pkg_dir,fs::dir_tree()) ``` ```{r, echo = FALSE} details::details(file.path(pkg_dir, 'data-raw','body.R'), summary = 'Click to see body.R') ``` ```{r, echo = FALSE} details::details(file.path(pkg_dir_R,'yy.R'), summary = 'Click to see yy.R') ``` ```{r, echo = FALSE} details::details(file.path(pkg_dir_R,'zz.R'), summary = 'Click to see zz.R') ``` ## Namespacing It has become common practice to use the namespace in function calls, and it is obligatory in order to pass a cran check. But, not everyone does it and if you’re not use to it, it’s a pain to go back and update your script. This is where `sinew::pretty_namespace` comes in. This function will go through your script and attach namespaces for you, with the same logic as the search path. ```{r} sinew::pretty_namespace(pkg_dir_R,overwrite = TRUE) ``` So now we have separate files with functions appropriatly associated with namespaces, and now we can add [roxygen2](https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.html) headers. ## Documentation Now we are ready to create the function documentation using [roxygen2](https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.html). We use `sinew::makeOxygen` to create a skeleton for [roxygen2](https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.html) documentation. This function returns a skeleton that includes title, description, return, import and other fields populated with information scraped from the function script. We can also run `sinew::makeOxygen` in batch mode using `sinew::makeOxyfile`. ```{r} sinew::sinew_opts$set(markdown_links = TRUE) ``` ```{r} sinew::makeOxyFile(input = pkg_dir_R, overwrite = TRUE, verbose = FALSE) ``` ```{r, echo = FALSE} details::details(file.path(pkg_dir_R,'yy.R'), summary = 'Click to see yy.R') ``` ```{r, echo = FALSE} details::details(file.path(pkg_dir_R,'zz.R'), summary = 'Click to see zz.R') ``` The premise of `sinew::makeOxygen` is to expand on the default skeleton in `RStudio`, so basic fields are in the output by default. Each field is given with a relevant placeholder giving a hint what is expected. The following is the meat add to these bones: - param default values: - If a default value is set for a function parameter it will be added to the end `@param` line. - import/importFrom - The package scrapes the script with make_import looking for declared namespaces to create the proper calls for `@import` and `@importFrom` which are placed at the bottom of the output. - seealso - linking to other packages is also taken care of when adding the field `@seealso`. Any functions that are included in `@importFrom` will have a link to them by default. ## DESCRIPTION FILE It is also important to update the package `DESCRIPTION` file `Imports` field. This can be done for you with `sinew::make_import`, by either creating a new `Imports` field or updating an existing one. ```{r} sinew::make_import(pkg_dir_R,format = 'description',desc_loc = pkg_dir) ``` ## Update documentation An important part of maintaining a package is keeping the documentation updated. Using `sinew::moga` we can achieve this painlessly. `sinew::moga` runs the same underlying script as `sinew::makeOxygen` but appends new information found into the current [roxygen2](https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.html) header instead of creating a new one. Lets say we updated `yy.R` to include another param and used another function from the `stats` package. So the [roxygen2](https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.html) header is now out of synch with the current script. ```{r} new_yy <- "#some comment #' @title FUNCTION_TITLE #' @description FUNCTION_DESCRIPTION #' @param a numeric, set the head to trim from random unif Default: 4 #' @return OUTPUT_DESCRIPTION #' @details DETAILS #' @examples #' \\dontrun{ #' if(interactive()){ #' #EXAMPLE1 #' } #' } #' @seealso #' \\code{\\link[utils]{head}} #' \\code{\\link[stats]{runif}} #' @rdname yy #' @export #' @author Jonathan Sidi #' @importFrom utils head #' @importFrom stats runif yy <- function(a=4,b=2){ x <- utils::head(stats::runif(10*b),a) stats::quantile(x,probs=.95) # a comment }" cat(new_yy, file = file.path(pkg_dir_R,'yy.R')) ``` ```{r} moga(file.path(pkg_dir_R,'yy.R'),overwrite = TRUE) ``` ```{r, echo = FALSE} details::details(file.path(pkg_dir_R,'yy.R'), summary = 'Click to see yy.R') ``` ## Oxygenize and Check ### Oxygenize ```{r} r_env_vars <- function () { vars <- c(R_LIBS = paste(.libPaths(), collapse = .Platform$path.sep), CYGWIN = "nodosfilewarning", R_TESTS = "", R_BROWSER = "false", R_PDFVIEWER = "false") if (is.na(Sys.getenv("NOT_CRAN", unset = NA))) { vars[["NOT_CRAN"]] <- "true" } vars } withr::with_envvar(r_env_vars(), roxygen2::roxygenise(pkg_dir)) ``` ### R CMD Check ```{r,eval=interactive()} check_res <- rcmdcheck::rcmdcheck(path = pkg_dir, args = '--as-cran',quiet = TRUE) ``` ```{r,echo=FALSE,eval=interactive()} details::details(check_res,summary = 'Check Results', open = TRUE) ``` ### Cleanup ```{r} unlink(pkg_dir, recursive = TRUE, force = TRUE) ```