Skip to content

Commit 4adf47f

Browse files
authored
support multiple zeros as an int (#1269)
1 parent 031fc93 commit 4adf47f

File tree

2 files changed

+49
-3
lines changed

2 files changed

+49
-3
lines changed

src/input/shared.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ fn clean_int_str(mut s: &str) -> Option<Cow<str>> {
115115
s = s.trim();
116116

117117
// strip loading zeros
118-
s = s.trim_start_matches('0');
118+
s = strip_leading_zeros(s)?;
119119

120120
// we don't want to parse as f64 then call `float_as_int` as it can lose precision for large ints, therefore
121121
// we strip `.0+` manually instead
@@ -137,6 +137,37 @@ fn clean_int_str(mut s: &str) -> Option<Cow<str>> {
137137
}
138138
}
139139

140+
/// strip leading zeros from a string, we can't simple use `s.trim_start_matches('0')`, because:
141+
/// - we need to keep one zero if the string is only zeros e.g. `000` -> `0`
142+
/// - we need to keep one zero if the string is a float which is an exact int e.g. `00.0` -> `0.0`
143+
/// - underscores within leading zeros should also be stripped e.g. `0_000` -> `0`, but not `_000`
144+
fn strip_leading_zeros(s: &str) -> Option<&str> {
145+
let mut char_iter = s.char_indices();
146+
match char_iter.next() {
147+
// if we get a leading zero we continue
148+
Some((_, '0')) => (),
149+
// if we get another digit we return the whole string
150+
Some((_, c)) if ('1'..='9').contains(&c) => return Some(s),
151+
// anything else is invalid, we return None
152+
_ => return None,
153+
};
154+
for (i, c) in char_iter {
155+
match c {
156+
// continue on more leading zeros or if we get an underscore we continue - we're "within the number"
157+
'0' | '_' => (),
158+
// any other digit we return the rest of the string
159+
'1'..='9' => return Some(&s[i..]),
160+
// if we get a dot we return the rest of the string but include the last zero
161+
'.' => return Some(&s[(i - 1)..]),
162+
// anything else is invalid, we return None
163+
_ => return None,
164+
}
165+
}
166+
// if the string is all zeros (or underscores), we return the last character
167+
// generally this will be zero, but could be an underscore, which will fail
168+
Some(&s[s.len() - 1..])
169+
}
170+
140171
pub fn float_as_int<'py>(input: &(impl Input<'py> + ?Sized), float: f64) -> ValResult<EitherInt<'py>> {
141172
if float.is_infinite() || float.is_nan() {
142173
Err(ValError::new(ErrorTypeDefaults::FiniteNumber, input))

tests/validators/test_int.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,37 @@
2020
(True, 1),
2121
(0, 0),
2222
('0', 0),
23+
('00', 0),
24+
('000', 0),
25+
('0_000', 0),
2326
(1, 1),
2427
(' 1 ', 1),
2528
(42, 42),
2629
('42', 42),
2730
(42.0, 42),
31+
('0.0', 0),
32+
('00.0', 0),
33+
('00.00', 0),
2834
('42.0', 42),
2935
('42.00', 42),
3036
('042', 42),
37+
('01', 1),
38+
('09', 9),
39+
('00_', Err('Input should be a valid integer, unable to parse string as an integer')),
40+
# next character after 9 is not valid
41+
('0:', Err('Input should be a valid integer, unable to parse string as an integer')),
3142
('4_2', 42),
43+
('0_42', 42),
3244
('4_2.0', 42),
3345
('04_2.0', 42),
3446
(' 04_2.0 ', 42),
35-
# because zeros are striped before underscores this is not allowed
36-
(' 0_42.0 ', Err('Input should be a valid integer, unable to parse string as an integer')),
47+
(' 0_42.0 ', 42),
48+
(' _042.0 ', Err('Input should be a valid integer, unable to parse string as an integer')),
49+
('42_', Err('Input should be a valid integer, unable to parse string as an integer')),
50+
('42_.0', Err('Input should be a valid integer, unable to parse string as an integer')),
3751
('000001', 1),
3852
('123456789.0', 123_456_789),
53+
(' ', Err('Input should be a valid integer, unable to parse string as an integer')),
3954
('1.', Err('Input should be a valid integer, unable to parse string as an integer')),
4055
('42.', Err('Input should be a valid integer, unable to parse string as an integer')),
4156
('123456789123456.00001', Err('Input should be a valid integer, unable to parse string as an integer')),

0 commit comments

Comments
 (0)