1use std::{path::Path, sync::Arc};
2
3use schemars::JsonSchema;
4
5use crate::{
6 GuideEntry, HumanViewDef, Middleware, OutputSchema, RuntimeGroupSpec, SchemaRegistry,
7 parse_guides_from_markdown,
8};
9
10pub type ModuleRegister = Arc<dyn Fn(&mut ModuleContext<'_>) -> RuntimeGroupSpec + Send + Sync>;
12
13pub trait CommandModule: Send + Sync + std::fmt::Debug + 'static {
18 fn category(&self) -> String;
20
21 fn guides(&self) -> Vec<GuideEntry> {
23 Vec::new()
24 }
25
26 fn views(&self) -> Vec<HumanViewDef> {
28 Vec::new()
29 }
30
31 fn register(&self, context: &mut ModuleContext<'_>) -> RuntimeGroupSpec;
33}
34
35#[derive(Clone)]
41pub struct Module {
42 pub category: String,
44 pub guides: Vec<GuideEntry>,
46 pub views: Vec<HumanViewDef>,
48 pub register: ModuleRegister,
50}
51
52impl Module {
53 #[must_use]
55 pub fn new<F>(category: impl Into<String>, register: F) -> Self
56 where
57 F: Fn(&mut ModuleContext<'_>) -> RuntimeGroupSpec + Send + Sync + 'static,
58 {
59 Self {
60 category: category.into(),
61 guides: Vec::new(),
62 views: Vec::new(),
63 register: Arc::new(register),
64 }
65 }
66
67 #[must_use]
69 pub fn from_command_module<M>(module: M) -> Self
70 where
71 M: CommandModule,
72 {
73 let category = module.category();
74 let guides = module.guides();
75 let views = module.views();
76 let module = Arc::new(module);
77 Self {
78 category,
79 guides,
80 views,
81 register: Arc::new(move |context| module.register(context)),
82 }
83 }
84
85 #[must_use]
87 pub fn with_guide(mut self, guide: GuideEntry) -> Self {
88 self.guides.push(guide);
89 self
90 }
91
92 #[must_use]
94 pub fn with_guides(mut self, guides: impl IntoIterator<Item = GuideEntry>) -> Self {
95 self.guides.extend(guides);
96 self
97 }
98
99 #[must_use]
101 pub fn with_guides_from_markdown(
102 self,
103 files: impl IntoIterator<Item = (impl AsRef<Path>, impl AsRef<[u8]>)>,
104 ) -> Self {
105 self.with_guides(parse_guides_from_markdown(files))
106 }
107
108 #[must_use]
110 pub fn with_view(mut self, view: HumanViewDef) -> Self {
111 self.views.push(view);
112 self
113 }
114}
115
116impl std::fmt::Debug for Module {
117 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 formatter
119 .debug_struct("Module")
120 .field("category", &self.category)
121 .field("guides", &self.guides)
122 .field("views", &self.views)
123 .finish_non_exhaustive()
124 }
125}
126
127#[derive(Debug)]
133pub struct ModuleContext<'middleware> {
134 middleware: &'middleware mut Middleware,
135 guides: Vec<GuideEntry>,
136 views: Vec<HumanViewDef>,
137}
138
139impl<'middleware> ModuleContext<'middleware> {
140 pub(crate) fn new(middleware: &'middleware mut Middleware) -> Self {
141 Self {
142 middleware,
143 guides: Vec::new(),
144 views: Vec::new(),
145 }
146 }
147
148 pub fn middleware(&self) -> &Middleware {
150 self.middleware
151 }
152
153 pub fn middleware_mut(&mut self) -> &mut Middleware {
155 self.middleware
156 }
157
158 pub fn config(&self) -> &crate::config::ConfigFile {
166 &self.middleware.config
167 }
168
169 pub fn schema_registry(&mut self) -> &mut SchemaRegistry {
171 &mut self.middleware.schema_registry
172 }
173
174 pub fn register_schema<T: OutputSchema>(&mut self, command_path: impl Into<String>) {
176 self.middleware
177 .schema_registry
178 .register::<T>(command_path.into());
179 }
180
181 pub fn register_json_schema<T: JsonSchema>(&mut self, command_path: impl Into<String>) {
183 self.middleware
184 .schema_registry
185 .register_json_schema::<T>(command_path.into());
186 }
187
188 pub fn register_view(&mut self, view: HumanViewDef) {
190 self.middleware.human_views.register(view.clone());
191 self.views.push(view);
192 }
193
194 pub fn add_guide(&mut self, guide: GuideEntry) {
196 self.guides.push(guide);
197 }
198
199 pub fn add_guides(&mut self, guides: impl IntoIterator<Item = GuideEntry>) {
201 self.guides.extend(guides);
202 }
203
204 pub fn add_guides_from_markdown(
206 &mut self,
207 files: impl IntoIterator<Item = (impl AsRef<Path>, impl AsRef<[u8]>)>,
208 ) {
209 self.add_guides(parse_guides_from_markdown(files));
210 }
211
212 pub(crate) fn into_parts(self) -> (Vec<GuideEntry>, Vec<HumanViewDef>) {
213 (self.guides, self.views)
214 }
215}