├── .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 | [](https://CRAN.R-project.org/package=cabinets)
20 | 
21 | [](https://www.tidyverse.org/lifecycle/#stable)
22 | [](https://codecov.io/gh/nt-williams/cabinets?branch=master)
23 | [](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 | [](https://CRAN.R-project.org/package=cabinets)
10 |  [](https://www.tidyverse.org/lifecycle/#stable)
12 | [](https://codecov.io/gh/nt-williams/cabinets?branch=master)
14 | [](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{