1use crate::models::Namespace;
4use serde::Serialize;
5use std::io::{self, Write};
6use std::str::FromStr;
7
8#[derive(Debug, Clone, Serialize)]
10pub struct NamespaceInfo {
11 pub namespace: String,
13 pub description: String,
15 pub signal_words: Vec<String>,
17}
18
19impl NamespaceInfo {
20 fn new(namespace: Namespace, description: &str, signal_words: &[&str]) -> Self {
22 Self {
23 namespace: namespace.to_string(),
24 description: description.to_string(),
25 signal_words: signal_words.iter().map(|s| (*s).to_string()).collect(),
26 }
27 }
28}
29
30#[must_use]
32pub fn get_all_namespaces() -> Vec<NamespaceInfo> {
33 vec![
34 NamespaceInfo::new(
35 Namespace::Decisions,
36 "Architectural and design decisions",
37 &["decided", "chose", "going with"],
38 ),
39 NamespaceInfo::new(
40 Namespace::Patterns,
41 "Discovered patterns and conventions",
42 &["always", "never", "convention"],
43 ),
44 NamespaceInfo::new(
45 Namespace::Learnings,
46 "Lessons learned from debugging",
47 &["TIL", "learned", "discovered"],
48 ),
49 NamespaceInfo::new(
50 Namespace::Context,
51 "Important background information",
52 &["because", "constraint", "requirement"],
53 ),
54 NamespaceInfo::new(
55 Namespace::TechDebt,
56 "Technical debt tracking",
57 &["TODO", "FIXME", "temporary", "hack"],
58 ),
59 NamespaceInfo::new(
60 Namespace::Blockers,
61 "Blockers and impediments",
62 &["blocked", "waiting", "depends on"],
63 ),
64 NamespaceInfo::new(
65 Namespace::Progress,
66 "Work progress and milestones",
67 &["completed", "milestone", "shipped"],
68 ),
69 NamespaceInfo::new(
70 Namespace::Apis,
71 "API documentation and contracts",
72 &["endpoint", "request", "response"],
73 ),
74 NamespaceInfo::new(
75 Namespace::Config,
76 "Configuration details",
77 &["environment", "setting", "variable"],
78 ),
79 NamespaceInfo::new(
80 Namespace::Security,
81 "Security findings and notes",
82 &["vulnerability", "CVE", "auth"],
83 ),
84 NamespaceInfo::new(
85 Namespace::Testing,
86 "Test strategies and edge cases",
87 &["test", "edge case", "coverage"],
88 ),
89 ]
90}
91
92#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
94pub enum NamespacesOutputFormat {
95 #[default]
97 Table,
98 Json,
100 Yaml,
102}
103
104impl FromStr for NamespacesOutputFormat {
105 type Err = std::convert::Infallible;
106
107 fn from_str(s: &str) -> Result<Self, Self::Err> {
108 Ok(match s.to_lowercase().as_str() {
109 "json" => Self::Json,
110 "yaml" => Self::Yaml,
111 _ => Self::Table,
112 })
113 }
114}
115
116pub fn write_table<W: Write>(
122 writer: &mut W,
123 namespaces: &[NamespaceInfo],
124 verbose: bool,
125) -> io::Result<()> {
126 if verbose {
127 writeln!(
128 writer,
129 "{:<14}{:<38}SIGNAL WORDS",
130 "NAMESPACE", "DESCRIPTION"
131 )?;
132 for ns in namespaces {
133 writeln!(
134 writer,
135 "{:<14}{:<38}{}",
136 ns.namespace,
137 ns.description,
138 ns.signal_words.join(", ")
139 )?;
140 }
141 } else {
142 writeln!(writer, "{:<14}DESCRIPTION", "NAMESPACE")?;
143 for ns in namespaces {
144 writeln!(writer, "{:<14}{}", ns.namespace, ns.description)?;
145 }
146 }
147 Ok(())
148}
149
150pub fn write_json<W: Write>(
156 writer: &mut W,
157 namespaces: &[NamespaceInfo],
158) -> Result<(), Box<dyn std::error::Error>> {
159 let json = serde_json::to_string_pretty(namespaces)?;
160 writeln!(writer, "{json}")?;
161 Ok(())
162}
163
164pub fn write_yaml<W: Write>(
170 writer: &mut W,
171 namespaces: &[NamespaceInfo],
172) -> Result<(), Box<dyn std::error::Error>> {
173 let yaml = serde_yaml_ng::to_string(namespaces)?;
174 write!(writer, "{yaml}")?;
175 Ok(())
176}
177
178pub fn cmd_namespaces(
184 format: NamespacesOutputFormat,
185 verbose: bool,
186) -> Result<(), Box<dyn std::error::Error>> {
187 let namespaces = get_all_namespaces();
188 let stdout = io::stdout();
189 let mut handle = stdout.lock();
190
191 match format {
192 NamespacesOutputFormat::Table => {
193 write_table(&mut handle, &namespaces, verbose)?;
194 Ok(())
195 },
196 NamespacesOutputFormat::Json => write_json(&mut handle, &namespaces),
197 NamespacesOutputFormat::Yaml => write_yaml(&mut handle, &namespaces),
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_get_all_namespaces() {
207 let namespaces = get_all_namespaces();
208 assert_eq!(namespaces.len(), 11);
209
210 assert_eq!(namespaces[0].namespace, "decisions");
212 assert_eq!(
213 namespaces[0].description,
214 "Architectural and design decisions"
215 );
216 assert!(!namespaces[0].signal_words.is_empty());
217 }
218
219 #[test]
220 fn test_output_format_from_str() {
221 assert_eq!(
222 NamespacesOutputFormat::from_str("json").unwrap(),
223 NamespacesOutputFormat::Json
224 );
225 assert_eq!(
226 NamespacesOutputFormat::from_str("JSON").unwrap(),
227 NamespacesOutputFormat::Json
228 );
229 assert_eq!(
230 NamespacesOutputFormat::from_str("yaml").unwrap(),
231 NamespacesOutputFormat::Yaml
232 );
233 assert_eq!(
234 NamespacesOutputFormat::from_str("table").unwrap(),
235 NamespacesOutputFormat::Table
236 );
237 assert_eq!(
238 NamespacesOutputFormat::from_str("invalid").unwrap(),
239 NamespacesOutputFormat::Table
240 );
241 }
242
243 #[test]
244 fn test_all_namespaces_have_signal_words() {
245 let namespaces = get_all_namespaces();
246 for ns in namespaces {
247 assert!(
248 !ns.signal_words.is_empty(),
249 "Namespace {} should have signal words",
250 ns.namespace
251 );
252 }
253 }
254
255 #[test]
256 fn test_namespace_descriptions_not_empty() {
257 let namespaces = get_all_namespaces();
258 for ns in namespaces {
259 assert!(
260 !ns.description.is_empty(),
261 "Namespace {} should have a description",
262 ns.namespace
263 );
264 }
265 }
266
267 #[test]
268 fn test_write_table_simple() {
269 let namespaces = get_all_namespaces();
270 let mut buffer = Vec::new();
271 write_table(&mut buffer, &namespaces, false).unwrap();
272 let output = String::from_utf8(buffer).unwrap();
273 assert!(output.contains("NAMESPACE"));
274 assert!(output.contains("DESCRIPTION"));
275 assert!(output.contains("decisions"));
276 }
277
278 #[test]
279 fn test_write_table_verbose() {
280 let namespaces = get_all_namespaces();
281 let mut buffer = Vec::new();
282 write_table(&mut buffer, &namespaces, true).unwrap();
283 let output = String::from_utf8(buffer).unwrap();
284 assert!(output.contains("SIGNAL WORDS"));
285 assert!(output.contains("decided, chose, going with"));
286 }
287
288 #[test]
289 fn test_write_json() {
290 let namespaces = get_all_namespaces();
291 let mut buffer = Vec::new();
292 write_json(&mut buffer, &namespaces).unwrap();
293 let output = String::from_utf8(buffer).unwrap();
294 assert!(output.contains("\"namespace\""));
295 assert!(output.contains("\"decisions\""));
296 }
297
298 #[test]
299 fn test_write_yaml() {
300 let namespaces = get_all_namespaces();
301 let mut buffer = Vec::new();
302 write_yaml(&mut buffer, &namespaces).unwrap();
303 let output = String::from_utf8(buffer).unwrap();
304 assert!(output.contains("namespace:"));
305 assert!(output.contains("decisions"));
306 }
307}