master
dev 1 year ago
parent ba53cd349b
commit 5030d4b474

@ -98,6 +98,7 @@ const (
type (
Capabilities uint16
@ -107,6 +108,17 @@ type (
// Métodos para verificar capacidades de color
func (c Capabilities) CanColorType (ct ColorType) bool {
switch ct {
case ColorTypeNone : return true
case ColorTypeBasic : return c&CanBasicColor != 0
case ColorTypeIndex : return c&Can256Color != 0
case ColorTypeTrue : return c&CanTrueColor != 0
default:
return false
}
}
func (c Capabilities) CanBasicColor () bool { return c&CanBasicColor != 0 }
func (c Capabilities) Can256Color () bool { return c&Can256Color != 0 }
func (c Capabilities) CanTrueColor () bool { return c&CanTrueColor != 0 }
@ -148,3 +160,4 @@ func (c Capabilities) String () string {
return strings.Join(cs, "|")
}

@ -1,10 +1,10 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto WIAWAIW
—————————————————————————————————————————————————————————————————————————————
Fichero z_itoa_test.go
Fichero capabilities_detect.go
Package ansi2
Autor Juan V. Navarro juanvnl@activething.com
Creado 28/08/2025
Creado 29/08/2025
—————————————————————————————————————————————————————————————————————————————
LICENSES AND TERMS OF USE
@ -47,32 +47,94 @@
Copyright (c) 2025 Active Thing
————————————————————————————————————————————————————————————————————————————— */
//go:build !windows
// +build !windows
package ansi2
import "testing"
func TestAppendItoa(t *testing.T) {
tests := []struct {
num uint8
expected string
}{
{0, "0"},
{5, "5"},
{9, "9"},
{10, "10"},
{42, "42"},
{99, "99"},
{100, "100"},
{123, "123"},
{255, "255"},
import (
"os"
"strings"
)
// DetectTerminalCapabilities detecta capacidades ANSI para un descriptor de salida en sistemas tipo Unix.
// - f: archivo de salida (por ejemplo os.Stdout o os.Stderr)
// - No usa procesos externos, se basa en heurísticas y en si f parece un dispositivo de caracteres.
func DetectTerminalCapabilities(f *os.File) Capabilities {
if f == nil {
f = os.Stdout
}
// Respeta NO_COLOR
if _, ok := os.LookupEnv("NO_COLOR"); ok {
return CanNone
}
// Si el descriptor no es un dispositivo de caracteres (p. ej. redirigido a archivo/pipe), no hay color.
if !isCharDevice(f) {
return CanNone
}
// FORCE_COLOR fuerza todas las capacidades
if _, ok := os.LookupEnv("FORCE_COLOR"); ok {
return forcedAllCapabilities()
}
for _, tt := range tests {
t.Run("num="+tt.expected, func(t *testing.T) {
buffer := AppendItoa([]byte{}, tt.num)
if got := string(buffer); got != tt.expected {
t.Errorf("AppendItoa(%d) = %q, want %q", tt.num, got, tt.expected)
}
})
term := strings.ToLower(os.Getenv("TERM"))
colorterm := strings.ToLower(os.Getenv("COLORTERM"))
termProgram := strings.ToLower(os.Getenv("TERM_PROGRAM"))
var caps Capabilities
// --- Colores ---
switch {
case strings.Contains(colorterm, "truecolor") || strings.Contains(colorterm, "24bit"):
caps |= CanTrueColor | Can256Color | CanBasicColor
case strings.Contains(term, "256color") || strings.Contains(term, "256-colors") || strings.Contains(term, "256-colour"):
caps |= Can256Color | CanBasicColor
case term != "" && strings.Contains(term, "color"):
caps |= CanBasicColor
}
}
// Heurísticas: terminales modernos que suelen soportar estilos avanzados y a menudo truecolor
if colorterm != "" ||
strings.Contains(term, "xterm") ||
strings.Contains(term, "xterm-256color") ||
strings.Contains(term, "screen") ||
strings.Contains(term, "tmux") ||
strings.Contains(term, "rxvt") ||
strings.Contains(term, "konsole") ||
strings.Contains(termProgram, "iterm") ||
strings.Contains(termProgram, "wezterm") ||
strings.Contains(termProgram, "alacritty") ||
strings.Contains(termProgram, "kitty") ||
strings.Contains(termProgram, "vscode") ||
strings.Contains(termProgram, "gnome") {
caps |= allStyleCapabilities()
}
// TMUX suele preservar capacidades, aseguramos al menos basic color si TERM sugiere color
if os.Getenv("TMUX") != "" {
if caps&CanBasicColor == 0 {
caps |= CanBasicColor
}
caps |= CanAllExtended
}
return caps
}
// isCharDevice intenta determinar si f es un dispositivo de caracteres (tty).
// Es una comprobación portable usando FileInfo.Mode & os.ModeCharDevice, que funciona en
// la mayoría de sistemas Unix-like de forma simple y sin dependencias externas.
func isCharDevice(f *os.File) bool {
if f == nil {
return false
}
fi, err := f.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}

@ -0,0 +1,150 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto WIAWAIW
—————————————————————————————————————————————————————————————————————————————
Fichero Canabilities_detect_windows.go
Package ansi2
Autor Juan V. Navarro juanvnl@activething.com
Creado 29/08/2025
—————————————————————————————————————————————————————————————————————————————
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/wiawiaw/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 | wiawiaw.com
git : g.activething.com | github.com/activething
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) 2025 Active Thing
————————————————————————————————————————————————————————————————————————————— */
//go:build windows
// +build windows
package ansi2
import (
"os"
"strings"
"syscall"
"unsafe"
)
// flags Windows console
const (
enablePOutput = 0x0001
enableVTerminal = 0x0004
)
// DetectTerminalCapabilities detecta capacidades ANSI para un descriptor de salida en Windows.
// - Intenta habilitar Virtual Terminal Processing para el handle dado (sin lanzar procesos externos).
// - Si el handle no es consola o no se puede habilitar, es conservador.
func DetectTerminalCapabilities(f *os.File) Capabilities {
if f == nil {
f = os.Stdout
}
// Respeta NO_COLOR
if _, ok := os.LookupEnv("NO_COLOR"); ok {
return CanNone
}
// FORCE_COLOR fuerza todas las capacidades
if _, ok := os.LookupEnv("FORCE_COLOR"); ok {
return CanAll
}
handle := f.Fd()
// Si no es consola (GetConsoleMode falla), entonces está redirigido -> sin color.
if !isConsole(handle) {
// Sin consola: pero si WT_SESSION / TERM_PROGRAM indican emuladores remotos, forzamos capacidades
if os.Getenv("WT_SESSION") != "" || strings.Contains(strings.ToLower(os.Getenv("TERM_PROGRAM")), "vscode") {
return CanAll
}
return CanNone
}
// Intentar habilitar Virtual Terminal Processing para ese handle.
enabled := enableVTProcessing(handle)
// Si logramos habilitar VT, asumimos soporte de estilos + colores básicos.
if enabled {
caps := CanBasicColor | CanAllExtended
colorterm := strings.ToLower(os.Getenv("COLORTERM"))
term := strings.ToLower(os.Getenv("TERM"))
if strings.Contains(colorterm, "truecolor") || strings.Contains(colorterm, "24bit") {
caps |= CanTrueColor | Can256Color
} else if strings.Contains(term, "256color") {
caps |= Can256Color
}
// También si estamos en Windows Terminal o VSCode, damos todo por hecho
if os.Getenv("WT_SESSION") != "" || strings.Contains(strings.ToLower(os.Getenv("TERM_PROGRAM")), "vscode") {
caps |= CanTrueColor | Can256Color
}
return caps
}
// Si no pudimos habilitar VT, devolvemos lo básico si estamos en consola.
// En versiones antiguas de consola sólo colores básicos funcionan.
return CanBasicColor
}
func isConsole(handle uintptr) bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
getConsoleMode := kernel32.NewProc("GetConsoleMode")
var mode uint32
r1, _, _ := getConsoleMode.Call(handle, uintptr(unsafe.Pointer(&mode)))
return r1 != 0
}
// enableVTProcessing intenta activar VT para el handle dado.
// Retorna true si se activó correctamente.
func enableVTProcessing(handle uintptr) bool {
kernel32 := syscall.NewLazyDLL("kernel32.dll")
getConsoleMode := kernel32.NewProc("GetConsoleMode")
setConsoleMode := kernel32.NewProc("SetConsoleMode")
var mode uint32
r1, _, _ := getConsoleMode.Call(handle, uintptr(unsafe.Pointer(&mode)))
if r1 == 0 {
return false
}
newMode := mode | enableVTerminal | enablePOutput
r1, _, _ = setConsoleMode.Call(handle, uintptr(newMode))
return r1 != 0
}

@ -209,3 +209,60 @@ func (c Color) trueColor () (r, g, b uint8) {return uint8((c & maskR) >> shiftR)
// RGBToIndex convierte un color RGB a un índice de 256 colores (aproximación).
func RGBToIndex(r, g, b uint8) uint8 {
// Mapeo al cubo 6x6x6 de la paleta de 256 colores (índices 16-231)
// Escala cada componente RGB a un valor de 0 a 5 (6 niveles)
r6 := (uint32(r) * 5 + 127) / 255 // Escala 0-255 a 0-5
g6 := (uint32(g) * 5 + 127) / 255
b6 := (uint32(b) * 5 + 127) / 255
// Índice: 16 + 36*r6 + 6*g6 + b6
return uint8(16 + 36*r6 + 6*g6 + b6)
}
// IndexToBasic convierte un índice de 256 colores a un color básico (0-15).
func IndexToBasic(index uint8) uint8 {
// Colores básicos (0-15) se devuelven directamente
if index < 16 {
return index
}
// Para índices 16-231 (cubo 6x6x6), aproximamos al color básico más cercano
if index < 232 {
r := (index - 16) / 36
g := ((index - 16) % 36) / 6
b := (index - 16) % 6
// Si algún componente es >= 3, usamos colores brillantes (8-15)
if r >= 3 || g >= 3 || b >= 3 {
// Aproximamos al color básico brillante basado en el componente dominante
if r >= g && r >= b {
return 9 // Rojo brillante
} else if g >= r && g >= b {
return 10 // Verde brillante
} else {
return 12 // Azul brillante
}
}
// Colores no brillantes (0-7)
if r >= g && r >= b {
return 1 // Rojo
} else if g >= r && g >= b {
return 2 // Verde
} else {
return 4 // Azul
}
}
// Índices 232-255 (escala de grises) se mapean a blanco o negro
if index >= 244 {
return 15 // Blanco para tonos claros
}
return 0 // Negro para tonos oscuros
}
// RGBToBasic convierte un color RGB a un color básico (0-15).
func RGBToBasic(r, g, b uint8) uint8 {
// Convierte primero a índice y luego a básico
return IndexToBasic(RGBToIndex(r, g, b))
}

@ -169,9 +169,9 @@ func (s Style) GetFgColorType() ColorType { return ColorType((s & maskFgType) >>
func (s Style) GetBgColorType() ColorType { return ColorType((s & maskBgType) >> shiftBgType) }
func (s Style) HasFgColor() bool { return s.GetFgColorType() != ColorTypeNone }
func (s Style) HasFgColor() bool { return s & maskFgType != 0 }
func (s Style) HasBgColor() bool { return s.GetBgColorType() != ColorTypeNone }
func (s Style) HasBgColor() bool { return s & maskBgType != 0 }
// =========================================================
@ -357,6 +357,9 @@ func (s Style) IndexBgColor(index uint8) Style { return s.Bg(MakeIndexColor(inde
func (s Style) GrayFgColor(gray uint8) Style { return s.Fg(MakeGrayColor(gray)) }
func (s Style) GrayBgColor(gray uint8) Style { return s.Bg(MakeGrayColor(gray)) }
func (s Style) BasicFgColor (colro uint8) Style { return s.Fg(MakeBasicColor(colro)) }
func (s Style) BasicBgColor (colro uint8) Style { return s.Bg(MakeBasicColor(colro)) }
func (s Style) Adapt (caps Capabilities) Style {
@ -364,14 +367,64 @@ func (s Style) Adapt (caps Capabilities) Style {
return 0
}
newStyle := Style(0)
// Adaptar formatos (bits 0–11)
newStyle |= (s & maskTextFormat) & Style(caps&maskTextFormat)
ct := s.GetFgColorType()
if !caps.CanColorType(ct) {
newStyle |= newStyle &^(maskFgType | maskFgData) | s.adaptColor(caps,ct,true)
}
ct = s.GetBgColorType()
if !caps.CanColorType(s.GetBgColorType()) {
newStyle |= newStyle &^(maskBgType | maskBgData) | s.adaptColor(caps,ct,false)
}
return newStyle | (s & maskTextFormat) & Style(caps&maskTextFormat)
}
return s
func (s Style) adaptColor(caps Capabilities, ct ColorType, fg bool) Style {
var (
color Color
maskType, maskData Style
)
if fg {
color = s.GetFg()
maskType = maskFgType
maskData = maskFgData
} else {
color = s.GetBg()
maskType = maskBgType
maskData = maskBgData
}
switch {
case ct == ColorTypeBasic && caps.CanBasicColor():
return s & (maskType | maskData)
case ct == ColorTypeIndex && caps.Can256Color():
return s & (maskType | maskData)
case ct == ColorTypeIndex && caps.CanBasicColor() && !caps.Can256Color():
//basicCl := IndexToBasic(color.indexColor())
if fg {
return Style(0).BasicFgColor(IndexToBasic(color.indexColor()))
}
return Style(0).BasicBgColor(IndexToBasic(color.indexColor()))
case ct == ColorTypeTrue && caps.CanTrueColor():
return s & (maskType | maskData)
case ct == ColorTypeTrue && caps.Can256Color():
//indexCl := RGBToIndex(color.trueColor())
if fg {
return Style(0).IndexFgColor(RGBToIndex(color.trueColor()))
}
return Style(0).IndexBgColor(RGBToIndex(color.trueColor()))
case ct == ColorTypeTrue && caps.CanBasicColor():
//basicCl := RGBToBasic(color.trueColor())
if fg {
return Style(0).BasicFgColor(RGBToBasic(color.trueColor()))
}
return Style(0).BasicBgColor(RGBToBasic(color.trueColor()))
}
return Style(0)
}
/*
func (s Style) Build () AnsiStyle {
hsFg := s & (maskFgType)
hsBg := s & (maskBgType)
@ -395,7 +448,6 @@ func (s Style) Build () AnsiStyle {
cl = cl - 8
bf = append(bf, '9')
}
bf = AppendItoa(bf, cl)
case ColorTypeIndex:
cl := s.GetFg().indexColor()
bf = append(bf, '3', '8', ';', '5', ';')
@ -425,7 +477,6 @@ func (s Style) Build () AnsiStyle {
} else {
bf = append(bf, '1','0','0'+cl - 8)
}
bf = AppendItoa(bf, cl)
case ColorTypeIndex:
cl := s.GetBg().indexColor()
bf = append(bf, '4', '8', ';', '5', ';')
@ -456,11 +507,104 @@ func (s Style) Build () AnsiStyle {
}
}
if !hs {
return NoStyle
}
return AnsiStyle(bf)
return AnsiStyle(append(bf,ansiCodeEndStyle))
}
*/
// En ansi-all.gos
func (s Style) Build() AnsiStyle {
hasFg := s & maskFgType
hasBg := s & maskBgType
hasFm := s & maskTextFormat
if hasFg|hasBg|hasFm == 0 {
return NoStyle
}
bf := make([]byte, 0, defaultStyleSize)
bf = append(bf, ansiFormatBegin...) // Agrega "\x1b["
needsSep := false
// --- Color de Primer Plano ---
if hasFg != 0 {
needsSep = true
switch s.GetFgColorType() {
case ColorTypeBasic:
c := s.GetFg().basicColor()
if c < 8 { // Códigos 30-37
bf = append(bf, '3', '0'+c)
} else { // Códigos 90-97
bf = append(bf, '9', '0'+(c-8))
}
case ColorTypeIndex:
c := s.GetFg().indexColor()
bf = append(bf, '3', '8', ';', '5', ';')
bf = AppendItoa(bf, c)
case ColorTypeTrue:
r, g, b := s.GetFg().trueColor()
bf = append(bf, '3', '8', ';', '2', ';')
bf = AppendItoa(bf, r)
bf = append(bf, ';')
bf = AppendItoa(bf, g)
bf = append(bf, ';')
bf = AppendItoa(bf, b)
}
}
// --- Color de Fondo ---
if hasBg != 0 {
if needsSep {
bf = append(bf, ';')
}
needsSep = true
switch s.GetBgColorType() {
case ColorTypeBasic:
c := s.GetBg().basicColor()
if c < 8 { // Códigos 40-47
bf = append(bf, '4', '0'+c)
} else { // Códigos 100-107
bf = append(bf, '1', '0', '0'+(c-8))
}
case ColorTypeIndex:
c := s.GetBg().indexColor()
bf = append(bf, '4', '8', ';', '5', ';')
bf = AppendItoa(bf, c)
case ColorTypeTrue:
r, g, b := s.GetBg().trueColor()
bf = append(bf, '4', '8', ';', '2', ';')
bf = AppendItoa(bf, r)
bf = append(bf, ';')
bf = AppendItoa(bf, g)
bf = append(bf, ';')
bf = AppendItoa(bf, b)
}
}
// --- Formato de Texto ---
if hasFm != 0 {
formatBits := s & maskTextFormat
for i := 0; i < numBitsFormat; i++ {
if formatBits&(1<<i) != 0 {
if needsSep {
bf = append(bf, ';')
}
needsSep = true
bf = append(bf, ansiFormatCodes[i]...)
}
}
}
// --- Finalizar Secuencia ---
return AnsiStyle(append(bf, ansiCodeEndStyle))
}
// =========================================================
@ -528,6 +672,9 @@ func BgBrightWhite () func(Style) Style { return func(s Style) Style { return s.
// Helpers avanzados para colores personalizados
func FgBasicColor(Basic uint8) func(Style) Style { return func(s Style) Style { return s.BasicFgColor (Basic) } }
func BgBasicColor(Basic uint8) func(Style) Style { return func(s Style) Style { return s.BasicBgColor (Basic) } }
func FgIndexColor(index uint8) func(Style) Style { return func(s Style) Style { return s.IndexFgColor (index) } }
func BgIndexColor(index uint8) func(Style) Style { return func(s Style) Style { return s.IndexBgColor (index) } }

@ -0,0 +1,331 @@
/* ——————————————————————————————————————————————————————————————————————————
Proyecto WIAWAIW
—————————————————————————————————————————————————————————————————————————————
Fichero z_ansi_all_test.go
Package ansi2
Autor Juan V. Navarro juanvnl@activething.com
Creado 29/08/2025
—————————————————————————————————————————————————————————————————————————————
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/wiawiaw/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 | wiawiaw.com
git : g.activething.com | github.com/activething
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) 2025 Active Thing
————————————————————————————————————————————————————————————————————————————— */
package ansi2
import (
"fmt"
"testing"
)
// Helper para comparar valores y reportar errores
func assertEqual[T comparable](t *testing.T, got, want T, msg ...string) {
t.Helper()
if got != want {
prefix := "Error"
if len(msg) > 0 {
prefix = msg[0]
}
t.Errorf("%s: got %v, want %v", prefix, got, want)
}
}
func TestMakeStyle(t *testing.T) {
t.Run("Create style with multiple options", func(t *testing.T) {
s := MakeStyle(Bold(), FgRed(), BgWhite())
assertEqual(t, s.IsBold(), true, "IsBold")
assertEqual(t, s.HasFgColor(), true, "HasFgColor")
assertEqual(t, s.HasBgColor(), true, "HasBgColor")
assertEqual(t, s.GetFg(), BasicRed(), "GetFg")
assertEqual(t, s.GetBg(), BasicWhite(), "GetBg")
})
t.Run("Create an empty style", func(t *testing.T) {
s := MakeStyle()
assertEqual(t, s.HasFormat(), false, "HasFormat")
assertEqual(t, s.HasColor(), false, "HasColor")
})
}
func TestStyle_Formatters(t *testing.T) {
s := Style(0)
t.Run("Bold vs Faint", func(t *testing.T) {
s1 := s.Bold()
assertEqual(t, s1.IsBold(), true, "s1.IsBold")
assertEqual(t, s1.IsFaint(), false, "s1.IsFaint")
s2 := s1.Faint()
assertEqual(t, s2.IsBold(), false, "s2.IsBold")
assertEqual(t, s2.IsFaint(), true, "s2.IsFaint")
})
t.Run("Blink", func(t *testing.T) {
s1 := s.SlowBlink()
assertEqual(t, s1.IsSlowBlink(), true, "s1.IsSlowBlink")
assertEqual(t, s1.IsRapidBlink(), false, "s1.IsRapidBlink")
s2 := s1.RapidBlink()
assertEqual(t, s2.IsSlowBlink(), false, "s2.IsSlowBlink")
assertEqual(t, s2.IsRapidBlink(), true, "s2.IsRapidBlink")
})
t.Run("Remove format", func(t *testing.T) {
s1 := MakeStyle(Bold(), Italic())
s2 := s1.NoBold()
assertEqual(t, s2.IsBold(), false, "s2.IsBold")
assertEqual(t, s2.IsItalic(), true, "s2.IsItalic")
})
}
func TestStyle_FgBg(t *testing.T) {
s := Style(0)
t.Run("Set and Get TrueColor", func(t *testing.T) {
s1 := s.TrueFgColor(10, 20, 30).TrueBgColor(40, 50, 60)
fg := s1.GetFg()
bg := s1.GetBg()
assertEqual(t, fg.ColorType(), ColorTypeTrue, "fg.ColorType")
assertEqual(t, bg.ColorType(), ColorTypeTrue, "bg.ColorType")
r, g, b, ok := fg.GetTrueColor()
assertEqual(t, ok, true, "fg.GetTrueColor ok")
assertEqual(t, r, uint8(10), "fg r")
assertEqual(t, g, uint8(20), "fg g")
assertEqual(t, b, uint8(30), "fg b")
r, g, b, ok = bg.GetTrueColor()
assertEqual(t, ok, true, "bg.GetTrueColor ok")
assertEqual(t, r, uint8(40), "bg r")
assertEqual(t, g, uint8(50), "bg g")
assertEqual(t, b, uint8(60), "bg b")
})
t.Run("Set and Get IndexColor", func(t *testing.T) {
s1 := s.IndexFgColor(123).IndexBgColor(234)
fg := s1.GetFg()
bg := s1.GetBg()
assertEqual(t, fg.ColorType(), ColorTypeIndex, "fg.ColorType")
assertEqual(t, bg.ColorType(), ColorTypeIndex, "bg.ColorType")
idx, ok := fg.GetIndexColor()
assertEqual(t, ok, true, "fg.GetIndexColor ok")
assertEqual(t, idx, uint8(123), "fg index")
idx, ok = bg.GetIndexColor()
assertEqual(t, ok, true, "bg.GetIndexColor ok")
assertEqual(t, idx, uint8(234), "bg index")
})
}
func TestStyle_Adapt(t *testing.T) {
// Estilo original con alta capacidad (TrueColor y Bold)
originalStyle := MakeStyle(Bold(), FgTrueColor(100, 150, 200), BgTrueColor(20, 40, 60))
t.Run("Adapt TrueColor to 256Color", func(t *testing.T) {
// Capacidades: soporta 256 colores y negrita
caps := Can256Color | CanFormatBold
adaptedStyle := originalStyle.Adapt(caps)
// El formato se debe mantener
assertEqual(t, adaptedStyle.IsBold(), true, "Adapted style should be bold")
// El color de primer plano debe ser de tipo Index
fg := adaptedStyle.GetFg()
assertEqual(t, fg.ColorType(), ColorTypeIndex, "Adapted Fg should be IndexColor")
fgIndex, _ := fg.GetIndexColor()
expectedFgIndex := RGBToIndex(100, 150, 200)
assertEqual(t, fgIndex, expectedFgIndex, "Adapted Fg index mismatch")
// El color de fondo debe ser de tipo Index
bg := adaptedStyle.GetBg()
assertEqual(t, bg.ColorType(), ColorTypeIndex, "Adapted Bg should be IndexColor")
bgIndex, _ := bg.GetIndexColor()
expectedBgIndex := RGBToIndex(20, 40, 60)
assertEqual(t, bgIndex, expectedBgIndex, "Adapted Bg index mismatch")
})
t.Run("Adapt TrueColor to BasicColor", func(t *testing.T) {
// Capacidades: solo soporta colores básicos
caps := CanBasicColor
adaptedStyle := originalStyle.Adapt(caps)
// El formato se debe descartar
assertEqual(t, adaptedStyle.IsBold(), false, "Adapted style should not be bold")
// El color de primer plano debe ser de tipo Basic
fg := adaptedStyle.GetFg()
assertEqual(t, fg.ColorType(), ColorTypeBasic, "Adapted Fg should be BasicColor")
fgBasic, _ := fg.GetBasicColor()
expectedFgBasic := RGBToBasic(100, 150, 200)
assertEqual(t, fgBasic, expectedFgBasic, "Adapted Fg basic color mismatch")
// El color de fondo debe ser de tipo Basic
bg := adaptedStyle.GetBg()
assertEqual(t, bg.ColorType(), ColorTypeBasic, "Adapted Bg should be BasicColor")
bgBasic, _ := bg.GetBasicColor()
expectedBgBasic := RGBToBasic(20, 40, 60)
assertEqual(t, bgBasic, expectedBgBasic, "Adapted Bg basic color mismatch")
})
t.Run("Adapt 256Color to BasicColor", func(t *testing.T) {
// Estilo con color indexado (un tipo de azul)
indexedStyle := MakeStyle(Italic(), FgIndexColor(21)) // 21 es un azul
caps := CanBasicColor | CanFormatItalic
adaptedStyle := indexedStyle.Adapt(caps)
assertEqual(t, adaptedStyle.IsItalic(), true, "Adapted style should be italic")
fg := adaptedStyle.GetFg()
assertEqual(t, fg.ColorType(), ColorTypeBasic, "Adapted Fg should be BasicColor")
fgBasic, _ := fg.GetBasicColor()
expectedFgBasic := IndexToBasic(21) // Debería convertirse a azul básico
assertEqual(t, fgBasic, expectedFgBasic, fmt.Sprintf("Expected basic blue (%d)", expectedFgBasic))
})
t.Run("Adapt to no color support", func(t *testing.T) {
caps := CanFormatBold // Solo soporta negrita, sin colores
adaptedStyle := originalStyle.Adapt(caps)
// El formato se mantiene
assertEqual(t, adaptedStyle.IsBold(), true, "Adapted style should be bold")
// No debe tener color
assertEqual(t, adaptedStyle.HasColor(), false, "Adapted style should have no color")
})
t.Run("Adapt to no capabilities", func(t *testing.T) {
caps := CanNone
adaptedStyle := originalStyle.Adapt(caps)
// El estilo resultante debe ser 0 (sin formato ni color)
assertEqual(t, adaptedStyle, Style(0), "Adapted style should be empty")
})
}
// En z_ansi_all_test.go (o como se llame tu archivo de pruebas)
func TestStyle_Build(t *testing.T) {
testCases := []struct {
name string
style Style
expected AnsiStyle
}{
{
name: "NoStyle",
style: Style(0),
expected: NoStyle,
},
{
name: "Bold only",
style: MakeStyle(Bold()),
expected: "\x1b[1m",
},
{
name: "Fg Basic Red",
style: MakeStyle(FgRed()),
expected: "\x1b[31m",
},
{
name: "Fg Bright Black",
style: MakeStyle(FgBrightBlack()),
expected: "\x1b[90m",
},
{
name: "Bg Basic Green",
style: MakeStyle(BgGreen()),
expected: "\x1b[42m",
},
{
name: "Bold and Fg Cyan",
style: MakeStyle(Bold(), FgCyan()),
expected: "\x1b[36;1m",
},
{
name: "Italic, Fg Yellow, Bg Blue",
style: MakeStyle(Italic(), FgYellow(), BgBlue()),
expected: "\x1b[33;44;3m",
},
{
name: "Fg Index Color",
style: MakeStyle(FgIndexColor(123)),
expected: "\x1b[38;5;123m",
},
{
name: "Bg Index Color",
style: MakeStyle(BgIndexColor(201)),
expected: "\x1b[48;5;201m",
},
{
name: "Fg TrueColor",
style: MakeStyle(FgTrueColor(10, 20, 30)),
expected: "\x1b[38;2;10;20;30m",
},
{
name: "Bg TrueColor",
style: MakeStyle(BgTrueColor(110, 120, 130)),
expected: "\x1b[48;2;110;120;130m",
},
{
name: "Complex Style",
style: MakeStyle(Underline(), CrossedOut(), FgTrueColor(50, 50, 50), BgIndexColor(250)),
expected: "\x1b[38;2;50;50;50;48;5;250;4;9m",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
got := tc.style.Build()
// Esta comparación ahora debería funcionar
assertEqual(t, got, tc.expected, "Build output mismatch")
})
}
}
func TestAnsiStyle_Stylize(t *testing.T) {
style := MakeStyle(FgRed()).Build()
text := "hello"
expected := "\x1b[31mhello\x1b[0m"
got := style.Stylize(text)
assertEqual(t, got, expected)
noStyle := NoStyle
assertEqual(t, noStyle.Stylize("text"), "text")
}
Loading…
Cancel
Save

Powered by TurnKey Linux.