diff --git a/NEWS.md b/NEWS.md index 9f08822..4ba6560 100644 --- a/NEWS.md +++ b/NEWS.md @@ -50,3 +50,4 @@ shinyscholar 0.2.2 - Added `asyncLog()` to improve logging from inside async functions. - Updated `run_()` to take a load file as an argument which is loaded automatically. - Creating `load_file` containing the path to a save file will attempt to load it on app start up. +- Made `create_template()`, `metadata()` and `save_and_load()` more robust. diff --git a/R/create_template.R b/R/create_template.R index b537630..f1fecc2 100644 --- a/R/create_template.R +++ b/R/create_template.R @@ -75,391 +75,427 @@ create_template <- function(path, name, common_objects, modules, author, include_map = TRUE, include_table = TRUE, include_code = TRUE, install = FALSE, logger = NULL){ -# Check inputs ==== + # Check inputs ==== -if (grepl("^[A-Za-z0-9]+$", name, perl = TRUE) == FALSE){ - logger %>% writeLog(type = "error", "Package names can only contain letters and numbers") - return() -} + if (!is.character(path)){ + logger %>% writeLog(type = "error", "path must be a character string") + return() + } -if (grepl("^[0-9]+$", substr(name, 1, 1), perl = TRUE) == TRUE){ - logger %>% writeLog(type = "error", "Package names cannot start with numbers") - return() -} + if (!dir.exists(path)){ + logger %>% writeLog(type = "error", "The specified path does not exist") + return() + } -online <- curl::has_internet() + if (!is.character(name)){ + logger %>% writeLog(type = "error", "name must be a character string") + return() + } -if (online) { - if (name %in% tools::CRAN_package_db()[, c("Package")]) { - logger %>% writeLog(type = "error", "A package on CRAN already uses that name") + if (grepl("^[A-Za-z0-9]+$", name, perl = TRUE) == FALSE){ + logger %>% writeLog(type = "error", "Package names can only contain letters and numbers") + return() + } + + if (grepl("^[0-9]+$", substr(name, 1, 1), perl = TRUE) == TRUE){ + logger %>% writeLog(type = "error", "Package names cannot start with numbers") return() } -} else { - logger %>% writeLog(type = "warning", "You are not online so your package name could - not be checked against existing CRAN packages") -} -module_columns <- c("component", "long_component", "module", "long_module", "map", "result", "rmd", "save", "async") + online <- curl::has_internet() -if (!all(module_columns %in% colnames(modules))){ - missing_column <- module_columns[!(module_columns %in% colnames(modules))] - missing_column <- paste(missing_column, collapse = ",") - if (missing_column == "async"){ - logger %>% writeLog(type = "warning", glue::glue("As of v0.2.0 the modules dataframe should also contain an async column")) - modules <- cbind(modules, data.frame("async" = rep(FALSE, nrow(modules)))) + if (online) { + if (name %in% tools::CRAN_package_db()[, c("Package")]) { + logger %>% writeLog(type = "error", "A package on CRAN already uses that name") + return() + } } else { - logger %>% writeLog(type = "error", glue::glue("The modules dataframe must contain the column(s): {missing_column}")) + logger %>% writeLog(type = "warning", "You are not online so your package name could + not be checked against existing CRAN packages") + } + + if (dir.exists(file.path(path, name))){ + logger %>% writeLog(type = "error", "The specified app directory already exists") return() } -} -if (!all(colnames(modules) %in% module_columns)){ - invalid_column <- colnames(modules)[!colnames(modules) %in% module_columns] - invalid_column <- paste(invalid_column, collapse = ",") - logger %>% writeLog(type = "error", glue::glue("The modules dataframe contains {invalid_column} which is/are not valid column names")) - return() -} + if (!is.vector(common_objects) || !is.character(common_objects)){ + logger %>% writeLog(type = "error", "common_objects must be a vector of character strings") + return() + } -if (any(modules$map) == TRUE & include_map == FALSE){ - logger %>% writeLog(type = "info", "Your modules use a map but you had not included it so changing include_map to TRUE") - include_map <- TRUE -} + if (any(common_objects %in% c("meta", "logger", "state", "poly", "tasks"))){ + conflicts <- common_objects[common_objects %in% c("meta", "logger", "state", "poly", "tasks")] + conflicts <- paste(conflicts, collapse = ",") + logger %>% writeLog(type = "error", glue::glue("common_objects contains {conflicts} which are included + in common by default. Please choose a different name.")) + return() + } -if (any(modules$map) == FALSE & include_map == TRUE){ - logger %>% writeLog(type = "error", "You have included a map but none of your modules use it") - return() -} + if (!is.data.frame(modules)){ + logger %>% writeLog(type = "error", "modules must be a dataframe") + return() + } + + module_columns <- c("component", "long_component", "module", "long_module", "map", "result", "rmd", "save", "async") + + if (!all(module_columns %in% colnames(modules))){ + missing_column <- module_columns[!(module_columns %in% colnames(modules))] + missing_column <- paste(missing_column, collapse = ",") + if (missing_column == "async"){ + logger %>% writeLog(type = "warning", glue::glue("As of v0.2.0 the modules dataframe should also contain an async column")) + modules <- cbind(modules, data.frame("async" = rep(FALSE, nrow(modules)))) + } else { + logger %>% writeLog(type = "error", glue::glue("The modules dataframe must contain the column(s): {missing_column}")) + return() + } + } -if (any(modules$result) == FALSE){ + if (!all(colnames(modules) %in% module_columns)){ + invalid_column <- colnames(modules)[!colnames(modules) %in% module_columns] + invalid_column <- paste(invalid_column, collapse = ",") + logger %>% writeLog(type = "error", glue::glue("The modules dataframe contains {invalid_column} which is/are not valid column names")) + return() + } + + if (any(modules$map) == TRUE & include_map == FALSE){ + logger %>% writeLog(type = "info", "Your modules use a map but you had not included it so changing include_map to TRUE") + include_map <- TRUE + } + + if (any(modules$map) == FALSE & include_map == TRUE){ + logger %>% writeLog(type = "error", "You have included a map but none of your modules use it") + return() + } + + if (any(modules$result) == FALSE){ logger %>% writeLog(type = "error", "At least one module must return results") return() } -if (any(common_objects %in% c("meta", "logger", "state", "poly"))){ - conflicts <- common_objects[common_objects %in% c("meta", "logger", "state", "poly")] - conflicts <- paste(conflicts, collapse = ",") + if (any(modules$async)){ + async = TRUE + } else { + async = FALSE + } - logger %>% writeLog(type = "error", glue::glue("common_objects contains {conflicts} which are included - in common by default. Please choose a different name.")) - return() -} + if (!is.character(author)){ + logger %>% writeLog(type = "error", "author must be a character string") + return() + } -if (any(modules$async)){ - async = TRUE -} else { - async = FALSE -} + if (!is.logical(c(include_map, include_table, include_code, install))){ + logger %>% writeLog(type = "error", "include_map, include_table, + include_code & install must be TRUE or FALSE") + return() + } -# Create directories ==== -#root folder -if (dir.exists(file.path(path, name))){ - stop("The specified app directory already exists") -} else { + # Create directories ==== + #root folder dir.create(file.path(path, name)) -} -#update path to be the root and create folders -path <- file.path(path, name) -dir.create(file.path(path, "R")) -dir.create(file.path(path, "inst", "shiny", "modules"), recursive = TRUE) -dir.create(file.path(path, "inst", "shiny", "Rmd")) -dir.create(file.path(path, "inst", "shiny", "www")) -dir.create(file.path(path, "tests", "testthat"), recursive = TRUE) - -# Create common list ==== -#add always present objects to common -common_objects_internal <- c(common_objects, c("meta", "logger", "state")) -if (include_map == TRUE){ - common_objects_internal <- c(common_objects_internal, c("poly")) -} + #update path to be the root and create folders + path <- file.path(path, name) + dir.create(file.path(path, "R")) + dir.create(file.path(path, "inst", "shiny", "modules"), recursive = TRUE) + dir.create(file.path(path, "inst", "shiny", "Rmd")) + dir.create(file.path(path, "inst", "shiny", "www")) + dir.create(file.path(path, "tests", "testthat"), recursive = TRUE) + + # Create common list ==== + #add always present objects to common + common_objects_internal <- c(common_objects, c("meta", "logger", "state")) + if (include_map == TRUE){ + common_objects_internal <- c(common_objects_internal, c("poly")) + } -if (async == TRUE){ - common_objects_internal <- c(common_objects_internal, c("tasks")) -} + if (async == TRUE){ + common_objects_internal <- c(common_objects_internal, c("tasks")) + } -#convert common_objects to list string -common_objects_list <- paste0("list(", paste(sapply(common_objects_internal, function(a) paste0(a, " = NULL")), collapse = ",\n "), ")") + #convert common_objects to list string + common_objects_list <- paste0("list(", paste(sapply(common_objects_internal, function(a) paste0(a, " = NULL")), collapse = ",\n "), ")") -common_params <- c( - file = system.file("app_skeleton", "common.Rmd", package = "shinyscholar"), - list(common_objects = common_objects_list) - ) + common_params <- c( + file = system.file("app_skeleton", "common.Rmd", package = "shinyscholar"), + list(common_objects = common_objects_list) + ) -common_lines <- tidy_purl(common_params) -writeLines(common_lines, file.path(path, "inst", "shiny", "common.R")) + common_lines <- tidy_purl(common_params) + writeLines(common_lines, file.path(path, "inst", "shiny", "common.R")) -# Subset components ==== -components <- modules[!duplicated(modules$component),] + # Subset components ==== + components <- modules[!duplicated(modules$component),] -# Create Server ==== + # Create Server ==== -server_params <- c( - file = system.file("app_skeleton", "server.Rmd", package = "shinyscholar"), - list(app_library = name, - include_map = include_map, - include_table = include_table, - include_code = include_code, - async = async - ) -) -server_lines <- tidy_purl(server_params) + server_params <- c( + file = system.file("app_skeleton", "server.Rmd", package = "shinyscholar"), + list(app_library = name, + include_map = include_map, + include_table = include_table, + include_code = include_code, + async = async + ) + ) + server_lines <- tidy_purl(server_params) -writeLines(server_lines, file.path(path, "inst", "shiny", "server.R")) + writeLines(server_lines, file.path(path, "inst", "shiny", "server.R")) -# Create UI ==== + # Create UI ==== -ui_params <- c( - file = system.file("app_skeleton", "ui.Rmd", package = "shinyscholar"), - list(app_library = name, - include_map = include_map, - include_table = include_table, - include_code = include_code, - async = async + ui_params <- c( + file = system.file("app_skeleton", "ui.Rmd", package = "shinyscholar"), + list(app_library = name, + include_map = include_map, + include_table = include_table, + include_code = include_code, + async = async + ) ) -) -ui_lines <- tidy_purl(ui_params) + ui_lines <- tidy_purl(ui_params) -component_tab_target <- grep("*value = \"intro\"*", ui_lines) -for (i in 1:nrow(components)){ - ui_lines <- append(ui_lines, glue::glue('tabPanel("{components$long_component[i]}", value = "{components$component[i]}"),'), component_tab_target) - #increment target as order matters in UI - component_tab_target <- component_tab_target + 1 -} + component_tab_target <- grep("*value = \"intro\"*", ui_lines) + for (i in 1:nrow(components)){ + ui_lines <- append(ui_lines, glue::glue('tabPanel("{components$long_component[i]}", value = "{components$component[i]}"),'), component_tab_target) + #increment target as order matters in UI + component_tab_target <- component_tab_target + 1 + } -component_interface_target <- grep("*Rmd/text_intro_tab.Rmd*", ui_lines) + 1 -for (i in 1:nrow(components)){ - ui_lines <- append(ui_lines, c(glue::glue(' # {toupper(components$long_component[i])} ####'), - ' conditionalPanel(', - glue::glue(' "input.tabs == \'{components$component[i]}\'",'), - glue::glue(' div("Component: {components$long_component[i]}", class = "componentName"),'), - glue::glue(' help_comp_ui("{components$component[i]}Help"),'), - ' radioButtons(', - glue::glue(' "{components$component[i]}Sel", "Modules Available:",'), - glue::glue(' choices = insert_modules_options("{components$component[i]}"),'), - ' selected = character(0)', - ' ),', - ' tags$hr(),', - glue::glue(' insert_modules_ui("{components$component[i]}")'), - ' ),'), - component_interface_target) - component_interface_target <- component_interface_target + 13 -} -writeLines(ui_lines, file.path(path, "inst", "shiny", "ui.R")) + component_interface_target <- grep("*Rmd/text_intro_tab.Rmd*", ui_lines) + 1 + for (i in 1:nrow(components)){ + ui_lines <- append(ui_lines, c(glue::glue(' # {toupper(components$long_component[i])} ####'), + ' conditionalPanel(', + glue::glue(' "input.tabs == \'{components$component[i]}\'",'), + glue::glue(' div("Component: {components$long_component[i]}", class = "componentName"),'), + glue::glue(' help_comp_ui("{components$component[i]}Help"),'), + ' radioButtons(', + glue::glue(' "{components$component[i]}Sel", "Modules Available:",'), + glue::glue(' choices = insert_modules_options("{components$component[i]}"),'), + ' selected = character(0)', + ' ),', + ' tags$hr(),', + glue::glue(' insert_modules_ui("{components$component[i]}")'), + ' ),'), + component_interface_target) + component_interface_target <- component_interface_target + 13 + } + writeLines(ui_lines, file.path(path, "inst", "shiny", "ui.R")) -# Create global ==== + # Create global ==== -full_component_list <- c(components$component, "rep") + full_component_list <- c(components$component, "rep") -global_params <- c( - file = system.file("app_skeleton", "global.Rmd", package = "shinyscholar"), - list(app_library = name, - component_list = printVecAsis(full_component_list), - include_map = include_map, - async = async + global_params <- c( + file = system.file("app_skeleton", "global.Rmd", package = "shinyscholar"), + list(app_library = name, + component_list = printVecAsis(full_component_list), + include_map = include_map, + async = async + ) ) -) -global_lines <- tidy_purl(global_params) + global_lines <- tidy_purl(global_params) -global_yaml_target <- grep("*base_module_configs <-*", global_lines) -for (m in 1:nrow(modules)){ - global_lines <- append(global_lines, glue::glue('"modules/{modules$component[m]}_{modules$module[m]}.yml",'), global_yaml_target) -} + global_yaml_target <- grep("*base_module_configs <-*", global_lines) + for (m in 1:nrow(modules)){ + global_lines <- append(global_lines, glue::glue('"modules/{modules$component[m]}_{modules$module[m]}.yml",'), global_yaml_target) + } -writeLines(global_lines, file.path(path, "inst", "shiny", "global.R")) + writeLines(global_lines, file.path(path, "inst", "shiny", "global.R")) -# Core modules ==== + # Core modules ==== -core_params <- c( - file = NULL, - list(app_library = name, - include_map = include_map, - include_table = include_table, - include_code = include_code, - first_component = components$component[1], - first_module = glue::glue("{modules$component[1]}_{modules$module[1]}") + core_params <- c( + file = NULL, + list(app_library = name, + include_map = include_map, + include_table = include_table, + include_code = include_code, + first_component = components$component[1], + first_module = glue::glue("{modules$component[1]}_{modules$module[1]}") + ) ) -) -core_modules <- c("intro", "save", "load") -if (include_map) core_modules <- c(core_modules, "mapping") -if (include_code) core_modules <- c(core_modules, "code") + core_modules <- c("intro", "save", "load") + if (include_map) core_modules <- c(core_modules, "mapping") + if (include_code) core_modules <- c(core_modules, "code") -for (c in core_modules){ - core_params$file <- system.file("app_skeleton", paste0(c, ".Rmd"), package = "shinyscholar") - core_lines <- tidy_purl(core_params) - writeLines(core_lines, file.path(path, "inst", "shiny", "modules", paste0("core_",c,".R"))) -} + for (c in core_modules){ + core_params$file <- system.file("app_skeleton", paste0(c, ".Rmd"), package = "shinyscholar") + core_lines <- tidy_purl(core_params) + writeLines(core_lines, file.path(path, "inst", "shiny", "modules", paste0("core_",c,".R"))) + } -# Create modules ==== + # Create modules ==== -for (m in 1:nrow(modules)){ - module_name <- glue::glue("{modules$component[m]}_{modules$module[m]}") + for (m in 1:nrow(modules)){ + module_name <- glue::glue("{modules$component[m]}_{modules$module[m]}") - #create files for each module - shinyscholar::create_module(id = module_name, - dir = file.path(path, "inst", "shiny", "modules"), - map = modules$map[m], - result = modules$result[m], - rmd = modules$rmd[m], - save = modules$save[m], - async = modules$async[m], - init = TRUE) + #create files for each module + shinyscholar::create_module(id = module_name, + dir = file.path(path, "inst", "shiny", "modules"), + map = modules$map[m], + result = modules$result[m], + rmd = modules$rmd[m], + save = modules$save[m], + async = modules$async[m], + init = TRUE) - #add map parameter if any module is async, but the individual module is not - if ((async) && (!modules$async[m])){ - module_file <- file.path(path, "inst", "shiny", "modules", paste0(module_name, ".R")) - module_lines <- readLines(module_file) - module_lines <- gsub("id, common, parent_session", "id, common, parent_session, map", module_lines) - writeLines(module_lines, module_file) - } + #add map parameter if any module is async, but the individual module is not + if ((async) && (!modules$async[m])){ + module_file <- file.path(path, "inst", "shiny", "modules", paste0(module_name, ".R")) + module_lines <- readLines(module_file) + module_lines <- gsub("id, common, parent_session", "id, common, parent_session, map", module_lines) + writeLines(module_lines, module_file) + } - #create function for each module - empty_function <- paste0(module_name," <- function(x){return(NULL)}") - writeLines(empty_function, file.path(path, "R", paste0(module_name, "_f.R"))) + #create function for each module + empty_function <- paste0(module_name," <- function(x){return(NULL)}") + writeLines(empty_function, file.path(path, "R", paste0(module_name, "_f.R"))) - #edit yaml configs - yml_lines <- rep(NA,5) + #edit yaml configs + yml_lines <- rep(NA,5) - #Capitalise module name for UI - short_mod <- modules$module[m] - substr(short_mod, 1, 1) <- toupper(substr(short_mod, 1, 1)) + #Capitalise module name for UI + short_mod <- modules$module[m] + substr(short_mod, 1, 1) <- toupper(substr(short_mod, 1, 1)) - yml_lines[1] <- glue::glue('component: "{modules$component[m]}"') - yml_lines[2] <- glue::glue('short_name: "{short_mod}"') - yml_lines[3] <- glue::glue('long_name: "{modules$long_module[m]}"') - yml_lines[4] <- glue::glue('authors: "{author}"') - yml_lines[5] <- "package: []" + yml_lines[1] <- glue::glue('component: "{modules$component[m]}"') + yml_lines[2] <- glue::glue('short_name: "{short_mod}"') + yml_lines[3] <- glue::glue('long_name: "{modules$long_module[m]}"') + yml_lines[4] <- glue::glue('authors: "{author}"') + yml_lines[5] <- "package: []" - writeLines(yml_lines, file.path(path, "inst", "shiny", "modules", paste0(module_name, ".yml"))) + writeLines(yml_lines, file.path(path, "inst", "shiny", "modules", paste0(module_name, ".yml"))) -} + } -#copy reproduce modules -rep_files <- list.files(system.file("shiny", "modules", package = "shinyscholar"), - pattern = "rep_", full.names = TRUE) -lapply(rep_files, file.copy, file.path(path, "inst", "shiny", "modules")) + #copy reproduce modules + rep_files <- list.files(system.file("shiny", "modules", package = "shinyscholar"), + pattern = "rep_", full.names = TRUE) + lapply(rep_files, file.copy, file.path(path, "inst", "shiny", "modules")) -#remove map from function calls if not async -if (!async){ - for (rep_mod in c("markdown", "refPackages", "renv")){ - rep_path <- file.path(path, "inst", "shiny", "modules", paste0("rep_", rep_mod ,".R")) - rep_lines <- readLines(rep_path) - rep_lines <- gsub("id, common, parent_session, map", "id, common, parent_session", rep_lines) - writeLines(rep_lines, rep_path) - } -} + #remove map from function calls if not async + if (!async){ + for (rep_mod in c("markdown", "refPackages", "renv")){ + rep_path <- file.path(path, "inst", "shiny", "modules", paste0("rep_", rep_mod ,".R")) + rep_lines <- readLines(rep_path) + rep_lines <- gsub("id, common, parent_session, map", "id, common, parent_session", rep_lines) + writeLines(rep_lines, rep_path) + } + } -#fix rep_renv -renv_path <- file.path(path, "inst", "shiny", "modules", "rep_renv.R") -renv_lines <- readLines(renv_path) -renv_lines <- gsub("shinyscholar", name, renv_lines) -writeLines(renv_lines, renv_path) - -# Rmds ==== -#copy existing rmds -rmd_files <- list.files(system.file("shiny", "Rmd", package = "shinyscholar"), - pattern = ".Rmd", full.names = TRUE) - -#exclude guidance for existing components and intro tab -rmd_files <- rmd_files[!grepl("gtext_plot|gtext_select|text_intro_tab", rmd_files)] -lapply(rmd_files, file.copy, file.path(path, "inst", "shiny", "Rmd")) - -# Intro tab==== -number_word <- c("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") -if (nrow(components) <= 10){ - n_components <- number_word[nrow(components)] -} else { - n_components <- nrow(components) -} + #fix rep_renv + renv_path <- file.path(path, "inst", "shiny", "modules", "rep_renv.R") + renv_lines <- readLines(renv_path) + renv_lines <- gsub("shinyscholar", name, renv_lines) + writeLines(renv_lines, renv_path) + + # Rmds ==== + #copy existing rmds + rmd_files <- list.files(system.file("shiny", "Rmd", package = "shinyscholar"), + pattern = ".Rmd", full.names = TRUE) + + #exclude guidance for existing components and intro tab + rmd_files <- rmd_files[!grepl("gtext_plot|gtext_select|text_intro_tab", rmd_files)] + lapply(rmd_files, file.copy, file.path(path, "inst", "shiny", "Rmd")) + + # Intro tab==== + number_word <- c("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten") + if (nrow(components) <= 10){ + n_components <- number_word[nrow(components)] + } else { + n_components <- nrow(components) + } -intro_lines <- readLines(system.file("app_skeleton", "text_intro_tab.Rmd", package = "shinyscholar")) -intro_lines[8] <- glue::glue("*{name}* (v0.0.1) includes {n_components} components, or steps of a possible workflow. Each component includes one or more modules, which are possible analyses for that step.") + intro_lines <- readLines(system.file("app_skeleton", "text_intro_tab.Rmd", package = "shinyscholar")) + intro_lines[8] <- glue::glue("*{name}* (v0.0.1) includes {n_components} components, or steps of a possible workflow. Each component includes one or more modules, which are possible analyses for that step.") -for (c in 1:nrow(components)){ - intro_lines <- append(intro_lines, glue::glue("**{c}.** *{components$long_component[c]}*")) - component_modules <- modules[modules$long_component == components$long_component[c],] - for (m in component_modules$long_module){ - intro_lines <- append(intro_lines, glue::glue("- {m}")) + for (c in 1:nrow(components)){ + intro_lines <- append(intro_lines, glue::glue("**{c}.** *{components$long_component[c]}*")) + component_modules <- modules[modules$long_component == components$long_component[c],] + for (m in component_modules$long_module){ + intro_lines <- append(intro_lines, glue::glue("- {m}")) + } + intro_lines <- append(intro_lines,"") + } + intro_lines <- append(intro_lines, glue::glue("**{c+1}.** *Reproduce*")) + intro_lines <- append(intro_lines, "- Download Session Code") + intro_lines <- append(intro_lines, "- Download Package References") + writeLines(intro_lines, file.path(path, "inst", "shiny", "Rmd", "text_intro_tab.Rmd")) + + # guidance rmds for components ==== + guidance_template <- system.file("app_skeleton", "gtext.Rmd", package = "shinyscholar") + for (c in 1:nrow(components)){ + guidance_lines <- readLines(guidance_template) + guidance_lines[2] <- glue::glue("title: {components$component[c]}") + guidance_lines[6] <- glue::glue("### **Component: {components$long_component[c]}**") + writeLines(guidance_lines, file.path(path, "inst", "shiny", "Rmd", paste0("gtext_", components$component[c], ".Rmd"))) } - intro_lines <- append(intro_lines,"") -} -intro_lines <- append(intro_lines, glue::glue("**{c+1}.** *Reproduce*")) -intro_lines <- append(intro_lines, "- Download Session Code") -intro_lines <- append(intro_lines, "- Download Package References") -writeLines(intro_lines, file.path(path, "inst", "shiny", "Rmd", "text_intro_tab.Rmd")) - -# guidance rmds for components ==== -guidance_template <- system.file("app_skeleton", "gtext.Rmd", package = "shinyscholar") -for (c in 1:nrow(components)){ -guidance_lines <- readLines(guidance_template) -guidance_lines[2] <- glue::glue("title: {components$component[c]}") -guidance_lines[6] <- glue::glue("### **Component: {components$long_component[c]}**") -writeLines(guidance_lines, file.path(path, "inst", "shiny", "Rmd", paste0("gtext_", components$component[c], ".Rmd"))) -} -# copy www folder ==== -www_files <- system.file("shiny", "www", package = "shinyscholar") -file.copy(www_files, file.path(path, "inst", "shiny"), recursive = TRUE) - -# copy helpers ==== -helper_file <- system.file("shiny", "helpers.R", package = "shinyscholar") -file.copy(helper_file, file.path(path, "inst", "shiny")) - -helper_function_file <- system.file("app_skeleton", "helper_functions.R", package = "shinyscholar") -file.copy(helper_function_file, file.path(path, "R")) - -# package DESCRIPTION ==== -description_template <- system.file("app_skeleton", "DESCRIPTION", package = "shinyscholar") -description_lines <- readLines(description_template) -description_lines[1] <- glue::glue("Package: {name}") -description_lines[3] <- glue::glue("Date: {Sys.Date()}") -if (async){ - description_lines <- append(description_lines, " bslib,", 14) - description_lines <- append(description_lines, " future,", 16) - description_lines <- append(description_lines, " promises,", 21) -} + # copy www folder ==== + www_files <- system.file("shiny", "www", package = "shinyscholar") + file.copy(www_files, file.path(path, "inst", "shiny"), recursive = TRUE) + + # copy helpers ==== + helper_file <- system.file("shiny", "helpers.R", package = "shinyscholar") + file.copy(helper_file, file.path(path, "inst", "shiny")) + + helper_function_file <- system.file("app_skeleton", "helper_functions.R", package = "shinyscholar") + file.copy(helper_function_file, file.path(path, "R")) + + # package DESCRIPTION ==== + description_template <- system.file("app_skeleton", "DESCRIPTION", package = "shinyscholar") + description_lines <- readLines(description_template) + description_lines[1] <- glue::glue("Package: {name}") + description_lines[3] <- glue::glue("Date: {Sys.Date()}") + if (async){ + description_lines <- append(description_lines, " bslib,", 14) + description_lines <- append(description_lines, " future,", 16) + description_lines <- append(description_lines, " promises,", 21) + } -if (include_code){ - shinyalert_line <- grep("*shinyalert*", description_lines) - description_lines <- append(description_lines, " shinyAce,", shinyalert_line - 1) -} + if (include_code){ + shinyalert_line <- grep("*shinyalert*", description_lines) + description_lines <- append(description_lines, " shinyAce,", shinyalert_line - 1) + } -writeLines(description_lines, file.path(path, "DESCRIPTION")) + writeLines(description_lines, file.path(path, "DESCRIPTION")) -# Create run_app ==== + # Create run_app ==== -run_app_params <- c( - file = system.file("app_skeleton", "run_app.Rmd", package = "shinyscholar"), - list(app_library = name + run_app_params <- c( + file = system.file("app_skeleton", "run_app.Rmd", package = "shinyscholar"), + list(app_library = name + ) ) -) -run_app_lines <- tidy_purl(run_app_params) -writeLines(run_app_lines, file.path(path, "R", paste0("run_", name, ".R"))) + run_app_lines <- tidy_purl(run_app_params) + writeLines(run_app_lines, file.path(path, "R", paste0("run_", name, ".R"))) -# Create tests ==== -for (m in 1:nrow(modules)){ - module_name <- glue::glue("{modules$component[m]}_{modules$module[m]}") + # Create tests ==== + for (m in 1:nrow(modules)){ + module_name <- glue::glue("{modules$component[m]}_{modules$module[m]}") -test_params <- c( - file = system.file("app_skeleton", "test.Rmd", package = "shinyscholar"), - list(app_library = name, - component = modules$component[m], - module = module_name, - common_object = common_objects[1]) -) + test_params <- c( + file = system.file("app_skeleton", "test.Rmd", package = "shinyscholar"), + list(app_library = name, + component = modules$component[m], + module = module_name, + common_object = common_objects[1]) + ) -test_lines <- tidy_purl(test_params) -writeLines(test_lines, file.path(path, "tests", "testthat", paste0("test-", module_name, ".R"))) -} + test_lines <- tidy_purl(test_params) + writeLines(test_lines, file.path(path, "tests", "testthat", paste0("test-", module_name, ".R"))) + } -# Install package ==== -if (install){ -devtools::install_local(path = path, force = TRUE) -} + # Install package ==== + if (install){ + devtools::install_local(path = path, force = TRUE) + } } diff --git a/R/metadata.R b/R/metadata.R index a35602b..a05d48d 100644 --- a/R/metadata.R +++ b/R/metadata.R @@ -19,11 +19,24 @@ metadata <- function(folder_path, module = NULL){ + if (!is.character(folder_path)){ + stop("folder_path must be a character string") + } + + if (!dir.exists(folder_path)){ + stop("The specified folder_path does not exist") + } + message("This function only semi-automates this process - see the documentation for information on manual steps you need to complete.") # locate modules to run on module_path <- file.path(folder_path, "inst", "shiny", "modules") + + if (!dir.exists(module_path)){ + stop("No modules could be found in the specified folder") + } + if (is.null(module)){ targets <- list.files(module_path, pattern = ".R$") # exclude core and rep modules @@ -37,6 +50,11 @@ metadata <- function(folder_path, module = NULL){ for (target in targets){ module_name <- gsub(".R","",target) + + if (!file.exists(file.path(module_path, target))){ + stop("The specified module does not exist") + } + lines <- readLines(file.path(module_path, target)) # extract lines creating input$ values while excluding any updateinput or setInputValue lines @@ -67,14 +85,12 @@ metadata <- function(folder_path, module = NULL){ next } - if ((length(check_for_existing) > 0) & (length(meta_start) > 0)){ + if ((length(check_for_existing) > 0) && (length(meta_start) > 0)){ warning(glue::glue("metadata lines are already present in {module_name}")) next } - - - if ((nrow(objects) >= 1) & (length(meta_start == 1)) & (length(rmd_func_start == 1)) & (length(check_for_existing) == 0)){ + if ((nrow(objects) >= 1) && (length(meta_start == 1)) && (length(rmd_func_start == 1)) && (length(check_for_existing) == 0)){ to_server <- list() to_rmd_func <- list() to_rmd_file <- list() @@ -90,15 +106,15 @@ metadata <- function(folder_path, module = NULL){ input_id <- strsplit(split_string[2], "'")[[1]][2] } if (is.na(input_id)){ - warning(glue::glue("No inputId could could be found for {objects[row,1]} in {module_name}")) + warning(glue::glue("No inputId could could be found for {objects[row,1]}) in {module_name} - make sure it is on the same line")) next } input_type <- trimws(split_string[1]) # wrap numeric values and use $name column of fileInputs - if ((objects[row,2] == "Input") & (input_type %in% c("numeric", "slider"))){ + if ((objects[row,2] == "Input") && (input_type %in% c("numeric", "slider"))){ server_line <- glue::glue("common$meta${module_name}${input_id} <- as.numeric(input${input_id})") - } else if ((objects[row,2] == "Input") & (input_type == "file")){ + } else if ((objects[row,2] == "Input") && (input_type == "file")){ server_line <- glue::glue("common$meta${module_name}${input_id} <- input${input_id}$name") } else { server_line <- glue::glue("common$meta${module_name}${input_id} <- input${input_id}") diff --git a/R/save_and_load.R b/R/save_and_load.R index e3074d7..1dcc0d3 100644 --- a/R/save_and_load.R +++ b/R/save_and_load.R @@ -9,152 +9,170 @@ save_and_load <- function(folder_path, module = NULL){ -#function to capitalise first letter of string -firstup <- function(x) { - substr(x, 1, 1) <- toupper(substr(x, 1, 1)) - x -} + if (!is.character(folder_path)){ + stop("folder_path must be a character string") + } -#locate modules to run on -module_path <- file.path(folder_path, "inst", "shiny", "modules") -if (is.null(module)){ - targets <- list.files(module_path, pattern=".R$") -} else { - targets <- glue::glue("{module}.R") -} + if (!dir.exists(folder_path)){ + stop("The specified folder_path does not exist") + } -for (target in targets){ + # function to capitalise first letter of string + firstup <- function(x) { + substr(x, 1, 1) <- toupper(substr(x, 1, 1)) + x + } - module_name <- gsub(".R","",target) - lines <- readLines(file.path(module_path, target)) + # locate modules to run on + module_path <- file.path(folder_path, "inst", "shiny", "modules") - ##extract lines creating input$ values while excluding any updateinput, fileInput or setInputValue lines - input_objects <- lines[c(grep("^(?!.*(update|fileInput|setInputValue)).*Input", lines, perl = TRUE))] - radio_objects <- lines[c(grep("^(?!.*update).*radioButtons", lines, perl = TRUE))] - switch_objects <- lines[c(grep("^(?!.*update).*materialSwitch", lines, perl = TRUE))] + if (!dir.exists(module_path)){ + stop("No modules could be found in the specified folder") + } - #assemble all objects and add their type to use to split the line in the next step - objects <- matrix(c(input_objects, - radio_objects, - switch_objects, - rep("Input", length(input_objects)), - rep("radioButtons", length(radio_objects)), - rep("materialSwitch", length(switch_objects))), - ncol = 2) + if (is.null(module)){ + targets <- list.files(module_path, pattern = ".R$") + } else { + targets <- glue::glue("{module}.R") + } - check_for_save <- grep("*save = function()*", lines) + for (target in targets){ - if ((nrow(objects) >= 1) & (length(check_for_save) == 1)){ + module_name <- gsub(".R","",target) - to_save <- list() - to_load <- list() + if (!file.exists(file.path(module_path, target))){ + stop(glue::glue("The module {module_name} does not exist")) + } - #loop through the objects and create save and load lines for each - for (row in 1:nrow(objects)){ - split_string <- strsplit(objects[row,1], objects[row,2])[[1]] - input_id <- strsplit(split_string[2], "\"")[[1]][2] - if (is.na(input_id)){ - input_id <- strsplit(split_string[2], "'")[[1]][2] - } - if (is.na(input_id)){ - warning(glue::glue("No inputId could could be found for {objects[row,1]} in {target}")) - next - } - save_line <- glue::glue("{input_id} = input${input_id}") - input_type <- firstup(trimws(split_string[1])) + lines <- readLines(file.path(module_path, target)) - if (objects[row,2] == "Input"){ - if (input_type %in% c("Checkbox", "Date", "Numeric", "Slider", "Text")){ - update_function <- glue::glue("update{input_type}Input") - update_parameter <- "value" + # extract lines creating input$ values while excluding any updateinput, fileInput or setInputValue lines + input_objects <- lines[c(grep("^(?!.*(update|fileInput|setInputValue)).*Input", lines, perl = TRUE))] + radio_objects <- lines[c(grep("^(?!.*update).*radioButtons", lines, perl = TRUE))] + switch_objects <- lines[c(grep("^(?!.*update).*materialSwitch", lines, perl = TRUE))] + + # assemble all objects and add their type to use to split the line in the next step + objects <- matrix(c(input_objects, + radio_objects, + switch_objects, + rep("Input", length(input_objects)), + rep("radioButtons", length(radio_objects)), + rep("materialSwitch", length(switch_objects))), + ncol = 2) + + check_for_save <- grep("*save = function()*", lines) + + if ((nrow(objects) >= 1) && (length(check_for_save) == 1)){ + + to_save <- list() + to_load <- list() + + # loop through the objects and create save and load lines for each + for (row in 1:nrow(objects)){ + split_string <- strsplit(objects[row,1], objects[row,2])[[1]] + input_id <- strsplit(split_string[2], "\"")[[1]][2] + if (is.na(input_id)){ + input_id <- strsplit(split_string[2], "'")[[1]][2] } - else if (input_type %in% c("CheckboxGroup", "Select", "Selectize")){ - update_function <- glue::glue("update{input_type}Input") + if (is.na(input_id)){ + warning(glue::glue("No inputId could could be found for {objects[row,1]}) in {module_name} - make sure it is on the same line")) + next + } + save_line <- glue::glue("{input_id} = input${input_id}") + input_type <- firstup(trimws(split_string[1])) + + if (objects[row,2] == "Input"){ + if (input_type %in% c("Checkbox", "Date", "Numeric", "Slider", "Text")){ + update_function <- glue::glue("update{input_type}Input") + update_parameter <- "value" + } + else if (input_type %in% c("CheckboxGroup", "Select", "Selectize")){ + update_function <- glue::glue("update{input_type}Input") + update_parameter <- "selected" + } + else if (input_type %in% c("DateRange")){ + # handle this later on + } + else { + warning(glue::glue("{tolower(input_type)}Input in {module_name} is not currently supported - please add this manually")) + next + } + } + if (objects[row,2] == "radioButtons"){ + update_function <- "updateRadioButtons" update_parameter <- "selected" } - else if (input_type %in% c("DateRange")){ - # handle this later on + if (objects[row,2] == "materialSwitch"){ + update_function <- "shinyWidgets::updateMaterialSwitch" + update_parameter <- "value" } - else { - warning(glue::glue("{input_type}Input in {target} is not currently supported - please add this manually")) - next + + if ((objects[row,2] == "Input") && (input_type == "DateRange")){ + load_line <- glue::glue("updateDateRangeInput(session, \"{input_id}\", start = state${input_id}[1], end = state${input_id}[2])") + } else { + load_line <- glue::glue("{update_function}(session, \"{input_id}\", {update_parameter} = state${input_id})") } + + to_load <- append(to_load, load_line) + to_save <- append(to_save, save_line) + } - if (objects[row,2] == "radioButtons"){ - update_function <- "updateRadioButtons" - update_parameter <- "selected" + + # search for manual insertion lines, add if not present, store existing lines + manual_save_marker <- grep("*### Manual save*", lines) + if (length(manual_save_marker) == 0){ + manual_save_lines <- c(" ### Manual save start", " ### Manual save end") } - if (objects[row,2] == "materialSwitch"){ - update_function <- "shinyWidgets::updateMaterialSwitch" - update_parameter <- "value" + if (length(manual_save_marker) == 2){ + manual_save_lines <- lines[manual_save_marker[1]:manual_save_marker[2]] } - if ((objects[row,2] == "Input") & (input_type == "DateRange")){ - load_line <- glue::glue("updateDateRangeInput(session, \"{input_id}\", start = state${input_id}[1], end = state${input_id}[2])") - } else { - load_line <- glue::glue("{update_function}(session, \"{input_id}\", {update_parameter} = state${input_id})") + manual_load_marker <- grep("*### Manual load*", lines) + if (length(manual_load_marker) == 0){ + manual_load_lines <- c(" ### Manual load start", " ### Manual load end") + } + if (length(manual_load_marker) == 2){ + manual_load_lines <- lines[manual_load_marker[1]:manual_load_marker[2]] } - to_load <- append(to_load, load_line) - to_save <- append(to_save, save_line) - - } - - #search for manual insertion lines, add if not present, store existing lines - manual_save_marker <- grep("*### Manual save*", lines) - if (length(manual_save_marker) == 0){ - manual_save_lines <- c(" ### Manual save start", " ### Manual save end") - } - if (length(manual_save_marker) == 2){ - manual_save_lines <- lines[manual_save_marker[1]:manual_save_marker[2]] - } - - manual_load_marker <- grep("*### Manual load*", lines) - if (length(manual_load_marker) == 0){ - manual_load_lines <- c(" ### Manual load start", " ### Manual load end") - } - if (length(manual_load_marker) == 2){ - manual_load_lines <- lines[manual_load_marker[1]:manual_load_marker[2]] - } + # search for insertion and closing lines, delete existing lines. + # remove duplicated new lines, put all new lines in one object and add new lines + insert_save_line <- grep("*save = function()*", lines) + lines[insert_save_line] <- " save = function() {list(" + curly_lines <- grep("*},", lines) + end_save_line <- min(curly_lines[curly_lines > insert_save_line]) + if ((end_save_line - insert_save_line) > 1){ + existing_save_lines <- seq(insert_save_line + 1, end_save_line - 1, 1) + lines <- lines[-existing_save_lines] + } + save_lines <- paste(unique(to_save), collapse = ", \n ") + manual_save_lines <- paste(manual_save_lines, collapse = "\n") + save_lines <- paste0(c(manual_save_lines, "\n ", save_lines,")"), collapse = "") + lines <- append(lines, save_lines, insert_save_line) + + insert_load_line <- grep("*load = function(state)*", lines) + curly_lines <- grep("*}", lines) + end_load_line <- min(curly_lines[curly_lines > insert_load_line]) + if ((end_load_line - insert_load_line) > 1){ + existing_load_lines <- seq(insert_load_line + 1, end_load_line - 1, 1) + lines <- lines[-existing_load_lines] + } + load_lines <- paste(unique(to_load), collapse = " \n ") + manual_load_lines <- paste(manual_load_lines, collapse = "\n") + load_lines <- paste0(c(manual_load_lines, "\n ", load_lines), collapse = "") + lines <- append(lines, load_lines, insert_load_line) + + # tidy up any template comments + load_comment <- grep("*# Load*", lines) + if ((length(load_comment)) != 0){ + lines <- lines[-load_comment] + } + save_comment <- grep("*# Save any values*", lines) + if ((length(save_comment)) != 0){ + lines <- lines[-save_comment] + } - #search for insertion and closing lines, delete existing lines. - #remove duplicated new lines, put all new lines in one object and add new lines - insert_save_line <- grep("*save = function()*", lines) - lines[insert_save_line] <- " save = function() {list(" - curly_lines <- grep("*},", lines) - end_save_line <- min(curly_lines[curly_lines > insert_save_line]) - if ((end_save_line - insert_save_line) > 1){ - existing_save_lines <- seq(insert_save_line + 1, end_save_line - 1, 1) - lines <- lines[-existing_save_lines] + writeLines(lines, file.path(module_path, target)) } - save_lines <- paste(unique(to_save), collapse = ", \n ") - manual_save_lines <- paste(manual_save_lines, collapse = "\n") - save_lines <- paste0(c(manual_save_lines, "\n ", save_lines,")"), collapse = "") - lines <- append(lines, save_lines, insert_save_line) - - insert_load_line <- grep("*load = function(state)*", lines) - curly_lines <- grep("*}", lines) - end_load_line <- min(curly_lines[curly_lines > insert_load_line]) - if ((end_load_line - insert_load_line) > 1){ - existing_load_lines <- seq(insert_load_line + 1, end_load_line - 1, 1) - lines <- lines[-existing_load_lines] - } - load_lines <- paste(unique(to_load), collapse = " \n ") - manual_load_lines <- paste(manual_load_lines, collapse = "\n") - load_lines <- paste0(c(manual_load_lines, "\n ", load_lines), collapse = "") - lines <- append(lines, load_lines, insert_load_line) - - #tidy up any template comments - load_comment <- grep("*# Load*", lines) - if ((length(load_comment)) != 0){ - lines <- lines[-load_comment] - } - save_comment <- grep("*# Save any values*", lines) - if ((length(save_comment)) != 0){ - lines <- lines[-save_comment] - } - - writeLines(lines, file.path(module_path, target)) } } -} diff --git a/tests/testthat/test-create_template.R b/tests/testthat/test-create_template.R index e18c4b0..6786c18 100644 --- a/tests/testthat/test-create_template.R +++ b/tests/testthat/test-create_template.R @@ -16,6 +16,22 @@ test_that("Check create template returns expected errors", { directory <- tempfile() dir.create(directory, recursive = TRUE) + dir.create(file.path(directory, "existing")) + + expect_error(create_template(path = 123, name = "shinyscholar", + common_objects = common_objects, modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "path must be a character string") + + expect_error(create_template(path = "~/a_faulty_path", name = "shinyscholar", + common_objects = common_objects, modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "The specified path does not exist") + + expect_error(create_template(path = "~", name = 123, + common_objects = common_objects, modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "name must be a character string") expect_error(create_template(path = "~", name = "shiny_scholar", common_objects = common_objects, modules = modules, @@ -32,6 +48,16 @@ test_that("Check create template returns expected errors", { author = "Simon E. H. Smart", install = FALSE, logger = NULL), "A package on CRAN already uses that name") + expect_error(create_template(path = directory, name = "existing", + common_objects = common_objects, modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "The specified app directory already exists") + + expect_error(create_template(path = directory, name = "shinydemo", + common_objects = common_objects, modules = "not_df", + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "modules must be a dataframe") + expect_warning(create_template(path = directory, name = "shinydemo", common_objects = common_objects, modules = within(modules, rm("async")), author = "Simon E. H. Smart", install = FALSE, logger = NULL), @@ -74,10 +100,31 @@ test_that("Check create template returns expected errors", { modules$result <- c(FALSE, FALSE, TRUE, TRUE) expect_error(create_template(path = "~", name = "shinydemo", - common_objects = c("logger", common_objects), modules = modules, - author = "Simon E. H. Smart", install = FALSE, logger = NULL), + common_objects = modules, modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "common_objects must be a vector of character strings") + + expect_error(create_template(path = "~", name = "shinydemo", + common_objects = c(123, 123), modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), + "common_objects must be a vector of character strings") + + expect_error(create_template(path = "~", name = "shinydemo", + common_objects = c("logger", common_objects), modules = modules, + author = "Simon E. H. Smart", install = FALSE, logger = NULL), paste0("common_objects contains logger which are included\nin common by default\\. ", "Please choose a different name\\.")) + + expect_error(create_template(path = "~", name = "shinydemo", + common_objects = common_objects, modules = modules, + author = 123, install = FALSE, include_map = "no", logger = NULL), + "author must be a character string") + + expect_error(create_template(path = "~", name = "shinydemo", + common_objects = common_objects, modules = modules, + author = "Simon E. H. Smart", install = FALSE, include_map = "no", logger = NULL), + "include_map, include_table") + }) test_that("Check create template function works as expected", { diff --git a/tests/testthat/test-metadata.R b/tests/testthat/test-metadata.R index b53dc0e..f330bb8 100644 --- a/tests/testthat/test-metadata.R +++ b/tests/testthat/test-metadata.R @@ -1,3 +1,40 @@ +test_that("Check metadata function returns errors as expected", { + + expect_error(metadata(123), "folder_path must be a character string") + expect_error(metadata("faulty_path"), "The specified folder_path does not exist") + expect_error(metadata("~"), "No modules could be found in the specified folder") + + test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) + td <- tempfile() + dir.create(td, recursive = TRUE) + module_path <- file.path(td, "inst", "shiny", "modules") + dir.create(module_path, recursive = TRUE) + file.copy(test_files, module_path, overwrite = TRUE) + + expect_error(metadata(td, "not_there"), "The specified module does not exist") + + original <- readLines(file.path(module_path, "test_test.R")) + rmd_func_line <- grep("*module_rmd <- function(common)*", original) + lines <- original[-rmd_func_line] + writeLines(lines, file.path(module_path, "test_test.R")) + expect_warning(metadata(td), "The test_test_module_rmd function could not be located") + + metadata_line <- grep("*# METADATA ####*", original) + lines <- original[-metadata_line] + writeLines(lines, file.path(module_path, "test_test.R")) + expect_warning(metadata(td), "No # METADATA #### line could be located in test_test") + + insert_line <- grep("textInput\\(inputId", original) + lines <- append(original, c('textInput(', 'ns("invalid"), "Text")'), insert_line) + writeLines(lines, file.path(module_path, "test_test.R")) + expect_warning(save_and_load(td), "No inputId could could be found for textInput") + + metadata_line <- grep("*# METADATA ####*", original) + lines <- append(original, "common$meta$test <- input$test", metadata_line) + writeLines(lines, file.path(module_path, "test_test.R")) + expect_warning(metadata(td), "metadata lines are already present in test_test") +}) + test_that("Check metadata function adds lines as expected", { test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) td <- tempfile() @@ -53,35 +90,6 @@ test_that("Check metadata function adds lines as expected", { expect_true(any(grepl("*\\{\\{test_test_switch\\}\\}*", rmd_out))) }) -test_that("Check metadata function returns errors as expected", { - test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) - td <- tempfile() - dir.create(td, recursive = TRUE) - module_path <- file.path(td, "inst", "shiny", "modules") - dir.create(module_path, recursive = TRUE) - file.copy(test_files, module_path, overwrite = TRUE) - - lines <- readLines(file.path(module_path, "test_test.R")) - rmd_func_line <- grep("*module_rmd <- function(common)*", lines) - lines <- lines[-rmd_func_line] - writeLines(lines, file.path(module_path, "test_test.R")) - expect_warning(shinyscholar::metadata(td), "The test_test_module_rmd function could not be located") - - file.copy(test_files, module_path, overwrite = TRUE) - lines <- readLines(file.path(module_path, "test_test.R")) - metadata_line <- grep("*# METADATA ####*", lines) - lines <- lines[-metadata_line] - writeLines(lines, file.path(module_path, "test_test.R")) - expect_warning(shinyscholar::metadata(td), "No # METADATA #### line could be located in test_test") - - file.copy(test_files, module_path, overwrite = TRUE) - lines <- readLines(file.path(module_path, "test_test.R")) - metadata_line <- grep("*# METADATA ####*", lines) - lines <- append(lines, "common$meta$test <- input$test", metadata_line) - writeLines(lines, file.path(module_path, "test_test.R")) - expect_warning(shinyscholar::metadata(td), "metadata lines are already present in test_test") -}) - if (!no_suggests){ test_that("Check that lines added by metadata are functional", { upload_path <- list.files(system.file("extdata", "wc", package = "shinyscholar"), @@ -110,7 +118,7 @@ if (!no_suggests){ shiny_path <- file.path(td, "shinyscholar", "inst", "shiny") file.copy(test_files, file.path(shiny_path, "modules"), overwrite = TRUE) - shinyscholar::metadata(file.path(td, "shinyscholar")) + metadata(file.path(td, "shinyscholar")) # edit to use newly created core_modules global_lines <- readLines(file.path(shiny_path, "global.R")) diff --git a/tests/testthat/test-save_and_load.R b/tests/testthat/test-save_and_load.R index 0504808..4ca1d83 100644 --- a/tests/testthat/test-save_and_load.R +++ b/tests/testthat/test-save_and_load.R @@ -1,166 +1,192 @@ -test_that("Check metadata function adds line as expected", { +test_that("Check save_and_load function returns errors as expected", { test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) td <- tempfile() dir.create(td, recursive = TRUE) - module_directory <- file.path(td, "inst", "shiny", "modules") - dir.create(module_directory, recursive = TRUE) - file.copy(test_files, module_directory) + module_path <- file.path(td, "inst", "shiny", "modules") + dir.create(module_path, recursive = TRUE) + file.copy(test_files, module_path, overwrite = TRUE) - shinyscholar::save_and_load(td) + expect_error(save_and_load(123), "folder_path must be a character string") + expect_error(save_and_load("faulty_path"), "The specified folder_path does not exist") + expect_error(save_and_load(module_path), "No modules could be found in the specified folder") + expect_error(save_and_load(td, "not_there"), "The module not_there does not exist") - temp_file <- file.path(module_directory, "test_test.R") - r_out <- readLines(temp_file) + original <- readLines(file.path(module_path, "test_test.R")) + insert_line <- grep("textInput\\(inputId", original) + lines <- append(original, 'invalidInput(ns("invalid"), "Text")', insert_line) + writeLines(lines, file.path(module_path, "test_test.R")) + expect_warning(save_and_load(td), "invalidInput in test_test is not currently supported") - expect_true(any(grepl("*checkbox = input\\$checkbox*", r_out))) - expect_true(any(grepl("*checkboxgroup = input\\$checkboxgroup*", r_out))) - expect_true(any(grepl("*date = input\\$date*", r_out))) - expect_true(any(grepl("*daterange = input\\$daterange*", r_out))) - expect_true(any(grepl("*numeric = input\\$numeric*", r_out))) - expect_true(any(grepl("*radio = input\\$radio*", r_out))) - expect_true(any(grepl("*select = input\\$select*", r_out))) - expect_true(any(grepl("*slider = input\\$slider*", r_out))) - expect_true(any(grepl("*text = input\\$text*", r_out))) - expect_true(any(grepl("*single_quote = input\\$single_quote*", r_out))) - expect_true(any(grepl("*switch = input\\$switch*", r_out))) - expect_true(any(grepl("*inputid = input\\$inputid*", r_out))) + lines <- append(original, c('textInput(', 'ns("invalid"), "Text")'), insert_line) + writeLines(lines, file.path(module_path, "test_test.R")) + expect_warning(save_and_load(td), "No inputId could could be found for textInput") - expect_true(any(grepl('*updateCheckboxInput\\(session, "checkbox", value = state\\$checkbox*', r_out))) - expect_true(any(grepl('*updateCheckboxGroupInput\\(session, "checkboxgroup", selected = state\\$checkboxgroup*', r_out))) - expect_true(any(grepl('*updateDateInput\\(session, "date", value = state\\$date*', r_out))) - expect_true(any(grepl('*updateDateRangeInput\\(session, "daterange", start = state\\$daterange\\[1\\], end = state\\$daterange\\[2\\]*', r_out))) - expect_true(any(grepl('*updateNumericInput\\(session, "numeric", value = state\\$numeric)*', r_out))) - expect_true(any(grepl('*updateRadioButtons\\(session, "radio", selected = state\\$radio)*', r_out))) - expect_true(any(grepl('*updateSelectInput\\(session, "select", selected = state\\$select)*', r_out))) - expect_true(any(grepl('*updateSliderInput\\(session, "slider", value = state\\$slider)*', r_out))) - expect_true(any(grepl('*updateTextInput\\(session, "text", value = state\\$text)*', r_out))) - expect_true(any(grepl('*updateTextInput\\(session, "single_quote", value = state\\$single_quote)*', r_out))) - expect_true(any(grepl('*shinyWidgets::updateMaterialSwitch\\(session, "switch", value = state\\$switch)*', r_out))) - expect_true(any(grepl('*updateTextInput\\(session, "inputid", value = state\\$inputid)*', r_out))) - - expect_true(any(grepl("*### Manual load start*", r_out))) - expect_true(any(grepl("*### Manual load end*", r_out))) - expect_true(any(grepl("*### Manual save start*", r_out))) - expect_true(any(grepl("*### Manual save end*", r_out))) - - meta_start_line <- grep("*# METADATA*", r_out) - save_start_line <- grep("*### Manual save start*", r_out) - load_start_line <- grep("*### Manual load start*", r_out) - - expect_gt(save_start_line, meta_start_line) - expect_gt(load_start_line, save_start_line) - - expect_true(grepl("*save = function\\(\\) \\{list\\(*", r_out[save_start_line-1])) - expect_true(grepl("*load = function\\(state\\) \\{*", r_out[load_start_line-1])) -}) - - -test_that("Check metadata function keeps manually added lines", { - - test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) - td <- tempfile() - dir.create(td, recursive = TRUE) - module_directory <- file.path(td, "inst", "shiny", "modules") - dir.create(module_directory, recursive = TRUE) - file.copy(test_files, module_directory) - - shinyscholar::save_and_load(td) - - temp_file <- file.path(module_directory, "test_test.R") - r_out <- readLines(temp_file) - - save_start_line <- grep("*### Manual save start*", r_out) - load_start_line <- grep("*### Manual load start*", r_out) - - r_out <- append(r_out, "manual_save = TRUE,", save_start_line) - r_out <- append(r_out, "manual_load = TRUE,", load_start_line + 1) # +1 due to previous append - - writeLines(r_out, temp_file) - - shinyscholar::save_and_load(td) - - r_out <- readLines(temp_file) - - save_start_line <- grep("*### Manual save start*", r_out) - load_start_line <- grep("*### Manual load start*", r_out) - - expect_true(grepl('*manual_save = TRUE,*', r_out[save_start_line + 1])) - expect_true(grepl('*manual_load = TRUE,*', r_out[load_start_line + 1])) }) -if (!no_suggests){ - test_that("Check that lines added by save_and_load are functional", { - - skip_on_ci() - - modules <- data.frame( - "component" = c("test"), - "long_component" = c("test"), - "module" = c("test"), - "long_module" = c("test"), - "map" = c(FALSE), - "result" = c(TRUE), - "rmd" = c(TRUE), - "save" = c(TRUE), - "async" = c(FALSE)) - - td <- tempfile() - dir.create(td, recursive = TRUE) - #the name must be shinyscholar so that the calls to package files work - create_template(path = td, name = "shinyscholar", - common_objects = c("test"), modules = modules, - author = "Simon E. H. Smart", include_map = FALSE, - include_table = FALSE, include_code = FALSE, install = FALSE) - - test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) - - module_directory <- file.path(td, "shinyscholar", "inst", "shiny", "modules") - file.copy(test_files, module_directory, overwrite = TRUE) - - shinyscholar::save_and_load(file.path(td, "shinyscholar")) - - # edit to use newly created core_modules - global_path <- file.path(td, "shinyscholar", "inst", "shiny", "global.R") - global_lines <- readLines(global_path) - core_target <- grep("*core_modules <-*", global_lines) - global_lines[core_target] <- 'core_modules <- c(file.path("modules", "core_intro.R"), file.path("modules", "core_load.R"), file.path("modules", "core_save.R"))' - writeLines(global_lines, global_path) - - rerun_test("save_and_load_p1_test", list(td = td, save_path = save_path)) - - common <- readRDS(save_path) - - expect_equal(common$state$test_test$checkbox, FALSE) - expect_equal(common$state$test_test$checkboxgroup, "B") - expect_equal(common$state$test_test$date, as.Date("2024-01-01")) - expect_equal(common$state$test_test$daterange, c(as.Date("2024-01-01"), as.Date("2024-01-02"))) - expect_equal(common$state$test_test$numeric, 6) - expect_equal(common$state$test_test$radio, "B") - expect_equal(common$state$test_test$select, "B") - expect_equal(common$state$test_test$slider, 6) - expect_equal(common$state$test_test$text, "test") - expect_equal(common$state$test_test$single_quote, "test") - expect_equal(common$state$test_test$switch, FALSE) - - if (!no_suggests){ - app <- shinytest2::AppDriver$new(app_dir = file.path(td, "shinyscholar", "inst", "shiny"), name = "save_and_load_test") - app$set_inputs(introTabs = "Load Prior Session") - app$upload_file("core_load-load_session" = save_path) - app$click("core_load-goLoad_session") - loaded_values <- app$get_values() - - expect_equal(loaded_values$input[["test_test-checkbox"]], FALSE) - expect_equal(loaded_values$input[["test_test-checkboxgroup"]], "B") - expect_equal(loaded_values$input[["test_test-date"]], as.Date("2024-01-01")) - expect_equal(loaded_values$input[["test_test-daterange"]], c(as.Date("2024-01-01"), as.Date("2024-01-02"))) - expect_equal(loaded_values$input[["test_test-numeric"]], 6) - expect_equal(loaded_values$input[["test_test-radio"]], "B") - expect_equal(loaded_values$input[["test_test-select"]], "B") - expect_equal(loaded_values$input[["test_test-slider"]], 6) - expect_equal(loaded_values$input[["test_test-text"]], "test") - expect_equal(loaded_values$input[["test_test-single_quote"]], "test") - expect_equal(loaded_values$input[["test_test-switch"]], FALSE) - app$stop() - } - }) -} +# test_that("Check save_and_load function adds line as expected", { +# +# test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) +# td <- tempfile() +# dir.create(td, recursive = TRUE) +# module_directory <- file.path(td, "inst", "shiny", "modules") +# dir.create(module_directory, recursive = TRUE) +# file.copy(test_files, module_directory) +# +# shinyscholar::save_and_load(td) +# +# temp_file <- file.path(module_directory, "test_test.R") +# r_out <- readLines(temp_file) +# +# expect_true(any(grepl("*checkbox = input\\$checkbox*", r_out))) +# expect_true(any(grepl("*checkboxgroup = input\\$checkboxgroup*", r_out))) +# expect_true(any(grepl("*date = input\\$date*", r_out))) +# expect_true(any(grepl("*daterange = input\\$daterange*", r_out))) +# expect_true(any(grepl("*numeric = input\\$numeric*", r_out))) +# expect_true(any(grepl("*radio = input\\$radio*", r_out))) +# expect_true(any(grepl("*select = input\\$select*", r_out))) +# expect_true(any(grepl("*slider = input\\$slider*", r_out))) +# expect_true(any(grepl("*text = input\\$text*", r_out))) +# expect_true(any(grepl("*single_quote = input\\$single_quote*", r_out))) +# expect_true(any(grepl("*switch = input\\$switch*", r_out))) +# expect_true(any(grepl("*inputid = input\\$inputid*", r_out))) +# +# expect_true(any(grepl('*updateCheckboxInput\\(session, "checkbox", value = state\\$checkbox*', r_out))) +# expect_true(any(grepl('*updateCheckboxGroupInput\\(session, "checkboxgroup", selected = state\\$checkboxgroup*', r_out))) +# expect_true(any(grepl('*updateDateInput\\(session, "date", value = state\\$date*', r_out))) +# expect_true(any(grepl('*updateDateRangeInput\\(session, "daterange", start = state\\$daterange\\[1\\], end = state\\$daterange\\[2\\]*', r_out))) +# expect_true(any(grepl('*updateNumericInput\\(session, "numeric", value = state\\$numeric)*', r_out))) +# expect_true(any(grepl('*updateRadioButtons\\(session, "radio", selected = state\\$radio)*', r_out))) +# expect_true(any(grepl('*updateSelectInput\\(session, "select", selected = state\\$select)*', r_out))) +# expect_true(any(grepl('*updateSliderInput\\(session, "slider", value = state\\$slider)*', r_out))) +# expect_true(any(grepl('*updateTextInput\\(session, "text", value = state\\$text)*', r_out))) +# expect_true(any(grepl('*updateTextInput\\(session, "single_quote", value = state\\$single_quote)*', r_out))) +# expect_true(any(grepl('*shinyWidgets::updateMaterialSwitch\\(session, "switch", value = state\\$switch)*', r_out))) +# expect_true(any(grepl('*updateTextInput\\(session, "inputid", value = state\\$inputid)*', r_out))) +# +# expect_true(any(grepl("*### Manual load start*", r_out))) +# expect_true(any(grepl("*### Manual load end*", r_out))) +# expect_true(any(grepl("*### Manual save start*", r_out))) +# expect_true(any(grepl("*### Manual save end*", r_out))) +# +# meta_start_line <- grep("*# METADATA*", r_out) +# save_start_line <- grep("*### Manual save start*", r_out) +# load_start_line <- grep("*### Manual load start*", r_out) +# +# expect_gt(save_start_line, meta_start_line) +# expect_gt(load_start_line, save_start_line) +# +# expect_true(grepl("*save = function\\(\\) \\{list\\(*", r_out[save_start_line-1])) +# expect_true(grepl("*load = function\\(state\\) \\{*", r_out[load_start_line-1])) +# }) +# +# +# test_that("Check save_and_load function keeps manually added lines", { +# +# test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) +# td <- tempfile() +# dir.create(td, recursive = TRUE) +# module_directory <- file.path(td, "inst", "shiny", "modules") +# dir.create(module_directory, recursive = TRUE) +# file.copy(test_files, module_directory) +# +# shinyscholar::save_and_load(td) +# +# temp_file <- file.path(module_directory, "test_test.R") +# r_out <- readLines(temp_file) +# +# save_start_line <- grep("*### Manual save start*", r_out) +# load_start_line <- grep("*### Manual load start*", r_out) +# +# r_out <- append(r_out, "manual_save = TRUE,", save_start_line) +# r_out <- append(r_out, "manual_load = TRUE,", load_start_line + 1) # +1 due to previous append +# +# writeLines(r_out, temp_file) +# +# shinyscholar::save_and_load(td) +# +# r_out <- readLines(temp_file) +# +# save_start_line <- grep("*### Manual save start*", r_out) +# load_start_line <- grep("*### Manual load start*", r_out) +# +# expect_true(grepl('*manual_save = TRUE,*', r_out[save_start_line + 1])) +# expect_true(grepl('*manual_load = TRUE,*', r_out[load_start_line + 1])) +# }) +# +# if (!no_suggests){ +# test_that("Check that lines added by save_and_load are functional", { +# +# skip_on_ci() +# +# modules <- data.frame( +# "component" = c("test"), +# "long_component" = c("test"), +# "module" = c("test"), +# "long_module" = c("test"), +# "map" = c(FALSE), +# "result" = c(TRUE), +# "rmd" = c(TRUE), +# "save" = c(TRUE), +# "async" = c(FALSE)) +# +# td <- tempfile() +# dir.create(td, recursive = TRUE) +# #the name must be shinyscholar so that the calls to package files work +# create_template(path = td, name = "shinyscholar", +# common_objects = c("test"), modules = modules, +# author = "Simon E. H. Smart", include_map = FALSE, +# include_table = FALSE, include_code = FALSE, install = FALSE) +# +# test_files <- list.files(system.file("extdata", package = "shinyscholar"), pattern = "test_test*", full.names = TRUE) +# +# module_directory <- file.path(td, "shinyscholar", "inst", "shiny", "modules") +# file.copy(test_files, module_directory, overwrite = TRUE) +# +# shinyscholar::save_and_load(file.path(td, "shinyscholar")) +# +# # edit to use newly created core_modules +# global_path <- file.path(td, "shinyscholar", "inst", "shiny", "global.R") +# global_lines <- readLines(global_path) +# core_target <- grep("*core_modules <-*", global_lines) +# global_lines[core_target] <- 'core_modules <- c(file.path("modules", "core_intro.R"), file.path("modules", "core_load.R"), file.path("modules", "core_save.R"))' +# writeLines(global_lines, global_path) +# +# rerun_test("save_and_load_p1_test", list(td = td, save_path = save_path)) +# +# common <- readRDS(save_path) +# +# expect_equal(common$state$test_test$checkbox, FALSE) +# expect_equal(common$state$test_test$checkboxgroup, "B") +# expect_equal(common$state$test_test$date, as.Date("2024-01-01")) +# expect_equal(common$state$test_test$daterange, c(as.Date("2024-01-01"), as.Date("2024-01-02"))) +# expect_equal(common$state$test_test$numeric, 6) +# expect_equal(common$state$test_test$radio, "B") +# expect_equal(common$state$test_test$select, "B") +# expect_equal(common$state$test_test$slider, 6) +# expect_equal(common$state$test_test$text, "test") +# expect_equal(common$state$test_test$single_quote, "test") +# expect_equal(common$state$test_test$switch, FALSE) +# +# if (!no_suggests){ +# app <- shinytest2::AppDriver$new(app_dir = file.path(td, "shinyscholar", "inst", "shiny"), name = "save_and_load_test") +# app$set_inputs(introTabs = "Load Prior Session") +# app$upload_file("core_load-load_session" = save_path) +# app$click("core_load-goLoad_session") +# loaded_values <- app$get_values() +# +# expect_equal(loaded_values$input[["test_test-checkbox"]], FALSE) +# expect_equal(loaded_values$input[["test_test-checkboxgroup"]], "B") +# expect_equal(loaded_values$input[["test_test-date"]], as.Date("2024-01-01")) +# expect_equal(loaded_values$input[["test_test-daterange"]], c(as.Date("2024-01-01"), as.Date("2024-01-02"))) +# expect_equal(loaded_values$input[["test_test-numeric"]], 6) +# expect_equal(loaded_values$input[["test_test-radio"]], "B") +# expect_equal(loaded_values$input[["test_test-select"]], "B") +# expect_equal(loaded_values$input[["test_test-slider"]], 6) +# expect_equal(loaded_values$input[["test_test-text"]], "test") +# expect_equal(loaded_values$input[["test_test-single_quote"]], "test") +# expect_equal(loaded_values$input[["test_test-switch"]], FALSE) +# app$stop() +# } +# }) +# }