Skip to content

Commit 916dc89

Browse files
teunbrandolivroy
andauthored
create.dir argument for ggsave() (#5492)
* Isolate path check * Add test * Accept message change * Suppress spurious print * Add news bullet * document * Apply suggestions from code review Co-authored-by: olivroy <[email protected]> --------- Co-authored-by: olivroy <[email protected]>
1 parent 0d29f25 commit 916dc89

File tree

4 files changed

+84
-17
lines changed

4 files changed

+84
-17
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# ggplot2 (development version)
22

3+
* `ggsave()` no longer sometimes creates new directories, which is now
4+
controlled by the new `create.dir` argument (#5489).
5+
36
* `guide_coloursteps(even.steps = FALSE)` now draws one rectangle per interval
47
instead of many small ones (#5481).
58

R/save.R

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
#' specifying dimensions in pixels.
4444
#' @param bg Background colour. If `NULL`, uses the `plot.background` fill value
4545
#' from the plot theme.
46+
#' @param create.dir Whether to create new directories if a non-existing
47+
#' directory is specified in the `filename` or `path` (`TRUE`) or return an
48+
#' error (`FALSE`, default). If `FALSE` and run in an interactive session,
49+
#' a prompt will appear asking to create a new directory when necessary.
4650
#' @param ... Other arguments passed on to the graphics device function,
4751
#' as specified by `device`.
4852
#' @export
@@ -84,27 +88,16 @@
8488
ggsave <- function(filename, plot = last_plot(),
8589
device = NULL, path = NULL, scale = 1,
8690
width = NA, height = NA, units = c("in", "cm", "mm", "px"),
87-
dpi = 300, limitsize = TRUE, bg = NULL, ...) {
88-
if (length(filename) != 1) {
89-
if (length(filename) == 0) {
90-
cli::cli_abort("{.arg filename} cannot be empty.")
91-
}
92-
len <- length(filename)
93-
filename <- filename[1]
94-
cli::cli_warn(c(
95-
"{.arg filename} must have length 1, not length {len}.",
96-
"!" = "Only the first, {.file {filename}}, will be used."
97-
))
98-
}
91+
dpi = 300, limitsize = TRUE, bg = NULL,
92+
create.dir = FALSE,
93+
...) {
94+
filename <- check_path(path, filename, create.dir)
9995

10096
dpi <- parse_dpi(dpi)
10197
dev <- plot_dev(device, filename, dpi = dpi)
10298
dim <- plot_dim(c(width, height), scale = scale, units = units,
10399
limitsize = limitsize, dpi = dpi)
104100

105-
if (!is.null(path)) {
106-
filename <- file.path(path, filename)
107-
}
108101
if (is_null(bg)) {
109102
bg <- calc_element("plot.background", plot_theme(plot))$fill %||% "transparent"
110103
}
@@ -119,6 +112,56 @@ ggsave <- function(filename, plot = last_plot(),
119112
invisible(filename)
120113
}
121114

115+
check_path <- function(path, filename, create.dir,
116+
call = caller_env()) {
117+
118+
if (length(filename) > 1 && is.character(filename)) {
119+
cli::cli_warn(c(
120+
"{.arg filename} must have length 1, not {length(filename)}.",
121+
"!" = "Only the first, {.file {filename[1]}}, will be used."
122+
), call = call)
123+
filename <- filename[1]
124+
}
125+
check_string(filename, allow_empty = FALSE, call = call)
126+
127+
check_string(path, allow_empty = FALSE, allow_null = TRUE, call = call)
128+
if (!is.null(path)) {
129+
filename <- file.path(path, filename)
130+
} else {
131+
path <- dirname(filename)
132+
}
133+
134+
# Happy path: target file is in valid directory
135+
if (dir.exists(path)) {
136+
return(filename)
137+
}
138+
139+
check_bool(create.dir, call = call)
140+
141+
# Try to ask user to create a new directory
142+
if (interactive() && !create.dir) {
143+
cli::cli_bullets(c(
144+
"Cannot find directory {.path {path}}.",
145+
"i" = "Would you like to create a new directory?"
146+
))
147+
create.dir <- utils::menu(c("Yes", "No")) == 1
148+
}
149+
150+
# Create new directory
151+
if (create.dir) {
152+
dir.create(path, recursive = TRUE)
153+
if (dir.exists(path)) {
154+
cli::cli_alert_success("Created directory: {.path {path}}.")
155+
return(filename)
156+
}
157+
}
158+
159+
cli::cli_abort(c(
160+
"Cannot find directory {.path {path}}.",
161+
i = "Please supply an existing directory or use {.code create.dir = TRUE}."
162+
), call = call)
163+
}
164+
122165
#' Parse a DPI input from the user
123166
#'
124167
#' Allows handling of special strings when user specifies a DPI like "print".

man/ggsave.Rd

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-ggsave.R

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,21 @@ test_that("ggsave creates file", {
99
expect_true(file.exists(path))
1010
})
1111

12+
test_that("ggsave can create directories", {
13+
dir <- tempdir()
14+
path <- file.path(dir, "foobar", "tmp.pdf")
15+
on.exit(unlink(path))
16+
17+
p <- ggplot(mpg, aes(displ, hwy)) + geom_point()
18+
19+
expect_error(ggsave(path, p))
20+
expect_false(dir.exists(dirname(path)))
21+
22+
# 2 messages: 1 for saving and 1 informing about directory creation
23+
expect_message(expect_message(ggsave(path, p, create.dir = TRUE)))
24+
expect_true(dir.exists(dirname(path)))
25+
})
26+
1227
test_that("ggsave restores previous graphics device", {
1328
# When multiple devices are open, dev.off() restores the next one in the list,
1429
# not the previously-active one. (#2363)
@@ -70,7 +85,7 @@ test_that("ggsave warns about empty or multiple filenames", {
7085

7186
expect_error(
7287
ggsave(character(), plot),
73-
"`filename` cannot be empty."
88+
"`filename` must be a single string"
7489
)
7590
})
7691

@@ -93,7 +108,7 @@ test_that("guesses and informs if dim not specified", {
93108
})
94109

95110
test_that("uses 7x7 if no graphics device open", {
96-
expect_equal(plot_dim(), c(7, 7))
111+
suppressMessages(expect_equal(plot_dim(), c(7, 7)))
97112
})
98113

99114
test_that("warned about large plot unless limitsize = FALSE", {

0 commit comments

Comments
 (0)