1use std::borrow::Cow;
8
9#[must_use]
20pub fn normalize_package_separator(module_name: &str) -> Cow<'_, str> {
21 perl_module_name::normalize_package_separator(module_name)
22}
23
24#[must_use]
35pub fn module_name_to_path(module_name: &str) -> String {
36 let normalized = normalize_package_separator(module_name);
37 format!("{}.pm", normalized.replace("::", "/"))
38}
39
40#[must_use]
54pub fn module_path_to_name(module_path: &str) -> String {
55 let normalized = module_path.replace('\\', "/");
56 let without_ext = strip_perl_extension(&normalized);
57 without_ext.replace('/', "::")
58}
59
60#[must_use]
79pub fn file_path_to_module_name(file_path: &str) -> String {
80 let normalized = file_path.replace('\\', "/");
81 let without_ext = strip_perl_extension(&normalized);
82
83 if let Some(relative_module_path) = strip_to_lib_relative_path(without_ext) {
84 return module_path_to_name(relative_module_path);
85 }
86
87 without_ext
88 .rsplit('/')
89 .next()
90 .filter(|segment| !segment.is_empty())
91 .unwrap_or(without_ext)
92 .to_string()
93}
94
95fn strip_to_lib_relative_path(path: &str) -> Option<&str> {
96 if let Some(stripped) = path.strip_prefix("lib/") {
97 return Some(stripped);
98 }
99
100 path.rfind("/lib/").map(|lib_idx| &path[lib_idx + "/lib/".len()..])
101}
102
103fn strip_perl_extension(path: &str) -> &str {
104 if let Some(stripped) = path.strip_suffix(".pm") {
105 stripped
106 } else if let Some(stripped) = path.strip_suffix(".pl") {
107 stripped
108 } else {
109 path
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::{
116 file_path_to_module_name, module_name_to_path, module_path_to_name,
117 normalize_package_separator,
118 };
119
120 #[test]
121 fn normalizes_legacy_package_separator() {
122 assert_eq!(normalize_package_separator("Foo'Bar"), "Foo::Bar");
123 assert_eq!(normalize_package_separator("Foo::Bar"), "Foo::Bar");
124 }
125
126 #[test]
127 fn converts_module_name_to_path() {
128 assert_eq!(module_name_to_path("Foo::Bar"), "Foo/Bar.pm");
129 assert_eq!(module_name_to_path("App::Config::Loader"), "App/Config/Loader.pm");
130 assert_eq!(module_name_to_path("Legacy'Package"), "Legacy/Package.pm");
131 }
132
133 #[test]
134 fn converts_module_path_to_name() {
135 assert_eq!(module_path_to_name("Foo/Bar.pm"), "Foo::Bar");
136 assert_eq!(module_path_to_name("lib/Foo/Bar.pm"), "lib::Foo::Bar");
137 }
138
139 #[test]
140 fn converts_windows_module_path_to_name() {
141 assert_eq!(module_path_to_name(r"Foo\Bar.pm"), "Foo::Bar");
142 assert_eq!(module_path_to_name(r"lib\Foo\Bar.pm"), "lib::Foo::Bar");
143 }
144
145 #[test]
146 fn strips_perl_extensions() {
147 assert_eq!(module_path_to_name("Foo/Bar.pm"), "Foo::Bar");
148 assert_eq!(module_path_to_name("script.pl"), "script");
149 }
150
151 #[test]
152 fn round_trips_common_module_name() {
153 let module = "MyApp::Service::Email";
154 let path = module_name_to_path(module);
155 assert_eq!(module_path_to_name(&path), module);
156 }
157
158 #[test]
159 fn converts_filesystem_path_with_lib_segment_to_module_name() {
160 assert_eq!(file_path_to_module_name("/workspace/lib/Foo/Bar.pm"), "Foo::Bar");
161 assert_eq!(file_path_to_module_name("lib/My/App.pm"), "My::App");
162 }
163
164 #[test]
165 fn converts_windows_filesystem_path_with_lib_segment_to_module_name() {
166 assert_eq!(file_path_to_module_name(r"C:\workspace\lib\Foo\Bar.pm"), "Foo::Bar");
167 }
168
169 #[test]
170 fn falls_back_to_file_stem_when_lib_segment_missing() {
171 assert_eq!(file_path_to_module_name("/workspace/scripts/sync_worker.pl"), "sync_worker");
172 assert_eq!(file_path_to_module_name("MyModule.pm"), "MyModule");
173 }
174}