trotter 1.0.2

Trotter 🎠 is an experimental crate that aims to make writing Gemini clients fun and easy.
Documentation
use std::fmt::Write;

#[derive(Debug)]
pub enum Symbol {
	Text(String),
	/// (url, text)
	Link(String, String),
	List(String),
	Quote(String),
	Header1(String),
	Header2(String),
	Header3(String),
	/// (alt-text, content)
	Codeblock(String, String),
}

#[derive(Debug)]
pub struct Gemtext(pub Vec<Symbol>);

impl std::fmt::Display for Gemtext {
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
		for s in &self.0 {
			match s {
				Symbol::Text(a) => writeln!(f, "{a}")?,
				Symbol::Link(a, b) => writeln!(f, "=> {a} {b}")?,
				Symbol::List(a) => writeln!(f, "* {a}")?,
				Symbol::Quote(a) => writeln!(f, "> {a}")?,
				Symbol::Header1(a) => writeln!(f, "# {a}")?,
				Symbol::Header2(a) => writeln!(f, "## {a}")?,
				Symbol::Header3(a) => writeln!(f, "### {a}")?,
				Symbol::Codeblock(a, b) => write!(f, "``` {a}\n{b}\n```\n")?,
			}
		}
		Ok(())
	}
}

impl Gemtext {
	pub fn inner(self) -> Vec<Symbol> {
		self.0
	}

	/// Parse a gemtext string into a vector of [`Symbol`].
	pub fn parse(s: &str) -> Self {
		let mut v: Vec<Symbol> = Vec::new();

		let mut lines = s.lines();
		loop {
			let Some(x) = lines.next() else {
				break;
			};

			if let Some(x) = x.strip_prefix("=>") {
				if let Some((link, name)) = x.trim().split_once(' ') {
					v.push(Symbol::Link(link.to_string(), name.trim().to_string()))
				} else {
					v.push(Symbol::Link(x.trim().to_string(), String::new()))
				}
				continue;
			}

			if let Some(x) = x.strip_prefix('*') {
				v.push(Symbol::List(x.trim().to_string()));
				continue;
			}
			if let Some(x) = x.strip_prefix('>') {
				v.push(Symbol::Quote(x.trim().to_string()));
				continue;
			}
			if let Some(x) = x.strip_prefix("###") {
				v.push(Symbol::Header3(x.trim().to_string()));
				continue;
			}
			if let Some(x) = x.strip_prefix("##") {
				v.push(Symbol::Header2(x.trim().to_string()));
				continue;
			}
			if let Some(x) = x.strip_prefix('#') {
				v.push(Symbol::Header1(x.trim().to_string()));
				continue;
			}

			if let Some(x) = x.strip_prefix("```") {
				// Get alt text
				let alt_text = x.trim().to_string();

				// Get block
				let mut block: Vec<&str> = Vec::new();
				loop {
					let Some(x) = lines.next() else {
						break;
					};
					if x.starts_with("```") {
						break;
					}
					block.push(x);
				}
				v.push(Symbol::Codeblock(alt_text, {
					let mut s = block.into_iter().fold(String::new(), |mut out, x| {
						let _ = writeln!(out, "{x}");
						out
					});
					s.pop(); // remove last \n
					s
				}));
				continue;
			}

			v.push(Symbol::Text(x.to_string()));
		}
		Gemtext(v)
	}
}