Nothing
#' Create init.R from template
#' @keywords internal
.create_init_file <- function(project_name, type, lintr, subdir = NULL) {
template_path <- system.file("templates/init.R", package = "framework")
if (!file.exists(template_path)) {
stop("Template init.R not found in package")
}
content <- readLines(template_path, warn = FALSE)
# Replace placeholders
content <- gsub("\\{\\{PROJECT_NAME\\}\\}", project_name, content)
content <- gsub("\\{\\{PROJECT_TYPE\\}\\}", type, content)
content <- gsub("\\{\\{LINTR\\}\\}", lintr, content)
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
target_file <- file.path(target_dir, "init.R")
writeLines(content, target_file)
message(sprintf("Created %s", target_file))
}
#' Create settings.yml from template
#' @keywords internal
.create_config_file <- function(type = "analysis", attach_defaults = TRUE, subdir = NULL) {
# Try type-specific template first, fall back to generic
template_name <- sprintf("templates/settings.%s.yml", type)
template_path <- system.file(template_name, package = "framework")
if (!file.exists(template_path)) {
# Fall back to generic template
template_path <- system.file("templates/settings.yml", package = "framework")
if (!file.exists(template_path)) {
stop("Template settings.yml not found in package")
}
}
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
target_file <- file.path(target_dir, "settings.yml")
# Read template content
content <- readLines(template_path, warn = FALSE)
# If attach_defaults is TRUE, replace the packages section with structured format
if (attach_defaults) {
# Find the packages section
packages_start <- grep("^\\s*packages:", content)
if (length(packages_start) > 0) {
# Find where packages section ends (next section or end of file)
section_headers <- grep("^\\s*[a-z_]+:", content)
next_section <- section_headers[section_headers > packages_start[1]]
packages_end <- if (length(next_section) > 0) next_section[1] - 1 else length(content)
# Remove old packages section
content <- content[-(packages_start:packages_end)]
# Insert new structured packages section
new_packages <- c(
" packages:",
" # Auto-attached packages (available without library() calls)",
" - name: dplyr",
" attached: true",
" - name: tidyr",
" attached: true",
" - name: ggplot2",
" attached: true",
" # Installed but not auto-attached (use library() when needed)",
" - name: readr",
" attached: false",
" - name: stringr",
" attached: false",
" - name: scales",
" attached: false",
""
)
# Insert at the packages_start position
content <- c(
content[1:(packages_start - 1)],
new_packages,
content[packages_start:length(content)]
)
}
}
# Write modified content
writeLines(content, target_file)
message(sprintf("Created %s", target_file))
}
#' Create development .Rprofile
#' @keywords internal
.create_dev_rprofile <- function(subdir = NULL) {
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
target_file <- file.path(target_dir, ".Rprofile")
# Get the user's home directory
home_dir <- Sys.getenv("HOME")
framework_dev_path <- file.path(home_dir, "code", "framework")
rprofile_content <- sprintf('# Development .Rprofile for Framework
# Auto-generated by init(.dev_mode = TRUE)
# This .Rprofile overrides library() to load framework from development directory
# Store original library function
original_library <- base::library
# Override library function
library <- function(package, help = NULL, pos = 2, lib.loc = NULL,
character.only = FALSE, logical.return = FALSE,
warn.conflicts = TRUE, quietly = FALSE,
verbose = getOption("verbose")) {
# Get the package name
if (!character.only) {
package <- as.character(substitute(package))
}
# If it\'s framework, use our custom loading
if (package == "framework") {
# First try to load from development directory
dev_path <- "%s"
if (dir.exists(dev_path)) {
if (requireNamespace("devtools", quietly = TRUE)) {
env <- devtools::load_all(dev_path, export_all = FALSE, quiet = TRUE)
message("Framework loaded from development directory: ", dev_path)
return(invisible(env))
} else {
warning("devtools package required for dev_mode. Install with: install.packages(\\"devtools\\")")
}
}
# If not in development, try to load as installed package
if (requireNamespace("framework", quietly = TRUE)) {
original_library("framework", character.only = TRUE, quietly = quietly,
warn.conflicts = warn.conflicts)
message("Framework loaded from installed package")
return(invisible(TRUE))
}
warning("Framework not found - neither in ", dev_path, " nor as installed package")
return(invisible(NULL))
}
# For all other packages, use the original library function
original_library(package = package, help = help, pos = pos, lib.loc = lib.loc,
character.only = TRUE, logical.return = logical.return,
warn.conflicts = warn.conflicts, quietly = quietly,
verbose = verbose)
}
message("Framework dev mode active - will load from: %s")
', framework_dev_path, framework_dev_path)
writeLines(rprofile_content, target_file)
message(sprintf("Created development .Rprofile: %s", target_file))
message(sprintf(" Framework will load from: %s", framework_dev_path))
}
#' Delete init.R after successful initialization
#' @keywords internal
.delete_init_file <- function(subdir = NULL) {
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
init_file <- file.path(target_dir, "init.R")
# Only delete if init.R exists
if (!file.exists(init_file)) {
return(invisible(NULL))
}
# Delete init.R
tryCatch({
file.remove(init_file)
message("\u2713 Cleaned up init.R (use bootstrap_project_init() to recreate for reference)")
}, error = function(e) {
warning(sprintf("Could not delete init.R: %s", e$message))
})
invisible(NULL)
}
#' Display next steps after initialization
#' @keywords internal
.display_next_steps <- function(project_name = NULL, type = "project", use_renv = FALSE) {
message("")
message("\u2713 Framework project initialized successfully!")
message("")
# Show summary of settings
message("Project Configuration:")
if (!is.null(project_name)) {
message(sprintf(" -Name: %s", project_name))
}
message(sprintf(" -Type: %s", type))
message(sprintf(" -renv: %s", if (use_renv) "enabled" else "disabled"))
message("")
message("Next steps:")
message(" 1. Review and edit settings.yml")
message(" 2. Start a new R session in this directory")
message(" 3. Run:")
message(" library(framework)")
message(" scaffold()")
message(" 4. Start analyzing!")
message("")
message("Optional:")
message(" -Add database connections: configure_connection()")
message(" -Store secrets: Edit .env file directly")
message("")
# Additional context based on project type
if (type == "course") {
message("Course-specific features:")
message(" -slides/ - Author lecture decks (render to slides/_rendered/{{ slug }}.html)")
message(" -assignments/ - Organize homework and lab materials")
message(" -data/ - Store shared datasets for demonstrations")
message("")
} else if (type == "presentation") {
message("Presentation tips:")
message(" -Use make_notebook() to create your presentation")
message(" -Quarto reveal.js format recommended")
message("")
}
}
#' Customize project files with user-specific substitutions
#' @keywords internal
.customize_project_files <- function(target_dir, author_name = NULL, author_email = NULL, author_affiliation = NULL) {
# Get author info from environment if not provided
if (is.null(author_name)) author_name <- Sys.getenv("FW_AUTHOR_NAME", "Your Name")
if (is.null(author_email)) author_email <- Sys.getenv("FW_AUTHOR_EMAIL", "")
if (is.null(author_affiliation)) author_affiliation <- Sys.getenv("FW_AUTHOR_AFFILIATION", "")
# Find all .qmd and .Rmd files that might have author placeholders
notebook_files <- c(
list.files(file.path(target_dir, "notebooks"), pattern = "\\.(qmd|Rmd)$", full.names = TRUE, recursive = TRUE),
list.files(file.path(target_dir, "slides"), pattern = "\\.(qmd|Rmd)$", full.names = TRUE, recursive = TRUE),
list.files(file.path(target_dir, "assignments"), pattern = "\\.(qmd|Rmd)$", full.names = TRUE, recursive = TRUE),
list.files(file.path(target_dir, "course_docs"), pattern = "\\.(qmd|Rmd)$", full.names = TRUE, recursive = TRUE)
)
# Remove non-existent paths
notebook_files <- notebook_files[file.exists(notebook_files)]
# Apply substitutions to each file
for (file_path in notebook_files) {
if (file.exists(file_path)) {
content <- readLines(file_path, warn = FALSE)
# Replace author placeholder patterns
# Pattern 1: "author: Your Name" or "author: [AUTHOR]"
content <- gsub('author:\\s*"?Your Name"?', paste0('author: "', author_name, '"'), content)
content <- gsub('author:\\s*"?\\[AUTHOR\\]"?', paste0('author: "', author_name, '"'), content)
# Pattern 2: Just "Your Name" in author field
content <- gsub('author:\\s+Your Name', paste0('author: ', author_name), content)
writeLines(content, file_path)
}
}
# Also update settings/author.yml if it exists
author_yml <- file.path(target_dir, "settings", "author.yml")
if (file.exists(author_yml)) {
content <- readLines(author_yml, warn = FALSE)
if (!is.null(author_name) && nzchar(author_name) && author_name != "Your Name") {
content <- gsub('name:\\s*"?Your Name"?', paste0('name: "', author_name, '"'), content)
}
if (!is.null(author_email) && nzchar(author_email)) {
content <- gsub('email:\\s*"?"?', paste0('email: "', author_email, '"'), content)
}
if (!is.null(author_affiliation) && nzchar(author_affiliation)) {
content <- gsub('affiliation:\\s*"?"?', paste0('affiliation: "', author_affiliation, '"'), content)
}
writeLines(content, author_yml)
}
}
#' Standard initialization process (shared by both paths)
#' @keywords internal
.init_standard <- function(project_name, type, lintr, author_name = NULL, author_email = NULL, author_affiliation = NULL, default_notebook_format = NULL, subdir, force, use_git = TRUE) {
# Validate arguments (already validated in init, but keep for internal calls)
checkmate::assert_string(project_name, min.chars = 1, null.ok = TRUE)
checkmate::assert_string(type, min.chars = 1)
checkmate::assert_string(lintr, min.chars = 1)
checkmate::assert_string(author_name, min.chars = 1, null.ok = TRUE)
checkmate::assert_string(author_email, min.chars = 1, null.ok = TRUE)
checkmate::assert_string(author_affiliation, min.chars = 1, null.ok = TRUE)
# Handle empty string as NULL
if (!is.null(default_notebook_format) && !nzchar(default_notebook_format)) {
default_notebook_format <- NULL
}
if (!is.null(default_notebook_format)) {
checkmate::assert_choice(default_notebook_format, c("quarto", "rmarkdown"))
}
checkmate::assert_string(subdir, min.chars = 1, null.ok = TRUE)
checkmate::assert_flag(force)
# NOTE: project_create() already checked for existing settings file, no need to check again here
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
# Derive project name
if (!is.null(project_name)) {
# Convert to lowercase and sanitize for filenames:
# 1. Convert to lowercase
# 2. Convert spaces to hyphens
# 3. Remove all special characters except hyphens
rproj_name <- tolower(project_name)
rproj_name <- gsub("\\s+", "-", rproj_name)
rproj_name <- gsub("[^a-z0-9-]", "", rproj_name)
} else {
project_name <- basename(getwd())
# Apply same sanitization to derived name
rproj_name <- tolower(project_name)
rproj_name <- gsub("\\s+", "-", rproj_name)
rproj_name <- gsub("[^a-z0-9-]", "", rproj_name)
}
# Validate template style files
lintr_template <- system.file("templates", paste0(".lintr.", lintr), package = "framework")
if (!file.exists(lintr_template)) stop(sprintf("Lintr style '%s' not found", lintr))
# Remove existing *.Rproj file (only one per project)
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
existing_rproj <- list.files(path = target_dir, pattern = "\\.Rproj$", full.names = TRUE)
if (length(existing_rproj)) file.remove(existing_rproj)
# Copy and rename .Rproj file
rproj_template <- system.file("templates", "project.Rproj", package = "framework")
if (!file.exists(rproj_template)) stop("Template project.Rproj file not found in package.")
rproj_target <- file.path(target_dir, paste0(rproj_name, ".Rproj"))
file.copy(rproj_template, rproj_target, overwrite = TRUE)
# Create IDE configuration files (VS Code workspace and settings)
.create_ide_configs(rproj_name, target_dir, python = FALSE)
# Copy and rename other template files
template_dir <- system.file("templates", package = "framework")
template_files <- list.files(template_dir, full.names = TRUE, all.files = TRUE, no.. = TRUE)
for (file in template_files) {
fname <- basename(file)
# Only copy specific utility templates to new projects
# Most templates are handled separately (settings, AI context, notebooks, etc.)
copy_templates <- list(
".editorconfig" = ".editorconfig",
".lintr.default" = ".lintr",
"scaffold.R" = "scaffold.R"
)
if (!fname %in% names(copy_templates)) next
new_name <- copy_templates[[fname]]
target_path <- file.path(target_dir, new_name)
dir.create(dirname(target_path), showWarnings = FALSE, recursive = TRUE)
success <- file.copy(file, target_path, overwrite = TRUE)
if (!success) warning(sprintf("Failed to copy template file: %s to %s", file, target_path))
# Substitute {subdir} in YAML-like config files
if (grepl("\\.ya?ml$", new_name)) {
content <- readLines(target_path)
subdir_prefix <- if (!is.null(subdir) && nzchar(subdir)) paste0(subdir, "/") else ""
content <- gsub("\\{subdir\\}", subdir_prefix, content)
writeLines(content, target_path)
}
}
# Copy project structure
structure_dir <- system.file("project_structure", type, package = "framework")
if (!dir.exists(structure_dir)) stop(sprintf("Project type '%s' not found", type))
all_dirs <- list.dirs(structure_dir, recursive = TRUE, full.names = TRUE)
all_dirs <- all_dirs[all_dirs != structure_dir]
for (dir in all_dirs) {
rel_path <- sub(paste0("^", structure_dir, "/?"), "", dir)
target_path <- file.path(target_dir, rel_path)
dir.create(target_path, showWarnings = FALSE, recursive = TRUE)
}
structure_files <- list.files(structure_dir, recursive = TRUE, full.names = TRUE, all.files = TRUE)
for (file in structure_files) {
rel_path <- sub(paste0("^", structure_dir, "/?"), "", file)
# Skip connections.yml for presentation projects (no database by default)
if (type == "presentation" && grepl("settings/connections\\.yml$", rel_path)) {
next
}
target_path <- file.path(target_dir, rel_path)
# Copy file as-is (project_structure files don't use .fr extension)
file.copy(file, target_path, overwrite = TRUE)
}
# README.md is now part of project_structure and copied above
# Post-copy customization hook: Apply user-specific substitutions
.customize_project_files(
target_dir = target_dir,
author_name = author_name,
author_email = author_email,
author_affiliation = author_affiliation
)
# Rename settings/ directory based on user preference
config_dir_pref <- Sys.getenv("FW_CONFIG_DIR", "settings")
if (config_dir_pref == "config" && dir.exists(file.path(target_dir, "settings"))) {
# Rename settings/ to config/
file.rename(
from = file.path(target_dir, "settings"),
to = file.path(target_dir, "config")
)
# Update references in settings file
config_path <- .get_settings_file(target_dir)
if (is.null(config_path)) {
config_path <- file.path(target_dir, "settings.yml")
}
if (file.exists(config_path)) {
config_content <- readLines(config_path, warn = FALSE)
# Replace "settings/" with "config/" in references
config_content <- gsub("settings/", "config/", config_content, fixed = TRUE)
writeLines(config_content, config_path)
}
}
# Remove template directories when config maps them to project root (".")
config_path_for_dirs <- .get_settings_file(target_dir)
if (!is.null(config_path_for_dirs) && file.exists(config_path_for_dirs)) {
config_for_dirs <- tryCatch(
settings_read(config_path_for_dirs),
error = function(e) NULL
)
if (!is.null(config_for_dirs) && !is.null(config_for_dirs$directories)) {
for (dir_name in names(config_for_dirs$directories)) {
dir_value <- config_for_dirs$directories[[dir_name]]
if (is.character(dir_value) && length(dir_value) == 1) {
dir_value_trim <- trimws(dir_value)
if (dir_value_trim %in% c(".", "./", "")) {
candidate_path <- file.path(target_dir, dir_name)
if (dir.exists(candidate_path)) {
contents <- list.files(candidate_path, all.files = TRUE, no.. = TRUE)
if (length(contents) == 0) {
unlink(candidate_path, recursive = TRUE)
}
}
}
}
}
}
}
# Update settings.yml (or config.yml) with author information and notebook format if provided
has_author_info <- (!is.null(author_name) && nzchar(author_name)) ||
(!is.null(author_email) && nzchar(author_email)) ||
(!is.null(author_affiliation) && nzchar(author_affiliation))
has_format_pref <- !is.null(default_notebook_format) && nzchar(default_notebook_format)
if (has_author_info || has_format_pref) {
# Find settings file (prefer settings.yml, fallback to config.yml)
config_path <- .get_settings_file(target_dir)
if (is.null(config_path)) {
config_path <- file.path(target_dir, "settings.yml")
}
if (file.exists(config_path)) {
config_content <- readLines(config_path, warn = FALSE)
config_modified <- FALSE
update_author_split <- function(author_file) {
author_path <- file.path(dirname(config_path), author_file)
if (!file.exists(author_path)) {
return(FALSE)
}
author_yaml <- tryCatch(yaml::read_yaml(author_path), error = function(e) NULL)
if (is.null(author_yaml)) {
return(FALSE)
}
if (is.null(author_yaml$author) || !is.list(author_yaml$author)) {
author_yaml$author <- list()
}
if (!is.null(author_name) && nzchar(author_name)) {
author_yaml$author$name <- author_name
}
if (!is.null(author_email) && nzchar(author_email)) {
author_yaml$author$email <- author_email
}
if (!is.null(author_affiliation) && nzchar(author_affiliation)) {
author_yaml$author$affiliation <- author_affiliation
}
yaml::write_yaml(author_yaml, author_path)
TRUE
}
update_options_split <- function(options_file) {
options_path <- file.path(dirname(config_path), options_file)
if (!file.exists(options_path)) {
return(FALSE)
}
options_yaml <- tryCatch(yaml::read_yaml(options_path), error = function(e) NULL)
if (is.null(options_yaml)) {
return(FALSE)
}
if (is.null(options_yaml$options) || !is.list(options_yaml$options)) {
options_yaml$options <- list()
}
if (!is.null(default_notebook_format) && nzchar(default_notebook_format)) {
options_yaml$options$default_notebook_format <- default_notebook_format
}
yaml::write_yaml(options_yaml, options_path)
TRUE
}
# Update author section if provided
if (has_author_info) {
author_start <- grep("^ author:", config_content)
if (length(author_start) > 0) {
author_entry <- config_content[author_start[1]]
author_file <- trimws(sub("^ author:\\s*", "", author_entry))
updated_split <- FALSE
if (nzchar(author_file) && grepl("\\.ya?ml$", author_file, ignore.case = TRUE)) {
updated_split <- update_author_split(author_file)
}
if (!updated_split) {
# Inline fallback
next_section_pattern <- "^ [a-z_]+:"
all_sections <- grep(next_section_pattern, config_content)
next_section <- all_sections[all_sections > author_start[1]]
author_end <- if (length(next_section) > 0) next_section[1] - 1 else length(config_content)
for (i in author_start:author_end) {
if (!is.null(author_name) && nzchar(author_name) && grepl("^ name:", config_content[i])) {
config_content[i] <- sprintf(" name: \"%s\"", author_name)
config_modified <- TRUE
}
if (!is.null(author_email) && nzchar(author_email) && grepl("^ email:", config_content[i])) {
config_content[i] <- sprintf(" email: \"%s\"", author_email)
config_modified <- TRUE
}
if (!is.null(author_affiliation) && nzchar(author_affiliation) && grepl("^ affiliation:", config_content[i])) {
config_content[i] <- sprintf(" affiliation: \"%s\"", author_affiliation)
config_modified <- TRUE
}
}
}
}
}
# Update default_notebook_format if provided
if (has_format_pref) {
options_line <- grep("^ options:", config_content)
updated_split <- FALSE
if (length(options_line) > 0) {
options_entry <- config_content[options_line[1]]
options_file <- trimws(sub("^ options:\\s*", "", options_entry))
if (nzchar(options_file) && grepl("\\.ya?ml$", options_file, ignore.case = TRUE)) {
updated_split <- update_options_split(options_file)
}
}
if (!updated_split) {
format_line <- grep("^ default_notebook_format:", config_content)
if (length(format_line) > 0) {
config_content[format_line[1]] <- sprintf(" default_notebook_format: %s", default_notebook_format)
config_modified <- TRUE
}
}
}
if (config_modified) {
writeLines(config_content, config_path)
}
}
}
# Ensure author placeholders in starter notebooks use resolved name
.replace_author_placeholders(target_dir)
.initialize_framework_db(target_dir)
.initialize_env_file(target_dir)
# Initialization complete (settings.yml/config.yml serves as marker)
message(sprintf("Project '%s' initialized successfully!", project_name))
}
#' Check if project is initialized
#'
#' Checks for existence of settings.yml/settings.yml to determine initialization status.
#'
#' @param subdir Optional subdirectory to check.
#' @return Logical indicating if project is initialized.
#' @keywords internal
.is_initialized <- function(subdir = NULL) {
# Validate arguments
checkmate::assert_string(subdir, min.chars = 1, null.ok = TRUE)
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
!is.null(.get_settings_file(target_dir))
}
#' Remove initialization
#'
#' Removes settings.yml/settings.yml to mark project as uninitialized.
#' WARNING: This will delete your project configuration!
#'
#' @param subdir Optional subdirectory to check.
#' @return Logical indicating if removal was successful.
#' @keywords internal
.remove_init <- function(subdir = NULL) {
# Validate arguments
checkmate::assert_string(subdir, min.chars = 1, null.ok = TRUE)
target_dir <- if (!is.null(subdir) && nzchar(subdir)) subdir else "."
config_file <- .get_settings_file(target_dir)
if (!is.null(config_file) && file.exists(config_file)) {
warning("Removing settings file - your project configuration will be deleted!", call. = FALSE)
unlink(config_file)
TRUE
} else {
FALSE
}
}
#' Bootstrap project initialization file
#'
#' Generates an init.R file showing the initialization logic.
#' Useful for documentation and understanding how the project was set up.
#'
#' @param output_file Path where init.R should be written. Default: "init.R"
#' @return Invisibly returns TRUE on success
#' @keywords internal
#' @export
bootstrap_project_init <- function(output_file = "init.R") {
checkmate::assert_string(output_file, min.chars = 1)
template_path <- system.file("templates/init.R", package = "framework")
if (!file.exists(template_path)) {
stop("Template init.R not found in package")
}
# Read template
content <- readLines(template_path, warn = FALSE)
# Add header explaining this is generated
header <- c(
"# This file was generated by bootstrap_project_init() for reference purposes.",
"# It shows the initialization logic used by framework::project_create().",
"# You can safely delete this file.",
"",
""
)
content <- c(header, content)
# Write file
writeLines(content, output_file)
message(sprintf("[ok] Created %s", output_file))
message(" This file shows initialization logic for reference.")
message(" Placeholders like {{PROJECT_NAME}} would be replaced during actual project_create().")
invisible(TRUE)
}
#' Remove .gitkeep files from data/ and functions/ directories
#' @keywords internal
.cleanup_gitkeep_files <- function(target_dir = ".") {
# Find all .gitkeep files in data/ and functions/ directories
data_gitkeeps <- list.files(
path = file.path(target_dir, "data"),
pattern = "^\\.gitkeep$",
recursive = TRUE,
full.names = TRUE,
all.files = TRUE
)
functions_gitkeeps <- list.files(
path = file.path(target_dir, "functions"),
pattern = "^\\.gitkeep$",
recursive = TRUE,
full.names = TRUE,
all.files = TRUE
)
all_gitkeeps <- c(data_gitkeeps, functions_gitkeeps)
if (length(all_gitkeeps) > 0) {
removed_count <- sum(file.remove(all_gitkeeps))
if (removed_count > 0) {
message(sprintf("\u2713 Cleaned up %d .gitkeep file%s", removed_count, if (removed_count == 1) "" else "s"))
}
}
invisible(NULL)
}
#' Initialize git repository
#' @keywords internal
.init_git_repo <- function(target_dir = ".") {
# Check git is available
if (!nzchar(Sys.which("git"))) {
message("Note: Git not found. Skipping repository initialization.")
return(invisible(NULL))
}
git_dir <- file.path(target_dir, ".git")
if (file.exists(git_dir)) {
return(invisible(NULL))
}
tryCatch({
# Initialize git
old_wd <- getwd()
on.exit(setwd(old_wd), add = TRUE)
if (!is.null(target_dir) && target_dir != ".") {
setwd(target_dir)
}
# Initialize repo
init_status <- system("git init", ignore.stdout = TRUE, ignore.stderr = TRUE)
if (init_status != 0) {
stop("git init failed")
}
# Add all files
add_status <- system("git add .", ignore.stdout = TRUE, ignore.stderr = TRUE)
if (add_status != 0) {
stop("git add failed")
}
# Force-add .gitignore files in private directories (defense-in-depth)
private_gitignores <- c(
"inputs/raw/.gitignore",
"inputs/intermediate/.gitignore",
"inputs/final/.gitignore",
"inputs/reference/.gitignore",
"outputs/private/.gitignore"
)
for (gitignore_path in private_gitignores) {
if (file.exists(gitignore_path)) {
system(paste0("git add -f ", gitignore_path), ignore.stdout = TRUE, ignore.stderr = TRUE)
}
}
message("\u2713 Git repository initialized")
}, error = function(e) {
message("Note: Could not initialize git repository. You can run 'git init' manually if needed.")
})
invisible(NULL)
}
#' Create initial git commit after all initialization is complete
#' @keywords internal
.create_initial_commit <- function(target_dir = ".") {
# Check git is available
if (!nzchar(Sys.which("git"))) {
return(invisible(NULL))
}
# Change to target directory
old_wd <- getwd()
on.exit(setwd(old_wd), add = TRUE)
if (!is.null(target_dir) && nzchar(target_dir) && target_dir != ".") {
if (dir.exists(target_dir)) {
setwd(target_dir)
} else {
target_path <- tryCatch(
normalizePath(target_dir, winslash = "/", mustWork = TRUE),
error = function(e) NULL
)
if (!is.null(target_path) && dir.exists(target_path)) {
setwd(target_path)
}
}
}
# Check if we're in a git repo
git_check <- system("git rev-parse --git-dir", ignore.stdout = TRUE, ignore.stderr = TRUE)
if (git_check != 0) {
return(invisible(NULL))
}
# Check if there are any commits yet
has_commits <- system("git rev-parse HEAD", ignore.stdout = TRUE, ignore.stderr = TRUE) == 0
if (!has_commits) {
# No commits yet - add all files (including any created after init, like .github/)
system("git add -A", ignore.stdout = TRUE, ignore.stderr = TRUE)
# Check identity before committing
user_name <- system("git config user.name", intern = TRUE, ignore.stderr = TRUE)
user_email <- system("git config user.email", intern = TRUE, ignore.stderr = TRUE)
if (length(user_name) == 0 || length(user_email) == 0) {
message("Note: Skipping initial commit. Configure git user with:")
message(" git config user.name \"Your Name\"")
message(" git config user.email \"your.email@example.com\"")
} else {
msg_file <- tempfile("framework_init_commit_")
writeLines("Project initialized.", msg_file)
on.exit(unlink(msg_file), add = TRUE)
commit_result <- system2("git", c("commit", "-F", msg_file), stdout = TRUE, stderr = TRUE)
if (is.null(attr(commit_result, "status")) || identical(attr(commit_result, "status"), 0)) {
message("\u2713 Initial commit created")
} else {
message("Note: Could not create initial commit. Configure git user with:")
message(" git config user.name \"Your Name\"")
message(" git config user.email \"your.email@example.com\"")
}
}
}
invisible(NULL)
}
#' Configure git hooks based on environment variables
#' @keywords internal
.configure_git_hooks <- function(target_dir = ".") {
# Read hook configuration from environment variables (set by new-project.sh)
hooks_enabled <- Sys.getenv("FW_HOOKS_ENABLED", "FALSE")
ai_sync_enabled <- Sys.getenv("FW_HOOK_AI_SYNC", "FALSE")
data_security_enabled <- Sys.getenv("FW_HOOK_DATA_SECURITY", "FALSE")
ai_canonical <- Sys.getenv("FW_AI_CANONICAL", "")
# Convert string booleans to logical
hooks_enabled <- toupper(hooks_enabled) == "TRUE"
ai_sync_enabled <- toupper(ai_sync_enabled) == "TRUE"
data_security_enabled <- toupper(data_security_enabled) == "TRUE"
if (!hooks_enabled) {
return(invisible(NULL))
}
config_path <- .get_settings_file(target_dir)
if (is.null(config_path)) {
config_path <- file.path(target_dir, "settings.yml")
}
if (file.exists(config_path)) {
tryCatch({
config_content <- readLines(config_path, warn = FALSE)
config_modified <- FALSE
update_git_split <- function(git_file) {
git_path <- file.path(dirname(config_path), git_file)
if (!file.exists(git_path)) {
return(FALSE)
}
git_yaml <- tryCatch(yaml::read_yaml(git_path), error = function(e) NULL)
if (is.null(git_yaml)) {
return(FALSE)
}
if (is.null(git_yaml$git) || !is.list(git_yaml$git)) {
git_yaml$git <- list()
}
if (is.null(git_yaml$git$hooks) || !is.list(git_yaml$git$hooks)) {
git_yaml$git$hooks <- list()
}
git_yaml$git$hooks$ai_sync <- ai_sync_enabled
git_yaml$git$hooks$data_security <- data_security_enabled
yaml::write_yaml(git_yaml, git_path)
TRUE
}
update_ai_split <- function(ai_file) {
if (!nzchar(ai_canonical)) {
return(FALSE)
}
ai_path <- file.path(dirname(config_path), ai_file)
if (!file.exists(ai_path)) {
return(FALSE)
}
ai_yaml <- tryCatch(yaml::read_yaml(ai_path), error = function(e) NULL)
if (is.null(ai_yaml)) {
return(FALSE)
}
if (is.null(ai_yaml$ai) || !is.list(ai_yaml$ai)) {
ai_yaml$ai <- list()
}
ai_yaml$ai$canonical_file <- ai_canonical
yaml::write_yaml(ai_yaml, ai_path)
TRUE
}
git_line <- grep("^ git:", config_content)
git_updated <- FALSE
if (length(git_line) > 0) {
git_entry <- config_content[git_line[1]]
git_file <- trimws(sub("^ git:\\s*", "", git_entry))
if (nzchar(git_file) && grepl("\\.ya?ml$", git_file, ignore.case = TRUE)) {
git_updated <- update_git_split(git_file)
}
}
if (!git_updated) {
config_content <- gsub(
"^(\\s*ai_sync:\\s*)false(\\s*.*)?$",
sprintf("\\1%s\\2", tolower(ai_sync_enabled)),
config_content
)
config_content <- gsub(
"^(\\s*data_security:\\s*)false(\\s*.*)?$",
sprintf("\\1%s\\2", tolower(data_security_enabled)),
config_content
)
config_modified <- TRUE
}
ai_line <- grep("^ ai:", config_content)
ai_updated <- FALSE
if (length(ai_line) > 0) {
ai_entry <- config_content[ai_line[1]]
ai_file <- trimws(sub("^ ai:\\s*", "", ai_entry))
if (nzchar(ai_file) && grepl("\\.ya?ml$", ai_file, ignore.case = TRUE)) {
ai_updated <- update_ai_split(ai_file)
}
}
if (!ai_updated && nzchar(ai_canonical)) {
config_content <- gsub(
"^(\\s*canonical_file:\\s*)\"\"(\\s*.*)?$",
sprintf("\\1\"%s\"\\2", ai_canonical),
config_content
)
config_modified <- TRUE
}
if (config_modified) {
writeLines(config_content, config_path)
}
}, error = function(e) {
warning("Could not update settings file with hook settings: ", e$message)
})
}
# Install hooks if any are enabled
if (ai_sync_enabled || data_security_enabled) {
tryCatch({
# Change to target directory for hooks_install
old_wd <- getwd()
on.exit(setwd(old_wd), add = TRUE)
if (!is.null(target_dir) && target_dir != ".") {
setwd(target_dir)
}
# Install hooks
git_hooks_install(config_file = (.get_settings_file() %||% "settings.yml"), force = TRUE, verbose = FALSE)
# Show message about what was installed
hooks_msg <- character()
if (ai_sync_enabled) hooks_msg <- c(hooks_msg, "AI context sync")
if (data_security_enabled) hooks_msg <- c(hooks_msg, "data security check")
message(sprintf("[ok] Installed git hooks: %s", paste(hooks_msg, collapse = ", ")))
}, error = function(e) {
message("Note: Could not install git hooks. You can run 'framework hooks:install' later")
})
}
invisible(NULL)
}
.resolve_project_author <- function(target_dir = ".") {
old_wd <- getwd()
on.exit(setwd(old_wd), add = TRUE)
if (!is.null(target_dir) && nzchar(target_dir) && target_dir != ".") {
if (dir.exists(target_dir)) {
setwd(target_dir)
}
}
cfg <- tryCatch(settings_read(), error = function(e) NULL)
author_name <- cfg$author$name
if (is.null(author_name) || !nzchar(author_name)) {
author_name <- "Your Name"
}
author_name
}
.replace_author_placeholders <- function(target_dir = ".") {
author_name <- .resolve_project_author(target_dir)
notebook_files <- list.files(
path = target_dir,
pattern = "\\.(qmd|QMD|Rmd|rmd)$",
recursive = TRUE,
full.names = TRUE
)
pattern <- 'author:\\s*("Your Name"|!expr config\\$author\\$name|"`r config\\$author\\$name`")'
replacement <- sprintf('author: "%s"', author_name)
for (file in notebook_files) {
lines <- readLines(file, warn = FALSE)
new_lines <- gsub(pattern, replacement, lines)
if (!identical(lines, new_lines)) {
writeLines(new_lines, file)
}
}
.ensure_notebook_output_dir(target_dir, author_name = author_name)
invisible(NULL)
}
.ensure_notebook_output_dir <- function(target_dir = ".", author_name = NULL) {
config_path <- .get_settings_file(target_dir)
if (is.null(config_path)) {
config_path = file.path(target_dir, "settings.yml")
}
if (!file.exists(config_path)) {
return(invisible(NULL))
}
cfg <- tryCatch(settings_read(config_path), error = function(e) NULL)
if (is.null(cfg)) {
return(invisible(NULL))
}
notebook_conf <- cfg$notebook
if (is.null(notebook_conf)) {
notebook_conf <- cfg$options$notebook
}
directories_conf <- cfg$directories
notebooks_dir <- directories_conf$notebooks
if (is.null(notebooks_dir) && is.list(cfg$options$notebook) && !is.null(cfg$options$notebook$dir)) {
notebooks_dir <- cfg$options$notebook$dir
}
desired_output <- "_rendered"
if (is.list(notebook_conf) && !is.null(notebook_conf$output_dir) && nzchar(notebook_conf$output_dir)) {
desired_output <- notebook_conf$output_dir
}
if (!is.null(notebooks_dir) && nzchar(notebooks_dir) && notebooks_dir != "." && notebooks_dir != "./") {
if (!startsWith(desired_output, notebooks_dir)) {
desired_output <- file.path(notebooks_dir, basename(desired_output))
}
}
.update_quarto_output_dir(target_dir, desired_output)
invisible(NULL)
}
.update_quarto_output_dir <- function(target_dir = ".", output_dir) {
quarto_file <- file.path(target_dir, "_quarto.yml")
if (!file.exists(quarto_file)) {
return(invisible(NULL))
}
lines <- readLines(quarto_file, warn = FALSE)
pattern <- '^\\s*output-dir:\\s*(.*)$'
replacement <- sprintf('output-dir: %s', output_dir)
new_lines <- sub(pattern, replacement, lines)
if (!identical(lines, new_lines)) {
writeLines(new_lines, quarto_file)
}
invisible(NULL)
}
.initialize_framework_db <- function(target_dir = ".") {
template_db <- system.file("templates", "framework.db", package = "framework")
if (!nzchar(template_db) || !file.exists(template_db)) {
return(invisible(NULL))
}
old_wd <- getwd()
on.exit(setwd(old_wd), add = TRUE)
if (!is.null(target_dir) && nzchar(target_dir) && target_dir != ".") {
if (dir.exists(target_dir)) {
setwd(target_dir)
}
}
if (!file.exists("framework.db")) {
if (file.copy(template_db, "framework.db", overwrite = FALSE)) {
message("\u2713 Initialized framework.db")
}
}
invisible(NULL)
}
.initialize_env_file <- function(target_dir = ".") {
env_path <- file.path(target_dir, ".env")
if (file.exists(env_path)) {
return(invisible(NULL))
}
template <- NULL
config <- try(read_frameworkrc(use_defaults = TRUE), silent = TRUE)
if (!inherits(config, "try-error") && !is.null(config$defaults$env)) {
template <- env_resolve_lines(config$defaults$env)
} else {
template <- env_default_template_lines()
}
writeLines(template, env_path)
message("\u2713 Created .env with default connection placeholders")
invisible(NULL)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.