
Rewrite a code file's data-file paths to the Psych-DS layout
Source:R/reproducibility_check.R
repro_rewrite_paths.RdA script reads and writes data by relative path (read_csv("raw/x.csv"),
saveRDS(m, "../out/model.rds")). When the release is re-laid-out to
Psych-DS (data under data/, per-study study-<group>/data/), those paths no
lives, using the file→target plan psychds_check produced.
Usage
repro_rewrite_paths(
code_text,
file_name,
plan,
lang = "R",
structure_df = NULL,
script_path = NULL
)Arguments
- code_text
the code text for a single file (character vector)
- file_name
the script's own name/path (used to derive its study group)
- plan
the
psychds_checktable: one row per file withfile_name,target_path,current_path, and (for converted tabular files)original_target- lang
the language:
"R"or"Python"are rewritten; any other value returns the empty frame. Python's format-string call forms (f-strings,.format(),os.path.join()) are NOT resolved to a call row the way R'ssprintf()/paste()/file.path()are (seecall_refs/.repro_format_call_refs()below) — only a literal quoted path is rewritten for Python, so a path built by string concatenation/formatting is left alone and reportedmatched = FALSErather than guessed at.- structure_df
optional
data_checkstructure table (file_name,file_location) — when supplied, enables the content-hash mirror collapsing described above.NULL(default) skips it.- script_path
optional path of the script in its repository. When several files share the name a reference gives, the one the reference names counted from the script's own folder or a folder above it (
current_pathinplan) is the file it means.NULL(default) skips this; the folders in the reference's own path still count.
Value
a data frame with one row per referenced path, columns ref (the
path as written, or the full call text when is_call), basename,
matched (logical, a plan file was found), target (its Psych-DS path,
or NA), ambiguous (logical, several plan files matched and the study
could not disambiguate), n_candidates, and is_call (logical, TRUE when
ref is a whole sprintf()/paste()-family call rather than a literal
path — see above), and name_in_code and name_in_deposit (both names when
they differ, else NA). A reference with no exact match also matches the one
plan file with the same extension and the same name apart from case, spaces
and punctuation. Empty frame when the script references no files.
Details
Matching is by basename (a script's ../data/x.csv and the repo's
raw/x.csv are the same file seen from different working directories, so the
prefix is ignored — the same choice code_file_refs() makes). When several
plan files share a basename (a demographics.csv in more than one study), the
ambiguity is resolved by study group: the candidate whose target path is
in the same study as the script (derived from the paths via
.data_group_from_path()) is chosen. Only when that still leaves more than
one candidate is the reference left unrewritten and flagged.
A plan row for a converted tabular file (psychds_check's convert/
original_target columns — a .dta/.sav/.xlsx/... source, whose
target_path is the re-exported _data.csv, with the untouched original
kept alongside at original_target) is matched by the reference's own
extension: a reference ending in the original's extension (read_dta( "math.dta")) is rewritten to original_target, since a format-specific
reader needs the real file, not the CSV re-export — using target_path
there would silently point the reader at bytes it cannot correctly parse.
A reference that already names the CSV form ("math_data.csv", or a bare
.csv extension) still rewrites to target_path as before.
A path assembled at runtime with sprintf()/paste()/paste0()/
file.path() (see .repro_format_call_refs()) is matched the same way,
using the format string's own literal basename (with any resolvable
placeholder substituted via .repro_simple_string_vars()) — but since the
true "reference" there is the WHOLE call, not the quoted substring inside
it, such rows carry is_call = TRUE and ref holds the full call text
(sprintf("%s/%s/x.csv", wd, wd_data)), so repro_write_scripts() replaces
the entire expression rather than corrupting it with a substring swap that
would orphan the call's trailing arguments. A call whose placeholders could
not all be resolved is reported unmatched (matched = FALSE) rather than
guessed, and is separately flagged in the report as fragile authoring
practice regardless of whether it resolved.
A basename can also collide across plan rows for a reason that is NOT real
ambiguity at all: a paper linking several OSF components that mirror each
other (see repro_materialize_layout()'s own de-duplication for the
execution-time counterpart of this) commonly lists the identical physical
file more than once, and the study-grouping step can assign DIFFERENT
(and for all-but-one, WRONG) groups to different mirrors of the same file
— e.g. one mirror's folder structure carries a "Study 1" hint the others
lack, so only that one gets grouped correctly. When structure_df is
supplied, candidates are first collapsed by the CONTENT HASH of their
resolved source files: byte-identical candidates count as ONE, using
whichever one's own group agrees with the majority (ties broken by the
first occurrence) — confirmed against a real corpus paper where 4 mirrors
of the same Anonymized_2024-08-23.csv were grouped ex1/ex1/ex1/ex3, which
made the existing "do all candidates already agree on one target"
shortcut fail (3 way agree, 1 disagrees) and fell through to ambiguous = TRUE even though there was only ever one real file. Without
structure_df (or when a candidate's source cannot be resolved/read),
this collapsing step is skipped and candidates are compared as before.
Examples
plan <- data.frame(
file_name = c("demographics.csv", "scores.csv"),
target_path = c("study-ex1/data/study-demographics_data.csv",
"study-ex1/data/study-scores_data.csv"),
current_path = c("ex1/demographics.csv", "ex1/scores.csv")
)
code_text <- c('d <- read.csv("data/demographics.csv")')
repro_rewrite_paths(code_text, "ex1/analysis.R", plan)
#> ref basename matched
#> 1 data/demographics.csv demographics.csv TRUE
#> target ambiguous n_candidates is_call
#> 1 study-ex1/data/study-demographics_data.csv FALSE 1 FALSE
#> name_in_code name_in_deposit in_join in_here
#> 1 <NA> <NA> FALSE FALSE