Skip to content

Commit 0e30fff

Browse files
authored
feat: add common conversion traits for Node and it's variants (#2)
2 parents 7b11ae9 + b041aa1 commit 0e30fff

File tree

17 files changed

+499
-221
lines changed

17 files changed

+499
-221
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ resolver = "2"
1313
license = "MIT"
1414
readme = "README.md"
1515
repository = "https://github.com/vidhanio/html-node"
16-
version = "0.2.0"
16+
version = "0.3.0"

html-node-core/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ html-escape = "0.2"
2323
paste = "1.0.14"
2424

2525
[features]
26-
axum = ["dep:axum"]
27-
typed = []
28-
serde = ["dep:serde"]
26+
axum = ["dep:axum"]
27+
pretty = []
28+
serde = ["dep:serde"]
29+
typed = []

html-node-core/src/http.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,20 @@
22
mod axum {
33
use axum::response::{Html, IntoResponse, Response};
44

5+
#[cfg(feature = "pretty")]
6+
use crate::pretty::Pretty;
57
use crate::Node;
68

79
impl IntoResponse for Node {
810
fn into_response(self) -> Response {
911
Html(self.to_string()).into_response()
1012
}
1113
}
14+
15+
#[cfg(feature = "pretty")]
16+
impl IntoResponse for Pretty {
17+
fn into_response(self) -> Response {
18+
Html(self.to_string()).into_response()
19+
}
20+
}
1221
}

html-node-core/src/lib.rs

Lines changed: 45 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,23 @@
88
#![warn(missing_docs)]
99
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
1010

11+
/// HTTP Server integrations.
1112
mod http;
1213

13-
#[allow(missing_docs)]
14+
/// [`crate::Node`] variant definitions.
15+
mod node;
16+
17+
/// Pretty printing utilities.
18+
#[cfg(feature = "pretty")]
19+
pub mod pretty;
20+
21+
/// Typed HTML Nodes.
1422
#[cfg(feature = "typed")]
1523
pub mod typed;
1624

1725
use std::fmt::{self, Display, Formatter};
1826

27+
pub use self::node::*;
1928
#[cfg(feature = "typed")]
2029
use self::typed::TypedElement;
2130

@@ -84,17 +93,18 @@ impl Node {
8493
pub fn from_typed<E: TypedElement>(element: E, children: Option<Vec<Self>>) -> Self {
8594
element.into_node(children)
8695
}
96+
97+
/// Wrap the node in a pretty-printing wrapper.
98+
#[cfg(feature = "pretty")]
99+
#[must_use]
100+
pub fn pretty(self) -> pretty::Pretty {
101+
self.into()
102+
}
87103
}
88104

89-
impl<I, N> From<I> for Node
90-
where
91-
I: IntoIterator<Item = N>,
92-
N: Into<Self>,
93-
{
94-
fn from(iter: I) -> Self {
95-
Self::Fragment(Fragment {
96-
children: iter.into_iter().map(Into::into).collect(),
97-
})
105+
impl Default for Node {
106+
fn default() -> Self {
107+
Self::EMPTY
98108
}
99109
}
100110

@@ -111,226 +121,48 @@ impl Display for Node {
111121
}
112122
}
113123

114-
impl Default for Node {
115-
fn default() -> Self {
116-
Self::EMPTY
124+
impl<I, N> From<I> for Node
125+
where
126+
I: IntoIterator<Item = N>,
127+
N: Into<Self>,
128+
{
129+
fn from(iter: I) -> Self {
130+
Self::Fragment(iter.into())
117131
}
118132
}
119133

120-
/// A comment.
121-
///
122-
/// ```html
123-
/// <!-- I'm a comment! -->
124-
/// ```
125-
#[derive(Debug, Clone, PartialEq, Eq)]
126-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127-
pub struct Comment {
128-
/// The text of the comment.
129-
///
130-
/// ```html
131-
/// <!-- comment -->
132-
/// ```
133-
pub comment: String,
134-
}
135-
136-
impl Display for Comment {
137-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
138-
write!(f, "<!-- {} -->", self.comment)
134+
impl From<Comment> for Node {
135+
fn from(comment: Comment) -> Self {
136+
Self::Comment(comment)
139137
}
140138
}
141139

142-
/// A doctype.
143-
///
144-
/// ```html
145-
/// <!DOCTYPE html>
146-
/// ```
147-
#[derive(Debug, Clone, PartialEq, Eq)]
148-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149-
pub struct Doctype {
150-
/// The value of the doctype.
151-
///
152-
/// ```html
153-
/// <!DOCTYPE synax>
154-
/// ```
155-
pub syntax: String,
156-
}
157-
158-
impl Display for Doctype {
159-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
160-
write!(f, "<!DOCTYPE {}>", self.syntax)
140+
impl From<Doctype> for Node {
141+
fn from(doctype: Doctype) -> Self {
142+
Self::Doctype(doctype)
161143
}
162144
}
163145

164-
/// A fragment.
165-
///
166-
/// ```html
167-
/// <>
168-
/// I'm in a fragment!
169-
/// </>
170-
/// ```
171-
#[derive(Debug, Clone, PartialEq, Eq)]
172-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173-
pub struct Fragment {
174-
/// The children of the fragment.
175-
///
176-
/// ```html
177-
/// <>
178-
/// <!-- I'm a child! -->
179-
/// <child>I'm another child!</child>
180-
/// </>
181-
pub children: Vec<Node>,
182-
}
183-
184-
impl Display for Fragment {
185-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
186-
write_children(f, &self.children, true)
146+
impl From<Fragment> for Node {
147+
fn from(fragment: Fragment) -> Self {
148+
Self::Fragment(fragment)
187149
}
188150
}
189151

190-
/// An element.
191-
///
192-
/// ```html
193-
/// <div class="container">
194-
/// I'm in an element!
195-
/// </div>
196-
/// ```
197-
#[derive(Debug, Clone, PartialEq, Eq)]
198-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199-
pub struct Element {
200-
/// The name of the element.
201-
///
202-
/// ```html
203-
/// <name>
204-
/// ```
205-
pub name: String,
206-
207-
/// The attributes of the element.
208-
///
209-
/// ```html
210-
/// <div attribute="value">
211-
/// ```
212-
pub attributes: Vec<(String, Option<String>)>,
213-
214-
/// The children of the element.
215-
///
216-
/// ```html
217-
/// <div>
218-
/// <!-- I'm a child! -->
219-
/// <child>I'm another child!</child>
220-
/// </div>
221-
/// ```
222-
pub children: Option<Vec<Node>>,
223-
}
224-
225-
impl Element {
226-
/// Create a new [`Element`] from a [`TypedElement`].
227-
#[cfg(feature = "typed")]
228-
pub fn from_typed<E: TypedElement>(element: E, children: Option<Vec<Node>>) -> Self {
229-
element.into_element(children)
230-
}
231-
}
232-
233-
impl Display for Element {
234-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
235-
write!(f, "<{}", self.name)?;
236-
237-
for (key, value) in &self.attributes {
238-
write!(f, " {key}")?;
239-
240-
if let Some(value) = value {
241-
let encoded_value = html_escape::encode_double_quoted_attribute(value);
242-
write!(f, r#"="{encoded_value}""#)?;
243-
}
244-
}
245-
write!(f, ">")?;
246-
247-
if let Some(children) = &self.children {
248-
write_children(f, children, false)?;
249-
250-
write!(f, "</{}>", self.name)?;
251-
};
252-
253-
Ok(())
254-
}
255-
}
256-
257-
/// A text node.
258-
///
259-
/// ```html
260-
/// <div>
261-
/// I'm a text node!
262-
/// </div>
263-
#[derive(Debug, Clone, PartialEq, Eq)]
264-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
265-
pub struct Text {
266-
/// The text of the node.
267-
///
268-
/// ```html
269-
/// <div>
270-
/// text
271-
/// </div>
272-
pub text: String,
273-
}
274-
275-
impl Display for Text {
276-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
277-
let encoded_value = html_escape::encode_text_minimal(&self.text);
278-
write!(f, "{encoded_value}")
152+
impl From<Element> for Node {
153+
fn from(element: Element) -> Self {
154+
Self::Element(element)
279155
}
280156
}
281157

282-
/// An unsafe text node.
283-
///
284-
/// # Warning
285-
///
286-
/// [`UnsafeText`] is not escaped when rendered, and as such, can allow
287-
/// for XSS attacks. Use with caution!
288-
#[derive(Debug, Clone, PartialEq, Eq)]
289-
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
290-
pub struct UnsafeText {
291-
/// The text of the node.
292-
pub text: String,
293-
}
294-
295-
impl Display for UnsafeText {
296-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
297-
write!(f, "{}", self.text)
158+
impl From<Text> for Node {
159+
fn from(text: Text) -> Self {
160+
Self::Text(text)
298161
}
299162
}
300163

301-
/// Writes the children of a node.
302-
///
303-
/// If the formatter is in alternate mode, then the children are put on their
304-
/// own lines.
305-
///
306-
/// If alternate mode is enabled and `is_fragment` is false, then each line
307-
/// is indented by 4 spaces.
308-
fn write_children(f: &mut Formatter<'_>, children: &[Node], is_fragment: bool) -> fmt::Result {
309-
if f.alternate() {
310-
let mut children_iter = children.iter();
311-
312-
if is_fragment {
313-
if let Some(first_child) = children_iter.next() {
314-
write!(f, "{first_child:#}")?;
315-
316-
for child in children_iter {
317-
write!(f, "\n{child:#}")?;
318-
}
319-
}
320-
} else {
321-
for child_str in children_iter.map(|child| format!("{child:#}")) {
322-
for line in child_str.lines() {
323-
write!(f, "\n {line}")?;
324-
}
325-
}
326-
327-
// exit inner block
328-
writeln!(f)?;
329-
}
330-
} else {
331-
for child in children {
332-
child.fmt(f)?;
333-
}
164+
impl From<UnsafeText> for Node {
165+
fn from(text: UnsafeText) -> Self {
166+
Self::UnsafeText(text)
334167
}
335-
Ok(())
336168
}

html-node-core/src/node.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
use std::fmt::{self, Display, Formatter};
2+
3+
mod comment;
4+
mod doctype;
5+
mod element;
6+
mod fragment;
7+
mod text;
8+
mod unsafe_text;
9+
10+
pub use self::{
11+
comment::Comment, doctype::Doctype, element::Element, fragment::Fragment, text::Text,
12+
unsafe_text::UnsafeText,
13+
};
14+
use crate::Node;
15+
16+
/// Writes the children of a node.
17+
///
18+
/// If the formatter is in alternate mode, then the children are put on their
19+
/// own lines.
20+
///
21+
/// If alternate mode is enabled and `is_fragment` is false, then each line
22+
/// is indented by 4 spaces.
23+
fn write_children(f: &mut Formatter<'_>, children: &[Node], is_fragment: bool) -> fmt::Result {
24+
if f.alternate() {
25+
let mut children_iter = children.iter();
26+
27+
if is_fragment {
28+
if let Some(first_child) = children_iter.next() {
29+
write!(f, "{first_child:#}")?;
30+
31+
for child in children_iter {
32+
write!(f, "\n{child:#}")?;
33+
}
34+
}
35+
} else {
36+
for child_str in children_iter.map(|child| format!("{child:#}")) {
37+
for line in child_str.lines() {
38+
write!(f, "\n {line}")?;
39+
}
40+
}
41+
42+
// exit inner block
43+
writeln!(f)?;
44+
}
45+
} else {
46+
for child in children {
47+
child.fmt(f)?;
48+
}
49+
}
50+
Ok(())
51+
}

0 commit comments

Comments
 (0)