use nom::IResult;
use nom_locate::LocatedSpan;
pub type Span<'a> = LocatedSpan<&'a str>;
pub fn count_indentation(input: &str) -> usize {
let mut spaces = 0;
for ch in input.chars() {
match ch {
' ' => spaces += 1,
'\t' => spaces += 4 - (spaces % 4), _ => break,
}
}
spaces
}
pub fn skip_indentation(input: Span, max_spaces: usize) -> IResult<Span, usize> {
let mut spaces: usize = 0;
let mut bytes: usize = 0;
for ch in input.fragment().chars() {
if spaces >= max_spaces {
break;
}
match ch {
' ' => {
spaces += 1;
bytes += 1;
}
'\t' => {
let tab_width = 4 - (spaces % 4);
if spaces + tab_width <= max_spaces {
spaces += tab_width;
bytes += 1;
} else {
break;
}
}
_ => break,
}
}
if bytes == 0 {
return Err(nom::Err::Error(nom::error::Error::new(
input,
nom::error::ErrorKind::Space,
)));
}
use nom::bytes::complete::take;
use nom::Parser;
let (remaining, _skipped) = take(bytes).parse(input)?;
Ok((remaining, spaces))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_test_count_indentation() {
assert_eq!(count_indentation(" text"), 4);
assert_eq!(count_indentation("\ttext"), 4);
assert_eq!(count_indentation(" \ttext"), 4);
assert_eq!(count_indentation(" \ttext"), 4);
assert_eq!(count_indentation(" \ttext"), 4);
assert_eq!(count_indentation("text"), 0);
assert_eq!(count_indentation(""), 0);
}
#[test]
fn smoke_test_skip_indentation() {
let input = Span::new(" text");
let result = skip_indentation(input, 4);
assert!(result.is_ok());
let (remaining, spaces) = result.unwrap();
assert_eq!(spaces, 4);
assert_eq!(*remaining.fragment(), "text");
}
#[test]
fn smoke_test_skip_indentation_with_tab() {
let input = Span::new("\ttext");
let result = skip_indentation(input, 4);
assert!(result.is_ok());
let (remaining, spaces) = result.unwrap();
assert_eq!(spaces, 4);
assert_eq!(*remaining.fragment(), "text");
}
#[test]
fn smoke_test_skip_indentation_partial() {
let input = Span::new(" text");
let result = skip_indentation(input, 4);
assert!(result.is_ok());
let (remaining, spaces) = result.unwrap();
assert_eq!(spaces, 4);
assert_eq!(*remaining.fragment(), " text");
}
}