#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SpacingBehavior {
Strict,
#[default]
Loose,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum DelimiterStrategy {
#[default]
FirstEquals,
PreferSpaced,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TabBehavior {
#[default]
Preserve,
ToSpaces,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CrlfBehavior {
#[default]
Preserve,
NormalizeToLf,
}
#[derive(Debug, Clone, Default)]
pub struct ParserOptions {
pub spacing: SpacingBehavior,
pub tabs: TabBehavior,
pub crlf: CrlfBehavior,
pub delimiter: DelimiterStrategy,
}
impl ParserOptions {
pub fn new() -> Self {
Self::default()
}
pub fn permissive() -> Self {
Self {
spacing: SpacingBehavior::Loose,
tabs: TabBehavior::ToSpaces,
crlf: CrlfBehavior::NormalizeToLf,
delimiter: DelimiterStrategy::PreferSpaced,
}
}
pub fn with_spacing(mut self, spacing: SpacingBehavior) -> Self {
self.spacing = spacing;
self
}
pub fn with_tabs(mut self, tabs: TabBehavior) -> Self {
self.tabs = tabs;
self
}
pub fn with_crlf(mut self, crlf: CrlfBehavior) -> Self {
self.crlf = crlf;
self
}
pub fn with_delimiter(mut self, delimiter: DelimiterStrategy) -> Self {
self.delimiter = delimiter;
self
}
pub(crate) fn is_strict_spacing(&self) -> bool {
matches!(self.spacing, SpacingBehavior::Strict)
}
pub(crate) fn prefer_spaced_delimiter(&self) -> bool {
matches!(self.delimiter, DelimiterStrategy::PreferSpaced)
}
pub(crate) fn preserve_tabs(&self) -> bool {
matches!(self.tabs, TabBehavior::Preserve)
}
pub(crate) fn preserve_crlf(&self) -> bool {
matches!(self.crlf, CrlfBehavior::Preserve)
}
pub(crate) fn process_tabs<'a>(&self, s: &'a str) -> std::borrow::Cow<'a, str> {
if self.preserve_tabs() {
std::borrow::Cow::Borrowed(s)
} else {
std::borrow::Cow::Owned(s.replace('\t', " "))
}
}
pub(crate) fn process_crlf<'a>(&self, s: &'a str) -> std::borrow::Cow<'a, str> {
if self.preserve_crlf() {
std::borrow::Cow::Borrowed(s)
} else {
std::borrow::Cow::Owned(s.replace("\r\n", "\n"))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_options() {
let opts = ParserOptions::new();
assert!(!opts.is_strict_spacing());
assert!(opts.preserve_tabs());
assert!(opts.preserve_crlf());
}
#[test]
fn test_permissive_options() {
let opts = ParserOptions::permissive();
assert!(!opts.is_strict_spacing());
assert!(!opts.preserve_tabs());
assert!(!opts.preserve_crlf());
}
#[test]
fn test_builder_pattern() {
let opts = ParserOptions::new()
.with_spacing(SpacingBehavior::Loose)
.with_tabs(TabBehavior::ToSpaces);
assert!(!opts.is_strict_spacing());
assert!(!opts.preserve_tabs());
assert!(opts.preserve_crlf());
}
#[test]
fn test_process_tabs_preserve() {
let opts = ParserOptions::new(); let input = "hello\tworld";
let result = opts.process_tabs(input);
assert_eq!(result, "hello\tworld");
}
#[test]
fn test_process_tabs_to_spaces() {
let opts = ParserOptions::new().with_tabs(TabBehavior::ToSpaces);
let input = "hello\tworld";
let result = opts.process_tabs(input);
assert_eq!(result, "hello world");
}
#[test]
fn test_process_tabs_multiple() {
let opts = ParserOptions::new().with_tabs(TabBehavior::ToSpaces);
let input = "\t\tindented\ttext\t";
let result = opts.process_tabs(input);
assert_eq!(result, " indented text ");
}
#[test]
fn test_process_crlf_preserve() {
let opts = ParserOptions::new(); let input = "line1\r\nline2";
let result = opts.process_crlf(input);
assert_eq!(result, "line1\r\nline2");
}
#[test]
fn test_process_crlf_normalize() {
let opts = ParserOptions::new().with_crlf(CrlfBehavior::NormalizeToLf);
let input = "line1\r\nline2\r\nline3";
let result = opts.process_crlf(input);
assert_eq!(result, "line1\nline2\nline3");
}
#[test]
fn test_process_crlf_mixed_endings() {
let opts = ParserOptions::new().with_crlf(CrlfBehavior::NormalizeToLf);
let input = "line1\r\nline2\nline3\r\n";
let result = opts.process_crlf(input);
assert_eq!(result, "line1\nline2\nline3\n");
}
}