R/make_file.R

Defines functions make_file ask_Makefile ask_me

Documented in make_file

#' Make me a Makefile
#'
#' Determine targets and prerequisites based on data files that are read/written
#' in your working directory. Make targets by running scripts.
#'
#' \code{make_file()} will \code{\link[base]{parse}} the R code in 'path' and
#' search for function calls that read/write data files. It uses partial
#' matching, so if \code{read.fun} is set to \code{"read"}, it will match
#' \code{read.delim}, \code{read.table}, \code{readRds} etc.
#'
#' The purpose of this is to extract the values of the \code{file} argument
#' from these calls and create a list of files that are being read/written.
#' (You can actually choose different argument but \code{file} seems like the
#' most sensible default. Partial matching applies here as well so \code{file}
#' and \code{xlsxFile} are both matched by \code{"file"}.)
#'
#' In order for this to work, use named \code{file} argument in your function
#' calls, e.g. \code{load(file = "myfile.RData")} and not
#' \code{load("myfile.RData")}, otherwise the parser will ignore it.
#'
#' Once the list of data files is obtained, a Makefile is created, using files
#' that were written as targets and files that were read as prerequisites.
#' The scripts that do the reading/writing become commands. By default, the
#' shell command for R files is \code{"Rscript \\"myfile.R\\""} and for Rmd
#' files \code{"Rscript -e \\"rmarkdown::render('myfile.Rmd')\\""}.
#'
#' If \code{DRY} is \code{TRUE}, the Makefile is just printed on the console.
#'
#' @param path Directory containing R scripts or a single file
#' @param script.all Which script produces the ultimate target? By default a script that creates targets on which nothing else depends
#' @param read.fun Vector of (partial) function names; e.g. \code{"read"} matches both \code{read.csv} and \code{readRds}.
#' @param write.fun Vector of (partial) function names; e.g. \code{"save"} matches both \code{save} and \code{saveRDS}.
#' @param read.arg Name of argument specifying the file read
#' @param write.arg Name of argument specifying the file written
#' @param script A named list of shell commands for specific file types
#' @param dry Should a Makefile be written (\code{TRUE}) or printed?
#' @importFrom MakefileR make_rule makefile write_makefile
#' @export

make_file = function(path = ".", script.all = NULL,
                     read.fun = c("read", "load"),
                     write.fun = c("write", "save"),
                     read.arg = "[Ff]ile",
                     write.arg = read.arg,
                     script = list(
                       R = "Rscript \"$(<)\"",
                       Rmd = "Rscript -e \"rmarkdown::render('$(<)')\""),
                     dry = FALSE) {

  parsed = parse_io(
    path = path, read.fun = read.fun, write.fun = write.fun,
    read.arg = read.arg, write.arg = write.arg, suffix = names(script))

  if (!is.null(script.all))
    script.all = file.path(path, script.all)

  tidy = tidy_io(io.calls = parsed, script = script)
  rules = as_Makefiler(io.tidy = tidy, script.all = script.all)

  if (dry)
    return(makefile(.dots = rules))
  else
    ask_Makefile(makefile(.dots = rules))
}

ask_Makefile = function(Makefile) {
  if (file.exists("Makefile") && as.logical(as.numeric(ask_me())))
    write_makefile(Makefile, "Makefile")
  else if (file.exists("Makefile"))
    message("Aborted")
  else
    write_makefile(Makefile, "Makefile")
}

ask_me = function(question = "Makefile already exists. Overwrite?",
                  opts = list(yes = 1, no = 0)) {
  message("---")
  repeat {
    message(question)
    for (this.opt in names(opts))
      message(paste0(opts[[this.opt]], ": ", this.opt))
    ans = readline(prompt="Answer: ")
    if (ans %in% unlist(opts))
      break
  }
  ans
}
jchrom/datamake documentation built on May 18, 2019, 10:23 p.m.