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