claude_agent/output_style/
loader.rs1use std::path::Path;
2
3use serde::{Deserialize, Serialize};
4
5use super::OutputStyle;
6use crate::common::{DocumentLoader, SourceType, is_markdown, parse_frontmatter};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct OutputStyleFrontmatter {
10 pub name: String,
11 #[serde(default)]
12 pub description: String,
13 #[serde(default, rename = "keep-coding-instructions")]
14 pub keep_coding_instructions: bool,
15 #[serde(default)]
16 pub source_type: Option<String>,
17}
18
19#[derive(Debug, Clone, Copy, Default)]
20pub struct OutputStyleLoader;
21
22impl OutputStyleLoader {
23 pub fn new() -> Self {
24 Self
25 }
26
27 fn build_style(
28 &self,
29 fm: OutputStyleFrontmatter,
30 body: String,
31 _path: Option<&Path>,
32 ) -> OutputStyle {
33 let source = SourceType::from_str_opt(fm.source_type.as_deref());
34
35 OutputStyle::new(fm.name, fm.description, body)
36 .with_source_type(source)
37 .with_keep_coding_instructions(fm.keep_coding_instructions)
38 }
39}
40
41impl DocumentLoader<OutputStyle> for OutputStyleLoader {
42 fn parse_content(&self, content: &str, path: Option<&Path>) -> crate::Result<OutputStyle> {
43 let doc = parse_frontmatter::<OutputStyleFrontmatter>(content)?;
44 Ok(self.build_style(doc.frontmatter, doc.body, path))
45 }
46
47 fn doc_type_name(&self) -> &'static str {
48 "output style"
49 }
50
51 fn file_filter(&self) -> fn(&Path) -> bool {
52 is_markdown
53 }
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::output_style::OutputStyleSourceType;
60
61 #[test]
62 fn test_parse_output_style_with_frontmatter() {
63 let content = r#"---
64name: test-style
65description: A test output style
66keep-coding-instructions: true
67---
68
69# Custom Instructions
70
71This is the custom prompt content.
72"#;
73
74 let loader = OutputStyleLoader::new();
75 let style = loader.parse_content(content, None).unwrap();
76
77 assert_eq!(style.name, "test-style");
78 assert_eq!(style.description, "A test output style");
79 assert!(style.keep_coding_instructions);
80 assert!(style.prompt.contains("Custom Instructions"));
81 }
82
83 #[test]
84 fn test_parse_output_style_without_keep_coding() {
85 let content = r#"---
86name: concise
87description: Be concise
88---
89
90Be brief and to the point.
91"#;
92
93 let loader = OutputStyleLoader::new();
94 let style = loader.parse_content(content, None).unwrap();
95
96 assert_eq!(style.name, "concise");
97 assert!(!style.keep_coding_instructions);
98 }
99
100 #[test]
101 fn test_parse_output_style_without_frontmatter() {
102 let content = "Just some content without frontmatter";
103 let loader = OutputStyleLoader::new();
104 let result = loader.parse_content(content, None);
105
106 assert!(result.is_err());
107 }
108
109 #[test]
110 fn test_parse_output_style_with_source_type() {
111 let content = r#"---
112name: builtin-style
113description: A builtin style
114source_type: builtin
115---
116
117Content here.
118"#;
119
120 let loader = OutputStyleLoader::new();
121 let style = loader.parse_content(content, None).unwrap();
122
123 assert_eq!(style.source_type, OutputStyleSourceType::Builtin);
124 }
125}