commit 5496a7064ceb413f7fbcb6406d560b8341dc2349 Author: mars Date: Sat Oct 8 22:52:24 2022 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..df4c94b --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,280 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "lexpr" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceee0b80e0043f17bf81130471e1b0975179af75fe657af45577d80e2698fe3b" +dependencies = [ + "itoa", + "lexpr-macros", + "proc-macro-hack", + "ryu", +] + +[[package]] +name = "lexpr-macros" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd627fb38e19c00d8d068618259205f7a91c91aeade5c15bc35dbca037bb1c35" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", +] + +[[package]] +name = "libc" +version = "0.2.134" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "329c933548736bc49fd575ee68c89e8be4d260064184389a5b77517cddd99ffb" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro2" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fcd952facd492f9be3ef0d0b7032a6e442ee9b361d4acc2b1d0c4aaa5f613a1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "textwrap" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + +[[package]] +name = "tml" +version = "0.1.0" +dependencies = [ + "ansi_term", + "lexpr", + "strum", + "textwrap", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-linebreak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" +dependencies = [ + "hashbrown", + "regex", +] + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..aaa614b --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "tml" +version = "0.1.0" +edition = "2021" + +[dependencies] +lexpr = "0.2" +strum = { version = "0.24", features = ["derive"] } +ansi_term = "0.12" +textwrap = "0.15" diff --git a/assets/simple.sex b/assets/simple.sex new file mode 100644 index 0000000..f8f72ba --- /dev/null +++ b/assets/simple.sex @@ -0,0 +1 @@ +((h1 "Hello world!")) diff --git a/assets/test.sex b/assets/test.sex new file mode 100644 index 0000000..c005ffe --- /dev/null +++ b/assets/test.sex @@ -0,0 +1,40 @@ +( +(h1 "Hello world!") + +(p "Text goes here.") + +(h2 "Heading 2") + +(p "Text +spread +across +multiple +lines.") + +(h3 "Heading 3") + +(p + "Some text can be " + (italic "italicized") + " and some text can be " + (bold "emboldened") ". " + "Other text can be " + (underline "underlined") ". " + "If you want to write text " + "but pretend that you didn't, " + "you can " + (strikethrough "strike it out") ".") + +(p + "You can even do " + (strikethrough (bold (italic (underline "EVERYTHING AT ONCE")))) + ".") + +(ul "Unordered list entry") +(ul "Unordered list entry") +(ul "Unordered list entry") + +(ol "List entry 1") +(ol "List entry 2") +(ol "List entry 3") +) diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f77e409 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,208 @@ +use ansi_term::Style; +use std::str::FromStr; +use strum::EnumString; + +#[derive(Clone, Debug)] +pub struct Stylesheet { + pub h1: Style, + pub h2: Style, + pub h3: Style, + pub h1_prefix: String, + pub h2_prefix: String, + pub h3_prefix: String, + pub p: Style, + pub underline: Style, + pub bold: Style, + pub italic: Style, + pub strikethrough: Style, + pub bullet: Style, + pub ul_prefix: String, + pub list_indent: usize, +} + +impl Default for Stylesheet { + fn default() -> Self { + use ansi_term::Color::*; + + let header_style = Blue.bold().underline(); + + Self { + h1: header_style, + h2: header_style, + h3: header_style, + h1_prefix: "# ".to_string(), + h2_prefix: "## ".to_string(), + h3_prefix: "### ".to_string(), + p: Style::new(), + underline: Style::new().underline(), + bold: Style::new().bold(), + italic: Style::new().italic(), + strikethrough: Style::new().strikethrough(), + bullet: Black.into(), + ul_prefix: "* ".to_string(), + list_indent: 4, + } + } +} + +#[derive(Clone, Copy, Debug, EnumString)] +#[strum(serialize_all = "kebab-case", ascii_case_insensitive)] +pub enum TagKind { + H1, + H2, + H3, + P, + Underline, + Italic, + Bold, + Strikethrough, + Ul, + Ol, +} + +#[derive(Clone, Debug)] +pub struct Tag { + pub kind: TagKind, + pub children: Vec, +} + +impl Tag { + pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec { + use TagKind::*; + + match self.kind { + H1 | H2 | H3 | P | Underline | Italic | Bold => { + let styled = self.style(style); + textwrap::wrap(&styled, available_width) + .into_iter() + .map(|s| s.to_string()) + .collect() + } + Ul => { + if available_width <= style.list_indent { + panic!("Ran out of available width!"); + } + + let available_width = available_width - style.list_indent; + let styled = self.style_children(style); + + let subsequent_indent: String = + std::iter::repeat(" ").take(style.list_indent).collect(); + + let options = textwrap::Options::new(available_width) + .initial_indent(&style.ul_prefix) + .subsequent_indent(&subsequent_indent); + + textwrap::wrap(&styled, options) + .into_iter() + .map(|s| s.to_string()) + .collect() + } + _ => unimplemented!(), + } + } + + pub fn style_children(&self, style: &Stylesheet) -> String { + let mut string = String::new(); + + for child in self.children.iter() { + let child_string = match child { + Node::Tag(tag) => tag.style(style), + Node::String(string) => string.to_owned(), + }; + + string.push_str(&child_string); + } + + string + } + + pub fn style(&self, style: &Stylesheet) -> String { + use TagKind::*; + let prefix = match self.kind { + H1 => Some(&style.h1_prefix), + H2 => Some(&style.h2_prefix), + H3 => Some(&style.h3_prefix), + _ => None, + }; + + let string = self.style_children(style); + + let style = match self.kind { + H1 => style.h1, + H2 => style.h2, + H3 => style.h3, + P => style.p, + Underline => style.underline, + Bold => style.bold, + Italic => style.italic, + Strikethrough => style.strikethrough, + _ => panic!("Cannot style {:?} tags", self.kind), + }; + + if let Some(prefix) = prefix { + let string = format!("{}{}", prefix, string); + style.paint(string).to_string() + } else { + style.paint(string).to_string() + } + } +} + +#[derive(Clone, Debug)] +pub enum Node { + Tag(Tag), + String(String), +} + +pub fn parse_cons(inner: lexpr::Cons) -> Tag { + let mut iter = inner.to_vec().0.into_iter(); + + let symbol = match iter.next() { + Some(lexpr::Value::Symbol(symbol)) => symbol, + _ => panic!("First element of cons must be a symbol"), + }; + + let kind = match TagKind::from_str(&symbol) { + Ok(kind) => kind, + _ => panic!("Unrecognized tag kind {}", symbol), + }; + + let mut children = Vec::new(); + + for node in iter { + let child = match node { + lexpr::Value::String(string) => Node::String(string.to_string()), + lexpr::Value::Cons(cons) => Node::Tag(parse_cons(cons)), + _ => unimplemented!("Invalid tag argument: {:#?}", node), + }; + + children.push(child); + } + + Tag { kind, children } +} + +fn main() { + let src_path = std::env::args().nth(1).unwrap(); + let src = std::fs::read_to_string(src_path).unwrap(); + let options = + lexpr::parse::Options::new().with_string_syntax(lexpr::parse::StringSyntax::Elisp); + let expr = lexpr::from_str_custom(&src, options).unwrap(); + + println!("{:#?}", expr); + + let cons = expr.as_cons().unwrap().to_vec().0; + let style = Stylesheet::default(); + let width = 40; + + for value in cons { + let tag_cons = value.as_cons().unwrap().to_owned(); + let tag = parse_cons(tag_cons); + for line in tag.layout(&style, width) { + println!("{}", line); + } + + println!(""); + } +}