diff --git a/DESCRIPTION b/DESCRIPTION index b932dac..528d808 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -56,7 +56,7 @@ Suggests: remotes, ggplot2, lattice -RoxygenNote: 6.1.0 +RoxygenNote: 6.1.1 Collate: 'Class-Instruction.R' 'Class-Add.R' @@ -88,8 +88,8 @@ Collate: 'dockerfile.R' 'package-installation-bespoke.R' 'package-installation-methods.R' + 'rstudio_containerit.R' 'utility-functions.R' 'utils.R' - 'rstudio_containerit.R' Remotes: github::r-hub/sysreqs, github::richfitz/stevedore diff --git a/R/addin.R b/R/addin.R index 0d5ae56..3c704ee 100644 --- a/R/addin.R +++ b/R/addin.R @@ -1,12 +1,19 @@ -interactive_addin = function() { +common_addin <- function() { + sys.source(pkg_file('scripts','common.R')) +} + +fromSession_addin <- function() { + common_addin() sys.source(pkg_file('scripts','package-interactive-session.R')) } -fromfile_addin = function() { +fromFile_addin <- function() { + common_addin() sys.source(pkg_file('scripts','package-load-file.R')) } -fromexpressions_addin = function() { +fromExpressions_addin <- function() { + common_addin() sys.source(pkg_file('scripts','package-expressions.R')) } diff --git a/R/dockerfile.R b/R/dockerfile.R index 1fa0173..caaec3c 100644 --- a/R/dockerfile.R +++ b/R/dockerfile.R @@ -422,7 +422,7 @@ dockerfileFromFile <- function(file, stop("The given file is not inside the context directory!") # make sure that the path is relative to context - rel_path <- .makeRelative(file, context) + rel_path <- .makeRelative(normalizePath(file), context) # execute script / markdowns or read Rdata file to obtain sessioninfo if (stringr::str_detect(string = file, diff --git a/inst/rstudio/addins.dcf b/inst/rstudio/addins.dcf index ef622c1..a7ee8c5 100644 --- a/inst/rstudio/addins.dcf +++ b/inst/rstudio/addins.dcf @@ -1,19 +1,19 @@ -Name: From interactive session -Description: Create dockerfile from interactive session -Binding: interactive_addin +Name: Dockerfile from interactive session +Description: Create Dockerfile from an interactive session +Binding: fromSession_addin Interactive: true -Name: From file -Description: Create dockerfile from file -Binding: fromfile_addin +Name: Dockerfile from file +Description: Create Dockerfile from a file (R script, R Markdown document) +Binding: fromFile_addin Interactive: true -Name: From list of expressions -Description: Create dockerfile from a list of expressions -Binding: fromexpressions_addin +Name: Dockerfile from expressions +Description: Create Dockerfile from a list of expressions +Binding: fromExpressions_addin Interactive: true -Name: From workspace directory -Description: Create dockerfile from a workspace directory -Binding: workspaceDirectory_addin +Name: Dockerfile from workspace +Description: Create Dockerfile from a workspace directory +Binding: fromWorkspaceDirectory_addin Interactive: true diff --git a/inst/scripts/common.R b/inst/scripts/common.R new file mode 100644 index 0000000..98c9b68 --- /dev/null +++ b/inst/scripts/common.R @@ -0,0 +1,18 @@ +# Common objects for Addin script files +file_dialog_volumes <- c("Working directory" = getwd(), "Home directory" = "~") + +save_button <- function() { + shinyFiles::shinySaveButton(id = "save", + label = "Save as...", + title = "Save file as ..." + # , filetype = list(dockerfile = "Dockerfile") + ) +} + +# Output docker instructions to console +print_docker_instructions <- function(file) { + cat("You can now use the Docker CLI to build the Dockerfile and run the image:", + "\n>> cd ", dirname(file), + "\n>> docker build . -t [tag] -f", basename(file), + "\n>> docker run -t [tag]") +} diff --git a/inst/scripts/package-expressions.R b/inst/scripts/package-expressions.R index f620b54..c43e0e2 100644 --- a/inst/scripts/package-expressions.R +++ b/inst/scripts/package-expressions.R @@ -1,66 +1,60 @@ ## GUI - package_expressions -expressionsAddIn <- function(){ - +fromExpressions <- function(){ + ui <- miniUI::miniPage( miniUI::gadgetTitleBar("Dockerfile creation"), miniUI::miniContentPanel( - shiny::p(shiny::strong("Create a .dockerfile file from a list of expressions")), + shiny::p(shiny::strong("Create a Dockerfile from a list of expressions")), shiny::p("Input a vector of expressions. For example;"), shiny::p(shiny::code( "c( expression(library(sp)), - expression(data(meuse)), + expression(data(meuse)), expression(mean(meuse[[\"zinc\"]])) )")), - shiny::p("For more information see the getting - started page at https://o2r.info/containerit/articles/containerit.html."), - shiny::textAreaInput("expressions", "List of Expressions", "", height="240px", width="500px"), + shiny::p("For more information see documentation at https://o2r.info/containerit/articles/containerit.html."), + shiny::textAreaInput(inputId = "expressions", + label = "List of Expressions", + value = "", + height = "240px", + width = "500px"), shiny::fillRow( - shiny::textInput("text",NULL, - value = paste0(getwd(),"/dockerfile.dockerfile")), - shinyFiles::shinySaveButton("save", "Save as...", "Save file as ...", - filetype=list(dockerfile="dockerfile")), - height = '50px' - ) + shiny::textInput(inputId = "text", + label = NULL, + value = file.path(getwd(), "Dockerfile")), + save_button(), + height = "50px" ) ) - - - + ) + server <- function(input, output, session){ shiny::observeEvent(input$save,{ - volumes <- c("Working directory"=getwd(),"Home Directory"="~") - shinyFiles::shinyFileSave(input, "save", roots=volumes, session=session) - fileinfo <- shinyFiles::parseSavePath(volumes, input$save) - if(length(fileinfo$datapath)!=0) { + shinyFiles::shinyFileSave(input, "save", + roots = file_dialog_volumes, + session = session) + fileinfo <- shinyFiles::parseSavePath(roots = file_dialog_volumes, + selection = input$save) + if (length(fileinfo$datapath) != 0) { shiny::updateTextInput(session, "text", value = fileinfo$datapath) } }) + shiny::observeEvent(input$done, { - - # Here is where your Shiny application might now go an affect the - # contents of a document open in RStudio, using the `rstudioapi` package. - # - # At the end, your application should call 'stopApp()' here, to ensure that - # the gadget is closed after 'done' is clicked. - + # Exit app to ensure that the gadget is closed after 'done' is clicked. + shiny::stopApp() + # Create the session object session <- containerit::clean_session(input$expressions, echo = TRUE) - # Create docker file - dockerfile_object <- containerit::dockerfile(from=session) - # Output to desired path + + # Create docker file and write to desired path + dockerfile_object <- containerit::dockerfile(from = session) containerit::write(dockerfile_object, file = input$text) - # Output docker instructions - cat(paste0("\nInstructions to run docker container from command line:\n - >> docker build . -t [tag] -f ", basename(fn_args[['output_filename']])), "\n - >> docker run -t [tag]") - # Exit app - shiny::stopApp() + + print_docker_instructions(input$text) }) - } - viewer <- shiny::dialogViewer(dialogName = "containerit") - shiny::runGadget(ui, server, viewer = viewer) + shiny::runGadget(app = ui, server = server, viewer = viewer) } -expressionsAddIn() \ No newline at end of file +fromExpressions() diff --git a/inst/scripts/package-interactive-session.R b/inst/scripts/package-interactive-session.R index 4458dd4..61c5ac4 100644 --- a/inst/scripts/package-interactive-session.R +++ b/inst/scripts/package-interactive-session.R @@ -1,57 +1,52 @@ ## GUI - package_interactive_session -interactiveAddIn <- function(){ - +fromSession <- function(){ + ui <- miniUI::miniPage( miniUI::gadgetTitleBar("Dockerfile creation"), miniUI::miniContentPanel( shiny::fillRow( - shiny::textInput("text",NULL, - value = paste0(getwd(),"/dockerfile.dockerfile")), - shinyFiles::shinySaveButton("save", "Select file", "Save file as ...", - filetype=list(dockerfile="dockerfile")), - height = '50px' - ), - shiny::checkboxInput("saveimage", "Save global R objects to dockerfile", TRUE) - ) + shiny::textInput(inputId = "text", + label = NULL, + value = file.path(getwd(), "Dockerfile")), + save_button(), + height = "50px" + ), + shiny::checkboxInput("saveimage", "Save global R objects to Dockerfile", TRUE) ) - - - + ) + server <- function(input, output, session){ - shiny::observeEvent(input$save,{ - volumes <- c("Working directory"=getwd(),"Home Directory"="~") - shinyFiles::shinyFileSave(input, "save", roots=volumes, session=session) - fileinfo <- shinyFiles::parseSavePath(volumes, input$save) - if(length(fileinfo$datapath)!=0) { - shiny::updateTextInput(session, "text", value = fileinfo$datapath) - } - }) - shiny::observeEvent(input$done, { - - # Here is where your Shiny application might now go an affect the - # contents of a document open in RStudio, using the `rstudioapi` package. - # - # At the end, your application should call 'stopApp()' here, to ensure that - # the gadget is closed after 'done' is clicked. - - # Exit app first - shiny::stopApp() - # Create docker file - dockerfile_object <- containerit::dockerfile(save_image=input$saveimage) - # Output to desired path - containerit::write(dockerfile_object, file = input$text) - - # Output docker instructions - cat(paste0("\nInstructions to run docker container from command line:\n - >> docker build . -t [tag] -f ", basename(fn_args[['output_filename']])), "\n - >> docker run -t [tag]") - }) - + shiny::observeEvent(eventExpr = input$save, + handlerExpr = { + volumes <- c("Working directory" = getwd(), "Home directory" = "~") + shinyFiles::shinyFileSave(input = input, + id = "save", + roots = volumes, + session = session) + fileinfo <- shinyFiles::parseSavePath(roots = volumes, + selection = input$save) + if (length(fileinfo$datapath) != 0) { + shiny::updateTextInput(session = session, + inputId = "text", + value = fileinfo$datapath) + } + }) + + shiny::observeEvent(eventExpr = input$done, + handlerExpr = { + # Exit app to ensure that the gadget is closed after 'done' is clicked. + shiny::stopApp() + + # Create Dockerfile and write to desired path + dockerfile_object <- containerit::dockerfile(save_image = input$saveimage) + containerit::write(dockerfile_object, file = input$text) + + print_docker_instructions(input$text) + }) } - viewer <- shiny::dialogViewer(dialogName = "containerit") - shiny::runGadget(ui, server, viewer = viewer) + shiny::runGadget(app = ui, server = server, viewer = viewer) } -interactiveAddIn() \ No newline at end of file +fromSession() diff --git a/inst/scripts/package-load-file.R b/inst/scripts/package-load-file.R index 9bd32cf..30c3d91 100644 --- a/inst/scripts/package-load-file.R +++ b/inst/scripts/package-load-file.R @@ -1,110 +1,107 @@ ## GUI - package_load_file -fromFileAddIn <- function(){ - +fromFile <- function(){ + + # This function takes a fileinfo object returned from shinyFiles::parseFilePaths(..) + # and, depending on the file type, a filename with .dockerfile extension + # and the other required calls to containerit::dockerfile, + # returns a list of the arguments determineDockerFunctionArguments <- function(input_filename) { - # This function takes fileinfo, returned from parseFilePaths - # and, depending on the file type, a filename with .dockerfile extension - # and the other required calls to containerit::dockerfile - # Return type is a list of the arguments - - # Determine the file type output <- list() - if (grepl(".R$",input_filename)) { + + output[["output_filename"]] <- "Dockerfile" + + # Determine the file type and create CMD accordingly + if (grepl(".R$", input_filename)) { output[['output_filename']] <- gsub(".R$",".dockerfile",input_filename) - output[['cmd']] <- containerit::CMD_Rscript(basename(input_filename)) - + output[["cmd"]] <- containerit::CMD_Rscript(basename(input_filename)) } else if (grepl(".Rmd$",input_filename)) { output[['output_filename']] <- gsub(".Rmd$",".dockerfile",input_filename) - output[['cmd']] <- containerit::CMD_Render(basename(input_filename)) - + output[["cmd"]] <- containerit::CMD_Render(basename(input_filename)) } else if (grepl("RData$|Rdata$",input_filename)) { - output[['output_filename']] <- gsub(".RData|.Rdata$",".dockerfile",input_filename) - output[['cmd']] <- containerit::Cmd("R") - } - else { + output[['output_filename']] <- gsub(".RData|.Rdata$",".Dockerfile",input_filename) + output[["cmd"]] <- containerit::Cmd("R") + } else { stop("File type not recognised") } + return(output) } - - + ui <- miniUI::miniPage( miniUI::gadgetTitleBar("Dockerfile creation"), miniUI::miniContentPanel( shiny::fillCol( - flex = c(1,2,1,8), - shiny::p(shiny::strong("Create a .dockerfile file from a file input.")), - shiny::p("Inputs accepted are; R scripts (.R), R markdown files (.Rmd), - or stored sessionInfo (sessionInfo.RData, sessioninfo.RData, - or session_info.RData). For more information see the getting - started page at https://o2r.info/containerit/articles/containerit.html."), - shiny::fillRow( - shiny::textInput("filename",NULL), - shinyFiles::shinyFilesButton("load", "Select file", "Load file",multiple=F), - height = '50px' - ), - shiny::textOutput('outputfile') + flex = c(1,2,1,1,1), + shiny::p(shiny::strong("Create a"), shiny::code("Dockerfile"), shiny::strong("from a workflow file")), + shiny::p(shiny::em("Accepted inputs:"), shiny::br(), + "R scripts (.R)", shiny::br(), + "R markdown files (.Rmd)", shiny::br(), + "Stored sessionInfo (R object named 'info' in a file sessionInfo.RData, sessioninfo.RData, or session_info.RData)"), + shiny::p("For more information see the ", shiny::a(href = "https://o2r.info/containerit/articles/containerit.html", "package Vignette"), "."), + shiny::fillRow( + shiny::textInput(inputId = "filename", + label = NULL), + shinyFiles::shinyFilesButton(id = "load", + label = "Select file", + title = "Load file", + multiple = F), + height = "50px" + ), + shiny::textOutput("outputfile") ) ) ) - - - + server <- function(input, output, session){ - volumes <- c("Working directory"=getwd(),"Home Directory"="~") shiny::observe({ - shinyFiles::shinyFileChoose(input,'load', roots = volumes, - filetypes=c("R","Rmd","Rdata","RData")) - fileinfo <- shinyFiles::parseFilePaths(volumes, input$load) + shinyFiles::shinyFileChoose(input = input, + id = 'load', + roots = file_dialog_volumes, + filetypes = c("R","Rmd","Rdata","RData")) + fileinfo <- shinyFiles::parseFilePaths(roots = file_dialog_volumes, + selection = input$load) if (length(fileinfo$datapath) != 0) { - shiny::updateTextInput(session, "filename", value = fileinfo$datapath) + shiny::updateTextInput(session = session, + inputId = "filename", + value = fileinfo$datapath) docker_output <- determineDockerFunctionArguments(fileinfo$datapath) output$outputfile <- shiny::renderText({ - paste("Output will be written to:",docker_output[['output_filename']]) + paste("Output will be written to:", file.path(dirname(fileinfo$datapath), docker_output[['output_filename']])) }) } }) + shiny::observeEvent(input$done, { - - # Here is where your Shiny application might now go an affect the - # contents of a document open in RStudio, using the `rstudioapi` package. - # - # At the end, your application should call 'stopApp()' here, to ensure that - # the gadget is closed after 'done' is clicked. - + # Exit app to ensure that the gadget is closed after 'done' is clicked. + shiny::stopApp() + # Throw error if nothing entered if (nchar(input$filename) == 0) { - stop("No file selected") - } + stop("No file selected") + } # Convert to an output file - fn_args <- determineDockerFunctionArguments(input$filename) + function_arguments <- determineDockerFunctionArguments(input$filename) # Store current directory curr_dir <- getwd() # Change to script directory setwd(dirname(input$filename)) # Create docker file - dockerfile_object <- containerit::dockerfile(from=basename(input$filename), + dockerfile_object <- containerit::dockerfile(from = basename(input$filename), copy = "script", - cmd=fn_args[['cmd']] - ) + cmd = function_arguments[['cmd']]) # Output to desired path - containerit::write(dockerfile_object, file = fn_args[['output_filename']]) + containerit::write(dockerfile_object, file = function_arguments[['output_filename']]) # Change back to original directory setwd(curr_dir) - # Output docker instructions - cat(paste0("\nInstructions to run docker container from command line:\n - >> docker build . -t [tag] -f ", basename(fn_args[['output_filename']])), "\n - >> docker run -t [tag]") - # Exit app - shiny::stopApp() + + print_docker_instructions(function_arguments[['output_filename']]) }) - + } - - - viewer <- shiny::dialogViewer(dialogName = "containerit") - shiny::runGadget(ui, server, viewer = viewer) + + viewer <- shiny::dialogViewer(dialogName = "containerit", height = 400) + shiny::runGadget(app = ui, server = server, viewer = viewer) } -fromFileAddIn() \ No newline at end of file +fromFile() diff --git a/inst/scripts/package-workspace-directory.R b/inst/scripts/package-workspace-directory.R index 7ddb79d..f62ee14 100644 --- a/inst/scripts/package-workspace-directory.R +++ b/inst/scripts/package-workspace-directory.R @@ -1,63 +1,53 @@ ## GUI - package workspace directory - - # modified shinyDir button to fit inside miniPage +fromWorkspaceDirectory <- function(){ -workspaceDirectory_addin <- function(){ - ui <- miniUI::miniPage( scrollable = TRUE, miniUI::gadgetTitleBar("Docker file creation"), miniUI::miniContentPanel( shiny::fillCol( shiny::fillRow( - shiny::actionButton("choose_dir", "Choose Directory"), - height = '50px'), + shiny::actionButton(inputId = "choose_dir", + label = "Choose directory"), + height = "50px"), shiny::fillRow( - shiny::fillCol(shiny::p("Directory to package:"), - shiny::textOutput('outputfilepath')) + shiny::fillCol(shiny::p("Directory to package:"), + shiny::textOutput("outputfilepath")) ), - shiny::fillRow(shiny::p("Choose the location of the directory you wish to package. Containerit searches for the first occurence of an R script, or otherwise the first occurence of an R markdown file. It then proceeds to package this file along with all other resources in the directory."), height = "30px") + shiny::fillRow(shiny::p("Choose the location of the directory you wish to package. Containerit searches for the first occurence of an R script, or otherwise the first occurence of an R Markdown file. It then proceeds to package this file along with all other resources in the directory."), + height = "30px") ) ) ) - + server <- function(input, output, session){ - path <- shiny::reactiveValues(data = NULL) #set to wd? - dockerfilename <- shiny::reactiveValues(data = NULL) - + dockerfilename <- shiny::reactiveValues(data = NULL) + shiny::observeEvent(input$choose_dir, { - path$data <- rstudioapi::selectDirectory() # capture user's path entry - filename<- gsub(".*/","",path$data) # extract the name of the dir - dockerfilename$data <- paste0(path$data,"/",filename,".dockerfile") + path$data <- rstudioapi::selectDirectory() + dirname <- basename(path$data) + dockerfilename$data <- file.path(path$data, paste0(dirname, ".Dockerfile")) output$outputfilepath <- shiny::renderText({path$data}) }) - - + shiny::observeEvent(input$done, { - - if(!is.null(path$data)){ - # Exit app first + if (!is.null(path$data) && !is.null(dockerfilename$data)) { shiny::stopApp() - # Create docker file - print(path$data) - dockerfile_object <- containerit::dockerfile(from = path$data) - #print(dockerfile_object) - # Output to desired path + + # Create docker file and output to desired path + dockerfile_object <- containerit::dockerfile(from = path$data, + copy = basename(path$data)) containerit::write(dockerfile_object, file = dockerfilename$data) - - # Output docker instructions - cat(paste0("\nInstructions to run docker container from command line:\n - >> docker build . -t [tag] -f ", basename(fn_args[['output_filename']])), "\n - >> docker run -t [tag]") - } #stop("Please Choose Directory Path") - + + print_docker_instructions(dockerfilename$data) + } }) - } - viewer <- shiny::dialogViewer(dialogName = "containerit", width = 800, height = 700) - shiny::runGadget(ui, server, viewer = viewer) + + viewer <- shiny::dialogViewer(dialogName = "containerit", height = 200) + shiny::runGadget(app = ui, server = server, viewer = viewer) } -workspaceDirectory_addin() \ No newline at end of file +fromWorkspaceDirectory()