mofa_plugins/skill/
parser.rs1use crate::skill::metadata::SkillMetadata;
4use anyhow::Result;
5use regex::Regex;
6use std::fs;
7use std::path::Path;
8
9pub struct SkillParser;
11
12impl SkillParser {
13 pub fn parse_frontmatter(content: &str) -> Result<(SkillMetadata, String)> {
15 let frontmatter_regex = Regex::new(r"^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$").unwrap();
17
18 if let Some(caps) = frontmatter_regex.captures(content) {
19 let yaml = &caps[1];
20 let markdown = &caps[2];
21
22 let metadata: SkillMetadata = serde_yaml::from_str(yaml)
23 .map_err(|e| anyhow::anyhow!("Failed to parse YAML frontmatter: {}", e))?;
24
25 Ok((metadata, markdown.to_string()))
26 } else {
27 anyhow::bail!("SKILL.md must start with YAML frontmatter")
28 }
29 }
30
31 pub fn parse_from_file(skill_md_path: impl AsRef<Path>) -> Result<(SkillMetadata, String)> {
33 let content = fs::read_to_string(skill_md_path.as_ref())?;
34 Self::parse_frontmatter(&content)
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41
42 #[test]
43 fn test_parse_frontmatter() {
44 let content = r#"---
45name: test_skill
46description: A test skill
47category: test
48tags: [test, example]
49version: "1.0.0"
50---
51
52# Test Skill
53
54This is a test skill."#;
55
56 let (metadata, markdown) = SkillParser::parse_frontmatter(content).unwrap();
57
58 assert_eq!(metadata.name, "test_skill");
59 assert_eq!(metadata.description, "A test skill");
60 assert_eq!(metadata.category, Some("test".to_string()));
61 assert_eq!(metadata.tags, vec!["test", "example"]);
62 assert_eq!(metadata.version, Some("1.0.0".to_string()));
63 assert!(markdown.contains("# Test Skill"));
64 }
65
66 #[test]
67 fn test_parse_frontmatter_no_frontmatter() {
68 let content = "# No Frontmatter\nJust content";
69 let result = SkillParser::parse_frontmatter(content);
70 assert!(result.is_err());
71 }
72}