mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-03 09:20:50 -05:00
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:
committed by
Ralf Haferkamp
parent
03045c5ccc
commit
5ebc596352
+1
@@ -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
@@ -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
@@ -0,0 +1,6 @@
|
||||
//go:build !go1.18
|
||||
// +build !go1.18
|
||||
|
||||
package color
|
||||
|
||||
type any = interface{}
|
||||
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+940
-685
File diff suppressed because it is too large
Load Diff
+2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
}
|
||||
Generated
Vendored
+24
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -23,3 +23,5 @@ const (
|
||||
|
||||
// NoIdx invalid index or length
|
||||
const NoIdx = -1
|
||||
|
||||
// const VarPathReg = `(\w[\w-]*(?:\.[\w-]+)*)`
|
||||
|
||||
+10
-3
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,2 +0,0 @@
|
||||
// Package fmtutil provide some format util functions.
|
||||
package fmtutil
|
||||
-116
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,6 @@
|
||||
package structs
|
||||
|
||||
// MapStruct simple copy src struct value to dst struct
|
||||
// func MapStruct(srcSt, dstSt any) {
|
||||
// // TODO
|
||||
// }
|
||||
+19
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
Generated
Vendored
+29
-105
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user