Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion R/add_r_files.R
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ add_r_files <- function(

dir_created <- create_if_needed(
"R",
type = "directory"
type = "directory",
warn_if_exists = FALSE
)

if (!dir_created) {
Expand Down
3 changes: 2 additions & 1 deletion R/modules_fn.R
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ add_module <- function(
golem_wd,
"R"
),
type = "directory"
type = "directory",
warn_if_exists = FALSE
)

if (!dir_created) {
Expand Down
135 changes: 93 additions & 42 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -17,73 +17,88 @@ create_if_needed <- function(
"file",
"directory"
),
content = NULL
content = NULL,
overwrite = FALSE,
warn_if_exists = TRUE
) {
type <- match.arg(
type
)

# Check if file or dir already exist
if (type == "file") {
dont_exist <- Negate(
fs_file_exists
)(
path
)
already_exists <- fs_file_exists(path)
} else if (type == "directory") {
dont_exist <- Negate(
fs_dir_exists
)(
path
)
already_exists <- fs_dir_exists(path)
}

# If it already exists, handle overwrite logic
if (already_exists) {
if (!overwrite) {
if (rlang_is_interactive()) {
# In interactive mode, ask user if they want to overwrite
ask <- yesno(
sprintf(
"The %s %s already exists, overwrite?",
basename(path),
type
)
)
if (!ask) {
return(TRUE) # File exists, user doesn't want to overwrite
}
} else {
# In non-interactive mode, warn but don't overwrite (if warnings enabled)
if (warn_if_exists) {
warning(
sprintf(
"The %s %s already exists and will not be overwritten. Set overwrite = TRUE to force overwrite.",
basename(path),
type
),
call. = FALSE
)
}
return(TRUE) # File exists, not overwriting
}
}
# If we reach here, either overwrite = TRUE or user said yes to overwrite
}
# If it doesn't exist, ask if we are allowed to create it
if (dont_exist) {
if (rlang_is_interactive()) {

# If it doesn't exist, or we're overwriting, create it
if (!already_exists || overwrite) {
if (rlang_is_interactive() && !already_exists) {
# In interactive mode, ask if we should create new files/dirs
ask <- ask_golem_creation_file(
path,
type
)
# Return early if the user doesn't allow
if (!ask) {
return(
FALSE
)
return(FALSE)
}
# Create the file
if (type == "file") {
fs_file_create(
path
)
}

# Create the file or directory
if (type == "file") {
fs_file_create(path)
if (!is.null(content)) {
write(
content,
path,
append = TRUE
)
} else if (type == "directory") {
fs_dir_create(
path,
recurse = TRUE
append = !overwrite # If overwriting, don't append
)
}
} else {
# We don't create the file if we are not in
# interactive mode
stop(
sprintf(
"The %s %s doesn't exist.",
basename(
path
),
type
)
} else if (type == "directory") {
fs_dir_create(
path,
recurse = TRUE
)
}
}

# TRUE means that file exists (either created or already there)
return(
TRUE
)
return(TRUE)
}

ask_golem_creation_file <- function(
Expand Down Expand Up @@ -415,3 +430,39 @@ signal_arg_is_deprecated <- function(
)
}
}

#' Sanitize R names
#'
#' Convert a string to a valid R name for functions and variables
#'
#' @param name character string to sanitize
#' @return character string with a valid R name
#' @noRd
sanitize_r_name <- function(name) {
if (is.na(name) || nchar(name) == 0) {
return("unnamed")
}

# Convert to lowercase and replace spaces with underscores
name <- tolower(name)
name <- gsub("[[:space:]]+", "_", name)

# Replace any non-alphanumeric characters with underscores
name <- gsub("[^a-zA-Z0-9_]", "_", name)

# Remove leading/trailing underscores and consolidate multiple underscores
name <- gsub("^_+|_+$", "", name)
name <- gsub("_+", "_", name)

# If name starts with a number, prefix with 'x'
if (grepl("^[0-9]", name)) {
name <- paste0("x", name)
}

# If name is empty after cleaning, use "unnamed"
if (nchar(name) == 0) {
name <- "unnamed"
}

return(name)
}
64 changes: 62 additions & 2 deletions tests/testthat/test-utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ test_that("rlang_is_interactive() works", {
})

test_that("create_if_needed creates a file if required", {
expect_error(
# Test: Non-interactive mode should now succeed and create the file
temp_file <- tempfile()
expect_true(
testthat::with_mocked_bindings(
rlang_is_interactive = function() {
return(
Expand All @@ -24,11 +26,13 @@ test_that("create_if_needed creates a file if required", {
},
code = {
create_if_needed(
tempfile()
temp_file
)
}
)
)
expect_true(file.exists(temp_file))
unlink(temp_file)
expect_false(
testthat::with_mocked_bindings(
rlang_is_interactive = function() {
Expand Down Expand Up @@ -489,3 +493,59 @@ test_that("signal_path_is_deprecated works", {
regexp = "mons"
)
})

test_that("sanitize_r_name works correctly", {
# Test spaces to underscores
expect_equal(
golem:::sanitize_r_name("ma fonction"),
"ma_fonction"
)

# Test special characters
expect_equal(
golem:::sanitize_r_name("my-special@function!"),
"my_special_function"
)

# Test uppercase to lowercase
expect_equal(
golem:::sanitize_r_name("UPPERCASE Function Name"),
"uppercase_function_name"
)

# Test leading number
expect_equal(
golem:::sanitize_r_name("123test"),
"x123test"
)

# Test multiple underscores consolidation
expect_equal(
golem:::sanitize_r_name("test___multiple___underscores"),
"test_multiple_underscores"
)

# Test leading/trailing underscores removal
expect_equal(
golem:::sanitize_r_name("_test_function_"),
"test_function"
)

# Test empty name
expect_equal(
golem:::sanitize_r_name(""),
"unnamed"
)

# Test NA name
expect_equal(
golem:::sanitize_r_name(NA_character_),
"unnamed"
)

# Test already valid name
expect_equal(
golem:::sanitize_r_name("valid_name"),
"valid_name"
)
})
Loading