compose_lens/model/
cpu_count.rs1#[derive(Debug, Clone, PartialEq, Eq)]
5#[non_exhaustive]
6pub enum CpuCount {
7 YamlInteger(String),
9 String(String),
11 NegativeYamlInteger(String),
13}
14
15impl CpuCount {
16 pub(crate) fn yaml_integer(raw: String) -> Self {
17 if raw.starts_with('-') && !negative_integer_is_zero(&raw) {
18 Self::NegativeYamlInteger(raw)
19 } else {
20 Self::YamlInteger(raw)
21 }
22 }
23
24 #[must_use]
26 pub const fn is_valid(&self) -> bool {
27 !matches!(self, Self::NegativeYamlInteger(_))
28 }
29
30 pub(crate) fn yaml_integer_spelling(value: &str) -> bool {
31 let digits = value
32 .strip_prefix('+')
33 .or_else(|| value.strip_prefix('-'))
34 .unwrap_or(value);
35 let (radix, digits) = if let Some(value) = digits.strip_prefix("0b") {
36 (2, value)
37 } else if let Some(value) = digits.strip_prefix("0o") {
38 (8, value)
39 } else if let Some(value) = digits.strip_prefix("0x") {
40 (16, value)
41 } else {
42 (10, digits)
43 };
44 let mut saw_digit = false;
45 let mut previous_separator = false;
46 for byte in digits.bytes() {
47 if byte == b'_' {
48 if !saw_digit || previous_separator {
49 return false;
50 }
51 previous_separator = true;
52 } else if if radix == 16 {
53 byte.is_ascii_hexdigit()
54 } else {
55 byte.is_ascii_digit() && (byte - b'0') < radix
56 } {
57 saw_digit = true;
58 previous_separator = false;
59 } else {
60 return false;
61 }
62 }
63 saw_digit && !previous_separator
64 }
65}
66
67fn negative_integer_is_zero(value: &str) -> bool {
68 let Some(value) = value.strip_prefix('-') else {
69 return false;
70 };
71 let digits = value
72 .strip_prefix("0b")
73 .or_else(|| value.strip_prefix("0o"))
74 .or_else(|| value.strip_prefix("0x"))
75 .unwrap_or(value);
76 digits.bytes().all(|byte| matches!(byte, b'0' | b'_'))
77}
78
79#[cfg(test)]
80mod tests {
81 use super::CpuCount;
82
83 #[test]
84 fn retains_unbounded_integer_spelling_and_negative_zero() {
85 for value in [
86 "0",
87 "-0",
88 "-0x0",
89 "0b1_0",
90 "0o7_7",
91 "0xCA_FE",
92 "999999999999999999999999999999",
93 ] {
94 assert!(CpuCount::yaml_integer(value.to_owned()).is_valid());
95 assert!(CpuCount::yaml_integer_spelling(value));
96 }
97 assert!(matches!(
98 CpuCount::yaml_integer("-1".to_owned()),
99 CpuCount::NegativeYamlInteger(value) if value == "-1"
100 ));
101 for value in ["1.0", "1e3", "0x_1", "1_"] {
102 assert!(!CpuCount::yaml_integer_spelling(value));
103 }
104 }
105}