Nothing
#' Decision management
#'
#' Opens a Shiny app that shows a visual diff of each modified file.
#'
#' @param source_path path to the original R files
#' @param output_path path to the updated R files
#' @param background whether to run the app in a background process. Default to `getOption("autoimport_background", FALSE)`.
#'
#' @section Warning:
#' Beware that using `background=TRUE` can bloat your system with multiple R session! \cr
#' You should probably kill the process when you are done:
#' ```r
#' p=import_review(background=TRUE)
#' p$kill()
#' ```
#'
#' @return nothing if `background==FALSE`, the ([callr::process]) object if `background==TRUE`
#' @source inspired by [testthat::snapshot_review()]
#' @export
#' @importFrom cli cli_inform
#' @importFrom dplyr arrange desc
#' @importFrom rlang check_installed
#' @importFrom stringr str_ends
import_review = function(source_path="R/",
output_path=get_target_dir(),
background=getOption("autoimport_background", FALSE)) {
check_installed("shiny", "for `import_review()` to work")
check_installed("diffviewer", "for `import_review()` to work")
data_files = review_files(source_path, output_path) %>%
arrange(desc(str_ends(old_files, "package.[Rr]")))
if(!any(data_files$changed)){
cli_inform("No changes to review.")
return(invisible(FALSE))
}
go = function(data_files){
data_files %>%
filter(changed) %>%
review_app()
rstudio_tickle()
}
if(isTRUE(background)){
check_installed("callr", "for `import_review()` to work in background")
brw = Sys.getenv("R_BROWSER")
x=callr::r_bg(go, args=list(data_files=data_files),
stdout="out", stderr="errors",
package="autoimport", env = c(R_BROWSER=brw))
return(x)
}
go(data_files)
invisible()
}
#' @importFrom digest digest
#' @importFrom fs file_exists path
#' @importFrom purrr map2_lgl
#' @importFrom tibble tibble
#' @noRd
#' @keywords internal
review_files = function(source_path="R/", output_path=get_target_dir()){
old_files = dir(source_path, full.names=TRUE)
assert_file_exists(old_files)
new_files = path(output_path, basename(old_files))
old_files = old_files[file_exists(new_files)]
new_files = new_files[file_exists(new_files)]
changed = map2_lgl(old_files, new_files, ~{
!identical(digest(.x, file=TRUE), digest(.y, file=TRUE))
})
tibble(old_files, new_files, changed)
}
# Shiny ---------------------------------------------------------------------------------------
#' @importFrom cli cli_inform
#' @importFrom fs file_move
#' @importFrom rlang set_names
#' @noRd
review_app = function(data_files){
case_index = seq_along(data_files$old_files) %>% set_names(data_files$old_files)
handled = rep(FALSE, length(case_index))
ui = shiny::fluidPage(
style = "margin: 0.5em",
shiny::fluidRow(style = "display: flex",
shiny::div(style = "flex: 1 1",
shiny::selectInput("cases", NULL, case_index, width = "100%")),
shiny::div(class = "btn-group", style = "margin-left: 1em; flex: 0 0 auto",
shiny::actionButton("stop", "Stop", class="btn-danger"),
shiny::actionButton("skip", "Skip"),
shiny::actionButton("accept", "Accept", class="btn-success"))
),
shiny::fluidRow(
diffviewer::visual_diff_output("diff")
)
)
server = function(input, output, session) {
old_path = data_files$old_files
new_path = data_files$new_files
i = shiny::reactive(as.numeric(input$cases))
output$diff = diffviewer::visual_diff_render({
file = old_path[i()]
new_file = new_path[i()]
assert_file_exists(file)
assert_file_exists(new_file)
diffviewer::visual_diff(file, new_file)
})
shiny::observeEvent(input$accept, {
cli_inform(c(">"="Accepting modification of '{.file {old_path[[i()]]}}'"))
file_move(new_path[[i()]], old_path[[i()]])
update_cases()
})
shiny::observeEvent(input$skip, {
cli_inform(c(">"="Skipping file '{.file {old_path[[i()]]}}'"))
i = next_case()
shiny::updateSelectInput(session, "cases", selected = i)
})
shiny::observeEvent(input$stop, {
cli_inform(c("x"="Stopping"))
shiny::stopApp()
})
update_cases = function(){
handled[[i()]] <<- TRUE
i = next_case()
shiny::updateSelectInput(session, "cases",
choices = case_index[!handled],
selected = i)
}
next_case = function(){
if(all(handled)){
cli_inform(c(v="Review complete"))
shiny::stopApp()
return()
}
remaining = case_index[!handled]
next_cases = which(remaining > i())
x = if(length(next_cases)==0) 1 else next_cases[[1]]
remaining[[x]]
}
}
cli_inform(c(
"Starting Shiny app for modification review",
i = "Use {.key Ctrl + C} or {.key Echap} to quit"
))
shiny::runApp(
shiny::shinyApp(ui, server),
quiet = TRUE,
launch.browser = shiny::paneViewer()
)
invisible()
}
# Helpers -----------------------------------------------------------------
# testthat:::rstudio_tickle
#' @importFrom rlang is_installed
#' @noRd
rstudio_tickle = function(){
if (!is_installed("rstudioapi")) {
return()
}
if (!rstudioapi::hasFun("executeCommand")) {
return()
}
rstudioapi::executeCommand("vcsRefresh")
rstudioapi::executeCommand("refreshFiles")
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.