continue working ish

This commit is contained in:
Schrottkatze 2023-05-04 10:02:34 +02:00
parent c3642676b2
commit b22570c2e2
6 changed files with 175 additions and 0 deletions

12
data/demo-result.html Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1 class="class">some content</h1>
<p>this is some text and contains <a href="https://example.com"></a> a link</p>
</body>
</html>

36
data/demo.ihl Normal file
View file

@ -0,0 +1,36 @@
// defined base element
document [
title: "example",
lang: "de",
] {
h1.class "some content";
p {
"this is some text and contains ";
a [ href: "https://example.com" ] "a link";
".";
}
}
// attributes are by default parsed as:
// key: value
// and are comma seperated.
//
// items can define custom parsers for attributes
// content is by default parsed as semicolon seperated further item structures
// plain text is just plain text
// definitions have a : after the name
// definitions only allow default parsing
// @slot is the attribute that defines the element body, but can be overwritten
navItem: [
dest: string,
] {
li.nav-item {
// use attributes using @
// interpolate in @{} blocks
a[ href: "https://example.com@{dest}" ] @dest;
}
}

1
example.html Normal file
View file

@ -0,0 +1 @@
<html><head><title>test file!</title></head><body><h1>hello world</h1><p>this is a test file</p><p>some text followed by <a href="https://schrottkatze.de">a hyperlink</a></p></body></html>

63
src/element.rs Normal file
View file

@ -0,0 +1,63 @@
use std::{collections::HashMap, fmt::{Display, Write}};
#[derive(Debug)]
pub struct Element<'a> {
pub name: &'a str,
pub attributes: Option<HashMap<&'a str, &'a str>>,
pub children: ElBody<'a>,
}
impl<'a> Display for Element<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"<{0}{1}>{2}</{0}>",
self.name,
if let Some(attrs) = &self.attributes {
let mut attributes = String::from(" ");
attrs.iter().for_each(|(key, value)| {
attributes
.write_fmt(format_args!("{key}=\"{value}\""))
.unwrap()
});
attributes
} else {
"".to_owned()
},
self.children
)
}
}
/// the direct content of `children`, so it can be a plaintext element
#[derive(Debug)]
pub enum ElBody<'a> {
Elements(Vec<ElContent<'a>>),
Text(&'a str),
}
impl<'a> Display for ElBody<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ElBody::Text(s) => f.write_str(s),
ElBody::Elements(els) => els.iter().try_for_each(|e| f.write_str(&e.to_string())),
}
}
}
#[derive(Debug)]
pub enum ElContent<'a> {
El(Element<'a>),
Text(&'a str),
}
impl<'a> Display for ElContent<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ElContent::Text(s) => f.write_str(s),
ElContent::El(el) => f.write_str(&el.to_string()),
}
}
}

53
src/parser.rs Normal file
View file

@ -0,0 +1,53 @@
use std::collections::HashMap;
use winnow::{IResult, combinator, character::alphanumeric1, branch, sequence::delimited, multi::many0, bytes::take_until0, Parser};
use crate::{element::{Element, ElBody, ElContent}, util::ws};
pub fn el_parser(input: &str) -> IResult<&str, Element> {
(
name_parser,
combinator::opt(el_attrlist_parser),
el_body_parser,
)
.map(|(name, attributes, children)| Element {
name,
attributes,
children,
})
.parse_next(input)
}
pub fn name_parser(input: &str) -> IResult<&str, &str> {
ws(alphanumeric1).parse_next(input)
}
pub fn el_body_parser(input: &str) -> IResult<&str, ElBody> {
ws(branch::alt((
string_parser.map(|s| ElBody::Text(s)),
delimited('{', many0(content_parser).map(|v| ElBody::Elements(v)), '}'),
)))
.parse_next(input)
}
pub fn el_attrlist_parser(input: &str) -> IResult<&str, HashMap<&str, &str>> {
ws(delimited('[', many0(attribute_parser), ']')).parse_next(input)
}
pub fn attribute_parser(input: &str) -> IResult<&str, (&str, &str)> {
(name_parser, '=', ws(string_parser))
.map(|(key, _, value)| (key, value))
.parse_next(input)
}
pub fn content_parser(input: &str) -> IResult<&str, ElContent> {
ws(branch::alt((
string_parser.map(|s| ElContent::Text(s)),
el_parser.map(|e| ElContent::El(e)),
)))
.parse_next(input)
}
pub fn string_parser(input: &str) -> IResult<&str, &str> {
delimited('"', take_until0("\""), '"').parse_next(input)
}

10
src/util.rs Normal file
View file

@ -0,0 +1,10 @@
use winnow::{error::ParseError, Parser, sequence::delimited, character::multispace0};
// whitespace combinator from [winnow docs](https://docs.rs/winnow/latest/winnow/_topic/language/index.html#wrapper-combinators-that-eat-whitespace-before-and-after-a-parser)
pub fn ws<'a, F, O, E: ParseError<&'a str>>(inner: F) -> impl Parser<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
delimited(multispace0, inner, multispace0)
}