| package termstyle |
|
|
| import ( |
| "fmt" |
| "os" |
| "strconv" |
| "strings" |
| ) |
|
|
| type Color string |
|
|
| type Style struct { |
| foreground Color |
| bold bool |
| } |
|
|
| func NewStyle() Style { |
| return Style{} |
| } |
|
|
| func (s Style) Foreground(color Color) Style { |
| s.foreground = color |
| return s |
| } |
|
|
| func (s Style) Bold(enabled bool) Style { |
| s.bold = enabled |
| return s |
| } |
|
|
| func (s Style) Render(text string) string { |
| if !ShouldColorizeFile(os.Stdout) { |
| return text |
| } |
| var codes []string |
| if s.bold { |
| codes = append(codes, "1") |
| } |
| if r, g, b, ok := parseHexColor(string(s.foreground)); ok { |
| codes = append(codes, fmt.Sprintf("38;2;%d;%d;%d", r, g, b)) |
| } |
| if len(codes) == 0 { |
| return text |
| } |
| return "\x1b[" + strings.Join(codes, ";") + "m" + text + "\x1b[0m" |
| } |
|
|
| func RenderToFile(file *os.File, style Style, text string) string { |
| if !ShouldColorizeFile(file) { |
| return text |
| } |
| return style.Render(text) |
| } |
|
|
| func ShouldColorizeFile(file *os.File) bool { |
| if file == nil { |
| return false |
| } |
| if os.Getenv("NO_COLOR") != "" { |
| return false |
| } |
| if force := os.Getenv("CLICOLOR_FORCE"); force != "" && force != "0" { |
| return true |
| } |
| if strings.EqualFold(os.Getenv("TERM"), "dumb") { |
| return false |
| } |
| info, err := file.Stat() |
| if err != nil { |
| return false |
| } |
| return (info.Mode() & os.ModeCharDevice) != 0 |
| } |
|
|
| func parseHexColor(value string) (int64, int64, int64, bool) { |
| trimmed := strings.TrimPrefix(strings.TrimSpace(value), "#") |
| if len(trimmed) != 6 { |
| return 0, 0, 0, false |
| } |
| raw, err := strconv.ParseUint(trimmed, 16, 32) |
| if err != nil { |
| return 0, 0, 0, false |
| } |
| return int64((raw >> 16) & 0xff), int64((raw >> 8) & 0xff), int64(raw & 0xff), true |
| } |
|
|