diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-08 03:12:05 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-08 03:12:05 +0900 |
| commit | e6f35b567564665c6cb741a06e4c4afcdc5ab317 (patch) | |
| tree | a1ae0e55afab95598ff0f6bec5b6a2e9f4f86cc0 /crates/mozart-core/src | |
| parent | b3fcdf3b5cf0d6b43109c4bcb3dfcbb6576abce5 (diff) | |
| download | php-mozart-e6f35b567564665c6cb741a06e4c4afcdc5ab317.tar.gz php-mozart-e6f35b567564665c6cb741a06e4c4afcdc5ab317.tar.zst php-mozart-e6f35b567564665c6cb741a06e4c4afcdc5ab317.zip | |
fix(search): align with Composer's RepositoryInterface::search
Replace the HTTP-only post-filtered implementation with a Repository::search
trait dispatch that mirrors ComposerRepository::search semantics for all
three modes (FULLTEXT/NAME/VENDOR). --only-name now does an OR-of-tokens
regex match against the full Packagist list.json index instead of a
substring match against a fulltext page, so e.g. \`mozart search --only-name
mono log\` matches \`monolog/monolog\` like Composer does. Other parity
fixes: regex::escape on non-fulltext queries, format check before mutex
check, 4-space JSON indent, OSC 8 terminal hyperlink emission when a
result has a url, <warning>\! Abandoned \!</warning> styling on abandoned
rows, and the Mozart-only "No packages found" warning is dropped to match
Composer's silent empty-result behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart-core/src')
| -rw-r--r-- | crates/mozart-core/src/console.rs | 47 |
1 files changed, 47 insertions, 0 deletions
diff --git a/crates/mozart-core/src/console.rs b/crates/mozart-core/src/console.rs index b8db17e..e0c224f 100644 --- a/crates/mozart-core/src/console.rs +++ b/crates/mozart-core/src/console.rs @@ -43,6 +43,30 @@ pub fn __format_warning_message(message: &str) -> ColoredString { } // --------------------------------------------------------------------------- +// Terminal hyperlinks (OSC 8) +// --------------------------------------------------------------------------- + +/// Wrap `text` in an OSC 8 terminal hyperlink escape sequence pointing at `url`. +/// +/// Mirrors Composer's `<href=URL>text</>` formatter tag, which Symfony Console +/// renders to the same OSC 8 sequence. Mozart's tag formatter is a +/// compile-time proc-macro that doesn't accept runtime attributes, so this +/// helper is the runtime path. +/// +/// When `decorated` is `false`, returns `text` unchanged (matching Symfony's +/// behavior of suppressing hyperlinks on non-decorated outputs). +/// +/// Control characters in `url` (`\x00`–`\x1f`, `\x7f`) are stripped to prevent +/// terminating the escape sequence early. +pub fn hyperlink(url: &str, text: &str, decorated: bool) -> String { + if !decorated { + return text.to_string(); + } + let safe_url: String = url.chars().filter(|c| !c.is_control()).collect(); + format!("\x1b]8;;{safe_url}\x1b\\{text}\x1b]8;;\x1b\\") +} + +// --------------------------------------------------------------------------- // Verbosity // --------------------------------------------------------------------------- @@ -413,4 +437,27 @@ mod tests { assert!(!make_console(Verbosity::VeryVerbose).is_debug()); assert!(make_console(Verbosity::Debug).is_debug()); } + + #[test] + fn test_hyperlink_decorated() { + let out = hyperlink("https://example.com", "click", true); + assert!(out.contains("https://example.com")); + assert!(out.contains("click")); + assert!(out.starts_with("\x1b]8;;")); + assert!(out.ends_with("\x1b]8;;\x1b\\")); + } + + #[test] + fn test_hyperlink_undecorated_returns_plain_text() { + let out = hyperlink("https://example.com", "click", false); + assert_eq!(out, "click"); + } + + #[test] + fn test_hyperlink_strips_control_chars_from_url() { + let out = hyperlink("https://example.com/\x00\x07path", "click", true); + assert!(out.contains("https://example.com/path")); + assert!(!out.contains('\x00')); + assert!(!out.contains('\x07')); + } } |
