save_output() writes object to file_path via
.f(object, file_path, ...), then – when manifest = TRUE (the
default) – appends a row to the project-level accumulator at
output_dir/accumulator.csv recording what was saved, how, and whether
the write succeeded. The accumulator is the working file later consumed
by generate_manifest(), which deduplicates it and reshapes it into the
output record, project-manifest.json.
Usage
save_output(
object,
file_path,
.f,
...,
manifest = TRUE,
note = NULL,
output_dir = NULL,
config = NULL
)Arguments
- object
The object to save.
- file_path
Character. A single destination path for
object. A relative path is written under the project root (see the Paths section), so"output/fit.rds"lands in the project's ownoutput/folder wherever the code runs. Its parent directory is created if it does not already exist, and the creation is reported. It is recorded in the accumulator relative to the project root ("output/fit.rds") when it lies inside the project, and as the absolute path otherwise.- .f
A function used to perform the save, called as
.f(object, file_path, ...). Supply the function itself (for examplesaveRDS,ggplot2::ggsave), not a call and not a string. Avoid reassignment indirection (my_fn <- ggsave; .f = my_fn) – the accumulator records the name exactly as written at the call site, so this records"my_fn"rather than"ggsave". Anonymous functions are recorded as"anonymous function: ..."with the body collapsed to a single truncated line.- ...
Additional arguments passed to
.f.- manifest
Logical. When
TRUE(default), append a row to the accumulator. WhenFALSE,objectis still saved via.f, but nothing is recorded.- note
Character or
NULL. An optional free-text note recorded alongside this row.- output_dir
Character or
NULL. Directory containing (or to contain) the accumulator. An explicit relative value is read from the project root (see the Paths section); an absolute one is used as given. IfNULL(the default), resolved fromconfig'soutput_dirconvention whenconfigis supplied, or"output"otherwise, and in either case taken from the project root: relative to the folder holdingconfig, or to the project root.- config
Character or
NULL. Path to a project configuration file (typically a project's own_toolero.yml, as written byinit_project()). Only consulted whenoutput_diris not supplied; an explicitoutput_diralways wins. Defaults toNULL, which leaves pre-0.5.1 behavior unchanged.
Details
The call to .f is wrapped in a narrowly-scoped tryCatch() – only the
.f(object, file_path, ...) call itself, not the rest of
save_output()'s body. On failure, a row is still appended recording
status = "failure" and the caught message, after which the original
condition is rethrown unmodified. Its class, message, and call are
preserved as caught, so downstream handlers behave as though .f() had
been called directly. This is the one place in the package where the
cli convention is deliberately not followed: cli::cli_abort() would
construct a new condition and discard the original class.
r_class records class(object) as a single pipe-separated field,
captured before the write, since class cannot be reliably recovered from
the file afterward. Timestamps are recorded in UTC with millisecond
precision so that they sort lexicographically – generate_manifest()
relies on this when keeping the latest row per file_path.
Project conventions
The accumulator for a document under reports/ lands in the project's
own output/ rather than in reports/output/, because paths start at
the project root. config is opt-in: without it, output_dir
defaults to output/ under the project root. When config is
supplied but cannot be read, this aborts with the same message
init_project() gives for a bad config, rather than silently falling
back to "output".
Paths
A relative path is read from the project root, not from the working
directory. So save_output(fit, "output/fit.rds", saveRDS) writes into
the project's own output/ folder whether you call it from the project
root, from a document under reports/, or from a job on an HTCondor
execute node. You never need to wrap a path in here::here(). Doing so
still works, because here::here() returns an absolute path and
absolute paths are used as given.
The project root is found afresh on every call, in this order:
The job's scratch directory, when
_CONDOR_SCRATCH_DIRnames one. HTCondor sets that variable itself, inside every job.The nearest folder above the working directory that carries a project marker: the
.herefileinit_project()writes, an.Rprojfile, a packageDESCRIPTION, or a version control folder such as.git.The working directory, when neither of the above applies.
Absolute paths (including those starting with ~, or a drive letter
on Windows) and URLs such as https:// or s3:// are used as given.
.. and ./ are not interpreted, so "../data/x.csv" means the folder
above the root; write an absolute path to leave the project. A bare
"." means the root itself.
Functions that read are forgiving about older code. A relative path that does not exist at the project root but does exist relative to the working directory is read from there, and a message says so. Functions that write never guess: a relative path is always written under the root.
A few arguments are deliberately left alone:
init_project()readspath,configanduse_rprofilefrom the working directory, because the project does not exist yet and so has no root.Under
Rscript,resolve_input_path()reads the command line argument as given. RunRscriptfrom the project root or pass an absolute path.Arguments passed on through
...to another function reach it untouched.Where a default is "beside the input" (
qmd_to_r()andgenerate_kb_xml()), the output follows wherever the input was read from.
Two limits are worth knowing. A project nested inside another project is
not supported: the nearest marker wins, which can surprise you when you
work from the outer one. And on an execute node ~ and $HOME cannot
be trusted ($HOME can be the submit node's path), so prefer relative
paths in job scripts.
Examples
output_dir <- withr::local_tempdir()
save_output(
object = mtcars,
file_path = fs::path(output_dir, "mtcars.rds"),
.f = saveRDS,
note = "Unmodified example data.",
output_dir = output_dir
)
#> ℹ Created the directory /tmp/RtmpVdwYt8/file1a898c0bba5 to hold mtcars.rds.
