Compare commits

...

11 commits

Author SHA1 Message Date
01d9e06d63 Run cargo fmt 2026-04-20 22:29:53 +02:00
d26ef729eb Update edition, URL, and lints 2026-04-20 22:29:38 +02:00
f08eda2758 Satisfy clippy 2025-04-16 22:16:08 +02:00
190e00ed62 Fix typo 2025-04-16 22:12:21 +02:00
bb7dedc9eb Bump version to 0.2.0 2025-01-01 22:40:28 +01:00
59b81d637b Relax lower bound on http dependency
Originally, I just used the current version when adding the dependency,
but I see no reason to forbid earlier compatible versions.
2025-01-01 22:37:44 +01:00
ec7a461758 Update axum-core to 0.5.0 2025-01-01 22:09:32 +01:00
353934381b Bump version to 0.1.3 2024-12-21 20:04:46 +01:00
0167d3cea3 Fix html comment rendering
Apparently I just completely forgot to actually include <!-- and -->,
and instead just rendered the comment contents.
2024-12-20 16:51:11 +01:00
ec7bc571b1 Add Rel attr 2024-12-19 18:02:26 +01:00
7290a23c85 Simplify usage example 2024-12-17 01:44:41 +01:00
9 changed files with 137 additions and 26 deletions

View file

@ -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

View file

@ -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"

View file

@ -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) {

View file

@ -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};

View file

@ -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;
} }

View file

@ -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.
/// ///

View file

@ -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");

View file

@ -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>&lt;!--abc--&gt; <!--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>"#,
);
}
} }

View file

@ -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(())
} }