Reform positional argument parsing (#523)
This diff makes positional argument parsing much cleaner, along with
adding a bunch of tests. Just's positional argument parsing is rather,
complex, so hopefully this reform allows it to both be correct and stay
correct.
User-visible changes:
- `just ..` is now accepted, with the same effect as `just ../`
- `just .` is also accepted, with the same effect as `just`
- It is now an error to pass arguments or overrides to subcommands
that do not accept them, namely `--dump`, `--edit`, `--list`,
`--show`, and `--summary`. It is also an error to pass arguments to
`--evaluate`, although `--evaluate` does of course still accept
overrides.
(This is a breaking change, but hopefully worth it, as it will allow us
to add arguments to subcommands which did not previously take
them, if we so desire.)
- Subcommands which do not accept arguments may now accept a
single search-directory argument, so `just --list ../` and
`just --dump foo/` are now accepted, with the former starting the
search for the justfile to list in the parent directory, and the latter
starting the search for the justfile to dump in `foo`.
2019-11-10 18:02:36 -08:00
|
|
|
use crate::common::*;
|
|
|
|
|
Gargantuan refactor (#522)
- Instead of changing the current directory with `env::set_current_dir`
to be implicitly inherited by subprocesses, we now use
`Command::current_dir` to set it explicitly. This feels much better,
since we aren't dependent on the implicit state of the process's
current directory.
- Subcommand execution is much improved.
- Added a ton of tests for config parsing, config execution, working
dir, and search dir.
- Error messages are improved. Many more will be colored.
- The Config is now onwed, instead of borrowing from the arguments and
the `clap::ArgMatches` object. This is a huge ergonomic improvement,
especially in tests, and I don't think anyone will notice.
- `--edit` now uses `$VISUAL`, `$EDITOR`, or `vim`, in that order,
matching git, which I think is what most people will expect.
- Added a cute `tmptree!{}` macro, for creating temporary directories
populated with directories and files for tests.
- Admitted that grammer is LL(k) and I don't know what `k` is.
2019-11-09 21:43:20 -08:00
|
|
|
#[derive(PartialEq, Clone, Debug)]
|
|
|
|
pub(crate) enum Subcommand {
|
2020-09-17 19:43:04 -07:00
|
|
|
Choose {
|
|
|
|
overrides: BTreeMap<String, String>,
|
|
|
|
chooser: Option<String>,
|
|
|
|
},
|
2020-01-15 01:20:38 -08:00
|
|
|
Completions {
|
|
|
|
shell: String,
|
|
|
|
},
|
2019-10-07 04:04:39 -07:00
|
|
|
Dump,
|
2019-10-31 17:39:25 -07:00
|
|
|
Edit,
|
Reform positional argument parsing (#523)
This diff makes positional argument parsing much cleaner, along with
adding a bunch of tests. Just's positional argument parsing is rather,
complex, so hopefully this reform allows it to both be correct and stay
correct.
User-visible changes:
- `just ..` is now accepted, with the same effect as `just ../`
- `just .` is also accepted, with the same effect as `just`
- It is now an error to pass arguments or overrides to subcommands
that do not accept them, namely `--dump`, `--edit`, `--list`,
`--show`, and `--summary`. It is also an error to pass arguments to
`--evaluate`, although `--evaluate` does of course still accept
overrides.
(This is a breaking change, but hopefully worth it, as it will allow us
to add arguments to subcommands which did not previously take
them, if we so desire.)
- Subcommands which do not accept arguments may now accept a
single search-directory argument, so `just --list ../` and
`just --dump foo/` are now accepted, with the former starting the
search for the justfile to list in the parent directory, and the latter
starting the search for the justfile to dump in `foo`.
2019-11-10 18:02:36 -08:00
|
|
|
Evaluate {
|
|
|
|
overrides: BTreeMap<String, String>,
|
|
|
|
},
|
2019-11-19 23:07:44 -08:00
|
|
|
Init,
|
|
|
|
List,
|
Reform positional argument parsing (#523)
This diff makes positional argument parsing much cleaner, along with
adding a bunch of tests. Just's positional argument parsing is rather,
complex, so hopefully this reform allows it to both be correct and stay
correct.
User-visible changes:
- `just ..` is now accepted, with the same effect as `just ../`
- `just .` is also accepted, with the same effect as `just`
- It is now an error to pass arguments or overrides to subcommands
that do not accept them, namely `--dump`, `--edit`, `--list`,
`--show`, and `--summary`. It is also an error to pass arguments to
`--evaluate`, although `--evaluate` does of course still accept
overrides.
(This is a breaking change, but hopefully worth it, as it will allow us
to add arguments to subcommands which did not previously take
them, if we so desire.)
- Subcommands which do not accept arguments may now accept a
single search-directory argument, so `just --list ../` and
`just --dump foo/` are now accepted, with the former starting the
search for the justfile to list in the parent directory, and the latter
starting the search for the justfile to dump in `foo`.
2019-11-10 18:02:36 -08:00
|
|
|
Run {
|
|
|
|
overrides: BTreeMap<String, String>,
|
|
|
|
arguments: Vec<String>,
|
|
|
|
},
|
|
|
|
Show {
|
|
|
|
name: String,
|
|
|
|
},
|
2019-10-31 17:39:25 -07:00
|
|
|
Summary,
|
2020-03-13 22:19:43 -07:00
|
|
|
Variables,
|
2019-10-07 04:04:39 -07:00
|
|
|
}
|
2020-03-16 17:20:14 -07:00
|
|
|
|
2020-05-03 20:35:53 -07:00
|
|
|
const FISH_RECIPE_COMPLETIONS: &str = r#"function __fish_just_complete_recipes
|
|
|
|
just --summary 2> /dev/null | tr " " "\n" || echo ""
|
|
|
|
end
|
|
|
|
|
|
|
|
# don't suggest files right off
|
|
|
|
complete -c just -n "__fish_is_first_arg" --no-files
|
|
|
|
|
|
|
|
# complete recipes
|
|
|
|
complete -c just -a '(__fish_just_complete_recipes)'
|
|
|
|
|
|
|
|
# autogenerated completions
|
|
|
|
"#;
|
|
|
|
|
2020-03-16 17:20:14 -07:00
|
|
|
const ZSH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[
|
|
|
|
(
|
|
|
|
r#" _arguments "${_arguments_options[@]}" \"#,
|
|
|
|
r#" local common=("#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
r#"'*--set=[Override <VARIABLE> with <VALUE>]' \"#,
|
|
|
|
r#"'*--set[Override <VARIABLE> with <VALUE>]: :_just_variables' \"#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
r#"'-s+[Show information about <RECIPE>]' \
|
|
|
|
'--show=[Show information about <RECIPE>]' \"#,
|
|
|
|
r#"'-s+[Show information about <RECIPE>]: :_just_commands' \
|
|
|
|
'--show=[Show information about <RECIPE>]: :_just_commands' \"#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
"'::ARGUMENTS -- Overrides and recipe(s) to run, defaulting to the first recipe in the \
|
|
|
|
justfile:_files' \\
|
|
|
|
&& ret=0
|
|
|
|
\x20\x20\x20\x20
|
|
|
|
",
|
|
|
|
r#")
|
|
|
|
|
|
|
|
_arguments "${_arguments_options[@]}" $common \
|
|
|
|
'1: :_just_commands' \
|
|
|
|
'*: :->args' \
|
|
|
|
&& ret=0
|
|
|
|
|
|
|
|
case $state in
|
|
|
|
args)
|
|
|
|
curcontext="${curcontext%:*}-${words[2]}:"
|
|
|
|
|
|
|
|
local lastarg=${words[${#words}]}
|
|
|
|
|
|
|
|
if [[ ${lastarg} = */* ]]; then
|
|
|
|
# Arguments contain slash would be recognised as a file
|
|
|
|
_arguments -s -S $common '*:: :_files'
|
|
|
|
else
|
|
|
|
# Show usage message
|
|
|
|
_message "`just --show ${words[2]}`"
|
|
|
|
# Or complete with other commands
|
|
|
|
#_arguments -s -S $common '*:: :_just_commands'
|
|
|
|
fi
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
|
|
|
|
return ret
|
|
|
|
"#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
" local commands; commands=(
|
|
|
|
\x20\x20\x20\x20\x20\x20\x20\x20
|
|
|
|
)",
|
|
|
|
r#" local commands; commands=(
|
|
|
|
${${${(M)"${(f)$(_call_program commands just --list)}":# *}/ ##/}/ ##/:Args: }
|
|
|
|
)
|
|
|
|
"#,
|
|
|
|
),
|
|
|
|
(
|
|
|
|
r#"_just "$@""#,
|
|
|
|
r#"(( $+functions[_just_variables] )) ||
|
|
|
|
_just_variables() {
|
|
|
|
local variables; variables=(
|
|
|
|
${(s: :)$(_call_program commands just --variables)}
|
|
|
|
)
|
|
|
|
|
|
|
|
_describe -t variables 'variables' variables
|
|
|
|
}
|
|
|
|
|
|
|
|
_just "$@""#,
|
|
|
|
),
|
|
|
|
];
|
|
|
|
|
2020-10-05 19:12:48 -07:00
|
|
|
const POWERSHELL_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
|
|
|
|
r#"$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
|
|
|
Sort-Object -Property ListItemText"#,
|
|
|
|
r#"function Get-JustFileRecipes([string[]]$CommandElements) {
|
|
|
|
$justFileIndex = $commandElements.IndexOf("--justfile");
|
|
|
|
|
|
|
|
if ($justFileIndex -ne -1 && $justFileIndex + 1 -le $commandElements.Length) {
|
|
|
|
$justFileLocation = $commandElements[$justFileIndex + 1]
|
|
|
|
}
|
|
|
|
|
|
|
|
$justArgs = @("--summary")
|
|
|
|
|
|
|
|
if (Test-Path $justFileLocation) {
|
|
|
|
$justArgs += @("--justfile", $justFileLocation)
|
|
|
|
}
|
|
|
|
|
|
|
|
$recipes = $(just @justArgs) -split ' '
|
|
|
|
return $recipes | ForEach-Object { [CompletionResult]::new($_) }
|
|
|
|
}
|
|
|
|
|
|
|
|
$elementValues = $commandElements | Select-Object -ExpandProperty Value
|
|
|
|
$recipes = Get-JustFileRecipes -CommandElements $elementValues
|
|
|
|
$completions += $recipes
|
|
|
|
$completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
|
|
|
|
Sort-Object -Property ListItemText"#,
|
|
|
|
)];
|
|
|
|
|
2020-10-05 17:58:30 -07:00
|
|
|
const BASH_COMPLETION_REPLACEMENTS: &[(&str, &str)] = &[(
|
|
|
|
r#" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
|
|
|
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
|
|
|
return 0
|
|
|
|
fi"#,
|
|
|
|
r#" if [[ ${cur} == -* ]] ; then
|
|
|
|
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
|
|
|
|
return 0
|
|
|
|
elif [[ ${COMP_CWORD} -eq 1 ]]; then
|
|
|
|
local recipes=$(just --summary --color never 2> /dev/null)
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
|
|
COMPREPLY=( $(compgen -W "${recipes}" -- "${cur}") )
|
|
|
|
return 0
|
|
|
|
fi
|
|
|
|
fi"#,
|
|
|
|
)];
|
|
|
|
|
2020-03-16 17:20:14 -07:00
|
|
|
impl Subcommand {
|
|
|
|
pub(crate) fn completions(shell: &str) -> Result<(), i32> {
|
2020-10-05 17:58:30 -07:00
|
|
|
use clap::Shell;
|
|
|
|
|
2020-03-16 17:20:14 -07:00
|
|
|
fn replace(haystack: &mut String, needle: &str, replacement: &str) -> Result<(), i32> {
|
|
|
|
if let Some(index) = haystack.find(needle) {
|
|
|
|
haystack.replace_range(index..index + needle.len(), replacement);
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
eprintln!("Failed to find text:");
|
|
|
|
eprintln!("{}", needle);
|
|
|
|
eprintln!("…in completion script:");
|
|
|
|
eprintln!("{}", haystack);
|
|
|
|
Err(EXIT_FAILURE)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let shell = shell
|
2020-10-05 17:58:30 -07:00
|
|
|
.parse::<Shell>()
|
2020-03-16 17:20:14 -07:00
|
|
|
.expect("Invalid value for clap::Shell");
|
|
|
|
|
|
|
|
let buffer = Vec::new();
|
|
|
|
let mut cursor = Cursor::new(buffer);
|
|
|
|
Config::app().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut cursor);
|
|
|
|
let buffer = cursor.into_inner();
|
|
|
|
let mut script = String::from_utf8(buffer).expect("Clap completion not UTF-8");
|
|
|
|
|
2020-10-05 17:58:30 -07:00
|
|
|
match shell {
|
|
|
|
Shell::Bash =>
|
|
|
|
for (needle, replacement) in BASH_COMPLETION_REPLACEMENTS {
|
|
|
|
replace(&mut script, needle, replacement)?;
|
|
|
|
},
|
|
|
|
Shell::Fish => {
|
|
|
|
script.insert_str(0, FISH_RECIPE_COMPLETIONS);
|
|
|
|
},
|
2020-10-05 19:12:48 -07:00
|
|
|
Shell::PowerShell =>
|
|
|
|
for (needle, replacement) in POWERSHELL_COMPLETION_REPLACEMENTS {
|
|
|
|
replace(&mut script, needle, replacement)?;
|
|
|
|
},
|
|
|
|
|
2020-10-05 17:58:30 -07:00
|
|
|
Shell::Zsh =>
|
|
|
|
for (needle, replacement) in ZSH_COMPLETION_REPLACEMENTS {
|
|
|
|
replace(&mut script, needle, replacement)?;
|
|
|
|
},
|
2020-10-05 19:12:48 -07:00
|
|
|
Shell::Elvish => {},
|
2020-05-03 20:35:53 -07:00
|
|
|
}
|
|
|
|
|
2020-03-16 17:20:14 -07:00
|
|
|
println!("{}", script.trim());
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|