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(&current_dir)?;

    process_mode_and_handle(&mode, &current_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());
        }
    }
}