diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-08 20:29:33 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-08 20:29:57 +0900 |
| commit | 92fa497cc345118198508fcf948ff650e8902434 (patch) | |
| tree | 5789f3d74b6ffb79dbcc8f59b012cd359caf9444 /crates/mozart-core/src/package_info.rs | |
| parent | b286af9ffe78d50b63bf5fda7fc796ab20f2552f (diff) | |
| download | php-mozart-92fa497cc345118198508fcf948ff650e8902434.tar.gz php-mozart-92fa497cc345118198508fcf948ff650e8902434.tar.zst php-mozart-92fa497cc345118198508fcf948ff650e8902434.zip | |
fix(licenses): align with Composer's LicensesCommand pipeline
Drive the command from Composer::require() and route the
(installed | locked) branch through the ported PackageSorter,
RepositoryUtils::filterRequiredPackages, and PackageInfo helpers
in mozart-core. --no-dev for installed packages now filters via
root.require closure instead of dev_package_names membership;
text output annotates the name cell with an OSC 8 hyperlink to
the view-source/homepage URL; summary ties resolve in first-seen
order via IndexMap + stable sort_by_key(Reverse(count)) to mirror
PHP's arsort().
Diffstat (limited to 'crates/mozart-core/src/package_info.rs')
| -rw-r--r-- | crates/mozart-core/src/package_info.rs | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/crates/mozart-core/src/package_info.rs b/crates/mozart-core/src/package_info.rs new file mode 100644 index 0000000..d67729a --- /dev/null +++ b/crates/mozart-core/src/package_info.rs @@ -0,0 +1,124 @@ +//! Mirrors `Composer\Util\PackageInfo`. +//! +//! The PHP class exposes two small static helpers that the `licenses`, +//! `show`, `outdated`, and `funding` commands lean on to produce a +//! "view source" link for a package — preferring the explicit +//! `support.source` URL, falling back to the package's `source` URL, +//! and finally the homepage. Empty strings are normalised to `None`. + +/// Minimal contract for the package fields [`view_source_url`] and +/// [`view_source_or_homepage_url`] consult. +pub trait PackageUrls { + /// `support.source` from `composer.json` (mirrors + /// `CompletePackageInterface::getSupport()['source']`). Returns + /// `None` when the support block is absent or the `source` key is + /// missing. + fn support_source(&self) -> Option<&str>; + /// `source.url` (mirrors `PackageInterface::getSourceUrl()`). + fn source_url(&self) -> Option<&str>; + /// `homepage` (mirrors `CompletePackageInterface::getHomepage()`). + fn homepage(&self) -> Option<&str>; +} + +/// Mirror of `PackageInfo::getViewSourceUrl`. +/// +/// PHP returns the support-source URL when it is set and non-empty, +/// otherwise `getSourceUrl()`. Empty strings are treated as absent. +pub fn view_source_url<P: PackageUrls + ?Sized>(package: &P) -> Option<String> { + if let Some(s) = package.support_source().filter(|s| !s.is_empty()) { + return Some(s.to_string()); + } + package + .source_url() + .filter(|s| !s.is_empty()) + .map(String::from) +} + +/// Mirror of `PackageInfo::getViewSourceOrHomepageUrl`. +/// +/// Falls back to the package homepage when no source URL is available. +/// An empty homepage string is normalised to `None`, matching PHP's +/// `if ($url === '') { return null; }` guard. +pub fn view_source_or_homepage_url<P: PackageUrls + ?Sized>(package: &P) -> Option<String> { + view_source_url(package) + .or_else(|| package.homepage().map(String::from)) + .filter(|s| !s.is_empty()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Default)] + struct P { + support_source: Option<String>, + source_url: Option<String>, + homepage: Option<String>, + } + + impl PackageUrls for P { + fn support_source(&self) -> Option<&str> { + self.support_source.as_deref() + } + fn source_url(&self) -> Option<&str> { + self.source_url.as_deref() + } + fn homepage(&self) -> Option<&str> { + self.homepage.as_deref() + } + } + + #[test] + fn prefers_support_source() { + let p = P { + support_source: Some("https://github.com/foo/bar".to_string()), + source_url: Some("https://example.com/repo".to_string()), + ..Default::default() + }; + assert_eq!( + view_source_url(&p).as_deref(), + Some("https://github.com/foo/bar") + ); + } + + #[test] + fn empty_support_source_falls_through_to_source_url() { + let p = P { + support_source: Some(String::new()), + source_url: Some("https://example.com/repo".to_string()), + ..Default::default() + }; + assert_eq!( + view_source_url(&p).as_deref(), + Some("https://example.com/repo") + ); + } + + #[test] + fn falls_back_to_homepage() { + let p = P { + homepage: Some("https://example.com/".to_string()), + ..Default::default() + }; + assert_eq!( + view_source_or_homepage_url(&p).as_deref(), + Some("https://example.com/") + ); + } + + #[test] + fn empty_homepage_is_none() { + let p = P { + homepage: Some(String::new()), + ..Default::default() + }; + assert!(view_source_or_homepage_url(&p).is_none()); + } + + #[test] + fn no_urls_at_all_returns_none() { + let p = P::default(); + assert!(view_source_url(&p).is_none()); + assert!(view_source_or_homepage_url(&p).is_none()); + } +} |
