Skip to content

Commit

Permalink
Allow listing submodule recipes with --list PATH (#2108)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored May 29, 2024
1 parent d14aae1 commit 9d2c6b8
Show file tree
Hide file tree
Showing 14 changed files with 277 additions and 39 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,24 @@ Available recipes:
lint
```

Recipes in submodules can be listed with `just --list PATH`, where `PATH` is a
space- or `::`-separated module path:

```
$ cat justfile
mod foo
$ cat foo.just
mod bar
$ cat bar.just
baz:
$ just --unstable foo bar
Available recipes:
baz
$ just --unstable foo::bar
Available recipes:
baz
```

`just --summary` is more concise:

```sh
Expand Down
8 changes: 8 additions & 0 deletions completions/just.bash
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ _just() {
COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
return 0
;;
--list)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-l)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--show)
COMPREPLY=($(compgen -f "${cur}"))
return 0
Expand Down
4 changes: 2 additions & 2 deletions completions/just.elvish
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ set edit:completion:arg-completer[just] = {|@words|
cand -c 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
cand --command 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
cand --completions 'Print shell completion script for <SHELL>'
cand -l 'List available recipes and their arguments'
cand --list 'List available recipes and their arguments'
cand -s 'Show information about <RECIPE>'
cand --show 'Show information about <RECIPE>'
cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
Expand Down Expand Up @@ -64,8 +66,6 @@ set edit:completion:arg-completer[just] = {|@words|
cand --evaluate 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.'
cand --fmt 'Format and overwrite justfile'
cand --init 'Initialize new justfile in project root'
cand -l 'List available recipes and their arguments'
cand --list 'List available recipes and their arguments'
cand --groups 'List recipe groups'
cand --man 'Print man page'
cand --summary 'List names of available recipes'
Expand Down
2 changes: 1 addition & 1 deletion completions/just.fish
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ complete -c just -l shell-arg -d 'Invoke shell with <SHELL-ARG> as an argument'
complete -c just -s d -l working-directory -d 'Use <WORKING-DIRECTORY> as working directory. --justfile must also be set' -r -F
complete -c just -s c -l command -d 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set' -r
complete -c just -l completions -d 'Print shell completion script for <SHELL>' -r -f -a "{bash '',elvish '',fish '',powershell '',zsh ''}"
complete -c just -s l -l list -d 'List available recipes and their arguments' -r
complete -c just -s s -l show -d 'Show information about <RECIPE>' -r
complete -c just -l dotenv-filename -d 'Search for environment file named <DOTENV-FILENAME> instead of `.env`' -r
complete -c just -s E -l dotenv-path -d 'Load <DOTENV-PATH> as environment file instead of searching for one' -r -F
Expand All @@ -72,7 +73,6 @@ complete -c just -s e -l edit -d 'Edit justfile with editor given by $VISUAL or
complete -c just -l evaluate -d 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable\'s value.'
complete -c just -l fmt -d 'Format and overwrite justfile'
complete -c just -l init -d 'Initialize new justfile in project root'
complete -c just -s l -l list -d 'List available recipes and their arguments'
complete -c just -l groups -d 'List recipe groups'
complete -c just -l man -d 'Print man page'
complete -c just -l summary -d 'List names of available recipes'
Expand Down
4 changes: 2 additions & 2 deletions completions/just.powershell
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set')
[CompletionResult]::new('--command', 'command', [CompletionResultType]::ParameterName, 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set')
[CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Print shell completion script for <SHELL>')
[CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
[CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
[CompletionResult]::new('-s', 's', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('--show', 'show', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
[CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`')
Expand Down Expand Up @@ -67,8 +69,6 @@ Register-ArgumentCompleter -Native -CommandName 'just' -ScriptBlock {
[CompletionResult]::new('--evaluate', 'evaluate', [CompletionResultType]::ParameterName, 'Evaluate and print all variables. If a variable name is given as an argument, only print that variable''s value.')
[CompletionResult]::new('--fmt', 'fmt', [CompletionResultType]::ParameterName, 'Format and overwrite justfile')
[CompletionResult]::new('--init', 'init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root')
[CompletionResult]::new('-l', 'l', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
[CompletionResult]::new('--list', 'list', [CompletionResultType]::ParameterName, 'List available recipes and their arguments')
[CompletionResult]::new('--groups', 'groups', [CompletionResultType]::ParameterName, 'List recipe groups')
[CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page')
[CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
Expand Down
4 changes: 2 additions & 2 deletions completions/just.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ _just() {
'*-c+[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \
'*--command=[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \
'*--completions=[Print shell completion script for <SHELL>]:SHELL:(bash elvish fish powershell zsh)' \
'-l+[List available recipes and their arguments]' \
'--list=[List available recipes and their arguments]' \
'-s+[Show information about <RECIPE>]: :(_just_commands)' \
'--show=[Show information about <RECIPE>]: :(_just_commands)' \
'(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: : ' \
Expand Down Expand Up @@ -62,8 +64,6 @@ _just() {
'--evaluate[Evaluate and print all variables. If a variable name is given as an argument, only print that variable'\''s value.]' \
'--fmt[Format and overwrite justfile]' \
'--init[Initialize new justfile in project root]' \
'-l[List available recipes and their arguments]' \
'--list[List available recipes and their arguments]' \
'--groups[List recipe groups]' \
'--man[Print man page]' \
'--summary[List names of available recipes]' \
Expand Down
43 changes: 25 additions & 18 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,8 @@ mod cmd {
VARIABLES,
];

pub(crate) const ARGLESS: &[&str] = &[
CHANGELOG, DUMP, EDIT, FORMAT, INIT, LIST, MAN, SUMMARY, VARIABLES,
];
pub(crate) const ARGLESS: &[&str] =
&[CHANGELOG, DUMP, EDIT, FORMAT, INIT, MAN, SUMMARY, VARIABLES];
}

mod arg {
Expand Down Expand Up @@ -417,7 +416,9 @@ impl Config {
Arg::new(cmd::LIST)
.short('l')
.long("list")
.action(ArgAction::SetTrue)
.num_args(0..)
.value_name("PATH")
.action(ArgAction::Set)
.help("List available recipes and their arguments"),
)
.arg(
Expand Down Expand Up @@ -663,8 +664,18 @@ impl Config {
Subcommand::Format
} else if matches.get_flag(cmd::INIT) {
Subcommand::Init
} else if matches.get_flag(cmd::LIST) {
Subcommand::List
} else if let Some(path) = matches.get_many::<String>(cmd::LIST) {
Subcommand::List {
path: path
.clone()
.map(|s| (*s).as_str())
.collect::<Vec<&str>>()
.as_slice()
.try_into()
.map_err(|()| ConfigError::ListPath {
path: path.cloned().collect(),
})?,
}
} else if matches.get_flag(cmd::GROUPS) {
Subcommand::Groups
} else if matches.get_flag(cmd::MAN) {
Expand Down Expand Up @@ -1273,13 +1284,19 @@ mod tests {
test! {
name: subcommand_list_long,
args: ["--list"],
subcommand: Subcommand::List,
subcommand: Subcommand::List{ path: ModulePath{ path: Vec::new(), spaced: false } },
}

test! {
name: subcommand_list_short,
args: ["-l"],
subcommand: Subcommand::List,
subcommand: Subcommand::List{ path: ModulePath{ path: Vec::new(), spaced: false } },
}

test! {
name: subcommand_list_arguments,
args: ["--list", "bar"],
subcommand: Subcommand::List{ path: ModulePath{ path: vec!["bar".into()], spaced: false } },
}

test! {
Expand Down Expand Up @@ -1511,16 +1528,6 @@ mod tests {
},
}

error! {
name: list_arguments,
args: ["--list", "bar"],
error: ConfigError::SubcommandArguments { subcommand, arguments },
check: {
assert_eq!(subcommand, cmd::LIST);
assert_eq!(arguments, &["bar"]);
},
}

error! {
name: dump_arguments,
args: ["--dump", "bar"],
Expand Down
5 changes: 3 additions & 2 deletions src/config_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ pub(crate) enum ConfigError {
#[snafu(display("Failed to get current directory: {}", source))]
CurrentDir { source: io::Error },
#[snafu(display(
"Internal config error, this may indicate a bug in just: {} \
"Internal config error, this may indicate a bug in just: {message} \
consider filing an issue: https://github.com/casey/just/issues/new",
message
))]
Internal { message: String },
#[snafu(display("Invalid module path `{}`", path.join(" ")))]
ListPath { path: Vec<String> },
#[snafu(display(
"Path-prefixed recipes may not be used with `--working-directory` or `--justfile`."
))]
Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ pub(crate) enum Error<'src> {
recipe: &'src str,
line_number: Option<usize>,
},
UnknownSubmodule {
path: ModulePath,
},
UnknownOverrides {
overrides: Vec<String>,
},
Expand Down Expand Up @@ -432,6 +435,9 @@ impl<'src> ColorDisplay for Error<'src> {
write!(f, "Recipe `{recipe}` failed for an unknown reason")?;
}
}
UnknownSubmodule { path } => {
write!(f, "Justfile does not contain submodule `{path}`")?;
}
UnknownOverrides { overrides } => {
let count = Count("Variable", overrides.len());
let overrides = List::and_ticked(overrides);
Expand Down
4 changes: 2 additions & 2 deletions src/lexer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,12 +273,12 @@ impl<'src> Lexer<'src> {
}

/// True if `c` can be the first character of an identifier
fn is_identifier_start(c: char) -> bool {
pub(crate) fn is_identifier_start(c: char) -> bool {
matches!(c, 'a'..='z' | 'A'..='Z' | '_')
}

/// True if `c` can be a continuation character of an identifier
fn is_identifier_continue(c: char) -> bool {
pub(crate) fn is_identifier_continue(c: char) -> bool {
Self::is_identifier_start(c) || matches!(c, '0'..='9' | '-')
}

Expand Down
18 changes: 10 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ pub(crate) use {
evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function,
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal,
output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe,
recipe_context::RecipeContext, recipe_resolver::RecipeResolver,
recipe_signature::RecipeSignature, scope::Scope, search::Search, search_config::SearchConfig,
search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang,
shell::Shell, show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind,
load_dotenv::load_dotenv, loader::Loader, module_path::ModulePath, name::Name,
namepath::Namepath, ordinal::Ordinal, output::output, output_error::OutputError,
parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform,
platform_interface::PlatformInterface, position::Position, positional::Positional, ran::Ran,
range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext,
recipe_resolver::RecipeResolver, recipe_signature::RecipeSignature, scope::Scope,
search::Search, search_config::SearchConfig, search_error::SearchError, set::Set,
setting::Setting, settings::Settings, shebang::Shebang, shell::Shell,
show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind,
string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
Expand Down Expand Up @@ -153,6 +154,7 @@ mod line;
mod list;
mod load_dotenv;
mod loader;
mod module_path;
mod name;
mod namepath;
mod ordinal;
Expand Down
100 changes: 100 additions & 0 deletions src/module_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use super::*;

#[derive(Debug, PartialEq, Clone)]
pub(crate) struct ModulePath {
pub(crate) path: Vec<String>,
pub(crate) spaced: bool,
}

impl TryFrom<&[&str]> for ModulePath {
type Error = ();

fn try_from(path: &[&str]) -> Result<Self, Self::Error> {
let spaced = path.len() > 1;

let path = if path.len() == 1 {
let first = path[0];

if first.starts_with(':') || first.ends_with(':') || first.contains(":::") {
return Err(());
}

first
.split("::")
.map(str::to_string)
.collect::<Vec<String>>()
} else {
path.iter().map(|s| (*s).to_string()).collect()
};

for name in &path {
if name.is_empty() {
return Err(());
}

for (i, c) in name.chars().enumerate() {
if i == 0 {
if !Lexer::is_identifier_start(c) {
return Err(());
}
} else if !Lexer::is_identifier_continue(c) {
return Err(());
}
}
}

Ok(Self { path, spaced })
}
}

impl Display for ModulePath {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
for (i, name) in self.path.iter().enumerate() {
if i > 0 {
if self.spaced {
write!(f, " ")?;
} else {
write!(f, "::")?;
}
}
write!(f, "{name}")?;
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn try_from_ok() {
#[track_caller]
fn case(path: &[&str], expected: &[&str], display: &str) {
let actual = ModulePath::try_from(path).unwrap();
assert_eq!(actual.path, expected);
assert_eq!(actual.to_string(), display);
}

case(&[], &[], "");
case(&["foo"], &["foo"], "foo");
case(&["foo0"], &["foo0"], "foo0");
case(&["foo", "bar"], &["foo", "bar"], "foo bar");
case(&["foo::bar"], &["foo", "bar"], "foo::bar");
}

#[test]
fn try_from_err() {
#[track_caller]
fn case(path: &[&str]) {
assert!(ModulePath::try_from(path).is_err());
}

case(&[":foo"]);
case(&["foo:"]);
case(&["foo:::bar"]);
case(&["0foo"]);
case(&["f$oo"]);
case(&[""]);
}
}
Loading

0 comments on commit 9d2c6b8

Please sign in to comment.