grimoire_css_lib/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
//! The main library module that orchestrates the core functionality of the Grimoire CSS system.
//!
//! This module provides:
//! - A **pure** function `start` that executes the core logic of Grimoire CSS without
//! printing or side effects.
//! - A **convenience** function [`start_as_cli`] for CLI usage. It includes logging,
//! timing, and user-facing output (spinners, styled text), but is _not idiomatic_
//! for a typical Rust library because it introduces side effects and depends on
//! console/UI crates. Use it only if you specifically want the same CLI behavior
//! outside of the main binary (e.g. in a Node.js wrapper).
mod buffer;
mod commands;
pub mod core;
mod infrastructure;
use crate::core::GrimoireCSSError;
use commands::process_mode_and_handle;
use console::{style, Emoji};
use indicatif::{ProgressBar, ProgressStyle};
use infrastructure::LightningCSSOptimizer;
use std::time::{Duration, Instant};
pub static SUCCESS: Emoji<'_, '_> = Emoji("🪄", "✔️");
pub static FAILURE: Emoji<'_, '_> = Emoji("☠️", "X");
pub static INFO: Emoji<'_, '_> = Emoji("📖", "i");
pub static SPINNER: [&str; 10] = ["🜁", "🜂", "🜃", "🜄", "✷", "☽", "☾", "🜇", "✶", ""];
/// Starts the Grimoire CSS system based on the given mode,
/// **without** performing any CLI-specific side effects.
///
/// This function determines the current working directory, initializes
/// the `LightningCSSOptimizer`, and then processes the mode, invoking the
/// appropriate command handlers.
///
/// # Arguments
///
/// * `mode` - A string representing the mode of operation (e.g., "build", "init").
///
/// # Returns
///
/// * `Ok(())` - If the mode is processed successfully.
/// * `Err(GrimoireCSSError)` - If there is an error during initialization or command execution.
///
/// # Errors
///
/// This function returns a `GrimoireCSSError` if the current directory cannot be determined,
/// the optimizer initialization fails, or the mode processing encounters an error.
///
/// # Examples
///
/// ```ignore
/// use grimoire_css_lib::start;
/// if let Err(e) = start("build".to_string()) {
/// eprintln!("Error: {e}");
/// }
/// ```
pub fn start(mode: String) -> Result<(), GrimoireCSSError> {
let current_dir = std::env::current_dir()?;
let css_optimizer = LightningCSSOptimizer::new(¤t_dir)?;
process_mode_and_handle(&mode, ¤t_dir, &css_optimizer)
}
/// Public function to read the saved messages from the buffer.
/// This function is accessible from the main.rs (or any other crate/binary)
/// for reading the buffer content.
pub fn get_logged_messages() -> Vec<String> {
buffer::read_messages()
}
/// A convenience function that **simulates CLI behavior** (timing, logging, spinner)
/// but is placed in the library crate to avoid duplicating code in multiple binaries
/// or wrappers.
///
/// # Warning
///
/// - This is **not** an idiomatic approach for a typical Rust library since it
/// introduces console-based side effects.
/// - If you do not want logs, spinners, or colorized output, **do not** call this
/// function. Instead, call [`start`] directly.
/// - This function **depends** on `console` and `indicatif` crates for styling
/// and progress-bar support, which might not be desired in all contexts.
///
/// # Arguments
///
/// * `args` - A vector of strings typically representing command-line arguments.
/// The first argument is expected to be the binary name, and the second argument
/// the mode (e.g. "build", "init").
///
/// # Returns
///
/// * `Ok(())` on success, or an `Err(GrimoireCSSError)` if invalid arguments or runtime
/// issues occur.
///
/// # Examples
///
/// ```ignore
/// use grimoire_css_lib::start_as_cli;
///
/// // Typically used in a real CLI setup:
/// let args = vec!["grimoire_css".to_string(), "build".to_string()];
/// if let Err(err) = start_as_cli(args) {
/// eprintln!("Failed: {err}");
/// }
/// ```
pub fn start_as_cli(args: Vec<String>) -> Result<(), GrimoireCSSError> {
let start_time = Instant::now();
println!("{} {}", INFO, style("Opened the Grimoire").blue().bold());
let pb = ProgressBar::new_spinner();
pb.set_message(style("Casting spells...").blue().italic().to_string());
pb.enable_steady_tick(Duration::from_millis(120));
pb.set_style(
ProgressStyle::default_spinner()
.tick_strings(&SPINNER)
.template("{spinner:.magenta} {msg}")
.unwrap(),
);
// Check if the user provided at least one argument (mode)
if args.len() < 2 {
pb.finish();
return Err(GrimoireCSSError::InvalidInput(format!(
"{}\n Follow: {} <mode>",
style(format!("{} Wrong usage!", FAILURE)).red().bold(),
style(&args[0]).yellow().italic()
)));
}
// Proceed with the main function, passing the first argument (mode)
match start(args[1].to_owned()) {
Ok(_) => {
pb.finish_and_clear();
output_saved_messages();
let duration = start_time.elapsed();
println!(
"{}",
style(format!("✨ Magic complete in {:.2?}!", duration))
.magenta()
.bold()
);
Ok(())
}
Err(e) => {
pb.finish();
println!(
"{}",
style(format!("{} Dark magic interfered!\n {}", FAILURE, e))
.red()
.bold()
);
Err(e)
}
}
}
fn output_saved_messages() {
let messages = get_logged_messages();
if !messages.is_empty() {
println!("{}", style("History:").dim().bold());
for msg in messages.iter() {
println!("{} {}", style(" •").dim(), style(msg).dim());
}
}
}