├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── DESCRIPTION ├── LICENSE ├── LICENSE.md ├── NAMESPACE ├── NEWS.md ├── R ├── block.R ├── cabinets-package.R ├── cabinets.R ├── checks.R ├── edit.R ├── feedback.R ├── git.R └── utils.R ├── README.Rmd ├── README.md ├── cabinets.Rproj ├── codecov.yml ├── cran-comments.md ├── man ├── FileCabinet.Rd ├── cabinets.Rd ├── cabinets_options_set.Rd ├── create_cabinet.Rd ├── delete_cabinet.Rd ├── edit_r_profile.Rd ├── get_cabinets.Rd └── new_cabinet_proj.Rd └── tests ├── testthat.R └── testthat ├── test-FileCabinet.R ├── test-blocks.R ├── test-check_cabinet.R ├── test-checking-permissions.txt ├── test-feedback.R ├── test-feedback.txt ├── test-file-cabinet.txt ├── test-find-existing-cabinets.txt ├── test-name-doesnt-exist.txt ├── test-name.R ├── test-none-available.txt ├── test-options.R ├── test-paths.R ├── test-permission-granted.txt ├── test-permissions.R ├── test-print-available.txt ├── test-project-doesnt-exist.txt └── test-projects.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^cabinets\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^codecov\.yml$ 5 | ^LICENSE\.md$ 6 | ^cran-comments\.md$ 7 | ^CRAN-RELEASE$ 8 | ^CODE_OF_CONDUCT\.md$ 9 | ^README\.md$ 10 | ^README\.Rmd$ 11 | ^\.github$ 12 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # For help debugging build failures open an issue on the RStudio community with the 'github-actions' tag. 2 | # https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - dev 8 | pull_request: 9 | branches: 10 | - master 11 | - dev 12 | 13 | name: R-CMD-check 14 | 15 | jobs: 16 | R-CMD-check: 17 | runs-on: ${{ matrix.config.os }} 18 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | config: 24 | - {os: macOS-latest, r: 'release'} 25 | - {os: windows-latest, r: 'release'} 26 | - {os: windows-latest, r: '3.6'} 27 | - {os: ubuntu-16.04, r: 'devel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 28 | - {os: ubuntu-16.04, r: 'release', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 29 | - {os: ubuntu-16.04, r: 'oldrel', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 30 | - {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 31 | 32 | env: 33 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 34 | RSPM: ${{ matrix.config.rspm }} 35 | 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: r-lib/actions/setup-r@master 39 | with: 40 | r-version: ${{ matrix.config.r }} 41 | - name: Install dependencies 42 | run: | 43 | install.packages(c("remotes", "rcmdcheck")) 44 | remotes::install_deps(dependencies = TRUE) 45 | shell: Rscript {0} 46 | - name: Check 47 | run: rcmdcheck::rcmdcheck(args = "--no-manual", error_on = "error") 48 | shell: Rscript {0} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who 4 | contribute through reporting issues, posting feature requests, updating documentation, 5 | submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for 8 | everyone, regardless of level of experience, gender, gender identity and expression, 9 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 10 | 11 | Examples of unacceptable behavior by participants include the use of sexual language or 12 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 13 | insults, or other unprofessional conduct. 14 | 15 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 16 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 17 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 18 | from the project team. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 21 | opening an issue or contacting one or more of the project maintainers. 22 | 23 | This Code of Conduct is adapted from the Contributor Covenant 24 | (https://www.contributor-covenant.org), version 1.0.0, available at 25 | https://contributor-covenant.org/version/1/0/0/. 26 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cabinets 2 | Title: Project Specific Workspace Organization Templates 3 | Version: 0.6.0 4 | Authors@R: 5 | person(given = "Nick", 6 | family = "Williams", 7 | role = c("aut", "cre"), 8 | email = "ntwilliams.personal@gmail.com", 9 | comment = c(ORCID = "0000-0002-1378-4831")) 10 | Description: Creates project specific directory and file templates that are 11 | written to a .Rprofile file. Upon starting a new R session, these templates 12 | can be used to streamline the creation of new directories that are 13 | standardized to the user's preferences and can include the initiation of a 14 | git repository, an RStudio R project, and project-local dependency management 15 | with the 'renv' package. 16 | License: MIT + file LICENSE 17 | Encoding: UTF-8 18 | LazyData: true 19 | RoxygenNote: 7.1.0 20 | URL: https://github.com/nt-williams/cabinets 21 | BugReports: https://github.com/nt-williams/cabinets/issues 22 | Imports: 23 | R6, 24 | glue, 25 | rstudioapi, 26 | crayon, 27 | fs, 28 | stringr, 29 | utils, 30 | git2r, 31 | cli, 32 | renv 33 | Suggests: 34 | testthat (>= 2.1.0), 35 | withr 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2019 2 | COPYRIGHT HOLDER: Nicholas Williams 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 Nicholas Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(FileCabinet) 4 | export(cabinets_options_set) 5 | export(create_cabinet) 6 | export(delete_cabinet) 7 | export(edit_r_profile) 8 | export(get_cabinets) 9 | export(new_cabinet_proj) 10 | importFrom(R6,R6Class) 11 | importFrom(utils,capture.output) 12 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # cabinets 0.6.0 2 | 3 | * `new_cabinet_proj()` has a new argument, `renv = TRUE` or (`FALSE`) that optionally allows for the creation of package management infrastructure with the {renv} package. 4 | 5 | # cabinets 0.5.0 6 | 7 | * new function `delete_cabinet()` for deleting cabinets without the user having to open the .Rprofile 8 | * upon creation of a new cabinet, R no longer automatically restarts if in RStudio 9 | * user feedback has generally been improved with the cli package 10 | * a variety of dependencies have been removed 11 | * creation of new RStudio projects has been handed over to the rstudioapi package 12 | 13 | # cabinets 0.4.0 14 | 15 | * `new_cabinet_proj()` now includes a feature for the initiation of a git repository when creating a new project. 16 | 17 | # cabinets 0.3.2.9000 18 | 19 | * bug fix for cabinets permission when .Rprofile already exists on first use. 20 | * bug fix for unrecognized characters when printing cabinet structure 21 | 22 | # cabinets 0.3.1 23 | 24 | * `edit_r_profile()` can now be called to automatically open the .Rprofile file for editing 25 | * CRAN policy violation resolved 26 | 27 | # cabinets 0.2.2 28 | 29 | * initial release on CRAN 30 | -------------------------------------------------------------------------------- /R/block.R: -------------------------------------------------------------------------------- 1 | 2 | block_delete <- function(block_start, block_end, 3 | path = file.path(normalizePath("~"), ".Rprofile")) { 4 | block_lines <- read_utf8(path) 5 | to_delete <- block_find(block_lines, 6 | block_start, 7 | block_end) 8 | to_delete <- seq(to_delete[1] - 1L, to_delete[2] + 1L) 9 | new_lines <- 10 | enc2utf8(gsub("\r?\n", platform_line_ending(), block_lines[-to_delete])) 11 | 12 | con <- file(path, open = "wb", encoding = "utf-8") 13 | writeLines(new_lines, con = con, sep = platform_line_ending()) 14 | on.exit(close(con)) 15 | } 16 | 17 | # based on code from the usethis package 18 | block_find <- function(lines, block_start, block_end) { 19 | if (is.null(lines)) { 20 | return(NULL) 21 | } 22 | 23 | start <- which(lines == block_start) 24 | end <- which(lines == block_end) 25 | 26 | if (length(start) == 0 && length(end) == 0) { 27 | return(NULL) 28 | } 29 | 30 | c(start + 1L, end - 1L) 31 | } 32 | -------------------------------------------------------------------------------- /R/cabinets-package.R: -------------------------------------------------------------------------------- 1 | #' Streamlined organization with project specific custom templates 2 | #' 3 | #' Cabinets makes it easy to create project specific file structure templates 4 | #' that can be referenced at the start of any R session. Cabinets works by writing 5 | #' project specific file templates to the .Rprofile file of the default working directory. 6 | #' Doing so allows the templates to be accessed in new R sessions without having to 7 | #' redefine them. On first use, users will be prompted for explicit permission to 8 | #' write to .Rprofile. Permission to write can be revoked at any time by setting the 9 | #' permission option in the .Rprofile file to FALSE. Due to these explicit permission 10 | #' requirements, cabinets will only work in interactive R sessions. 11 | #' 12 | #' @docType package 13 | #' @name cabinets 14 | #' @importFrom utils capture.output 15 | #' @importFrom R6 R6Class 16 | NULL 17 | -------------------------------------------------------------------------------- /R/cabinets.R: -------------------------------------------------------------------------------- 1 | #' R6 class for a cabinet 2 | #' 3 | #' Constructs an R6 class of FileCabinet. Objects of class 4 | #' FileCabinet contain information that is used by \code{new_cabinet_proj()} 5 | #' to create project directories. 6 | #' 7 | #' @export 8 | FileCabinet <- R6Class('FileCabinet', 9 | public = list( 10 | #' @field name cabinet name. 11 | name = NULL, 12 | 13 | #' @field directory the path to where future directories will be created, a string. 14 | directory = NULL, 15 | 16 | #' @field structure the directory structure, a list. 17 | structure = NULL, 18 | 19 | #' @details 20 | #' Create a new `FileCabinet` object. 21 | #' 22 | #' @param name cabinet name. 23 | #' @param directory the path to where future directories will be created, a string. 24 | #' @param structure the directory structure, a list. 25 | #' @return A cabinet object. 26 | #' 27 | #' @examples 28 | #' FileCabinet$new("test", "a/path", 29 | #' list(code = NULL, 'data/derived' = NULL, 'data/source' = NULL)) 30 | initialize = function(name, directory, structure) { 31 | stopifnot(is.character(name), length(name) == 1) 32 | stopifnot(is.character(directory)) 33 | stopifnot(is.list(structure)) 34 | 35 | self$name <- name 36 | self$directory <- fs::path_tidy(directory) 37 | self$structure <- structure 38 | }, 39 | 40 | #' @details 41 | #' Print an object of class FileCabinet. 42 | 43 | print = function() { 44 | cat('Cabinet name: ', 45 | cat_green(self$name), 46 | '\n', 47 | sep = '') 48 | cat('Cabinet path: ', 49 | cat_path(self$directory), 50 | '\n', 51 | sep = '') 52 | cat('Cabinet structure: \n') 53 | print_structure(self$structure) 54 | } 55 | ) 56 | ) 57 | 58 | #' Create a cabinet template 59 | #' 60 | #' \code{create_cabinet} writes code to the .Rprofile file so 61 | #' that when new R sessions are started, the newly created 62 | #' cabinet, an R6 object of class FileCabinet, is available 63 | #' in the global environment as a hidden object. The cabinet 64 | #' simply stores file location and file template information 65 | #' that \code{new_cabinet_proj} uses to create new projects 66 | #' with the pre-defined structure. 67 | #' 68 | #' @param name Name of the cabinet; character of length 1. 69 | #' This is how the cabinet will be referenced, so best to 70 | #' chose something memorable. 71 | #' @param directory The file path for where the cabinet will exist. 72 | #' @param structure A list of paths of folders/files to 73 | #' create. See details for further explanation. 74 | #' @param .alias An optional name for the object the cabinet 75 | #' will be stored in R as. Defaults to \code{name}. 76 | #' 77 | #' @return An R6 object of class FileCabinet. The code to 78 | #' generate this object is written to the .Rprofile file 79 | #' of the home directory. 80 | #' @details Before writing to or creating a .Rprofile file, 81 | #' cabinets will explicitly ask for the user's permission to on exit. 82 | #' The cabinet structure should be defined using a list with the 83 | #' names defining folder paths. List values should be set to NULL. 84 | #' @seealso \code{\link{new_cabinet_proj}} 85 | #' @export 86 | create_cabinet <- function(name, 87 | directory, 88 | structure, 89 | .alias = name) { 90 | check_interactive() 91 | check_permissions() 92 | check_r_profile() 93 | check_name(name) 94 | write_cabinet(name, directory, structure, .alias) 95 | created_cabinet(.alias) 96 | } 97 | 98 | write_cabinet <- function(name, directory, structure, .alias) { 99 | directory <- fs::path_tidy(paste(directory, collapse = .Platform$file.sep)) 100 | 101 | newFileCabinet <- 102 | call("$", x = call("::", 103 | pkg = substitute(cabinets), 104 | name = substitute(FileCabinet)), 105 | name = substitute(new)) 106 | 107 | value <- 108 | as.call(list(newFileCabinet, 109 | name = name, 110 | directory = directory, 111 | structure = structure)) 112 | 113 | cabinet <- call("<-", x = as.symbol(paste0(".", .alias)), value = value) 114 | con <- file(file.path(normalizePath("~"), ".Rprofile"), open = "a") 115 | writeLines(glue::glue("## {name} cabinet start"), con = con) 116 | capture.output(cabinet, file = con, append = TRUE) 117 | writeLines(glue::glue("## {name} cabinet end"), con = con) 118 | on.exit(close(con)) 119 | } 120 | 121 | #' Create a new project using a cabinet template 122 | #' 123 | #' Generate new project directories using cabinet templates. 124 | #' 125 | #' @param cabinet The name of the cabinet template. Available cabinets can 126 | #' be found using \code{get_cabinets()}. 127 | #' @param project_name The name of the project to store in the cabinet, 128 | #' a character string. Can be a file path pointing to a directory 129 | #' within the specified cabinet. 130 | #' @param r_project Logical, should an Rproject be created. Default is 131 | #' TRUE if working in RStudio (only works in RStudio). 132 | #' @param open Logical, if creating an Rproject, should that project 133 | #' be opened once created. Default is TRUE if working in 134 | #' RStudio (only works in RStudio). 135 | #' @param renv Logical, should a \code{renv} project be initiated. 136 | #' If \code{TRUE}, \code{renv} project infrastructure will be created using 137 | #' \code{\link[renv]{scaffold}}. 138 | #' @param git Logical, should a git repository be initiated. 139 | #' @param git_root A path relative to the project to initiate the 140 | #' git repository. Default is NULL and the repository is 141 | #' initiated at the root of the project. 142 | #' @param git_ignore Character vector of files and directories 143 | #' to add to .gitignore file. 144 | #' 145 | #' @return Creates a new directory at the path specified in the 146 | #' cabinet template. If \code{r_project = TRUE}, a .Rproj file 147 | #' will also be created using the project name. If open is set 148 | #' to TRUE, the new R project will opened in a new R session. 149 | #' @seealso \code{\link{create_cabinet}} 150 | #' @export 151 | new_cabinet_proj <- function(cabinet, 152 | project_name, 153 | r_project = TRUE, 154 | open = TRUE, 155 | renv = TRUE, 156 | git = TRUE, 157 | git_root = NULL, 158 | git_ignore = NULL) { 159 | 160 | check_cabinet(deparse(substitute(cabinet))) 161 | 162 | if (in_rstudio() == FALSE) { 163 | r_project <- FALSE 164 | } 165 | 166 | proj_path <- file.path(cabinet$directory, project_name) 167 | proj_folders <- file.path(proj_path, names(cabinet$structure)) 168 | 169 | check_project(proj_path) 170 | creating_project(project_name, cabinet$name) 171 | 172 | if (r_project) { 173 | rstudioapi::initializeProject(proj_path) 174 | } else { 175 | dir.create(proj_path, recursive = TRUE) 176 | open <- FALSE 177 | } 178 | 179 | create_subdirectories(proj_folders) 180 | 181 | if (renv) { 182 | capture.output(renv::scaffold(project = proj_path)) 183 | initiating_renv() 184 | } 185 | 186 | if (git) { 187 | if (is.null(git_root)) { 188 | git_root <- proj_path 189 | } else { 190 | git_root <- file.path(proj_path, git_root) 191 | } 192 | use_git(git_root, git_ignore) 193 | } 194 | 195 | if (open) { 196 | opening_project(project_name) 197 | Sys.sleep(2) 198 | rstudioapi::openProject(proj_path, TRUE) 199 | } 200 | } 201 | 202 | #' Open .Rprofile for editing 203 | #' 204 | #' \code{edit_r_profile} opens the .Rprofile file for editing. 205 | #' If the .Rprofile file doesn't exist an error message will be returned. 206 | #' This is essentially a wrapper function for \code{file.edit}. 207 | #' 208 | #' @return A message that .Rprofile is being opened or that it doesn't exist. 209 | #' @export 210 | edit_r_profile <- function() { 211 | rprof_path <- file.path(normalizePath("~"), ".Rprofile") 212 | status <- tryCatch(if (file.exists(rprof_path)) { 213 | cli::cli_text("Opening .Rprofile") 214 | go(rprof_path) 215 | } else { 216 | stop() 217 | }, error = function(e) { 218 | no_r_profile() 219 | }) 220 | invisible(status) 221 | } 222 | -------------------------------------------------------------------------------- /R/checks.R: -------------------------------------------------------------------------------- 1 | #' Set cabinets options 2 | #' 3 | #' @param ... Options to be set 4 | #' @param .envir Environment to set options in; if NULL, will use environment at \code{parent.frame()} 5 | #' 6 | #' @return If no options are set, returns the options specified in \code{options}. 7 | #' @details Mainly used for specifying if cabinets has permission to write to .Rprofile. Permission can be revoked at any time by opening the .Rprofile file and setting \code{"cabinets.permission" = FALSE}. 8 | #' @export 9 | cabinets_options_set <- function(..., .envir = NULL) { 10 | if (is.null(.envir)) { 11 | .envir <- parent.frame() 12 | } else { 13 | .envir <- .envir 14 | } 15 | new_opts <- list(...) 16 | old_opts <- lapply(names(new_opts), getOption) 17 | do.call(base::options, new_opts) 18 | } 19 | 20 | ask_permission <- function() { 21 | interact <- getOption("cabinet.testing") 22 | if (is.null(interact)) { 23 | switch(utils::menu( 24 | c("YES, I do give permission.", 25 | "NO, I do not give permission."), 26 | title = "Do you give permission to write .Rprofile and directory files?" 27 | ), 28 | perm_yes(), 29 | perm_no() 30 | ) 31 | } else { 32 | if (identical(getOption("cabinet.testingPerm"), TRUE)) { 33 | perm_yes() 34 | } else { 35 | perm_no() 36 | } 37 | } 38 | } 39 | 40 | check_interactive <- function() { 41 | if (!interactive()) { 42 | stop("cabinets can only be run in an interactive R session.") 43 | } 44 | } 45 | 46 | check_permissions <- function() { 47 | consent <- getOption("cabinets.permission") 48 | 49 | if (is.null(consent)) { 50 | ask_permission() 51 | } else if (identical(consent, TRUE)) { 52 | cli::cli_alert_success("Checking for permissions") 53 | } else if (identical(consent, FALSE)) { 54 | stop("Permission denied.", call. = FALSE) 55 | } 56 | } 57 | 58 | check_r_profile <- function() { 59 | file_stat <- !file.exists(file.path(normalizePath("~"), ".Rprofile")) 60 | status <- tryCatch( 61 | if (file_stat) { 62 | new_rprof() 63 | } else { 64 | old_rprof() 65 | } 66 | ) 67 | invisible(status) 68 | } 69 | 70 | check_name <- function(name) { 71 | name_stat <- exists(paste0(".", name), envir = .GlobalEnv) 72 | status <- tryCatch( 73 | if (name_stat) { 74 | stop() 75 | } else { 76 | checking_name() 77 | }, error = function(e) { 78 | stop("Cabinet already exists!", call. = FALSE) 79 | } 80 | ) 81 | invisible(status) 82 | } 83 | 84 | check_cabinet <- function(cabinet) { 85 | status <- tryCatch( 86 | if (exists(cabinet, envir = .GlobalEnv)) { 87 | checking_existence() 88 | } else { 89 | stop() 90 | }, error = function(e) { 91 | cabinet_not_found() 92 | stop_quietly() 93 | } 94 | ) 95 | invisible(status) 96 | } 97 | 98 | check_project <- function(proj_path) { 99 | status <- tryCatch( 100 | if (dir.exists(proj_path)) { 101 | stop() 102 | } else { 103 | checking_project() 104 | }, error = function(e) { 105 | stop("Project already exists in cabinet!", call. = FALSE) 106 | } 107 | ) 108 | invisible(status) 109 | } 110 | 111 | check_git <- function() { 112 | files <- git2r::git_config_files() 113 | git_stat <- is.na(files[3, "path"]) 114 | config_stat <- git2r::config() 115 | 116 | if (git_stat || length(config_stat) == 0) { 117 | status <- FALSE 118 | } else { 119 | status <- TRUE 120 | checking_git() 121 | } 122 | 123 | return(status) 124 | } 125 | -------------------------------------------------------------------------------- /R/edit.R: -------------------------------------------------------------------------------- 1 | 2 | cab_start <- "## {name} cabinet start" 3 | cab_end <- "## {name} cabinet end" 4 | 5 | #' Delete Cabinets 6 | #' 7 | #' @param cabinet An R6 object of class Cabinet written to the .Rprofile. 8 | #' 9 | #' @export 10 | delete_cabinet <- function(cabinet) { 11 | name <- cabinet$name 12 | cab_start <- glue::glue(cab_start) 13 | cab_end <- glue::glue(cab_end) 14 | block_delete(cab_start, cab_end) 15 | on.exit(rm(list = paste0(".", name), envir = .GlobalEnv)) 16 | } 17 | -------------------------------------------------------------------------------- /R/feedback.R: -------------------------------------------------------------------------------- 1 | 2 | #' Print available cabinets 3 | #' 4 | #' \code{get_cabinets} returns objects of class FileCabinet. 5 | #' 6 | #' @param envir The environment to check in. The default is the global environment. 7 | #' 8 | #' @return Objects of class FileCabinet found in the global environment. 9 | #' @export 10 | #' 11 | #' @examples 12 | #' get_cabinets() 13 | get_cabinets <- function(envir = parent.frame()) { 14 | hidden <- as.list(ls(all.names = TRUE, envir = envir)) 15 | classes <- lapply(hidden, function(x) class(eval(parse(text = x), envir = envir))) 16 | 17 | if (any(sapply(classes, function(x) "FileCabinet" %in% x))) { 18 | cli::cli_text("These are the available cabinets:") 19 | for (i in seq_along(classes)) { 20 | if ("FileCabinet" %in% classes[[i]]) cli::cli_ul(hidden[[i]]) 21 | } 22 | } else { 23 | no_cabinets() 24 | } 25 | } 26 | 27 | creating_project <- function(project_name, cabinet_name) { 28 | cli::cli_alert_success("Creating {project_name} using cabinet template: {.field {p0('.', cabinet_name)}}") 29 | } 30 | 31 | opening_project <- function(project_name) { 32 | cli::cli_ul("Opening R project, {basename(project_name)}, in a new session") 33 | } 34 | 35 | checking_project <- function() { 36 | cli::cli_alert_success("Checking if project already exists") 37 | } 38 | 39 | checking_existence <- function() { 40 | cli::cli_alert_success("Checking cabinet existence") 41 | } 42 | 43 | cabinet_not_found <- function() { 44 | cli::cli_alert_danger("Cabinet not found!") 45 | get_cabinets() 46 | } 47 | 48 | checking_name <- function() { 49 | cli::cli_alert_success("Checking cabinet name") 50 | } 51 | 52 | checking_git <- function() { 53 | cli::cli_alert_success("Checking for git configuration") 54 | } 55 | 56 | no_cabinets <- function() { 57 | cli::cli_alert_danger("No cabinets found. Cabinets can be created using {.code create_cabinets()}") 58 | } 59 | 60 | no_r_profile <- function() { 61 | cli::cli_alert_danger("{.path .Rprofile} doesn't exist") 62 | } 63 | 64 | created_cabinet <- function(name) { 65 | cli::cli_alert_success("Cabinet .{name} created") 66 | cli::cli_ul(c("Restart R for changes to take effect", 67 | "Cabinet can be called with .{name}")) 68 | } 69 | 70 | initiated_git <- function(root) { 71 | cli::cli_alert_success("Git repository initiated in {.path {root}}") 72 | } 73 | 74 | no_git <- function() { 75 | cli::cli_alert_warning("Git not found or git not fully configured") 76 | cli::cli_ul("Check out {.url https://happygitwithr.com/} for configuring git with R.") 77 | } 78 | 79 | initiating_renv <- function() { 80 | cli::cli_alert_success("Creating renv infrastructure") 81 | } 82 | -------------------------------------------------------------------------------- /R/git.R: -------------------------------------------------------------------------------- 1 | 2 | use_git <- function(git_root, git_ignore = NULL) { 3 | status <- tryCatch( 4 | if (check_git()) { 5 | init_git(git_root, git_ignore) 6 | } else { 7 | warning() 8 | }, warning = function(w) { 9 | no_git() 10 | } 11 | ) 12 | invisible(status) 13 | } 14 | 15 | init_git <- function(git_root, git_ignore = NULL) { 16 | ignores <- c(".Rproj.user", ".Rhistory", ".Rdata", ".Ruserdata") 17 | if (!is.null(git_ignore)) ignores <- c(ignores, git_ignore) 18 | ignores <- paste0(ignores, "\n", collapse = "") 19 | git2r::init(git_root) 20 | gi <- file.path(git_root, ".gitignore") 21 | writeLines(ignores, gi) 22 | initiated_git(git_root) 23 | } 24 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | in_rstudio <- function() { 2 | (Sys.getenv("RSTUDIO") == "1") && !nzchar(Sys.getenv("RSTUDIO_TERM")) 3 | } 4 | 5 | cat_green <- function(...) { 6 | crayon::green(...) 7 | } 8 | 9 | cat_path <- function(...) { 10 | crayon::blue(...) 11 | } 12 | 13 | create_subdirectories <- function(folders) { 14 | for (i in 1:length(folders)) { 15 | dir.create(folders[i], recursive = TRUE) 16 | } 17 | } 18 | 19 | get_paths <- function(x) { 20 | files <- fs::path_tidy(x) 21 | n <- stringr::str_count(files, "/") 22 | recursively_repeat <- function(.x, .reps, .f, ...) { 23 | if (.reps == 0) { 24 | .x 25 | } else { 26 | recursively_repeat(.f(.x, ...), .reps - 1, .f, ...) 27 | } 28 | } 29 | sapply(0:n, function(y) recursively_repeat(files, y, dirname)) 30 | } 31 | 32 | print_structure <- function(x, ...) { 33 | files <- paste0("./", 34 | unique(unlist(sapply(names(x), 35 | get_paths), 36 | use.names = FALSE))) 37 | by_dir <- split(files, fs::path_dir(files)) 38 | ch <- str_chars() 39 | 40 | print_files <- function(x, indent) { 41 | leafs <- by_dir[[x]] 42 | for (i in seq_along(leafs)) { 43 | if (i == length(leafs)) { 44 | cat(indent, 45 | p0(ch$l, ch$h, ch$h, " "), 46 | fs::path_file(leafs[[i]]), 47 | "\n", 48 | sep = "") 49 | print_files(leafs[[i]], 50 | paste0(indent, " ")) 51 | } 52 | else { 53 | cat(indent, 54 | p0(ch$j, ch$h, ch$h, " "), 55 | fs::path_file(leafs[[i]]), 56 | "\n", 57 | sep = "") 58 | print_files(leafs[[i]], 59 | paste0(indent, 60 | p0(ch$v, " "))) 61 | } 62 | } 63 | } 64 | print_files(".", "") 65 | invisible(files) 66 | } 67 | 68 | str_chars <- function() { 69 | list(h = "\u2500", 70 | v = "\u2502", 71 | l = "\u2514", 72 | j = "\u251C") 73 | 74 | } 75 | 76 | p0 <- function(...) paste0(..., collapse = "") 77 | 78 | go <- function(path) { 79 | utils::file.edit(path) 80 | } 81 | 82 | # taken from the usethis package! 83 | read_utf8 <- function(path, n = -1L) { 84 | base::readLines(path, n = n, encoding = "UTF-8", warn = FALSE) 85 | } 86 | 87 | # taken from the usethis package! 88 | platform_line_ending <- function() { 89 | if (.Platform$OS.type == "windows") "\r\n" else "\n" 90 | } 91 | 92 | perm_no <- function() { 93 | cabinets_options_set("cabinets.permission" = FALSE) 94 | stop("Permission denied.", call. = FALSE) 95 | } 96 | 97 | perm_yes <- function() { 98 | cli::cli_alert_success("Checking for permissions") 99 | cabinets_options_set("cabinets.permission" = TRUE) 100 | } 101 | 102 | new_rprof <- function() { 103 | r_profile <- file.path(normalizePath("~"), ".Rprofile") 104 | file.create(r_profile) 105 | 106 | permission <- glue::glue( 107 | "# cabinets permission 108 | cabinets::cabinets_options_set('cabinets.permission' = TRUE)" 109 | ) 110 | 111 | writeLines(permission, r_profile) 112 | cli::cli_alert_success("Creating .Rprofile") 113 | } 114 | 115 | old_rprof <- function() { 116 | r_profile_path <- file.path(normalizePath("~"), ".Rprofile") 117 | rprof_lines <- readLines(r_profile_path) 118 | perm_status <- any(grepl("cabinets_options_set", rprof_lines)) 119 | 120 | permission <- glue::glue( 121 | "# cabinets permission 122 | cabinets::cabinets_options_set('cabinets.permission' = TRUE)" 123 | ) 124 | 125 | if (perm_status) { 126 | on.exit() 127 | } else { 128 | r_profile <- file(r_profile_path, open = "a") 129 | writeLines(permission, r_profile) 130 | close(r_profile) 131 | } 132 | cli::cli_alert_success("Checking for .Rprofile") 133 | } 134 | 135 | stop_quietly <- function() { 136 | opt <- options(show.error.messages = FALSE) 137 | on.exit(options(opt)) 138 | stop() 139 | } 140 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # cabinets 17 | 18 | 19 | [![CRAN status](https://www.r-pkg.org/badges/version/cabinets)](https://CRAN.R-project.org/package=cabinets) 20 | ![](http://cranlogs.r-pkg.org/badges/grand-total/cabinets) 21 | [![Lifecycle: stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://www.tidyverse.org/lifecycle/#stable) 22 | [![Codecov test coverage](https://codecov.io/gh/nt-williams/cabinets/branch/master/graph/badge.svg)](https://codecov.io/gh/nt-williams/cabinets?branch=master) 23 | [![R build status](https://github.com/nt-williams/cabinets/workflows/R-CMD-check/badge.svg)](https://github.com/nt-williams/cabinets/actions) 24 | 25 | 26 | Reproducibility can be tedious, **cabinets** makes it easier! **cabinets** creates project specific file structure templates that can be referenced at the start of any R session. These templates can then be used to initiate future projects with the option for them to be initiatied with a git repository. **cabinets** works by writing project specific file templates to the .Rprofile file of the default working directory. Doing so allows the templates to be accessed in new R sessions without having to redefine them. 27 | 28 | **cabinets** has two main functions: `create_cabinet()` and `new_cabinet_proj()`. `create_cabinet()` constructs an R6 object of class `FileCabinet` which is then written to a .Rprofile file. At the start of fresh R sessions, the .Rprofile file loads the previously created cabinets for further use. `new_cabinet_proj()` can then be used to create future projects based on a cabinet template. Projects have the option of being created with an RStudio project and a git repository. 29 | 30 | Users will be prompted for explicit permission to write to .Rprofile on first use. Permission to write can be revoked at any time by setting the permission option in the .Rprofile file to `FALSE`. Due to these explicit permission requirements, **cabinets** will only work in interactive R sessions. 31 | 32 | ## Installation 33 | 34 | You can install the released version of **cabinets** from [CRAN](https://CRAN.R-project.org) with: 35 | 36 | ``` r 37 | install.packages("cabinets") 38 | ``` 39 | 40 | And the development version from [GitHub](https://github.com/) with: 41 | 42 | ``` r 43 | # install.packages("devtools") 44 | devtools::install_github("nt-williams/cabinets@dev") 45 | ``` 46 | 47 | ## Motivation 48 | 49 | Different organizations, research groups, projects, etc. have different standard directories. One organization might have the standard file structure: 50 | 51 | ``` 52 | ├── data 53 | │ ├── derived 54 | │ └── source 55 | ├── code 56 | ├── reports 57 | ├── documents 58 | └── log 59 | ``` 60 | 61 | While another uses a structure like this: 62 | 63 | ``` 64 | ├── Code 65 | │ ├── ReportsCode 66 | │ └── AnalysisCode 67 | ├── Notes 68 | └── Log 69 | ``` 70 | 71 | **cabinets** makes it easy to define these templates once and then call them for use whenever starting new projects in R. 72 | 73 | ## Example 74 | 75 | A cabinet has three components: a name, a location, and a file structure. The location defines where new directories will be created from a cabinet template. The file structure defines, well, the structure of this directory. We use a list to define the structure, where the name of each list index is a unique relative path within the new directory. Using the first project file structure described above, lets create a new cabinet. 76 | 77 | ``` r 78 | loc <- "~/Desktop" 79 | 80 | file_str <- list( 81 | 'data' = NULL, 82 | 'code' = NULL, 83 | 'data/derived' = NULL, 84 | 'data/source' = NULL, 85 | 'reports' = NULL, 86 | 'documents' = NULL, 87 | 'log' = NULL 88 | ) 89 | 90 | create_cabinet(name = "contract", 91 | directory = loc, 92 | structure = file_str) 93 | 94 | #> ✓ Checking for permissions 95 | #> ✓ Checking for .Rprofile 96 | #> ✓ Checking cabinet name 97 | #> ✓ Cabinet .contract created 98 | #> ● Restart R for changes to take effect 99 | #> ● Cabinet can be called with .contract 100 | ``` 101 | 102 | The cabinet is now created and doesn't have to be redefined in future R sessions. To examine the cabinet we just call it. 103 | 104 | ``` r 105 | .contract 106 | 107 | #> Cabinet name: contract 108 | #> Cabinet path: ~/Desktop 109 | #> Cabinet structure: 110 | #> ├── data 111 | #> │ ├── derived 112 | #> │ └── source 113 | #> ├── code 114 | #> ├── reports 115 | #> ├── documents 116 | #> └── log 117 | ``` 118 | 119 | `new_cabinet_proj()` is then used to create a new directory from a cabinet template. 120 | 121 | ``` r 122 | new_cabinet_proj(cabinet = .contract, 123 | project_name = "project", 124 | git = TRUE, 125 | git_ignore = "data", 126 | renv = TRUE) 127 | ``` 128 | 129 | ## Similar implementations 130 | 131 | Similar implementations exist elsewhere. **cabinets** is unique for giving the user the ability to design their own project templates. These are all great packages, use what's best for you! 132 | 133 | * [workflowr](https://github.com/jdblischak/workflowr) 134 | * [projects](https://github.com/NikKrieger/projects) 135 | * [starters](https://github.com/lockedata/starters) 136 | * [ProjectTemplate](https://github.com/KentonWhite/ProjectTemplate) 137 | 138 | ## Contributing 139 | 140 | Please note that the **cabinets** project is released with a 141 | [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 142 | By contributing to this project, you agree to abide by its terms. 143 | 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # cabinets 5 | 6 | 7 | 8 | [![CRAN 9 | status](https://www.r-pkg.org/badges/version/cabinets)](https://CRAN.R-project.org/package=cabinets) 10 | ![](http://cranlogs.r-pkg.org/badges/grand-total/cabinets) [![Lifecycle: 11 | stable](https://img.shields.io/badge/lifecycle-stable-brightgreen.svg)](https://www.tidyverse.org/lifecycle/#stable) 12 | [![Codecov test 13 | coverage](https://codecov.io/gh/nt-williams/cabinets/branch/master/graph/badge.svg)](https://codecov.io/gh/nt-williams/cabinets?branch=master) 14 | [![R build 15 | status](https://github.com/nt-williams/cabinets/workflows/R-CMD-check/badge.svg)](https://github.com/nt-williams/cabinets/actions) 16 | 17 | 18 | Reproducibility can be tedious, **cabinets** makes it easier\! 19 | **cabinets** creates project specific file structure templates that can 20 | be referenced at the start of any R session. These templates can then be 21 | used to initiate future projects with the option for them to be 22 | initiatied with a git repository. **cabinets** works by writing project 23 | specific file templates to the .Rprofile file of the default working 24 | directory. Doing so allows the templates to be accessed in new R 25 | sessions without having to redefine them. 26 | 27 | **cabinets** has two main functions: `create_cabinet()` and 28 | `new_cabinet_proj()`. `create_cabinet()` constructs an R6 object of 29 | class `FileCabinet` which is then written to a .Rprofile file. At the 30 | start of fresh R sessions, the .Rprofile file loads the previously 31 | created cabinets for further use. `new_cabinet_proj()` can then be used 32 | to create future projects based on a cabinet template. Projects have the 33 | option of being created with an RStudio project and a git repository. 34 | 35 | Users will be prompted for explicit permission to write to .Rprofile on 36 | first use. Permission to write can be revoked at any time by setting the 37 | permission option in the .Rprofile file to `FALSE`. Due to these 38 | explicit permission requirements, **cabinets** will only work in 39 | interactive R sessions. 40 | 41 | ## Installation 42 | 43 | You can install the released version of **cabinets** from 44 | [CRAN](https://CRAN.R-project.org) with: 45 | 46 | ``` r 47 | install.packages("cabinets") 48 | ``` 49 | 50 | And the development version from [GitHub](https://github.com/) with: 51 | 52 | ``` r 53 | # install.packages("devtools") 54 | devtools::install_github("nt-williams/cabinets@dev") 55 | ``` 56 | 57 | ## Motivation 58 | 59 | Different organizations, research groups, projects, etc. have different 60 | standard directories. One organization might have the standard file 61 | structure: 62 | 63 | ├── data 64 | │ ├── derived 65 | │ └── source 66 | ├── code 67 | ├── reports 68 | ├── documents 69 | └── log 70 | 71 | While another uses a structure like this: 72 | 73 | ├── Code 74 | │ ├── ReportsCode 75 | │ └── AnalysisCode 76 | ├── Notes 77 | └── Log 78 | 79 | **cabinets** makes it easy to define these templates once and then call 80 | them for use whenever starting new projects in R. 81 | 82 | ## Example 83 | 84 | A cabinet has three components: a name, a location, and a file 85 | structure. The location defines where new directories will be created 86 | from a cabinet template. The file structure defines, well, the structure 87 | of this directory. We use a list to define the structure, where the name 88 | of each list index is a unique relative path within the new directory. 89 | Using the first project file structure described above, lets create a 90 | new cabinet. 91 | 92 | ``` r 93 | loc <- "~/Desktop" 94 | 95 | file_str <- list( 96 | 'data' = NULL, 97 | 'code' = NULL, 98 | 'data/derived' = NULL, 99 | 'data/source' = NULL, 100 | 'reports' = NULL, 101 | 'documents' = NULL, 102 | 'log' = NULL 103 | ) 104 | 105 | create_cabinet(name = "contract", 106 | directory = loc, 107 | structure = file_str) 108 | 109 | #> ✓ Checking for permissions 110 | #> ✓ Checking for .Rprofile 111 | #> ✓ Checking cabinet name 112 | #> ✓ Cabinet .contract created 113 | #> ● Restart R for changes to take effect 114 | #> ● Cabinet can be called with .contract 115 | ``` 116 | 117 | The cabinet is now created and doesn’t have to be redefined in future R 118 | sessions. To examine the cabinet we just call it. 119 | 120 | ``` r 121 | .contract 122 | 123 | #> Cabinet name: contract 124 | #> Cabinet path: ~/Desktop 125 | #> Cabinet structure: 126 | #> ├── data 127 | #> │ ├── derived 128 | #> │ └── source 129 | #> ├── code 130 | #> ├── reports 131 | #> ├── documents 132 | #> └── log 133 | ``` 134 | 135 | `new_cabinet_proj()` is then used to create a new directory from a 136 | cabinet template. 137 | 138 | ``` r 139 | new_cabinet_proj(cabinet = .contract, 140 | project_name = "project", 141 | git = TRUE, 142 | git_ignore = "data", 143 | renv = TRUE) 144 | ``` 145 | 146 | ## Similar implementations 147 | 148 | Similar implementations exist elsewhere. **cabinets** is unique for 149 | giving the user the ability to design their own project templates. These 150 | are all great packages, use what’s best for you\! 151 | 152 | - [workflowr](https://github.com/jdblischak/workflowr) 153 | - [projects](https://github.com/NikKrieger/projects) 154 | - [starters](https://github.com/lockedata/starters) 155 | - [ProjectTemplate](https://github.com/KentonWhite/ProjectTemplate) 156 | 157 | ## Contributing 158 | 159 | Please note that the **cabinets** project is released with a 160 | [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By contributing to 161 | this project, you agree to abide by its terms. 162 | -------------------------------------------------------------------------------- /cabinets.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 4 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: false 2 | 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | target: auto 8 | threshold: 1% 9 | patch: 10 | default: 11 | target: auto 12 | threshold: 1% 13 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Version 0.6.0 2 | 3 | ## Test environments 4 | 5 | * local OS X install, R 4.0.3 6 | * Windows (GitHub Actions), R 4.0.3 7 | * Windows (GitHub Actions), R 3.6 8 | * Windows (Win Builder), R devel 9 | * Ubuntu 16.04 (GitHub Actions), R devel 10 | * Ubuntu 16.04 (GitHub Actions), R 4.0.3 11 | * Ubuntu 16.04 (GitHub Actions), R 3.5 12 | 13 | ## R CMD check results 14 | 15 | 0 errors | 0 warnings | 0 notes 16 | 17 | ## Downstream dependencies 18 | 19 | There are no downstream dependencies 20 | 21 | 22 | -------------------------------------------------------------------------------- /man/FileCabinet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cabinets.R 3 | \name{FileCabinet} 4 | \alias{FileCabinet} 5 | \title{R6 class for a cabinet} 6 | \description{ 7 | Constructs an R6 class of FileCabinet. Objects of class 8 | FileCabinet contain information that is used by \code{new_cabinet_proj()} 9 | to create project directories. 10 | } 11 | \examples{ 12 | 13 | ## ------------------------------------------------ 14 | ## Method `FileCabinet$new` 15 | ## ------------------------------------------------ 16 | 17 | FileCabinet$new("test", "a/path", 18 | list(code = NULL, 'data/derived' = NULL, 'data/source' = NULL)) 19 | } 20 | \section{Public fields}{ 21 | \if{html}{\out{
}} 22 | \describe{ 23 | \item{\code{name}}{cabinet name.} 24 | 25 | \item{\code{directory}}{the path to where future directories will be created, a string.} 26 | 27 | \item{\code{structure}}{the directory structure, a list.} 28 | } 29 | \if{html}{\out{
}} 30 | } 31 | \section{Methods}{ 32 | \subsection{Public methods}{ 33 | \itemize{ 34 | \item \href{#method-new}{\code{FileCabinet$new()}} 35 | \item \href{#method-print}{\code{FileCabinet$print()}} 36 | \item \href{#method-clone}{\code{FileCabinet$clone()}} 37 | } 38 | } 39 | \if{html}{\out{
}} 40 | \if{html}{\out{}} 41 | \if{latex}{\out{\hypertarget{method-new}{}}} 42 | \subsection{Method \code{new()}}{ 43 | \subsection{Usage}{ 44 | \if{html}{\out{
}}\preformatted{FileCabinet$new(name, directory, structure)}\if{html}{\out{
}} 45 | } 46 | 47 | \subsection{Arguments}{ 48 | \if{html}{\out{
}} 49 | \describe{ 50 | \item{\code{name}}{cabinet name.} 51 | 52 | \item{\code{directory}}{the path to where future directories will be created, a string.} 53 | 54 | \item{\code{structure}}{the directory structure, a list.} 55 | } 56 | \if{html}{\out{
}} 57 | } 58 | \subsection{Details}{ 59 | Create a new `FileCabinet` object. 60 | } 61 | 62 | \subsection{Returns}{ 63 | A cabinet object. 64 | } 65 | \subsection{Examples}{ 66 | \if{html}{\out{
}} 67 | \preformatted{FileCabinet$new("test", "a/path", 68 | list(code = NULL, 'data/derived' = NULL, 'data/source' = NULL)) 69 | } 70 | \if{html}{\out{
}} 71 | 72 | } 73 | 74 | } 75 | \if{html}{\out{
}} 76 | \if{html}{\out{}} 77 | \if{latex}{\out{\hypertarget{method-print}{}}} 78 | \subsection{Method \code{print()}}{ 79 | \subsection{Usage}{ 80 | \if{html}{\out{
}}\preformatted{FileCabinet$print()}\if{html}{\out{
}} 81 | } 82 | 83 | \subsection{Details}{ 84 | Print an object of class FileCabinet. 85 | } 86 | 87 | } 88 | \if{html}{\out{
}} 89 | \if{html}{\out{}} 90 | \if{latex}{\out{\hypertarget{method-clone}{}}} 91 | \subsection{Method \code{clone()}}{ 92 | The objects of this class are cloneable with this method. 93 | \subsection{Usage}{ 94 | \if{html}{\out{
}}\preformatted{FileCabinet$clone(deep = FALSE)}\if{html}{\out{
}} 95 | } 96 | 97 | \subsection{Arguments}{ 98 | \if{html}{\out{
}} 99 | \describe{ 100 | \item{\code{deep}}{Whether to make a deep clone.} 101 | } 102 | \if{html}{\out{
}} 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /man/cabinets.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cabinets-package.R 3 | \docType{package} 4 | \name{cabinets} 5 | \alias{cabinets} 6 | \title{Streamlined organization with project specific custom templates} 7 | \description{ 8 | Cabinets makes it easy to create project specific file structure templates 9 | that can be referenced at the start of any R session. Cabinets works by writing 10 | project specific file templates to the .Rprofile file of the default working directory. 11 | Doing so allows the templates to be accessed in new R sessions without having to 12 | redefine them. On first use, users will be prompted for explicit permission to 13 | write to .Rprofile. Permission to write can be revoked at any time by setting the 14 | permission option in the .Rprofile file to FALSE. Due to these explicit permission 15 | requirements, cabinets will only work in interactive R sessions. 16 | } 17 | -------------------------------------------------------------------------------- /man/cabinets_options_set.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/checks.R 3 | \name{cabinets_options_set} 4 | \alias{cabinets_options_set} 5 | \title{Set cabinets options} 6 | \usage{ 7 | cabinets_options_set(..., .envir = NULL) 8 | } 9 | \arguments{ 10 | \item{...}{Options to be set} 11 | 12 | \item{.envir}{Environment to set options in; if NULL, will use environment at \code{parent.frame()}} 13 | } 14 | \value{ 15 | If no options are set, returns the options specified in \code{options}. 16 | } 17 | \description{ 18 | Set cabinets options 19 | } 20 | \details{ 21 | Mainly used for specifying if cabinets has permission to write to .Rprofile. Permission can be revoked at any time by opening the .Rprofile file and setting \code{"cabinets.permission" = FALSE}. 22 | } 23 | -------------------------------------------------------------------------------- /man/create_cabinet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cabinets.R 3 | \name{create_cabinet} 4 | \alias{create_cabinet} 5 | \title{Create a cabinet template} 6 | \usage{ 7 | create_cabinet(name, directory, structure, .alias = name) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the cabinet; character of length 1. 11 | This is how the cabinet will be referenced, so best to 12 | chose something memorable.} 13 | 14 | \item{directory}{The file path for where the cabinet will exist.} 15 | 16 | \item{structure}{A list of paths of folders/files to 17 | create. See details for further explanation.} 18 | 19 | \item{.alias}{An optional name for the object the cabinet 20 | will be stored in R as. Defaults to \code{name}.} 21 | } 22 | \value{ 23 | An R6 object of class FileCabinet. The code to 24 | generate this object is written to the .Rprofile file 25 | of the home directory. 26 | } 27 | \description{ 28 | \code{create_cabinet} writes code to the .Rprofile file so 29 | that when new R sessions are started, the newly created 30 | cabinet, an R6 object of class FileCabinet, is available 31 | in the global environment as a hidden object. The cabinet 32 | simply stores file location and file template information 33 | that \code{new_cabinet_proj} uses to create new projects 34 | with the pre-defined structure. 35 | } 36 | \details{ 37 | Before writing to or creating a .Rprofile file, 38 | cabinets will explicitly ask for the user's permission to on exit. 39 | The cabinet structure should be defined using a list with the 40 | names defining folder paths. List values should be set to NULL. 41 | } 42 | \seealso{ 43 | \code{\link{new_cabinet_proj}} 44 | } 45 | -------------------------------------------------------------------------------- /man/delete_cabinet.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/edit.R 3 | \name{delete_cabinet} 4 | \alias{delete_cabinet} 5 | \title{Delete Cabinets} 6 | \usage{ 7 | delete_cabinet(cabinet) 8 | } 9 | \arguments{ 10 | \item{cabinet}{An R6 object of class Cabinet written to the .Rprofile.} 11 | } 12 | \description{ 13 | Delete Cabinets 14 | } 15 | -------------------------------------------------------------------------------- /man/edit_r_profile.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cabinets.R 3 | \name{edit_r_profile} 4 | \alias{edit_r_profile} 5 | \title{Open .Rprofile for editing} 6 | \usage{ 7 | edit_r_profile() 8 | } 9 | \value{ 10 | A message that .Rprofile is being opened or that it doesn't exist. 11 | } 12 | \description{ 13 | \code{edit_r_profile} opens the .Rprofile file for editing. 14 | If the .Rprofile file doesn't exist an error message will be returned. 15 | This is essentially a wrapper function for \code{file.edit}. 16 | } 17 | -------------------------------------------------------------------------------- /man/get_cabinets.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/feedback.R 3 | \name{get_cabinets} 4 | \alias{get_cabinets} 5 | \title{Print available cabinets} 6 | \usage{ 7 | get_cabinets(envir = parent.frame()) 8 | } 9 | \arguments{ 10 | \item{envir}{The environment to check in. The default is the global environment.} 11 | } 12 | \value{ 13 | Objects of class FileCabinet found in the global environment. 14 | } 15 | \description{ 16 | \code{get_cabinets} returns objects of class FileCabinet. 17 | } 18 | \examples{ 19 | get_cabinets() 20 | } 21 | -------------------------------------------------------------------------------- /man/new_cabinet_proj.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cabinets.R 3 | \name{new_cabinet_proj} 4 | \alias{new_cabinet_proj} 5 | \title{Create a new project using a cabinet template} 6 | \usage{ 7 | new_cabinet_proj( 8 | cabinet, 9 | project_name, 10 | r_project = TRUE, 11 | open = TRUE, 12 | renv = TRUE, 13 | git = TRUE, 14 | git_root = NULL, 15 | git_ignore = NULL 16 | ) 17 | } 18 | \arguments{ 19 | \item{cabinet}{The name of the cabinet template. Available cabinets can 20 | be found using \code{get_cabinets()}.} 21 | 22 | \item{project_name}{The name of the project to store in the cabinet, 23 | a character string. Can be a file path pointing to a directory 24 | within the specified cabinet.} 25 | 26 | \item{r_project}{Logical, should an Rproject be created. Default is 27 | TRUE if working in RStudio (only works in RStudio).} 28 | 29 | \item{open}{Logical, if creating an Rproject, should that project 30 | be opened once created. Default is TRUE if working in 31 | RStudio (only works in RStudio).} 32 | 33 | \item{renv}{Logical, should a \code{renv} project be initiated. 34 | If \code{TRUE}, \code{renv} project infrastructure will be created using 35 | \code{\link[renv]{scaffold}}.} 36 | 37 | \item{git}{Logical, should a git repository be initiated.} 38 | 39 | \item{git_root}{A path relative to the project to initiate the 40 | git repository. Default is NULL and the repository is 41 | initiated at the root of the project.} 42 | 43 | \item{git_ignore}{Character vector of files and directories 44 | to add to .gitignore file.} 45 | } 46 | \value{ 47 | Creates a new directory at the path specified in the 48 | cabinet template. If \code{r_project = TRUE}, a .Rproj file 49 | will also be created using the project name. If open is set 50 | to TRUE, the new R project will opened in a new R session. 51 | } 52 | \description{ 53 | Generate new project directories using cabinet templates. 54 | } 55 | \seealso{ 56 | \code{\link{create_cabinet}} 57 | } 58 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(cabinets) 3 | 4 | test_check("cabinets") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-FileCabinet.R: -------------------------------------------------------------------------------- 1 | context("create FileCabinet") 2 | 3 | test_that("creating FileCabinet", { 4 | x <- FileCabinet$new(name = "test_cab", 5 | directory = "a/random/path", 6 | structure = list('test' = NULL)) 7 | 8 | expect_output(x$print()) 9 | }) 10 | -------------------------------------------------------------------------------- /tests/testthat/test-blocks.R: -------------------------------------------------------------------------------- 1 | context("Blocks") 2 | 3 | test_that("deleting blocks", { 4 | withr::with_tempfile("block", { 5 | con <- file(block, open = "a") 6 | writeLines("blank space", con = con) 7 | writeLines(glue::glue("## test cabinet start"), con = con) 8 | writeLines("Text to delete", con = con) 9 | writeLines(glue::glue("## test cabinet end"), con = con) 10 | writeLines("blank space", con = con) 11 | close(con) 12 | 13 | expect_equal(read_utf8(block), 14 | c("blank space", "## test cabinet start", 15 | "Text to delete", "## test cabinet end", 16 | "blank space")) 17 | block_delete("## test cabinet start", "## test cabinet end", block) 18 | expect_equal(read_utf8(block), c("blank space", "blank space")) 19 | }, fileext = "txt") 20 | }) 21 | 22 | -------------------------------------------------------------------------------- /tests/testthat/test-check_cabinet.R: -------------------------------------------------------------------------------- 1 | context("check_cabinet function") 2 | 3 | test_that("finding existing cabinets", { 4 | withr::with_environment(.GlobalEnv, { 5 | .test_cab <<- NULL 6 | cli::cli_div(theme = list(".alert-success" = list(before = "PASSING "))) 7 | verify_output(test_path("test-find-existing-cabinets.txt"), { 8 | check_cabinet(".test_cab") 9 | }) 10 | rm(.test_cab, envir = .GlobalEnv) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /tests/testthat/test-checking-permissions.txt: -------------------------------------------------------------------------------- 1 | > check_permissions() 2 | Message: PASSING Checking for permissions 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/test-feedback.R: -------------------------------------------------------------------------------- 1 | context("Other feedback") 2 | 3 | env <- new.env() 4 | 5 | test_that("correct feedback", { 6 | cli::cli_div(theme = list(".alert-success" = list(before = "PASSING "))) 7 | cli::cli_div(theme = list(ul = list("list-style-type" = "*"))) 8 | cli::cli_div(theme = list(".alert-danger" = list(before = "PASSING "))) 9 | cli::cli_div(theme = list(".alert-warning" = list(before = "PASSING "))) 10 | verify_output(test_path("test-feedback.txt"), { 11 | creating_project("hello", "world") 12 | opening_project("test") 13 | checking_existence() 14 | checking_git() 15 | no_cabinets() 16 | no_r_profile() 17 | initiated_git("test") 18 | created_cabinet("test") 19 | no_git() 20 | initiating_renv() 21 | }) 22 | }) 23 | 24 | test_that("finding cabinets", { 25 | withr::with_environment(env, { 26 | cli::cli_div(theme = list(ul = list("list-style-type" = "*"))) 27 | rm(list = ls(), envir = env) 28 | assign("x", FileCabinet$new(name = "test_cab", 29 | directory = "a/random/path", 30 | structure = list('test' = NULL)), 31 | envir = env) 32 | verify_output(test_path("test-print-available.txt"), { 33 | get_cabinets(env) 34 | }) 35 | }) 36 | }) 37 | -------------------------------------------------------------------------------- /tests/testthat/test-feedback.txt: -------------------------------------------------------------------------------- 1 | > creating_project("hello", "world") 2 | Message: PASSING Creating hello using cabinet template: .world 3 | 4 | > opening_project("test") 5 | Message: * Opening R project, test, in a new session 6 | 7 | > checking_existence() 8 | Message: PASSING Checking cabinet existence 9 | 10 | > checking_git() 11 | Message: PASSING Checking for git configuration 12 | 13 | > no_cabinets() 14 | Message: PASSING No cabinets found. Cabinets can be created using `create_cabinets()` 15 | 16 | > no_r_profile() 17 | Message: PASSING '.Rprofile' doesn't exist 18 | 19 | > initiated_git("test") 20 | Message: PASSING Git repository initiated in test 21 | 22 | > created_cabinet("test") 23 | Message: PASSING Cabinet .test created 24 | 25 | Message: * Restart R for changes to take effect 26 | 27 | Message: * Cabinet can be called with .test 28 | 29 | > no_git() 30 | Message: PASSING Git not found or git not fully configured 31 | 32 | Message: * Check out for configuring git with R. 33 | 34 | > initiating_renv() 35 | Message: PASSING Creating renv infrastructure 36 | 37 | -------------------------------------------------------------------------------- /tests/testthat/test-file-cabinet.txt: -------------------------------------------------------------------------------- 1 | > FileCabinet$new(name = "test", directory = "hello/world", structure = list( 2 | + `hello/world` = NULL)) 3 | Cabinet name: test 4 | Cabinet path: hello/world 5 | Cabinet structure: 6 | └── hello 7 | └── world 8 | 9 | -------------------------------------------------------------------------------- /tests/testthat/test-find-existing-cabinets.txt: -------------------------------------------------------------------------------- 1 | > check_cabinet(".test_cab") 2 | Message: PASSING Checking cabinet existence 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/test-name-doesnt-exist.txt: -------------------------------------------------------------------------------- 1 | > check_name("test_cab") 2 | Message: PASSING Checking cabinet name 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/test-name.R: -------------------------------------------------------------------------------- 1 | context("check name") 2 | 3 | test_that("name already exists", { 4 | withr::with_environment(.GlobalEnv, { 5 | .test_cab <<- NULL 6 | expect_error(check_name("test_cab")) 7 | rm(.test_cab, envir = .GlobalEnv) 8 | }) 9 | }) 10 | 11 | test_that("name doesn't exist", { 12 | withr::with_environment(.GlobalEnv, { 13 | cli::cli_div(theme = list(".alert-success" = list(before = "PASSING "))) 14 | verify_output(test_path("test-name-doesnt-exist.txt"), { 15 | check_name("test_cab") 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /tests/testthat/test-none-available.txt: -------------------------------------------------------------------------------- 1 | > get_cabinets(env) 2 | Message: x No cabinets found. Cabinets can be created using `create_cabinets()` 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/test-options.R: -------------------------------------------------------------------------------- 1 | context("setting cabinet options") 2 | 3 | test_that("options can be set", { 4 | cabinets_options_set("cabinets.permission" = FALSE) 5 | expect_false(getOption("cabinets.permission")) 6 | }) 7 | -------------------------------------------------------------------------------- /tests/testthat/test-paths.R: -------------------------------------------------------------------------------- 1 | 2 | context("Path specific") 3 | 4 | test_that("Parse paths correctly", { 5 | expect_equal(get_paths("hello/world/test"), c("hello/world/test", "hello/world", "hello")) 6 | }) 7 | -------------------------------------------------------------------------------- /tests/testthat/test-permission-granted.txt: -------------------------------------------------------------------------------- 1 | > ask_permission() 2 | Message: PASSING Checking for permissions 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/test-permissions.R: -------------------------------------------------------------------------------- 1 | context("permission checking") 2 | 3 | test_that("permission is granted", { 4 | options("cabinet.testing" = TRUE) 5 | options("cabinet.testingPerm" = TRUE) 6 | 7 | cli::cli_div(theme = list(".alert-success" = list(before = "PASSING "))) 8 | verify_output(test_path("test-permission-granted.txt"), { 9 | ask_permission() 10 | }) 11 | }) 12 | 13 | test_that("permission is denied", { 14 | options("cabinet.testing" = TRUE) 15 | options("cabinet.testingPerm" = FALSE) 16 | 17 | expect_error(ask_permission()) 18 | }) 19 | 20 | test_that("check permission catches permission", { 21 | options("cabinets.permission" = FALSE) 22 | expect_error(check_permissions()) 23 | 24 | options("cabinets.permission" = TRUE) 25 | 26 | cli::cli_div(theme = list(".alert-success" = list(before = "PASSING "))) 27 | verify_output(test_path("test-checking-permissions.txt"), { 28 | check_permissions() 29 | }) 30 | 31 | options("cabinets.permission" = NULL) 32 | options("cabinet.testing" = TRUE) 33 | options("cabinet.testingPerm" = FALSE) 34 | expect_error(check_permissions()) 35 | }) 36 | 37 | test_that("permission functions work", { 38 | opts <- options() 39 | expect_error(perm_no()) 40 | options(opts) 41 | }) 42 | -------------------------------------------------------------------------------- /tests/testthat/test-print-available.txt: -------------------------------------------------------------------------------- 1 | > get_cabinets(env) 2 | Message: These are the available cabinets: 3 | 4 | Message: * x 5 | 6 | -------------------------------------------------------------------------------- /tests/testthat/test-project-doesnt-exist.txt: -------------------------------------------------------------------------------- 1 | > check_project(file.path("a", "random", "path")) 2 | Message: PASSING Checking if project already exists 3 | 4 | -------------------------------------------------------------------------------- /tests/testthat/test-projects.R: -------------------------------------------------------------------------------- 1 | context("pre-existing projects") 2 | 3 | test_that("project directory already exists", { 4 | temp_dir <- tempdir() 5 | expect_error(check_project(temp_dir)) 6 | unlink(temp_dir) 7 | }) 8 | 9 | skip_if(!cli::is_utf8_output()) 10 | 11 | test_that("project doesn't exist", { 12 | cli::cli_div(theme = list(".alert-success" = list(before = "PASSING "))) 13 | verify_output(test_path("test-project-doesnt-exist.txt"), { 14 | check_project(file.path("a", "random", "path")) 15 | }) 16 | }) 17 | --------------------------------------------------------------------------------