Skip to main content

perl_module_name/
lib.rs

1//! Perl module-name separator normalization and variant helpers.
2//!
3//! This crate has a single responsibility: normalize and project Perl module
4//! names across canonical (`::`) and legacy (`'`) package separator forms.
5
6#![deny(unsafe_code)]
7#![warn(rust_2018_idioms)]
8#![warn(missing_docs)]
9#![warn(clippy::all)]
10
11use std::borrow::Cow;
12
13/// Normalize legacy package separator `'` to canonical `::`.
14///
15/// # Examples
16///
17/// ```
18/// use perl_module_name::normalize_package_separator;
19///
20/// assert_eq!(normalize_package_separator("Foo'Bar"), "Foo::Bar");
21/// assert_eq!(normalize_package_separator("Foo::Bar"), "Foo::Bar");
22/// ```
23#[must_use]
24pub fn normalize_package_separator(module_name: &str) -> Cow<'_, str> {
25    if module_name.contains('\'') {
26        Cow::Owned(module_name.replace('\'', "::"))
27    } else {
28        Cow::Borrowed(module_name)
29    }
30}
31
32/// Project canonical package separator `::` to legacy `'`.
33///
34/// # Examples
35///
36/// ```
37/// use perl_module_name::legacy_package_separator;
38///
39/// assert_eq!(legacy_package_separator("Foo::Bar"), "Foo'Bar");
40/// assert_eq!(legacy_package_separator("Foo'Bar"), "Foo'Bar");
41/// ```
42#[must_use]
43pub fn legacy_package_separator(module_name: &str) -> Cow<'_, str> {
44    if module_name.contains("::") {
45        Cow::Owned(module_name.replace("::", "'"))
46    } else {
47        Cow::Borrowed(module_name)
48    }
49}
50
51/// Build canonical + legacy module rename pairs.
52///
53/// The returned vector always includes the canonical `::` pair. It also
54/// includes the legacy `'` pair when it differs.
55///
56/// # Examples
57///
58/// ```
59/// use perl_module_name::module_variant_pairs;
60///
61/// let variants = module_variant_pairs("Foo::Bar", "New::Path");
62/// assert_eq!(
63///     variants,
64///     vec![
65///         ("Foo::Bar".to_string(), "New::Path".to_string()),
66///         ("Foo'Bar".to_string(), "New'Path".to_string()),
67///     ]
68/// );
69/// ```
70#[must_use]
71pub fn module_variant_pairs(old_module: &str, new_module: &str) -> Vec<(String, String)> {
72    let canonical_old = normalize_package_separator(old_module).into_owned();
73    let canonical_new = normalize_package_separator(new_module).into_owned();
74
75    let canonical = (canonical_old.clone(), canonical_new.clone());
76    let legacy = (
77        legacy_package_separator(&canonical_old).into_owned(),
78        legacy_package_separator(&canonical_new).into_owned(),
79    );
80
81    if legacy == canonical { vec![canonical] } else { vec![canonical, legacy] }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::{legacy_package_separator, module_variant_pairs, normalize_package_separator};
87
88    #[test]
89    fn normalizes_legacy_separator() {
90        assert_eq!(normalize_package_separator("Foo'Bar"), "Foo::Bar");
91        assert_eq!(normalize_package_separator("Foo::Bar"), "Foo::Bar");
92    }
93
94    #[test]
95    fn projects_canonical_separator_to_legacy() {
96        assert_eq!(legacy_package_separator("Foo::Bar"), "Foo'Bar");
97        assert_eq!(legacy_package_separator("Foo'Bar"), "Foo'Bar");
98    }
99
100    #[test]
101    fn builds_canonical_and_legacy_variant_pairs() {
102        assert_eq!(
103            module_variant_pairs("Foo::Bar", "New::Path"),
104            vec![
105                ("Foo::Bar".to_string(), "New::Path".to_string()),
106                ("Foo'Bar".to_string(), "New'Path".to_string()),
107            ]
108        );
109    }
110
111    #[test]
112    fn deduplicates_pair_when_no_separator_variants_exist() {
113        assert_eq!(module_variant_pairs("strict", "warnings").len(), 1);
114    }
115}