Initial QSfera import
This commit is contained in:
+126
@@ -0,0 +1,126 @@
|
||||
// Package basefn provide some no-dependents util functions
|
||||
package basefn
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
)
|
||||
|
||||
// Panicf format panic message use fmt.Sprintf
|
||||
func Panicf(format string, v ...any) { panic(fmt.Sprintf(format, v...)) }
|
||||
|
||||
// PanicIf if cond = true, panics with an error message
|
||||
func PanicIf(cond bool, fmtAndArgs ...any) {
|
||||
if cond {
|
||||
panic(errors.New(comfunc.FormatWithArgs(fmtAndArgs)))
|
||||
}
|
||||
}
|
||||
|
||||
// PanicErr panics if error is not empty
|
||||
func PanicErr(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// MustOK if error is not empty, will panic
|
||||
func MustOK(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Must return like (v, error). will panic on error, otherwise return v.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // old
|
||||
// v, err := fn()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // new
|
||||
// v := goutil.Must(fn())
|
||||
func Must[T any](v T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// MustIgnore for return like (v, error). Ignore return v and will panic on error.
|
||||
//
|
||||
// Useful for io, file operation func: (n int, err error)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// // old
|
||||
// _, err := fn()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
//
|
||||
// // new
|
||||
// basefn.MustIgnore(fn())
|
||||
func MustIgnore(_ any, err error) { PanicErr(err) }
|
||||
|
||||
// ErrOnFail return input error on cond is false, otherwise return nil
|
||||
func ErrOnFail(cond bool, err error) error {
|
||||
return OrError(cond, err)
|
||||
}
|
||||
|
||||
// OrError return input error on cond is false, otherwise return nil
|
||||
func OrError(cond bool, err error) error {
|
||||
if !cond {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FirstOr get first elem or elseVal
|
||||
func FirstOr[T any](sl []T, elseVal T) T {
|
||||
if len(sl) > 0 {
|
||||
return sl[0]
|
||||
}
|
||||
return elseVal
|
||||
}
|
||||
|
||||
// OrValue get. like: if cond { okVal } else { elVal }
|
||||
func OrValue[T any](cond bool, okVal, elVal T) T {
|
||||
if cond {
|
||||
return okVal
|
||||
}
|
||||
return elVal
|
||||
}
|
||||
|
||||
// OrReturn call okFunc() on condition is true, else call elseFn()
|
||||
//
|
||||
// like expr: if cond { okFunc() } else { elseFn() }
|
||||
func OrReturn[T any](cond bool, okFn, elseFn func() T) T {
|
||||
if cond {
|
||||
return okFn()
|
||||
}
|
||||
return elseFn()
|
||||
}
|
||||
|
||||
// ErrFunc type
|
||||
type ErrFunc func() error
|
||||
|
||||
// CallOn call func on condition is true
|
||||
func CallOn(cond bool, fn ErrFunc) error {
|
||||
if cond {
|
||||
return fn()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CallOrElse call okFunc() on condition is true, else call elseFn()
|
||||
func CallOrElse(cond bool, okFn, elseFn ErrFunc) error {
|
||||
if cond {
|
||||
return okFn()
|
||||
}
|
||||
return elseFn()
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
# CColor
|
||||
|
||||
`CColor` is a simple command-line color output library that uses ANSI color codes to output text with colors.
|
||||
Its main code is a partial code extracted and simplified from `gookit/color`, which only supports simple 16-color ANSI color output.
|
||||
|
||||
> Tip: If you want to render with richer colors, use the [github.com/gookit/color](https://github.com/gookit/color) package.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/x/ccolor
|
||||
```
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# CColor
|
||||
|
||||
`CColor` 是一个简单的命令行颜色输出库,它使用 ANSI 颜色代码来输出带有颜色的文本。
|
||||
它的主要代码是抽取和简化自 `gookit/color` 的部分代码,仅支持简单的 16 色 ANSI 颜色输出。
|
||||
|
||||
> **提示**:如果想要使用更丰富的颜色渲染,请使用 [github.com/gookit/color](https://github.com/gookit/color) 包。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/x/ccolor
|
||||
```
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
// Package ccolor is a simple color render library for terminal.
|
||||
// Its main code is the code that is extracted and simplified from gookit/color,
|
||||
//
|
||||
// TIP:
|
||||
//
|
||||
// If you want to render with richer colors, use the https://github.com/gookit/color package.
|
||||
package ccolor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/gookit/goutil/x/termenv"
|
||||
)
|
||||
|
||||
// color render templates
|
||||
//
|
||||
// ESC 操作的表示:
|
||||
//
|
||||
// "\033"(Octal 8进制) = "\x1b"(Hexadecimal 16进制) = 27 (10进制)
|
||||
const (
|
||||
// StartSet chars
|
||||
StartSet = "\x1b["
|
||||
// ResetSet close all properties.
|
||||
ResetSet = "\x1b[0m"
|
||||
// SettingTpl string.
|
||||
SettingTpl = "\x1b[%sm"
|
||||
// FullColorTpl for build color code
|
||||
FullColorTpl = "\x1b[%sm%s\x1b[0m"
|
||||
// CodeSuffix string for color code.
|
||||
CodeSuffix = "[0m"
|
||||
)
|
||||
|
||||
// CodeExpr regex to clear color codes eg "\033[1;36mText\x1b[0m"
|
||||
const CodeExpr = `\033\[[\d;?]+m`
|
||||
|
||||
var (
|
||||
// last error
|
||||
lastErr error
|
||||
// output the default io.Writer message print
|
||||
output io.Writer = os.Stdout
|
||||
// match color codes
|
||||
codeRegex = regexp.MustCompile(CodeExpr)
|
||||
)
|
||||
|
||||
// SetOutput set output writer
|
||||
func SetOutput(w io.Writer) { output = w }
|
||||
|
||||
// LastErr info
|
||||
func LastErr() error {
|
||||
defer func() {
|
||||
lastErr = nil
|
||||
}()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
//
|
||||
// ---------------- support detect from termenv ----------------
|
||||
//
|
||||
|
||||
// Disable color of current terminal.
|
||||
func Disable() { termenv.DisableColor() }
|
||||
|
||||
// Level value of current terminal.
|
||||
func Level() termenv.ColorLevel { return termenv.TermColorLevel() }
|
||||
|
||||
// IsSupportColor returns true if the terminal supports color.
|
||||
func IsSupportColor() bool { return termenv.IsSupportColor() }
|
||||
|
||||
// IsSupport256Color returns true if the terminal supports 256 colors.
|
||||
func IsSupport256Color() bool { return termenv.IsSupport256Color() }
|
||||
|
||||
// IsSupportTrueColor returns true if the terminal supports true color.
|
||||
func IsSupportTrueColor() bool { return termenv.IsSupportTrueColor() }
|
||||
|
||||
//
|
||||
// ---------------- for testing ----------------
|
||||
//
|
||||
|
||||
// ForceEnableColor setting value. TIP: use for unit testing.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ccolor.ForceEnableColor()
|
||||
// defer ccolor.RevertColorSupport()
|
||||
func ForceEnableColor() {
|
||||
termenv.ForceEnableColor()
|
||||
}
|
||||
|
||||
// RevertColorSupport value
|
||||
func RevertColorSupport() {
|
||||
termenv.RevertColorSupport()
|
||||
}
|
||||
|
||||
//
|
||||
// ---------------- print with color tag style ----------------
|
||||
//
|
||||
|
||||
// Print parse color tag and print messages
|
||||
func Print(v ...any) { Fprint(output, v...) }
|
||||
|
||||
// Printf format and print messages
|
||||
func Printf(format string, v ...any) { Fprintf(output, format, v...) }
|
||||
|
||||
// Println messages with new line
|
||||
func Println(v ...any) { Fprintln(output, v...) }
|
||||
|
||||
// Sprint parse color tags, return rendered string
|
||||
func Sprint(v ...any) string {
|
||||
return ReplaceTag(fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Sprintf format and return rendered string
|
||||
func Sprintf(format string, a ...any) string {
|
||||
return ReplaceTag(fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Fprint auto parse color-tag, print rendered messages to the writer
|
||||
func Fprint(w io.Writer, v ...any) {
|
||||
_, lastErr = fmt.Fprint(w, ReplaceTag(fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
// Fprintf auto parse color-tag, print rendered messages to the writer.
|
||||
func Fprintf(w io.Writer, format string, v ...any) {
|
||||
_, lastErr = fmt.Fprint(w, ReplaceTag(fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
// Fprintln auto parse color-tag, print rendered messages to the writer
|
||||
func Fprintln(w io.Writer, v ...any) {
|
||||
_, lastErr = fmt.Fprintln(w, ReplaceTag(formatLikePrintln(v)))
|
||||
}
|
||||
|
||||
// Lprint passes colored messages to a log.Logger for printing.
|
||||
func Lprint(l *log.Logger, v ...any) {
|
||||
l.Print(ReplaceTag(fmt.Sprint(v...)))
|
||||
}
|
||||
+376
@@ -0,0 +1,376 @@
|
||||
package ccolor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Color Color16, 16 color value type
|
||||
// 3(2^3=8) OR 4(2^4=16) bite color.
|
||||
type Color uint8
|
||||
|
||||
/*************************************************************
|
||||
* Basic 16 color definition
|
||||
*************************************************************/
|
||||
|
||||
// Boundary value for foreground/background color 16
|
||||
//
|
||||
// - base: fg 30~37, bg 40~47
|
||||
// - light: fg 90~97, bg 100~107
|
||||
const (
|
||||
fgBase uint8 = 30
|
||||
fgMax uint8 = 37
|
||||
bgBase uint8 = 40
|
||||
bgMax uint8 = 47
|
||||
|
||||
hiFgBase uint8 = 90
|
||||
hiFgMax uint8 = 97
|
||||
hiBgBase uint8 = 100
|
||||
hiBgMax uint8 = 107
|
||||
|
||||
// optMax max option value. range: 0 - 9
|
||||
optMax = 10
|
||||
)
|
||||
|
||||
// Foreground colors. basic foreground colors 30 - 37
|
||||
const (
|
||||
FgBlack Color = iota + 30
|
||||
FgRed
|
||||
FgGreen
|
||||
FgYellow
|
||||
FgBlue
|
||||
FgMagenta // 品红
|
||||
FgCyan // 青色
|
||||
FgWhite
|
||||
// FgDefault revert default FG
|
||||
FgDefault Color = 39
|
||||
)
|
||||
|
||||
// Extra foreground color 90 - 97(非标准)
|
||||
const (
|
||||
FgDarkGray Color = iota + 90 // 亮黑(灰)
|
||||
FgLightRed
|
||||
FgLightGreen
|
||||
FgLightYellow
|
||||
FgLightBlue
|
||||
FgLightMagenta
|
||||
FgLightCyan
|
||||
FgLightWhite
|
||||
// FgGray is alias of FgDarkGray
|
||||
FgGray Color = 90 // 亮黑(灰)
|
||||
)
|
||||
|
||||
// Background colors. basic background colors 40 - 47
|
||||
const (
|
||||
BgBlack Color = iota + 40
|
||||
BgRed
|
||||
BgGreen
|
||||
BgYellow // BgBrown like yellow
|
||||
BgBlue
|
||||
BgMagenta
|
||||
BgCyan
|
||||
BgWhite
|
||||
// BgDefault revert default BG
|
||||
BgDefault Color = 49
|
||||
)
|
||||
|
||||
// Extra background color 100 - 107 (non-standard)
|
||||
const (
|
||||
BgDarkGray Color = iota + 100
|
||||
BgLightRed
|
||||
BgLightGreen
|
||||
BgLightYellow
|
||||
BgLightBlue
|
||||
BgLightMagenta
|
||||
BgLightCyan
|
||||
BgLightWhite
|
||||
// BgGray is alias of BgDarkGray
|
||||
BgGray Color = 100
|
||||
)
|
||||
|
||||
// Option settings. range: 0 - 9
|
||||
const (
|
||||
OpReset Color = iota // 0 重置所有设置
|
||||
OpBold // 1 加粗
|
||||
OpFuzzy // 2 模糊(不是所有的终端仿真器都支持)
|
||||
OpItalic // 3 斜体(不是所有的终端仿真器都支持)
|
||||
OpUnderscore // 4 下划线
|
||||
OpBlink // 5 闪烁
|
||||
OpFastBlink // 6 快速闪烁(未广泛支持)
|
||||
OpReverse // 7 颠倒的 交换背景色与前景色
|
||||
OpConcealed // 8 隐匿的
|
||||
OpStrikethrough // 9 删除的,删除线(未广泛支持)
|
||||
)
|
||||
|
||||
// There are basic and light foreground color aliases
|
||||
const (
|
||||
Red = FgRed
|
||||
Cyan = FgCyan
|
||||
Gray = FgDarkGray // is light Black
|
||||
Blue = FgBlue
|
||||
Black = FgBlack
|
||||
Green = FgGreen
|
||||
White = FgWhite
|
||||
Yellow = FgYellow
|
||||
Magenta = FgMagenta
|
||||
|
||||
// special
|
||||
|
||||
Bold = OpBold
|
||||
Normal = FgDefault
|
||||
|
||||
// extra light
|
||||
|
||||
LightRed = FgLightRed
|
||||
LightCyan = FgLightCyan
|
||||
LightBlue = FgLightBlue
|
||||
LightGreen = FgLightGreen
|
||||
LightWhite = FgLightWhite
|
||||
LightYellow = FgLightYellow
|
||||
LightMagenta = FgLightMagenta
|
||||
|
||||
HiRed = FgLightRed
|
||||
HiCyan = FgLightCyan
|
||||
HiBlue = FgLightBlue
|
||||
HiGreen = FgLightGreen
|
||||
HiWhite = FgLightWhite
|
||||
HiYellow = FgLightYellow
|
||||
HiMagenta = FgLightMagenta
|
||||
|
||||
BgHiRed = BgLightRed
|
||||
BgHiCyan = BgLightCyan
|
||||
BgHiBlue = BgLightBlue
|
||||
BgHiGreen = BgLightGreen
|
||||
BgHiWhite = BgLightWhite
|
||||
BgHiYellow = BgLightYellow
|
||||
BgHiMagenta = BgLightMagenta
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* Color render methods
|
||||
*************************************************************/
|
||||
|
||||
// Text render a text message
|
||||
func (c Color) Text(message string) string { return RenderString(c.String(), message) }
|
||||
|
||||
// Render messages by color setting
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// green := ccolor.FgGreen.Render
|
||||
// fmt.Println(green("message"))
|
||||
func (c Color) Render(a ...any) string { return RenderCode(c.String(), a...) }
|
||||
|
||||
// Renderln messages by color setting.
|
||||
// like fmt.Println, will add spaces for each argument
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// green := ccolor.FgGreen.Renderln
|
||||
// fmt.Println(green("message"))
|
||||
func (c Color) Renderln(a ...any) string { return RenderWithSpaces(c.String(), a...) }
|
||||
|
||||
// Sprint render messages by color setting. is alias of the Render()
|
||||
func (c Color) Sprint(a ...any) string { return RenderCode(c.String(), a...) }
|
||||
|
||||
// Sprintf format and render message.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// green := ccolor.Green.Sprintf
|
||||
// colored := green("message")
|
||||
func (c Color) Sprintf(format string, args ...any) string {
|
||||
return RenderString(c.String(), fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Print messages.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ccolor.Green.Print("message")
|
||||
//
|
||||
// OR:
|
||||
//
|
||||
// green := ccolor.FgGreen.Print
|
||||
// green("message")
|
||||
func (c Color) Print(args ...any) {
|
||||
doPrint(c.Code(), fmt.Sprint(args...))
|
||||
}
|
||||
|
||||
// Printf format and print messages.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ccolor.Cyan.Printf("string %s", "arg0")
|
||||
func (c Color) Printf(format string, a ...any) {
|
||||
doPrint(c.Code(), fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
// Println messages with new line
|
||||
func (c Color) Println(a ...any) { doPrintln(c.String(), a) }
|
||||
|
||||
// Light current color. eg: 36(FgCyan) -> 96(FgLightCyan).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// lightCyan := Cyan.Light()
|
||||
// lightCyan.Print("message")
|
||||
func (c Color) Light() Color {
|
||||
val := uint8(c)
|
||||
if val >= 30 && val <= 47 {
|
||||
return Color(val + 60)
|
||||
}
|
||||
|
||||
// don't change
|
||||
return c
|
||||
}
|
||||
|
||||
// Darken current color. eg. 96(FgLightCyan) -> 36(FgCyan)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cyan := LightCyan.Darken()
|
||||
// cyan.Print("message")
|
||||
func (c Color) Darken() Color {
|
||||
val := uint8(c)
|
||||
if val >= 90 && val <= 107 {
|
||||
return Color(val - 60)
|
||||
}
|
||||
|
||||
// don't change
|
||||
return c
|
||||
}
|
||||
|
||||
// ToFg always convert fg
|
||||
func (c Color) ToFg() Color {
|
||||
val := uint8(c)
|
||||
// option code, don't change
|
||||
if val < 10 {
|
||||
return c
|
||||
}
|
||||
return Color(Bg2Fg(val))
|
||||
}
|
||||
|
||||
// ToBg always convert bg
|
||||
func (c Color) ToBg() Color {
|
||||
val := uint8(c)
|
||||
// option code, don't change
|
||||
if val < 10 {
|
||||
return c
|
||||
}
|
||||
return Color(Fg2Bg(val))
|
||||
}
|
||||
|
||||
// Code convert to code string. eg "35"
|
||||
func (c Color) Code() string {
|
||||
return strconv.FormatInt(int64(c), 10)
|
||||
}
|
||||
|
||||
// String convert to code string. eg "35"
|
||||
func (c Color) String() string {
|
||||
return strconv.FormatInt(int64(c), 10)
|
||||
}
|
||||
|
||||
// IsBg check is background color
|
||||
func (c Color) IsBg() bool {
|
||||
val := uint8(c)
|
||||
return val >= bgBase && val <= bgMax || val >= hiBgBase && val <= hiBgMax
|
||||
}
|
||||
|
||||
// IsFg check is foreground color
|
||||
func (c Color) IsFg() bool {
|
||||
val := uint8(c)
|
||||
return val >= fgBase && val <= fgMax || val >= hiFgBase && val <= hiFgMax
|
||||
}
|
||||
|
||||
// IsOption check is option code: 0-9
|
||||
func (c Color) IsOption() bool { return uint8(c) < optMax }
|
||||
|
||||
// IsValid color value
|
||||
func (c Color) IsValid() bool { return uint8(c) < hiBgMax }
|
||||
|
||||
/*************************************************************
|
||||
* basic color maps
|
||||
*************************************************************/
|
||||
|
||||
// FgColors foreground colors map
|
||||
var FgColors = map[string]Color{
|
||||
"black": FgBlack,
|
||||
"red": FgRed,
|
||||
"green": FgGreen,
|
||||
"yellow": FgYellow,
|
||||
"blue": FgBlue,
|
||||
"magenta": FgMagenta,
|
||||
"cyan": FgCyan,
|
||||
"white": FgWhite,
|
||||
"default": FgDefault,
|
||||
}
|
||||
|
||||
// BgColors background colors map
|
||||
var BgColors = map[string]Color{
|
||||
"black": BgBlack,
|
||||
"red": BgRed,
|
||||
"green": BgGreen,
|
||||
"yellow": BgYellow,
|
||||
"blue": BgBlue,
|
||||
"magenta": BgMagenta,
|
||||
"cyan": BgCyan,
|
||||
"white": BgWhite,
|
||||
"default": BgDefault,
|
||||
}
|
||||
|
||||
// ExFgColors extra foreground colors map
|
||||
var ExFgColors = map[string]Color{
|
||||
"darkGray": FgDarkGray,
|
||||
"lightRed": FgLightRed,
|
||||
"lightGreen": FgLightGreen,
|
||||
"lightYellow": FgLightYellow,
|
||||
"lightBlue": FgLightBlue,
|
||||
"lightMagenta": FgLightMagenta,
|
||||
"lightCyan": FgLightCyan,
|
||||
"lightWhite": FgLightWhite,
|
||||
}
|
||||
|
||||
// ExBgColors extra background colors map
|
||||
var ExBgColors = map[string]Color{
|
||||
"darkGray": BgDarkGray,
|
||||
"lightRed": BgLightRed,
|
||||
"lightGreen": BgLightGreen,
|
||||
"lightYellow": BgLightYellow,
|
||||
"lightBlue": BgLightBlue,
|
||||
"lightMagenta": BgLightMagenta,
|
||||
"lightCyan": BgLightCyan,
|
||||
"lightWhite": BgLightWhite,
|
||||
}
|
||||
|
||||
// AllOptions color options map
|
||||
var AllOptions = map[string]Color{
|
||||
"reset": OpReset,
|
||||
"bold": OpBold,
|
||||
"fuzzy": OpFuzzy,
|
||||
"italic": OpItalic,
|
||||
"underscore": OpUnderscore,
|
||||
"blink": OpBlink,
|
||||
"reverse": OpReverse,
|
||||
"concealed": OpConcealed,
|
||||
}
|
||||
|
||||
// Bg2Fg bg color value to fg value
|
||||
func Bg2Fg(val uint8) uint8 {
|
||||
if val >= bgBase && val <= 47 { // is bg
|
||||
val = val - 10
|
||||
} else if val >= hiBgBase && val <= 107 { // is hi bg
|
||||
val = val - 10
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Fg2Bg fg color value to bg value
|
||||
func Fg2Bg(val uint8) uint8 {
|
||||
if val >= fgBase && val <= 37 { // is fg
|
||||
val = val + 10
|
||||
} else if val >= hiFgBase && val <= 97 { // is hi fg
|
||||
val = val + 10
|
||||
}
|
||||
return val
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
package ccolor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// output colored text like uses custom tag.
|
||||
const (
|
||||
// MatchExpr regex to match color tags
|
||||
//
|
||||
// Notice: golang 不支持反向引用. 即不支持使用 \1 引用第一个匹配 ([a-z=;]+)
|
||||
// MatchExpr = `<([a-z=;]+)>(.*?)<\/\1>`
|
||||
// 所以调整一下 统一使用 `</>` 来结束标签,例如 "<info>some text</>"
|
||||
//
|
||||
// - NOTE: 不支持自定义属性。如有需要请使用 gookit/color 包
|
||||
//
|
||||
// (?s:...) s - 让 "." 匹配换行
|
||||
MatchExpr = `<([0-9a-zA-Z_]+)>(?s:(.*?))<\/>`
|
||||
|
||||
// StripExpr regex used for removing color tags
|
||||
// StripExpr = `<[\/]?[a-zA-Z=;]+>`
|
||||
// 随着上面的做一些调整
|
||||
StripExpr = `<[\/]?[0-9a-zA-Z_=,;]*>`
|
||||
)
|
||||
|
||||
var (
|
||||
matchRegex = regexp.MustCompile(MatchExpr)
|
||||
stripRegex = regexp.MustCompile(StripExpr)
|
||||
)
|
||||
|
||||
/*************************************************************
|
||||
* internal defined color tags
|
||||
*************************************************************/
|
||||
|
||||
// There are internal defined fg color tags
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// <tag>content text</>
|
||||
//
|
||||
// @notice 加 0 在前面是为了防止之前的影响到现在的设置
|
||||
var colorTags = map[string]string{
|
||||
// basic tags
|
||||
"red": "0;31",
|
||||
"red1": "1;31", // with bold
|
||||
"redB": "1;31",
|
||||
"red_b": "1;31",
|
||||
"blue": "0;34",
|
||||
"blue1": "1;34", // with bold
|
||||
"blueB": "1;34",
|
||||
"blue_b": "1;34",
|
||||
"cyan": "0;36",
|
||||
"cyan1": "1;36", // with bold
|
||||
"cyanB": "1;36",
|
||||
"cyan_b": "1;36",
|
||||
"green": "0;32",
|
||||
"green1": "1;32", // with bold
|
||||
"greenB": "1;32",
|
||||
"green_b": "1;32",
|
||||
"black": "0;30",
|
||||
"white": "1;37",
|
||||
"default": "0;39", // no color
|
||||
"normal": "0;39", // no color
|
||||
"brown": "0;33", // #A52A2A
|
||||
"yellow": "0;33",
|
||||
"ylw": "0;33",
|
||||
"ylw0": "0;33",
|
||||
"yellowB": "1;33", // with bold
|
||||
"ylw1": "1;33",
|
||||
"ylwB": "1;33",
|
||||
"magenta": "0;35",
|
||||
"mga": "0;35", // short name
|
||||
"magentaB": "1;35", // with bold
|
||||
"magenta1": "1;35",
|
||||
"mgb": "1;35",
|
||||
"mga1": "1;35",
|
||||
"mgaB": "1;35",
|
||||
|
||||
// light/hi tags
|
||||
|
||||
"gray": "0;90",
|
||||
"darkGray": "0;90",
|
||||
"dark_gray": "0;90",
|
||||
"lightYellow": "0;93",
|
||||
"light_yellow": "0;93",
|
||||
"hiYellow": "0;93",
|
||||
"hi_yellow": "0;93",
|
||||
"hiYellowB": "1;93", // with bold
|
||||
"hi_yellow_b": "1;93",
|
||||
"lightMagenta": "0;95",
|
||||
"light_magenta": "0;95",
|
||||
"hiMagenta": "0;95",
|
||||
"hi_magenta": "0;95",
|
||||
"lightMagenta1": "1;95", // with bold
|
||||
"hiMagentaB": "1;95", // with bold
|
||||
"hi_magenta_b": "1;95",
|
||||
"lightRed": "0;91",
|
||||
"light_red": "0;91",
|
||||
"hiRed": "0;91",
|
||||
"hi_red": "0;91",
|
||||
"lightRedB": "1;91", // with bold
|
||||
"light_red_b": "1;91",
|
||||
"hi_red_b": "1;91",
|
||||
"lightGreen": "0;92",
|
||||
"light_green": "0;92",
|
||||
"hiGreen": "0;92",
|
||||
"hi_green": "0;92",
|
||||
"lightGreenB": "1;92",
|
||||
"light_green_b": "1;92",
|
||||
"hi_green_b": "1;92",
|
||||
"lightBlue": "0;94",
|
||||
"light_blue": "0;94",
|
||||
"hiBlue": "0;94",
|
||||
"hi_blue": "0;94",
|
||||
"lightBlueB": "1;94",
|
||||
"light_blue_b": "1;94",
|
||||
"hi_blue_b": "1;94",
|
||||
"lightCyan": "0;96",
|
||||
"light_cyan": "0;96",
|
||||
"hiCyan": "0;96",
|
||||
"hi_cyan": "0;96",
|
||||
"lightCyanB": "1;96",
|
||||
"light_cyan_b": "1;96",
|
||||
"hi_cyan_b": "1;96",
|
||||
"lightWhite": "0;97;40",
|
||||
"light_white": "0;97;40",
|
||||
|
||||
// option
|
||||
"bold": "1",
|
||||
"b": "1",
|
||||
"italic": "3",
|
||||
"i": "3", // italic
|
||||
"underscore": "4",
|
||||
"us": "4", // short name for 'underscore'
|
||||
"blink": "5",
|
||||
"fb": "6", // fast blink
|
||||
"reverse": "7",
|
||||
"st": "9", // strikethrough
|
||||
|
||||
// alert tags, like bootstrap's alert
|
||||
"suc": "1;32", // same "green" and "bold"
|
||||
"success": "1;32",
|
||||
"info": "0;32", // same "green",
|
||||
"comment": "0;33", // same "brown"
|
||||
"note": "36;1",
|
||||
"notice": "36;4",
|
||||
"warn": "0;1;33",
|
||||
"warning": "0;30;43",
|
||||
"primary": "0;34",
|
||||
"danger": "1;31", // same "red" but add bold
|
||||
"err": "97;41",
|
||||
"error": "97;41", // fg light white; bg red
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* parse color tags
|
||||
*************************************************************/
|
||||
|
||||
// Render parse color tags, return rendered string.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// text := Render("<info>hello</> <cyan>world</>!")
|
||||
// fmt.Println(text)
|
||||
func Render(a ...any) string {
|
||||
if len(a) == 0 {
|
||||
return ""
|
||||
}
|
||||
return ReplaceTag(fmt.Sprint(a...))
|
||||
}
|
||||
|
||||
// ReplaceTag parse string, replace color tag and return rendered string
|
||||
func ReplaceTag(str string) string { return ParseTagByEnv(str) }
|
||||
|
||||
// ParseTagByEnv parse given string. will check package setting.
|
||||
func ParseTagByEnv(str string) string {
|
||||
// disable OR not support color
|
||||
if shouldCleanColor() {
|
||||
return ClearTag(str)
|
||||
}
|
||||
return ParseTag(str)
|
||||
}
|
||||
|
||||
// ParseTag parse given string, replace color tag and return rendered string
|
||||
//
|
||||
// Use built in tags:
|
||||
//
|
||||
// <TAG_NAME>CONTENT</>
|
||||
// // e.g: `<info>message</>`
|
||||
//
|
||||
// TIP: code is from gookit/color package
|
||||
//
|
||||
// - Not support custom attributes
|
||||
// - Not support c256 or rgb color
|
||||
func ParseTag(str string) string {
|
||||
// not contains color tag
|
||||
if !strings.Contains(str, "</>") {
|
||||
return str
|
||||
}
|
||||
|
||||
// find color tags by regex. str eg: "<fg=white;bg=blue;op=bold>content</>"
|
||||
matched := matchRegex.FindAllStringSubmatch(str, -1)
|
||||
|
||||
// item: 0 full text 1 tag name 2 tag content
|
||||
for _, item := range matched {
|
||||
full, tag, body := item[0], item[1], item[2]
|
||||
|
||||
// use defined color tag name: "<info>content</>" -> tag: "info"
|
||||
if code := colorTags[tag]; len(code) > 0 {
|
||||
str = strings.Replace(str, full, RenderString(code, body), 1)
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// ClearTag clear-all tag for a string
|
||||
func ClearTag(s string) string {
|
||||
if !strings.Contains(s, "</>") {
|
||||
return s
|
||||
}
|
||||
return stripRegex.ReplaceAllString(s, "")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods
|
||||
*************************************************************/
|
||||
|
||||
// GetTagCode get color code by tag name
|
||||
func GetTagCode(name string) string { return colorTags[name] }
|
||||
|
||||
// ApplyTag for messages
|
||||
func ApplyTag(tag string, a ...any) string {
|
||||
return RenderCode(GetTagCode(tag), a...)
|
||||
}
|
||||
|
||||
// WrapTag wrap a tag for a string "<tag>content</>"
|
||||
func WrapTag(s string, tag string) string {
|
||||
if s == "" || tag == "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("<%s>%s</>", tag, s)
|
||||
}
|
||||
|
||||
// ColorTags get all internal color tags
|
||||
func ColorTags() map[string]string { return colorTags }
|
||||
|
||||
// IsDefinedTag is defined tag name
|
||||
func IsDefinedTag(name string) bool {
|
||||
_, ok := colorTags[name]
|
||||
return ok
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
package ccolor
|
||||
|
||||
/*************************************************************
|
||||
* quick use color print message
|
||||
*************************************************************/
|
||||
|
||||
// Redp print message with Red color
|
||||
func Redp(a ...any) { Red.Print(a...) }
|
||||
|
||||
// Redf print message with Red color
|
||||
func Redf(format string, a ...any) { Red.Printf(format, a...) }
|
||||
|
||||
// Redln print message line with Red color
|
||||
func Redln(a ...any) { Red.Println(a...) }
|
||||
|
||||
// Bluep print message with Blue color
|
||||
func Bluep(a ...any) { Blue.Print(a...) }
|
||||
|
||||
// Bluef print message with Blue color
|
||||
func Bluef(format string, a ...any) { Blue.Printf(format, a...) }
|
||||
|
||||
// Blueln print message line with Blue color
|
||||
func Blueln(a ...any) { Blue.Println(a...) }
|
||||
|
||||
// Cyanp print message with Cyan color
|
||||
func Cyanp(a ...any) { Cyan.Print(a...) }
|
||||
|
||||
// Cyanf print message with Cyan color
|
||||
func Cyanf(format string, a ...any) { Cyan.Printf(format, a...) }
|
||||
|
||||
// Cyanln print message line with Cyan color
|
||||
func Cyanln(a ...any) { Cyan.Println(a...) }
|
||||
|
||||
// Grayp print message with gray color
|
||||
func Grayp(a ...any) { Gray.Print(a...) }
|
||||
|
||||
// Grayf print message with gray color
|
||||
func Grayf(format string, a ...any) { Gray.Printf(format, a...) }
|
||||
|
||||
// Grayln print message line with gray color
|
||||
func Grayln(a ...any) { Gray.Println(a...) }
|
||||
|
||||
// Greenp print message with green color
|
||||
func Greenp(a ...any) { Green.Print(a...) }
|
||||
|
||||
// Greenf print message with green color
|
||||
func Greenf(format string, a ...any) { Green.Printf(format, a...) }
|
||||
|
||||
// Greenln print message line with green color
|
||||
func Greenln(a ...any) { Green.Println(a...) }
|
||||
|
||||
// Yellowp print message with yellow color
|
||||
func Yellowp(a ...any) { Yellow.Print(a...) }
|
||||
|
||||
// Yellowf print message with yellow color
|
||||
func Yellowf(format string, a ...any) { Yellow.Printf(format, a...) }
|
||||
|
||||
// Yellowln print message line with yellow color
|
||||
func Yellowln(a ...any) { Yellow.Println(a...) }
|
||||
|
||||
// Magentap print message with magenta color
|
||||
func Magentap(a ...any) { Magenta.Print(a...) }
|
||||
|
||||
// Magentaf print message with magenta color
|
||||
func Magentaf(format string, a ...any) { Magenta.Printf(format, a...) }
|
||||
|
||||
// Magentaln print message line with magenta color
|
||||
func Magentaln(a ...any) { Magenta.Println(a...) }
|
||||
|
||||
/*************************************************************
|
||||
* quick use style print message
|
||||
*************************************************************/
|
||||
|
||||
// Infop print message with info color
|
||||
func Infop(a ...any) { Info.Print(a...) }
|
||||
|
||||
// Infof print message with info style
|
||||
func Infof(format string, a ...any) { Info.Printf(format, a...) }
|
||||
|
||||
// Infoln print message with info style
|
||||
func Infoln(a ...any) { Info.Println(a...) }
|
||||
|
||||
// Successp print message with success color
|
||||
func Successp(a ...any) { Success.Print(a...) }
|
||||
|
||||
// Successf print message with success style
|
||||
func Successf(format string, a ...any) { Success.Printf(format, a...) }
|
||||
|
||||
// Successln print message with success style
|
||||
func Successln(a ...any) { Success.Println(a...) }
|
||||
|
||||
// Errorp print message with error color
|
||||
func Errorp(a ...any) { Error.Print(a...) }
|
||||
|
||||
// Errorf print message with error style
|
||||
func Errorf(format string, a ...any) { Error.Printf(format, a...) }
|
||||
|
||||
// Errorln print message with error style
|
||||
func Errorln(a ...any) { Error.Println(a...) }
|
||||
|
||||
// Warnp print message with warn color
|
||||
func Warnp(a ...any) { Warn.Print(a...) }
|
||||
|
||||
// Warnf print message with warn style
|
||||
func Warnf(format string, a ...any) { Warn.Printf(format, a...) }
|
||||
|
||||
// Warnln print message with warn style
|
||||
func Warnln(a ...any) { Warn.Println(a...) }
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package ccolor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// String color style string. TODO
|
||||
// eg:
|
||||
//
|
||||
// s := String("red,bold")
|
||||
// s.Println("some text message")
|
||||
type String string
|
||||
|
||||
// Style for color render.
|
||||
type Style struct {
|
||||
Fg Color
|
||||
Bg Color
|
||||
Opts []Color
|
||||
}
|
||||
|
||||
// NewStyle fg, bg and options
|
||||
func NewStyle(fg Color, bg Color, opts ...Color) *Style {
|
||||
return &Style{
|
||||
Fg: fg,
|
||||
Bg: bg,
|
||||
Opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// Print like fmt.Print, but with color
|
||||
func (s *Style) Print(v ...any) {
|
||||
doPrint(s.String(), fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Println like fmt.Println, but with color
|
||||
func (s *Style) Println(v ...any) {
|
||||
doPrintln(s.String(), v)
|
||||
}
|
||||
|
||||
// Printf render and print text
|
||||
func (s *Style) Printf(format string, v ...any) {
|
||||
doPrint(s.String(), fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Sprint like fmt.Sprint, but with color
|
||||
func (s *Style) Sprint(v ...any) string {
|
||||
return RenderString(s.String(), fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// Sprintln like fmt.Sprintln, but with color
|
||||
func (s *Style) Sprintln(v ...any) string {
|
||||
return RenderWithSpaces(s.String(), v...)
|
||||
}
|
||||
|
||||
// Sprintf format and render message.
|
||||
func (s *Style) Sprintf(format string, v ...any) string {
|
||||
return RenderString(s.String(), fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
// Fprint like fmt.Fprint, but with color
|
||||
func (s *Style) Fprint(w io.Writer, v ...any) {
|
||||
doPrintTo(w, s.String(), fmt.Sprint(v...))
|
||||
}
|
||||
|
||||
// String convert style setting to color code string.
|
||||
func (s *Style) String() string {
|
||||
var codes []string
|
||||
if s.Fg.IsFg() {
|
||||
codes = append(codes, s.Fg.String())
|
||||
}
|
||||
if s.Bg.IsBg() {
|
||||
codes = append(codes, s.Bg.String())
|
||||
}
|
||||
|
||||
if len(s.Opts) > 0 {
|
||||
codes = append(codes, ColorsToCode(s.Opts...))
|
||||
}
|
||||
|
||||
if len(codes) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(codes, ";")
|
||||
}
|
||||
|
||||
var (
|
||||
// Info color style
|
||||
Info = &Style{Fg: FgGreen}
|
||||
// Warn color style
|
||||
Warn = &Style{Fg: FgYellow}
|
||||
// Error color style
|
||||
Error = NewStyle(FgLightWhite, BgRed)
|
||||
// Debug color style
|
||||
Debug = &Style{Fg: FgCyan}
|
||||
// Success color style
|
||||
Success = &Style{Fg: FgGreen, Opts: []Color{OpBold}}
|
||||
)
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
package ccolor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/x/termenv"
|
||||
)
|
||||
|
||||
// ColorsToCode convert colors to code. return like "32;45;3"
|
||||
func ColorsToCode(colors ...Color) string {
|
||||
if len(colors) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var codes []string
|
||||
for _, color := range colors {
|
||||
codes = append(codes, color.String())
|
||||
}
|
||||
|
||||
return strings.Join(codes, ";")
|
||||
}
|
||||
|
||||
func shouldCleanColor() bool {
|
||||
return termenv.NoColor() || !termenv.IsSupportColor()
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* render color code
|
||||
*************************************************************/
|
||||
|
||||
// RenderCode render message by color code.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// msg := RenderCode("3;32;45", "some", "message")
|
||||
func RenderCode(code string, args ...any) string {
|
||||
var message string
|
||||
if ln := len(args); ln == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
message = fmt.Sprint(args...)
|
||||
if len(code) == 0 {
|
||||
return message
|
||||
}
|
||||
|
||||
// disabled OR not support color
|
||||
if shouldCleanColor() {
|
||||
return ClearCode(message)
|
||||
}
|
||||
|
||||
// return fmt.Sprintf(FullColorTpl, code, message)
|
||||
return StartSet + code + "m" + message + ResetSet
|
||||
}
|
||||
|
||||
// RenderString render a string with color code.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// msg := RenderString("3;32;45", "a message")
|
||||
func RenderString(code string, str string) string {
|
||||
if len(code) == 0 || str == "" {
|
||||
return str
|
||||
}
|
||||
|
||||
// disabled OR not support color
|
||||
if shouldCleanColor() {
|
||||
return ClearCode(str)
|
||||
}
|
||||
|
||||
// return fmt.Sprintf(FullColorTpl, code, str)
|
||||
return StartSet + code + "m" + str + ResetSet
|
||||
}
|
||||
|
||||
// RenderWithSpaces Render code with spaces.
|
||||
// If the number of args is > 1, a space will be added between the args
|
||||
func RenderWithSpaces(code string, args ...any) string {
|
||||
msg := formatLikePrintln(args)
|
||||
if len(code) == 0 {
|
||||
return msg
|
||||
}
|
||||
|
||||
// disabled OR not support color
|
||||
if shouldCleanColor() {
|
||||
return ClearCode(msg)
|
||||
}
|
||||
return StartSet + code + "m" + msg + ResetSet
|
||||
}
|
||||
|
||||
// ClearCode clear color codes.
|
||||
//
|
||||
// eg:
|
||||
//
|
||||
// "\033[36;1mText\x1b[0m" -> "Text"
|
||||
func ClearCode(str string) string {
|
||||
if !strings.Contains(str, CodeSuffix) {
|
||||
return str
|
||||
}
|
||||
return codeRegex.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods for print
|
||||
*************************************************************/
|
||||
|
||||
// new implementation, support render full color code on pwsh.exe, cmd.exe
|
||||
func doPrint(code, str string) {
|
||||
_, lastErr = fmt.Fprint(output, RenderString(code, str))
|
||||
}
|
||||
|
||||
func doPrintTo(w io.Writer, code, str string) {
|
||||
_, lastErr = fmt.Fprint(w, RenderString(code, str))
|
||||
}
|
||||
|
||||
// new implementation, support render full color code on pwsh.exe, cmd.exe
|
||||
func doPrintln(code string, args []any) {
|
||||
_, lastErr = fmt.Fprintln(output, RenderString(code, formatLikePrintln(args)))
|
||||
}
|
||||
|
||||
// use Println, will add spaces for each arg
|
||||
func formatLikePrintln(args []any) (message string) {
|
||||
if ln := len(args); ln == 0 {
|
||||
message = ""
|
||||
} else if ln == 1 {
|
||||
message = fmt.Sprint(args[0])
|
||||
} else {
|
||||
message = fmt.Sprintln(args...)
|
||||
// clear last "\n"
|
||||
message = message[:len(message)-1]
|
||||
}
|
||||
return
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
// Package encodes provide some util for encode/decode data
|
||||
package encodes
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// BaseEncoder interface
|
||||
type BaseEncoder interface {
|
||||
Encode(dst []byte, src []byte)
|
||||
EncodeToString(src []byte) string
|
||||
Decode(dst []byte, src []byte) (n int, err error)
|
||||
DecodeString(s string) ([]byte, error)
|
||||
}
|
||||
|
||||
//
|
||||
// -------------------- base encode --------------------
|
||||
//
|
||||
|
||||
// base32 encoding with no padding
|
||||
var (
|
||||
B32Std = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
B32Hex = base32.HexEncoding.WithPadding(base32.NoPadding)
|
||||
)
|
||||
|
||||
// B32Encode base32 encode
|
||||
func B32Encode(str string) string {
|
||||
return B32Std.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
// B32Decode base32 decode
|
||||
func B32Decode(str string) string {
|
||||
dec, _ := B32Std.DecodeString(str)
|
||||
return string(dec)
|
||||
}
|
||||
|
||||
// base64 encoding with no padding
|
||||
var (
|
||||
B64Std = base64.StdEncoding.WithPadding(base64.NoPadding)
|
||||
B64URL = base64.URLEncoding.WithPadding(base64.NoPadding)
|
||||
)
|
||||
|
||||
// B64Encode base64 encode
|
||||
func B64Encode(str string) string {
|
||||
return B64Std.EncodeToString([]byte(str))
|
||||
}
|
||||
|
||||
// B64EncodeBytes base64 encode
|
||||
func B64EncodeBytes(src []byte) []byte {
|
||||
buf := make([]byte, B64Std.EncodedLen(len(src)))
|
||||
B64Std.Encode(buf, src)
|
||||
return buf
|
||||
}
|
||||
|
||||
// B64Decode base64 decode
|
||||
func B64Decode(str string) string {
|
||||
dec, _ := B64Std.DecodeString(str)
|
||||
return string(dec)
|
||||
}
|
||||
|
||||
// B64DecodeBytes base64 decode
|
||||
func B64DecodeBytes(str []byte) []byte {
|
||||
dbuf := make([]byte, B64Std.DecodedLen(len(str)))
|
||||
n, _ := B64Std.Decode(dbuf, str)
|
||||
return dbuf[:n]
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
# GoInfo
|
||||
|
||||
`goutil/x/goinfo` provide some useful info for golang.
|
||||
|
||||
> Github: https://github.com/gookit/goutil
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/x/goinfo
|
||||
```
|
||||
|
||||
## Go docs
|
||||
|
||||
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil)
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
gover := goinfo.GoVersion() // eg: "1.15.6"
|
||||
|
||||
```
|
||||
|
||||
## Testings
|
||||
|
||||
```shell
|
||||
go test -v ./goinfo/...
|
||||
```
|
||||
|
||||
Test limit by regexp:
|
||||
|
||||
```shell
|
||||
go test -v -run ^TestSetByKeys ./goinfo/...
|
||||
```
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package goinfo
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/gookit/goutil/strutil"
|
||||
)
|
||||
|
||||
// FullFcName struct.
|
||||
type FullFcName struct {
|
||||
// FullName eg: "github.com/gookit/goutil/x/goinfo.PanicIf"
|
||||
FullName string
|
||||
pkgPath string // "github.com/gookit/goutil/x/goinfo"
|
||||
pkgName string // "goinfo"
|
||||
funcName string // "PanicIf"
|
||||
}
|
||||
|
||||
// Parse the full func name.
|
||||
func (ffn *FullFcName) Parse() {
|
||||
if ffn.funcName != "" {
|
||||
return
|
||||
}
|
||||
|
||||
i := strings.LastIndex(ffn.FullName, "/")
|
||||
|
||||
ffn.pkgPath = ffn.FullName[:i+1]
|
||||
// spilt get pkg and func name
|
||||
ffn.pkgName, ffn.funcName = strutil.MustCut(ffn.FullName[i+1:], ".")
|
||||
ffn.pkgPath += ffn.pkgName
|
||||
}
|
||||
|
||||
// PkgPath string get. eg: github.com/gookit/goutil/x/goinfo
|
||||
func (ffn *FullFcName) PkgPath() string {
|
||||
ffn.Parse()
|
||||
return ffn.pkgPath
|
||||
}
|
||||
|
||||
// PkgName string get. eg: goinfo
|
||||
func (ffn *FullFcName) PkgName() string {
|
||||
ffn.Parse()
|
||||
return ffn.pkgName
|
||||
}
|
||||
|
||||
// FuncName get short func name. eg: PanicIf
|
||||
func (ffn *FullFcName) FuncName() string {
|
||||
ffn.Parse()
|
||||
return ffn.funcName
|
||||
}
|
||||
|
||||
// String get full func name string, pkg path and func name.
|
||||
func (ffn *FullFcName) String() string {
|
||||
return ffn.FullName
|
||||
}
|
||||
|
||||
// FuncName get full func name, contains pkg path.
|
||||
//
|
||||
// eg:
|
||||
//
|
||||
// // OUTPUT: github.com/gookit/goutil/x/goinfo.PkgName
|
||||
// goinfo.FuncName(goinfo.PkgName)
|
||||
func FuncName(fn any) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
|
||||
}
|
||||
|
||||
// CutFuncName get pkg path and short func name
|
||||
// eg:
|
||||
//
|
||||
// "github.com/gookit/goutil/x/goinfo.FuncName" => [github.com/gookit/goutil/x/goinfo, FuncName]
|
||||
func CutFuncName(fullFcName string) (pkgPath, shortFnName string) {
|
||||
ffn := FullFcName{FullName: fullFcName}
|
||||
return ffn.PkgPath(), ffn.FuncName()
|
||||
}
|
||||
|
||||
// PkgName get current package name
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// fullFcName := goinfo.FuncName(fn)
|
||||
// pgkName := goinfo.PkgName(fullFcName)
|
||||
func PkgName(fullFcName string) string {
|
||||
for {
|
||||
lastPeriod := strings.LastIndex(fullFcName, ".")
|
||||
lastSlash := strings.LastIndex(fullFcName, "/")
|
||||
|
||||
if lastPeriod > lastSlash {
|
||||
fullFcName = fullFcName[:lastPeriod]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return fullFcName
|
||||
}
|
||||
|
||||
// GoodFuncName reports whether the function name is a valid identifier.
|
||||
func GoodFuncName(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for i, r := range name {
|
||||
switch {
|
||||
case r == '_':
|
||||
case i == 0 && !unicode.IsLetter(r):
|
||||
return false
|
||||
case !unicode.IsLetter(r) && !unicode.IsDigit(r):
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
// Package goinfo provide some standard util functions for go.
|
||||
package goinfo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GoVersion get go runtime version. eg: "1.18.2"
|
||||
func GoVersion() string {
|
||||
return runtime.Version()[2:]
|
||||
}
|
||||
|
||||
// GoInfo define
|
||||
//
|
||||
// On os by:
|
||||
//
|
||||
// go env GOVERSION GOOS GOARCH
|
||||
// go version // "go version go1.19 darwin/amd64"
|
||||
type GoInfo struct {
|
||||
Version string
|
||||
GoOS string
|
||||
Arch string
|
||||
}
|
||||
|
||||
// match "go version go1.19 darwin/amd64"
|
||||
var goVerRegex = regexp.MustCompile(`\sgo([\d.]+)\s(\w+)/(\w+)`)
|
||||
|
||||
// ParseGoVersion get info by parse `go version` results.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// line, err := sysutil.ExecLine("go version")
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// info, err := goinfo.ParseGoVersion()
|
||||
// dump.P(info)
|
||||
func ParseGoVersion(line string) (*GoInfo, error) {
|
||||
// eg: [" go1.19 darwin/amd64", "1.19", "darwin", "amd64"]
|
||||
lines := goVerRegex.FindStringSubmatch(line)
|
||||
if len(lines) != 4 {
|
||||
return nil, errors.New("input go version info is invalid")
|
||||
}
|
||||
|
||||
info := &GoInfo{
|
||||
Version: strings.TrimPrefix(lines[1], "go"),
|
||||
}
|
||||
info.GoOS = lines[2]
|
||||
info.Arch = lines[3]
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// OsGoInfo fetch and parse
|
||||
func OsGoInfo() (*GoInfo, error) {
|
||||
cmdArgs := []string{"env", "GOVERSION", "GOOS", "GOARCH"}
|
||||
bs, err := exec.Command("go", cmdArgs...).Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(string(bs)), "\n")
|
||||
if len(lines) != len(cmdArgs)-1 {
|
||||
return nil, errors.New("returns go info is not full")
|
||||
}
|
||||
|
||||
info := &GoInfo{}
|
||||
info.Version = strings.TrimPrefix(lines[0], "go")
|
||||
info.GoOS = lines[1]
|
||||
info.Arch = lines[2]
|
||||
|
||||
return info, nil
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package goinfo
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/x/basefn"
|
||||
)
|
||||
|
||||
// some commonly consts
|
||||
var (
|
||||
DefStackLen = 10000
|
||||
MaxStackLen = 100000
|
||||
)
|
||||
|
||||
// GetCallStacks stacks is a wrapper for runtime.
|
||||
// If all is true, Stack that attempts to recover the data for all goroutines.
|
||||
//
|
||||
// from glog package
|
||||
func GetCallStacks(all bool) []byte {
|
||||
// We don't know how big the traces are, so grow a few times if they don't fit.
|
||||
// Start large, though.
|
||||
n := DefStackLen
|
||||
if all {
|
||||
n = MaxStackLen
|
||||
}
|
||||
|
||||
// 4<<10 // 4 KB should be enough
|
||||
var trace []byte
|
||||
for i := 0; i < 10; i++ {
|
||||
trace = make([]byte, n)
|
||||
bts := runtime.Stack(trace, all)
|
||||
if bts < len(trace) {
|
||||
return trace[:bts]
|
||||
}
|
||||
n *= 2
|
||||
}
|
||||
return trace
|
||||
}
|
||||
|
||||
// GetCallerInfo get caller func name and with base filename and line.
|
||||
//
|
||||
// returns:
|
||||
//
|
||||
// github.com/gookit/goutil/x/goinfo_test.someFunc2(),stack_test.go:26
|
||||
func GetCallerInfo(skip int) string {
|
||||
skip++ // ignore current func
|
||||
cs := GetCallersInfo(skip, skip+1)
|
||||
return basefn.FirstOr(cs, "")
|
||||
}
|
||||
|
||||
// SimpleCallersInfo returns an array of strings containing
|
||||
// the func name, file and line number of each stack frame leading.
|
||||
func SimpleCallersInfo(skip, num int) []string {
|
||||
skip++ // ignore current func
|
||||
return GetCallersInfo(skip, skip+num)
|
||||
}
|
||||
|
||||
// GetCallersInfo returns an array of strings containing
|
||||
// the func name, file and line number of each stack frame leading.
|
||||
//
|
||||
// NOTICE: max should > skip
|
||||
func GetCallersInfo(skip, max int) []string {
|
||||
var (
|
||||
pc uintptr
|
||||
ok bool
|
||||
line int
|
||||
file, name string
|
||||
)
|
||||
|
||||
callers := make([]string, 0, max-skip)
|
||||
for i := skip; i < max; i++ {
|
||||
pc, file, line, ok = runtime.Caller(i)
|
||||
if !ok {
|
||||
// The breaks below failed to terminate the loop, and we ran off the
|
||||
// end of the call stack.
|
||||
break
|
||||
}
|
||||
|
||||
// This is a huge edge case, but it will panic if this is the case
|
||||
if file == "<autogenerated>" {
|
||||
break
|
||||
}
|
||||
|
||||
fc := runtime.FuncForPC(pc)
|
||||
if fc == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if strings.ContainsRune(file, '/') {
|
||||
name = fc.Name()
|
||||
file = filepath.Base(file)
|
||||
// eg: github.com/gookit/goutil/x/goinfo_test.someFunc2(),stack_test.go:26
|
||||
callers = append(callers, name+"(),"+file+":"+strconv.Itoa(line))
|
||||
}
|
||||
|
||||
// Drop the package
|
||||
// segments := strings.Split(name, ".")
|
||||
// name = segments[len(segments)-1]
|
||||
}
|
||||
|
||||
return callers
|
||||
}
|
||||
|
||||
// CallerInfo struct
|
||||
type CallerInfo struct {
|
||||
PC uintptr
|
||||
Fc *runtime.Func
|
||||
File string
|
||||
Line int
|
||||
}
|
||||
|
||||
// String convert
|
||||
func (ci *CallerInfo) String() string {
|
||||
return ci.File + ":" + strconv.Itoa(ci.Line)
|
||||
}
|
||||
|
||||
// CallerFilterFunc type
|
||||
type CallerFilterFunc func(file string, fc *runtime.Func) bool
|
||||
|
||||
// CallersInfos returns an array of the CallerInfo, can with filters
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cs := sysutil.CallersInfos(3, 2)
|
||||
// for _, ci := range cs {
|
||||
// fc := runtime.FuncForPC(pc)
|
||||
// // maybe need check fc = nil
|
||||
// fnName = fc.Name()
|
||||
// }
|
||||
func CallersInfos(skip, num int, filters ...CallerFilterFunc) []*CallerInfo {
|
||||
filterLn := len(filters)
|
||||
callers := make([]*CallerInfo, 0, num)
|
||||
for i := skip; i < skip+num; i++ {
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
// The breaks below failed to terminate the loop, and we ran off the
|
||||
// end of the call stack.
|
||||
break
|
||||
}
|
||||
|
||||
fc := runtime.FuncForPC(pc)
|
||||
if fc == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if filterLn > 0 && filters[0] != nil {
|
||||
// filter - return false for skip
|
||||
if !filters[0](file, fc) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// collecting
|
||||
callers = append(callers, &CallerInfo{
|
||||
PC: pc,
|
||||
Fc: fc,
|
||||
File: file,
|
||||
Line: line,
|
||||
})
|
||||
}
|
||||
|
||||
return callers
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
# Stdio
|
||||
|
||||
`stdio` provide some standard IO util functions.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/gookit/goutil/x/stdio
|
||||
```
|
||||
|
||||
## Go docs
|
||||
|
||||
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/x/stdio)
|
||||
|
||||
## Usage
|
||||
|
||||
Please see tests.
|
||||
|
||||
## Testings
|
||||
|
||||
```shell
|
||||
go test -v ./stdio/...
|
||||
```
|
||||
|
||||
Test limit by regexp:
|
||||
|
||||
```shell
|
||||
go test -v -run ^TestSetByKeys ./stdio/...
|
||||
```
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package stdio
|
||||
|
||||
import "io"
|
||||
|
||||
// Flusher interface
|
||||
type Flusher interface {
|
||||
Flush() error
|
||||
}
|
||||
|
||||
// Syncer interface
|
||||
type Syncer interface {
|
||||
Sync() error
|
||||
}
|
||||
|
||||
// FlushWriter is the interface satisfied by logging destinations.
|
||||
type FlushWriter interface {
|
||||
Flusher
|
||||
// Writer the output writer
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// FlushCloseWriter is the interface satisfied by logging destinations.
|
||||
type FlushCloseWriter interface {
|
||||
Flusher
|
||||
// WriteCloser the output writer
|
||||
io.WriteCloser
|
||||
}
|
||||
|
||||
// SyncCloseWriter is the interface satisfied by logging destinations.
|
||||
// such as os.File
|
||||
type SyncCloseWriter interface {
|
||||
Syncer
|
||||
// WriteCloser the output writer
|
||||
io.WriteCloser
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Fprint to writer, will ignore error
|
||||
func Fprint(w io.Writer, a ...any) {
|
||||
_, _ = fmt.Fprint(w, a...)
|
||||
}
|
||||
|
||||
// Fprintf to writer, will ignore error
|
||||
func Fprintf(w io.Writer, tpl string, vs ...any) {
|
||||
_, _ = fmt.Fprintf(w, tpl, vs...)
|
||||
}
|
||||
|
||||
// Fprintln to writer, will ignore error
|
||||
func Fprintln(w io.Writer, a ...any) {
|
||||
_, _ = fmt.Fprintln(w, a...)
|
||||
}
|
||||
|
||||
// WriteStringTo a writer, will ignore error
|
||||
func WriteStringTo(w io.Writer, ss ...string) {
|
||||
if len(ss) == 1 {
|
||||
_, _ = io.WriteString(w, ss[0])
|
||||
} else if len(ss) > 1 {
|
||||
_, _ = io.WriteString(w, strings.Join(ss, ""))
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
// Package stdio provide some standard IO util functions.
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DiscardReader anything from the reader
|
||||
func DiscardReader(src io.Reader) {
|
||||
_, _ = io.Copy(io.Discard, src)
|
||||
}
|
||||
|
||||
// ReadString read contents from io.Reader, return empty string on error
|
||||
func ReadString(r io.Reader) string {
|
||||
bs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// MustReadReader read contents from io.Reader, will panic on error
|
||||
func MustReadReader(r io.Reader) []byte {
|
||||
bs, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return bs
|
||||
}
|
||||
|
||||
// NewIOReader instance by input: string, bytes, io.Reader
|
||||
func NewIOReader(in any) io.Reader {
|
||||
switch typIn := in.(type) {
|
||||
case []byte:
|
||||
return bytes.NewReader(typIn)
|
||||
case string:
|
||||
return strings.NewReader(typIn)
|
||||
case io.Reader:
|
||||
return typIn
|
||||
}
|
||||
panic("invalid input type for create reader")
|
||||
}
|
||||
|
||||
// NewScanner instance by input data or reader
|
||||
func NewScanner(in any) *bufio.Scanner {
|
||||
switch typIn := in.(type) {
|
||||
case io.Reader:
|
||||
return bufio.NewScanner(typIn)
|
||||
case []byte:
|
||||
return bufio.NewScanner(bytes.NewReader(typIn))
|
||||
case string:
|
||||
return bufio.NewScanner(strings.NewReader(typIn))
|
||||
case *bufio.Scanner:
|
||||
return typIn
|
||||
default:
|
||||
panic("invalid input type for create scanner")
|
||||
}
|
||||
}
|
||||
|
||||
// SafeClose close io.Closer, ignore error
|
||||
func SafeClose(c io.Closer) {
|
||||
_ = c.Close()
|
||||
}
|
||||
|
||||
// WriteByte to stdout, will ignore error
|
||||
func WriteByte(b byte) {
|
||||
_, _ = os.Stdout.Write([]byte{b})
|
||||
}
|
||||
|
||||
// WriteBytes to stdout, will ignore error
|
||||
func WriteBytes(bs []byte) {
|
||||
_, _ = os.Stdout.Write(bs)
|
||||
}
|
||||
|
||||
// WritelnBytes to stdout, will ignore error
|
||||
func WritelnBytes(bs []byte) {
|
||||
_, _ = os.Stdout.Write(bs)
|
||||
_, _ = os.Stdout.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
// WriteString to stdout. will ignore error
|
||||
func WriteString(s string) {
|
||||
_, _ = os.Stdout.WriteString(s)
|
||||
}
|
||||
|
||||
// Writeln string to stdout. will ignore error
|
||||
func Writeln(s string) {
|
||||
_, _ = os.Stdout.WriteString(s)
|
||||
_, _ = os.Stdout.Write([]byte("\n"))
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package stdio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriteWrapper warp io.Writer support more operate methods.
|
||||
type WriteWrapper struct {
|
||||
Out io.Writer
|
||||
}
|
||||
|
||||
// WrapW instance
|
||||
func WrapW(w io.Writer) *WriteWrapper {
|
||||
return &WriteWrapper{Out: w}
|
||||
}
|
||||
|
||||
// NewWriteWrapper instance
|
||||
func NewWriteWrapper(w io.Writer) *WriteWrapper {
|
||||
return &WriteWrapper{Out: w}
|
||||
}
|
||||
|
||||
// Write bytes data
|
||||
func (w *WriteWrapper) Write(p []byte) (n int, err error) {
|
||||
return w.Out.Write(p)
|
||||
}
|
||||
|
||||
// Writef data to output
|
||||
func (w *WriteWrapper) Writef(tpl string, vs ...any) (n int, err error) {
|
||||
return fmt.Fprintf(w.Out, tpl, vs...)
|
||||
}
|
||||
|
||||
// WriteByte data
|
||||
func (w *WriteWrapper) WriteByte(c byte) error {
|
||||
_, err := w.Out.Write([]byte{c})
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteString data
|
||||
func (w *WriteWrapper) WriteString(s string) (n int, err error) {
|
||||
if sw, ok := w.Out.(io.StringWriter); ok {
|
||||
return sw.WriteString(s)
|
||||
}
|
||||
return w.Out.Write([]byte(s))
|
||||
}
|
||||
|
||||
// String get write data string
|
||||
func (w *WriteWrapper) String() string {
|
||||
if sw, ok := w.Out.(fmt.Stringer); ok {
|
||||
return sw.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
package termenv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ColorLevel is the color level supported by a terminal.
|
||||
type ColorLevel uint8
|
||||
|
||||
const (
|
||||
TermColorNone ColorLevel = iota // not support color
|
||||
TermColor16 // 16(4bit) ANSI color supported
|
||||
TermColor256 // 256(8bit) color supported
|
||||
TermColorTrue // support TRUE(RGB) color
|
||||
)
|
||||
|
||||
// String returns the string name of the color level.
|
||||
func (l ColorLevel) String() string {
|
||||
switch l {
|
||||
case TermColor16:
|
||||
return "ansiColor"
|
||||
case TermColor256:
|
||||
return "256color"
|
||||
case TermColorTrue:
|
||||
return "trueColor"
|
||||
default:
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
|
||||
// NoColor returns true if the NO_COLOR environment variable is set.
|
||||
func NoColor() bool { return noColor }
|
||||
|
||||
// TermColorLevel returns the color support level for the current terminal.
|
||||
func TermColorLevel() ColorLevel { return colorLevel }
|
||||
|
||||
// IsSupportColor returns true if the terminal supports color.
|
||||
func IsSupportColor() bool { return colorLevel > TermColorNone }
|
||||
|
||||
// IsSupport256Color returns true if the terminal supports 256 colors.
|
||||
func IsSupport256Color() bool { return colorLevel >= TermColor256 }
|
||||
|
||||
// IsSupportTrueColor returns true if the terminal supports true color.
|
||||
func IsSupportTrueColor() bool { return colorLevel == TermColorTrue }
|
||||
|
||||
//
|
||||
// ---------------- Force set color support ----------------
|
||||
//
|
||||
|
||||
var backLevel ColorLevel
|
||||
|
||||
// SetColorLevel value force.
|
||||
func SetColorLevel(level ColorLevel) {
|
||||
// backup old value
|
||||
backLevel = colorLevel
|
||||
|
||||
// force set color level
|
||||
colorLevel = level
|
||||
supportColor = level > TermColorNone
|
||||
noColor = supportColor == false
|
||||
}
|
||||
|
||||
// DisableColor in the current terminal
|
||||
func DisableColor() {
|
||||
// backup old value
|
||||
backLevel = colorLevel
|
||||
|
||||
// force disable color
|
||||
noColor = true
|
||||
supportColor = false
|
||||
colorLevel = TermColorNone
|
||||
}
|
||||
|
||||
// ForceEnableColor flags value. TIP: use for unit testing.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ccolor.ForceEnableColor()
|
||||
// defer ccolor.RevertColorSupport()
|
||||
func ForceEnableColor() {
|
||||
// backup old value
|
||||
backLevel = colorLevel
|
||||
|
||||
// force enables color
|
||||
noColor = false
|
||||
supportColor = true
|
||||
colorLevel = TermColor256
|
||||
// return colorLevel
|
||||
}
|
||||
|
||||
// RevertColorSupport flags to init value.
|
||||
func RevertColorSupport() {
|
||||
// revert color flags var
|
||||
colorLevel = backLevel
|
||||
supportColor = backLevel > TermColorNone
|
||||
noColor = os.Getenv("NO_COLOR") == ""
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* helper methods for detect color supports
|
||||
*************************************************************/
|
||||
|
||||
// DetectColorLevel for current env
|
||||
//
|
||||
// NOTICE: The method will detect terminal info each time.
|
||||
//
|
||||
// if only want to get current color level, please direct call IsSupportColor() or TermColorLevel()
|
||||
func DetectColorLevel() ColorLevel {
|
||||
level, _ := detectTermColorLevel()
|
||||
return level
|
||||
}
|
||||
|
||||
// on TERM=screen: not support true-color
|
||||
const noTrueColorTerm = "screen"
|
||||
|
||||
// detect terminal color support level
|
||||
//
|
||||
// refer https://github.com/Delta456/box-cli-maker
|
||||
func detectTermColorLevel() (level ColorLevel, needVTP bool) {
|
||||
isWin := runtime.GOOS == "windows"
|
||||
termVal := os.Getenv("TERM")
|
||||
|
||||
if termVal != noTrueColorTerm {
|
||||
// On JetBrains Terminal
|
||||
// - TERM value not set, but support true-color
|
||||
// env:
|
||||
// TERMINAL_EMULATOR=JetBrains-JediTerm
|
||||
val := os.Getenv("TERMINAL_EMULATOR")
|
||||
if val == "JetBrains-JediTerm" {
|
||||
debugf("True Color support on JetBrains-JediTerm, is win: %v", isWin)
|
||||
return TermColorTrue, false
|
||||
}
|
||||
}
|
||||
|
||||
level = detectColorLevelFromEnv(termVal, isWin)
|
||||
|
||||
// fallback: simple detect by TERM value string.
|
||||
if level == TermColorNone {
|
||||
debugf("level=none - fallback check special term color support")
|
||||
level, needVTP = detectSpecialTermColor(termVal)
|
||||
debugf("color level by detectSpecialTermColor: %s", level.String())
|
||||
} else {
|
||||
debugf("color level by detectColorLevelFromEnv: %s", level.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// detectColorFromEnv returns the color level COLORTERM, FORCE_COLOR,
|
||||
// TERM_PROGRAM, or determined from the TERM environment variable.
|
||||
//
|
||||
// refer the github.com/xo/terminfo.ColorLevelFromEnv()
|
||||
// https://en.wikipedia.org/wiki/Terminfo
|
||||
func detectColorLevelFromEnv(termVal string, isWin bool) ColorLevel {
|
||||
if termVal == noTrueColorTerm { // on TERM=screen: not support true-color
|
||||
return TermColor256
|
||||
}
|
||||
|
||||
// check for overriding environment variables
|
||||
colorTerm, termProg, forceColor := os.Getenv("COLORTERM"), os.Getenv("TERM_PROGRAM"), os.Getenv("FORCE_COLOR")
|
||||
switch {
|
||||
case strings.Contains(colorTerm, "truecolor") || strings.Contains(colorTerm, "24bit"):
|
||||
return TermColorTrue
|
||||
case colorTerm != "" || forceColor != "":
|
||||
return TermColor16
|
||||
case termProg == "Apple_Terminal":
|
||||
return TermColor256
|
||||
case termProg == "Terminus" || termProg == "Hyper":
|
||||
return TermColorTrue
|
||||
case termProg == "iTerm.app":
|
||||
// check iTerm version
|
||||
termVer := os.Getenv("TERM_PROGRAM_VERSION")
|
||||
if termVer != "" {
|
||||
i, err := strconv.Atoi(strings.Split(termVer, ".")[0])
|
||||
if err != nil {
|
||||
setLastErr(errors.New("invalid TERM_PROGRAM_VERSION=" + termVer))
|
||||
return TermColor256 // return TermColorNone
|
||||
}
|
||||
if i == 3 {
|
||||
return TermColorTrue
|
||||
}
|
||||
}
|
||||
return TermColor256
|
||||
}
|
||||
|
||||
// otherwise determine from TERM's max_colors capability
|
||||
// if !isWin && termVal != "" {
|
||||
// debugf("TERM=%s - TODO check color level by load terminfo file", termVal)
|
||||
// return TermColor16
|
||||
// }
|
||||
|
||||
// no TERM env value. default return none level
|
||||
return TermColorNone
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
//go:build !windows
|
||||
|
||||
package termenv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
)
|
||||
|
||||
// detect special term color support on macOS, linux, unix
|
||||
func detectSpecialTermColor(termVal string) (ColorLevel, bool) {
|
||||
if termVal == "" {
|
||||
// detect WSL as it has True Color support
|
||||
// on Windows WSL:
|
||||
// - runtime.GOOS == "Linux"
|
||||
// - support true-color
|
||||
if checkfn.IsWSL() {
|
||||
debugf("True Color support on WSL environment")
|
||||
return TermColorTrue, false
|
||||
}
|
||||
return TermColorNone, false
|
||||
}
|
||||
|
||||
debugf("terminfo check - fallback detect color by check TERM value")
|
||||
|
||||
// on TERM=screen:
|
||||
// - support 256, not support true-color. test on macOS
|
||||
if termVal == noTrueColorTerm {
|
||||
return TermColor256, false
|
||||
}
|
||||
|
||||
if strings.Contains(termVal, "256color") {
|
||||
return TermColor256, false
|
||||
}
|
||||
|
||||
if strings.Contains(termVal, "xterm") {
|
||||
return TermColor256, false
|
||||
}
|
||||
return TermColor16, false
|
||||
}
|
||||
|
||||
func syscallStdinFd() int { return syscall.Stdin }
|
||||
|
||||
func syscallStdoutFd() int { return syscall.Stdout }
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
//go:build windows
|
||||
|
||||
package termenv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// Get the Windows Version and Build Number
|
||||
var majorVersion, _, buildNumber = windows.RtlGetNtVersionNumbers()
|
||||
|
||||
// refer
|
||||
//
|
||||
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_windows.go#L30-L57
|
||||
// https://github.com/gookit/color/issues/25#issuecomment-738727917
|
||||
//
|
||||
// detects the color level supported on Windows: CMD, PowerShell
|
||||
func detectSpecialTermColor(_ string) (tl ColorLevel, needVTP bool) {
|
||||
if os.Getenv("ConEmuANSI") == "ON" {
|
||||
debugf("True Color support by ConEmuANSI=ON")
|
||||
// ConEmuANSI is "ON" for generic ANSI support
|
||||
// but True Color option is enabled by default
|
||||
// I am just assuming that people wouldn't have disabled it
|
||||
// Even if it is not enabled then ConEmu will auto round off accordingly
|
||||
return TermColorTrue, false
|
||||
}
|
||||
|
||||
// Before Windows 10 Build Number 10586, console never supported ANSI Colors
|
||||
if buildNumber < 10586 || majorVersion < 10 {
|
||||
// Detect if using ANSICON on older systems
|
||||
if os.Getenv("ANSICON") != "" {
|
||||
conVersion := os.Getenv("ANSICON_VER")
|
||||
// 8-bit Colors were only supported after v1.81 release
|
||||
if conVersion >= "181" {
|
||||
return TermColor256, false
|
||||
}
|
||||
return TermColor16, false
|
||||
}
|
||||
|
||||
return TermColorNone, false
|
||||
}
|
||||
|
||||
// True Color is not available before build 14931 so fallback to 8-bit color.
|
||||
if buildNumber < 14931 {
|
||||
return TermColor256, true
|
||||
}
|
||||
|
||||
// Windows 10 build 14931 is the first release that supports 16m/TrueColor
|
||||
debugf("support True Color on windows version is >= build 14931")
|
||||
return TermColorTrue, true
|
||||
}
|
||||
|
||||
// TryEnableVTP try force enables colors on Windows terminal
|
||||
func TryEnableVTP(enable bool) bool {
|
||||
if !enable {
|
||||
return false
|
||||
}
|
||||
|
||||
// enable colors on Windows terminal
|
||||
if tryEnableOnCONOUT() {
|
||||
debugf("True-Color by enable VirtualTerminalProcessing on windows")
|
||||
return true
|
||||
}
|
||||
|
||||
// initKernel32Proc()
|
||||
suc := tryEnableOnStdout()
|
||||
debugf("True-Color by enable VirtualTerminalProcessing on windows")
|
||||
return suc
|
||||
}
|
||||
|
||||
func tryEnableOnCONOUT() bool {
|
||||
outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
|
||||
if err != nil {
|
||||
setLastErr(err)
|
||||
return false
|
||||
}
|
||||
|
||||
err = EnableVTProcessing(outHandle, true)
|
||||
if err != nil {
|
||||
setLastErr(err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func tryEnableOnStdout() bool {
|
||||
// try direct open syscall.Stdout
|
||||
err := EnableVTProcessing(syscall.Stdout, true)
|
||||
if err != nil {
|
||||
setLastErr(err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// related docs
|
||||
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences
|
||||
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
|
||||
var (
|
||||
// isMSys bool
|
||||
kernel32 *syscall.LazyDLL
|
||||
|
||||
procGetConsoleMode *syscall.LazyProc
|
||||
procSetConsoleMode *syscall.LazyProc
|
||||
)
|
||||
|
||||
func initKernel32Proc() {
|
||||
if kernel32 != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// load related Windows dll
|
||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
|
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
|
||||
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
|
||||
}
|
||||
|
||||
/*************************************************************
|
||||
* render full color code on Windows(8,16,24bit color)
|
||||
*************************************************************/
|
||||
|
||||
// EnableVTProcessing Enable virtual terminal processing on Windows
|
||||
//
|
||||
// ref from github.com/konsorten/go-windows-terminal-sequences
|
||||
// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := EnableVTProcessing(syscall.Stdout, true)
|
||||
// // support print color text
|
||||
// err = EnableVTProcessing(syscall.Stdout, false)
|
||||
func EnableVTProcessing(stream syscall.Handle, enable bool) error {
|
||||
var mode uint32
|
||||
// Check if it is currently in the terminal
|
||||
// err := syscall.GetConsoleMode(syscall.Stdout, &mode)
|
||||
err := syscall.GetConsoleMode(stream, &mode)
|
||||
if err != nil {
|
||||
debugf("enable Windows VirtualTerminalProcessing error: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters
|
||||
if enable {
|
||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
} else {
|
||||
mode &^= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
}
|
||||
|
||||
err = windows.SetConsoleMode(windows.Handle(stream), mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode))
|
||||
// if ret == 0 {
|
||||
// return err
|
||||
// }
|
||||
return nil
|
||||
}
|
||||
|
||||
// on Windows, must convert 'syscall.Stdin' to int
|
||||
func syscallStdinFd() int {
|
||||
return int(syscall.Stdin)
|
||||
}
|
||||
|
||||
// on Windows, must convert to int
|
||||
func syscallStdoutFd() int {
|
||||
return int(syscall.Stdout)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package termenv
|
||||
|
||||
func init() {
|
||||
// terminal supports color OR noColor=true: Don't need to enable virtual process
|
||||
if colorLevel != TermColorNone || noColor {
|
||||
return
|
||||
}
|
||||
|
||||
// try force enable colors on Windows terminal
|
||||
TryEnableVTP(needVTP)
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
// Package termenv provides detect color support of the current terminal.
|
||||
// And with some utils for terminal env.
|
||||
package termenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
lastErr error
|
||||
// debug mode
|
||||
debugMode bool
|
||||
// support color of current terminal
|
||||
supportColor bool
|
||||
|
||||
// value of os color render and display
|
||||
//
|
||||
// NOTICE:
|
||||
// if ENV: NO_COLOR is not empty, will disable color render.
|
||||
noColor = os.Getenv("NO_COLOR") != ""
|
||||
|
||||
// the color support level for current terminal
|
||||
// needVTP - need enable VTP, only for Windows OS
|
||||
colorLevel, needVTP = detectTermColorLevel()
|
||||
)
|
||||
|
||||
// SetDebugMode sets debug mode.
|
||||
func SetDebugMode(enable bool) { debugMode = enable }
|
||||
|
||||
// LastErr returns the last error.
|
||||
func LastErr() error {
|
||||
defer func() {
|
||||
lastErr = nil // reset on get
|
||||
}()
|
||||
return lastErr
|
||||
}
|
||||
|
||||
func debugf(tpl string, v ...any) {
|
||||
if debugMode {
|
||||
fmt.Printf("TERMENV: "+tpl+"\n", v...)
|
||||
}
|
||||
}
|
||||
|
||||
func setLastErr(err error) {
|
||||
if err != nil {
|
||||
debugf("TERMENV: last error: %v", err)
|
||||
lastErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// exec: `stty -a 2>&1`
|
||||
// const (
|
||||
// mac: speed 9600 baud; 97 rows; 362 columns;
|
||||
// macSttyMsgPattern = `(\d+)\s+rows;\s*(\d+)\s+columns;`
|
||||
// linux: speed 38400 baud; rows 97; columns 362; line = 0;
|
||||
// linuxSttyMsgPattern = `rows\s+(\d+);\s*columns\s+(\d+);`
|
||||
// )
|
||||
var terminalWidth, terminalHeight int
|
||||
|
||||
// GetTermSize for current console terminal. will first try to get from environment variables COLUMNS and LINES.
|
||||
func GetTermSize(refresh ...bool) (w int, h int) {
|
||||
if terminalWidth > 0 && (len(refresh) == 0 || !refresh[0]) {
|
||||
return terminalWidth, terminalHeight
|
||||
}
|
||||
|
||||
// 首先尝试从环境变量获取
|
||||
if cols := os.Getenv("COLUMNS"); cols != "" {
|
||||
if width, err := strconv.Atoi(cols); err == nil && width > 0 {
|
||||
terminalWidth = width
|
||||
}
|
||||
}
|
||||
if rows := os.Getenv("LINES"); rows != "" {
|
||||
if height, err := strconv.Atoi(rows); err == nil && height > 0 {
|
||||
terminalHeight = height
|
||||
}
|
||||
}
|
||||
if terminalWidth > 0 && terminalHeight > 0 {
|
||||
return terminalWidth, terminalHeight
|
||||
}
|
||||
|
||||
var err error
|
||||
w, h, err = term.GetSize(syscallStdoutFd())
|
||||
if err != nil {
|
||||
debugf("get terminal size error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// cache result
|
||||
terminalWidth, terminalHeight = w, h
|
||||
debugf("get terminal size: %d,%d", w, h)
|
||||
return
|
||||
}
|
||||
|
||||
// ReadPassword from console terminal
|
||||
func ReadPassword(question ...string) string {
|
||||
if len(question) > 0 {
|
||||
print(question[0])
|
||||
} else {
|
||||
print("Enter Password: ")
|
||||
}
|
||||
|
||||
bs, err := term.ReadPassword(syscallStdinFd())
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
println() // new line
|
||||
return string(bs)
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package termenv
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/goutil/internal/checkfn"
|
||||
"github.com/gookit/goutil/internal/comfunc"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
cmdList = []string{"cmd", "cmd.exe"}
|
||||
pwshList = []string{"powershell", "powershell.exe", "pwsh", "pwsh.exe"}
|
||||
)
|
||||
|
||||
// IsTerminal 检查是否为终端设备中
|
||||
func IsTerminal() bool {
|
||||
fd := int(os.Stdout.Fd())
|
||||
return term.IsTerminal(fd)
|
||||
}
|
||||
|
||||
// CurrentShell get current used shell env file.
|
||||
//
|
||||
// eg "/bin/zsh" "/bin/bash".
|
||||
// if onlyName=true, will return "zsh", "bash"
|
||||
func CurrentShell(onlyName bool, fallbackShell ...string) string {
|
||||
return comfunc.CurrentShell(onlyName, fallbackShell...)
|
||||
}
|
||||
|
||||
// HasShellEnv has shell env check.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// HasShellEnv("sh")
|
||||
// HasShellEnv("bash")
|
||||
func HasShellEnv(shell string) bool {
|
||||
// can also use: "echo $0"
|
||||
out, err := shellExec("echo OK", shell)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return strings.TrimSpace(out) == "OK"
|
||||
}
|
||||
|
||||
// IsShellSpecialVar reports whether the character identifies a special
|
||||
// shell variable such as $*.
|
||||
func IsShellSpecialVar(c uint8) bool {
|
||||
switch c {
|
||||
case '*', '#', '$', '@', '!', '?', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func shellExec(expr, shell string) (string, error) {
|
||||
// "-c" for bash,sh,zsh shell
|
||||
mark := "-c"
|
||||
if shell == "" {
|
||||
shell = CurrentShell(true, "sh")
|
||||
}
|
||||
|
||||
// special for Windows shell
|
||||
if runtime.GOOS == "windows" {
|
||||
// use cmd.exe, mark is "/c"
|
||||
if checkfn.StringsContains(cmdList, shell) {
|
||||
mark = "/c"
|
||||
} else if checkfn.StringsContains(pwshList, shell) {
|
||||
// "-Command" for powershell
|
||||
mark = "-Command"
|
||||
}
|
||||
}
|
||||
|
||||
cmd := exec.Command(shell, mark, expr)
|
||||
bs, err := cmd.CombinedOutput()
|
||||
return string(bs), err
|
||||
}
|
||||
Reference in New Issue
Block a user