---
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)
```