css_inline 0.22.1-x86_64-linux → 0.23.0-x86_64-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bcd2f649b631a269a948367abd803cd20283211325d833ca7adb15620870d389
4
- data.tar.gz: 9c6ed07bbcc38089ed075119753cbc44d454210a8f10abea4642b69c8a5a6231
3
+ metadata.gz: f856424bb823350abac20fc97badd15ceecb5c0a62c6c7ca5e691457759cd69a
4
+ data.tar.gz: 6626b3203f393f7d890d646df3f9fb399541633cda6daa76dd8d4a569eb36825
5
5
  SHA512:
6
- metadata.gz: 3f049aad785b6a3f4e042ac6af2e6b663879eb98ba4d588683394f914d1a50e220c438feadcf1c8919662c518db0805575892382bf97ac406c7f7c9e8aea7e0b
7
- data.tar.gz: ee728e31098e0f7566048228ff7b7839c60c8a124bee150e871f14a27f7b87100a6e2d5c763d14a0b775e6f51e3c1b3183cef9759d48390fe8b0332da46a74ab
6
+ metadata.gz: 2926928cd617c4c3a27845d8e23a1c1fd668dc65a5f830215aa83f284cd98f19155aaa963f1b88c89b7f1b21dfcca3bd979339f577ac9905a1211f73a3b86bd2
7
+ data.tar.gz: e2cd325653025892a5f63a0c57043269d84429bc7da64b3f7c597bba337915e62796e882f33db68a29e92621f2f35008e7f4c84d8645384b6b755d1aea54b773
data/README.md CHANGED
@@ -161,6 +161,7 @@ The module functions accept the same keyword arguments, for example `CSSInline.i
161
161
  - `remove_inlined_selectors`. Remove inlined selectors from `<style>` blocks. Default: `false`
162
162
  - `apply_width_attributes`. Add `width` HTML attributes from CSS `width` properties on `table`, `td`, `th` and `img`. Default: `false`
163
163
  - `apply_height_attributes`. Add `height` HTML attributes from CSS `height` properties on `table`, `td`, `th` and `img`. Default: `false`
164
+ - `resolve_css_variables`. Replace `var()` references with the values of the custom properties they refer to and drop the `--*` declarations. Default: `false`
164
165
 
165
166
  To skip CSS inlining for an HTML tag, add the `data-css-inline="ignore"` attribute to it:
166
167
 
@@ -282,7 +283,7 @@ end
282
283
  `premailer` fails on "Realistic email 2" with `ArgumentError: Cannot parse 0 calc((100% - 500px) / 2)`.
283
284
 
284
285
  Inputs come from [`benchmarks/benchmarks.json`](https://github.com/Stranger6667/css-inline/blob/master/benchmarks/benchmarks.json): Basic usage is `simple`, Realistic email 1 and 2 are `big_email_1` and `big_email_2`, GitHub Page is `big_page`.
285
- The benchmark code is in [`test/bench.rb`](https://github.com/Stranger6667/css-inline/blob/master/bindings/ruby/test/bench.rb) and uses [`benchmark-ips`](https://github.com/evanphx/benchmark-ips) (5 s per case; `roadie` completes one run on the GitHub page in that time).
286
+ The benchmark code is in [`test/bench.rb`](https://github.com/Stranger6667/css-inline/blob/master/crates/css-inline-ruby/test/bench.rb) and uses [`benchmark-ips`](https://github.com/evanphx/benchmark-ips) (5 s per case; `roadie` completes one run on the GitHub page in that time).
286
287
  We measured these results with stable `rustc 1.99` on Ruby `3.4.8`, Ryzen 9 9950X.
287
288
 
288
289
  ## Further reading
Binary file
Binary file
Binary file
Binary file
data/src/lib.rs ADDED
@@ -0,0 +1,325 @@
1
+ //! Ruby bindings for css-inline
2
+ #![warn(
3
+ clippy::pedantic,
4
+ clippy::doc_markdown,
5
+ clippy::redundant_closure,
6
+ clippy::explicit_iter_loop,
7
+ clippy::match_same_arms,
8
+ clippy::needless_borrow,
9
+ clippy::print_stdout,
10
+ clippy::arithmetic_side_effects,
11
+ clippy::cast_possible_truncation,
12
+ clippy::unwrap_used,
13
+ clippy::map_unwrap_or,
14
+ clippy::trivially_copy_pass_by_ref,
15
+ clippy::needless_pass_by_value,
16
+ missing_debug_implementations,
17
+ trivial_casts,
18
+ trivial_numeric_casts,
19
+ unreachable_pub,
20
+ unused_extern_crates,
21
+ unused_import_braces,
22
+ unused_qualifications,
23
+ variant_size_differences,
24
+ rust_2018_idioms,
25
+ rust_2018_compatibility,
26
+ rust_2021_compatibility
27
+ )]
28
+ use css_inline as rust_inline;
29
+ use magnus::{
30
+ DataTypeFunctions, RHash, Ruby, TryConvert, TypedData, Value, function, method,
31
+ prelude::*,
32
+ scan_args::{Args, get_kwargs, scan_args},
33
+ typed_data::Obj,
34
+ };
35
+ use rayon::prelude::*;
36
+ use std::{
37
+ borrow::Cow,
38
+ num::NonZeroUsize,
39
+ sync::{Arc, Mutex},
40
+ };
41
+
42
+ type RubyResult<T> = Result<T, magnus::Error>;
43
+
44
+ #[allow(clippy::struct_excessive_bools)]
45
+ struct Options {
46
+ /// Whether to inline CSS from "style" tags.
47
+ ///
48
+ /// Sometimes HTML may include a lot of boilerplate styles, that are not applicable in every
49
+ /// scenario, and it is useful to ignore them and use `extra_css` instead.
50
+ inline_style_tags: Option<bool>,
51
+ /// Keep "style" tags after inlining.
52
+ keep_style_tags: Option<bool>,
53
+ /// Keep "link" tags after inlining.
54
+ keep_link_tags: Option<bool>,
55
+ /// Keep "at-rules" after inlining.
56
+ keep_at_rules: Option<bool>,
57
+ /// Remove trailing semicolons and spaces between properties and values.
58
+ minify_css: Option<bool>,
59
+ /// Used for loading external stylesheets via relative URLs.
60
+ base_url: Option<String>,
61
+ /// Whether remote stylesheets should be loaded or not.
62
+ load_remote_stylesheets: Option<bool>,
63
+ /// An LRU Cache for external stylesheets.
64
+ cache: Option<Obj<StylesheetCache>>,
65
+ /// Additional CSS to inline.
66
+ extra_css: Option<String>,
67
+ /// Pre-allocate capacity for HTML nodes during parsing.
68
+ /// It can improve performance when you have an estimate of the number of nodes in your HTML document.
69
+ preallocate_node_capacity: Option<usize>,
70
+ /// Remove selectors that were successfully inlined from inline `<style>` blocks.
71
+ remove_inlined_selectors: Option<bool>,
72
+ /// Apply `width` HTML attributes from CSS `width` properties on supported elements.
73
+ apply_width_attributes: Option<bool>,
74
+ /// Apply `height` HTML attributes from CSS `height` properties on supported elements.
75
+ apply_height_attributes: Option<bool>,
76
+ /// Substitute `var()` references with the values of the custom properties they refer to
77
+ /// and drop the `--*` declarations from the inlined styles.
78
+ resolve_css_variables: Option<bool>,
79
+ }
80
+
81
+ impl TryConvert for Options {
82
+ fn try_convert(v: Value) -> Result<Self, magnus::Error> {
83
+ let h = RHash::try_convert(v)?;
84
+ let ruby = Ruby::get_with(v);
85
+ Ok(Self {
86
+ inline_style_tags: h.aref::<_, Option<bool>>(ruby.to_symbol("inline_style_tags"))?,
87
+ keep_style_tags: h.aref::<_, Option<bool>>(ruby.to_symbol("keep_style_tags"))?,
88
+ keep_link_tags: h.aref::<_, Option<bool>>(ruby.to_symbol("keep_link_tags"))?,
89
+ keep_at_rules: h.aref::<_, Option<bool>>(ruby.to_symbol("keep_at_rules"))?,
90
+ minify_css: h.aref::<_, Option<bool>>(ruby.to_symbol("minify_css"))?,
91
+ base_url: h.aref::<_, Option<String>>(ruby.to_symbol("base_url"))?,
92
+ load_remote_stylesheets: h
93
+ .aref::<_, Option<bool>>(ruby.to_symbol("load_remote_stylesheets"))?,
94
+ cache: h.aref::<_, Option<Obj<StylesheetCache>>>(ruby.to_symbol("cache"))?,
95
+ extra_css: h.aref::<_, Option<String>>(ruby.to_symbol("extra_css"))?,
96
+ preallocate_node_capacity: h
97
+ .aref::<_, Option<usize>>(ruby.to_symbol("preallocate_node_capacity"))?,
98
+ remove_inlined_selectors: h
99
+ .aref::<_, Option<bool>>(ruby.to_symbol("remove_inlined_selectors"))?,
100
+ apply_width_attributes: h
101
+ .aref::<_, Option<bool>>(ruby.to_symbol("apply_width_attributes"))?,
102
+ apply_height_attributes: h
103
+ .aref::<_, Option<bool>>(ruby.to_symbol("apply_height_attributes"))?,
104
+ resolve_css_variables: h
105
+ .aref::<_, Option<bool>>(ruby.to_symbol("resolve_css_variables"))?,
106
+ })
107
+ }
108
+ }
109
+
110
+ fn parse_options<Req>(
111
+ args: &Args<Req, (), (), (), RHash, ()>,
112
+ ) -> RubyResult<rust_inline::InlineOptions<'static>> {
113
+ let kwargs: Options = Options::try_convert(args.keywords.as_value())?;
114
+ Ok(rust_inline::InlineOptions {
115
+ inline_style_tags: kwargs.inline_style_tags.unwrap_or(true),
116
+ keep_style_tags: kwargs.keep_style_tags.unwrap_or(false),
117
+ keep_link_tags: kwargs.keep_link_tags.unwrap_or(false),
118
+ keep_at_rules: kwargs.keep_at_rules.unwrap_or(false),
119
+ minify_css: kwargs.minify_css.unwrap_or(false),
120
+ base_url: parse_url(kwargs.base_url)?,
121
+ load_remote_stylesheets: kwargs.load_remote_stylesheets.unwrap_or(true),
122
+ cache: kwargs
123
+ .cache
124
+ .map(|cache| Mutex::new(rust_inline::StylesheetCache::new(cache.size))),
125
+ extra_css: kwargs.extra_css.map(Cow::Owned),
126
+ preallocate_node_capacity: kwargs.preallocate_node_capacity.unwrap_or(32),
127
+ resolver: Arc::new(rust_inline::DefaultStylesheetResolver),
128
+ remove_inlined_selectors: kwargs.remove_inlined_selectors.unwrap_or(false),
129
+ apply_width_attributes: kwargs.apply_width_attributes.unwrap_or(false),
130
+ apply_height_attributes: kwargs.apply_height_attributes.unwrap_or(false),
131
+ resolve_css_variables: kwargs.resolve_css_variables.unwrap_or(false),
132
+ })
133
+ }
134
+
135
+ #[derive(DataTypeFunctions, TypedData)]
136
+ #[magnus(class = "CSSInline::StylesheetCache")]
137
+ struct StylesheetCache {
138
+ size: NonZeroUsize,
139
+ }
140
+
141
+ impl StylesheetCache {
142
+ fn new(args: &[Value]) -> RubyResult<StylesheetCache> {
143
+ fn error() -> magnus::Error {
144
+ let ruby = Ruby::get().expect("Always called from a Ruby thread");
145
+ magnus::Error::new(
146
+ ruby.exception_arg_error(),
147
+ "Cache size must be an integer greater than zero",
148
+ )
149
+ }
150
+
151
+ let args: Args<(), (), (), (), RHash, ()> = scan_args::<(), _, _, _, RHash, _>(args)?;
152
+ let kwargs = get_kwargs::<_, (), (Option<usize>,), ()>(args.keywords, &[], &["size"])
153
+ .map_err(|_| error())?;
154
+ let size = NonZeroUsize::new(kwargs.optional.0.unwrap_or(8)).ok_or_else(error)?;
155
+ Ok(StylesheetCache { size })
156
+ }
157
+ }
158
+
159
+ #[magnus::wrap(class = "CSSInline::CSSInliner")]
160
+ struct CSSInliner {
161
+ inner: rust_inline::CSSInliner<'static>,
162
+ }
163
+
164
+ struct InlineErrorWrapper(rust_inline::InlineError);
165
+
166
+ impl From<InlineErrorWrapper> for magnus::Error {
167
+ fn from(error: InlineErrorWrapper) -> Self {
168
+ let ruby = Ruby::get().expect("Always called from a Ruby thread");
169
+ match error.0 {
170
+ rust_inline::InlineError::IO(error) => {
171
+ magnus::Error::new(ruby.exception_arg_error(), error.to_string())
172
+ }
173
+ rust_inline::InlineError::Network { error, location } => {
174
+ magnus::Error::new(ruby.exception_arg_error(), format!("{error}: {location}"))
175
+ }
176
+ rust_inline::InlineError::ParseError(message) => {
177
+ magnus::Error::new(ruby.exception_arg_error(), message.to_string())
178
+ }
179
+ rust_inline::InlineError::MissingStyleSheet { .. } => {
180
+ magnus::Error::new(ruby.exception_arg_error(), error.0.to_string())
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ struct UrlError {
187
+ error: rust_inline::ParseError,
188
+ url: String,
189
+ }
190
+
191
+ impl From<UrlError> for magnus::Error {
192
+ fn from(error: UrlError) -> magnus::Error {
193
+ let ruby = Ruby::get().expect("Always called from a Ruby thread");
194
+ magnus::Error::new(
195
+ ruby.exception_arg_error(),
196
+ format!("{}: {}", error.error, error.url),
197
+ )
198
+ }
199
+ }
200
+
201
+ fn parse_url(url: Option<String>) -> RubyResult<Option<rust_inline::Url>> {
202
+ Ok(if let Some(url) = url {
203
+ Some(rust_inline::Url::parse(url.as_str()).map_err(|error| UrlError { error, url })?)
204
+ } else {
205
+ None
206
+ })
207
+ }
208
+
209
+ impl CSSInliner {
210
+ fn new(args: &[Value]) -> RubyResult<CSSInliner> {
211
+ let args = scan_args::<(), _, _, _, _, _>(args)?;
212
+ let options = parse_options(&args)?;
213
+ Ok(CSSInliner {
214
+ inner: rust_inline::CSSInliner::new(options),
215
+ })
216
+ }
217
+
218
+ #[allow(clippy::needless_pass_by_value)]
219
+ fn inline(&self, html: String) -> RubyResult<String> {
220
+ Ok(self.inner.inline(&html).map_err(InlineErrorWrapper)?)
221
+ }
222
+
223
+ #[allow(clippy::needless_pass_by_value)]
224
+ fn inline_fragment(&self, html: String, css: String) -> RubyResult<String> {
225
+ Ok(self
226
+ .inner
227
+ .inline_fragment(&html, &css)
228
+ .map_err(InlineErrorWrapper)?)
229
+ }
230
+
231
+ #[allow(clippy::needless_pass_by_value)]
232
+ fn inline_many(&self, html: Vec<String>) -> RubyResult<Vec<String>> {
233
+ inline_many_impl(&html, &self.inner)
234
+ }
235
+
236
+ #[allow(clippy::needless_pass_by_value)]
237
+ fn inline_many_fragments(
238
+ &self,
239
+ html: Vec<String>,
240
+ css: Vec<String>,
241
+ ) -> RubyResult<Vec<String>> {
242
+ inline_many_fragments_impl(&html, &css, &self.inner)
243
+ }
244
+ }
245
+
246
+ fn inline(args: &[Value]) -> RubyResult<String> {
247
+ let args = scan_args::<(String,), _, _, _, _, _>(args)?;
248
+ let options = parse_options(&args)?;
249
+ let html = args.required.0;
250
+ Ok(rust_inline::CSSInliner::new(options)
251
+ .inline(&html)
252
+ .map_err(InlineErrorWrapper)?)
253
+ }
254
+
255
+ fn inline_fragment(args: &[Value]) -> RubyResult<String> {
256
+ let args = scan_args::<(String, String), _, _, _, _, _>(args)?;
257
+ let options = parse_options(&args)?;
258
+ let html = args.required.0;
259
+ let css = args.required.1;
260
+ Ok(rust_inline::CSSInliner::new(options)
261
+ .inline_fragment(&html, &css)
262
+ .map_err(InlineErrorWrapper)?)
263
+ }
264
+
265
+ fn inline_many(args: &[Value]) -> RubyResult<Vec<String>> {
266
+ let args = scan_args::<(Vec<String>,), _, _, _, _, _>(args)?;
267
+ let options = parse_options(&args)?;
268
+ let inliner = rust_inline::CSSInliner::new(options);
269
+ inline_many_impl(&args.required.0, &inliner)
270
+ }
271
+
272
+ fn inline_many_impl(
273
+ htmls: &[String],
274
+ inliner: &rust_inline::CSSInliner<'static>,
275
+ ) -> RubyResult<Vec<String>> {
276
+ let output: Result<Vec<_>, _> = htmls.par_iter().map(|html| inliner.inline(html)).collect();
277
+ Ok(output.map_err(InlineErrorWrapper)?)
278
+ }
279
+
280
+ fn inline_many_fragments(args: &[Value]) -> RubyResult<Vec<String>> {
281
+ let args = scan_args::<(Vec<String>, Vec<String>), _, _, _, _, _>(args)?;
282
+ let options = parse_options(&args)?;
283
+ let inliner = rust_inline::CSSInliner::new(options);
284
+ inline_many_fragments_impl(&args.required.0, &args.required.1, &inliner)
285
+ }
286
+
287
+ fn inline_many_fragments_impl(
288
+ htmls: &[String],
289
+ css: &[String],
290
+ inliner: &rust_inline::CSSInliner<'static>,
291
+ ) -> RubyResult<Vec<String>> {
292
+ let output: Result<Vec<_>, _> = htmls
293
+ .par_iter()
294
+ .zip(css)
295
+ .map(|(html, css)| inliner.inline_fragment(html, css))
296
+ .collect();
297
+ Ok(output.map_err(InlineErrorWrapper)?)
298
+ }
299
+
300
+ #[magnus::init(name = "css_inline")]
301
+ fn init(ruby: &Ruby) -> RubyResult<()> {
302
+ let module = ruby.define_module("CSSInline")?;
303
+
304
+ module.define_module_function("inline", function!(inline, -1))?;
305
+ module.define_module_function("inline_fragment", function!(inline_fragment, -1))?;
306
+ module.define_module_function("inline_many", function!(inline_many, -1))?;
307
+ module.define_module_function(
308
+ "inline_many_fragments",
309
+ function!(inline_many_fragments, -1),
310
+ )?;
311
+
312
+ let class = module.define_class("CSSInliner", ruby.class_object())?;
313
+ class.define_singleton_method("new", function!(CSSInliner::new, -1))?;
314
+ class.define_method("inline", method!(CSSInliner::inline, 1))?;
315
+ class.define_method("inline_fragment", method!(CSSInliner::inline_fragment, 2))?;
316
+ class.define_method("inline_many", method!(CSSInliner::inline_many, 1))?;
317
+ class.define_method(
318
+ "inline_many_fragments",
319
+ method!(CSSInliner::inline_many_fragments, 2),
320
+ )?;
321
+
322
+ let class = module.define_class("StylesheetCache", ruby.class_object())?;
323
+ class.define_singleton_method("new", function!(StylesheetCache::new, -1))?;
324
+ Ok(())
325
+ }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: css_inline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.22.1
4
+ version: 0.23.0
5
5
  platform: x86_64-linux
6
6
  authors:
7
7
  - Dmitry Dygalo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-14 00:00:00.000000000 Z
11
+ date: 2026-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -33,21 +33,20 @@ executables: []
33
33
  extensions: []
34
34
  extra_rdoc_files: []
35
35
  files:
36
- - Cargo.lock
37
- - Cargo.toml
38
36
  - README.md
39
37
  - lib/css_inline.rb
40
38
  - lib/css_inline/3.2/css_inline.so
41
39
  - lib/css_inline/3.3/css_inline.so
42
40
  - lib/css_inline/3.4/css_inline.so
43
41
  - lib/css_inline/4.0/css_inline.so
42
+ - src/lib.rs
44
43
  homepage: https://github.com/Stranger6667/css-inline
45
44
  licenses:
46
45
  - MIT
47
46
  metadata:
48
47
  bug_tracker_uri: https://github.com/Stranger6667/css-inline/issues
49
- changelog_uri: https://github.com/Stranger6667/css-inline/tree/master/bindings/ruby/CHANGELOG.md
50
- source_code_uri: https://github.com/Stranger6667/css-inline/tree/master/bindings/ruby
48
+ changelog_uri: https://github.com/Stranger6667/css-inline/tree/master/crates/css-inline-ruby/CHANGELOG.md
49
+ source_code_uri: https://github.com/Stranger6667/css-inline/tree/master/crates/css-inline-ruby
51
50
  funding_uri: https://github.com/sponsors/Stranger6667/
52
51
  post_install_message:
53
52
  rdoc_options: