Compare commits
No commits in common. "master" and "v1.0.0" have entirely different histories.
21
README.md
21
README.md
@ -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`).
|
|
||||||
|
126
color.go
126
color.go
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("\x1b[%dm%s\x1b[0m", c, fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetectShellColors checks if the current shell supports colors
|
func (c Color) Printf(format string, args ...any) {
|
||||||
func DetectShellColors() bool {
|
if !supportsColors() {
|
||||||
// Check NO_COLOR environment variable (standard)
|
fmt.Printf(format, args...)
|
||||||
if os.Getenv("NO_COLOR") != "" {
|
return
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check FORCE_COLOR environment variable
|
fmt.Printf("\x1b[%dm%s\x1b[0m", c, fmt.Sprintf(format, args...))
|
||||||
if os.Getenv("FORCE_COLOR") != "" {
|
}
|
||||||
|
|
||||||
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if stdout is a terminal
|
// Check for 256 colors support
|
||||||
if !isTerminal(os.Stdout) {
|
if strings.Contains(term, "256color") {
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check TERM environment variable
|
// Check for basic color support
|
||||||
term := os.Getenv("TERM")
|
if term == "xterm" || term == "screen" || term == "vt100" || strings.Contains(term, "color") {
|
||||||
if term == "" || term == "dumb" {
|
return true
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common color-supporting terminals
|
return false
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -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") != ""
|
|
||||||
}
|
|
@ -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 {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := tt.colorFunc("test")
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("got %q, want %q", result, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestColorsDisabled(t *testing.T) {
|
func TestPrintf(t *testing.T) {
|
||||||
// Disable colors
|
color.Black.Printf("%s\n", "Black")
|
||||||
color.SetColors(false)
|
color.White.Printf("%s\n", "White")
|
||||||
|
color.Red.Printf("%s\n", "Red")
|
||||||
input := "test"
|
color.Green.Printf("%s\n", "Green")
|
||||||
expected := "test"
|
color.Blue.Printf("%s\n", "Blue")
|
||||||
|
color.Yellow.Printf("%s\n", "Yellow")
|
||||||
colorFuncs := []func(string) string{
|
color.Magenta.Printf("%s\n", "Magenta")
|
||||||
color.Red, color.Green, color.Yellow, color.Blue, color.Purple, color.Cyan, color.White, color.Gray, color.Reset,
|
color.Cyan.Printf("%s\n", "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) {
|
func TestPrintln(t *testing.T) {
|
||||||
// Test enabling colors
|
color.Black.Println("Black")
|
||||||
color.SetColors(true)
|
color.White.Println("White")
|
||||||
if !color.ColorsEnabled() {
|
color.Red.Println("Red")
|
||||||
t.Error("expected colors to be enabled")
|
color.Green.Println("Green")
|
||||||
}
|
color.Blue.Println("Blue")
|
||||||
|
color.Yellow.Println("Yellow")
|
||||||
// Test disabling colors
|
color.Magenta.Println("Magenta")
|
||||||
color.SetColors(false)
|
color.Cyan.Println("Cyan")
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user