build(deps): bump github.com/gookit/config/v2 from 2.2.6 to 2.2.7
Bumps [github.com/gookit/config/v2](https://github.com/gookit/config) from 2.2.6 to 2.2.7. - [Release notes](https://github.com/gookit/config/releases) - [Commits](https://github.com/gookit/config/compare/v2.2.6...v2.2.7) --- updated-dependencies: - dependency-name: github.com/gookit/config/v2 dependency-version: 2.2.7 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
+1
-1
@@ -1,3 +1,3 @@
|
||||
# common func for internal use
|
||||
# Common func for internal use
|
||||
|
||||
- don't depend on other external packages
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package comfunc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Cmdline build
|
||||
func Cmdline(args []string, binName ...string) string {
|
||||
b := new(strings.Builder)
|
||||
|
||||
if len(binName) > 0 {
|
||||
b.WriteString(binName[0])
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
for i, a := range args {
|
||||
if i > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
if strings.ContainsRune(a, '"') {
|
||||
b.WriteString(fmt.Sprintf(`'%s'`, a))
|
||||
} else if a == "" || strings.ContainsRune(a, '\'') || strings.ContainsRune(a, ' ') {
|
||||
b.WriteString(fmt.Sprintf(`"%s"`, a))
|
||||
} else {
|
||||
b.WriteString(a)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ShellQuote quote a string on contains ', ", SPACE. refer strconv.Quote()
|
||||
func ShellQuote(a string) string {
|
||||
if a == "" {
|
||||
return `""`
|
||||
}
|
||||
|
||||
// use quote char
|
||||
var quote byte
|
||||
|
||||
// has double quote
|
||||
if pos := strings.IndexByte(a, '"'); pos > -1 {
|
||||
if !checkNeedQuote(a, pos, '"') {
|
||||
return a
|
||||
}
|
||||
|
||||
quote = '\''
|
||||
} else if pos := strings.IndexByte(a, '\''); pos > -1 {
|
||||
// single quote
|
||||
if !checkNeedQuote(a, pos, '\'') {
|
||||
return a
|
||||
}
|
||||
quote = '"'
|
||||
} else if strings.IndexByte(a, ' ') > -1 {
|
||||
quote = '"'
|
||||
}
|
||||
|
||||
// no quote char OR not need quote
|
||||
if quote == 0 {
|
||||
return a
|
||||
}
|
||||
return fmt.Sprintf("%c%s%c", quote, a, quote)
|
||||
}
|
||||
|
||||
func checkNeedQuote(a string, pos int, char byte) bool {
|
||||
// end with char. eg: "
|
||||
lastIsQ := a[len(a)-1] == char
|
||||
|
||||
// start with char. eg: "
|
||||
if pos == 0 {
|
||||
if lastIsQ {
|
||||
return false
|
||||
}
|
||||
|
||||
if pos1 := strings.IndexByte(a[pos+1:], char); pos1 > -1 {
|
||||
// eg: `"one two" three four`
|
||||
lastS := a[pos1+pos+1:]
|
||||
if !strings.ContainsRune(lastS, ' ') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startS := a[:pos]
|
||||
|
||||
// eg: `--one="two three"`
|
||||
if lastIsQ && strings.IndexByte(startS, ' ') == -1 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package comfunc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Cmdline build
|
||||
func Cmdline(args []string, binName ...string) string {
|
||||
b := new(strings.Builder)
|
||||
|
||||
if len(binName) > 0 {
|
||||
b.WriteString(binName[0])
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
for i, a := range args {
|
||||
if i > 0 {
|
||||
b.WriteByte(' ')
|
||||
}
|
||||
|
||||
if strings.ContainsRune(a, '"') {
|
||||
b.WriteString(fmt.Sprintf(`'%s'`, a))
|
||||
} else if a == "" || strings.ContainsRune(a, '\'') || strings.ContainsRune(a, ' ') {
|
||||
b.WriteString(fmt.Sprintf(`"%s"`, a))
|
||||
} else {
|
||||
b.WriteString(a)
|
||||
}
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
-79
@@ -1,12 +1,8 @@
|
||||
package comfunc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Environ like os.Environ, but will returns key-value map[string]string data.
|
||||
@@ -24,78 +20,3 @@ func Environ() map[string]string {
|
||||
}
|
||||
return envMap
|
||||
}
|
||||
|
||||
var (
|
||||
// TIP: extend unit d,w. eg: "1d", "2w"
|
||||
// time.ParseDuration() is max support hour "h".
|
||||
durStrReg = regexp.MustCompile(`^(-?\d+)(ns|us|µs|ms|s|m|h|d|w)$`)
|
||||
|
||||
// match long duration string. eg: "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks", "1month"
|
||||
// time.ParseDuration() is not supported.
|
||||
durStrRegL = regexp.MustCompile(`^(-?\d+)([hdmsw][a-zA-Z]{2,8})$`)
|
||||
)
|
||||
|
||||
// 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 "month", "months":
|
||||
// max unit is hour, so need convert by 24 * 30 * n
|
||||
n, _ := strconv.Atoi(num)
|
||||
s = strconv.Itoa(n*24*30) + "h"
|
||||
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)
|
||||
}
|
||||
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package comfunc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var commentsPrefixes = []string{"#", ";", "//"}
|
||||
|
||||
// ParseEnvLineOption parse env line options
|
||||
type ParseEnvLineOption struct {
|
||||
// NotInlineComments dont parse inline comments.
|
||||
// - default: false. will parse inline comments
|
||||
NotInlineComments bool
|
||||
// SkipOnErrorLine skip error line, continue parse next line
|
||||
// - False: return error, clear parsed map
|
||||
SkipOnErrorLine bool
|
||||
}
|
||||
|
||||
// ParseEnvLines parse simple multiline k-v string to a string-map.
|
||||
// Can use to parse simple INI or DOTENV file contents.
|
||||
//
|
||||
// NOTE:
|
||||
//
|
||||
// - It's like INI/ENV format contents.
|
||||
// - Support comments line starts with: "#", ";", "//"
|
||||
// - Support inline comments split with: " #" eg: name=tom # a comments
|
||||
// - DON'T support submap parse.
|
||||
func ParseEnvLines(text string, opt ParseEnvLineOption) (mp map[string]string, err error) {
|
||||
lines := strings.Split(text, "\n")
|
||||
ln := len(lines)
|
||||
if ln == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
strMap := make(map[string]string, ln)
|
||||
|
||||
for _, line := range lines {
|
||||
if line = strings.TrimSpace(line); line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// skip comments line
|
||||
if line[0] == '#' || line[0] == ';' || strings.HasPrefix(line, "//") {
|
||||
continue
|
||||
}
|
||||
|
||||
// invalid line
|
||||
if strings.IndexByte(line, '=') < 1 {
|
||||
if opt.SkipOnErrorLine {
|
||||
continue
|
||||
}
|
||||
|
||||
strMap = nil
|
||||
err = fmt.Errorf("invalid line contents: must match `KEY=VAL`(line: %s)", line)
|
||||
return
|
||||
}
|
||||
|
||||
key, value := SplitLineToKv(line, "=")
|
||||
|
||||
// check and remove inline comments
|
||||
if !opt.NotInlineComments {
|
||||
if pos := strings.Index(value, " #"); pos > 0 {
|
||||
value = strings.TrimRight(value[0:pos], " \t")
|
||||
}
|
||||
}
|
||||
|
||||
strMap[key] = value
|
||||
}
|
||||
|
||||
return strMap, nil
|
||||
}
|
||||
|
||||
// SplitLineToKv parse string line to k-v. eg:
|
||||
//
|
||||
// 'DEBUG=true' => ['DEBUG', 'true']
|
||||
//
|
||||
// NOTE: line must contain '=', allow: 'ENV_KEY='
|
||||
func SplitLineToKv(line, sep string) (string, string) {
|
||||
nodes := strings.SplitN(line, sep, 2)
|
||||
envKey := strings.TrimSpace(nodes[0])
|
||||
|
||||
// key cannot be empty
|
||||
if envKey == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
if len(nodes) < 2 {
|
||||
if strings.Contains(line, sep) {
|
||||
return envKey, ""
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
return envKey, strings.TrimSpace(nodes[1])
|
||||
}
|
||||
+38
-19
@@ -1,7 +1,6 @@
|
||||
package comfunc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -51,6 +50,11 @@ func ExecCmd(binName string, args []string, workDir ...string) (string, error) {
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
var (
|
||||
cmdList = []string{"cmd", "cmd.exe"}
|
||||
pwshList = []string{"powershell", "powershell.exe", "pwsh", "pwsh.exe"}
|
||||
)
|
||||
|
||||
// ShellExec exec command by shell
|
||||
// cmdLine e.g. "ls -al"
|
||||
func ShellExec(cmdLine string, shells ...string) (string, error) {
|
||||
@@ -60,48 +64,63 @@ func ShellExec(cmdLine string, shells ...string) (string, error) {
|
||||
shell = shells[0]
|
||||
}
|
||||
|
||||
var out bytes.Buffer
|
||||
|
||||
cmd := exec.Command(shell, "-c", cmdLine)
|
||||
cmd.Stdout = &out
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return out.String(), nil
|
||||
bs, err := cmd.Output()
|
||||
return string(bs), err
|
||||
}
|
||||
|
||||
// curShell cache
|
||||
var curShell string
|
||||
// curShellCache value
|
||||
var curShellCache string
|
||||
|
||||
// CurrentShell get current used shell env file.
|
||||
//
|
||||
// eg "/bin/zsh" "/bin/bash".
|
||||
// if onlyName=true, will return "zsh", "bash"
|
||||
func CurrentShell(onlyName bool) (binPath string) {
|
||||
// return like: "/bin/zsh" "/bin/bash". if onlyName=true, will return "zsh", "bash"
|
||||
func CurrentShell(onlyName bool, fallbackShell ...string) (binPath string) {
|
||||
var err error
|
||||
if curShell == "" {
|
||||
binPath = os.Getenv("SHELL")
|
||||
|
||||
fbShell := ""
|
||||
if len(fallbackShell) > 0 {
|
||||
fbShell = fallbackShell[0]
|
||||
}
|
||||
|
||||
if curShellCache == "" {
|
||||
// 检查父进程名称
|
||||
parentProcess := os.Getenv("GOPROCESS")
|
||||
if parentProcess != "" {
|
||||
return parentProcess
|
||||
}
|
||||
|
||||
binPath = os.Getenv("SHELL") // 适用于 Unix-like 系统
|
||||
if len(binPath) == 0 {
|
||||
// TODO check on Windows
|
||||
binPath, err = ShellExec("echo $SHELL")
|
||||
if err != nil {
|
||||
return ""
|
||||
return fbShell
|
||||
}
|
||||
}
|
||||
|
||||
binPath = strings.TrimSpace(binPath)
|
||||
// cache result
|
||||
curShell = binPath
|
||||
curShellCache = binPath
|
||||
} else {
|
||||
binPath = curShell
|
||||
binPath = curShellCache
|
||||
}
|
||||
|
||||
if onlyName && len(binPath) > 0 {
|
||||
binPath = filepath.Base(binPath)
|
||||
} else if len(binPath) == 0 {
|
||||
binPath = fbShell
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func checkWinCurrentShell() string {
|
||||
// 在 Windows 上,可以检查 COMSPEC 环境变量
|
||||
comSpec := os.Getenv("COMSPEC")
|
||||
// 没法检查 pwsh, 返回的还是 cmd
|
||||
return comSpec
|
||||
}
|
||||
|
||||
// HasShellEnv has shell env check.
|
||||
//
|
||||
// Usage:
|
||||
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package comfunc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// check is duration string. TIP: extend unit d,w. eg: "1d", "2w"
|
||||
//
|
||||
// time.ParseDuration() is max support hour "h".
|
||||
durStrReg = regexp.MustCompile(`^-?([0-9]+(?:\.[0-9]*)?(ns|us|µs|ms|s|m|h|d|w))+$`)
|
||||
|
||||
// check long duration string. 验证整体格式是否符合
|
||||
//
|
||||
// eg: "1hour", "2hours", "3minutes", "4mins", "5days", "1weeks", "1month"
|
||||
//
|
||||
// time.ParseDuration() is not support long unit.
|
||||
durStrRegL = regexp.MustCompile(`^-?([0-9]+(?:\.[0-9]*)?[nuµsmhdw][a-zA-Z]{0,8})+$`)
|
||||
// use for parse duration string. see ToDuration()
|
||||
//
|
||||
// NOTE: 解析时,不能加最后的 `+` 会导致只匹配了最后一组 时间单位
|
||||
durStrRegL2 = regexp.MustCompile(`-?([0-9]+(?:\.[0-9]*)?)([nuµsmhdw][a-z]{0,8})`)
|
||||
)
|
||||
|
||||
// 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 extends unit d, w at the end of string. such as "1d", "2w".
|
||||
// - support extends unit: month, week, day
|
||||
// - support long string unit at the 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
|
||||
}
|
||||
|
||||
// check duration string is valid
|
||||
if !durStrRegL.MatchString(s) {
|
||||
return 0, fmt.Errorf("invalid duration string: %s", s)
|
||||
}
|
||||
|
||||
// if ln < 4 AND end != d|w, directly call time.ParseDuration()
|
||||
if ln < 4 && s[ln-1] != 'd' && s[ln-1] != 'w' {
|
||||
return time.ParseDuration(s)
|
||||
}
|
||||
|
||||
// time.ParseDuration() is not support long unit.
|
||||
ssList := durStrRegL2.FindAllStringSubmatch(s, -1)
|
||||
// fmt.Println(ssList)
|
||||
bts := make([]byte, 0, ln)
|
||||
if s[0] == '-' {
|
||||
bts = append(bts, '-')
|
||||
}
|
||||
|
||||
// only one element. eg: "1day"
|
||||
if len(ssList) == 1 {
|
||||
bts = parseLongUnit(ssList[0], bts)
|
||||
} else {
|
||||
// more than one element. eg: "1day2hour3min"
|
||||
for _, ss := range ssList {
|
||||
if len(ss) == 3 {
|
||||
bts = parseLongUnit(ss, bts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return time.ParseDuration(string(bts))
|
||||
}
|
||||
|
||||
// convert to short unit
|
||||
func parseLongUnit(ss []string, bts []byte) []byte {
|
||||
// eg: "3sec" -> ss=[3sec, -3, sec]
|
||||
num, unit := ss[1], ss[2]
|
||||
switch unit {
|
||||
case "month", "months":
|
||||
// time lib max unit is hour, so need convert by 24 * 30*n
|
||||
bts = appendNumToBytes(bts, num, 24*30)
|
||||
bts = append(bts, 'h')
|
||||
case "w", "week", "weeks":
|
||||
// time lib max unit is hour, so need convert by 24 * 7*n
|
||||
bts = appendNumToBytes(bts, num, 24*7)
|
||||
bts = append(bts, 'h')
|
||||
case "d", "day", "days":
|
||||
// time lib max unit is hour, so need convert by 24*n
|
||||
bts = appendNumToBytes(bts, num, 24)
|
||||
bts = append(bts, 'h')
|
||||
case "hour", "hours":
|
||||
bts = append(bts, num...)
|
||||
bts = append(bts, 'h')
|
||||
case "min", "mins", "minute", "minutes":
|
||||
bts = append(bts, num...)
|
||||
bts = append(bts, 'm')
|
||||
case "sec", "secs", "second", "seconds":
|
||||
bts = append(bts, num...)
|
||||
bts = append(bts, 's')
|
||||
default:
|
||||
first := ss[0]
|
||||
|
||||
// '-' has been added on ToDuration()
|
||||
if first[0] == '-' {
|
||||
bts = append(bts, first[1:]...)
|
||||
} else {
|
||||
bts = append(bts, first...)
|
||||
}
|
||||
}
|
||||
|
||||
return bts
|
||||
}
|
||||
|
||||
func appendNumToBytes(bts []byte, num string, multiple int) []byte {
|
||||
if strings.ContainsRune(num, '.') {
|
||||
f, _ := strconv.ParseFloat(num, 64) // is float number
|
||||
val := f * float64(multiple)
|
||||
|
||||
// 使用 Float 保留两位小数 -> 会始终有两位小数,即使是N.00
|
||||
// bts = strconv.AppendFloat(bts, val, 'f', 2, 64)
|
||||
|
||||
// 四舍五入到两位小数
|
||||
rounded := math.Round(val*100) / 100
|
||||
// 使用 AppendFloat 自动去除末尾的 .0 或 .00
|
||||
bts = strconv.AppendFloat(bts, rounded, 'f', -1, 64)
|
||||
} else {
|
||||
n, _ := strconv.Atoi(num)
|
||||
bts = strconv.AppendInt(bts, int64(n*multiple), 10)
|
||||
}
|
||||
|
||||
return bts
|
||||
}
|
||||
Reference in New Issue
Block a user