claude_code_statusline_core/modules/
registry.rs1use super::{Module, ModuleConfig, claude_model::ClaudeModelModule, directory::DirectoryModule};
8#[cfg(feature = "git")]
9use super::{git_branch::GitBranchModule, git_status::GitStatusModule};
10use crate::types::context::Context;
11
12pub trait ModuleFactory: Send + Sync {
14 fn name(&self) -> &'static str;
16
17 fn create(&self, context: &Context) -> Box<dyn Module>;
19
20 fn config<'a>(&self, context: &'a Context) -> Option<&'a dyn ModuleConfig>;
22}
23
24pub struct Registry {
26 factories: Vec<Box<dyn ModuleFactory>>,
27}
28
29impl Registry {
30 pub fn new() -> Self {
32 Self {
33 factories: Vec::new(),
34 }
35 }
36
37 pub fn with_defaults() -> Self {
39 let mut reg = Self::new();
40 reg.register_factory(DirectoryFactory);
41 reg.register_factory(ClaudeModelFactory);
42 #[cfg(feature = "git")]
43 {
44 reg.register_factory(GitBranchFactory);
45 reg.register_factory(GitStatusFactory);
46 }
47 reg
48 }
49
50 pub fn register_factory<F: ModuleFactory + 'static>(&mut self, f: F) {
52 self.factories.push(Box::new(f));
53 }
54
55 pub fn create(&self, name: &str, context: &Context) -> Option<Box<dyn Module>> {
57 self.factories
58 .iter()
59 .find(|f| f.name() == name)
60 .map(|f| f.create(context))
61 }
62
63 pub fn config<'a>(&self, name: &str, context: &'a Context) -> Option<&'a dyn ModuleConfig> {
65 self.factories
66 .iter()
67 .find(|f| f.name() == name)
68 .and_then(|f| f.config(context))
69 }
70
71 #[allow(dead_code)]
73 pub fn list(&self) -> Vec<&'static str> {
74 self.factories.iter().map(|f| f.name()).collect()
75 }
76}
77
78impl Default for Registry {
79 fn default() -> Self {
80 Self::new()
81 }
82}
83
84struct DirectoryFactory;
87impl ModuleFactory for DirectoryFactory {
88 fn name(&self) -> &'static str {
89 "directory"
90 }
91 fn create(&self, context: &Context) -> Box<dyn Module> {
92 Box::new(DirectoryModule::from_context(context))
93 }
94 fn config<'a>(&self, context: &'a Context) -> Option<&'a dyn ModuleConfig> {
95 Some(&context.config.directory)
96 }
97}
98
99struct ClaudeModelFactory;
100impl ModuleFactory for ClaudeModelFactory {
101 fn name(&self) -> &'static str {
102 "claude_model"
103 }
104 fn create(&self, context: &Context) -> Box<dyn Module> {
105 Box::new(ClaudeModelModule::from_context(context))
106 }
107 fn config<'a>(&self, context: &'a Context) -> Option<&'a dyn ModuleConfig> {
108 Some(&context.config.claude_model)
109 }
110}
111
112#[cfg(feature = "git")]
113struct GitBranchFactory;
114#[cfg(feature = "git")]
115impl ModuleFactory for GitBranchFactory {
116 fn name(&self) -> &'static str {
117 "git_branch"
118 }
119 fn create(&self, context: &Context) -> Box<dyn Module> {
120 Box::new(GitBranchModule::from_context(context))
121 }
122 fn config<'a>(&self, context: &'a Context) -> Option<&'a dyn ModuleConfig> {
123 Some(&context.config.git_branch)
124 }
125}
126
127#[cfg(feature = "git")]
128struct GitStatusFactory;
129#[cfg(feature = "git")]
130impl ModuleFactory for GitStatusFactory {
131 fn name(&self) -> &'static str {
132 "git_status"
133 }
134 fn create(&self, context: &Context) -> Box<dyn Module> {
135 Box::new(GitStatusModule::from_context(context))
136 }
137 fn config<'a>(&self, context: &'a Context) -> Option<&'a dyn ModuleConfig> {
138 Some(&context.config.git_status)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::config::Config;
146 use crate::types::claude::{ClaudeInput, ModelInfo};
147 use crate::types::context::Context;
148
149 #[test]
150 fn default_registry_lists_core_modules() {
151 let reg = Registry::with_defaults();
152 let names = reg.list();
153 assert!(names.contains(&"directory"));
154 assert!(names.contains(&"claude_model"));
155 #[cfg(feature = "git")]
156 {
157 assert!(names.contains(&"git_branch"));
158 assert!(names.contains(&"git_status"));
159 }
160 }
161
162 #[test]
163 fn create_and_config_work_for_known_modules() {
164 let cfg = Config::default();
165 let input = ClaudeInput {
166 hook_event_name: None,
167 session_id: "s".into(),
168 transcript_path: None,
169 cwd: "/tmp".into(),
170 model: ModelInfo {
171 id: "claude-opus".into(),
172 display_name: "Opus".into(),
173 },
174 workspace: None,
175 version: None,
176 output_style: None,
177 };
178 let ctx = Context::new(input, cfg);
179 let reg = Registry::with_defaults();
180 let m = reg.create("directory", &ctx).expect("module");
181 assert_eq!(m.name(), "directory");
182 assert!(reg.config("directory", &ctx).is_some());
183 assert!(reg.create("unknown", &ctx).is_none());
184 }
185}