feat: parser de frontmatter YAML para .md de funciones y tipos
Implementa ParseFunctionMD y ParseTypeMD que extraen frontmatter YAML de archivos .md y construyen los structs del registry. Soporta los tres kinds (function, pipeline, component) con sus campos especificos. Extrae automaticamente el ejemplo del body si existe seccion ## Ejemplo. Dependencia: gopkg.in/yaml.v3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,4 +2,7 @@ module fn-registry
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.37
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v1.14.37
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
github.com/mattn/go-sqlite3 v1.14.37 h1:3DOZp4cXis1cUIpCfXLtmlGolNLp2VEqhiB/PARNBIg=
|
||||
github.com/mattn/go-sqlite3 v1.14.37/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// rawFunction mirrors the YAML frontmatter of a function .md file.
|
||||
type rawFunction struct {
|
||||
Name string `yaml:"name"`
|
||||
Kind string `yaml:"kind"`
|
||||
Lang string `yaml:"lang"`
|
||||
Domain string `yaml:"domain"`
|
||||
Version string `yaml:"version"`
|
||||
Purity string `yaml:"purity"`
|
||||
Signature string `yaml:"signature"`
|
||||
Description string `yaml:"description"`
|
||||
Tags []string `yaml:"tags"`
|
||||
UsesFunctions []string `yaml:"uses_functions"`
|
||||
UsesTypes []string `yaml:"uses_types"`
|
||||
Returns []string `yaml:"returns"`
|
||||
ReturnsOptional bool `yaml:"returns_optional"`
|
||||
ErrorType string `yaml:"error_type"`
|
||||
Imports []string `yaml:"imports"`
|
||||
Tested bool `yaml:"tested"`
|
||||
Tests []string `yaml:"tests"`
|
||||
TestFilePath string `yaml:"test_file_path"`
|
||||
FilePath string `yaml:"file_path"`
|
||||
|
||||
// Component fields
|
||||
Props []PropDef `yaml:"props"`
|
||||
Emits []string `yaml:"emits"`
|
||||
HasState *bool `yaml:"has_state"`
|
||||
Framework string `yaml:"framework"`
|
||||
Variant []string `yaml:"variant"`
|
||||
}
|
||||
|
||||
// rawType mirrors the YAML frontmatter of a type .md file.
|
||||
type rawType struct {
|
||||
Name string `yaml:"name"`
|
||||
Lang string `yaml:"lang"`
|
||||
Domain string `yaml:"domain"`
|
||||
Version string `yaml:"version"`
|
||||
Algebraic string `yaml:"algebraic"`
|
||||
Definition string `yaml:"definition"`
|
||||
Description string `yaml:"description"`
|
||||
Tags []string `yaml:"tags"`
|
||||
UsesTypes []string `yaml:"uses_types"`
|
||||
FilePath string `yaml:"file_path"`
|
||||
}
|
||||
|
||||
// extractFrontmatter splits a .md file into YAML frontmatter and body.
|
||||
func extractFrontmatter(data []byte) ([]byte, []byte, error) {
|
||||
content := data
|
||||
if !bytes.HasPrefix(content, []byte("---\n")) && !bytes.HasPrefix(content, []byte("---\r\n")) {
|
||||
return nil, nil, fmt.Errorf("missing opening --- in frontmatter")
|
||||
}
|
||||
|
||||
// Skip opening ---
|
||||
rest := content[4:]
|
||||
idx := bytes.Index(rest, []byte("\n---"))
|
||||
if idx < 0 {
|
||||
return nil, nil, fmt.Errorf("missing closing --- in frontmatter")
|
||||
}
|
||||
|
||||
fm := rest[:idx]
|
||||
body := rest[idx+4:] // skip \n---
|
||||
return fm, body, nil
|
||||
}
|
||||
|
||||
// ParseFunctionMD parses a function .md file into a Function.
|
||||
func ParseFunctionMD(path string) (*Function, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", path, err)
|
||||
}
|
||||
|
||||
fm, body, err := extractFrontmatter(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %w", path, err)
|
||||
}
|
||||
|
||||
var raw rawFunction
|
||||
if err := yaml.Unmarshal(fm, &raw); err != nil {
|
||||
return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
|
||||
}
|
||||
|
||||
if raw.Name == "" {
|
||||
return nil, fmt.Errorf("%s: name is required", path)
|
||||
}
|
||||
if raw.Kind == "" {
|
||||
return nil, fmt.Errorf("%s: kind is required", path)
|
||||
}
|
||||
if raw.Description == "" {
|
||||
return nil, fmt.Errorf("%s: description is required", path)
|
||||
}
|
||||
|
||||
example := extractExample(body)
|
||||
|
||||
f := &Function{
|
||||
ID: GenerateID(raw.Name, raw.Lang, raw.Domain),
|
||||
Name: raw.Name,
|
||||
Kind: Kind(raw.Kind),
|
||||
Lang: raw.Lang,
|
||||
Domain: raw.Domain,
|
||||
Version: raw.Version,
|
||||
Purity: Purity(raw.Purity),
|
||||
Signature: raw.Signature,
|
||||
Description: raw.Description,
|
||||
Tags: raw.Tags,
|
||||
UsesFunctions: raw.UsesFunctions,
|
||||
UsesTypes: raw.UsesTypes,
|
||||
Returns: raw.Returns,
|
||||
ReturnsOptional: raw.ReturnsOptional,
|
||||
ErrorType: raw.ErrorType,
|
||||
Imports: raw.Imports,
|
||||
Tested: raw.Tested,
|
||||
Tests: raw.Tests,
|
||||
TestFilePath: raw.TestFilePath,
|
||||
FilePath: raw.FilePath,
|
||||
Props: raw.Props,
|
||||
Emits: raw.Emits,
|
||||
HasState: raw.HasState,
|
||||
Framework: raw.Framework,
|
||||
Variant: raw.Variant,
|
||||
}
|
||||
|
||||
if example != "" && f.Example == "" {
|
||||
f.Example = example
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseTypeMD parses a type .md file into a Type.
|
||||
func ParseTypeMD(path string) (*Type, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", path, err)
|
||||
}
|
||||
|
||||
fm, _, err := extractFrontmatter(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %w", path, err)
|
||||
}
|
||||
|
||||
var raw rawType
|
||||
if err := yaml.Unmarshal(fm, &raw); err != nil {
|
||||
return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
|
||||
}
|
||||
|
||||
if raw.Name == "" {
|
||||
return nil, fmt.Errorf("%s: name is required", path)
|
||||
}
|
||||
if raw.Description == "" {
|
||||
return nil, fmt.Errorf("%s: description is required", path)
|
||||
}
|
||||
|
||||
t := &Type{
|
||||
ID: GenerateID(raw.Name, raw.Lang, raw.Domain),
|
||||
Name: raw.Name,
|
||||
Lang: raw.Lang,
|
||||
Domain: raw.Domain,
|
||||
Version: raw.Version,
|
||||
Algebraic: Algebraic(raw.Algebraic),
|
||||
Definition: strings.TrimSpace(raw.Definition),
|
||||
Description: raw.Description,
|
||||
Tags: raw.Tags,
|
||||
UsesTypes: raw.UsesTypes,
|
||||
FilePath: raw.FilePath,
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// extractExample pulls the first code block after an "## Ejemplo" heading.
|
||||
func extractExample(body []byte) string {
|
||||
lines := strings.Split(string(body), "\n")
|
||||
inExample := false
|
||||
inCode := false
|
||||
var code []string
|
||||
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "## Ejemplo") {
|
||||
inExample = true
|
||||
continue
|
||||
}
|
||||
if inExample && !inCode && strings.HasPrefix(trimmed, "```") {
|
||||
inCode = true
|
||||
continue
|
||||
}
|
||||
if inCode {
|
||||
if strings.HasPrefix(trimmed, "```") {
|
||||
return strings.Join(code, "\n")
|
||||
}
|
||||
code = append(code, line)
|
||||
}
|
||||
if inExample && !inCode && strings.HasPrefix(trimmed, "##") {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user