grimoire_css_lib/
lib.rs

1//! Core library module that orchestrates the core functionality of the Grimoire CSS system 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;
15pub mod core;
16mod infrastructure;
17
18use crate::core::GrimoireCssError;
19use commands::{handle_in_memory, process_mode_and_handle};
20use console::{style, Emoji};
21use core::{CompiledCssInMemory, ConfigInMemory};
22use indicatif::{ProgressBar, ProgressStyle};
23use infrastructure::LightningCssOptimizer;
24use std::time::{Duration, Instant};
25
26pub static SUCCESS: Emoji<'_, '_> = Emoji("🪄", "✔️");
27pub static FAILURE: Emoji<'_, '_> = Emoji("☠️", "X");
28pub static INFO: Emoji<'_, '_> = Emoji("📖", "i");
29
30pub static SPINNER: [&str; 39] = [
31    "🜁", "🜂", "🜃", "🜄", "🜇", "☽", "☾", "⚯", "⚮", "⚭", "✷", "✶", "⛦", "◈", "❖", "ᚠ", "ᚹ", "ᚻ", "ᛃ",
32    "ᛉ", "ᛊ", "ᛗ", "ᛘ", "ᛚ", "ᛜ", "ᛝ", "ᛞ", "ᛟ", "ᛠ", "ᛡ", "ᛢ", "ᛣ", "ᛤ", "ᛥ", "ᛦ", "ᛧ", "ᛨ", "ᛩ",
33    " ",
34];
35
36/// Starts the Grimoire CSS system based on the given mode,
37/// **without** performing any CLI-specific side effects.
38///
39/// This function determines the current working directory, initializes
40/// the `LightningCSSOptimizer`, and then processes the mode, invoking the
41/// appropriate command handlers.
42///
43/// # Arguments
44///
45/// * `mode` - A string representing the mode of operation (e.g., "build", "init").
46///
47/// # Returns
48///
49/// * `Ok(())` - If the mode is processed successfully.
50/// * `Err(GrimoireCSSError)` - If there is an error during initialization or command execution.
51///
52/// # Errors
53///
54/// This function returns a `GrimoireCSSError` if the current directory cannot be determined,
55/// the optimizer initialization fails, or the mode processing encounters an error.
56///
57/// # Examples
58///
59/// ```ignore
60/// use grimoire_css_lib::start;
61/// if let Err(e) = start("build".to_string()) {
62///     eprintln!("Error: {e}");
63/// }
64/// ```
65pub fn start(mode: &str) -> Result<(), GrimoireCssError> {
66    let current_dir = std::env::current_dir()?;
67    let css_optimizer = LightningCssOptimizer::new(&current_dir)?;
68
69    process_mode_and_handle(mode, &current_dir, &css_optimizer)
70}
71
72pub fn start_in_memory(
73    config: &ConfigInMemory,
74) -> Result<Vec<CompiledCssInMemory>, GrimoireCssError> {
75    let css_optimizer = LightningCssOptimizer::new_from(
76        config.browserslist_content.as_deref().unwrap_or_default(),
77    )?;
78
79    handle_in_memory(config, &css_optimizer)
80}
81
82/// Public function to read the saved messages from the buffer.
83/// This function is accessible from the main.rs (or any other crate/binary)
84/// for reading the buffer content.
85pub fn get_logged_messages() -> Vec<String> {
86    buffer::read_messages()
87}
88
89/// A convenience function that **simulates CLI behavior** (timing, logging, spinner)
90/// but is placed in the library crate to avoid duplicating code in multiple binaries
91/// or wrappers.
92///
93/// # Warning
94///
95/// - This is **not** an idiomatic approach for a typical Rust library since it
96///   introduces console-based side effects.
97/// - If you do not want logs, spinners, or colorized output, **do not** call this
98///   function. Instead, call [`start`] directly.
99/// - This function **depends** on `console` and `indicatif` crates for styling
100///   and progress-bar support, which might not be desired in all contexts.
101///
102/// # Arguments
103///
104/// * `args` - A vector of strings typically representing command-line arguments.
105///   The first argument is expected to be the binary name, and the second argument
106///   the mode (e.g. "build", "init").
107///
108/// # Returns
109///
110/// * `Ok(())` on success, or an `Err(GrimoireCSSError)` if invalid arguments or runtime
111///   issues occur.
112///
113/// # Examples
114///
115/// ```ignore
116/// use grimoire_css_lib::start_as_cli;
117///
118/// // Typically used in a real CLI setup:
119/// let args = vec!["grimoire_css".to_string(), "build".to_string()];
120/// if let Err(err) = start_as_cli(args) {
121///     eprintln!("Failed: {err}");
122/// }
123/// ```
124pub fn start_as_cli(args: Vec<String>) -> Result<(), GrimoireCssError> {
125    // print empty line for better readability
126    println!();
127
128    // Check if the user provided at least one argument (mode)
129    if args.len() < 2 {
130        let message = format!(
131            "{}\n    {} ",
132            style(format!("{} Wrong usage!", FAILURE)).red().bold(),
133            style("Follow: grimoire_css <mode> ('build' or 'init')").italic()
134        );
135        println!("{}", message);
136
137        return Err(GrimoireCssError::InvalidInput(message));
138    }
139
140    println!(
141        "{} {}",
142        INFO,
143        style("Open the Grimoire").color256(55).bold()
144    );
145
146    let pb = ProgressBar::new_spinner();
147    pb.set_message(style("Casting spells...").color256(55).italic().to_string());
148    pb.enable_steady_tick(Duration::from_millis(120));
149    pb.set_style(
150        ProgressStyle::default_spinner()
151            .tick_strings(&SPINNER)
152            .template("{spinner:.magenta} {msg}")
153            .unwrap(),
154    );
155
156    let start_time = Instant::now();
157
158    // Proceed with the main function, passing the first argument (mode)
159    match start(args[1].as_str()) {
160        Ok(_) => {
161            pb.finish_and_clear();
162
163            let duration = start_time.elapsed();
164            println!(
165                "{}",
166                style(format!(
167                    "{} {:.2?}",
168                    style("✨ Magic complete in").color256(55).bold(),
169                    style(duration).color256(55).bold().underlined(),
170                ))
171            );
172
173            output_saved_messages();
174
175            // print empty line for better readability
176            println!();
177            Ok(())
178        }
179        Err(e) => {
180            pb.finish();
181
182            println!(
183                "{}",
184                style(format!("{} Dark magic interfered!\n {}", FAILURE, e))
185                    .red()
186                    .bold()
187            );
188
189            // print empty line for better readability
190            println!();
191            Err(e)
192        }
193    }
194}
195
196fn output_saved_messages() {
197    let messages = get_logged_messages();
198
199    if !messages.is_empty() {
200        for msg in &messages {
201            println!(
202                "{} {}",
203                style("    •").color256(53),
204                style(msg).color256(53)
205            );
206        }
207    }
208}