Add --global-justfile flag (#1846)

This commit is contained in:
Greg Shuflin 2024-05-19 02:29:13 -07:00 committed by GitHub
parent 104608d8cc
commit a343f5c80c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 182 additions and 54 deletions

View File

@ -3268,13 +3268,30 @@ Before `just` was a fancy Rust program it was a tiny shell script that called
`make`. You can find the old version in
[contrib/just.sh](https://github.com/casey/just/blob/master/contrib/just.sh).
### User `justfile`s
### Global and User `justfile`s
If you want some recipes to be available everywhere, you have a few options.
First, create a `justfile` in `~/.user.justfile` with some recipes.
#### Global Justfile
#### Recipe Aliases
`just --global-justfile`, or `just -g` for short, searches the following paths,
in-order, for a justfile:
- `$XDG_CONFIG_HOME/just/justfile`
- `$HOME/.config/just/justfile`
- `$HOME/justfile`
- `$HOME/.justfile`
You can put recipes that are used across many projects in a global justfile to
easily invoke them from any directory.
#### User justfile tips
You can also adopt some of the following workflows. These tips assume you've
created a `justfile` at `~/.user.justfile`, but you can put this `justfile`
at any convenient path on your system.
##### Recipe Aliases
If you want to call the recipes in `~/.user.justfile` by name, and don't mind
creating an alias for every recipe, add the following to your shell's
@ -3293,7 +3310,7 @@ It took me way too long to realize that you could create recipe aliases like
this. Notwithstanding my tardiness, I am very pleased to bring you this major
advance in `justfile` technology.
#### Forwarding Alias
##### Forwarding Alias
If you'd rather not create aliases for every recipe, you can create a single alias:
@ -3308,7 +3325,7 @@ I'm pretty sure that nobody actually uses this feature, but it's there.
¯\\\_(ツ)\_/¯
#### Customization
##### Customization
You can customize the above aliases with additional options. For example, if
you'd prefer to have the recipes in your `justfile` run in your home directory,

View File

@ -30,7 +30,7 @@ _just() {
case "${cmd}" in
"$1")
opts="-n -f -q -u -v -d -c -e -l -s -E -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --man --show --summary --variables --dotenv-filename --dotenv-path --help --version [ARGUMENTS]..."
opts="-n -f -q -u -v -d -c -e -l -s -E -g -h -V --check --chooser --color --command-color --yes --dry-run --dump-format --highlight --list-heading --list-prefix --no-aliases --no-deps --no-dotenv --no-highlight --justfile --quiet --set --shell --shell-arg --shell-command --clear-shell-args --unsorted --unstable --verbose --working-directory --changelog --choose --command --completions --dump --edit --evaluate --fmt --init --list --man --show --summary --variables --dotenv-filename --dotenv-path --global-justfile --help --version [ARGUMENTS]..."
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0

View File

@ -69,6 +69,8 @@ set edit:completion:arg-completer[just] = {|@words|
cand --man 'Print man page'
cand --summary 'List names of available recipes'
cand --variables 'List names of variables'
cand -g 'Use global justfile'
cand --global-justfile 'Use global justfile'
cand -h 'Print help'
cand --help 'Print help'
cand -V 'Print version'

View File

@ -76,5 +76,6 @@ complete -c just -s l -l list -d 'List available recipes and their arguments'
complete -c just -l man -d 'Print man page'
complete -c just -l summary -d 'List names of available recipes'
complete -c just -l variables -d 'List names of variables'
complete -c just -s g -l global-justfile -d 'Use global justfile'
complete -c just -s h -l help -d 'Print help'
complete -c just -s V -l version -d 'Print version'

View File

@ -72,6 +72,8 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page')
[CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
[CompletionResult]::new('--variables', 'variables', [CompletionResultType]::ParameterName, 'List names of variables')
[CompletionResult]::new('-g', 'g', [CompletionResultType]::ParameterName, 'Use global justfile')
[CompletionResult]::new('--global-justfile', 'global-justfile', [CompletionResultType]::ParameterName, 'Use global justfile')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')

View File

@ -67,6 +67,8 @@ _just() {
'--man[Print man page]' \
'--summary[List names of available recipes]' \
'--variables[List names of variables]' \
'(-f --justfile -d --working-directory)-g[Use global justfile]' \
'(-f --justfile -d --working-directory)--global-justfile[Use global justfile]' \
'-h[Print help]' \
'--help[Print help]' \
'-V[Print version]' \

View File

@ -94,6 +94,7 @@ mod arg {
pub(crate) const DOTENV_PATH: &str = "DOTENV-PATH";
pub(crate) const DRY_RUN: &str = "DRY-RUN";
pub(crate) const DUMP_FORMAT: &str = "DUMP-FORMAT";
pub(crate) const GLOBAL_JUSTFILE: &str = "GLOBAL_JUSTFILE";
pub(crate) const HIGHLIGHT: &str = "HIGHLIGHT";
pub(crate) const JUSTFILE: &str = "JUSTFILE";
pub(crate) const LIST_HEADING: &str = "LIST-HEADING";
@ -465,6 +466,15 @@ impl Config {
.action(ArgAction::Append)
.help("Overrides and recipe(s) to run, defaulting to the first recipe in the justfile"),
)
.arg(
Arg::new(arg::GLOBAL_JUSTFILE)
.action(ArgAction::SetTrue)
.long("global-justfile")
.short('g')
.conflicts_with(arg::JUSTFILE)
.conflicts_with(arg::WORKING_DIRECTORY)
.help("Use global justfile")
)
}
fn color_from_matches(matches: &ArgMatches) -> ConfigResult<Color> {
@ -520,6 +530,39 @@ impl Config {
}
}
fn search_config(matches: &ArgMatches, positional: &Positional) -> ConfigResult<SearchConfig> {
if matches.get_flag(arg::GLOBAL_JUSTFILE) {
return Ok(SearchConfig::GlobalJustfile);
}
let justfile = matches.get_one::<PathBuf>(arg::JUSTFILE).map(Into::into);
let working_directory = matches
.get_one::<PathBuf>(arg::WORKING_DIRECTORY)
.map(Into::into);
if let Some(search_directory) = positional.search_directory.as_ref().map(PathBuf::from) {
if justfile.is_some() || working_directory.is_some() {
return Err(ConfigError::SearchDirConflict);
}
Ok(SearchConfig::FromSearchDirectory { search_directory })
} else {
match (justfile, working_directory) {
(None, None) => Ok(SearchConfig::FromInvocationDirectory),
(Some(justfile), None) => Ok(SearchConfig::WithJustfile { justfile }),
(Some(justfile), Some(working_directory)) => {
Ok(SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,
})
}
(None, Some(_)) => Err(ConfigError::internal(
"--working-directory set without --justfile",
)),
}
}
}
pub(crate) fn from_matches(matches: &ArgMatches) -> ConfigResult<Self> {
let invocation_directory = env::current_dir().context(config_error::CurrentDirContext)?;
@ -545,39 +588,11 @@ impl Config {
.map(|s| s.map(String::as_str)),
);
for (name, value) in positional.overrides {
for (name, value) in &positional.overrides {
overrides.insert(name.clone(), value.clone());
}
let search_config = {
let justfile = matches.get_one::<PathBuf>(arg::JUSTFILE).map(Into::into);
let working_directory = matches
.get_one::<PathBuf>(arg::WORKING_DIRECTORY)
.map(Into::into);
if let Some(search_directory) = positional.search_directory.map(PathBuf::from) {
if justfile.is_some() || working_directory.is_some() {
return Err(ConfigError::SearchDirConflict);
}
SearchConfig::FromSearchDirectory { search_directory }
} else {
match (justfile, working_directory) {
(None, None) => SearchConfig::FromInvocationDirectory,
(Some(justfile), None) => SearchConfig::WithJustfile { justfile },
(Some(justfile), Some(working_directory)) => {
SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,
}
}
(None, Some(_)) => {
return Err(ConfigError::internal(
"--working-directory set without --justfile",
))
}
}
}
};
let search_config = Self::search_config(matches, &positional)?;
for subcommand in cmd::ARGLESS {
if matches.get_flag(subcommand) {

View File

@ -1,7 +1,7 @@
use {super::*, std::path::Component};
const DEFAULT_JUSTFILE_NAME: &str = JUSTFILE_NAMES[0];
pub(crate) const JUSTFILE_NAMES: &[&str] = &["justfile", ".justfile"];
pub(crate) const JUSTFILE_NAMES: [&str; 2] = ["justfile", ".justfile"];
const PROJECT_ROOT_CHILDREN: &[&str] = &[".bzr", ".git", ".hg", ".svn", "_darcs"];
pub(crate) struct Search {
@ -10,6 +10,29 @@ pub(crate) struct Search {
}
impl Search {
fn global_justfile_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
if let Some(config_dir) = dirs::config_dir() {
paths.push(config_dir.join("just").join(DEFAULT_JUSTFILE_NAME));
}
if let Some(home_dir) = dirs::home_dir() {
paths.push(
home_dir
.join(".config")
.join("just")
.join(DEFAULT_JUSTFILE_NAME),
);
for justfile_name in JUSTFILE_NAMES {
paths.push(home_dir.join(justfile_name));
}
}
paths
}
pub(crate) fn find(
search_config: &SearchConfig,
invocation_directory: &Path,
@ -18,21 +41,24 @@ impl Search {
SearchConfig::FromInvocationDirectory => Self::find_next(invocation_directory),
SearchConfig::FromSearchDirectory { search_directory } => {
let search_directory = Self::clean(invocation_directory, search_directory);
let justfile = Self::justfile(&search_directory)?;
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Self {
justfile,
working_directory,
})
}
SearchConfig::GlobalJustfile => Ok(Self {
justfile: Self::global_justfile_paths()
.iter()
.find(|path| path.exists())
.cloned()
.ok_or(SearchError::GlobalJustfileNotFound)?,
working_directory: Self::project_root(invocation_directory)?,
}),
SearchConfig::WithJustfile { justfile } => {
let justfile = Self::clean(invocation_directory, justfile);
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Self {
justfile,
working_directory,
@ -50,9 +76,7 @@ impl Search {
pub(crate) fn find_next(starting_dir: &Path) -> SearchResult<Self> {
let justfile = Self::justfile(starting_dir)?;
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Self {
justfile,
working_directory,
@ -66,39 +90,30 @@ impl Search {
match search_config {
SearchConfig::FromInvocationDirectory => {
let working_directory = Self::project_root(invocation_directory)?;
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
Ok(Self {
justfile,
working_directory,
})
}
SearchConfig::FromSearchDirectory { search_directory } => {
let search_directory = Self::clean(invocation_directory, search_directory);
let working_directory = Self::project_root(&search_directory)?;
let justfile = working_directory.join(DEFAULT_JUSTFILE_NAME);
Ok(Self {
justfile,
working_directory,
})
}
SearchConfig::GlobalJustfile => Err(SearchError::GlobalJustfileInit),
SearchConfig::WithJustfile { justfile } => {
let justfile = Self::clean(invocation_directory, justfile);
let working_directory = Self::working_directory_from_justfile(&justfile)?;
Ok(Self {
justfile,
working_directory,
})
}
SearchConfig::WithJustfileAndWorkingDirectory {
justfile,
working_directory,

View File

@ -9,6 +9,8 @@ pub(crate) enum SearchConfig {
FromInvocationDirectory,
/// As in `Invocation`, but start from `search_directory`.
FromSearchDirectory { search_directory: PathBuf },
/// Search for global justfile
GlobalJustfile,
/// Use user-specified justfile, with the working directory set to the
/// directory that contains it.
WithJustfile { justfile: PathBuf },

View File

@ -3,6 +3,10 @@ use super::*;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub(crate) enum SearchError {
#[snafu(display("Cannot initialize global justfile"))]
GlobalJustfileInit,
#[snafu(display("Global justfile not found"))]
GlobalJustfileNotFound,
#[snafu(display(
"I/O error reading directory `{}`: {}",
directory.display(),

View File

@ -1,5 +1,6 @@
use super::*;
#[derive(Debug)]
pub(crate) struct Source<'src> {
pub(crate) file_depth: u32,
pub(crate) namepath: Namepath<'src>,

65
tests/global.rs Normal file
View File

@ -0,0 +1,65 @@
use super::*;
#[test]
#[cfg(target_os = "macos")]
fn macos() {
let tempdir = tempdir();
let path = tempdir.path().to_owned();
Test::with_tempdir(tempdir)
.no_justfile()
.test_round_trip(false)
.write(
"Library/Application Support/just/justfile",
"@default:\n echo foo",
)
.env("HOME", path.to_str().unwrap())
.args(["--global-justfile"])
.stdout("foo\n")
.run();
}
#[test]
#[cfg(all(unix, not(target_os = "macos")))]
fn not_macos() {
let tempdir = tempdir();
let path = tempdir.path().to_owned();
Test::with_tempdir(tempdir)
.no_justfile()
.test_round_trip(false)
.write("just/justfile", "@default:\n echo foo")
.env("XDG_CONFIG_HOME", path.to_str().unwrap())
.args(["--global-justfile"])
.stdout("foo\n")
.run();
}
#[test]
#[cfg(unix)]
fn unix() {
let tempdir = tempdir();
let path = tempdir.path().to_owned();
let tempdir = Test::with_tempdir(tempdir)
.no_justfile()
.test_round_trip(false)
.write("justfile", "@default:\n echo foo")
.env("HOME", path.to_str().unwrap())
.args(["--global-justfile"])
.stdout("foo\n")
.run()
.tempdir;
Test::with_tempdir(tempdir)
.no_justfile()
.test_round_trip(false)
.write(".config/just/justfile", "@default:\n echo bar")
.env("HOME", path.to_str().unwrap())
.args(["--global-justfile"])
.stdout("bar\n")
.run();
}

View File

@ -59,6 +59,8 @@ mod export;
mod fallback;
mod fmt;
mod functions;
#[cfg(unix)]
mod global;
mod ignore_comments;
mod imports;
mod init;