| package parser |
|
|
| import ( |
| "fmt" |
| "os" |
| "strings" |
|
|
| "huggingface.co/turenlabs/Vigil/source/pkg/types" |
| ) |
|
|
| |
| 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) |
| } |
|
|
| |
| 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 |
| } |
|
|
| |
| |
| |
| func splitFrontmatter(content string) (frontmatter, body string, bodyStartLine int, err error) { |
| trimmed := strings.TrimSpace(content) |
| if !strings.HasPrefix(trimmed, "---") { |
| return "", content, 1, nil |
| } |
|
|
| |
| 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:] |
| } |
|
|
| |
| if strings.HasPrefix(rest, "---") { |
| body = strings.TrimLeft(rest[3:], " \t\r\n") |
| |
| 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") |
|
|
| |
| fmLines := strings.Count(frontmatter, "\n") + 1 |
| bodyStartLine = leadingLines + 1 + fmLines + 1 |
|
|
| |
| 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 |
| } |
|
|
| |
| |
| 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") |
|
|
| |
| 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 |
| } |
|
|
| |
| 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": |
| |
| } |
| } |
| } |
| } |
|
|