grimoire_css_lib/
lib.rs

1//! Core library module that orchestrates the core functionality of the Grimoire CSS engine.
2//!
3//! This module provides two main functions:
4//! - [`start`] - Pure function that executes core CSS processing logic
5//! - [`start_in_memory`] - Function for processing CSS in memory without file I/O
6//! - [`start_as_cli`] - CLI wrapper with logging and user feedback (spinners, colors), it is _not idiomatic_
7//!   for a typical Rust library because it introduces side effects and depends on
8//!   console/UI crates. Use it only if you specifically want the same CLI behavior
9//!   outside of the main binary (e.g. in a Node.js wrapper or CLI application).
10//!
11//! Choose [`start`] for library usage and [`start_as_cli`] for CLI applications.
12
13mod buffer;
14mod commands;
15mod core;
16mod infrastructure;
17
18use commands::{handle_in_memory, process_mode_and_handle};
19use console::style;
20use core::{compiled_css::CompiledCssInMemory, config::ConfigInMemory};
21use indicatif::{ProgressBar, ProgressStyle};
22use infrastructure::{GrimoireCssDiagnostic, LightningCssOptimizer};
23use miette::GraphicalReportHandler;
24use std::time::{Duration, Instant};
25
26pub use core::{GrimoireCssError, color, component, config, spell::Spell};
27
28static GRIMM_CALM: &str = " |(• ε •)|";
29static GRIMM_HAPPY: &str = " ヽ(• ε •)ノ";
30static GRIMM_CURSED: &str = " |(x ~ x)|";
31static GRIMM_CASTING: [&str; 8] = [
32    GRIMM_CALM,
33    " (|¬ヘ¬)|",
34    " \\(°o°)/",
35    " (∩¬ロ¬)⊃━▪ ~",
36    " (∩¬ロ¬)⊃━▪ ~·",
37    " (∩¬ロ¬)⊃━▪ ~·•",
38    " (∩¬ロ¬)⊃━▪ ~·•●",
39    GRIMM_HAPPY,
40];
41
42/// Starts the Grimoire CSS system based on the given mode,
43/// **without** performing any CLI-specific side effects.
44///
45/// This function determines the current working directory, initializes
46/// the `LightningCSSOptimizer`, and then processes the mode, invoking the
47/// appropriate command handlers.
48///
49/// # Arguments
50///
51/// * `mode` - A string representing the mode of operation (e.g., "build", "init").
52///
53/// # Returns
54///
55/// * `Ok(())` - If the mode is processed successfully.
56/// * `Err(GrimoireCSSError)` - If there is an error during initialization or command execution.
57///
58/// # Errors
59///
60/// This function returns a `GrimoireCSSError` if the current directory cannot be determined,
61/// the optimizer initialization fails, or the mode processing encounters an error.
62///
63/// # Examples
64///
65/// ```ignore
66/// use grimoire_css_lib::start;
67/// if let Err(e) = start("build".to_string()) {
68///     eprintln!("Error: {e}");
69/// }
70/// ```
71pub fn start(mode: &str) -> Result<(), GrimoireCssError> {
72    let current_dir = std::env::current_dir()?;
73    let css_optimizer = LightningCssOptimizer::new(&current_dir)?;
74
75    process_mode_and_handle(mode, &current_dir, &css_optimizer)
76}
77
78pub fn start_in_memory(
79    config: &ConfigInMemory,
80) -> Result<Vec<CompiledCssInMemory>, GrimoireCssError> {
81    let css_optimizer = LightningCssOptimizer::new_from(
82        config.browserslist_content.as_deref().unwrap_or_default(),
83    )?;
84
85    handle_in_memory(config, &css_optimizer)
86}
87
88/// Public function to read the saved messages from the buffer.
89/// This function is accessible from the main.rs (or any other crate/binary)
90/// for reading the buffer content.
91pub fn get_logged_messages() -> Vec<String> {
92    buffer::read_messages()
93}
94
95/// A convenience function that **simulates CLI behavior** (timing, logging, spinner)
96/// but is placed in the library crate to avoid duplicating code in multiple binaries
97/// or wrappers.
98///
99/// # Warning
100///
101/// - This is **not** an idiomatic approach for a typical Rust library since it
102///   introduces console-based side effects.
103/// - If you do not want logs, spinners, or colorized output, **do not** call this
104///   function. Instead, call [`start`] directly.
105/// - This function **depends** on `console` and `indicatif` crates for styling
106///   and progress-bar support, which might not be desired in all contexts.
107///
108/// # Arguments
109///
110/// * `args` - A vector of strings typically representing command-line arguments.
111///   The first argument is expected to be the binary name, and the second argument
112///   the mode (e.g. "build", "init").
113///
114/// # Returns
115///
116/// * `Ok(())` on success, or an `Err(GrimoireCSSError)` if invalid arguments or runtime
117///   issues occur.
118///
119/// # Examples
120///
121/// ```ignore
122/// use grimoire_css_lib::start_as_cli;
123///
124/// // Typically used in a real CLI setup:
125/// let args = vec!["grimoire_css".to_string(), "build".to_string()];
126/// if let Err(err) = start_as_cli(args) {
127///     eprintln!("Failed: {err}");
128/// }
129/// ```
130pub fn start_as_cli(args: Vec<String>) -> Result<(), GrimoireCssError> {
131    println!();
132
133    println!(
134        "{}  Ritual initiated",
135        style(" Grimoire CSS ").white().on_color256(55).bright(),
136    );
137
138    // Check if the user provided at least one argument (mode)
139    if args.len() < 2 {
140        let message = format!(
141            "{}  {} ",
142            style(" Cursed! ").white().on_red().bright(),
143            "Follow: grimoire_css <mode> ('build', 'init', 'shorten')"
144        );
145
146        println!();
147        println!("{GRIMM_CURSED}");
148        println!();
149        println!("{message}");
150
151        return Err(GrimoireCssError::InvalidInput(message));
152    }
153
154    println!();
155
156    let pb = ProgressBar::new_spinner();
157    pb.set_style(ProgressStyle::default_spinner().tick_strings(&GRIMM_CASTING));
158    pb.enable_steady_tick(Duration::from_millis(220));
159    pb.set_draw_target(indicatif::ProgressDrawTarget::stdout_with_hz(10));
160
161    let start_time = Instant::now();
162
163    // Proceed with the main function, passing the first argument (mode)
164    match start(&args[1]) {
165        Ok(_) => {
166            pb.finish_and_clear();
167
168            print!("\r\x1b[2K{GRIMM_HAPPY}  Spells cast successfully.\n");
169
170            let duration = start_time.elapsed();
171
172            output_saved_messages();
173
174            println!();
175
176            println!(
177                "{}",
178                style(format!(
179                    "{}",
180                    style(format!(" Enchanted in {duration:.2?}! "))
181                        .white()
182                        .on_color256(55)
183                        .bright(),
184                ))
185            );
186
187            println!();
188
189            Ok(())
190        }
191        Err(e) => {
192            pb.finish_and_clear();
193            print!("\r\x1b[2K{GRIMM_CURSED}\n");
194
195            println!();
196            println!("{}", style(" Cursed! ").white().on_red().bright());
197            println!();
198
199            let diagnostic: GrimoireCssDiagnostic = (&e).into();
200            let mut out = String::new();
201            GraphicalReportHandler::new()
202                .render_report(&mut out, &diagnostic)
203                .unwrap();
204            println!("{out}");
205
206            Err(e)
207        }
208    }
209}
210
211fn output_saved_messages() {
212    let messages = get_logged_messages();
213
214    if !messages.is_empty() {
215        println!();
216        for msg in &messages {
217            println!("  • {msg}");
218        }
219    }
220}