Bump github.com/gookit/config/v2 from 2.1.8 to 2.2.2

Bumps [github.com/gookit/config/v2](https://github.com/gookit/config) from 2.1.8 to 2.2.2.
- [Release notes](https://github.com/gookit/config/releases)
- [Commits](https://github.com/gookit/config/compare/v2.1.8...v2.2.2)

---
updated-dependencies:
- dependency-name: github.com/gookit/config/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-06-13 06:43:49 +00:00
committed by Ralf Haferkamp
parent 03045c5ccc
commit 5ebc596352
190 changed files with 18973 additions and 4170 deletions
+1
View File
@@ -570,6 +570,7 @@ Check out these projects, which use https://github.com/gookit/color :
- [xo/terminfo](https://github.com/xo/terminfo)
- [beego/bee](https://github.com/beego/bee)
- [issue9/term](https://github.com/issue9/term)
- [muesli/termenv](https://github.com/muesli/termenv)
- [ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code)
- [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map)
- [Terminal Colors](https://gist.github.com/XVilka/8346728)
+1
View File
@@ -578,6 +578,7 @@ const (
## 参考项目
- [inhere/console](https://github.com/inhere/php-console)
- [muesli/termenv](https://github.com/muesli/termenv)
- [xo/terminfo](https://github.com/xo/terminfo)
- [beego/bee](https://github.com/beego/bee)
- [issue9/term](https://github.com/issue9/term)
+6
View File
@@ -0,0 +1,6 @@
//go:build !go1.18
// +build !go1.18
package color
type any = interface{}
+2 -2
View File
@@ -183,7 +183,7 @@ func InnerErrs() []error {
// Usage:
//
// msg := RenderCode("3;32;45", "some", "message")
func RenderCode(code string, args ...interface{}) string {
func RenderCode(code string, args ...any) string {
var message string
if ln := len(args); ln == 0 {
return ""
@@ -205,7 +205,7 @@ func RenderCode(code string, args ...interface{}) string {
// 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 ...interface{}) string {
func RenderWithSpaces(code string, args ...any) string {
msg := formatArgsForPrintln(args)
if len(code) == 0 {
return msg
+31 -24
View File
@@ -188,57 +188,65 @@ func (c Color) Text(message string) string { return RenderString(c.String(), mes
// Render messages by color setting
//
// Usage:
// green := color.FgGreen.Render
// fmt.Println(green("message"))
func (c Color) Render(a ...interface{}) string { return RenderCode(c.String(), a...) }
//
// green := color.FgGreen.Render
// fmt.Println(green("message"))
func (c Color) Render(a ...any) string { return RenderCode(c.String(), a...) }
// Renderln messages by color setting.
// like Println, will add spaces for each argument
//
// Usage:
// green := color.FgGreen.Renderln
// fmt.Println(green("message"))
func (c Color) Renderln(a ...interface{}) string { return RenderWithSpaces(c.String(), a...) }
//
// green := color.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 ...interface{}) string { return RenderCode(c.String(), a...) }
func (c Color) Sprint(a ...any) string { return RenderCode(c.String(), a...) }
// Sprintf format and render message.
//
// Usage:
// green := color.Green.Sprintf
// colored := green("message")
func (c Color) Sprintf(format string, args ...interface{}) string {
//
// green := color.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:
// color.Green.Print("message")
//
// color.Green.Print("message")
//
// OR:
// green := color.FgGreen.Print
// green("message")
func (c Color) Print(args ...interface{}) {
//
// green := color.FgGreen.Print
// green("message")
func (c Color) Print(args ...any) {
doPrintV2(c.Code(), fmt.Sprint(args...))
}
// Printf format and print messages.
//
// Usage:
// color.Cyan.Printf("string %s", "arg0")
func (c Color) Printf(format string, a ...interface{}) {
//
// color.Cyan.Printf("string %s", "arg0")
func (c Color) Printf(format string, a ...any) {
doPrintV2(c.Code(), fmt.Sprintf(format, a...))
}
// Println messages with new line
func (c Color) Println(a ...interface{}) { doPrintlnV2(c.String(), a) }
func (c Color) Println(a ...any) { doPrintlnV2(c.String(), a) }
// Light current color. eg: 36(FgCyan) -> 96(FgLightCyan).
//
// Usage:
// lightCyan := Cyan.Light()
// lightCyan.Print("message")
//
// lightCyan := Cyan.Light()
// lightCyan.Print("message")
func (c Color) Light() Color {
val := int(c)
if val >= 30 && val <= 47 {
@@ -252,8 +260,9 @@ func (c Color) Light() Color {
// Darken current color. eg. 96(FgLightCyan) -> 36(FgCyan)
//
// Usage:
// cyan := LightCyan.Darken()
// cyan.Print("message")
//
// cyan := LightCyan.Darken()
// cyan.Print("message")
func (c Color) Darken() Color {
val := int(c)
if val >= 90 && val <= 107 {
@@ -461,9 +470,7 @@ func Fg2Bg(val uint8) uint8 {
}
// Basic2nameMap data
func Basic2nameMap() map[uint8]string {
return basic2nameMap
}
func Basic2nameMap() map[uint8]string { return basic2nameMap }
// func initName2basicMap() map[string]uint8 {
// n2b := make(map[string]uint8, len(basic2nameMap))
+29 -23
View File
@@ -19,16 +19,19 @@ from wikipedia, 256 color:
// tpl for 8 bit 256 color(`2^8`)
//
// format:
// ESC[ … 38;5;<n> … m // 选择前景色
// ESC[ … 48;5;<n> … m // 选择景色
//
// ESC[ … 38;5;<n> … m // 选择景色
// ESC[ … 48;5;<n> … m // 选择背景色
//
// example:
// fg "\x1b[38;5;242m"
// bg "\x1b[48;5;208m"
// both "\x1b[38;5;242;48;5;208m"
//
// fg "\x1b[38;5;242m"
// bg "\x1b[48;5;208m"
// both "\x1b[38;5;242;48;5;208m"
//
// links:
// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#8位
//
// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#8位
const (
TplFg256 = "38;5;%d"
TplBg256 = "48;5;%d"
@@ -45,12 +48,14 @@ const (
// 颜色值使用10进制和16进制都可 0x98 = 152
//
// The color consists of two uint8:
// 0: color value
// 1: color type; Fg=0, Bg=1, >1: unset value
//
// 0: color value
// 1: color type; Fg=0, Bg=1, >1: unset value
//
// example:
// fg color: [152, 0]
// bg color: [152, 1]
//
// fg color: [152, 0]
// bg color: [152, 1]
//
// NOTICE: now support 256 color on windows CMD, PowerShell
// lint warn - Name starts with package name
@@ -87,27 +92,27 @@ func (c Color256) Reset() error {
}
// Print print message
func (c Color256) Print(a ...interface{}) {
func (c Color256) Print(a ...any) {
doPrintV2(c.String(), fmt.Sprint(a...))
}
// Printf format and print message
func (c Color256) Printf(format string, a ...interface{}) {
func (c Color256) Printf(format string, a ...any) {
doPrintV2(c.String(), fmt.Sprintf(format, a...))
}
// Println print message with newline
func (c Color256) Println(a ...interface{}) {
func (c Color256) Println(a ...any) {
doPrintlnV2(c.String(), a)
}
// Sprint returns rendered message
func (c Color256) Sprint(a ...interface{}) string {
func (c Color256) Sprint(a ...any) string {
return RenderCode(c.String(), a...)
}
// Sprintf returns format and rendered message
func (c Color256) Sprintf(format string, a ...interface{}) string {
func (c Color256) Sprintf(format string, a ...any) string {
return RenderString(c.String(), fmt.Sprintf(format, a...))
}
@@ -206,9 +211,10 @@ type Style256 struct {
// S256 create a color256 style
//
// Usage:
// s := color.S256()
// s := color.S256(132) // fg
// s := color.S256(132, 203) // fg and bg
//
// s := color.S256()
// s := color.S256(132) // fg
// s := color.S256(132, 203) // fg and bg
func S256(fgAndBg ...uint8) *Style256 {
s := &Style256{}
vl := len(fgAndBg)
@@ -256,27 +262,27 @@ func (s *Style256) AddOpts(opts ...Color) *Style256 {
}
// Print message
func (s *Style256) Print(a ...interface{}) {
func (s *Style256) Print(a ...any) {
doPrintV2(s.String(), fmt.Sprint(a...))
}
// Printf format and print message
func (s *Style256) Printf(format string, a ...interface{}) {
func (s *Style256) Printf(format string, a ...any) {
doPrintV2(s.String(), fmt.Sprintf(format, a...))
}
// Println print message with newline
func (s *Style256) Println(a ...interface{}) {
func (s *Style256) Println(a ...any) {
doPrintlnV2(s.String(), a)
}
// Sprint returns rendered message
func (s *Style256) Sprint(a ...interface{}) string {
func (s *Style256) Sprint(a ...any) string {
return RenderCode(s.Code(), a...)
}
// Sprintf returns format and rendered message
func (s *Style256) Sprintf(format string, a ...interface{}) string {
func (s *Style256) Sprintf(format string, a ...any) string {
return RenderString(s.Code(), fmt.Sprintf(format, a...))
}
+50 -40
View File
@@ -8,20 +8,24 @@ import (
// 24 bit RGB color
// RGB:
// R 0-255 G 0-255 B 0-255
// R 00-FF G 00-FF B 00-FF (16进制)
//
// R 0-255 G 0-255 B 0-255
// R 00-FF G 00-FF B 00-FF (16进制)
//
// Format:
// ESC[ … 38;2;<r>;<g>;<b> … m // Select RGB foreground color
// ESC[ … 48;2;<r>;<g>;<b> … m // Choose RGB background color
//
// ESC[ … 38;2;<r>;<g>;<b> … m // Select RGB foreground color
// ESC[ … 48;2;<r>;<g>;<b> … m // Choose RGB background color
//
// links:
// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24位
//
// https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#24位
//
// example:
// fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m
// bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m
// both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m
//
// fg: \x1b[38;2;30;144;255mMESSAGE\x1b[0m
// bg: \x1b[48;2;30;144;255mMESSAGE\x1b[0m
// both: \x1b[38;2;233;90;203;48;2;30;144;255mMESSAGE\x1b[0m
const (
TplFgRGB = "38;2;%d;%d;%d"
TplBgRGB = "48;2;%d;%d;%d"
@@ -45,10 +49,11 @@ const (
// The last digit represents the foreground(0), background(1), >1 is unset value
//
// Usage:
// // 0, 1, 2 is R,G,B.
// // 3rd: Fg=0, Bg=1, >1: unset value
// RGBColor{30,144,255, 0}
// RGBColor{30,144,255, 1}
//
// // 0, 1, 2 is R,G,B.
// // 3rd: Fg=0, Bg=1, >1: unset value
// RGBColor{30,144,255, 0}
// RGBColor{30,144,255, 1}
//
// NOTICE: now support RGB color on Windows CMD, PowerShell
type RGBColor [4]uint8
@@ -59,9 +64,10 @@ var emptyRGBColor = RGBColor{3: 99}
// RGB color create.
//
// Usage:
// c := RGB(30,144,255)
// c := RGB(30,144,255, true)
// c.Print("message")
//
// c := RGB(30,144,255)
// c := RGB(30,144,255, true)
// c.Print("message")
func RGB(r, g, b uint8, isBg ...bool) RGBColor {
rgb := RGBColor{r, g, b}
if len(isBg) > 0 && isBg[0] {
@@ -90,11 +96,12 @@ func RgbFromInts(rgb []int, isBg ...bool) RGBColor {
// HEX create RGB color from a HEX color string.
//
// Usage:
// c := HEX("ccc") // rgb: [204 204 204]
// c := HEX("aabbcc") // rgb: [170 187 204]
// c := HEX("#aabbcc")
// c := HEX("0xaabbcc")
// c.Print("message")
//
// c := HEX("ccc") // rgb: [204 204 204]
// c := HEX("aabbcc") // rgb: [170 187 204]
// c := HEX("#aabbcc")
// c := HEX("0xaabbcc")
// c.Print("message")
func HEX(hex string, isBg ...bool) RGBColor {
if rgb := HexToRgb(hex); len(rgb) > 0 {
return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...)
@@ -139,11 +146,12 @@ func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor {
// Support use color name in the {namedRgbMap}
//
// Usage:
// c := RGBFromString("170,187,204")
// c.Print("message")
//
// c := RGBFromString("brown")
// c.Print("message with color brown")
// c := RGBFromString("170,187,204")
// c.Print("message")
//
// c := RGBFromString("brown")
// c.Print("message with color brown")
func RGBFromString(rgb string, isBg ...bool) RGBColor {
// use color name in the {namedRgbMap}
if rgbVal, ok := namedRgbMap[rgb]; ok {
@@ -180,27 +188,27 @@ func (c RGBColor) Reset() error {
}
// Print print message
func (c RGBColor) Print(a ...interface{}) {
func (c RGBColor) Print(a ...any) {
doPrintV2(c.String(), fmt.Sprint(a...))
}
// Printf format and print message
func (c RGBColor) Printf(format string, a ...interface{}) {
func (c RGBColor) Printf(format string, a ...any) {
doPrintV2(c.String(), fmt.Sprintf(format, a...))
}
// Println print message with newline
func (c RGBColor) Println(a ...interface{}) {
func (c RGBColor) Println(a ...any) {
doPrintlnV2(c.String(), a)
}
// Sprint returns rendered message
func (c RGBColor) Sprint(a ...interface{}) string {
func (c RGBColor) Sprint(a ...any) string {
return RenderCode(c.String(), a...)
}
// Sprintf returns format and rendered message
func (c RGBColor) Sprintf(format string, a ...interface{}) string {
func (c RGBColor) Sprintf(format string, a ...any) string {
return RenderString(c.String(), fmt.Sprintf(format, a...))
}
@@ -279,8 +287,8 @@ func (c RGBColor) C16() Color { return c.Basic() }
// All are composed of 4 digits uint8, the first three digits are the color value;
// The last bit is different from RGBColor, here it indicates whether the value is set.
//
// 1 Has been set
// ^1 Not set
// 1 Has been set
// ^1 Not set
type RGBStyle struct {
// Name of the style
Name string
@@ -303,8 +311,9 @@ func NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle {
// HEXStyle create a RGBStyle from HEX color string.
//
// Usage:
// s := HEXStyle("aabbcc", "eee")
// s.Print("message")
//
// s := HEXStyle("aabbcc", "eee")
// s.Print("message")
func HEXStyle(fg string, bg ...string) *RGBStyle {
s := &RGBStyle{}
if len(bg) > 0 {
@@ -320,8 +329,9 @@ func HEXStyle(fg string, bg ...string) *RGBStyle {
// RGBStyleFromString create a RGBStyle from color value string.
//
// Usage:
// s := RGBStyleFromString("170,187,204", "70,87,4")
// s.Print("message")
//
// s := RGBStyleFromString("170,187,204", "70,87,4")
// s.Print("message")
func RGBStyleFromString(fg string, bg ...string) *RGBStyle {
s := &RGBStyle{}
if len(bg) > 0 {
@@ -363,27 +373,27 @@ func (s *RGBStyle) AddOpts(opts ...Color) *RGBStyle {
}
// Print print message
func (s *RGBStyle) Print(a ...interface{}) {
func (s *RGBStyle) Print(a ...any) {
doPrintV2(s.String(), fmt.Sprint(a...))
}
// Printf format and print message
func (s *RGBStyle) Printf(format string, a ...interface{}) {
func (s *RGBStyle) Printf(format string, a ...any) {
doPrintV2(s.String(), fmt.Sprintf(format, a...))
}
// Println print message with newline
func (s *RGBStyle) Println(a ...interface{}) {
func (s *RGBStyle) Println(a ...any) {
doPrintlnV2(s.String(), a)
}
// Sprint returns rendered message
func (s *RGBStyle) Sprint(a ...interface{}) string {
func (s *RGBStyle) Sprint(a ...any) string {
return RenderCode(s.String(), a...)
}
// Sprintf returns format and rendered message
func (s *RGBStyle) Sprintf(format string, a ...interface{}) string {
func (s *RGBStyle) Sprintf(format string, a ...any) string {
return RenderString(s.String(), fmt.Sprintf(format, a...))
}
+31 -25
View File
@@ -41,7 +41,8 @@ var (
// There are internal defined fg color tags
//
// Usage:
// <tag>content text</>
//
// <tag>content text</>
//
// @notice 加 0 在前面是为了防止之前的影响到现在的设置
var colorTags = map[string]string{
@@ -324,15 +325,17 @@ func (tp *TagParser) ParseByEnv(str string) string {
return tp.Parse(str)
}
// Parse parse given string, replace color tag and return rendered string
// Parse given string, replace color tag and return rendered string
//
// Use built in tags:
// <TAG_NAME>CONTENT</>
// // e.g: `<info>message</>`
//
// <TAG_NAME>CONTENT</>
// // e.g: `<info>message</>`
//
// Custom tag attributes:
// `<fg=VALUE;bg=VALUE;op=VALUES>CONTENT</>`
// // e.g: `<fg=167;bg=232>wel</>`
//
// `<fg=VALUE;bg=VALUE;op=VALUES>CONTENT</>`
// // e.g: `<fg=167;bg=232>wel</>`
func (tp *TagParser) Parse(str string) string {
// not contains color tag
if !strings.Contains(str, "</>") {
@@ -376,26 +379,30 @@ func ReplaceTag(str string) string {
// ParseCodeFromAttr parse color attributes.
//
// attr format:
// // VALUE please see var: FgColors, BgColors, AllOptions
// "fg=VALUE;bg=VALUE;op=VALUE"
//
// // VALUE please see var: FgColors, BgColors, AllOptions
// "fg=VALUE;bg=VALUE;op=VALUE"
//
// 16 color:
// "fg=yellow"
// "bg=red"
// "op=bold,underscore" // option is allow multi value
// "fg=white;bg=blue;op=bold"
// "fg=white;op=bold,underscore"
//
// "fg=yellow"
// "bg=red"
// "op=bold,underscore" // option is allow multi value
// "fg=white;bg=blue;op=bold"
// "fg=white;op=bold,underscore"
//
// 256 color:
//
// "fg=167"
// "fg=167;bg=23"
// "fg=167;bg=23;op=bold"
//
// True color:
// // hex
//
// // hex
// "fg=fc1cac"
// "fg=fc1cac;bg=c2c3c4"
// // r,g,b
// // r,g,b
// "fg=23,45,214"
// "fg=23,45,214;bg=109,99,88"
func ParseCodeFromAttr(attr string) (code string) {
@@ -476,12 +483,10 @@ func ClearTag(s string) string {
*************************************************************/
// GetTagCode get color code by tag name
func GetTagCode(name string) string {
return colorTags[name]
}
func GetTagCode(name string) string { return colorTags[name] }
// ApplyTag for messages
func ApplyTag(tag string, a ...interface{}) string {
func ApplyTag(tag string, a ...any) string {
return RenderCode(GetTagCode(tag), a...)
}
@@ -510,11 +515,12 @@ func IsDefinedTag(name string) bool {
// Tag value is a defined style name
// Usage:
// Tag("info").Println("message")
//
// Tag("info").Println("message")
type Tag string
// Print messages
func (tg Tag) Print(a ...interface{}) {
func (tg Tag) Print(a ...any) {
name := string(tg)
str := fmt.Sprint(a...)
@@ -526,7 +532,7 @@ func (tg Tag) Print(a ...interface{}) {
}
// Printf format and print messages
func (tg Tag) Printf(format string, a ...interface{}) {
func (tg Tag) Printf(format string, a ...any) {
name := string(tg)
str := fmt.Sprintf(format, a...)
@@ -538,7 +544,7 @@ func (tg Tag) Printf(format string, a ...interface{}) {
}
// Println messages line
func (tg Tag) Println(a ...interface{}) {
func (tg Tag) Println(a ...any) {
name := string(tg)
if stl := GetStyle(name); !stl.IsEmpty() {
stl.Println(a...)
@@ -548,12 +554,12 @@ func (tg Tag) Println(a ...interface{}) {
}
// Sprint render messages
func (tg Tag) Sprint(a ...interface{}) string {
func (tg Tag) Sprint(a ...any) string {
return RenderCode(GetTagCode(string(tg)), a...)
}
// Sprintf format and render messages
func (tg Tag) Sprintf(format string, a ...interface{}) string {
func (tg Tag) Sprintf(format string, a ...any) string {
tag := string(tg)
str := fmt.Sprintf(format, a...)
+32 -21
View File
@@ -9,18 +9,19 @@ import "fmt"
// PrinterFace interface
type PrinterFace interface {
fmt.Stringer
Sprint(a ...interface{}) string
Sprintf(format string, a ...interface{}) string
Print(a ...interface{})
Printf(format string, a ...interface{})
Println(a ...interface{})
Sprint(a ...any) string
Sprintf(format string, a ...any) string
Print(a ...any)
Printf(format string, a ...any)
Println(a ...any)
}
// Printer a generic color message printer.
//
// Usage:
// p := &Printer{Code: "32;45;3"}
// p.Print("message")
//
// p := &Printer{Code: "32;45;3"}
// p.Print("message")
type Printer struct {
// NoColor disable color.
NoColor bool
@@ -40,27 +41,27 @@ func (p *Printer) String() string {
}
// Sprint returns rendering colored messages
func (p *Printer) Sprint(a ...interface{}) string {
func (p *Printer) Sprint(a ...any) string {
return RenderCode(p.String(), a...)
}
// Sprintf returns format and rendering colored messages
func (p *Printer) Sprintf(format string, a ...interface{}) string {
func (p *Printer) Sprintf(format string, a ...any) string {
return RenderString(p.String(), fmt.Sprintf(format, a...))
}
// Print rendering colored messages
func (p *Printer) Print(a ...interface{}) {
func (p *Printer) Print(a ...any) {
doPrintV2(p.String(), fmt.Sprint(a...))
}
// Printf format and rendering colored messages
func (p *Printer) Printf(format string, a ...interface{}) {
func (p *Printer) Printf(format string, a ...any) {
doPrintV2(p.String(), fmt.Sprintf(format, a...))
}
// Println rendering colored messages with newline
func (p *Printer) Println(a ...interface{}) {
func (p *Printer) Println(a ...any) {
doPrintlnV2(p.Code, a)
}
@@ -77,46 +78,56 @@ func (p *Printer) IsEmpty() bool {
type SimplePrinter struct{}
// Print message
func (s *SimplePrinter) Print(v ...interface{}) {
func (s *SimplePrinter) Print(v ...any) {
Print(v...)
}
// Printf message
func (s *SimplePrinter) Printf(format string, v ...interface{}) {
func (s *SimplePrinter) Printf(format string, v ...any) {
Printf(format, v...)
}
// Println message
func (s *SimplePrinter) Println(v ...interface{}) {
func (s *SimplePrinter) Println(v ...any) {
Println(v...)
}
// Successf message
func (s *SimplePrinter) Successf(format string, a ...any) {
Success.Printf(format, a...)
}
// Successln message
func (s *SimplePrinter) Successln(a ...any) {
Success.Println(a...)
}
// Infof message
func (s *SimplePrinter) Infof(format string, a ...interface{}) {
func (s *SimplePrinter) Infof(format string, a ...any) {
Info.Printf(format, a...)
}
// Infoln message
func (s *SimplePrinter) Infoln(a ...interface{}) {
func (s *SimplePrinter) Infoln(a ...any) {
Info.Println(a...)
}
// Warnf message
func (s *SimplePrinter) Warnf(format string, a ...interface{}) {
func (s *SimplePrinter) Warnf(format string, a ...any) {
Warn.Printf(format, a...)
}
// Warnln message
func (s *SimplePrinter) Warnln(a ...interface{}) {
func (s *SimplePrinter) Warnln(a ...any) {
Warn.Println(a...)
}
// Errorf message
func (s *SimplePrinter) Errorf(format string, a ...interface{}) {
func (s *SimplePrinter) Errorf(format string, a ...any) {
Error.Printf(format, a...)
}
// Errorln message
func (s *SimplePrinter) Errorln(a ...interface{}) {
func (s *SimplePrinter) Errorln(a ...any) {
Error.Println(a...)
}
+33 -33
View File
@@ -5,104 +5,104 @@ package color
*************************************************************/
// Redp print message with Red color
func Redp(a ...interface{}) { Red.Print(a...) }
func Redp(a ...any) { Red.Print(a...) }
// Redf print message with Red color
func Redf(format string, a ...interface{}) { Red.Printf(format, a...) }
func Redf(format string, a ...any) { Red.Printf(format, a...) }
// Redln print message line with Red color
func Redln(a ...interface{}) { Red.Println(a...) }
func Redln(a ...any) { Red.Println(a...) }
// Bluep print message with Blue color
func Bluep(a ...interface{}) { Blue.Print(a...) }
func Bluep(a ...any) { Blue.Print(a...) }
// Bluef print message with Blue color
func Bluef(format string, a ...interface{}) { Blue.Printf(format, a...) }
func Bluef(format string, a ...any) { Blue.Printf(format, a...) }
// Blueln print message line with Blue color
func Blueln(a ...interface{}) { Blue.Println(a...) }
func Blueln(a ...any) { Blue.Println(a...) }
// Cyanp print message with Cyan color
func Cyanp(a ...interface{}) { Cyan.Print(a...) }
func Cyanp(a ...any) { Cyan.Print(a...) }
// Cyanf print message with Cyan color
func Cyanf(format string, a ...interface{}) { Cyan.Printf(format, a...) }
func Cyanf(format string, a ...any) { Cyan.Printf(format, a...) }
// Cyanln print message line with Cyan color
func Cyanln(a ...interface{}) { Cyan.Println(a...) }
func Cyanln(a ...any) { Cyan.Println(a...) }
// Grayp print message with Gray color
func Grayp(a ...interface{}) { Gray.Print(a...) }
func Grayp(a ...any) { Gray.Print(a...) }
// Grayf print message with Gray color
func Grayf(format string, a ...interface{}) { Gray.Printf(format, a...) }
func Grayf(format string, a ...any) { Gray.Printf(format, a...) }
// Grayln print message line with Gray color
func Grayln(a ...interface{}) { Gray.Println(a...) }
func Grayln(a ...any) { Gray.Println(a...) }
// Greenp print message with Green color
func Greenp(a ...interface{}) { Green.Print(a...) }
func Greenp(a ...any) { Green.Print(a...) }
// Greenf print message with Green color
func Greenf(format string, a ...interface{}) { Green.Printf(format, a...) }
func Greenf(format string, a ...any) { Green.Printf(format, a...) }
// Greenln print message line with Green color
func Greenln(a ...interface{}) { Green.Println(a...) }
func Greenln(a ...any) { Green.Println(a...) }
// Yellowp print message with Yellow color
func Yellowp(a ...interface{}) { Yellow.Print(a...) }
func Yellowp(a ...any) { Yellow.Print(a...) }
// Yellowf print message with Yellow color
func Yellowf(format string, a ...interface{}) { Yellow.Printf(format, a...) }
func Yellowf(format string, a ...any) { Yellow.Printf(format, a...) }
// Yellowln print message line with Yellow color
func Yellowln(a ...interface{}) { Yellow.Println(a...) }
func Yellowln(a ...any) { Yellow.Println(a...) }
// Magentap print message with Magenta color
func Magentap(a ...interface{}) { Magenta.Print(a...) }
func Magentap(a ...any) { Magenta.Print(a...) }
// Magentaf print message with Magenta color
func Magentaf(format string, a ...interface{}) { Magenta.Printf(format, a...) }
func Magentaf(format string, a ...any) { Magenta.Printf(format, a...) }
// Magentaln print message line with Magenta color
func Magentaln(a ...interface{}) { Magenta.Println(a...) }
func Magentaln(a ...any) { Magenta.Println(a...) }
/*************************************************************
* quick use style print message
*************************************************************/
// Infop print message with Info color
func Infop(a ...interface{}) { Info.Print(a...) }
func Infop(a ...any) { Info.Print(a...) }
// Infof print message with Info style
func Infof(format string, a ...interface{}) { Info.Printf(format, a...) }
func Infof(format string, a ...any) { Info.Printf(format, a...) }
// Infoln print message with Info style
func Infoln(a ...interface{}) { Info.Println(a...) }
func Infoln(a ...any) { Info.Println(a...) }
// Successp print message with success color
func Successp(a ...interface{}) { Success.Print(a...) }
func Successp(a ...any) { Success.Print(a...) }
// Successf print message with success style
func Successf(format string, a ...interface{}) { Success.Printf(format, a...) }
func Successf(format string, a ...any) { Success.Printf(format, a...) }
// Successln print message with success style
func Successln(a ...interface{}) { Success.Println(a...) }
func Successln(a ...any) { Success.Println(a...) }
// Errorp print message with Error color
func Errorp(a ...interface{}) { Error.Print(a...) }
func Errorp(a ...any) { Error.Print(a...) }
// Errorf print message with Error style
func Errorf(format string, a ...interface{}) { Error.Printf(format, a...) }
func Errorf(format string, a ...any) { Error.Printf(format, a...) }
// Errorln print message with Error style
func Errorln(a ...interface{}) { Error.Println(a...) }
func Errorln(a ...any) { Error.Println(a...) }
// Warnp print message with Warn color
func Warnp(a ...interface{}) { Warn.Print(a...) }
func Warnp(a ...any) { Warn.Print(a...) }
// Warnf print message with Warn style
func Warnf(format string, a ...interface{}) { Warn.Printf(format, a...) }
func Warnf(format string, a ...any) { Warn.Printf(format, a...) }
// Warnln print message with Warn style
func Warnln(a ...interface{}) { Warn.Println(a...) }
func Warnln(a ...any) { Warn.Println(a...) }
+34 -27
View File
@@ -12,12 +12,14 @@ import (
// Style a 16 color style. can add: fg color, bg color, color options
//
// Example:
// color.Style{color.FgGreen}.Print("message")
//
// color.Style{color.FgGreen}.Print("message")
type Style []Color
// New create a custom style
//
// Usage:
//
// color.New(color.FgGreen).Print("message")
// equals to:
// color.Style{color.FgGreen}.Print("message")
@@ -37,43 +39,45 @@ func (s *Style) Add(cs ...Color) {
// Render render text
// Usage:
// color.New(color.FgGreen).Render("text")
// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text")
func (s Style) Render(a ...interface{}) string {
//
// color.New(color.FgGreen).Render("text")
// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text")
func (s Style) Render(a ...any) string {
return RenderCode(s.String(), a...)
}
// Renderln render text line.
// like Println, will add spaces for each argument
// Usage:
// color.New(color.FgGreen).Renderln("text", "more")
// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more")
func (s Style) Renderln(a ...interface{}) string {
//
// color.New(color.FgGreen).Renderln("text", "more")
// color.New(color.FgGreen, color.BgBlack, color.OpBold).Render("text", "more")
func (s Style) Renderln(a ...any) string {
return RenderWithSpaces(s.String(), a...)
}
// Sprint is alias of the 'Render'
func (s Style) Sprint(a ...interface{}) string {
func (s Style) Sprint(a ...any) string {
return RenderCode(s.String(), a...)
}
// Sprintf format and render message.
func (s Style) Sprintf(format string, a ...interface{}) string {
func (s Style) Sprintf(format string, a ...any) string {
return RenderString(s.String(), fmt.Sprintf(format, a...))
}
// Print render and Print text
func (s Style) Print(a ...interface{}) {
func (s Style) Print(a ...any) {
doPrintV2(s.String(), fmt.Sprint(a...))
}
// Printf render and print text
func (s Style) Printf(format string, a ...interface{}) {
func (s Style) Printf(format string, a ...any) {
doPrintV2(s.Code(), fmt.Sprintf(format, a...))
}
// Println render and print text line
func (s Style) Println(a ...interface{}) {
func (s Style) Println(a ...any) {
doPrintlnV2(s.String(), a)
}
@@ -115,20 +119,20 @@ func (t *Theme) Save() {
}
// Tips use name as title, only apply style for name
func (t *Theme) Tips(format string, a ...interface{}) {
func (t *Theme) Tips(format string, a ...any) {
// only apply style for name
t.Print(strings.ToUpper(t.Name) + ": ")
Printf(format+"\n", a...)
}
// Prompt use name as title, and apply style for message
func (t *Theme) Prompt(format string, a ...interface{}) {
func (t *Theme) Prompt(format string, a ...any) {
title := strings.ToUpper(t.Name) + ":"
t.Println(title, fmt.Sprintf(format, a...))
}
// Block like Prompt, but will wrap a empty line
func (t *Theme) Block(format string, a ...interface{}) {
func (t *Theme) Block(format string, a ...any) {
title := strings.ToUpper(t.Name) + ":\n"
t.Println(title, fmt.Sprintf(format, a...))
@@ -140,10 +144,11 @@ func (t *Theme) Block(format string, a ...interface{}) {
// internal themes(like bootstrap style)
// Usage:
// color.Info.Print("message")
// color.Info.Printf("a %s message", "test")
// color.Warn.Println("message")
// color.Error.Println("message")
//
// color.Info.Print("message")
// color.Info.Printf("a %s message", "test")
// color.Warn.Println("message")
// color.Error.Println("message")
var (
// Info color style
Info = &Theme{"info", Style{OpReset, FgGreen}}
@@ -175,7 +180,8 @@ var (
// Themes internal defined themes.
// Usage:
// color.Themes["info"].Println("message")
//
// color.Themes["info"].Println("message")
var Themes = map[string]*Theme{
"info": Info,
"note": Note,
@@ -211,7 +217,8 @@ func GetTheme(name string) *Theme {
// Styles internal defined styles, like bootstrap styles.
// Usage:
// color.Styles["info"].Println("message")
//
// color.Styles["info"].Println("message")
var Styles = map[string]Style{
"info": {OpReset, FgGreen},
"note": {OpBold, FgLightCyan},
@@ -285,31 +292,31 @@ func (s *Scheme) Style(name string) Style {
}
// Infof message print
func (s *Scheme) Infof(format string, a ...interface{}) {
func (s *Scheme) Infof(format string, a ...any) {
s.Styles["info"].Printf(format, a...)
}
// Infoln message print
func (s *Scheme) Infoln(v ...interface{}) {
func (s *Scheme) Infoln(v ...any) {
s.Styles["info"].Println(v...)
}
// Warnf message print
func (s *Scheme) Warnf(format string, a ...interface{}) {
func (s *Scheme) Warnf(format string, a ...any) {
s.Styles["warn"].Printf(format, a...)
}
// Warnln message print
func (s *Scheme) Warnln(v ...interface{}) {
func (s *Scheme) Warnln(v ...any) {
s.Styles["warn"].Println(v...)
}
// Errorf message print
func (s *Scheme) Errorf(format string, a ...interface{}) {
func (s *Scheme) Errorf(format string, a ...any) {
s.Styles["error"].Printf(format, a...)
}
// Errorln message print
func (s *Scheme) Errorln(v ...interface{}) {
func (s *Scheme) Errorln(v ...any) {
s.Styles["error"].Println(v...)
}
+15 -36
View File
@@ -32,39 +32,31 @@ func ResetTerminal() error {
*************************************************************/
// Print render color tag and print messages
func Print(a ...interface{}) {
func Print(a ...any) {
Fprint(output, a...)
}
// Printf format and print messages
func Printf(format string, a ...interface{}) {
func Printf(format string, a ...any) {
Fprintf(output, format, a...)
}
// Println messages with new line
func Println(a ...interface{}) {
func Println(a ...any) {
Fprintln(output, a...)
}
// Fprint print rendered messages to writer
//
// Notice: will ignore print error
func Fprint(w io.Writer, a ...interface{}) {
func Fprint(w io.Writer, a ...any) {
_, err := fmt.Fprint(w, Render(a...))
saveInternalError(err)
// if isLikeInCmd {
// renderColorCodeOnCmd(func() {
// _, _ = fmt.Fprint(w, Render(a...))
// })
// } else {
// _, _ = fmt.Fprint(w, Render(a...))
// }
}
// Fprintf print format and rendered messages to writer.
// Notice: will ignore print error
func Fprintf(w io.Writer, format string, a ...interface{}) {
func Fprintf(w io.Writer, format string, a ...any) {
str := fmt.Sprintf(format, a...)
_, err := fmt.Fprint(w, ReplaceTag(str))
saveInternalError(err)
@@ -72,7 +64,7 @@ func Fprintf(w io.Writer, format string, a ...interface{}) {
// Fprintln print rendered messages line to writer
// Notice: will ignore print error
func Fprintln(w io.Writer, a ...interface{}) {
func Fprintln(w io.Writer, a ...any) {
str := formatArgsForPrintln(a)
_, err := fmt.Fprintln(w, ReplaceTag(str))
saveInternalError(err)
@@ -80,7 +72,7 @@ func Fprintln(w io.Writer, a ...interface{}) {
// Lprint passes colored messages to a log.Logger for printing.
// Notice: should be goroutine safe
func Lprint(l *log.Logger, a ...interface{}) {
func Lprint(l *log.Logger, a ...any) {
l.Print(Render(a...))
}
@@ -90,7 +82,7 @@ func Lprint(l *log.Logger, a ...interface{}) {
//
// text := Render("<info>hello</> <cyan>world</>!")
// fmt.Println(text)
func Render(a ...interface{}) string {
func Render(a ...any) string {
if len(a) == 0 {
return ""
}
@@ -98,28 +90,23 @@ func Render(a ...interface{}) string {
}
// Sprint parse color tags, return rendered string
func Sprint(a ...interface{}) string {
func Sprint(a ...any) string {
if len(a) == 0 {
return ""
}
return ReplaceTag(fmt.Sprint(a...))
}
// Sprintf format and return rendered string
func Sprintf(format string, a ...interface{}) string {
func Sprintf(format string, a ...any) string {
return ReplaceTag(fmt.Sprintf(format, a...))
}
// String alias of the ReplaceTag
func String(s string) string {
return ReplaceTag(s)
}
func String(s string) string { return ReplaceTag(s) }
// Text alias of the ReplaceTag
func Text(s string) string {
return ReplaceTag(s)
}
func Text(s string) string { return ReplaceTag(s) }
// Uint8sToInts convert []uint8 to []int
// func Uint8sToInts(u8s []uint8 ) []int {
@@ -138,25 +125,17 @@ func Text(s string) string {
func doPrintV2(code, str string) {
_, err := fmt.Fprint(output, RenderString(code, str))
saveInternalError(err)
// if isLikeInCmd {
// renderColorCodeOnCmd(func() {
// _, _ = fmt.Fprint(output, RenderString(code, str))
// })
// } else {
// _, _ = fmt.Fprint(output, RenderString(code, str))
// }
}
// new implementation, support render full color code on pwsh.exe, cmd.exe
func doPrintlnV2(code string, args []interface{}) {
func doPrintlnV2(code string, args []any) {
str := formatArgsForPrintln(args)
_, err := fmt.Fprintln(output, RenderString(code, str))
saveInternalError(err)
}
// use Println, will add spaces for each arg
func formatArgsForPrintln(args []interface{}) (message string) {
func formatArgsForPrintln(args []any) (message string) {
if ln := len(args); ln == 0 {
message = ""
} else if ln == 1 {
@@ -178,7 +157,7 @@ func formatArgsForPrintln(args []interface{}) (message string) {
// return debugMode == "on"
// }
func debugf(f string, v ...interface{}) {
func debugf(f string, v ...any) {
if debugMode {
fmt.Print("COLOR_DEBUG: ")
fmt.Printf(f, v...)
+20 -13
View File
@@ -84,7 +84,7 @@ package main
import (
"github.com/gookit/config/v2"
"github.com/gookit/config/v2/yamlv3"
"github.com/gookit/config/v2/yaml"
)
// go run ./examples/yaml.go
@@ -92,7 +92,7 @@ func main() {
config.WithOptions(config.ParseEnv)
// add driver for support yaml content
config.AddDriver(yamlv3.Driver)
config.AddDriver(yaml.Driver)
err := config.LoadFiles("testdata/yml_base.yml")
if err != nil {
@@ -389,7 +389,7 @@ Support parse default value by struct tag `default`
c := config.New("test").WithOptions(config.ParseDefault)
// only set name
c.SetData(map[string]interface{}{
c.SetData(map[string]any{
"name": "inhere",
})
@@ -421,7 +421,7 @@ dump.Println(user)
### Load Config
- `LoadOSEnvs(nameToKeyMap map[string]string)` Load data from os ENV
- `LoadData(dataSource ...interface{}) (err error)` Load from struts or maps
- `LoadData(dataSource ...any) (err error)` Load from struts or maps
- `LoadFlags(keys []string) (err error)` Load from CLI flags
- `LoadExists(sourceFiles ...string) (err error)`
- `LoadFiles(sourceFiles ...string) (err error)`
@@ -445,7 +445,7 @@ dump.Println(user)
- `Strings(key string) (arr []string)`
- `SubDataMap(key string) maputi.Data`
- `StringMap(key string) (mp map[string]string)`
- `Get(key string, findByPath ...bool) (value interface{})`
- `Get(key string, findByPath ...bool) (value any)`
**Mapping data to struct:**
@@ -455,14 +455,14 @@ dump.Println(user)
### Setting Values
- `Set(key string, val interface{}, setByPath ...bool) (err error)`
- `Set(key string, val any, setByPath ...bool) (err error)`
### Useful Methods
- `Getenv(name string, defVal ...string) (val string)`
- `AddDriver(driver Driver)`
- `Data() map[string]interface{}`
- `SetData(data map[string]interface{})` set data to override the Config.Data
- `Data() map[string]any`
- `SetData(data map[string]any)` set data to override the Config.Data
- `Exists(key string, findByPath ...bool) bool`
- `DumpTo(out io.Writer, format string) (n int64, err error)`
@@ -497,11 +497,18 @@ Check out these projects, which use https://github.com/gookit/config :
## See also
- Ini parse [gookit/ini/parser](https://github.com/gookit/ini/tree/master/parser)
- Properties parse [gookit/properties](https://github.com/gookit/properties)
- Json5 parse [json5](https://github.com/yosuke-furukawa/json5)
- Yaml parse [go-yaml](https://github.com/go-yaml/yaml)
- Toml parse [go toml](https://github.com/BurntSushi/toml)
- Ini parser [gookit/ini/parser](https://github.com/gookit/ini/tree/master/parser)
- Properties parser [gookit/properties](https://github.com/gookit/properties)
- Json5 parser
- [yosuke-furukawa/json5](https://github.com/yosuke-furukawa/json5)
- [titanous/json5](https://github.com/titanous/json5)
- Json parser
- [goccy/go-json](https://github.com/goccy/go-json)
- [json-iterator/go](https://github.com/json-iterator/go)
- Yaml parser
- [goccy/go-yaml](https://github.com/goccy/go-yaml)
- [go-yaml/yaml](https://github.com/go-yaml/yaml)
- Toml parser [go toml](https://github.com/BurntSushi/toml)
- Data merge [mergo](https://github.com/imdario/mergo)
- Map structure [mapstructure](https://github.com/mitchellh/mapstructure)
+13 -10
View File
@@ -83,7 +83,7 @@ package main
import (
"github.com/gookit/config/v2"
"github.com/gookit/config/v2/yamlv3"
"github.com/gookit/config/v2/yaml"
)
// go run ./examples/yaml.go
@@ -92,7 +92,7 @@ func main() {
config.WithOptions(config.ParseEnv)
// 添加驱动程序以支持yaml内容解析(除了JSON是默认支持,其他的则是按需使用)
config.AddDriver(yamlv3.Driver)
config.AddDriver(yaml.Driver)
// 加载配置,可以同时传入多个文件
err := config.LoadFiles("testdata/yml_base.yml")
@@ -371,7 +371,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值
c := config.New("test").WithOptions(config.ParseDefault)
// only set name
c.SetData(map[string]interface{}{
c.SetData(map[string]any{
"name": "inhere",
})
@@ -402,7 +402,7 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值
### 载入配置
- `LoadData(dataSource ...interface{}) (err error)` 从struct或map加载数据
- `LoadData(dataSource ...any) (err error)` 从struct或map加载数据
- `LoadFlags(keys []string) (err error)` 从命令行参数载入数据
- `LoadOSEnvs(nameToKeyMap map[string]string)` 从ENV载入数据
- `LoadExists(sourceFiles ...string) (err error)` 从存在的配置文件里加载数据,会忽略不存在的文件
@@ -427,25 +427,25 @@ NEW: 支持通过结构标签 `default` 解析并设置默认值
- `Strings(key string) (arr []string)`
- `StringMap(key string) (mp map[string]string)`
- `SubDataMap(key string) maputi.Data`
- `Get(key string, findByPath ...bool) (value interface{})`
- `Get(key string, findByPath ...bool) (value any)`
**将数据映射到结构体:**
- `BindStruct(key string, dst interface{}) error`
- `MapOnExists(key string, dst interface{}) error`
- `BindStruct(key string, dst any) error`
- `MapOnExists(key string, dst any) error`
### 设置值
- `Set(key string, val interface{}, setByPath ...bool) (err error)`
- `Set(key string, val any, setByPath ...bool) (err error)`
### 有用的方法
- `Getenv(name string, defVal ...string) (val string)`
- `AddDriver(driver Driver)`
- `Data() map[string]interface{}`
- `Data() map[string]any`
- `Exists(key string, findByPath ...bool) bool`
- `DumpTo(out io.Writer, format string) (n int64, err error)`
- `SetData(data map[string]interface{})` 设置数据以覆盖 `Config.Data`
- `SetData(data map[string]any)` 设置数据以覆盖 `Config.Data`
## 单元测试
@@ -484,6 +484,9 @@ go test -cover ./...
- Toml 解析 [go toml](https://github.com/BurntSushi/toml)
- 数据合并 [mergo](https://github.com/imdario/mergo)
- 映射数据到结构体 [mapstructure](https://github.com/mitchellh/mapstructure)
- JSON5 解析
- [yosuke-furukawa/json5](https://github.com/yosuke-furukawa/json5)
- [titanous/json5](https://github.com/titanous/json5)
## License
-6
View File
@@ -1,6 +0,0 @@
package config
// alias of interface{}
//
// TIP: cannot add `go:build !go1.18` in file head, that require the go.mod set `go 1.18`
type any = interface{}
+38 -25
View File
@@ -90,13 +90,15 @@ type Config struct {
loadedUrls []string
loadedFiles []string
driverNames []string
reloading bool
// driver alias to name map.
aliasMap map[string]string
reloading bool
// TODO Deprecated decoder and encoder, use driver instead
// drivers map[string]Driver
// decoders["toml"] = func(blob []byte, v interface{}) (err error){}
// decoders["yaml"] = func(blob []byte, v interface{}) (err error){}
// decoders["toml"] = func(blob []byte, v any) (err error){}
// decoders["yaml"] = func(blob []byte, v any) (err error){}
decoders map[string]Decoder
encoders map[string]Encoder
@@ -109,17 +111,9 @@ type Config struct {
sMapCache map[string]strMap
}
// New config instance
func New(name string) *Config {
return &Config{
name: name,
opts: newDefaultOption(),
data: make(map[string]any),
// default add JSON driver
encoders: map[string]Encoder{JSON: JSONEncoder},
decoders: map[string]Decoder{JSON: JSONDecoder},
}
// New config instance, default add JSON driver
func New(name string, opts ...OptionFn) *Config {
return NewEmpty(name).WithDriver(JSONDriver).WithOptions(opts...)
}
// NewEmpty config instance
@@ -132,6 +126,7 @@ func NewEmpty(name string) *Config {
// don't add any drivers
encoders: map[string]Encoder{},
decoders: map[string]Decoder{},
aliasMap: make(map[string]string),
}
}
@@ -140,15 +135,13 @@ func NewWith(name string, fn func(c *Config)) *Config {
return New(name).With(fn)
}
// NewWithOptions config instance
func NewWithOptions(name string, opts ...func(opts *Options)) *Config {
// NewWithOptions config instance. alias of New()
func NewWithOptions(name string, opts ...OptionFn) *Config {
return New(name).WithOptions(opts...)
}
// Default get the default instance
func Default() *Config {
return dc
}
func Default() *Config { return dc }
/*************************************************************
* config drivers
@@ -171,6 +164,11 @@ func AddDriver(driver Driver) { dc.AddDriver(driver) }
// AddDriver set a decoder and encoder driver for a format.
func (c *Config) AddDriver(driver Driver) {
format := driver.Name()
if len(driver.Aliases()) > 0 {
for _, alias := range driver.Aliases() {
c.aliasMap[alias] = format
}
}
c.driverNames = append(c.driverNames, format)
c.decoders[format] = driver.GetDecoder()
@@ -179,21 +177,21 @@ func (c *Config) AddDriver(driver Driver) {
// HasDecoder has decoder
func (c *Config) HasDecoder(format string) bool {
format = fixFormat(format)
format = c.resolveFormat(format)
_, ok := c.decoders[format]
return ok
}
// HasEncoder has encoder
func (c *Config) HasEncoder(format string) bool {
format = fixFormat(format)
format = c.resolveFormat(format)
_, ok := c.encoders[format]
return ok
}
// DelDriver delete driver of the format
func (c *Config) DelDriver(format string) {
format = fixFormat(format)
format = c.resolveFormat(format)
delete(c.decoders, format)
delete(c.encoders, format)
}
@@ -205,6 +203,21 @@ func (c *Config) DelDriver(format string) {
// Name get config name
func (c *Config) Name() string { return c.name }
// AddAlias add alias for a format(driver name)
func AddAlias(format, alias string) { dc.AddAlias(format, alias) }
// AddAlias add alias for a format(driver name)
//
// Example:
//
// config.AddAlias("ini", "conf")
func (c *Config) AddAlias(format, alias string) {
c.aliasMap[alias] = format
}
// AliasMap get alias map
func (c *Config) AliasMap() map[string]string { return c.aliasMap }
// Error get last error, will clear after read.
func (c *Config) Error() error {
err := c.err
@@ -237,8 +250,8 @@ func (c *Config) ClearAll() {
c.ClearData()
c.ClearCaches()
c.loadedUrls = []string{}
c.loadedFiles = []string{}
c.aliasMap = make(map[string]string)
// options
c.opts.Readonly = false
}
@@ -246,7 +259,7 @@ func (c *Config) ClearAll() {
func (c *Config) ClearData() {
c.fireHook(OnCleanData)
c.data = make(map[string]interface{})
c.data = make(map[string]any)
c.loadedUrls = []string{}
c.loadedFiles = []string{}
}
+33 -6
View File
@@ -1,6 +1,5 @@
package config
// default json driver(encoder/decoder)
import (
"encoding/json"
@@ -11,10 +10,19 @@ import (
// TODO refactor: rename GetDecoder() to Decode(), rename GetEncoder() to Encode()
type Driver interface {
Name() string
Aliases() []string // alias format names, use for resolve format name
GetDecoder() Decoder
GetEncoder() Encoder
}
// DriverV2 interface.
type DriverV2 interface {
Name() string // driver name, also is format name.
Aliases() []string // alias format names, use for resolve format name
Decode(blob []byte, v any) (err error)
Encode(v any) (out []byte, err error)
}
// Decoder for decode yml,json,toml format content
type Decoder func(blob []byte, v any) (err error)
@@ -24,6 +32,7 @@ type Encoder func(v any) (out []byte, err error)
// StdDriver struct
type StdDriver struct {
name string
aliases []string
decoder Decoder
encoder Encoder
}
@@ -33,9 +42,24 @@ func NewDriver(name string, dec Decoder, enc Encoder) *StdDriver {
return &StdDriver{name: name, decoder: dec, encoder: enc}
}
// WithAliases set aliases for driver
func (d *StdDriver) WithAliases(aliases ...string) *StdDriver {
d.aliases = aliases
return d
}
// WithAlias add alias for driver
func (d *StdDriver) WithAlias(alias string) *StdDriver {
d.aliases = append(d.aliases, alias)
return d
}
// Name of driver
func (d *StdDriver) Name() string {
return d.name
func (d *StdDriver) Name() string { return d.name }
// Aliases format name of driver
func (d *StdDriver) Aliases() []string {
return d.aliases
}
// Decode of driver
@@ -59,13 +83,11 @@ func (d *StdDriver) GetEncoder() Encoder {
}
/*************************************************************
* json driver
* JSON driver
*************************************************************/
var (
// JSONAllowComments support write comments on json file.
//
// Deprecated: please use JSONDriver.ClearComments = true
JSONAllowComments = true
// JSONMarshalIndent if not empty, will use json.MarshalIndent for encode data.
@@ -107,6 +129,11 @@ func (d *jsonDriver) Name() string {
return d.driverName
}
// Aliases of the driver
func (d *jsonDriver) Aliases() []string {
return nil
}
// Decode for the driver
func (d *jsonDriver) Decode(data []byte, v any) error {
if d.ClearComments {
+8 -2
View File
@@ -55,6 +55,9 @@ func MapOnExists(key string, dst any) error {
}
// MapOnExists mapping data to the dst structure only on key exists.
//
// - Support ParseEnv on mapping
// - Support ParseDefault on mapping
func (c *Config) MapOnExists(key string, dst any) error {
err := c.Structure(key, dst)
if err != nil && err == ErrNotFound {
@@ -66,12 +69,15 @@ func (c *Config) MapOnExists(key string, dst any) error {
// Structure get config data and binding to the dst structure.
//
// - Support ParseEnv on mapping
// - Support ParseDefault on mapping
//
// Usage:
//
// dbInfo := Db{}
// config.Structure("db", &dbInfo)
func (c *Config) Structure(key string, dst any) error {
var data interface{}
var data any
// binding all data
if key == "" {
data = c.data
@@ -133,7 +139,7 @@ func (c *Config) DumpTo(out io.Writer, format string) (n int64, err error) {
var ok bool
var encoder Encoder
format = fixFormat(format)
format = c.resolveFormat(format)
if encoder, ok = c.encoders[format]; !ok {
err = errors.New("not exists/register encoder for the format: " + format)
return
+28 -9
View File
@@ -4,13 +4,15 @@ import (
"errors"
"flag"
"fmt"
"io/ioutil"
"io"
"io/fs"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/gookit/goutil/errorx"
"github.com/gookit/goutil/fsutil"
"github.com/imdario/mergo"
)
@@ -42,6 +44,10 @@ func LoadExists(sourceFiles ...string) error { return dc.LoadExists(sourceFiles.
// LoadExists load and parse config files, but will ignore not exists file.
func (c *Config) LoadExists(sourceFiles ...string) (err error) {
for _, file := range sourceFiles {
if file == "" {
continue
}
if err = c.loadFile(file, true, ""); err != nil {
return
}
@@ -72,7 +78,7 @@ func (c *Config) LoadRemote(format, url string) (err error) {
}
// read response content
bts, err := ioutil.ReadAll(resp.Body)
bts, err := io.ReadAll(resp.Body)
if err == nil {
if err = c.parseSourceCode(format, bts); err != nil {
return
@@ -191,15 +197,21 @@ func LoadData(dataSource ...any) error { return dc.LoadData(dataSource...) }
//
// The dataSources can be:
// - map[string]any
// - map[string]string
func (c *Config) LoadData(dataSources ...any) (err error) {
if c.opts.Delimiter == 0 {
c.opts.Delimiter = defaultDelimiter
}
for _, ds := range dataSources {
if smp, ok := ds.(map[string]string); ok {
c.LoadSMap(smp)
continue
}
err = mergo.Merge(&c.data, ds, mergo.WithOverride)
if err != nil {
return
return errorx.WithStack(err)
}
}
@@ -207,6 +219,14 @@ func (c *Config) LoadData(dataSources ...any) (err error) {
return
}
// LoadSMap to config
func (c *Config) LoadSMap(smp map[string]string) {
for k, v := range smp {
c.data[k] = v
}
c.fireHook(OnLoadData)
}
// LoadSources load one or multi byte data
func LoadSources(format string, src []byte, more ...[]byte) error {
return dc.LoadSources(format, src, more...)
@@ -314,9 +334,8 @@ func (c *Config) LoadFromDir(dirPath, format string) (err error) {
extName := "." + format
extLen := len(extName)
return fsutil.FindInDir(dirPath, func(fPath string, fi os.FileInfo) error {
baseName := fi.Name()
return fsutil.FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error {
baseName := ent.Name()
if strings.HasSuffix(baseName, extName) {
data, err := c.parseSourceToMap(format, fsutil.MustReadFile(fPath))
if err != nil {
@@ -386,7 +405,7 @@ func (c *Config) loadFile(file string, loadExist bool, format string) (err error
defer fd.Close()
// read file content
bts, err := ioutil.ReadAll(fd)
bts, err := io.ReadAll(fd)
if err == nil {
// get format for file ext
if format == "" {
@@ -431,8 +450,8 @@ func (c *Config) loadDataMap(data map[string]any) (err error) {
}
// parse config source code to Config.
func (c *Config) parseSourceToMap(format string, blob []byte) (map[string]interface{}, error) {
format = fixFormat(format)
func (c *Config) parseSourceToMap(format string, blob []byte) (map[string]any, error) {
format = c.resolveFormat(format)
decode := c.decoders[format]
if decode == nil {
return nil, errors.New("not register decoder for the format: " + format)
+5 -2
View File
@@ -50,6 +50,9 @@ type Options struct {
// WatchChange bool
}
// OptionFn option func
type OptionFn func(*Options)
func newDefaultOption() *Options {
return &Options{
ParseKey: true,
@@ -159,10 +162,10 @@ func WithHookFunc(fn HookFunc) func(*Options) {
func EnableCache(opts *Options) { opts.EnableCache = true }
// WithOptions with options
func WithOptions(opts ...func(*Options)) { dc.WithOptions(opts...) }
func WithOptions(opts ...OptionFn) { dc.WithOptions(opts...) }
// WithOptions apply some options
func (c *Config) WithOptions(opts ...func(opts *Options)) *Config {
func (c *Config) WithOptions(opts ...OptionFn) *Config {
if !c.IsEmpty() {
panic("config: Cannot set options after data has been loaded")
}
+49 -9
View File
@@ -90,32 +90,46 @@ func (c *Config) Exists(key string, findByPath ...bool) (ok bool) {
*************************************************************/
// Data return all config data
func Data() map[string]interface{} { return dc.Data() }
func Data() map[string]any { return dc.Data() }
// Data get all config data
func (c *Config) Data() map[string]interface{} {
// Data get all config data.
//
// Note: will don't apply any options, like ParseEnv
func (c *Config) Data() map[string]any {
return c.data
}
// Keys return all config data
func Keys() []string { return dc.Keys() }
// Keys get all config data
func (c *Config) Keys() []string {
keys := make([]string, 0, len(c.data))
for key := range c.data {
keys = append(keys, key)
}
return keys
}
// Get config value by key string, support get sub-value by key path(eg. 'map.key'),
//
// - ok is true, find value from config
// - ok is false, not found or error
func Get(key string, findByPath ...bool) interface{} { return dc.Get(key, findByPath...) }
func Get(key string, findByPath ...bool) any { return dc.Get(key, findByPath...) }
// Get config value by key
func (c *Config) Get(key string, findByPath ...bool) interface{} {
func (c *Config) Get(key string, findByPath ...bool) any {
val, _ := c.GetValue(key, findByPath...)
return val
}
// GetValue get value by given key string.
func GetValue(key string, findByPath ...bool) (interface{}, bool) {
func GetValue(key string, findByPath ...bool) (any, bool) {
return dc.GetValue(key, findByPath...)
}
// GetValue get value by given key string.
func (c *Config) GetValue(key string, findByPath ...bool) (value interface{}, ok bool) {
func (c *Config) GetValue(key string, findByPath ...bool) (value any, ok bool) {
sep := c.opts.Delimiter
if key = formatKey(key, string(sep)); key == "" {
c.addError(ErrKeyIsEmpty)
@@ -235,6 +249,18 @@ func (c *Config) String(key string, defVal ...string) string {
return value
}
// MustString get a string by key, will panic on empty or not exists
func MustString(key string) string { return dc.MustString(key) }
// MustString get a string by key, will panic on empty or not exists
func (c *Config) MustString(key string) string {
value, ok := c.getString(key)
if !ok {
panic("config: string value not found, key: " + key)
}
return value
}
func (c *Config) getString(key string) (value string, ok bool) {
// find from cache
if c.opts.EnableCache && len(c.strCache) > 0 {
@@ -257,8 +283,11 @@ func (c *Config) getString(key string) (value string, ok bool) {
value = envutil.ParseEnvValue(value)
}
default:
// value = fmt.Sprintf("%v", val)
value, _ = strutil.AnyToString(val, false)
var err error
value, err = strutil.AnyToString(val, false)
if err != nil {
return "", false
}
}
// add cache
@@ -520,6 +549,17 @@ func (c *Config) Strings(key string) (arr []string) {
return
}
// StringsBySplit get []string by split a string value.
func StringsBySplit(key, sep string) []string { return dc.StringsBySplit(key, sep) }
// StringsBySplit get []string by split a string value.
func (c *Config) StringsBySplit(key, sep string) (ss []string) {
if str, ok := c.getString(key); ok {
ss = strutil.Split(str, sep)
}
return
}
// StringMap get config data as a map[string]string
func StringMap(key string) map[string]string { return dc.StringMap(key) }
+14 -23
View File
@@ -13,12 +13,15 @@ import (
// ValDecodeHookFunc returns a mapstructure.DecodeHookFunc
// that parse ENV var, and more custom parse
func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
str := data.(string)
if parseEnv {
str = envutil.ParseEnvValue(str)
}
if len(str) < 2 {
return str, nil
}
@@ -32,14 +35,19 @@ func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc {
return dur, nil
}
}
} else if parseEnv { // parse ENV value
str = envutil.ParseEnvValue(str)
}
return str, nil
}
}
// resolve format, check is alias
func (c *Config) resolveFormat(f string) string {
if name, ok := c.aliasMap[f]; ok {
return name
}
return f
}
/*************************************************************
* Deprecated methods
*************************************************************/
@@ -55,7 +63,7 @@ func SetDecoder(format string, decoder Decoder) {
//
// Deprecated: please use driver instead
func (c *Config) SetDecoder(format string, decoder Decoder) {
format = fixFormat(format)
format = c.resolveFormat(format)
c.decoders[format] = decoder
}
@@ -79,7 +87,7 @@ func SetEncoder(format string, encoder Encoder) {
//
// Deprecated: please use driver instead
func (c *Config) SetEncoder(format string, encoder Encoder) {
format = fixFormat(format)
format = c.resolveFormat(format)
c.encoders[format] = encoder
}
@@ -141,20 +149,3 @@ func parseVarNameAndType(key string) (string, string) {
func formatKey(key, sep string) string {
return strings.Trim(strings.TrimSpace(key), sep)
}
// resolve fix inc/conf/yaml format
func fixFormat(f string) string {
if f == Yml {
f = Yaml
}
if f == "inc" {
f = Ini
}
// eg nginx config file.
if f == "conf" {
f = Hcl
}
return f
}
+2 -4
View File
@@ -5,10 +5,9 @@ Usage please see example:
*/
package yaml
// see https://pkg.go.dev/gopkg.in/yaml.v2
import (
"github.com/goccy/go-yaml"
"github.com/gookit/config/v2"
"gopkg.in/yaml.v2"
)
// Decoder the yaml content decoder
@@ -18,5 +17,4 @@ var Decoder config.Decoder = yaml.Unmarshal
var Encoder config.Encoder = yaml.Marshal
// Driver for yaml
// TIP: recommended use the yamlv3.Driver
var Driver = config.NewDriver(config.Yaml, Decoder, Encoder)
var Driver = config.NewDriver(config.Yaml, Decoder, Encoder).WithAliases(config.Yml)
+1 -1
View File
@@ -29,7 +29,7 @@ readme:
readme-c: ## Generate or update README file and commit change to git
readme-c: readme
git add README.* internal
git commit -m "doc: update and re-generate README docs"
git commit -m ":memo: doc: update and re-generate README docs"
csfix: ## Fix code style for all files by go fmt
csfix:
+939 -686
View File
File diff suppressed because it is too large Load Diff
+940 -685
View File
File diff suppressed because it is too large Load Diff
+2
View File
@@ -12,6 +12,8 @@ go get github.com/gookit/goutil/arrutil
## Functions API
> **Note**: doc by run `go doc ./arrutil`
```go
func AnyToString(arr any) string
func CloneSlice(data any) interface{}
+18 -4
View File
@@ -104,11 +104,15 @@ func RandomOne[T any](arr []T) T {
}
// Unique value in the given slice data.
func Unique[T ~string | comdef.XintOrFloat](arr []T) []T {
valMap := make(map[T]struct{}, len(arr))
uniArr := make([]T, 0, len(arr))
func Unique[T ~string | comdef.XintOrFloat](list []T) []T {
if len(list) < 2 {
return list
}
for _, t := range arr {
valMap := make(map[T]struct{}, len(list))
uniArr := make([]T, 0, len(list))
for _, t := range list {
if _, ok := valMap[t]; !ok {
valMap[t] = struct{}{}
uniArr = append(uniArr, t)
@@ -116,3 +120,13 @@ func Unique[T ~string | comdef.XintOrFloat](arr []T) []T {
}
return uniArr
}
// IndexOf value in given slice.
func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int {
for i, v := range list {
if v == val {
return i
}
}
return -1
}
+41 -4
View File
@@ -4,6 +4,7 @@ import (
"reflect"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/mathutil"
)
@@ -40,12 +41,48 @@ func StringsHas(ss []string, val string) bool {
return false
}
// HasValue check array(strings, intXs, uintXs) should be contained the given value(int(X),string).
func HasValue(arr, val any) bool {
return Contains(arr, val)
// NotIn check the given value whether not in the list
func NotIn[T comdef.ScalarType](value T, list []T) bool {
return !In(value, list)
}
// Contains check array(strings, intXs, uintXs) should be contained the given value(int(X),string).
// In check the given value whether in the list
func In[T comdef.ScalarType](value T, list []T) bool {
for _, elem := range list {
if elem == value {
return true
}
}
return false
}
// ContainsAll check given values is sub-list of sample list.
func ContainsAll[T comdef.ScalarType](list, values []T) bool {
return IsSubList(values, list)
}
// IsSubList check given values is sub-list of sample list.
func IsSubList[T comdef.ScalarType](values, list []T) bool {
for _, value := range values {
if !In(value, list) {
return false
}
}
return true
}
// IsParent check given values is parent-list of samples.
func IsParent[T comdef.ScalarType](values, list []T) bool {
return IsSubList(list, values)
}
// HasValue check array(strings, intXs, uintXs) should be contained the given value(int(X),string).
func HasValue(arr, val any) bool { return Contains(arr, val) }
// Contains check slice/array(strings, intXs, uintXs) should be contained the given value(int(X),string).
//
// TIP: Difference the In(), Contains() will try to convert value type,
// and Contains() support array type.
func Contains(arr, val any) bool {
if val == nil || arr == nil {
return false
+52
View File
@@ -160,6 +160,58 @@ func CloneSlice(data any) any {
return reflect.AppendSlice(reflect.New(reflect.SliceOf(typeOfData.Elem())).Elem(), reflect.ValueOf(data)).Interface()
}
// Differences Produces the set difference of two slice according to a comparer function.
//
// first: the first slice. MUST BE A SLICE.
// second: the second slice. MUST BE A SLICE.
// fn: the comparer function.
// returns: the difference of the two slices.
func Differences[T any](first, second []T, fn Comparer) []T {
typeOfFirst := reflect.TypeOf(first)
if typeOfFirst.Kind() != reflect.Slice {
panic("collections.Excepts: first must be a slice")
}
typeOfSecond := reflect.TypeOf(second)
if typeOfSecond.Kind() != reflect.Slice {
panic("collections.Excepts: second must be a slice")
}
firstLen := len(first)
if firstLen == 0 {
return CloneSlice(second).([]T)
}
secondLen := len(second)
if secondLen == 0 {
return CloneSlice(first).([]T)
}
max := firstLen
if secondLen > firstLen {
max = secondLen
}
result := make([]T, 0)
for i := 0; i < max; i++ {
if i < firstLen {
s := first[i]
if i, _ := TwowaySearch(second, s, fn); i < 0 {
result = append(result, s)
}
}
if i < secondLen {
t := second[i]
if i, _ := TwowaySearch(first, t, fn); i < 0 {
result = append(result, t)
}
}
}
return result
}
// Excepts Produces the set difference of two slice according to a comparer function.
//
// first: the first slice. MUST BE A SLICE.
+4 -2
View File
@@ -2,12 +2,14 @@ package arrutil
// type MapFn func(obj T) (target V, find bool)
// Map an object list [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
// Map a list to new list
//
// eg: mapping [object0{},object1{},...] to flatten list [object0.someKey, object1.someKey, ...]
func Map[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
flatArr := make([]V, 0, len(list))
for _, obj := range list {
if target, find := mapFn(obj); find {
if target, ok := mapFn(obj); ok {
flatArr = append(flatArr, target)
}
}
+143 -39
View File
@@ -6,7 +6,9 @@ import (
"strconv"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/strutil"
)
@@ -14,7 +16,7 @@ import (
var ErrInvalidType = errors.New("the input param type is invalid")
/*************************************************************
* helper func for strings
* Join func for slice
*************************************************************/
// JoinStrings alias of strings.Join
@@ -27,32 +29,21 @@ func StringsJoin(sep string, ss ...string) string {
return strings.Join(ss, sep)
}
// StringsToInts string slice to int slice
func StringsToInts(ss []string) (ints []int, err error) {
for _, str := range ss {
iVal, err := strconv.Atoi(str)
if err != nil {
return nil, err
// JoinSlice join []any slice to string.
func JoinSlice(sep string, arr ...any) string {
if arr == nil {
return ""
}
var sb strings.Builder
for i, v := range arr {
if i > 0 {
sb.WriteString(sep)
}
ints = append(ints, iVal)
sb.WriteString(strutil.QuietString(v))
}
return
}
// MustToStrings convert array or slice to []string
func MustToStrings(arr any) []string {
ret, _ := ToStrings(arr)
return ret
}
// StringsToSlice convert []string to []any
func StringsToSlice(ss []string) []any {
args := make([]any, len(ss))
for i, s := range ss {
args[i] = s
}
return args
return sb.String()
}
/*************************************************************
@@ -88,14 +79,79 @@ func MustToInt64s(arr any) []int64 {
func SliceToInt64s(arr []any) []int64 {
i64s := make([]int64, len(arr))
for i, v := range arr {
i64s[i] = mathutil.MustInt64(v)
i64s[i] = mathutil.QuietInt64(v)
}
return i64s
}
// StringsAsInts convert and ignore error
func StringsAsInts(ss []string) []int {
ints, _ := StringsTryInts(ss)
return ints
}
// StringsToInts string slice to int slice
func StringsToInts(ss []string) (ints []int, err error) {
return StringsTryInts(ss)
}
// StringsTryInts string slice to int slice
func StringsTryInts(ss []string) (ints []int, err error) {
for _, str := range ss {
iVal, err := strconv.Atoi(str)
if err != nil {
return nil, err
}
ints = append(ints, iVal)
}
return
}
// AnyToSlice convert any(allow: array,slice) to []any
func AnyToSlice(sl any) (ls []any, err error) {
rfKeys := reflect.ValueOf(sl)
if rfKeys.Kind() != reflect.Slice && rfKeys.Kind() != reflect.Array {
return nil, ErrInvalidType
}
for i := 0; i < rfKeys.Len(); i++ {
ls = append(ls, rfKeys.Index(i).Interface())
}
return
}
// AnyToStrings convert array or slice to []string
func AnyToStrings(arr any) []string {
ret, _ := ToStrings(arr)
return ret
}
// MustToStrings convert array or slice to []string
func MustToStrings(arr any) []string {
ret, err := ToStrings(arr)
if err != nil {
panic(err)
}
return ret
}
// StringsToSlice convert []string to []any
func StringsToSlice(ss []string) []any {
args := make([]any, len(ss))
for i, s := range ss {
args[i] = s
}
return args
}
// ToStrings convert any(allow: array,slice) to []string
func ToStrings(arr any) (ret []string, err error) {
rv := reflect.ValueOf(arr)
if rv.Kind() == reflect.String {
return []string{rv.String()}, nil
}
if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
err = ErrInvalidType
return
@@ -114,13 +170,47 @@ func ToStrings(arr any) (ret []string, err error) {
// SliceToStrings convert []any to []string
func SliceToStrings(arr []any) []string {
return QuietStrings(arr)
}
// QuietStrings convert []any to []string
func QuietStrings(arr []any) []string {
ss := make([]string, len(arr))
for i, v := range arr {
ss[i] = strutil.MustString(v)
ss[i] = strutil.QuietString(v)
}
return ss
}
// ConvType convert type of slice elements to new type slice, by the given newElemTyp type.
//
// Supports conversion between []string, []intX, []uintX, []floatX.
//
// Usage:
//
// ints, _ := arrutil.ConvType([]string{"12", "23"}, 1) // []int{12, 23}
func ConvType[T any, R any](arr []T, newElemTyp R) ([]R, error) {
newArr := make([]R, len(arr))
elemTyp := reflect.TypeOf(newElemTyp)
for i, elem := range arr {
var anyElem any = elem
// type is same.
if _, ok := anyElem.(R); ok {
newArr[i] = anyElem.(R)
continue
}
// need conv type.
rfVal, err := reflects.ValueByType(elem, elemTyp)
if err != nil {
return nil, err
}
newArr[i] = rfVal.Interface().(R)
}
return newArr, nil
}
// AnyToString simple and quickly convert any array, slice to string
func AnyToString(arr any) string {
return NewFormatter(arr).Format()
@@ -143,26 +233,40 @@ func ToString(arr []any) string {
if i > 0 {
sb.WriteByte(',')
}
sb.WriteString(strutil.MustString(v))
sb.WriteString(strutil.QuietString(v))
}
sb.WriteByte(']')
return sb.String()
}
// JoinSlice join []any slice to string.
func JoinSlice(sep string, arr ...any) string {
if arr == nil {
return ""
}
// CombineToMap combine two slice to map[K]V.
//
// If keys length is greater than values, the extra keys will be ignored.
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
ln := len(values)
mp := make(map[K]V, len(keys))
var sb strings.Builder
for i, v := range arr {
if i > 0 {
sb.WriteString(sep)
for i, key := range keys {
if i >= ln {
break
}
sb.WriteString(strutil.MustString(v))
mp[key] = values[i]
}
return sb.String()
return mp
}
// CombineToSMap combine two string-slice to map[string]string
func CombineToSMap(keys, values []string) map[string]string {
ln := len(values)
mp := make(map[string]string, len(keys))
for i, key := range keys {
if ln > i {
mp[key] = values[i]
} else {
mp[key] = ""
}
}
return mp
}
+13
View File
@@ -42,6 +42,11 @@ func (ss Strings) Join(sep string) string {
// Has given element
func (ss Strings) Has(sub string) bool {
return ss.Contains(sub)
}
// Contains given element
func (ss Strings) Contains(sub string) bool {
for _, s := range ss {
if s == sub {
return true
@@ -49,3 +54,11 @@ func (ss Strings) Has(sub string) bool {
}
return false
}
// First element value.
func (ss Strings) First() string {
if len(ss) > 0 {
return ss[0]
}
return ""
}
+80
View File
@@ -0,0 +1,80 @@
// Package basefn provide some no-dependents util functions
package basefn
import "fmt"
// Panicf format panic message use fmt.Sprintf
func Panicf(format string, v ...any) {
panic(fmt.Sprintf(format, v...))
}
// MustOK if error is not empty, will panic
func MustOK(err error) {
if err != nil {
panic(err)
}
}
// Must if error is not empty, will panic
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
// 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
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()
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()
}
@@ -1,6 +1,28 @@
package fmtutil
package basefn
import "fmt"
import (
"fmt"
)
// DataSize format bytes number friendly. eg: 1024 => 1KB, 1024*1024 => 1MB
//
// Usage:
//
// file, err := os.Open(path)
// fl, err := file.Stat()
// fmtSize := DataSize(fl.Size())
func DataSize(size uint64) string {
switch {
case size < 1024:
return fmt.Sprintf("%dB", size)
case size < 1024*1024:
return fmt.Sprintf("%.2fK", float64(size)/1024)
case size < 1024*1024*1024:
return fmt.Sprintf("%.2fM", float64(size)/1024/1024)
default:
return fmt.Sprintf("%.2fG", float64(size)/1024/1024/1024)
}
}
var timeFormats = [][]int{
{0},
+55
View File
@@ -0,0 +1,55 @@
# Bytes Util
Provide some commonly bytes util functions.
## Install
```shell
go get github.com/gookit/goutil/byteutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/byteutil)
## Functions API
> **Note**: doc by run `go doc ./byteutil`
```go
func AppendAny(dst []byte, v any) []byte
func FirstLine(bs []byte) []byte
func IsNumChar(c byte) bool
func Md5(src any) []byte
func Random(length int) ([]byte, error)
func SafeString(bs []byte, err error) string
func StrOrErr(bs []byte, err error) (string, error)
func String(b []byte) string
func ToString(b []byte) string
type Buffer struct{ ... }
func NewBuffer() *Buffer
type BytesEncoder interface{ ... }
type ChanPool struct{ ... }
func NewChanPool(maxSize int, width int, capWidth int) *ChanPool
type StdEncoder struct{ ... }
func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./byteutil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./byteutil/...
```
+65
View File
@@ -0,0 +1,65 @@
package byteutil
import (
"bytes"
"fmt"
"strings"
)
// Buffer wrap and extends the bytes.Buffer
type Buffer struct {
bytes.Buffer
}
// NewBuffer instance
func NewBuffer() *Buffer {
return &Buffer{}
}
// WriteAny type value to buffer
func (b *Buffer) WriteAny(vs ...any) {
for _, v := range vs {
_, _ = b.Buffer.WriteString(fmt.Sprint(v))
}
}
// QuietWriteByte to buffer
func (b *Buffer) QuietWriteByte(c byte) {
_ = b.WriteByte(c)
}
// QuietWritef write message to buffer
func (b *Buffer) QuietWritef(tpl string, vs ...any) {
_, _ = b.WriteString(fmt.Sprintf(tpl, vs...))
}
// Writeln write message to buffer with newline
func (b *Buffer) Writeln(ss ...string) {
b.QuietWriteln(ss...)
}
// QuietWriteln write message to buffer with newline
func (b *Buffer) QuietWriteln(ss ...string) {
_, _ = b.WriteString(strings.Join(ss, ""))
_ = b.WriteByte('\n')
}
// QuietWriteString to buffer
func (b *Buffer) QuietWriteString(ss ...string) {
_, _ = b.WriteString(strings.Join(ss, ""))
}
// MustWriteString to buffer
func (b *Buffer) MustWriteString(ss ...string) {
_, err := b.WriteString(strings.Join(ss, ""))
if err != nil {
panic(err)
}
}
// ResetAndGet buffer string.
func (b *Buffer) ResetAndGet() string {
s := b.String()
b.Reset()
return s
}
+115
View File
@@ -0,0 +1,115 @@
package byteutil
import (
"bytes"
"fmt"
"math/rand"
"strconv"
"time"
"unsafe"
)
// Random bytes generate
func Random(length int) ([]byte, error) {
b := make([]byte, length)
// Note that err == nil only if we read len(b) bytes.
if _, err := rand.Read(b); err != nil {
return nil, err
}
return b, nil
}
// FirstLine from command output
func FirstLine(bs []byte) []byte {
if i := bytes.IndexByte(bs, '\n'); i >= 0 {
return bs[0:i]
}
return bs
}
// StrOrErr convert to string, return empty string on error.
func StrOrErr(bs []byte, err error) (string, error) {
if err != nil {
return "", err
}
return string(bs), err
}
// SafeString convert to string, return empty string on error.
func SafeString(bs []byte, err error) string {
if err != nil {
return ""
}
return string(bs)
}
// String unsafe convert bytes to string
func String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// ToString convert bytes to string
func ToString(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
// AppendAny append any value to byte slice
func AppendAny(dst []byte, v any) []byte {
if v == nil {
return append(dst, "<nil>"...)
}
switch val := v.(type) {
case []byte:
dst = append(dst, val...)
case string:
dst = append(dst, val...)
case int:
dst = strconv.AppendInt(dst, int64(val), 10)
case int8:
dst = strconv.AppendInt(dst, int64(val), 10)
case int16:
dst = strconv.AppendInt(dst, int64(val), 10)
case int32:
dst = strconv.AppendInt(dst, int64(val), 10)
case int64:
dst = strconv.AppendInt(dst, val, 10)
case uint:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint8:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint16:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint32:
dst = strconv.AppendUint(dst, uint64(val), 10)
case uint64:
dst = strconv.AppendUint(dst, val, 10)
case float32:
dst = strconv.AppendFloat(dst, float64(val), 'f', -1, 32)
case float64:
dst = strconv.AppendFloat(dst, val, 'f', -1, 64)
case bool:
dst = strconv.AppendBool(dst, val)
case time.Time:
dst = val.AppendFormat(dst, time.RFC3339)
case time.Duration:
dst = strconv.AppendInt(dst, int64(val), 10)
case error:
dst = append(dst, val.Error()...)
case fmt.Stringer:
dst = append(dst, val.String()...)
default:
dst = append(dst, fmt.Sprint(v)...)
}
return dst
}
// Cut bytes. like the strings.Cut()
func Cut(bs []byte, sep byte) (before, after []byte, found bool) {
if i := bytes.IndexByte(bs, sep); i >= 0 {
return bs[:i], bs[i+1:], true
}
before = bs
return
}
+19
View File
@@ -0,0 +1,19 @@
// Package byteutil Provide some bytes utils functions or structs
package byteutil
import (
"crypto/md5"
"fmt"
)
// Md5 Generate a 32-bit md5 bytes
func Md5(src any) []byte {
h := md5.New()
if s, ok := src.(string); ok {
h.Write([]byte(s))
} else {
h.Write([]byte(fmt.Sprint(src)))
}
return h.Sum(nil)
}
+4
View File
@@ -0,0 +1,4 @@
package byteutil
// IsNumChar returns true if the given character is a numeric, otherwise false.
func IsNumChar(c byte) bool { return c >= '0' && c <= '9' }
+63
View File
@@ -0,0 +1,63 @@
package byteutil
import (
"encoding/base64"
"encoding/hex"
)
// BytesEncoder interface
type BytesEncoder interface {
Encode(src []byte) []byte
Decode(src []byte) ([]byte, error)
}
// StdEncoder implement the BytesEncoder
type StdEncoder struct {
encodeFn func(src []byte) []byte
decodeFn func(src []byte) ([]byte, error)
}
// NewStdEncoder instance
func NewStdEncoder(encFn func(src []byte) []byte, decFn func(src []byte) ([]byte, error)) *StdEncoder {
return &StdEncoder{
encodeFn: encFn,
decodeFn: decFn,
}
}
// Encode input
func (e *StdEncoder) Encode(src []byte) []byte {
return e.encodeFn(src)
}
// Decode input
func (e *StdEncoder) Decode(src []byte) ([]byte, error) {
return e.decodeFn(src)
}
var (
// HexEncoder instance
HexEncoder = NewStdEncoder(func(src []byte) []byte {
dst := make([]byte, hex.EncodedLen(len(src)))
hex.Encode(dst, src)
return dst
}, func(src []byte) ([]byte, error) {
n, err := hex.Decode(src, src)
return src[:n], err
})
// B64Encoder instance
B64Encoder = NewStdEncoder(func(src []byte) []byte {
b64Dst := make([]byte, base64.StdEncoding.EncodedLen(len(src)))
base64.StdEncoding.Encode(b64Dst, src)
return b64Dst
}, func(src []byte) ([]byte, error) {
dBuf := make([]byte, base64.StdEncoding.DecodedLen(len(src)))
n, err := base64.StdEncoding.Decode(dBuf, src)
if err != nil {
return nil, err
}
return dBuf[:n], err
})
)
+64
View File
@@ -0,0 +1,64 @@
package byteutil
// ChanPool struct
//
// Usage:
//
// bp := strutil.NewByteChanPool(500, 1024, 1024)
// buf:=bp.Get()
// defer bp.Put(buf)
// // use buf do something ...
//
// refer https://www.flysnow.org/2020/08/21/golang-chan-byte-pool.html
// from https://github.com/minio/minio/blob/master/internal/bpool/bpool.go
type ChanPool struct {
c chan []byte
w int
wcap int
}
// NewChanPool instance
func NewChanPool(maxSize int, width int, capWidth int) *ChanPool {
return &ChanPool{
c: make(chan []byte, maxSize),
w: width,
wcap: capWidth,
}
}
// Get gets a []byte from the BytePool, or creates a new one if none are
// available in the pool.
func (bp *ChanPool) Get() (b []byte) {
select {
case b = <-bp.c:
// reuse existing buffer
default:
// create new buffer
if bp.wcap > 0 {
b = make([]byte, bp.w, bp.wcap)
} else {
b = make([]byte, bp.w)
}
}
return
}
// Put returns the given Buffer to the BytePool.
func (bp *ChanPool) Put(b []byte) {
select {
case bp.c <- b:
// buffer went back into pool
default:
// buffer didn't go back into pool, just discard
}
}
// Width returns the width of the byte arrays in this pool.
func (bp *ChanPool) Width() (n int) {
return bp.w
}
// WidthCap returns the cap width of the byte arrays in this pool.
func (bp *ChanPool) WidthCap() (n int) {
return bp.wcap
}
+36 -42
View File
@@ -2,27 +2,28 @@ package cmdline
import (
"strings"
"github.com/gookit/goutil/strutil"
)
// LineBuilder build command line string.
// codes refer from strings.Builder
type LineBuilder struct {
buf []byte
strings.Builder
}
// NewBuilder create
func NewBuilder(binFile string, args ...string) *LineBuilder {
b := &LineBuilder{}
b.AddArg(binFile)
if binFile != "" {
b.AddArg(binFile)
}
b.AddArray(args)
return b
}
// LineBuild build command line string by given args.
func LineBuild(binFile string, args []string) string {
return NewBuilder(binFile, args...).String()
}
// AddArg to builder
func (b *LineBuilder) AddArg(arg string) {
_, _ = b.WriteString(arg)
@@ -40,52 +41,45 @@ func (b *LineBuilder) AddArray(args []string) {
}
}
// AddAny args to builder
func (b *LineBuilder) AddAny(args ...any) {
for _, arg := range args {
_, _ = b.WriteString(strutil.SafeString(arg))
}
}
// WriteString arg string to the builder, will auto quote special string.
// refer strconv.Quote()
func (b *LineBuilder) WriteString(a string) (int, error) {
var quote byte
if strings.ContainsRune(a, '"') {
if pos := strings.IndexByte(a, '"'); pos > -1 {
quote = '\''
} else if a == "" || strings.ContainsRune(a, '\'') || strings.ContainsRune(a, ' ') {
// fix: a = `--pretty=format:"one two three"`
if pos > 0 && '"' == a[len(a)-1] {
quote = 0
}
} else if pos := strings.IndexByte(a, '\''); pos > -1 {
quote = '"'
// fix: a = "--pretty=format:'one two three'"
if pos > 0 && '\'' == a[len(a)-1] {
quote = 0
}
} else if a == "" || strings.ContainsRune(a, ' ') {
quote = '"'
}
// add sep on first write.
if b.buf != nil {
b.buf = append(b.buf, ' ')
// add sep on not-first write.
if b.Len() != 0 {
_ = b.WriteByte(' ')
}
// no quote char
// no quote char OR not need quote
if quote == 0 {
b.buf = append(b.buf, a...)
return len(a) + 1, nil
return b.Builder.WriteString(a)
}
b.buf = append(b.buf, quote) // add start quote
b.buf = append(b.buf, a...)
b.buf = append(b.buf, quote) // add end quote
return len(a) + 3, nil
_ = b.WriteByte(quote) // add start quote
n, err := b.Builder.WriteString(a)
_ = b.WriteByte(quote) // add end quote
return n, err
}
// String to command line string
func (b *LineBuilder) String() string {
return string(b.buf)
}
// Len of the builder
func (b *LineBuilder) Len() int {
return len(b.buf)
}
// Reset builder
func (b *LineBuilder) Reset() {
b.buf = nil
}
// grow copies the buffer to a new, larger buffer so that there are at least n
// bytes of capacity beyond len(b.buf).
// func (b *LineBuilder) grow(n int) {
// buf := make([]byte, len(b.buf), 2*cap(b.buf)+n)
// copy(buf, b.buf)
// b.buf = buf
// }
+10
View File
@@ -1,2 +1,12 @@
// Package cmdline provide quick build and parse cmd line string.
package cmdline
// LineBuild build command line string by given args.
func LineBuild(binFile string, args []string) string {
return NewBuilder(binFile, args...).String()
}
// ParseLine input command line text. alias of the StringToOSArgs()
func ParseLine(line string) []string {
return NewParser(line).Parse()
}
+115 -100
View File
@@ -1,9 +1,13 @@
package cmdline
import (
"os"
"bytes"
"os/exec"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
// LineParser struct
@@ -11,7 +15,7 @@ import (
type LineParser struct {
parsed bool
// Line the full input command line text
// eg `kite top sub -a "the a message" --foo val1 --bar "val 2"`
// eg `kite top sub -a "this is a message" --foo val1 --bar "val 2"`
Line string
// ParseEnv parse ENV var on the line.
ParseEnv bool
@@ -19,6 +23,11 @@ type LineParser struct {
nodes []string
// the parsed args
args []string
// temp value
quoteChar byte
quoteIndex int // if > 0, mark is not on start
tempNode bytes.Buffer
}
// NewParser create
@@ -26,11 +35,10 @@ func NewParser(line string) *LineParser {
return &LineParser{Line: line}
}
// ParseLine input command line text. alias of the StringToOSArgs()
func ParseLine(line string) []string {
p := &LineParser{Line: line}
return p.Parse()
// WithParseEnv with parse ENV var
func (p *LineParser) WithParseEnv() *LineParser {
p.ParseEnv = true
return p
}
// AlsoEnvParse input command line text to os.Args, will parse ENV var
@@ -39,90 +47,13 @@ func (p *LineParser) AlsoEnvParse() []string {
return p.Parse()
}
// Parse input command line text to os.Args
func (p *LineParser) Parse() []string {
if p.parsed {
return p.args
}
// NewExecCmd quick create exec.Cmd by cmdline string
func (p *LineParser) NewExecCmd() *exec.Cmd {
// parse get bin and args
binName, args := p.BinAndArgs()
p.parsed = true
p.Line = strings.TrimSpace(p.Line)
if p.Line == "" {
return p.args
}
// enable parse Env var
if p.ParseEnv {
p.Line = os.ExpandEnv(p.Line)
}
p.nodes = strings.Split(p.Line, " ")
if len(p.nodes) == 1 {
p.args = p.nodes
return p.args
}
// temp value
var quoteChar, fullNode string
for _, node := range p.nodes {
if node == "" {
continue
}
nodeLen := len(node)
start, end := node[:1], node[nodeLen-1:]
var clearTemp bool
if start == "'" || start == `"` {
noStart := node[1:]
if quoteChar == "" { // start
// only one words. eg: `-m "msg"`
if end == start {
p.args = append(p.args, node[1:nodeLen-1])
continue
}
fullNode += noStart
quoteChar = start
} else if quoteChar == start { // invalid. eg: `-m "this is "message` `-m "this is "message"`
p.appendWithPrefix(strings.Trim(node, quoteChar), fullNode)
clearTemp = true // clear temp value
} else if quoteChar == end { // eg: `"has inner 'quote'"`
p.appendWithPrefix(node[:nodeLen-1], fullNode)
clearTemp = true // clear temp value
} else { // goon. eg: `-m "the 'some' message"`
fullNode += " " + node
}
} else if end == "'" || end == `"` {
noEnd := node[:nodeLen-1]
if quoteChar == "" { // end
p.appendWithPrefix(noEnd, fullNode)
clearTemp = true // clear temp value
} else if quoteChar == end { // end
p.appendWithPrefix(noEnd, fullNode)
clearTemp = true // clear temp value
} else { // goon. eg: `-m "the 'some' message"`
fullNode += " " + node
}
} else {
if quoteChar != "" {
fullNode += " " + node
} else {
p.args = append(p.args, node)
}
}
// clear temp value
if clearTemp {
quoteChar, fullNode = "", ""
}
}
if fullNode != "" {
p.args = append(p.args, fullNode)
}
return p.args
// create a new Cmd instance
return exec.Command(binName, args...)
}
// BinAndArgs get binName and args
@@ -141,19 +72,103 @@ func (p *LineParser) BinAndArgs() (bin string, args []string) {
return
}
// NewExecCmd quick create exec.Cmd by cmdline string
func (p *LineParser) NewExecCmd() *exec.Cmd {
// parse get bin and args
binName, args := p.BinAndArgs()
// Parse input command line text to os.Args
func (p *LineParser) Parse() []string {
if p.parsed {
return p.args
}
// create a new Cmd instance
return exec.Command(binName, args...)
p.parsed = true
p.Line = strings.TrimSpace(p.Line)
if p.Line == "" {
return p.args
}
// enable parse Env var
if p.ParseEnv {
p.Line = comfunc.ParseEnvVar(p.Line, nil)
}
p.nodes = strings.Split(p.Line, " ")
if len(p.nodes) == 1 {
p.args = p.nodes
return p.args
}
for i := 0; i < len(p.nodes); i++ {
node := p.nodes[i]
if node == "" {
continue
}
p.parseNode(node)
}
p.nodes = p.nodes[:0]
if p.tempNode.Len() > 0 {
p.appendTempNode()
}
return p.args
}
func (p *LineParser) appendWithPrefix(node, prefix string) {
if prefix != "" {
p.args = append(p.args, prefix+" "+node)
func (p *LineParser) parseNode(node string) {
maxIdx := len(node) - 1
start, end := node[0], node[maxIdx]
// in quotes
if p.quoteChar != 0 {
p.tempNode.WriteByte(' ')
// end quotes
if end == p.quoteChar {
if p.quoteIndex > 0 {
p.tempNode.WriteString(node) // eg: node="--pretty=format:'one two'"
} else {
p.tempNode.WriteString(node[:maxIdx]) // remove last quote
}
p.appendTempNode()
} else { // goon ... write to temp node
p.tempNode.WriteString(node)
}
return
}
// quote start
if start == comdef.DoubleQuote || start == comdef.SingleQuote {
// only one words. eg: `-m "msg"`
if end == start {
p.args = append(p.args, node[1:maxIdx])
return
}
p.quoteChar = start
p.tempNode.WriteString(node[1:])
} else if end == comdef.DoubleQuote || end == comdef.SingleQuote {
p.args = append(p.args, node) // only one node: `msg"`
} else {
p.args = append(p.args, node)
// eg: --pretty=format:'one two three'
if strutil.ContainsByte(node, comdef.DoubleQuote) {
p.quoteIndex = 1 // mark is not on start
p.quoteChar = comdef.DoubleQuote
} else if strutil.ContainsByte(node, comdef.SingleQuote) {
p.quoteIndex = 1
p.quoteChar = comdef.SingleQuote
}
// in quote, append to temp-node
if p.quoteChar != 0 {
p.tempNode.WriteString(node)
} else {
p.args = append(p.args, node)
}
}
}
func (p *LineParser) appendTempNode() {
p.args = append(p.args, p.tempNode.String())
// reset context value
p.quoteChar = 0
p.quoteIndex = 0
p.tempNode.Reset()
}
+2
View File
@@ -23,3 +23,5 @@ const (
// NoIdx invalid index or length
const NoIdx = -1
// const VarPathReg = `(\w[\w-]*(?:\.[\w-]+)*)`
+10 -3
View File
@@ -7,12 +7,11 @@ type Int interface {
// Uint interface type
type Uint interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Xint interface type. all int or uint types
type Xint interface {
// equal: ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint32 | ~uint64
Int | Uint
}
@@ -26,11 +25,19 @@ type IntOrFloat interface {
Int | Float
}
// XintOrFloat interface type. all (x)int and float types
// XintOrFloat interface type. all int, uint and float types
type XintOrFloat interface {
Int | Uint | Float
}
// SortedType interface type.
// that supports the operators < <= >= >.
//
// contains: (x)int, float, ~string types
type SortedType interface {
Int | Uint | Float | ~string
}
// ScalarType interface type.
//
// contains: (x)int, float, ~string, ~bool types
+2
View File
@@ -14,6 +14,8 @@ go get github.com/gookit/goutil/envutil
## Functions API
> **Note**: doc by run `go doc ./envutil`
```go
func Environ() map[string]string
func GetBool(name string, def ...bool) bool
+21 -2
View File
@@ -1,3 +1,4 @@
// Package envutil provide some commonly ENV util functions.
package envutil
import (
@@ -41,9 +42,27 @@ func ParseValue(val string) (newVal string) {
return comfunc.ParseEnvVar(val, ValueGetter)
}
// SetEnvs to os
func SetEnvs(mp map[string]string) {
// SetEnvMap set multi ENV(string-map) to os
func SetEnvMap(mp map[string]string) {
for key, value := range mp {
_ = os.Setenv(key, value)
}
}
// SetEnvs set multi k-v ENV pairs to os
func SetEnvs(kvPairs ...string) {
if len(kvPairs)%2 == 1 {
panic("envutil.SetEnvs: odd argument count")
}
for i := 0; i < len(kvPairs); i += 2 {
_ = os.Setenv(kvPairs[i], kvPairs[i+1])
}
}
// UnsetEnvs from os
func UnsetEnvs(keys ...string) {
for _, key := range keys {
_ = os.Unsetenv(key)
}
}
+42 -3
View File
@@ -2,6 +2,7 @@ package envutil
import (
"os"
"path/filepath"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
@@ -40,7 +41,45 @@ func GetBool(name string, def ...bool) bool {
return false
}
// Environ like os.Environ, but will returns key-value map[string]string data.
func Environ() map[string]string {
return comfunc.Environ()
// GetMulti ENV values by input names.
func GetMulti(names ...string) map[string]string {
valMap := make(map[string]string, len(names))
for _, name := range names {
if val := os.Getenv(name); val != "" {
valMap[name] = val
}
}
return valMap
}
// EnvPaths get and split $PATH to []string
func EnvPaths() []string {
return filepath.SplitList(os.Getenv("PATH"))
}
// EnvMap like os.Environ, but will returns key-value map[string]string data.
func EnvMap() map[string]string { return comfunc.Environ() }
// Environ like os.Environ, but will returns key-value map[string]string data.
func Environ() map[string]string { return comfunc.Environ() }
// SearchEnvKeys values by given keywords
func SearchEnvKeys(keywords string) map[string]string {
return SearchEnv(keywords, false)
}
// SearchEnv values by given keywords
func SearchEnv(keywords string, matchValue bool) map[string]string {
founded := make(map[string]string)
for name, val := range comfunc.Environ() {
if strutil.IContains(name, keywords) {
founded[name] = val
} else if matchValue && strutil.IContains(val, keywords) {
founded[name] = val
}
}
return founded
}
+61
View File
@@ -0,0 +1,61 @@
package errorx
import (
"errors"
"fmt"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/comfunc"
)
// IsTrue assert result is true, otherwise will return error
func IsTrue(result bool, fmtAndArgs ...any) error {
if !result {
return errors.New(formatErrMsg("result should be True", fmtAndArgs))
}
return nil
}
// IsFalse assert result is false, otherwise will return error
func IsFalse(result bool, fmtAndArgs ...any) error {
if result {
return errors.New(formatErrMsg("result should be False", fmtAndArgs))
}
return nil
}
// IsIn value should be in the list, otherwise will return error
func IsIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error {
if arrutil.NotIn(value, list) {
var errMsg string
if len(fmtAndArgs) > 0 {
errMsg = comfunc.FormatTplAndArgs(fmtAndArgs)
} else {
errMsg = fmt.Sprintf("value should be in the %v", list)
}
return errors.New(errMsg)
}
return nil
}
// NotIn value should not be in the list, otherwise will return error
func NotIn[T comdef.ScalarType](value T, list []T, fmtAndArgs ...any) error {
if arrutil.In(value, list) {
var errMsg string
if len(fmtAndArgs) > 0 {
errMsg = comfunc.FormatTplAndArgs(fmtAndArgs)
} else {
errMsg = fmt.Sprintf("value should not be in the %v", list)
}
return errors.New(errMsg)
}
return nil
}
func formatErrMsg(errMsg string, fmtAndArgs []any) string {
if len(fmtAndArgs) > 0 {
errMsg = comfunc.FormatTplAndArgs(fmtAndArgs)
}
return errMsg
}
+7
View File
@@ -37,6 +37,11 @@ func Fail(code int, msg string) ErrorR {
return &errorR{code: code, msg: msg}
}
// Failf code with error response
func Failf(code int, tpl string, v ...any) ErrorR {
return &errorR{code: code, msg: fmt.Sprintf(tpl, v...)}
}
// Suc success response reply
func Suc(msg string) ErrorR {
return &errorR{code: 0, msg: msg}
@@ -110,6 +115,8 @@ func (e ErrMap) One() error {
// Errors multi error list
type Errors []error
// ErrList alias for Errors
type ErrList = Errors
// Error string
+21 -1
View File
@@ -5,11 +5,31 @@ import (
"fmt"
)
// E new a raw go error. alias of errors.New()
func E(msg string) error {
return errors.New(msg)
}
// Err new a raw go error. alias of errors.New()
func Err(msg string) error {
return errors.New(msg)
}
// Raw new a raw go error. alias of errors.New()
func Raw(msg string) error {
return errors.New(msg)
}
// Ef new a raw go error. alias of errors.New()
func Ef(tpl string, vars ...any) error {
return fmt.Errorf(tpl, vars...)
}
// Errf new a raw go error. alias of errors.New()
func Errf(tpl string, vars ...any) error {
return fmt.Errorf(tpl, vars...)
}
// Rawf new a raw go error. alias of errors.New()
func Rawf(tpl string, vars ...any) error {
return fmt.Errorf(tpl, vars...)
@@ -54,7 +74,7 @@ func ToErrorX(err error) (ex *ErrorX, ok bool) {
return
}
// Has check err has contains target, or err is eq target.
// Has contains target error, or err is eq target.
// alias of errors.Is()
func Has(err, target error) bool {
return errors.Is(err, target)
-2
View File
@@ -1,2 +0,0 @@
// Package fmtutil provide some format util functions.
package fmtutil
-116
View File
@@ -1,116 +0,0 @@
package fmtutil
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"unicode"
)
// data size
const (
OneKByte = 1024
OneMByte = 1024 * 1024
OneGByte = 1024 * 1024
)
// DataSize format bytes number friendly.
//
// Usage:
//
// file, err := os.Open(path)
// fl, err := file.Stat()
// fmtSize := DataSize(fl.Size())
func DataSize(size uint64) string {
switch {
case size < 1024:
return fmt.Sprintf("%dB", size)
case size < 1024*1024:
return fmt.Sprintf("%.2fK", float64(size)/1024)
case size < 1024*1024*1024:
return fmt.Sprintf("%.2fM", float64(size)/1024/1024)
default:
return fmt.Sprintf("%.2fG", float64(size)/1024/1024/1024)
}
}
// SizeToString alias of the DataSize
func SizeToString(size uint64) string { return DataSize(size) }
// StringToByte alias of the ParseByte
func StringToByte(sizeStr string) uint64 { return ParseByte(sizeStr) }
// ParseByte converts size string like 1GB/1g or 12mb/12M into an unsigned integer number of bytes
func ParseByte(sizeStr string) uint64 {
sizeStr = strings.TrimSpace(sizeStr)
lastPos := len(sizeStr) - 1
if lastPos < 1 {
return 0
}
if sizeStr[lastPos] == 'b' || sizeStr[lastPos] == 'B' {
// last second char is k,m,g
lastSec := sizeStr[lastPos-1]
if lastSec > 'A' {
lastPos -= 1
}
}
multiplier := float64(1)
switch unicode.ToLower(rune(sizeStr[lastPos])) {
case 'k':
multiplier = 1 << 10
sizeStr = strings.TrimSpace(sizeStr[:lastPos])
case 'm':
multiplier = 1 << 20
sizeStr = strings.TrimSpace(sizeStr[:lastPos])
case 'g':
multiplier = 1 << 30
sizeStr = strings.TrimSpace(sizeStr[:lastPos])
default: // b
multiplier = 1
sizeStr = strings.TrimSpace(sizeStr[:lastPos])
}
size, _ := strconv.ParseFloat(sizeStr, 64)
if size < 0 {
return 0
}
return uint64(size * multiplier)
}
// PrettyJSON get pretty Json string
func PrettyJSON(v any) (string, error) {
out, err := json.MarshalIndent(v, "", " ")
return string(out), err
}
// StringsToInts string slice to int slice.
// Deprecated: please use the arrutil.StringsToInts()
func StringsToInts(ss []string) (ints []int, err error) {
for _, str := range ss {
iVal, err := strconv.Atoi(str)
if err != nil {
return []int{}, err
}
ints = append(ints, iVal)
}
return
}
// ArgsWithSpaces it like Println, will add spaces for each argument
func ArgsWithSpaces(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
}
+136
View File
@@ -0,0 +1,136 @@
# FileSystem Util
`fsutil` Provide some commonly file system util functions.
## Install
```shell
go get github.com/gookit/goutil/fsutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/fsutil)
## Find files
```go
// find all files in dir
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {
fmt.Println(filePath)
return nil
})
// find files with filters
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {
fmt.Println(filePath)
return nil
}, fsutil.ExcludeDotFile)
```
## Functions API
> **Note**: doc by run `go doc ./fsutil`
```go
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool
func CopyFile(srcPath, dstPath string) error
func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*os.File, error)
func DeleteIfExist(fPath string) error
func DeleteIfFileExist(fPath string) error
func Dir(fpath string) string
func DiscardReader(src io.Reader)
func ExcludeDotFile(_ string, ent fs.DirEntry) bool
func Expand(pathStr string) string
func ExpandPath(pathStr string) string
func Extname(fpath string) string
func FileExists(path string) bool
func FileExt(fpath string) string
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
func GetContents(in any) []byte
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error)
func IsAbsPath(aPath string) bool
func IsDir(path string) bool
func IsFile(path string) bool
func IsImageFile(path string) bool
func IsZipFile(filepath string) bool
func JoinPaths(elem ...string) string
func JoinSubPaths(basePath string, elem ...string) string
func LineScanner(in any) *bufio.Scanner
func MimeType(path string) (mime string)
func MkDirs(perm os.FileMode, dirPaths ...string) error
func MkParentDir(fpath string) error
func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error
func Mkdir(dirPath string, perm os.FileMode) error
func MustCopyFile(srcPath, dstPath string)
func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File
func MustReadFile(filePath string) []byte
func MustReadReader(r io.Reader) []byte
func MustRemove(fPath string)
func Name(fpath string) string
func NewIOReader(in any) (r io.Reader, err error)
func OSTempDir(pattern string) (string, error)
func OSTempFile(pattern string) (*os.File, error)
func OnlyFindDir(_ string, ent fs.DirEntry) bool
func OnlyFindFile(_ string, ent fs.DirEntry) bool
func OpenAppendFile(filepath string) (*os.File, error)
func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error)
func OpenReadFile(filepath string) (*os.File, error)
func OpenTruncFile(filepath string) (*os.File, error)
func PathExists(path string) bool
func PathMatch(pattern, s string) bool
func PathName(fpath string) string
func PutContents(filePath string, data any, fileFlag ...int) (int, error)
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error)
func QuietRemove(fPath string)
func ReadAll(in any) []byte
func ReadExistFile(filePath string) []byte
func ReadFile(filePath string) []byte
func ReadOrErr(in any) ([]byte, error)
func ReadReader(r io.Reader) []byte
func ReadString(in any) string
func ReadStringOrErr(in any) (string, error)
func ReaderMimeType(r io.Reader) (mime string)
func Realpath(pathStr string) string
func Remove(fPath string) error
func ResolvePath(pathStr string) string
func RmFileIfExist(fPath string) error
func RmIfExist(fPath string) error
func SearchNameUp(dirPath, name string) string
func SearchNameUpx(dirPath, name string) (string, bool)
func SlashPath(path string) string
func SplitPath(pathStr string) (dir, name string)
func Suffix(fpath string) string
func TempDir(dir, pattern string) (string, error)
func TempFile(dir, pattern string) (*os.File, error)
func TextScanner(in any) *scanner.Scanner
func ToAbsPath(p string) string
func UnixPath(path string) string
func Unzip(archive, targetDir string) (err error)
func WalkDir(dir string, fn fs.WalkDirFunc) error
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error
func WriteOSFile(f *os.File, data any) (n int, err error)
type FilterFunc func(fPath string, ent fs.DirEntry) bool
func ExcludeSuffix(ss ...string) FilterFunc
func IncludeSuffix(ss ...string) FilterFunc
type HandleFunc func(fPath string, ent fs.DirEntry) error
```
## Code Check & Testing
```bash
gofmt -w -l ./
golint ./...
```
**Testing**:
```shell
go test -v ./fsutil/...
```
**Test limit by regexp**:
```shell
go test -v -run ^TestSetByKeys ./fsutil/...
```
+22 -4
View File
@@ -4,14 +4,17 @@ import (
"bytes"
"os"
"path"
"path/filepath"
)
// perm for create dir or file
var (
// DefaultDirPerm perm and flags for create log file
DefaultDirPerm os.FileMode = 0775
DefaultFilePerm os.FileMode = 0665
OnlyReadFilePerm os.FileMode = 0444
)
var (
// DefaultFileFlags for create and write
DefaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
// OnlyReadFileFlags open file for read
@@ -41,7 +44,7 @@ func PathExists(path string) bool {
// IsDir reports whether the named directory exists.
func IsDir(path string) bool {
if path == "" {
if path == "" || len(path) > 468 {
return false
}
@@ -58,7 +61,7 @@ func FileExists(path string) bool {
// IsFile reports whether the named file or directory exists.
func IsFile(path string) bool {
if path == "" {
if path == "" || len(path) > 468 {
return false
}
@@ -70,7 +73,13 @@ func IsFile(path string) bool {
// IsAbsPath is abs path.
func IsAbsPath(aPath string) bool {
return path.IsAbs(aPath)
if len(aPath) > 0 {
if aPath[0] == '/' {
return true
}
return filepath.IsAbs(aPath)
}
return false
}
// ImageMimeTypes refer net/http package
@@ -118,3 +127,12 @@ func IsZipFile(filepath string) bool {
return bytes.Equal(buf, []byte("PK\x03\x04"))
}
// PathMatch check for a string. alias of path.Match()
func PathMatch(pattern, s string) bool {
ok, err := path.Match(pattern, s)
if err != nil {
ok = false
}
return ok
}
+156
View File
@@ -0,0 +1,156 @@
package fsutil
import (
"io/fs"
"os"
"path/filepath"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/strutil"
)
// SearchNameUp find file/dir name in dirPath or parent dirs,
// return the name of directory path
//
// Usage:
//
// repoDir := fsutil.SearchNameUp("/path/to/dir", ".git")
func SearchNameUp(dirPath, name string) string {
dir, _ := SearchNameUpx(dirPath, name)
return dir
}
// SearchNameUpx find file/dir name in dirPath or parent dirs,
// return the name of directory path and dir is changed.
func SearchNameUpx(dirPath, name string) (string, bool) {
var level int
dirPath = ToAbsPath(dirPath)
for {
namePath := filepath.Join(dirPath, name)
if PathExists(namePath) {
return dirPath, level > 0
}
level++
prevLn := len(dirPath)
dirPath = filepath.Dir(dirPath)
if prevLn == len(dirPath) {
return "", false
}
}
}
// WalkDir walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root.
func WalkDir(dir string, fn fs.WalkDirFunc) error {
return filepath.WalkDir(dir, fn)
}
// GlobWithFunc handle matched file
//
// - TIP: will be not find in subdir.
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) {
files, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, filePath := range files {
err = fn(filePath)
if err != nil {
break
}
}
return
}
type (
// FilterFunc type for FindInDir
//
// - return False will skip handle the file.
FilterFunc func(fPath string, ent fs.DirEntry) bool
// HandleFunc type for FindInDir
HandleFunc func(fPath string, ent fs.DirEntry) error
)
// OnlyFindDir on find
func OnlyFindDir(_ string, ent fs.DirEntry) bool {
return ent.IsDir()
}
// OnlyFindFile on find
func OnlyFindFile(_ string, ent fs.DirEntry) bool {
return !ent.IsDir()
}
// ExcludeNames on find
func ExcludeNames(names ...string) FilterFunc {
return func(_ string, ent fs.DirEntry) bool {
return !arrutil.StringsHas(names, ent.Name())
}
}
// IncludeSuffix on find
func IncludeSuffix(ss ...string) FilterFunc {
return func(_ string, ent fs.DirEntry) bool {
return strutil.HasOneSuffix(ent.Name(), ss)
}
}
// ExcludeDotFile on find
func ExcludeDotFile(_ string, ent fs.DirEntry) bool {
return ent.Name()[0] != '.'
}
// ExcludeSuffix on find
func ExcludeSuffix(ss ...string) FilterFunc {
return func(_ string, ent fs.DirEntry) bool {
return !strutil.HasOneSuffix(ent.Name(), ss)
}
}
// ApplyFilters handle
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool {
for _, filter := range filters {
if !filter(fPath, ent) {
return true
}
}
return false
}
// FindInDir code refer the go pkg: path/filepath.glob()
//
// - TIP: will be not find in subdir.
//
// filters: return false will skip the file.
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) {
fi, err := os.Stat(dir)
if err != nil || !fi.IsDir() {
return // ignore I/O error
}
// names, _ := d.Readdirnames(-1)
// sort.Strings(names)
des, err := os.ReadDir(dir)
if err != nil {
return
}
for _, ent := range des {
filePath := dir + "/" + ent.Name()
// apply filters
if len(filters) > 0 && ApplyFilters(filePath, ent, filters) {
continue
}
if err := handleFn(filePath, ent); err != nil {
return err
}
}
return nil
}
+50
View File
@@ -5,6 +5,10 @@ import (
"io"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/gookit/goutil/internal/comfunc"
)
const (
@@ -77,3 +81,49 @@ func ReaderMimeType(r io.Reader) (mime string) {
return http.DetectContentType(buf[:n])
}
// JoinPaths elements, alias of filepath.Join()
func JoinPaths(elem ...string) string {
return filepath.Join(elem...)
}
// JoinSubPaths elements, like the filepath.Join()
func JoinSubPaths(basePath string, elem ...string) string {
paths := make([]string, len(elem)+1)
paths[0] = basePath
copy(paths[1:], elem)
return filepath.Join(paths...)
}
// SlashPath alias of filepath.ToSlash
func SlashPath(path string) string {
return filepath.ToSlash(path)
}
// UnixPath like of filepath.ToSlash, but always replace
func UnixPath(path string) string {
if !strings.ContainsRune(path, '\\') {
return path
}
return strings.ReplaceAll(path, "\\", "/")
}
// ToAbsPath convert process. will expand home dir
//
// TIP: will don't check path
func ToAbsPath(p string) string {
if len(p) == 0 || IsAbsPath(p) {
return p
}
// expand home dir
if p[0] == '~' {
return comfunc.ExpandHome(p)
}
wd, err := os.Getwd()
if err != nil {
return p
}
return filepath.Join(wd, p)
}
+31 -88
View File
@@ -1,7 +1,6 @@
package fsutil
import (
"io/ioutil"
"os"
"path"
"path/filepath"
@@ -9,116 +8,60 @@ import (
"github.com/gookit/goutil/internal/comfunc"
)
// Dir get dir path, without last name.
func Dir(fpath string) string {
return filepath.Dir(fpath)
}
// Dir get dir path from filepath, without last name.
func Dir(fpath string) string { return filepath.Dir(fpath) }
// PathName get file/dir name from full path
func PathName(fpath string) string {
return path.Base(fpath)
}
func PathName(fpath string) string { return path.Base(fpath) }
// Name get file/dir name from full path
// Name get file/dir name from full path.
//
// eg: path/to/main.go => main.go
func Name(fpath string) string {
if fpath == "" {
return ""
}
return filepath.Base(fpath)
}
// FileExt get filename ext. alias of path.Ext()
func FileExt(fpath string) string {
return path.Ext(fpath)
//
// eg: path/to/main.go => ".go"
func FileExt(fpath string) string { return path.Ext(fpath) }
// Extname get filename ext. alias of path.Ext()
//
// eg: path/to/main.go => "go"
func Extname(fpath string) string {
if ext := path.Ext(fpath); len(ext) > 0 {
return ext[1:]
}
return ""
}
// Suffix get filename ext. alias of path.Ext()
func Suffix(fpath string) string {
return path.Ext(fpath)
}
//
// eg: path/to/main.go => ".go"
func Suffix(fpath string) string { return path.Ext(fpath) }
// Expand will parse first `~` as user home dir path.
func Expand(pathStr string) string {
return comfunc.ExpandPath(pathStr)
return comfunc.ExpandHome(pathStr)
}
// ExpandPath will parse `~` as user home dir path.
func ExpandPath(pathStr string) string {
return comfunc.ExpandPath(pathStr)
return comfunc.ExpandHome(pathStr)
}
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
func Realpath(pathStr string) string {
return path.Clean(pathStr)
// ResolvePath will parse `~` and env var in path
func ResolvePath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
// return comfunc.ParseEnvVar()
return os.ExpandEnv(pathStr)
}
// SplitPath splits path immediately following the final Separator, separating it into a directory and file name component
func SplitPath(pathStr string) (dir, name string) {
return filepath.Split(pathStr)
}
// GlobWithFunc handle matched file
func GlobWithFunc(pattern string, fn func(filePath string) error) (err error) {
files, err := filepath.Glob(pattern)
if err != nil {
return err
}
for _, filePath := range files {
err = fn(filePath)
if err != nil {
break
}
}
return
}
type (
// FilterFunc type for FindInDir
FilterFunc func(fPath string, fi os.FileInfo) bool
// HandleFunc type for FindInDir
HandleFunc func(fPath string, fi os.FileInfo) error
)
// FindInDir code refer the go pkg: path/filepath.glob()
//
// filters: return false will skip the file.
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) {
fi, err := os.Stat(dir)
if err != nil {
return // ignore I/O error
}
if !fi.IsDir() {
return // ignore I/O error
}
// names, _ := d.Readdirnames(-1)
// sort.Strings(names)
stats, err := ioutil.ReadDir(dir)
if err != nil {
return
}
for _, fi := range stats {
baseName := fi.Name()
filePath := dir + "/" + baseName
// call filters
if len(filters) > 0 {
var filtered = false
for _, filter := range filters {
if !filter(filePath, fi) {
filtered = true
break
}
}
if filtered {
continue
}
}
if err := handleFn(filePath, fi); err != nil {
return err
}
}
return nil
}
+19
View File
@@ -0,0 +1,19 @@
//go:build !windows
package fsutil
import (
"path"
"github.com/gookit/goutil/internal/comfunc"
)
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
func Realpath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
if !IsAbsPath(pathStr) {
pathStr = JoinSubPaths(comfunc.Workdir(), pathStr)
}
return path.Clean(pathStr)
}
+17
View File
@@ -0,0 +1,17 @@
package fsutil
import (
"path/filepath"
"github.com/gookit/goutil/internal/comfunc"
)
// Realpath returns the shortest path name equivalent to path by purely lexical processing.
func Realpath(pathStr string) string {
pathStr = comfunc.ExpandHome(pathStr)
if !IsAbsPath(pathStr) {
pathStr = JoinSubPaths(comfunc.Workdir(), pathStr)
}
return filepath.Clean(pathStr)
}
+69 -165
View File
@@ -4,11 +4,13 @@ import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"io/fs"
"os"
"path"
"path/filepath"
"strings"
"github.com/gookit/goutil/basefn"
)
// Mkdir alias of os.MkdirAll()
@@ -16,6 +18,27 @@ func Mkdir(dirPath string, perm os.FileMode) error {
return os.MkdirAll(dirPath, perm)
}
// MkDirs batch make multi dirs at once
func MkDirs(perm os.FileMode, dirPaths ...string) error {
for _, dirPath := range dirPaths {
if err := os.MkdirAll(dirPath, perm); err != nil {
return err
}
}
return nil
}
// MkSubDirs batch make multi sub-dirs at once
func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error {
for _, dirName := range subDirs {
dirPath := parentDir + "/" + dirName
if err := os.MkdirAll(dirPath, perm); err != nil {
return err
}
}
return nil
}
// MkParentDir quick create parent dir
func MkParentDir(fpath string) error {
dirPath := filepath.Dir(fpath)
@@ -25,61 +48,11 @@ func MkParentDir(fpath string) error {
return nil
}
// DiscardReader anything from the reader
func DiscardReader(src io.Reader) {
_, _ = io.Copy(ioutil.Discard, src)
}
// MustReadFile read file contents, will panic on error
func MustReadFile(filePath string) []byte {
bs, err := ioutil.ReadFile(filePath)
if err != nil {
panic(err)
}
return bs
}
// MustReadReader read contents from io.Reader, will panic on error
func MustReadReader(r io.Reader) []byte {
// TODO go 1.16+ bs, err := io.ReadAll(r)
bs, err := ioutil.ReadAll(r)
if err != nil {
panic(err)
}
return bs
}
// GetContents read contents from path or io.Reader, will panic on error
func GetContents(in any) []byte {
if fPath, ok := in.(string); ok {
return MustReadFile(fPath)
}
if r, ok := in.(io.Reader); ok {
return MustReadReader(r)
}
panic("invalid type of input")
}
// ReadExistFile read file contents if existed, will panic on error
func ReadExistFile(filePath string) []byte {
if IsFile(filePath) {
bs, err := ioutil.ReadFile(filePath)
if err != nil {
panic(err)
}
return bs
}
return nil
}
// ************************************************************
// open/create files
// ************************************************************
// some flag consts for open file
// some commonly flag consts for open file
const (
FsCWAFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND // create, append write-only
FsCWTFlags = os.O_CREATE | os.O_WRONLY | os.O_TRUNC // create, override write-only
@@ -88,6 +61,10 @@ const (
)
// OpenFile like os.OpenFile, but will auto create dir.
//
// Usage:
//
// file, err := OpenFile("path/to/file.txt", FsCWFlags, 0666)
func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) {
fileDir := path.Dir(filepath)
if err := os.MkdirAll(fileDir, DefaultDirPerm); err != nil {
@@ -101,20 +78,39 @@ func OpenFile(filepath string, flag int, perm os.FileMode) (*os.File, error) {
return file, nil
}
/* TODO MustOpenFile() */
// QuickOpenFile like os.OpenFile, open for write, if not exists, will create it.
// MustOpenFile like os.OpenFile, but will auto create dir.
//
// Tip: file flag default is FsCWAFlags
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) {
flag := FsCWAFlags
if len(fileFlag) > 0 {
flag = fileFlag[0]
// Usage:
//
// file := MustOpenFile("path/to/file.txt", FsCWFlags, 0666)
func MustOpenFile(filepath string, flag int, perm os.FileMode) *os.File {
file, err := OpenFile(filepath, flag, perm)
if err != nil {
panic(err)
}
return file
}
// QuickOpenFile like os.OpenFile, open for append write. if not exists, will create it.
//
// Alias of OpenAppendFile()
func QuickOpenFile(filepath string, fileFlag ...int) (*os.File, error) {
flag := basefn.FirstOr(fileFlag, FsCWAFlags)
return OpenFile(filepath, flag, DefaultFilePerm)
}
// OpenAppendFile like os.OpenFile, open for append write. if not exists, will create it.
func OpenAppendFile(filepath string, filePerm ...os.FileMode) (*os.File, error) {
perm := basefn.FirstOr(filePerm, DefaultFilePerm)
return OpenFile(filepath, FsCWAFlags, perm)
}
// OpenTruncFile like os.OpenFile, open for override write. if not exists, will create it.
func OpenTruncFile(filepath string, filePerm ...os.FileMode) (*os.File, error) {
perm := basefn.FirstOr(filePerm, DefaultFilePerm)
return OpenFile(filepath, FsCWTFlags, perm)
}
// OpenReadFile like os.OpenFile, open file for read contents
func OpenReadFile(filepath string) (*os.File, error) {
return os.OpenFile(filepath, FsRFlags, OnlyReadFilePerm)
@@ -134,11 +130,7 @@ func CreateFile(fpath string, filePerm, dirPerm os.FileMode, fileFlag ...int) (*
}
}
flag := FsCWTFlags
if len(fileFlag) > 0 {
flag = fileFlag[0]
}
flag := basefn.FirstOr(fileFlag, FsCWAFlags)
return os.OpenFile(fpath, flag, filePerm)
}
@@ -151,105 +143,6 @@ func MustCreateFile(filePath string, filePerm, dirPerm os.FileMode) *os.File {
return file
}
// ************************************************************
// write, copy files
// ************************************************************
// PutContents create file and write contents to file at once.
//
// data type allow: string, []byte, io.Reader
//
// Tip: file flag default is FsCWAFlags
//
// Usage:
//
// fsutil.PutContents(filePath, contents, fsutil.FsCWTFlags)
func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
// create and open file
dstFile, err := QuickOpenFile(filePath, fileFlag...)
if err != nil {
return 0, err
}
defer dstFile.Close()
switch typData := data.(type) {
case []byte:
return dstFile.Write(typData)
case string:
return dstFile.WriteString(typData)
case io.Reader: // eg: buffer
n, err := io.Copy(dstFile, typData)
return int(n), err
default:
panic("PutContents: data type only allow: []byte, string, io.Reader")
}
}
// WriteFile create file and write contents to file, can set perm for file.
//
// data type allow: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags
//
// Usage:
//
// fsutil.WriteFile(filePath, contents, 0666, fsutil.FsCWAFlags)
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error {
flag := FsCWTFlags
if len(fileFlag) > 0 {
flag = fileFlag[0]
}
f, err := os.OpenFile(filePath, flag, perm)
if err != nil {
return err
}
switch typData := data.(type) {
case []byte:
_, err = f.Write(typData)
case string:
_, err = f.WriteString(typData)
case io.Reader: // eg: buffer
_, err = io.Copy(f, typData)
default:
_ = f.Close()
panic("WriteFile: data type only allow: []byte, string, io.Reader")
}
if err1 := f.Close(); err1 != nil && err == nil {
err = err1
}
return err
}
// CopyFile copy a file to another file path.
func CopyFile(srcPath, dstPath string) error {
srcFile, err := os.OpenFile(srcPath, FsRFlags, 0)
if err != nil {
return err
}
defer srcFile.Close()
// create and open file
dstFile, err := QuickOpenFile(dstPath, FsCWTFlags)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}
// MustCopyFile copy file to another path.
func MustCopyFile(srcPath, dstPath string) {
err := CopyFile(srcPath, dstPath)
if err != nil {
panic(err)
}
}
// ************************************************************
// remove files
// ************************************************************
@@ -302,6 +195,18 @@ func DeleteIfFileExist(fPath string) error {
return nil
}
// RemoveSub removes all sub files and dirs of dirPath, but not remove dirPath.
func RemoveSub(dirPath string, fns ...FilterFunc) error {
return FindInDir(dirPath, func(fPath string, ent fs.DirEntry) error {
if ent.IsDir() {
if err := RemoveSub(fPath, fns...); err != nil {
return err
}
}
return os.Remove(fPath)
}, fns...)
}
// ************************************************************
// other operates
// ************************************************************
@@ -319,7 +224,6 @@ func Unzip(archive, targetDir string) (err error) {
}
for _, file := range reader.File {
if strings.Contains(file.Name, "..") {
return fmt.Errorf("illegal file path in zip: %v", file.Name)
}
+137
View File
@@ -0,0 +1,137 @@
package fsutil
import (
"bufio"
"errors"
"io"
"os"
"text/scanner"
)
// NewIOReader instance by input file path or io.Reader
func NewIOReader(in any) (r io.Reader, err error) {
switch typIn := in.(type) {
case string: // as file path
return OpenReadFile(typIn)
case io.Reader:
return typIn, nil
}
return nil, errors.New("invalid input type, allow: string, io.Reader")
}
// DiscardReader anything from the reader
func DiscardReader(src io.Reader) {
_, _ = io.Copy(io.Discard, src)
}
// ReadFile read file contents, will panic on error
func ReadFile(filePath string) []byte {
return MustReadFile(filePath)
}
// MustReadFile read file contents, will panic on error
func MustReadFile(filePath string) []byte {
bs, err := os.ReadFile(filePath)
if err != nil {
panic(err)
}
return bs
}
// ReadReader read contents from io.Reader, will panic on error
func ReadReader(r io.Reader) []byte { return MustReadReader(r) }
// 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
}
// ReadString read contents from path or io.Reader, will panic on in type error
func ReadString(in any) string {
return string(GetContents(in))
}
// ReadStringOrErr read contents from path or io.Reader, will panic on in type error
func ReadStringOrErr(in any) (string, error) {
r, err := NewIOReader(in)
if err != nil {
return "", err
}
bs, err := io.ReadAll(r)
if err != nil {
return "", err
}
return string(bs), nil
}
// ReadAll read contents from path or io.Reader, will panic on in type error
func ReadAll(in any) []byte { return GetContents(in) }
// GetContents read contents from path or io.Reader, will panic on in type error
func GetContents(in any) []byte {
r, err := NewIOReader(in)
if err != nil {
panic(err)
}
return MustReadReader(r)
}
// ReadOrErr read contents from path or io.Reader, will panic on in type error
func ReadOrErr(in any) ([]byte, error) {
r, err := NewIOReader(in)
if err != nil {
return nil, err
}
return io.ReadAll(r)
}
// ReadExistFile read file contents if existed, will panic on error
func ReadExistFile(filePath string) []byte {
if IsFile(filePath) {
bs, err := os.ReadFile(filePath)
if err != nil {
panic(err)
}
return bs
}
return nil
}
// TextScanner from filepath or io.Reader, will panic on in type error
//
// Usage:
//
// s := fsutil.TextScanner("/path/to/file")
// for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
// fmt.Printf("%s: %s\n", s.Position, s.TokenText())
// }
func TextScanner(in any) *scanner.Scanner {
var s scanner.Scanner
r, err := NewIOReader(in)
if err != nil {
panic(err)
}
s.Init(r)
s.Filename = "text-scanner"
return &s
}
// LineScanner create from filepath or io.Reader
//
// s := fsutil.LineScanner("/path/to/file")
// for s.Scan() {
// fmt.Println(s.Text())
// }
func LineScanner(in any) *bufio.Scanner {
r, err := NewIOReader(in)
if err != nil {
panic(err)
}
return bufio.NewScanner(r)
}
+101
View File
@@ -0,0 +1,101 @@
package fsutil
import (
"io"
"os"
"github.com/gookit/goutil/basefn"
)
// ************************************************************
// write, copy files
// ************************************************************
// PutContents create file and write contents to file at once.
//
// data type allow: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags (override write)
//
// Usage:
//
// fsutil.PutContents(filePath, contents, fsutil.FsCWAFlags) // append write
func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
f, err := QuickOpenFile(filePath, basefn.FirstOr(fileFlag, FsCWTFlags))
if err != nil {
return 0, err
}
return WriteOSFile(f, data)
}
// WriteFile create file and write contents to file, can set perm for file.
//
// data type allow: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags (override write)
//
// Usage:
//
// fsutil.WriteFile(filePath, contents, fsutil.DefaultFilePerm, fsutil.FsCWAFlags)
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error {
flag := basefn.FirstOr(fileFlag, FsCWTFlags)
f, err := OpenFile(filePath, flag, perm)
if err != nil {
return err
}
_, err = WriteOSFile(f, data)
return err
}
// WriteOSFile write data to give os.File, then close file.
//
// data type allow: string, []byte, io.Reader
func WriteOSFile(f *os.File, data any) (n int, err error) {
switch typData := data.(type) {
case []byte:
n, err = f.Write(typData)
case string:
n, err = f.WriteString(typData)
case io.Reader: // eg: buffer
var n64 int64
n64, err = io.Copy(f, typData)
n = int(n64)
default:
_ = f.Close()
panic("WriteFile: data type only allow: []byte, string, io.Reader")
}
if err1 := f.Close(); err1 != nil && err == nil {
err = err1
}
return n, err
}
// CopyFile copy a file to another file path.
func CopyFile(srcPath, dstPath string) error {
srcFile, err := os.OpenFile(srcPath, FsRFlags, 0)
if err != nil {
return err
}
defer srcFile.Close()
// create and open file
dstFile, err := QuickOpenFile(dstPath, FsCWTFlags)
if err != nil {
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, srcFile)
return err
}
// MustCopyFile copy file to another path.
func MustCopyFile(srcPath, dstPath string) {
err := CopyFile(srcPath, dstPath)
if err != nil {
panic(err)
}
}
+37
View File
@@ -0,0 +1,37 @@
package goutil
import "github.com/gookit/goutil/stdutil"
// FuncName get func name
func FuncName(f any) string {
return stdutil.FuncName(f)
}
// Go is a basic promise implementation: it wraps calls a function in a goroutine
// and returns a channel which will later return the function's return value.
func Go(f func() error) error {
ch := make(chan error)
go func() {
ch <- f()
}()
return <-ch
}
// 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()
}
+38 -16
View File
@@ -11,14 +11,9 @@ import (
// Value alias of stdutil.Value
type Value = stdutil.Value
// Go is a basic promise implementation: it wraps calls a function in a goroutine
// and returns a channel which will later return the function's return value.
func Go(f func() error) error {
ch := make(chan error)
go func() {
ch <- f()
}()
return <-ch
// Panicf format panic message use fmt.Sprintf
func Panicf(format string, v ...any) {
panic(fmt.Sprintf(format, v...))
}
// PanicIfErr if error is not empty, will panic
@@ -42,14 +37,12 @@ func MustOK(err error) {
}
}
// Panicf format panic message use fmt.Sprintf
func Panicf(format string, v ...any) {
panic(fmt.Sprintf(format, v...))
}
// FuncName get func name
func FuncName(f any) string {
return stdutil.FuncName(f)
// Must if error is not empty, will panic
func Must[T any](v T, err error) T {
if err != nil {
panic(err)
}
return v
}
// PkgName get current package name. alias of stdutil.PkgName()
@@ -61,3 +54,32 @@ func FuncName(f any) string {
func PkgName(funcName string) string {
return stdutil.PkgName(funcName)
}
// 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
}
// OrValue get
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()
func OrReturn[T any](cond bool, okFn, elseFn func() T) T {
if cond {
return okFn()
}
return elseFn()
}
+36
View File
@@ -3,6 +3,7 @@ package goutil
import (
"context"
"github.com/gookit/goutil/structs"
"golang.org/x/sync/errgroup"
)
@@ -44,3 +45,38 @@ func (g *ErrGroup) Add(handlers ...func() error) {
g.Go(handler)
}
}
// RunFn func
type RunFn func(ctx *structs.Data) error
// QuickRun struct
type QuickRun struct {
ctx *structs.Data
// err error
fns []RunFn
}
// NewQuickRun instance
func NewQuickRun() *QuickRun {
return &QuickRun{
ctx: structs.NewData(),
}
}
// Add func for run
func (p *QuickRun) Add(fns ...RunFn) *QuickRun {
p.fns = append(p.fns, fns...)
return p
}
// Run all func
func (p *QuickRun) Run() error {
for i, fn := range p.fns {
p.ctx.Set("index", i)
if err := fn(p.ctx); err != nil {
return err
}
}
return nil
}
+96 -1
View File
@@ -1,9 +1,12 @@
package comfunc
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
)
// Environ like os.Environ, but will returns key-value map[string]string data.
@@ -65,7 +68,6 @@ func ParseEnvVar(val string, getFn func(string) string) (newVal string) {
if len(ss) == 2 {
name, def = strings.TrimSpace(ss[0]), strings.TrimSpace(ss[1])
} else {
def = eVar // use raw value
name = strings.TrimSpace(ss[0])
}
@@ -77,3 +79,96 @@ func ParseEnvVar(val string, getFn func(string) string) (newVal string) {
return eVal
})
}
// FormatTplAndArgs message
func FormatTplAndArgs(fmtAndArgs []any) string {
if len(fmtAndArgs) == 0 || fmtAndArgs == nil {
return ""
}
ln := len(fmtAndArgs)
first := fmtAndArgs[0]
if ln == 1 {
if msgAsStr, ok := first.(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", first)
}
// is template string.
if tplStr, ok := first.(string); ok {
return fmt.Sprintf(tplStr, fmtAndArgs[1:]...)
}
return fmt.Sprint(fmtAndArgs...)
}
var (
// TIP: extend unit d,w
// time.ParseDuration() is not supported. eg: "1d", "2w"
durStrReg = regexp.MustCompile(`^(-?\d+)(ns|us|µs|ms|s|m|h|d|w)$`)
// match long duration string, such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks"
// time.ParseDuration() is not supported.
durStrRegL = regexp.MustCompile(`^(-?\d+)([a-zA-Z]{3,})$`)
)
// IsDuration check the string is a duration string.
func IsDuration(s string) bool {
if s == "0" || durStrReg.MatchString(s) {
return true
}
return durStrRegL.MatchString(s)
}
// ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
//
// Diff of time.ParseDuration:
// - support extend unit d, w at the end of string. such as "1d", "2w".
// - support long string unit at end. such as "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks".
//
// If the string is not a valid duration string, it will return an error.
func ToDuration(s string) (time.Duration, error) {
ln := len(s)
if ln == 0 {
return 0, fmt.Errorf("empty duration string")
}
s = strings.ToLower(s)
if s == "0" {
return 0, nil
}
// extend unit d,w, time.ParseDuration() is not supported. eg: "1d", "2w"
if lastUnit := s[ln-1]; lastUnit == 'd' {
s = s + "ay"
} else if lastUnit == 'w' {
s = s + "eek"
}
// long unit, time.ParseDuration() is not supported. eg: "-3sec" => [3sec -3 sec]
ss := durStrRegL.FindStringSubmatch(s)
if len(ss) == 3 {
num, unit := ss[1], ss[2]
// convert to short unit
switch unit {
case "week", "weeks":
// max unit is hour, so need convert by 24 * 7 * n
n, _ := strconv.Atoi(num)
s = strconv.Itoa(n*24*7) + "h"
case "day", "days":
// max unit is hour, so need convert by 24 * n
n, _ := strconv.Atoi(num)
s = strconv.Itoa(n*24) + "h"
case "hour", "hours":
s = num + "h"
case "min", "mins", "minute", "minutes":
s = num + "m"
case "sec", "secs", "second", "seconds":
s = num + "s"
}
}
return time.ParseDuration(s)
}
+21 -14
View File
@@ -8,8 +8,14 @@ import (
"strings"
)
// ExpandPath will parse first `~` as user home dir path.
func ExpandPath(pathStr string) string {
// Workdir get
func Workdir() string {
dir, _ := os.Getwd()
return dir
}
// ExpandHome will parse first `~` as user home dir path.
func ExpandHome(pathStr string) string {
if len(pathStr) == 0 {
return pathStr
}
@@ -26,7 +32,6 @@ func ExpandPath(pathStr string) string {
if err != nil {
return pathStr
}
return homeDir + pathStr[1:]
}
@@ -47,7 +52,7 @@ func ExecCmd(binName string, args []string, workDir ...string) (string, error) {
}
// ShellExec exec command by shell
// cmdLine eg. "ls -al"
// cmdLine e.g. "ls -al"
func ShellExec(cmdLine string, shells ...string) (string, error) {
// shell := "/bin/sh"
shell := "sh"
@@ -73,23 +78,26 @@ var curShell string
//
// eg "/bin/zsh" "/bin/bash".
// if onlyName=true, will return "zsh", "bash"
func CurrentShell(onlyName bool) (path string) {
func CurrentShell(onlyName bool) (binPath string) {
var err error
if curShell == "" {
path, err = ShellExec("echo $SHELL")
if err != nil {
return ""
binPath = os.Getenv("SHELL")
if len(binPath) == 0 {
binPath, err = ShellExec("echo $SHELL")
if err != nil {
return ""
}
}
path = strings.TrimSpace(path)
binPath = strings.TrimSpace(binPath)
// cache result
curShell = path
curShell = binPath
} else {
path = curShell
binPath = curShell
}
if onlyName && len(path) > 0 {
path = filepath.Base(path)
if onlyName && len(binPath) > 0 {
binPath = filepath.Base(binPath)
}
return
}
@@ -106,6 +114,5 @@ func HasShellEnv(shell string) bool {
if err != nil {
return false
}
return strings.TrimSpace(out) == "OK"
}
+39 -2
View File
@@ -5,7 +5,6 @@ import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"os"
"regexp"
"strings"
@@ -18,7 +17,16 @@ func WriteFile(filePath string, data any) error {
if err != nil {
return err
}
return ioutil.WriteFile(filePath, jsonBytes, 0664)
return os.WriteFile(filePath, jsonBytes, 0664)
}
// WritePretty write pretty data to JSON file
func WritePretty(filePath string, data any) error {
bs, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
return os.WriteFile(filePath, bs, 0664)
}
// ReadFile Read JSON file data
@@ -92,6 +100,35 @@ func Mapping(src, dst any) error {
return Decode(bts, dst)
}
// IsJSON check if the string is valid JSON. (Note: uses json.Unmarshal)
func IsJSON(s string) bool {
if s == "" {
return false
}
var js json.RawMessage
return json.Unmarshal([]byte(s), &js) == nil
}
// IsJSONFast simple and fast check input string is valid JSON.
func IsJSONFast(s string) bool {
ln := len(s)
if ln < 2 {
return false
}
if ln == 2 {
return s == "{}" || s == "[]"
}
// object
if s[0] == '{' {
return s[ln-1] == '}' && s[1] == '"'
}
// array
return s[0] == '[' && s[ln-1] == ']'
}
// `(?s:` enable match multi line
var jsonMLComments = regexp.MustCompile(`(?s:/\*.*?\*/\s*)`)
+19 -1
View File
@@ -21,7 +21,25 @@ func HasKey(mp, key any) (ok bool) {
return
}
// HasAllKeys check of the given map.
// HasOneKey check of the given map. return the first exist key
func HasOneKey(mp any, keys ...any) (ok bool, key any) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
return
}
for _, key = range keys {
for _, keyRv := range rftVal.MapKeys() {
if reflects.IsEqual(keyRv.Interface(), key) {
return true, key
}
}
}
return false, nil
}
// HasAllKeys check of the given map. return the first not exist key
func HasAllKeys(mp any, keys ...any) (ok bool, noKey any) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
+42 -7
View File
@@ -1,9 +1,12 @@
package maputil
import (
"errors"
"reflect"
"strings"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/strutil"
)
@@ -15,22 +18,54 @@ func KeyToLower(src map[string]string) map[string]string {
k = strings.ToLower(k)
newMp[k] = v
}
return newMp
}
// ToStringMap convert map[string]any to map[string]string
func ToStringMap(src map[string]any) map[string]string {
newMp := make(map[string]string, len(src))
strMp := make(map[string]string, len(src))
for k, v := range src {
newMp[k] = strutil.MustString(v)
strMp[k] = strutil.SafeString(v)
}
return newMp
return strMp
}
// HttpQueryString convert map[string]any data to http query string.
func HttpQueryString(data map[string]any) string {
// CombineToSMap combine two string-slice to SMap(map[string]string)
func CombineToSMap(keys, values []string) SMap {
return arrutil.CombineToSMap(keys, values)
}
// CombineToMap combine two any slice to map[K]V. alias of arrutil.CombineToMap
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
return arrutil.CombineToMap(keys, values)
}
// ToAnyMap convert map[TYPE1]TYPE2 to map[string]any
func ToAnyMap(mp any) map[string]any {
amp, _ := TryAnyMap(mp)
return amp
}
// TryAnyMap convert map[TYPE1]TYPE2 to map[string]any
func TryAnyMap(mp any) (map[string]any, error) {
if aMp, ok := mp.(map[string]any); ok {
return aMp, nil
}
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
return nil, errors.New("input is not a map value")
}
anyMp := make(map[string]any, rv.Len())
for _, key := range rv.MapKeys() {
anyMp[key.String()] = rv.MapIndex(key).Interface()
}
return anyMp, nil
}
// HTTPQueryString convert map[string]any data to http query string.
func HTTPQueryString(data map[string]any) string {
ss := make([]string, 0, len(data))
for k, v := range data {
ss = append(ss, k+"="+strutil.QuietString(v))
+53 -12
View File
@@ -3,12 +3,15 @@ package maputil
import (
"strings"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/strutil"
)
// Data an map data type
type Data map[string]any
// Map alias of Data
type Map = Data
// Has value on the data map
@@ -123,6 +126,14 @@ func (d Data) Int64(key string) int64 {
return 0
}
// Uint value get
func (d Data) Uint(key string) uint64 {
if val, ok := d.GetByPath(key); ok {
return mathutil.QuietUint(val)
}
return 0
}
// Str value get by key
func (d Data) Str(key string) string {
if val, ok := d.GetByPath(key); ok {
@@ -137,14 +148,15 @@ func (d Data) Bool(key string) bool {
if !ok {
return false
}
if bl, ok := val.(bool); ok {
return bl
}
if str, ok := val.(string); ok {
return strutil.QuietBool(str)
switch tv := val.(type) {
case string:
return strutil.QuietBool(tv)
case bool:
return tv
default:
return false
}
return false
}
// Strings get []string value
@@ -154,10 +166,16 @@ func (d Data) Strings(key string) []string {
return nil
}
if ss, ok := val.([]string); ok {
return ss
switch typVal := val.(type) {
case string:
return []string{typVal}
case []string:
return typVal
case []any:
return arrutil.SliceToStrings(typVal)
default:
return nil
}
return nil
}
// StrSplit get strings by split key value
@@ -176,6 +194,11 @@ func (d Data) StringsByStr(key string) []string {
return nil
}
// StrMap get map[string]string value
func (d Data) StrMap(key string) map[string]string {
return d.StringMap(key)
}
// StringMap get map[string]string value
func (d Data) StringMap(key string) map[string]string {
val, ok := d.GetByPath(key)
@@ -183,10 +206,14 @@ func (d Data) StringMap(key string) map[string]string {
return nil
}
if smp, ok := val.(map[string]string); ok {
return smp
switch tv := val.(type) {
case map[string]string:
return tv
case map[string]any:
return ToStringMap(tv)
default:
return nil
}
return nil
}
// Sub get sub value as new Data
@@ -217,3 +244,17 @@ func (d Data) ToStringMap() map[string]string {
func (d Data) String() string {
return ToString(d)
}
// Load other data to current data map
func (d Data) Load(sub map[string]any) {
for name, val := range sub {
d[name] = val
}
}
// LoadSMap to data
func (d Data) LoadSMap(smp map[string]string) {
for name, val := range smp {
d[name] = val
}
}
+88 -33
View File
@@ -6,6 +6,12 @@ import (
"strings"
)
// some consts for separators
const (
Wildcard = "*"
PathSep = "."
)
// DeepGet value by key path. eg "top" "top.sub"
func DeepGet(mp map[string]any, path string) (val any) {
val, _ = GetByPath(path, mp)
@@ -25,48 +31,96 @@ func GetByPath(path string, mp map[string]any) (val any, ok bool) {
}
// no sub key
if len(mp) == 0 || !strings.ContainsRune(path, '.') {
if len(mp) == 0 || strings.IndexByte(path, '.') < 1 {
return nil, false
}
// has sub key. eg. "top.sub"
keys := strings.Split(path, ".")
topK := keys[0]
return GetByPathKeys(mp, keys)
}
// GetByPathKeys get value by path keys from a map(map[string]any). eg "top" "top.sub"
//
// Example:
//
// mp := map[string]any{
// "top": map[string]any{
// "sub": "value",
// },
// }
// val, ok := GetByPathKeys(mp, []string{"top", "sub"}) // return "value", true
func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool) {
kl := len(keys)
if kl == 0 {
return mp, true
}
// find top item data use top key
var item any
topK := keys[0]
if item, ok = mp[topK]; !ok {
return
}
for _, k := range keys[1:] {
// find sub item data use sub key
for i, k := range keys[1:] {
switch tData := item.(type) {
case map[string]string: // is simple map
case map[string]string: // is string map
if item, ok = tData[k]; !ok {
return
}
case map[string]any: // is map(decode from toml/json)
case map[string]any: // is map(decode from toml/json/yaml)
if item, ok = tData[k]; !ok {
return
}
case map[any]any: // is map(decode from yaml)
case map[any]any: // is map(decode from yaml.v2)
if item, ok = tData[k]; !ok {
return
}
case []any: // is a slice
if item, ok = getBySlice(k, tData); !ok {
return
case []map[string]any: // is an any-map slice
if k == Wildcard {
if kl == i+2 {
return tData, true
}
sl := make([]any, 0, len(tData))
for _, v := range tData {
if val, ok = GetByPathKeys(v, keys[i+2:]); ok {
sl = append(sl, val)
}
}
return sl, true
}
case []string, []int, []float32, []float64, []bool, []rune:
slice := reflect.ValueOf(tData)
sData := make([]any, slice.Len())
for i := 0; i < slice.Len(); i++ {
sData[i] = slice.Index(i).Interface()
// k is index number
idx, err := strconv.Atoi(k)
if err != nil {
return nil, false
}
if item, ok = getBySlice(k, sData); !ok {
return
if idx >= len(tData) {
return nil, false
}
default: // error
item = tData[idx]
default:
rv := reflect.ValueOf(tData)
// check is slice
if rv.Kind() == reflect.Slice {
i, err := strconv.Atoi(k)
if err != nil {
return nil, false
}
if i >= rv.Len() {
return nil, false
}
item = rv.Index(i).Interface()
continue
}
// as error
return nil, false
}
}
@@ -74,17 +128,6 @@ func GetByPath(path string, mp map[string]any) (val any, ok bool) {
return item, true
}
func getBySlice(k string, slice []any) (val any, ok bool) {
i, err := strconv.ParseInt(k, 10, 64)
if err != nil {
return nil, false
}
if size := int64(len(slice)); i >= size {
return nil, false
}
return slice[i], true
}
// Keys get all keys of the given map.
func Keys(mp any) (keys []string) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
@@ -101,14 +144,26 @@ func Keys(mp any) (keys []string) {
// Values get all values from the given map.
func Values(mp any) (values []any) {
rftVal := reflect.Indirect(reflect.ValueOf(mp))
if rftVal.Kind() != reflect.Map {
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
return
}
values = make([]any, 0, rftVal.Len())
for _, key := range rftVal.MapKeys() {
values = append(values, rftVal.MapIndex(key).Interface())
values = make([]any, 0, rv.Len())
for _, key := range rv.MapKeys() {
values = append(values, rv.MapIndex(key).Interface())
}
return
}
// EachAnyMap iterates the given map and calls the given function for each item.
func EachAnyMap(mp any, fn func(key string, val any)) {
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
panic("not a map value")
}
for _, key := range rv.MapKeys() {
fn(key.String(), rv.MapIndex(key).Interface())
}
}
+27
View File
@@ -16,6 +16,26 @@ const (
KeySepChar = '.'
)
// SimpleMerge simple merge two data map by string key.
// will merge the src to dst map
func SimpleMerge(src, dst map[string]any) map[string]any {
if len(src) == 0 {
return dst
}
if len(dst) == 0 {
return src
}
for key, val := range src {
dst[key] = val
}
return dst
}
// func DeepMerge(src, dst map[string]any, deep int) map[string]any { TODO
// }
// MergeSMap simple merge two string map. merge src to dst map
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string {
return MergeStringMap(src, dst, ignoreCase)
@@ -23,6 +43,13 @@ func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string {
// MergeStringMap simple merge two string map. merge src to dst map
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string {
if len(src) == 0 {
return dst
}
if len(dst) == 0 {
return src
}
for k, v := range src {
if ignoreCase {
k = strings.ToLower(k)
+9
View File
@@ -111,6 +111,15 @@ func (m SMap) Values() []string {
return ss
}
// ToKVPairs slice convert. eg: {k1:v1,k2:v2} => {k1,v1,k2,v2}
func (m SMap) ToKVPairs() []string {
pairs := make([]string, 0, len(m)*2)
for k, v := range m {
pairs = append(pairs, k, v)
}
return pairs
}
// String data to string
func (m SMap) String() string {
return ToString2(m)
+42 -26
View File
@@ -1,6 +1,8 @@
package mathutil
// Compare intX,floatX value by given op. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
import "github.com/gookit/goutil/comdef"
// Compare any intX,floatX value by given op. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
//
// Usage:
//
@@ -42,40 +44,54 @@ func Compare(srcVal, dstVal any, op string) (ok bool) {
return CompInt64(srcInt, dstInt, op)
}
// CompInt64 compare int64, returns the srcI64 op dstI64
func CompInt64(srcI64, dstI64 int64, op string) (ok bool) {
// CompInt compare int,uint value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompInt[T comdef.Xint](srcVal, dstVal T, op string) (ok bool) {
return CompValue(srcVal, dstVal, op)
}
// CompInt64 compare int64 value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompInt64(srcVal, dstVal int64, op string) bool {
return CompValue(srcVal, dstVal, op)
}
// CompFloat compare float64,float32 value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompFloat[T comdef.Float](srcVal, dstVal T, op string) (ok bool) {
return CompValue(srcVal, dstVal, op)
}
// CompValue compare intX,uintX,floatX value. returns `srcVal op(=,!=,<,<=,>,>=) dstVal`
func CompValue[T comdef.XintOrFloat](srcVal, dstVal T, op string) (ok bool) {
switch op {
case "<", "lt":
ok = srcI64 < dstI64
ok = srcVal < dstVal
case "<=", "lte":
ok = srcI64 <= dstI64
ok = srcVal <= dstVal
case ">", "gt":
ok = srcI64 > dstI64
ok = srcVal > dstVal
case ">=", "gte":
ok = srcI64 >= dstI64
ok = srcVal >= dstVal
case "=", "eq":
ok = srcI64 == dstI64
ok = srcVal == dstVal
case "!=", "ne", "neq":
ok = srcI64 != dstI64
ok = srcVal != dstVal
}
return
}
// CompFloat compare float64
func CompFloat(srcF64, dstF64 float64, op string) (ok bool) {
switch op {
case "<", "lt":
ok = srcF64 < dstF64
case "<=", "lte":
ok = srcF64 <= dstF64
case ">", "gt":
ok = srcF64 > dstF64
case ">=", "gte":
ok = srcF64 >= dstF64
case "=", "eq":
ok = srcF64 == dstF64
case "!=", "ne", "neq":
ok = srcF64 != dstF64
}
return
// InRange check if val in int/float range [min, max]
func InRange[T comdef.IntOrFloat](val, min, max T) bool {
return val >= min && val <= max
}
// OutRange check if val not in int/float range [min, max]
func OutRange[T comdef.IntOrFloat](val, min, max T) bool {
return val < min || val > max
}
// InUintRange check if val in unit range [min, max]
func InUintRange[T comdef.Uint](val, min, max T) bool {
if max == 0 {
return val >= min
}
return val >= min && val <= max
}
+9 -3
View File
@@ -172,6 +172,12 @@ func Int64(in any) (int64, error) {
return ToInt64(in)
}
// SafeInt64 convert value to int64, will ignore error
func SafeInt64(in any) int64 {
i64, _ := ToInt64(in)
return i64
}
// QuietInt64 convert value to int64, will ignore error
func QuietInt64(in any) int64 {
i64, _ := ToInt64(in)
@@ -366,7 +372,7 @@ func TryToString(val any, defaultAsErr bool) (str string, err error) {
case int32: // same as `rune`
str = strconv.Itoa(int(value))
case int64:
str = strconv.Itoa(int(value))
str = strconv.FormatInt(value, 10)
case uint:
str = strconv.FormatUint(uint64(value), 10)
case uint8:
@@ -382,8 +388,8 @@ func TryToString(val any, defaultAsErr bool) (str string, err error) {
case float64:
str = strconv.FormatFloat(value, 'f', -1, 64)
case time.Duration:
str = strconv.FormatUint(uint64(value.Nanoseconds()), 10)
case json.Number:
str = strconv.FormatInt(int64(value), 10)
case fmt.Stringer:
str = value.String()
default:
if defaultAsErr {
+49 -4
View File
@@ -1,10 +1,42 @@
// Package mathutil provide math(int, number) util functions. eg: convert, math calc, random
package mathutil
import "math"
import (
"math"
// MaxFloat compare and return max value
func MaxFloat(x, y float64) float64 {
return math.Max(x, y)
"github.com/gookit/goutil/comdef"
)
// Min compare two value and return max value
func Min[T comdef.XintOrFloat](x, y T) T {
if x < y {
return x
}
return y
}
// Max compare two value and return max value
func Max[T comdef.XintOrFloat](x, y T) T {
if x > y {
return x
}
return y
}
// SwapMin compare and always return [min, max] value
func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) {
if x < y {
return x, y
}
return y, x
}
// SwapMax compare and always return [max, min] value
func SwapMax[T comdef.XintOrFloat](x, y T) (T, T) {
if x > y {
return x, y
}
return y, x
}
// MaxInt compare and return max value
@@ -38,3 +70,16 @@ func SwapMaxI64(x, y int64) (int64, int64) {
}
return y, x
}
// MaxFloat compare and return max value
func MaxFloat(x, y float64) float64 {
return math.Max(x, y)
}
// OrElse return s OR nv(new-value) on s is empty
func OrElse[T comdef.XintOrFloat](in, nv T) T {
if in != 0 {
return in
}
return nv
}
+5 -5
View File
@@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/gookit/goutil/fmtutil"
"github.com/gookit/goutil/basefn"
)
// IsNumeric returns true if the given character is a numeric, otherwise false.
@@ -17,7 +17,6 @@ func Percent(val, total int) float64 {
if total == 0 {
return float64(0)
}
return (float64(val) / float64(total)) * 100
}
@@ -26,12 +25,13 @@ func ElapsedTime(startTime time.Time) string {
return fmt.Sprintf("%.3f", time.Since(startTime).Seconds()*1000)
}
// DataSize format value. alias format.DataSize()
// DataSize format value to data size string. eg: 1024 => 1KB, 1024*1024 => 1MB
// alias format.DataSize()
func DataSize(size uint64) string {
return fmtutil.DataSize(size)
return basefn.DataSize(size)
}
// HowLongAgo calc time. alias format.HowLongAgo()
func HowLongAgo(sec int64) string {
return fmtutil.HowLongAgo(sec)
return basefn.HowLongAgo(sec)
}
+29 -1
View File
@@ -5,7 +5,7 @@ import (
"reflect"
)
// HasChild check. eg: array, slice, map, struct
// HasChild type check. eg: array, slice, map, struct
func HasChild(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Map, reflect.Struct:
@@ -14,6 +14,34 @@ func HasChild(v reflect.Value) bool {
return false
}
// IsArrayOrSlice check. eg: array, slice
func IsArrayOrSlice(k reflect.Kind) bool {
return k == reflect.Slice || k == reflect.Array
}
// IsSimpleKind kind in: string, bool, intX, uintX, floatX
func IsSimpleKind(k reflect.Kind) bool {
if reflect.String == k {
return true
}
return k > reflect.Invalid && k <= reflect.Float64
}
// IsAnyInt check is intX or uintX type
func IsAnyInt(k reflect.Kind) bool {
return k >= reflect.Int && k <= reflect.Uintptr
}
// IsIntx check is intX or uintX type
func IsIntx(k reflect.Kind) bool {
return k >= reflect.Int && k <= reflect.Int64
}
// IsUintX check is intX or uintX type
func IsUintX(k reflect.Kind) bool {
return k >= reflect.Uint && k <= reflect.Uintptr
}
// IsNil reflect value
func IsNil(v reflect.Value) bool {
switch v.Kind() {
+37 -2
View File
@@ -38,12 +38,19 @@ func BaseTypeVal(v reflect.Value) (value any, err error) {
// ValueByType create reflect.Value by give reflect.Type
func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) {
if typ.Kind() <= reflect.Float64 {
// handle kind: string, bool, intX, uintX, floatX
if typ.Kind() == reflect.String || typ.Kind() <= reflect.Float64 {
return ValueByKind(val, typ.Kind())
}
// check type. like map, slice
newRv := reflect.ValueOf(val)
// try auto convert slice type
if IsArrayOrSlice(newRv.Kind()) && IsArrayOrSlice(typ.Kind()) {
return ConvSlice(newRv, typ.Elem())
}
// check type. like map
if newRv.Type() == typ {
return newRv, nil
}
@@ -123,6 +130,34 @@ func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
return
}
// ConvSlice make new type slice from old slice, will auto convert element type.
//
// TIPs:
//
// Only support kind: string, bool, intX, uintX, floatX
func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error) {
if !IsArrayOrSlice(oldSlRv.Kind()) {
panic("only allow array or slice type value")
}
// do not need convert type
if oldSlRv.Type().Elem() == newElemTyp {
return oldSlRv, nil
}
newSlTyp := reflect.SliceOf(newElemTyp)
newSlRv := reflect.MakeSlice(newSlTyp, 0, 0)
for i := 0; i < oldSlRv.Len(); i++ {
newElemV, err := ValueByKind(oldSlRv.Index(i).Interface(), newElemTyp.Kind())
if err != nil {
return reflect.Value{}, err
}
newSlRv = reflect.Append(newSlRv, newElemV)
}
return newSlRv, nil
}
// String convert
func String(rv reflect.Value) string {
s, _ := ValToString(rv, false)
+76 -10
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"reflect"
"strconv"
"unsafe"
)
// Elem returns the value that the interface v contains
@@ -49,23 +50,56 @@ func Len(v reflect.Value) int {
return -1
}
// SliceSubKind get sub-elem kind of the array, slice, variadic-var.
// SliceSubKind get sub-elem kind of the array, slice, variadic-var. alias SliceElemKind()
func SliceSubKind(typ reflect.Type) reflect.Kind {
return SliceElemKind(typ)
}
// SliceElemKind get sub-elem kind of the array, slice, variadic-var.
//
// Usage:
//
// SliceSubKind(reflect.TypeOf([]string{"abc"})) // reflect.String
func SliceSubKind(typ reflect.Type) reflect.Kind {
if typ.Kind() == reflect.Slice {
// SliceElemKind(reflect.TypeOf([]string{"abc"})) // reflect.String
func SliceElemKind(typ reflect.Type) reflect.Kind {
if typ.Kind() == reflect.Slice || typ.Kind() == reflect.Array {
return typ.Elem().Kind()
}
return reflect.Invalid
}
// SetValue to a reflect.Value
// UnexportedValue quickly get unexported value by reflect.Value
//
// NOTE: this method is unsafe, use it carefully.
// should ensure rv is addressable by field.CanAddr()
//
// refer: https://stackoverflow.com/questions/42664837/how-to-access-unexported-struct-fields
func UnexportedValue(rv reflect.Value) any {
if rv.CanAddr() {
// create new value from addr, now can be read and set.
return reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem().Interface()
}
// If the rv is not addressable this trick won't work, but you can create an addressable copy like this
rs2 := reflect.New(rv.Type()).Elem()
rs2.Set(rv)
rv = rs2.Field(0)
rv = reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem()
// Now rv can be read. TIP: Setting will succeed but only affects the temporary copy.
return rv.Interface()
}
// SetUnexportedValue quickly set unexported field value by reflect
//
// NOTE: this method is unsafe, use it carefully.
// should ensure rv is addressable by field.CanAddr()
func SetUnexportedValue(rv reflect.Value, value any) {
reflect.NewAt(rv.Type(), unsafe.Pointer(rv.UnsafeAddr())).Elem().Set(reflect.ValueOf(value))
}
// SetValue to a `reflect.Value`. will auto convert type if needed.
func SetValue(rv reflect.Value, val any) error {
// get real type of the ptr value
if rv.Kind() == reflect.Ptr {
// init if is nil
if rv.IsNil() {
elemTyp := rv.Type().Elem()
rv.Set(reflect.New(elemTyp))
@@ -76,12 +110,44 @@ func SetValue(rv reflect.Value, val any) error {
}
rv1, err := ValueByType(val, rv.Type())
if err != nil {
return err
if err == nil {
rv.Set(rv1)
}
return err
}
// SetRValue to a `reflect.Value`. will direct set value without convert type.
func SetRValue(rv, val reflect.Value) {
if rv.Kind() == reflect.Ptr {
if rv.IsNil() {
elemTyp := rv.Type().Elem()
rv.Set(reflect.New(elemTyp))
}
rv = reflect.Indirect(rv)
}
rv.Set(rv1)
return nil
rv.Set(val)
}
// EachMap process any map data
func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) {
if fn == nil {
return
}
if mp.Kind() != reflect.Map {
panic("only allow map value data")
}
for _, key := range mp.MapKeys() {
fn(key, mp.MapIndex(key))
}
}
// EachStrAnyMap process any map data as string key and any value
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) {
EachMap(mp, func(key, val reflect.Value) {
fn(String(key), val.Interface())
})
}
// FlatFunc custom collect handle func
+4 -18
View File
@@ -7,8 +7,8 @@ import (
)
// QuietFprint to writer, will ignore error
func QuietFprint(w io.Writer, ss ...string) {
_, _ = fmt.Fprint(w, strings.Join(ss, ""))
func QuietFprint(w io.Writer, a ...any) {
_, _ = fmt.Fprint(w, a...)
}
// QuietFprintf to writer, will ignore error
@@ -17,25 +17,11 @@ func QuietFprintf(w io.Writer, tpl string, vs ...any) {
}
// QuietFprintln to writer, will ignore error
func QuietFprintln(w io.Writer, ss ...string) {
_, _ = fmt.Fprintln(w, strings.Join(ss, ""))
func QuietFprintln(w io.Writer, a ...any) {
_, _ = fmt.Fprintln(w, a...)
}
// QuietWriteString to writer, will ignore error
func QuietWriteString(w io.Writer, ss ...string) {
_, _ = io.WriteString(w, strings.Join(ss, ""))
}
// DiscardReader anything from the reader
func DiscardReader(src io.Reader) {
_, _ = io.Copy(io.Discard, src)
}
// 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
}
+87
View File
@@ -1,2 +1,89 @@
// 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
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")
}
}
// WriteByte to stdout
func WriteByte(b byte) {
_, _ = os.Stdout.Write([]byte{b})
}
// WriteBytes to stdout
func WriteBytes(bs []byte) {
_, _ = os.Stdout.Write(bs)
}
// WritelnBytes to stdout
func WritelnBytes(bs []byte) {
_, _ = os.Stdout.Write(bs)
_, _ = os.Stdout.Write([]byte("\n"))
}
// WriteString to stdout
func WriteString(s string) {
_, _ = os.Stdout.WriteString(s)
}
// Writeln to stdout
func Writeln(s string) {
_, _ = os.Stdout.WriteString(s)
_, _ = os.Stdout.Write([]byte("\n"))
}
+3
View File
@@ -33,6 +33,9 @@ func (w *WriteWrapper) WriteByte(c byte) error {
// 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))
}
+89 -21
View File
@@ -2,7 +2,11 @@ package structs
import (
"errors"
"fmt"
"reflect"
"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/reflects"
)
// ToMap quickly convert structs to map by reflect
@@ -25,16 +29,73 @@ func TryToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
return StructToMap(st, optFns...)
}
// ToSMap quickly and safe convert structs to map[string]string by reflect
func ToSMap(st any, optFns ...MapOptFunc) map[string]string {
mp, _ := StructToMap(st, optFns...)
return maputil.ToStringMap(mp)
}
// TryToSMap quickly convert structs to map[string]string by reflect
func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error) {
mp, err := StructToMap(st, optFns...)
if err != nil {
return nil, err
}
return maputil.ToStringMap(mp), nil
}
// MustToSMap alias of ToStringMap(), but will panic on error
func MustToSMap(st any, optFns ...MapOptFunc) map[string]string {
mp, err := StructToMap(st, optFns...)
if err != nil {
panic(err)
}
return maputil.ToStringMap(mp)
}
// ToString quickly format struct to string
func ToString(st any, optFns ...MapOptFunc) string {
mp, err := StructToMap(st, optFns...)
if err == nil {
return maputil.ToString(mp)
}
return fmt.Sprint(st)
}
const defaultFieldTag = "json"
// MapOptions struct
// MapOptions for convert struct to map
type MapOptions struct {
// TagName for map filed. default is "json"
TagName string
// ParseDepth for parse. TODO support depth
ParseDepth int
// MergeAnonymous struct fields to parent map. default is true
MergeAnonymous bool
// ExportPrivate export private fields. default is false
ExportPrivate bool
}
// MapOptFunc define
type MapOptFunc func(opt *MapOptions)
// WithMapTagName set tag name for map field
func WithMapTagName(tagName string) MapOptFunc {
return func(opt *MapOptions) {
opt.TagName = tagName
}
}
// MergeAnonymous merge anonymous struct fields to parent map
func MergeAnonymous(opt *MapOptions) {
opt.MergeAnonymous = true
}
// ExportPrivate merge anonymous struct fields to parent map
func ExportPrivate(opt *MapOptions) {
opt.ExportPrivate = true
}
// StructToMap quickly convert structs to map[string]any by reflect.
// Can custom export field name by tag `json` or custom tag
func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
@@ -43,13 +104,9 @@ func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
return mp, nil
}
obj := reflect.ValueOf(st)
if obj.Kind() == reflect.Ptr {
obj = obj.Elem()
}
obj := reflect.Indirect(reflect.ValueOf(st))
if obj.Kind() != reflect.Struct {
return mp, errors.New("must be an struct")
return mp, errors.New("must be an struct value")
}
opt := &MapOptions{TagName: defaultFieldTag}
@@ -57,23 +114,25 @@ func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error) {
fn(opt)
}
mp, err := structToMap(obj, opt.TagName)
_, err := structToMap(obj, opt, mp)
return mp, err
}
func structToMap(obj reflect.Value, tagName string) (map[string]any, error) {
refType := obj.Type()
mp := make(map[string]any)
func structToMap(obj reflect.Value, opt *MapOptions, mp map[string]any) (map[string]any, error) {
if mp == nil {
mp = make(map[string]any)
}
refType := obj.Type()
for i := 0; i < obj.NumField(); i++ {
ft := refType.Field(i)
name := ft.Name
// skip don't exported field
if name[0] >= 'a' && name[0] <= 'z' {
// skip un-exported field
if !opt.ExportPrivate && IsUnexported(name) {
continue
}
tagVal, ok := ft.Tag.Lookup(tagName)
tagVal, ok := ft.Tag.Lookup(opt.TagName)
if ok && tagVal != "" {
sMap, err := ParseTagValueDefault(name, tagVal)
if err != nil {
@@ -81,24 +140,33 @@ func structToMap(obj reflect.Value, tagName string) (map[string]any, error) {
}
name = sMap.Default("name", name)
// un-exported field
if name == "" {
if name == "" { // un-exported field
continue
}
}
field := obj.Field(i)
field := reflect.Indirect(obj.Field(i))
if field.Kind() == reflect.Struct {
sub, err := structToMap(field, tagName)
if err != nil {
return nil, err
// collect anonymous struct values to parent.
if ft.Anonymous && opt.MergeAnonymous {
_, err := structToMap(field, opt, mp)
if err != nil {
return nil, err
}
} else { // collect struct values to submap
sub, err := structToMap(field, opt, nil)
if err != nil {
return nil, err
}
mp[name] = sub
}
mp[name] = sub
continue
}
if field.CanInterface() {
mp[name] = field.Interface()
} else if field.CanAddr() { // for unexported field
mp[name] = reflects.UnexportedValue(field)
}
}
+6
View File
@@ -0,0 +1,6 @@
package structs
// MapStruct simple copy src struct value to dst struct
// func MapStruct(srcSt, dstSt any) {
// // TODO
// }
+19
View File
@@ -173,3 +173,22 @@ func (d *Data) BoolVal(key string) bool {
func (d *Data) String() string {
return maputil.ToString(d.data)
}
// OrderedMap data TODO
type OrderedMap struct {
maputil.Data
len int
keys []string
// vals []any
}
// NewOrderedMap instance.
func NewOrderedMap(len int) *OrderedMap {
return &OrderedMap{len: len}
}
// Set key and value to map
func (om *OrderedMap) Set(key string, val any) {
om.keys = append(om.keys, key)
om.Data.Set(key, val)
}
+179
View File
@@ -0,0 +1,179 @@
package structs
import (
"errors"
"reflect"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/strutil"
)
const defaultInitTag = "default"
// InitOptFunc define
type InitOptFunc func(opt *InitOptions)
// InitOptions struct
type InitOptions struct {
// TagName default value tag name. tag: default
TagName string
// ParseEnv var name on default value. eg: `default:"${APP_ENV}"`
//
// default: false
ParseEnv bool
// ValueHook before set value hook TODO
ValueHook func(val string) any
}
// Init struct default value by field "default" tag.
func Init(ptr any, optFns ...InitOptFunc) error {
return InitDefaults(ptr, optFns...)
}
// InitDefaults init struct default value by field "default" tag.
//
// TIPS:
//
// Support init field types: string, bool, intX, uintX, floatX, array, slice
//
// Example:
//
// type User1 struct {
// Name string `default:"inhere"`
// Age int32 `default:"30"`
// }
//
// u1 := &User1{}
// err = structs.InitDefaults(u1)
// fmt.Printf("%+v\n", u1) // Output: {Name:inhere Age:30}
func InitDefaults(ptr any, optFns ...InitOptFunc) error {
rv := reflect.ValueOf(ptr)
if rv.Kind() != reflect.Ptr {
return errors.New("must be provider an pointer value")
}
rv = rv.Elem()
if rv.Kind() != reflect.Struct {
return errors.New("must be provider an struct value")
}
opt := &InitOptions{TagName: defaultInitTag}
for _, fn := range optFns {
fn(opt)
}
return initDefaults(rv, opt)
}
func initDefaults(rv reflect.Value, opt *InitOptions) error {
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
sf := rt.Field(i)
// skip don't exported field
if IsUnexported(sf.Name) {
continue
}
val, hasTag := sf.Tag.Lookup(opt.TagName)
if !hasTag || val == "-" {
continue
}
fv := rv.Field(i)
if fv.Kind() == reflect.Struct {
if err := initDefaults(fv, opt); err != nil {
return err
}
continue
}
// skip on field has value
if !fv.IsZero() {
// special: handle for pointer struct field
if fv.Kind() == reflect.Pointer {
fv = fv.Elem()
if fv.Kind() == reflect.Struct {
if err := initDefaults(fv, opt); err != nil {
return err
}
}
}
continue
}
// handle for pointer field
if fv.Kind() == reflect.Pointer {
if fv.IsNil() {
fv.Set(reflect.New(fv.Type().Elem()))
}
fv = fv.Elem()
if fv.Kind() == reflect.Struct {
if err := initDefaults(fv, opt); err != nil {
return err
}
continue
}
} else if fv.Kind() == reflect.Slice {
el := sf.Type.Elem()
isPtr := el.Kind() == reflect.Pointer
if isPtr {
el = el.Elem()
}
// init sub struct in slice. like `[]SubStruct` or `[]*SubStruct`
if el.Kind() == reflect.Struct {
// make sub-struct and init. like: `SubStruct`
subFv := reflect.New(el)
subFvE := subFv.Elem()
if err := initDefaults(subFvE, opt); err != nil {
return err
}
// make new slice and set value.
newFv := reflect.MakeSlice(reflect.SliceOf(sf.Type.Elem()), 0, 1)
if isPtr {
newFv = reflect.Append(newFv, subFv)
} else {
newFv = reflect.Append(newFv, subFvE)
}
fv.Set(newFv)
continue
}
}
if err := initDefaultValue(fv, val, opt.ParseEnv); err != nil {
return err
}
}
return nil
}
func initDefaultValue(fv reflect.Value, val string, parseEnv bool) error {
if val == "" || !fv.CanSet() {
return nil
}
// parse env var
if parseEnv {
val = comfunc.ParseEnvVar(val, nil)
}
var anyVal any = val
// simple slice: convert simple kind(string,intX,uintX,...) to slice. eg: "1,2,3" => []int{1,2,3}
if reflects.IsArrayOrSlice(fv.Kind()) && reflects.IsSimpleKind(reflects.SliceElemKind(fv.Type())) {
ss := strutil.SplitTrimmed(val, ",")
valRv, err := reflects.ConvSlice(reflect.ValueOf(ss), fv.Type().Elem())
if err == nil {
reflects.SetRValue(fv, valRv)
}
return err
}
// set value
return reflects.SetValue(fv, anyVal)
}
+12 -4
View File
@@ -1,7 +1,15 @@
// Package structs Provide some extends util functions for struct. eg: tag parse, struct init, value set
package structs
// MapStruct simple copy src struct value to dst struct
// func MapStruct(srcSt, dstSt any) {
// // TODO
// }
// IsExported field name on struct
func IsExported(name string) bool {
return name[0] >= 'A' && name[0] <= 'Z'
}
// IsUnexported field name on struct
func IsUnexported(name string) bool {
if name[0] == '_' {
return true
}
return name[0] >= 'a' && name[0] <= 'z'
}
+14 -3
View File
@@ -204,6 +204,14 @@ func ParseTagValueDefault(field, tagVal string) (mp maputil.SMap, err error) {
return
}
// ParseTagValueQuick quick parse tag value string by sep(;)
func ParseTagValueQuick(tagVal string, defines []string) maputil.SMap {
parseFn := ParseTagValueDefine(";", defines)
mp, _ := parseFn("", tagVal)
return mp
}
// ParseTagValueDefine parse tag value string by given defines.
//
// Examples:
@@ -235,7 +243,11 @@ func ParseTagValueDefine(sep string, defines []string) TagValFunc {
// ParseTagValueNamed parse k-v tag value string. it's like INI format contents.
//
// eg: "name=int0;shorts=i;required=true;desc=int option message"
// Examples:
//
// eg: "name=val0;shorts=i;required=true;desc=a message"
// =>
// {name: val0, shorts: i, required: true, desc: a message}
func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap, err error) {
ss := strutil.Split(tagVal, ";")
ln := len(ss)
@@ -250,8 +262,7 @@ func ParseTagValueNamed(field, tagVal string, keys ...string) (mp maputil.SMap,
return
}
kvNodes := strings.SplitN(s, "=", 2)
key, val := kvNodes[0], strings.TrimSpace(kvNodes[1])
key, val := strutil.TrimCut(s, "=")
if len(keys) > 0 && !arrutil.StringsHas(keys, key) {
err = fmt.Errorf("parse tag error on field '%s': invalid key name '%s'", field, key)
return
+7 -2
View File
@@ -35,6 +35,11 @@ func (v *Value) Val() any {
return v.V
}
// Val get
// func (v *Value) ValOr[T any](defVal T) T {
// return v.V
// }
// Int value get
func (v *Value) Int() int {
if v.V == nil {
@@ -102,7 +107,7 @@ func (v *Value) Strings() (ss []string) {
return
}
// SplitToStrings split string value to strings
// SplitToStrings split string value to strings. sep default is comma(,)
func (v *Value) SplitToStrings(sep ...string) (ss []string) {
if v.V == nil {
return
@@ -114,7 +119,7 @@ func (v *Value) SplitToStrings(sep ...string) (ss []string) {
return
}
// SplitToInts split string value to []int
// SplitToInts split string value to []int. sep default is comma(,)
func (v *Value) SplitToInts(sep ...string) (ss []int) {
if v.V == nil {
return
+51
View File
@@ -0,0 +1,51 @@
package structs
import "reflect"
// Wrapper struct for read or set field value TODO
type Wrapper struct {
// src any // source data struct
rv reflect.Value
// FieldTagName field name for read/write value. default tag: json
FieldTagName string
}
// Wrap create a struct wrapper
func Wrap(src any) *Wrapper {
return NewWrapper(src)
}
// NewWrapper create a struct wrapper
func NewWrapper(src any) *Wrapper {
return WrapValue(reflect.ValueOf(src))
}
// WrapValue create a struct wrapper
func WrapValue(rv reflect.Value) *Wrapper {
rv = reflect.Indirect(rv)
if rv.Kind() != reflect.Struct {
panic("must be provider an struct value")
}
return &Wrapper{rv: rv}
}
// Get field value by name
func (r *Wrapper) Get(name string) any {
val, ok := r.Lookup(name)
if !ok {
return nil
}
return val
}
// Lookup field value by name
func (r *Wrapper) Lookup(name string) (val any, ok bool) {
fv := r.rv.FieldByName(name)
if !fv.IsValid() {
return
}
return fv.Interface(), true
}
@@ -5,111 +5,22 @@ import (
"fmt"
"reflect"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/reflects"
)
const defaultInitTag = "default"
// InitOptFunc define
type InitOptFunc func(opt *InitOptions)
// InitOptions struct
type InitOptions struct {
// TagName default value tag name. tag: default
TagName string
// ParseEnv var name on default value. eg: `default:"${APP_ENV}"`
//
// default: false
ParseEnv bool
// ValueHook before set value hook TODO
ValueHook func(val string) any
}
// InitDefaults init struct default value by field "default" tag.
//
// TIPS:
//
// Only support init set: string, bool, intX, uintX, floatX
//
// Example:
//
// type User1 struct {
// Name string `default:"inhere"`
// Age int32 `default:"30"`
// }
//
// u1 := &User1{}
// err = structs.InitDefaults(u1)
// fmt.Printf("%+v\n", u1)
// // Output: {Name:inhere Age:30}
func InitDefaults(ptr any, optFns ...InitOptFunc) error {
// NewWriter create a struct writer
func NewWriter(ptr any) *Wrapper {
rv := reflect.ValueOf(ptr)
if rv.Kind() != reflect.Ptr {
return errors.New("must be provider an pointer value")
if rv.Kind() != reflect.Pointer {
panic("must be provider an pointer value")
}
rv = rv.Elem()
if rv.Kind() != reflect.Struct {
return errors.New("must be provider an struct value")
}
opt := &InitOptions{TagName: defaultInitTag}
for _, fn := range optFns {
fn(opt)
}
return initDefaults(rv, opt)
}
func initDefaults(rv reflect.Value, opt *InitOptions) error {
rt := rv.Type()
for i := 0; i < rt.NumField(); i++ {
ft := rt.Field(i)
// skip don't exported field
if ft.Name[0] >= 'a' && ft.Name[0] <= 'z' {
continue
}
fv := rv.Field(i)
if fv.Kind() == reflect.Struct {
if err := initDefaults(fv, opt); err != nil {
return err
}
continue
}
// skip on field has value
if !fv.IsZero() {
continue
}
tagVal := ft.Tag.Get(opt.TagName)
if err := initDefaultValue(fv, tagVal, opt.ParseEnv); err != nil {
return err
}
}
return nil
}
func initDefaultValue(fv reflect.Value, val string, parseEnv bool) error {
if val == "" || !fv.CanSet() {
return nil
}
// parse env var
if parseEnv {
val = comfunc.ParseEnvVar(val, nil)
}
// set value
return reflects.SetValue(fv, val)
return WrapValue(rv)
}
/*************************************************************
* load values to a struct
* set values to a struct
*************************************************************/
// SetOptFunc define
@@ -127,15 +38,22 @@ type SetOptions struct {
//
// see InitDefaults()
ParseDefault bool
// DefaultValTag name. tag: default
DefaultValTag string
// ParseDefaultEnv parse env var on default tag. eg: `default:"${APP_ENV}"`
//
// default: false
ParseDefaultEnv bool
}
// SetValues set data values to struct ptr
// WithParseDefault value by tag "default"
func WithParseDefault(opt *SetOptions) {
opt.ParseDefault = true
}
// SetValues set values to struct ptr from map data.
//
// TIPS:
//
@@ -152,7 +70,7 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error {
}
opt := &SetOptions{
FieldTagName: "json",
FieldTagName: defaultFieldTag,
DefaultValTag: defaultInitTag,
}
@@ -163,7 +81,7 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error {
}
func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error {
if data == nil || len(data) == 0 {
if len(data) == 0 {
return nil
}
@@ -184,7 +102,6 @@ func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error {
if err != nil {
return err
}
name = info.Get("name")
}
@@ -192,20 +109,27 @@ func setValues(rv reflect.Value, data map[string]any, opt *SetOptions) error {
val, ok := data[name]
// set field value by default tag.
if !ok && fv.IsZero() {
if !ok && opt.ParseDefault && fv.IsZero() {
defVal := ft.Tag.Get(opt.DefaultValTag)
if err := initDefaultValue(fv, defVal, opt.ParseDefaultEnv); err != nil {
return err
}
continue
}
// handle for pointer field
if fv.Kind() == reflect.Pointer {
if fv.IsNil() {
fv.Set(reflect.New(fv.Type().Elem()))
}
fv = fv.Elem()
}
// field is struct
if fv.Kind() == reflect.Struct {
asMp, ok := val.(map[string]any)
if !ok {
return fmt.Errorf("field is struct, must provide map data value")
asMp, err := maputil.TryAnyMap(val)
if err != nil {
return fmt.Errorf("must provide map data for field %q, err=%v", ft.Name, err)
}
if err := setValues(fv, asMp, opt); err != nil {
+5 -105
View File
@@ -1,69 +1,17 @@
package strutil
import (
"bytes"
"fmt"
"strings"
"github.com/gookit/goutil/byteutil"
)
// Buffer wrap and extends the bytes.Buffer
type Buffer struct {
bytes.Buffer
}
type Buffer = byteutil.Buffer
// NewBuffer instance
func NewBuffer() *Buffer {
return &Buffer{}
}
// WriteAny type value to buffer
func (b *Buffer) WriteAny(vs ...any) {
for _, v := range vs {
_, _ = b.Buffer.WriteString(fmt.Sprint(v))
}
}
// QuietWriteByte to buffer
func (b *Buffer) QuietWriteByte(c byte) {
_ = b.WriteByte(c)
}
// QuietWritef write message to buffer
func (b *Buffer) QuietWritef(tpl string, vs ...any) {
_, _ = b.WriteString(fmt.Sprintf(tpl, vs...))
}
// Writeln write message to buffer with newline
func (b *Buffer) Writeln(ss ...string) {
b.QuietWriteln(ss...)
}
// QuietWriteln write message to buffer with newline
func (b *Buffer) QuietWriteln(ss ...string) {
_, _ = b.WriteString(strings.Join(ss, ""))
_ = b.WriteByte('\n')
}
// QuietWriteString to buffer
func (b *Buffer) QuietWriteString(ss ...string) {
_, _ = b.WriteString(strings.Join(ss, ""))
}
// MustWriteString to buffer
func (b *Buffer) MustWriteString(ss ...string) {
_, err := b.WriteString(strings.Join(ss, ""))
if err != nil {
panic(err)
}
}
// ResetAndGet buffer string.
func (b *Buffer) ResetAndGet() string {
s := b.String()
b.Reset()
return s
}
// ByteChanPool struct
//
// Usage:
@@ -72,57 +20,9 @@ func (b *Buffer) ResetAndGet() string {
// buf:=bp.Get()
// defer bp.Put(buf)
// // use buf do something ...
//
// refer https://www.flysnow.org/2020/08/21/golang-chan-byte-pool.html
// from https://github.com/minio/minio/blob/master/internal/bpool/bpool.go
type ByteChanPool struct {
c chan []byte
w int
wcap int
}
type ByteChanPool = byteutil.ChanPool
// NewByteChanPool instance
func NewByteChanPool(maxSize int, width int, capWidth int) *ByteChanPool {
return &ByteChanPool{
c: make(chan []byte, maxSize),
w: width,
wcap: capWidth,
}
}
// Get gets a []byte from the BytePool, or creates a new one if none are
// available in the pool.
func (bp *ByteChanPool) Get() (b []byte) {
select {
case b = <-bp.c:
// reuse existing buffer
default:
// create new buffer
if bp.wcap > 0 {
b = make([]byte, bp.w, bp.wcap)
} else {
b = make([]byte, bp.w)
}
}
return
}
// Put returns the given Buffer to the BytePool.
func (bp *ByteChanPool) Put(b []byte) {
select {
case bp.c <- b:
// buffer went back into pool
default:
// buffer didn't go back into pool, just discard
}
}
// Width returns the width of the byte arrays in this pool.
func (bp *ByteChanPool) Width() (n int) {
return bp.w
}
// WidthCap returns the cap width of the byte arrays in this pool.
func (bp *ByteChanPool) WidthCap() (n int) {
return bp.wcap
func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool {
return byteutil.NewChanPool(maxSize, width, capWidth)
}
+187 -34
View File
@@ -1,6 +1,7 @@
package strutil
import (
"path"
"regexp"
"strings"
"unicode"
@@ -10,22 +11,13 @@ import (
// Equal check, alias of strings.EqualFold
var Equal = strings.EqualFold
// NoCaseEq check two strings is equals and case-insensitivity
func NoCaseEq(s, t string) bool {
return strings.EqualFold(s, t)
}
// IsNumChar returns true if the given character is a numeric, otherwise false.
func IsNumChar(c byte) bool {
return c >= '0' && c <= '9'
}
func IsNumChar(c byte) bool { return c >= '0' && c <= '9' }
var numReg = regexp.MustCompile(`^\d+$`)
// IsNumeric returns true if the given string is a numeric, otherwise false.
func IsNumeric(s string) bool {
return numReg.MatchString(s)
}
func IsNumeric(s string) bool { return numReg.MatchString(s) }
// IsAlphabet char
func IsAlphabet(char uint8) bool {
@@ -47,16 +39,31 @@ func IsAlphaNum(c uint8) bool {
}
// StrPos alias of the strings.Index
func StrPos(s, sub string) int {
return strings.Index(s, sub)
}
func StrPos(s, sub string) int { return strings.Index(s, sub) }
// BytePos alias of the strings.IndexByte
func BytePos(s string, bt byte) int {
return strings.IndexByte(s, bt)
func BytePos(s string, bt byte) int { return strings.IndexByte(s, bt) }
// IEqual ignore case check given two string is equals.
func IEqual(s1, s2 string) bool { return strings.EqualFold(s1, s2) }
// NoCaseEq check two strings is equals and case-insensitivity
func NoCaseEq(s, t string) bool { return strings.EqualFold(s, t) }
// IContains ignore case check substr in the given string.
func IContains(s, sub string) bool {
return strings.Contains(strings.ToLower(s), strings.ToLower(sub))
}
// HasOneSub substr in the given string.
// ContainsByte in given string.
func ContainsByte(s string, c byte) bool {
return strings.IndexByte(s, c) >= 0
}
// ContainsOne substr(s) in the given string. alias of HasOneSub()
func ContainsOne(s string, subs []string) bool { return HasOneSub(s, subs) }
// HasOneSub substr(s) in the given string.
func HasOneSub(s string, subs []string) bool {
for _, sub := range subs {
if strings.Contains(s, sub) {
@@ -66,6 +73,9 @@ func HasOneSub(s string, subs []string) bool {
return false
}
// ContainsAll substr(s) in the given string. alias of HasAllSubs()
func ContainsAll(s string, subs []string) bool { return HasAllSubs(s, subs) }
// HasAllSubs all substr in the given string.
func HasAllSubs(s string, subs []string) bool {
for _, sub := range subs {
@@ -103,6 +113,16 @@ func HasSuffix(s string, suffix string) bool { return strings.HasSuffix(s, suffi
// IsEndOf alias of the strings.HasSuffix
func IsEndOf(s, suffix string) bool { return strings.HasSuffix(s, suffix) }
// HasOneSuffix the string end withs one of the subs
func HasOneSuffix(s string, suffixes []string) bool {
for _, suffix := range suffixes {
if strings.HasSuffix(s, suffix) {
return true
}
}
return false
}
// IsValidUtf8 valid utf8 string check
func IsValidUtf8(s string) bool { return utf8.ValidString(s) }
@@ -112,22 +132,16 @@ func IsValidUtf8(s string) bool { return utf8.ValidString(s) }
var spaceTable = [256]int8{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
// IsSpace returns true if the given character is a space, otherwise false.
func IsSpace(c byte) bool {
return spaceTable[c] == 1
}
func IsSpace(c byte) bool { return spaceTable[c] == 1 }
// IsEmpty returns true if the given string is empty.
func IsEmpty(s string) bool { return len(s) == 0 }
// IsBlank returns true if the given string is all space characters.
func IsBlank(s string) bool {
return IsBlankBytes([]byte(s))
}
func IsBlank(s string) bool { return IsBlankBytes([]byte(s)) }
// IsNotBlank returns true if the given string is not blank.
func IsNotBlank(s string) bool {
return !IsBlankBytes([]byte(s))
}
func IsNotBlank(s string) bool { return !IsBlankBytes([]byte(s)) }
// IsBlankBytes returns true if the given []byte is all space characters.
func IsBlankBytes(bs []byte) bool {
@@ -140,21 +154,35 @@ func IsBlankBytes(bs []byte) bool {
}
// IsSymbol reports whether the rune is a symbolic character.
func IsSymbol(r rune) bool {
return unicode.IsSymbol(r)
func IsSymbol(r rune) bool { return unicode.IsSymbol(r) }
// HasEmpty value for input strings
func HasEmpty(ss ...string) bool {
for _, s := range ss {
if s == "" {
return true
}
}
return false
}
// IsAllEmpty for input strings
func IsAllEmpty(ss ...string) bool {
for _, s := range ss {
if s != "" {
return false
}
}
return true
}
var verRegex = regexp.MustCompile(`^[0-9][\d.]+(-\w+)?$`)
// IsVersion number. eg: 1.2.0
func IsVersion(s string) bool {
return verRegex.MatchString(s)
}
func IsVersion(s string) bool { return verRegex.MatchString(s) }
// Compare for two string.
func Compare(s1, s2, op string) bool {
return VersionCompare(s1, s2, op)
}
func Compare(s1, s2, op string) bool { return VersionCompare(s1, s2, op) }
// VersionCompare for two version string.
func VersionCompare(v1, v2, op string) bool {
@@ -173,3 +201,128 @@ func VersionCompare(v1, v2, op string) bool {
return v1 == v2
}
}
// SimpleMatch all sub-string in the give text string.
//
// Difference the ContainsAll:
//
// - start with ^ for exclude contains check.
// - end with $ for check end with keyword.
func SimpleMatch(s string, keywords []string) bool {
for _, keyword := range keywords {
kln := len(keyword)
if kln == 0 {
continue
}
// exclude
if kln > 1 && keyword[0] == '^' {
if strings.Contains(s, keyword[1:]) {
return false
}
continue
}
// end with
if kln > 1 && keyword[kln-1] == '$' {
return strings.HasSuffix(s, keyword[:kln-1])
}
// include
if !strings.Contains(s, keyword) {
return false
}
}
return true
}
// QuickMatch check for a string. pattern can be a sub string.
func QuickMatch(pattern, s string) bool {
if strings.ContainsRune(pattern, '*') {
return GlobMatch(pattern, s)
}
return strings.Contains(s, pattern)
}
// PathMatch check for a string match the pattern. alias of the path.Match()
//
// TIP: `*` can match any char, not contain `/`.
func PathMatch(pattern, s string) bool {
ok, err := path.Match(pattern, s)
if err != nil {
ok = false
}
return ok
}
// GlobMatch check for a string match the pattern.
//
// Difference with PathMatch() is: `*` can match any char, contain `/`.
func GlobMatch(pattern, s string) bool {
// replace `/` to `S` for path.Match
pattern = strings.Replace(pattern, "/", "S", -1)
s = strings.Replace(s, "/", "S", -1)
ok, err := path.Match(pattern, s)
if err != nil {
ok = false
}
return ok
}
// LikeMatch simple check for a string match the pattern. pattern like the SQL LIKE.
func LikeMatch(pattern, s string) bool {
ln := len(pattern)
if ln < 2 {
return false
}
// eg `%abc` `%abc%`
if pattern[0] == '%' {
if ln > 2 && pattern[ln-1] == '%' {
return strings.Contains(s, pattern[1:ln-1])
} else {
return strings.HasSuffix(s, pattern[1:])
}
}
// eg `abc%`
if pattern[ln-1] == '%' {
return strings.HasPrefix(s, pattern[:ln-1])
}
return pattern == s
}
// MatchNodePath check for a string match the pattern.
//
// Use on pattern:
// - `*` match any to sep
// - `**` match any to end. only allow at start or end on pattern.
//
// Example:
//
// strutil.MatchNodePath()
func MatchNodePath(pattern, s string, sep string) bool {
if pattern == "**" || pattern == s {
return true
}
if pattern == "" {
return len(s) == 0
}
if i := strings.Index(pattern, "**"); i >= 0 {
if i == 0 { // at start
return strings.HasSuffix(s, pattern[2:])
}
return strings.HasPrefix(s, pattern[:len(pattern)-2])
}
pattern = strings.Replace(pattern, sep, "/", -1)
s = strings.Replace(s, sep, "/", -1)
ok, err := path.Match(pattern, s)
if err != nil {
ok = false
}
return ok
}
+50 -99
View File
@@ -1,7 +1,6 @@
package strutil
import (
"encoding/json"
"errors"
"fmt"
"reflect"
@@ -17,8 +16,10 @@ import (
)
var (
ErrDateLayout = errors.New("invalid date layout string")
ErrInvalidParam = errors.New("invalid input parameter")
// ErrDateLayout error
ErrDateLayout = errors.New("invalid date layout string")
// ErrInvalidParam error
ErrInvalidParam = errors.New("invalid input for parse time")
// some regex for convert string.
toSnakeReg = regexp.MustCompile("[A-Z][a-z]")
@@ -39,6 +40,8 @@ var (
func Quote(s string) string { return strconv.Quote(s) }
// Unquote remove start and end quotes by single-quote or double-quote
//
// tip: strconv.Unquote cannot unquote single-quote
func Unquote(s string) string {
ln := len(s)
if ln < 2 {
@@ -70,6 +73,16 @@ func Join(sep string, ss ...string) string { return strings.Join(ss, sep) }
// JoinList alias of strings.Join
func JoinList(sep string, ss []string) string { return strings.Join(ss, sep) }
// JoinAny type to string
func JoinAny(sep string, parts ...any) string {
ss := make([]string, 0, len(parts))
for _, part := range parts {
ss = append(ss, QuietString(part))
}
return strings.Join(ss, sep)
}
// Implode alias of strings.Join
func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) }
@@ -77,39 +90,48 @@ func Implode(sep string, ss ...string) string { return strings.Join(ss, sep) }
* convert value to string
*************************************************************/
// String convert val to string
// String convert value to string, return error on failed
func String(val any) (string, error) {
return AnyToString(val, true)
}
// ToString convert value to string, return error on failed
func ToString(val any) (string, error) {
return AnyToString(val, true)
}
// QuietString convert value to string, will ignore error
func QuietString(in any) string {
val, _ := AnyToString(in, false)
return val
}
// MustString convert value to string, TODO will panic on error
func MustString(in any) string {
// SafeString convert value to string, will ignore error
func SafeString(in any) string {
val, _ := AnyToString(in, false)
return val
}
// MustString convert value to string, will panic on error
func MustString(in any) string {
val, err := AnyToString(in, false)
if err != nil {
panic(err)
}
return val
}
// StringOrErr convert value to string, return error on failed
func StringOrErr(val any) (string, error) {
return AnyToString(val, true)
}
// ToString convert value to string
func ToString(val any) (string, error) {
return AnyToString(val, true)
}
// AnyToString convert value to string.
//
// if defaultAsErr:
// For defaultAsErr:
//
// False will use fmt.Sprint convert complex type
// True will return error on fail.
// - False will use fmt.Sprint convert complex type
// - True will return error on fail.
func AnyToString(val any, defaultAsErr bool) (str string, err error) {
if val == nil {
return
@@ -147,8 +169,8 @@ func AnyToString(val any, defaultAsErr bool) (str string, err error) {
case []byte:
str = string(value)
case time.Duration:
str = value.String()
case json.Number:
str = strconv.FormatInt(int64(value), 10)
case fmt.Stringer:
str = value.String()
default:
if defaultAsErr {
@@ -203,7 +225,10 @@ func QuietBool(s string) bool {
// MustBool convert, will panic on error
func MustBool(s string) bool {
val, _ := comfunc.StrToBool(strings.TrimSpace(s))
val, err := comfunc.StrToBool(strings.TrimSpace(s))
if err != nil {
panic(err)
}
return val
}
@@ -226,6 +251,12 @@ func ToInt(s string) (int, error) {
return strconv.Atoi(strings.TrimSpace(s))
}
// Int2 convert string to int, will ignore error
func Int2(s string) int {
val, _ := ToInt(s)
return val
}
// QuietInt convert string to int, will ignore error
func QuietInt(s string) int {
val, _ := ToInt(s)
@@ -234,8 +265,7 @@ func QuietInt(s string) int {
// MustInt convert string to int, will panic on error
func MustInt(s string) int {
val, _ := ToInt(s)
return val
return IntOrPanic(s)
}
// IntOrPanic convert value to int, will panic on error
@@ -334,87 +364,8 @@ func ToSlice(s string, sep ...string) []string {
// return cliutil.StringToOSArgs(s) // error: import cycle not allowed
// }
// MustToTime convert date string to time.Time
func MustToTime(s string, layouts ...string) time.Time {
t, err := ToTime(s, layouts...)
if err != nil {
panic(err)
}
return t
}
// auto match use some commonly layouts.
// key is layout length.
var layoutMap = map[int][]string{
6: {"200601", "060102", time.Kitchen},
8: {"20060102"},
10: {"2006-01-02"},
13: {"2006-01-02 15"},
15: {time.Stamp},
16: {"2006-01-02 15:04"},
19: {"2006-01-02 15:04:05", time.RFC822, time.StampMilli},
20: {"2006-01-02 15:04:05Z"},
21: {time.RFC822Z},
22: {time.StampMicro},
24: {time.ANSIC},
25: {time.RFC3339, time.StampNano},
// 26: {time.Layout}, // must go >= 1.19
28: {time.UnixDate},
29: {time.RFC1123},
30: {time.RFC850},
31: {time.RFC1123Z},
35: {time.RFC3339Nano},
}
// ToTime convert date string to time.Time
func ToTime(s string, layouts ...string) (t time.Time, err error) {
// custom layout
if len(layouts) > 0 {
if len(layouts[0]) > 0 {
return time.Parse(layouts[0], s)
}
err = ErrDateLayout
return
}
// auto match use some commonly layouts.
strLn := len(s)
maybeLayouts, ok := layoutMap[strLn]
if !ok {
err = ErrInvalidParam
return
}
var hasAlphaT bool
if pos := strings.IndexByte(s, 'T'); pos > 0 && pos < 12 {
hasAlphaT = true
}
hasSlashR := strings.IndexByte(s, '/') > 0
for _, layout := range maybeLayouts {
// date string has "T". eg: "2006-01-02T15:04:05"
if hasAlphaT {
layout = strings.Replace(layout, " ", "T", 1)
}
// date string has "/". eg: "2006/01/02 15:04:05"
if hasSlashR {
layout = strings.Replace(layout, "-", "/", -1)
}
t, err = time.Parse(layout, s)
if err == nil {
return
}
}
// t, err = time.ParseInLocation(layout, s, time.Local)
return
}
// ToDuration parses a duration string. such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
func ToDuration(s string) (time.Duration, error) {
return time.ParseDuration(s)
return comfunc.ToDuration(s)
}
+54
View File
@@ -0,0 +1,54 @@
package strutil
import (
"crypto/hmac"
"crypto/md5"
"crypto/sha256"
"encoding/hex"
"fmt"
)
// Md5 Generate a 32-bit md5 string
func Md5(src any) string {
return hex.EncodeToString(Md5Bytes(src))
}
// MD5 Generate a 32-bit md5 string
func MD5(src any) string { return Md5(src) }
// GenMd5 Generate a 32-bit md5 string
func GenMd5(src any) string { return Md5(src) }
// Md5Bytes Generate a 32-bit md5 bytes
func Md5Bytes(src any) []byte {
h := md5.New()
if s, ok := src.(string); ok {
h.Write([]byte(s))
} else {
h.Write([]byte(fmt.Sprint(src)))
}
return h.Sum(nil)
}
// HashPasswd for quick hash an input password string
func HashPasswd(pwd, key string) string {
hm := hmac.New(sha256.New, []byte(key))
hm.Write([]byte(pwd))
return hex.EncodeToString(hm.Sum(nil))
}
// VerifyPasswd for quick verify input password is valid
//
// - pwdMAC from db or config, generated by EncryptPasswd()
func VerifyPasswd(pwdMAC, pwd, key string) bool {
decBts, err := hex.DecodeString(pwdMAC)
if err != nil {
return false
}
hm := hmac.New(sha256.New, []byte(key))
hm.Write([]byte(pwd))
return hmac.Equal(decBts, hm.Sum(nil))
}

Some files were not shown because too many files have changed in this diff Show More