principles.md

usethis design principles

This is an experiment in making key package design principles explicit, versus only implicit in the code. The goal is to make maintenance easier, when spread out over time and across people.

Active project

Many usethis functions act on the active project, the path to which is stored in the internal environment proj, specifically in proj$cur. We do this instead of constantly passing around a base path or relying on the working directory. It is implied that such functions create or modify files inside the active project. This is mostly true of use_*() functions, though there are exceptions. For example, use_course() makes no reference to the active project.

The project is activated upon first need, i.e. eventually some function calls proj_get() and, if proj$cur is NULL, we attempt to activate a project at (or above) current working directory.

Direct read/write of proj$cur should never happen, even internally. Instead, use proj_get() and proj_set(). If that is not possible (i.e. you don't want to trigger project activation), use proj_get_() and proj_set_(). If one must use proj_set_() directly, remember the stored project path should be processed with proj_path_prep().

Form paths to files within the project with proj_path(). Get paths relative to the project with proj_rel_path().

Activation upon load or attach? No.

We've contemplated project activation in .onLoad() or .onAttach(), but it's not clear which is more appropriate. Which suggests that neither is appropriate. If we ever do this, zzz.R would include something like this:

.onLoad <- function(libname, pkgname) {
  try(proj_set(".", quiet = TRUE), silent = TRUE)
}

Why not .onAttach()?

Helper functions

With some ambivalence, internally-oriented helpers like write_union() are exported. This helps developers who are extending usethis to create a package to standardize project setup within their own organization.

The downside is that we aren't exactly sure yet what we're willing to guarantee about these helpers.

Permission to overwrite

write_over() returns FALSE and does nothing if there is no need to overwrite (proposed file contents are same as existing) or if user says "no" and returns TRUE if it overwrites. So if downstream logic depends on whether something new was written, consult the return value. write_over() is rarely called directly, but is usually called via use_template(), in which case the same handling should apply to its return value.

Helpers and the active project

Two opposing mindsets:

Ideally, the exported file writing helpers would not make direct reference to the active project. However, we violate this, with due care, when it benefits us:

Git/GitHub helpers generally assume we're working on the Git repo that is also the active project. These are unexported. Prefer git_repo() to proj_get(), when you have a choice, to get the benefit of the check_uses_git() that's in git_repo().

Home directory

usethis relies on fs for file system operations. The main thing users will notice is the treatment of home directory on Windows. A Windows user's home directory is interpreted as C:\Users\username (typical of Unix-oriented tools, like Git and ssh; also matches Python), as opposed to C:\Users\username\Documents (R's default on Windows). In order to be consistent everywhere, all paths supplied by the user should be processed with user_path_prep().

Communicating with the user

User-facing messages are ALWAYS emitted via helpers in utils-ui.R. This is all intentional and should be preserved.

This is so we can control verbosity package-wide with the usethis.quiet option, which defaults to FALSE.

Note that usethis exports many ui_*() functions for historical reasons, all defined in ui-legacy.R, and all marked as superseded now. See the developer-facing article "Converting usethis's UI to use cli" for more.

Git/GitHub

Which GitHub remote configs the pr_*() functions accept, plus how and why they do it:

When you need to create a new Git URL and have to decide between HTTPS or SSH:

If the default summoning of Git credentials or GitHub PAT doesn't work for the user, help them diagnose that. But we are NOT in the credential management business and we aren't going to offer fine control of this at, say, the level of individual functions.

Functions that might make a commit should use challenge_uncommitted_changes() in the initial sanity-checking block to encourage starting in a clean state, i.e. with no uncommitted files or, if untracked = TRUE is specified, also with no untracked files. We allow people to proceed at their own risk.

Always make commits with git_commit_ask(). This why git_commit(), which wraps gert::git_add() and gert::git_commit(), is defined inside git_commit_ask(). Whenever possible, specify paths for git_commit_ask(). It should almost always be possible to know exactly which files we might have touched or created. If you need to make a commit in a noninteractive context, like a test, use gert::git_commit().

Use git_uncommitted(untracked = TRUE) and git_ask_commit(untracked = TRUE) if it's possible that the work we've done has created a new file that should be tracked. Use untracked = FALSE if our work should only modify and pre-existing file.



r-lib/usethis documentation built on March 20, 2024, 8:51 p.m.