Skip to content

Commit 0eb739b

Browse files
committed
chore(cli): Remove pico-args
Signed-off-by: Dmitry Dygalo <[email protected]>
1 parent df215f8 commit 0eb739b

File tree

3 files changed

+199
-62
lines changed

3 files changed

+199
-62
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+
### Changed
6+
7+
- **CLI**: Remove `pico-args`
8+
59
## [0.15.0] - 2025-06-17
610

711
### Changed

css-inline/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ name = "css-inline"
2323

2424
[features]
2525
default = ["cli", "http", "file", "stylesheet-cache"]
26-
cli = ["pico-args", "rayon"]
26+
cli = ["rayon"]
2727
http = ["reqwest"]
2828
file = []
2929
stylesheet-cache = ["lru"]
@@ -33,7 +33,6 @@ cssparser = "0.35.0"
3333
html5ever = "0.31.0"
3434
indexmap = "2.1"
3535
lru = { version = "0.14.0", optional = true }
36-
pico-args = { version = "0.3", optional = true }
3736
precomputed-hash = "0.1.1"
3837
rayon = { version = "1.10", optional = true }
3938
reqwest = { version = "0.12.0", optional = true, default-features = false, features = ["rustls-tls", "blocking"] }

css-inline/src/main.rs

Lines changed: 194 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,165 @@ fn main() {
66

77
#[cfg(feature = "cli")]
88
fn main() -> Result<(), Box<dyn std::error::Error>> {
9+
use core::fmt;
910
use css_inline::{CSSInliner, DefaultStylesheetResolver, InlineOptions};
1011
use rayon::prelude::*;
1112
use std::{
1213
borrow::Cow,
14+
env,
15+
error::Error,
1316
ffi::OsString,
14-
fmt::{Display, Write as FmtWrite},
17+
fmt::Write as FmtWrite,
1518
fs::{read_to_string, File},
1619
io::{self, Read, Write},
1720
path::Path,
21+
str::FromStr,
1822
sync::{
1923
atomic::{AtomicI32, Ordering},
2024
Arc,
2125
},
2226
};
2327

28+
fn parse_url(url: Option<&str>) -> Result<Option<url::Url>, url::ParseError> {
29+
Ok(if let Some(url) = url {
30+
Some(url::Url::parse(url)?)
31+
} else {
32+
None
33+
})
34+
}
35+
36+
#[derive(Debug)]
37+
struct ParseError {
38+
message: String,
39+
}
40+
41+
impl fmt::Display for ParseError {
42+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43+
write!(f, "{}", self.message)
44+
}
45+
}
46+
47+
impl Error for ParseError {}
48+
49+
struct ParsedArgs {
50+
help: bool,
51+
version: bool,
52+
files: Vec<String>,
53+
inline_style_tags: bool,
54+
keep_style_tags: bool,
55+
keep_link_tags: bool,
56+
base_url: Option<String>,
57+
extra_css: Option<String>,
58+
output_filename_prefix: Option<OsString>,
59+
load_remote_stylesheets: bool,
60+
#[cfg(feature = "stylesheet-cache")]
61+
cache_size: Option<usize>,
62+
}
63+
64+
impl Default for ParsedArgs {
65+
fn default() -> Self {
66+
Self {
67+
help: false,
68+
version: false,
69+
files: Vec::new(),
70+
inline_style_tags: true,
71+
keep_style_tags: false,
72+
keep_link_tags: false,
73+
base_url: None,
74+
extra_css: None,
75+
output_filename_prefix: None,
76+
load_remote_stylesheets: false,
77+
#[cfg(feature = "stylesheet-cache")]
78+
cache_size: None,
79+
}
80+
}
81+
}
82+
83+
#[cfg(feature = "stylesheet-cache")]
84+
macro_rules! if_cfg_feature_stylesheet_cache {
85+
($val:expr) => {
86+
$val
87+
};
88+
}
89+
90+
#[cfg(not(feature = "stylesheet-cache"))]
91+
macro_rules! if_cfg_feature_stylesheet_cache {
92+
// Empty string that won't match
93+
($val:expr) => {
94+
""
95+
};
96+
}
97+
98+
fn requires_value(flag: &str) -> bool {
99+
matches!(
100+
flag,
101+
"inline-style-tags"
102+
| "base-url"
103+
| "extra-css"
104+
| "output-filename-prefix"
105+
| if_cfg_feature_stylesheet_cache!("cache-size")
106+
)
107+
}
108+
109+
fn parse_value<T>(value: &str, flag: &str) -> Result<T, ParseError>
110+
where
111+
T: FromStr,
112+
T::Err: fmt::Display,
113+
{
114+
value.parse::<T>().map_err(|e| ParseError {
115+
message: format!("Failed to parse value '{value}' for flag '{flag}': {e}"),
116+
})
117+
}
118+
119+
fn handle_flag_with_value(
120+
parsed: &mut ParsedArgs,
121+
flag: &str,
122+
value: &str,
123+
) -> Result<(), ParseError> {
124+
match flag {
125+
"inline-style-tags" => parsed.inline_style_tags = parse_value(value, flag)?,
126+
"base-url" => parsed.base_url = Some(value.to_string()),
127+
"extra-css" => parsed.extra_css = Some(value.to_string()),
128+
"output-filename-prefix" => {
129+
parsed.output_filename_prefix = Some(value.to_string().into());
130+
}
131+
#[cfg(feature = "stylesheet-cache")]
132+
"cache-size" => parsed.cache_size = Some(parse_value(value, flag)?),
133+
_ => {
134+
return Err(ParseError {
135+
message: format!("Unknown flag: --{flag}"),
136+
})
137+
}
138+
}
139+
Ok(())
140+
}
141+
142+
fn handle_boolean_flag(parsed: &mut ParsedArgs, flag: &str) -> Result<(), ParseError> {
143+
match flag {
144+
"help" | "h" => parsed.help = true,
145+
"version" | "v" => parsed.version = true,
146+
"keep-style-tags" => parsed.keep_style_tags = true,
147+
"keep-link-tags" => parsed.keep_link_tags = true,
148+
"load-remote-stylesheets" => parsed.load_remote_stylesheets = true,
149+
_ => {
150+
return Err(ParseError {
151+
message: format!("Unknown flag: {flag}"),
152+
})
153+
}
154+
}
155+
Ok(())
156+
}
157+
158+
fn format_error(filename: Option<&str>, error: impl fmt::Display) {
159+
let mut buffer = String::with_capacity(128);
160+
if let Some(filename) = filename {
161+
writeln!(buffer, "Filename: {filename}").expect("Failed to write to buffer");
162+
}
163+
buffer.push_str("Status: ERROR\n");
164+
writeln!(buffer, "Details: {error}").expect("Failed to write to buffer");
165+
eprintln!("{}", buffer.trim());
166+
}
167+
24168
const VERSION_MESSAGE: &[u8] =
25169
concat!("css-inline ", env!("CARGO_PKG_VERSION"), "\n").as_bytes();
26170
const HELP_MESSAGE: &[u8] = concat!(
@@ -74,86 +218,76 @@ OPTIONS:
74218
)
75219
.as_bytes();
76220

77-
struct Args {
78-
inline_style_tags: bool,
79-
keep_style_tags: bool,
80-
keep_link_tags: bool,
81-
base_url: Option<String>,
82-
extra_css: Option<String>,
83-
output_filename_prefix: Option<OsString>,
84-
load_remote_stylesheets: bool,
85-
#[cfg(feature = "stylesheet-cache")]
86-
cache_size: Option<usize>,
87-
files: Vec<String>,
88-
}
221+
let mut raw_args = env::args().skip(1);
222+
let mut args = ParsedArgs::default();
89223

90-
fn parse_url(url: Option<String>) -> Result<Option<url::Url>, url::ParseError> {
91-
Ok(if let Some(url) = url {
92-
Some(url::Url::parse(url.as_str())?)
224+
while let Some(arg) = raw_args.next() {
225+
if let Some(flag) = arg.strip_prefix("--") {
226+
// Handle --key=value format
227+
if let Some((flag, value)) = flag.split_once('=') {
228+
handle_flag_with_value(&mut args, flag, value)?;
229+
} else {
230+
// Handle --key format (boolean or expecting value)
231+
if requires_value(flag) {
232+
// Expects a value
233+
if let Some(value) = raw_args.next() {
234+
handle_flag_with_value(&mut args, flag, &value)?;
235+
} else {
236+
eprintln!("Error parsing arguments: Flag --{flag} requires a value");
237+
std::process::exit(1);
238+
}
239+
} else {
240+
// Boolean flag
241+
handle_boolean_flag(&mut args, flag)?;
242+
}
243+
}
244+
} else if let Some(flag) = arg.strip_prefix('-') {
245+
if flag.len() == 1 {
246+
// Single character short flag
247+
handle_boolean_flag(&mut args, flag)?;
248+
} else {
249+
eprintln!("Error parsing arguments: Invalid flag: -{flag}");
250+
std::process::exit(1);
251+
}
93252
} else {
94-
None
95-
})
96-
}
97-
98-
fn format_error(filename: Option<&str>, error: impl Display) {
99-
let mut buffer = String::with_capacity(128);
100-
if let Some(filename) = filename {
101-
writeln!(buffer, "Filename: {}", filename).expect("Failed to write to buffer");
253+
// Positional argument (file)
254+
args.files.push(arg);
102255
}
103-
buffer.push_str("Status: ERROR\n");
104-
writeln!(buffer, "Details: {}", error).expect("Failed to write to buffer");
105-
eprintln!("{}", buffer.trim());
106256
}
107257

108-
let mut args = pico_args::Arguments::from_env();
109258
let exit_code = AtomicI32::new(0);
110-
if args.contains(["-h", "--help"]) {
259+
if args.help {
111260
io::stdout().write_all(HELP_MESSAGE)?;
112-
} else if args.contains(["-v", "--version"]) {
261+
} else if args.version {
113262
io::stdout().write_all(VERSION_MESSAGE)?;
114263
} else {
115-
let args = Args {
116-
inline_style_tags: args
117-
.opt_value_from_str("--inline-style-tags")?
118-
.unwrap_or(true),
119-
keep_style_tags: args.contains("--keep-style-tags"),
120-
keep_link_tags: args.contains("--keep-link-tags"),
121-
base_url: args.opt_value_from_str("--base-url")?,
122-
extra_css: args.opt_value_from_str("--extra-css")?,
123-
output_filename_prefix: args.opt_value_from_str("--output-filename-prefix")?,
124-
load_remote_stylesheets: args.contains("--load-remote-stylesheets"),
125-
#[cfg(feature = "stylesheet-cache")]
126-
cache_size: args.opt_value_from_str("--cache-size")?,
127-
files: args.free()?,
128-
};
129-
let base_url = match parse_url(args.base_url) {
264+
let base_url = match parse_url(args.base_url.as_deref()) {
130265
Ok(base_url) => base_url,
131266
Err(error) => {
132267
format_error(None, error);
133268
std::process::exit(1);
134269
}
135270
};
271+
#[cfg(feature = "stylesheet-cache")]
272+
let cache = if let Some(size) = args.cache_size {
273+
if size == 0 {
274+
eprintln!("ERROR: Cache size must be an integer greater than zero");
275+
std::process::exit(1);
276+
}
277+
std::num::NonZeroUsize::new(size)
278+
.map(css_inline::StylesheetCache::new)
279+
.map(std::sync::Mutex::new)
280+
} else {
281+
None
282+
};
136283
let options = InlineOptions {
137284
inline_style_tags: args.inline_style_tags,
138285
keep_style_tags: args.keep_style_tags,
139286
keep_link_tags: args.keep_link_tags,
140287
base_url,
141288
load_remote_stylesheets: args.load_remote_stylesheets,
142289
#[cfg(feature = "stylesheet-cache")]
143-
cache: {
144-
if let Some(size) = args.cache_size {
145-
if size == 0 {
146-
eprintln!("ERROR: Cache size must be an integer greater than zero");
147-
std::process::exit(1);
148-
}
149-
150-
std::num::NonZeroUsize::new(size)
151-
.map(css_inline::StylesheetCache::new)
152-
.map(std::sync::Mutex::new)
153-
} else {
154-
None
155-
}
156-
},
290+
cache,
157291
extra_css: args.extra_css.as_deref().map(Cow::Borrowed),
158292
preallocate_node_capacity: 32,
159293
resolver: Arc::new(DefaultStylesheetResolver),
@@ -192,7 +326,7 @@ OPTIONS:
192326
})
193327
.for_each(|result| match result {
194328
Ok((filename, result)) => match result {
195-
Ok(_) => println!("{filename}: SUCCESS"),
329+
Ok(()) => println!("{filename}: SUCCESS"),
196330
Err(error) => {
197331
format_error(Some(filename.as_str()), error);
198332
exit_code.store(1, Ordering::SeqCst);

0 commit comments

Comments
 (0)