mod block;
mod component;
mod inner_text;
mod match_expr;
mod raw_block;
mod rust_block;
mod rust_expr;
mod rust_expr_paren;
mod rust_expr_simple;
mod template;
mod template_params;
mod text;
mod use_directive;
use crate::{
config::Config,
error::{E, rename_rules},
node::*,
parser::{
block::BlockParser, component::ComponentParser, inner_text::InnerTextParser,
match_expr::MatchExprParser, raw_block::RawBlockParser, rust_block::RustBlockParser,
rust_expr::RustExprParser, rust_expr_paren::RustExprParenParser,
rust_expr_simple::RustExprSimpleParser, template::TemplateParser,
template_params::TemplateParamsParser, text::TextParser, use_directive::UseDirectiveParser,
},
};
use pest::{
Parser, Span,
error::Error,
iterators::{Pair, Pairs},
};
use pest_derive::Parser;
use std::{
collections::HashMap,
path::{Path, PathBuf},
};
#[derive(Parser)]
#[grammar = "rshtml.pest"]
pub struct RsHtmlParser {
config: Config,
files: Vec<PathBuf>,
pub sources: HashMap<PathBuf, String>,
fns: Vec<Function>,
}
impl RsHtmlParser {
pub fn new() -> Self {
Self {
config: Config::default(),
files: Vec::new(),
sources: HashMap::new(),
fns: Vec::new(),
}
}
fn build_nodes_from_pairs(
&mut self,
pairs: Pairs<Rule>,
) -> Result<Vec<Node>, Box<Error<Rule>>> {
let mut nodes = Vec::new();
for pair in pairs {
match pair.as_rule() {
Rule::template_content => {
let inner_nodes = self.build_nodes_from_pairs(pair.into_inner())?;
nodes.extend(inner_nodes);
}
Rule::template_params | Rule::block | Rule::text | Rule::inner_text => {
nodes.push(self.build_ast_node(pair)?);
}
_ => {}
}
}
Ok(nodes)
}
fn build_ast_node(&mut self, pair: Pair<Rule>) -> Result<Node, Box<Error<Rule>>> {
match pair.as_rule() {
Rule::template => TemplateParser::parse(self, pair),
Rule::text => TextParser::parse(self, pair),
Rule::inner_text => InnerTextParser::parse(self, pair),
Rule::template_params => TemplateParamsParser::parse(self, pair),
Rule::block => BlockParser::parse(self, pair),
Rule::rust_block => RustBlockParser::parse(self, pair),
Rule::rust_expr_simple => RustExprSimpleParser::parse(self, pair),
Rule::rust_expr_paren => RustExprParenParser::parse(self, pair),
Rule::rust_expr => RustExprParser::parse(self, pair),
Rule::match_expr => MatchExprParser::parse(self, pair),
Rule::component => ComponentParser::parse(self, pair),
Rule::child_content_directive => Ok(Node::ChildContent),
Rule::raw_block => RawBlockParser::parse(self, pair),
Rule::use_directive => UseDirectiveParser::parse(self, pair),
Rule::continue_directive => Ok(Node::ContinueDirective),
Rule::break_directive => Ok(Node::BreakDirective),
rule => Err(E::mes(format!("Error: Unknown rule: {rule:?}")).span(pair.as_span())),
}
}
fn parse_template(&mut self, path: &Path) -> Result<Node, Box<Error<Rule>>> {
let input = self.read_template(path).map_err(|err| {
E::mes(format!(
"Error reading template: {err:?}, path: {}",
path.display()
))
.span(Span::new(path.to_string_lossy().to_string().as_str(), 0, 0).unwrap())
})?;
let mut pairs = Self::parse(Rule::template, &input)?;
let template_pair = pairs.next().ok_or(
E::mes("Error: Empty template").position(pest::Position::new("Template", 0).unwrap()),
)?;
if template_pair.as_rule() == Rule::template {
if self.files.contains(&path.to_owned()) {
return Err(E::mes(format!(
"Error: Circular call detected for '{}'",
path.display()
))
.span(template_pair.as_span()));
}
self.sources
.entry(path.to_owned())
.or_insert_with(|| input.clone());
self.files.push(path.to_owned());
let ast = self.build_ast_node(template_pair)?;
self.files.pop();
Ok(ast)
} else {
Err(E::pos(Rule::template).span(template_pair.as_span()))
}
}
fn read_template(&self, path: &Path) -> Result<String, String> {
let view_path = self.config.base_path.join(path);
let template = std::fs::read_to_string(&view_path).map_err(|err| {
format!(
"Error reading template: {:?}, path: {}",
err,
view_path.to_string_lossy()
)
})?;
Ok(template)
}
pub fn run(&mut self, path: &str, config: Config) -> Result<Node, Box<Error<Rule>>> {
self.config = config;
let path = PathBuf::from(path);
self.parse_template(&path).map_err(|err| rename_rules(*err))
}
fn extract_component_name(&self, path: &Path) -> Option<String> {
let filename = path.file_name().and_then(|n| n.to_str())?;
let component_name = filename.strip_suffix(".rs.html").unwrap_or(filename);
Some(component_name.to_owned())
}
}
pub trait IParser {
fn parse(parser: &mut RsHtmlParser, pair: Pair<Rule>) -> Result<Node, Box<Error<Rule>>>;
}