ONNX
security
malware-detection
Vigil / source /pkg /parser /parser.go
turentomer's picture
Make published source self-contained
a2a3348 verified
Raw
History Blame Contribute Delete
3.77 kB
package parser
import (
"fmt"
"os"
"strings"
"huggingface.co/turenlabs/Vigil/source/pkg/types"
)
// Parse reads a Claude Code skill markdown file and extracts frontmatter + body.
func Parse(filePath string) (*types.SkillFile, error) {
raw, err := os.ReadFile(filePath)
if err != nil {
return nil, fmt.Errorf("reading skill file: %w", err)
}
return ParseBytes(raw, filePath)
}
// ParseBytes parses skill file content from bytes.
func ParseBytes(raw []byte, filePath string) (*types.SkillFile, error) {
content := string(raw)
frontmatter, body, bodyStartLine, err := splitFrontmatter(content)
if err != nil {
return nil, fmt.Errorf("parsing frontmatter: %w", err)
}
sf := &types.SkillFile{
RawContent: content,
FilePath: filePath,
Body: body,
BodyStartLine: bodyStartLine,
}
if frontmatter != "" {
parseFrontmatterFields(frontmatter, sf)
}
return sf, nil
}
// splitFrontmatter splits a markdown file into YAML frontmatter and body.
// Frontmatter is delimited by --- on the first line and a closing ---.
// bodyStartLine is the 1-based line number where the body begins in the original content.
func splitFrontmatter(content string) (frontmatter, body string, bodyStartLine int, err error) {
trimmed := strings.TrimSpace(content)
if !strings.HasPrefix(trimmed, "---") {
return "", content, 1, nil
}
// Count leading blank lines that TrimSpace removed
leadingLines := 0
for i, ch := range content {
if ch == '\n' {
leadingLines++
} else if ch != ' ' && ch != '\t' && ch != '\r' {
_ = i
break
}
}
rest := trimmed[3:]
rest = strings.TrimLeft(rest, " \t")
if len(rest) > 0 && rest[0] == '\n' {
rest = rest[1:]
} else if len(rest) > 1 && rest[0] == '\r' && rest[1] == '\n' {
rest = rest[2:]
}
// Handle empty frontmatter: starts with closing ---
if strings.HasPrefix(rest, "---") {
body = strings.TrimLeft(rest[3:], " \t\r\n")
// Opening --- (1) + closing --- (1) + leadingLines
bodyStartLine = leadingLines + 3
return "", body, bodyStartLine, nil
}
idx := strings.Index(rest, "\n---")
if idx == -1 {
return "", content, 1, nil
}
frontmatter = rest[:idx]
body = strings.TrimLeft(rest[idx+4:], " \t\r\n")
// Count lines: leading blank + opening --- line + frontmatter lines + closing --- line
fmLines := strings.Count(frontmatter, "\n") + 1
bodyStartLine = leadingLines + 1 + fmLines + 1 // opening--- + fm lines + closing---
// Account for blank lines trimmed between closing --- and body start
afterClosing := rest[idx+4:]
for _, ch := range afterClosing {
if ch == '\n' {
bodyStartLine++
} else if ch != ' ' && ch != '\t' && ch != '\r' {
break
}
}
return frontmatter, body, bodyStartLine, nil
}
// parseFrontmatterFields extracts known fields from simple YAML frontmatter.
// Handles flat key: value pairs and simple list items (- value).
func parseFrontmatterFields(fm string, sf *types.SkillFile) {
lines := strings.Split(fm, "\n")
var currentKey string
for _, line := range lines {
trimmed := strings.TrimRight(line, " \t\r")
// List item under current key
if strings.HasPrefix(trimmed, " - ") || strings.HasPrefix(trimmed, " - ") {
val := strings.TrimSpace(trimmed[strings.Index(trimmed, "- ")+2:])
if currentKey == "triggers" {
sf.Triggers = append(sf.Triggers, val)
}
continue
}
// Key: value pair
colonIdx := strings.Index(trimmed, ":")
if colonIdx > 0 {
key := strings.TrimSpace(trimmed[:colonIdx])
val := strings.TrimSpace(trimmed[colonIdx+1:])
currentKey = key
switch key {
case "name":
sf.Name = val
case "description":
sf.Description = val
case "type":
sf.Type = val
case "triggers":
// Value comes on subsequent lines as list items
}
}
}
}