Skip to main content

zenith_cli/commands/plugin/
assets.rs

1//! Embedded skill assets and small accessors over them.
2//!
3//! The `SKILL_FILES` / `COMMAND_FILES` tables are generated by `build.rs` from
4//! the canonical skill source under `assets/skill` and `assets/plugin-commands`.
5
6include!(concat!(env!("OUT_DIR"), "/skill_assets.rs"));
7
8/// The relative path of the skill's entry file within `SKILL_FILES`.
9pub const SKILL_ENTRY: &str = "SKILL.md";
10
11/// Look up an embedded skill file by its relative path.
12pub fn skill_file(rel: &str) -> Option<&'static str> {
13    SKILL_FILES
14        .iter()
15        .find(|(name, _)| *name == rel)
16        .map(|(_, body)| *body)
17}
18
19/// The raw `SKILL.md` (with its YAML frontmatter), as authored.
20pub fn skill_md_raw() -> &'static str {
21    skill_file(SKILL_ENTRY).unwrap_or("")
22}
23
24/// The `SKILL.md` body with any leading YAML frontmatter removed.
25pub fn skill_md_body() -> &'static str {
26    strip_frontmatter(skill_md_raw())
27}
28
29/// The `description:` field from the skill frontmatter, if present.
30pub fn skill_description() -> Option<String> {
31    frontmatter(skill_md_raw()).and_then(|fm| {
32        fm.lines()
33            .find_map(|l| l.trim().strip_prefix("description:"))
34            .map(|v| unquote(v.trim()))
35    })
36}
37
38/// Split a `---` frontmatter block off the front of `s`, accepting LF or CRLF.
39/// Returns `(frontmatter_without_fences, body)`.
40fn split_frontmatter(s: &str) -> (Option<&str>, &str) {
41    let s = s.strip_prefix('\u{feff}').unwrap_or(s);
42    let rest = if let Some(rest) = s.strip_prefix("---\r\n") {
43        rest
44    } else if let Some(rest) = s.strip_prefix("---\n") {
45        rest
46    } else {
47        return (None, s);
48    };
49
50    for (line_start, line) in rest.split_inclusive('\n').scan(0usize, |offset, line| {
51        let line_start = *offset;
52        *offset += line.len();
53        Some((line_start, line))
54    }) {
55        if line.trim_end_matches(['\r', '\n']) == "---" {
56            return (Some(&rest[..line_start]), &rest[line_start + line.len()..]);
57        }
58    }
59
60    (None, s)
61}
62
63fn frontmatter(s: &str) -> Option<&str> {
64    split_frontmatter(s).0
65}
66
67fn strip_frontmatter(s: &str) -> &str {
68    split_frontmatter(s).1
69}
70
71/// Strip a single pair of surrounding double or single quotes.
72fn unquote(s: &str) -> String {
73    let bytes = s.as_bytes();
74    if bytes.len() >= 2 {
75        let first = bytes[0];
76        let last = bytes[bytes.len() - 1];
77        if (first == b'"' && last == b'"') || (first == b'\'' && last == b'\'') {
78            return s[1..s.len() - 1].to_owned();
79        }
80    }
81    s.to_owned()
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn split_frontmatter_accepts_lf() {
90        let (fm, body) = split_frontmatter("---\ndescription: test\n---\n# Body\n");
91
92        assert_eq!(fm, Some("description: test\n"));
93        assert_eq!(body, "# Body\n");
94    }
95
96    #[test]
97    fn split_frontmatter_accepts_crlf() {
98        let (fm, body) = split_frontmatter("---\r\ndescription: test\r\n---\r\n# Body\r\n");
99
100        assert_eq!(fm, Some("description: test\r\n"));
101        assert_eq!(body, "# Body\r\n");
102    }
103
104    #[test]
105    fn split_frontmatter_accepts_utf8_bom() {
106        let (fm, body) = split_frontmatter("\u{feff}---\r\ndescription: test\r\n---\r\n# Body\r\n");
107
108        assert_eq!(fm, Some("description: test\r\n"));
109        assert_eq!(body, "# Body\r\n");
110    }
111
112    #[test]
113    fn split_frontmatter_leaves_plain_markdown_unchanged() {
114        let input = "# Body\n---\nnot frontmatter\n";
115
116        assert_eq!(split_frontmatter(input), (None, input));
117    }
118}