From 3442c2be1c4554a01adf8f404c3367bc2d107e5f Mon Sep 17 00:00:00 2001 From: Marc Addeo Date: Thu, 12 Dec 2024 18:36:24 -0500 Subject: [PATCH] Configure alias style in `--list` with `--alias-style` (#2342) --- src/alias_style.rs | 8 +++ src/analyzer.rs | 2 +- src/color.rs | 126 ++++++++++++++++++++++--------------------- src/config.rs | 16 ++++++ src/lib.rs | 2 + src/parser.rs | 2 +- src/subcommand.rs | 87 +++++++++++++++++++++--------- tests/alias_style.rs | 123 ++++++++++++++++++++++++++++++++++++++++++ tests/lib.rs | 1 + tests/misc.rs | 40 +++++++++----- 10 files changed, 306 insertions(+), 101 deletions(-) create mode 100644 src/alias_style.rs create mode 100644 tests/alias_style.rs diff --git a/src/alias_style.rs b/src/alias_style.rs new file mode 100644 index 0000000000..2f380d7c8f --- /dev/null +++ b/src/alias_style.rs @@ -0,0 +1,8 @@ +use super::*; + +#[derive(Debug, PartialEq, Clone, ValueEnum)] +pub(crate) enum AliasStyle { + Left, + Right, + Separate, +} diff --git a/src/analyzer.rs b/src/analyzer.rs index 1087bdeec9..e160d8bcb4 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -201,7 +201,7 @@ impl<'run, 'src> Analyzer<'run, 'src> { Rc::clone(next) }), }), - doc, + doc: doc.filter(|doc| !doc.is_empty()), groups: groups.into(), loaded: loaded.into(), modules: self.modules, diff --git a/src/color.rs b/src/color.rs index 7742597be4..723dcf615d 100644 --- a/src/color.rs +++ b/src/color.rs @@ -12,27 +12,16 @@ pub(crate) struct Color { } impl Color { - fn restyle(self, style: Style) -> Self { - Self { style, ..self } - } - - fn redirect(self, stream: impl IsTerminal) -> Self { - Self { - is_terminal: stream.is_terminal(), - ..self - } - } - - fn effective_style(&self) -> Style { - if self.active() { - self.style - } else { - Style::new() + pub(crate) fn active(&self) -> bool { + match self.use_color { + UseColor::Always => true, + UseColor::Never => false, + UseColor::Auto => self.is_terminal, } } - pub(crate) fn auto() -> Self { - Self::default() + pub(crate) fn alias(self) -> Self { + self.restyle(Style::new().fg(Purple)) } pub(crate) fn always() -> Self { @@ -42,25 +31,38 @@ impl Color { } } - pub(crate) fn never() -> Self { - Self { - use_color: UseColor::Never, - ..Self::default() - } + pub(crate) fn annotation(self) -> Self { + self.restyle(Style::new().fg(Purple)) } - pub(crate) fn stderr(self) -> Self { - self.redirect(io::stderr()) + pub(crate) fn auto() -> Self { + Self::default() } - pub(crate) fn stdout(self) -> Self { - self.redirect(io::stdout()) + pub(crate) fn banner(self) -> Self { + self.restyle(Style::new().fg(Cyan).bold()) + } + + pub(crate) fn command(self, foreground: Option) -> Self { + self.restyle(Style { + foreground, + is_bold: true, + ..Style::default() + }) } pub(crate) fn context(self) -> Self { self.restyle(Style::new().fg(Blue).bold()) } + pub(crate) fn diff_added(self) -> Self { + self.restyle(Style::new().fg(Green)) + } + + pub(crate) fn diff_deleted(self) -> Self { + self.restyle(Style::new().fg(Red)) + } + pub(crate) fn doc(self) -> Self { self.restyle(Style::new().fg(Blue)) } @@ -69,6 +71,14 @@ impl Color { self.restyle(Style::new().fg(Cyan)) } + fn effective_style(&self) -> Style { + if self.active() { + self.style + } else { + Style::new() + } + } + pub(crate) fn error(self) -> Self { self.restyle(Style::new().fg(Red).bold()) } @@ -77,65 +87,59 @@ impl Color { self.restyle(Style::new().fg(Yellow).bold()) } - pub(crate) fn warning(self) -> Self { - self.restyle(Style::new().fg(Yellow).bold()) + pub(crate) fn message(self) -> Self { + self.restyle(Style::new().bold()) } - pub(crate) fn banner(self) -> Self { - self.restyle(Style::new().fg(Cyan).bold()) + pub(crate) fn never() -> Self { + Self { + use_color: UseColor::Never, + ..Self::default() + } } - pub(crate) fn command(self, foreground: Option) -> Self { - self.restyle(Style { - foreground, - is_bold: true, - ..Style::default() - }) + pub(crate) fn paint<'a>(&self, text: &'a str) -> ANSIGenericString<'a, str> { + self.effective_style().paint(text) } pub(crate) fn parameter(self) -> Self { self.restyle(Style::new().fg(Cyan)) } - pub(crate) fn message(self) -> Self { - self.restyle(Style::new().bold()) - } - - pub(crate) fn annotation(self) -> Self { - self.restyle(Style::new().fg(Purple)) - } - - pub(crate) fn string(self) -> Self { - self.restyle(Style::new().fg(Green)) + pub(crate) fn prefix(&self) -> Prefix { + self.effective_style().prefix() } - pub(crate) fn diff_added(self) -> Self { - self.restyle(Style::new().fg(Green)) + fn redirect(self, stream: impl IsTerminal) -> Self { + Self { + is_terminal: stream.is_terminal(), + ..self + } } - pub(crate) fn diff_deleted(self) -> Self { - self.restyle(Style::new().fg(Red)) + fn restyle(self, style: Style) -> Self { + Self { style, ..self } } - pub(crate) fn active(&self) -> bool { - match self.use_color { - UseColor::Always => true, - UseColor::Never => false, - UseColor::Auto => self.is_terminal, - } + pub(crate) fn stderr(self) -> Self { + self.redirect(io::stderr()) } - pub(crate) fn paint<'a>(&self, text: &'a str) -> ANSIGenericString<'a, str> { - self.effective_style().paint(text) + pub(crate) fn stdout(self) -> Self { + self.redirect(io::stdout()) } - pub(crate) fn prefix(&self) -> Prefix { - self.effective_style().prefix() + pub(crate) fn string(self) -> Self { + self.restyle(Style::new().fg(Green)) } pub(crate) fn suffix(&self) -> Suffix { self.effective_style().suffix() } + + pub(crate) fn warning(self) -> Self { + self.restyle(Style::new().fg(Yellow).bold()) + } } impl From for Color { diff --git a/src/config.rs b/src/config.rs index df7e91ef56..575e73cd1e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ use { #[derive(Debug, PartialEq)] pub(crate) struct Config { + pub(crate) alias_style: AliasStyle, pub(crate) allow_missing: bool, pub(crate) check: bool, pub(crate) color: Color, @@ -86,6 +87,7 @@ mod cmd { } mod arg { + pub(crate) const ALIAS_STYLE: &str = "ALIAS_STYLE"; pub(crate) const ALLOW_MISSING: &str = "ALLOW-MISSING"; pub(crate) const ARGUMENTS: &str = "ARGUMENTS"; pub(crate) const CHECK: &str = "CHECK"; @@ -145,6 +147,16 @@ impl Config { .usage(AnsiColor::Yellow.on_default() | Effects::BOLD) .valid(AnsiColor::Green.on_default()), ) + .arg( + Arg::new(arg::ALIAS_STYLE) + .long("alias-style") + .env("JUST_ALIAS_STYLE") + .action(ArgAction::Set) + .value_parser(clap::value_parser!(AliasStyle)) + .default_value("right") + .help("Set list command alias display style") + .conflicts_with(arg::NO_ALIASES), + ) .arg( Arg::new(arg::CHECK) .long("check") @@ -739,6 +751,10 @@ impl Config { let explain = matches.get_flag(arg::EXPLAIN); Ok(Self { + alias_style: matches + .get_one::(arg::ALIAS_STYLE) + .unwrap() + .clone(), allow_missing: matches.get_flag(arg::ALLOW_MISSING), check: matches.get_flag(arg::CHECK), color: (*matches.get_one::(arg::COLOR).unwrap()).into(), diff --git a/src/lib.rs b/src/lib.rs index 7ce669ea1a..30777e513d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub(crate) use { crate::{ alias::Alias, + alias_style::AliasStyle, analyzer::Analyzer, argument_parser::ArgumentParser, assignment::Assignment, @@ -179,6 +180,7 @@ pub mod summary; pub mod request; mod alias; +mod alias_style; mod analyzer; mod argument_parser; mod assignment; diff --git a/src/parser.rs b/src/parser.rs index 5d7821a335..2258c13b3b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -952,7 +952,7 @@ impl<'run, 'src> Parser<'run, 'src> { attributes, body, dependencies, - doc, + doc: doc.filter(|doc| !doc.is_empty()), file_depth: self.file_depth, import_offsets: self.import_offsets.clone(), name, diff --git a/src/subcommand.rs b/src/subcommand.rs index 8d34578845..fbb5cb0ba8 100644 --- a/src/subcommand.rs +++ b/src/subcommand.rs @@ -432,40 +432,64 @@ impl Subcommand { } fn list_module(config: &Config, module: &Justfile, depth: usize) { - fn format_doc( + fn print_doc_and_aliases( config: &Config, name: &str, doc: Option<&str>, + aliases: &[&str], max_signature_width: usize, signature_widths: &BTreeMap<&str, usize>, ) { - if let Some(doc) = doc { - if !doc.is_empty() && doc.lines().count() <= 1 { - let color = config.color.stdout(); - print!( - "{:padding$}{} ", - "", - color.doc().paint("#"), - padding = max_signature_width.saturating_sub(signature_widths[name]) + 1, - ); + let color = config.color.stdout(); - let mut end = 0; - for backtick in backtick_re().find_iter(doc) { - let prefix = &doc[end..backtick.start()]; - if !prefix.is_empty() { - print!("{}", color.doc().paint(prefix)); - } - print!("{}", color.doc_backtick().paint(backtick.as_str())); - end = backtick.end(); - } + let inline_aliases = config.alias_style != AliasStyle::Separate && !aliases.is_empty(); + + if inline_aliases || doc.is_some() { + print!( + "{:padding$}{}", + "", + color.doc().paint("#"), + padding = max_signature_width.saturating_sub(signature_widths[name]) + 1, + ); + } - let suffix = &doc[end..]; - if !suffix.is_empty() { - print!("{}", color.doc().paint(suffix)); + let print_aliases = || { + print!( + " {}", + color.alias().paint(&format!( + "[alias{}: {}]", + if aliases.len() == 1 { "" } else { "es" }, + aliases.join(", ") + )) + ); + }; + + if inline_aliases && config.alias_style == AliasStyle::Left { + print_aliases(); + } + + if let Some(doc) = doc { + print!(" "); + let mut end = 0; + for backtick in backtick_re().find_iter(doc) { + let prefix = &doc[end..backtick.start()]; + if !prefix.is_empty() { + print!("{}", color.doc().paint(prefix)); } + print!("{}", color.doc_backtick().paint(backtick.as_str())); + end = backtick.end(); + } + + let suffix = &doc[end..]; + if !suffix.is_empty() { + print!("{}", color.doc().paint(suffix)); } } + if inline_aliases && config.alias_style == AliasStyle::Right { + print_aliases(); + } + println!(); } @@ -589,8 +613,14 @@ impl Subcommand { if let Some(recipes) = recipe_groups.get(&group) { for recipe in recipes { + let recipe_alias_entries = if config.alias_style == AliasStyle::Separate { + aliases.get(recipe.name()) + } else { + None + }; + for (i, name) in iter::once(&recipe.name()) - .chain(aliases.get(recipe.name()).unwrap_or(&Vec::new())) + .chain(recipe_alias_entries.unwrap_or(&Vec::new())) .enumerate() { let doc = if i == 0 { @@ -616,10 +646,14 @@ impl Subcommand { RecipeSignature { name, recipe }.color_display(config.color.stdout()) ); - format_doc( + print_doc_and_aliases( config, name, - doc.as_deref(), + doc.filter(|doc| doc.lines().count() <= 1).as_deref(), + aliases + .get(recipe.name()) + .map(Vec::as_slice) + .unwrap_or_default(), max_signature_width, &signature_widths, ); @@ -638,10 +672,11 @@ impl Subcommand { Self::list_module(config, submodule, depth + 1); } else { print!("{list_prefix}{} ...", submodule.name()); - format_doc( + print_doc_and_aliases( config, submodule.name(), submodule.doc.as_deref(), + &[], max_signature_width, &signature_widths, ); diff --git a/tests/alias_style.rs b/tests/alias_style.rs new file mode 100644 index 0000000000..dddea0d8ce --- /dev/null +++ b/tests/alias_style.rs @@ -0,0 +1,123 @@ +use super::*; + +#[test] +fn default() { + Test::new() + .justfile( + " + alias f := foo + + # comment + foo: + + bar: + ", + ) + .args(["--list"]) + .stdout( + " + Available recipes: + bar + foo # comment [alias: f] + ", + ) + .run(); +} + +#[test] +fn multiple() { + Test::new() + .justfile( + " + alias a := foo + alias b := foo + + # comment + foo: + + bar: + ", + ) + .args(["--list"]) + .stdout( + " + Available recipes: + bar + foo # comment [aliases: a, b] + ", + ) + .run(); +} + +#[test] +fn right() { + Test::new() + .justfile( + " + alias f := foo + + # comment + foo: + + bar: + ", + ) + .args(["--alias-style=right", "--list"]) + .stdout( + " + Available recipes: + bar + foo # comment [alias: f] + ", + ) + .run(); +} + +#[test] +fn left() { + Test::new() + .justfile( + " + alias f := foo + + # comment + foo: + + bar: + ", + ) + .args(["--alias-style=left", "--list"]) + .stdout( + " + Available recipes: + bar + foo # [alias: f] comment + ", + ) + .run(); +} + +#[test] +fn separate() { + Test::new() + .justfile( + " + alias f := foo + + # comment + foo: + + bar: + ", + ) + .args(["--alias-style=separate", "--list"]) + .stdout( + " + Available recipes: + bar + foo # comment + f # alias for `foo` + ", + ) + .run(); +} diff --git a/tests/lib.rs b/tests/lib.rs index 3ee9c3801f..a86a3da769 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -36,6 +36,7 @@ fn default() -> T { #[macro_use] mod test; +mod alias_style; mod allow_duplicate_recipes; mod allow_duplicate_variables; mod allow_missing; diff --git a/tests/misc.rs b/tests/misc.rs index ab16d5e208..79b609f314 100644 --- a/tests/misc.rs +++ b/tests/misc.rs @@ -11,9 +11,30 @@ test! { args: ("--list"), stdout: " Available recipes: - foo - f # alias for `foo` - ", + foo # [alias: f] + ", +} + +#[test] +fn alias_listing_with_doc() { + Test::new() + .justfile( + " + # foo command + foo: + echo foo + + alias f := foo + ", + ) + .arg("--list") + .stdout( + " + Available recipes: + foo # foo command [alias: f] + ", + ) + .run(); } test! { @@ -22,9 +43,7 @@ test! { args: ("--list"), stdout: " Available recipes: - foo - f # alias for `foo` - fo # alias for `foo` + foo # [aliases: f, fo] ", } @@ -34,8 +53,7 @@ test! { args: ("--list"), stdout: " Available recipes: - foo PARAM='foo' - f PARAM='foo' # alias for `foo` + foo PARAM='foo' # [alias: f] ", } @@ -927,8 +945,7 @@ a: stdout: r" Available recipes: a - b - c # alias for `b` + b # [alias: c] ", } @@ -942,8 +959,7 @@ a: args: ("--list", "--unsorted"), stdout: r" Available recipes: - b - c # alias for `b` + b # [alias: c] a ", }