Skip to content

Commit b2366f4

Browse files
committed
chore: Add error handling, lints & docs
1 parent ea74258 commit b2366f4

File tree

3 files changed

+129
-11
lines changed

3 files changed

+129
-11
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,5 @@ edition = "2018"
77
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
88

99
[dependencies]
10-
html5ever = "*"
1110
cssparser = "*"
1211
kuchiki = "0.8"

src/error.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//! Errors that may happen during inlining.
2+
use std::io;
3+
4+
/// Inlining error
5+
#[derive(Debug)]
6+
pub enum InlineError {
7+
/// Input-output error. May happen during writing the resulting HTML.
8+
IO(io::Error),
9+
/// Syntax errors or unsupported selectors.
10+
ParseError,
11+
}
12+
13+
impl From<io::Error> for InlineError {
14+
fn from(error: io::Error) -> Self {
15+
InlineError::IO(error)
16+
}
17+
}

src/lib.rs

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,114 @@
1+
//! # css-inline
2+
//!
3+
//! A crate for inlining CSS into HTML documents. When you send HTML emails you need to use "style"
4+
//! attributes instead of "style" tags.
5+
//!
6+
//! For example, this HTML:
7+
//!
8+
//! ```html
9+
//! <html>
10+
//! <head>
11+
//! <title>Test</title>
12+
//! <style>
13+
//! h1, h2 { color:blue; }
14+
//! strong { text-decoration:none }
15+
//! p { font-size:2px }
16+
//! p.footer { font-size: 1px}
17+
//! </style>
18+
//! </head>
19+
//! <body>
20+
//! <h1>Big Text</h1>
21+
//! <p>
22+
//! <strong>Solid</strong>
23+
//! </p>
24+
//! <p class="footer">Foot notes</p>
25+
//! </body>
26+
//! </html>
27+
//! ```
28+
//!
29+
//! Will be turned into this:
30+
//!
31+
//! ```html
32+
//! <html>
33+
//! <head>
34+
//! <title>Test</title>
35+
//! </head>
36+
//! <body>
37+
//! <h1 style="color:blue;">Big Text</h1>
38+
//! <p style="font-size:2px;">
39+
//! <strong style="text-decoration:none;">Solid</strong>
40+
//! </p>
41+
//! <p style="font-size:1px;">Foot notes</p>
42+
//! </body>
43+
//! </html>
44+
//! ```
45+
//!
46+
//! ## Example:
47+
//!
48+
//! ```rust
49+
//! const HTML: &str = r#"<html>
50+
//! <head>
51+
//! <title>Test</title>
52+
//! <style>
53+
//! h1, h2 { color:blue; }
54+
//! strong { text-decoration:none }
55+
//! p { font-size:2px }
56+
//! p.footer { font-size: 1px}
57+
//! </style>
58+
//! </head>
59+
//! <body>
60+
//! <h1>Big Text</h1>
61+
//! <p>
62+
//! <strong>Solid</strong>
63+
//! </p>
64+
//! <p class="footer">Foot notes</p>
65+
//! </body>
66+
//! </html>"#;
67+
//!
68+
//!fn main() -> Result<(), css_inline::InlineError> {
69+
//! let inlined = css_inline::inline(HTML)?;
70+
//! // Do something with inlined HTML, e.g. send an email
71+
//! Ok(())
72+
//! }
73+
//!
74+
//! ```
75+
#![warn(
76+
clippy::doc_markdown,
77+
clippy::redundant_closure,
78+
clippy::explicit_iter_loop,
79+
clippy::match_same_arms,
80+
clippy::needless_borrow,
81+
clippy::print_stdout,
82+
clippy::integer_arithmetic,
83+
clippy::cast_possible_truncation,
84+
clippy::result_unwrap_used,
85+
clippy::result_map_unwrap_or_else,
86+
clippy::option_unwrap_used,
87+
clippy::option_map_unwrap_or_else,
88+
clippy::option_map_unwrap_or,
89+
clippy::trivially_copy_pass_by_ref,
90+
clippy::needless_pass_by_value,
91+
missing_docs,
92+
missing_debug_implementations,
93+
trivial_casts,
94+
trivial_numeric_casts,
95+
unused_extern_crates,
96+
unused_import_braces,
97+
unused_qualifications,
98+
variant_size_differences
99+
)]
1100
use crate::parse::Declaration;
2101
use kuchiki::traits::TendrilSink;
3102
use kuchiki::{parse_html, ElementData, NodeDataRef, Selectors};
4-
use std::io;
5103

104+
pub mod error;
6105
mod parse;
7106

107+
pub use error::InlineError;
108+
8109
#[derive(Debug)]
9110
struct Rule {
10-
selectors: kuchiki::Selectors,
111+
selectors: Selectors,
11112
declarations: Vec<Declaration>,
12113
}
13114

@@ -20,7 +121,7 @@ impl Rule {
20121
}
21122
}
22123

23-
fn process_style_node(node: NodeDataRef<ElementData>) -> Vec<Rule> {
124+
fn process_style_node(node: &NodeDataRef<ElementData>) -> Vec<Rule> {
24125
let css = node.text_contents();
25126
let mut parse_input = cssparser::ParserInput::new(css.as_str());
26127
let mut parser = parse::CSSParser::new(&mut parse_input);
@@ -31,16 +132,17 @@ fn process_style_node(node: NodeDataRef<ElementData>) -> Vec<Rule> {
31132
.ok()
32133
})
33134
.collect::<Result<Vec<_>, _>>()
34-
.unwrap()
135+
.map_err(|_| error::InlineError::ParseError)
136+
.expect("Parsing error") // Should return Result instead
35137
}
36138

37139
/// Inline CSS styles from <style> tags to matching elements in the HTML tree.
38-
pub fn inline(html: &str) -> Result<String, io::Error> {
140+
pub fn inline(html: &str) -> Result<String, InlineError> {
39141
let document = parse_html().one(html);
40142
let rules = document
41143
.select("style")
42-
.unwrap()
43-
.map(process_style_node)
144+
.map_err(|_| error::InlineError::ParseError)?
145+
.map(|ref node| process_style_node(node))
44146
.flatten();
45147

46148
for rule in rules {
@@ -63,9 +165,9 @@ pub fn inline(html: &str) -> Result<String, io::Error> {
63165
let mut out = vec![];
64166
document
65167
.select("html")
66-
.unwrap()
168+
.map_err(|_| error::InlineError::ParseError)?
67169
.next()
68-
.unwrap()
170+
.expect("HTML tag should be present") // Should it?
69171
.as_node()
70172
.serialize(&mut out)?;
71173
Ok(String::from_utf8_lossy(&out).to_string())
@@ -96,7 +198,7 @@ p.footer { font-size: 1px}
96198

97199
#[test]
98200
fn test_inline() {
99-
let inlined = inline(HTML).unwrap();
201+
let inlined = inline(HTML).expect("Should be valid");
100202
assert_eq!(
101203
inlined,
102204
r#"<html><head>

0 commit comments

Comments
 (0)