klmr / klmr/box

Extract and visualize dependencies

Open
#299 1 comment 3 reactions 0 assignees View on GitHub
✨ new feature
Dominant language
R
Stars
979
Forks
49
PR merge metrics
No merged PRs in 30d

Description

### Please describe your feature request

Great package Konrad! I love it and find it very useful for complicated projects.

One thing that I was missing every now and then was a way to extract and maybe visualize dependencies of a file/function.

Do you think something like this is worth adding to `box`?
I'd be happy to create a proper draft PR (with less dependencies, cleaner code, tests, etc).

# Quick Example

A quick-and-dirty function I threw together looks like this: `get_box_dependencies ` which either takes a file or a text and reports its dependencies:

```R
library(stringr)

# extracts the box dependencies from a file or a text string
get_box_dependencies <- function(file = NULL, text = NULL) {
stopifnot(
"Either file or text must be provided but not both" = xor(is.null(file),
is.null(text)))
if (!is.null(file)) text <- paste(readLines(file), collapse = "\n")

# remove commented code
x <- text |> str_replace_all("#[^\\n]+", "")

# extract box dependencies
box_txt <- str_extract_all(x, "(?<=box::use\\()[^\\)]*") |>
unlist() |>
paste(collapse = "\n") |>
str_replace_all("\\n", " ")

if (nchar(box_txt) == 0) {
return(tibble::tibble(
type = character(0),
dependency = character(0),
exports = list(),
exports_agg = character(0)
))
}
# [a-zA-Z0-9\\.\\/]+ Name regex for the packages/files
# (\\[[^\\]]+\\])? Maybe followed by text in []
deps <- str_extract_all(box_txt, "[a-zA-Z0-9_\\.\\/]+(\\[[^\\]]+\\])?")[[1]]
is_file <- str_detect(deps, "\\/")

reps_res <- deps |> str_replace_all("\\[[^\\]]+\\]", "")
export_names <- deps |>
str_extract_all("(?<=\\[)[^\\]]+(?=\\])") |>
sapply(str_split, pattern = ", *")

tibble::tibble(
type = ifelse(is_file, "file", "package"),
dependency = ifelse(!endsWith(reps_res, ".R") & is_file,
paste0(reps_res, ".R"), reps_res),
exports = export_names,
exports_agg = sapply(export_names, \(x) paste(unlist(x), collapse = " "))
)
}
```
An example of this function is this
```R
test_string <- "
box::use(
pkg1[fun1, fun2,
fun3, fun4],
pkg2[...],
pkg3
# pkg4[fun5] not used
)

box::use(
./file1 ,
./file2[ffun1, ffun2,
ffun3],
folder/file3[...]
)
"
get_box_dependencies(text = test_string)
#> # A tibble: 6 × 4
#> type dependency exports exports_agg
#>
#> 1 package pkg1 "fun1 fun2 fun3 fun4"
#> 2 package pkg2 "..."
#> 3 package pkg3 ""
#> 4 file ./file1.R ""
#> 5 file ./file2.R "ffun1 ffun2 ffun3"
#> 6 file folder/file3.R "..."
```

A larger (but non-reproducible) example output is this code which lists all dependencies and its connections in a directory `app/`

```R
files <- list.files("app", pattern = "\\.R$", full.names = TRUE, recursive = TRUE)
> res <- purrr::map_dfr(files, get_box_dependencies, .id = "file") |>
+ dplyr::mutate(file = files[as.numeric(file)])
> res
# A tibble: 116 × 5
file type dependency exports exports_agg

1 app/logic/misc.R package shiny div
2 app/logic/misc.R package shinyWidgets downloadBttn
3 app/logic/misc.R package data.table ...
4 app/logic/misc.R package dplyr group_by
5 app/main.R package shiny div h1 NS tagList moduleServer sliderInput i…
```

**Caveats** to the solution above:

- this does not work when `box` is used within a function. Eg the following `bar` dependency is picked up but is not reported to belong to `foo`

```r
#' @export
foo <- function(...) {
box::use(bar[baz])
}
```

- At the moment the function depends on `stringr` and `tibble` (to a lesser degree) and lots of regex. This can be brought down if needed.

Contributor guide

Open the contributing guide

Research direction

No repository files or tests are named; start by reading the issue's prototype get_box_dependencies and the box::use syntax it parses. Define the supported extraction and visualization scope, including files, packages, exports, and function-scoped use, then validate the result against the examples and caveats in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
r
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.