tml/src/main.rs

209 lines
5.5 KiB
Rust

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<Node>,
}
impl Tag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
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!("");
}
}