
Write path-rewritten scripts into the materialised layout
Source:R/reproducibility_check.R
repro_write_scripts.RdEach script's referenced data paths are rewritten to their Psych-DS target
(via the per-file repro_rewrite_paths() result) so that, run from root,
the reads/writes resolve against the materialised tree. The rewritten script
text is written to root/<script target_path> (or, when the plan has no
target for the script itself, to root/<basename> at the top level so it is
still runnable). A reference left ambiguous or unmatched is written unchanged
— the static report already flagged it.
Usage
repro_write_scripts(
code_text_list,
rewrite_list,
plan,
root,
inject_libs = NULL,
lang = "R",
script_paths = NULL,
writes_in_place = FALSE,
replace_env_calls = FALSE,
run_dirs = NULL,
prepend = NULL
)Arguments
- code_text_list
named list of per-file code text (names are file_names)
- rewrite_list
named list of
repro_rewrite_paths()results, same order- plan
the
psychds_checktable (to find each script's own target_path)- root
the materialised layout root
- inject_libs
optional named character vector/list (names are
file_names already present incode_text_list) — one or more package names to inject aslibrary(<pkg>)calls at the very top of that script's text before it is written. Used by the module's corrective re-run for acould not find function "X"error (see.repro_missing_fn_pkgs()for which packages) — reusing this function's existing text-rewrite plumbing rather than a separate rewrite path.NULL(default) injects nothing, unchanged from before this parameter existed. R only: there is no Python corrective re-run (the module's missing-dependency/undefined-name recovery is namespace-export-based — see.repro_find_export_pkg()— with no equivalent built for Python'simportstatements), so this is ignored for a file whoselangis"Python".- lang
the language each element of
code_text_listis in: a single string applied to every file (default"R"), or a named character vector keyed by file_name for a mixed-language set (same shape asrepro_file_io()'s ownlang). Selects the write-redirect function names (.repro_redirect_writes()) and the working-directory override that is replaced with a statement that does nothing (setwd()becomesinvisible(NULL)in R,os.chdir()becomespassin Python). Python's font-substitution analogue is deliberately NOT built: an unavailable font is a matplotlib WARNING with a silent DejaVu Sans fallback, not the hard "invalid font type" failurefamily =/base_family =causes in R's graphics devices (the actual reason this transform exists for R) — rewriting Python's manyrcParams/plt.rc()/fontdict=spellings would be solving a failure mode Python's own ecosystem does not have here.- script_paths
optional character vector, one per element of
code_text_list: the path (relative toroot) to write that script to, as the package layout (.repro_package_plan()) needs.NULL(default), or anNAelement, places the script by its basename inplan, as before.- writes_in_place
if
TRUE(the package layout), a write stays where the code puts it, so a later script finds the file where it expects it; only a write to an absolute path or out of the package is redirected tooutput/. A quoted relative path whose folder exists from the script's own folder (or its.Rprojfolder) but not from the top is first rewritten to its path from the top, for reads and writes alike.FALSE(default) redirects every write, as before.- replace_env_calls
if
TRUE, the calls to groundhog, checkpoint and renv in R code are replaced, since packages are installed before the run, and such a call would need the network (see.repro_neutralise_env_calls()).FALSE(default) leaves them.- run_dirs
optional folder each script runs from, from
root(.repro_run_folders()), which becomes itsrun_dir.NULL(default) runs every script fromroot.- prepend
optional named list, by file name: lines to put at the top of a script, such as loading another script first (
.repro_run_after_lines()).
Value
a data frame with file_name, script_path (absolute path written),
run_dir (the working directory a run should use — always root),
setwd_removed (count of setwd()/os.chdir() calls replaced with a
statement that does nothing),
env_calls_replaced (count of groundhog, checkpoint and renv calls replaced),
slashes_fixed (the quoted Windows paths given /, as they were, joined by
"; "; see .repro_slash_paths()),
setwd_folder_removed (the paths made relative to the working folder
because they lay under the folder of a removed setwd(), as
"before -> after", joined by "; "; see .repro_strip_setwd_folder()),
setwd_paths (comma-joined paths those calls named, for the warning),
family_replaced (count of family =/base_family = named-font
arguments replaced with "sans", so a plot call does not fail with
"invalid font type" for a font not registered on the machine running the
sandbox), family_detail (semicolon-joined <old> -> "sans" text for
each replacement), and library_injected (the package name(s) injected
via inject_libs for that file, comma-joined, or NA when none was).
Details
A setwd() call in analysis code is bad practice: it hardcodes an assumption
about where the code runs (often an absolute path on the author's own machine,
e.g. setwd("D:/Dropbox/...")), and it overrides the working directory the
runner sets to the materialised layout — so the script's relative reads then
resolve against a directory that does not exist here, and the reproduction
fails for a reason that has nothing to do with the analysis. To run the code
we therefore comment out every setwd() call (recorded per file in
setwd_removed and flagged in the report). The transform is in the same
spirit as the path rewriting: it makes the author's script runnable in its new
location without changing the analysis. A script that only reproduces after a
setwd() is removed is reported as "reproducible after ignoring a setwd()
that should not have been in the code and needs to be fixed".
Examples
root <- file.path(tempdir(), "scripts_demo")
plan <- data.frame(file_name = c("analysis.R", "x.csv"),
target_path = c("analysis/analysis.R", "data/study-x_data.csv"))
code <- list(analysis.R = 'a <- read.csv("x.csv")')
rw <- list(analysis.R = repro_rewrite_paths(code$analysis.R, "analysis.R", plan))
out <- repro_write_scripts(code, rw, plan, root)
#> [repro] rewriting 'analysis.R' (22 bytes) ...
readLines(out$script_path)
#> [1] "a <- read.csv(\"data/study-x_data.csv\")"