remove treehouse-format crate and collapse everything into src
This commit is contained in:
parent
ca127a9411
commit
b792688776
66 changed files with 145 additions and 112 deletions
|
@ -1,7 +0,0 @@
|
|||
[package]
|
||||
name = "treehouse-format"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "1.0.47"
|
|
@ -1,81 +0,0 @@
|
|||
use std::ops::Range;
|
||||
|
||||
use crate::{
|
||||
pull::{Attributes, BranchEvent, BranchKind, Parser},
|
||||
ParseError, ParseErrorKind,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Roots {
|
||||
pub attributes: Option<Attributes>,
|
||||
pub branches: Vec<Branch>,
|
||||
}
|
||||
|
||||
impl Roots {
|
||||
pub fn parse(parser: &mut Parser) -> Result<Self, ParseError> {
|
||||
let attributes = parser.top_level_attributes()?;
|
||||
|
||||
let mut branches = vec![];
|
||||
while let Some((branch, indent_level)) = Branch::parse_with_indent_level(parser)? {
|
||||
if indent_level != 0 {
|
||||
return Err(ParseErrorKind::RootIndentLevel.at(branch.kind_span));
|
||||
}
|
||||
branches.push(branch);
|
||||
}
|
||||
Ok(Self {
|
||||
attributes,
|
||||
branches,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Branch {
|
||||
pub indent_level: usize,
|
||||
pub attributes: Option<Attributes>,
|
||||
pub kind: BranchKind,
|
||||
pub kind_span: Range<usize>,
|
||||
pub content: Range<usize>,
|
||||
pub children: Vec<Branch>,
|
||||
}
|
||||
|
||||
impl From<BranchEvent> for Branch {
|
||||
fn from(branch: BranchEvent) -> Self {
|
||||
Self {
|
||||
indent_level: branch.indent_level,
|
||||
attributes: branch.attributes,
|
||||
kind: branch.kind,
|
||||
kind_span: branch.kind_span,
|
||||
content: branch.content,
|
||||
children: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Branch {
|
||||
pub fn parse_with_indent_level(
|
||||
parser: &mut Parser,
|
||||
) -> Result<Option<(Self, usize)>, ParseError> {
|
||||
if let Some(branch_event) = parser.next_branch()? {
|
||||
let own_indent_level = branch_event.indent_level;
|
||||
let mut branch = Branch::from(branch_event);
|
||||
let children_indent_level = parser.peek_indent_level();
|
||||
if children_indent_level > own_indent_level {
|
||||
while parser.peek_indent_level() == children_indent_level {
|
||||
if let Some(child) = Branch::parse(parser)? {
|
||||
branch.children.push(child);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Some((branch, own_indent_level)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(parser: &mut Parser) -> Result<Option<Self>, ParseError> {
|
||||
Ok(Self::parse_with_indent_level(parser)?.map(|(branch, _)| branch))
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
use std::ops::Range;
|
||||
|
||||
pub mod ast;
|
||||
pub mod pull;
|
||||
|
||||
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
|
||||
pub enum ParseErrorKind {
|
||||
#[error("branch kind (`+` or `-`) expected")]
|
||||
BranchKindExpected,
|
||||
|
||||
#[error("root branches must not be indented")]
|
||||
RootIndentLevel,
|
||||
|
||||
#[error("at least {expected} spaces of indentation were expected, but got {got}")]
|
||||
InconsistentIndentation { got: usize, expected: usize },
|
||||
|
||||
#[error("unterminated code block")]
|
||||
UnterminatedCodeBlock,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
|
||||
#[error("{range:?}: {kind}")]
|
||||
pub struct ParseError {
|
||||
pub kind: ParseErrorKind,
|
||||
pub range: Range<usize>,
|
||||
}
|
||||
|
||||
impl ParseErrorKind {
|
||||
pub fn at(self, range: Range<usize>) -> ParseError {
|
||||
ParseError { kind: self, range }
|
||||
}
|
||||
}
|
|
@ -1,233 +0,0 @@
|
|||
use std::{convert::identity, ops::Range};
|
||||
|
||||
use crate::{ParseError, ParseErrorKind};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BranchKind {
|
||||
/// Expanded by default.
|
||||
Expanded,
|
||||
/// Folded by default.
|
||||
Collapsed,
|
||||
}
|
||||
|
||||
impl BranchKind {
|
||||
pub fn char(&self) -> char {
|
||||
match self {
|
||||
BranchKind::Expanded => '-',
|
||||
BranchKind::Collapsed => '+',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BranchEvent {
|
||||
pub indent_level: usize,
|
||||
pub kind: BranchKind,
|
||||
pub kind_span: Range<usize>,
|
||||
pub content: Range<usize>,
|
||||
pub attributes: Option<Attributes>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Attributes {
|
||||
pub percent: Range<usize>,
|
||||
pub data: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Parser<'a> {
|
||||
pub input: &'a str,
|
||||
pub position: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum AllowCodeBlocks {
|
||||
No,
|
||||
Yes,
|
||||
}
|
||||
|
||||
impl Parser<'_> {
|
||||
fn current(&self) -> Option<char> {
|
||||
self.input[self.position..].chars().next()
|
||||
}
|
||||
|
||||
fn current_starts_with(&self, s: &str) -> bool {
|
||||
self.input[self.position..].starts_with(s)
|
||||
}
|
||||
|
||||
fn advance(&mut self) {
|
||||
self.position += self.current().map(|c| c.len_utf8()).unwrap_or(0);
|
||||
}
|
||||
|
||||
fn eat_as_long_as(&mut self, c: char) -> usize {
|
||||
let mut count = 0;
|
||||
while self.current() == Some(c) {
|
||||
count += 1;
|
||||
self.advance();
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
fn eat_while(&mut self, cond: impl Fn(char) -> bool) {
|
||||
while self.current().map(&cond).is_some_and(|x| x) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
fn eat_until_line_break(&mut self) {
|
||||
loop {
|
||||
match self.current() {
|
||||
Some('\r') => {
|
||||
self.advance();
|
||||
if self.current() == Some('\n') {
|
||||
self.advance();
|
||||
break;
|
||||
}
|
||||
}
|
||||
Some('\n') => {
|
||||
self.advance();
|
||||
break;
|
||||
}
|
||||
Some(_) => self.advance(),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_indent_level(&mut self) -> usize {
|
||||
let position = self.position;
|
||||
let indent_level = self.eat_as_long_as(' ');
|
||||
self.position = position;
|
||||
indent_level
|
||||
}
|
||||
|
||||
fn eat_indented_lines_until(
|
||||
&mut self,
|
||||
indent_level: usize,
|
||||
cond: impl Fn(char) -> bool,
|
||||
allow_code_blocks: AllowCodeBlocks,
|
||||
) -> Result<(), ParseError> {
|
||||
let mut code_block: Option<Range<usize>> = None;
|
||||
loop {
|
||||
if let Some(range) = &code_block {
|
||||
self.eat_while(|c| c == ' ');
|
||||
if self.current_starts_with("```") {
|
||||
code_block = None;
|
||||
self.position += 3;
|
||||
self.eat_until_line_break();
|
||||
continue;
|
||||
}
|
||||
self.eat_until_line_break();
|
||||
|
||||
if self.current().is_none() {
|
||||
return Err(ParseErrorKind::UnterminatedCodeBlock.at(range.clone()));
|
||||
}
|
||||
} else {
|
||||
self.eat_while(|c| c == ' ');
|
||||
if allow_code_blocks == AllowCodeBlocks::Yes && self.current_starts_with("```") {
|
||||
code_block = Some(self.position..self.position + 3);
|
||||
self.position += 3;
|
||||
continue;
|
||||
}
|
||||
|
||||
self.eat_until_line_break();
|
||||
let before_indentation = self.position;
|
||||
let line_indent_level = self.eat_as_long_as(' ');
|
||||
let after_indentation = self.position;
|
||||
if self.current().map(&cond).is_some_and(identity) || self.current().is_none() {
|
||||
self.position = before_indentation;
|
||||
break;
|
||||
} else if !matches!(self.current(), Some('\n') | Some('\r'))
|
||||
&& line_indent_level < indent_level
|
||||
{
|
||||
return Err(ParseErrorKind::InconsistentIndentation {
|
||||
got: line_indent_level,
|
||||
expected: indent_level,
|
||||
}
|
||||
.at(before_indentation..after_indentation));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn top_level_attributes(&mut self) -> Result<Option<Attributes>, ParseError> {
|
||||
let start = self.position;
|
||||
match self.current() {
|
||||
Some('%') => {
|
||||
let after_one_percent = self.position;
|
||||
self.advance();
|
||||
if self.current() == Some('%') {
|
||||
self.advance();
|
||||
let after_two_percent = self.position;
|
||||
self.eat_indented_lines_until(
|
||||
0,
|
||||
|c| c == '-' || c == '+' || c == '%',
|
||||
AllowCodeBlocks::No,
|
||||
)?;
|
||||
let end = self.position;
|
||||
Ok(Some(Attributes {
|
||||
percent: start..after_two_percent,
|
||||
data: after_two_percent..end,
|
||||
}))
|
||||
} else {
|
||||
self.position = after_one_percent;
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_branch(&mut self) -> Result<Option<BranchEvent>, ParseError> {
|
||||
if self.current().is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let indent_level = self.eat_as_long_as(' ');
|
||||
|
||||
let attributes = if self.current() == Some('%') {
|
||||
let start = self.position;
|
||||
self.advance();
|
||||
let after_percent = self.position;
|
||||
self.eat_indented_lines_until(
|
||||
indent_level,
|
||||
|c| c == '-' || c == '+',
|
||||
AllowCodeBlocks::No,
|
||||
)?;
|
||||
self.eat_as_long_as(' ');
|
||||
let end = self.position;
|
||||
Some(Attributes {
|
||||
percent: start..after_percent,
|
||||
data: after_percent..end,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let kind_start = self.position;
|
||||
let kind = match self.current() {
|
||||
Some('-') => BranchKind::Expanded,
|
||||
Some('+') => BranchKind::Collapsed,
|
||||
_ => return Err(ParseErrorKind::BranchKindExpected.at(kind_start..kind_start + 1)),
|
||||
};
|
||||
self.advance();
|
||||
let kind_end = self.position;
|
||||
|
||||
let content_start = self.position;
|
||||
self.eat_indented_lines_until(
|
||||
indent_level,
|
||||
|c| c == '-' || c == '+' || c == '%',
|
||||
AllowCodeBlocks::Yes,
|
||||
)?;
|
||||
let content_end = self.position;
|
||||
|
||||
Ok(Some(BranchEvent {
|
||||
indent_level,
|
||||
attributes,
|
||||
kind,
|
||||
kind_span: kind_start..kind_end,
|
||||
content: content_start..content_end,
|
||||
}))
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue