feat: campos documentation, notes y code en registry
Añade campos documentation, notes y code a functions y types. El parser extrae el contenido del .md y el código fuente del archivo referenciado en file_path. El indexer los almacena en SQLite y los incluye en FTS5 para búsqueda sobre código y documentación. Nueva migración 003_documentation.sql para añadir las columnas.
This commit is contained in:
+93
-37
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -73,7 +74,8 @@ func extractFrontmatter(data []byte) ([]byte, []byte, error) {
|
||||
}
|
||||
|
||||
// ParseFunctionMD parses a function .md file into a Function.
|
||||
func ParseFunctionMD(path string) (*Function, error) {
|
||||
// root is the registry root directory, used to resolve file_path for code reading.
|
||||
func ParseFunctionMD(path string, root string) (*Function, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", path, err)
|
||||
@@ -99,7 +101,7 @@ func ParseFunctionMD(path string) (*Function, error) {
|
||||
return nil, fmt.Errorf("%s: description is required", path)
|
||||
}
|
||||
|
||||
example := extractExample(body)
|
||||
sections := extractSections(body)
|
||||
|
||||
f := &Function{
|
||||
ID: GenerateID(raw.Name, raw.Lang, raw.Domain),
|
||||
@@ -118,6 +120,9 @@ func ParseFunctionMD(path string) (*Function, error) {
|
||||
ReturnsOptional: raw.ReturnsOptional,
|
||||
ErrorType: raw.ErrorType,
|
||||
Imports: raw.Imports,
|
||||
Example: sections.example,
|
||||
Notes: sections.notes,
|
||||
Documentation: sections.documentation,
|
||||
Tested: raw.Tested,
|
||||
Tests: raw.Tests,
|
||||
TestFilePath: raw.TestFilePath,
|
||||
@@ -129,21 +134,25 @@ func ParseFunctionMD(path string) (*Function, error) {
|
||||
Variant: raw.Variant,
|
||||
}
|
||||
|
||||
if example != "" && f.Example == "" {
|
||||
f.Example = example
|
||||
if root != "" && raw.FilePath != "" {
|
||||
codePath := filepath.Join(root, raw.FilePath)
|
||||
if codeData, err := os.ReadFile(codePath); err == nil {
|
||||
f.Code = string(codeData)
|
||||
}
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// ParseTypeMD parses a type .md file into a Type.
|
||||
func ParseTypeMD(path string) (*Type, error) {
|
||||
// root is the registry root directory, used to resolve file_path for code reading.
|
||||
func ParseTypeMD(path string, root string) (*Type, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", path, err)
|
||||
}
|
||||
|
||||
fm, _, err := extractFrontmatter(data)
|
||||
fm, body, err := extractFrontmatter(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %w", path, err)
|
||||
}
|
||||
@@ -160,49 +169,96 @@ func ParseTypeMD(path string) (*Type, error) {
|
||||
return nil, fmt.Errorf("%s: description is required", path)
|
||||
}
|
||||
|
||||
sections := extractSections(body)
|
||||
|
||||
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,
|
||||
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,
|
||||
Examples: sections.example,
|
||||
Notes: sections.notes,
|
||||
Documentation: sections.documentation,
|
||||
FilePath: raw.FilePath,
|
||||
}
|
||||
|
||||
if root != "" && raw.FilePath != "" {
|
||||
codePath := filepath.Join(root, raw.FilePath)
|
||||
if codeData, err := os.ReadFile(codePath); err == nil {
|
||||
t.Code = string(codeData)
|
||||
}
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// extractExample pulls the first code block after an "## Ejemplo" heading.
|
||||
func extractExample(body []byte) string {
|
||||
// bodySections holds the extracted sections from a .md body.
|
||||
type bodySections struct {
|
||||
example string // content under ## Ejemplo
|
||||
notes string // content under ## Notas
|
||||
documentation string // everything else
|
||||
}
|
||||
|
||||
// extractSections splits the markdown body into named sections.
|
||||
// Known sections (## Ejemplo, ## Notas) are extracted separately.
|
||||
// All other content (including unknown ## headings) goes into documentation.
|
||||
func extractSections(body []byte) bodySections {
|
||||
lines := strings.Split(string(body), "\n")
|
||||
inExample := false
|
||||
inCode := false
|
||||
var code []string
|
||||
var s bodySections
|
||||
|
||||
type section struct {
|
||||
name string
|
||||
lines []string
|
||||
}
|
||||
|
||||
var current *section
|
||||
var sections []section
|
||||
|
||||
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")
|
||||
if strings.HasPrefix(trimmed, "## ") {
|
||||
if current != nil {
|
||||
sections = append(sections, *current)
|
||||
}
|
||||
code = append(code, line)
|
||||
current = §ion{name: trimmed}
|
||||
continue
|
||||
}
|
||||
if inExample && !inCode && strings.HasPrefix(trimmed, "##") {
|
||||
break
|
||||
if current != nil {
|
||||
current.lines = append(current.lines, line)
|
||||
} else {
|
||||
// Content before any ## heading goes to documentation
|
||||
sections = append(sections, section{name: "_preamble", lines: []string{line}})
|
||||
}
|
||||
}
|
||||
return ""
|
||||
if current != nil {
|
||||
sections = append(sections, *current)
|
||||
}
|
||||
|
||||
var docParts []string
|
||||
for _, sec := range sections {
|
||||
content := strings.TrimSpace(strings.Join(sec.lines, "\n"))
|
||||
if content == "" && sec.name == "_preamble" {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(sec.name, "## Ejemplo"):
|
||||
s.example = content
|
||||
case strings.HasPrefix(sec.name, "## Notas"):
|
||||
s.notes = content
|
||||
case sec.name == "_preamble":
|
||||
docParts = append(docParts, content)
|
||||
default:
|
||||
// Unknown sections go to documentation with their heading
|
||||
docParts = append(docParts, sec.name+"\n\n"+content)
|
||||
}
|
||||
}
|
||||
s.documentation = strings.TrimSpace(strings.Join(docParts, "\n\n"))
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user