Compare commits

...

2 Commits

Author SHA1 Message Date
mars dec3406a91 Figlet and code block layout 2022-10-09 15:44:38 -06:00
mars 04517ad670 Remove leaf tags + better child semantics 2022-10-09 15:22:45 -06:00
6 changed files with 147 additions and 84 deletions

7
Cargo.lock generated
View File

@ -37,6 +37,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "figlet-rs"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01af5c4fc8506204d2280509e9d57d1b41e54e587c185ac09baef30d21ae63ed"
[[package]]
name = "getrandom"
version = "0.2.7"
@ -218,6 +224,7 @@ name = "tml"
version = "0.1.0"
dependencies = [
"ansi_term",
"figlet-rs",
"lexpr",
"strum",
"textwrap",

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
figlet-rs = "0.1"
lexpr = "0.2"
strum = { version = "0.24", features = ["derive"] }
ansi_term = "0.12"

View File

@ -1,6 +1,17 @@
(
(h1 "Hello world!")
(figlet "Hello figlet!")
(code "
#include <stdio.h>
int main(int argc, const char *argv[]) {
printf(\"Hello, world!\\n\");
return 0;
}
")
(p "Text goes here.")
(h2 "Heading 2")

View File

@ -14,6 +14,23 @@ impl TextTag {
.collect()
}
pub fn collect_plain_text(&self) -> String {
if self.kind != TextTagKind::Plain {
panic!("Attempted to collect plain text of {:?} tag", self.kind);
}
let mut string = String::new();
for child in self.children.iter() {
match child {
TextNode::Text(text) => string.push_str(&text),
TextNode::Tag(_) => panic!("Found child text tag while collecting plain text"),
}
}
string
}
pub fn style_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
@ -91,6 +108,21 @@ impl BlockTag {
})
.collect()
}
Figlet => {
let font = figlet_rs::FIGfont::standard().unwrap();
let text = self.collect_plain_text_children();
let figure = font.convert(&text).expect("Failed to FIG-ify text");
figure
.to_string()
.lines()
.map(ToString::to_string)
.collect()
}
Code => self
.collect_plain_text_children()
.lines()
.map(ToString::to_string)
.collect(),
P => self.layout_text_children(style, available_width),
Ul => self.layout_list_items(style, available_width, false),
Ol => self.layout_list_items(style, available_width, true),
@ -150,13 +182,29 @@ impl BlockTag {
lines.extend(li.into_iter().map(|line| format!("{}{}", spacer, line)));
}
BlockNode::Leaf(_) => panic!("Unexpected leaf tag in {:?}", self.kind),
}
}
lines
}
pub fn collect_plain_text_children(&self) -> String {
let mut string = String::new();
for child in self.children.iter() {
match child {
BlockNode::Text(tag) => {
string.push_str(&tag.collect_plain_text());
}
BlockNode::Block(tag) => {
panic!("Unexpected {:?} tag in {:?} children", tag.kind, self.kind);
}
}
}
string
}
pub fn layout_text_children<'a>(
&self,
style: &Stylesheet,

View File

@ -15,14 +15,8 @@ impl Parser {
for val in parser.vals.into_iter().rev() {
let mut parser = Self::from_value(&val);
let kind = parser
.parse_any_tag_kind()
.as_block()
.expect("Root tag must be a block tag");
let kind = parser.parse_block_kind();
let tag = parser.parse_block_tag(kind);
tags.push(tag);
}
@ -56,10 +50,7 @@ impl Parser {
let node = match child {
Value::Cons(cons) => {
let mut parser = Self::from_cons(&cons);
let kind = parser
.parse_any_tag_kind()
.as_text()
.expect("Text tag can only have other text tags as children");
let kind = parser.parse_text_kind();
TextNode::Tag(parser.parse_text_tag(kind))
}
Value::String(string) => TextNode::Text(string.to_string()),
@ -72,35 +63,42 @@ impl Parser {
TextTag { kind, children }
}
pub fn parse_leaf_tag(&mut self, kind: LeafTagKind) -> LeafTag {
let (_args, children) = self.parse_args();
if !children.is_empty() {
panic!("Leaf tag cannot have children");
}
LeafTag { kind }
}
pub fn parse_block_tag(&mut self, kind: BlockTagKind) -> BlockTag {
let (_args, children_vals) = self.parse_args();
let allowed_children = kind.allowed_children();
let mut children = Vec::new();
for child in children_vals.into_iter() {
let node = match child {
Value::Cons(cons) => {
let mut parser = Self::from_cons(&cons);
match parser.parse_any_tag_kind() {
AnyTagKind::Text(kind) => BlockNode::Text(parser.parse_text_tag(kind)),
AnyTagKind::Leaf(kind) => BlockNode::Leaf(parser.parse_leaf_tag(kind)),
AnyTagKind::Block(kind) => BlockNode::Block(parser.parse_block_tag(kind)),
let symbol = parser.parse_symbol();
if let Ok(child_kind) = BlockTagKind::from_str(&symbol) {
if allowed_children.allows_block(&child_kind) {
BlockNode::Block(parser.parse_block_tag(child_kind))
} else {
panic!("{:?} tag cannot contain {:?} tag", kind, child_kind);
}
} else if let Ok(child_kind) = TextTagKind::from_str(&symbol) {
if allowed_children.allows_styled_text() {
BlockNode::Text(parser.parse_text_tag(child_kind))
} else {
panic!("{:?} tag cannot contain styled text", kind);
}
} else {
panic!("Unrecognized tag {}", symbol);
}
}
Value::String(string) => {
if allowed_children.allows_plain_text() {
BlockNode::Text(TextTag {
kind: TextTagKind::Plain,
children: vec![TextNode::Text(string.to_string())],
})
} else {
panic!("{:?} tag cannot contain plain text", kind);
}
}
Value::String(string) => BlockNode::Text(TextTag {
kind: TextTagKind::Plain,
children: vec![TextNode::Text(string.to_string())],
}),
_ => panic!("Block tag child must be either a string or a cons"),
};
@ -110,9 +108,14 @@ impl Parser {
BlockTag { kind, children }
}
pub fn parse_any_tag_kind(&mut self) -> AnyTagKind {
pub fn parse_block_kind(&mut self) -> BlockTagKind {
let symbol = self.parse_symbol();
AnyTagKind::from_str(&symbol).expect("Unrecognized tag")
BlockTagKind::from_str(&symbol).expect("Unrecognized block tag")
}
pub fn parse_text_kind(&mut self) -> TextTagKind {
let symbol = self.parse_symbol();
TextTagKind::from_str(&symbol).expect("Unrecognized text tag")
}
pub fn parse_symbol(&mut self) -> String {

View File

@ -1,9 +1,9 @@
use std::str::FromStr;
use strum::EnumString;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum TextTagKind {
#[strum(disabled)]
Plain,
Italic,
Bold,
@ -23,18 +23,6 @@ pub struct TextTag {
pub children: Vec<TextNode>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum LeafTagKind {
Figlet,
Code,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct LeafTag {
pub kind: LeafTagKind,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BlockTagKind {
@ -42,6 +30,8 @@ pub enum BlockTagKind {
H2,
H3,
P,
Figlet,
Code,
Ul,
Ol,
Li,
@ -51,10 +41,25 @@ pub enum BlockTagKind {
Td,
}
impl BlockTagKind {
pub fn allowed_children(&self) -> AllowedChildren {
use BlockTagKind::*;
match self {
H1 | H2 | H3 | P => AllowedChildren::StyledText,
Figlet | Code => AllowedChildren::PlainText,
Ul | Ol => AllowedChildren::StyledTextAndSpecific(&[Li, Ul, Ol]),
Li => AllowedChildren::StyledText,
Div => AllowedChildren::All,
Table => AllowedChildren::Specific(&[BlockTagKind::Tr]),
Tr => AllowedChildren::Specific(&[BlockTagKind::Td]),
Td => AllowedChildren::All,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum BlockNode {
Text(TextTag),
Leaf(LeafTag),
Block(BlockTag),
}
@ -64,51 +69,39 @@ pub struct BlockTag {
pub children: Vec<BlockNode>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum AnyTagKind {
Text(TextTagKind),
Leaf(LeafTagKind),
Block(BlockTagKind),
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AllowedChildren {
All,
Block,
PlainText,
StyledText,
Specific(&'static [BlockTagKind]),
StyledTextAndSpecific(&'static [BlockTagKind]),
}
impl AnyTagKind {
pub fn as_text(self) -> Option<TextTagKind> {
if let AnyTagKind::Text(text) = self {
Some(text)
} else {
None
impl AllowedChildren {
pub fn allows_styled_text(&self) -> bool {
use AllowedChildren::*;
match self {
Specific(_) | Block | PlainText => false,
_ => true,
}
}
pub fn as_leaf(self) -> Option<LeafTagKind> {
if let AnyTagKind::Leaf(leaf) = self {
Some(leaf)
} else {
None
pub fn allows_plain_text(&self) -> bool {
use AllowedChildren::*;
match self {
Specific(_) | Block => false,
_ => true,
}
}
pub fn as_block(self) -> Option<BlockTagKind> {
if let AnyTagKind::Block(block) = self {
Some(block)
} else {
None
}
}
}
impl FromStr for AnyTagKind {
type Err = strum::ParseError;
fn from_str(string: &str) -> Result<Self, strum::ParseError> {
if let Ok(text) = TextTagKind::from_str(string) {
Ok(AnyTagKind::Text(text))
} else if let Ok(leaf) = LeafTagKind::from_str(string) {
Ok(AnyTagKind::Leaf(leaf))
} else if let Ok(block) = BlockTagKind::from_str(string) {
Ok(AnyTagKind::Block(block))
} else {
Err(strum::ParseError::VariantNotFound)
pub fn allows_block(&self, kind: &BlockTagKind) -> bool {
use AllowedChildren::*;
match self {
All | Block => true,
PlainText | StyledText => false,
Specific(allowed) | StyledTextAndSpecific(allowed) => allowed.contains(kind),
}
}
}