1mod change_id;
4mod error;
5mod module_id;
6mod spec_id;
7
8pub use change_id::parse_change_id;
9pub use change_id::{ChangeId, ParsedChangeId};
10pub use error::IdParseError;
11pub use module_id::parse_module_id;
12pub use module_id::{ModuleId, ParsedModuleId};
13pub use spec_id::parse_spec_id;
14pub use spec_id::{ParsedSpecId, SpecId};
15
16pub fn looks_like_change_id(input: &str) -> bool {
18 let input = input.trim();
19 if input.is_empty() {
20 return false;
21 }
22
23 let mut digit_prefix_len = 0usize;
24 let mut has_hyphen = false;
25 let mut has_underscore = false;
26
27 for ch in input.chars() {
28 if ch.is_ascii_digit() && digit_prefix_len == 0 {
29 digit_prefix_len = 1;
30 continue;
31 }
32
33 if ch.is_ascii_digit() && digit_prefix_len > 0 {
34 digit_prefix_len += 1;
35 continue;
36 }
37
38 if digit_prefix_len == 0 {
39 break;
40 }
41
42 match ch {
43 '-' => has_hyphen = true,
44 '_' => has_underscore = true,
45 _ => {}
46 }
47 }
48
49 digit_prefix_len > 0 && has_hyphen && has_underscore
50}
51
52pub fn looks_like_module_id(input: &str) -> bool {
54 let input = input.trim();
55 let Some(first) = input.chars().next() else {
56 return false;
57 };
58 first.is_ascii_digit()
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64
65 #[test]
66 fn looks_like_change_id_requires_digits_hyphen_and_underscore() {
67 assert!(looks_like_change_id("001-02_hello"));
68 assert!(!looks_like_change_id("-02_hello"));
69 assert!(!looks_like_change_id("001_hello"));
70 assert!(!looks_like_change_id("001-02hello"));
71 assert!(!looks_like_change_id("abc-02_hello"));
72 }
73
74 #[test]
75 fn looks_like_module_id_is_digit_prefixed() {
76 assert!(looks_like_module_id("001"));
77 assert!(looks_like_module_id("001_demo"));
78 assert!(looks_like_module_id(" 001_demo "));
79 assert!(!looks_like_module_id(""));
80 assert!(!looks_like_module_id("demo"));
81 assert!(!looks_like_module_id("_001_demo"));
82 }
83}