factorio_frontend/
paths.rs1use std::path::{Path, PathBuf};
2
3#[must_use]
7pub fn module_name_from_source(source_dir: &Path, source_path: &Path) -> Option<String> {
8 let relative = source_path.strip_prefix(source_dir).ok()?;
9
10 if relative.file_name().is_some_and(|name| name == "lib.rs") {
11 return None;
12 }
13
14 if relative.file_name().is_some_and(|name| name == "mod.rs") {
15 let parent = relative.parent()?;
16 if parent.as_os_str().is_empty() {
17 return None;
18 }
19 return Some(path_to_module_name(parent));
20 }
21
22 Some(path_to_module_name(&relative.with_extension("")))
23}
24
25fn path_to_module_name(path: &Path) -> String {
26 path.iter()
27 .map(|component| component.to_string_lossy().into_owned())
28 .collect::<Vec<_>>()
29 .join(".")
30}
31
32#[must_use]
34pub fn lua_output_path(output_dir: &Path, module_name: &str) -> PathBuf {
35 let mut path = output_dir.to_path_buf();
36 for segment in module_name.split('.') {
37 path.push(segment);
38 }
39 path.set_extension("lua");
40 path
41}
42
43#[must_use]
45pub fn require_local_name(module_path: &str) -> String {
46 module_path.replace('.', "_")
47}
48
49#[allow(clippy::indexing_slicing)]
51pub fn split_crate_path(segments: &[String]) -> (String, Vec<String>) {
52 if segments.is_empty() {
53 return (String::new(), Vec::new());
54 }
55
56 let mut module_parts = Vec::new();
57 let mut item_start = segments.len();
58
59 for (index, segment) in segments.iter().enumerate() {
60 if starts_with_uppercase(segment) {
61 item_start = index;
62 break;
63 }
64 module_parts.push(segment.as_str());
65 }
66
67 if module_parts.is_empty() {
68 module_parts.push(segments[0].as_str());
69 item_start = 1;
70 }
71
72 (module_parts.join("."), segments[item_start..].to_vec())
73}
74
75fn starts_with_uppercase(value: &str) -> bool {
76 value.chars().next().is_some_and(char::is_uppercase)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn maps_nested_source_paths_to_module_names() {
85 let source_dir = Path::new("/project/src");
86
87 assert_eq!(
88 module_name_from_source(source_dir, Path::new("/project/src/player/extra_info.rs")),
89 Some("player.extra_info".to_string())
90 );
91 assert_eq!(
92 module_name_from_source(source_dir, Path::new("/project/src/player/mod.rs")),
93 Some("player".to_string())
94 );
95 }
96
97 #[test]
98 fn splits_crate_paths_into_module_and_item_segments() {
99 assert_eq!(
100 split_crate_path(&[
101 "player".to_string(),
102 "extra_info".to_string(),
103 "MyType".to_string(),
104 ]),
105 ("player.extra_info".to_string(), vec!["MyType".to_string()])
106 );
107 assert_eq!(
108 split_crate_path(&[
109 "player".to_string(),
110 "MyPlayer".to_string(),
111 "new".to_string(),
112 ]),
113 (
114 "player".to_string(),
115 vec!["MyPlayer".to_string(), "new".to_string()]
116 )
117 );
118 assert_eq!(
119 split_crate_path(&["player".to_string(), "extra_info".to_string()]),
120 ("player.extra_info".to_string(), Vec::new())
121 );
122 }
123}