New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

render_site with Rmd parameters #903

Open
zeehio opened this Issue Dec 4, 2016 · 8 comments

Comments

Projects
None yet
3 participants
@zeehio
Copy link
Contributor

zeehio commented Dec 4, 2016

I started to use the rmarkdown::render_site function. At some point I had an Rmd file with parameters, and I wanted to render it several times with different options.

To do that, I adapted a custom site generator. I would like to know if you are interested in a pull request or at least export some functions so I don't need to use :::.

render_site with rmarkdown parameters

I wrote a custom site generator named site_with_params based on default_site. The main changes with respect to the default_site are:

  • The yaml header of index.Rmd needs to have site: site_with_params to use this site generator.
  • An additional files section to _site.yml is required, that states how html files need to be rendered. An example of _site.yml is shown here (note the "files" section):
name: "my-website"
navbar:
  title: "My Website"
  left:
    - text: "Home"
      href: index.html
    - text: "Domestic results"
       href: domestic.html
    - text: "Foreign results"
      href: foreign.html
files:
  index.html:
    src: index.Rmd
  domestic.html:
    src: domestic_foreign.Rmd
    params:
      type_of_flight: "domestic"
  foreign.html:
    src: domestic_foreign.Rmd
    params:
      type_of_flight: "foreign"

Questions

  • Would you would be interested in a pull request offering this "site with parameters" feature?
  • In case you are not interested, would it be possible to export rmarkdown:::site_config, rmarkdown:::input_as_dir, rmarkdown:::knitr_files_dir, rmarkdown:::dir_exists, rmarkdown:::copy_site_resources and rmarkdown:::knitr_root_cache_dir so it is easy to provide custom site generators in other packages?

Actual code

The code I wrote is adapted from default_site and uses several non-exported rmarkdown functions.

This could be submitted as a PR if there is interest:

#' Generate site with Rmd files with parameters
#' To use this site generator set \code{site: rmarkdown::site_with_params}
#' in the yaml header of the \code{index.Rmd} file.
#' Then in the \code{_site.yml} file, add a files section:
#'
#' \code{
#' files:
#'   index.html:
#'     src: index.Rmd
#'     params:
#'       opt1: Val1
#'   about.html:
#'     src: about.Rmd
#'     params:
#'       opt1: Val2
#' }
#'
#' @param input
#'
#' @export
site_with_params <- function(input, encoding = getOption("encoding"), ...) {

  # get the site config
  config <- rmarkdown:::site_config(input, encoding)
  if (is.null(config))
    stop("No site configuration (_site.yml) file found.")

  input_dir <- rmarkdown:::input_as_dir(input)
  output_file_options <- config[["files"]]
  # config[["files"]] has:
  # files:
  #   index.html:
  #     src: index.Rmd
  #     params:
  #       opt1: Val1
  #   about.html:
  #     src: about.Rmd
  #     params:
  #       opt1: Val2

  # define render function (use ... to gracefully handle future args)
  render <- function(input_file,
                     output_format,
                     envir,
                     quiet,
                     encoding, ...) {

    # track outputs
    outputs <- c()

    # see if this is an incremental render
    incremental <- !is.null(input_file)


    if (incremental) {
      all_input_files <- vapply(output_file_options, function(x) x[["src"]], character(1))
      output_files <- names(output_file_options[all_input_files %in% input_file])
    } else {
      output_files <- names(output_file_options)
    }
    sapply(output_files, function(x) {
      # we suppress messages so that "Output created" isn't emitted
      # (which could result in RStudio previewing the wrong file)
      output <- suppressMessages(
        rmarkdown::render(file.path(input_dir, output_file_options[[x]][["src"]]),
                          output_format = output_format,
                          output_file = x,
                          params = output_file_options[[x]]$params,
                          output_options = list(lib_dir = "site_libs",
                                                self_contained = FALSE),
                          envir = envir,
                          quiet = quiet,
                          encoding = encoding)
      )

      # add to global list of outputs
      outputs <<- c(outputs, output)

      # check for files dir and add that as well
      sidecar_files_dir <- rmarkdown:::knitr_files_dir(output)
      files_dir_info <- file.info(sidecar_files_dir)
      if (isTRUE(files_dir_info$isdir))
        outputs <<- c(outputs, sidecar_files_dir)
    })

    # do we have a relative output directory? if so then remove,
    # recreate, and copy outputs to it (we don't however remove
    # it for incremental builds)
    if (config$output_dir != '.') {

      # remove and recreate output dir if necessary
      output_dir <- file.path(input, config$output_dir)
      if (file.exists(output_dir)) {
        if (!incremental) {
          unlink(output_dir, recursive = TRUE)
          dir.create(output_dir)
        }
      } else {
        dir.create(output_dir)
      }

      # move outputs
      for (output in outputs) {

        # don't move it if it's a _files dir that has a _cache dir
        if (grepl("^.*_files$", output)) {
          cache_dir <- gsub("_files$", "_cache", output)
          if (rmarkdown:::dir_exists(cache_dir))
            next;
        }

        output_dest <- file.path(output_dir, basename(output))
        if (rmarkdown:::dir_exists(output_dest))
          unlink(output_dest, recursive = TRUE)
        file.rename(output, output_dest)
      }

      # copy lib dir a directory at a time (allows it to work with incremental)
      lib_dir <- file.path(input, "site_libs")
      output_lib_dir <- file.path(output_dir, "site_libs")
      if (!file.exists(output_lib_dir))
        dir.create(output_lib_dir)
      libs <- list.files(lib_dir)
      for (lib in libs)
        file.copy(file.path(lib_dir, lib), output_lib_dir, recursive = TRUE)
      unlink(lib_dir, recursive = TRUE)

      # copy other files
      rmarkdown:::copy_site_resources(input, encoding)
    }

    # Print output created for rstudio preview
    if (!quiet) {
      # determine output file
      output_file <- ifelse(is.null(input_file),
                            "index.html",
                            output_files)
      if (config$output_dir != ".")
        output_file <- file.path(config$output_dir, output_file)
      message("\nOutput created: ", output_file)
    }
  }

  # define clean function
  clean <- function() {

    # build list of generated files
    generated <- c()


    # get html files
    html_files <- names(config[["files"]])

    # _files peers are always removed (they could be here due to
    # output_dir == "." or due to a _cache existing for the page)
    html_supporting <- paste0(rmarkdown:::knitr_files_dir(html_files), '/')
    generated <- c(generated, html_supporting)

    # _cache peers are always removed
    html_cache <- paste0(rmarkdown:::knitr_root_cache_dir(html_files), '/')
    generated <- c(generated, html_cache)

    # for rendering in the current directory we need to eliminate
    # output files for our inputs (including _files) and the lib dir
    if (config$output_dir == ".") {

      # .html peers
      generated <- c(generated, html_files)

      # site_libs dir
      generated <- c(generated, "site_libs/")

      # for an explicit output_dir just remove the directory
    } else {
      generated <- c(generated, paste0(config$output_dir, '/'))
    }

    # filter out by existence
    generated[file.exists(file.path(input, generated))]
  }

  # return site generator
  list(
    name = config$name,
    output_dir = config$output_dir,
    render = render,
    clean = clean
  )
}
@jjallaire

This comment has been minimized.

Copy link
Member

jjallaire commented Dec 4, 2016

What if we just added support for a params argument for the render_site function? We could furthermore allow this parameter to be the path to a YAML file.

@zeehio

This comment has been minimized.

Copy link
Contributor Author

zeehio commented Dec 4, 2016

That would be great!

My only concern is that the same .Rmd file may be used as the input file for several output html files (with different parameters) and the params argument should be able to accept that use case.

Looking at the render function in the default_site, when it wants to do an "incremental" rendering, it is based on the input_file. This is fine if there is a single output file for each input file, but it gets a bit confusing in the case where we have a single Rmd file generating several html files.

To me it would make more sense to say "render this output file" and then it would render it using the corresponding Rmd input file and parameters... but maybe I am missing something or overcomplicating things...

Thanks for the quick reply on a Sunday... I wasn't expecting that 😮 👍

@jjallaire

This comment has been minimized.

Copy link
Member

jjallaire commented Dec 4, 2016

zeehio added a commit to zeehio/rmarkdown that referenced this issue Dec 4, 2016

render_site compatibility with Rmd params.
render_site understands the `output_files` field in the `_site.yml` file to
describe which source file should an html file use and give Rmd parameters
if needed. (closes rstudio#903)

The output_files feature can be used as well to set as source for an html file
a R script that will be spinned.

This commit also adds a `autospin` field in `_site.yml` that, if set to `true`
will render all the `R` scripts spinning them first (closes rstudio#892)
@zeehio

This comment has been minimized.

Copy link
Contributor Author

zeehio commented Dec 4, 2016

I thought it would be better to do a PR with the changes and if necessary do further discussion there.

We were lucky and I also fixed #892 with almost zero extra effort. Looking forward to the next release! 😃

@jjallaire

This comment has been minimized.

Copy link
Member

jjallaire commented Dec 6, 2016

@zeehio I'm going to take this PR back up after the next CRAN release (2 or 3 weeks out).

@zeehio

This comment has been minimized.

Copy link
Contributor Author

zeehio commented Feb 1, 2017

@jjallaire this is just a kind reminder of the issue, given your previous comment of taking the PR back up after the 1.3 release 👍

@yihui yihui added this to the v1.8 milestone Oct 16, 2017

@yihui

This comment has been minimized.

Copy link
Member

yihui commented Oct 16, 2017

@zeehio Sorry for leaving this issue aside for so long. Bad news is we probably still don't have the bandwidth to review your PR. I have currently scheduled it for v1.8, the next version after 1.7 (no ETA yet; probably early next year). I'd appreciate it if you could resolve the GIT conflicts before then.

@zeehio

This comment has been minimized.

Copy link
Contributor Author

zeehio commented Oct 17, 2017

@yihui, I fully understand the bandwidth limitations. Like you, I am a bit full until mid January, but I will do my best to find the time to resolve conflicts. I appreciate a lot your kind reply, keep up doing your great work!

@yihui yihui modified the milestones: v1.8, v1.9 Nov 15, 2017

@yihui yihui modified the milestones: v1.9, v1.10 Mar 4, 2018

@yihui yihui modified the milestones: v1.10, v1.11 Jun 15, 2018

@yihui yihui removed this from the v1.11 milestone Jun 25, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment