Skip to content

feat: Option to inline additional CSS #48

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

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

## [0.3.3] - 2020-07-07

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ ARGS:
"inlined.example.html".

OPTIONS:
--inline-style-tags
Whether to inline CSS from "style" tags. The default value is `true`. To disable inlining
from "style" tags use `-inline-style-tags=false`.

--remove-style-tags
Remove "style" tags after inlining.

Expand All @@ -122,4 +126,7 @@ OPTIONS:

--load-remote-stylesheets
Whether remote stylesheets should be loaded or not.

--extra-css
Additional CSS to inline.
```
1 change: 1 addition & 0 deletions python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added

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

## [0.3.2] - 2020-07-09

Expand Down
21 changes: 14 additions & 7 deletions python/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use css_inline as rust_inline;
use pyo3::{create_exception, exceptions, prelude::*, types::PyList, wrap_pyfunction};
use rayon::prelude::*;
use std::borrow::Cow;

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

/// CSSInliner(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)
/// CSSInliner(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)
///
/// Customizable CSS inliner.
#[pyclass]
#[text_signature = "(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)"]
#[text_signature = "(inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)"]
struct CSSInliner {
inner: rust_inline::CSSInliner,
inner: rust_inline::CSSInliner<'static>,
}

#[pymethods]
Expand All @@ -52,12 +53,14 @@ impl CSSInliner {
remove_style_tags: Option<bool>,
base_url: Option<String>,
load_remote_stylesheets: Option<bool>,
extra_css: Option<String>,
) -> PyResult<Self> {
let options = rust_inline::InlineOptions {
inline_style_tags: inline_style_tags.unwrap_or(true),
remove_style_tags: remove_style_tags.unwrap_or(false),
base_url: parse_url(base_url)?,
load_remote_stylesheets: load_remote_stylesheets.unwrap_or(true),
extra_css: extra_css.map(Cow::Owned),
};
Ok(CSSInliner {
inner: rust_inline::CSSInliner::new(options),
Expand All @@ -81,45 +84,49 @@ impl CSSInliner {
}
}

/// inline(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)
/// inline(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)
///
/// Inline CSS in the given HTML document
#[pyfunction]
#[text_signature = "(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)"]
#[text_signature = "(html, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)"]
fn inline(
html: &str,
inline_style_tags: Option<bool>,
remove_style_tags: Option<bool>,
base_url: Option<String>,
load_remote_stylesheets: Option<bool>,
extra_css: Option<&str>,
) -> PyResult<String> {
let options = rust_inline::InlineOptions {
inline_style_tags: inline_style_tags.unwrap_or(true),
remove_style_tags: remove_style_tags.unwrap_or(false),
base_url: parse_url(base_url)?,
load_remote_stylesheets: load_remote_stylesheets.unwrap_or(true),
extra_css: extra_css.map(Cow::Borrowed),
};
let inliner = rust_inline::CSSInliner::new(options);
Ok(inliner.inline(html).map_err(InlineErrorWrapper)?)
}

/// inline_many(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)
/// inline_many(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)
///
/// Inline CSS in multiple HTML documents
#[pyfunction]
#[text_signature = "(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True)"]
#[text_signature = "(htmls, inline_style_tags=True, remove_style_tags=False, base_url=None, load_remote_stylesheets=True, extra_css=None)"]
fn inline_many(
htmls: &PyList,
inline_style_tags: Option<bool>,
remove_style_tags: Option<bool>,
base_url: Option<String>,
load_remote_stylesheets: Option<bool>,
extra_css: Option<&str>,
) -> PyResult<Vec<String>> {
let options = rust_inline::InlineOptions {
inline_style_tags: inline_style_tags.unwrap_or(true),
remove_style_tags: remove_style_tags.unwrap_or(false),
base_url: parse_url(base_url)?,
load_remote_stylesheets: load_remote_stylesheets.unwrap_or(true),
extra_css: extra_css.map(Cow::Borrowed),
};
let inliner = rust_inline::CSSInliner::new(options);
inline_many_impl(&inliner, htmls)
Expand Down
26 changes: 18 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ pub use url::{ParseError, Url};

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

impl InlineOptions {
impl InlineOptions<'_> {
/// Options for "compact" HTML output
#[inline]
pub fn compact() -> Self {
Expand All @@ -150,18 +155,20 @@ impl InlineOptions {
remove_style_tags: true,
base_url: None,
load_remote_stylesheets: true,
extra_css: None,
}
}
}

impl Default for InlineOptions {
impl Default for InlineOptions<'_> {
#[inline]
fn default() -> Self {
InlineOptions {
inline_style_tags: true,
remove_style_tags: false,
base_url: None,
load_remote_stylesheets: true,
extra_css: None,
}
}
}
Expand All @@ -170,14 +177,14 @@ type Result<T> = std::result::Result<T, InlineError>;

/// Customizable CSS inliner.
#[derive(Debug)]
pub struct CSSInliner {
options: InlineOptions,
pub struct CSSInliner<'a> {
options: InlineOptions<'a>,
}

impl CSSInliner {
impl<'a> CSSInliner<'a> {
/// Create a new `CSSInliner` instance with given options.
#[inline]
pub fn new(options: InlineOptions) -> Self {
pub fn new(options: InlineOptions<'a>) -> Self {
CSSInliner { options }
}

Expand Down Expand Up @@ -241,6 +248,9 @@ impl CSSInliner {
}
}
}
if let Some(extra_css) = &self.options.extra_css {
process_css(&document, extra_css)?;
}
document.serialize(target)?;
Ok(())
}
Expand Down Expand Up @@ -315,7 +325,7 @@ fn process_css(document: &NodeRef, css: &str) -> Result<()> {
Ok(())
}

impl Default for CSSInliner {
impl Default for CSSInliner<'_> {
#[inline]
fn default() -> Self {
CSSInliner::new(Default::default())
Expand Down
9 changes: 8 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use css_inline::{CSSInliner, InlineOptions};
use rayon::prelude::*;
use std::borrow::Cow;
use std::error::Error;
use std::fs::File;
use std::io::{self, Read};
Expand Down Expand Up @@ -38,7 +39,10 @@ OPTIONS:
Used for loading external stylesheets via relative URLs.

--load-remote-stylesheets
Whether remote stylesheets should be loaded or not."#
Whether remote stylesheets should be loaded or not.

--extra-css
Additional CSS to inline."#
);

struct Args {
Expand All @@ -47,6 +51,7 @@ struct Args {
inline_style_tags: bool,
remove_style_tags: bool,
base_url: Option<String>,
extra_css: Option<String>,
load_remote_stylesheets: bool,
files: Vec<String>,
}
Expand All @@ -69,6 +74,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.unwrap_or(true),
remove_style_tags: args.contains("--remove-style-tags"),
base_url: args.opt_value_from_str("--base-url")?,
extra_css: args.opt_value_from_str("--extra-css")?,
load_remote_stylesheets: args.contains("--load-remote-stylesheets"),
files: args.free()?,
};
Expand All @@ -83,6 +89,7 @@ fn main() -> Result<(), Box<dyn Error>> {
remove_style_tags: args.remove_style_tags,
base_url: parse_url(args.base_url)?,
load_remote_stylesheets: args.load_remote_stylesheets,
extra_css: args.extra_css.as_deref().map(Cow::Borrowed),
};
let inliner = CSSInliner::new(options);
if args.files.is_empty() {
Expand Down
16 changes: 16 additions & 0 deletions tests/test_inlining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,22 @@ fn do_not_process_style_tag_and_remove() {
)
}

#[test]
fn extra_css() {
let html = html!("h1 {background-color: blue;}", "<h1>Hello world!</h1>");
let options = InlineOptions {
inline_style_tags: false,
extra_css: Some("h1 {background-color: green;}".into()),
..Default::default()
};
let inliner = CSSInliner::new(options);
let result = inliner.inline(&html).unwrap();
assert_eq!(
result,
"<html><head><title>Test</title><style>h1 {background-color: blue;}</style></head><body><h1 style=\"background-color: green;\">Hello world!</h1></body></html>"
)
}

#[test]
fn remote_file_stylesheet() {
let html = r#"
Expand Down