Allow listing submodule recipes with --list PATH
(#2108)
This commit is contained in:
parent
d14aae1c29
commit
9d2c6b8858
18
README.md
18
README.md
@ -656,6 +656,24 @@ Available recipes:
|
|||||||
lint
|
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:
|
`just --summary` is more concise:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
@ -116,6 +116,14 @@ _just() {
|
|||||||
COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
|
COMPREPLY=($(compgen -W "bash elvish fish powershell zsh" -- "${cur}"))
|
||||||
return 0
|
return 0
|
||||||
;;
|
;;
|
||||||
|
--list)
|
||||||
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
-l)
|
||||||
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
--show)
|
--show)
|
||||||
COMPREPLY=($(compgen -f "${cur}"))
|
COMPREPLY=($(compgen -f "${cur}"))
|
||||||
return 0
|
return 0
|
||||||
|
@ -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 -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 --command 'Run an arbitrary command with the working directory, `.env`, overrides, and exports set'
|
||||||
cand --completions 'Print shell completion script for <SHELL>'
|
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 -s 'Show information about <RECIPE>'
|
||||||
cand --show 'Show information about <RECIPE>'
|
cand --show 'Show information about <RECIPE>'
|
||||||
cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
|
cand --dotenv-filename 'Search for environment file named <DOTENV-FILENAME> instead of `.env`'
|
||||||
@ -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 --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 --fmt 'Format and overwrite justfile'
|
||||||
cand --init 'Initialize new justfile in project root'
|
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 --groups 'List recipe groups'
|
||||||
cand --man 'Print man page'
|
cand --man 'Print man page'
|
||||||
cand --summary 'List names of available recipes'
|
cand --summary 'List names of available recipes'
|
||||||
|
@ -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 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 -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 -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 -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 -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
|
complete -c just -s E -l dotenv-path -d 'Load <DOTENV-PATH> as environment file instead of searching for one' -r -F
|
||||||
@ -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 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 fmt -d 'Format and overwrite justfile'
|
||||||
complete -c just -l init -d 'Initialize new justfile in project root'
|
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 groups -d 'List recipe groups'
|
||||||
complete -c just -l man -d 'Print man page'
|
complete -c just -l man -d 'Print man page'
|
||||||
complete -c just -l summary -d 'List names of available recipes'
|
complete -c just -l summary -d 'List names of available recipes'
|
||||||
|
@ -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('-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('--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('--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('-s', 's', [CompletionResultType]::ParameterName, 'Show information about <RECIPE>')
|
||||||
[CompletionResult]::new('--show', 'show', [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`')
|
[CompletionResult]::new('--dotenv-filename', 'dotenv-filename', [CompletionResultType]::ParameterName, 'Search for environment file named <DOTENV-FILENAME> instead of `.env`')
|
||||||
@ -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('--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('--fmt', 'fmt', [CompletionResultType]::ParameterName, 'Format and overwrite justfile')
|
||||||
[CompletionResult]::new('--init', 'init', [CompletionResultType]::ParameterName, 'Initialize new justfile in project root')
|
[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('--groups', 'groups', [CompletionResultType]::ParameterName, 'List recipe groups')
|
||||||
[CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page')
|
[CompletionResult]::new('--man', 'man', [CompletionResultType]::ParameterName, 'Print man page')
|
||||||
[CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
|
[CompletionResult]::new('--summary', 'summary', [CompletionResultType]::ParameterName, 'List names of available recipes')
|
||||||
|
@ -31,6 +31,8 @@ _just() {
|
|||||||
'*-c+[Run an arbitrary command with the working directory, \`.env\`, overrides, and exports set]: : ' \
|
'*-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]: : ' \
|
'*--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)' \
|
'*--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)' \
|
'-s+[Show information about <RECIPE>]: :(_just_commands)' \
|
||||||
'--show=[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\`]: : ' \
|
'(-E --dotenv-path)--dotenv-filename=[Search for environment file named <DOTENV-FILENAME> instead of \`.env\`]: : ' \
|
||||||
@ -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.]' \
|
'--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]' \
|
'--fmt[Format and overwrite justfile]' \
|
||||||
'--init[Initialize new justfile in project root]' \
|
'--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]' \
|
'--groups[List recipe groups]' \
|
||||||
'--man[Print man page]' \
|
'--man[Print man page]' \
|
||||||
'--summary[List names of available recipes]' \
|
'--summary[List names of available recipes]' \
|
||||||
|
@ -80,9 +80,8 @@ mod cmd {
|
|||||||
VARIABLES,
|
VARIABLES,
|
||||||
];
|
];
|
||||||
|
|
||||||
pub(crate) const ARGLESS: &[&str] = &[
|
pub(crate) const ARGLESS: &[&str] =
|
||||||
CHANGELOG, DUMP, EDIT, FORMAT, INIT, LIST, MAN, SUMMARY, VARIABLES,
|
&[CHANGELOG, DUMP, EDIT, FORMAT, INIT, MAN, SUMMARY, VARIABLES];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mod arg {
|
mod arg {
|
||||||
@ -417,7 +416,9 @@ impl Config {
|
|||||||
Arg::new(cmd::LIST)
|
Arg::new(cmd::LIST)
|
||||||
.short('l')
|
.short('l')
|
||||||
.long("list")
|
.long("list")
|
||||||
.action(ArgAction::SetTrue)
|
.num_args(0..)
|
||||||
|
.value_name("PATH")
|
||||||
|
.action(ArgAction::Set)
|
||||||
.help("List available recipes and their arguments"),
|
.help("List available recipes and their arguments"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -663,8 +664,18 @@ impl Config {
|
|||||||
Subcommand::Format
|
Subcommand::Format
|
||||||
} else if matches.get_flag(cmd::INIT) {
|
} else if matches.get_flag(cmd::INIT) {
|
||||||
Subcommand::Init
|
Subcommand::Init
|
||||||
} else if matches.get_flag(cmd::LIST) {
|
} else if let Some(path) = matches.get_many::<String>(cmd::LIST) {
|
||||||
Subcommand::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) {
|
} else if matches.get_flag(cmd::GROUPS) {
|
||||||
Subcommand::Groups
|
Subcommand::Groups
|
||||||
} else if matches.get_flag(cmd::MAN) {
|
} else if matches.get_flag(cmd::MAN) {
|
||||||
@ -1273,13 +1284,19 @@ mod tests {
|
|||||||
test! {
|
test! {
|
||||||
name: subcommand_list_long,
|
name: subcommand_list_long,
|
||||||
args: ["--list"],
|
args: ["--list"],
|
||||||
subcommand: Subcommand::List,
|
subcommand: Subcommand::List{ path: ModulePath{ path: Vec::new(), spaced: false } },
|
||||||
}
|
}
|
||||||
|
|
||||||
test! {
|
test! {
|
||||||
name: subcommand_list_short,
|
name: subcommand_list_short,
|
||||||
args: ["-l"],
|
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! {
|
test! {
|
||||||
@ -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! {
|
error! {
|
||||||
name: dump_arguments,
|
name: dump_arguments,
|
||||||
args: ["--dump", "bar"],
|
args: ["--dump", "bar"],
|
||||||
|
@ -6,11 +6,12 @@ pub(crate) enum ConfigError {
|
|||||||
#[snafu(display("Failed to get current directory: {}", source))]
|
#[snafu(display("Failed to get current directory: {}", source))]
|
||||||
CurrentDir { source: io::Error },
|
CurrentDir { source: io::Error },
|
||||||
#[snafu(display(
|
#[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",
|
consider filing an issue: https://github.com/casey/just/issues/new",
|
||||||
message
|
|
||||||
))]
|
))]
|
||||||
Internal { message: String },
|
Internal { message: String },
|
||||||
|
#[snafu(display("Invalid module path `{}`", path.join(" ")))]
|
||||||
|
ListPath { path: Vec<String> },
|
||||||
#[snafu(display(
|
#[snafu(display(
|
||||||
"Path-prefixed recipes may not be used with `--working-directory` or `--justfile`."
|
"Path-prefixed recipes may not be used with `--working-directory` or `--justfile`."
|
||||||
))]
|
))]
|
||||||
|
@ -160,6 +160,9 @@ pub(crate) enum Error<'src> {
|
|||||||
recipe: &'src str,
|
recipe: &'src str,
|
||||||
line_number: Option<usize>,
|
line_number: Option<usize>,
|
||||||
},
|
},
|
||||||
|
UnknownSubmodule {
|
||||||
|
path: ModulePath,
|
||||||
|
},
|
||||||
UnknownOverrides {
|
UnknownOverrides {
|
||||||
overrides: Vec<String>,
|
overrides: Vec<String>,
|
||||||
},
|
},
|
||||||
@ -432,6 +435,9 @@ impl<'src> ColorDisplay for Error<'src> {
|
|||||||
write!(f, "Recipe `{recipe}` failed for an unknown reason")?;
|
write!(f, "Recipe `{recipe}` failed for an unknown reason")?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
UnknownSubmodule { path } => {
|
||||||
|
write!(f, "Justfile does not contain submodule `{path}`")?;
|
||||||
|
}
|
||||||
UnknownOverrides { overrides } => {
|
UnknownOverrides { overrides } => {
|
||||||
let count = Count("Variable", overrides.len());
|
let count = Count("Variable", overrides.len());
|
||||||
let overrides = List::and_ticked(overrides);
|
let overrides = List::and_ticked(overrides);
|
||||||
|
@ -273,12 +273,12 @@ impl<'src> Lexer<'src> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// True if `c` can be the first character of an identifier
|
/// 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' | '_')
|
matches!(c, 'a'..='z' | 'A'..='Z' | '_')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True if `c` can be a continuation character of an identifier
|
/// 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' | '-')
|
Self::is_identifier_start(c) || matches!(c, '0'..='9' | '-')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
src/lib.rs
18
src/lib.rs
@ -25,14 +25,15 @@ pub(crate) use {
|
|||||||
evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function,
|
evaluator::Evaluator, expression::Expression, fragment::Fragment, function::Function,
|
||||||
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
|
interrupt_guard::InterruptGuard, interrupt_handler::InterruptHandler, item::Item,
|
||||||
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
|
justfile::Justfile, keyed::Keyed, keyword::Keyword, lexer::Lexer, line::Line, list::List,
|
||||||
load_dotenv::load_dotenv, loader::Loader, name::Name, namepath::Namepath, ordinal::Ordinal,
|
load_dotenv::load_dotenv, loader::Loader, module_path::ModulePath, name::Name,
|
||||||
output::output, output_error::OutputError, parameter::Parameter, parameter_kind::ParameterKind,
|
namepath::Namepath, ordinal::Ordinal, output::output, output_error::OutputError,
|
||||||
parser::Parser, platform::Platform, platform_interface::PlatformInterface, position::Position,
|
parameter::Parameter, parameter_kind::ParameterKind, parser::Parser, platform::Platform,
|
||||||
positional::Positional, ran::Ran, range_ext::RangeExt, recipe::Recipe,
|
platform_interface::PlatformInterface, position::Position, positional::Positional, ran::Ran,
|
||||||
recipe_context::RecipeContext, recipe_resolver::RecipeResolver,
|
range_ext::RangeExt, recipe::Recipe, recipe_context::RecipeContext,
|
||||||
recipe_signature::RecipeSignature, scope::Scope, search::Search, search_config::SearchConfig,
|
recipe_resolver::RecipeResolver, recipe_signature::RecipeSignature, scope::Scope,
|
||||||
search_error::SearchError, set::Set, setting::Setting, settings::Settings, shebang::Shebang,
|
search::Search, search_config::SearchConfig, search_error::SearchError, set::Set,
|
||||||
shell::Shell, show_whitespace::ShowWhitespace, source::Source, string_kind::StringKind,
|
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,
|
string_literal::StringLiteral, subcommand::Subcommand, suggestion::Suggestion, table::Table,
|
||||||
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
|
thunk::Thunk, token::Token, token_kind::TokenKind, unresolved_dependency::UnresolvedDependency,
|
||||||
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
|
unresolved_recipe::UnresolvedRecipe, use_color::UseColor, variables::Variables,
|
||||||
@ -153,6 +154,7 @@ mod line;
|
|||||||
mod list;
|
mod list;
|
||||||
mod load_dotenv;
|
mod load_dotenv;
|
||||||
mod loader;
|
mod loader;
|
||||||
|
mod module_path;
|
||||||
mod name;
|
mod name;
|
||||||
mod namepath;
|
mod namepath;
|
||||||
mod ordinal;
|
mod ordinal;
|
||||||
|
100
src/module_path.rs
Normal file
100
src/module_path.rs
Normal file
@ -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(&[""]);
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,9 @@ pub(crate) enum Subcommand {
|
|||||||
Format,
|
Format,
|
||||||
Groups,
|
Groups,
|
||||||
Init,
|
Init,
|
||||||
List,
|
List {
|
||||||
|
path: ModulePath,
|
||||||
|
},
|
||||||
Man,
|
Man,
|
||||||
Run {
|
Run {
|
||||||
arguments: Vec<String>,
|
arguments: Vec<String>,
|
||||||
@ -88,7 +90,7 @@ impl Subcommand {
|
|||||||
Dump => Self::dump(config, ast, justfile)?,
|
Dump => Self::dump(config, ast, justfile)?,
|
||||||
Format => Self::format(config, &search, src, ast)?,
|
Format => Self::format(config, &search, src, ast)?,
|
||||||
Groups => Self::groups(config, justfile),
|
Groups => Self::groups(config, justfile),
|
||||||
List => Self::list(config, 0, justfile),
|
List { path } => Self::list_module(config, justfile, path)?,
|
||||||
Show { ref name } => Self::show(config, name, justfile)?,
|
Show { ref name } => Self::show(config, name, justfile)?,
|
||||||
Summary => Self::summary(config, justfile),
|
Summary => Self::summary(config, justfile),
|
||||||
Variables => Self::variables(justfile),
|
Variables => Self::variables(justfile),
|
||||||
@ -477,6 +479,23 @@ impl Subcommand {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_module(
|
||||||
|
config: &Config,
|
||||||
|
mut module: &Justfile,
|
||||||
|
path: &ModulePath,
|
||||||
|
) -> Result<(), Error<'static>> {
|
||||||
|
for name in &path.path {
|
||||||
|
module = module
|
||||||
|
.modules
|
||||||
|
.get(name)
|
||||||
|
.ok_or_else(|| Error::UnknownSubmodule { path: path.clone() })?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self::list(config, 0, module);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn list(config: &Config, level: usize, justfile: &Justfile) {
|
fn list(config: &Config, level: usize, justfile: &Justfile) {
|
||||||
let aliases = if config.no_aliases {
|
let aliases = if config.no_aliases {
|
||||||
BTreeMap::new()
|
BTreeMap::new()
|
||||||
|
@ -254,3 +254,80 @@ fn unsorted_list_order() {
|
|||||||
)
|
)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_submodule() {
|
||||||
|
Test::new()
|
||||||
|
.write("foo.just", "bar:")
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
mod foo
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.test_round_trip(false)
|
||||||
|
.args(["--unstable", "--list", "foo"])
|
||||||
|
.stdout(
|
||||||
|
"
|
||||||
|
Available recipes:
|
||||||
|
bar
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_nested_submodule() {
|
||||||
|
Test::new()
|
||||||
|
.write("foo.just", "mod bar")
|
||||||
|
.write("bar.just", "baz:")
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
mod foo
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.test_round_trip(false)
|
||||||
|
.args(["--unstable", "--list", "foo", "bar"])
|
||||||
|
.stdout(
|
||||||
|
"
|
||||||
|
Available recipes:
|
||||||
|
baz
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
|
||||||
|
Test::new()
|
||||||
|
.write("foo.just", "mod bar")
|
||||||
|
.write("bar.just", "baz:")
|
||||||
|
.justfile(
|
||||||
|
"
|
||||||
|
mod foo
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.test_round_trip(false)
|
||||||
|
.args(["--unstable", "--list", "foo::bar"])
|
||||||
|
.stdout(
|
||||||
|
"
|
||||||
|
Available recipes:
|
||||||
|
baz
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_invalid_path() {
|
||||||
|
Test::new()
|
||||||
|
.args(["--unstable", "--list", "$hello"])
|
||||||
|
.stderr("error: Invalid module path `$hello`\n")
|
||||||
|
.status(1)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn list_unknown_submodule() {
|
||||||
|
Test::new()
|
||||||
|
.args(["--unstable", "--list", "hello"])
|
||||||
|
.stderr("error: Justfile does not contain submodule `hello`\n")
|
||||||
|
.status(1)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user