Compare commits
10 Commits
0e3b8ea611
...
8fb89ad70b
Author | SHA1 | Date |
---|---|---|
mars | 8fb89ad70b | |
mars | 2355d2c768 | |
mars | bda4432d25 | |
mars | c7ccaaa0e5 | |
mars | 0cbff0802e | |
mars | 88d3794eb9 | |
mars | d9dc49b584 | |
mars | d60ee159a7 | |
mars | 293fa8af2b | |
mars | 3df84a24a1 |
|
@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
137
src/ast.rs
137
src/ast.rs
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
33
src/block.rs
33
src/block.rs
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
26
src/dom.rs
26
src/dom.rs
|
@ -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 {
|
||||
|
|
346
src/layout.rs
346
src/layout.rs
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>;
|
26
src/style.rs
26
src/style.rs
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
115
src/tag.rs
115
src/tag.rs
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
28
src/text.rs
28
src/text.rs
|
@ -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>,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue