logparse 0.1.1

parse arbitrary messages containing rust-like debug output to syntax highlight them
Documentation
use super::ast::*;
use crate::parse_input;
use crate::{Config, into_spans};
use proptest::prelude::*;
use proptest::proptest;

proptest! {
    #[test]
    fn proptest_from_segments(original in Segments::arb(Token::arb())) {
        let stringified = original.to_string();
        let parsed = parse_input(&stringified).unwrap();
        let stringified_again = parsed.to_string();
        assert_eq!(stringified, stringified_again, "parsed: `{parsed:#?}`");
    }

    #[test]
    fn proptest_from_random_text(original in ".*") {
        let parsed = parse_input(&original).unwrap();
        let stringified_again = parsed.to_string();
        assert_eq!(original, stringified_again, "parsed: `{parsed:#?}`");
    }

    #[test]
    fn proptest_into_spans(original in Segments::arb(Token::arb())) {
        let stringified = original.to_string();
        let spans = into_spans(original, Config {collapse_space: false});
        let spans_concatenated = spans.into_iter().map(|i| i.text).collect::<String>();
        assert_eq!(stringified, spans_concatenated);
    }
}

impl AnyString<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        let prefix = "\\w*";
        let ty = any::<QuoteType>();
        let contents = "\\w*";
        let num_hashtags = 0usize..3;
        let suffix = "\\w*";

        (prefix, ty, contents, num_hashtags, suffix).prop_map(
            |(prefix, ty, contents, num_hashtags, suffix)| Self {
                prefix: prefix.into(),
                ty,
                contents: contents.into(),
                num_hashtags,
                suffix: suffix.into(),
            },
        )
    }
}

impl PathSegment<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        (any::<PathSep>(), "\\w*").prop_map(|(leading_separator, segment)| Self {
            leading_separator,
            segment: segment.into(),
        })
    }
}

impl FileLocation<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        use proptest::option::*;
        ("[0-9]{0,4}", of("[0-9]{0,4}")).prop_map(|(line, offset)| Self {
            line: line.into(),
            offset: offset.map(Into::into),
        })
    }
}

impl Space<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        " *".prop_map(|spaces| Self(spaces.into()))
    }
}

impl FileName<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        use proptest::option::*;
        (
            any::<PathSep>(),
            "\\w*",
            of(".{0,3}"),
            of(FileLocation::arb()),
        )
            .prop_map(
                |(leading_separator, segment, ext_excluding_dot, location)| Self {
                    leading_separator,
                    segment: segment.into(),
                    ext_excluding_dot: ext_excluding_dot.map(Into::into),
                    location,
                },
            )
    }
}

impl Path<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        use proptest::{char::*, collection::*, option::*};
        (
            of(range('A', 'Z')),
            vec(PathSegment::arb(), 0..3),
            FileName::arb(),
        )
            .prop_map(|(drive_excluding_colon, segments, filename)| Self {
                drive_excluding_colon,
                segments,
                filename,
            })
    }
}

impl Number<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        prop_oneof![
            any::<i64>().prop_map(|number| Self(number.to_string().into())),
            any::<f64>().prop_map(|number| Self(number.to_string().into()))
        ]
    }
}

impl Atom<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        "[a-zA-Z]+".prop_map(|i| Self::Text(i.into()))
    }
}

impl Token<'static> {
    #[cfg(test)]
    fn arb() -> impl Strategy<Value = Self> {
        let leaf = prop_oneof![
            Just(Self::True),
            Just(Self::False),
            Just(Self::None),
            Path::arb().prop_map(Self::Path),
            AnyString::arb().prop_map(Self::String),
            Number::arb().prop_map(Self::Number),
            Atom::arb().prop_map(Self::Atom),
        ];

        leaf.prop_recursive(4, 64, 16, |token| {
            Delimited::arb(token).prop_map(Self::Delimited).boxed()
        })
    }
}

impl Delimited<'static> {
    #[cfg(test)]
    fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
        use proptest::option::*;
        (
            of((Atom::arb(), Space::arb())),
            any::<Delimiter>(),
            Segments::arb(token),
        )
            .prop_map(|(prefix, delimiter, contents)| Self {
                prefix,
                delimiter,
                contents,
            })
    }
}

impl Segment<'static> {
    #[cfg(test)]
    fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
        (Space::arb(), token).prop_map(|(leading_space, token)| Self {
            leading_space,
            token,
        })
    }
}

impl Segments<'static> {
    #[cfg(test)]
    fn arb(token: impl Strategy<Value = Token<'static>>) -> impl Strategy<Value = Self> {
        use proptest::collection::*;

        (vec(Segment::arb(token), 1..10), Space::arb()).prop_map(|(segments, trailing_space)| {
            Self {
                segments,
                trailing_space,
            }
        })
    }
}