diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-22 22:53:09 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-22 22:53:22 +0900 |
| commit | 6f3802fd9f39c4e5847d130b4417b5cdfb66972d (patch) | |
| tree | 166cca2cf0645d280bfa376a513a049c70241dea | |
| parent | 1d33728151b282949e7e14646e722d7775de4453 (diff) | |
| download | php-mozart-6f3802fd9f39c4e5847d130b4417b5cdfb66972d.tar.gz php-mozart-6f3802fd9f39c4e5847d130b4417b5cdfb66972d.tar.zst php-mozart-6f3802fd9f39c4e5847d130b4417b5cdfb66972d.zip | |
refactor(console): add console_format! proc macro and migrate all commands
Introduce a Symfony Console-style tag macro that replaces verbose
patterns like `console::info(&format!("text {name}"))` with
`console_format!("<info>text {name}</info>")`. Supports all 6 tag
types (info, comment, error, question, highlight, warning) with
format argument distribution across multiple tagged segments.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
25 files changed, 1054 insertions, 474 deletions
@@ -1130,12 +1130,23 @@ dependencies = [ ] [[package]] +name = "mozart-console-macros" +version = "0.1.0" +dependencies = [ + "mozart-core", + "proc-macro2", + "quote", + "syn", +] + +[[package]] name = "mozart-core" version = "0.1.0" dependencies = [ "anyhow", "colored", "dialoguer", + "mozart-console-macros", "mozart-spdx-licenses", "regex", "serde", @@ -27,12 +27,15 @@ filetime = "0.2.27" flate2 = "1.1.9" md5 = "0.7.0" predicates = "3.1.4" +proc-macro2 = "1.0.106" +quote = "1.0.44" regex = "1.12.3" reqwest = { version = "0.13.2", features = ["json"] } self-replace = "1.5.0" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" sha1 = "0.10.6" +syn = { version = "2.0.117", features = ["full", "parsing"] } tar = "0.4.44" tempfile = "3.25.0" tokio = { version = "1.49.0", features = ["full"] } diff --git a/crates/mozart-console-macros/Cargo.toml b/crates/mozart-console-macros/Cargo.toml new file mode 100644 index 0000000..19a2fe5 --- /dev/null +++ b/crates/mozart-console-macros/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "mozart-console-macros" +version.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true + +[dev-dependencies] +mozart-core.workspace = true diff --git a/crates/mozart-console-macros/src/codegen.rs b/crates/mozart-console-macros/src/codegen.rs new file mode 100644 index 0000000..11e37f9 --- /dev/null +++ b/crates/mozart-console-macros/src/codegen.rs @@ -0,0 +1,155 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::Expr; +use syn::punctuated::Punctuated; + +use crate::parser::Segment; + +/// Returns true if the string contains any format placeholders (`{}`, `{name}`, `{0}`, `{:<10}`, etc.) +/// but not escaped braces `{{` or `}}`. +fn has_placeholders(s: &str) -> bool { + let mut chars = s.chars().peekable(); + while let Some(ch) = chars.next() { + if ch == '{' { + match chars.peek() { + Some('{') => { + chars.next(); // skip escaped + } + _ => return true, + } + } else if ch == '}' && chars.peek() == Some(&'}') { + chars.next(); // skip escaped + } + } + false +} + +/// Count implicit positional placeholders (`{}` and `{:spec}`) in a format string. +/// Named (`{name}`) and numbered (`{0}`) placeholders are NOT counted +/// since they don't consume positional arguments. +fn count_positional_placeholders(s: &str) -> usize { + let mut count = 0; + let mut chars = s.chars().peekable(); + while let Some(ch) = chars.next() { + if ch == '{' { + match chars.peek() { + Some('{') => { + chars.next(); // escaped + } + Some('}') => { + // `{}` — implicit positional + count += 1; + chars.next(); + } + Some(':') => { + // `{:spec}` — implicit positional with format spec + count += 1; + for c in chars.by_ref() { + if c == '}' { + break; + } + } + } + Some(c) if c.is_ascii_digit() => { + // `{0}`, `{0:spec}` — explicit positional, skip + for c in chars.by_ref() { + if c == '}' { + break; + } + } + } + _ => { + // `{name}` or `{name:spec}` — named, skip + for c in chars.by_ref() { + if c == '}' { + break; + } + } + } + } + } else if ch == '}' && chars.peek() == Some(&'}') { + chars.next(); + } + } + count +} + +pub fn generate( + segments: &[Segment], + extra_args: &Punctuated<Expr, syn::Token![,]>, +) -> TokenStream { + // Single segment: pass all extra args + if segments.len() == 1 { + return generate_single(&segments[0], extra_args); + } + + // Multiple segments: distribute positional args across segments + let mut pos = 0usize; + let mut seg_bindings = Vec::new(); + let mut seg_idents = Vec::new(); + + for (i, segment) in segments.iter().enumerate() { + let content = segment_content(segment); + let n = count_positional_placeholders(content); + let end = (pos + n).min(extra_args.len()); + let slice: Punctuated<Expr, syn::Token![,]> = extra_args + .iter() + .skip(pos) + .take(end - pos) + .cloned() + .collect(); + pos = end; + + let ident = quote::format_ident!("__seg{}", i); + let expr = generate_single(segment, &slice); + seg_bindings.push(quote! { let #ident = #expr; }); + seg_idents.push(ident); + } + + // Build a format string with one `{}` per segment + let fmt_str = seg_idents.iter().map(|_| "{}").collect::<Vec<_>>().join(""); + + quote! { + { + #(#seg_bindings)* + ::std::format!(#fmt_str, #(#seg_idents),*) + } + } +} + +fn segment_content(segment: &Segment) -> &str { + match segment { + Segment::Plain(s) => s, + Segment::Tagged { content, .. } => content, + } +} + +fn generate_single(segment: &Segment, args: &Punctuated<Expr, syn::Token![,]>) -> TokenStream { + match segment { + Segment::Plain(text) => { + if has_placeholders(text) { + let lit = proc_macro2::Literal::string(text); + quote! { ::std::format!(#lit, #args) } + } else { + quote! { ::std::string::String::from(#text) } + } + } + Segment::Tagged { tag, content } => { + let func = quote::format_ident!("{}", tag); + if has_placeholders(content) { + let lit = proc_macro2::Literal::string(content); + quote! { + ::std::string::ToString::to_string( + &::mozart_core::console::#func(&::std::format!(#lit, #args)) + ) + } + } else { + quote! { + ::std::string::ToString::to_string( + &::mozart_core::console::#func(#content) + ) + } + } + } + } +} diff --git a/crates/mozart-console-macros/src/lib.rs b/crates/mozart-console-macros/src/lib.rs new file mode 100644 index 0000000..3af6f82 --- /dev/null +++ b/crates/mozart-console-macros/src/lib.rs @@ -0,0 +1,70 @@ +mod codegen; +mod parser; + +use proc_macro::TokenStream; +use syn::Expr; +use syn::punctuated::Punctuated; + +/// Format a string with Symfony Console-style tags. +/// +/// Supported tags: `<info>`, `<comment>`, `<error>`, `<question>`, `<highlight>`, `<warning>`. +/// +/// # Examples +/// +/// ```ignore +/// // Single tagged segment +/// console_format!("<info>All packages are up to date.</info>") +/// +/// // With format arguments +/// console_format!("<info>Removing {name} from require-dev</info>") +/// +/// // Mixed tags +/// console_format!("<info>{}</info> : <comment>{}</comment>", label, value) +/// +/// // Plain text (equivalent to format!) +/// console_format!("plain text {}", x) +/// ``` +#[proc_macro] +pub fn console_format(input: TokenStream) -> TokenStream { + let input2: proc_macro2::TokenStream = input.into(); + match console_format_impl(input2) { + Ok(tokens) => tokens.into(), + Err(err) => err.into_compile_error().into(), + } +} + +fn console_format_impl( + input: proc_macro2::TokenStream, +) -> Result<proc_macro2::TokenStream, syn::Error> { + let args: ConsoleFormatArgs = syn::parse2(input)?; + let segments = parser::parse_format_string(&args.format_str) + .map_err(|msg| syn::Error::new(args.format_str_span, msg))?; + Ok(codegen::generate(&segments, &args.extra_args)) +} + +struct ConsoleFormatArgs { + format_str: String, + format_str_span: proc_macro2::Span, + extra_args: Punctuated<Expr, syn::Token![,]>, +} + +impl syn::parse::Parse for ConsoleFormatArgs { + fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { + let lit: syn::LitStr = input.parse()?; + let format_str = lit.value(); + let format_str_span = lit.span(); + + let extra_args = if input.peek(syn::Token![,]) { + input.parse::<syn::Token![,]>()?; + Punctuated::parse_terminated(input)? + } else { + Punctuated::new() + }; + + Ok(ConsoleFormatArgs { + format_str, + format_str_span, + extra_args, + }) + } +} diff --git a/crates/mozart-console-macros/src/parser.rs b/crates/mozart-console-macros/src/parser.rs new file mode 100644 index 0000000..a854967 --- /dev/null +++ b/crates/mozart-console-macros/src/parser.rs @@ -0,0 +1,249 @@ +const KNOWN_TAGS: &[&str] = &[ + "info", + "comment", + "error", + "question", + "highlight", + "warning", +]; + +#[derive(Debug, Clone, PartialEq)] +pub enum Segment { + Plain(String), + Tagged { tag: String, content: String }, +} + +pub fn parse_format_string(input: &str) -> Result<Vec<Segment>, String> { + let mut segments: Vec<Segment> = Vec::new(); + let mut chars = input.char_indices().peekable(); + let mut plain_buf = String::new(); + + while let Some(&(i, ch)) = chars.peek() { + if ch == '<' { + // Try to match an opening tag + if let Some((tag, after_tag)) = try_parse_open_tag(input, i) { + // Flush plain buffer + if !plain_buf.is_empty() { + segments.push(Segment::Plain(std::mem::take(&mut plain_buf))); + } + + // Advance past the opening tag + while chars.peek().is_some_and(|&(j, _)| j < after_tag) { + chars.next(); + } + + // Collect content until closing tag + let closing = format!("</{tag}>"); + let content_start = after_tag; + let Some(close_pos) = input[content_start..].find(&closing) else { + return Err(format!("unclosed <{tag}> tag")); + }; + let content_end = content_start + close_pos; + let content = &input[content_start..content_end]; + + // Check for nested tags + if contains_known_tag(content) { + return Err(format!("nested tags are not supported inside <{tag}>")); + } + + segments.push(Segment::Tagged { + tag: tag.to_string(), + content: content.to_string(), + }); + + // Advance past the closing tag + let after_close = content_end + closing.len(); + while chars.peek().is_some_and(|&(j, _)| j < after_close) { + chars.next(); + } + } else { + // Not a known tag, treat as literal + plain_buf.push(ch); + chars.next(); + } + } else { + plain_buf.push(ch); + chars.next(); + } + } + + if !plain_buf.is_empty() { + segments.push(Segment::Plain(plain_buf)); + } + + Ok(segments) +} + +/// Try to parse an opening tag like `<info>` at position `pos`. +/// Returns `(tag_name, byte_index_after_closing_angle)` on success. +fn try_parse_open_tag(input: &str, pos: usize) -> Option<(&str, usize)> { + let rest = &input[pos + 1..]; // skip '<' + // Must not start with '/' + if rest.starts_with('/') { + return None; + } + let end = rest.find('>')?; + let tag_name = &rest[..end]; + if KNOWN_TAGS.contains(&tag_name) { + Some((tag_name, pos + 1 + end + 1)) + } else { + None + } +} + +/// Check if a string contains any known opening tag (for nesting detection). +fn contains_known_tag(s: &str) -> bool { + for tag in KNOWN_TAGS { + if s.contains(&format!("<{tag}>")) { + return true; + } + } + false +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn plain_text_only() { + let result = parse_format_string("hello world").unwrap(); + assert_eq!(result, vec![Segment::Plain("hello world".into())]); + } + + #[test] + fn single_tag() { + let result = parse_format_string("<info>hello</info>").unwrap(); + assert_eq!( + result, + vec![Segment::Tagged { + tag: "info".into(), + content: "hello".into() + }] + ); + } + + #[test] + fn tag_with_placeholder() { + let result = parse_format_string("<info>Removing {name}</info>").unwrap(); + assert_eq!( + result, + vec![Segment::Tagged { + tag: "info".into(), + content: "Removing {name}".into() + }] + ); + } + + #[test] + fn multiple_tags() { + let result = parse_format_string("<info>{}</info> : <comment>{}</comment>").unwrap(); + assert_eq!( + result, + vec![ + Segment::Tagged { + tag: "info".into(), + content: "{}".into() + }, + Segment::Plain(" : ".into()), + Segment::Tagged { + tag: "comment".into(), + content: "{}".into() + }, + ] + ); + } + + #[test] + fn all_tag_types() { + for tag in KNOWN_TAGS { + let input = format!("<{tag}>text</{tag}>"); + let result = parse_format_string(&input).unwrap(); + assert_eq!( + result, + vec![Segment::Tagged { + tag: tag.to_string(), + content: "text".into() + }] + ); + } + } + + #[test] + fn unknown_tag_treated_as_literal() { + let result = parse_format_string("<bold>text</bold>").unwrap(); + assert_eq!(result, vec![Segment::Plain("<bold>text</bold>".into())]); + } + + #[test] + fn unclosed_tag_error() { + let result = parse_format_string("<info>text"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("unclosed")); + } + + #[test] + fn nested_tag_error() { + let result = parse_format_string("<info><comment>text</comment></info>"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("nested")); + } + + #[test] + fn escaped_braces() { + let result = parse_format_string("<info>{{literal}}</info>").unwrap(); + assert_eq!( + result, + vec![Segment::Tagged { + tag: "info".into(), + content: "{{literal}}".into() + }] + ); + } + + #[test] + fn adjacent_tags() { + let result = parse_format_string("<info>a</info><comment>b</comment>").unwrap(); + assert_eq!( + result, + vec![ + Segment::Tagged { + tag: "info".into(), + content: "a".into() + }, + Segment::Tagged { + tag: "comment".into(), + content: "b".into() + }, + ] + ); + } + + #[test] + fn plain_before_and_after_tag() { + let result = parse_format_string("before <info>middle</info> after").unwrap(); + assert_eq!( + result, + vec![ + Segment::Plain("before ".into()), + Segment::Tagged { + tag: "info".into(), + content: "middle".into() + }, + Segment::Plain(" after".into()), + ] + ); + } + + #[test] + fn empty_content_tag() { + let result = parse_format_string("<info></info>").unwrap(); + assert_eq!( + result, + vec![Segment::Tagged { + tag: "info".into(), + content: String::new() + }] + ); + } +} diff --git a/crates/mozart-console-macros/tests/integration.rs b/crates/mozart-console-macros/tests/integration.rs new file mode 100644 index 0000000..36a4e03 --- /dev/null +++ b/crates/mozart-console-macros/tests/integration.rs @@ -0,0 +1,94 @@ +use mozart_core::console_format; + +#[test] +fn plain_text_no_tags() { + let result = console_format!("hello world"); + assert_eq!(result, "hello world"); +} + +#[test] +fn plain_text_with_format_args() { + let x = 42; + let result = console_format!("value is {}", x); + assert_eq!(result, "value is 42"); +} + +#[test] +fn single_info_tag() { + // The output should contain the text (colored), verify it contains the raw text + let result = console_format!("<info>done</info>"); + assert!(result.contains("done"), "expected 'done' in: {result}"); +} + +#[test] +fn single_tag_with_format_arg() { + let name = "foo"; + let result = console_format!("<info>Removing {name}</info>"); + assert!( + result.contains("Removing foo"), + "expected 'Removing foo' in: {result}" + ); +} + +#[test] +fn multiple_tags() { + let label = "pkg"; + let version = "1.0"; + let result = console_format!("<info>{}</info> : <comment>{}</comment>", label, version); + assert!(result.contains("pkg"), "expected 'pkg' in: {result}"); + assert!(result.contains("1.0"), "expected '1.0' in: {result}"); + assert!(result.contains(" : "), "expected ' : ' in: {result}"); +} + +#[test] +fn comment_tag() { + let result = console_format!("<comment>note</comment>"); + assert!(result.contains("note")); +} + +#[test] +fn error_tag() { + let result = console_format!("<error>fail</error>"); + assert!(result.contains("fail")); +} + +#[test] +fn question_tag() { + let result = console_format!("<question>ask</question>"); + assert!(result.contains("ask")); +} + +#[test] +fn highlight_tag() { + let result = console_format!("<highlight>important</highlight>"); + assert!(result.contains("important")); +} + +#[test] +fn warning_tag() { + let result = console_format!("<warning>caution</warning>"); + assert!(result.contains("caution")); +} + +#[test] +fn escaped_braces() { + let result = console_format!("<info>{{literal}}</info>"); + assert!( + result.contains("{literal}"), + "expected '{{literal}}' in: {result}" + ); +} + +#[test] +fn tag_with_plain_before_after() { + let result = console_format!("before <info>middle</info> after"); + assert!(result.contains("before ")); + assert!(result.contains("middle")); + assert!(result.contains(" after")); +} + +#[test] +fn unknown_tag_is_literal() { + let result = console_format!("<bold>text</bold>"); + assert_eq!(result, "<bold>text</bold>"); +} diff --git a/crates/mozart-core/Cargo.toml b/crates/mozart-core/Cargo.toml index 599dbb3..391c310 100644 --- a/crates/mozart-core/Cargo.toml +++ b/crates/mozart-core/Cargo.toml @@ -11,6 +11,7 @@ dialoguer.workspace = true regex.workspace = true serde.workspace = true serde_json.workspace = true +mozart-console-macros = { version = "0.1.0", path = "../mozart-console-macros" } [dev-dependencies] tempfile.workspace = true diff --git a/crates/mozart-core/src/lib.rs b/crates/mozart-core/src/lib.rs index aef7af3..510257b 100644 --- a/crates/mozart-core/src/lib.rs +++ b/crates/mozart-core/src/lib.rs @@ -6,3 +6,5 @@ pub mod platform; pub mod suggest; pub mod validation; pub mod version_bumper; + +pub use mozart_console_macros::console_format; diff --git a/crates/mozart/src/commands/about.rs b/crates/mozart/src/commands/about.rs index 83332b7..9c58db3 100644 --- a/crates/mozart/src/commands/about.rs +++ b/crates/mozart/src/commands/about.rs @@ -1,5 +1,6 @@ use clap::Args; use mozart_core::console; +use mozart_core::console_format; #[derive(Args)] pub struct AboutArgs {} @@ -11,18 +12,12 @@ pub async fn execute( ) -> anyhow::Result<()> { let version = env!("CARGO_PKG_VERSION"); console.write_stdout( - &console::info(&format!( - "Mozart - Dependency Manager for PHP - version {version}" - )) - .to_string(), + &console_format!("<info>Mozart - Dependency Manager for PHP - version {version}</info>"), console::Verbosity::Normal, ); console.write_stdout( - &console::comment( - "Mozart is a dependency manager tracking local dependencies of your projects and libraries. -See https://getcomposer.org/ for more information.", - ) - .to_string(), + &console_format!("<comment>Mozart is a dependency manager tracking local dependencies of your projects and libraries. +See https://getcomposer.org/ for more information.</comment>"), console::Verbosity::Normal, ); Ok(()) diff --git a/crates/mozart/src/commands/browse.rs b/crates/mozart/src/commands/browse.rs index 51ca340..6586a12 100644 --- a/crates/mozart/src/commands/browse.rs +++ b/crates/mozart/src/commands/browse.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use std::path::{Path, PathBuf}; use std::process::Command; @@ -55,12 +56,9 @@ pub async fn execute( } } None => { - console.info(&format!( - "{}", - mozart_core::console::warning(&format!( - "No URL found for package \"{}\".", - package_name - )) + console.info(&console_format!( + "<warning>No URL found for package \"{}\".</warning>", + package_name )); exit_code = 1; } diff --git a/crates/mozart/src/commands/bump.rs b/crates/mozart/src/commands/bump.rs index f295cd9..b322dbe 100644 --- a/crates/mozart/src/commands/bump.rs +++ b/crates/mozart/src/commands/bump.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use std::collections::HashMap; use std::path::PathBuf; @@ -51,24 +52,10 @@ pub async fn execute( match root.package_type.as_deref() { Some("project") => {} Some(pkg_type) => { - console.info(&format!( - "{}", - mozart_core::console::warning(&format!( - "Warning: Bumping constraints for a non-project package (type=\"{pkg_type}\"). \ - Libraries should not pin their dependencies." - )) - )); + console.info(&console_format!("<warning>Warning: Bumping constraints for a non-project package (type=\"{pkg_type}\"). Libraries should not pin their dependencies.</warning>")); } None if !args.dev_only => { - console.info(&format!( - "{}", - mozart_core::console::warning( - "Warning: Bumping constraints for a non-project package. \ - No type was set so it defaults to \"library\". \ - Libraries should not pin their dependencies. \ - Consider using --dev-only or setting the type to \"project\"." - ) - )); + console.info(&console_format!("<warning>Warning: Bumping constraints for a non-project package. No type was set so it defaults to \"library\". Libraries should not pin their dependencies. Consider using --dev-only or setting the type to \"project\".</warning>")); } None => {} } @@ -161,10 +148,10 @@ pub async fn execute( if total_changes == 0 { println!( "{}", - mozart_core::console::info(&format!( - "No requirements to update in {}.", + console_format!( + "<info>No requirements to update in {}.</info>", composer_json_path.display() - )) + ) ); return Ok(()); } @@ -172,21 +159,21 @@ pub async fn execute( if args.dry_run { println!( "{}", - mozart_core::console::info(&format!( - "{} would be updated with:", + console_format!( + "<info>{} would be updated with:</info>", composer_json_path.display() - )) + ) ); for (name, _old, new) in &require_changes { println!( "{}", - mozart_core::console::info(&format!(" - require.{name}: {new}")) + console_format!("<info> - require.{name}: {new}</info>") ); } for (name, _old, new) in &require_dev_changes { println!( "{}", - mozart_core::console::info(&format!(" - require-dev.{name}: {new}")) + console_format!("<info> - require-dev.{name}: {new}</info>") ); } // Return exit code 1 when dry-run detects changes (useful for CI to detect un-bumped constraints) @@ -216,10 +203,10 @@ pub async fn execute( println!( "{}", - mozart_core::console::info(&format!( - "{} has been updated ({total_changes} changes).", + console_format!( + "<info>{} has been updated ({total_changes} changes).</info>", composer_json_path.display() - )) + ) ); Ok(()) diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index b4e3c3c..2156477 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -1,5 +1,5 @@ use clap::Args; -use mozart_core::console; +use mozart_core::console_format; use mozart_core::package::{self, Stability}; use mozart_core::validation; use mozart_registry::downloader; @@ -142,7 +142,7 @@ fn remove_vcs_metadata(target_dir: &Path) -> anyhow::Result<()> { std::fs::remove_dir_all(&path)?; eprintln!( "{}", - console::comment(&format!("Removed VCS metadata directory: {vcs_dir}")) + console_format!("<comment>Removed VCS metadata directory: {vcs_dir}</comment>") ); } } @@ -177,37 +177,27 @@ pub async fn execute( ) -> anyhow::Result<()> { // --- Handle deprecated / no-op flags --- if args.prefer_source { - console.info(&format!( - "{}", - console::warning("Source installs not yet supported, falling back to dist.") + console.info(&console_format!( + "<warning>Source installs not yet supported, falling back to dist.</warning>" )); } if args.dev { - console.info(&format!( - "{}", - console::warning( - "The --dev flag is deprecated. Dev packages are installed by default." - ) + console.info(&console_format!( + "<warning>The --dev flag is deprecated. Dev packages are installed by default.</warning>" )); } if args.no_custom_installers { - console.info(&format!( - "{}", - console::warning( - "The --no-custom-installers flag is deprecated. Use --no-plugins instead." - ) + console.info(&console_format!( + "<warning>The --no-custom-installers flag is deprecated. Use --no-plugins instead.</warning>" )); } if !args.repository.is_empty() || args.repository_url.is_some() || args.add_repository { - console.info(&format!( - "{}", - console::warning( - "Custom repository options (--repository, --repository-url, --add-repository) \ - are not yet supported and will be ignored." - ) + console.info(&console_format!( + "<warning>Custom repository options (--repository, --repository-url, --add-repository) \ + are not yet supported and will be ignored.</warning>" )); } @@ -272,9 +262,8 @@ pub async fn execute( }; // --- Step 4: Fetch package versions and find best match --- - console.info(&format!( - "{}", - console::info(&format!("Creating project from package {package_name}")) + console.info(&console_format!( + "<info>Creating project from package {package_name}</info>" )); console.info("Loading composer repositories with package information"); @@ -310,9 +299,8 @@ pub async fn execute( let concrete_version = best.version.clone(); - console.info(&format!( - "{}", - console::info(&format!("Installing {package_name} ({concrete_version})")) + console.info(&console_format!( + "<info>Installing {package_name} ({concrete_version})</info>" )); // --- Step 5: Create target directory and download+extract --- @@ -342,9 +330,9 @@ pub async fn execute( other => anyhow::bail!("Unsupported dist type: {other}"), } - console.info(&format!( - "{}", - console::info(&format!("Created project in {}", target_dir.display())) + console.info(&console_format!( + "<info>Created project in {}</info>", + target_dir.display() )); // --- Step 7: VCS removal --- @@ -359,12 +347,9 @@ pub async fn execute( let composer_path = target_dir.join("composer.json"); if !composer_path.exists() { - console.info(&format!( - "{}", - console::warning(&format!( - "No composer.json found in {}. Skipping dependency installation.", - target_dir.display() - )) + console.info(&console_format!( + "<warning>No composer.json found in {}. Skipping dependency installation.</warning>", + target_dir.display() )); return Ok(()); } @@ -377,9 +362,8 @@ pub async fn execute( // --- Step 6 continued: dependency resolution and install --- if args.no_install { - console.info(&format!( - "{}", - console::comment("Skipping dependency installation (--no-install).") + console.info(&console_format!( + "<comment>Skipping dependency installation (--no-install).</comment>" )); return Ok(()); } @@ -450,13 +434,10 @@ pub async fn execute( .filter(|c| matches!(c.kind, super::update::ChangeKind::Install { .. })) .collect(); - console.info(&format!( - "{}", - console::info(&format!( - "Package operations: {} install{}, 0 updates, 0 removals", - installs.len(), - if installs.len() == 1 { "" } else { "s" }, - )) + console.info(&console_format!( + "<info>Package operations: {} install{}, 0 updates, 0 removals</info>", + installs.len(), + if installs.len() == 1 { "" } else { "s" } )); for change in &changes { @@ -479,9 +460,8 @@ pub async fn execute( .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); if prefer_source { - console.info(&format!( - "{}", - console::warning("Source installs are not yet supported. Falling back to dist.") + console.info(&console_format!( + "<warning>Source installs are not yet supported. Falling back to dist.</warning>" )); } diff --git a/crates/mozart/src/commands/init.rs b/crates/mozart/src/commands/init.rs index 3ac7976..edb2af4 100644 --- a/crates/mozart/src/commands/init.rs +++ b/crates/mozart/src/commands/init.rs @@ -2,6 +2,7 @@ use anyhow::{Context, bail}; use clap::Args; use colored::Colorize; use mozart_core::console; +use mozart_core::console_format; use mozart_core::package::{ self, RawAuthor, RawAutoload, RawPackageData, RawRepository, Stability, }; @@ -96,9 +97,8 @@ pub async fn execute( console.info(&json); console.info(""); - if !console.confirm(&format!( - "Do you confirm generation [{}]?", - console::comment("yes") + if !console.confirm(&console_format!( + "Do you confirm generation [<comment>yes</comment>]?" )) { console.error("Command aborted"); bail!("Command aborted"); @@ -124,11 +124,8 @@ pub async fn execute( if console.interactive && working_dir.join(".git").is_dir() { let gitignore_path = working_dir.join(".gitignore"); if !has_vendor_ignore(&gitignore_path) - && console.confirm(&format!( - "Would you like the {} directory added to your {} [{}]?", - console::info("vendor"), - console::info(".gitignore"), - console::comment("yes"), + && console.confirm(&console_format!( + "Would you like the <info>vendor</info> directory added to your <info>.gitignore</info> [<comment>yes</comment>]?" )) { add_vendor_ignore(&gitignore_path)?; @@ -139,13 +136,11 @@ pub async fn execute( if let Some(ref autoload) = composer.autoload && let Some((ns, path)) = autoload.psr4.iter().next() { - console.info(&format!( - "PSR-4 autoloading configured. Use \"{}\" in {path}", - console::comment(&format!("namespace {ns};")), + console.info(&console_format!( + "PSR-4 autoloading configured. Use \"<comment>namespace {ns};</comment>\" in {path}" )); - console.info(&format!( - "Include the Composer autoloader with: {}", - console::comment("require 'vendor/autoload.php';"), + console.info(&console_format!( + "Include the Composer autoloader with: <comment>require 'vendor/autoload.php';</comment>" )); } @@ -213,9 +208,9 @@ async fn build_interactive( .clone() .unwrap_or_else(|| get_default_package_name(working_dir)); let name = console.ask_validated( - &format!( - "Package name (<vendor>/<name>) [{}]", - mozart_core::console::comment(&default_name), + &console_format!( + "Package name (<vendor>/<name>) [<comment>{}</comment>]", + &default_name, ), &default_name, |val| { @@ -233,10 +228,7 @@ async fn build_interactive( // Description let default_desc = args.description.clone().unwrap_or_default(); let description = console.ask( - &format!( - "Description [{}]", - mozart_core::console::comment(&default_desc) - ), + &console_format!("Description [<comment>{}</comment>]", &default_desc), &default_desc, ); let description = if description.is_empty() { @@ -252,14 +244,11 @@ async fn build_interactive( .or_else(get_default_author) .unwrap_or_default(); let author_input = console.ask( - &format!( - "Author [{}n to skip]", - if !default_author.is_empty() { - format!("{}, ", mozart_core::console::comment(&default_author)) - } else { - String::new() - } - ), + &if !default_author.is_empty() { + console_format!("Author [<comment>{}</comment>, n to skip]", &default_author) + } else { + "Author [n to skip]".to_string() + }, &default_author, ); let authors = if author_input == "n" || author_input == "no" || author_input.is_empty() { @@ -277,9 +266,9 @@ async fn build_interactive( // Minimum Stability let default_stability = args.stability.clone().unwrap_or_default(); let stability_input = console.ask( - &format!( - "Minimum Stability [{}]", - mozart_core::console::comment(&default_stability), + &console_format!( + "Minimum Stability [<comment>{}</comment>]", + &default_stability ), &default_stability, ); @@ -297,9 +286,9 @@ async fn build_interactive( // Package Type let default_type = args.r#type.clone().unwrap_or_default(); let type_input = console.ask( - &format!( - "Package Type (e.g. library, project, metapackage, composer-plugin) [{}]", - mozart_core::console::comment(&default_type), + &console_format!( + "Package Type (e.g. library, project, metapackage, composer-plugin) [<comment>{}</comment>]", + &default_type, ), &default_type, ); @@ -312,10 +301,7 @@ async fn build_interactive( // License let default_license = args.license.clone().unwrap_or_default(); let license_input = console.ask( - &format!( - "License [{}]", - mozart_core::console::comment(&default_license), - ), + &console_format!("License [<comment>{}</comment>]", &default_license), &default_license, ); let license = if license_input.is_empty() { @@ -331,10 +317,7 @@ async fn build_interactive( .unwrap_or(Stability::Stable); console.info(""); - console.info(&format!( - "{}", - mozart_core::console::info("Define your dependencies.") - )); + console.info(&console_format!("<info>Define your dependencies.</info>")); console.info(""); let mut require = parse_requirements(&args.require)?; @@ -346,9 +329,8 @@ async fn build_interactive( // Dev Dependencies console.info(""); - console.info(&format!( - "{}", - mozart_core::console::info("Define your dev dependencies.") + console.info(&console_format!( + "<info>Define your dev dependencies.</info>" )); console.info(""); @@ -368,10 +350,9 @@ async fn build_interactive( let default_autoload = args.autoload.clone().unwrap_or_else(|| "src/".to_string()); let namespace = validation::namespace_from_package_name(&name).unwrap_or_default(); let autoload_input = console.ask( - &format!( - "Add PSR-4 autoload mapping? Maps namespace \"{}\" to the entered relative path. [{}, n to skip]", - namespace, - mozart_core::console::comment(&default_autoload), + &console_format!( + "Add PSR-4 autoload mapping? Maps namespace \"{namespace}\" to the entered relative path. [<comment>{}</comment>, n to skip]", + &default_autoload, ), &default_autoload, ); @@ -437,7 +418,7 @@ async fn interactive_search_packages( Err(e) => { eprintln!( "{}", - console::warning(&format!("Search failed: {e}. Try again.")) + console_format!("<warning>Search failed: {e}. Try again.</warning>") ); continue; } @@ -456,9 +437,9 @@ async fn interactive_search_packages( if filtered.is_empty() { eprintln!( "{}", - console::warning(&format!( - "No new packages found for \"{query}\" (total: {total})." - )) + console_format!( + "<warning>No new packages found for \"{query}\" (total: {total}).</warning>" + ) ); continue; } @@ -511,7 +492,10 @@ async fn interactive_search_packages( } else if num <= filtered.len() { filtered[num - 1].name.to_lowercase() } else { - eprintln!("{}", console::warning(&format!("Invalid selection: {num}"))); + eprintln!( + "{}", + console_format!("<warning>Invalid selection: {num}</warning>") + ); continue; } } else { @@ -523,7 +507,7 @@ async fn interactive_search_packages( match validation::parse_require_string(&package_name) { Ok((n, v)) => (n.to_lowercase(), v), Err(e) => { - eprintln!("{}", console::warning(&format!("Invalid: {e}"))); + eprintln!("{}", console_format!("<warning>Invalid: {e}</warning>")); continue; } } @@ -531,16 +515,16 @@ async fn interactive_search_packages( if !validation::validate_package_name(&package_name) { eprintln!( "{}", - console::warning(&format!("Invalid package name: \"{package_name}\"")) + console_format!("<warning>Invalid package name: \"{package_name}\"</warning>") ); continue; } eprintln!( "{}", - console::info(&format!( - "Using version constraint for {package_name} from Packagist..." - )) + console_format!( + "<info>Using version constraint for {package_name} from Packagist...</info>" + ) ); match packagist::fetch_package_versions(&package_name, None).await { @@ -555,17 +539,19 @@ async fn interactive_search_packages( ); eprintln!( "{}", - console::info(&format!("Using version {c} for {package_name}")) + console_format!( + "<info>Using version {c} for {package_name}</info>" + ) ); (package_name, c) } None => { eprintln!( "{}", - console::warning(&format!( - "Could not find a version of \"{package_name}\" matching \ - your minimum-stability. Try specifying it explicitly." - )) + console_format!( + "<warning>Could not find a version of \"{package_name}\" matching \ + your minimum-stability. Try specifying it explicitly.</warning>" + ) ); continue; } @@ -574,9 +560,9 @@ async fn interactive_search_packages( Err(e) => { eprintln!( "{}", - console::warning(&format!( - "Could not fetch versions for \"{package_name}\": {e}" - )) + console_format!( + "<warning>Could not fetch versions for \"{package_name}\": {e}</warning>" + ) ); continue; } diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 6f8cbe3..4105a7b 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -1,5 +1,6 @@ use clap::Args; use mozart_core::console; +use mozart_core::console_format; use mozart_registry::downloader; use mozart_registry::installed; use mozart_registry::lockfile; @@ -361,15 +362,15 @@ pub async fn install_from_lock( } else { eprintln!( "{}", - console::info(&format!( - "Package operations: {} install{}, {} update{}, {} removal{}", + console_format!( + "<info>Package operations: {} install{}, {} update{}, {} removal{}</info>", installs.len(), if installs.len() == 1 { "" } else { "s" }, updates.len(), if updates.len() == 1 { "" } else { "s" }, removals.len(), if removals.len() == 1 { "" } else { "s" }, - )) + ) ); } @@ -460,16 +461,15 @@ pub async fn install_from_lock( if config.classmap_authoritative { eprintln!( "{}", - console::info( - "Classmap-authoritative mode: autoloader will only look up classes in the classmap." + console_format!( + "<info>Classmap-authoritative mode: autoloader will only look up classes in the classmap.</info>" ) ); } else if config.optimize_autoloader { eprintln!( "{}", - console::info( - "Optimize autoloader: classmap scanning is not yet fully supported. \ - PSR-4/PSR-0 autoloading will still be used." + console_format!( + "<info>Optimize autoloader: classmap scanning is not yet fully supported. PSR-4/PSR-0 autoloading will still be used.</info>" ) ); } @@ -524,14 +524,14 @@ pub async fn execute( } if args.dev { - console.info(&console::warning( - "The --dev option is deprecated. Dev packages are installed by default.", + console.info(&console_format!( + "<warning>The --dev option is deprecated. Dev packages are installed by default.</warning>" )); } if args.no_suggest { - console.info(&console::warning( - "The --no-suggest option is deprecated and has no effect.", + console.info(&console_format!( + "<warning>The --no-suggest option is deprecated and has no effect.</warning>" )); } @@ -539,8 +539,8 @@ pub async fn execute( // If no lock file present, fall back to update (matching Composer behavior). let lock_path = working_dir.join("composer.lock"); if !lock_path.exists() { - console.info(&console::warning( - "No composer.lock file present. Updating dependencies to latest instead of installing from lock file.", + console.info(&console_format!( + "<warning>No composer.lock file present. Updating dependencies to latest instead of installing from lock file.</warning>" )); let update_args = super::update::UpdateArgs { packages: vec![], @@ -584,8 +584,8 @@ pub async fn execute( if composer_json_path.exists() { let content = std::fs::read_to_string(&composer_json_path)?; if !lock.is_fresh(&content) { - console.info(&console::warning( - "Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `mozart update`." + console.info(&console_format!( + "<warning>Warning: The lock file is not up to date with the latest changes in composer.json. You may be getting outdated dependencies. It is recommended that you run `mozart update`.</warning>" )); } } @@ -598,8 +598,8 @@ pub async fn execute( .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); if prefer_source { - console.info(&console::warning( - "Warning: Source installs are not yet supported. Falling back to dist.", + console.info(&console_format!( + "<warning>Warning: Source installs are not yet supported. Falling back to dist.</warning>" )); } diff --git a/crates/mozart/src/commands/prohibits.rs b/crates/mozart/src/commands/prohibits.rs index a6a70d5..55a8175 100644 --- a/crates/mozart/src/commands/prohibits.rs +++ b/crates/mozart/src/commands/prohibits.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use std::path::PathBuf; #[derive(Args)] @@ -62,10 +63,11 @@ pub async fn execute( if results.is_empty() { println!( "{}", - mozart_core::console::info(&format!( - "{} {} can be installed.", - args.package, args.version - )) + console_format!( + "<info>{} {} can be installed.</info>", + args.package, + args.version + ) ); return Ok(()); } diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index ee02ad8..2b1f1ec 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -1,5 +1,5 @@ use clap::Args; -use mozart_core::console; +use mozart_core::console_format; use mozart_core::package; use mozart_core::validation; use mozart_registry::lockfile; @@ -108,20 +108,10 @@ pub async fn execute( // Step 2: Handle deprecated flags if args.update_with_dependencies { - console.info(&format!( - "{}", - console::warning( - "The -w / --update-with-dependencies flag is deprecated. Use --with-all-dependencies instead." - ) - )); + console.info(&console_format!("<warning>The -w / --update-with-dependencies flag is deprecated. Use --with-all-dependencies instead.</warning>")); } if args.update_with_all_dependencies { - console.info(&format!( - "{}", - console::warning( - "The -W / --update-with-all-dependencies flag is deprecated. Use --with-all-dependencies instead." - ) - )); + console.info(&console_format!("<warning>The -W / --update-with-all-dependencies flag is deprecated. Use --with-all-dependencies instead.</warning>")); } // Step 3: Resolve working directory and read composer.json @@ -160,41 +150,31 @@ pub async fn execute( if raw.require_dev.contains_key(&name) { println!( "{}", - console::info(&format!("Removing {name} from require-dev")) + console_format!("<info>Removing {name} from require-dev</info>") ); raw.require_dev.remove(&name); any_removed = true; } else { - console.info(&format!( - "{}", - console::warning(&format!( - "{name} is not required in require-dev and has not been removed." - )) - )); + console.info(&console_format!("<warning>{name} is not required in require-dev and has not been removed.</warning>")); } } else { // Auto-detect: look in require first, then require-dev if raw.require.contains_key(&name) { println!( "{}", - console::info(&format!("Removing {name} from require")) + console_format!("<info>Removing {name} from require</info>") ); raw.require.remove(&name); any_removed = true; } else if raw.require_dev.contains_key(&name) { println!( "{}", - console::info(&format!("Removing {name} from require-dev")) + console_format!("<info>Removing {name} from require-dev</info>") ); raw.require_dev.remove(&name); any_removed = true; } else { - console.info(&format!( - "{}", - console::warning(&format!( - "{name} is not required in your composer.json and has not been removed." - )) - )); + console.info(&console_format!("<warning>{name} is not required in your composer.json and has not been removed.</warning>")); } } } @@ -203,7 +183,7 @@ pub async fn execute( if args.dry_run { println!( "{}", - console::comment("Dry run: composer.json not modified.") + console_format!("<comment>Dry run: composer.json not modified.</comment>") ); } else if any_removed { package::write_to_file(&raw, &composer_path)?; @@ -213,7 +193,9 @@ pub async fn execute( if args.no_update { println!( "{}", - console::comment("Not updating dependencies, only modifying composer.json.") + console_format!( + "<comment>Not updating dependencies, only modifying composer.json.</comment>" + ) ); return Ok(()); } @@ -291,13 +273,7 @@ pub async fn execute( match lockfile::LockFile::read_from_file(&lock_path) { Ok(l) => Some(l), Err(e) => { - console.info(&format!( - "{}", - console::warning(&format!( - "Could not read existing composer.lock: {}. Treating as a fresh install.", - e - )) - )); + console.info(&console_format!("<warning>Could not read existing composer.lock: {}. Treating as a fresh install.</warning>", e)); None } } @@ -338,12 +314,7 @@ pub async fn execute( // For --minimal-changes, additionally pin packages beyond the allow list if args.minimal_changes { - console.info(&format!( - "{}", - console::info( - "Minimal changes mode: preserving locked versions for non-removed packages." - ) - )); + console.info(&console_format!("<info>Minimal changes mode: preserving locked versions for non-removed packages.</info>")); } resolved = super::update::apply_partial_update(resolved, lock, &allow_list); @@ -383,17 +354,14 @@ pub async fn execute( .filter(|c| matches!(c.kind, super::update::ChangeKind::Remove { .. })) .collect(); - console.info(&format!( - "{}", - console::info(&format!( - "Package operations: {} install{}, {} update{}, {} removal{}", - installs.len(), - if installs.len() == 1 { "" } else { "s" }, - updates.len(), - if updates.len() == 1 { "" } else { "s" }, - removals.len(), - if removals.len() == 1 { "" } else { "s" }, - )) + console.info(&console_format!( + "<info>Package operations: {} install{}, {} update{}, {} removal{}</info>", + installs.len(), + if installs.len() == 1 { "" } else { "s" }, + updates.len(), + if updates.len() == 1 { "" } else { "s" }, + removals.len(), + if removals.len() == 1 { "" } else { "s" }, )); // Print individual change lines @@ -561,7 +529,9 @@ async fn remove_unused( console.info(&format!("Found {} unused package(s).", unused.len())); if args.dry_run { - console.info(&console::comment("Dry run: lock file not modified.")); + console.info(&console_format!( + "<comment>Dry run: lock file not modified.</comment>" + )); return Ok(()); } diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 28b4e5e..2082f43 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -1,5 +1,5 @@ use clap::Args; -use mozart_core::console; +use mozart_core::console_format; use mozart_core::package::{self, Stability}; use mozart_core::validation; use mozart_registry::lockfile; @@ -170,7 +170,7 @@ async fn interactive_search_packages( Err(e) => { eprintln!( "{}", - console::warning(&format!("Search failed: {e}. Try again.")) + console_format!("<warning>Search failed: {e}. Try again.</warning>") ); continue; } @@ -186,9 +186,9 @@ async fn interactive_search_packages( if filtered.is_empty() { eprintln!( "{}", - console::warning(&format!( - "No new packages found for \"{query}\" (total: {total})." - )) + console_format!( + "<warning>No new packages found for \"{query}\" (total: {total}).</warning>" + ) ); continue; } @@ -243,7 +243,10 @@ async fn interactive_search_packages( } else if num <= filtered.len() { filtered[num - 1].name.to_lowercase() } else { - eprintln!("{}", console::warning(&format!("Invalid selection: {num}"))); + eprintln!( + "{}", + console_format!("<warning>Invalid selection: {num}</warning>") + ); continue; } } else { @@ -256,7 +259,7 @@ async fn interactive_search_packages( match validation::parse_require_string(&package_name) { Ok((n, v)) => (n.to_lowercase(), v), Err(e) => { - eprintln!("{}", console::warning(&format!("Invalid: {e}"))); + eprintln!("{}", console_format!("<warning>Invalid: {e}</warning>")); continue; } } @@ -264,16 +267,16 @@ async fn interactive_search_packages( if !validation::validate_package_name(&package_name) { eprintln!( "{}", - console::warning(&format!("Invalid package name: \"{package_name}\"")) + console_format!("<warning>Invalid package name: \"{package_name}\"</warning>") ); continue; } eprintln!( "{}", - console::info(&format!( - "Using version constraint for {package_name} from Packagist..." - )) + console_format!( + "<info>Using version constraint for {package_name} from Packagist...</info>" + ) ); match packagist::fetch_package_versions(&package_name, None).await { @@ -292,17 +295,18 @@ async fn interactive_search_packages( }; eprintln!( "{}", - console::info(&format!("Using version {c} for {package_name}")) + console_format!( + "<info>Using version {c} for {package_name}</info>" + ) ); (package_name, c) } None => { eprintln!( "{}", - console::warning(&format!( - "Could not find a version of \"{package_name}\" matching \ - your minimum-stability. Try specifying it explicitly." - )) + console_format!( + "<warning>Could not find a version of \"{package_name}\" matching your minimum-stability. Try specifying it explicitly.</warning>" + ) ); continue; } @@ -311,9 +315,9 @@ async fn interactive_search_packages( Err(e) => { eprintln!( "{}", - console::warning(&format!( - "Could not fetch versions for \"{package_name}\": {e}" - )) + console_format!( + "<warning>Could not fetch versions for \"{package_name}\": {e}</warning>" + ) ); continue; } @@ -403,19 +407,15 @@ pub async fn execute( // Handle deprecated flags if args.no_suggest { - console.info(&console::warning( - "The --no-suggest option is deprecated and has no effect.", + console.info(&console_format!( + "<warning>The --no-suggest option is deprecated and has no effect.</warning>" )); } if args.update_with_dependencies { - console.info(&console::warning( - "The -w / --update-with-dependencies flag is deprecated. Use --with-dependencies instead." - )); + console.info(&console_format!("<warning>The -w / --update-with-dependencies flag is deprecated. Use --with-dependencies instead.</warning>")); } if args.update_with_all_dependencies { - console.info(&console::warning( - "The -W / --update-with-all-dependencies flag is deprecated. Use --with-all-dependencies instead." - )); + console.info(&console_format!("<warning>The -W / --update-with-all-dependencies flag is deprecated. Use --with-all-dependencies instead.</warning>")); } // Resolve working directory @@ -461,9 +461,9 @@ pub async fn execute( println!( "{}", - console::info(&format!( - "Using version constraint for {name} from Packagist..." - )) + console_format!( + "<info>Using version constraint for {name} from Packagist...</info>" + ) ); let versions = packagist::fetch_package_versions(&name, None).await?; @@ -488,7 +488,7 @@ pub async fn execute( println!( "{}", - console::info(&format!("Using version {constraint} for {name}")) + console_format!("<info>Using version {constraint} for {name}</info>") ); (name, constraint) @@ -510,14 +510,14 @@ pub async fn execute( if let Some(existing) = target.get(name) { println!( "{}", - console::comment(&format!( - "Updating {name} from {existing} to {constraint} in {section_name}" - )) + console_format!( + "<comment>Updating {name} from {existing} to {constraint} in {section_name}</comment>" + ) ); } else { println!( "{}", - console::info(&format!("Adding {name} ({constraint}) to {section_name}")) + console_format!("<info>Adding {name} ({constraint}) to {section_name}</info>") ); } @@ -536,7 +536,7 @@ pub async fn execute( if args.dry_run { println!( "{}", - console::comment("Dry run: composer.json not modified.") + console_format!("<comment>Dry run: composer.json not modified.</comment>") ); } else { package::write_to_file(&raw, &composer_path)?; @@ -546,7 +546,9 @@ pub async fn execute( if args.no_update { println!( "{}", - console::comment("Not updating dependencies, only modifying composer.json.") + console_format!( + "<comment>Not updating dependencies, only modifying composer.json.</comment>" + ) ); return Ok(()); } @@ -622,10 +624,7 @@ pub async fn execute( match lockfile::LockFile::read_from_file(&lock_path) { Ok(l) => Some(l), Err(e) => { - console.info(&console::warning(&format!( - "Could not read existing composer.lock: {}. Treating as a fresh install.", - e - ))); + console.info(&console_format!("<warning>Could not read existing composer.lock: {}. Treating as a fresh install.</warning>", e)); None } } @@ -760,9 +759,7 @@ pub async fn execute( .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); if prefer_source { - console.info(&mozart_core::console::warning( - "Warning: Source installs are not yet supported. Falling back to dist.", - )); + console.info(&console_format!("<warning>Warning: Source installs are not yet supported. Falling back to dist.</warning>")); } super::install::install_from_lock( diff --git a/crates/mozart/src/commands/search.rs b/crates/mozart/src/commands/search.rs index f145460..06e67c9 100644 --- a/crates/mozart/src/commands/search.rs +++ b/crates/mozart/src/commands/search.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use mozart_registry::packagist::SearchResult; #[derive(Args)] @@ -75,9 +76,9 @@ pub async fn execute( if !matches!(format, "text" | "json") { eprintln!( "{}", - mozart_core::console::error(&format!( - "Unsupported format \"{format}\". See help for supported formats." - )) + console_format!( + "<error>Unsupported format \"{format}\". See help for supported formats.</error>" + ) ); std::process::exit(1); } @@ -107,7 +108,7 @@ pub async fn execute( if results.is_empty() { eprintln!( "{}", - mozart_core::console::warning(&format!("No packages found for \"{query}\"")) + console_format!("<warning>No packages found for \"{query}\"</warning>") ); return Ok(()); } @@ -130,13 +131,9 @@ pub async fn execute( println!( "{} {} {}", - mozart_core::console::info(&format!( - "{:<width$}", - result.name, - width = name_width - )), - mozart_core::console::comment(&dl_str), - mozart_core::console::comment(&fav_str), + console_format!("<info>{:<width$}</info>", result.name, width = name_width), + console_format!("<comment>{}</comment>", dl_str), + console_format!("<comment>{}</comment>", fav_str), ); if !result.description.is_empty() { println!(" {}", result.description); diff --git a/crates/mozart/src/commands/self_update.rs b/crates/mozart/src/commands/self_update.rs index ad9ae74..56e6bf2 100644 --- a/crates/mozart/src/commands/self_update.rs +++ b/crates/mozart/src/commands/self_update.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use std::io::Write; use std::path::{Path, PathBuf}; @@ -283,9 +284,9 @@ async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> a if args.version.is_none() && target_version == current_version { println!( "{}", - mozart_core::console::info(&format!( - "Mozart is already at the latest version ({current_version})" - )) + console_format!( + "<info>Mozart is already at the latest version ({current_version})</info>" + ) ); return Ok(()); } @@ -334,14 +335,17 @@ async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> a println!( "{}", - mozart_core::console::info(&format!( - "Mozart updated successfully from {current_version} to {target_version}" - )) + console_format!( + "<info>Mozart updated successfully from {current_version} to {target_version}</info>" + ) ); if args.clean_backups { clean_backups(data_dir)?; - println!("{}", mozart_core::console::comment("Old backups removed.")); + println!( + "{}", + console_format!("<comment>Old backups removed.</comment>") + ); } Ok(()) @@ -372,10 +376,10 @@ fn rollback(current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { println!( "{}", - mozart_core::console::info(&format!( - "Rollback successful. Restored from {}", + console_format!( + "<info>Rollback successful. Restored from {}</info>", backup.file_name().unwrap_or_default().to_string_lossy() - )) + ) ); let _ = current_exe; // suppress unused warning diff --git a/crates/mozart/src/commands/show.rs b/crates/mozart/src/commands/show.rs index d329984..7a9eade 100644 --- a/crates/mozart/src/commands/show.rs +++ b/crates/mozart/src/commands/show.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use std::collections::{HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -152,8 +153,8 @@ async fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Resul if !root.require.is_empty() || !root.require_dev.is_empty() { eprintln!( "{}", - mozart_core::console::warning( - "No dependencies installed. Try running mozart install or update." + console_format!( + "<warning>No dependencies installed. Try running mozart install or update.</warning>" ) ); } @@ -362,45 +363,53 @@ async fn show_installed_package_list( .map(|li| classify_update_category(&entry.version_normalized, &li.version_normalized)); let name_str = match category { - Some(ListUpdateKind::Compatible) => mozart_core::console::highlight(&format!( - "{:<width$}", - entry.name, - width = name_width - )) - .to_string(), - Some(ListUpdateKind::Incompatible) => mozart_core::console::comment(&format!( - "{:<width$}", - entry.name, - width = name_width - )) - .to_string(), - _ => mozart_core::console::info(&format!("{:<width$}", entry.name, width = name_width)) - .to_string(), + Some(ListUpdateKind::Compatible) => { + console_format!( + "<highlight>{:<width$}</highlight>", + entry.name, + width = name_width + ) + } + Some(ListUpdateKind::Incompatible) => { + console_format!( + "<comment>{:<width$}</comment>", + entry.name, + width = name_width + ) + } + _ => { + console_format!("<info>{:<width$}</info>", entry.name, width = name_width) + } }; - let version_str = - mozart_core::console::comment(&format!("{:<width$}", version, width = version_width)) - .to_string(); + let version_str = console_format!( + "<comment>{:<width$}</comment>", + version, + width = version_width + ); if show_latest { let latest_str = match entry.latest_info.as_ref() { Some(li) => { let lv = format_version(&li.version); match category { - Some(ListUpdateKind::Compatible) => mozart_core::console::highlight( - &format!("{:<width$}", lv, width = latest_width), - ) - .to_string(), - Some(ListUpdateKind::Incompatible) => mozart_core::console::comment( - &format!("{:<width$}", lv, width = latest_width), - ) - .to_string(), - _ => mozart_core::console::info(&format!( - "{:<width$}", - lv, - width = latest_width - )) - .to_string(), + Some(ListUpdateKind::Compatible) => { + console_format!( + "<highlight>{:<width$}</highlight>", + lv, + width = latest_width + ) + } + Some(ListUpdateKind::Incompatible) => { + console_format!( + "<comment>{:<width$}</comment>", + lv, + width = latest_width + ) + } + _ => { + console_format!("<info>{:<width$}</info>", lv, width = latest_width) + } } } None => format!("{:<width$}", "", width = latest_width), @@ -539,36 +548,40 @@ fn show_installed_package_detail( let vendor_dir = working_dir.join("vendor"); - println!("{} : {}", mozart_core::console::info("name"), pkg.name); + println!("{} : {}", console_format!("<info>name</info>"), pkg.name); println!( "{} : {}", - mozart_core::console::info("descrip."), + console_format!("<info>descrip.</info>"), get_installed_description(pkg) ); println!( "{} : {}", - mozart_core::console::info("keywords"), + console_format!("<info>keywords</info>"), get_installed_keywords(pkg) ); println!( "{} : {}", - mozart_core::console::info("versions"), + console_format!("<info>versions</info>"), format_version_highlight(&pkg.version) ); println!( "{} : {}", - mozart_core::console::info("type"), + console_format!("<info>type</info>"), pkg.package_type.as_deref().unwrap_or("library") ); // License if let Some(licenses) = get_installed_license(pkg) { - println!("{} : {}", mozart_core::console::info("license"), licenses); + println!("{} : {}", console_format!("<info>license</info>"), licenses); } // Homepage if let Some(homepage) = get_installed_homepage(pkg) { - println!("{} : {}", mozart_core::console::info("homepage"), homepage); + println!( + "{} : {}", + console_format!("<info>homepage</info>"), + homepage + ); } // Source @@ -581,9 +594,9 @@ fn show_installed_package_detail( .unwrap_or(""); println!( "{} : [{}] {} {}", - mozart_core::console::info("source"), + console_format!("<info>source</info>"), source_type, - mozart_core::console::comment(source_url), + console_format!("<comment>{}</comment>", source_url), source_ref ); } @@ -595,9 +608,9 @@ fn show_installed_package_detail( let dist_ref = dist.get("reference").and_then(|v| v.as_str()).unwrap_or(""); println!( "{} : [{}] {} {}", - mozart_core::console::info("dist"), + console_format!("<info>dist</info>"), dist_type, - mozart_core::console::comment(dist_url), + console_format!("<comment>{}</comment>", dist_url), dist_ref ); } @@ -607,7 +620,7 @@ fn show_installed_package_detail( if install_path.exists() { println!( "{} : {}", - mozart_core::console::info("path"), + console_format!("<info>path</info>"), install_path.display() ); } @@ -617,10 +630,10 @@ fn show_installed_package_detail( && !requires.is_empty() { println!(); - println!("{}", mozart_core::console::info("requires")); + println!("{}", console_format!("<info>requires</info>")); for (name, constraint) in requires { let c = constraint.as_str().unwrap_or(""); - println!("{} {}", name, mozart_core::console::comment(c)); + println!("{} {}", name, console_format!("<comment>{}</comment>", c)); } } @@ -632,10 +645,10 @@ fn show_installed_package_detail( && !requires_dev.is_empty() { println!(); - println!("{}", mozart_core::console::info("requires (dev)")); + println!("{}", console_format!("<info>requires (dev)</info>")); for (name, constraint) in requires_dev { let c = constraint.as_str().unwrap_or(""); - println!("{} {}", name, mozart_core::console::comment(c)); + println!("{} {}", name, console_format!("<comment>{}</comment>", c)); } } @@ -800,45 +813,53 @@ async fn show_locked_package_list( .map(|li| classify_update_category(&entry.version_normalized, &li.version_normalized)); let name_str = match category { - Some(ListUpdateKind::Compatible) => mozart_core::console::highlight(&format!( - "{:<width$}", - entry.name, - width = name_width - )) - .to_string(), - Some(ListUpdateKind::Incompatible) => mozart_core::console::comment(&format!( - "{:<width$}", - entry.name, - width = name_width - )) - .to_string(), - _ => mozart_core::console::info(&format!("{:<width$}", entry.name, width = name_width)) - .to_string(), + Some(ListUpdateKind::Compatible) => { + console_format!( + "<highlight>{:<width$}</highlight>", + entry.name, + width = name_width + ) + } + Some(ListUpdateKind::Incompatible) => { + console_format!( + "<comment>{:<width$}</comment>", + entry.name, + width = name_width + ) + } + _ => { + console_format!("<info>{:<width$}</info>", entry.name, width = name_width) + } }; - let version_str = - mozart_core::console::comment(&format!("{:<width$}", version, width = version_width)) - .to_string(); + let version_str = console_format!( + "<comment>{:<width$}</comment>", + version, + width = version_width + ); if show_latest { let latest_str = match entry.latest_info.as_ref() { Some(li) => { let lv = format_version(&li.version); match category { - Some(ListUpdateKind::Compatible) => mozart_core::console::highlight( - &format!("{:<width$}", lv, width = latest_width), - ) - .to_string(), - Some(ListUpdateKind::Incompatible) => mozart_core::console::comment( - &format!("{:<width$}", lv, width = latest_width), - ) - .to_string(), - _ => mozart_core::console::info(&format!( - "{:<width$}", - lv, - width = latest_width - )) - .to_string(), + Some(ListUpdateKind::Compatible) => { + console_format!( + "<highlight>{:<width$}</highlight>", + lv, + width = latest_width + ) + } + Some(ListUpdateKind::Incompatible) => { + console_format!( + "<comment>{:<width$}</comment>", + lv, + width = latest_width + ) + } + _ => { + console_format!("<info>{:<width$}</info>", lv, width = latest_width) + } } } None => format!("{:<width$}", "", width = latest_width), @@ -915,10 +936,10 @@ fn show_locked_package_detail( } }; - println!("{} : {}", mozart_core::console::info("name"), pkg.name); + println!("{} : {}", console_format!("<info>name</info>"), pkg.name); println!( "{} : {}", - mozart_core::console::info("descrip."), + console_format!("<info>descrip.</info>"), pkg.description.as_deref().unwrap_or("") ); @@ -928,16 +949,20 @@ fn show_locked_package_detail( .as_ref() .map(|kw| kw.join(", ")) .unwrap_or_default(); - println!("{} : {}", mozart_core::console::info("keywords"), keywords); + println!( + "{} : {}", + console_format!("<info>keywords</info>"), + keywords + ); println!( "{} : * {}", - mozart_core::console::info("versions"), + console_format!("<info>versions</info>"), format_version(&pkg.version) ); println!( "{} : {}", - mozart_core::console::info("type"), + console_format!("<info>type</info>"), pkg.package_type.as_deref().unwrap_or("library") ); @@ -945,23 +970,27 @@ fn show_locked_package_detail( if let Some(ref licenses) = pkg.license { println!( "{} : {}", - mozart_core::console::info("license"), + console_format!("<info>license</info>"), licenses.join(", ") ); } // Homepage if let Some(ref homepage) = pkg.homepage { - println!("{} : {}", mozart_core::console::info("homepage"), homepage); + println!( + "{} : {}", + console_format!("<info>homepage</info>"), + homepage + ); } // Source if let Some(ref source) = pkg.source { println!( "{} : [{}] {} {}", - mozart_core::console::info("source"), + console_format!("<info>source</info>"), source.source_type, - mozart_core::console::comment(&source.url), + console_format!("<comment>{}</comment>", &source.url), source.reference.as_deref().unwrap_or("") ); } @@ -970,9 +999,9 @@ fn show_locked_package_detail( if let Some(ref dist) = pkg.dist { println!( "{} : [{}] {} {}", - mozart_core::console::info("dist"), + console_format!("<info>dist</info>"), dist.dist_type, - mozart_core::console::comment(&dist.url), + console_format!("<comment>{}</comment>", &dist.url), dist.reference.as_deref().unwrap_or("") ); } @@ -980,18 +1009,26 @@ fn show_locked_package_detail( // Requires if !pkg.require.is_empty() { println!(); - println!("{}", mozart_core::console::info("requires")); + println!("{}", console_format!("<info>requires</info>")); for (name, constraint) in &pkg.require { - println!("{} {}", name, mozart_core::console::comment(constraint)); + println!( + "{} {}", + name, + console_format!("<comment>{}</comment>", constraint) + ); } } // Requires (dev) if !pkg.require_dev.is_empty() { println!(); - println!("{}", mozart_core::console::info("requires (dev)")); + println!("{}", console_format!("<info>requires (dev)</info>")); for (name, constraint) in &pkg.require_dev { - println!("{} {}", name, mozart_core::console::comment(constraint)); + println!( + "{} {}", + name, + console_format!("<comment>{}</comment>", constraint) + ); } } @@ -1000,9 +1037,13 @@ fn show_locked_package_detail( && !suggests.is_empty() { println!(); - println!("{}", mozart_core::console::info("suggests")); + println!("{}", console_format!("<info>suggests</info>")); for (name, reason) in suggests { - println!("{} {}", name, mozart_core::console::comment(reason)); + println!( + "{} {}", + name, + console_format!("<comment>{}</comment>", reason) + ); } } @@ -1023,39 +1064,51 @@ fn show_self(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { return Ok(()); } - println!("{} : {}", mozart_core::console::info("name"), root.name); + println!("{} : {}", console_format!("<info>name</info>"), root.name); println!( "{} : {}", - mozart_core::console::info("descrip."), + console_format!("<info>descrip.</info>"), root.description.as_deref().unwrap_or("") ); println!( "{} : {}", - mozart_core::console::info("type"), + console_format!("<info>type</info>"), root.package_type.as_deref().unwrap_or("project") ); if let Some(ref license) = root.license { - println!("{} : {}", mozart_core::console::info("license"), license); + println!("{} : {}", console_format!("<info>license</info>"), license); } if let Some(ref homepage) = root.homepage { - println!("{} : {}", mozart_core::console::info("homepage"), homepage); + println!( + "{} : {}", + console_format!("<info>homepage</info>"), + homepage + ); } // Requires if !root.require.is_empty() { println!(); - println!("{}", mozart_core::console::info("requires")); + println!("{}", console_format!("<info>requires</info>")); for (name, constraint) in &root.require { - println!("{} {}", name, mozart_core::console::comment(constraint)); + println!( + "{} {}", + name, + console_format!("<comment>{}</comment>", constraint) + ); } } // Requires (dev) if !root.require_dev.is_empty() { println!(); - println!("{}", mozart_core::console::info("requires (dev)")); + println!("{}", console_format!("<info>requires (dev)</info>")); for (name, constraint) in &root.require_dev { - println!("{} {}", name, mozart_core::console::comment(constraint)); + println!( + "{} {}", + name, + console_format!("<comment>{}</comment>", constraint) + ); } } @@ -1109,9 +1162,12 @@ fn show_tree(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { // Print root println!( - "{} {}", - mozart_core::console::info(&root.name), - mozart_core::console::comment(root.description.as_deref().unwrap_or("")) + "{}", + console_format!( + "<info>{}</info> <comment>{}</comment>", + &root.name, + root.description.as_deref().unwrap_or("") + ) ); // Render each root dependency as a tree @@ -1155,10 +1211,9 @@ fn print_tree_node( let version = format_version(&pkg.version); println!( - "{} {} {} {}", + "{} {} {}", prefix, - mozart_core::console::info(pkg_name), - mozart_core::console::comment(&version), + console_format!("<info>{}</info> <comment>{}</comment>", pkg_name, &version), description ); @@ -1215,7 +1270,7 @@ fn print_tree_node( println!( "{} {} {} (not installed)", prefix, - mozart_core::console::comment(pkg_name), + console_format!("<comment>{}</comment>", pkg_name), constraint ); } @@ -1336,8 +1391,12 @@ fn show_platform(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { for (name, version, _source) in &platform_packages { println!( "{} {}", - mozart_core::console::info(&format!("{:<width$}", name, width = name_width)), - mozart_core::console::comment(&format!("{:<width$}", version, width = version_width)), + console_format!("<info>{:<width$}</info>", name, width = name_width), + console_format!( + "<comment>{:<width$}</comment>", + version, + width = version_width + ), ); } @@ -1366,8 +1425,8 @@ async fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<( let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; println!( "{}", - mozart_core::console::info( - "Available versions for locked packages (from Packagist):" + console_format!( + "<info>Available versions for locked packages (from Packagist):</info>" ) ); println!(); @@ -1391,8 +1450,8 @@ async fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<( eprintln!( "{}", - mozart_core::console::warning( - "No dependencies installed. Try running mozart install or update." + console_format!( + "<warning>No dependencies installed. Try running mozart install or update.</warning>" ) ); return Ok(()); @@ -1401,7 +1460,7 @@ async fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<( println!( "{}", - mozart_core::console::info("Available versions for installed packages (from Packagist):") + console_format!("<info>Available versions for installed packages (from Packagist):</info>") ); println!(); @@ -1467,10 +1526,10 @@ async fn show_available_versions(pkg_name: &str, args: &ShowArgs) -> anyhow::Res println!( "{}", - mozart_core::console::info(&format!("Available versions for {pkg_name}:")) + console_format!("<info>Available versions for {pkg_name}:</info>") ); for v in &versions { - println!(" {}", mozart_core::console::comment(&v.version)); + println!(" {}", console_format!("<comment>{}</comment>", &v.version)); } Ok(()) } @@ -1481,7 +1540,7 @@ async fn show_available_versions_inline(pkg_name: &str) { if versions.is_empty() { println!( "{}: no versions found", - mozart_core::console::info(pkg_name) + console_format!("<info>{}</info>", pkg_name) ); return; } @@ -1498,15 +1557,15 @@ async fn show_available_versions_inline(pkg_name: &str) { }; println!( "{}: {}{}", - mozart_core::console::info(pkg_name), - mozart_core::console::comment(&shown.join(", ")), + console_format!("<info>{}</info>", pkg_name), + console_format!("<comment>{}</comment>", &shown.join(", ")), rest ); } Err(_) => { println!( "{}: (could not fetch from Packagist)", - mozart_core::console::comment(pkg_name) + console_format!("<comment>{}</comment>", pkg_name) ); } } diff --git a/crates/mozart/src/commands/suggests.rs b/crates/mozart/src/commands/suggests.rs index b61e1c3..3fb2f00 100644 --- a/crates/mozart/src/commands/suggests.rs +++ b/crates/mozart/src/commands/suggests.rs @@ -1,5 +1,6 @@ use clap::Args; use mozart_core::console; +use mozart_core::console_format; use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::{Path, PathBuf}; @@ -135,8 +136,8 @@ pub async fn execute( if diff > 0 { println!( "{} by transitive dependencies can be shown with {}", - console::info(&format!("{diff} additional suggestions")), - console::info("--all"), + console_format!("<info>{diff} additional suggestions</info>"), + console_format!("<info>--all</info>"), ); } } @@ -435,7 +436,7 @@ fn render_list(suggestions: &[&Suggestion]) { targets.sort_unstable(); targets.dedup(); for t in targets { - println!("{}", console::info(t)); + println!("{}", console_format!("<info>{}</info>", t)); } } @@ -446,13 +447,19 @@ fn render_by_package(suggestions: &[&Suggestion]) { grouped.entry(s.source.as_str()).or_default().push(s); } for (source, items) in &grouped { - println!("{} suggests:", console::comment(source)); + println!( + "{}", + console_format!("<comment>{}</comment> suggests:", source) + ); for s in items { let reason = sanitize_reason(&s.reason); if reason.is_empty() { - println!(" - {}", console::info(&s.target)); + println!("{}", console_format!(" - <info>{}</info>", &s.target)); } else { - println!(" - {}: {}", console::info(&s.target), reason); + println!( + "{}", + console_format!(" - <info>{}</info>: {}", &s.target, reason) + ); } } println!(); @@ -466,13 +473,19 @@ fn render_by_suggestion(suggestions: &[&Suggestion]) { grouped.entry(s.target.as_str()).or_default().push(s); } for (target, items) in &grouped { - println!("{} is suggested by:", console::info(target)); + println!( + "{}", + console_format!("<info>{}</info> is suggested by:", target) + ); for s in items { let reason = sanitize_reason(&s.reason); if reason.is_empty() { - println!(" - {}", console::comment(&s.source)); + println!("{}", console_format!(" - <comment>{}</comment>", &s.source)); } else { - println!(" - {}: {}", console::comment(&s.source), reason); + println!( + "{}", + console_format!(" - <comment>{}</comment>: {}", &s.source, reason) + ); } } println!(); diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 7fbec1c..8bd694b 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -1,5 +1,6 @@ use clap::Args; use mozart_core::console; +use mozart_core::console_format; use mozart_core::package::{self, Stability}; use mozart_registry::lockfile; use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest, ResolvedPackage}; @@ -703,13 +704,13 @@ pub async fn execute( // Step 2: Handle deprecated flags if args.dev { - console.info(&console::warning( - "The --dev option is deprecated. Dev packages are updated by default.", + console.info(&console_format!( + "<warning>The --dev option is deprecated. Dev packages are updated by default.</warning>" )); } if args.no_suggest { - console.info(&console::warning( - "The --no-suggest option is deprecated and has no effect.", + console.info(&console_format!( + "<warning>The --no-suggest option is deprecated and has no effect.</warning>" )); } @@ -810,10 +811,10 @@ pub async fn execute( match lockfile::LockFile::read_from_file(&lock_path) { Ok(l) => Some(l), Err(e) => { - console.info(&console::warning(&format!( - "Could not read existing composer.lock: {}. Treating as a fresh install.", + console.info(&console_format!( + "<warning>Could not read existing composer.lock: {}. Treating as a fresh install.</warning>", e - ))); + )); None } } @@ -879,8 +880,8 @@ pub async fn execute( if args.interactive { match &old_lock { None => { - console.info(&console::warning( - "No lock file found. --interactive mode skipped.", + console.info(&console_format!( + "<warning>No lock file found. --interactive mode skipped.</warning>" )); vec![] } @@ -1121,8 +1122,8 @@ pub async fn execute( .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); if prefer_source { - console.info(&mozart_core::console::warning( - "Warning: Source installs are not yet supported. Falling back to dist.", + console.info(&console_format!( + "<warning>Warning: Source installs are not yet supported. Falling back to dist.</warning>" )); } diff --git a/crates/mozart/src/commands/validate.rs b/crates/mozart/src/commands/validate.rs index 2cd460c..614a1cc 100644 --- a/crates/mozart/src/commands/validate.rs +++ b/crates/mozart/src/commands/validate.rs @@ -1,4 +1,5 @@ use clap::Args; +use mozart_core::console_format; use std::path::{Path, PathBuf}; #[derive(Args)] @@ -446,9 +447,9 @@ fn validate_dependencies( format!("{}/{}", vendor_str, pkg_entry.file_name().to_string_lossy()); eprintln!( "{}", - mozart_core::console::warning(&format!( - "{pkg_name}: composer.json contains invalid JSON" - )) + console_format!( + "<warning>{pkg_name}: composer.json contains invalid JSON</warning>" + ) ); continue; }; @@ -463,17 +464,11 @@ fn validate_dependencies( format!("{}/{}", vendor_str, pkg_entry.file_name().to_string_lossy()); for e in &result.errors { - eprintln!( - "{}", - mozart_core::console::error(&format!("{pkg_name}: {e}")) - ); + eprintln!("{}", console_format!("<error>{pkg_name}: {e}</error>")); dep_errors += 1; } for w in &result.warnings { - eprintln!( - "{}", - mozart_core::console::warning(&format!("{pkg_name}: {w}")) - ); + eprintln!("{}", console_format!("<warning>{pkg_name}: {w}</warning>")); dep_warnings += 1; } } @@ -541,16 +536,14 @@ fn output_result( if result.has_errors() { eprintln!( "{}", - mozart_core::console::error(&format!( - "{name} is invalid, the following errors/warnings were found:" - )) + console_format!( + "<error>{name} is invalid, the following errors/warnings were found:</error>" + ) ); } else if result.has_publish_errors() && check_publish { eprintln!( "{}", - mozart_core::console::info(&format!( - "{name} is valid for simple usage with Composer but has" - )) + console_format!("<info>{name} is valid for simple usage with Composer but has</info>") ); eprintln!( "{}", @@ -567,7 +560,7 @@ fn output_result( } else if result.has_warnings() { eprintln!( "{}", - mozart_core::console::info(&format!("{name} is valid, but with a few warnings")) + console_format!("<info>{name} is valid, but with a few warnings</info>") ); eprintln!( "{}", @@ -579,15 +572,10 @@ fn output_result( let kind = if check_lock { "errors" } else { "warnings" }; println!( "{}", - mozart_core::console::info(&format!( - "{name} is valid but your composer.lock has some {kind}" - )) + console_format!("<info>{name} is valid but your composer.lock has some {kind}</info>") ); } else { - println!( - "{}", - mozart_core::console::info(&format!("{name} is valid")) - ); + println!("{}", console_format!("<info>{name} is valid</info>")); } // Collect error and warning message lines diff --git a/crates/mozart/src/main.rs b/crates/mozart/src/main.rs index 04dd6d1..ed7c4ad 100644 --- a/crates/mozart/src/main.rs +++ b/crates/mozart/src/main.rs @@ -48,13 +48,16 @@ async fn main() { if let Some(mozart_err) = e.downcast_ref::<exit_code::MozartError>() { // Only print a message when there is one (bail_silent produces empty message). if !mozart_err.message.is_empty() { - eprintln!("{}", mozart_core::console::error(&mozart_err.message)); + eprintln!( + "{}", + mozart_core::console_format!("<error>{}</error>", mozart_err.message) + ); } std::process::exit(mozart_err.exit_code); } // Generic anyhow error — print and exit with GENERAL_ERROR. - eprintln!("{}", mozart_core::console::error(&format!("{e:#}"))); + eprintln!("{}", mozart_core::console_format!("<error>{e:#}</error>")); std::process::exit(exit_code::GENERAL_ERROR); } } |
