csskit 0.0.22

Refreshing CSS!
use crate::{CliError, CliResult, GlobalConfig, InputArgs};
use bumpalo::Bump;
use clap::Args;
use css_ast::{CssAtomSet, StyleSheet, Visitable};
use css_lexer::Lexer;
use css_parse::{CursorCompactWriteSink, CursorOverlaySink, Parser, ToCursors};
use csskit_highlight::{AnsiHighlightCursorStream, DefaultAnsiTheme, TokenHighlighter};
use csskit_transform::{CssMinifierFeature, Transformer};
use std::io::Read;

/// Minify CSS files to compress them optimized delivery.
#[derive(Debug, Args)]
pub struct Min {
	/// A list of CSS files to build. Each input will result in one output file.
	#[command(flatten)]
	content: InputArgs,

	/// Where to save files.
	#[arg(short, long, group = "output_file", value_parser)]
	output: Option<String>,

	/// Don't write any files, instead report each change that would have been made.
	/// This will exit with a non-zero status code if any changes need to be made. Useful for CI.
	#[arg(long, value_parser)]
	check: bool,
}

impl Min {
	pub fn run(&self, config: GlobalConfig) -> CliResult {
		let Min { content, output, check } = self;
		let color = config.colors() && output.is_none() && !*check;
		let bump = Bump::default();
		let start = std::time::Instant::now();
		if *check && output.is_some() {
			eprintln!("Ignoring output option, because check was passed");
		}
		let mut checks = 0;
		for (file_name, mut source) in content.sources()? {
			let mut source_string = String::new();
			source.read_to_string(&mut source_string)?;
			let source_text = source_string.as_str();
			let lexer = Lexer::new(&CssAtomSet::ATOMS, source_text);
			let mut parser = Parser::new(&bump, source_text, lexer);
			let mut result = parser.parse_entirely::<StyleSheet>();
			if let Some(ref mut stylesheet) = result.output {
				let mut transformer =
					Transformer::new_in(&bump, CssMinifierFeature::all_bits(), &CssAtomSet::ATOMS, source_text);
				transformer.transform(stylesheet);
				let overlays = transformer.overlays();

				let mut str = String::new();
				if color {
					let mut highlighter = TokenHighlighter::new();
					stylesheet.accept(&mut highlighter);
					let ansi = AnsiHighlightCursorStream::new(&mut str, &highlighter, DefaultAnsiTheme);
					{
						let mut stream = CursorOverlaySink::new(
							source_text,
							&overlays,
							CursorCompactWriteSink::new(source_text, ansi),
						);
						result.to_cursors(&mut stream);
					}
				} else {
					{
						let mut stream = CursorOverlaySink::new(
							source_text,
							&overlays,
							CursorCompactWriteSink::new(source_text, &mut str),
						);
						result.to_cursors(&mut stream);
					}
				};
				if *check {
					if str != source_text {
						println!("{str}");
						checks += 1;
					}
				} else if let Some(file) = output {
					std::fs::write(file, str.as_bytes())?;
				} else {
					println!("{str}");
				}
			} else {
				for compact_err in result.errors {
					let report = crate::commands::format_diagnostic_error(&compact_err, &source_string, file_name);
					println!("{report}");
				}
			}
		}
		eprintln!("Slurped up CSS in {:?}! Neat!", start.elapsed());
		if checks > 0 { Err(CliError::Checks(checks))? } else { Ok(()) }
	}
}