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
|
||||
|
||||
Supported colors: red, green, yellow, blue, purple, cyan, white, gray
|
||||
Supported colors: black, white, red, green, blue, yellow, magenta, cyan
|
||||
|
||||
```go
|
||||
fmt.Print(color.Blue("Blue text"))
|
||||
fmt.Printf("Status: %s\n", color.Green("OK"))
|
||||
fmt.Println(color.Red("Error occurred"))
|
||||
|
||||
blue := color.Blue("Blue text")
|
||||
warning := color.Yellow("Warning message")
|
||||
|
||||
color.SetColors(false)
|
||||
color.SetColors(true)
|
||||
|
||||
if color.ColorsEnabled() {
|
||||
// ...
|
||||
}
|
||||
color.Blue.Print("Blue")
|
||||
color.Blue.Printf("%s", "Blue")
|
||||
color.Blue.Println("Blue")
|
||||
blue := color.Blue.String("Blue")
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ANSI color codes
|
||||
// Color type
|
||||
type Color int
|
||||
|
||||
// Color codes
|
||||
const (
|
||||
resetCode = "\033[0m"
|
||||
redCode = "\033[31m"
|
||||
greenCode = "\033[32m"
|
||||
yellowCode = "\033[33m"
|
||||
blueCode = "\033[34m"
|
||||
purpleCode = "\033[35m"
|
||||
cyanCode = "\033[36m"
|
||||
whiteCode = "\033[37m"
|
||||
grayCode = "\033[90m"
|
||||
Black Color = iota + 30
|
||||
Red
|
||||
Green
|
||||
Yellow
|
||||
Blue
|
||||
Magenta
|
||||
Cyan
|
||||
White
|
||||
)
|
||||
|
||||
var (
|
||||
useColors bool
|
||||
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 (c Color) String(args ...any) string {
|
||||
return fmt.Sprintf("\x1b[%dm%s\x1b[0m", c, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
func makeColorFunc(code string) func(string) string {
|
||||
return func(text string) string {
|
||||
colorMu.RLock()
|
||||
enabled := useColors
|
||||
colorMu.RUnlock()
|
||||
|
||||
if enabled {
|
||||
return code + text + resetCode
|
||||
}
|
||||
return text
|
||||
func (c Color) Print(args ...any) {
|
||||
if !supportsColors() {
|
||||
fmt.Print(args...)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\x1b[%dm%s\x1b[0m", c, fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// DetectShellColors checks if the current shell supports colors
|
||||
func DetectShellColors() bool {
|
||||
// Check NO_COLOR environment variable (standard)
|
||||
if os.Getenv("NO_COLOR") != "" {
|
||||
return false
|
||||
func (c Color) Printf(format string, args ...any) {
|
||||
if !supportsColors() {
|
||||
fmt.Printf(format, args...)
|
||||
return
|
||||
}
|
||||
|
||||
// Check FORCE_COLOR environment variable
|
||||
if os.Getenv("FORCE_COLOR") != "" {
|
||||
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 if stdout is a terminal
|
||||
if !isTerminal(os.Stdout) {
|
||||
return false
|
||||
// Check for 256 colors support
|
||||
if strings.Contains(term, "256color") {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check TERM environment variable
|
||||
term := os.Getenv("TERM")
|
||||
if term == "" || term == "dumb" {
|
||||
return false
|
||||
// Check for basic color support
|
||||
if term == "xterm" || term == "screen" || term == "vt100" || strings.Contains(term, "color") {
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
return false
|
||||
}
|
||||
|
@ -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"
|
||||
)
|
||||
|
||||
func TestColorFunctions(t *testing.T) {
|
||||
// Enable colors for testing
|
||||
color.SetColors(true)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
colorFunc func(string) string
|
||||
expected string
|
||||
}{
|
||||
{"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 TestPrint(t *testing.T) {
|
||||
color.Black.Print("Black\n")
|
||||
color.White.Print("White\n")
|
||||
color.Red.Print("Red\n")
|
||||
color.Green.Print("Green\n")
|
||||
color.Blue.Print("Blue\n")
|
||||
color.Yellow.Print("Yellow\n")
|
||||
color.Magenta.Print("Magenta\n")
|
||||
color.Cyan.Print("Cyan\n")
|
||||
}
|
||||
|
||||
func TestColorsDisabled(t *testing.T) {
|
||||
// Disable colors
|
||||
color.SetColors(false)
|
||||
|
||||
input := "test"
|
||||
expected := "test"
|
||||
|
||||
colorFuncs := []func(string) string{
|
||||
color.Red, color.Green, color.Yellow, color.Blue, color.Purple, color.Cyan, color.White, color.Gray, color.Reset,
|
||||
}
|
||||
|
||||
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 TestPrintf(t *testing.T) {
|
||||
color.Black.Printf("%s\n", "Black")
|
||||
color.White.Printf("%s\n", "White")
|
||||
color.Red.Printf("%s\n", "Red")
|
||||
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 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)
|
||||
}
|
||||
func TestPrintln(t *testing.T) {
|
||||
color.Black.Println("Black")
|
||||
color.White.Println("White")
|
||||
color.Red.Println("Red")
|
||||
color.Green.Println("Green")
|
||||
color.Blue.Println("Blue")
|
||||
color.Yellow.Println("Yellow")
|
||||
color.Magenta.Println("Magenta")
|
||||
color.Cyan.Println("Cyan")
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user