treehouse/crates/treehouse-format/src/pull.rs

234 lines
6.9 KiB
Rust
Raw Normal View History

use std::{convert::identity, ops::Range};
2023-08-18 13:25:20 +02:00
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>,
2023-08-18 20:58:07 +02:00
pub attributes: Option<Attributes>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attributes {
pub percent: Range<usize>,
pub data: Range<usize>,
2023-08-18 13:25:20 +02:00
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parser<'a> {
pub input: &'a str,
pub position: usize,
}
2023-08-27 16:22:22 +02:00
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum AllowCodeBlocks {
No,
Yes,
}
2023-08-18 13:25:20 +02:00
impl<'a> Parser<'a> {
fn current(&self) -> Option<char> {
self.input[self.position..].chars().next()
}
2023-08-27 16:22:22 +02:00
fn current_starts_with(&self, s: &str) -> bool {
self.input[self.position..].starts_with(s)
}
2023-08-18 13:25:20 +02:00
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
}
2023-08-27 16:22:22 +02:00
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,
}
}
}
2023-08-18 13:25:20 +02:00
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,
2023-08-27 16:22:22 +02:00
allow_code_blocks: AllowCodeBlocks,
) -> Result<(), ParseError> {
2023-08-27 16:22:22 +02:00
let mut code_block: Option<Range<usize>> = None;
loop {
2023-08-27 16:22:22 +02:00
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();
2023-08-27 16:22:22 +02:00
continue;
}
self.eat_until_line_break();
2023-08-27 16:22:22 +02:00
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();
2023-08-27 16:22:22 +02:00
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;
2023-08-28 19:21:45 +02:00
} else if !matches!(self.current(), Some('\n') | Some('\r'))
&& line_indent_level < indent_level
2023-08-27 16:22:22 +02:00
{
return Err(ParseErrorKind::InconsistentIndentation {
got: line_indent_level,
expected: indent_level,
}
.at(before_indentation..after_indentation));
}
}
}
Ok(())
}
2024-01-18 22:46:57 +01:00
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),
}
}
2023-08-18 13:25:20 +02:00
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();
2023-08-18 20:58:07 +02:00
let after_percent = self.position;
2023-08-27 16:22:22 +02:00
self.eat_indented_lines_until(
indent_level,
|c| c == '-' || c == '+',
AllowCodeBlocks::No,
)?;
self.eat_as_long_as(' ');
let end = self.position;
2023-08-18 20:58:07 +02:00
Some(Attributes {
percent: start..after_percent,
data: after_percent..end,
})
} else {
2023-08-18 20:58:07 +02:00
None
};
2023-08-18 13:25:20 +02:00
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;
2023-08-27 16:22:22 +02:00
self.eat_indented_lines_until(
indent_level,
|c| c == '-' || c == '+' || c == '%',
AllowCodeBlocks::Yes,
)?;
2023-08-18 13:25:20 +02:00
let content_end = self.position;
Ok(Some(BranchEvent {
indent_level,
attributes,
2023-08-18 13:25:20 +02:00
kind,
kind_span: kind_start..kind_end,
content: content_start..content_end,
}))
}
}