In basics we introduced the essential elements that make up a module and then built a super simple module.
On this page, we will build a module for a slightly more complex program while
introducing some more functionalities of outsider
.
figlet
For this walkthrough, we will be creating a slightly more sophisticated module
than om..echo
by building a module for figlet
,
a program that takes textual input and returns ASCII art.
The program itself only takes text input and returns text output. We, however,
will use outsider
to create a module that can either take an input file or
text and then return ASCII art to either the console or to an output file.
Not all command-line programs follow the same standards (different input/output
file arguments, different help pages ...), so outsider
comes with a series of
functions to provide options for developers for how best they want to
encapsulate the program in module form.
(For reference, this module does already exist in the wild: dombennett/om..figlet)
To get started, we will first need our module skeleton.
library(outsider.devtools) flpth <- module_skeleton(repo_user = 'dombennett', program_name = 'figlet', docker_user = 'dombennett', flpth = tempdir(), service = 'github') # folder name where module is stored print(basename(flpth))
## [1] "om..figlet"
Next we will need to develop the Dockerfile! figlet
is easy enough to install
on Ubuntu Linux, we can just use the "apt-get" system to install it. (Here I'm
editing the file from within R to make the process reproducible, you can edit
the file directly with a text editor.)
dockerfile_text <- " # select Ubuntu Linux image FROM ubuntu:latest # Use the 'apt-get' system to install figlet RUN apt-get update && apt-get install -y figlet # set the working_dir/ RUN mkdir /working_dir WORKDIR /working_dir " # write to latest Dockerfile cat(dockerfile_text, file = file.path(flpth, 'inst', 'dockerfiles', 'latest', 'Dockerfile'))
Now, we have our Dockerfile set-up and our module skeleton, we must develop the
R code. The skeleton code for our figlet
-module is currently:
#' @name figlet #' @title figlet #' @description Run figlet #' @param ... Arguments #' @example /examples/example.R #' @export figlet <- function(...) { # convert the ... into a argument list arglist <- arglist_get(...) # create an outsider object: describe the arguments and program otsdr <- outsider_init(pkgnm = 'om..figlet', cmd = 'figlet', arglist = arglist) # run the command run(otsdr) }
The above function would work fine. It would pass any arguments from the user
in R to the figlet
program. But because figlet
does not come with input/output arguments, we will need to hardcode these into our function and
use some if statements and bash to create this functionality. Because this will
require calling several commands via the terminal, we can create a shell script
that we can then pass through outsider
to our container.
function_text <- " #' @name figlet #' @title figlet #' @description Run figlet with input and output files. #' @param arglist Arguments for figlet #' @param input_file Text input file #' @param output_file ASCII art output file #' @details If no input_file, will use arglist argument. #' If no output_file, will print to console. #' @example /examples/example.R #' @export figlet <- function(arglist = arglist_get(...), input_file = NULL, output_file = NULL) { wd <- NULL # construct shell script from arglist arglist <- c('figlet', arglist) if (!is.null(input_file)) { # cat the input_file contents to figlet # (basename is used because on the container, # filepaths cannot be used.) arglist <- c('cat', basename(input_file), '|', arglist) } if (!is.null(output_file)) { # write out the results of figlet to output_file arglist <- c(arglist, '>', basename(output_file)) # determine where returned files should be sent wd <- dirpath_get(output_file) } # write arglist to temp file script <- file.path(tempdir(), 'script.sh') on.exit(file.remove(script)) # ensure script is written in binary format script_cnntn <- file(script, 'wb') cmds <- paste(arglist, collapse = ' ') # debug print print(cmds) write(x = cmds, file = script_cnntn) close(script_cnntn) # initialise outsider container by specifying the command, # the arguments, the files to be sent, and the directory to where # returned files should be sent otsdr <- outsider_init(pkgnm = 'om..figlet', cmd = 'bash', arglist = 'script.sh', wd = wd, files_to_send = c(script, input_file)) # run the command run(otsdr) } " # write to R/functions.R cat(function_text, file = file.path(flpth, 'R', 'functions.R'))
What does
arglist
do? This function takes R objects and converts them into character strings that then be passed to a command-line program. It can take as many arguments as you want. For example, withfiglet
you may want to convert the value ofa
to ASCII art. In this way, you would simply be able to passa
to thefiglet
function rather than spell it out again.What's with the
#'@
? These are the roxygen tags. They determine the documentation of the function. See "Object documentation" for more details.
Ok, let's build the module!
module_build(flpth = flpth)
## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running devtools::document() ... ## ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## Updating om..figlet documentation
## First time using roxygen2. Upgrading automatically...
## Updating roxygen version in /private/var/folders/x9/m8kwpxps2v93xk52zqzm5lkh0000gp/T/RtmpquGDgt/om..figlet/DESCRIPTION
## Writing NAMESPACE
## Loading om..figlet
## Writing NAMESPACE ## Writing figlet.Rd ## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running devtools::install() ... ## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## checking for file ‘/private/var/folders/x9/m8kwpxps2v93xk52zqzm5lkh0000gp/T/RtmpquGDgt/om..figlet/DESCRIPTION’ ... ✓ checking for file ‘/private/var/folders/x9/m8kwpxps2v93xk52zqzm5lkh0000gp/T/RtmpquGDgt/om..figlet/DESCRIPTION’ ## ─ preparing ‘om..figlet’: ## checking DESCRIPTION meta-information ... ✓ checking DESCRIPTION meta-information ## ─ checking for LF line-endings in source and make files and shell scripts ## ─ checking for empty or unneeded directories ## ─ building ‘om..figlet_0.0.1.tar.gz’ ## ## Running /Library/Frameworks/R.framework/Resources/bin/R CMD INSTALL \ ## /var/folders/x9/m8kwpxps2v93xk52zqzm5lkh0000gp/T//RtmpquGDgt/om..figlet_0.0.1.tar.gz --install-tests ## - * installing to library ‘/Library/Frameworks/R.framework/Versions/3.6/Resources/library’ ## | * installing *source* package ‘om..figlet’ ... ## - ** using staged installation ## | ** R ## - ** inst ## | ** byte-compile and prepare package for lazy loading ## - | ** help ## - *** installing help indices ## | ** building package indices ## - ** testing if installed package can be loaded from temporary location ## | ** testing if installed package can be loaded from final location ## - ** testing if installed package keeps a record of temporary installation path ## | * DONE (om..figlet) ## - | ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running docker_build() ## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Command: ## docker build -t dombennett/om_figlet:latest /Library/Frameworks/R.framework/Versions/3.6/Resources/library/om..figlet/dockerfiles/latest ## ..................................................................................................................... ## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running devtools::build_readme() ... ## ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## Building om..figlet readme
Does it work?
library(outsider) figlet <- module_import('figlet', repo = 'dombennett/om..figlet') # without files figlet(arglist = 'hello!')
## [1] "figlet hello!"
# with input file input_file <- file.path(tempdir(), 'input_figlet.txt') cat('This is from the input text file!', file = input_file) figlet(arglist = '', input_file = input_file)
## [1] "cat input_figlet.txt | figlet "
# with output file output_file <- file.path(tempdir(), 'output_figlet.txt') figlet(arglist = 'Into the output file', output_file = output_file)
## [1] "figlet Into the output file > output_figlet.txt"
cat(readLines(con = output_file), sep = '\n')
## ___ _ _ _ _ _ ## |_ _|_ __ | |_ ___ | |_| |__ ___ ___ _ _| |_ _ __ _ _| |_ ## | || '_ \| __/ _ \ | __| '_ \ / _ \ / _ \| | | | __| '_ \| | | | __| ## | || | | | || (_) | | |_| | | | __/ | (_) | |_| | |_| |_) | |_| | |_ ## |___|_| |_|\__\___/ \__|_| |_|\___| \___/ \__,_|\__| .__/ \__,_|\__| ## |_| ## __ _ _ ## / _(_) | ___ ## | |_| | |/ _ \ ## | _| | | __/ ## |_| |_|_|\___| ##
# from input file to output file... with font block! figlet(arglist = c('-f', 'block'), input_file = input_file, output_file = output_file)
## [1] "cat input_figlet.txt | figlet -f block > output_figlet.txt"
cat(readLines(con = output_file), sep = '\n')
## ## _|_|_|_|_| _| _| _| ## _| _|_|_| _|_|_| _|_|_| ## _| _| _| _| _|_| _| _|_| ## _| _| _| _| _|_| _| _|_| ## _| _| _| _| _|_|_| _| _|_|_| ## ## ## ## _|_| ## _| _| _|_| _|_| _|_|_| _|_| ## _|_|_|_| _|_| _| _| _| _| _| ## _| _| _| _| _| _| _| ## _| _| _|_| _| _| _| ## ## ## ## _| _| _| _| ## _|_|_|_| _|_|_| _|_| _|_|_| _|_|_| _| _| _|_|_|_| ## _| _| _| _|_|_|_| _| _| _| _| _| _| _| _| ## _| _| _| _| _| _| _| _| _| _| _| _| ## _|_| _| _| _|_|_| _| _| _| _|_|_| _|_|_| _|_| ## _| ## _| ## ## _| _| _|_| _| _| _| ## _|_|_|_| _|_| _| _| _|_|_|_| _| _| _|_| _| ## _| _|_|_|_| _|_| _| _|_|_|_| _| _| _|_|_|_| _| ## _| _| _| _| _| _| _| _| _| ## _|_| _|_|_| _| _| _|_| _| _| _| _|_|_| _| ## ##
# clean-up file.remove(c(input_file, output_file))
## [1] TRUE TRUE
Ok! So we have a functioning module. But how do we know it's a functioning module? To check our module structure, configuration and functioning we have a few helpful functions.
# make sure the folder structure is correct module_check(flpth = flpth)
## DESCRIPTION found ✓ ## R folder with files found ✓ ## inst found ✓ ## inst/om.yml found ✓ ## inst/dockerfiles found ✓ ## inst/dockerfiles/latest with one Dockerfile found ✓
# check are the names of the module components module_identities(flpth = flpth)
## R package name: 'om..figlet' ## URL: 'https://github.com/dombennett/om..figlet' ## Docker images: 'dombennett/om_figlet:latest'
# test that the module works # module_test(flpth = flpth)
Running module_test
we will find that there is an issue with the module.
module_test
checks the functioning of the code by running the example; our
current example tries to call -h
with figlet
-- which is
an invalid argument. To correct this we can simply updated our example.
example_text <- " library(outsider) figlet <- module_import('figlet', repo = 'dombennett/om..figlet') figlet('hello!') " # write to examples/example.R cat(example_text, file = file.path(flpth, 'examples', 'example.R')) # re-build package module_build(flpth = flpth, build_image = FALSE, verbose = FALSE)
## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running devtools::document() ... ## ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## Updating om..figlet documentation
## Writing NAMESPACE
## Loading om..figlet
## Writing NAMESPACE ## Writing figlet.Rd ## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running devtools::install() ... ## ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## ## Attaching package: 'om..figlet'
## The following object is masked _by_ '.GlobalEnv': ## ## figlet
## ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ## Running devtools::build_readme() ... ## ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## Building om..figlet readme
Now re-running the test ...
module_test(flpth = flpth)
## Running /Library/Frameworks/R.framework/Resources/bin/R CMD INSTALL \ ## /private/var/folders/x9/m8kwpxps2v93xk52zqzm5lkh0000gp/T/RtmpquGDgt/om..figlet --install-tests ## * installing to library ‘/Library/Frameworks/R.framework/Versions/3.6/Resources/library’ ## - * installing *source* package ‘om..figlet’ ... ## | ** using staged installation ## - ** R ## | ** inst ## - ** byte-compile and prepare package for lazy loading ## | - ** help ## | *** installing help indices ## - ** building package indices ## | ** testing if installed package can be loaded from temporary location ## - ** testing if installed package can be loaded from final location ## | ** testing if installed package keeps a record of temporary installation path ## - * DONE (om..figlet) ## | -
## ## Attaching package: 'om..figlet'
## The following object is masked _by_ '.GlobalEnv': ## ## figlet
## [1] "figlet hello!"
## Removing package from '/Library/Frameworks/R.framework/Versions/3.6/Resources/library' ## (as 'lib' is unspecified)
## Yikes! The module works! You are super-excellent!
Nice! The package passes. That means we can upload the package to GitHub and Docker-Hub using the same functions as we saw in "basic".
module_upload(flpth = flpth, code_sharing = TRUE, dockerhub = TRUE)
Delete it all
module_uninstall(repo = 'om..figlet') unlink(x = flpth, recursive = TRUE, force = TRUE)
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.