bootstrap: iniciar bitácora operativa

This commit is contained in:
2026-08-13 00:43:15 -06:00
commit 1ecfb022b1
85 changed files with 7967 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
package clusterlog
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func parseMarkdown(path string) (PageMeta, error) {
data, err := os.ReadFile(path)
if err != nil {
return PageMeta{}, err
}
text := strings.ReplaceAll(string(data), "\r\n", "\n")
if !strings.HasPrefix(text, "+++\n") {
return PageMeta{}, fmt.Errorf("%s no tiene front matter TOML delimitado por +++", path)
}
rest := strings.TrimPrefix(text, "+++\n")
idx := strings.Index(rest, "\n+++\n")
if idx < 0 {
if strings.HasSuffix(rest, "\n+++") {
idx = len(rest) - len("\n+++")
} else {
return PageMeta{}, fmt.Errorf("%s tiene front matter sin cierre +++", path)
}
}
rawFront := rest[:idx]
bodyStart := idx + len("\n+++\n")
body := ""
if bodyStart <= len(rest) {
body = rest[bodyStart:]
}
meta := PageMeta{
Path: path,
Body: body,
Root: map[string]any{},
Taxonomies: map[string][]string{},
Extra: map[string]any{},
RawFront: rawFront,
ContentHash: bytesSHA256(data),
}
section := "root"
scanner := bufio.NewScanner(strings.NewReader(rawFront))
scanner.Buffer(make([]byte, 1024), 1024*1024)
lineNo := 0
for scanner.Scan() {
lineNo++
line := strings.TrimSpace(stripTOMLComment(scanner.Text()))
if line == "" {
continue
}
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
section = strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(line, "["), "]"))
continue
}
key, raw, ok := strings.Cut(line, "=")
if !ok {
continue
}
key = strings.TrimSpace(key)
raw = strings.TrimSpace(raw)
value, err := parseTOMLValue(raw)
if err != nil {
return PageMeta{}, fmt.Errorf("%s:%d: %w", path, lineNo, err)
}
switch section {
case "root":
meta.Root[key] = value
case "taxonomies":
values, ok := value.([]string)
if ok {
meta.Taxonomies[key] = values
}
case "extra":
meta.Extra[key] = value
default:
// El parser intencionalmente ignora secciones no usadas por clusterlog.
}
}
if err := scanner.Err(); err != nil {
return PageMeta{}, err
}
return meta, nil
}
func stripTOMLComment(line string) string {
inString := false
escaped := false
for i, r := range line {
if escaped {
escaped = false
continue
}
if r == '\\' && inString {
escaped = true
continue
}
if r == '"' {
inString = !inString
continue
}
if r == '#' && !inString {
return line[:i]
}
}
return line
}
func parseTOMLValue(raw string) (any, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", nil
}
if strings.HasPrefix(raw, "\"") {
value, err := strconv.Unquote(raw)
if err != nil {
return nil, fmt.Errorf("cadena TOML inválida: %w", err)
}
return value, nil
}
if raw == "true" {
return true, nil
}
if raw == "false" {
return false, nil
}
if strings.HasPrefix(raw, "[") && strings.HasSuffix(raw, "]") {
return parseTOMLStringArray(raw)
}
if n, err := strconv.Atoi(raw); err == nil {
return n, nil
}
// Fechas TOML y otros escalares simples se conservan como texto.
return raw, nil
}
func parseTOMLStringArray(raw string) ([]string, error) {
content := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(raw, "["), "]"))
if content == "" {
return []string{}, nil
}
var values []string
var current strings.Builder
inString := false
escaped := false
flush := func() error {
item := strings.TrimSpace(current.String())
current.Reset()
if item == "" {
return nil
}
value, err := strconv.Unquote(item)
if err != nil {
return fmt.Errorf("arreglo TOML inválido: %w", err)
}
values = append(values, value)
return nil
}
for _, r := range content {
if escaped {
current.WriteRune(r)
escaped = false
continue
}
if r == '\\' && inString {
current.WriteRune(r)
escaped = true
continue
}
if r == '"' {
inString = !inString
current.WriteRune(r)
continue
}
if r == ',' && !inString {
if err := flush(); err != nil {
return nil, err
}
continue
}
current.WriteRune(r)
}
if inString {
return nil, fmt.Errorf("arreglo TOML con cadena sin cerrar")
}
if err := flush(); err != nil {
return nil, err
}
return values, nil
}
func pageSlug(meta PageMeta) string {
if value := meta.String("slug"); value != "" {
return value
}
return slugify(strings.TrimSuffix(filepathBase(meta.Path), ".md"))
}
func filepathBase(path string) string {
path = strings.ReplaceAll(path, "\\", "/")
if idx := strings.LastIndex(path, "/"); idx >= 0 {
return path[idx+1:]
}
return path
}