|
|
|
@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
/* ——————————————————————————————————————————————————————————————————————————
|
|
|
|
|
|
|
|
Proyecto FWS
|
|
|
|
|
|
|
|
—————————————————————————————————————————————————————————————————————————————
|
|
|
|
|
|
|
|
Fichero tree_writer.go
|
|
|
|
|
|
|
|
Package errs
|
|
|
|
|
|
|
|
Autor Juan V. Navarro juanvnl@activething.com
|
|
|
|
|
|
|
|
Creado 25/01/2026
|
|
|
|
|
|
|
|
—————————————————————————————————————————————————————————————————————————————
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LICENSES AND TERMS OF USE
|
|
|
|
|
|
|
|
-------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This software is licensed under the Elastic License v2.0 (the "License").
|
|
|
|
|
|
|
|
For full terms and additional information regarding permitted and prohibited
|
|
|
|
|
|
|
|
uses, please visit:
|
|
|
|
|
|
|
|
https://activething.com/ATGO/licenses
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You may use, copy, modify, and redistribute this software internally within
|
|
|
|
|
|
|
|
your organization for any purpose, including research, development, and
|
|
|
|
|
|
|
|
testing, subject to the terms of this License.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You may NOT, however, use, provide, distribute, or make this software
|
|
|
|
|
|
|
|
available to any third party as part of a hosted service, SaaS offering, or
|
|
|
|
|
|
|
|
commercial product without first obtaining a commercial license from
|
|
|
|
|
|
|
|
Active Thing.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
You may combine this software with other code, provided that such
|
|
|
|
|
|
|
|
combination does not circumvent the restrictions of this License.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
|
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
|
|
|
FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. In no event shall
|
|
|
|
|
|
|
|
the authors or copyright holders be liable for any claim, damages, or other
|
|
|
|
|
|
|
|
liability arising from the use of this software.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
—————————————————————————————————————————————————————————————————————————————
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Web : activething.com | activething.com/FWS
|
|
|
|
|
|
|
|
git : g.activething.com | github.com/activething/FWS
|
|
|
|
|
|
|
|
Correo : dev@activething.com
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
—————————————————————————————————————————————————————————————————————————————
|
|
|
|
|
|
|
|
No deseo caminar sobre el agua", dijo Siddhartha.
|
|
|
|
|
|
|
|
Que los antiguos chamanes se contenten con tales habilidades.
|
|
|
|
|
|
|
|
—— Hermann Hesse, Siddhartha
|
|
|
|
|
|
|
|
—————————————————————————————————————————————————————————————————————————————
|
|
|
|
|
|
|
|
Copyright (c) 2026 Active Thing
|
|
|
|
|
|
|
|
————————————————————————————————————————————————————————————————————————————— */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package errs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
|
|
"bufio"
|
|
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type (
|
|
|
|
|
|
|
|
TreeWriter interface {
|
|
|
|
|
|
|
|
Write (t *Tree, w io.Writer) error
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WriteTreeFnc func (t *Tree, w io.Writer) error
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (f WriteTreeFnc) Write(t *Tree, w io.Writer) error { return f(t, w) }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Optimizamos shortenPath para evitar split/join y asignaciones innecesarias.
|
|
|
|
|
|
|
|
func shortenPathOptimized(path string) string {
|
|
|
|
|
|
|
|
last := strings.LastIndexByte(path, '/')
|
|
|
|
|
|
|
|
if last == -1 {
|
|
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Buscamos el penúltimo separador
|
|
|
|
|
|
|
|
secondLast := strings.LastIndexByte(path[:last], '/')
|
|
|
|
|
|
|
|
if secondLast == -1 {
|
|
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Devolvemos una rodaja de la cadena original precedida por .../
|
|
|
|
|
|
|
|
return ".../" + path[secondLast+1:]
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CTreeWriter imprime la jerarquía de errores de manera visual y semántica.
|
|
|
|
|
|
|
|
// Iconos:
|
|
|
|
|
|
|
|
// ❌ Error
|
|
|
|
|
|
|
|
// ℹ️ Meta / atributo
|
|
|
|
|
|
|
|
// ⚠️ Stack
|
|
|
|
|
|
|
|
// Conectores para causas:
|
|
|
|
|
|
|
|
// ├─▶ (child)
|
|
|
|
|
|
|
|
// └─▶ (last)
|
|
|
|
|
|
|
|
func CTreeWriter(t *Tree, w io.Writer) error {
|
|
|
|
|
|
|
|
if t == nil || t.root == nil {
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Usamos bufio para agrupar escrituras pequeñas y evitar syscalls constantes
|
|
|
|
|
|
|
|
bw := bufio.NewWriter(w)
|
|
|
|
|
|
|
|
defer bw.Flush()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Buffer para gestionar la indentación sin crear constantes strings
|
|
|
|
|
|
|
|
// "│ " o " " ocupan 4 bytes por nivel. 32 niveles = 128 bytes.
|
|
|
|
|
|
|
|
indentBuf := make([]byte, 0, 128)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var walk func(n *TreeNode, hasSibling bool, isRoot bool) error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
walk = func(n *TreeNode, hasSibling bool, isRoot bool) error {
|
|
|
|
|
|
|
|
// 1. DIBUJAR NODO
|
|
|
|
|
|
|
|
if isRoot {
|
|
|
|
|
|
|
|
bw.WriteString("❌ ")
|
|
|
|
|
|
|
|
bw.WriteString(n.Err.Error())
|
|
|
|
|
|
|
|
bw.WriteByte('\n')
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
bw.Write(indentBuf)
|
|
|
|
|
|
|
|
if hasSibling {
|
|
|
|
|
|
|
|
bw.WriteString("├─▶ ❌ ")
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
bw.WriteString("└─▶ ❌ ")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bw.WriteString(n.Err.Error())
|
|
|
|
|
|
|
|
bw.WriteByte('\n')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 2. CALCULAR PREFIJO PARA ATRIBUTOS (Metas y Stacks)
|
|
|
|
|
|
|
|
// El prefijo depende de si el nodo actual tiene hermanos o no
|
|
|
|
|
|
|
|
oldLen := len(indentBuf)
|
|
|
|
|
|
|
|
if !isRoot {
|
|
|
|
|
|
|
|
if hasSibling {
|
|
|
|
|
|
|
|
indentBuf = append(indentBuf, "│ "...)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
indentBuf = append(indentBuf, " "...)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Prefijo base para metas: indentación actual + espacio del icono
|
|
|
|
|
|
|
|
attrPrefix := " " // Espacio equivalente al conector "├─▶ "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 3. RENDERIZAR METAS
|
|
|
|
|
|
|
|
for _, m := range n.Metas {
|
|
|
|
|
|
|
|
bw.Write(indentBuf)
|
|
|
|
|
|
|
|
bw.WriteString(attrPrefix)
|
|
|
|
|
|
|
|
bw.WriteString("ℹ️ ")
|
|
|
|
|
|
|
|
bw.WriteString(m.Name())
|
|
|
|
|
|
|
|
bw.WriteString(" = ")
|
|
|
|
|
|
|
|
fmt.Fprint(bw, m.Value()) // Solo usamos fmt para valores desconocidos
|
|
|
|
|
|
|
|
bw.WriteByte('\n')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 4. RENDERIZAR STACK
|
|
|
|
|
|
|
|
if len(n.Stack) > 0 {
|
|
|
|
|
|
|
|
frames := n.Stack.Frames()
|
|
|
|
|
|
|
|
bw.Write(indentBuf)
|
|
|
|
|
|
|
|
bw.WriteString(attrPrefix)
|
|
|
|
|
|
|
|
bw.WriteString("⚠️ stack (")
|
|
|
|
|
|
|
|
bw.WriteString(strconv.Itoa(len(frames)))
|
|
|
|
|
|
|
|
bw.WriteString(" frames)\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, f := range frames {
|
|
|
|
|
|
|
|
bw.Write(indentBuf)
|
|
|
|
|
|
|
|
bw.WriteString(attrPrefix)
|
|
|
|
|
|
|
|
bw.WriteString(" ")
|
|
|
|
|
|
|
|
bw.WriteString(shortenPathOptimized(f.File))
|
|
|
|
|
|
|
|
bw.WriteByte(':')
|
|
|
|
|
|
|
|
bw.WriteString(strconv.Itoa(f.LineNumber))
|
|
|
|
|
|
|
|
bw.WriteByte(' ')
|
|
|
|
|
|
|
|
bw.WriteString(f.FuncName)
|
|
|
|
|
|
|
|
bw.WriteByte('\n')
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 5. RECURSIÓN PARA CAUSAS
|
|
|
|
|
|
|
|
for i, c := range n.Causes {
|
|
|
|
|
|
|
|
isLast := i == len(n.Causes)-1
|
|
|
|
|
|
|
|
if err := walk(c, !isLast, false); err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Restaurar el buffer de indentación al volver de la recursión
|
|
|
|
|
|
|
|
indentBuf = indentBuf[:oldLen]
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return walk(t.root, false, true)
|
|
|
|
|
|
|
|
}
|