123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- #' Dockerize R Markdown Documents
- #'
- #' Generate \code{Dockerfile} for R Markdown documents.
- #' Rabix is supported if there is certain metadata in the R Markdown
- #' document: the function will generate a \code{Rabixfile} containing
- #' the parsed running parameters under the output directory.
- #'
- #' After running \link{lift}, run \link{drender} on the document to
- #' render the Dockerized R Markdown document using Docker containers.
- #' See \code{vignette('liftr-intro')} for details about the extended
- #' YAML front-matter metadata format used by liftr.
- #'
- #' @param input Input (R Markdown or Shiny R markdown) file or shiny app folder.
- #' @param output_dir Directory to output \code{Dockerfile}.
- #' If not provided, will be the same directory as \code{input}.
- #' @param dockerfile a Dockerfile path, if not, use our template.
- #' @param ... Extra arguments passed to liftShinyApp function.
- #'
- #' @return \code{Dockerfile} (and \code{Rabixfile} if possible).
- #'
- #' @export lift
- #'
- #' @importFrom knitr knit
- #' @importFrom yaml yaml.load as.yaml
- #' @importFrom rsconnect appDependencies
- #'
- #' @examples
- #' # 1. Dockerized R Markdown document
- #' dir_docker = paste0(tempdir(), '/lift_docker/')
- #' dir.create(dir_docker)
- #' file.copy(system.file("examples/docker.Rmd", package = "liftr"), dir_docker)
- #' # use lift() to parse Rmd and generate Dockerfile
- #' lift(paste0(dir_docker, "docker.Rmd"))
- #' # view generated Dockerfile
- #' readLines(paste0(dir_docker, "Dockerfile"))
- #'
- #' # 2. Dockerized R Markdown document with Rabix options
- #' dir_rabix = paste0(tempdir(), '/lift_rabix/')
- #' dir.create(dir_rabix)
- #' file.copy(system.file("template/rabix.Rmd", package = "liftr"), dir_rabix)
- #' lift(input = paste0(dir_rabix, "rabix.Rmd"))
- #' # view generated Dockerfile
- #' readLines(paste0(dir_rabix, "Dockerfile"))
- #' # view generated Rabixfile
- #' readLines(paste0(dir_rabix, "Rabixfile"))
- lift = function(input = NULL, output_dir = NULL, dockerfile = NULL, ...) {
- if (is.null(input))
- stop('missing input file')
- if(!is.na(file.info(input)$isdir) && file.info(input)$isdir){
- if(is_shinyapp(input)){
- message("parsing shiny app dependecies and genrate liftr file ...")
- lift_shinyapp(input, output_dir = output_dir, ...)
- return()
- }else{
- stop("your input folder is not a shinyapp folder")
- }
- }else{
- ## treat as file
- if (!file.exists(input)){
- stop('input file or shiny app folder does not exist')
- }
- }
- opt_all_list = parse_rmd(input)
- # liftr options handling
- if (is.null(opt_all_list$liftr))
- stop('Cannot find `liftr` option in file header')
- opt_list = opt_all_list$liftr
- # base image
- if (!is.null(opt_list$from)) {
- liftr_from = opt_list$from
- } else {
- liftr_from = 'rocker/r-base:latest'
- }
- # maintainer name
- if (!is.null(opt_list$maintainer)) {
- liftr_maintainer = opt_list$maintainer
- } else {
- stop('Cannot find `maintainer` option in file header')
- }
- if (!is.null(opt_list$maintainer_email)) {
- liftr_maintainer_email = opt_list$maintainer_email
- } else {
- stop('Cannot find `maintainer_email` option in file header')
- }
- # system dependencies
- if (!is.null(opt_list$syslib)) {
- liftr_syslib =
- paste(readLines(system.file('template/syslib.Rmd', package = 'liftr')),
- paste(opt_list$syslib, collapse = ' '), sep = ' ')
- } else {
- liftr_syslib = NULL
- }
- # texlive
- if (!is.null(opt_list$latex)) {
- if (opt_list$latex == TRUE) {
- liftr_texlive =
- paste(readLines(system.file('template/texlive.Rmd', package = 'liftr')),
- collapse = '\n')
- } else {
- liftr_texlive = NULL
- }
- } else {
- liftr_texlive = NULL
- }
- # pandoc
- # this solves https://github.com/road2stat/liftr/issues/12
- if (is_from_bioc(liftr_from) | is_from_rstudio(liftr_from)) {
- liftr_pandoc = NULL
- } else {
- if (!is.null(opt_list$pandoc)) {
- if (opt_list$pandoc == FALSE) {
- liftr_pandoc = NULL
- } else {
- liftr_pandoc = paste(readLines(
- system.file('template/pandoc.Rmd', package = 'liftr')), collapse = '\n')
- }
- } else {
- liftr_pandoc = NULL
- ## liftr_pandoc = paste(readLines(
- ## system.file('template/pandoc.Rmd', package = 'liftr')), collapse = '\n')
- }
- }
- # Factory packages
- liftr_factorypkgs = c('devtools', 'knitr', 'rmarkdown', 'shiny', 'RCurl', 'rsconnect')
- liftr_factorypkg = quote_str(liftr_factorypkgs)
- # CRAN packages
- if (!is.null(opt_list$cranpkg)) {
- liftr_cranpkgs = quote_str(opt_list$cranpkg)
- tmp = tempfile()
- invisible(knit(input = system.file('template/cranpkg.Rmd', package = 'liftr'),
- output = tmp, quiet = TRUE))
- liftr_cranpkg = readLines(tmp)
- } else {
- liftr_cranpkg = NULL
- }
- # Bioconductor packages
- if (!is.null(opt_list$biocpkg)) {
- liftr_biocpkgs = quote_str(opt_list$biocpkg)
- tmp = tempfile()
- invisible(knit(input = system.file('template/biocpkg.Rmd', package = 'liftr'),
- output = tmp, quiet = TRUE))
- liftr_biocpkg = readLines(tmp)
- } else {
- liftr_biocpkg = NULL
- }
- # GitHub packages
- if (!is.null(opt_list$ghpkg)) {
- liftr_ghpkgs = quote_str(opt_list$ghpkg)
- tmp = tempfile()
- invisible(knit(input = system.file('template/ghpkg.Rmd', package = 'liftr'),
- output = tmp,
- quiet = TRUE))
- liftr_ghpkg = readLines(tmp)
- } else {
- liftr_ghpkg = NULL
- }
- # extra: plain docker file line, like ADD, COPY, CMD etc
- if (!is.null(opt_list$extra)) {
- liftr_extra = opt_list$extra
- if(get_type(input) == "shinydoc"){
- liftr_extra = c(liftr_extra,
- paste0("ADD ", basename(input), " /srv/shiny-server/"))
- }
- } else {
- if(get_type(input) == "shinydoc"){
- liftr_extra = paste0("ADD ", basename(input), " /srv/shiny-server/")
- }else{
- liftr_extra = NULL
- }
- }
- # write Dockerfile
- if (is.null(output_dir)) output_dir = file_dir(input)
- if(is.null(dockerfile)){
- dockerfile = system.file('template/Dockerfile.Rmd', package = 'liftr')
- }
- .out.dockerfile = paste0(normalizePath(output_dir), '/Dockerfile')
- message("Dockerfile:", .out.dockerfile)
- invisible(knit(dockerfile,
- output = .out.dockerfile,
- quiet = TRUE))
- # handling rabix info
- if (!is.null(opt_list$rabix)) {
- if (opt_list$rabix == TRUE) {
- if (is.null(opt_list$rabix_d))
- stop('Cannot find `rabix_d` option in file header')
- liftr_rabix_d = paste0('\"', normalizePath(opt_list$rabix_d,
- mustWork = FALSE), '\"')
- if (is.null(opt_list$rabix_json))
- stop('Cannot find `rabix_json` option in file header')
- liftr_rabix_json = paste0('\"', opt_list$rabix_json, '\"')
- if (!is.null(opt_list$rabix_args)) {
- liftr_rabix_with_args = '-- '
- rabix_args_vec = unlist(opt_list$rabix_args)
- liftr_rabix_args =
- paste(paste0('--', paste(names(rabix_args_vec),
- rabix_args_vec)),
- collapse = ' ')
- } else {
- liftr_rabix_with_args = NULL
- liftr_rabix_args = NULL
- }
- .out.rabixfile = paste0(normalizePath(output_dir), '/Rabixfile')
- invisible(knit(system.file('template/Rabixfile.Rmd',
- package = 'liftr'),
- output = .out.rabixfile,
- quiet = TRUE))
- }
- }
- return(list(dockerfile = .out.dockerfile, rmd = input))
- }
- #' parse Rmarkdown header
- #'
- #' parse Rmarkdown header and return a list
- #'
- #' The header section is use three hyphens --- as start line & end line,
- #' or three hyphens --- as start line with three dots ...
- #' as end line
- #'
- #' @param input Rmd file to be parsed into a list
- #'
- #' @export parse_rmd
- #' @aliases parse_rmd
- #' @examples
- #' fl = system.file("examples/docker.Rmd", package = "liftr")
- #' parse_rmd(fl)
- parse_rmd = function(input){
- # locate YAML metadata block
- doc_content = readLines(normalizePath(input))
- header_pos = which(doc_content == '---')
- # handling YAML blocks ending with three dots
- if (length(header_pos) == 1L) {
- header_dot_pos = which(doc_content == '...')
- if (length(header_dot_pos) == 0L) {
- stop('Cannot correctly locate YAML metadata block.
- Please use three hyphens (---) as start line & end line,
- or three hyphens (---) as start line with three dots (...)
- as end line.')
- } else {
- header_pos[2L] = header_dot_pos[1L]
- }
- }
- doc_yaml = paste(doc_content[(header_pos[1L] + 1L):
- (header_pos[2L] - 1L)],
- collapse = '\n')
- yaml.load(doc_yaml)
- }
- trans_name <- function(x){
- names(x)[names(x) == "Bioconductor"] <- "biocpkg"
- names(x)[names(x) == "CRAN"] <- "cranpkg"
- x
- }
- create_lift_file = function(appDir = getwd(), appFiles = NULL, output_file = "docker.Rmd",
- maintainer = NULL, email = NULL,
- from = "rocker/shiny"){
- stopifnot(dir.exists(appDir))
- .out <- file.path(normalizePath(dirname(appDir)), output_file)
- .base <- paste0("COPY ", basename(appDir), " /srv/shiny-server/", basename(appDir))
- ## add dummy maintain name
- if(is.null(maintainer)){
- maintainer = Sys.info()[names(Sys.info()) == "user"]
- message("maintainer name is not provided, user your system user name as maintainer name: ", maintainer)
- }
- if(is.null(email)){
- email = paste0(maintainer, "@dummy.com")
- message("email is not provided, create fake email address for placeholder: ", email)
- }
- .h <- list(maintainer = maintainer,
- maintainer_email = email,
- from = from,
- extra = list(.base),
- shiny = TRUE)
- ## add dummy email name
- ## search for liftr.rmd
- if(!file.exists(.out)){
- ad = appDependencies(appDir = appDir, appFiles = appFiles)
- lst = by(ad, ad$source, function(x){
- as.list(x$package)
- })
- lst = trans_name(lst)
- res = list(liftr = c(.h,lst))
- message("Shiny liftr file: ", .out)
- con = file(.out)
- txt = "---"
- txt = c(txt, as.yaml(res))
- txt = c(txt, "---")
- writeLines(txt, con = .out)
- close(con)
- }else{
- message(.out, " exists.")
- }
- .out
- }
- #' Dockerize an Shiny App
- #'
- #' Parse dependecies from a shiny app folder and lift it into a Dockerfile
- #'
- #' @param appDir Directory containing application. Defaults to current working directory.
- #' @param appFiles The files and directories to bundle and deploy (only if upload = TRUE). Can be NULL, in which case all the files in the directory containing the application are bundled. Takes precedence over appFileManifest if both are supplied.
- #' @param output_file A temporariy R markdown file with liftr header passed from shina app folder.
- #' @param output_dir output_dir Directory to output \code{Dockerfile}. If not provided, will be the same directory as \code{input}.
- #' @param maintainer maintainer information for Dockerfile
- #' @param email email address for Dockerfile
- #' @param shiny_base base image for shiny, by default it's rocker/shiny
- #' @export lift_shinyapp
- #' @aliases lift_shinyapp
- #' @examples
- #' \dontrun{
- #' lift_shinayapp("test_app_folder")
- #' }
- lift_shinyapp <- function(appDir = getwd(), appFiles = NULL, output_file = "docker.Rmd", output_dir = NULL,
- maintainer = NULL, email = NULL,
- shiny_base = "rocker/shiny"){
- .out <- create_lift_file(appDir = appDir, appFiles = appFiles, output_file = output_file,
- maintainer = maintainer, email = email,
- from = shiny_base)
- lift(.out, output_dir)
- }
- #' lift a docopt string
- #'
- #' lift a docopt string used for command line
- #'
- #' parse Rmarkdown header from rabix field
- #'
- #' @param input input Rmarkdown file or a function name (character)
- #' @export lift_docopt
- #' @aliases lift_docopt
- #' @return a string used for docopt
- #' @examples
- #' fl = system.file("examples/runif.Rmd", package = "liftr")
- #' opts = lift_docopt(fl)
- #' \dontrun{
- #' require(docopt)
- #' docopt(opts)
- #' docopt(lift_docopt("mean.default"))
- #' }
- lift_docopt = function(input){
- if(file.exists(input)){
- res = lift_docopt_from_header(input)
- }else{
- message("file doesn't exist, try to try this as a function")
- res = lift_docopt_from_function(input)
- }
- res
- }
- lift_docopt_from_header = function(input){
- opt_all_list = parse_rmd(input)
- ol <- opt_all_list$rabix
- .in <- ol$inputs
- txt <- paste("usage:", ol$baseCommand, "[options]")
- txt <- c(txt, "options:")
- ol <- lapply(.in, function(x){
- .nm <- x$prefix
- .t <- x$type
- .type <- paste0('<', deType(.t), '>')
- .o <- paste(.nm, .type, sep = "=")
- .des <- x$description
- .default <- x$default
- if(!is.null(.default)){
- .des <- paste0(.des, " [default: ", .default, "]")
- }
- list(name = .o, description = .des)
- })
- for(i in 1:length(ol)){
- txt <- c(txt, paste(" ", ol[[i]]$name, ol[[i]]$description))
- }
- paste(txt, collapse = "\n")
- }
- lift_docopt_from_function = function(input){
- ol = opt_all_list = rdarg(input)
- txt <- paste0("usage: ", input, ".R", " [options]")
- nms <- names(ol)
- lst <- NULL
- for(nm in nms){
- .nm = paste0("--", nm)
- .t = guess_type(nm, input)
- .type = paste0('<', deType(.t), '>')
- .o = paste(.nm, .type, sep = "=")
- .des = ol[[nm]]
- .def = guess_default(nm, input)
- if(!is.null(.def)){
- .des <- paste0(.des, " [default: ", .def, "]")
- }
- lst = c(lst, list(list(name = .o, description = .des)))
- }
- for(i in 1:length(lst)){
- txt <- c(txt, paste(" ", lst[[i]]$name, lst[[i]]$description))
- }
- ## Fixme:
- paste(txt, collapse = "\n")
- }
- lift_cmd = function(input, output_dir = NULL, shebang = "#!/usr/local/bin/Rscript",
- docker_root = "/"){
- if(file.exists(input)){
- opt_all_list = parse_rmd(input)
- if (is.null(output_dir))
- output_dir = dirname(normalizePath(input))
- tmp = file.path(output_dir, opt_all_list$rabix$baseCommand)
- message("command line file: ", tmp)
- con = file(tmp)
- txt = lift_docopt(input)
- txt = c(shebang, "'", paste0(txt, " ' -> doc"))
- paste("library(docopt)\n opts <- docopt(doc) \n
- rmarkdown::render('",
- docker_root, basename(input), "', BiocStyle::html_document(toc = TRUE),
- output_dir = '.', params = lst)
- " )-> .final
- txt <- c(txt, .final)
- writeLines(txt, con = con)
- close(con)
- }else{
- message("consider you passed a function name (character)")
- if (is.null(output_dir))
- output_dir = getwd()
- .baseCommand <- paste0(input, ".R")
- tmp = file.path(output_dir, .baseCommand)
- message("command line file: ", tmp)
- con = file(tmp)
- txt = lift_docopt(input)
- txt = c(shebang, "'", paste0(txt, " ' -> doc"))
- txt = c(txt, "library(docopt)\n opts <- docopt(doc)")
- .final = gen_list(input)
- txt <- c(txt, .final)
- writeLines(txt, con = con)
- close(con)
- }
- Sys.chmod(tmp)
- tmp
- }
- con_fun = function(type){
- res = switch(deType(type),
- int = "as.integer",
- float = "as.numeric",
- boolean = "as.logical",
- NULL)
- res
- }
- gen_list = function(fun){
- lst = rdarg(fun)
- lst = lst[names(lst) != "..."]
- nms = names(lst)
- txt = NULL
- for(nm in nms){
- .t = con_fun(guess_type(nm, fun))
- if(!is.null(.t)){
- txt = c(txt, paste0(nm, " = ", .t, "(", "opts$", nm, ")"))
- }else{
- txt = c(txt, paste0(nm, " = ", "opts$", nm))
- }
- }
- txt = paste("list(", paste(txt, collapse = ","), ")")
- paste("do.call(", fun, ",", txt, ")")
- }
- guess_type = function(nm, fun){
- dl = formals(fun)
- if(!is.null(dl[[nm]])){
- .c <- class(dl[[nm]])
- if(.c == "name"){
- return("string")
- }else{
- return(deType(.c))
- }
- }else{
- return("string")
- }
- }
- guess_default = function(nm, fun){
- dl = formals(fun)
- if(!is.null(dl[[nm]])){
- .c <- class(dl[[nm]])
- if(.c == "name"){
- return(NULL)
- }else{
- return(dl[[nm]])
- }
- }else{
- return(NULL)
- }
- }
- #' #' install from liftr rmarkdown with headers
- #' #'
- #' #' install from liftr rmarkdown with headers
- #' #' @param rmd A rmarkdown with lift header
- #' #' @export install_from_rmd
- #' #' @examples
- #' #' \dontrun{
- #' #' install_from_rmd("test.rmd")
- #' #' }
- #' install_from_rmd = function(rmd){
- #' opt_list = parse_rmd(rmd)
- #' liftr_cranpkgs = opt_list$liftr$cranpkg
- #' liftr_biocpkgs = opt_list$liftr$biocpkg
- #' liftr_ghpkgs = opt_list$liftr$ghpkg
- #' if(!is.null(liftr_cranpkgs)){
- #' source('https://cdn.rawgit.com/road2stat/liftrlib/fab41764ea8b56677d05c70c86225774164b6ca0/install_cran.R')
- #' install_cran(liftr_cranpkgs)
- #' }
- #' if(!is.null(liftr_biocpkgs)){
- #' source('http://bioconductor.org/biocLite.R')
- #' biocLite(c(liftr_biocpkgs))
- #' }
- #' if(!is.null(liftr_ghpkgs)){
- #' devtools::install_github(c(liftr_ghpkgs))
- #' }
- #'
- #' }
- #'
|