continue working ish
This commit is contained in:
parent
c3642676b2
commit
b22570c2e2
6 changed files with 175 additions and 0 deletions
12
data/demo-result.html
Normal file
12
data/demo-result.html
Normal 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
36
data/demo.ihl
Normal 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
1
example.html
Normal 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
63
src/element.rs
Normal 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
53
src/parser.rs
Normal 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
10
src/util.rs
Normal 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)
|
||||||
|
}
|
Loading…
Reference in a new issue