Skip to content

Commit

Permalink
add anchor
Browse files Browse the repository at this point in the history
  • Loading branch information
jcornaz committed Nov 1, 2024
1 parent dd40b7f commit 2eb680c
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Meta (`meta`, `title`)
* Text (`h1` to `h6`, and `text`)
* Container (`div`)
* Anchor (`a`) and related attributes (`href`, `download`)
* Escape hatches (`raw` and `raw_unsafe`)
* implement `From<(&'static str, Cow<'static, str)>` for `Attribute`
* implement `From<&'static str>` and `From<String>` for `Element`
Expand Down
8 changes: 8 additions & 0 deletions src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ pub fn id(id: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("id", id)
}

pub fn href(url: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("href", url)
}

pub fn download() -> Attribute {
Attribute::new_flag("download")
}

pub fn class<'a>(classes: impl IntoIterator<Item = &'a str>) -> Attribute {
let mut values = String::new();
let mut iter = classes.into_iter();
Expand Down
7 changes: 7 additions & 0 deletions src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ pub fn h6(
Element::new("h6", attributes, children)
}

pub fn a(
attributes: impl IntoIterator<Item = Attribute>,
children: impl IntoIterator<Item = Element>,
) -> Element {
Element::new("a", attributes, children)
}

/// HTML escaped text
pub fn text(value: impl Into<Cow<'static, str>>) -> Element {
ElementInner::Text(value.into()).into()
Expand Down
22 changes: 18 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub struct Attribute {
#[derive(Debug, Clone)]
enum AttributeValue {
String(Cow<'static, str>),
Flag,
}

impl Default for Document {
Expand Down Expand Up @@ -147,22 +148,35 @@ fn write_attributes(
attribute.name,
html_escape::encode_double_quoted_attribute(s)
)?,
AttributeValue::Flag => write!(f, " {}", attribute.name,)?,
}
}
Ok(())
}

impl Attribute {
pub fn new(name: &'static str, value: impl Into<Cow<'static, str>>) -> Self {
debug_assert!(
!name.is_empty() && name.chars().all(|c| !c.is_whitespace()),
"invalid attribute name: '{name}'"
);
assert_valid_attribute_name(name);
Self {
name,
value: AttributeValue::String(value.into()),
}
}

pub fn new_flag(name: &'static str) -> Self {
assert_valid_attribute_name(name);
Self {
name,
value: AttributeValue::Flag,
}
}
}

fn assert_valid_attribute_name(name: &str) {
debug_assert!(
!name.is_empty() && name.chars().all(|c| !c.is_whitespace()),
"invalid attribute name: '{name}'"
);
}

pub fn html(
Expand Down
9 changes: 9 additions & 0 deletions tests/model_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ fn should_panic_for_invalid_attribute_name(
Attribute::new(name, "value");
}

#[rstest]
#[cfg(debug_assertions)]
#[should_panic]
fn should_panic_for_invalid_attribute_flag_name(
#[values("hello world", "hello\tworld", "hello\nworld", "")] name: &'static str,
) {
Attribute::new_flag(name);
}

#[rstest]
#[cfg(debug_assertions)]
#[should_panic]
Expand Down
3 changes: 3 additions & 0 deletions tests/render_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ fn should_render_html_document() {
#[case(id("foo"), "id=\"foo\"")]
#[case(class(["foo"]), "class=\"foo\"")]
#[case(class(["foo", "bar"]), "class=\"foo bar\"")]
#[case(href("foo"), "href=\"foo\"")]
#[case(download(), "download")]
fn should_render_attribute(#[case] attr: Attribute, #[case] expected: &str) {
let string = div([attr], []).to_string();
assert_eq!(string, format!("<div {expected}></div>"));
Expand All @@ -56,6 +58,7 @@ fn should_render_attribute(#[case] attr: Attribute, #[case] expected: &str) {
#[case(h4([id("foo")], [text("hello")]), "<h4 id=\"foo\">hello</h4>")]
#[case(h5([id("foo")], [text("hello")]), "<h5 id=\"foo\">hello</h5>")]
#[case(h6([id("foo")], [text("hello")]), "<h6 id=\"foo\">hello</h6>")]
#[case(a([href("/somepath")], ["visit this cool link!".into()]), "<a href=\"/somepath\">visit this cool link!</a>")]
fn should_render_element(#[case] def: Element, #[case] expected: &str) {
assert_eq!(def.to_string(), expected);
}
Expand Down

0 comments on commit 2eb680c

Please sign in to comment.