Skip to content

Commit eb0d6f9

Browse files
committed
feat: Command Line Interface
1 parent 90d30b3 commit eb0d6f9

File tree

5 files changed

+167
-2
lines changed

5 files changed

+167
-2
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## [Unreleased]
44

5+
### Added
6+
7+
- Command Line Interface. [#33](https://github.com/Stranger6667/css-inline/issues/33)
8+
59
## [0.2.0] - 2020-06-25
610

711
### Added

Cargo.toml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,20 @@ keywords = ["html", "css", "css-inline"]
1111
exclude = [".github", ".pre-commit-config.yaml", ".yamllint", ".gitignore", "tests"]
1212
categories = ["web-programming"]
1313

14-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14+
[[bin]]
15+
name = "css-inline"
16+
17+
[features]
18+
default = ["cli"]
19+
cli = ["pico-args", "rayon"]
1520

1621
[dependencies]
1722
cssparser = "0"
1823
kuchiki = "0.8"
1924
attohttpc = "0"
2025
url = "2"
26+
pico-args = { version = "0.3.2", optional = true }
27+
rayon = { version = "1.3.1", optional = true }
2128

2229
[dev-dependencies]
2330
criterion = ">= 0.1"

README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,36 @@ fn main() -> Result<(), css_inline::InlineError> {
9090
- `remove_style_tags`. Remove "style" tags after inlining.
9191
- `base_url`. Base URL to resolve relative URLs
9292
- `load_remote_stylesheets`. Whether remote stylesheets should be loaded or not
93+
94+
## Command Line Interface
95+
96+
`css-inline` provides a command-line interface:
97+
98+
```bash
99+
$ css-inline --help
100+
101+
css-inline inlines CSS into HTML documents.
102+
103+
USAGE:
104+
css-inline [OPTIONS] [PATH ...]
105+
command | css-inline [OPTIONS]
106+
107+
ARGS:
108+
<PATH>...
109+
An HTML document to process. In each specified document "css-inline" will look for
110+
all relevant "style" and "link" tags, will load CSS from them and then will inline it
111+
to the HTML tags, according to the relevant CSS selectors.
112+
When multiple documents are specified, they will be processed in parallel and each inlined
113+
file will be saved with "inlined." prefix. E.g. for "example.html", there will be
114+
"inlined.example.html".
115+
116+
OPTIONS:
117+
--remove-style-tags
118+
Remove "style" tags after inlining.
119+
120+
--base-url
121+
Used for loading external stylesheets via relative URLs.
122+
123+
--load-remote-stylesheets
124+
Whether remote stylesheets should be loaded or not.
125+
```

python/Cargo.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,12 @@ crate-type = ["cdylib"]
1313
[build-dependencies]
1414
built = { version = "0.4", features = ["chrono"] }
1515

16+
[dependencies.css-inline]
17+
path = ".."
18+
version = "= 0.2.0"
19+
default-features = false
20+
1621
[dependencies]
17-
css-inline = { path = "..", version = "= 0.2.0" }
1822
url = "2"
1923
rayon = "1"
2024
pyo3 = { version = ">= 0.10", features = ["extension-module"] }

src/main.rs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
use css_inline::{CSSInliner, InlineOptions};
2+
use rayon::prelude::*;
3+
use std::error::Error;
4+
use std::fs::File;
5+
use std::io::{self, Read};
6+
7+
const VERSION: &str = env!("CARGO_PKG_VERSION");
8+
const HELP_MESSAGE: &str = concat!(
9+
"css-inline ",
10+
env!("CARGO_PKG_VERSION"),
11+
r#"
12+
Dmitry Dygalo <[email protected]>
13+
14+
css-inline inlines CSS into HTML documents.
15+
16+
USAGE:
17+
css-inline [OPTIONS] [PATH ...]
18+
command | css-inline [OPTIONS]
19+
20+
ARGS:
21+
<PATH>...
22+
An HTML document to process. In each specified document "css-inline" will look for
23+
all relevant "style" and "link" tags, will load CSS from them and then will inline it
24+
to the HTML tags, according to the relevant CSS selectors.
25+
When multiple documents are specified, they will be processed in parallel and each inlined
26+
file will be saved with "inlined." prefix. E.g. for "example.html", there will be
27+
"inlined.example.html".
28+
29+
OPTIONS:
30+
--remove-style-tags
31+
Remove "style" tags after inlining.
32+
33+
--base-url
34+
Used for loading external stylesheets via relative URLs.
35+
36+
--load-remote-stylesheets
37+
Whether remote stylesheets should be loaded or not."#
38+
);
39+
40+
struct Args {
41+
help: bool,
42+
version: bool,
43+
remove_style_tags: bool,
44+
base_url: Option<String>,
45+
load_remote_stylesheets: bool,
46+
files: Vec<String>,
47+
}
48+
49+
fn parse_url(url: Option<String>) -> Result<Option<url::Url>, url::ParseError> {
50+
Ok(if let Some(url) = url {
51+
Some(url::Url::parse(url.as_str())?)
52+
} else {
53+
None
54+
})
55+
}
56+
57+
fn main() -> Result<(), Box<dyn Error>> {
58+
let mut args = pico_args::Arguments::from_env();
59+
let args = Args {
60+
help: args.contains(["-h", "--help"]),
61+
version: args.contains(["-v", "--version"]),
62+
remove_style_tags: args.contains("--remove-style-tags"),
63+
base_url: args.opt_value_from_str("--base-url")?,
64+
load_remote_stylesheets: args.contains("--load-remote-stylesheets"),
65+
files: args.free()?,
66+
};
67+
68+
if args.help {
69+
println!("{}", HELP_MESSAGE)
70+
} else if args.version {
71+
println!("css-inline {}", VERSION)
72+
} else {
73+
let options = InlineOptions {
74+
remove_style_tags: args.remove_style_tags,
75+
base_url: parse_url(args.base_url)?,
76+
load_remote_stylesheets: args.load_remote_stylesheets,
77+
};
78+
let inliner = CSSInliner::new(options);
79+
if args.files.is_empty() {
80+
let mut buffer = String::new();
81+
io::stdin().read_to_string(&mut buffer)?;
82+
inliner.inline_to(buffer.as_str().trim(), &mut io::stdout())?;
83+
} else {
84+
let results: Vec<_> = args
85+
.files
86+
.par_iter()
87+
.map(|filename| {
88+
File::open(filename)
89+
.and_then(read_file)
90+
.and_then(|contents| {
91+
let new_filename = format!("inlined.{}", filename);
92+
File::create(new_filename).and_then(|file| Ok((file, contents)))
93+
})
94+
.and_then(|(mut file, contents)| {
95+
Ok((filename, inliner.inline_to(contents.as_str(), &mut file)))
96+
})
97+
.map_err(|error| (filename, error))
98+
})
99+
.collect();
100+
for result in results {
101+
match result {
102+
Ok((filename, result)) => match result {
103+
Ok(_) => println!("{}: SUCCESS", filename),
104+
Err(error) => println!("{}: FAILURE ({})", filename, error),
105+
},
106+
Err((filename, error)) => println!("{}: FAILURE ({})", filename, error),
107+
}
108+
}
109+
}
110+
}
111+
Ok(())
112+
}
113+
114+
fn read_file(mut file: File) -> io::Result<String> {
115+
let mut contents = String::new();
116+
file.read_to_string(&mut contents).and(Ok(contents))
117+
}

0 commit comments

Comments
 (0)