fml 0.6.5

Friendly Markup Language
Documentation
#![allow(unused)]

mod delimiteds;
mod headings;
mod nestables;
mod scripts;

use std::ops::Not;

use super::{
    Stream,
    macros::{not, slice_till},
};
use crate::{
    ast::{FmlColor, FmlList, FmlValue, FmlValues, ListItem},
    parser::{
        ESCAPABLE_TOKENS, State,
        functions::{
            macros::*,
            styles::{
                delimiteds::delimiteds,
                headings::headings,
                nestables::nestables,
                scripts::{line_scripts, scripts},
            },
        },
    },
};

use winnow::{
    Result,
    ascii::*,
    combinator::{repeat, *},
    error::{ContextError, ParserError, StrContext},
    prelude::*,
    token::*,
};

// = begin reused code =
// Source - https://stackoverflow.com/a/47541878
// Posted by trent, modified by community. See post 'Timeline' for change history
// Retrieved 2026-08-15, License - CC BY-SA 3.0
#[cfg(not(windows))]
const LINE_ENDING: &'static str = "\n";
#[cfg(windows)]
const LINE_ENDING: &'static str = "\r\n";
// = end reused code =

/// Helper for parsing already found scopes
fn inner_fml(scope: &str, state: State) -> Result<FmlValues> {
    let mut stream = Stream {
        input: scope,
        state,
    };
    fml_values(stream)
}
/// Helper for optionally wrapping an FmlValue in colored values
fn wrap_in_colors(mut fml: FmlValue, fg: Option<String>, bg: Option<String>) -> FmlValue {
    if let Some(color) = fg {
        fml = FmlValue::ColorFg(FmlColor {
            color,
            body: vec![fml].into(),
        })
    }
    if let Some(color) = bg {
        fml = FmlValue::ColorBg(FmlColor {
            color,
            body: vec![fml].into(),
        })
    }
    fml
}

fn line_spanning<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
    let is_in_heading = input.state.is_in_heading;
    let is_in_script = input.state.is_in_script;

    alt((
        nestables,
        cond(!is_in_heading, headings).verify_map(|x| x),
        cond(!is_in_script, line_scripts).verify_map(|x| x),
    ))
    .parse_next(input)
}

// The lone Code Block
// also used in section detection, hence it's public
pub fn code_block<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
    let block_end = || preceded(multispace0, "```");

    // not(end) prevents one-word-codeblocks being interpreted as none-codeblocks cuz of a
    // misguided language detection
    let lang =
        terminated(slice_till!(1.., not!(multispace1)), not(block_end())).map(ToString::to_string);

    let body_inner = alt((
        r"\```".value("```"),
        slice_till!(1.., not!(alt((r"\```", block_end())))),
    ));

    // Repetition of "at least once" along with terminating multispace0 prevents blank codeblocks
    let body = repeat_till(1.., body_inner, peek(block_end())).map(|(acc, _term)| acc);
    (
        delimited(
            "```",
            opt(terminated(lang, multispace1)),
            // remove blank lines between start/lang and first non-blank line
            repeat(0.., (space0, line_ending)).map(|()| ()),
        ),
        terminated(body, block_end()),
    )
        .verify(|(_, body): &(Option<String>, String)| !body.trim().is_empty())
        .map(|(lang, body)| FmlValue::CodeBlock { lang, body })
        .parse_next(input)
}

// And once all the styles have been defined

pub fn fml_values<'i>(input: Stream<'i>) -> Result<FmlValues> {
    repeat(1.., alt((styled_text, plain_text)))
        .map(FmlValues)
        .parse(input)
        .map_err(|e| e.into_inner())
}

fn styled_text<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
    let is_sol = input.state.is_sol;
    let is_in_script = input.state.is_in_script;

    alt((
        code_block,
        delimiteds,
        cond(is_sol, line_spanning).verify_map(|x| x),
        cond(!is_in_script, scripts).verify_map(|x| x),
    ))
    .parse_next(input)
}

fn plain_text<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
    fn line_ending_stateful<'i>(input: &mut Stream<'i>) -> Result<&'i str> {
        let in_heading = input.state.is_in_heading;
        let res = (line_ending, opt(peek(line_spanning))).parse_next(input);
        if res.is_ok() {
            input.state.is_sol = true;
        }

        res.map(|(nl, peek)| if peek.is_some() { "" } else { nl })
    }
    fn normal_char<'i>(input: &mut Stream<'i>) -> Result<&'i str> {
        let res = not!(
            esc_identity!(one_of(ESCAPABLE_TOKENS)),
            line_ending,
            styled_text
        )
        .parse_next(input);
        let is_sol = &mut input.state.is_sol;
        if res.is_ok() && *is_sol {
            *is_sol = false;
        }
        res
    }

    // let state: &mut State = &mut input.state;

    let pt = alt((
        normal_char,
        esc_identity!(one_of(ESCAPABLE_TOKENS)),
        line_ending_stateful,
    ));

    (
        // since plaintext gets called once everything else has failed,
        // we can be sure the first char aint a style
        any,
        repeat(0.., pt).fold(String::new, |mut acc, new| {
            acc.push_str(new);
            acc
        }),
    )
        .map(|(ch, mut s): (char, String)| {
            s.insert(0, ch);
            FmlValue::Text(s)
        })
        .parse_next(input)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn str2stream<'i>(input: &'i str) -> Stream<'i> {
        Stream {
            input,
            state: State::default(),
        }
    }

    #[cfg(test)]
    mod delimited {
        use super::*;

        #[test]
        fn normal() {
            let mut inp = r"*styled text*";
            let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
            let exp = "styled text".to_string();
            assert_eq!(res.unwrap(), exp);
        }

        #[test]
        fn escaped() {
            let mut inp = r"*styled \* line \of t\\ext*";
            let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
            let exp = r"styled * line \of t\ext".to_string();
            assert_eq!(res.unwrap(), exp);
        }

        #[test]
        fn unescaped() {
            let mut inp = r"*styled text\\*";
            let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
            let exp = r"styled text\".to_string();
            assert_eq!(res.unwrap(), exp);
        }

        #[cfg(test)]
        mod fails {
            use super::*;

            #[test]
            fn empty() {
                let mut inp = r"**";
                let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
                assert!(res.is_err());
            }

            #[test]
            fn blank() {
                let mut inp = r"* 	*";
                let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
                assert!(res.is_err());
            }

            #[test]
            fn blank_multiline() {
                let mut inp = r"* 	
					*";
                let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
                assert!(res.is_err());
            }

            #[test]
            fn escape_start() {
                let mut inp = r"\*styled text*";
                let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
                assert!(res.is_err());
            }

            #[test]
            fn escape_end() {
                let mut inp = r"*styled text\*";
                let res: Result<String> = delimited_esc!("*").parse_next(&mut inp);
                assert!(res.is_err());
            }
        }
    }

    #[cfg(test)]
    mod code_block {
        use super::*;

        #[test]
        fn one_line() {
            let inp = r"```javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()```";
            let res = code_block.parse(str2stream(inp));
            let exp = FmlValue::CodeBlock {
                lang: None,
                body:
                    r"javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()"
                        .to_string(),
            };
            assert_eq!(res.unwrap(), exp);
        }

        #[test]
        fn one_line_lang() {
            let inp = r"```java javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()```";
            let res = code_block.parse(str2stream(inp));
            let exp = FmlValue::CodeBlock {
                lang: Some("java".to_string()),
                body:
                    r"javax.util.AbstractShortestJavaClassPath.MandatoryGlobalState.doSomething()"
                        .to_string(),
            };
            assert_eq!(res.unwrap(), exp);
        }

        #[test]
        fn multiline() {
            let inp = r#"```
fn main() {
    println!("Hello, world!");
}
```"#;
            let res = code_block.parse(str2stream(inp));
            let exp = FmlValue::CodeBlock {
                lang: None,
                body: r#"fn main() {
    println!("Hello, world!");
}"#
                .to_string(),
            };
            assert_eq!(res.unwrap(), exp);
        }

        #[test]
        fn multiline_lang() {
            let inp = r#"```rust
fn main() {
    println!("Hello, world!");
}
```"#;
            let res = code_block.parse(str2stream(inp));
            let exp = FmlValue::CodeBlock {
                lang: Some("rust".to_string()),
                body: r#"fn main() {
    println!("Hello, world!");
}"#
                .to_string(),
            };
            assert_eq!(res.unwrap(), exp);
        }

        mod fails {
            use super::*;

            #[test]
            fn empty() {
                let inp = "``````";
                let res = code_block.parse(str2stream(inp));
                assert!(res.is_err());
            }

            #[test]
            fn blank() {
                let inp = "```	  	 ```";
                let res = code_block.parse(str2stream(inp));
                eprintln!("{res:#?}");
                assert!(res.is_err());
            }

            #[test]
            fn blank_multiline() {
                let inp = r"```

					```";
                let res = code_block.parse(str2stream(inp));
                eprintln!("{res:#?}");
                assert!(res.is_err());
            }
        }

        // KUUUUUUUUURRWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
        // note to self: repeat_till applies middle parser BEFORE checking with the third
        // #[test]
        // fn fuck() {
        // 	use winnow::combinator::repeat_till;

        // 	fn parser<'i>(s: &mut &'i str) -> Result<String> {
        // 		repeat_till(1.., any, "e").map(|(s, _)| s).parse_next(s)
        // 	}

        //	assert!(parser.parse_peek("e").is_err());
        //  assert!(parser.parse_peek("ae").is_ok());
        // 	assert!(parser.parse("ee").is_err()); // FAILS!!!
        // }
    }
}