Compare commits
2 Commits
c7804d8f68
...
dec3406a91
Author | SHA1 | Date |
---|---|---|
mars | dec3406a91 | |
mars | 04517ad670 |
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
67
src/parse.rs
67
src/parse.rs
|
@ -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 {
|
||||
|
|
95
src/tag.rs
95
src/tag.rs
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue