use std::path::PathBuf;
use anyhow::anyhow;
use oxvg_ast::{
parse::roxmltree::parse_with_options,
visitor::Info,
xmlwriter::{Indent, Options, Space},
};
use oxvg_optimiser::{Extends, Jobs};
use roxmltree::ParsingOptions;
use crate::{
args::RunCommand,
config::{self, Config},
walk::{Output, Walk},
};
#[derive(clap::Args, Debug)]
pub struct Optimise {
#[clap(flatten)]
pub walk: Walk,
#[clap(long, short, num_args(0..=1))]
pub config: Option<Vec<PathBuf>>,
#[clap(long, short)]
pub extends: Option<Extends>,
#[clap(long, short, default_value = "none")]
pub pretty: Indent,
#[clap(long, short, default_value = "auto")]
pub space: Space,
}
impl RunCommand for Optimise {
async fn run(self, config: Config) -> anyhow::Result<()> {
let config = self.handle_config(config)?;
let Some(config) = config else {
return Ok(());
};
let jobs = config
.optimise
.as_ref()
.map(config::Optimise::resolve_jobs)
.unwrap_or_default();
self.walk(jobs)
}
}
impl Optimise {
pub fn walk(self, jobs: Jobs) -> anyhow::Result<()> {
self.walk.run(move || {
let jobs = jobs.clone();
let format_options = Options {
indent: self.pretty,
trim_whitespace: self.space,
..Options::default()
};
Box::new(move |source, path, output| {
let result = parse_with_options(
source,
ParsingOptions {
allow_dtd: true,
..ParsingOptions::default()
},
#[allow(clippy::cast_precision_loss)]
|dom, allocator| -> anyhow::Result<()> {
let input_bytes = source.len() as f64;
let info = Info {
path: path.cloned(),
multipass_count: 0,
allocator,
};
jobs.run(dom, &info)
.map_err(|e| anyhow::Error::msg(e.to_string()))?;
let output = Output {
options: format_options,
dom,
input: path,
destination: output,
input_bytes,
};
output.output()?;
Ok(())
},
);
match result {
Err(err) => eprintln!("{err}"),
Ok(Err(err)) => eprintln!("{err}"),
Ok(Ok(())) => {}
}
})
})
}
fn handle_config(&self, config: Config) -> anyhow::Result<Option<Config>> {
if let Some(config_paths) = &self.config {
if let Some(config_path) = config_paths.first() {
log::debug!("using specified config");
let file = std::fs::File::open(config_path)?;
serde_json::from_reader(file)
.map_err(|e| anyhow!(e))
.map(Some)
} else {
log::debug!("printing config");
serde_json::to_writer(
std::io::stdout(),
&Config {
optimise: Some(config.optimise.unwrap_or_default()),
lint: Some(config.lint.unwrap_or_default()),
},
)?;
Ok(None)
}
} else if let Some(extends) = &self.extends {
Ok(Some(Config {
optimise: Some(crate::config::Optimise {
extends: Some(extends.clone()),
jobs: Jobs::none(),
omit: None,
}),
lint: config.lint,
}))
} else {
log::debug!("using inferred config");
Ok(Some(config))
}
}
}