Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Global justfile #1846

Merged
merged 34 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
ca79892
global justfile
neunenak Jan 14, 2024
7b6e1a8
Completions
neunenak Apr 10, 2024
7cd5e09
Document global
neunenak Jan 14, 2024
9b4b3bf
GLobal justfile search locations
neunenak Jan 16, 2024
1f085d7
clarify readme
neunenak Jan 16, 2024
e83614d
Fix completions
neunenak Apr 10, 2024
08d3643
Merge branch 'master' into global-justfile
neunenak May 6, 2024
ab7e4c3
Merge branch 'master' into global-justfile
neunenak May 9, 2024
9400dea
Merge remote-tracking branch 'origin/master' into global-justfile
casey May 15, 2024
e7f8824
Update completions
casey May 15, 2024
e14a364
Reform
casey May 15, 2024
4989fc1
Enhance
casey May 15, 2024
b44a11f
Modify
casey May 15, 2024
2891d5c
Modify
casey May 15, 2024
c837a98
Modify
casey May 15, 2024
cee9c2a
Inline global justfile name
casey May 15, 2024
d29d996
Forbid initializing global justfile
casey May 15, 2024
93b320d
Adjust
casey May 15, 2024
129e7fa
Revise
casey May 15, 2024
d7b78e7
Merge remote-tracking branch 'upstream/master' into global-justfile
neunenak May 16, 2024
72163c3
address PR comments
neunenak May 16, 2024
4d914ab
Expected stderr
neunenak May 16, 2024
9f183e8
only run test on linux
neunenak May 16, 2024
8410769
Merge branch 'master' into global-justfile
neunenak May 18, 2024
46ff85b
Merge branch 'master' into global-justfile
neunenak May 19, 2024
db4a492
Merge branch 'master' into global-justfile
neunenak May 19, 2024
8c36760
Merge remote-tracking branch 'origin/master' into global-justfile
casey May 19, 2024
66ae1ca
Refactor
casey May 19, 2024
135032f
Update completion scripts
casey May 19, 2024
3e61115
Reform
casey May 19, 2024
52c1afe
Expand tests
casey May 19, 2024
7cb2b04
Tweak
casey May 19, 2024
4983bca
Reword
casey May 19, 2024
c31d64b
Modify
casey May 19, 2024
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
27 changes: 22 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:

Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion completions/just.bash
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions completions/just.elvish
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions completions/just.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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'
2 changes: 2 additions & 0 deletions completions/just.powershell
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
2 changes: 2 additions & 0 deletions completions/just.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -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]' \
Expand Down
75 changes: 45 additions & 30 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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)?;

Expand All @@ -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) {
Expand Down
51 changes: 33 additions & 18 deletions src/search.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions src/search_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
4 changes: 4 additions & 0 deletions src/search_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions src/source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::*;

#[derive(Debug)]
pub(crate) struct Source<'src> {
pub(crate) file_depth: u32,
pub(crate) namepath: Namepath<'src>,
Expand Down
Loading