R/internals.R

Defines functions excerpt_slides excerpt_no_slides excerpt_to_file is_spin_file run_knitr run_knit query_min_py_version get_current_tag get_asciidoc get_python_major get_python_version discover_python hint_writing write_default_output .onAttach

.onAttach <- function(...) { # Exclude Linting
    if (!isTRUE(getOption("write_to_disk")))
        packageStartupMessage("\n", hint_writing())
}

write_default_output <- function(msg, adoc_file) {
        lines <- readLines(system.file("files", "default.html",
                                       package = "rasciidoc"))
        lines <- sub("DEFAULT_TEXT", paste(msg, collapse = "<br>"), lines)
        writeLines(lines, con = sub("\\.[A-z]*$", ".html", adoc_file))
        status <- FALSE
        return(status)
}

hint_writing <- function(path = "the input file") {
    t <- paste0("Due to the CRAN policy of not writing \"anywhere else on the",
                " file system apart from the R session's temporary directory\"",
                " we work on a temporary copy of ", path, ".",
                "\n", "Thus all internal sourcing and internal links will be",
                " broken and any output is written to ", tempdir(), ".",
                " Set the option \"write_to_disk\" to TRUE (using",
                "\n", "\toptions(\"write_to_disk\" = TRUE)", "\n",
                ") to bypass this. You may want to include the above line into",
                " your ~/.Rprofile.")
    return(t)
}

discover_python <- function(first_only = TRUE, stop_on_error = TRUE) {
    candidates <- sapply(c("python", "python2", "python3"),
                         function(x) return(as.character(Sys.which(x))))
    ## <<--- Adapted from reticulate(1.16)::py_discover_config()
    # provide other common locations
    if (is_windows()) {
        candidates <- c(candidates,
                        reticulate::py_versions_windows()$executable_path)
    } else {
        candidates <- c(candidates,
                        "/usr/bin/python3",
                        "/usr/local/bin/python3",
                        "/opt/python/bin/python3",
                        "/opt/local/python/bin/python3",
                        "/usr/bin/python",
                        "/usr/local/bin/python",
                        "/opt/python/bin/python",
                        "/opt/local/python/bin/python",
                        path.expand("~/anaconda3/bin/python"),
                        path.expand("~/anaconda/bin/python")
                        )
    }
    candidates <- unique(candidates)
    # filter locations by existence
    if (length(candidates) > 0)
        python_versions <- candidates[file.exists(candidates)]
    ## --->>
    if (isTRUE(first_only)) python_versions <- python_versions[1]
    if (is.na(python_versions) && isTRUE(stop_on_error))
        throw("Found no python installation!")
    return(python_versions)
}

get_python_version <- function(python) {
    python_version <- sub("Python ", "",
                          system2(python, "--version",
                                  stderr = TRUE, stdout = TRUE))
    # NOTE: I remove release candidate markers from the current python
    # version. I do so because python 2.7.18rc1 is
    # currently (2020-04-14)
    # installed on some CRAN maschines
    #(r-devel-linux-x86_64-debian-clang).
    # And package_version can't deal with release candidate markers.
    # Since release candidates "can only have bugfixes applied that have
    # been reviewed by other core developers"
    # (https://devguide.python.org/devcycle/#release-candidate-rc).
    # So it should be pretty save to do so. And I do not know any way to
    # determine the last stable version before an rc
    # (3.4.0rc1 gives what?).
    python_version <- sub("rc.*$", "", python_version)
    return(python_version)
}

get_python_major <- function(python_version, use2 = FALSE) {
    python_major <- package_version(python_version)[[c(1, 1)]]
    python_major <- as.character(python_major)
    if (isTRUE(use2) && python_major == "3" &&
        fritools::is_installed("python2")) {
        # asciidoc was origninally written in python2, so python2 wins.
        # TODO: if python2 is available, but the version is not
        # sufficient,should I fall back to python3?
        python_major <- "2"
    }
    return(python_major)
}

# NOTE: Matthew Peveler messes with the current asciidoc
# python3-implementation, so we use a historic working version.
# pass NULL to use the current one.
get_asciidoc <- function(version = NA,
                         tag = NA,
                         clean = FALSE # only for testing!
                         ) {
   if (is.na(version)) {
       python <- discover_python(stop_on_error = FALSE)
       if (is.na(python)) {
           version <- "2"
       } else {
           version <- get_python_major(get_python_version(python))
       }
   } else {
       python <- Sys.which(paste0("python", version))
   }

   local_asciidoc_path <- file.path(tempdir(), "asciidoc")
   local_asciidoc_path <- normalizePath(local_asciidoc_path,
                                        mustWork = FALSE)
   config_file <- normalizePath(file.path(local_asciidoc_path,
                                          "rasciidoc_config.R"),
                                mustWork = FALSE)

   if (file.exists(config_file)) {
       source(config_file, local = TRUE)
   } else {
       unlink(local_asciidoc_path, recursive = TRUE, force = TRUE)
       if (isTRUE(clean)) on.exit(unlink(local_asciidoc_path, recursive = TRUE,
                                         force = TRUE))
       dir.create(local_asciidoc_path)
       url <- switch(version,
                     "2" = "https://github.com/asciidoc-py/asciidoc-py2",
                     "3" = "https://github.com/asciidoc-py/asciidoc-py",
                     throw(paste("Could not find python version 2",
                                 "nor python version 3."))
                     )
       if (fritools::is_installed("git")) {
           # gert fails to clone on some machines, so try to use a system
           # installation of git first.
           if (fritools::is_running_on_fvafrcu_machines() &&
               fritools::is_windows()) {
               # FVAFR messes with its proxies...
               # this is a private local setting.
               # Don't bother.
               url <- sub("^(http)s", "\\1", url)
           }
           system(paste("git clone", url, local_asciidoc_path))

       } else {
           gert::git_clone(url = url, path = local_asciidoc_path)
       }
       # reset to the a tagged release: we don't want any unfunctional
       # devel stuff in there.

       if (is.null(tag)) {
           the_tag <- get_current_tag(local_asciidoc_path)
       } else {
           if (is.na(tag)) {
               if (identical(version, "3")) {
                   the_tag <- "9.1.0"
               } else {
                   the_tag <- get_current_tag(local_asciidoc_path)
               }
           } else {
               the_tag <- tag
           }
       }

       gert::git_reset_hard(repo = local_asciidoc_path,
                            ref = as.character(the_tag))

       asciidoc_source <- normalizePath(list.files(local_asciidoc_path,
                                                   pattern =
                                                       "^asciidoc.py$",
                                                   recursive = TRUE,
                                                   full.names = TRUE))
       python_version <- get_python_version(python)
       min_py_version <- query_min_py_version(file = asciidoc_source,
                                              python_version =
                                                  version)
       if (!is_version_sufficient(python_version, min_py_version))
           throw(paste0("Could find not find python >= ", min_py_version,
                        "."))
       res <- list("python_cmd" = python,
                   "python_version" = python_version,
                   "asciidoc_source" = asciidoc_source,
                   "url" = url,
                   "tag" = the_tag
                   )
       dump("res", config_file)
   }
   return(res)
}

get_current_tag <- function(local_asciidoc_path) {
    tags <- gert::git_tag_list(repo = local_asciidoc_path)[["name"]]
    if (any(grepl("[[:alpha:]]", tags))) {
        tags <- tags[-grep("[[:alpha:]]", tags)]
    }
    current_tag <- sort(package_version(tags), decreasing = TRUE)[1]
    return(current_tag)
}

query_min_py_version <- function(file, python_version) {
    required <- grep("^MIN_PYTHON_VERSION", readLines(file),
                     value = TRUE)
    min_py_version <- switch(python_version,
                             "2" = sub("'.*", "",
                                       sub("^MIN_PYTHON_VERSION = '",
                                           "",
                                           required)),
                             "3" = sub(", ", ".", sub(".*\\((.*)\\).*",
                                                      "\\1",
                                                      required)),
                             throw(paste("Could not find python version 2",
                                         "nor python version 3."))
                             )
    return(min_py_version)
}

run_knit <- function(file_name, knit = NA,
                     write_to_disk = getOption("write_to_disk"),
                     envir = parent.frame()) {
    if (is.na(knit)) {
        r_code_pattern <- "//begin.rcode"
        if (any(grepl(r_code_pattern, readLines(file_name)))) {
            knit <- TRUE
            warning("Setting option knit to TRUE based on the file contents!")
        }
    }
    if (is.na(knit)) {
        if (grepl("\\.R.*$", file_name)) {
            knit <- TRUE
            warning("Setting option knit to TRUE based on the file name given!")
        }
    }
    if (isTRUE(knit)) {
        output_basename <- sub("\\.[Rr](.*)", ".\\1", basename(file_name))
        if (isTRUE(write_to_disk)) {
            knit_out_file <- file.path(dirname(file_name), output_basename)
        } else {
            message(hint_writing(file_name))
            knit_out_file <- file.path(tempdir(), output_basename)
        }
        ops <- options() ## TODO: knitr changes the options?!
        file_name <- knitr::knit(file_name, output = knit_out_file,
                                 envir = envir)
        options(ops) ## restore old options
    }
    return(file_name)
}

run_knitr <- function(file_name, working_directory = dirname(file_name),
                      knit = NA,
                      hooks = NULL,
                      write_to_disk = getOption("write_to_disk"),
                      replacement = NULL,
                      envir = parent.frame()) {
    current_hooks <- knitr::knit_hooks$get()
    adjust_asciidoc_hooks(hooks = hooks, replacement = replacement)
    on.exit(knitr::knit_hooks$set(current_hooks))
    file_name <- normalizePath(file_name)
    withr::with_dir(working_directory, {
                        if (is_spin_file(file_name)) {
                            content <- knitr::spin(text = readLines(file_name),
                                                   knit = TRUE,
                                                   report = FALSE,
                                                   envir = envir)
                            output_basename <- sub("\\.[Rr]", ".asciidoc",
                                                   basename(file_name))
                            if (isTRUE(write_to_disk)) {
                                out_file <- file.path(dirname(file_name),
                                                      output_basename)
                            } else {
                                message(hint_writing(file_name))
                                out_file <- file.path(tempdir(),
                                                      output_basename)
                            }
                            writeLines(content, out_file)
                        } else {
                            out_file <- run_knit(file_name, knit = knit,
                                                 envir = envir,
                                                 write_to_disk = write_to_disk)
                        }
                        out_file <- normalizePath(out_file)
                      })
    return(out_file)
}

is_spin_file <- function(file_name) {
    is_r_file <- grepl("^.*\\.[rR]$", file_name)
    has_roxygen_comment <- any(grepl("^#'", readLines(file_name)))
    has_spin_knitr_chunk_options <- any(grepl("^#-|^#\\+",
                                              readLines(file_name)))
    is_spin <- is_r_file && has_roxygen_comment || has_spin_knitr_chunk_options
    return(is_spin)
}

excerpt_to_file <- function(file_name,
                            begin_pattern, end_pattern,
                            exclusion_pattern, inclusion_pattern,
                            write_to_disk = getOption("write_to_disk"),
                            output_name = NA) {
    if (is.na(output_name))
        output_name <- basename(tempfile(fileext = ".Rasciidoc"))
    if (isTRUE(write_to_disk)) {
        output_directory <- dirname(file_name)
    } else {
        message(hint_writing(file_name))
        output_directory <- tempdir()
    }
    glbt <- fritools::get_lines_between_tags
    excerpt <- glbt(file_name = file_name, keep_tagged_lines = FALSE,
                    begin_pattern = begin_pattern,
                    end_pattern = end_pattern,
                    from_first_line = TRUE, to_last_line = TRUE)
    excerpt <- grep(exclusion_pattern, excerpt, invert = TRUE, value = TRUE)
    excerpt <- sub(paste0(inclusion_pattern, ".*"), "", excerpt)
    # The asciidoc file has to be _here_ for sourcing to work!
    excerpt_file <- file.path(output_directory, output_name)
    writeLines(excerpt, excerpt_file)
    return(excerpt_file)
}

excerpt_no_slides <- function(file_name,
                              write_to_disk = getOption("write_to_disk")
                              ) {
    return(excerpt_to_file(file_name = file_name,
                           begin_pattern = "^// *end_only_slide",
                           end_pattern = "^// *begin_only_slide",
                           inclusion_pattern = "// *no_slide",
                           exclusion_pattern = "// *slide_only",
                           write_to_disk = write_to_disk,
                           output_name = paste0(basename(file_name), "_ex"))
    )
}

excerpt_slides <- function(file_name,
                           write_to_disk = getOption("write_to_disk")
                           ) {
    return(excerpt_to_file(file_name = file_name,
                           begin_pattern = "^// *end_no_slide",
                           end_pattern = "^// *begin_no_slide",
                           inclusion_pattern = "// *slide_only",
                           exclusion_pattern = "// *no_slide",
                           write_to_disk = write_to_disk,
                           output_name = sub("(^.*)(\\.[rR]?asc.*)$",
                                             "\\1_slides\\2",
                                             basename(file_name)))
    )
}

Try the rasciidoc package in your browser

Any scripts or data that you put into this service are public.

rasciidoc documentation built on Aug. 16, 2023, 1:09 a.m.