forked from mars/tml
209 lines
5.5 KiB
Rust
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!("");
|
|
}
|
|
}
|