Skip to content

Commit 35c62ea

Browse files
committed
feat: Option to inline additional CSS
1 parent f234621 commit 35c62ea

File tree

7 files changed

+66
-16
lines changed

7 files changed

+66
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- Option to disable processing of "style" tags. [#45](https://github.com/Stranger6667/css-inline/issues/45)
8+
- Option to inline additional CSS. [#45](https://github.com/Stranger6667/css-inline/issues/45)
89

910
## [0.3.3] - 2020-07-07
1011

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ ARGS:
114114
"inlined.example.html".
115115

116116
OPTIONS:
117+
--inline-style-tags
118+
Whether to inline CSS from "style" tags. The default value is `true`. To disable inlining
119+
from "style" tags use `-inline-style-tags=false`.
120+
117121
--remove-style-tags
118122
Remove "style" tags after inlining.
119123

@@ -122,4 +126,7 @@ OPTIONS:
122126

123127
--load-remote-stylesheets
124128
Whether remote stylesheets should be loaded or not.
129+
130+
--extra-css
131+
Additional CSS to inline.
125132
```

python/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Added
66

77
- Option to disable processing of "style" tags. [#45](https://github.com/Stranger6667/css-inline/issues/45)
8+
- Option to inline additional CSS. [#45](https://github.com/Stranger6667/css-inline/issues/45)
89

910
## [0.3.2] - 2020-07-09
1011

python/src/lib.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use css_inline as rust_inline;
22
use pyo3::{create_exception, exceptions, prelude::*, types::PyList, wrap_pyfunction};
33
use rayon::prelude::*;
4+
use std::borrow::Cow;
45

56
const MODULE_DOCSTRING: &str = "Fast CSS inlining written in Rust";
67
const INLINE_ERROR_DOCSTRING: &str = "An error that can occur during CSS inlining";
@@ -35,13 +36,13 @@ fn parse_url(url: Option<String>) -> PyResult<Option<url::Url>> {
3536
})
3637
}
3738

38-
/// CSSInliner(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)
39+
/// CSSInliner(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)
3940
///
4041
/// Customizable CSS inliner.
4142
#[pyclass]
42-
#[text_signature = "(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)"]
43+
#[text_signature = "(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)"]
4344
struct CSSInliner {
44-
inner: rust_inline::CSSInliner,
45+
inner: rust_inline::CSSInliner<'static>,
4546
}
4647

4748
#[pymethods]
@@ -52,12 +53,14 @@ impl CSSInliner {
5253
remove_style_tags: Option<bool>,
5354
base_url: Option<String>,
5455
load_remote_stylesheets: Option<bool>,
56+
extra_css: Option<String>,
5557
) -> PyResult<Self> {
5658
let options = rust_inline::InlineOptions {
5759
inline_style_tags: inline_style_tags.unwrap_or(true),
5860
remove_style_tags: remove_style_tags.unwrap_or(false),
5961
base_url: parse_url(base_url)?,
6062
load_remote_stylesheets: load_remote_stylesheets.unwrap_or(true),
63+
extra_css: extra_css.map(Cow::Owned),
6164
};
6265
Ok(CSSInliner {
6366
inner: rust_inline::CSSInliner::new(options),
@@ -81,45 +84,49 @@ impl CSSInliner {
8184
}
8285
}
8386

84-
/// inline(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)
87+
/// inline(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)
8588
///
8689
/// Inline CSS in the given HTML document
8790
#[pyfunction]
88-
#[text_signature = "(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)"]
91+
#[text_signature = "(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)"]
8992
fn inline(
9093
html: &str,
9194
inline_style_tags: Option<bool>,
9295
remove_style_tags: Option<bool>,
9396
base_url: Option<String>,
9497
load_remote_stylesheets: Option<bool>,
98+
extra_css: Option<&str>,
9599
) -> PyResult<String> {
96100
let options = rust_inline::InlineOptions {
97101
inline_style_tags: inline_style_tags.unwrap_or(true),
98102
remove_style_tags: remove_style_tags.unwrap_or(false),
99103
base_url: parse_url(base_url)?,
100104
load_remote_stylesheets: load_remote_stylesheets.unwrap_or(true),
105+
extra_css: extra_css.map(Cow::Borrowed),
101106
};
102107
let inliner = rust_inline::CSSInliner::new(options);
103108
Ok(inliner.inline(html).map_err(InlineErrorWrapper)?)
104109
}
105110

106-
/// inline_many(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)
111+
/// inline_many(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)
107112
///
108113
/// Inline CSS in multiple HTML documents
109114
#[pyfunction]
110-
#[text_signature = "(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)"]
115+
#[text_signature = "(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)"]
111116
fn inline_many(
112117
htmls: &PyList,
113118
inline_style_tags: Option<bool>,
114119
remove_style_tags: Option<bool>,
115120
base_url: Option<String>,
116121
load_remote_stylesheets: Option<bool>,
122+
extra_css: Option<&str>,
117123
) -> PyResult<Vec<String>> {
118124
let options = rust_inline::InlineOptions {
119125
inline_style_tags: inline_style_tags.unwrap_or(true),
120126
remove_style_tags: remove_style_tags.unwrap_or(false),
121127
base_url: parse_url(base_url)?,
122128
load_remote_stylesheets: load_remote_stylesheets.unwrap_or(true),
129+
extra_css: extra_css.map(Cow::Borrowed),
123130
};
124131
let inliner = rust_inline::CSSInliner::new(options);
125132
inline_many_impl(&inliner, htmls)

src/lib.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ pub use url::{ParseError, Url};
130130

131131
/// Configuration options for CSS inlining process.
132132
#[derive(Debug)]
133-
pub struct InlineOptions {
133+
pub struct InlineOptions<'a> {
134134
/// Whether to inline CSS from "style" tags
135135
pub inline_style_tags: bool,
136136
/// Remove "style" tags after inlining
@@ -139,9 +139,14 @@ pub struct InlineOptions {
139139
pub base_url: Option<Url>,
140140
/// Whether remote stylesheets should be loaded or not
141141
pub load_remote_stylesheets: bool,
142+
// The point of using `Cow` here is bindings, where it is problematic to pass a reference
143+
// without dealing with memory leaks & unsafe. With `Cow` we can use moved values as `String` in
144+
// Python wrapper for `CSSInliner` and `&str` in Rust & simple functions on the Python side
145+
/// Additional CSS to inline
146+
pub extra_css: Option<Cow<'a, str>>,
142147
}
143148

144-
impl InlineOptions {
149+
impl InlineOptions<'_> {
145150
/// Options for "compact" HTML output
146151
#[inline]
147152
pub fn compact() -> Self {
@@ -150,18 +155,20 @@ impl InlineOptions {
150155
remove_style_tags: true,
151156
base_url: None,
152157
load_remote_stylesheets: true,
158+
extra_css: None,
153159
}
154160
}
155161
}
156162

157-
impl Default for InlineOptions {
163+
impl Default for InlineOptions<'_> {
158164
#[inline]
159165
fn default() -> Self {
160166
InlineOptions {
161167
inline_style_tags: true,
162168
remove_style_tags: false,
163169
base_url: None,
164170
load_remote_stylesheets: true,
171+
extra_css: None,
165172
}
166173
}
167174
}
@@ -170,14 +177,14 @@ type Result<T> = std::result::Result<T, InlineError>;
170177

171178
/// Customizable CSS inliner.
172179
#[derive(Debug)]
173-
pub struct CSSInliner {
174-
options: InlineOptions,
180+
pub struct CSSInliner<'a> {
181+
options: InlineOptions<'a>,
175182
}
176183

177-
impl CSSInliner {
184+
impl<'a> CSSInliner<'a> {
178185
/// Create a new `CSSInliner` instance with given options.
179186
#[inline]
180-
pub fn new(options: InlineOptions) -> Self {
187+
pub fn new(options: InlineOptions<'a>) -> Self {
181188
CSSInliner { options }
182189
}
183190

@@ -241,6 +248,9 @@ impl CSSInliner {
241248
}
242249
}
243250
}
251+
if let Some(extra_css) = &self.options.extra_css {
252+
process_css(&document, extra_css)?;
253+
}
244254
document.serialize(target)?;
245255
Ok(())
246256
}
@@ -315,7 +325,7 @@ fn process_css(document: &NodeRef, css: &str) -> Result<()> {
315325
Ok(())
316326
}
317327

318-
impl Default for CSSInliner {
328+
impl Default for CSSInliner<'_> {
319329
#[inline]
320330
fn default() -> Self {
321331
CSSInliner::new(Default::default())

src/main.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use css_inline::{CSSInliner, InlineOptions};
22
use rayon::prelude::*;
3+
use std::borrow::Cow;
34
use std::error::Error;
45
use std::fs::File;
56
use std::io::{self, Read};
@@ -38,7 +39,10 @@ OPTIONS:
3839
Used for loading external stylesheets via relative URLs.
3940
4041
--load-remote-stylesheets
41-
Whether remote stylesheets should be loaded or not."#
42+
Whether remote stylesheets should be loaded or not.
43+
44+
--extra-css
45+
Additional CSS to inline."#
4246
);
4347

4448
struct Args {
@@ -47,6 +51,7 @@ struct Args {
4751
inline_style_tags: bool,
4852
remove_style_tags: bool,
4953
base_url: Option<String>,
54+
extra_css: Option<String>,
5055
load_remote_stylesheets: bool,
5156
files: Vec<String>,
5257
}
@@ -69,6 +74,7 @@ fn main() -> Result<(), Box<dyn Error>> {
6974
.unwrap_or(true),
7075
remove_style_tags: args.contains("--remove-style-tags"),
7176
base_url: args.opt_value_from_str("--base-url")?,
77+
extra_css: args.opt_value_from_str("--extra-css")?,
7278
load_remote_stylesheets: args.contains("--load-remote-stylesheets"),
7379
files: args.free()?,
7480
};
@@ -83,6 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
8389
remove_style_tags: args.remove_style_tags,
8490
base_url: parse_url(args.base_url)?,
8591
load_remote_stylesheets: args.load_remote_stylesheets,
92+
extra_css: args.extra_css.as_deref().map(Cow::Borrowed),
8693
};
8794
let inliner = CSSInliner::new(options);
8895
if args.files.is_empty() {

tests/test_inlining.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use css_inline::{inline, CSSInliner, InlineOptions, Url};
2+
use std::borrow::Cow;
23

34
macro_rules! html {
45
($style: expr, $body: expr) => {
@@ -140,6 +141,22 @@ fn do_not_process_style_tag_and_remove() {
140141
)
141142
}
142143

144+
#[test]
145+
fn extra_css() {
146+
let html = html!("h1 {background-color: blue;}", "<h1>Hello world!</h1>");
147+
let options = InlineOptions {
148+
inline_style_tags: false,
149+
extra_css: Some(Cow::Borrowed("h1 {background-color: green;}")),
150+
..Default::default()
151+
};
152+
let inliner = CSSInliner::new(options);
153+
let result = inliner.inline(&html).unwrap();
154+
assert_eq!(
155+
result,
156+
"<html><head><title>Test</title><style>h1 {background-color: blue;}</style></head><body><h1 style=\"background-color: green;\">Hello world!</h1></body></html>"
157+
)
158+
}
159+
143160
#[test]
144161
fn remote_file_stylesheet() {
145162
let html = r#"

0 commit comments

Comments
 (0)