diff --git a/Cargo.toml b/Cargo.toml index 173a0cf..9d5f365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -156,7 +156,6 @@ as_pointer_underscore = "warn" as_underscore = "warn" default_numeric_fallback = "warn" deref_by_slicing = "warn" -else_if_without_else = "warn" empty_drop = "warn" empty_enum_variants_with_brackets = "warn" error_impl_error = "warn" @@ -198,7 +197,6 @@ semicolon_outside_block = "warn" shadow_reuse = "warn" shadow_same = "warn" shadow_unrelated = "warn" -single_char_lifetime_names = "warn" string_add = "warn" string_lit_chars_any = "warn" unnecessary_self_imports = "warn" diff --git a/src/handlers/graph.rs b/src/handlers/graph.rs index d8120f3..5f4f90e 100644 --- a/src/handlers/graph.rs +++ b/src/handlers/graph.rs @@ -16,7 +16,9 @@ pub async fn node(Path(id): Path) -> Response { context.insert("incoming", &graph.incoming.get(&id)); let escaped_text = tera::escape_html(&node.text); - let out_text = crate::syntax::content::parse(&escaped_text); + let out_text = crate::syntax::content::parse(&crate::syntax::content::lex( + &escaped_text, + )); context.insert("text", &out_text); let not_found = node.clone() == empty_node; diff --git a/src/syntax/content.rs b/src/syntax/content.rs index 3dba7c9..c218e08 100644 --- a/src/syntax/content.rs +++ b/src/syntax/content.rs @@ -1,57 +1,149 @@ -use std::fmt::Write as _; -use crate::dev::log; +struct Lexeme<'l> { + pub raw: &'l str, + pub first: &'l str, +} -pub fn parse(text: &str) -> String { +impl<'l> Lexeme<'l> { + pub fn new(text: &'l str) -> Lexeme<'l> { + let vec: Vec<&'l str> = text.split(" ").collect(); + + Self { + raw: text, + first: vec.first().unwrap_or_else(|| unreachable!()), + } + } +} + +pub enum Token { + Paragraph(paragraph::Paragraph), + Header(header::Header), +} + +pub fn lex(text: &str) -> Vec { + let mut tokens = Vec::new(); + + for line in text + .lines() + .filter(|x| !x.is_empty()) + .filter(|x| !x.replace(" ", "").is_empty()) + { + let lexeme = Lexeme::new(line); + if header::matches(&lexeme) { + tokens.push(Token::Header(header::lex(&lexeme))); + } else if paragraph::matches(&lexeme) { + tokens.push(Token::Paragraph(paragraph::lex(&lexeme))); + } + } + + tokens +} + +pub fn parse(tokens: &Vec) -> String { let mut out_text: Vec = Vec::new(); - - for line in text.lines() { - if line.is_empty() || line.replace(" ", "").is_empty() { - continue; - } - - let mut out_line: String = line.to_owned(); - let words: Vec = line.split(" ").map(str::to_string).collect(); - let first_word: &String = - words.first().unwrap_or_else(|| unreachable!()); - - if is_header(first_word) { - out_line = parse_header(&out_line, first_word); - } - // if not special, default to treating line as a paragraph - else { - out_line.insert_str(0, "

"); - out_line.push_str("

"); - } - - out_text.push(out_line); + for token in tokens { + out_text.push(match token { + Token::Paragraph(p) => p.to_string(), + Token::Header(h) => h.to_string(), + }); } out_text.join("\n") } -fn is_header(lexeme: &str) -> bool { - !lexeme.trim().is_empty() - && lexeme.replace("#", "").is_empty() - && lexeme.len() <= 6 +mod paragraph { + use std::fmt::Display; + use super::Lexeme; + + pub struct Paragraph { + text: String, + } + + impl Display for Paragraph { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "

{}

", &self.text) + } + } + + pub fn matches(lexeme: &Lexeme) -> bool { + !lexeme.raw.trim().is_empty() + } + + pub fn lex(lexeme: &Lexeme) -> Paragraph { + Paragraph { + text: lexeme.raw.trim().to_owned(), + } + } } -fn parse_header(line: &str, first_word: &str) -> String { - log(&parse_header, &format!("Parsing: {line:?}")); +mod header { + use crate::dev::log; + use std::fmt::Display; + use super::Lexeme; - let header_level = first_word.len(); - log(&parse, &format!("Header level is {header_level}")); - let header_text = line.to_owned().replace(first_word, ""); - let mut w = String::with_capacity(header_text.len().strict_add(9)); - let alloc = w.capacity(); - match write!(w, "{header_text}") { - Ok(()) => (), - Err(e) => panic!("{e:?}"), + enum Level { + One, + Two, + Three, + Four, + Five, + Six, } - if alloc != w.capacity() { - log( - &parse_header, - &format!("w reallocated to {} despite prediction", w.capacity()), - ); + + impl Display for Level { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match *self { + Level::One => write!(f, "1"), + Level::Two => write!(f, "2"), + Level::Three => write!(f, "3"), + Level::Four => write!(f, "4"), + Level::Five => write!(f, "5"), + Level::Six => write!(f, "6"), + } + } + } + + pub struct Header { + level: Level, + text: String, + } + + impl Header { + fn new(level: usize, text: &str) -> Self { + Self { + level: match level { + 1 => Level::One, + 2 => Level::Two, + 3 => Level::Three, + 4 => Level::Four, + 5 => Level::Five, + 6 => Level::Six, + _ => panic!( + "Attempted to construct a header with invalid level" + ), + }, + text: text.to_owned(), + } + } + } + + impl Display for Header { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", &self.level, self.text, &self.level) + } + } + + pub fn matches(lexeme: &Lexeme) -> bool { + !lexeme.first.trim().is_empty() + && lexeme.first.replace("#", "").is_empty() + && lexeme.first.len() <= 6 + } + + pub fn lex(lexeme: &Lexeme) -> Header { + let header_level = lexeme.first.len(); + log(&lex, &format!("Header level is {header_level}")); + + let header_text = lexeme.raw.replace(lexeme.first, ""); + + Header::new(header_level, &header_text) } - w }