1mod change_id;
4mod error;
5mod module_id;
6mod spec_id;
7pub(crate) mod sub_module_id;
8
9pub use change_id::parse_change_id;
10pub use change_id::{ChangeId, ParsedChangeId};
11pub use error::IdParseError;
12pub use module_id::parse_module_id;
13pub use module_id::{ModuleId, ParsedModuleId};
14pub use spec_id::parse_spec_id;
15pub use spec_id::{ParsedSpecId, SpecId};
16pub use sub_module_id::parse_sub_module_id;
17pub use sub_module_id::{ParsedSubModuleId, SubModuleId};
18
19pub(crate) fn is_all_ascii_digits(s: &str) -> bool {
23 if s.is_empty() {
24 return false;
25 }
26 for c in s.chars() {
27 if !c.is_ascii_digit() {
28 return false;
29 }
30 }
31 true
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39pub enum ItoIdKind {
40 ModuleId,
42 SubModuleId,
44 ModuleChangeId,
46 SubModuleChangeId,
48}
49
50pub fn classify_id(input: &str) -> ItoIdKind {
66 let prefix = match input.split_once('_') {
69 Some((left, _)) => left,
70 None => input,
71 };
72
73 let has_dot = prefix.contains('.');
74 let has_hyphen = prefix.contains('-');
75
76 if has_dot && has_hyphen {
77 ItoIdKind::SubModuleChangeId
78 } else if has_dot {
79 ItoIdKind::SubModuleId
80 } else if has_hyphen {
81 ItoIdKind::ModuleChangeId
82 } else {
83 ItoIdKind::ModuleId
84 }
85}
86
87pub fn looks_like_change_id(input: &str) -> bool {
92 let input = input.trim();
93 if input.is_empty() {
94 return false;
95 }
96
97 let mut digit_prefix_len = 0usize;
98 let mut has_hyphen = false;
99 let mut has_underscore = false;
100
101 for ch in input.chars() {
102 if ch.is_ascii_digit() && digit_prefix_len == 0 {
103 digit_prefix_len = 1;
104 continue;
105 }
106
107 if ch.is_ascii_digit() && digit_prefix_len > 0 {
108 digit_prefix_len += 1;
109 continue;
110 }
111
112 if digit_prefix_len == 0 {
113 break;
114 }
115
116 match ch {
117 '-' => has_hyphen = true,
118 '_' => has_underscore = true,
119 '.' => {}
120 _ => {}
121 }
122 }
123
124 digit_prefix_len > 0 && has_hyphen && has_underscore
125}
126
127pub fn looks_like_module_id(input: &str) -> bool {
129 let input = input.trim();
130 let Some(first) = input.chars().next() else {
131 return false;
132 };
133 first.is_ascii_digit()
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn looks_like_change_id_requires_digits_hyphen_and_underscore() {
142 assert!(looks_like_change_id("001-02_hello"));
143 assert!(!looks_like_change_id("-02_hello"));
144 assert!(!looks_like_change_id("001_hello"));
145 assert!(!looks_like_change_id("001-02hello"));
146 assert!(!looks_like_change_id("abc-02_hello"));
147 }
148
149 #[test]
150 fn looks_like_change_id_recognizes_sub_module_format() {
151 assert!(looks_like_change_id("005.01-03_my-change"));
152 assert!(looks_like_change_id("5.1-2_foo"));
153 }
154
155 #[test]
156 fn looks_like_module_id_is_digit_prefixed() {
157 assert!(looks_like_module_id("001"));
158 assert!(looks_like_module_id("001_demo"));
159 assert!(looks_like_module_id(" 001_demo "));
160 assert!(!looks_like_module_id(""));
161 assert!(!looks_like_module_id("demo"));
162 assert!(!looks_like_module_id("_001_demo"));
163 }
164
165 #[test]
166 fn classify_id_module_change_id() {
167 assert_eq!(classify_id("005-01_my-change"), ItoIdKind::ModuleChangeId);
168 assert_eq!(classify_id("1-2_foo"), ItoIdKind::ModuleChangeId);
169 }
170
171 #[test]
172 fn classify_id_sub_module_change_id() {
173 assert_eq!(
174 classify_id("005.01-03_my-change"),
175 ItoIdKind::SubModuleChangeId
176 );
177 assert_eq!(classify_id("5.1-2_foo"), ItoIdKind::SubModuleChangeId);
178 }
179
180 #[test]
181 fn classify_id_sub_module_id() {
182 assert_eq!(classify_id("005.01"), ItoIdKind::SubModuleId);
183 assert_eq!(classify_id("005.01_core-api"), ItoIdKind::SubModuleId);
184 }
185
186 #[test]
187 fn classify_id_module_id() {
188 assert_eq!(classify_id("005"), ItoIdKind::ModuleId);
189 assert_eq!(classify_id("005_dev-tooling"), ItoIdKind::ModuleId);
190 assert_eq!(classify_id("1"), ItoIdKind::ModuleId);
191 }
192
193 #[test]
194 fn classify_id_hyphen_without_underscore_is_module_change_id() {
195 assert_eq!(classify_id("005-01"), ItoIdKind::ModuleChangeId);
199 }
200}