|
7 | 7 | package skill |
8 | 8 |
|
9 | 9 | import ( |
10 | | - "strings" |
11 | | - |
12 | 10 | "gopkg.in/yaml.v3" |
13 | 11 |
|
14 | | - "github.com/ActiveMemory/ctx/internal/config/token" |
15 | 12 | errSkill "github.com/ActiveMemory/ctx/internal/err/skill" |
| 13 | + "github.com/ActiveMemory/ctx/internal/parse" |
16 | 14 | ) |
17 | 15 |
|
18 | | -// parseManifest extracts YAML frontmatter and markdown body from a |
19 | | -// SKILL.md file. The frontmatter is delimited by --- lines. |
20 | | -func parseManifest(data []byte, name, dir string) (*Skill, error) { |
21 | | - raw, body, splitErr := splitFrontmatter(data) |
| 16 | +// parseManifest extracts YAML frontmatter and markdown |
| 17 | +// body from a SKILL.md file. |
| 18 | +func parseManifest( |
| 19 | + data []byte, name, dir string, |
| 20 | +) (*Skill, error) { |
| 21 | + raw, body, splitErr := parse.SplitFrontmatter( |
| 22 | + data, |
| 23 | + ) |
22 | 24 | if splitErr != nil { |
23 | 25 | return nil, errSkill.Load(name, splitErr) |
24 | 26 | } |
25 | 27 |
|
26 | 28 | sk := &Skill{} |
27 | | - if unmarshalErr := yaml.Unmarshal(raw, sk); unmarshalErr != nil { |
28 | | - return nil, errSkill.InvalidYAML(name, unmarshalErr) |
| 29 | + if unmarshalErr := yaml.Unmarshal( |
| 30 | + raw, sk, |
| 31 | + ); unmarshalErr != nil { |
| 32 | + return nil, errSkill.InvalidYAML( |
| 33 | + name, unmarshalErr, |
| 34 | + ) |
29 | 35 | } |
30 | 36 |
|
31 | 37 | sk.Body = body |
32 | 38 | sk.Dir = dir |
33 | 39 | return sk, nil |
34 | 40 | } |
35 | | - |
36 | | -// splitFrontmatter separates YAML frontmatter from the markdown body. |
37 | | -// Frontmatter must start with a --- line and end with a second --- line. |
38 | | -func splitFrontmatter( |
39 | | - data []byte, |
40 | | -) (frontmatter []byte, body string, err error) { |
41 | | - content := strings.TrimLeft(string(data), token.TrimCR) |
42 | | - |
43 | | - if !strings.HasPrefix(content, token.FrontmatterDelimiter) { |
44 | | - return nil, "", errSkill.MissingOpeningDelimiter() |
45 | | - } |
46 | | - |
47 | | - // Skip the opening delimiter line. |
48 | | - rest := content[len(token.FrontmatterDelimiter):] |
49 | | - rest = strings.TrimPrefix(rest, token.NewlineLF) |
50 | | - |
51 | | - needle := token.NewlineLF + token.FrontmatterDelimiter |
52 | | - idx := strings.Index(rest, needle) |
53 | | - if idx < 0 { |
54 | | - return nil, "", errSkill.MissingClosingDelimiter() |
55 | | - } |
56 | | - |
57 | | - fm := rest[:idx] |
58 | | - |
59 | | - // Skip past the closing delimiter line. |
60 | | - after := rest[idx+1+len(token.FrontmatterDelimiter):] |
61 | | - // Trim exactly one leading newline from the body if present. |
62 | | - after = strings.TrimPrefix(after, token.NewlineLF) |
63 | | - |
64 | | - return []byte(fm), after, nil |
65 | | -} |
0 commit comments