Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+126
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}