claude_code_statusline_core/
engine.rs1use crate::Config;
9use crate::debug::DebugLogger;
10use crate::error::CoreError;
11use crate::modules::render_module_with_timeout;
12use crate::parser::extract_modules_from_format;
13use crate::types::claude::ClaudeInput;
14use crate::types::context::Context;
15use std::collections::HashMap;
16
17pub struct Engine {
19 config: Config,
20}
21
22impl Engine {
23 pub fn new(config: Config) -> Self {
25 Self { config }
26 }
27
28 pub fn render(&self, input: &ClaudeInput) -> Result<String, CoreError> {
30 let logger = DebugLogger::new(self.config.debug);
31 let context = Context::new(input.clone(), self.config.clone());
32
33 let format = &context.config.format;
34 let module_names = extract_modules_from_format(format);
35
36 let module_outputs: HashMap<String, String> = {
38 #[cfg(feature = "parallel")]
39 {
40 use rayon::prelude::*;
41 module_names
42 .par_iter()
43 .filter_map(|name| {
44 if name == "character" {
45 return None;
46 }
47 render_module_with_timeout(name, &context, &logger)
48 .map(|out| (name.clone(), out))
49 })
50 .collect()
51 }
52
53 #[cfg(not(feature = "parallel"))]
54 {
55 let mut map = HashMap::new();
56 for name in &module_names {
57 if name == "character" {
58 continue;
59 }
60 if let Some(out) = render_module_with_timeout(name, &context, &logger) {
61 map.insert(name.clone(), out);
62 }
63 }
64 map
65 }
66 };
67
68 let mut tokens: HashMap<&str, String> = HashMap::new();
71 for (k, v) in &module_outputs {
72 tokens.insert(k.as_str(), v.clone());
73 }
74 let mut rendered = crate::style::render_with_style_template(format, &tokens, "");
75 rendered.push_str("\x1b[0m");
78 Ok(rendered)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::types::claude::{ClaudeInput, ModelInfo, WorkspaceInfo};
86
87 #[test]
88 fn engine_renders_default_format() {
89 let input = ClaudeInput {
90 hook_event_name: None,
91 session_id: "test-session".into(),
92 transcript_path: None,
93 cwd: "/tmp".into(),
94 model: ModelInfo {
95 id: "claude-opus".into(),
96 display_name: "Opus".into(),
97 },
98 workspace: Some(WorkspaceInfo {
99 current_dir: "/tmp".into(),
100 project_dir: Some("/tmp".into()),
101 }),
102 version: Some("1.0.0".into()),
103 output_style: None,
104 };
105 let cfg = Config::default();
106 let engine = Engine::new(cfg);
107 let out = engine.render(&input).expect("render ok");
108 let plain = String::from_utf8(strip_ansi_escapes::strip(out)).unwrap();
109 assert!(plain.contains("/tmp"));
110 assert!(plain.contains("Opus"));
111 }
112}