Skip to contents

A 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_check table: one row per file with file_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's sprintf()/paste()/file.path() are (see call_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 reported matched = FALSE rather than guessed at.

structure_df

optional data_check structure 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_path in plan) 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