Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 01d9e06d63 | |||
| d26ef729eb | |||
| f08eda2758 | |||
| 190e00ed62 | |||
| bb7dedc9eb | |||
| 59b81d637b | |||
| ec7a461758 | |||
| 353934381b | |||
| 0167d3cea3 | |||
| ec7bc571b1 | |||
| 7290a23c85 |
9 changed files with 137 additions and 26 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -17,6 +17,23 @@ A dependency update to an incompatible version is considered a breaking change.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
## v0.2.0 - 2025-01-01
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **(breaking)** Updated `axum-core` dependency to `0.5.0`
|
||||||
|
- Relaxed lower bound on `http` dependency to `1.0.0`
|
||||||
|
|
||||||
|
## v0.1.3 - 2024-12-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- `html::attr::Rel`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Rendering of HTML comments
|
||||||
|
|
||||||
## v0.1.2 - 2024-12-14
|
## v0.1.2 - 2024-12-14
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
20
Cargo.toml
20
Cargo.toml
|
|
@ -1,10 +1,10 @@
|
||||||
[package]
|
[package]
|
||||||
name = "el"
|
name = "el"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
authors = ["Garmelon <garmelon@plugh.de>"]
|
authors = ["Garmelon <garmelon@plugh.de>"]
|
||||||
description = "Write and manipulate HTML elements as data"
|
description = "Write and manipulate HTML elements as data"
|
||||||
repository = "https://github.com/Garmelon/el"
|
repository = "https://git.plugh.de/Garmelon/el"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["html", "svg", "mathml", "hiccup"]
|
keywords = ["html", "svg", "mathml", "hiccup"]
|
||||||
categories = ["web-programming", "template-engine"]
|
categories = ["web-programming", "template-engine"]
|
||||||
|
|
@ -13,16 +13,18 @@ categories = ["web-programming", "template-engine"]
|
||||||
axum = ["dep:axum-core", "dep:http"]
|
axum = ["dep:axum-core", "dep:http"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum-core = { version = "0.4.5", optional = true }
|
axum-core = { version = "0.5.0", optional = true }
|
||||||
http = { version = "1.1.0", optional = true }
|
http = { version = "1.0.0", optional = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
rust.unsafe_code = { level = "forbid", priority = 1 }
|
rust.unsafe_code = { level = "forbid", priority = 1 }
|
||||||
# Lint groups
|
# Lint groups
|
||||||
rust.deprecated_safe = "warn"
|
rust.deprecated-safe = "warn"
|
||||||
rust.future_incompatible = "warn"
|
rust.future-incompatible = "warn"
|
||||||
rust.keyword_idents = "warn"
|
rust.keyword-idents = "warn"
|
||||||
rust.rust_2018_idioms = "warn"
|
rust.nonstandard-style = "warn"
|
||||||
|
rust.refining-impl-trait = "warn"
|
||||||
|
rust.rust-2018-idioms = "warn"
|
||||||
rust.unused = "warn"
|
rust.unused = "warn"
|
||||||
# Individual lints
|
# Individual lints
|
||||||
rust.let_underscore_drop = "warn"
|
rust.let_underscore_drop = "warn"
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ and named after a small helper function I once wrote in JS.
|
||||||
## Show me a simple example
|
## Show me a simple example
|
||||||
|
|
||||||
```rs
|
```rs
|
||||||
use el::{Attr, Render, html::*};
|
use el::{Render, html::*};
|
||||||
|
|
||||||
let page: String = html((
|
let page: String = html((
|
||||||
head((
|
head((
|
||||||
|
|
@ -35,7 +35,7 @@ See the top-level crate documentation for more info.
|
||||||
|
|
||||||
## But what about that small helper function?
|
## But what about that small helper function?
|
||||||
|
|
||||||
Here it is in full, for posteriority:
|
Here it is in full, for posterity:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
function el(name, attributes, ...children) {
|
function el(name, attributes, ...children) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use axum_core::response::IntoResponse;
|
use axum_core::response::IntoResponse;
|
||||||
use http::{header, HeaderValue, StatusCode};
|
use http::{HeaderValue, StatusCode, header};
|
||||||
|
|
||||||
use crate::{Document, Render};
|
use crate::{Document, Render};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,9 @@ pub fn is_valid_raw_text(tag_name: &str, text: &str) -> bool {
|
||||||
// "[...] followed by characters that case-insensitively match the tag
|
// "[...] followed by characters that case-insensitively match the tag
|
||||||
// name of the element [...]"
|
// name of the element [...]"
|
||||||
//
|
//
|
||||||
// Note: Since we know that tag names are ascii-only, we can convert
|
// Note: Since we know that tag names are ascii-only, we can use an
|
||||||
// both to lowercase for a case-insensitive comparison without weird
|
// ASCII-based case insensitive comparison without unicode shenanigans.
|
||||||
// unicode shenanigans.
|
if !potential_tag_name.eq_ignore_ascii_case(tag_name) {
|
||||||
if potential_tag_name.to_ascii_lowercase() != tag_name.to_ascii_lowercase() {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::{btree_map::Entry, BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap, btree_map::Entry};
|
||||||
|
|
||||||
/// The kind of an element.
|
/// The kind of an element.
|
||||||
///
|
///
|
||||||
|
|
@ -501,8 +501,12 @@ element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11);
|
||||||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12);
|
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12);
|
||||||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13);
|
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13);
|
||||||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14);
|
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14);
|
||||||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15);
|
element_component_tuple!(
|
||||||
element_component_tuple!(C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16);
|
C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15
|
||||||
|
);
|
||||||
|
element_component_tuple!(
|
||||||
|
C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, C11, C12, C13, C14, C15, C16
|
||||||
|
);
|
||||||
|
|
||||||
/// A full HTML document including doctype.
|
/// A full HTML document including doctype.
|
||||||
///
|
///
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,35 @@ macro_rules! attr_enum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
(
|
||||||
|
$name:ident as $article:ident $actual:expr, separated by $separator:expr;
|
||||||
|
at $url:expr;
|
||||||
|
$( $valname:ident => $valstr:expr, )*
|
||||||
|
) => {
|
||||||
|
#[doc = concat!("Create (or append to) ", stringify!($article), " `", $actual, "` attribute")]
|
||||||
|
#[doc = concat!("(", $url, ").")]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum $name {
|
||||||
|
$(
|
||||||
|
#[doc = concat!("The value `", stringify!($valstr), "`.")]
|
||||||
|
$valname,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
$( Self::$valname => $valstr.fmt(f), )*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElementComponent for $name {
|
||||||
|
fn add_to_element(self, element: &mut Element) {
|
||||||
|
Attr::append($actual, self, $separator).add_to_element(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////
|
////////////////
|
||||||
|
|
@ -757,6 +786,40 @@ attr_enum! {
|
||||||
UnsafeUrl => "unsafe-url",
|
UnsafeUrl => "unsafe-url",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
attr_enum! {
|
||||||
|
Rel as a "rel", separated by " ";
|
||||||
|
at url!(normal, "rel");
|
||||||
|
Alternate => "alternate",
|
||||||
|
Author => "author",
|
||||||
|
Bookmark => "bookmark",
|
||||||
|
Canonical => "canonical",
|
||||||
|
DnsPrefetch => "dns-prefetch",
|
||||||
|
External => "external",
|
||||||
|
Expect => "expect",
|
||||||
|
Help => "help",
|
||||||
|
Icon => "icon",
|
||||||
|
License => "license",
|
||||||
|
Manifest => "manifest",
|
||||||
|
Me => "me",
|
||||||
|
Modulepreload => "modulepreload",
|
||||||
|
Next => "next",
|
||||||
|
Nofollow => "nofollow",
|
||||||
|
Noopener => "noopener",
|
||||||
|
Noreferrer => "noreferrer",
|
||||||
|
Opener => "opener",
|
||||||
|
Pingback => "pingback",
|
||||||
|
Preconnect => "preconnect",
|
||||||
|
Prefetch => "prefetch",
|
||||||
|
Preload => "preload",
|
||||||
|
Prerender => "prerender",
|
||||||
|
Prev => "prev",
|
||||||
|
PrivacyPolicy => "privacy-policy",
|
||||||
|
Search => "search",
|
||||||
|
Stylesheet => "stylesheet",
|
||||||
|
Tag => "tag",
|
||||||
|
TermsOfService => "terms-of-service",
|
||||||
|
}
|
||||||
|
|
||||||
attr_append! {
|
attr_append! {
|
||||||
rel as a "rel", separated by " ";
|
rel as a "rel", separated by " ";
|
||||||
at url!(normal, "rel");
|
at url!(normal, "rel");
|
||||||
|
|
|
||||||
34
src/lib.rs
34
src/lib.rs
|
|
@ -33,7 +33,7 @@
|
||||||
//! ## Usage example
|
//! ## Usage example
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```
|
||||||
//! use el::{Attr, Render, html::*};
|
//! use el::{Render, html::*};
|
||||||
//!
|
//!
|
||||||
//! let page: String = html((
|
//! let page: String = html((
|
||||||
//! head((
|
//! head((
|
||||||
|
|
@ -84,7 +84,7 @@ pub use self::{element::*, render::*};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{html::*, Attr, Element, Render};
|
use crate::{Attr, Content, Element, Render, html::*};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn simple_website() {
|
fn simple_website() {
|
||||||
|
|
@ -130,9 +130,11 @@ mod tests {
|
||||||
|
|
||||||
assert!(script("hello </script> world").render_to_string().is_err());
|
assert!(script("hello </script> world").render_to_string().is_err());
|
||||||
|
|
||||||
assert!(script("hello </ScRiPt ... world")
|
assert!(
|
||||||
.render_to_string()
|
script("hello </ScRiPt ... world")
|
||||||
.is_err());
|
.render_to_string()
|
||||||
|
.is_err()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -189,4 +191,26 @@ mod tests {
|
||||||
r#"<html lang="EN"></html>"#,
|
r#"<html lang="EN"></html>"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn comments() {
|
||||||
|
assert_eq!(
|
||||||
|
html(("<!--abc--> ", Content::comment("abc")))
|
||||||
|
.render_to_string()
|
||||||
|
.unwrap(),
|
||||||
|
r#"<html><!--abc--> <!--abc--></html>"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html(Content::comment("Hello <!-- world -->!"))
|
||||||
|
.render_to_string()
|
||||||
|
.unwrap(),
|
||||||
|
r#"<html><!--Hello <!== world ==>!--></html>"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
html(Content::comment("-><!-")).render_to_string().unwrap(),
|
||||||
|
r#"<html><!-- -><!- --></html>"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use std::{error, fmt};
|
use std::{error, fmt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
check,
|
Document, check,
|
||||||
element::{Content, Element, ElementKind},
|
element::{Content, Element, ElementKind},
|
||||||
Document,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The cause of an [`Error`].
|
/// The cause of an [`Error`].
|
||||||
|
|
@ -246,6 +245,8 @@ fn render_text<W: fmt::Write>(w: &mut W, text: &str) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_comment<W: fmt::Write>(w: &mut W, text: &str) -> Result<()> {
|
fn render_comment<W: fmt::Write>(w: &mut W, text: &str) -> Result<()> {
|
||||||
|
write!(w, "<!--")?;
|
||||||
|
|
||||||
// A comment...
|
// A comment...
|
||||||
// - must not start with the string ">"
|
// - must not start with the string ">"
|
||||||
// - must not start with the string "->"
|
// - must not start with the string "->"
|
||||||
|
|
@ -269,6 +270,7 @@ fn render_comment<W: fmt::Write>(w: &mut W, text: &str) -> Result<()> {
|
||||||
write!(w, " ")?;
|
write!(w, " ")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write!(w, "-->")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue