Skip to content

Commit

Permalink
write documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jcornaz committed Nov 2, 2024
1 parent f47a898 commit 6cd157f
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 28 deletions.
69 changes: 57 additions & 12 deletions src/attr.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
//! Common attributes
//!
//! Note that you may create your own attribute by using [`Attribute::new`] or [`Attribute::new_flag`]
//! Or by leveraging on of the `From` implementation on [`Attribute`]
use alloc::{borrow::Cow, string::String};

use crate::Attribute;
Expand All @@ -8,96 +13,120 @@ impl<T: Into<Cow<'static, str>>> From<(&'static str, T)> for Attribute {
}
}

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

/// `id` attribute
pub fn id(id: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("id", id)
}

pub enum Target {
/// `lang` attribute (usually on `html` element)
pub fn lang(lang: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("lang", lang)
}

/// Represent an anchor target
pub enum AnchorTarget {
/// `_blank`
Blank,
/// `_self`
Self_,
/// `_parent`
Parent,
/// `_top`
Top,
/// Frame name
Frame(Cow<'static, str>),
}

pub fn target(target: Target) -> Attribute {
/// `target` attribute for `<a>`
pub fn target(target: AnchorTarget) -> Attribute {
Attribute::new(
"target",
match target {
Target::Blank => "_blank".into(),
Target::Self_ => "_self".into(),
Target::Parent => "_parent".into(),
Target::Top => "_top".into(),
Target::Frame(name) => name,
AnchorTarget::Blank => "_blank".into(),
AnchorTarget::Self_ => "_self".into(),
AnchorTarget::Parent => "_parent".into(),
AnchorTarget::Top => "_top".into(),
AnchorTarget::Frame(name) => name,
},
)
}

/// Alias for `target(Target::Blank)`
pub fn target_blank() -> Attribute {
target(Target::Blank)
target(AnchorTarget::Blank)
}

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

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

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

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

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

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

/// `type` atribute
pub fn type_(url: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("type", url)
}

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

/// `defer` atribute
pub fn defer() -> Attribute {
Attribute::new_flag("defer")
}

/// `async` atribute
pub fn async_() -> Attribute {
Attribute::new_flag("async")
}

/// `crossorigin="anonymous"`
pub fn crossorigin_anonymous() -> Attribute {
Attribute::new("crossorigin", "anonymous")
}

/// `crossorigin="use-credentials"`
pub fn crossorigin_use_credentials() -> Attribute {
Attribute::new("crossorigin", "use-credentials")
}

/// `download` flag attribute
pub fn download() -> Attribute {
Attribute::new_flag("download")
}

/// `download` attribute with a file name argument
pub fn download_with_name(name: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("download", name)
}

/// `charset` attribute
pub fn charset(charset: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("charset", charset)
}
Expand All @@ -107,14 +136,30 @@ pub fn charset_utf_8() -> Attribute {
charset("UTF-8")
}

/// `name` attribute
pub fn name(name: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("name", name)
}

/// `content` attribute
pub fn content(content: impl Into<Cow<'static, str>>) -> Attribute {
Attribute::new("content", content)
}

/// `class` attribute
///
/// It takes a list of clases and join them together
///
/// ## Example
///
/// ```
/// # use fun_html::attr::class;
///
/// assert_eq!(
/// class(["foo", "bar", "baz"]).to_string(),
/// r#"class="foo bar baz""#,
/// );
/// ```
pub fn class<'a>(classes: impl IntoIterator<Item = &'a str>) -> Attribute {
let mut values = String::new();
let mut iter = classes.into_iter();
Expand Down
6 changes: 6 additions & 0 deletions src/elt.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
//! Common HTML elements
//!
//! Note that you may create your own element with [`Element::new`] or [`Element::new_void`]
//!
//! It is also possible to inline raw HTML with [`raw`] and [`raw_unsafe`]
use alloc::{borrow::Cow, string::String, vec::Vec};

use crate::{Attribute, Element, ElementInner};
Expand Down
66 changes: 55 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs)]

//! This crate provides a simple and efficient way to generate HTML using Rust functions,
//! with an intuitive and composable API to create HTML elements.
Expand Down Expand Up @@ -53,8 +54,10 @@ use alloc::{borrow::Cow, fmt::Display, vec::Vec};

/// An HTML document (`<!DOCTYPE html>`)
///
/// ## Example
///
/// ```
/// use fun_html::{Document, html, elt::{head, body}};
/// # use fun_html::{Document, html, elt::{head, body}};
/// let doc: Document = html([], [head([], []), body([], [])]);
///
/// assert_eq!(doc.to_string(), "<!DOCTYPE html>\n<html><head></head><body></body></html>");
Expand All @@ -64,8 +67,12 @@ pub struct Document(Element);

/// An HTML element
///
/// It can be created via [`Self::new`], [`Self::new_void`]
///
/// ## Example
///
/// ```
/// use fun_html::{Element, elt::div};
/// # use fun_html::{Element, elt::div};
/// let element: Element = div([], []);
///
/// assert_eq!(element.to_string(), "<div></div>");
Expand All @@ -90,6 +97,22 @@ enum ElementInner {
Multiple(Vec<Element>),
}

/// An attribute
///
/// It can be created via [`Self::new`], [`Self::new_flag`]
/// or by converting from ither `(&'static str, &'static str)` or `(&'static str, String)`.
///
/// See [`attr`] for a collection of common attributes
///
/// ## Example
///
/// ```
/// # use fun_html::attr::id;
/// assert_eq!(
/// id("foo").to_string(),
/// r#"id="foo""#,
/// )
/// ```
#[derive(Debug, Clone)]
pub struct Attribute {
name: &'static str,
Expand Down Expand Up @@ -135,6 +158,8 @@ impl Element {

/// Create a new [void] HTML element from its tag and attributes
///
/// ("void" element cannot have children and do not need a closing tag)
///
/// [void]: https://developer.mozilla.org/en-US/docs/Glossary/Void_element
pub fn new_void(tag: &'static str, attributes: impl IntoIterator<Item = Attribute>) -> Self {
assert_valid_tag_name(tag);
Expand Down Expand Up @@ -197,20 +222,25 @@ fn write_attributes(
attributes: &Vec<Attribute>,
) -> Result<(), alloc::fmt::Error> {
for attribute in attributes {
match &attribute.value {
AttributeValue::String(s) => write!(
f,
" {}=\"{}\"",
attribute.name,
html_escape::encode_double_quoted_attribute(s)
)?,
AttributeValue::Flag => write!(f, " {}", attribute.name,)?,
}
write!(f, " {attribute}")?;
}
Ok(())
}

impl Display for Attribute {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name)?;
match &self.value {
AttributeValue::String(s) => {
write!(f, "=\"{}\"", html_escape::encode_double_quoted_attribute(s))
}
AttributeValue::Flag => Ok(()),
}
}
}

impl Attribute {
/// Create a new attribute
pub fn new(name: &'static str, value: impl Into<Cow<'static, str>>) -> Self {
assert_valid_attribute_name(name);
Self {
Expand All @@ -219,6 +249,7 @@ impl Attribute {
}
}

/// Create a new flag attribute (that doesn't take a value)
pub fn new_flag(name: &'static str) -> Self {
assert_valid_attribute_name(name);
Self {
Expand All @@ -235,6 +266,19 @@ fn assert_valid_attribute_name(name: &str) {
);
}

/// Create an HTML [`Document`]
///
/// You must pass the [`elt::head`] and [`elt::body`] element as you would with any other element.
///
/// ## Example
///
/// ```
/// # use fun_html::{html, elt::{head, body, title, h1}, attr::{lang}};
/// let document = html([lang("en")], [
/// head([], [title([], "Greetings")]),
/// body([], [h1([], ["Hello world!".into()])]),
/// ]);
/// ```
pub fn html(
attributes: impl IntoIterator<Item = Attribute>,
children: impl IntoIterator<Item = Element>,
Expand Down
10 changes: 5 additions & 5 deletions tests/render_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ fn should_render_html_document() {
#[case(crossorigin_use_credentials(), "crossorigin=\"use-credentials\"")]
#[case(download(), "download")]
#[case(download_with_name("myfile.txt"), "download=\"myfile.txt\"")]
#[case(target(Target::Blank), "target=\"_blank\"")]
#[case(target(AnchorTarget::Blank), "target=\"_blank\"")]
#[case(target_blank(), "target=\"_blank\"")]
#[case(target(Target::Self_), "target=\"_self\"")]
#[case(target(Target::Top), "target=\"_top\"")]
#[case(target(Target::Parent), "target=\"_parent\"")]
#[case(target(Target::Frame("myframe".into())), "target=\"myframe\"")]
#[case(target(AnchorTarget::Self_), "target=\"_self\"")]
#[case(target(AnchorTarget::Top), "target=\"_top\"")]
#[case(target(AnchorTarget::Parent), "target=\"_parent\"")]
#[case(target(AnchorTarget::Frame("myframe".into())), "target=\"myframe\"")]
#[case(charset("foobar"), "charset=\"foobar\"")]
#[case(charset_utf_8(), "charset=\"UTF-8\"")]
#[case(name("hello"), "name=\"hello\"")]
Expand Down

0 comments on commit 6cd157f

Please sign in to comment.