# Motivation

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 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.

- 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

    library(sinew)

    pkg_dir <- file.path(tempdir(),'pkg')
    usethis::create_package(path = pkg_dir, open = FALSE)
    #> ✓ Creating '/var/folders/kx/t4h_mm1910sb7vhm_gnfnx2c0000gn/T/Rtmp79mfWP/pkg/'
    #> ✓ Setting active project to '/private/var/folders/kx/t4h_mm1910sb7vhm_gnfnx2c0000gn/T/Rtmp79mfWP/pkg'
    #> ✓ Creating 'R/'
    #> ✓ Writing 'DESCRIPTION'
    #> Package: pkg
    #> Title: What the Package Does (One Line, Title Case)
    #> Version: 0.0.0.9000
    #> Authors@R (parsed):
    #>     * First Last <first.last@example.com> [aut, cre] (YOUR-ORCID-ID)
    #> Description: What the package does (one paragraph).
    #> License: `use_mit_license()`, `use_gpl3_license()` or friends to pick a
    #>     license
    #> Encoding: UTF-8
    #> Roxygen: list(markdown = TRUE)
    #> RoxygenNote: 7.1.2
    #> ✓ Writing 'NAMESPACE'
    withr::with_dir(pkg_dir, usethis::use_data_raw(open = FALSE))
    #> ✓ Creating 'data-raw/'
    #> ✓ Adding '^data-raw$' to '.Rbuildignore'
    #> ✓ Writing 'data-raw/DATASET.R'
    #> • Finish the data preparation script in 'data-raw/DATASET.R'
    #> • Use `usethis::use_data()` to add prepared data to package
    withr::with_dir(pkg_dir, usethis::use_mit_license(copyright_holder = "John Doe"))
    #> ✓ Setting License field in DESCRIPTION to 'MIT + file LICENSE'
    #> ✓ Writing 'LICENSE'
    #> ✓ Writing 'LICENSE.md'
    #> ✓ Adding '^LICENSE\\.md$' to '.Rbuildignore'
    withr::with_dir(pkg_dir, usethis::use_roxygen_md())

    withr::with_dir(pkg_dir,fs::dir_tree())
    #> .
    #> ├── DESCRIPTION
    #> ├── LICENSE
    #> ├── LICENSE.md
    #> ├── NAMESPACE
    #> ├── R
    #> └── data-raw
    #>     └── DATASET.R

Lets create a temporary file that will contain the script.

    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.

    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

    withr::with_dir(pkg_dir,fs::dir_tree())
    #> .
    #> ├── DESCRIPTION
    #> ├── LICENSE
    #> ├── LICENSE.md
    #> ├── NAMESPACE
    #> ├── R
    #> │   ├── yy.R
    #> │   └── zz.R
    #> └── data-raw
    #>     ├── DATASET.R
    #>     ├── body.R
    #>     └── file96225394b61.R

Click to see body.R

    yy(6)
    zz(30,3)

Click to see yy.R

    #some comment
    yy <- function(a=4){
      head(runif(10),a)
      # a comment
    }

Click to see zz.R

    zz <- function(v=10,a=8){
      head(runif(v),a)
    }

## 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.

    sinew::pretty_namespace(pkg_dir_R,overwrite = TRUE)
    #>
    #> functions changed in '/var/folders/kx/t4h_mm1910sb7vhm_gnfnx2c0000gn/T//Rtmp79mfWP/pkg/R/yy.R':
    #>
    #> ✓: found, x: not found, (): instances, ☒: user intervention
    #>
    #>  ✓ utils::head  (1)
    #>  ✓ stats::runif (1)
    #>
    #>
    #> functions changed in '/var/folders/kx/t4h_mm1910sb7vhm_gnfnx2c0000gn/T//Rtmp79mfWP/pkg/R/zz.R':
    #>
    #> ✓: found, x: not found, (): instances, ☒: user intervention
    #>
    #>  ✓ utils::head  (1)
    #>  ✓ stats::runif (1)

So now we have separate files with functions appropriatly associated with namespaces, and now we can add roxygen2 headers.

## Documentation

Now we are ready to create the function documentation using roxygen2. We use `sinew::makeOxygen` to create a skeleton for roxygen2 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`.

    sinew::sinew_opts$set(markdown_links = TRUE)

    sinew::makeOxyFile(input = pkg_dir_R, overwrite = TRUE, verbose = FALSE)
    #> #' @title FUNCTION_TITLE
    #> #' @description FUNCTION_DESCRIPTION
    #> #' @param a PARAM_DESCRIPTION, Default: 4
    #> #' @return OUTPUT_DESCRIPTION
    #> #' @details DETAILS
    #> #' @examples
    #> #' \dontrun{
    #> #' if(interactive()){
    #> #'  #EXAMPLE1
    #> #'  }
    #> #' }
    #> #' @seealso
    #> #'  [head][utils::head]
    #> #'  [runif][stats::runif]
    #> #' @rdname yy
    #> #' @export
    #> #' @importFrom utils head
    #> #' @importFrom stats runif
    #> #' @title FUNCTION_TITLE
    #> #' @description FUNCTION_DESCRIPTION
    #> #' @param v PARAM_DESCRIPTION, Default: 10
    #> #' @param a PARAM_DESCRIPTION, Default: 8
    #> #' @return OUTPUT_DESCRIPTION
    #> #' @details DETAILS
    #> #' @examples
    #> #' \dontrun{
    #> #' if(interactive()){
    #> #'  #EXAMPLE1
    #> #'  }
    #> #' }
    #> #' @seealso
    #> #'  [head][utils::head]
    #> #'  [runif][stats::runif]
    #> #' @rdname zz
    #> #' @export
    #> #' @importFrom utils head
    #> #' @importFrom stats runif

Click to see yy.R

    #some comment
    #' @title FUNCTION_TITLE
    #' @description FUNCTION_DESCRIPTION
    #' @param a PARAM_DESCRIPTION, Default: 4
    #' @return OUTPUT_DESCRIPTION
    #' @details DETAILS
    #' @examples
    #' \dontrun{
    #' if(interactive()){
    #'  #EXAMPLE1
    #'  }
    #' }
    #' @seealso
    #'  [head][utils::head]
    #'  [runif][stats::runif]
    #' @rdname yy
    #' @export
    #' @importFrom utils head
    #' @importFrom stats runif
    yy <- function(a=4){
      utils::head(stats::runif(10),a)
      # a comment
    }

Click to see zz.R

    #' @title FUNCTION_TITLE
    #' @description FUNCTION_DESCRIPTION
    #' @param v PARAM_DESCRIPTION, Default: 10
    #' @param a PARAM_DESCRIPTION, Default: 8
    #' @return OUTPUT_DESCRIPTION
    #' @details DETAILS
    #' @examples
    #' \dontrun{
    #' if(interactive()){
    #'  #EXAMPLE1
    #'  }
    #' }
    #' @seealso
    #'  [head][utils::head]
    #'  [runif][stats::runif]
    #' @rdname zz
    #' @export
    #' @importFrom utils head
    #' @importFrom stats runif
    zz <- function(v=10,a=8){
      utils::head(stats::runif(v),a)
    }

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.

    sinew::make_import(pkg_dir_R,format = 'description',desc_loc = pkg_dir)
    #> Imports:
    #>  stats,
    #>  utils

## 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 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 header is now out of synch with the current script.

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

    moga(file.path(pkg_dir_R,'yy.R'),overwrite = TRUE)
    #> #' @title FUNCTION_TITLE
    #> #' @description FUNCTION_DESCRIPTION
    #> #' @param a numeric, set the head to trim from random unif Default: 4
    #> #' @param b PARAM_DESCRIPTION, Default: 2
    #> #' @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

Click to see yy.R

    #some comment
    #' @title FUNCTION_TITLE
    #' @description FUNCTION_DESCRIPTION
    #' @param a numeric, set the head to trim from random unif Default: 4
    #' @param b PARAM_DESCRIPTION, Default: 2
    #' @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
    }

## Oxygenize and Check

### Oxygenize

    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))
    #> ℹ Loading pkg
    #> Writing NAMESPACE
    #> Writing NAMESPACE
    #> Writing yy.Rd
    #> Writing zz.Rd

### R CMD Check

    check_res <- rcmdcheck::rcmdcheck(path = pkg_dir, args = '--as-cran',quiet = TRUE)

### Cleanup

    unlink(pkg_dir, recursive = TRUE, force = TRUE)