Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

5 changed files with 82 additions and 246 deletions

View File

@ -1,21 +1,10 @@
# Color # Color
Supported colors: red, green, yellow, blue, purple, cyan, white, gray Supported colors: black, white, red, green, blue, yellow, magenta, cyan
```go ```go
fmt.Print(color.Blue("Blue text")) color.Blue.Print("Blue")
fmt.Printf("Status: %s\n", color.Green("OK")) color.Blue.Printf("%s", "Blue")
fmt.Println(color.Red("Error occurred")) color.Blue.Println("Blue")
blue := color.Blue.String("Blue")
blue := color.Blue("Blue text")
warning := color.Yellow("Warning message")
color.SetColors(false)
color.SetColors(true)
if color.ColorsEnabled() {
// ...
}
``` ```
Colors are automatically detected based on terminal capabilities and environment variables (`NO_COLOR`, `FORCE_COLOR`, `TERM`).

134
color.go
View File

@ -1,101 +1,75 @@
package color package color
import ( import (
"fmt"
"os" "os"
"strings" "strings"
"sync"
) )
// ANSI color codes // Color type
type Color int
// Color codes
const ( const (
resetCode = "\033[0m" Black Color = iota + 30
redCode = "\033[31m" Red
greenCode = "\033[32m" Green
yellowCode = "\033[33m" Yellow
blueCode = "\033[34m" Blue
purpleCode = "\033[35m" Magenta
cyanCode = "\033[36m" Cyan
whiteCode = "\033[37m" White
grayCode = "\033[90m"
) )
var ( func (c Color) String(args ...any) string {
useColors bool return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, fmt.Sprint(args...))
colorMu sync.RWMutex
)
// Color function; makes a call to makeColorFunc with the associated color code
var (
Reset = makeColorFunc(resetCode)
Red = makeColorFunc(redCode)
Green = makeColorFunc(greenCode)
Yellow = makeColorFunc(yellowCode)
Blue = makeColorFunc(blueCode)
Purple = makeColorFunc(purpleCode)
Cyan = makeColorFunc(cyanCode)
White = makeColorFunc(whiteCode)
Gray = makeColorFunc(grayCode)
)
func init() {
useColors = DetectShellColors()
} }
func makeColorFunc(code string) func(string) string { func (c Color) Print(args ...any) {
return func(text string) string { if !supportsColors() {
colorMu.RLock() fmt.Print(args...)
enabled := useColors return
colorMu.RUnlock()
if enabled {
return code + text + resetCode
}
return text
}
} }
// DetectShellColors checks if the current shell supports colors fmt.Printf("\x1b[%dm%s\x1b[0m", c, fmt.Sprint(args...))
func DetectShellColors() bool {
// Check NO_COLOR environment variable (standard)
if os.Getenv("NO_COLOR") != "" {
return false
} }
// Check FORCE_COLOR environment variable func (c Color) Printf(format string, args ...any) {
if os.Getenv("FORCE_COLOR") != "" { if !supportsColors() {
fmt.Printf(format, args...)
return
}
fmt.Printf("\x1b[%dm%s\x1b[0m", c, fmt.Sprintf(format, args...))
}
func (c Color) Println(args ...any) {
if !supportsColors() {
fmt.Println(args...)
return
}
fmt.Printf("\x1b[%dm%s\x1b[0m\n", c, fmt.Sprint(args...))
}
func supportsColors() bool {
colorterm := os.Getenv("COLORTERM")
term := os.Getenv("TERM")
// Check for true color (24-bit support)
if strings.Contains(colorterm, "truecolor") || strings.Contains(colorterm, "24bit") {
return true
}
// Check for 256 colors support
if strings.Contains(term, "256color") {
return true
}
// Check for basic color support
if term == "xterm" || term == "screen" || term == "vt100" || strings.Contains(term, "color") {
return true return true
} }
// Check if stdout is a terminal
if !isTerminal(os.Stdout) {
return false return false
} }
// Check TERM environment variable
term := os.Getenv("TERM")
if term == "" || term == "dumb" {
return false
}
// Common color-supporting terminals
return strings.Contains(term, "color") ||
strings.Contains(term, "xterm") ||
strings.Contains(term, "screen") ||
strings.Contains(term, "tmux") ||
term == "linux" ||
isWindowsTerminal()
}
// SetColors enables or disables colors globally
func SetColors(enabled bool) {
colorMu.Lock()
useColors = enabled
colorMu.Unlock()
}
// ColorsEnabled returns current global color setting
func ColorsEnabled() bool {
colorMu.RLock()
defer colorMu.RUnlock()
return useColors
}

View File

@ -1,33 +0,0 @@
//go:build !windows
// +build !windows
package color
import (
"os"
"syscall"
"unsafe"
)
const ioctlReadTermios = 0x5401
// isTerminal checks if the file is a terminal
func isTerminal(f *os.File) bool {
fd := f.Fd()
var termios syscall.Termios
r1, _, errno := syscall.Syscall6(
syscall.SYS_IOCTL,
fd,
ioctlReadTermios,
uintptr(unsafe.Pointer(&termios)),
0, 0, 0,
)
return r1 == 0 && errno == 0
}
// isWindowsTerminal always returns false on Unix
func isWindowsTerminal() bool {
return false
}

View File

@ -1,29 +0,0 @@
//go:build windows
// +build windows
package color
import (
"os"
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
)
// isTerminal checks if the file is a terminal on Windows
func isTerminal(f *os.File) bool {
handle := syscall.Handle(f.Fd())
var mode uint32
r1, _, _ := procGetConsoleMode.Call(uintptr(handle), uintptr(unsafe.Pointer(&mode)))
return r1 != 0
}
// isWindowsTerminal checks for Windows Terminal
func isWindowsTerminal() bool {
return os.Getenv("WT_SESSION") != ""
}

View File

@ -6,100 +6,35 @@ import (
color "git.sharkk.net/Go/Color" color "git.sharkk.net/Go/Color"
) )
func TestColorFunctions(t *testing.T) { func TestPrint(t *testing.T) {
// Enable colors for testing color.Black.Print("Black\n")
color.SetColors(true) color.White.Print("White\n")
color.Red.Print("Red\n")
tests := []struct { color.Green.Print("Green\n")
name string color.Blue.Print("Blue\n")
colorFunc func(string) string color.Yellow.Print("Yellow\n")
expected string color.Magenta.Print("Magenta\n")
}{ color.Cyan.Print("Cyan\n")
{"Red", color.Red, "\033[31mtest\033[0m"},
{"Green", color.Green, "\033[32mtest\033[0m"},
{"Yellow", color.Yellow, "\033[33mtest\033[0m"},
{"Blue", color.Blue, "\033[34mtest\033[0m"},
{"Purple", color.Purple, "\033[35mtest\033[0m"},
{"Cyan", color.Cyan, "\033[36mtest\033[0m"},
{"White", color.White, "\033[37mtest\033[0m"},
{"Gray", color.Gray, "\033[90mtest\033[0m"},
{"Reset", color.Reset, "\033[0mtest\033[0m"},
} }
for _, tt := range tests { func TestPrintf(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { color.Black.Printf("%s\n", "Black")
result := tt.colorFunc("test") color.White.Printf("%s\n", "White")
if result != tt.expected { color.Red.Printf("%s\n", "Red")
t.Errorf("got %q, want %q", result, tt.expected) color.Green.Printf("%s\n", "Green")
} color.Blue.Printf("%s\n", "Blue")
}) color.Yellow.Printf("%s\n", "Yellow")
} color.Magenta.Printf("%s\n", "Magenta")
color.Cyan.Printf("%s\n", "Cyan")
} }
func TestColorsDisabled(t *testing.T) { func TestPrintln(t *testing.T) {
// Disable colors color.Black.Println("Black")
color.SetColors(false) color.White.Println("White")
color.Red.Println("Red")
input := "test" color.Green.Println("Green")
expected := "test" color.Blue.Println("Blue")
color.Yellow.Println("Yellow")
colorFuncs := []func(string) string{ color.Magenta.Println("Magenta")
color.Red, color.Green, color.Yellow, color.Blue, color.Purple, color.Cyan, color.White, color.Gray, color.Reset, color.Cyan.Println("Cyan")
}
for _, colorFunc := range colorFuncs {
result := colorFunc(input)
if result != expected {
t.Errorf("expected plain text when colors disabled, got %q", result)
}
}
// Re-enable for other tests
color.SetColors(true)
}
func TestSetColorsAndColorsEnabled(t *testing.T) {
// Test enabling colors
color.SetColors(true)
if !color.ColorsEnabled() {
t.Error("expected colors to be enabled")
}
// Test disabling colors
color.SetColors(false)
if color.ColorsEnabled() {
t.Error("expected colors to be disabled")
}
// Restore default state
color.SetColors(color.DetectShellColors())
}
func TestEmptyString(t *testing.T) {
color.SetColors(true)
result := color.Red("")
expected := "\033[31m\033[0m"
if result != expected {
t.Errorf("got %q, want %q", result, expected)
}
}
func BenchmarkColorFunction(b *testing.B) {
color.SetColors(true)
input := "benchmark test"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = color.Red(input)
}
}
func BenchmarkColorFunctionDisabled(b *testing.B) {
color.SetColors(false)
input := "benchmark test"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = color.Red(input)
}
} }