2024-06-08 14:56:21 -07:00
|
|
|
use {super::*, clap_mangen::Man};
|
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
|
|
|
|
2022-02-23 11:47:43 -08:00
|
|
|
const INIT_JUSTFILE: &str = "default:\n echo 'Hello, world!'\n";
|
2021-07-26 17:19:52 -07:00
|
|
|
|
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 {
|
2021-07-31 13:53:27 -07:00
|
|
|
Changelog,
|
2020-09-17 19:43:04 -07:00
|
|
|
Choose {
|
|
|
|
overrides: BTreeMap<String, String>,
|
2021-09-16 06:44:40 -07:00
|
|
|
chooser: Option<String>,
|
2020-09-17 19:43:04 -07:00
|
|
|
},
|
2021-05-09 20:35:35 -07:00
|
|
|
Command {
|
|
|
|
arguments: Vec<OsString>,
|
2021-09-16 06:44:40 -07:00
|
|
|
binary: OsString,
|
2021-05-09 20:35:35 -07:00
|
|
|
overrides: BTreeMap<String, String>,
|
|
|
|
},
|
2020-01-15 01:20:38 -08:00
|
|
|
Completions {
|
2024-06-08 14:56:21 -07:00
|
|
|
shell: completions::Shell,
|
2020-01-15 01:20:38 -08:00
|
|
|
},
|
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>,
|
2021-09-16 06:44:40 -07:00
|
|
|
variable: Option<String>,
|
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
|
|
|
},
|
2021-06-08 01:01:27 -07:00
|
|
|
Format,
|
2024-05-25 00:32:25 -07:00
|
|
|
Groups,
|
2019-11-19 23:07:44 -08:00
|
|
|
Init,
|
2024-05-29 01:08:29 -07:00
|
|
|
List {
|
|
|
|
path: ModulePath,
|
|
|
|
},
|
2024-05-15 00:28:50 -07:00
|
|
|
Man,
|
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 {
|
|
|
|
arguments: Vec<String>,
|
2022-03-30 22:13:59 -07:00
|
|
|
overrides: BTreeMap<String, String>,
|
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
|
|
|
},
|
|
|
|
Show {
|
2024-05-29 18:41:37 -07:00
|
|
|
path: ModulePath,
|
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
|
|
|
},
|
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
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
impl Subcommand {
|
2024-06-14 16:11:22 -07:00
|
|
|
pub(crate) fn execute<'src>(&self, config: &Config, loader: &'src Loader) -> RunResult<'src> {
|
2021-07-26 17:19:52 -07:00
|
|
|
use Subcommand::*;
|
2020-03-16 17:20:14 -07:00
|
|
|
|
2021-07-31 13:53:27 -07:00
|
|
|
match self {
|
|
|
|
Changelog => {
|
|
|
|
Self::changelog();
|
|
|
|
return Ok(());
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2024-05-14 20:29:40 -07:00
|
|
|
Completions { shell } => return Self::completions(*shell),
|
2021-07-31 13:53:27 -07:00
|
|
|
Init => return Self::init(config),
|
2024-05-15 00:28:50 -07:00
|
|
|
Man => return Self::man(),
|
2022-03-30 22:13:59 -07:00
|
|
|
Run {
|
|
|
|
arguments,
|
|
|
|
overrides,
|
|
|
|
} => return Self::run(config, loader, arguments, overrides),
|
2021-09-16 06:44:40 -07:00
|
|
|
_ => {}
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
2020-10-05 19:12:48 -07:00
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
let search = Search::find(&config.search_config, &config.invocation_directory)?;
|
2020-10-05 19:12:48 -07:00
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
if let Edit = self {
|
|
|
|
return Self::edit(&search);
|
|
|
|
}
|
2020-10-05 19:12:48 -07:00
|
|
|
|
2023-11-21 11:28:59 -08:00
|
|
|
let compilation = Self::compile(config, loader, &search)?;
|
|
|
|
let justfile = &compilation.justfile;
|
|
|
|
let ast = compilation.root_ast();
|
|
|
|
let src = compilation.root_src();
|
2020-10-05 19:12:48 -07:00
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
match self {
|
2021-09-16 06:44:40 -07:00
|
|
|
Choose { overrides, chooser } => {
|
|
|
|
Self::choose(config, justfile, &search, overrides, chooser.as_deref())?;
|
|
|
|
}
|
2021-09-16 07:51:45 -07:00
|
|
|
Command { overrides, .. } | Evaluate { overrides, .. } => {
|
2022-01-30 12:16:10 -08:00
|
|
|
justfile.run(config, &search, overrides, &[])?;
|
2021-09-16 07:51:45 -07:00
|
|
|
}
|
2021-11-17 00:07:48 -08:00
|
|
|
Dump => Self::dump(config, ast, justfile)?,
|
2022-01-30 12:16:10 -08:00
|
|
|
Format => Self::format(config, &search, src, ast)?,
|
2024-05-25 00:32:25 -07:00
|
|
|
Groups => Self::groups(config, justfile),
|
2024-05-29 19:15:10 -07:00
|
|
|
List { path } => Self::list(config, justfile, path)?,
|
2024-05-29 18:41:37 -07:00
|
|
|
Show { path } => Self::show(config, justfile, path)?,
|
2021-07-28 18:06:57 -07:00
|
|
|
Summary => Self::summary(config, justfile),
|
2021-07-26 17:19:52 -07:00
|
|
|
Variables => Self::variables(justfile),
|
2024-05-15 00:28:50 -07:00
|
|
|
Changelog | Completions { .. } | Edit | Init | Man | Run { .. } => unreachable!(),
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
2020-10-05 17:58:30 -07:00
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
fn groups(config: &Config, justfile: &Justfile) {
|
|
|
|
println!("Recipe groups:");
|
2024-06-14 13:35:03 -07:00
|
|
|
for group in justfile.public_groups(config) {
|
2024-05-25 00:32:25 -07:00
|
|
|
println!("{}{group}", config.list_prefix);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-21 11:28:59 -08:00
|
|
|
fn run<'src>(
|
2022-03-30 22:13:59 -07:00
|
|
|
config: &Config,
|
|
|
|
loader: &'src Loader,
|
|
|
|
arguments: &[String],
|
|
|
|
overrides: &BTreeMap<String, String>,
|
2024-06-14 16:11:22 -07:00
|
|
|
) -> RunResult<'src> {
|
2023-01-03 22:31:56 -08:00
|
|
|
if matches!(
|
|
|
|
config.search_config,
|
|
|
|
SearchConfig::FromInvocationDirectory | SearchConfig::FromSearchDirectory { .. }
|
|
|
|
) {
|
|
|
|
let starting_path = match &config.search_config {
|
2022-09-20 22:46:53 -07:00
|
|
|
SearchConfig::FromInvocationDirectory => config.invocation_directory.clone(),
|
2023-01-03 22:31:56 -08:00
|
|
|
SearchConfig::FromSearchDirectory { search_directory } => {
|
2023-10-16 20:07:09 -07:00
|
|
|
env::current_dir().unwrap().join(search_directory)
|
2023-01-03 22:31:56 -08:00
|
|
|
}
|
2022-09-20 22:46:53 -07:00
|
|
|
_ => unreachable!(),
|
|
|
|
};
|
2022-03-30 22:13:59 -07:00
|
|
|
|
2023-01-03 22:31:56 -08:00
|
|
|
let mut path = starting_path.clone();
|
|
|
|
|
2022-03-30 22:13:59 -07:00
|
|
|
let mut unknown_recipes_errors = None;
|
|
|
|
|
|
|
|
loop {
|
|
|
|
let search = match Search::find_next(&path) {
|
|
|
|
Err(SearchError::NotFound) => match unknown_recipes_errors {
|
|
|
|
Some(err) => return Err(err),
|
|
|
|
None => return Err(SearchError::NotFound.into()),
|
|
|
|
},
|
|
|
|
Err(err) => return Err(err.into()),
|
|
|
|
Ok(search) => {
|
2023-01-13 13:36:52 -08:00
|
|
|
if config.verbosity.loquacious() && path != starting_path {
|
2022-03-30 22:13:59 -07:00
|
|
|
eprintln!(
|
|
|
|
"Trying {}",
|
2023-01-03 22:31:56 -08:00
|
|
|
starting_path
|
2022-03-30 22:13:59 -07:00
|
|
|
.strip_prefix(path)
|
|
|
|
.unwrap()
|
|
|
|
.components()
|
|
|
|
.map(|_| path::Component::ParentDir)
|
|
|
|
.collect::<PathBuf>()
|
|
|
|
.join(search.justfile.file_name().unwrap())
|
|
|
|
.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
search
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
match Self::run_inner(config, loader, arguments, overrides, &search) {
|
2024-06-13 19:41:45 -07:00
|
|
|
Err((err @ Error::UnknownRecipe { .. }, true)) => {
|
2022-03-30 22:13:59 -07:00
|
|
|
match search.justfile.parent().unwrap().parent() {
|
|
|
|
Some(parent) => {
|
|
|
|
unknown_recipes_errors.get_or_insert(err);
|
|
|
|
path = parent.into();
|
|
|
|
}
|
|
|
|
None => return Err(err),
|
|
|
|
}
|
|
|
|
}
|
2022-10-19 19:00:09 -07:00
|
|
|
result => return result.map_err(|(err, _fallback)| err),
|
2022-03-30 22:13:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Self::run_inner(
|
|
|
|
config,
|
|
|
|
loader,
|
|
|
|
arguments,
|
|
|
|
overrides,
|
|
|
|
&Search::find(&config.search_config, &config.invocation_directory)?,
|
|
|
|
)
|
2022-10-19 19:00:09 -07:00
|
|
|
.map_err(|(err, _fallback)| err)
|
2022-03-30 22:13:59 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn run_inner<'src>(
|
|
|
|
config: &Config,
|
|
|
|
loader: &'src Loader,
|
|
|
|
arguments: &[String],
|
|
|
|
overrides: &BTreeMap<String, String>,
|
|
|
|
search: &Search,
|
2022-10-19 19:00:09 -07:00
|
|
|
) -> Result<(), (Error<'src>, bool)> {
|
2023-11-21 11:28:59 -08:00
|
|
|
let compilation = Self::compile(config, loader, search).map_err(|err| (err, false))?;
|
|
|
|
let justfile = &compilation.justfile;
|
2022-10-19 19:00:09 -07:00
|
|
|
justfile
|
|
|
|
.run(config, search, overrides, arguments)
|
|
|
|
.map_err(|err| (err, justfile.settings.fallback))
|
2022-03-30 22:13:59 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn compile<'src>(
|
|
|
|
config: &Config,
|
|
|
|
loader: &'src Loader,
|
|
|
|
search: &Search,
|
2024-06-14 16:11:22 -07:00
|
|
|
) -> RunResult<'src, Compilation<'src>> {
|
2023-12-27 20:27:15 -08:00
|
|
|
let compilation = Compiler::compile(config.unstable, loader, &search.justfile)?;
|
2022-03-30 22:13:59 -07:00
|
|
|
|
|
|
|
if config.verbosity.loud() {
|
2023-11-21 11:28:59 -08:00
|
|
|
for warning in &compilation.justfile.warnings {
|
2022-03-30 22:13:59 -07:00
|
|
|
eprintln!("{}", warning.color_display(config.color.stderr()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-21 11:28:59 -08:00
|
|
|
Ok(compilation)
|
2022-03-30 22:13:59 -07:00
|
|
|
}
|
|
|
|
|
2021-07-31 13:53:27 -07:00
|
|
|
fn changelog() {
|
|
|
|
print!("{}", include_str!("../CHANGELOG.md"));
|
|
|
|
}
|
|
|
|
|
2021-07-26 17:19:52 -07:00
|
|
|
fn choose<'src>(
|
|
|
|
config: &Config,
|
2023-11-21 11:28:59 -08:00
|
|
|
justfile: &Justfile<'src>,
|
2021-07-26 17:19:52 -07:00
|
|
|
search: &Search,
|
|
|
|
overrides: &BTreeMap<String, String>,
|
|
|
|
chooser: Option<&str>,
|
2024-06-14 16:11:22 -07:00
|
|
|
) -> RunResult<'src> {
|
2024-06-14 13:35:03 -07:00
|
|
|
let mut recipes = Vec::<&Recipe>::new();
|
2024-05-20 23:22:56 -07:00
|
|
|
let mut stack = vec![justfile];
|
|
|
|
while let Some(module) = stack.pop() {
|
|
|
|
recipes.extend(
|
|
|
|
module
|
2024-05-25 01:01:37 -07:00
|
|
|
.public_recipes(config)
|
2024-05-20 23:22:56 -07:00
|
|
|
.iter()
|
|
|
|
.filter(|recipe| recipe.min_arguments() == 0),
|
|
|
|
);
|
|
|
|
stack.extend(module.modules.values());
|
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
|
|
|
|
if recipes.is_empty() {
|
|
|
|
return Err(Error::NoChoosableRecipes);
|
|
|
|
}
|
|
|
|
|
2024-06-08 18:01:24 -07:00
|
|
|
let chooser = if let Some(chooser) = chooser {
|
|
|
|
OsString::from(chooser)
|
|
|
|
} else {
|
|
|
|
let mut chooser = OsString::new();
|
|
|
|
chooser.push("fzf --multi --preview 'just --unstable --color always --justfile \"");
|
|
|
|
chooser.push(&search.justfile);
|
|
|
|
chooser.push("\" --show {}'");
|
|
|
|
chooser
|
|
|
|
};
|
2021-07-26 17:19:52 -07:00
|
|
|
|
|
|
|
let result = justfile
|
|
|
|
.settings
|
2022-01-30 12:16:10 -08:00
|
|
|
.shell_command(config)
|
2021-07-26 17:19:52 -07:00
|
|
|
.arg(&chooser)
|
|
|
|
.current_dir(&search.working_directory)
|
|
|
|
.stdin(Stdio::piped())
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.spawn();
|
|
|
|
|
|
|
|
let mut child = match result {
|
|
|
|
Ok(child) => child,
|
|
|
|
Err(io_error) => {
|
2022-08-08 19:50:31 -07:00
|
|
|
let (shell_binary, shell_arguments) = justfile.settings.shell(config);
|
2021-07-26 17:19:52 -07:00
|
|
|
return Err(Error::ChooserInvoke {
|
2022-08-08 19:50:31 -07:00
|
|
|
shell_binary: shell_binary.to_owned(),
|
|
|
|
shell_arguments: shell_arguments.join(" "),
|
2021-07-26 17:19:52 -07:00
|
|
|
chooser,
|
|
|
|
io_error,
|
|
|
|
});
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
for recipe in recipes {
|
2024-05-30 10:24:06 -07:00
|
|
|
writeln!(
|
|
|
|
child.stdin.as_mut().unwrap(),
|
|
|
|
"{}",
|
|
|
|
recipe.namepath.spaced()
|
|
|
|
)
|
|
|
|
.map_err(|io_error| Error::ChooserWrite {
|
|
|
|
io_error,
|
|
|
|
chooser: chooser.clone(),
|
|
|
|
})?;
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let output = match child.wait_with_output() {
|
|
|
|
Ok(output) => output,
|
|
|
|
Err(io_error) => {
|
|
|
|
return Err(Error::ChooserRead { io_error, chooser });
|
2021-09-16 06:44:40 -07:00
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
if !output.status.success() {
|
|
|
|
return Err(Error::ChooserStatus {
|
|
|
|
status: output.status,
|
|
|
|
chooser,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
|
|
|
|
|
|
let recipes = stdout
|
|
|
|
.split_whitespace()
|
|
|
|
.map(str::to_owned)
|
|
|
|
.collect::<Vec<String>>();
|
|
|
|
|
|
|
|
justfile.run(config, search, overrides, &recipes)
|
|
|
|
}
|
|
|
|
|
2024-06-08 14:56:21 -07:00
|
|
|
fn completions(shell: completions::Shell) -> RunResult<'static, ()> {
|
|
|
|
println!("{}", shell.script()?);
|
2020-03-16 17:20:14 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
|
2024-06-14 16:11:22 -07:00
|
|
|
fn dump(config: &Config, ast: &Ast, justfile: &Justfile) -> RunResult<'static> {
|
2021-11-17 00:07:48 -08:00
|
|
|
match config.dump_format {
|
|
|
|
DumpFormat::Json => {
|
2023-11-21 11:28:59 -08:00
|
|
|
serde_json::to_writer(io::stdout(), justfile)
|
2021-11-17 00:07:48 -08:00
|
|
|
.map_err(|serde_json_error| Error::DumpJson { serde_json_error })?;
|
|
|
|
println!();
|
|
|
|
}
|
2022-12-15 16:53:21 -08:00
|
|
|
DumpFormat::Just => print!("{ast}"),
|
2021-11-17 00:07:48 -08:00
|
|
|
}
|
|
|
|
Ok(())
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
|
|
|
|
2024-06-14 16:11:22 -07:00
|
|
|
fn edit(search: &Search) -> RunResult<'static> {
|
2021-07-26 17:19:52 -07:00
|
|
|
let editor = env::var_os("VISUAL")
|
|
|
|
.or_else(|| env::var_os("EDITOR"))
|
|
|
|
.unwrap_or_else(|| "vim".into());
|
|
|
|
|
|
|
|
let error = Command::new(&editor)
|
|
|
|
.current_dir(&search.working_directory)
|
|
|
|
.arg(&search.justfile)
|
|
|
|
.status();
|
|
|
|
|
|
|
|
let status = match error {
|
|
|
|
Err(io_error) => return Err(Error::EditorInvoke { editor, io_error }),
|
|
|
|
Ok(status) => status,
|
|
|
|
};
|
|
|
|
|
|
|
|
if !status.success() {
|
|
|
|
return Err(Error::EditorStatus { editor, status });
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-14 16:11:22 -07:00
|
|
|
fn format(config: &Config, search: &Search, src: &str, ast: &Ast) -> RunResult<'static> {
|
2021-07-26 17:19:52 -07:00
|
|
|
config.require_unstable("The `--fmt` command is currently unstable.")?;
|
|
|
|
|
2021-10-31 23:18:11 -07:00
|
|
|
let formatted = ast.to_string();
|
|
|
|
|
2021-10-31 21:27:59 -07:00
|
|
|
if config.check {
|
2022-09-11 02:25:38 -07:00
|
|
|
return if formatted == src {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2022-12-20 20:57:58 -08:00
|
|
|
if !config.verbosity.quiet() {
|
|
|
|
use similar::{ChangeTag, TextDiff};
|
|
|
|
|
|
|
|
let diff = TextDiff::configure()
|
|
|
|
.algorithm(similar::Algorithm::Patience)
|
|
|
|
.diff_lines(src, &formatted);
|
|
|
|
|
|
|
|
for op in diff.ops() {
|
|
|
|
for change in diff.iter_changes(op) {
|
|
|
|
let (symbol, color) = match change.tag() {
|
2023-01-13 10:30:27 -08:00
|
|
|
ChangeTag::Delete => ("-", config.color.stdout().diff_deleted()),
|
|
|
|
ChangeTag::Equal => (" ", config.color.stdout()),
|
|
|
|
ChangeTag::Insert => ("+", config.color.stdout().diff_added()),
|
2022-12-20 20:57:58 -08:00
|
|
|
};
|
|
|
|
|
2023-01-13 10:30:27 -08:00
|
|
|
print!("{}{symbol}{change}{}", color.prefix(), color.suffix());
|
2022-12-20 20:57:58 -08:00
|
|
|
}
|
2021-10-31 23:18:11 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-31 21:27:59 -07:00
|
|
|
Err(Error::FormatCheckFoundDiff)
|
2021-10-31 23:18:11 -07:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
fs::write(&search.justfile, formatted).map_err(|io_error| Error::WriteJustfile {
|
|
|
|
justfile: search.justfile.clone(),
|
|
|
|
io_error,
|
|
|
|
})?;
|
|
|
|
|
|
|
|
if config.verbosity.loud() {
|
|
|
|
eprintln!("Wrote justfile to `{}`", search.justfile.display());
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
2021-10-31 23:18:11 -07:00
|
|
|
|
|
|
|
Ok(())
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
|
|
|
|
2024-06-14 16:11:22 -07:00
|
|
|
fn init(config: &Config) -> RunResult<'static> {
|
2021-07-26 17:19:52 -07:00
|
|
|
let search = Search::init(&config.search_config, &config.invocation_directory)?;
|
|
|
|
|
|
|
|
if search.justfile.is_file() {
|
|
|
|
Err(Error::InitExists {
|
|
|
|
justfile: search.justfile,
|
|
|
|
})
|
|
|
|
} else if let Err(io_error) = fs::write(&search.justfile, INIT_JUSTFILE) {
|
|
|
|
Err(Error::WriteJustfile {
|
|
|
|
justfile: search.justfile,
|
|
|
|
io_error,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
if config.verbosity.loud() {
|
|
|
|
eprintln!("Wrote justfile to `{}`", search.justfile.display());
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-14 16:11:22 -07:00
|
|
|
fn man() -> RunResult<'static> {
|
2024-05-15 00:28:50 -07:00
|
|
|
let mut buffer = Vec::<u8>::new();
|
|
|
|
|
|
|
|
Man::new(Config::app())
|
|
|
|
.render(&mut buffer)
|
|
|
|
.expect("writing to buffer cannot fail");
|
|
|
|
|
|
|
|
let mut stdout = io::stdout().lock();
|
|
|
|
|
|
|
|
stdout
|
|
|
|
.write_all(&buffer)
|
|
|
|
.map_err(|io_error| Error::StdoutIo { io_error })?;
|
|
|
|
|
|
|
|
stdout
|
|
|
|
.flush()
|
|
|
|
.map_err(|io_error| Error::StdoutIo { io_error })?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2024-06-14 16:11:22 -07:00
|
|
|
fn list(config: &Config, mut module: &Justfile, path: &ModulePath) -> RunResult<'static> {
|
2024-05-29 01:08:29 -07:00
|
|
|
for name in &path.path {
|
|
|
|
module = module
|
|
|
|
.modules
|
|
|
|
.get(name)
|
2024-06-13 19:41:45 -07:00
|
|
|
.ok_or_else(|| Error::UnknownSubmodule {
|
|
|
|
path: path.to_string(),
|
|
|
|
})?;
|
2024-05-29 01:08:29 -07:00
|
|
|
}
|
|
|
|
|
2024-05-30 10:28:54 -07:00
|
|
|
Self::list_module(config, module, 0);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn list_module(config: &Config, module: &Justfile, depth: usize) {
|
2024-05-25 00:32:25 -07:00
|
|
|
let aliases = if config.no_aliases {
|
|
|
|
BTreeMap::new()
|
|
|
|
} else {
|
|
|
|
let mut aliases = BTreeMap::<&str, Vec<&str>>::new();
|
2024-05-29 19:15:10 -07:00
|
|
|
for alias in module.aliases.values().filter(|alias| !alias.is_private()) {
|
2024-05-25 00:32:25 -07:00
|
|
|
aliases
|
|
|
|
.entry(alias.target.name.lexeme())
|
|
|
|
.or_default()
|
|
|
|
.push(alias.name.lexeme());
|
|
|
|
}
|
|
|
|
aliases
|
|
|
|
};
|
2024-05-14 19:37:00 -07:00
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
let signature_widths = {
|
|
|
|
let mut signature_widths: BTreeMap<&str, usize> = BTreeMap::new();
|
2024-05-20 00:25:18 -07:00
|
|
|
|
2024-05-29 19:15:10 -07:00
|
|
|
for (name, recipe) in &module.recipes {
|
2024-05-25 00:32:25 -07:00
|
|
|
if !recipe.is_public() {
|
2024-03-26 12:20:46 -07:00
|
|
|
continue;
|
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
for name in iter::once(name).chain(aliases.get(name).unwrap_or(&Vec::new())) {
|
|
|
|
signature_widths.insert(
|
|
|
|
name,
|
|
|
|
UnicodeWidthStr::width(
|
|
|
|
RecipeSignature { name, recipe }
|
|
|
|
.color_display(Color::never())
|
|
|
|
.to_string()
|
|
|
|
.as_str(),
|
|
|
|
),
|
|
|
|
);
|
2024-03-26 12:20:46 -07:00
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
signature_widths
|
|
|
|
};
|
2021-07-26 17:19:52 -07:00
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
let max_signature_width = signature_widths
|
2024-05-20 00:25:18 -07:00
|
|
|
.values()
|
|
|
|
.copied()
|
2024-05-25 00:32:25 -07:00
|
|
|
.filter(|width| *width <= 50)
|
2024-05-20 00:25:18 -07:00
|
|
|
.max()
|
2024-05-25 00:32:25 -07:00
|
|
|
.unwrap_or(0);
|
|
|
|
|
2024-05-30 10:28:54 -07:00
|
|
|
let list_prefix = config.list_prefix.repeat(depth + 1);
|
|
|
|
|
|
|
|
if depth == 0 {
|
|
|
|
print!("{}", config.list_heading);
|
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
let groups = {
|
|
|
|
let mut groups = BTreeMap::<Option<String>, Vec<&Recipe>>::new();
|
2024-05-29 19:15:10 -07:00
|
|
|
for recipe in module.public_recipes(config) {
|
2024-05-25 00:32:25 -07:00
|
|
|
let recipe_groups = recipe.groups();
|
|
|
|
if recipe_groups.is_empty() {
|
|
|
|
groups.entry(None).or_default().push(recipe);
|
|
|
|
} else {
|
|
|
|
for group in recipe_groups {
|
|
|
|
groups.entry(Some(group)).or_default().push(recipe);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
groups
|
|
|
|
};
|
2021-07-26 17:19:52 -07:00
|
|
|
|
2024-06-14 20:04:47 -07:00
|
|
|
let mut ordered = module
|
|
|
|
.public_groups(config)
|
|
|
|
.into_iter()
|
|
|
|
.map(Some)
|
|
|
|
.collect::<Vec<Option<String>>>();
|
|
|
|
|
|
|
|
if groups.contains_key(&None) {
|
|
|
|
ordered.insert(0, None);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i, group) in ordered.into_iter().enumerate() {
|
2024-05-25 00:32:25 -07:00
|
|
|
if i > 0 {
|
|
|
|
println!();
|
|
|
|
}
|
2024-05-19 23:47:57 -07:00
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
let no_groups = groups.contains_key(&None) && groups.len() == 1;
|
|
|
|
|
|
|
|
if !no_groups {
|
2024-05-30 10:28:54 -07:00
|
|
|
print!("{list_prefix}");
|
2024-06-14 20:04:47 -07:00
|
|
|
if let Some(group) = &group {
|
|
|
|
println!("[{group}]");
|
2024-05-25 00:32:25 -07:00
|
|
|
} else {
|
|
|
|
println!("(no group)");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-14 20:04:47 -07:00
|
|
|
for recipe in groups.get(&group).unwrap() {
|
2024-05-25 00:32:25 -07:00
|
|
|
for (i, name) in iter::once(&recipe.name())
|
|
|
|
.chain(aliases.get(recipe.name()).unwrap_or(&Vec::new()))
|
|
|
|
.enumerate()
|
|
|
|
{
|
|
|
|
let doc = if i == 0 {
|
2024-05-25 02:26:04 -07:00
|
|
|
recipe.doc().map(Cow::Borrowed)
|
2024-05-25 00:32:25 -07:00
|
|
|
} else {
|
|
|
|
Some(Cow::Owned(format!("alias for `{}`", recipe.name)))
|
|
|
|
};
|
|
|
|
|
2024-05-25 18:12:55 -07:00
|
|
|
if let Some(doc) = &doc {
|
|
|
|
if doc.lines().count() > 1 {
|
|
|
|
for line in doc.lines() {
|
|
|
|
println!(
|
2024-05-30 10:28:54 -07:00
|
|
|
"{list_prefix}{} {}",
|
2024-05-25 18:12:55 -07:00
|
|
|
config.color.stdout().doc().paint("#"),
|
|
|
|
config.color.stdout().doc().paint(line),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
print!(
|
2024-05-30 10:28:54 -07:00
|
|
|
"{list_prefix}{}",
|
2024-05-25 18:12:55 -07:00
|
|
|
RecipeSignature { name, recipe }.color_display(config.color.stdout())
|
|
|
|
);
|
|
|
|
|
2024-05-25 00:32:25 -07:00
|
|
|
if let Some(doc) = doc {
|
2024-05-25 18:12:55 -07:00
|
|
|
if doc.lines().count() <= 1 {
|
|
|
|
print!(
|
|
|
|
"{:padding$}{} {}",
|
|
|
|
"",
|
|
|
|
config.color.stdout().doc().paint("#"),
|
|
|
|
config.color.stdout().doc().paint(&doc),
|
|
|
|
padding = max_signature_width.saturating_sub(signature_widths[name]) + 1,
|
|
|
|
);
|
|
|
|
}
|
2024-05-25 00:32:25 -07:00
|
|
|
}
|
|
|
|
println!();
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
2024-05-25 00:32:25 -07:00
|
|
|
}
|
|
|
|
}
|
2024-05-19 23:47:57 -07:00
|
|
|
|
2024-05-30 10:28:54 -07:00
|
|
|
if config.list_submodules {
|
|
|
|
for (i, submodule) in module.modules(config).into_iter().enumerate() {
|
|
|
|
if i + groups.len() > 0 {
|
|
|
|
println!();
|
|
|
|
}
|
2024-05-29 19:15:10 -07:00
|
|
|
|
2024-05-30 10:28:54 -07:00
|
|
|
println!("{list_prefix}{}:", submodule.name());
|
|
|
|
|
|
|
|
Self::list_module(config, submodule, depth + 1);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
for submodule in module.modules(config) {
|
|
|
|
println!("{list_prefix}{} ...", submodule.name(),);
|
|
|
|
}
|
|
|
|
}
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
|
|
|
|
2024-05-29 18:41:37 -07:00
|
|
|
fn show<'src>(
|
|
|
|
config: &Config,
|
|
|
|
mut module: &Justfile<'src>,
|
|
|
|
path: &ModulePath,
|
2024-06-14 16:11:22 -07:00
|
|
|
) -> RunResult<'src> {
|
2024-05-29 18:41:37 -07:00
|
|
|
for name in &path.path[0..path.path.len() - 1] {
|
|
|
|
module = module
|
|
|
|
.modules
|
|
|
|
.get(name)
|
2024-06-13 19:41:45 -07:00
|
|
|
.ok_or_else(|| Error::UnknownSubmodule {
|
|
|
|
path: path.to_string(),
|
|
|
|
})?;
|
2024-05-29 18:41:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
let name = path.path.last().unwrap();
|
|
|
|
|
|
|
|
if let Some(alias) = module.get_alias(name) {
|
|
|
|
let recipe = module.get_recipe(alias.target.name.lexeme()).unwrap();
|
2022-12-15 16:53:21 -08:00
|
|
|
println!("{alias}");
|
2021-07-28 18:06:57 -07:00
|
|
|
println!("{}", recipe.color_display(config.color.stdout()));
|
2021-07-26 17:19:52 -07:00
|
|
|
Ok(())
|
2024-05-29 18:41:37 -07:00
|
|
|
} else if let Some(recipe) = module.get_recipe(name) {
|
2021-07-28 18:06:57 -07:00
|
|
|
println!("{}", recipe.color_display(config.color.stdout()));
|
2021-07-26 17:19:52 -07:00
|
|
|
Ok(())
|
|
|
|
} else {
|
2024-06-13 19:41:45 -07:00
|
|
|
Err(Error::UnknownRecipe {
|
|
|
|
recipe: name.to_owned(),
|
2024-05-29 18:41:37 -07:00
|
|
|
suggestion: module.suggest_recipe(name),
|
2021-07-26 17:19:52 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-21 11:28:59 -08:00
|
|
|
fn summary(config: &Config, justfile: &Justfile) {
|
2023-12-28 19:06:48 -08:00
|
|
|
let mut printed = 0;
|
|
|
|
Self::summary_recursive(config, &mut Vec::new(), &mut printed, justfile);
|
|
|
|
println!();
|
|
|
|
|
|
|
|
if printed == 0 && config.verbosity.loud() {
|
|
|
|
eprintln!("Justfile contains no recipes.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn summary_recursive<'a>(
|
|
|
|
config: &Config,
|
|
|
|
components: &mut Vec<&'a str>,
|
|
|
|
printed: &mut usize,
|
|
|
|
justfile: &'a Justfile,
|
|
|
|
) {
|
|
|
|
let path = components.join("::");
|
|
|
|
|
2024-05-25 01:01:37 -07:00
|
|
|
for recipe in justfile.public_recipes(config) {
|
2023-12-28 19:06:48 -08:00
|
|
|
if *printed > 0 {
|
|
|
|
print!(" ");
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
2023-12-28 19:06:48 -08:00
|
|
|
if path.is_empty() {
|
|
|
|
print!("{}", recipe.name());
|
|
|
|
} else {
|
|
|
|
print!("{}::{}", path, recipe.name());
|
|
|
|
}
|
|
|
|
*printed += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (name, module) in &justfile.modules {
|
|
|
|
components.push(name);
|
|
|
|
Self::summary_recursive(config, components, printed, module);
|
|
|
|
components.pop();
|
2021-07-26 17:19:52 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-21 11:28:59 -08:00
|
|
|
fn variables(justfile: &Justfile) {
|
2021-07-26 17:19:52 -07:00
|
|
|
for (i, (_, assignment)) in justfile.assignments.iter().enumerate() {
|
|
|
|
if i > 0 {
|
|
|
|
print!(" ");
|
|
|
|
}
|
|
|
|
print!("{}", assignment.name);
|
|
|
|
}
|
|
|
|
println!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn init_justfile() {
|
|
|
|
testing::compile(INIT_JUSTFILE);
|
|
|
|
}
|
2020-03-16 17:20:14 -07:00
|
|
|
}
|