Compare commits

...

10 Commits

Author SHA1 Message Date
mars 8fb89ad70b Refactor to layout module 2022-10-22 22:23:47 -06:00
mars 2355d2c768 Add auto margins 2022-10-22 22:12:04 -06:00
mars bda4432d25 Add border kind parsing 2022-10-22 22:06:26 -06:00
mars c7ccaaa0e5 Clean up use of ParseError 2022-10-22 21:50:49 -06:00
mars 0cbff0802e Add and parse AxisSize 2022-10-22 21:18:09 -06:00
mars 88d3794eb9 Better ParseValue + Color parsing + border colors 2022-10-22 17:52:33 -06:00
mars d9dc49b584 Args parsing traits + block margins and padding 2022-10-22 17:38:17 -06:00
mars d60ee159a7 Refactor Args into args module 2022-10-22 12:58:50 -06:00
mars 293fa8af2b Refactor Source into source module 2022-10-22 12:42:46 -06:00
mars 3df84a24a1 Remove old files 2022-10-22 12:36:32 -06:00
11 changed files with 403 additions and 494 deletions

79
src/args.rs Normal file
View File

@ -0,0 +1,79 @@
use std::str::FromStr;
use crate::ast::{ParseErrorKind, ParseResult, Value, ValueKind};
use crate::source::WithSource;
#[derive(Clone, Debug, Default)]
pub struct Args(Vec<Arg>);
impl Args {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn contains(&mut self, name: &str) -> bool {
self.0.iter().find(|arg| arg.name.inner == name).is_some()
}
pub fn get(&mut self, name: &str) -> Option<Arg> {
let idx = self.0.iter().position(|old| old.name.inner == name);
idx.map(|idx| self.0.remove(idx))
}
pub fn insert(&mut self, arg: Arg) -> Option<Arg> {
let existing = self
.0
.iter()
.position(|old| old.name.inner == arg.name.inner);
self.0.push(arg);
existing.map(|idx| self.0.remove(idx))
}
}
#[derive(Clone, Debug)]
pub struct Arg {
pub name: WithSource<String>,
pub val: Value,
}
pub trait ParseArgs: Sized {
fn parse_args(args: &mut Args) -> ParseResult<Self>;
}
pub trait ParseValue: Sized {
fn parse_value(val: &Value) -> ParseResult<Self>;
}
impl ParseValue for usize {
fn parse_value(val: &Value) -> ParseResult<Self> {
match val.inner {
ValueKind::Number(num) => Ok(num as Self),
_ => Err(val.map(ParseErrorKind::InvalidValue)),
}
}
}
impl<T: ParseValue> ParseValue for Option<T> {
fn parse_value(val: &Value) -> ParseResult<Self> {
if let ValueKind::Symbol(symbol) = &val.inner {
if symbol.to_lowercase() == "none" {
return Ok(None);
}
}
T::parse_value(val).map(|t| Some(t))
}
}
pub(crate) trait ParseSymbol: FromStr {}
impl<T: ParseSymbol> ParseValue for T {
fn parse_value(val: &Value) -> ParseResult<Self> {
match &val.inner {
ValueKind::Symbol(symbol) => {
T::from_str(&symbol).map_err(|_| val.map(ParseErrorKind::InvalidValue))
}
_ => Err(val.map(ParseErrorKind::InvalidValue)),
}
}
}

View File

@ -1,9 +1,12 @@
use std::fmt::{Debug, Display};
use std::ops::{Deref, Range};
use std::ops::Range;
use std::sync::Arc;
use logos::{Lexer, Logos};
use crate::args::{Arg, Args};
use crate::source::{Source, SourcePosition, SourceRange, WithSource};
#[derive(Logos, Copy, Clone, Debug, PartialEq, Eq)]
#[logos(subpattern ident = r"[a-zA-Z][a-zA-Z0-9]*")]
pub enum TokenKind {
@ -181,9 +184,14 @@ impl<'a> AstBuilder<'a> {
TokenKind::Eof => break vec![],
TokenKind::ParenClose => break vec![],
TokenKind::Keyword => {
let name = self.token_to_string(token)?;
let name = self.token_to_string(token.clone())?;
if args.contains(&name) {
return Err(token.map(ParseErrorKind::DuplicateArgument(name.inner)));
}
let val = self.expect_value()?;
args.insert(Arg { name, val })?;
args.insert(Arg { name, val });
}
_ => break self.parse_list_from(token)?,
}
@ -264,62 +272,6 @@ pub enum ValueKind {
pub type Value = WithSource<ValueKind>;
#[derive(Clone)]
pub struct WithSource<T> {
pub inner: T,
pub source: Arc<Source>,
pub range: SourceRange,
}
impl<T: Debug> Debug for WithSource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl<T: Debug + Display> Display for WithSource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, " {}", self.inner)?;
for line_idx in self.range.start.row..=self.range.end.row {
let line = &self.source.lines[line_idx];
writeln!(f, " {:<4}| {}", line_idx + 1, line)?;
let start = if line_idx == self.range.start.row {
self.range.start.col
} else {
0
};
let end = if line_idx == self.range.end.row {
self.range.end.col
} else {
line.len()
};
let spaces: String = std::iter::repeat(" ").take(start).collect();
let carats: String = std::iter::repeat("^").take(end - start).collect();
writeln!(f, " | {}{}", spaces, carats)?;
}
Ok(())
}
}
impl<T> AsRef<T> for WithSource<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T> Deref for WithSource<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl WithSource<String> {
pub fn to_val_string(self) -> Value {
Value {
@ -338,47 +290,6 @@ impl WithSource<String> {
}
}
impl<T> WithSource<T> {
pub fn map<O>(&self, other: O) -> WithSource<O> {
WithSource {
inner: other,
source: self.source.to_owned(),
range: self.range.clone(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Args(Vec<Arg>);
impl Args {
pub fn new() -> Self {
Self(Vec::new())
}
pub fn insert(&mut self, arg: Arg) -> ParseResult<()> {
for existing in self.0.iter() {
if arg.name.inner == existing.name.inner {
return Err(ParseError {
source: arg.name.source,
range: arg.name.range,
inner: ParseErrorKind::DuplicateArgument(arg.name.inner),
});
}
}
self.0.push(arg);
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct Arg {
pub name: WithSource<String>,
pub val: Value,
}
#[derive(Clone, Debug)]
pub struct Tag {
pub id: WithSource<String>,
@ -391,28 +302,6 @@ pub struct TagBody {
pub children: Vec<Value>,
}
#[derive(Clone, Debug)]
pub struct Source {
pub full: String,
pub lines: Vec<String>,
}
impl Source {
pub fn from_string(full: String) -> Self {
Self {
lines: full.lines().map(ToString::to_string).collect(),
full,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SourcePosition {
pub row: usize,
pub col: usize,
}
pub type SourceRange = Range<SourcePosition>;
pub type ParseError = WithSource<ParseErrorKind>;
pub type ParseResult<T> = Result<T, ParseError>;
@ -420,9 +309,11 @@ pub type ParseResult<T> = Result<T, ParseError>;
pub enum ParseErrorKind {
InvalidToken,
DuplicateArgument(String),
MissingArgument(String),
UnmatchedParen,
UnexpectedToken(TokenKind, Option<TokenKind>),
UnexpectedEof,
InvalidValue,
BadNumber,
}
@ -434,12 +325,14 @@ impl Display for ParseErrorKind {
match self {
InvalidToken => write!(f, "invalid token"),
DuplicateArgument(name) => write!(f, "duplicate {} argument", name),
MissingArgument(name) => write!(f, "missing {} argument", name),
UnmatchedParen => write!(f, "unmatched parentheses"),
UnexpectedToken(kind, Some(expected)) => {
write!(f, "unexpected {:?} token (expected {:?})", kind, expected)
}
UnexpectedToken(kind, None) => write!(f, "unexpected token {:?}", kind),
UnexpectedEof => write!(f, "unexpected end-of-file"),
InvalidValue => write!(f, "invalid value"),
BadNumber => write!(f, "badly-formed number"),
}
}

View File

@ -1,5 +1,11 @@
use strum::EnumString;
use crate::args::{Args, ParseArgs};
use crate::ast::ParseResult;
use crate::border::BorderKind;
use crate::layout::{Margin, Rect, Size};
use crate::text::Color;
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BlockTagKind {
@ -53,3 +59,30 @@ impl AllowedChildren {
}
}
}
#[derive(Clone, Debug)]
pub struct Block {
pub size: Size,
pub margin: Rect<Margin>,
pub padding: Rect<usize>,
pub border: Rect<Option<BorderKind>>,
pub border_color: Rect<Color>,
}
impl ParseArgs for Block {
fn parse_args(args: &mut Args) -> ParseResult<Self> {
let size = Size::parse_args(args)?;
let margin = Rect::parse_rect(args, "margin", Margin::Fixed(1))?;
let padding = Rect::parse_rect(args, "padding", 0)?;
let border = Rect::parse_rect(args, "border", None)?;
let border_color = Rect::parse_rect(args, "border-color", Color::White)?;
Ok(Self {
size,
margin,
padding,
border,
border_color,
})
}
}

9
src/border.rs Normal file
View File

@ -0,0 +1,9 @@
use strum::EnumString;
#[derive(Copy, Clone, Debug, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BorderKind {
Single,
}
impl crate::args::ParseSymbol for BorderKind {}

View File

@ -1,12 +1,12 @@
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
use crate::ast::{self, TagBody, WithSource};
use crate::block::{AllowedChildren, BlockTagKind};
use crate::args::ParseArgs;
use crate::ast::{self, ParseErrorKind, TagBody};
use crate::block::{AllowedChildren, Block, BlockTagKind};
use crate::node::{NodeId, NodeStore};
use crate::text::{
TaggedText, TextBlock, TextLayout, TextParseError, TextParseErrorKind, TextStyle, TextTagKind,
};
use crate::source::WithSource;
use crate::text::{TaggedText, TextBlock, TextLayout, TextParseErrorKind, TextStyle, TextTagKind};
#[derive(Clone, Debug)]
pub struct Dom {
@ -211,6 +211,11 @@ impl<'a> DomWalkState<'a> {
let node = self.builder.new_node();
self.block.children.push(node);
let mut args = body.args.clone();
let block = Block::parse_args(&mut args).map_err(|e| e.map(e.inner.clone().into()))?;
println!("{:#?}", block);
self.builder.insert(node, block);
let mut block = BlockBuilder::new(kind, node);
let mut state = DomWalkState {
block: &mut block,
@ -293,9 +298,16 @@ pub type DomResult<T> = Result<T, WithSource<DomErrorKind>>;
pub enum DomErrorKind {
InvalidTag,
InvalidChild,
ParseError(ParseErrorKind),
TextError(TextParseErrorKind),
}
impl From<ParseErrorKind> for DomErrorKind {
fn from(e: ParseErrorKind) -> Self {
DomErrorKind::ParseError(e)
}
}
impl From<TextParseErrorKind> for DomErrorKind {
fn from(e: TextParseErrorKind) -> Self {
DomErrorKind::TextError(e)
@ -309,6 +321,10 @@ impl std::fmt::Display for DomErrorKind {
return e.fmt(f);
}
if let ParseError(e) = &self {
return e.fmt(f);
}
write!(f, "DOM error: ")?;
match self {

View File

@ -1,242 +1,156 @@
use crate::style::Stylesheet;
use crate::tag::*;
use crate::args::*;
use crate::ast::{ParseErrorKind, ParseResult, Value, ValueKind};
use crate::source::WithSource;
impl TextTag {
pub fn layout<'a>(
&self,
style: &Stylesheet,
options: impl Into<textwrap::Options<'a>>,
) -> Vec<String> {
let styled = self.style(style);
textwrap::wrap(&styled, options)
.into_iter()
.map(|s| s.to_string())
.collect()
}
#[derive(Clone, Debug)]
pub struct RenderedBlock {
pub lines: Vec<String>,
pub width: usize,
pub height: usize,
}
#[derive(Clone, Debug)]
pub enum AxisSize {
Fixed(usize),
Bounded(usize, Option<usize>),
}
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"),
impl AxisSize {
pub fn parse(args: &mut Args, name: &str, mut min: usize) -> ParseResult<Self> {
if let Some(arg) = args.get(name) {
Ok(AxisSize::Fixed(ParseValue::parse_value(&arg.val)?))
} else {
let min_name = format!("min-{}", name);
if let Some(arg) = args.get(&min_name) {
min = ParseValue::parse_value(&arg.val)?;
}
}
string
}
pub fn style_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
let child_string = match child {
TextNode::Tag(tag) => tag.style(style),
TextNode::Text(string) => string.to_owned(),
let max_name = format!("max-{}", name);
let max = if let Some(arg) = args.get(&max_name) {
Some(ParseValue::parse_value(&arg.val)?)
} else {
None
};
string.push_str(&child_string);
Ok(AxisSize::Bounded(min, max))
}
string
}
pub fn style(&self, style: &Stylesheet) -> String {
let string = self.style_children(style);
use ansi_term::{Style, Color};
use TextTagKind::*;
let s = Style::new();
let painter = match self.kind {
Plain => return string,
Black => Color::Black.into(),
Red => Color::Red.into(),
Green => Color::Green.into(),
Yellow => Color::Yellow.into(),
Blue => Color::Blue.into(),
Magenta => Color::Purple.into(),
Cyan => Color::Cyan.into(),
White => Color::White.into(),
Underline => s.underline(),
Bold => s.bold(),
Italic => s.italic(),
Strikethrough => s.strikethrough(),
};
painter.paint(string).to_string()
}
}
impl BlockTag {
pub fn layout(&self, style: &Stylesheet, available_width: usize) -> Vec<String> {
use BlockTagKind::*;
match self.kind {
H1 | H2 | H3 => {
const INDENT: usize = 4;
#[derive(Clone, Debug)]
pub struct Size {
pub w: AxisSize,
pub h: AxisSize,
}
let styled = self.style_text_children(style);
impl ParseArgs for Size {
fn parse_args(args: &mut Args) -> ParseResult<Self> {
let w = AxisSize::parse(args, "width", 1)?;
let h = AxisSize::parse(args, "height", 1)?;
Ok(Self { w, h })
}
}
let bullets = match self.kind {
H1 => "# ",
H2 => "## ",
H3 => "### ",
_ => unreachable!(),
};
#[derive(Clone, Debug)]
pub enum Margin {
Auto,
Fixed(usize),
}
let indent: String = std::iter::repeat(" ").take(INDENT).collect();
impl ParseValue for Margin {
fn parse_value(val: &Value) -> ParseResult<Self> {
match &val.inner {
ValueKind::Symbol(symbol) if symbol.to_lowercase() == "auto" => Ok(Margin::Auto),
_ => Ok(Margin::Fixed(usize::parse_value(val)?)),
}
}
}
let options = textwrap::Options::new(available_width)
.initial_indent(&bullets)
.subsequent_indent(&indent);
#[derive(Copy, Clone, Debug)]
pub struct Rect<T> {
pub l: T,
pub t: T,
pub r: T,
pub b: T,
}
let lines = textwrap::wrap(&styled, options);
let mut max_len = 0;
lines
.iter()
.enumerate()
.map(|(index, line)| {
let mut line = line.to_string();
max_len = max_len.max(line.len());
let painter = if index == lines.len() - 1 {
while line.len() < max_len {
line.push(' ');
}
&style.header_style_floor
} else {
&style.header_style_float
};
painter.paint(line).to_string()
})
.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),
Li | Tr | Td => panic!("{:?} tag cannot be directly laid out", self.kind),
_ => unimplemented!(),
impl<T: Clone + ParseValue> Rect<T> {
pub fn parse_shorthand(parsed: WithSource<&[T]>) -> ParseResult<Self> {
match parsed.len() {
1 => Ok(Self {
l: parsed[0].clone(),
t: parsed[0].clone(),
r: parsed[0].clone(),
b: parsed[0].clone(),
}),
2 => Ok(Self {
t: parsed[0].clone(),
b: parsed[0].clone(),
l: parsed[1].clone(),
r: parsed[1].clone(),
}),
3 => Ok(Self {
t: parsed[0].clone(),
l: parsed[1].clone(),
r: parsed[1].clone(),
b: parsed[2].clone(),
}),
4 => Ok(Self {
t: parsed[0].clone(),
r: parsed[1].clone(),
b: parsed[2].clone(),
l: parsed[3].clone(),
}),
_ => Err(parsed.map(ParseErrorKind::InvalidValue)),
}
}
pub fn layout_list_items(
&self,
style: &Stylesheet,
available_width: usize,
ordered: bool,
) -> Vec<String> {
let indent = style.list_indent;
let mut lines = Vec::new();
let mut index = 1;
let spacer: String = std::iter::repeat(" ").take(indent).collect();
pub fn parse_rect(args: &mut Args, name: &str, default: T) -> ParseResult<Self> {
let mut get_side = |side: &str| -> Option<Value> {
let name = format!("{}-{}", name, side);
args.get(&name).map(|arg| arg.val)
};
let prefix = format!("{:>width$} ", style.ul_prefix, width = indent - 2);
let mut prefix = style.bullet.paint(prefix).to_string();
let l = get_side("left");
let t = get_side("top");
let r = get_side("right");
let b = get_side("bottom");
let available_width = if available_width > indent {
available_width - indent
if let Some(short) = args.get(name) {
if l.is_some() || t.is_some() || r.is_some() || b.is_some() {
let e = ParseErrorKind::DuplicateArgument(name.to_string());
return Err(short.name.map(e));
}
match &short.val.inner {
ValueKind::List(vals) => {
let mut parsed = Vec::new();
for val in vals.iter() {
parsed.push(T::parse_value(val)?);
}
Self::parse_shorthand(short.val.map(&parsed))
}
_ => {
let val = T::parse_value(&short.val)?;
Self::parse_shorthand(short.val.map(&[val]))
}
}
} else {
0
};
for child in self.children.iter() {
if ordered {
prefix = format!("{:>width$}. ", index, width = indent - 2);
prefix = style.bullet.paint(prefix).to_string();
}
let options = textwrap::Options::new(available_width)
.initial_indent(&prefix)
.subsequent_indent(&spacer);
match child {
BlockNode::Text(tag) => {
index += 1;
let mut li = tag.layout(style, options);
lines.append(&mut li);
let parse_value = |val: Option<Value>| -> ParseResult<T> {
if let Some(val) = val {
T::parse_value(&val)
} else {
Ok(default.clone())
}
BlockNode::Block(tag) => {
let li = match tag.kind {
BlockTagKind::Ol => tag.layout_list_items(style, available_width, true),
BlockTagKind::Ul => tag.layout_list_items(style, available_width, false),
BlockTagKind::Li => {
index += 1;
let mut li = tag.layout_text_children(style, options);
lines.append(&mut li);
continue;
}
_ => panic!("Unexpected {:?} in {:?}", tag.kind, self.kind),
};
};
lines.extend(li.into_iter().map(|line| format!("{}{}", spacer, line)));
}
}
Ok(Self {
l: parse_value(l)?,
t: parse_value(t)?,
r: parse_value(r)?,
b: parse_value(b)?,
})
}
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,
options: impl Into<textwrap::Options<'a>>,
) -> Vec<String> {
let styled = self.style_text_children(style);
textwrap::wrap(&styled, options)
.into_iter()
.map(|s| s.to_string())
.collect()
}
pub fn style_text_children(&self, style: &Stylesheet) -> String {
let mut string = String::new();
for child in self.children.iter() {
match child {
BlockNode::Text(tag) => string.push_str(&tag.style(style)),
_ => panic!("{:?} node attempted to style a non-text child", self.kind),
}
}
string
}
}

View File

@ -1,14 +1,18 @@
pub mod args;
pub mod ast;
pub mod block;
pub mod border;
pub mod dom;
pub mod layout;
pub mod node;
pub mod source;
pub mod text;
fn main() {
let src_path = std::env::args().nth(1).unwrap();
let src = std::fs::read_to_string(src_path).unwrap();
let source = std::sync::Arc::new(ast::Source::from_string(src.clone()));
let source = std::sync::Arc::new(source::Source::from_string(src.clone()));
let mut ast_builder = ast::AstBuilder::new(&source);
let body = match ast_builder.parse_tag_body() {
Ok(body) => body,

92
src/source.rs Normal file
View File

@ -0,0 +1,92 @@
use std::fmt::{Debug, Display};
use std::ops::{Deref, Range};
use std::sync::Arc;
#[derive(Clone)]
pub struct WithSource<T> {
pub inner: T,
pub source: Arc<Source>,
pub range: SourceRange,
}
impl<T: Debug> Debug for WithSource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.inner.fmt(f)
}
}
impl<T: Debug + Display> Display for WithSource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
writeln!(f, " {}", self.inner)?;
for line_idx in self.range.start.row..=self.range.end.row {
let line = &self.source.lines[line_idx];
writeln!(f, " {:<4}| {}", line_idx + 1, line)?;
let start = if line_idx == self.range.start.row {
self.range.start.col
} else {
0
};
let end = if line_idx == self.range.end.row {
self.range.end.col
} else {
line.len()
};
let spaces: String = std::iter::repeat(" ").take(start).collect();
let carats: String = std::iter::repeat("^").take(end - start).collect();
writeln!(f, " | {}{}", spaces, carats)?;
}
Ok(())
}
}
impl<T> AsRef<T> for WithSource<T> {
fn as_ref(&self) -> &T {
&self.inner
}
}
impl<T> Deref for WithSource<T> {
type Target = T;
fn deref(&self) -> &T {
&self.inner
}
}
impl<T> WithSource<T> {
pub fn map<O>(&self, other: O) -> WithSource<O> {
WithSource {
inner: other,
source: self.source.to_owned(),
range: self.range.clone(),
}
}
}
#[derive(Clone, Debug)]
pub struct Source {
pub full: String,
pub lines: Vec<String>,
}
impl Source {
pub fn from_string(full: String) -> Self {
Self {
lines: full.lines().map(ToString::to_string).collect(),
full,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SourcePosition {
pub row: usize,
pub col: usize,
}
pub type SourceRange = Range<SourcePosition>;

View File

@ -1,26 +0,0 @@
use ansi_term::Style;
#[derive(Clone, Debug)]
pub struct Stylesheet {
pub header_style_float: Style,
pub header_style_floor: Style,
pub header_prefix: String,
pub bullet: Style,
pub ul_prefix: String,
pub list_indent: usize,
}
impl Default for Stylesheet {
fn default() -> Self {
use ansi_term::Color::*;
Self {
header_style_float: Blue.bold(),
header_style_floor: Blue.bold().underline(),
header_prefix: "#".to_string(),
bullet: Black.into(),
ul_prefix: "*".to_string(),
list_indent: 4,
}
}
}

View File

@ -1,115 +0,0 @@
use strum::EnumString;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum TextTagKind {
#[strum(disabled)]
Plain,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
Italic,
Bold,
Underline,
Strikethrough,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum TextNode {
Text(String),
Tag(TextTag),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct TextTag {
pub kind: TextTagKind,
pub children: Vec<TextNode>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
pub enum BlockTagKind {
H1,
H2,
H3,
P,
Figlet,
Code,
Ul,
Ol,
Li,
Div,
Table,
Tr,
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),
Block(BlockTag),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct BlockTag {
pub kind: BlockTagKind,
pub children: Vec<BlockNode>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AllowedChildren {
All,
Block,
PlainText,
StyledText,
Specific(&'static [BlockTagKind]),
StyledTextAndSpecific(&'static [BlockTagKind]),
}
impl AllowedChildren {
pub fn allows_styled_text(&self) -> bool {
use AllowedChildren::*;
match self {
Specific(_) | Block | PlainText => false,
_ => true,
}
}
pub fn allows_plain_text(&self) -> bool {
use AllowedChildren::*;
match self {
Specific(_) | Block => false,
_ => true,
}
}
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),
}
}
}

View File

@ -2,7 +2,9 @@ use std::str::FromStr;
use strum::EnumString;
use crate::ast::{self, WithSource};
use crate::ast;
use crate::layout::RenderedBlock;
use crate::source::WithSource;
#[derive(Clone, Debug, Hash, PartialEq, Eq, EnumString)]
#[strum(serialize_all = "kebab-case", ascii_case_insensitive)]
@ -53,6 +55,8 @@ pub enum Color {
White,
}
impl crate::args::ParseSymbol for Color {}
impl From<Color> for ansi_term::Color {
fn from(c: Color) -> Self {
use ansi_term::Color as C;
@ -305,14 +309,20 @@ impl TextLayout {
}
}
pub fn render(&self, base: &TextStyle) -> RenderedText {
RenderedText {
lines: self.lines.iter().map(|text| text.render(base)).collect(),
pub fn render(&self, base: &TextStyle) -> RenderedBlock {
let mut lines = Vec::new();
let mut width = 0;
for line in self.lines.iter() {
let line = line.render(base);
width = width.max(textwrap::core::display_width(&line));
lines.push(line);
}
RenderedBlock {
height: lines.len(),
width,
lines,
}
}
}
#[derive(Clone, Debug)]
pub struct RenderedText {
pub lines: Vec<String>,
}