tcss_cli/commands/
compile.rs1use 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
11pub 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 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 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 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 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 output::success(&format!(
60 "Compiled {} → {}",
61 input.display(),
62 output_path.display()
63 ));
64
65 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
72pub fn compile_source(source: &str, minify: bool) -> Result<String> {
74 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 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 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