Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract and visualize dependencies #299

Open
DavZim opened this issue Oct 21, 2022 · 1 comment
Open

Extract and visualize dependencies #299

DavZim opened this issue Oct 21, 2022 · 1 comment

Comments

@DavZim
Copy link

DavZim commented Oct 21, 2022

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:

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

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          
#>   <chr>   <chr>          <list>     <chr>                
#> 1 package pkg1           <list [1]> "fun1 fun2 fun3 fun4"
#> 2 package pkg2           <list [1]> "..."                
#> 3 package pkg3           <list [0]> ""                   
#> 4 file    ./file1.R      <list [0]> ""                   
#> 5 file    ./file2.R      <list [1]> "ffun1 ffun2 ffun3"  
#> 6 file    folder/file3.R <list [1]> "..."   

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

 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                                  
   <chr>            <chr>   <chr>        <list>     <chr>                                        
 1 app/logic/misc.R package shiny        <chr [1]>  div                                          
 2 app/logic/misc.R package shinyWidgets <chr [1]>  downloadBttn                                 
 3 app/logic/misc.R package data.table   <chr [1]>  ...                                          
 4 app/logic/misc.R package dplyr        <chr [1]>  group_by                                     
 5 app/main.R       package shiny        <list [1]> 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
#' @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.
@DavZim
Copy link
Author

DavZim commented Jan 17, 2023

One advantage of having this, is that it would also enable box to warn the user if a function is imported using box::use() but not actually used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant