tcss_cli/commands/
compile.rs

1//! Compile command implementation
2
3use anyhow::{Context, Result};
4use std::fs;
5use std::path::Path;
6use std::time::Instant;
7use tcss_core::{Generator, GeneratorOptions, Lexer, Parser};
8
9use crate::output;
10
11/// Compile a TCSS file to CSS
12pub fn compile_file(
13    input: &Path,
14    output: Option<&Path>,
15    minify: bool,
16    _verbose: bool,
17) -> Result<()> {
18    let start_time = Instant::now();
19
20    output::verbose(&format!("Compiling: {}", input.display()));
21    output::debug_value("Input file", &input);
22    output::debug_value("Minify", &minify);
23
24    // Read input file
25    output::debug("Reading input file...");
26    let source = fs::read_to_string(input)
27        .with_context(|| format!("Failed to read input file: {}", input.display()))?;
28
29    let input_size = source.len() as u64;
30    output::debug(&format!("Read {} bytes from input file", input_size));
31
32    // Compile TCSS to CSS
33    output::debug("Starting compilation...");
34    let compile_start = Instant::now();
35    let css = compile_source(&source, minify)?;
36    let compile_duration = compile_start.elapsed();
37
38    output::debug(&format!("Compilation took {:?}", compile_duration));
39    output::debug(&format!("Generated {} bytes of CSS", css.len()));
40
41    // Determine output path
42    let output_path = match output {
43        Some(path) => path.to_path_buf(),
44        None => {
45            let mut path = input.to_path_buf();
46            path.set_extension("css");
47            path
48        }
49    };
50
51    output::debug_value("Output file", &output_path);
52
53    // Write output file
54    output::debug("Writing output file...");
55    fs::write(&output_path, &css)
56        .with_context(|| format!("Failed to write output file: {}", output_path.display()))?;
57
58    // Success message
59    output::success(&format!(
60        "Compiled {} → {}",
61        input.display(),
62        output_path.display()
63    ));
64
65    // Show file size and timing in verbose mode
66    output::file_size(&output_path.display().to_string(), css.len() as u64);
67    output::timing("Compilation", start_time.elapsed().as_millis());
68
69    Ok(())
70}
71
72/// Compile TCSS source code to CSS
73pub fn compile_source(source: &str, minify: bool) -> Result<String> {
74    // Tokenize
75    output::debug("Phase 1: Tokenization");
76    let mut lexer = Lexer::new(source);
77    let tokens = lexer
78        .tokenize()
79        .map_err(|e| anyhow::anyhow!("Lexer error: {}", e))?;
80
81    output::debug(&format!("Generated {} tokens", tokens.len()));
82
83    // Parse
84    output::debug("Phase 2: Parsing");
85    let mut parser = Parser::new(tokens);
86    let program = parser
87        .parse()
88        .map_err(|e| anyhow::anyhow!("Parser error: {}", e))?;
89
90    output::debug(&format!("Parsed {} AST nodes", program.nodes.len()));
91
92    // Generate CSS
93    output::debug("Phase 3: CSS Generation");
94    let mut generator = Generator::with_options(GeneratorOptions {
95        minify,
96        indent: "  ".to_string(),
97        trailing_newline: true,
98    });
99
100    let css = generator
101        .generate(&program)
102        .map_err(|e| anyhow::anyhow!("Generator error: {}", e))?;
103
104    output::debug(&format!("Generated {} bytes of CSS", css.len()));
105
106    Ok(css)
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn test_compile_source() {
115        let source = r#"
116@var primary: #3498db
117
118.button:
119    background: primary
120    padding: 16px
121"#;
122
123        let css = compile_source(source, false).unwrap();
124        assert!(css.contains(".button"));
125        assert!(css.contains("background: #3498db"));
126        assert!(css.contains("padding: 16px"));
127    }
128
129    #[test]
130    fn test_compile_source_minified() {
131        let source = r#"
132.button:
133    padding: 16px
134"#;
135
136        let css = compile_source(source, true).unwrap();
137        assert!(css.contains(".button{padding:16px;}"));
138    }
139}
140