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:
dependabot[bot]
2025-08-15 14:15:33 +00:00
committed by GitHub
parent 28770fe20d
commit 89a7d171ee
180 changed files with 7454 additions and 9644 deletions

7
go.mod
View File

@@ -40,7 +40,7 @@ require (
github.com/google/go-cmp v0.7.0
github.com/google/go-tika v0.3.1
github.com/google/uuid v1.6.0
github.com/gookit/config/v2 v2.2.6
github.com/gookit/config/v2 v2.2.7
github.com/gorilla/mux v1.8.1
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
github.com/invopop/validation v0.8.0
@@ -203,6 +203,7 @@ require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-test/deep v1.1.0 // indirect
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
@@ -219,8 +220,7 @@ require (
github.com/google/go-tpm v0.9.5 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/renameio/v2 v2.0.0 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/gookit/goutil v0.6.18 // indirect
github.com/gookit/goutil v0.7.1 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/schema v1.4.1 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
@@ -314,7 +314,6 @@ require (
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/yashtewari/glob-intersection v0.2.0 // indirect
go.etcd.io/etcd/api/v3 v3.6.4 // indirect

18
go.sum
View File

@@ -430,6 +430,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
@@ -551,14 +553,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
github.com/gookit/config/v2 v2.2.6 h1:8ZbkSr3gnFg1En8za9X3vldnZca3y3C7kaBLGsdLghE=
github.com/gookit/config/v2 v2.2.6/go.mod h1:++APDf3Ebj6mjzW1ALkegvg1evQKyx4FpuQqQZ2s2WM=
github.com/gookit/goutil v0.6.18 h1:MUVj0G16flubWT8zYVicIuisUiHdgirPAkmnfD2kKgw=
github.com/gookit/goutil v0.6.18/go.mod h1:AY/5sAwKe7Xck+mEbuxj0n/bc3qwrGNe3Oeulln7zBA=
github.com/gookit/ini/v2 v2.2.3 h1:nSbN+x9OfQPcMObTFP+XuHt8ev6ndv/fWWqxFhPMu2E=
github.com/gookit/ini/v2 v2.2.3/go.mod h1:Vu6p7P7xcfmb8KYu3L0ek8bqu/Im63N81q208SCCZY4=
github.com/gookit/config/v2 v2.2.7 h1:P58/uENzkDp7r7Hp8YSZxOhZ/F5a5Y/AzyhDUkQYa9A=
github.com/gookit/config/v2 v2.2.7/go.mod h1:QST99HmkZXXD/HkZmOm1OXpgdAnc6Rl9syGl+u62Pi8=
github.com/gookit/goutil v0.7.1 h1:AaFJPN9mrdeYBv8HOybri26EHGCC34WJVT7jUStGJsI=
github.com/gookit/goutil v0.7.1/go.mod h1:vJS9HXctYTCLtCsZot5L5xF+O1oR17cDYO9R0HxBmnU=
github.com/gookit/ini/v2 v2.3.2 h1:W6tzOGE6zOLQelH2xhcH8BIBZPtnEpJgQ+J6SsAKBSw=
github.com/gookit/ini/v2 v2.3.2/go.mod h1:StKSqY5niArRwYBS8Z71+iWUt5ow47qt359sS9YQLYY=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/gophercloud v0.16.0/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD8oQD0Ryhz6+6ykq75PJ79iPyEqYHfwZ4l7OsA=
@@ -1141,8 +1141,6 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg=

View File

@@ -0,0 +1,21 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
[*.go]
indent_style = tab
[{Makefile,*.mk}]
indent_style = tab
[*.nix]
indent_size = 2
[.golangci.yaml]
indent_size = 2

View File

@@ -0,0 +1,6 @@
/.devenv/
/.direnv/
/.pre-commit-config.yaml
/bin/
/build/
/var/

View File

@@ -0,0 +1,48 @@
version: "2"
run:
timeout: 10m
linters:
enable:
- govet
- ineffassign
# - misspell
- nolintlint
# - revive
disable:
- errcheck
- staticcheck
- unused
settings:
misspell:
locale: US
nolintlint:
allow-unused: false # report any unused nolint directives
require-specific: false # don't require nolint directives to be specific about which linter is being skipped
formatters:
enable:
- gci
- gofmt
- gofumpt
- goimports
# - golines
settings:
gci:
sections:
- standard
- default
- localmodule
gofmt:
simplify: true
rewrite-rules:
- pattern: interface{}
replacement: any
exclusions:
paths:
- internal/

104
vendor/github.com/go-viper/mapstructure/v2/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,104 @@
> [!WARNING]
> As of v2 of this library, change log can be found in GitHub releases.
## 1.5.1
* Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282]
* Fix map of slices not decoding properly in certain cases. [GH-266]
## 1.5.0
* New option `IgnoreUntaggedFields` to ignore decoding to any fields
without `mapstructure` (or the configured tag name) set [GH-277]
* New option `ErrorUnset` which makes it an error if any fields
in a target struct are not set by the decoding process. [GH-225]
* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240]
* Decoding to slice from array no longer crashes [GH-265]
* Decode nested struct pointers to map [GH-271]
* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280]
* Fix issue where fields with `,omitempty` would sometimes decode
into a map with an empty string key [GH-281]
## 1.4.3
* Fix cases where `json.Number` didn't decode properly [GH-261]
## 1.4.2
* Custom name matchers to support any sort of casing, formatting, etc. for
field names. [GH-250]
* Fix possible panic in ComposeDecodeHookFunc [GH-251]
## 1.4.1
* Fix regression where `*time.Time` value would be set to empty and not be sent
to decode hooks properly [GH-232]
## 1.4.0
* A new decode hook type `DecodeHookFuncValue` has been added that has
access to the full values. [GH-183]
* Squash is now supported with embedded fields that are struct pointers [GH-205]
* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206]
## 1.3.3
* Decoding maps from maps creates a settable value for decode hooks [GH-203]
## 1.3.2
* Decode into interface type with a struct value is supported [GH-187]
## 1.3.1
* Squash should only squash embedded structs. [GH-194]
## 1.3.0
* Added `",omitempty"` support. This will ignore zero values in the source
structure when encoding. [GH-145]
## 1.2.3
* Fix duplicate entries in Keys list with pointer values. [GH-185]
## 1.2.2
* Do not add unsettable (unexported) values to the unused metadata key
or "remain" value. [GH-150]
## 1.2.1
* Go modules checksum mismatch fix
## 1.2.0
* Added support to capture unused values in a field using the `",remain"` value
in the mapstructure tag. There is an example to showcase usage.
* Added `DecoderConfig` option to always squash embedded structs
* `json.Number` can decode into `uint` types
* Empty slices are preserved and not replaced with nil slices
* Fix panic that can occur in when decoding a map into a nil slice of structs
* Improved package documentation for godoc
## 1.1.2
* Fix error when decode hook decodes interface implementation into interface
type. [GH-140]
## 1.1.1
* Fix panic that can happen in `decodePtr`
## 1.1.0
* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133]
* Support struct to struct decoding [GH-137]
* If source map value is nil, then destination map value is nil (instead of empty)
* If source slice value is nil, then destination slice value is nil (instead of empty)
* If source pointer is nil, then destination pointer is set to nil (instead of
allocated zero value of type)
## 1.0.0
* Initial tagged stable release.

View File

@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2016 Anmol Sethi
Copyright (c) 2013 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

81
vendor/github.com/go-viper/mapstructure/v2/README.md generated vendored Normal file
View File

@@ -0,0 +1,81 @@
# mapstructure
[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/go-viper/mapstructure/ci.yaml?style=flat-square)](https://github.com/go-viper/mapstructure/actions/workflows/ci.yaml)
[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2)
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/go-viper/mapstructure?style=flat-square&color=61CFDD)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/go-viper/mapstructure/badge?style=flat-square)](https://deps.dev/go/github.com%252Fgo-viper%252Fmapstructure%252Fv2)
mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.
This library is most useful when decoding values from some data stream (JSON,
Gob, etc.) where you don't _quite_ know the structure of the underlying data
until you read a part of it. You can therefore read a `map[string]interface{}`
and use this library to decode it into the proper underlying native Go
structure.
## Installation
```shell
go get github.com/go-viper/mapstructure/v2
```
## Migrating from `github.com/mitchellh/mapstructure`
[@mitchehllh](https://github.com/mitchellh) announced his intent to archive some of his unmaintained projects (see [here](https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc) and [here](https://github.com/mitchellh/mapstructure/issues/349)). This is a repository achieved the "blessed fork" status.
You can migrate to this package by changing your import paths in your Go files to `github.com/go-viper/mapstructure/v2`.
The API is the same, so you don't need to change anything else.
Here is a script that can help you with the migration:
```shell
sed -i 's|github.com/mitchellh/mapstructure|github.com/go-viper/mapstructure/v2|g' $(find . -type f -name '*.go')
```
If you need more time to migrate your code, that is absolutely fine.
Some of the latest fixes are backported to the v1 release branch of this package, so you can use the Go modules `replace` feature until you are ready to migrate:
```shell
replace github.com/mitchellh/mapstructure => github.com/go-viper/mapstructure v1.6.0
```
## Usage & Example
For usage and examples see the [documentation](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2).
The `Decode` function has examples associated with it there.
## But Why?!
Go offers fantastic standard libraries for decoding formats such as JSON.
The standard method is to have a struct pre-created, and populate that struct
from the bytes of the encoded format. This is great, but the problem is if
you have configuration or an encoding that changes slightly depending on
specific fields. For example, consider this JSON:
```json
{
"type": "person",
"name": "Mitchell"
}
```
Perhaps we can't populate a specific structure without first reading
the "type" field from the JSON. We could always do two passes over the
decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a `map[string]interface{}`
structure, read the "type" key, then use something like this library
to decode it into the proper structure.
## Credits
Mapstructure was originally created by [@mitchellh](https://github.com/mitchellh).
This is a maintained fork of the original library.
Read more about the reasons for the fork [here](https://github.com/mitchellh/mapstructure/issues/349).
## License
The project is licensed under the [MIT License](LICENSE).

View File

@@ -0,0 +1,714 @@
package mapstructure
import (
"encoding"
"errors"
"fmt"
"net"
"net/netip"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
// typedDecodeHook takes a raw DecodeHookFunc (an any) and turns
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
// Create variables here so we can reference them with the reflect pkg
var f1 DecodeHookFuncType
var f2 DecodeHookFuncKind
var f3 DecodeHookFuncValue
// Fill in the variables into this interface and the rest is done
// automatically using the reflect package.
potential := []any{f1, f2, f3}
v := reflect.ValueOf(h)
vt := v.Type()
for _, raw := range potential {
pt := reflect.ValueOf(raw).Type()
if vt.ConvertibleTo(pt) {
return v.Convert(pt).Interface()
}
}
return nil
}
// cachedDecodeHook takes a raw DecodeHookFunc (an any) and turns
// it into a closure to be used directly
// if the type fails to convert we return a closure always erroring to keep the previous behaviour
func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (any, error) {
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
return func(from reflect.Value, to reflect.Value) (any, error) {
return f(from.Type(), to.Type(), from.Interface())
}
case DecodeHookFuncKind:
return func(from reflect.Value, to reflect.Value) (any, error) {
return f(from.Kind(), to.Kind(), from.Interface())
}
case DecodeHookFuncValue:
return func(from reflect.Value, to reflect.Value) (any, error) {
return f(from, to)
}
default:
return func(from reflect.Value, to reflect.Value) (any, error) {
return nil, errors.New("invalid decode hook signature")
}
}
}
// DecodeHookExec executes the given decode hook. This should be used
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
// that took reflect.Kind instead of reflect.Type.
func DecodeHookExec(
raw DecodeHookFunc,
from reflect.Value, to reflect.Value,
) (any, error) {
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
return f(from.Type(), to.Type(), from.Interface())
case DecodeHookFuncKind:
return f(from.Kind(), to.Kind(), from.Interface())
case DecodeHookFuncValue:
return f(from, to)
default:
return nil, errors.New("invalid decode hook signature")
}
}
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
// automatically composes multiple DecodeHookFuncs.
//
// The composed funcs are called in order, with the result of the
// previous transformation.
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
cached := make([]func(from reflect.Value, to reflect.Value) (any, error), 0, len(fs))
for _, f := range fs {
cached = append(cached, cachedDecodeHook(f))
}
return func(f reflect.Value, t reflect.Value) (any, error) {
var err error
data := f.Interface()
newFrom := f
for _, c := range cached {
data, err = c(newFrom, t)
if err != nil {
return nil, err
}
if v, ok := data.(reflect.Value); ok {
newFrom = v
} else {
newFrom = reflect.ValueOf(data)
}
}
return data, nil
}
}
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc {
cached := make([]func(from reflect.Value, to reflect.Value) (any, error), 0, len(ff))
for _, f := range ff {
cached = append(cached, cachedDecodeHook(f))
}
return func(a, b reflect.Value) (any, error) {
var allErrs string
var out any
var err error
for _, c := range cached {
out, err = c(a, b)
if err != nil {
allErrs += err.Error() + "\n"
continue
}
return out, nil
}
return nil, errors.New(allErrs)
}
}
// StringToSliceHookFunc returns a DecodeHookFunc that converts
// string to []string by splitting on the given sep.
func StringToSliceHookFunc(sep string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.SliceOf(f) {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
// StringToWeakSliceHookFunc brings back the old (pre-v2) behavior of [StringToSliceHookFunc].
//
// As of mapstructure v2.0.0 [StringToSliceHookFunc] checks if the return type is a string slice.
// This function removes that check.
func StringToWeakSliceHookFunc(sep string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
// strings to time.Duration.
func StringToTimeDurationHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Duration(5)) {
return data, nil
}
// Convert it by parsing
d, err := time.ParseDuration(data.(string))
return d, wrapTimeParseDurationError(err)
}
}
// StringToTimeLocationHookFunc returns a DecodeHookFunc that converts
// strings to *time.Location.
func StringToTimeLocationHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Local) {
return data, nil
}
d, err := time.LoadLocation(data.(string))
return d, wrapTimeParseLocationError(err)
}
}
// StringToURLHookFunc returns a DecodeHookFunc that converts
// strings to *url.URL.
func StringToURLHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(&url.URL{}) {
return data, nil
}
// Convert it by parsing
u, err := url.Parse(data.(string))
return u, wrapUrlError(err)
}
}
// StringToIPHookFunc returns a DecodeHookFunc that converts
// strings to net.IP
func StringToIPHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IP{}) {
return data, nil
}
// Convert it by parsing
ip := net.ParseIP(data.(string))
if ip == nil {
return net.IP{}, fmt.Errorf("failed parsing ip")
}
return ip, nil
}
}
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
// strings to net.IPNet
func StringToIPNetHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IPNet{}) {
return data, nil
}
// Convert it by parsing
_, net, err := net.ParseCIDR(data.(string))
return net, wrapNetParseError(err)
}
}
// StringToTimeHookFunc returns a DecodeHookFunc that converts
// strings to time.Time.
func StringToTimeHookFunc(layout string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}
// Convert it by parsing
ti, err := time.Parse(layout, data.(string))
return ti, wrapTimeParseError(err)
}
}
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
// the decoder.
//
// Note that this is significantly different from the WeaklyTypedInput option
// of the DecoderConfig.
func WeaklyTypedHook(
f reflect.Kind,
t reflect.Kind,
data any,
) (any, error) {
dataVal := reflect.ValueOf(data)
switch t {
case reflect.String:
switch f {
case reflect.Bool:
if dataVal.Bool() {
return "1", nil
}
return "0", nil
case reflect.Float32:
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
case reflect.Int:
return strconv.FormatInt(dataVal.Int(), 10), nil
case reflect.Slice:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
return string(dataVal.Interface().([]uint8)), nil
}
case reflect.Uint:
return strconv.FormatUint(dataVal.Uint(), 10), nil
}
}
return data, nil
}
func RecursiveStructToMapHookFunc() DecodeHookFunc {
return func(f reflect.Value, t reflect.Value) (any, error) {
if f.Kind() != reflect.Struct {
return f.Interface(), nil
}
var i any = struct{}{}
if t.Type() != reflect.TypeOf(&i).Elem() {
return f.Interface(), nil
}
m := make(map[string]any)
t.Set(reflect.ValueOf(m))
return f.Interface(), nil
}
}
// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
// strings to the UnmarshalText function, when the target type
// implements the encoding.TextUnmarshaler interface
func TextUnmarshallerHookFunc() DecodeHookFuncType {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
result := reflect.New(t).Interface()
unmarshaller, ok := result.(encoding.TextUnmarshaler)
if !ok {
return data, nil
}
str, ok := data.(string)
if !ok {
str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String()
}
if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {
return nil, err
}
return result, nil
}
}
// StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts
// strings to netip.Addr.
func StringToNetIPAddrHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(netip.Addr{}) {
return data, nil
}
// Convert it by parsing
addr, err := netip.ParseAddr(data.(string))
return addr, wrapNetIPParseAddrError(err)
}
}
// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts
// strings to netip.AddrPort.
func StringToNetIPAddrPortHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(netip.AddrPort{}) {
return data, nil
}
// Convert it by parsing
addrPort, err := netip.ParseAddrPort(data.(string))
return addrPort, wrapNetIPParseAddrPortError(err)
}
}
// StringToNetIPPrefixHookFunc returns a DecodeHookFunc that converts
// strings to netip.Prefix.
func StringToNetIPPrefixHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data any,
) (any, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(netip.Prefix{}) {
return data, nil
}
// Convert it by parsing
prefix, err := netip.ParsePrefix(data.(string))
return prefix, wrapNetIPParsePrefixError(err)
}
}
// StringToBasicTypeHookFunc returns a DecodeHookFunc that converts
// strings to basic types.
// int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, float32, float64, bool, byte, rune, complex64, complex128
func StringToBasicTypeHookFunc() DecodeHookFunc {
return ComposeDecodeHookFunc(
StringToInt8HookFunc(),
StringToUint8HookFunc(),
StringToInt16HookFunc(),
StringToUint16HookFunc(),
StringToInt32HookFunc(),
StringToUint32HookFunc(),
StringToInt64HookFunc(),
StringToUint64HookFunc(),
StringToIntHookFunc(),
StringToUintHookFunc(),
StringToFloat32HookFunc(),
StringToFloat64HookFunc(),
StringToBoolHookFunc(),
// byte and rune are aliases for uint8 and int32 respectively
// StringToByteHookFunc(),
// StringToRuneHookFunc(),
StringToComplex64HookFunc(),
StringToComplex128HookFunc(),
)
}
// StringToInt8HookFunc returns a DecodeHookFunc that converts
// strings to int8.
func StringToInt8HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Int8 {
return data, nil
}
// Convert it by parsing
i64, err := strconv.ParseInt(data.(string), 0, 8)
return int8(i64), wrapStrconvNumError(err)
}
}
// StringToUint8HookFunc returns a DecodeHookFunc that converts
// strings to uint8.
func StringToUint8HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Uint8 {
return data, nil
}
// Convert it by parsing
u64, err := strconv.ParseUint(data.(string), 0, 8)
return uint8(u64), wrapStrconvNumError(err)
}
}
// StringToInt16HookFunc returns a DecodeHookFunc that converts
// strings to int16.
func StringToInt16HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Int16 {
return data, nil
}
// Convert it by parsing
i64, err := strconv.ParseInt(data.(string), 0, 16)
return int16(i64), wrapStrconvNumError(err)
}
}
// StringToUint16HookFunc returns a DecodeHookFunc that converts
// strings to uint16.
func StringToUint16HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Uint16 {
return data, nil
}
// Convert it by parsing
u64, err := strconv.ParseUint(data.(string), 0, 16)
return uint16(u64), wrapStrconvNumError(err)
}
}
// StringToInt32HookFunc returns a DecodeHookFunc that converts
// strings to int32.
func StringToInt32HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Int32 {
return data, nil
}
// Convert it by parsing
i64, err := strconv.ParseInt(data.(string), 0, 32)
return int32(i64), wrapStrconvNumError(err)
}
}
// StringToUint32HookFunc returns a DecodeHookFunc that converts
// strings to uint32.
func StringToUint32HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Uint32 {
return data, nil
}
// Convert it by parsing
u64, err := strconv.ParseUint(data.(string), 0, 32)
return uint32(u64), wrapStrconvNumError(err)
}
}
// StringToInt64HookFunc returns a DecodeHookFunc that converts
// strings to int64.
func StringToInt64HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Int64 {
return data, nil
}
// Convert it by parsing
i64, err := strconv.ParseInt(data.(string), 0, 64)
return int64(i64), wrapStrconvNumError(err)
}
}
// StringToUint64HookFunc returns a DecodeHookFunc that converts
// strings to uint64.
func StringToUint64HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 {
return data, nil
}
// Convert it by parsing
u64, err := strconv.ParseUint(data.(string), 0, 64)
return uint64(u64), wrapStrconvNumError(err)
}
}
// StringToIntHookFunc returns a DecodeHookFunc that converts
// strings to int.
func StringToIntHookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Int {
return data, nil
}
// Convert it by parsing
i64, err := strconv.ParseInt(data.(string), 0, 0)
return int(i64), wrapStrconvNumError(err)
}
}
// StringToUintHookFunc returns a DecodeHookFunc that converts
// strings to uint.
func StringToUintHookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Uint {
return data, nil
}
// Convert it by parsing
u64, err := strconv.ParseUint(data.(string), 0, 0)
return uint(u64), wrapStrconvNumError(err)
}
}
// StringToFloat32HookFunc returns a DecodeHookFunc that converts
// strings to float32.
func StringToFloat32HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Float32 {
return data, nil
}
// Convert it by parsing
f64, err := strconv.ParseFloat(data.(string), 32)
return float32(f64), wrapStrconvNumError(err)
}
}
// StringToFloat64HookFunc returns a DecodeHookFunc that converts
// strings to float64.
func StringToFloat64HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Float64 {
return data, nil
}
// Convert it by parsing
f64, err := strconv.ParseFloat(data.(string), 64)
return f64, wrapStrconvNumError(err)
}
}
// StringToBoolHookFunc returns a DecodeHookFunc that converts
// strings to bool.
func StringToBoolHookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Bool {
return data, nil
}
// Convert it by parsing
b, err := strconv.ParseBool(data.(string))
return b, wrapStrconvNumError(err)
}
}
// StringToByteHookFunc returns a DecodeHookFunc that converts
// strings to byte.
func StringToByteHookFunc() DecodeHookFunc {
return StringToUint8HookFunc()
}
// StringToRuneHookFunc returns a DecodeHookFunc that converts
// strings to rune.
func StringToRuneHookFunc() DecodeHookFunc {
return StringToInt32HookFunc()
}
// StringToComplex64HookFunc returns a DecodeHookFunc that converts
// strings to complex64.
func StringToComplex64HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Complex64 {
return data, nil
}
// Convert it by parsing
c128, err := strconv.ParseComplex(data.(string), 64)
return complex64(c128), wrapStrconvNumError(err)
}
}
// StringToComplex128HookFunc returns a DecodeHookFunc that converts
// strings to complex128.
func StringToComplex128HookFunc() DecodeHookFunc {
return func(f reflect.Type, t reflect.Type, data any) (any, error) {
if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 {
return data, nil
}
// Convert it by parsing
c128, err := strconv.ParseComplex(data.(string), 128)
return c128, wrapStrconvNumError(err)
}
}

244
vendor/github.com/go-viper/mapstructure/v2/errors.go generated vendored Normal file
View File

@@ -0,0 +1,244 @@
package mapstructure
import (
"errors"
"fmt"
"net"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
// Error interface is implemented by all errors emitted by mapstructure.
//
// Use [errors.As] to check if an error implements this interface.
type Error interface {
error
mapstructure()
}
// DecodeError is a generic error type that holds information about
// a decoding error together with the name of the field that caused the error.
type DecodeError struct {
name string
err error
}
func newDecodeError(name string, err error) *DecodeError {
return &DecodeError{
name: name,
err: err,
}
}
func (e *DecodeError) Name() string {
return e.name
}
func (e *DecodeError) Unwrap() error {
return e.err
}
func (e *DecodeError) Error() string {
return fmt.Sprintf("'%s' %s", e.name, e.err)
}
func (*DecodeError) mapstructure() {}
// ParseError is an error type that indicates a value could not be parsed
// into the expected type.
type ParseError struct {
Expected reflect.Value
Value any
Err error
}
func (e *ParseError) Error() string {
return fmt.Sprintf("cannot parse value as '%s': %s", e.Expected.Type(), e.Err)
}
func (*ParseError) mapstructure() {}
// UnconvertibleTypeError is an error type that indicates a value could not be
// converted to the expected type.
type UnconvertibleTypeError struct {
Expected reflect.Value
Value any
}
func (e *UnconvertibleTypeError) Error() string {
return fmt.Sprintf(
"expected type '%s', got unconvertible type '%s'",
e.Expected.Type(),
reflect.TypeOf(e.Value),
)
}
func (*UnconvertibleTypeError) mapstructure() {}
func wrapStrconvNumError(err error) error {
if err == nil {
return nil
}
if err, ok := err.(*strconv.NumError); ok {
return &strconvNumError{Err: err}
}
return err
}
type strconvNumError struct {
Err *strconv.NumError
}
func (e *strconvNumError) Error() string {
return "strconv." + e.Err.Func + ": " + e.Err.Err.Error()
}
func (e *strconvNumError) Unwrap() error { return e.Err }
func wrapUrlError(err error) error {
if err == nil {
return nil
}
if err, ok := err.(*url.Error); ok {
return &urlError{Err: err}
}
return err
}
type urlError struct {
Err *url.Error
}
func (e *urlError) Error() string {
return fmt.Sprintf("%s", e.Err.Err)
}
func (e *urlError) Unwrap() error { return e.Err }
func wrapNetParseError(err error) error {
if err == nil {
return nil
}
if err, ok := err.(*net.ParseError); ok {
return &netParseError{Err: err}
}
return err
}
type netParseError struct {
Err *net.ParseError
}
func (e *netParseError) Error() string {
return "invalid " + e.Err.Type
}
func (e *netParseError) Unwrap() error { return e.Err }
func wrapTimeParseError(err error) error {
if err == nil {
return nil
}
if err, ok := err.(*time.ParseError); ok {
return &timeParseError{Err: err}
}
return err
}
type timeParseError struct {
Err *time.ParseError
}
func (e *timeParseError) Error() string {
if e.Err.Message == "" {
return fmt.Sprintf("parsing time as %q: cannot parse as %q", e.Err.Layout, e.Err.LayoutElem)
}
return "parsing time " + e.Err.Message
}
func (e *timeParseError) Unwrap() error { return e.Err }
func wrapNetIPParseAddrError(err error) error {
if err == nil {
return nil
}
if errMsg := err.Error(); strings.HasPrefix(errMsg, "ParseAddr") {
errPieces := strings.Split(errMsg, ": ")
return fmt.Errorf("ParseAddr: %s", errPieces[len(errPieces)-1])
}
return err
}
func wrapNetIPParseAddrPortError(err error) error {
if err == nil {
return nil
}
errMsg := err.Error()
if strings.HasPrefix(errMsg, "invalid port ") {
return errors.New("invalid port")
} else if strings.HasPrefix(errMsg, "invalid ip:port ") {
return errors.New("invalid ip:port")
}
return err
}
func wrapNetIPParsePrefixError(err error) error {
if err == nil {
return nil
}
if errMsg := err.Error(); strings.HasPrefix(errMsg, "netip.ParsePrefix") {
errPieces := strings.Split(errMsg, ": ")
return fmt.Errorf("netip.ParsePrefix: %s", errPieces[len(errPieces)-1])
}
return err
}
func wrapTimeParseDurationError(err error) error {
if err == nil {
return nil
}
errMsg := err.Error()
if strings.HasPrefix(errMsg, "time: unknown unit ") {
return errors.New("time: unknown unit")
} else if strings.HasPrefix(errMsg, "time: ") {
idx := strings.LastIndex(errMsg, " ")
return errors.New(errMsg[:idx])
}
return err
}
func wrapTimeParseLocationError(err error) error {
if err == nil {
return nil
}
errMsg := err.Error()
if strings.Contains(errMsg, "unknown time zone") || strings.HasPrefix(errMsg, "time: unknown format") {
return fmt.Errorf("invalid time zone format: %w", err)
}
return err
}

294
vendor/github.com/go-viper/mapstructure/v2/flake.lock generated vendored Normal file
View File

@@ -0,0 +1,294 @@
{
"nodes": {
"cachix": {
"inputs": {
"devenv": [
"devenv"
],
"flake-compat": [
"devenv"
],
"git-hooks": [
"devenv"
],
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1742042642,
"narHash": "sha256-D0gP8srrX0qj+wNYNPdtVJsQuFzIng3q43thnHXQ/es=",
"owner": "cachix",
"repo": "cachix",
"rev": "a624d3eaf4b1d225f918de8543ed739f2f574203",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat",
"git-hooks": "git-hooks",
"nix": "nix",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1744876578,
"narHash": "sha256-8MTBj2REB8t29sIBLpxbR0+AEGJ7f+RkzZPAGsFd40c=",
"owner": "cachix",
"repo": "devenv",
"rev": "7ff7c351bba20d0615be25ecdcbcf79b57b85fe1",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"devenv"
],
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1742649964,
"narHash": "sha256-DwOTp7nvfi8mRfuL1escHDXabVXFGT1VlPD1JHrtrco=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "dcf5072734cb576d2b0c59b2ac44f5050b5eac82",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"nix": {
"inputs": {
"flake-compat": [
"devenv"
],
"flake-parts": "flake-parts",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_2",
"nixpkgs-23-11": [
"devenv"
],
"nixpkgs-regression": [
"devenv"
],
"pre-commit-hooks": [
"devenv"
]
},
"locked": {
"lastModified": 1741798497,
"narHash": "sha256-E3j+3MoY8Y96mG1dUIiLFm2tZmNbRvSiyN7CrSKuAVg=",
"owner": "domenkozar",
"repo": "nix",
"rev": "f3f44b2baaf6c4c6e179de8cbb1cc6db031083cd",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1743296961,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1733477122,
"narHash": "sha256-qamMCz5mNpQmgBwc8SB5tVMlD5sbwVIToVZtSxMph9s=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1744536153,
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs_4"
}
}
},
"root": "root",
"version": 7
}

46
vendor/github.com/go-viper/mapstructure/v2/flake.nix generated vendored Normal file
View File

@@ -0,0 +1,46 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
devenv.url = "github:cachix/devenv";
};
outputs =
inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
imports = [
inputs.devenv.flakeModule
];
systems = [
"x86_64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
perSystem =
{ pkgs, ... }:
rec {
devenv.shells = {
default = {
languages = {
go.enable = true;
};
pre-commit.hooks = {
nixpkgs-fmt.enable = true;
};
packages = with pkgs; [
golangci-lint
];
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767
containers = pkgs.lib.mkForce { };
};
ci = devenv.shells.default;
};
};
};
}

View File

@@ -0,0 +1,11 @@
package errors
import "errors"
func New(text string) error {
return errors.New(text)
}
func As(err error, target interface{}) bool {
return errors.As(err, target)
}

View File

@@ -0,0 +1,9 @@
//go:build go1.20
package errors
import "errors"
func Join(errs ...error) error {
return errors.Join(errs...)
}

View File

@@ -0,0 +1,61 @@
//go:build !go1.20
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package errors
// Join returns an error that wraps the given errors.
// Any nil error values are discarded.
// Join returns nil if every value in errs is nil.
// The error formats as the concatenation of the strings obtained
// by calling the Error method of each element of errs, with a newline
// between each string.
//
// A non-nil error returned by Join implements the Unwrap() []error method.
func Join(errs ...error) error {
n := 0
for _, err := range errs {
if err != nil {
n++
}
}
if n == 0 {
return nil
}
e := &joinError{
errs: make([]error, 0, n),
}
for _, err := range errs {
if err != nil {
e.errs = append(e.errs, err)
}
}
return e
}
type joinError struct {
errs []error
}
func (e *joinError) Error() string {
// Since Join returns nil if every value in errs is nil,
// e.errs cannot be empty.
if len(e.errs) == 1 {
return e.errs[0].Error()
}
b := []byte(e.errs[0].Error())
for _, err := range e.errs[1:] {
b = append(b, '\n')
b = append(b, err.Error()...)
}
// At this point, b has at least one byte '\n'.
// return unsafe.String(&b[0], len(b))
return string(b)
}
func (e *joinError) Unwrap() []error {
return e.errs
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
//go:build !go1.20
package mapstructure
import "reflect"
func isComparable(v reflect.Value) bool {
k := v.Kind()
switch k {
case reflect.Invalid:
return false
case reflect.Array:
switch v.Type().Elem().Kind() {
case reflect.Interface, reflect.Array, reflect.Struct:
for i := 0; i < v.Type().Len(); i++ {
// if !v.Index(i).Comparable() {
if !isComparable(v.Index(i)) {
return false
}
}
return true
}
return v.Type().Comparable()
case reflect.Interface:
// return v.Elem().Comparable()
return isComparable(v.Elem())
case reflect.Struct:
for i := 0; i < v.NumField(); i++ {
return false
// if !v.Field(i).Comparable() {
if !isComparable(v.Field(i)) {
return false
}
}
return true
default:
return v.Type().Comparable()
}
}

View File

@@ -0,0 +1,10 @@
//go:build go1.20
package mapstructure
import "reflect"
// TODO: remove once we drop support for Go <1.20
func isComparable(v reflect.Value) bool {
return v.Comparable()
}

View File

@@ -1,20 +0,0 @@
*.log
*.swp
.idea
*.patch
### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.DS_Store
app
demo

View File

View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2016 inhere
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,580 +0,0 @@
# CLI Color
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/color?style=flat-square)
[![Actions Status](https://github.com/gookit/color/workflows/action-tests/badge.svg)](https://github.com/gookit/color/actions)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/7fef8d74c1d64afc99ce0f2c6d3f8af1)](https://www.codacy.com/gh/gookit/color/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=gookit/color&amp;utm_campaign=Badge_Grade)
[![GoDoc](https://godoc.org/github.com/gookit/color?status.svg)](https://pkg.go.dev/github.com/gookit/color?tab=overview)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/color)](https://github.com/gookit/color)
[![Build Status](https://travis-ci.org/gookit/color.svg?branch=master)](https://travis-ci.org/gookit/color)
[![Coverage Status](https://coveralls.io/repos/github/gookit/color/badge.svg?branch=master)](https://coveralls.io/github/gookit/color?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/color)](https://goreportcard.com/report/github.com/gookit/color)
A command-line color library with 16/256/True color support, universal API methods and Windows support.
> **[中文说明](README.zh-CN.md)**
Basic color preview:
![basic-color](_examples/images/basic-color2.png)
Now, 256 colors and RGB colors have also been supported to work in Windows CMD and PowerShell:
![color-on-cmd-pwsh](_examples/images/color-on-cmd-pwsh.jpg)
## Features
- Simple to use, zero dependencies
- Supports rich color output: 16-color (4-bit), 256-color (8-bit), true color (24-bit, RGB)
- 16-color output is the most commonly used and most widely supported, working on any Windows version
- Since `v1.2.4` **the 256-color (8-bit), true color (24-bit) support windows CMD and PowerShell**
- See [this gist](https://gist.github.com/XVilka/8346728) for information on true color support
- Support converts `HEX` `HSL` value to RGB color
- Generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf`
- Supports HTML tag-style color rendering, such as `<green>message</> <fg=red;bg=blue>text</>`.
- In addition to using built-in tags, it also supports custom color attributes
- Custom color attributes support the use of 16 color names, 256 color values, rgb color values and hex color values
- Support working on Windows `cmd` and `powerShell` terminal
- Basic colors: `Bold`, `Black`, `White`, `Gray`, `Red`, `Green`, `Yellow`, `Blue`, `Magenta`, `Cyan`
- Additional styles: `Info`, `Note`, `Light`, `Error`, `Danger`, `Notice`, `Success`, `Comment`, `Primary`, `Warning`, `Question`, `Secondary`
- Support by set `NO_COLOR` for disable color or use `FORCE_COLOR` for force open color render.
- Support Rgb, 256, 16 color conversion
## GoDoc
- [godoc for gopkg](https://pkg.go.dev/gopkg.in/gookit/color.v1)
- [godoc for github](https://pkg.go.dev/github.com/gookit/color)
## Install
```bash
go get github.com/gookit/color
```
## Quick start
```go
package main
import (
"fmt"
"github.com/gookit/color"
)
func main() {
// quick use package func
color.Redp("Simple to use color")
color.Redln("Simple to use color")
color.Greenp("Simple to use color\n")
color.Cyanln("Simple to use color")
color.Yellowln("Simple to use color")
// quick use like fmt.Print*
color.Red.Println("Simple to use color")
color.Green.Print("Simple to use color\n")
color.Cyan.Printf("Simple to use %s\n", "color")
color.Yellow.Printf("Simple to use %s\n", "color")
// use like func
red := color.FgRed.Render
green := color.FgGreen.Render
fmt.Printf("%s line %s library\n", red("Command"), green("color"))
// custom color
color.New(color.FgWhite, color.BgBlack).Println("custom color style")
// can also:
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
// internal theme/style:
color.Info.Tips("message")
color.Info.Prompt("message")
color.Info.Println("message")
color.Warn.Println("message")
color.Error.Println("message")
// use style tag
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>\n")
// Custom label attr: Supports the use of 16 color names, 256 color values, rgb color values and hex color values
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
// apply a style tag
color.Tag("info").Println("info style text")
// prompt message
color.Info.Prompt("prompt style message")
color.Warn.Prompt("prompt style message")
// tips message
color.Info.Tips("tips style message")
color.Warn.Tips("tips style message")
}
```
Run demo: `go run ./_examples/demo.go`
![colored-out](_examples/images/color-demo.jpg)
## Basic/16 color
Supported on any Windows version. Provide generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf`
```go
color.Bold.Println("bold message")
color.Cyan.Println("yellow message")
color.Yellow.Println("yellow message")
color.Magenta.Println("yellow message")
// Only use foreground color
color.FgCyan.Printf("Simple to use %s\n", "color")
// Only use background color
color.BgRed.Printf("Simple to use %s\n", "color")
```
Run demo: `go run ./_examples/color_16.go`
![basic-color](_examples/images/basic-color.png)
### Custom build color
```go
// Full custom: foreground, background, option
myStyle := color.New(color.FgWhite, color.BgBlack, color.OpBold)
myStyle.Println("custom color style")
// can also:
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
```
custom set console settings:
```go
// set console color
color.Set(color.FgCyan)
// print message
fmt.Print("message")
// reset console settings
color.Reset()
```
### Additional styles
provide generic API methods: `Print`, `Printf`, `Println`, `Sprint`, `Sprintf`
print message use defined style:
```go
color.Info.Println("Info message")
color.Notice.Println("Notice message")
color.Error.Println("Error message")
// ...
```
Run demo: `go run ./_examples/theme_basic.go`
![theme-basic](_examples/images/theme-basic.png)
**Tips style**
```go
color.Info.Tips("Info tips message")
color.Notice.Tips("Notice tips message")
color.Error.Tips("Error tips message")
color.Secondary.Tips("Secondary tips message")
```
Run demo: `go run ./_examples/theme_tips.go`
![theme-tips](_examples/images/theme-tips.png)
**Prompt Style**
```go
color.Info.Prompt("Info prompt message")
color.Notice.Prompt("Notice prompt message")
color.Error.Prompt("Error prompt message")
// ...
```
Run demo: `go run ./_examples/theme_prompt.go`
![theme-prompt](_examples/images/theme-prompt.png)
**Block Style**
```go
color.Danger.Block("Danger block message")
color.Warn.Block("Warn block message")
// ...
```
Run demo: `go run ./_examples/theme_block.go`
![theme-block](_examples/images/theme-block.png)
## 256-color usage
> 256 colors support Windows CMD, PowerShell environment after `v1.2.4`
### Set the foreground or background color
- `color.C256(val uint8, isBg ...bool) Color256`
```go
c := color.C256(132) // fg color
c.Println("message")
c.Printf("format %s", "message")
c := color.C256(132, true) // bg color
c.Println("message")
c.Printf("format %s", "message")
```
### 256-color style
Can be used to set foreground and background colors at the same time.
- `S256(fgAndBg ...uint8) *Style256`
```go
s := color.S256(32, 203)
s.Println("message")
s.Printf("format %s", "message")
```
with options:
```go
s := color.S256(32, 203)
s.SetOpts(color.Opts{color.OpBold})
s.Println("style with options")
s.Printf("style with %s\n", "options")
```
Run demo: `go run ./_examples/color_256.go`
![color-tags](_examples/images/color-256.png)
## RGB/True color
> RGB colors support Windows `CMD`, `PowerShell` environment after `v1.2.4`
**Preview:**
> Run demo: `Run demo: go run ./_examples/color_rgb.go`
![color-rgb](_examples/images/color-rgb.png)
example:
```go
color.RGB(30, 144, 255).Println("message. use RGB number")
color.HEX("#1976D2").Println("blue-darken")
color.HEX("#D50000", true).Println("red-accent. use HEX style")
color.RGBStyleFromString("213,0,0").Println("red-accent. use RGB number")
color.HEXStyle("eee", "D50000").Println("deep-purple color")
```
### Set the foreground or background color
- `color.RGB(r, g, b uint8, isBg ...bool) RGBColor`
```go
c := color.RGB(30,144,255) // fg color
c.Println("message")
c.Printf("format %s", "message")
c := color.RGB(30,144,255, true) // bg color
c.Println("message")
c.Printf("format %s", "message")
```
Create a style from an hexadecimal color string:
- `color.HEX(hex string, isBg ...bool) RGBColor`
```go
c := color.HEX("ccc") // can also: "cccccc" "#cccccc"
c.Println("message")
c.Printf("format %s", "message")
c = color.HEX("aabbcc", true) // as bg color
c.Println("message")
c.Printf("format %s", "message")
```
### RGB color style
Can be used to set the foreground and background colors at the same time.
- `color.NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle`
```go
s := color.NewRGBStyle(RGB(20, 144, 234), RGB(234, 78, 23))
s.Println("message")
s.Printf("format %s", "message")
```
Create a style from an hexadecimal color string:
- `color.HEXStyle(fg string, bg ...string) *RGBStyle`
```go
s := color.HEXStyle("11aa23", "eee")
s.Println("message")
s.Printf("format %s", "message")
```
with options:
```go
s := color.HEXStyle("11aa23", "eee")
s.SetOpts(color.Opts{color.OpBold})
s.Println("style with options")
s.Printf("style with %s\n", "options")
```
## HTML-like tag usage
`Print,Printf,Println` functions support auto parse and render color tags.
```go
text := `
<mga1>gookit/color:</>
A <green>command-line</>
<cyan>color library</> with <fg=167;bg=232>256-color</>
and <fg=11aa23;op=bold>True-color</> support,
<fg=mga;op=i>universal API</> methods
and <cyan>Windows</> support.
`
color.Print(text)
```
Preview, code please see [_examples/demo_tag.go](_examples/demo_tag.go):
![demo_tag](_examples/images/demo_tag.png)
**Tag formats:**
- Use built in tags: `<TAG_NAME>CONTENT</>` e.g: `<info>message</>`
- Custom tag attributes: `<fg=VALUE;bg=VALUE;op=VALUES>CONTENT</>` e.g: `<fg=167;bg=232>wel</>`
> **Supported** on Windows `cmd.exe` `PowerShell`.
Examples:
```go
// use style tag
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>")
color.Println("<suc>hello</>")
color.Println("<error>hello</>")
color.Println("<warning>hello</>")
// custom color attributes
color.Print("<fg=yellow;bg=black;op=underscore;>hello, welcome</>\n")
// Custom label attr: Supports the use of 16 color names, 256 color values, rgb color values and hex color values
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
```
### Tag attributes
tag attributes format:
```text
attr format:
// 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"
256 color:
"fg=167"
"fg=167;bg=23"
"fg=167;bg=23;op=bold"
True color:
// hex
"fg=fc1cac"
"fg=fc1cac;bg=c2c3c4"
// r,g,b
"fg=23,45,214"
"fg=23,45,214;bg=109,99,88"
```
> tag attributes parse please see `func ParseCodeFromAttr()`
### Built-in tags
Built-in tags please see var `colorTags` in [color_tag.go](color_tag.go)
```go
// use style tag
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>")
color.Println("<suc>hello</>")
color.Println("<error>hello</>")
```
Run demo: `go run ./_examples/color_tag.go`
![color-tags](_examples/images/color-tags.png)
**Use `color.Tag` build message**:
```go
// set a style tag
color.Tag("info").Print("info style text")
color.Tag("info").Printf("%s style text", "info")
color.Tag("info").Println("info style text")
```
## Color convert
Supports conversion between Rgb, 256, 16 colors, `Rgb <=> 256 <=> 16`
```go
basic := color.Red
basic.Println("basic color")
c256 := color.Red.C256()
c256.Println("256 color")
c256.C16().Println("basic color")
rgb := color.Red.RGB()
rgb.Println("rgb color")
rgb.C256().Println("256 color")
```
### Convert utils
`color` has many built-in color conversion utility functions.
```go
func Basic2hex(val uint8) string
func Bg2Fg(val uint8) uint8
func Fg2Bg(val uint8) uint8
func C256ToRgb(val uint8) (rgb []uint8)
func C256ToRgbV1(val uint8) (rgb []uint8)
func Hex2basic(hex string, asBg ...bool) uint8
func Hex2rgb(hex string) []int
func HexToRGB(hex string) []int
func HexToRgb(hex string) (rgb []int)
func HslIntToRgb(h, s, l int) (rgb []uint8)
func HslToRgb(h, s, l float64) (rgb []uint8)
func HsvToRgb(h, s, v int) (rgb []uint8)
func Rgb2ansi(r, g, b uint8, isBg bool) uint8
func Rgb2basic(r, g, b uint8, isBg bool) uint8
func Rgb2hex(rgb []int) string
func Rgb2short(r, g, b uint8) uint8
func RgbTo256(r, g, b uint8) uint8
func RgbTo256Table() map[string]uint8
func RgbToAnsi(r, g, b uint8, isBg bool) uint8
func RgbToHex(rgb []int) string
func RgbToHsl(r, g, b uint8) []float64
func RgbToHslInt(r, g, b uint8) []int
```
**Convert to `RGBColor`**:
- `func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor`
- `func RGBFromString(rgb string, isBg ...bool) RGBColor`
- `func HEX(hex string, isBg ...bool) RGBColor`
- `func HSL(h, s, l float64, isBg ...bool) RGBColor`
- `func HSLInt(h, s, l int, isBg ...bool) RGBColor`
## Util functions
There are some useful functions reference
- `Disable()` disable color render
- `SetOutput(io.Writer)` custom set the colored text output writer
- `ForceOpenColor()` force open color render
- `Colors2code(colors ...Color) string` Convert colors to code. return like "32;45;3"
- `ClearCode(str string) string` Use for clear color codes
- `ClearTag(s string) string` clear all color html-tag for a string
- `IsConsole(w io.Writer)` Determine whether w is one of stderr, stdout, stdin
> More useful func please see https://pkg.go.dev/github.com/gookit/color
### Detect color level
`color` automatically checks the color levels supported by the current environment.
```go
// Level is the color level supported by a terminal.
type Level = terminfo.ColorLevel
// terminal color available level alias of the terminfo.ColorLevel*
const (
LevelNo = terminfo.ColorLevelNone // not support color.
Level16 = terminfo.ColorLevelBasic // basic - 3/4 bit color supported
Level256 = terminfo.ColorLevelHundreds // hundreds - 8-bit color supported
LevelRgb = terminfo.ColorLevelMillions // millions - (24 bit)true color supported
)
```
- `func SupportColor() bool` Whether the current environment supports color output
- `func Support256Color() bool` Whether the current environment supports 256-color output
- `func SupportTrueColor() bool` Whether the current environment supports (RGB)True-color output
- `func TermColorLevel() Level` Get the currently supported color level
## Projects using color
Check out these projects, which use https://github.com/gookit/color :
- https://github.com/Delta456/box-cli-maker Make Highly Customized Boxes for your CLI
- https://github.com/flipped-aurora/gin-vue-admin 基于gin+vue搭建的后台系统框架
- https://github.com/JanDeDobbeleer/oh-my-posh A prompt theme engine for any shell.
- https://github.com/jesseduffield/lazygit Simple terminal UI for git commands
- https://github.com/olivia-ai/olivia 💁Your new best friend powered by an artificial neural network
- https://github.com/pterm/pterm PTerm is a modern Go module to beautify console output. Featuring charts, progressbars, tables, trees, etc.
- https://github.com/securego/gosec Golang security checker
- https://github.com/TNK-Studio/lazykube ⎈ The lazier way to manage kubernetes.
- [+ See More](https://pkg.go.dev/github.com/gookit/color?tab=importedby)
## Gookit packages
- [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files
- [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP
- [gookit/gcli](https://github.com/gookit/gcli) build CLI application, tool library, running CLI commands
- [gookit/slog](https://github.com/gookit/slog) Concise and extensible go log library
- [gookit/event](https://github.com/gookit/event) Lightweight event manager and dispatcher implements by Go
- [gookit/cache](https://github.com/gookit/cache) Generic cache use and cache manager for golang. support File, Memory, Redis, Memcached.
- [gookit/config](https://github.com/gookit/config) Go config management. support JSON, YAML, TOML, INI, HCL, ENV and Flags
- [gookit/color](https://github.com/gookit/color) A command-line color library with true color support, universal API methods and Windows support
- [gookit/filter](https://github.com/gookit/filter) Provide filtering, sanitizing, and conversion of golang data
- [gookit/validate](https://github.com/gookit/validate) Use for data validation and filtering. support Map, Struct, Form data
- [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
- More, please see https://github.com/gookit
## See also
- [inhere/console](https://github.com/inhere/php-console)
- [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)
## License
[MIT](/LICENSE)

View File

@@ -1,591 +0,0 @@
# CLI Color
![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/gookit/color?style=flat-square)
[![Actions Status](https://github.com/gookit/color/workflows/action-tests/badge.svg)](https://github.com/gookit/color/actions)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/7fef8d74c1d64afc99ce0f2c6d3f8af1)](https://www.codacy.com/gh/gookit/color/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=gookit/color&amp;utm_campaign=Badge_Grade)
[![GoDoc](https://godoc.org/github.com/gookit/color?status.svg)](https://pkg.go.dev/github.com/gookit/color?tab=overview)
[![GitHub tag (latest SemVer)](https://img.shields.io/github/tag/gookit/color)](https://github.com/gookit/color)
[![Build Status](https://travis-ci.org/gookit/color.svg?branch=master)](https://travis-ci.org/gookit/color)
[![Coverage Status](https://coveralls.io/repos/github/gookit/color/badge.svg?branch=master)](https://coveralls.io/github/gookit/color?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/gookit/color)](https://goreportcard.com/report/github.com/gookit/color)
Golang下的命令行色彩使用库, 拥有丰富的色彩(16/256/True)渲染输出通用的API方法兼容Windows系统
> **[EN README](README.md)**
基本颜色预览:
![basic-color](_examples/images/basic-color2.png)
现在256色和RGB色彩也已经支持windows CMD和PowerShell中工作
![color-on-cmd-pwsh](_examples/images/color-on-cmd-pwsh.jpg)
## 功能特色
- 使用简单方便
- 支持丰富的颜色输出, 16色(4bit)256色(8bit)RGB色彩(24bit, RGB)
- 16色(4bit)是最常用和支持最广的支持Windows `cmd.exe`
-`v1.2.4`**256色(8bit)RGB色彩(24bit)均支持Windows CMD和PowerShell终端**
- 请查看 [this gist](https://gist.github.com/XVilka/8346728) 了解支持RGB色彩的终端
- 支持转换 `HEX` `HSL` 等为RGB色彩
- 提供通用的API方法`Print` `Printf` `Println` `Sprint` `Sprintf`
- 同时支持html标签式的颜色渲染除了使用内置标签同时支持自定义颜色属性
- 例如: `this an <green>message</> <fg=red;bg=blue>text</>` 标签内部文本将会渲染对应色彩
- 自定义颜色属性: 支持使用16色彩名称256色彩值rgb色彩值以及hex色彩值
- 基础色彩: `Bold` `Black` `White` `Gray` `Red` `Green` `Yellow` `Blue` `Magenta` `Cyan`
- 扩展风格: `Info` `Note` `Light` `Error` `Danger` `Notice` `Success` `Comment` `Primary` `Warning` `Question` `Secondary`
- 支持通过设置环境变量 `NO_COLOR` 来禁用色彩,或者使用 `FORCE_COLOR` 来强制使用色彩渲染.
- 支持 Rgb, 256, 16 色彩之间的互相转换
- 支持Linux、Mac同时兼容Windows系统环境
## GoDoc
- [godoc for gopkg](https://pkg.go.dev/gopkg.in/gookit/color.v1)
- [godoc for github](https://pkg.go.dev/github.com/gookit/color)
## 安装
```bash
go get github.com/gookit/color
```
## 快速开始
如下,引入当前包就可以快速的使用
```go
package main
import (
"fmt"
"github.com/gookit/color"
)
func main() {
// 简单快速的使用,跟 fmt.Print* 类似
color.Redp("Simple to use color")
color.Redln("Simple to use color")
color.Greenp("Simple to use color\n")
color.Cyanln("Simple to use color")
color.Yellowln("Simple to use color")
// 简单快速的使用,跟 fmt.Print* 类似
color.Red.Println("Simple to use color")
color.Green.Print("Simple to use color\n")
color.Cyan.Printf("Simple to use %s\n", "color")
color.Yellow.Printf("Simple to use %s\n", "color")
// use like func
red := color.FgRed.Render
green := color.FgGreen.Render
fmt.Printf("%s line %s library\n", red("Command"), green("color"))
// 自定义颜色
color.New(color.FgWhite, color.BgBlack).Println("custom color style")
// 也可以:
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
// internal style:
color.Info.Println("message")
color.Warn.Println("message")
color.Error.Println("message")
// 使用内置颜色标签
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>\n")
// 自定义标签: 支持使用16色彩名称256色彩值rgb色彩值以及hex色彩值
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
// apply a style tag
color.Tag("info").Println("info style text")
// prompt message
color.Info.Prompt("prompt style message")
color.Warn.Prompt("prompt style message")
// tips message
color.Info.Tips("tips style message")
color.Warn.Tips("tips style message")
}
```
> 运行 demo: `go run ./_examples/demo.go`
![colored-out](_examples/images/color-demo.jpg)
## 基础颜色(16-color)
提供通用的API方法`Print` `Printf` `Println` `Sprint` `Sprintf`
> 支持在windows `cmd.exe` `powerShell` 等终端使用
```go
color.Bold.Println("bold message")
color.Black.Println("bold message")
color.White.Println("bold message")
// ...
// Only use foreground color
color.FgCyan.Printf("Simple to use %s\n", "color")
// Only use background color
color.BgRed.Printf("Simple to use %s\n", "color")
```
> 运行demo: `go run ./_examples/color_16.go`
![basic-color](_examples/images/basic-color.png)
### 构建风格
```go
// 仅设置前景色
color.FgCyan.Printf("Simple to use %s\n", "color")
// 仅设置背景色
color.BgRed.Printf("Simple to use %s\n", "color")
// 完全自定义: 前景色 背景色 选项
style := color.New(color.FgWhite, color.BgBlack, color.OpBold)
style.Println("custom color style")
// 也可以:
color.Style{color.FgCyan, color.OpBold}.Println("custom color style")
```
直接设置控制台属性:
```go
// 设置console颜色
color.Set(color.FgCyan)
// 输出信息
fmt.Print("message")
// 重置console颜色
color.Reset()
```
> 当然color已经内置丰富的色彩风格支持
### 扩展风格方法
提供通用的API方法`Print` `Printf` `Println` `Sprint` `Sprintf`
> 支持在windows `cmd.exe` `powerShell` 等终端使用
基础使用:
```go
// print message
color.Info.Println("Info message")
color.Note.Println("Note message")
color.Notice.Println("Notice message")
// ...
```
Run demo: `go run ./_examples/theme_basic.go`
![theme-basic](_examples/images/theme-basic.png)
**简约提示风格**
```go
color.Info.Tips("Info tips message")
color.Notice.Tips("Notice tips message")
color.Error.Tips("Error tips message")
// ...
```
Run demo: `go run ./_examples/theme_tips.go`
![theme-tips](_examples/images/theme-tips.png)
**着重提示风格**
```go
color.Info.Prompt("Info prompt message")
color.Error.Prompt("Error prompt message")
color.Danger.Prompt("Danger prompt message")
```
Run demo: `go run ./_examples/theme_prompt.go`
![theme-prompt](_examples/images/theme-prompt.png)
**强调提示风格**
```go
color.Warn.Block("Warn block message")
color.Debug.Block("Debug block message")
color.Question.Block("Question block message")
```
Run demo: `go run ./_examples/theme_block.go`
![theme-block](_examples/images/theme-block.png)
## 256 色彩使用
> 256色彩在 `v1.2.4` 后支持Windows CMD,PowerShell 环境
### 使用前景或后景色
- `color.C256(val uint8, isBg ...bool) Color256`
```go
c := color.C256(132) // fg color
c.Println("message")
c.Printf("format %s", "message")
c := color.C256(132, true) // bg color
c.Println("message")
c.Printf("format %s", "message")
```
### 使用256 色彩风格
> 可同时设置前景和背景色
- `color.S256(fgAndBg ...uint8) *Style256`
```go
s := color.S256(32, 203)
s.Println("message")
s.Printf("format %s", "message")
```
可以同时添加选项设置:
```go
s := color.S256(32, 203)
s.SetOpts(color.Opts{color.OpBold})
s.Println("style with options")
s.Printf("style with %s\n", "options")
```
> 运行 demo: `go run ./_examples/color_256.go`
![color-tags](_examples/images/color-256.png)
## RGB/True色彩使用
> RGB色彩在 `v1.2.4` 后支持 Windows `CMD`, `PowerShell` 环境
**效果预览:**
> 运行 demo: `Run demo: go run ./_examples/color_rgb.go`
![color-rgb](_examples/images/color-rgb.png)
代码示例:
```go
color.RGB(30, 144, 255).Println("message. use RGB number")
color.HEX("#1976D2").Println("blue-darken")
color.HEX("#D50000", true).Println("red-accent. use HEX style")
color.RGBStyleFromString("213,0,0").Println("red-accent. use RGB number")
color.HEXStyle("eee", "D50000").Println("deep-purple color")
```
### 使用前景或后景色
- `color.RGB(r, g, b uint8, isBg ...bool) RGBColor`
```go
c := color.RGB(30,144,255) // fg color
c.Println("message")
c.Printf("format %s", "message")
c := color.RGB(30,144,255, true) // bg color
c.Println("message")
c.Printf("format %s", "message")
```
- `color.HEX(hex string, isBg ...bool) RGBColor` 从16进制颜色创建
```go
c := color.HEX("ccc") // 也可以写为: "cccccc" "#cccccc"
c.Println("message")
c.Printf("format %s", "message")
c = color.HEX("aabbcc", true) // as bg color
c.Println("message")
c.Printf("format %s", "message")
```
### 使用RGB风格
> TIP: 可同时设置前景和背景色
- `color.NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle`
```go
s := color.NewRGBStyle(RGB(20, 144, 234), RGB(234, 78, 23))
s.Println("message")
s.Printf("format %s", "message")
```
- `color.HEXStyle(fg string, bg ...string) *RGBStyle` 从16进制颜色创建
```go
s := color.HEXStyle("11aa23", "eee")
s.Println("message")
s.Printf("format %s", "message")
```
- 可以同时添加选项设置:
```go
s := color.HEXStyle("11aa23", "eee")
s.SetOpts(color.Opts{color.OpBold})
s.Println("style with options")
s.Printf("style with %s\n", "options")
```
## 使用颜色标签
`Print,Printf,Println` 等方法支持自动解析并渲染 HTML 风格的颜色标签
> **支持** 在windows `cmd.exe` `PowerShell` 使用
简单示例:
```go
text := `
<mga1>gookit/color:</>
A <green>command-line</>
<cyan>color library</> with <fg=167;bg=232>256-color</>
and <fg=11aa23;op=bold>True-color</> support,
<fg=mga;op=i>universal API</> methods
and <cyan>Windows</> support.
`
color.Print(text)
```
输出效果, 示例代码请看 [_examples/demo_tag.go](_examples/demo_tag.go):
![demo_tag](_examples/images/demo_tag.png)
**颜色标签格式:**
- 直接使用内置风格标签: `<TAG_NAME>CONTENT</>` e.g: `<info>message</>`
- 自定义标签属性: `<fg=VALUE;bg=VALUE;op=VALUES>CONTENT</>` e.g: `<fg=167;bg=232>wel</>`
使用内置的颜色标签,可以非常方便简单的构建自己需要的任何格式
> 同时支持自定义颜色属性: 支持使用16色彩名称256色彩值rgb色彩值以及hex色彩值
```go
// 使用内置的 color tag
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>")
color.Println("<suc>hello</>")
color.Println("<error>hello</>")
color.Println("<warning>hello</>")
// 自定义颜色属性
color.Print("<fg=yellow;bg=black;op=underscore;>hello, welcome</>\n")
// 自定义颜色属性: 支持使用16色彩名称256色彩值rgb色彩值以及hex色彩值
color.Println("<fg=11aa23>he</><bg=120,35,156>llo</>, <fg=167;bg=232>wel</><fg=red>come</>")
```
### 自定义标签属性
标签属性格式:
```text
attr format:
// 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"
256 color:
"fg=167"
"fg=167;bg=23"
"fg=167;bg=23;op=bold"
True color:
// hex
"fg=fc1cac"
"fg=fc1cac;bg=c2c3c4"
// r,g,b
"fg=23,45,214"
"fg=23,45,214;bg=109,99,88"
```
> tag attributes parse please see `func ParseCodeFromAttr()`
### 内置标签
内置标签请参见变量 `colorTags` 定义, 源文件 [color_tag.go](color_tag.go)
```go
// use style tag
color.Print("<suc>he</><comment>llo</>, <cyan>wel</><red>come</>")
color.Println("<suc>hello</>")
color.Println("<error>hello</>")
```
> 运行 demo: `go run ./_examples/color_tag.go`
![color-tags](_examples/images/color-tags.png)
**使用 `color.Tag` 包装标签**:
可以使用通用的输出API方法,给后面输出的文本信息加上给定的颜色风格标签
```go
// set a style tag
color.Tag("info").Print("info style text")
color.Tag("info").Printf("%s style text", "info")
color.Tag("info").Println("info style text")
```
## 颜色转换
支持 Rgb, 256, 16 色彩之间的互相转换 `Rgb <=> 256 <=> 16`
```go
basic := color.Red
basic.Println("basic color")
c256 := color.Red.C256()
c256.Println("256 color")
c256.C16().Println("basic color")
rgb := color.Red.RGB()
rgb.Println("rgb color")
rgb.C256().Println("256 color")
```
### 颜色转换方法
`color` 内置了许多颜色转换工具方法
```go
func Basic2hex(val uint8) string
func Bg2Fg(val uint8) uint8
func Fg2Bg(val uint8) uint8
func C256ToRgb(val uint8) (rgb []uint8)
func C256ToRgbV1(val uint8) (rgb []uint8)
func Hex2basic(hex string, asBg ...bool) uint8
func Hex2rgb(hex string) []int
func HexToRGB(hex string) []int
func HexToRgb(hex string) (rgb []int)
func HslIntToRgb(h, s, l int) (rgb []uint8)
func HslToRgb(h, s, l float64) (rgb []uint8)
func HsvToRgb(h, s, v int) (rgb []uint8)
func Rgb2ansi(r, g, b uint8, isBg bool) uint8
func Rgb2basic(r, g, b uint8, isBg bool) uint8
func Rgb2hex(rgb []int) string
func Rgb2short(r, g, b uint8) uint8
func RgbTo256(r, g, b uint8) uint8
func RgbTo256Table() map[string]uint8
func RgbToAnsi(r, g, b uint8, isBg bool) uint8
func RgbToHex(rgb []int) string
func RgbToHsl(r, g, b uint8) []float64
func RgbToHslInt(r, g, b uint8) []int
```
**转换为 `RGBColor`**:
- `func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor`
- `func RGBFromString(rgb string, isBg ...bool) RGBColor`
- `func HEX(hex string, isBg ...bool) RGBColor`
- `func HSL(h, s, l float64, isBg ...bool) RGBColor`
- `func HSLInt(h, s, l int, isBg ...bool) RGBColor`
## 工具方法参考
一些有用的工具方法参考
- `Disable()` 禁用颜色渲染输出
- `SetOutput(io.Writer)` 自定义设置渲染后的彩色文本输出位置
- `ForceOpenColor()` 强制开启颜色渲染
- `ClearCode(str string) string` Use for clear color codes
- `Colors2code(colors ...Color) string` Convert colors to code. return like "32;45;3"
- `ClearTag(s string) string` clear all color html-tag for a string
- `IsConsole(w io.Writer)` Determine whether w is one of stderr, stdout, stdin
- 更多请查看文档 https://pkg.go.dev/github.com/gookit/color
### 检测支持的颜色级别
`color` 会自动检查当前环境支持的颜色级别
```go
// Level is the color level supported by a terminal.
type Level = terminfo.ColorLevel
// terminal color available level alias of the terminfo.ColorLevel*
const (
LevelNo = terminfo.ColorLevelNone // not support color.
Level16 = terminfo.ColorLevelBasic // basic - 3/4 bit color supported
Level256 = terminfo.ColorLevelHundreds // hundreds - 8-bit color supported
LevelRgb = terminfo.ColorLevelMillions // millions - (24 bit)true color supported
)
```
- `func SupportColor() bool` 当前环境是否支持色彩输出
- `func Support256Color() bool` 当前环境是否支持256色彩输出
- `func SupportTrueColor() bool` 当前环境是否支持(RGB)True色彩输出
- `func TermColorLevel() Level` 获取当前支持的颜色级别
## 使用Color的项目
看看这些使用了 https://github.com/gookit/color 的项目:
- https://github.com/Delta456/box-cli-maker Make Highly Customized Boxes for your CLI
- https://github.com/flipped-aurora/gin-vue-admin 基于gin+vue搭建的后台系统框架
- https://github.com/JanDeDobbeleer/oh-my-posh A prompt theme engine for any shell.
- https://github.com/jesseduffield/lazygit Simple terminal UI for git commands
- https://github.com/olivia-ai/olivia 💁Your new best friend powered by an artificial neural network
- https://github.com/pterm/pterm PTerm is a modern Go module to beautify console output. Featuring charts, progressbars, tables, trees, etc.
- https://github.com/securego/gosec Golang security checker
- https://github.com/TNK-Studio/lazykube ⎈ The lazier way to manage kubernetes.
- [+ See More](https://pkg.go.dev/github.com/gookit/color?tab=importedby)
## Gookit 工具包
- [gookit/ini](https://github.com/gookit/ini) INI配置读取管理支持多文件加载数据覆盖合并, 解析ENV变量, 解析变量引用
- [gookit/rux](https://github.com/gookit/rux) Simple and fast request router for golang HTTP
- [gookit/gcli](https://github.com/gookit/gcli) Go的命令行应用工具库运行CLI命令支持命令行色彩用户交互进度显示数据格式化显示
- [gookit/slog](https://github.com/gookit/slog) 简洁易扩展的go日志库
- [gookit/event](https://github.com/gookit/event) Go实现的轻量级的事件管理、调度程序库, 支持设置监听器的优先级, 支持对一组事件进行监听
- [gookit/cache](https://github.com/gookit/cache) 通用的缓存使用包装库通过包装各种常用的驱动来提供统一的使用API
- [gookit/config](https://github.com/gookit/config) Go应用配置管理支持多种格式JSON, YAML, TOML, INI, HCL, ENV, Flags多文件加载远程文件加载数据合并
- [gookit/color](https://github.com/gookit/color) CLI 控制台颜色渲染工具库, 拥有简洁的使用API支持16色256色RGB色彩渲染输出
- [gookit/filter](https://github.com/gookit/filter) 提供对Golang数据的过滤净化转换
- [gookit/validate](https://github.com/gookit/validate) Go通用的数据验证与过滤库使用简单内置大部分常用验证、过滤器
- [gookit/goutil](https://github.com/gookit/goutil) Go 的一些工具函数,格式化,特殊处理,常用信息获取等
- 更多请查看 https://github.com/gookit
## 参考项目
- [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)
- [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI转义序列)
- [Standard ANSI color map](https://conemu.github.io/en/AnsiEscapeCodes.html#Standard_ANSI_color_map)
- [Terminal Colors](https://gist.github.com/XVilka/8346728)
## License
MIT

View File

@@ -1,6 +0,0 @@
//go:build !go1.18
// +build !go1.18
package color
type any = interface{}

View File

@@ -1,251 +0,0 @@
/*
Package color is command line color library.
Support rich color rendering output, universal API method, compatible with Windows system
Source code and other details for the project are available at GitHub:
https://github.com/gookit/color
More usage please see README and tests.
*/
package color
import (
"fmt"
"io"
"os"
"regexp"
"strings"
"github.com/xo/terminfo"
)
// color render templates
//
// ESC 操作的表示:
//
// "\033"(Octal 8进制) = "\x1b"(Hexadecimal 16进制) = 27 (10进制)
const (
// StartSet chars
StartSet = "\x1b["
// ResetSet close all properties.
ResetSet = "\x1b[0m"
// SettingTpl string.
SettingTpl = "\x1b[%sm"
// FullColorTpl for build color code
FullColorTpl = "\x1b[%sm%s\x1b[0m"
// CodeSuffix string for color code.
CodeSuffix = "[0m"
)
// CodeExpr regex to clear color codes eg "\033[1;36mText\x1b[0m"
const CodeExpr = `\033\[[\d;?]+m`
var (
// Enable switch color render and display
//
// NOTICE:
// if ENV: NO_COLOR is not empty, will disable color render.
Enable = os.Getenv("NO_COLOR") == ""
// RenderTag render HTML tag on call color.Xprint, color.PrintX
RenderTag = true
// debug mode for development.
//
// set env:
// COLOR_DEBUG_MODE=on
// or:
// COLOR_DEBUG_MODE=on go run ./_examples/envcheck.go
debugMode = os.Getenv("COLOR_DEBUG_MODE") == "on"
// inner errors record on detect color level
innerErrs []error
// output the default io.Writer message print
output io.Writer = os.Stdout
// mark current env, It's like in `cmd.exe`
// if not in windows, it's always False.
isLikeInCmd bool
// the color support level for current terminal
// needVTP - need enable VTP, only for Windows OS
colorLevel, needVTP = detectTermColorLevel()
// match color codes
codeRegex = regexp.MustCompile(CodeExpr)
// mark current env is support color.
// Always: isLikeInCmd != supportColor
// supportColor = IsSupportColor()
)
// TermColorLevel Get the currently supported color level
func TermColorLevel() Level {
return colorLevel
}
// SupportColor Whether the current environment supports color output
func SupportColor() bool {
return colorLevel > terminfo.ColorLevelNone
}
// Support16Color on the current ENV
// func Support16Color() bool {
// return colorLevel > terminfo.ColorLevelNone
// }
// Support256Color Whether the current environment supports 256-color output
func Support256Color() bool {
return colorLevel > terminfo.ColorLevelBasic
}
// SupportTrueColor Whether the current environment supports (RGB)True-color output
func SupportTrueColor() bool {
return colorLevel > terminfo.ColorLevelHundreds
}
/*************************************************************
* global settings
*************************************************************/
// Set console color attributes
func Set(colors ...Color) (int, error) {
code := Colors2code(colors...)
err := SetTerminal(code)
return 0, err
}
// Reset reset console color attributes
func Reset() (int, error) {
err := ResetTerminal()
return 0, err
}
// Disable disable color output
func Disable() bool {
oldVal := Enable
Enable = false
return oldVal
}
// NotRenderTag on call color.Xprint, color.PrintX
func NotRenderTag() {
RenderTag = false
}
// SetOutput set default colored text output
func SetOutput(w io.Writer) {
output = w
}
// ResetOutput reset output
func ResetOutput() {
output = os.Stdout
}
// ResetOptions reset all package option setting
func ResetOptions() {
RenderTag = true
Enable = true
output = os.Stdout
}
// ForceSetColorLevel force open color render
func ForceSetColorLevel(level terminfo.ColorLevel) terminfo.ColorLevel {
oldLevelVal := colorLevel
colorLevel = level
return oldLevelVal
}
// ForceColor force open color render
func ForceColor() terminfo.ColorLevel {
return ForceOpenColor()
}
// ForceOpenColor force open color render
func ForceOpenColor() terminfo.ColorLevel {
// TODO should set level to ?
return ForceSetColorLevel(terminfo.ColorLevelMillions)
}
// IsLikeInCmd check result
//
// Deprecated: please don't use
func IsLikeInCmd() bool {
return isLikeInCmd
}
// InnerErrs info
func InnerErrs() []error {
return innerErrs
}
/*************************************************************
* render color code
*************************************************************/
// RenderCode render message by color code.
//
// Usage:
//
// msg := RenderCode("3;32;45", "some", "message")
func RenderCode(code string, args ...any) string {
var message string
if ln := len(args); ln == 0 {
return ""
}
message = fmt.Sprint(args...)
if len(code) == 0 {
return message
}
// disabled OR not support color
if !Enable || !SupportColor() {
return ClearCode(message)
}
// return fmt.Sprintf(FullColorTpl, code, message)
return StartSet + code + "m" + message + ResetSet
}
// RenderWithSpaces Render code with spaces.
// If the number of args is > 1, a space will be added between the args
func RenderWithSpaces(code string, args ...any) string {
msg := formatArgsForPrintln(args)
if len(code) == 0 {
return msg
}
// disabled OR not support color
if !Enable || !SupportColor() {
return ClearCode(msg)
}
return StartSet + code + "m" + msg + ResetSet
}
// RenderString render a string with color code.
//
// Usage:
//
// msg := RenderString("3;32;45", "a message")
func RenderString(code string, str string) string {
if len(code) == 0 || str == "" {
return str
}
// disabled OR not support color
if !Enable || !SupportColor() {
return ClearCode(str)
}
// return fmt.Sprintf(FullColorTpl, code, str)
return StartSet + code + "m" + str + ResetSet
}
// ClearCode clear color codes.
//
// eg:
//
// "\033[36;1mText\x1b[0m" -> "Text"
func ClearCode(str string) string {
if !strings.Contains(str, CodeSuffix) {
return str
}
return codeRegex.ReplaceAllString(str, "")
}

View File

@@ -1,303 +0,0 @@
package color
import (
"fmt"
"strconv"
"strings"
)
/*
from wikipedia, 256 color:
ESC[ … 38;5;<n> … m选择前景色
ESC[ … 48;5;<n> … m选择背景色
0- 7标准颜色同 ESC[3037m
8- 15高强度颜色同 ESC[9097m
16-2316 × 6 × 6 立方216色: 16 + 36 × r + 6 × g + b (0 ≤ r, g, b ≤ 5)
232-255从黑到白的24阶灰度色
*/
// tpl for 8 bit 256 color(`2^8`)
//
// format:
//
// 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"
//
// links:
//
// 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"
Fg256Pfx = "38;5;"
Bg256Pfx = "48;5;"
)
/*************************************************************
* 8bit(256) Color: Bit8Color Color256
*************************************************************/
// Color256 256 color (8 bit), uint8 range at 0 - 255.
// Support 256 color on windows CMD, PowerShell
//
// 颜色值使用10进制和16进制都可 0x98 = 152
//
// The color consists of two uint8:
//
// 0: color value
// 1: color type; Fg=0, Bg=1, >1: unset value
//
// example:
//
// fg color: [152, 0]
// bg color: [152, 1]
//
// lint warn - Name starts with package name
type Color256 [2]uint8
type Bit8Color = Color256 // alias
var emptyC256 = Color256{1: 99}
// Bit8 create a color256
func Bit8(val uint8, isBg ...bool) Color256 {
return C256(val, isBg...)
}
// C256 create a color256
func C256(val uint8, isBg ...bool) Color256 {
bc := Color256{val}
// mark is bg color
if len(isBg) > 0 && isBg[0] {
bc[1] = AsBg
}
return bc
}
// Set terminal by 256 color code
func (c Color256) Set() error {
return SetTerminal(c.String())
}
// Reset terminal. alias of the ResetTerminal()
func (c Color256) Reset() error {
return ResetTerminal()
}
// Print print message
func (c Color256) Print(a ...any) {
doPrintV2(c.String(), fmt.Sprint(a...))
}
// Printf format and print message
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 ...any) {
doPrintlnV2(c.String(), a)
}
// Sprint returns rendered message
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 ...any) string {
return RenderString(c.String(), fmt.Sprintf(format, a...))
}
// C16 convert color-256 to 16 color.
func (c Color256) C16() Color {
return c.Basic()
}
// Basic convert color-256 to basic 16 color.
func (c Color256) Basic() Color {
return Color(c[0]) // TODO
}
// RGB convert color-256 to RGB color.
func (c Color256) RGB() RGBColor {
return RGBFromSlice(C256ToRgb(c[0]), c[1] == AsBg)
}
// RGBColor convert color-256 to RGB color.
func (c Color256) RGBColor() RGBColor {
return c.RGB()
}
// Value return color value
func (c Color256) Value() uint8 {
return c[0]
}
// Code convert to color code string. eg: "12"
func (c Color256) Code() string {
return strconv.Itoa(int(c[0]))
}
// FullCode convert to color code string with prefix. eg: "38;5;12"
func (c Color256) FullCode() string {
return c.String()
}
// String convert to color code string with prefix. eg: "38;5;12"
func (c Color256) String() string {
if c[1] == AsFg { // 0 is Fg
return Fg256Pfx + strconv.Itoa(int(c[0]))
}
if c[1] == AsBg { // 1 is Bg
return Bg256Pfx + strconv.Itoa(int(c[0]))
}
return "" // empty
}
// IsFg color
func (c Color256) IsFg() bool { return c[1] == AsFg }
// ToFg 256 color
func (c Color256) ToFg() Color256 {
c[1] = AsFg
return c
}
// IsBg color
func (c Color256) IsBg() bool { return c[1] == AsBg }
// ToBg 256 color
func (c Color256) ToBg() Color256 {
c[1] = AsBg
return c
}
// IsEmpty value
func (c Color256) IsEmpty() bool { return c[1] > 1 }
/*************************************************************
* 8bit(256) Style
*************************************************************/
// Style256 definition
//
// 前/背景色
// 都是由两位uint8组成, 第一位是色彩值;
// 第二位与 Bit8Color 不一样的是,在这里表示是否设置了值 0 未设置 !=0 已设置
type Style256 struct {
// Name of the style
Name string
// color options of the style
opts Opts
// fg and bg color
fg, bg Color256
}
// S256 create a color256 style
//
// Usage:
//
// 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)
if vl > 0 { // with fg
s.fg = Color256{fgAndBg[0], 1}
if vl > 1 { // and with bg
s.bg = Color256{fgAndBg[1], 1}
}
}
return s
}
// Set fg and bg color value, can also with color options
func (s *Style256) Set(fgVal, bgVal uint8, opts ...Color) *Style256 {
s.fg = Color256{fgVal, 1}
s.bg = Color256{bgVal, 1}
s.opts.Add(opts...)
return s
}
// SetBg set bg color value
func (s *Style256) SetBg(bgVal uint8) *Style256 {
s.bg = Color256{bgVal, 1}
return s
}
// SetFg set fg color value
func (s *Style256) SetFg(fgVal uint8) *Style256 {
s.fg = Color256{fgVal, 1}
return s
}
// SetOpts set options
func (s *Style256) SetOpts(opts Opts) *Style256 {
s.opts = opts
return s
}
// AddOpts add options
func (s *Style256) AddOpts(opts ...Color) *Style256 {
s.opts.Add(opts...)
return s
}
// Print message
func (s *Style256) Print(a ...any) {
doPrintV2(s.String(), fmt.Sprint(a...))
}
// Printf format and print message
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 ...any) {
doPrintlnV2(s.String(), a)
}
// Sprint returns rendered message
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 ...any) string {
return RenderString(s.Code(), fmt.Sprintf(format, a...))
}
// Code convert to color code string
func (s *Style256) Code() string {
return s.String()
}
// String convert to color code string
func (s *Style256) String() string {
var ss []string
if s.fg[1] > 0 {
ss = append(ss, Fg256Pfx+strconv.FormatInt(int64(s.fg[0]), 10))
}
if s.bg[1] > 0 {
ss = append(ss, Bg256Pfx+strconv.FormatInt(int64(s.bg[0]), 10))
}
if s.opts.IsValid() {
ss = append(ss, s.opts.String())
}
return strings.Join(ss, ";")
}

View File

@@ -1,443 +0,0 @@
package color
import (
"fmt"
"strconv"
"strings"
)
// 24 bit RGB color
// RGB:
//
// 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
//
// links:
//
// 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
const (
TplFgRGB = "38;2;%d;%d;%d"
TplBgRGB = "48;2;%d;%d;%d"
FgRGBPfx = "38;2;"
BgRGBPfx = "48;2;"
)
// mark color is fg or bg.
const (
AsFg uint8 = iota
AsBg
)
/*************************************************************
* RGB Color(Bit24Color, TrueColor)
*************************************************************/
// RGBColor definition.
// Support RGB color on Windows CMD, PowerShell
//
// The first to third digits represent the color value.
// 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}
type RGBColor [4]uint8
// create an empty RGBColor
var emptyRGBColor = RGBColor{3: 99}
// RGB color create.
//
// Usage:
//
// 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] {
rgb[3] = AsBg
}
return rgb
}
// Rgb alias of the RGB()
func Rgb(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
// Bit24 alias of the RGB()
func Bit24(r, g, b uint8, isBg ...bool) RGBColor { return RGB(r, g, b, isBg...) }
// RgbFromInt create instance from int r,g,b value
func RgbFromInt(r, g, b int, isBg ...bool) RGBColor {
return RGB(uint8(r), uint8(g), uint8(b), isBg...)
}
// RgbFromInts create instance from []int r,g,b value
func RgbFromInts(rgb []int, isBg ...bool) RGBColor {
return RGB(uint8(rgb[0]), uint8(rgb[1]), uint8(rgb[2]), isBg...)
}
// 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")
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...)
}
// mark is empty
return emptyRGBColor
}
// Hex alias of the HEX()
func Hex(hex string, isBg ...bool) RGBColor { return HEX(hex, isBg...) }
// RGBFromHEX quick RGBColor from hex string, alias of HEX()
func RGBFromHEX(hex string, isBg ...bool) RGBColor { return HEX(hex, isBg...) }
// HSL create RGB color from a hsl value.
// more see HslToRgb()
func HSL(h, s, l float64, isBg ...bool) RGBColor {
rgb := HslToRgb(h, s, l)
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
}
// Hsl alias of the HSL()
func Hsl(h, s, l float64, isBg ...bool) RGBColor { return HSL(h, s, l, isBg...) }
// HSLInt create RGB color from a hsl int value.
// more see HslIntToRgb()
func HSLInt(h, s, l int, isBg ...bool) RGBColor {
rgb := HslIntToRgb(h, s, l)
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
}
// HslInt alias of the HSLInt()
func HslInt(h, s, l int, isBg ...bool) RGBColor { return HSLInt(h, s, l, isBg...) }
// RGBFromSlice quick RGBColor from slice
func RGBFromSlice(rgb []uint8, isBg ...bool) RGBColor {
return RGB(rgb[0], rgb[1], rgb[2], isBg...)
}
// RGBFromString create RGB color from a string.
// 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")
func RGBFromString(rgb string, isBg ...bool) RGBColor {
// use color name in the {namedRgbMap}
if rgbVal, ok := namedRgbMap[rgb]; ok {
rgb = rgbVal
}
// use rgb string.
ss := stringToArr(rgb, ",")
if len(ss) != 3 {
return emptyRGBColor
}
var ar [3]uint8
for i, val := range ss {
iv, err := strconv.Atoi(val)
if err != nil || !isValidUint8(iv) {
return emptyRGBColor
}
ar[i] = uint8(iv)
}
return RGB(ar[0], ar[1], ar[2], isBg...)
}
// Set terminal by rgb/true color code
func (c RGBColor) Set() error {
return SetTerminal(c.String())
}
// Reset terminal. alias of the ResetTerminal()
func (c RGBColor) Reset() error {
return ResetTerminal()
}
// Print print message
func (c RGBColor) Print(a ...any) {
doPrintV2(c.String(), fmt.Sprint(a...))
}
// Printf format and print message
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 ...any) {
doPrintlnV2(c.String(), a)
}
// Sprint returns rendered message
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 ...any) string {
return RenderString(c.String(), fmt.Sprintf(format, a...))
}
// Values to RGB values
func (c RGBColor) Values() []int {
return []int{int(c[0]), int(c[1]), int(c[2])}
}
// Code to color code string without prefix. eg: "204;123;56"
func (c RGBColor) Code() string {
return fmt.Sprintf("%d;%d;%d", c[0], c[1], c[2])
}
// Hex color rgb to hex string. as in "ff0080".
func (c RGBColor) Hex() string {
return fmt.Sprintf("%02x%02x%02x", c[0], c[1], c[2])
}
// RgbString to color code string without prefix. eg: "204,123,56"
func (c RGBColor) RgbString() string {
return fmt.Sprintf("%d,%d,%d", c[0], c[1], c[2])
}
// FullCode to color code string with prefix
func (c RGBColor) FullCode() string {
return c.String()
}
// String to color code string with prefix. eg: "38;2;204;123;56"
func (c RGBColor) String() string {
if c[3] == AsFg {
return fmt.Sprintf(TplFgRGB, c[0], c[1], c[2])
}
if c[3] == AsBg {
return fmt.Sprintf(TplBgRGB, c[0], c[1], c[2])
}
// c[3] > 1 is empty
return ""
}
// ToBg convert to background color
func (c RGBColor) ToBg() RGBColor {
c[3] = AsBg
return c
}
// ToFg convert to foreground color
func (c RGBColor) ToFg() RGBColor {
c[3] = AsFg
return c
}
// IsEmpty value
func (c RGBColor) IsEmpty() bool {
return c[3] > AsBg
}
// IsValid value
// func (c RGBColor) IsValid() bool {
// return c[3] <= AsBg
// }
// C256 returns the closest approximate 256 (8 bit) color
func (c RGBColor) C256() Color256 {
return C256(RgbTo256(c[0], c[1], c[2]), c[3] == AsBg)
}
// Basic returns the closest approximate 16 (4 bit) color
func (c RGBColor) Basic() Color {
// return Color(RgbToAnsi(c[0], c[1], c[2], c[3] == AsBg))
return Color(Rgb2basic(c[0], c[1], c[2], c[3] == AsBg))
}
// Color returns the closest approximate 16 (4 bit) color
func (c RGBColor) Color() Color { return c.Basic() }
// C16 returns the closest approximate 16 (4 bit) color
func (c RGBColor) C16() Color { return c.Basic() }
/*************************************************************
* RGB Style
*************************************************************/
// RGBStyle supports set foreground and background color
//
// 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
type RGBStyle struct {
// Name of the style
Name string
// color options of the style
opts Opts
// fg and bg color
fg, bg RGBColor
}
// NewRGBStyle create a RGBStyle.
func NewRGBStyle(fg RGBColor, bg ...RGBColor) *RGBStyle {
s := &RGBStyle{}
if len(bg) > 0 {
s.SetBg(bg[0])
}
return s.SetFg(fg)
}
// HEXStyle create a RGBStyle from HEX color string.
//
// Usage:
//
// s := HEXStyle("aabbcc", "eee")
// s.Print("message")
func HEXStyle(fg string, bg ...string) *RGBStyle {
s := &RGBStyle{}
if len(bg) > 0 {
s.SetBg(HEX(bg[0]))
}
if len(fg) > 0 {
s.SetFg(HEX(fg))
}
return s
}
// RGBStyleFromString create a RGBStyle from color value string.
//
// Usage:
//
// s := RGBStyleFromString("170,187,204", "70,87,4")
// s.Print("message")
func RGBStyleFromString(fg string, bg ...string) *RGBStyle {
s := &RGBStyle{}
if len(bg) > 0 {
s.SetBg(RGBFromString(bg[0]))
}
return s.SetFg(RGBFromString(fg))
}
// Set fg and bg color, can also with color options
func (s *RGBStyle) Set(fg, bg RGBColor, opts ...Color) *RGBStyle {
return s.SetFg(fg).SetBg(bg).SetOpts(opts)
}
// SetFg set fg color
func (s *RGBStyle) SetFg(fg RGBColor) *RGBStyle {
fg[3] = 1 // add fixed value, mark is valid
s.fg = fg
return s
}
// SetBg set bg color
func (s *RGBStyle) SetBg(bg RGBColor) *RGBStyle {
bg[3] = 1 // add fixed value, mark is valid
s.bg = bg
return s
}
// SetOpts set color options
func (s *RGBStyle) SetOpts(opts Opts) *RGBStyle {
s.opts = opts
return s
}
// AddOpts add options
func (s *RGBStyle) AddOpts(opts ...Color) *RGBStyle {
s.opts.Add(opts...)
return s
}
// Print print message
func (s *RGBStyle) Print(a ...any) {
doPrintV2(s.String(), fmt.Sprint(a...))
}
// Printf format and print message
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 ...any) {
doPrintlnV2(s.String(), a)
}
// Sprint returns rendered message
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 ...any) string {
return RenderString(s.String(), fmt.Sprintf(format, a...))
}
// Code convert to color code string
func (s *RGBStyle) Code() string {
return s.String()
}
// FullCode convert to color code string
func (s *RGBStyle) FullCode() string {
return s.String()
}
// String convert to color code string
func (s *RGBStyle) String() string {
var ss []string
// last value ensure is enable.
if s.fg[3] == 1 {
ss = append(ss, fmt.Sprintf(TplFgRGB, s.fg[0], s.fg[1], s.fg[2]))
}
if s.bg[3] == 1 {
ss = append(ss, fmt.Sprintf(TplBgRGB, s.bg[0], s.bg[1], s.bg[2]))
}
if s.opts.IsValid() {
ss = append(ss, s.opts.String())
}
return strings.Join(ss, ";")
}
// IsEmpty style
func (s *RGBStyle) IsEmpty() bool {
return s.fg[3] != 1 && s.bg[3] != 1
}

View File

@@ -1,567 +0,0 @@
package color
import (
"fmt"
"regexp"
"strings"
)
// output colored text like use html tag. (not support windows cmd)
const (
// MatchExpr regex to match color tags
//
// Notice: golang 不支持反向引用. 即不支持使用 \1 引用第一个匹配 ([a-z=;]+)
// MatchExpr = `<([a-z=;]+)>(.*?)<\/\1>`
// 所以调整一下 统一使用 `</>` 来结束标签,例如 "<info>some text</>"
//
// allow custom attrs, eg: "<fg=white;bg=blue;op=bold>content</>"
// (?s:...) s - 让 "." 匹配换行
MatchExpr = `<([0-9a-zA-Z_=,;]+)>(?s:(.*?))<\/>`
// AttrExpr regex to match custom color attributes
// eg: "<fg=white;bg=blue;op=bold>content</>"
AttrExpr = `(fg|bg|op)[\s]*=[\s]*([0-9a-zA-Z,]+);?`
// StripExpr regex used for removing color tags
// StripExpr = `<[\/]?[a-zA-Z=;]+>`
// 随着上面的做一些调整
StripExpr = `<[\/]?[0-9a-zA-Z_=,;]*>`
)
var (
attrRegex = regexp.MustCompile(AttrExpr)
matchRegex = regexp.MustCompile(MatchExpr)
stripRegex = regexp.MustCompile(StripExpr)
)
/*************************************************************
* internal defined color tags
*************************************************************/
// There are internal defined fg color tags
//
// Usage:
//
// <tag>content text</>
//
// @notice 加 0 在前面是为了防止之前的影响到现在的设置
var colorTags = map[string]string{
// basic tags
"red": "0;31",
"red1": "1;31", // with bold
"redB": "1;31",
"red_b": "1;31",
"blue": "0;34",
"blue1": "1;34", // with bold
"blueB": "1;34",
"blue_b": "1;34",
"cyan": "0;36",
"cyan1": "1;36", // with bold
"cyanB": "1;36",
"cyan_b": "1;36",
"green": "0;32",
"green1": "1;32", // with bold
"greenB": "1;32",
"green_b": "1;32",
"black": "0;30",
"white": "1;37",
"default": "0;39", // no color
"normal": "0;39", // no color
"brown": "0;33", // #A52A2A
"yellow": "0;33",
"ylw0": "0;33",
"yellowB": "1;33", // with bold
"ylw1": "1;33",
"ylwB": "1;33",
"magenta": "0;35",
"mga": "0;35", // short name
"magentaB": "1;35", // with bold
"magenta1": "1;35",
"mgb": "1;35",
"mga1": "1;35",
"mgaB": "1;35",
// light/hi tags
"gray": "0;90",
"darkGray": "0;90",
"dark_gray": "0;90",
"lightYellow": "0;93",
"light_yellow": "0;93",
"hiYellow": "0;93",
"hi_yellow": "0;93",
"hiYellowB": "1;93", // with bold
"hi_yellow_b": "1;93",
"lightMagenta": "0;95",
"light_magenta": "0;95",
"hiMagenta": "0;95",
"hi_magenta": "0;95",
"lightMagenta1": "1;95", // with bold
"hiMagentaB": "1;95", // with bold
"hi_magenta_b": "1;95",
"lightRed": "0;91",
"light_red": "0;91",
"hiRed": "0;91",
"hi_red": "0;91",
"lightRedB": "1;91", // with bold
"light_red_b": "1;91",
"hi_red_b": "1;91",
"lightGreen": "0;92",
"light_green": "0;92",
"hiGreen": "0;92",
"hi_green": "0;92",
"lightGreenB": "1;92",
"light_green_b": "1;92",
"hi_green_b": "1;92",
"lightBlue": "0;94",
"light_blue": "0;94",
"hiBlue": "0;94",
"hi_blue": "0;94",
"lightBlueB": "1;94",
"light_blue_b": "1;94",
"hi_blue_b": "1;94",
"lightCyan": "0;96",
"light_cyan": "0;96",
"hiCyan": "0;96",
"hi_cyan": "0;96",
"lightCyanB": "1;96",
"light_cyan_b": "1;96",
"hi_cyan_b": "1;96",
"lightWhite": "0;97;40",
"light_white": "0;97;40",
// option
"bold": "1",
"b": "1",
"italic": "3",
"i": "3", // italic
"underscore": "4",
"us": "4", // short name for 'underscore'
"blink": "5",
"fb": "6", // fast blink
"reverse": "7",
"st": "9", // strikethrough
// alert tags, like bootstrap's alert
"suc": "1;32", // same "green" and "bold"
"success": "1;32",
"info": "0;32", // same "green",
"comment": "0;33", // same "brown"
"note": "36;1",
"notice": "36;4",
"warn": "0;1;33",
"warning": "0;30;43",
"primary": "0;34",
"danger": "1;31", // same "red" but add bold
"err": "97;41",
"error": "97;41", // fg light white; bg red
}
/*************************************************************
* internal defined tag attributes
*************************************************************/
// built-in attributes for fg,bg 16-colors and op codes.
var (
attrFgs = map[string]string{
// basic colors
"black": FgBlack.Code(),
"red": "31",
"green": "32",
"brown": "33", // #A52A2A
"yellow": "33",
"ylw": "33",
"blue": "34",
"cyan": "36",
"magenta": "35",
"mga": "35",
"white": FgWhite.Code(),
"default": "39", // no color
"normal": "39", // no color
// light/hi colors
"darkGray": FgDarkGray.Code(),
"dark_gray": "90",
"gray": "90",
"lightYellow": "93",
"light_yellow": "93",
"hiYellow": "93",
"hi_yellow": "93",
"lightMagenta": "95",
"light_magenta": "95",
"hiMagenta": "95",
"hi_magenta": "95",
"hi_mga": "95",
"lightRed": "91",
"light_red": "91",
"hiRed": "91",
"hi_red": "91",
"lightGreen": "92",
"light_green": "92",
"hiGreen": "92",
"hi_green": "92",
"lightBlue": "94",
"light_blue": "94",
"hiBlue": "94",
"hi_blue": "94",
"lightCyan": "96",
"light_cyan": "96",
"hiCyan": "96",
"hi_cyan": "96",
"lightWhite": "97",
"light_white": "97",
}
attrBgs = map[string]string{
// basic colors
"black": BgBlack.Code(),
"red": "41",
"green": "42",
"brown": "43", // #A52A2A
"yellow": "43",
"ylw": "43",
"blue": "44",
"cyan": "46",
"magenta": "45",
"mga": "45",
"white": FgWhite.Code(),
"default": "49", // no color
"normal": "49", // no color
// light/hi colors
"darkGray": BgDarkGray.Code(),
"dark_gray": "100",
"gray": "100",
"lightYellow": "103",
"light_yellow": "103",
"hiYellow": "103",
"hi_yellow": "103",
"lightMagenta": "105",
"light_magenta": "105",
"hiMagenta": "105",
"hi_magenta": "105",
"hi_mga": "105",
"lightRed": "101",
"light_red": "101",
"hiRed": "101",
"hi_red": "101",
"lightGreen": "102",
"light_green": "102",
"hiGreen": "102",
"hi_green": "102",
"lightBlue": "104",
"light_blue": "104",
"hiBlue": "104",
"hi_blue": "104",
"lightCyan": "106",
"light_cyan": "106",
"hiCyan": "106",
"hi_cyan": "106",
"lightWhite": BgLightWhite.Code(),
"light_white": "107",
}
attrOpts = map[string]string{
"reset": OpReset.Code(),
"bold": OpBold.Code(),
"b": OpBold.Code(),
"fuzzy": OpFuzzy.Code(),
"italic": OpItalic.Code(),
"i": OpItalic.Code(),
"underscore": OpUnderscore.Code(),
"us": OpUnderscore.Code(),
"u": OpUnderscore.Code(),
"blink": OpBlink.Code(),
"fastblink": OpFastBlink.Code(),
"fb": OpFastBlink.Code(),
"reverse": OpReverse.Code(),
"concealed": OpConcealed.Code(),
"strikethrough": OpStrikethrough.Code(),
"st": OpStrikethrough.Code(),
}
)
/*************************************************************
* parse color tags
*************************************************************/
var (
tagParser = TagParser{}
// regex for match color 256 code
rxNumStr = regexp.MustCompile("^[0-9]{1,3}$")
rxHexCode = regexp.MustCompile("^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
)
// TagParser struct
type TagParser struct {
disable bool
}
// NewTagParser create
func NewTagParser() *TagParser {
return &TagParser{}
}
// func (tp *TagParser) Disable() *TagParser {
// tp.disable = true
// return tp
// }
// ParseByEnv parse given string. will check package setting.
func (tp *TagParser) ParseByEnv(str string) string {
// disable handler TAG
if !RenderTag {
return str
}
// disable OR not support color
if !Enable || !SupportColor() {
return ClearTag(str)
}
return tp.Parse(str)
}
// Parse given string, replace color tag and return rendered string
//
// Use built in tags:
//
// <TAG_NAME>CONTENT</>
// // e.g: `<info>message</>`
//
// Custom tag attributes:
//
// `<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, "</>") {
return str
}
// find color tags by regex. str eg: "<fg=white;bg=blue;op=bold>content</>"
matched := matchRegex.FindAllStringSubmatch(str, -1)
// item: 0 full text 1 tag name 2 tag content
for _, item := range matched {
full, tag, body := item[0], item[1], item[2]
// use defined color tag name: "<info>content</>" -> tag: "info"
if !strings.ContainsRune(tag, '=') {
if code := colorTags[tag]; len(code) > 0 {
str = strings.Replace(str, full, RenderString(code, body), 1)
} else if code, ok := namedRgbMap[tag]; ok {
code = strings.Replace(code, ",", ";", -1)
now := RenderString(FgRGBPfx+code, body)
str = strings.Replace(str, full, now, 1)
}
continue
}
// custom color in tag
// - basic: "fg=white;bg=blue;op=bold"
if code := ParseCodeFromAttr(tag); len(code) > 0 {
str = strings.Replace(str, full, RenderString(code, body), 1)
}
}
return str
}
// ReplaceTag parse string, replace color tag and return rendered string
func ReplaceTag(str string) string {
return tagParser.ParseByEnv(str)
}
// ParseCodeFromAttr parse color attributes.
//
// attr format:
//
// // 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"
//
// 256 color:
//
// "fg=167"
// "fg=167;bg=23"
// "fg=167;bg=23;op=bold"
//
// True color:
//
// // hex
// "fg=fc1cac"
// "fg=fc1cac;bg=c2c3c4"
// // r,g,b
// "fg=23,45,214"
// "fg=23,45,214;bg=109,99,88"
func ParseCodeFromAttr(attr string) (code string) {
if !strings.ContainsRune(attr, '=') {
return
}
attr = strings.Trim(attr, ";=,")
if len(attr) == 0 {
return
}
var codes []string
matched := attrRegex.FindAllStringSubmatch(attr, -1)
for _, item := range matched {
pos, val := item[1], item[2]
switch pos {
case "fg":
if code, ok := attrFgs[val]; ok { // attr fg
codes = append(codes, code)
} else if code := rgbHex256toCode(val, false); code != "" {
codes = append(codes, code)
}
case "bg":
if code, ok := attrBgs[val]; ok { // attr bg
codes = append(codes, code)
} else if code := rgbHex256toCode(val, true); code != "" {
codes = append(codes, code)
}
case "op": // options allow multi value
if strings.Contains(val, ",") {
ns := strings.Split(val, ",")
for _, n := range ns {
if code, ok := attrOpts[n]; ok { // attr ops
codes = append(codes, code)
}
}
} else if code, ok := attrOpts[val]; ok {
codes = append(codes, code)
}
}
}
return strings.Join(codes, ";")
}
func rgbHex256toCode(val string, isBg bool) (code string) {
if len(val) == 6 && rxHexCode.MatchString(val) { // hex: "fc1cac"
code = HEX(val, isBg).String()
} else if strings.ContainsRune(val, ',') { // rgb: "231,178,161"
code = strings.Replace(val, ",", ";", -1)
if isBg {
code = BgRGBPfx + code
} else {
code = FgRGBPfx + code
}
} else if len(val) < 4 && rxNumStr.MatchString(val) { // 256 code
if isBg {
code = Bg256Pfx + val
} else {
code = Fg256Pfx + val
}
}
return
}
// ClearTag clear all tag for a string
func ClearTag(s string) string {
if !strings.Contains(s, "</>") {
return s
}
return stripRegex.ReplaceAllString(s, "")
}
/*************************************************************
* helper methods
*************************************************************/
// GetTagCode get color code by tag name
func GetTagCode(name string) string { return colorTags[name] }
// ApplyTag for messages
func ApplyTag(tag string, a ...any) string {
return RenderCode(GetTagCode(tag), a...)
}
// WrapTag wrap a tag for a string "<tag>content</>"
func WrapTag(s string, tag string) string {
if s == "" || tag == "" {
return s
}
return fmt.Sprintf("<%s>%s</>", tag, s)
}
// GetColorTags get all internal color tags
func GetColorTags() map[string]string {
return colorTags
}
// IsDefinedTag is defined tag name
func IsDefinedTag(name string) bool {
_, ok := colorTags[name]
return ok
}
/*************************************************************
* Tag extra
*************************************************************/
// Tag value is a defined style name
// Usage:
//
// Tag("info").Println("message")
type Tag string
// Print messages
func (tg Tag) Print(a ...any) {
name := string(tg)
str := fmt.Sprint(a...)
if stl := GetStyle(name); !stl.IsEmpty() {
stl.Print(str)
} else {
doPrintV2(GetTagCode(name), str)
}
}
// Printf format and print messages
func (tg Tag) Printf(format string, a ...any) {
name := string(tg)
str := fmt.Sprintf(format, a...)
if stl := GetStyle(name); !stl.IsEmpty() {
stl.Print(str)
} else {
doPrintV2(GetTagCode(name), str)
}
}
// Println messages line
func (tg Tag) Println(a ...any) {
name := string(tg)
if stl := GetStyle(name); !stl.IsEmpty() {
stl.Println(a...)
} else {
doPrintlnV2(GetTagCode(name), a)
}
}
// Sprint render messages
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 ...any) string {
tag := string(tg)
str := fmt.Sprintf(format, a...)
return RenderString(GetTagCode(tag), str)
}

View File

@@ -1,966 +0,0 @@
package color
import (
"fmt"
"math"
"sort"
"strconv"
"strings"
)
// values from https://github.com/go-terminfo/terminfo
// var (
// RgbaBlack = image_color.RGBA{0, 0, 0, 255}
// Red = color.RGBA{205, 0, 0, 255}
// Green = color.RGBA{0, 205, 0, 255}
// Orange = color.RGBA{205, 205, 0, 255}
// Blue = color.RGBA{0, 0, 238, 255}
// Magenta = color.RGBA{205, 0, 205, 255}
// Cyan = color.RGBA{0, 205, 205, 255}
// LightGrey = color.RGBA{229, 229, 229, 255}
//
// DarkGrey = color.RGBA{127, 127, 127, 255}
// LightRed = color.RGBA{255, 0, 0, 255}
// LightGreen = color.RGBA{0, 255, 0, 255}
// Yellow = color.RGBA{255, 255, 0, 255}
// LightBlue = color.RGBA{92, 92, 255, 255}
// LightMagenta = color.RGBA{255, 0, 255, 255}
// LightCyan = color.RGBA{0, 255, 255, 255}
// White = color.RGBA{255, 255, 255, 255}
// )
var (
// ---------- basic(16) <=> 256 color convert ----------
basicTo256Map = map[uint8]uint8{
30: 0, // black 000000
31: 160, // red c51e14
32: 34, // green 1dc121
33: 184, // yellow c7c329
34: 20, // blue 0a2fc4
35: 170, // magenta c839c5
36: 44, // cyan 20c5c6
37: 188, // white c7c7c7
90: 59, // lightBlack 686868
91: 203, // lightRed fd6f6b
92: 83, // lightGreen 67f86f
93: 227, // lightYellow fffa72
94: 69, // lightBlue 6a76fb
95: 213, // lightMagenta fd7cfc
96: 87, // lightCyan 68fdfe
97: 15, // lightWhite ffffff
}
// ---------- basic(16) <=> RGB color convert ----------
// refer from Hyper app
// Tip: only keep foreground color, background color need convert to foreground color for convert to RGB
basic2hexMap = map[uint8]string{
30: "000000", // black
31: "c51e14", // red
32: "1dc121", // green
33: "c7c329", // yellow
34: "0a2fc4", // blue
35: "c839c5", // magenta
36: "20c5c6", // cyan
37: "c7c7c7", // white
// - don't add bg color, convert to fg color for convert to RGB
// 40: "000000", // black
// 41: "c51e14", // red
// 42: "1dc121", // green
// 43: "c7c329", // yellow
// 44: "0a2fc4", // blue
// 45: "c839c5", // magenta
// 46: "20c5c6", // cyan
// 47: "c7c7c7", // white
90: "686868", // lightBlack/darkGray
91: "fd6f6b", // lightRed
92: "67f86f", // lightGreen
93: "fffa72", // lightYellow
94: "6a76fb", // lightBlue
95: "fd7cfc", // lightMagenta
96: "68fdfe", // lightCyan
97: "ffffff", // lightWhite
// - don't add bg color
// 100: "686868", // lightBlack/darkGray
// 101: "fd6f6b", // lightRed
// 102: "67f86f", // lightGreen
// 103: "fffa72", // lightYellow
// 104: "6a76fb", // lightBlue
// 105: "fd7cfc", // lightMagenta
// 106: "68fdfe", // lightCyan
// 107: "ffffff", // lightWhite
}
// will convert data from basic2hexMap
hex2basicMap = initHex2basicMap()
// ---------- 256 <=> RGB color convert ----------
// adapted from https://gist.github.com/MicahElliott/719710
c256ToHexMap = init256ToHexMap()
// rgb to 256 color look-up table
// RGB hex => 256 code
hexTo256Table = map[string]uint8{
// Primary 3-bit (8 colors). Unique representation!
"000000": 0,
"800000": 1,
"008000": 2,
"808000": 3,
"000080": 4,
"800080": 5,
"008080": 6,
"c0c0c0": 7,
// Equivalent "bright" versions of original 8 colors.
"808080": 8,
"ff0000": 9,
"00ff00": 10,
"ffff00": 11,
"0000ff": 12,
"ff00ff": 13,
"00ffff": 14,
"ffffff": 15,
// values commented out below are duplicates from the prior sections
// Strictly ascending.
// "000000": 16,
"000001": 16, // up: avoid key conflicts, value + 1
"00005f": 17,
"000087": 18,
"0000af": 19,
"0000d7": 20,
// "0000ff": 21,
"0000fe": 21, // up: avoid key conflicts, value - 1
"005f00": 22,
"005f5f": 23,
"005f87": 24,
"005faf": 25,
"005fd7": 26,
"005fff": 27,
"008700": 28,
"00875f": 29,
"008787": 30,
"0087af": 31,
"0087d7": 32,
"0087ff": 33,
"00af00": 34,
"00af5f": 35,
"00af87": 36,
"00afaf": 37,
"00afd7": 38,
"00afff": 39,
"00d700": 40,
"00d75f": 41,
"00d787": 42,
"00d7af": 43,
"00d7d7": 44,
"00d7ff": 45,
// "00ff00": 46,
"00ff01": 46, // up: avoid key conflicts, value + 1
"00ff5f": 47,
"00ff87": 48,
"00ffaf": 49,
"00ffd7": 50,
// "00ffff": 51,
"00fffe": 51, // up: avoid key conflicts, value - 1
"5f0000": 52,
"5f005f": 53,
"5f0087": 54,
"5f00af": 55,
"5f00d7": 56,
"5f00ff": 57,
"5f5f00": 58,
"5f5f5f": 59,
"5f5f87": 60,
"5f5faf": 61,
"5f5fd7": 62,
"5f5fff": 63,
"5f8700": 64,
"5f875f": 65,
"5f8787": 66,
"5f87af": 67,
"5f87d7": 68,
"5f87ff": 69,
"5faf00": 70,
"5faf5f": 71,
"5faf87": 72,
"5fafaf": 73,
"5fafd7": 74,
"5fafff": 75,
"5fd700": 76,
"5fd75f": 77,
"5fd787": 78,
"5fd7af": 79,
"5fd7d7": 80,
"5fd7ff": 81,
"5fff00": 82,
"5fff5f": 83,
"5fff87": 84,
"5fffaf": 85,
"5fffd7": 86,
"5fffff": 87,
"870000": 88,
"87005f": 89,
"870087": 90,
"8700af": 91,
"8700d7": 92,
"8700ff": 93,
"875f00": 94,
"875f5f": 95,
"875f87": 96,
"875faf": 97,
"875fd7": 98,
"875fff": 99,
"878700": 100,
"87875f": 101,
"878787": 102,
"8787af": 103,
"8787d7": 104,
"8787ff": 105,
"87af00": 106,
"87af5f": 107,
"87af87": 108,
"87afaf": 109,
"87afd7": 110,
"87afff": 111,
"87d700": 112,
"87d75f": 113,
"87d787": 114,
"87d7af": 115,
"87d7d7": 116,
"87d7ff": 117,
"87ff00": 118,
"87ff5f": 119,
"87ff87": 120,
"87ffaf": 121,
"87ffd7": 122,
"87ffff": 123,
"af0000": 124,
"af005f": 125,
"af0087": 126,
"af00af": 127,
"af00d7": 128,
"af00ff": 129,
"af5f00": 130,
"af5f5f": 131,
"af5f87": 132,
"af5faf": 133,
"af5fd7": 134,
"af5fff": 135,
"af8700": 136,
"af875f": 137,
"af8787": 138,
"af87af": 139,
"af87d7": 140,
"af87ff": 141,
"afaf00": 142,
"afaf5f": 143,
"afaf87": 144,
"afafaf": 145,
"afafd7": 146,
"afafff": 147,
"afd700": 148,
"afd75f": 149,
"afd787": 150,
"afd7af": 151,
"afd7d7": 152,
"afd7ff": 153,
"afff00": 154,
"afff5f": 155,
"afff87": 156,
"afffaf": 157,
"afffd7": 158,
"afffff": 159,
"d70000": 160,
"d7005f": 161,
"d70087": 162,
"d700af": 163,
"d700d7": 164,
"d700ff": 165,
"d75f00": 166,
"d75f5f": 167,
"d75f87": 168,
"d75faf": 169,
"d75fd7": 170,
"d75fff": 171,
"d78700": 172,
"d7875f": 173,
"d78787": 174,
"d787af": 175,
"d787d7": 176,
"d787ff": 177,
"d7af00": 178,
"d7af5f": 179,
"d7af87": 180,
"d7afaf": 181,
"d7afd7": 182,
"d7afff": 183,
"d7d700": 184,
"d7d75f": 185,
"d7d787": 186,
"d7d7af": 187,
"d7d7d7": 188,
"d7d7ff": 189,
"d7ff00": 190,
"d7ff5f": 191,
"d7ff87": 192,
"d7ffaf": 193,
"d7ffd7": 194,
"d7ffff": 195,
// "ff0000": 196,
"ff0001": 196, // up: avoid key conflicts, value + 1
"ff005f": 197,
"ff0087": 198,
"ff00af": 199,
"ff00d7": 200,
// "ff00ff": 201,
"ff00fe": 201, // up: avoid key conflicts, value - 1
"ff5f00": 202,
"ff5f5f": 203,
"ff5f87": 204,
"ff5faf": 205,
"ff5fd7": 206,
"ff5fff": 207,
"ff8700": 208,
"ff875f": 209,
"ff8787": 210,
"ff87af": 211,
"ff87d7": 212,
"ff87ff": 213,
"ffaf00": 214,
"ffaf5f": 215,
"ffaf87": 216,
"ffafaf": 217,
"ffafd7": 218,
"ffafff": 219,
"ffd700": 220,
"ffd75f": 221,
"ffd787": 222,
"ffd7af": 223,
"ffd7d7": 224,
"ffd7ff": 225,
// "ffff00": 226,
"ffff01": 226, // up: avoid key conflicts, value + 1
"ffff5f": 227,
"ffff87": 228,
"ffffaf": 229,
"ffffd7": 230,
// "ffffff": 231,
"fffffe": 231, // up: avoid key conflicts, value - 1
// Gray-scale range.
"080808": 232,
"121212": 233,
"1c1c1c": 234,
"262626": 235,
"303030": 236,
"3a3a3a": 237,
"444444": 238,
"4e4e4e": 239,
"585858": 240,
"626262": 241,
"6c6c6c": 242,
"767676": 243,
// "808080": 244,
"808081": 244, // up: avoid key conflicts, value + 1
"8a8a8a": 245,
"949494": 246,
"9e9e9e": 247,
"a8a8a8": 248,
"b2b2b2": 249,
"bcbcbc": 250,
"c6c6c6": 251,
"d0d0d0": 252,
"dadada": 253,
"e4e4e4": 254,
"eeeeee": 255,
}
incs = []uint8{0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
)
func initHex2basicMap() map[string]uint8 {
h2b := make(map[string]uint8, len(basic2hexMap))
// ini data map
for u, s := range basic2hexMap {
h2b[s] = u
}
return h2b
}
func init256ToHexMap() map[uint8]string {
c256toh := make(map[uint8]string, len(hexTo256Table))
// ini data map
for hex, c256 := range hexTo256Table {
c256toh[c256] = hex
}
return c256toh
}
// RgbTo256Table mapping data
func RgbTo256Table() map[string]uint8 {
return hexTo256Table
}
// Colors2code convert colors to code. return like "32;45;3"
func Colors2code(colors ...Color) string {
if len(colors) == 0 {
return ""
}
var codes []string
for _, color := range colors {
codes = append(codes, color.String())
}
return strings.Join(codes, ";")
}
/*************************************************************
* HEX code <=> RGB/True color code
*************************************************************/
// Hex2rgb alias of the HexToRgb()
func Hex2rgb(hex string) []int { return HexToRgb(hex) }
// HexToRGB alias of the HexToRgb()
func HexToRGB(hex string) []int { return HexToRgb(hex) }
// HexToRgb convert hex color string to RGB numbers
//
// Usage:
//
// rgb := HexToRgb("ccc") // rgb: [204 204 204]
// rgb := HexToRgb("aabbcc") // rgb: [170 187 204]
// rgb := HexToRgb("#aabbcc") // rgb: [170 187 204]
// rgb := HexToRgb("0xad99c0") // rgb: [170 187 204]
func HexToRgb(hex string) (rgb []int) {
hex = strings.TrimSpace(hex)
if hex == "" {
return
}
// like from css. eg "#ccc" "#ad99c0"
if hex[0] == '#' {
hex = hex[1:]
}
hex = strings.ToLower(hex)
switch len(hex) {
case 3: // "ccc"
hex = string([]byte{hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]})
case 8: // "0xad99c0"
hex = strings.TrimPrefix(hex, "0x")
}
// recheck
if len(hex) != 6 {
return
}
// convert string to int64
if i64, err := strconv.ParseInt(hex, 16, 32); err == nil {
color := int(i64)
// parse int
rgb = make([]int, 3)
rgb[0] = color >> 16
rgb[1] = (color & 0x00FF00) >> 8
rgb[2] = color & 0x0000FF
}
return
}
// Rgb2hex alias of the RgbToHex()
func Rgb2hex(rgb []int) string { return RgbToHex(rgb) }
// RgbToHex convert RGB-code to hex-code
//
// Usage:
//
// hex := RgbToHex([]int{170, 187, 204}) // hex: "aabbcc"
func RgbToHex(rgb []int) string {
hexNodes := make([]string, len(rgb))
for _, v := range rgb {
hexNodes = append(hexNodes, strconv.FormatInt(int64(v), 16))
}
return strings.Join(hexNodes, "")
}
/*************************************************************
* 4bit(16) color <=> RGB/True color
*************************************************************/
// BasicToHex convert basic color to hex string.
func BasicToHex(val uint8) string {
val = Bg2Fg(val)
return basic2hexMap[val]
}
// Basic2hex convert basic color to hex string.
func Basic2hex(val uint8) string {
return BasicToHex(val)
}
// Hex2basic convert hex string to basic color code.
func Hex2basic(hex string, asBg ...bool) uint8 {
val := hex2basicMap[hex]
if len(asBg) > 0 && asBg[0] {
return Fg2Bg(val)
}
return val
}
// Rgb2basic alias of the RgbToAnsi()
func Rgb2basic(r, g, b uint8, isBg bool) uint8 {
// is basic color, direct use static map data.
hex := RgbToHex([]int{int(r), int(g), int(b)})
if val, ok := hex2basicMap[hex]; ok {
if isBg {
return val + 10
}
return val
}
return RgbToAnsi(r, g, b, isBg)
}
// Rgb2ansi convert RGB-code to 16-code, alias of the RgbToAnsi()
func Rgb2ansi(r, g, b uint8, isBg bool) uint8 {
return RgbToAnsi(r, g, b, isBg)
}
// RgbToAnsi convert RGB-code to 16-code
// refer https://github.com/radareorg/radare2/blob/master/libr/cons/rgb.c#L249-L271
func RgbToAnsi(r, g, b uint8, isBg bool) uint8 {
var bright, c, k uint8
base := compareVal(isBg, BgBase, FgBase)
// eco bright-specific
if r == 0x80 && g == 0x80 && b == 0x80 { // 0x80=128
bright = 53
} else if r == 0xff || g == 0xff || b == 0xff { // 0xff=255
bright = 60
} // else bright = 0
if r == g && g == b {
// 0x7f=127
// r = (r > 0x7f) ? 1 : 0;
r = compareVal(r > 0x7f, 1, 0)
g = compareVal(g > 0x7f, 1, 0)
b = compareVal(b > 0x7f, 1, 0)
} else {
k = (r + g + b) / 3
// r = (r >= k) ? 1 : 0;
r = compareVal(r >= k, 1, 0)
g = compareVal(g >= k, 1, 0)
b = compareVal(b >= k, 1, 0)
}
// c = (r ? 1 : 0) + (g ? (b ? 6 : 2) : (b ? 4 : 0))
c = compareVal(r > 0, 1, 0)
if g > 0 {
c += compareVal(b > 0, 6, 2)
} else {
c += compareVal(b > 0, 4, 0)
}
return base + bright + c
}
/*************************************************************
* 8bit(256) color <=> RGB/True color
*************************************************************/
// Rgb2short convert RGB-code to 256-code
func Rgb2short(r, g, b uint8) uint8 {
return RgbTo256(r, g, b)
}
// RgbTo256 convert RGB-code to 256-code
func RgbTo256(r, g, b uint8) uint8 {
res := make([]uint8, 3)
for partI, part := range [3]uint8{r, g, b} {
i := 0
for i < len(incs)-1 {
s, b := incs[i], incs[i+1] // smaller, bigger
if s <= part && part <= b {
s1 := math.Abs(float64(s) - float64(part))
b1 := math.Abs(float64(b) - float64(part))
var closest uint8
if s1 < b1 {
closest = s
} else {
closest = b
}
res[partI] = closest
break
}
i++
}
}
hex := fmt.Sprintf("%02x%02x%02x", res[0], res[1], res[2])
equiv := hexTo256Table[hex]
return equiv
}
// C256ToRgb convert an 256 color code to RGB numbers
func C256ToRgb(val uint8) (rgb []uint8) {
hex := c256ToHexMap[val]
// convert to rgb code
rgbInts := Hex2rgb(hex)
return []uint8{
uint8(rgbInts[0]),
uint8(rgbInts[1]),
uint8(rgbInts[2]),
}
}
// C256ToRgbV1 convert an 256 color code to RGB numbers
// refer https://github.com/torvalds/linux/commit/cec5b2a97a11ade56a701e83044d0a2a984c67b4
func C256ToRgbV1(val uint8) (rgb []uint8) {
var r, g, b uint8
if val < 8 { // Standard colours.
// r = val&1 ? 0xaa : 0x00;
r = compareVal(val&1 == 1, 0xaa, 0x00)
g = compareVal(val&2 == 2, 0xaa, 0x00)
b = compareVal(val&4 == 4, 0xaa, 0x00)
} else if val < 16 {
// r = val & 1 ? 0xff : 0x55;
r = compareVal(val&1 == 1, 0xff, 0x55)
g = compareVal(val&2 == 2, 0xff, 0x55)
b = compareVal(val&4 == 4, 0xff, 0x55)
} else if val < 232 { /* 6x6x6 colour cube. */
r = (val - 16) / 36 * 85 / 2
g = (val - 16) / 6 % 6 * 85 / 2
b = (val - 16) % 6 * 85 / 2
} else { /* Grayscale ramp. */
nv := uint8(int(val)*10 - 2312)
// set value
r, g, b = nv, nv, nv
}
return []uint8{r, g, b}
}
/**************************************************************
* HSL color <=> RGB/True color
************************************************************
* h,s,l = Hue, Saturation, Lightness
*
* refers
* http://en.wikipedia.org/wiki/HSL_color_space
* https://www.w3.org/TR/css-color-3/#hsl-color
* https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
* https://github.com/less/less.js/blob/master/packages/less/src/less/functions/color.js
* https://github.com/d3/d3-color/blob/v3.0.1/README.md#hsl
*
* examples:
* color: hsl(0, 100%, 50%) // red
* color: hsl(120, 100%, 50%) // lime
* color: hsl(120, 100%, 25%) // dark green
* color: hsl(120, 100%, 75%) // light green
* color: hsl(120, 75%, 75%) // pastel green, and so on
*/
// HslIntToRgb Converts an HSL color value to RGB
// Assumes h: 0-360, s: 0-100, l: 0-100
// returns r, g, and b in the set [0, 255].
//
// Usage:
//
// HslIntToRgb(0, 100, 50) // red
// HslIntToRgb(120, 100, 50) // lime
// HslIntToRgb(120, 100, 25) // dark green
// HslIntToRgb(120, 100, 75) // light green
func HslIntToRgb(h, s, l int) (rgb []uint8) {
return HslToRgb(float64(h)/360, float64(s)/100, float64(l)/100)
}
// HslToRgb Converts an HSL color value to RGB. Conversion formula
// adapted from http://en.wikipedia.org/wiki/HSL_color_space.
// Assumes h, s, and l are contained in the set [0, 1]
// returns r, g, and b in the set [0, 255].
//
// Usage:
//
// rgbVals := HslToRgb(0, 1, 0.5) // red
func HslToRgb(h, s, l float64) (rgb []uint8) {
var r, g, b float64
if s == 0 { // achromatic
r, g, b = l, l, l
} else {
var hue2rgb = func(p, q, t float64) float64 {
if t < 0.0 {
t += 1
}
if t > 1.0 {
t -= 1
}
if t < 1.0/6.0 {
return p + (q-p)*6.0*t
}
if t < 1.0/2.0 {
return q
}
if t < 2.0/3.0 {
return p + (q-p)*(2.0/3.0-t)*6.0
}
return p
}
// q = l < 0.5 ? l * (1 + s) : l + s - l*s
var q float64
if l < 0.5 {
q = l * (1.0 + s)
} else {
q = l + s - l*s
}
var p = 2.0*l - q
r = hue2rgb(p, q, h+1.0/3.0)
g = hue2rgb(p, q, h)
b = hue2rgb(p, q, h-1.0/3.0)
}
// return []uint8{uint8(r * 255), uint8(g * 255), uint8(b * 255)}
return []uint8{
uint8(math.Round(r * 255)),
uint8(math.Round(g * 255)),
uint8(math.Round(b * 255)),
}
}
// RgbToHslInt Converts an RGB color value to HSL. Conversion formula
// Assumes r, g, and b are contained in the set [0, 255] and
// returns [h,s,l] h: 0-360, s: 0-100, l: 0-100.
func RgbToHslInt(r, g, b uint8) []int {
f64s := RgbToHsl(r, g, b)
return []int{int(f64s[0] * 360), int(f64s[1] * 100), int(f64s[2] * 100)}
}
// RgbToHsl Converts an RGB color value to HSL. Conversion formula
//
// adapted from http://en.wikipedia.org/wiki/HSL_color_space.
//
// Assumes r, g, and b are contained in the set [0, 255] and
// returns h, s, and l in the set [0, 1].
func RgbToHsl(r, g, b uint8) []float64 {
// to float64
fr, fg, fb := float64(r), float64(g), float64(b)
// percentage
pr, pg, pb := float64(r)/255.0, float64(g)/255.0, float64(b)/255.0
ps := []float64{pr, pg, pb}
sort.Float64s(ps)
min, max := ps[0], ps[2]
// max := math.Max(math.Max(pr, pg), pb)
// min := math.Min(math.Min(pr, pg), pb)
mid := (max + min) / 2
h, s, l := mid, mid, mid
if max == min {
h, s = 0, 0 // achromatic
} else {
var d = max - min
// s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
s = compareF64Val(l > 0.5, d/(2-max-min), d/(max+min))
switch max {
case fr:
// h = (g - b) / d + (g < b ? 6 : 0)
h = (fg - fb) / d
h += compareF64Val(g < b, 6, 0)
case fg:
h = (fb-fr)/d + 2
case fb:
h = (fr-fg)/d + 4
}
h /= 6
}
return []float64{h, s, l}
}
/**************************************************************
* HSV color <=> RGB/True color
************************************************************
* h,s,l = Hue, Saturation, Value(Brightness)
*
* refers
* https://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
* https://github.com/less/less.js/blob/master/packages/less/src/less/functions/color.js
* https://github.com/d3/d3-color/blob/v3.0.1/README.md#hsl
*/
// HsvToRgb Converts an HSL color value to RGB. Conversion formula
// adapted from https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
// Assumes h: 0-360, s: 0-100, l: 0-100
// returns r, g, and b in the set [0, 255].
func HsvToRgb(h, s, v int) (rgb []uint8) {
// TODO ...
return
}
// Named rgb colors
// https://www.w3.org/TR/css-color-3/#svg-color
var namedRgbMap = map[string]string{
"aliceblue": "240,248,255", // #F0F8FF
"antiquewhite": "250,235,215", // #FAEBD7
"aqua": "0,255,255", // #00FFFF
"aquamarine": "127,255,212", // #7FFFD4
"azure": "240,255,255", // #F0FFFF
"beige": "245,245,220", // #F5F5DC
"bisque": "255,228,196", // #FFE4C4
"black": "0,0,0", // #000000
"blanchedalmond": "255,235,205", // #FFEBCD
"blue": "0,0,255", // #0000FF
"blueviolet": "138,43,226", // #8A2BE2
"brown": "165,42,42", // #A52A2A
"burlywood": "222,184,135", // #DEB887
"cadetblue": "95,158,160", // #5F9EA0
"chartreuse": "127,255,0", // #7FFF00
"chocolate": "210,105,30", // #D2691E
"coral": "255,127,80", // #FF7F50
"cornflowerblue": "100,149,237", // #6495ED
"cornsilk": "255,248,220", // #FFF8DC
"crimson": "220,20,60", // #DC143C
"cyan": "0,255,255", // #00FFFF
"darkblue": "0,0,139", // #00008B
"darkcyan": "0,139,139", // #008B8B
"darkgoldenrod": "184,134,11", // #B8860B
"darkgray": "169,169,169", // #A9A9A9
"darkgreen": "0,100,0", // #006400
"darkgrey": "169,169,169", // #A9A9A9
"darkkhaki": "189,183,107", // #BDB76B
"darkmagenta": "139,0,139", // #8B008B
"darkolivegreen": "85,107,47", // #556B2F
"darkorange": "255,140,0", // #FF8C00
"darkorchid": "153,50,204", // #9932CC
"darkred": "139,0,0", // #8B0000
"darksalmon": "233,150,122", // #E9967A
"darkseagreen": "143,188,143", // #8FBC8F
"darkslateblue": "72,61,139", // #483D8B
"darkslategray": "47,79,79", // #2F4F4F
"darkslategrey": "47,79,79", // #2F4F4F
"darkturquoise": "0,206,209", // #00CED1
"darkviolet": "148,0,211", // #9400D3
"deeppink": "255,20,147", // #FF1493
"deepskyblue": "0,191,255", // #00BFFF
"dimgray": "105,105,105", // #696969
"dimgrey": "105,105,105", // #696969
"dodgerblue": "30,144,255", // #1E90FF
"firebrick": "178,34,34", // #B22222
"floralwhite": "255,250,240", // #FFFAF0
"forestgreen": "34,139,34", // #228B22
"fuchsia": "255,0,255", // #FF00FF
"gainsboro": "220,220,220", // #DCDCDC
"ghostwhite": "248,248,255", // #F8F8FF
"gold": "255,215,0", // #FFD700
"goldenrod": "218,165,32", // #DAA520
"gray": "128,128,128", // #808080
"green": "0,128,0", // #008000
"greenyellow": "173,255,47", // #ADFF2F
"grey": "128,128,128", // #808080
"honeydew": "240,255,240", // #F0FFF0
"hotpink": "255,105,180", // #FF69B4
"indianred": "205,92,92", // #CD5C5C
"indigo": "75,0,130", // #4B0082
"ivory": "255,255,240", // #FFFFF0
"khaki": "240,230,140", // #F0E68C
"lavender": "230,230,250", // #E6E6FA
"lavenderblush": "255,240,245", // #FFF0F5
"lawngreen": "124,252,0", // #7CFC00
"lemonchiffon": "255,250,205", // #FFFACD
"lightblue": "173,216,230", // #ADD8E6
"lightcoral": "240,128,128", // #F08080
"lightcyan": "224,255,255", // #E0FFFF
"lightgoldenrodyellow": "250,250,210", // #FAFAD2
"lightgray": "211,211,211", // #D3D3D3
"lightgreen": "144,238,144", // #90EE90
"lightgrey": "211,211,211", // #D3D3D3
"lightpink": "255,182,193", // #FFB6C1
"lightsalmon": "255,160,122", // #FFA07A
"lightseagreen": "32,178,170", // #20B2AA
"lightskyblue": "135,206,250", // #87CEFA
"lightslategray": "119,136,153", // #778899
"lightslategrey": "119,136,153", // #778899
"lightsteelblue": "176,196,222", // #B0C4DE
"lightyellow": "255,255,224", // #FFFFE0
"lime": "0,255,0", // #00FF00
"limegreen": "50,205,50", // #32CD32
"linen": "250,240,230", // #FAF0E6
"magenta": "255,0,255", // #FF00FF
"maroon": "128,0,0", // #800000
"mediumaquamarine": "102,205,170", // #66CDAA
"mediumblue": "0,0,205", // #0000CD
"mediumorchid": "186,85,211", // #BA55D3
"mediumpurple": "147,112,219", // #9370DB
"mediumseagreen": "60,179,113", // #3CB371
"mediumslateblue": "123,104,238", // #7B68EE
"mediumspringgreen": "0,250,154", // #00FA9A
"mediumturquoise": "72,209,204", // #48D1CC
"mediumvioletred": "199,21,133", // #C71585
"midnightblue": "25,25,112", // #191970
"mintcream": "245,255,250", // #F5FFFA
"mistyrose": "255,228,225", // #FFE4E1
"moccasin": "255,228,181", // #FFE4B5
"navajowhite": "255,222,173", // #FFDEAD
"navy": "0,0,128", // #000080
"oldlace": "253,245,230", // #FDF5E6
"olive": "128,128,0", // #808000
"olivedrab": "107,142,35", // #6B8E23
"orange": "255,165,0", // #FFA500
"orangered": "255,69,0", // #FF4500
"orchid": "218,112,214", // #DA70D6
"palegoldenrod": "238,232,170", // #EEE8AA
"palegreen": "152,251,152", // #98FB98
"paleturquoise": "175,238,238", // #AFEEEE
"palevioletred": "219,112,147", // #DB7093
"papayawhip": "255,239,213", // #FFEFD5
"peachpuff": "255,218,185", // #FFDAB9
"peru": "205,133,63", // #CD853F
"pink": "255,192,203", // #FFC0CB
"plum": "221,160,221", // #DDA0DD
"powderblue": "176,224,230", // #B0E0E6
"purple": "128,0,128", // #800080
"red": "255,0,0", // #FF0000
"rosybrown": "188,143,143", // #BC8F8F
"royalblue": "65,105,225", // #4169E1
"saddlebrown": "139,69,19", // #8B4513
"salmon": "250,128,114", // #FA8072
"sandybrown": "244,164,96", // #F4A460
"seagreen": "46,139,87", // #2E8B57
"seashell": "255,245,238", // #FFF5EE
"sienna": "160,82,45", // #A0522D
"silver": "192,192,192", // #C0C0C0
"skyblue": "135,206,235", // #87CEEB
"slateblue": "106,90,205", // #6A5ACD
"slategray": "112,128,144", // #708090
"slategrey": "112,128,144", // #708090
"snow": "255,250,250", // #FFFAFA
"springgreen": "0,255,127", // #00FF7F
"steelblue": "70,130,180", // #4682B4
"tan": "210,180,140", // #D2B48C
"teal": "0,128,128", // #008080
"thistle": "216,191,216", // #D8BFD8
"tomato": "255,99,71", // #FF6347
"turquoise": "64,224,208", // #40E0D0
"violet": "238,130,238", // #EE82EE
"wheat": "245,222,179", // #F5DEB3
"white": "255,255,255", // #FFFFFF
"whitesmoke": "245,245,245", // #F5F5F5
"yellow": "255,255,0", // #FFFF00
"yellowgreen": "154,205,50", // #9ACD32
}

View File

@@ -1,291 +0,0 @@
package color
import (
"io"
"os"
"runtime"
"strconv"
"strings"
"syscall"
"github.com/xo/terminfo"
)
// Level is the color level supported by a terminal.
type Level = terminfo.ColorLevel
// terminal color available level alias of the terminfo.ColorLevel*
const (
LevelNo = terminfo.ColorLevelNone // not support color.
Level16 = terminfo.ColorLevelBasic // basic - 3/4 bit color supported
Level256 = terminfo.ColorLevelHundreds // hundreds - 8-bit color supported
LevelRgb = terminfo.ColorLevelMillions // millions - (24 bit)true color supported
)
/*************************************************************
* helper methods for detect color supports
*************************************************************/
// DetectColorLevel for current env
//
// NOTICE: The method will detect terminal info each times,
// if only want to get current color level, please direct call SupportColor() or TermColorLevel()
func DetectColorLevel() Level {
level, _ := detectTermColorLevel()
return level
}
// detect terminal color support level
//
// refer https://github.com/Delta456/box-cli-maker
func detectTermColorLevel() (level Level, needVTP bool) {
// on windows WSL:
// - runtime.GOOS == "Linux"
// - support true-color
// env:
// WSL_DISTRO_NAME=Debian
if val := os.Getenv("WSL_DISTRO_NAME"); val != "" {
// detect WSL as it has True Color support
if detectWSL() {
debugf("True Color support on WSL environment")
return terminfo.ColorLevelMillions, false
}
}
isWin := runtime.GOOS == "windows"
termVal := os.Getenv("TERM")
// on TERM=screen: not support true-color
if termVal != "screen" {
// On JetBrains Terminal
// - support true-color
// env:
// TERMINAL_EMULATOR=JetBrains-JediTerm
val := os.Getenv("TERMINAL_EMULATOR")
if val == "JetBrains-JediTerm" {
debugf("True Color support on JetBrains-JediTerm, is win: %v", isWin)
return terminfo.ColorLevelMillions, isWin
}
}
// level, err = terminfo.ColorLevelFromEnv()
level = detectColorLevelFromEnv(termVal, isWin)
debugf("color level by detectColorLevelFromEnv: %s", level.String())
// fallback: simple detect by TERM value string.
if level == terminfo.ColorLevelNone {
debugf("level none - fallback check special term color support")
// on Windows: enable VTP as it has True Color support
level, needVTP = detectSpecialTermColor(termVal)
}
return
}
// detectColorFromEnv returns the color level COLORTERM, FORCE_COLOR,
// TERM_PROGRAM, or determined from the TERM environment variable.
//
// refer the terminfo.ColorLevelFromEnv()
// https://en.wikipedia.org/wiki/Terminfo
func detectColorLevelFromEnv(termVal string, isWin bool) Level {
// check for overriding environment variables
colorTerm, termProg, forceColor := os.Getenv("COLORTERM"), os.Getenv("TERM_PROGRAM"), os.Getenv("FORCE_COLOR")
switch {
case strings.Contains(colorTerm, "truecolor") || strings.Contains(colorTerm, "24bit"):
if termVal == "screen" { // on TERM=screen: not support true-color
return terminfo.ColorLevelHundreds
}
return terminfo.ColorLevelMillions
case colorTerm != "" || forceColor != "":
return terminfo.ColorLevelBasic
case termProg == "Apple_Terminal":
return terminfo.ColorLevelHundreds
case termProg == "Terminus" || termProg == "Hyper":
if termVal == "screen" { // on TERM=screen: not support true-color
return terminfo.ColorLevelHundreds
}
return terminfo.ColorLevelMillions
case termProg == "iTerm.app":
if termVal == "screen" { // on TERM=screen: not support true-color
return terminfo.ColorLevelHundreds
}
// check iTerm version
ver := os.Getenv("TERM_PROGRAM_VERSION")
if ver != "" {
i, err := strconv.Atoi(strings.Split(ver, ".")[0])
if err != nil {
saveInternalError(terminfo.ErrInvalidTermProgramVersion)
// return terminfo.ColorLevelNone
return terminfo.ColorLevelHundreds
}
if i == 3 {
return terminfo.ColorLevelMillions
}
}
return terminfo.ColorLevelHundreds
}
// otherwise determine from TERM's max_colors capability
if !isWin && termVal != "" {
debugf("TERM=%s - check color level by load terminfo file", termVal)
ti, err := terminfo.Load(termVal)
if err != nil {
saveInternalError(err)
return terminfo.ColorLevelNone
}
debugf("the loaded term info file is: %s", ti.File)
v, ok := ti.Nums[terminfo.MaxColors]
switch {
case !ok || v <= 16:
return terminfo.ColorLevelNone
case ok && v >= 256:
return terminfo.ColorLevelHundreds
}
return terminfo.ColorLevelBasic
}
// no TERM env value. default return none level
return terminfo.ColorLevelNone
// return terminfo.ColorLevelBasic
}
var detectedWSL bool
var wslContents string
// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
func detectWSL() bool {
if !detectedWSL {
detectedWSL = true
b := make([]byte, 1024)
// `cat /proc/version`
// on mac:
// !not the file!
// on linux(debian,ubuntu,alpine):
// Linux version 4.19.121-linuxkit (root@18b3f92ade35) (gcc version 9.2.0 (Alpine 9.2.0)) #1 SMP Thu Jan 21 15:36:34 UTC 2021
// on win git bash, conEmu:
// MINGW64_NT-10.0-19042 version 3.1.7-340.x86_64 (@WIN-N0G619FD3UK) (gcc version 9.3.0 (GCC) ) 2020-10-23 13:08 UTC
// on WSL:
// Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020
f, err := os.Open("/proc/version")
if err == nil {
_, _ = f.Read(b) // ignore error
if err = f.Close(); err != nil {
saveInternalError(err)
}
wslContents = string(b)
return strings.Contains(wslContents, "Microsoft")
}
}
return false
}
/*
// refer
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_unix.go#L27-L45
// detect WSL as it has True Color support
func isWSL() bool {
// on windows WSL:
// - runtime.GOOS == "Linux"
// - support true-color
// WSL_DISTRO_NAME=Debian
if val := os.Getenv("WSL_DISTRO_NAME"); val == "" {
return false
}
// `cat /proc/sys/kernel/osrelease`
// on mac:
// !not the file!
// on linux:
// 4.19.121-linuxkit
// on WSL Output:
// 4.4.0-19041-Microsoft
wsl, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
if err != nil {
saveInternalError(err)
return false
}
// it gives "Microsoft" for WSL and "microsoft" for WSL 2
// it supports True-color
content := strings.ToLower(string(wsl))
return strings.Contains(content, "microsoft")
}
*/
/*************************************************************
* helper methods for check env
*************************************************************/
// IsWindows OS env
func IsWindows() bool {
return runtime.GOOS == "windows"
}
// IsConsole Determine whether w is one of stderr, stdout, stdin
func IsConsole(w io.Writer) bool {
o, ok := w.(*os.File)
if !ok {
return false
}
fd := o.Fd()
// fix: cannot use 'o == os.Stdout' to compare
return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr)
}
// IsMSys msys(MINGW64) environment, does not necessarily support color
func IsMSys() bool {
// like "MSYSTEM=MINGW64"
return len(os.Getenv("MSYSTEM")) > 0
}
// IsSupportColor check current console is support color.
//
// NOTICE: The method will detect terminal info each times,
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
func IsSupportColor() bool {
return IsSupport16Color()
}
// IsSupport16Color check current console is support color.
//
// NOTICE: The method will detect terminal info each times,
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
func IsSupport16Color() bool {
level, _ := detectTermColorLevel()
return level > terminfo.ColorLevelNone
}
// IsSupport256Color render check
//
// NOTICE: The method will detect terminal info each times,
// if only want to get current color level, please direct call SupportColor() or TermColorLevel()
func IsSupport256Color() bool {
level, _ := detectTermColorLevel()
return level > terminfo.ColorLevelBasic
}
// IsSupportRGBColor check. alias of the IsSupportTrueColor()
//
// NOTICE: The method will detect terminal info each times,
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
func IsSupportRGBColor() bool {
return IsSupportTrueColor()
}
// IsSupportTrueColor render check.
//
// NOTICE: The method will detect terminal info each times,
// if only want get current color level, please direct call SupportColor() or TermColorLevel()
//
// ENV:
// "COLORTERM=truecolor"
// "COLORTERM=24bit"
func IsSupportTrueColor() bool {
level, _ := detectTermColorLevel()
return level > terminfo.ColorLevelHundreds
}

View File

@@ -1,49 +0,0 @@
//go:build !windows
// +build !windows
// The method in the file has no effect
// Only for compatibility with non-Windows systems
package color
import (
"strings"
"syscall"
"github.com/xo/terminfo"
)
// detect special term color support
func detectSpecialTermColor(termVal string) (Level, bool) {
if termVal == "" {
return terminfo.ColorLevelNone, false
}
debugf("terminfo check fail - fallback detect color by check TERM value")
// on TERM=screen:
// - support 256, not support true-color. test on macOS
if termVal == "screen" {
return terminfo.ColorLevelHundreds, false
}
if strings.Contains(termVal, "256color") {
return terminfo.ColorLevelHundreds, false
}
if strings.Contains(termVal, "xterm") {
return terminfo.ColorLevelHundreds, false
// return terminfo.ColorLevelBasic, false
}
// return terminfo.ColorLevelNone, nil
return terminfo.ColorLevelBasic, false
}
// IsTerminal returns true if the given file descriptor is a terminal.
//
// Usage:
// IsTerminal(os.Stdout.Fd())
func IsTerminal(fd uintptr) bool {
return fd == uintptr(syscall.Stdout) || fd == uintptr(syscall.Stdin) || fd == uintptr(syscall.Stderr)
}

View File

@@ -1,250 +0,0 @@
//go:build windows
// +build windows
// Display color on Windows
//
// refer:
//
// golang.org/x/sys/windows
// golang.org/x/crypto/ssh/terminal
// https://docs.microsoft.com/en-us/windows/console
package color
import (
"os"
"syscall"
"unsafe"
"github.com/xo/terminfo"
"golang.org/x/sys/windows"
)
// related docs
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences
// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
var (
// isMSys bool
kernel32 *syscall.LazyDLL
procGetConsoleMode *syscall.LazyProc
procSetConsoleMode *syscall.LazyProc
)
func init() {
if !SupportColor() {
isLikeInCmd = true
return
}
// if disabled.
if !Enable {
return
}
// if at Windows's ConEmu, Cmder, putty ... terminals not need VTP
// -------- try force enable colors on windows terminal -------
tryEnableVTP(needVTP)
// fetch console screen buffer info
// err := getConsoleScreenBufferInfo(uintptr(syscall.Stdout), &defScreenInfo)
}
// try force enable colors on Windows terminal
func tryEnableVTP(enable bool) bool {
if !enable {
return false
}
debugf("True-Color by enable VirtualTerminalProcessing on windows")
initKernel32Proc()
// enable colors on Windows terminal
if tryEnableOnCONOUT() {
return true
}
return tryEnableOnStdout()
}
func initKernel32Proc() {
if kernel32 != nil {
return
}
// load related Windows dll
// https://docs.microsoft.com/en-us/windows/console/setconsolemode
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
}
func tryEnableOnCONOUT() bool {
outHandle, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0)
if err != nil {
saveInternalError(err)
return false
}
err = EnableVirtualTerminalProcessing(outHandle, true)
if err != nil {
saveInternalError(err)
return false
}
return true
}
func tryEnableOnStdout() bool {
// try direct open syscall.Stdout
err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
if err != nil {
saveInternalError(err)
return false
}
return true
}
// Get the Windows Version and Build Number
var (
winVersion, _, buildNumber = windows.RtlGetNtVersionNumbers()
)
// refer
//
// https://github.com/Delta456/box-cli-maker/blob/7b5a1ad8a016ce181e7d8b05e24b54ff60b4b38a/detect_windows.go#L30-L57
// https://github.com/gookit/color/issues/25#issuecomment-738727917
//
// detects the color level supported on Windows: cmd, powerShell
func detectSpecialTermColor(termVal string) (tl Level, needVTP bool) {
if os.Getenv("ConEmuANSI") == "ON" {
debugf("support True Color by ConEmuANSI=ON")
// ConEmuANSI is "ON" for generic ANSI support
// but True Color option is enabled by default
// I am just assuming that people wouldn't have disabled it
// Even if it is not enabled then ConEmu will auto round off
// accordingly
return terminfo.ColorLevelMillions, false
}
// Before Windows 10 Build Number 10586, console never supported ANSI Colors
if buildNumber < 10586 || winVersion < 10 {
// Detect if using ANSICON on older systems
if os.Getenv("ANSICON") != "" {
conVersion := os.Getenv("ANSICON_VER")
// 8-bit Colors were only supported after v1.81 release
if conVersion >= "181" {
return terminfo.ColorLevelHundreds, false
}
return terminfo.ColorLevelBasic, false
}
return terminfo.ColorLevelNone, false
}
// True Color is not available before build 14931 so fallback to 8-bit color.
if buildNumber < 14931 {
return terminfo.ColorLevelHundreds, true
}
// Windows 10 build 14931 is the first release that supports 16m/TrueColor
debugf("support True Color on windows version is >= build 14931")
return terminfo.ColorLevelMillions, true
}
/*************************************************************
* render full color code on Windows(8,16,24bit color)
*************************************************************/
// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters
const (
// equals to docs page's ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
EnableVirtualTerminalProcessingMode uint32 = 0x4
)
// EnableVirtualTerminalProcessing Enable virtual terminal processing
//
// ref from github.com/konsorten/go-windows-terminal-sequences
// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples
//
// Usage:
//
// err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
// // support print color text
// err = EnableVirtualTerminalProcessing(syscall.Stdout, false)
func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error {
var mode uint32
// Check if it is currently in the terminal
// err := syscall.GetConsoleMode(syscall.Stdout, &mode)
err := syscall.GetConsoleMode(stream, &mode)
if err != nil {
// fmt.Println("EnableVirtualTerminalProcessing", err)
return err
}
if enable {
mode |= EnableVirtualTerminalProcessingMode
} else {
mode &^= EnableVirtualTerminalProcessingMode
}
ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode))
if ret == 0 {
return err
}
return nil
}
// renderColorCodeOnCmd enable cmd color render.
// func renderColorCodeOnCmd(fn func()) {
// err := EnableVirtualTerminalProcessing(syscall.Stdout, true)
// // if is not in terminal, will clear color tag.
// if err != nil {
// // panic(err)
// fn()
// return
// }
//
// // force open color render
// old := ForceOpenColor()
// fn()
// // revert color setting
// supportColor = old
//
// err = EnableVirtualTerminalProcessing(syscall.Stdout, false)
// if err != nil {
// panic(err)
// }
// }
/*************************************************************
* render simple color code on Windows
*************************************************************/
// IsTty returns true if the given file descriptor is a terminal.
func IsTty(fd uintptr) bool {
initKernel32Proc()
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}
// IsTerminal returns true if the given file descriptor is a terminal.
//
// Usage:
//
// fd := os.Stdout.Fd()
// fd := uintptr(syscall.Stdout) // for Windows
// IsTerminal(fd)
func IsTerminal(fd uintptr) bool {
initKernel32Proc()
var st uint32
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0)
return r != 0 && e == 0
}

View File

@@ -1,25 +0,0 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta charset="UTF-8">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify/themes/vue.css">
<title>Color - A command-line color library with true color support, universal API methods and Windows support.</title>
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
repo: 'gookit/color',
maxLevel: 3,
// 加载 _navbar.md
// loadNavbar: true,
loadNavbar: '_examples/navbar.md',
// 加载 _sidebar.md
// loadSidebar: true,
}
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
</body>
</html>

View File

@@ -1,133 +0,0 @@
package color
import "fmt"
/*************************************************************
* colored message Printer
*************************************************************/
// PrinterFace interface
type PrinterFace interface {
fmt.Stringer
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")
type Printer struct {
// NoColor disable color.
NoColor bool
// Code color code string. eg "32;45;3"
Code string
}
// NewPrinter instance
func NewPrinter(colorCode string) *Printer {
return &Printer{Code: colorCode}
}
// String returns color code string. eg: "32;45;3"
func (p *Printer) String() string {
// panic("implement me")
return p.Code
}
// Sprint returns rendering colored messages
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 ...any) string {
return RenderString(p.String(), fmt.Sprintf(format, a...))
}
// Print rendering colored messages
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 ...any) {
doPrintV2(p.String(), fmt.Sprintf(format, a...))
}
// Println rendering colored messages with newline
func (p *Printer) Println(a ...any) {
doPrintlnV2(p.Code, a)
}
// IsEmpty color code
func (p *Printer) IsEmpty() bool {
return p.Code == ""
}
/*************************************************************
* SimplePrinter struct
*************************************************************/
// SimplePrinter use for quick use color print on inject to struct
type SimplePrinter struct{}
// Print message
func (s *SimplePrinter) Print(v ...any) {
Print(v...)
}
// Printf message
func (s *SimplePrinter) Printf(format string, v ...any) {
Printf(format, v...)
}
// Println message
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 ...any) {
Info.Printf(format, a...)
}
// Infoln message
func (s *SimplePrinter) Infoln(a ...any) {
Info.Println(a...)
}
// Warnf message
func (s *SimplePrinter) Warnf(format string, a ...any) {
Warn.Printf(format, a...)
}
// Warnln message
func (s *SimplePrinter) Warnln(a ...any) {
Warn.Println(a...)
}
// Errorf message
func (s *SimplePrinter) Errorf(format string, a ...any) {
Error.Printf(format, a...)
}
// Errorln message
func (s *SimplePrinter) Errorln(a ...any) {
Error.Println(a...)
}

View File

@@ -1,324 +0,0 @@
package color
import (
"fmt"
"strings"
)
/*************************************************************
* 16 color Style
*************************************************************/
// Style a 16 color style. can add: fg color, bg color, color options
//
// Example:
//
// 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")
func New(colors ...Color) Style {
return colors
}
// Save to global styles map
func (s Style) Save(name string) {
AddStyle(name, s)
}
// Add to global styles map
func (s *Style) Add(cs ...Color) {
*s = append(*s, cs...)
}
// Render colored text
//
// Usage:
//
// 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 with newline.
// 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 ...any) string {
return RenderWithSpaces(s.String(), a...)
}
// Sprint is alias of the 'Render'
func (s Style) Sprint(a ...any) string {
return RenderCode(s.String(), a...)
}
// Sprintf format and render message.
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 ...any) {
doPrintV2(s.String(), fmt.Sprint(a...))
}
// Printf render and print text
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 ...any) {
doPrintlnV2(s.String(), a)
}
// Code convert to code string. returns like "32;45;3"
func (s Style) Code() string {
return s.String()
}
// String convert to code string. returns like "32;45;3"
func (s Style) String() string {
return Colors2code(s...)
}
// IsEmpty style
func (s Style) IsEmpty() bool {
return len(s) == 0
}
/*************************************************************
* Theme(extended Style)
*************************************************************/
// Theme definition. extends from Style
type Theme struct {
// Name theme name
Name string
// Style for the theme
Style
}
// NewTheme instance
func NewTheme(name string, style Style) *Theme {
return &Theme{name, style}
}
// Save to themes map
func (t *Theme) Save() {
AddTheme(t.Name, t.Style)
}
// Tips use name as title, only apply style for name
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 ...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 ...any) {
title := strings.ToUpper(t.Name) + ":\n"
t.Println(title, fmt.Sprintf(format, a...))
}
/*************************************************************
* Theme: internal themes
*************************************************************/
// 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")
var (
// Info color style
Info = &Theme{"info", Style{OpReset, FgGreen}}
// Note color style
Note = &Theme{"note", Style{OpBold, FgLightCyan}}
// Warn color style
Warn = &Theme{"warning", Style{OpBold, FgYellow}}
// Light color style
Light = &Theme{"light", Style{FgLightWhite, BgBlack}}
// Error color style
Error = &Theme{"error", Style{FgLightWhite, BgRed}}
// Danger color style
Danger = &Theme{"danger", Style{OpBold, FgRed}}
// Debug color style
Debug = &Theme{"debug", Style{OpReset, FgCyan}}
// Notice color style
Notice = &Theme{"notice", Style{OpBold, FgCyan}}
// Comment color style
Comment = &Theme{"comment", Style{OpReset, FgYellow}}
// Success color style
Success = &Theme{"success", Style{OpBold, FgGreen}}
// Primary color style
Primary = &Theme{"primary", Style{OpReset, FgBlue}}
// Question color style
Question = &Theme{"question", Style{OpReset, FgMagenta}}
// Secondary color style
Secondary = &Theme{"secondary", Style{FgDarkGray}}
)
// Themes internal defined themes.
// Usage:
//
// color.Themes["info"].Println("message")
var Themes = map[string]*Theme{
"info": Info,
"note": Note,
"light": Light,
"error": Error,
"debug": Debug,
"danger": Danger,
"notice": Notice,
"success": Success,
"comment": Comment,
"primary": Primary,
"warning": Warn,
"question": Question,
"secondary": Secondary,
}
// AddTheme add a theme and style
func AddTheme(name string, style Style) {
Themes[name] = NewTheme(name, style)
Styles[name] = style
}
// GetTheme get defined theme by name
func GetTheme(name string) *Theme {
return Themes[name]
}
/*************************************************************
* internal styles
*************************************************************/
// Styles internal defined styles, like bootstrap styles.
// Usage:
//
// color.Styles["info"].Println("message")
var Styles = map[string]Style{
"info": {OpReset, FgGreen},
"note": {OpBold, FgLightCyan},
"light": {FgLightWhite, BgRed},
"error": {FgLightWhite, BgRed},
"danger": {OpBold, FgRed},
"notice": {OpBold, FgCyan},
"success": {OpBold, FgGreen},
"comment": {OpReset, FgMagenta},
"primary": {OpReset, FgBlue},
"warning": {OpBold, FgYellow},
"question": {OpReset, FgMagenta},
"secondary": {FgDarkGray},
}
// some style name alias
var styleAliases = map[string]string{
"err": "error",
"suc": "success",
"warn": "warning",
}
// AddStyle add a style
func AddStyle(name string, s Style) {
Styles[name] = s
}
// GetStyle get defined style by name
func GetStyle(name string) Style {
if s, ok := Styles[name]; ok {
return s
}
if realName, ok := styleAliases[name]; ok {
return Styles[realName]
}
// empty style
return New()
}
/*************************************************************
* color scheme
*************************************************************/
// Scheme struct
type Scheme struct {
Name string
Styles map[string]Style
}
// NewScheme create new Scheme
func NewScheme(name string, styles map[string]Style) *Scheme {
return &Scheme{Name: name, Styles: styles}
}
// NewDefaultScheme create an defuault color Scheme
func NewDefaultScheme(name string) *Scheme {
return NewScheme(name, map[string]Style{
"info": {OpReset, FgGreen},
"warn": {OpBold, FgYellow},
"error": {FgLightWhite, BgRed},
})
}
// Style get by name
func (s *Scheme) Style(name string) Style {
return s.Styles[name]
}
// Infof message print
func (s *Scheme) Infof(format string, a ...any) {
s.Styles["info"].Printf(format, a...)
}
// Infoln message print
func (s *Scheme) Infoln(v ...any) {
s.Styles["info"].Println(v...)
}
// Warnf message print
func (s *Scheme) Warnf(format string, a ...any) {
s.Styles["warn"].Printf(format, a...)
}
// Warnln message print
func (s *Scheme) Warnln(v ...any) {
s.Styles["warn"].Println(v...)
}
// Errorf message print
func (s *Scheme) Errorf(format string, a ...any) {
s.Styles["error"].Printf(format, a...)
}
// Errorln message print
func (s *Scheme) Errorln(v ...any) {
s.Styles["error"].Println(v...)
}

View File

@@ -1,209 +0,0 @@
package color
import (
"fmt"
"io"
"log"
"strings"
)
// SetTerminal by given code.
func SetTerminal(code string) error {
if !Enable || !SupportColor() {
return nil
}
_, err := fmt.Fprintf(output, SettingTpl, code)
return err
}
// ResetTerminal terminal setting.
func ResetTerminal() error {
if !Enable || !SupportColor() {
return nil
}
_, err := fmt.Fprint(output, ResetSet)
return err
}
/*************************************************************
* print methods(will auto parse color tags)
*************************************************************/
// Print render color tag and print messages
func Print(a ...any) {
Fprint(output, a...)
}
// Printf format and print messages
func Printf(format string, a ...any) {
Fprintf(output, format, a...)
}
// Println messages with new line
func Println(a ...any) {
Fprintln(output, a...)
}
// Fprint print rendered messages to writer
//
// Notice: will ignore print error
func Fprint(w io.Writer, a ...any) {
_, err := fmt.Fprint(w, Render(a...))
saveInternalError(err)
}
// Fprintf print format and rendered messages to writer.
// Notice: will ignore print error
func Fprintf(w io.Writer, format string, a ...any) {
str := fmt.Sprintf(format, a...)
_, err := fmt.Fprint(w, ReplaceTag(str))
saveInternalError(err)
}
// Fprintln print rendered messages line to writer
// Notice: will ignore print error
func Fprintln(w io.Writer, a ...any) {
str := formatArgsForPrintln(a)
_, err := fmt.Fprintln(w, ReplaceTag(str))
saveInternalError(err)
}
// Lprint passes colored messages to a log.Logger for printing.
// Notice: should be goroutine safe
func Lprint(l *log.Logger, a ...any) {
l.Print(Render(a...))
}
// Render parse color tags, return rendered string.
//
// Usage:
//
// text := Render("<info>hello</> <cyan>world</>!")
// fmt.Println(text)
func Render(a ...any) string {
if len(a) == 0 {
return ""
}
return ReplaceTag(fmt.Sprint(a...))
}
// Sprint parse color tags, return rendered 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 ...any) string {
return ReplaceTag(fmt.Sprintf(format, a...))
}
// String alias of the ReplaceTag
func String(s string) string { return ReplaceTag(s) }
// Text alias of the ReplaceTag
func Text(s string) string { return ReplaceTag(s) }
// Uint8sToInts convert []uint8 to []int
// func Uint8sToInts(u8s []uint8 ) []int {
// ints := make([]int, len(u8s))
// for i, u8 := range u8s {
// ints[i] = int(u8)
// }
// return ints
// }
/*************************************************************
* helper methods for print
*************************************************************/
// new implementation, support render full color code on pwsh.exe, cmd.exe
func doPrintV2(code, str string) {
_, err := fmt.Fprint(output, RenderString(code, str))
saveInternalError(err)
}
// new implementation, support render full color code on pwsh.exe, cmd.exe
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 []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
}
/*************************************************************
* helper methods
*************************************************************/
// is on debug mode
// func isDebugMode() bool {
// return debugMode == "on"
// }
func debugf(f string, v ...any) {
if debugMode {
fmt.Print("COLOR_DEBUG: ")
fmt.Printf(f, v...)
fmt.Println()
}
}
// equals: return ok ? val1 : val2
func isValidUint8(val int) bool {
return val >= 0 && val < 256
}
// equals: return ok ? val1 : val2
func compareVal(ok bool, val1, val2 uint8) uint8 {
if ok {
return val1
}
return val2
}
// equals: return ok ? val1 : val2
func compareF64Val(ok bool, val1, val2 float64) float64 {
if ok {
return val1
}
return val2
}
func saveInternalError(err error) {
if err != nil {
debugf("inner error: %s", err.Error())
innerErrs = append(innerErrs, err)
}
}
func stringToArr(str, sep string) (arr []string) {
str = strings.TrimSpace(str)
if str == "" {
return
}
ss := strings.Split(str, sep)
for _, val := range ss {
if val = strings.TrimSpace(val); val != "" {
arr = append(arr, val)
}
}
return
}

View File

@@ -421,7 +421,10 @@ config.WithOptions(func(opt *Options) {
### Options: Parse default
Support parse default value by struct tag `default`
Support parse default value by struct tag `default`, and support parse fields in sub struct.
> **NOTE**⚠️ If you want to parse a sub-struct, you need to set the `default:""` flag on the parent struct,
> otherwise the fields of the sub-struct will not be resolved.
```go
// add option: config.ParseDefault

View File

@@ -26,7 +26,7 @@
- `Readonly` 支持设置配置数据只读
- `EnableCache` 支持设置配置数据缓存
- `ParseEnv` 支持获取时自动解析string值里的ENV变量(`shell: ${SHELL}` -> `shell: /bin/zsh`)
- `ParseDefault` 支持在绑定数据到结构体时解析默认值 (tag: `default:"def_value"`, 配合ParseEnv也支持ENV变量)
- `ParseDefault` 支持在绑定数据到结构体时解析默认值 (tag: `default:"def_value"`, 配合`ParseEnv`也支持ENV变量)
- `ParseTime` 支持绑定数据到struct时自动转换 `10s`,`2m``time.Duration`
- 完整选项设置请查看 `config.Options`
- 支持将全部或部分配置数据绑定到结构体 `config.BindStruct("key", &s)`
@@ -414,7 +414,9 @@ config.WithOptions(func(opt *Options) {
### 选项: 解析默认值
NEW: 支持通过结构标签 `default` 解析并设置默认值
NEW: 支持通过结构标签 `default` 解析并设置默认值,支持嵌套解析处理。
> 注意 ⚠️ 如果想要解析子结构体字段,需要对父结构体设置 `default:""` 标记,否则不会解析子结构体的字段。
```go
// add option: config.ParseDefault

View File

@@ -7,8 +7,8 @@ import (
"io"
"os"
"github.com/go-viper/mapstructure/v2"
"github.com/gookit/goutil/structs"
"github.com/mitchellh/mapstructure"
)
// Decode all config data to the dst ptr
@@ -37,17 +37,13 @@ func (c *Config) Decode(dst any) error {
func MapStruct(key string, dst any) error { return dc.MapStruct(key, dst) }
// MapStruct alias method of the 'Structure'
func (c *Config) MapStruct(key string, dst any) error {
return c.Structure(key, dst)
}
func (c *Config) MapStruct(key string, dst any) error { return c.Structure(key, dst) }
// BindStruct alias method of the 'Structure'
func BindStruct(key string, dst any) error { return dc.BindStruct(key, dst) }
// BindStruct alias method of the 'Structure'
func (c *Config) BindStruct(key string, dst any) error {
return c.Structure(key, dst)
}
func (c *Config) BindStruct(key string, dst any) error { return c.Structure(key, dst) }
// MapOnExists mapping data to the dst structure only on key exists.
func MapOnExists(key string, dst any) error {
@@ -63,7 +59,6 @@ func (c *Config) MapOnExists(key string, dst any) error {
if err != nil && err == ErrNotFound {
return nil
}
return err
}
@@ -86,6 +81,7 @@ func (c *Config) Structure(key string, dst any) (err error) {
if c.opts.ParseDefault {
err = structs.InitDefaults(dst, func(opt *structs.InitOptions) {
opt.ParseEnv = c.opts.ParseEnv
opt.ParseTime = c.opts.ParseTime // add ParseTime support on parse default value
})
}
return
@@ -116,6 +112,7 @@ func (c *Config) Structure(key string, dst any) (err error) {
if c.opts.ParseDefault {
err = structs.InitDefaults(dst, func(opt *structs.InitOptions) {
opt.ParseEnv = c.opts.ParseEnv
opt.ParseTime = c.opts.ParseTime
})
}
return err

View File

@@ -4,8 +4,8 @@ import (
"strings"
"dario.cat/mergo"
"github.com/go-viper/mapstructure/v2"
"github.com/gookit/goutil"
"github.com/mitchellh/mapstructure"
)
// there are some event names for config data changed.
@@ -33,12 +33,15 @@ type Options struct {
// ParseDefault tag on binding data to struct. default: false
//
// - tag: default
//
// NOTE: If you want to parse a substruct, you need to set the `default:""` flag on the struct,
// otherwise the fields that will not resolve to it will not be resolved.
ParseDefault bool
// Readonly config is readonly. default: false
Readonly bool
// EnableCache enable config data cache. default: false
EnableCache bool
// ParseKey support key path, allow find value by key path. default: true
// ParseKey support key path, allow finding value by key path. default: true
//
// - eg: 'key.sub' will find `map[key]sub`
ParseKey bool

View File

@@ -4,10 +4,10 @@ import (
"os"
"reflect"
"strings"
"time"
"github.com/go-viper/mapstructure/v2"
"github.com/gookit/goutil/envutil"
"github.com/mitchellh/mapstructure"
"github.com/gookit/goutil/reflects"
)
// ValDecodeHookFunc returns a mapstructure.DecodeHookFunc
@@ -31,15 +31,9 @@ func ValDecodeHookFunc(parseEnv, parseTime bool) mapstructure.DecodeHookFunc {
return str, nil
}
// start char is number(1-9)
if str[0] > '0' && str[0] <= '9' {
// parse time string. eg: 10s
if parseTime && t.Kind() == reflect.Int64 {
dur, err := time.ParseDuration(str)
if err == nil {
return dur, nil
}
}
// feat: support parse time or duration string. eg: 10s
if parseTime && str[0] > '0' && str[0] <= '9' {
return reflects.ToTimeOrDuration(str, t)
}
return str, nil
}

View File

@@ -7,56 +7,63 @@
[![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master)
[![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](https://pkg.go.dev/github.com/gookit/goutil)
💪 Useful utils(**700+**) package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more.
💪 Useful utils(**800+**) package for the Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem, system, testing and more.
> **[中文说明](README.zh-CN.md)**
**Basic packages:**
## Packages
### Basic packages
- [`arrutil`](arrutil): Array/Slice util functions. eg: check, convert, formatting, enum, collections
- [`cliutil`](cliutil) Command-line util functions. eg: colored print, read input, exec command
- [`envutil`](envutil) ENV util for current runtime env information. eg: get one, get info, parse var
- [`fmtutil`](fmtutil) Format data util functions. eg: data, size, time
- [`fsutil`](fsutil) Filesystem util functions, quick create, read and write file. eg: file and dir check, operate
- [`goinfo`](goinfo) provide some standard util functions for go.
- [`jsonutil`](jsonutil) Provide some util functions for quick read, write, encode, decode JSON data.
- [`byteutil`](byteutil): Provide some common bytes util functions. eg: convert, check and more
- [`maputil`](maputil) Map data util functions. eg: convert, sub-value get, simple merge
- [`mathutil`](mathutil) Math(int, number) util functions. eg: convert, math calc, random
- [`netutil`](netutil) Network util functions. eg: Ip, IpV4, IpV6, Mac, Port, Hostname, etc.
- [`reflects`](reflects) Provide extends reflect util functions.
- [`structs`](structs) Provide some extends util functions for struct. eg: tag parse, struct data init
- [`strutil`](strutil) String util functions. eg: bytes, check, convert, encode, format and more
- [`sysutil`](sysutil) System util functions. eg: sysenv, exec, user, process
- [`cliutil`](cliutil) Command-line util functions. eg: colored print, read input, exec command
- [`envutil`](envutil) ENV util for current runtime env information. eg: get one, get info, parse var
- [`fsutil`](fsutil) Filesystem util functions, quick create, read and write file. eg: file and dir check, operate
- [`jsonutil`](jsonutil) Provide some util functions for quick read, write, encode, decode JSON data.
**Extra packages:**
### Debug & Test & Errors
- [`dump`](dump): GO value printing tool. print slice, map will auto wrap each element and display the call location
- [`errorx`](errorx) Provide an enhanced error implements for go, allow with stacktrace and wrap another error.
- [`assert`](testutil/assert) Provides commonly asserts functions for help testing
- [`testutil`](testutil) Test help util functions. eg: http test, mock ENV value
- [`fakeobj`](x/fakeobj) provides a fake object for testing. such as fake fs.File, fs.FileInfo, fs.DirEntry etc.
### Extra Tools packages
- [`cflag`](cflag): Wraps and extends go `flag.FlagSet` to build simple command line applications
- cli util:
- [cmdline](cliutil/cmdline) Provide cmdline parse, args build to cmdline
- [`dump`](dump): GO value printing tool. print slice, map will auto wrap each element and display the call location
- [`encodes`](encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc.
- [`errorx`](errorx) Provide an enhanced error implements for go, allow with stacktrace and wrap another error.
- file util:
- [`finder`](fsutil/finder) Provides a simple and convenient filedir lookup function, supports filtering, excluding, matching, ignoring, etc.
- net util:
- [httpreq](netutil/httpreq) An easier-to-use HTTP client that wraps http.Client, and with some http utils.
- string util:
- [textscan](strutil/textscan) Implemented a parser that quickly scans and analyzes text content. It can be used to parse INI, Properties and other formats
- [textutil](strutil/textutil) Provide some extensions text handle util functions. eg: text replace, etc.
- [syncs](syncs) Provides synchronization primitives util functions.
- system util:
- [clipboard](sysutil/clipboard) Provide a simple clipboard read and write operations.
- [cmdr](sysutil/cmdr) Provide for quick build and run a cmd, batch run multi cmd tasks
- [process](sysutil/process) Provide some process handle util functions.
- [`testutil`](testutil) Test help util functions. eg: http test, mock ENV value
- [assert](testutil/assert) Provides commonly asserts functions for help testing
- [fakeobj](testutil/fakeobj) provides a fake object for testing. such as fake fs.File, fs.FileInfo, fs.DirEntry etc.
- [`ccolor`](x/ccolor): Simple command-line color output library that uses ANSI color codes to output text with colors.
- [`timex`](timex) Provides an enhanced time.Time implementation. Add more commonly used functional methods
- Provides datetime format parsing like `Y-m-d H:i:s`
- such as: DayStart(), DayAfter(), DayAgo(), DateFormat() and more.
- [httpreq](netutil/httpreq) An easier-to-use HTTP client that wraps http.Client, and with some http utils.
- [syncs](syncs) Provides synchronization primitives util functions.
**More ...**
- [`cmdline`](cliutil/cmdline) Provide cmdline parse, args build to cmdline
- [`encodes`](encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc.
- [`finder`](x/finder) Provides a simple and convenient file/dir lookup function, supports filtering, excluding, matching, ignoring, etc.
- [`netutil`](netutil) Network util functions. eg: Ip, IpV4, IpV6, Mac, Port, Hostname, etc.
- [textutil](strutil/textutil) Provide some extensions text handle util functions. eg: text replace, etc.
- [textscan](strutil/textscan) Implemented a parser that quickly scans and analyzes text content. It can be used to parse INI, Properties and other formats
- [`cmdr`](sysutil/cmdr) Provide for quick build and run a cmd, batch run multi cmd tasks
- [`clipboard`](x/clipboard) Provide a simple clipboard read and write operations.
- [`process`](sysutil/process) Provide some process handle util functions.
- [`fmtutil`](x/fmtutil) Format data util functions. eg: data, size, time
- [`goinfo`](x/goinfo) provide some standard util functions for go.
## Go Doc
Please see [Go doc](https://pkg.go.dev/github.com/gookit/goutil)
Please see [Go doc](https://pkg.go.dev/github.com/gookit/goutil).
Wiki docs on [DeepWiki - gookit/goutil](https://deepwiki.com/gookit/goutil)
## Install
@@ -166,8 +173,9 @@ func Remove[T comdef.Compared](ls []T, val T) []T
func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T
func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V
func Unique[T ~string | comdef.XintOrFloat](list []T) []T
func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int
func Unique[T comdef.NumberOrString](list []T) []T
func IndexOf[T comdef.NumberOrString](val T, list []T) int
func FirstOr[T any](list []T, defVal ...T) T
// source at arrutil/strings.go
func StringsToAnys(ss []string) []any
func StringsToSlice(ss []string) []any
@@ -212,6 +220,7 @@ ss, err := arrutil.ToStrings([]int{1, 2}) // ss: []string{"1", "2"}
func NewBuffer() *Buffer
// source at byteutil/byteutil.go
func Md5(src any) []byte
func Md5Sum(src any) []byte
func ShortMd5(src any) []byte
func Random(length int) ([]byte, error)
func FirstLine(bs []byte) []byte
@@ -221,6 +230,7 @@ func SafeCut(bs []byte, sep byte) (before, after []byte)
func SafeCuts(bs []byte, sep []byte) (before, after []byte)
// source at byteutil/check.go
func IsNumChar(c byte) bool
func IsAlphaChar(c byte) bool
// source at byteutil/conv.go
func StrOrErr(bs []byte, err error) (string, error)
func SafeString(bs []byte, err error) string
@@ -242,7 +252,7 @@ func NewChanPool(chSize int, width int, capWidth int) *ChanPool
```go
// source at cflag/app.go
func NewApp(fns ...func(app *App)) *App
func NewCmd(name, desc string) *Cmd
func NewCmd(name, desc string, runFunc ...func(c *Cmd) error) *Cmd
// source at cflag/cflag.go
func SetDebug(open bool)
func New(fns ...func(c *CFlags)) *CFlags
@@ -275,7 +285,7 @@ func ReplaceShorts(args []string, shortsMap map[string]string) []string
`cflag` usage please see [cflag/README.md](cflag/README.md)
### CLI/Console
### CLI Utils
> Package `github.com/gookit/goutil/cliutil`
@@ -371,7 +381,7 @@ Build line: ./myapp -a val0 -m "this is message" arg0
> More, please see [./cliutil/README](cliutil/README.md)
### Dumper
### Var Dumper
> Package `github.com/gookit/goutil/dump`
@@ -446,20 +456,6 @@ Preview:
![](dump/_examples/preview-nested-struct.png)
### Encodes
> Package `github.com/gookit/goutil/encodes`
```go
// source at encodes/encodes.go
func B32Encode(str string) string
func B32Decode(str string) string
func B64Encode(str string) string
func B64EncodeBytes(src []byte) []byte
func B64Decode(str string) string
func B64DecodeBytes(str []byte) []byte
```
### ENV/Environment
> Package `github.com/gookit/goutil/envutil`
@@ -471,14 +467,16 @@ func ParseOrErr(val string) (string, error)
func ParseValue(val string) string
func VarParse(val string) string
func ParseEnvValue(val string) string
func SetEnvMap(mp map[string]string)
func SetEnvs(kvPairs ...string)
func UnsetEnvs(keys ...string)
func SplitText2map(text string) map[string]string
func SplitLineToKv(line string) (string, string)
// source at envutil/get.go
func Getenv(name string, def ...string) string
func MustGet(name string) string
func GetInt(name string, def ...int) int
func GetBool(name string, def ...bool) bool
func GetOne(names []string, defVal ...string) string
func GetMulti(names ...string) map[string]string
func OnExist(name string, fn func(val string)) bool
func EnvPaths() []string
func EnvMap() map[string]string
func Environ() map[string]string
@@ -490,7 +488,6 @@ func IsWindows() bool
func IsMac() bool
func IsLinux() bool
func IsMSys() bool
func IsWSL() bool
func IsTerminal(fd uintptr) bool
func StdIsTerminal() bool
func IsConsole(out io.Writer) bool
@@ -499,6 +496,12 @@ func IsSupportColor() bool
func IsSupport256Color() bool
func IsSupportTrueColor() bool
func IsGithubActions() bool
// source at envutil/set.go
func SetEnvMap(mp map[string]string)
func SetEnvs(kvPairs ...string)
func UnsetEnvs(keys ...string)
func LoadText(text string)
func LoadString(line string) bool
```
#### ENV Util Usage
@@ -562,7 +565,8 @@ func Err(msg string) error
func Raw(msg string) error
func Ef(tpl string, vars ...any) error
func Errf(tpl string, vars ...any) error
func Rawf(tpl string, vars ...any) error
func Rf(tpl string, vs ...any) error
func Rawf(tpl string, vs ...any) error
func Cause(err error) error
func Unwrap(err error) error
func Previous(err error) error
@@ -651,24 +655,6 @@ runtime.goexit()
```
### Format Utils
> Package `github.com/gookit/goutil/fmtutil`
```go
// source at fmtutil/fmtutil.go
func StringOrJSON(v any) ([]byte, error)
// source at fmtutil/format.go
func DataSize(size uint64) string
func SizeToString(size uint64) string
func StringToByte(sizeStr string) uint64
func ParseByte(sizeStr string) uint64
func PrettyJSON(v any) (string, error)
func ArgsWithSpaces(vs []any) (message string)
// source at fmtutil/time.go
func HowLongAgo(sec int64) string
```
### File System
> Package `github.com/gookit/goutil/fsutil`
@@ -688,7 +674,7 @@ func PathMatch(pattern, s string) bool
func NewEntry(fPath string, ent fs.DirEntry) Entry
func NewFileInfo(fPath string, info fs.FileInfo) FileInfo
// source at fsutil/find.go
func FilePathInDirs(file string, dirs ...string) string
func FilePathInDirs(fPath string, dirs ...string) string
func FirstExists(paths ...string) string
func FirstExistsDir(paths ...string) string
func FirstExistsFile(paths ...string) string
@@ -707,20 +693,25 @@ func ExcludeDotFile(_ string, ent fs.DirEntry) bool
func ExcludeSuffix(ss ...string) FilterFunc
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
func FileInDirs(paths []string, names ...string) string
// source at fsutil/fsutil.go
func JoinPaths(elem ...string) string
func JoinSubPaths(basePath string, elem ...string) string
func JoinPaths3(basePath, secPath string, elems ...string) string
func JoinSubPaths(basePath string, elems ...string) string
func SlashPath(path string) string
func UnixPath(path string) string
func ToAbsPath(p string) string
func Must2(_ any, err error)
// source at fsutil/info.go
func DirPath(fpath string) string
func Dir(fpath string) string
func PathName(fpath string) string
func Name(fpath string) string
func FileExt(fpath string) string
func Extname(fpath string) string
func Suffix(fpath string) string
func DirPath(fPath string) string
func Dir(fPath string) string
func PathName(fPath string) string
func PathNoExt(fPath string) string
func Name(fPath string) string
func NameNoExt(fPath string) string
func FileExt(fPath string) string
func Extname(fPath string) string
func Suffix(fPath string) string
func Expand(pathStr string) string
func ExpandPath(pathStr string) string
func ResolvePath(pathStr string) string
@@ -781,6 +772,7 @@ func OSTempDir(pattern string) (string, error)
func TempDir(dir, pattern string) (string, error)
func MustSave(filePath string, data any, optFns ...OpenOptionFunc)
func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error
func WriteData(filePath string, data any, fileFlag ...int) (int, error)
func PutContents(filePath string, data any, fileFlag ...int) (int, error)
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error
func WriteOSFile(f *os.File, data any) (n int, err error)
@@ -819,27 +811,6 @@ func main() {
```
### Go Info
> Package `github.com/gookit/goutil/goinfo`
```go
// source at goinfo/gofunc.go
func FuncName(fn any) string
func CutFuncName(fullFcName string) (pkgPath, shortFnName string)
func PkgName(fullFcName string) string
func GoodFuncName(name string) bool
// source at goinfo/goinfo.go
func GoVersion() string
func ParseGoVersion(line string) (*GoInfo, error)
func OsGoInfo() (*GoInfo, error)
// source at goinfo/stack.go
func GetCallStacks(all bool) []byte
func GetCallerInfo(skip int) string
func SimpleCallersInfo(skip, num int) []string
func GetCallersInfo(skip, max int) []string
```
### JSON Utils
> Package `github.com/gookit/goutil/jsonutil`
@@ -855,6 +826,7 @@ func EncodeUnescapeHTML(v any) ([]byte, error)
func Decode(bts []byte, ptr any) error
func DecodeString(str string, ptr any) error
func DecodeReader(r io.Reader, ptr any) error
func DecodeFile(file string, ptr any) error
// source at jsonutil/jsonutil.go
func WriteFile(filePath string, data any) error
func WritePretty(filePath string, data any) error
@@ -880,9 +852,14 @@ func HasOneKey(mp any, keys ...any) (ok bool, key any)
func HasAllKeys(mp any, keys ...any) (ok bool, noKey any)
// source at maputil/convert.go
func KeyToLower(src map[string]string) map[string]string
func AnyToStrMap(src any) map[string]string
func ToStringMap(src map[string]any) map[string]string
func ToL2StringMap(groupsMap map[string]any) map[string]map[string]string
func CombineToSMap(keys, values []string) SMap
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V
func SliceToSMap(kvPairs ...string) map[string]string
func SliceToMap(kvPairs ...any) map[string]any
func SliceToTypeMap[T any](valFunc func(any) T, kvPairs ...any) map[string]T
func ToAnyMap(mp any) map[string]any
func TryAnyMap(mp any) (map[string]any, error)
func HTTPQueryString(data map[string]any) string
@@ -901,14 +878,21 @@ func GetFromAny(path string, data any) (val any, ok bool)
func GetByPath(path string, mp map[string]any) (val any, ok bool)
func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool)
func Keys(mp any) (keys []string)
func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K)
func FirstKey[T any](mp map[string]T) string
func Values(mp any) (values []any)
func TypedValues[K comdef.SimpleType, V any](mp map[K]V) (values []V)
func EachAnyMap(mp any, fn func(key string, val any))
func EachTypedMap[K comdef.SimpleType, V any](mp map[K]V, fn func(key K, val V))
// source at maputil/maputil.go
func SimpleMerge(src, dst map[string]any) map[string]any
func Merge1level(mps ...map[string]any) map[string]any
func DeepMerge(src, dst map[string]any, deep int) map[string]any
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeStrMap(src, dst map[string]string) map[string]string
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeMultiSMap(mps ...map[string]string) map[string]string
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string
func FilterSMap(sm map[string]string) map[string]string
func MakeByPath(path string, val any) (mp map[string]any)
func MakeByKeys(keys []string, val any) (mp map[string]any)
@@ -922,21 +906,23 @@ func SetByKeys(mp *map[string]any, keys []string, val any) (err error)
> Package `github.com/gookit/goutil/mathutil`
```go
// source at mathutil/calc.go
func Abs[T comdef.Int](val T) T
// source at mathutil/check.go
func IsNumeric(c byte) bool
func Compare(first, second any, op string) bool
func CompInt[T comdef.Xint](first, second T, op string) (ok bool)
func CompInt64(first, second int64, op string) bool
func CompFloat[T comdef.Float](first, second T, op string) (ok bool)
func CompValue[T comdef.XintOrFloat](first, second T, op string) (ok bool)
func InRange[T comdef.IntOrFloat](val, min, max T) bool
func OutRange[T comdef.IntOrFloat](val, min, max T) bool
func CompValue[T comdef.Number](first, second T, op string) (ok bool)
func InRange[T comdef.Number](val, min, max T) bool
func OutRange[T comdef.Number](val, min, max T) bool
func InUintRange[T comdef.Uint](val, min, max T) bool
// source at mathutil/compare.go
func Min[T comdef.XintOrFloat](x, y T) T
func Max[T comdef.XintOrFloat](x, y T) T
func SwapMin[T comdef.XintOrFloat](x, y T) (T, T)
func SwapMax[T comdef.XintOrFloat](x, y T) (T, T)
func Min[T comdef.Number](x, y T) T
func Max[T comdef.Number](x, y T) T
func SwapMin[T comdef.Number](x, y T) (T, T)
func SwapMax[T comdef.Number](x, y T) (T, T)
func MaxInt(x, y int) int
func SwapMaxInt(x, y int) (int, int)
func MaxI64(x, y int64) int64
@@ -1011,15 +997,15 @@ func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error)
func DataSize(size uint64) string
func HowLongAgo(sec int64) string
// source at mathutil/mathutil.go
func OrElse[T comdef.XintOrFloat](val, defVal T) T
func ZeroOr[T comdef.XintOrFloat](val, defVal T) T
func LessOr[T comdef.XintOrFloat](val, max, devVal T) T
func LteOr[T comdef.XintOrFloat](val, max, devVal T) T
func GreaterOr[T comdef.XintOrFloat](val, min, defVal T) T
func GteOr[T comdef.XintOrFloat](val, min, defVal T) T
func Mul[T1, T2 comdef.XintOrFloat](a T1, b T2) float64
func OrElse[T comdef.Number](val, defVal T) T
func ZeroOr[T comdef.Number](val, defVal T) T
func LessOr[T comdef.Number](val, max, devVal T) T
func LteOr[T comdef.Number](val, max, devVal T) T
func GreaterOr[T comdef.Number](val, min, defVal T) T
func GteOr[T comdef.Number](val, min, defVal T) T
func Mul[T1, T2 comdef.Number](a T1, b T2) float64
func MulF2i(a, b float64) int
func Div[T1, T2 comdef.XintOrFloat](a T1, b T2) float64
func Div[T1, T2 comdef.Number](a T1, b T2) float64
func DivInt[T comdef.Integer](a, b T) int
func DivF2i(a, b float64) int
func Percent(val, total int) float64
@@ -1028,8 +1014,6 @@ func RandomInt(min, max int) int
func RandInt(min, max int) int
func RandIntWithSeed(min, max int, seed int64) int
func RandomIntWithSeed(min, max int, seed int64) int
// source at mathutil/value.go
func New[T comdef.IntOrFloat](v T) *Num[T]
```
### Reflects
@@ -1038,6 +1022,8 @@ func New[T comdef.IntOrFloat](v T) *Num[T]
```go
// source at reflects/check.go
func IsTimeType(t reflect.Type) bool
func IsDurationType(t reflect.Type) bool
func HasChild(v reflect.Value) bool
func IsArrayOrSlice(k reflect.Kind) bool
func IsSimpleKind(k reflect.Kind) bool
@@ -1046,6 +1032,7 @@ func IsIntLike(k reflect.Kind) bool
func IsIntx(k reflect.Kind) bool
func IsUintX(k reflect.Kind) bool
func IsNil(v reflect.Value) bool
func IsValidPtr(v reflect.Value) bool
func CanBeNil(typ reflect.Type) bool
func IsFunc(val any) bool
func IsEqual(src, dst any) bool
@@ -1056,12 +1043,13 @@ func BaseTypeVal(v reflect.Value) (value any, err error)
func ToBaseVal(v reflect.Value) (value any, err error)
func ConvToType(val any, typ reflect.Type) (rv reflect.Value, err error)
func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error)
func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error)
func ConvToKind(val any, kind reflect.Kind) (rv reflect.Value, err error)
func ValueByKind(val any, kind reflect.Kind) (reflect.Value, error)
func ConvToKind(val any, kind reflect.Kind, fallback ...ConvFunc) (rv reflect.Value, err error)
func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error)
func String(rv reflect.Value) string
func ToString(rv reflect.Value) (str string, err error)
func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error)
func ToTimeOrDuration(str string, typ reflect.Type) (any, error)
// source at reflects/func.go
func NewFunc(fn any) *FuncX
func Call2(fn reflect.Value, args []reflect.Value) (reflect.Value, error)
@@ -1069,8 +1057,9 @@ func Call(fn reflect.Value, args []reflect.Value, opt *CallOpt) ([]reflect.Value
func SafeCall2(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error)
func SafeCall(fun reflect.Value, args []reflect.Value) (ret []reflect.Value, err error)
// source at reflects/map.go
func EachMap(mp reflect.Value, fn func(key, val reflect.Value))
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any))
func TryAnyMap(mp reflect.Value) (map[string]any, error)
func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) (err error)
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) error
func FlatMap(rv reflect.Value, fn FlatFunc)
// source at reflects/slice.go
func MakeSliceByElem(elTyp reflect.Type, len, cap int) reflect.Value
@@ -1097,32 +1086,6 @@ func Wrap(rv reflect.Value) Value
func ValueOf(v any) Value
```
### Stdio
> Package `github.com/gookit/goutil/stdio`
```go
// source at stdio/ioutil.go
func QuietFprint(w io.Writer, a ...any)
func QuietFprintf(w io.Writer, tpl string, vs ...any)
func QuietFprintln(w io.Writer, a ...any)
func QuietWriteString(w io.Writer, ss ...string)
// source at stdio/stdio.go
func DiscardReader(src io.Reader)
func ReadString(r io.Reader) string
func MustReadReader(r io.Reader) []byte
func NewIOReader(in any) io.Reader
func NewScanner(in any) *bufio.Scanner
func WriteByte(b byte)
func WriteBytes(bs []byte)
func WritelnBytes(bs []byte)
func WriteString(s string)
func Writeln(s string)
// source at stdio/writer.go
func WrapW(w io.Writer) *WriteWrapper
func NewWriteWrapper(w io.Writer) *WriteWrapper
```
### Structs
> Package `github.com/gookit/goutil/structs`
@@ -1139,8 +1102,10 @@ func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error)
func MustToSMap(st any, optFns ...MapOptFunc) map[string]string
func ToString(st any, optFns ...MapOptFunc) string
func WithMapTagName(tagName string) MapOptFunc
func WithUserFunc(fn CustomUserFunc) MapOptFunc
func MergeAnonymous(opt *MapOptions)
func ExportPrivate(opt *MapOptions)
func WithIgnoreEmpty(opt *MapOptions)
func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error)
// source at structs/copy.go
func MapStruct(srcSt, dstSt any)
@@ -1171,6 +1136,8 @@ func WrapValue(rv reflect.Value) *Wrapper
// source at structs/writer.go
func NewWriter(ptr any) *Wrapper
func WithParseDefault(opt *SetOptions)
func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc
func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error
func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
```
@@ -1180,10 +1147,12 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
```go
// source at strutil/bytes.go
func NewBuffer() *Buffer
func NewBuffer(initSize ...int) *Buffer
func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool
// source at strutil/check.go
func IsNumChar(c byte) bool
func IsInt(s string) bool
func IsFloat(s string) bool
func IsNumeric(s string) bool
func IsAlphabet(char uint8) bool
func IsAlphaNum(c uint8) bool
@@ -1195,8 +1164,10 @@ func IContains(s, sub string) bool
func ContainsByte(s string, c byte) bool
func ContainsOne(s string, subs []string) bool
func HasOneSub(s string, subs []string) bool
func IContainsOne(s string, subs []string) bool
func ContainsAll(s string, subs []string) bool
func HasAllSubs(s string, subs []string) bool
func IContainsAll(s string, subs []string) bool
func IsStartsOf(s string, prefixes []string) bool
func HasOnePrefix(s string, prefixes []string) bool
func HasPrefix(s string, prefix string) bool
@@ -1214,6 +1185,7 @@ func IsSymbol(r rune) bool
func HasEmpty(ss ...string) bool
func IsAllEmpty(ss ...string) bool
func IsVersion(s string) bool
func IsVarName(s string) bool
func Compare(s1, s2, op string) bool
func VersionCompare(v1, v2, op string) bool
func SimpleMatch(s string, keywords []string) bool
@@ -1231,6 +1203,7 @@ func Quote(s string) string
func Unquote(s string) string
func Join(sep string, ss ...string) string
func JoinList(sep string, ss []string) string
func JoinComma(ss []string) string
func JoinAny(sep string, parts ...any) string
func Implode(sep string, ss ...string) string
func String(val any) (string, error)
@@ -1330,6 +1303,7 @@ func IndentBytes(b, prefix []byte) []byte
func MicroTimeID() string
func MicroTimeHexID() string
func MTimeHexID() string
func MTimeBase36() string
func MTimeBaseID(toBase int) string
func DatetimeNo(prefix string) string
func DateSN(prefix string) string
@@ -1338,6 +1312,8 @@ func DateSNV2(prefix string, extBase ...int) string
func Md5(src any) string
func MD5(src any) string
func GenMd5(src any) string
func Md5Simple(src any) string
func Md5Base62(src any) string
func Md5Bytes(src any) []byte
func ShortMd5(src any) string
func HashPasswd(pwd, key string) string
@@ -1387,6 +1363,7 @@ func RunesWidth(rs []rune) (w int)
func Truncate(s string, w int, tail string) string
func TextTruncate(s string, w int, tail string) string
func Utf8Truncate(s string, w int, tail string) string
func Chunk[T ~string](s T, size int) []T
func TextSplit(s string, w int) []string
func Utf8Split(s string, w int) []string
func TextWrap(s string, w int) string
@@ -1408,6 +1385,7 @@ func SplitNValid(s, sep string, n int) (ss []string)
func SplitN(s, sep string, n int) (ss []string)
func SplitTrimmed(s, sep string) (ss []string)
func SplitNTrimmed(s, sep string, n int) (ss []string)
func SplitByWhitespace(s string) []string
func Substr(s string, pos, length int) string
func SplitInlineComment(val string, strict ...bool) (string, string)
func FirstLine(output string) string
@@ -1432,12 +1410,12 @@ func SubstrCount(s, substr string, params ...uint64) (int, error)
```go
// source at syncs/chan.go
func WaitCloseSignals(onClose func(sig os.Signal))
func Go(f func() error) error
// source at syncs/group.go
func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context)
func NewErrGroup(limit ...int) *ErrGroup
// source at syncs/signal.go
func WaitCloseSignals(onClose func(sig os.Signal), sigCh ...chan os.Signal)
func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error))
```
@@ -1453,15 +1431,14 @@ func QuickExec(cmdLine string, workDir ...string) (string, error)
func ExecLine(cmdLine string, workDir ...string) (string, error)
func ExecCmd(binName string, args []string, workDir ...string) (string, error)
func ShellExec(cmdLine string, shells ...string) (string, error)
// source at sysutil/stack.go
func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo
// source at sysutil/sysenv.go
func IsMSys() bool
func IsWSL() bool
func IsConsole(out io.Writer) bool
func IsTerminal(fd uintptr) bool
func StdIsTerminal() bool
func Hostname() string
func CurrentShell(onlyName bool) (path string)
func CurrentShell(onlyName bool, fallbackShell ...string) string
func HasShellEnv(shell string) bool
func IsShellSpecialVar(c uint8) bool
func FindExecutable(binName string) (string, error)
@@ -1471,11 +1448,13 @@ func Getenv(name string, def ...string) string
func Environ() map[string]string
func EnvMapWith(newEnv map[string]string) map[string]string
func EnvPaths() []string
func SearchPath(keywords string, limit int) []string
func ToEnvPATH(paths []string) string
func SearchPath(keywords string, limit int, opts ...SearchPathOption) []string
// source at sysutil/sysgo.go
func GoVersion() string
func ParseGoVersion(line string) (*GoInfo, error)
func OsGoInfo() (*GoInfo, error)
func CallersInfos(skip, num int, filters ...goinfo.CallerFilterFunc) []*CallerInfo
// source at sysutil/sysutil.go
func Workdir() string
func BinDir() string
@@ -1484,16 +1463,16 @@ func BinFile() string
func Open(fileOrURL string) error
func OpenBrowser(fileOrURL string) error
func OpenFile(path string) error
// source at sysutil/sysutil_nonwin.go
func Kill(pid int, signal syscall.Signal) error
func ProcessExists(pid int) bool
// source at sysutil/sysutil_unix.go
// source at sysutil/sysutil_linux.go
func IsWin() bool
func IsWindows() bool
func IsMac() bool
func IsDarwin() bool
func IsLinux() bool
func OpenURL(URL string) error
// source at sysutil/sysutil_nonwin.go
func Kill(pid int, signal syscall.Signal) error
func ProcessExists(pid int) bool
// source at sysutil/user.go
func MustFindUser(uname string) *user.User
func LoginUser() *user.User
@@ -1501,12 +1480,13 @@ func CurrentUser() *user.User
func UHomeDir() string
func UserHomeDir() string
func HomeDir() string
func UserDir(subPath string) string
func UserCacheDir(subPath string) string
func UserConfigDir(subPath string) string
func UserDir(subPaths ...string) string
func UserCacheDir(subPaths ...string) string
func UserConfigDir(subPaths ...string) string
func ExpandPath(path string) string
func ExpandHome(path string) string
// source at sysutil/user_nonwin.go
func IsAdmin() bool
func ChangeUserByName(newUname string) error
func ChangeUserUidGid(newUID int, newGid int) error
func ChangeUserUIDGid(newUID int, newGid int) (err error)
@@ -1524,15 +1504,18 @@ func MockEnvValue(key, val string, fn func(nv string))
func MockEnvValues(kvMap map[string]string, fn func())
func MockOsEnvByText(envText string, fn func())
func MockOsEnv(mp map[string]string, fn func())
func SetOsEnvs(mp map[string]string) string
func RemoveTmpEnvs(tmpKey string)
func ClearOSEnv()
func RevertOSEnv()
func RunOnCleanEnv(runFn func())
func MockCleanOsEnv(mp map[string]string, fn func())
// source at testutil/httpmock.go
func NewHttpRequest(method, path string, data *MD) *http.Request
func NewHTTPRequest(method, path string, data *MD) *http.Request
func MockRequest(h http.Handler, method, path string, data *MD) *httptest.ResponseRecorder
func MockHttpServer() *EchoServer
func TestMain(m *testing.M)
func NewEchoServer() *httptest.Server
func NewEchoServer() *EchoServer
func BuildEchoReply(r *http.Request) *EchoReply
func ParseRespToReply(w *http.Response) *EchoReply
func ParseBodyToReply(bd io.ReadCloser) *EchoReply
@@ -1548,7 +1531,7 @@ func SetTimeLocalUTC()
func RestoreTimeLocal()
// source at testutil/writer.go
func NewTestWriter() *TestWriter
func NewDirEnt(fpath string, isDir ...bool) *fakeobj.DirEntry
func NewDirEnt(fPath string, isDir ...bool) *fakeobj.DirEntry
```
### Timex
@@ -1752,17 +1735,14 @@ Testing in docker:
```shell
cd goutil
docker run -ti -v $(pwd):/go/work golang:1.18
root@xx:/go/work# go test ./...
docker run -ti -v $(pwd):/go/goutil -e GOPROXY=https://goproxy.cn,direct golang:1.23
# on Windows
docker run -ti -v "${PWD}:/go/goutil" -e GOPROXY=https://goproxy.cn,direct golang:1.23
root@xx:/go/goutil# go test ./...
```
## Related
- https://github.com/duke-git/lancet
- https://github.com/samber/lo
- https://github.com/zyedidia/generic
- https://github.com/thoas/go-funk
## Gookit packages
- [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files
@@ -1778,6 +1758,13 @@ root@xx:/go/work# go test ./...
- [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
- More, please see https://github.com/gookit
## Related
- https://github.com/duke-git/lancet
- https://github.com/samber/lo
- https://github.com/zyedidia/generic
- https://github.com/thoas/go-funk
## License
[MIT](LICENSE)

View File

@@ -7,55 +7,63 @@
[![Coverage Status](https://coveralls.io/repos/github/gookit/goutil/badge.svg?branch=master)](https://coveralls.io/github/gookit/goutil?branch=master)
[![Go Reference](https://pkg.go.dev/badge/github.com/gookit/goutil.svg)](https://pkg.go.dev/github.com/gookit/goutil)
`goutil` Go 常用功能的扩展工具库(**700+**)。包含数字byte, 字符串slice/数组Map结构体反射文本文件错误时间日期测试特殊处理格式化常用信息获取等等。
`goutil` Go 常用功能的扩展工具库(**800+**)。包含数字byte, 字符串slice/数组Map结构体反射文本文件错误时间日期测试特殊处理格式化常用信息获取等等。
> **[EN README](README.md)**
**基础工具包**
## 工具包说明
### 基础工具包
- [`arrutil`](arrutil) array/slice 相关操作的函数工具包 如:类型转换,元素检查等等
- [`cliutil`](cliutil) CLI 的一些工具函数包. eg: read input, exec command
- [cmdline](cliutil/cmdline) 提供 cmdline 解析args 构建到 cmdline
- [`envutil`](envutil) ENV 信息获取判断工具包. eg: get one, get info, parse var
- [`fmtutil`](fmtutil) 格式化数据工具函数 eg数据size
- [`fsutil`](fsutil) 文件系统操作相关的工具函数包. eg: file and dir check, operate
- [`goinfo`](goinfo) 提供一些与Go info, runtime 相关的工具函数。
- [`jsonutil`](jsonutil) 一些用于快速读取、写入、编码、解码 JSON 数据的实用函数。
- [`byteutil`](byteutil): 提供一些常用的 byte 操作函数工具包. eg: convert, check and more
- [`maputil`](maputil) map 相关操作的函数工具包. eg: convert, sub-value get, simple merge
- [`mathutil`](mathutil) int/number 相关操作的函数工具包. eg: convert, math calc, random
- [`netutil`](netutil) Network util functions. eg: Ip, IpV4, IpV6, Mac, Port, Hostname, etc.
- [`reflects`](reflects) 提供一些扩展性的反射使用工具函数.
- [`structs`](structs) 为 struct 提供一些扩展 util 函数。 eg: tag parse, struct data
- [`strutil`](strutil) string 相关操作的函数工具包. eg: bytes, check, convert, encode, format and more
- [`cliutil`](cliutil) CLI 的一些工具函数包. eg: read input, exec command
- [`envutil`](envutil) ENV 信息获取判断工具包. eg: get one, get info, parse var
- [`fsutil`](fsutil) 文件系统操作相关的工具函数包. eg: file and dir check, operate
- [`jsonutil`](jsonutil) 一些用于快速读取、写入、编码、解码 JSON 数据的实用函数。
- [`sysutil`](sysutil) system 相关操作的函数工具包. eg: sysenv, exec, user, process
**扩展工具包**
### Debug & Test & Errors
- [`cflag`](./cflag): 包装和扩展 go `flag.FlagSet` 以方便快速的构建简单的命令行应用程序
- [`dump`](./dump) GO变量打印工具打印 slice, map 会自动换行显示每个元素,同时会显示打印调用位置
- [`encodes`](encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc.
- [`errorx`](./errorx) 为 go 提供增强的错误实现,允许带有堆栈跟踪信息和包装另一个错误。
- [`finder`](./fsutil/finder) 提供简单方便的file/dir查找功能支持过滤、排除、匹配、忽略等。
- netutil 子包:
- `netutil/httpreq` 包装 http.Client 实现的更加易于使用的HTTP客户端, 和一些 http 工具函数
- strutil 子包:
- [textscan](strutil/textscan) 实现了一个快速扫描和分析文本内容的解析器. 可用于解析 INI, Properties 等格式内容
- [textutil](strutil/textutil) 提供一些常用的扩展文本处理功能函数。
- [syncs](syncs) 提供一些同步、协程、信号相关的工具函数.
- sysutil 子包:
- [clipboard](sysutil/clipboard) 提供简单的剪贴板读写操作工具库
- [cmdr](sysutil/cmdr) 提供快速构建和运行一个cmd批量运行多个cmd任务
- [process](sysutil/process) 提供一些进程操作相关的实用功能。
- [`testutil`](testutil) test help 相关操作的函数工具包. eg: http test, mock ENV value
- [assert](testutil/assert) 用于帮助测试的断言函数工具包,方便编写单元测试。
- [fakeobj](testutil/fakeobj) 提供一些接口的的实现,用于模拟测试. 例如 fs.File, fs.FileInfo, fs.DirEntry 等等.
- [`assert`](testutil/assert) 用于帮助测试的断言函数工具包,方便编写单元测试。
- [`fakeobj`](x/fakeobj) 提供一些接口的MOCK的实现,用于模拟测试. 例如 fs.File, fs.FileInfo, fs.DirEntry 等等.
### 扩展工具包
- [`cflag`](cflag): 包装和扩展 go `flag.FlagSet` 以方便快速的构建简单的命令行应用程序
- [`ccolor`](x/ccolor): 简单的命令行颜色输出库,它使用 ANSI 颜色代码来输出带有颜色的文本。
- [`syncs`](syncs) 提供一些同步、协程、信号相关的工具函数.
- [`httpreq`](netutil/httpreq) 包装 http.Client 实现的更加易于使用的HTTP客户端, 和一些 http 工具函数
- [`clipboard`](sysutil/clipboard) 提供简单的OS剪贴板读写操作工具库
- [`timex`](timex) 提供增强的 time.Time 实现。添加更多常用的功能方法
- 提供类似 `Y-m-d H:i:s` 的日期时间格式解析处理
- 常用时间方法。例如: DayStart(), DayAfter(), DayAgo(), DateFormat() 等等
**更多 ...**
- [`netutil`](netutil) Network util functions. eg: Ip, IpV4, IpV6, Mac, Port, Hostname, etc.
- [`cmdline`](cliutil/cmdline) 提供 cmdline 解析args 构建到 cmdline
- [`encodes`](x/encodes): Provide some encoding/decoding, hash, crypto util functions. eg: base64, hex, etc.
- [`finder`](x/finder) 提供简单方便的file/dir查找功能支持过滤、排除、匹配、忽略等。
- [textscan](strutil/textscan) 实现了一个快速扫描和分析文本内容的解析器. 可用于解析 INI, Properties 等格式内容
- [textutil](strutil/textutil) 提供一些常用的扩展文本处理功能函数。
- [cmdr](sysutil/cmdr) 提供快速构建和运行一个cmd批量运行多个cmd任务
- [process](sysutil/process) 提供一些进程操作相关的实用功能。
- [`fmtutil`](x/fmtutil) 格式化数据工具函数 eg数据size
- [`goinfo`](x/goinfo) 提供一些与Go info, runtime 相关的工具函数。
## GoDoc
- [Godoc for github](https://pkg.go.dev/github.com/gookit/goutil)
- Wiki docs on [DeepWiki - gookit/goutil](https://deepwiki.com/gookit/goutil)
## 获取
@@ -95,7 +103,7 @@ dump.Print(somevar, somevar2, ...)
![preview-nested-struct](dump/_examples/preview-nested-struct.png)
## Packages
## Packages Details
### Array and Slice
@@ -165,8 +173,9 @@ func Remove[T comdef.Compared](ls []T, val T) []T
func Filter[T any](ls []T, filter ...comdef.MatchFunc[T]) []T
func Map[T any, V any](list []T, mapFn MapFn[T, V]) []V
func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V
func Unique[T ~string | comdef.XintOrFloat](list []T) []T
func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int
func Unique[T comdef.NumberOrString](list []T) []T
func IndexOf[T comdef.NumberOrString](val T, list []T) int
func FirstOr[T any](list []T, defVal ...T) T
// source at arrutil/strings.go
func StringsToAnys(ss []string) []any
func StringsToSlice(ss []string) []any
@@ -211,6 +220,7 @@ ss, err := arrutil.ToStrings([]int{1, 2}) // ss: []string{"1", "2"}
func NewBuffer() *Buffer
// source at byteutil/byteutil.go
func Md5(src any) []byte
func Md5Sum(src any) []byte
func ShortMd5(src any) []byte
func Random(length int) ([]byte, error)
func FirstLine(bs []byte) []byte
@@ -220,6 +230,7 @@ func SafeCut(bs []byte, sep byte) (before, after []byte)
func SafeCuts(bs []byte, sep []byte) (before, after []byte)
// source at byteutil/check.go
func IsNumChar(c byte) bool
func IsAlphaChar(c byte) bool
// source at byteutil/conv.go
func StrOrErr(bs []byte, err error) (string, error)
func SafeString(bs []byte, err error) string
@@ -241,7 +252,7 @@ func NewChanPool(chSize int, width int, capWidth int) *ChanPool
```go
// source at cflag/app.go
func NewApp(fns ...func(app *App)) *App
func NewCmd(name, desc string) *Cmd
func NewCmd(name, desc string, runFunc ...func(c *Cmd) error) *Cmd
// source at cflag/cflag.go
func SetDebug(open bool)
func New(fns ...func(c *CFlags)) *CFlags
@@ -274,7 +285,7 @@ func ReplaceShorts(args []string, shortsMap map[string]string) []string
`cflag` 使用说明请看 [cflag/README.zh-CN.md](cflag/README.zh-CN.md)
### CLI/Console
### CLI Utils
> Package `github.com/gookit/goutil/cliutil`
@@ -370,7 +381,7 @@ Build line: ./myapp -a val0 -m "this is message" arg0
> More, please see [./cliutil/README](cliutil/README.md)
### Dumper
### Var Dumper
> Package `github.com/gookit/goutil/dump`
@@ -445,20 +456,6 @@ Preview:
![](dump/_examples/preview-nested-struct.png)
### Encodes
> Package `github.com/gookit/goutil/encodes`
```go
// source at encodes/encodes.go
func B32Encode(str string) string
func B32Decode(str string) string
func B64Encode(str string) string
func B64EncodeBytes(src []byte) []byte
func B64Decode(str string) string
func B64DecodeBytes(str []byte) []byte
```
### ENV/Environment
> Package `github.com/gookit/goutil/envutil`
@@ -470,14 +467,16 @@ func ParseOrErr(val string) (string, error)
func ParseValue(val string) string
func VarParse(val string) string
func ParseEnvValue(val string) string
func SetEnvMap(mp map[string]string)
func SetEnvs(kvPairs ...string)
func UnsetEnvs(keys ...string)
func SplitText2map(text string) map[string]string
func SplitLineToKv(line string) (string, string)
// source at envutil/get.go
func Getenv(name string, def ...string) string
func MustGet(name string) string
func GetInt(name string, def ...int) int
func GetBool(name string, def ...bool) bool
func GetOne(names []string, defVal ...string) string
func GetMulti(names ...string) map[string]string
func OnExist(name string, fn func(val string)) bool
func EnvPaths() []string
func EnvMap() map[string]string
func Environ() map[string]string
@@ -489,7 +488,6 @@ func IsWindows() bool
func IsMac() bool
func IsLinux() bool
func IsMSys() bool
func IsWSL() bool
func IsTerminal(fd uintptr) bool
func StdIsTerminal() bool
func IsConsole(out io.Writer) bool
@@ -498,6 +496,12 @@ func IsSupportColor() bool
func IsSupport256Color() bool
func IsSupportTrueColor() bool
func IsGithubActions() bool
// source at envutil/set.go
func SetEnvMap(mp map[string]string)
func SetEnvs(kvPairs ...string)
func UnsetEnvs(keys ...string)
func LoadText(text string)
func LoadString(line string) bool
```
#### ENV Util Usage
@@ -563,7 +567,8 @@ func Err(msg string) error
func Raw(msg string) error
func Ef(tpl string, vars ...any) error
func Errf(tpl string, vars ...any) error
func Rawf(tpl string, vars ...any) error
func Rf(tpl string, vs ...any) error
func Rawf(tpl string, vs ...any) error
func Cause(err error) error
func Unwrap(err error) error
func Previous(err error) error
@@ -652,24 +657,6 @@ runtime.goexit()
```
### Format Utils
> Package `github.com/gookit/goutil/fmtutil`
```go
// source at fmtutil/fmtutil.go
func StringOrJSON(v any) ([]byte, error)
// source at fmtutil/format.go
func DataSize(size uint64) string
func SizeToString(size uint64) string
func StringToByte(sizeStr string) uint64
func ParseByte(sizeStr string) uint64
func PrettyJSON(v any) (string, error)
func ArgsWithSpaces(vs []any) (message string)
// source at fmtutil/time.go
func HowLongAgo(sec int64) string
```
### File System
> Package `github.com/gookit/goutil/fsutil`
@@ -689,7 +676,7 @@ func PathMatch(pattern, s string) bool
func NewEntry(fPath string, ent fs.DirEntry) Entry
func NewFileInfo(fPath string, info fs.FileInfo) FileInfo
// source at fsutil/find.go
func FilePathInDirs(file string, dirs ...string) string
func FilePathInDirs(fPath string, dirs ...string) string
func FirstExists(paths ...string) string
func FirstExistsDir(paths ...string) string
func FirstExistsFile(paths ...string) string
@@ -708,20 +695,25 @@ func ExcludeDotFile(_ string, ent fs.DirEntry) bool
func ExcludeSuffix(ss ...string) FilterFunc
func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
func FileInDirs(paths []string, names ...string) string
// source at fsutil/fsutil.go
func JoinPaths(elem ...string) string
func JoinSubPaths(basePath string, elem ...string) string
func JoinPaths3(basePath, secPath string, elems ...string) string
func JoinSubPaths(basePath string, elems ...string) string
func SlashPath(path string) string
func UnixPath(path string) string
func ToAbsPath(p string) string
func Must2(_ any, err error)
// source at fsutil/info.go
func DirPath(fpath string) string
func Dir(fpath string) string
func PathName(fpath string) string
func Name(fpath string) string
func FileExt(fpath string) string
func Extname(fpath string) string
func Suffix(fpath string) string
func DirPath(fPath string) string
func Dir(fPath string) string
func PathName(fPath string) string
func PathNoExt(fPath string) string
func Name(fPath string) string
func NameNoExt(fPath string) string
func FileExt(fPath string) string
func Extname(fPath string) string
func Suffix(fPath string) string
func Expand(pathStr string) string
func ExpandPath(pathStr string) string
func ResolvePath(pathStr string) string
@@ -782,6 +774,7 @@ func OSTempDir(pattern string) (string, error)
func TempDir(dir, pattern string) (string, error)
func MustSave(filePath string, data any, optFns ...OpenOptionFunc)
func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error
func WriteData(filePath string, data any, fileFlag ...int) (int, error)
func PutContents(filePath string, data any, fileFlag ...int) (int, error)
func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) error
func WriteOSFile(f *os.File, data any) (n int, err error)
@@ -820,27 +813,6 @@ func main() {
```
### Go Info
> Package `github.com/gookit/goutil/goinfo`
```go
// source at goinfo/gofunc.go
func FuncName(fn any) string
func CutFuncName(fullFcName string) (pkgPath, shortFnName string)
func PkgName(fullFcName string) string
func GoodFuncName(name string) bool
// source at goinfo/goinfo.go
func GoVersion() string
func ParseGoVersion(line string) (*GoInfo, error)
func OsGoInfo() (*GoInfo, error)
// source at goinfo/stack.go
func GetCallStacks(all bool) []byte
func GetCallerInfo(skip int) string
func SimpleCallersInfo(skip, num int) []string
func GetCallersInfo(skip, max int) []string
```
### JSON Utils
> Package `github.com/gookit/goutil/jsonutil`
@@ -856,6 +828,7 @@ func EncodeUnescapeHTML(v any) ([]byte, error)
func Decode(bts []byte, ptr any) error
func DecodeString(str string, ptr any) error
func DecodeReader(r io.Reader, ptr any) error
func DecodeFile(file string, ptr any) error
// source at jsonutil/jsonutil.go
func WriteFile(filePath string, data any) error
func WritePretty(filePath string, data any) error
@@ -881,9 +854,14 @@ func HasOneKey(mp any, keys ...any) (ok bool, key any)
func HasAllKeys(mp any, keys ...any) (ok bool, noKey any)
// source at maputil/convert.go
func KeyToLower(src map[string]string) map[string]string
func AnyToStrMap(src any) map[string]string
func ToStringMap(src map[string]any) map[string]string
func ToL2StringMap(groupsMap map[string]any) map[string]map[string]string
func CombineToSMap(keys, values []string) SMap
func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V
func SliceToSMap(kvPairs ...string) map[string]string
func SliceToMap(kvPairs ...any) map[string]any
func SliceToTypeMap[T any](valFunc func(any) T, kvPairs ...any) map[string]T
func ToAnyMap(mp any) map[string]any
func TryAnyMap(mp any) (map[string]any, error)
func HTTPQueryString(data map[string]any) string
@@ -902,14 +880,21 @@ func GetFromAny(path string, data any) (val any, ok bool)
func GetByPath(path string, mp map[string]any) (val any, ok bool)
func GetByPathKeys(mp map[string]any, keys []string) (val any, ok bool)
func Keys(mp any) (keys []string)
func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K)
func FirstKey[T any](mp map[string]T) string
func Values(mp any) (values []any)
func TypedValues[K comdef.SimpleType, V any](mp map[K]V) (values []V)
func EachAnyMap(mp any, fn func(key string, val any))
func EachTypedMap[K comdef.SimpleType, V any](mp map[K]V, fn func(key K, val V))
// source at maputil/maputil.go
func SimpleMerge(src, dst map[string]any) map[string]any
func Merge1level(mps ...map[string]any) map[string]any
func DeepMerge(src, dst map[string]any, deep int) map[string]any
func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeStrMap(src, dst map[string]string) map[string]string
func MergeStringMap(src, dst map[string]string, ignoreCase bool) map[string]string
func MergeMultiSMap(mps ...map[string]string) map[string]string
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string
func FilterSMap(sm map[string]string) map[string]string
func MakeByPath(path string, val any) (mp map[string]any)
func MakeByKeys(keys []string, val any) (mp map[string]any)
@@ -923,21 +908,23 @@ func SetByKeys(mp *map[string]any, keys []string, val any) (err error)
> Package `github.com/gookit/goutil/mathutil`
```go
// source at mathutil/calc.go
func Abs[T comdef.Int](val T) T
// source at mathutil/check.go
func IsNumeric(c byte) bool
func Compare(first, second any, op string) bool
func CompInt[T comdef.Xint](first, second T, op string) (ok bool)
func CompInt64(first, second int64, op string) bool
func CompFloat[T comdef.Float](first, second T, op string) (ok bool)
func CompValue[T comdef.XintOrFloat](first, second T, op string) (ok bool)
func InRange[T comdef.IntOrFloat](val, min, max T) bool
func OutRange[T comdef.IntOrFloat](val, min, max T) bool
func CompValue[T comdef.Number](first, second T, op string) (ok bool)
func InRange[T comdef.Number](val, min, max T) bool
func OutRange[T comdef.Number](val, min, max T) bool
func InUintRange[T comdef.Uint](val, min, max T) bool
// source at mathutil/compare.go
func Min[T comdef.XintOrFloat](x, y T) T
func Max[T comdef.XintOrFloat](x, y T) T
func SwapMin[T comdef.XintOrFloat](x, y T) (T, T)
func SwapMax[T comdef.XintOrFloat](x, y T) (T, T)
func Min[T comdef.Number](x, y T) T
func Max[T comdef.Number](x, y T) T
func SwapMin[T comdef.Number](x, y T) (T, T)
func SwapMax[T comdef.Number](x, y T) (T, T)
func MaxInt(x, y int) int
func SwapMaxInt(x, y int) (int, int)
func MaxI64(x, y int64) int64
@@ -1012,15 +999,15 @@ func ToStringWith(in any, optFns ...comfunc.ConvOptionFn) (string, error)
func DataSize(size uint64) string
func HowLongAgo(sec int64) string
// source at mathutil/mathutil.go
func OrElse[T comdef.XintOrFloat](val, defVal T) T
func ZeroOr[T comdef.XintOrFloat](val, defVal T) T
func LessOr[T comdef.XintOrFloat](val, max, devVal T) T
func LteOr[T comdef.XintOrFloat](val, max, devVal T) T
func GreaterOr[T comdef.XintOrFloat](val, min, defVal T) T
func GteOr[T comdef.XintOrFloat](val, min, defVal T) T
func Mul[T1, T2 comdef.XintOrFloat](a T1, b T2) float64
func OrElse[T comdef.Number](val, defVal T) T
func ZeroOr[T comdef.Number](val, defVal T) T
func LessOr[T comdef.Number](val, max, devVal T) T
func LteOr[T comdef.Number](val, max, devVal T) T
func GreaterOr[T comdef.Number](val, min, defVal T) T
func GteOr[T comdef.Number](val, min, defVal T) T
func Mul[T1, T2 comdef.Number](a T1, b T2) float64
func MulF2i(a, b float64) int
func Div[T1, T2 comdef.XintOrFloat](a T1, b T2) float64
func Div[T1, T2 comdef.Number](a T1, b T2) float64
func DivInt[T comdef.Integer](a, b T) int
func DivF2i(a, b float64) int
func Percent(val, total int) float64
@@ -1029,8 +1016,6 @@ func RandomInt(min, max int) int
func RandInt(min, max int) int
func RandIntWithSeed(min, max int, seed int64) int
func RandomIntWithSeed(min, max int, seed int64) int
// source at mathutil/value.go
func New[T comdef.IntOrFloat](v T) *Num[T]
```
### Reflects
@@ -1039,6 +1024,8 @@ func New[T comdef.IntOrFloat](v T) *Num[T]
```go
// source at reflects/check.go
func IsTimeType(t reflect.Type) bool
func IsDurationType(t reflect.Type) bool
func HasChild(v reflect.Value) bool
func IsArrayOrSlice(k reflect.Kind) bool
func IsSimpleKind(k reflect.Kind) bool
@@ -1047,6 +1034,7 @@ func IsIntLike(k reflect.Kind) bool
func IsIntx(k reflect.Kind) bool
func IsUintX(k reflect.Kind) bool
func IsNil(v reflect.Value) bool
func IsValidPtr(v reflect.Value) bool
func CanBeNil(typ reflect.Type) bool
func IsFunc(val any) bool
func IsEqual(src, dst any) bool
@@ -1057,12 +1045,13 @@ func BaseTypeVal(v reflect.Value) (value any, err error)
func ToBaseVal(v reflect.Value) (value any, err error)
func ConvToType(val any, typ reflect.Type) (rv reflect.Value, err error)
func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error)
func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error)
func ConvToKind(val any, kind reflect.Kind) (rv reflect.Value, err error)
func ValueByKind(val any, kind reflect.Kind) (reflect.Value, error)
func ConvToKind(val any, kind reflect.Kind, fallback ...ConvFunc) (rv reflect.Value, err error)
func ConvSlice(oldSlRv reflect.Value, newElemTyp reflect.Type) (rv reflect.Value, err error)
func String(rv reflect.Value) string
func ToString(rv reflect.Value) (str string, err error)
func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error)
func ToTimeOrDuration(str string, typ reflect.Type) (any, error)
// source at reflects/func.go
func NewFunc(fn any) *FuncX
func Call2(fn reflect.Value, args []reflect.Value) (reflect.Value, error)
@@ -1070,8 +1059,9 @@ func Call(fn reflect.Value, args []reflect.Value, opt *CallOpt) ([]reflect.Value
func SafeCall2(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error)
func SafeCall(fun reflect.Value, args []reflect.Value) (ret []reflect.Value, err error)
// source at reflects/map.go
func EachMap(mp reflect.Value, fn func(key, val reflect.Value))
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any))
func TryAnyMap(mp reflect.Value) (map[string]any, error)
func EachMap(mp reflect.Value, fn func(key, val reflect.Value)) (err error)
func EachStrAnyMap(mp reflect.Value, fn func(key string, val any)) error
func FlatMap(rv reflect.Value, fn FlatFunc)
// source at reflects/slice.go
func MakeSliceByElem(elTyp reflect.Type, len, cap int) reflect.Value
@@ -1098,32 +1088,6 @@ func Wrap(rv reflect.Value) Value
func ValueOf(v any) Value
```
### Stdio
> Package `github.com/gookit/goutil/stdio`
```go
// source at stdio/ioutil.go
func QuietFprint(w io.Writer, a ...any)
func QuietFprintf(w io.Writer, tpl string, vs ...any)
func QuietFprintln(w io.Writer, a ...any)
func QuietWriteString(w io.Writer, ss ...string)
// source at stdio/stdio.go
func DiscardReader(src io.Reader)
func ReadString(r io.Reader) string
func MustReadReader(r io.Reader) []byte
func NewIOReader(in any) io.Reader
func NewScanner(in any) *bufio.Scanner
func WriteByte(b byte)
func WriteBytes(bs []byte)
func WritelnBytes(bs []byte)
func WriteString(s string)
func Writeln(s string)
// source at stdio/writer.go
func WrapW(w io.Writer) *WriteWrapper
func NewWriteWrapper(w io.Writer) *WriteWrapper
```
### Structs
> Package `github.com/gookit/goutil/structs`
@@ -1140,8 +1104,10 @@ func TryToSMap(st any, optFns ...MapOptFunc) (map[string]string, error)
func MustToSMap(st any, optFns ...MapOptFunc) map[string]string
func ToString(st any, optFns ...MapOptFunc) string
func WithMapTagName(tagName string) MapOptFunc
func WithUserFunc(fn CustomUserFunc) MapOptFunc
func MergeAnonymous(opt *MapOptions)
func ExportPrivate(opt *MapOptions)
func WithIgnoreEmpty(opt *MapOptions)
func StructToMap(st any, optFns ...MapOptFunc) (map[string]any, error)
// source at structs/copy.go
func MapStruct(srcSt, dstSt any)
@@ -1172,6 +1138,8 @@ func WrapValue(rv reflect.Value) *Wrapper
// source at structs/writer.go
func NewWriter(ptr any) *Wrapper
func WithParseDefault(opt *SetOptions)
func WithBeforeSetFn(fn BeforeSetFunc) SetOptFunc
func BindData(ptr any, data map[string]any, optFns ...SetOptFunc) error
func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
```
@@ -1181,10 +1149,12 @@ func SetValues(ptr any, data map[string]any, optFns ...SetOptFunc) error
```go
// source at strutil/bytes.go
func NewBuffer() *Buffer
func NewBuffer(initSize ...int) *Buffer
func NewByteChanPool(maxSize, width, capWidth int) *ByteChanPool
// source at strutil/check.go
func IsNumChar(c byte) bool
func IsInt(s string) bool
func IsFloat(s string) bool
func IsNumeric(s string) bool
func IsAlphabet(char uint8) bool
func IsAlphaNum(c uint8) bool
@@ -1196,8 +1166,10 @@ func IContains(s, sub string) bool
func ContainsByte(s string, c byte) bool
func ContainsOne(s string, subs []string) bool
func HasOneSub(s string, subs []string) bool
func IContainsOne(s string, subs []string) bool
func ContainsAll(s string, subs []string) bool
func HasAllSubs(s string, subs []string) bool
func IContainsAll(s string, subs []string) bool
func IsStartsOf(s string, prefixes []string) bool
func HasOnePrefix(s string, prefixes []string) bool
func HasPrefix(s string, prefix string) bool
@@ -1215,6 +1187,7 @@ func IsSymbol(r rune) bool
func HasEmpty(ss ...string) bool
func IsAllEmpty(ss ...string) bool
func IsVersion(s string) bool
func IsVarName(s string) bool
func Compare(s1, s2, op string) bool
func VersionCompare(v1, v2, op string) bool
func SimpleMatch(s string, keywords []string) bool
@@ -1232,6 +1205,7 @@ func Quote(s string) string
func Unquote(s string) string
func Join(sep string, ss ...string) string
func JoinList(sep string, ss []string) string
func JoinComma(ss []string) string
func JoinAny(sep string, parts ...any) string
func Implode(sep string, ss ...string) string
func String(val any) (string, error)
@@ -1331,6 +1305,7 @@ func IndentBytes(b, prefix []byte) []byte
func MicroTimeID() string
func MicroTimeHexID() string
func MTimeHexID() string
func MTimeBase36() string
func MTimeBaseID(toBase int) string
func DatetimeNo(prefix string) string
func DateSN(prefix string) string
@@ -1339,6 +1314,8 @@ func DateSNV2(prefix string, extBase ...int) string
func Md5(src any) string
func MD5(src any) string
func GenMd5(src any) string
func Md5Simple(src any) string
func Md5Base62(src any) string
func Md5Bytes(src any) []byte
func ShortMd5(src any) string
func HashPasswd(pwd, key string) string
@@ -1388,6 +1365,7 @@ func RunesWidth(rs []rune) (w int)
func Truncate(s string, w int, tail string) string
func TextTruncate(s string, w int, tail string) string
func Utf8Truncate(s string, w int, tail string) string
func Chunk[T ~string](s T, size int) []T
func TextSplit(s string, w int) []string
func Utf8Split(s string, w int) []string
func TextWrap(s string, w int) string
@@ -1409,6 +1387,7 @@ func SplitNValid(s, sep string, n int) (ss []string)
func SplitN(s, sep string, n int) (ss []string)
func SplitTrimmed(s, sep string) (ss []string)
func SplitNTrimmed(s, sep string, n int) (ss []string)
func SplitByWhitespace(s string) []string
func Substr(s string, pos, length int) string
func SplitInlineComment(val string, strict ...bool) (string, string)
func FirstLine(output string) string
@@ -1433,12 +1412,12 @@ func SubstrCount(s, substr string, params ...uint64) (int, error)
```go
// source at syncs/chan.go
func WaitCloseSignals(onClose func(sig os.Signal))
func Go(f func() error) error
// source at syncs/group.go
func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context)
func NewErrGroup(limit ...int) *ErrGroup
// source at syncs/signal.go
func WaitCloseSignals(onClose func(sig os.Signal), sigCh ...chan os.Signal)
func SignalHandler(ctx context.Context, signals ...os.Signal) (execute func() error, interrupt func(error))
```
@@ -1454,15 +1433,14 @@ func QuickExec(cmdLine string, workDir ...string) (string, error)
func ExecLine(cmdLine string, workDir ...string) (string, error)
func ExecCmd(binName string, args []string, workDir ...string) (string, error)
func ShellExec(cmdLine string, shells ...string) (string, error)
// source at sysutil/stack.go
func CallersInfos(skip, num int, filters ...func(file string, fc *runtime.Func) bool) []*CallerInfo
// source at sysutil/sysenv.go
func IsMSys() bool
func IsWSL() bool
func IsConsole(out io.Writer) bool
func IsTerminal(fd uintptr) bool
func StdIsTerminal() bool
func Hostname() string
func CurrentShell(onlyName bool) (path string)
func CurrentShell(onlyName bool, fallbackShell ...string) string
func HasShellEnv(shell string) bool
func IsShellSpecialVar(c uint8) bool
func FindExecutable(binName string) (string, error)
@@ -1472,11 +1450,13 @@ func Getenv(name string, def ...string) string
func Environ() map[string]string
func EnvMapWith(newEnv map[string]string) map[string]string
func EnvPaths() []string
func SearchPath(keywords string, limit int) []string
func ToEnvPATH(paths []string) string
func SearchPath(keywords string, limit int, opts ...SearchPathOption) []string
// source at sysutil/sysgo.go
func GoVersion() string
func ParseGoVersion(line string) (*GoInfo, error)
func OsGoInfo() (*GoInfo, error)
func CallersInfos(skip, num int, filters ...goinfo.CallerFilterFunc) []*CallerInfo
// source at sysutil/sysutil.go
func Workdir() string
func BinDir() string
@@ -1485,16 +1465,16 @@ func BinFile() string
func Open(fileOrURL string) error
func OpenBrowser(fileOrURL string) error
func OpenFile(path string) error
// source at sysutil/sysutil_nonwin.go
func Kill(pid int, signal syscall.Signal) error
func ProcessExists(pid int) bool
// source at sysutil/sysutil_unix.go
// source at sysutil/sysutil_linux.go
func IsWin() bool
func IsWindows() bool
func IsMac() bool
func IsDarwin() bool
func IsLinux() bool
func OpenURL(URL string) error
// source at sysutil/sysutil_nonwin.go
func Kill(pid int, signal syscall.Signal) error
func ProcessExists(pid int) bool
// source at sysutil/user.go
func MustFindUser(uname string) *user.User
func LoginUser() *user.User
@@ -1502,12 +1482,13 @@ func CurrentUser() *user.User
func UHomeDir() string
func UserHomeDir() string
func HomeDir() string
func UserDir(subPath string) string
func UserCacheDir(subPath string) string
func UserConfigDir(subPath string) string
func UserDir(subPaths ...string) string
func UserCacheDir(subPaths ...string) string
func UserConfigDir(subPaths ...string) string
func ExpandPath(path string) string
func ExpandHome(path string) string
// source at sysutil/user_nonwin.go
func IsAdmin() bool
func ChangeUserByName(newUname string) error
func ChangeUserUidGid(newUID int, newGid int) error
func ChangeUserUIDGid(newUID int, newGid int) (err error)
@@ -1525,15 +1506,18 @@ func MockEnvValue(key, val string, fn func(nv string))
func MockEnvValues(kvMap map[string]string, fn func())
func MockOsEnvByText(envText string, fn func())
func MockOsEnv(mp map[string]string, fn func())
func SetOsEnvs(mp map[string]string) string
func RemoveTmpEnvs(tmpKey string)
func ClearOSEnv()
func RevertOSEnv()
func RunOnCleanEnv(runFn func())
func MockCleanOsEnv(mp map[string]string, fn func())
// source at testutil/httpmock.go
func NewHttpRequest(method, path string, data *MD) *http.Request
func NewHTTPRequest(method, path string, data *MD) *http.Request
func MockRequest(h http.Handler, method, path string, data *MD) *httptest.ResponseRecorder
func MockHttpServer() *EchoServer
func TestMain(m *testing.M)
func NewEchoServer() *httptest.Server
func NewEchoServer() *EchoServer
func BuildEchoReply(r *http.Request) *EchoReply
func ParseRespToReply(w *http.Response) *EchoReply
func ParseBodyToReply(bd io.ReadCloser) *EchoReply
@@ -1549,7 +1533,7 @@ func SetTimeLocalUTC()
func RestoreTimeLocal()
// source at testutil/writer.go
func NewTestWriter() *TestWriter
func NewDirEnt(fpath string, isDir ...bool) *fakeobj.DirEntry
func NewDirEnt(fPath string, isDir ...bool) *fakeobj.DirEntry
```
### Timex
@@ -1749,13 +1733,6 @@ go test -v -run ^TestErr$
go test -v -run ^TestErr$ ./testutil/assert/...
```
## Related
- https://github.com/duke-git/lancet
- https://github.com/samber/lo
- https://github.com/zyedidia/generic
- https://github.com/thoas/go-funk
## Gookit packages
- [gookit/ini](https://github.com/gookit/ini) Go config management, use INI files
@@ -1771,6 +1748,13 @@ go test -v -run ^TestErr$ ./testutil/assert/...
- [gookit/goutil](https://github.com/gookit/goutil) Some utils for the Go: string, array/slice, map, format, cli, env, filesystem, test and more
- More, please see https://github.com/gookit
## Related
- https://github.com/duke-git/lancet
- https://github.com/samber/lo
- https://github.com/zyedidia/generic
- https://github.com/thoas/go-funk
## License
[MIT](LICENSE)

View File

@@ -0,0 +1,50 @@
## ArrUtil
`arrutil` 是一个用于操作数组和切片的工具包,提供了丰富的功能来简化 Go 语言中数组和切片的处理。
## Install
```shell
go get github.com/gookit/goutil/arrutil
```
## Go docs
- [Go docs](https://pkg.go.dev/github.com/gookit/goutil/arrutil)
## 基本功能
主要包括以下功能:
1. **数组/切片的基本操作**
- `RandomOne`:从数组或切片中随机获取一个元素。
- `Reverse`:反转数组或切片中的元素顺序。
2. **检查和查找**
- `Contains``HasValue`:检查数组或切片是否包含特定值。
- `InStrings``StringsHas`:检查字符串切片中是否包含特定字符串。
- `IntsHas``Int64sHas`:检查整数切片中是否包含特定整数值。
- `Find``FindOrDefault`:根据谓词函数查找元素,如果没有找到则返回默认值。
3. **集合操作**
- `Union`:计算两个切片的并集。
- `Intersects`:计算两个切片的交集。
- `Excepts``Differences`:计算两个切片的差集。
- `TwowaySearch`:在切片中双向搜索特定元素。
4. **转换和格式化**
- `ToInt64s``ToStrings`:将任意类型的切片转换为整数或字符串切片。
- `JoinSlice``JoinStrings`:将切片中的元素连接成一个字符串。
- `FormatIndent`:将数组或切片格式化为带有缩进的字符串。
5. **排序和过滤**
- `Sort`:对切片进行排序。
- `Filter`:根据条件过滤切片中的元素。
- `Remove`:从切片中移除特定元素。
6. **其他实用功能**
- `Unique`:去除切片中的重复元素。
- `FirstOr`:获取切片的第一个元素,如果切片为空则返回默认值。
这些功能使得在 Go 语言中处理数组和切片变得更加方便和高效。无论是进行数据处理、集合运算还是字符串操作,`arrutil` 都提供了一系列简洁且易于使用的函数来帮助开发者完成任务。

View File

@@ -126,7 +126,7 @@ func SliceToInt64s(arr []any) []int64 {
}
/*************************************************************
* convert func for anys
* convert func for any-slice
*************************************************************/
// AnyToSlice convert any(allow: array,slice) to []any
@@ -159,22 +159,28 @@ func MustToStrings(arr any) []string {
// 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
// try direct convert
switch typVal := arr.(type) {
case string:
return []string{typVal}, nil
case []string:
return typVal, nil
case []any:
return SliceToStrings(typVal), nil
}
// try use reflect to convert
rv := reflect.ValueOf(arr)
if rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {
err = ErrInvalidType
return
}
for i := 0; i < rv.Len(); i++ {
str, err := strutil.ToString(rv.Index(i).Interface())
if err != nil {
return []string{}, err
str, err1 := strutil.ToString(rv.Index(i).Interface())
if err1 != nil {
return nil, err1
}
ret = append(ret, str)
}
return
@@ -182,11 +188,6 @@ func ToStrings(arr any) (ret []string, err error) {
// SliceToStrings safe convert []any to []string
func SliceToStrings(arr []any) []string {
return QuietStrings(arr)
}
// QuietStrings safe convert []any to []string
func QuietStrings(arr []any) []string {
ss := make([]string, len(arr))
for i, v := range arr {
ss[i] = strutil.SafeString(v)
@@ -194,6 +195,9 @@ func QuietStrings(arr []any) []string {
return ss
}
// QuietStrings safe convert []any to []string
func QuietStrings(arr []any) []string { return SliceToStrings(arr) }
// ConvType convert type of slice elements to new type slice, by the given newElemTyp type.
//
// Supports conversion between []string, []intX, []uintX, []floatX.

View File

@@ -74,7 +74,7 @@ func Column[T any, V any](list []T, mapFn func(obj T) (val V, find bool)) []V {
}
// Unique value in the given slice data.
func Unique[T ~string | comdef.XintOrFloat](list []T) []T {
func Unique[T comdef.NumberOrString](list []T) []T {
if len(list) < 2 {
return list
}
@@ -92,7 +92,7 @@ func Unique[T ~string | comdef.XintOrFloat](list []T) []T {
}
// IndexOf value in given slice.
func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int {
func IndexOf[T comdef.NumberOrString](val T, list []T) int {
for i, v := range list {
if v == val {
return i
@@ -100,3 +100,16 @@ func IndexOf[T ~string | comdef.XintOrFloat](val T, list []T) int {
}
return -1
}
// FirstOr get first value of slice, if slice is empty, return the default value.
func FirstOr[T any](list []T, defVal ...T) T {
if len(list) > 0 {
return list[0]
}
if len(defVal) > 0 {
return defVal[0]
}
var zero T
return zero
}

View File

@@ -47,6 +47,10 @@ func StringsTryInts(ss []string) (ints []int, err error) {
// StringsUnique unique string slice
func StringsUnique(ss []string) []string {
if len(ss) == 0 {
return ss
}
var unique []string
for _, s := range ss {
if !StringsContains(unique, s) {

View File

@@ -1,6 +1,6 @@
# Bytes Util
Provide some commonly bytes util functions.
Provide some common bytes util functions.
## Install

View File

@@ -13,6 +13,14 @@ import (
// Md5 Generate a 32-bit md5 bytes
func Md5(src any) []byte {
bs := Md5Sum(src)
dst := make([]byte, hex.EncodedLen(len(bs)))
hex.Encode(dst, bs)
return dst
}
// Md5Sum Generate a md5 bytes
func Md5Sum(src any) []byte {
h := md5.New()
switch val := src.(type) {
@@ -24,16 +32,11 @@ func Md5(src any) []byte {
h.Write([]byte(fmt.Sprint(src)))
}
bs := h.Sum(nil) // cap(bs) == 16
dst := make([]byte, hex.EncodedLen(len(bs)))
hex.Encode(dst, bs)
return dst
return h.Sum(nil) // cap(bs) == 16
}
// ShortMd5 Generate a 16-bit md5 bytes. remove first 8 and last 8 bytes from 32-bit md5.
func ShortMd5(src any) []byte {
return Md5(src)[8:24]
}
// ShortMd5 Generate a 16-bit md5 bytes. remove the first 8 and last 8 bytes from 32-bit md5.
func ShortMd5(src any) []byte { return Md5(src)[8:24] }
// Random bytes generate
func Random(length int) ([]byte, error) {

View File

@@ -2,3 +2,9 @@ package byteutil
// IsNumChar returns true if the given character is a numeric, otherwise false.
func IsNumChar(c byte) bool { return c >= '0' && c <= '9' }
// IsAlphaChar returns true if the given character is a alphabet, otherwise false.
func IsAlphaChar(c byte) bool {
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
}

View File

@@ -1,86 +0,0 @@
package goutil
import (
"reflect"
"github.com/gookit/goutil/internal/checkfn"
"github.com/gookit/goutil/reflects"
)
// IsNil value check
func IsNil(v any) bool {
if v == nil {
return true
}
return reflects.IsNil(reflect.ValueOf(v))
}
// IsZero value check, alias of the IsEmpty()
var IsZero = IsEmpty
// IsEmpty value check
func IsEmpty(v any) bool {
if v == nil {
return true
}
return reflects.IsEmpty(reflect.ValueOf(v))
}
// Alias of the IsEmptyReal()
var IsZeroReal = IsEmptyReal
// IsEmptyReal checks for empty given value and also real empty value if the passed value is a pointer
func IsEmptyReal(v any) bool {
if v == nil {
return true
}
return reflects.IsEmptyReal(reflect.ValueOf(v))
}
// IsFunc value
func IsFunc(val any) bool {
if val == nil {
return false
}
return reflect.TypeOf(val).Kind() == reflect.Func
}
// IsEqual determines if two objects are considered equal.
//
// TIP: cannot compare function type
func IsEqual(src, dst any) bool {
if src == nil || dst == nil {
return src == dst
}
// cannot compare function type
if IsFunc(src) || IsFunc(dst) {
return false
}
return reflects.IsEqual(src, dst)
}
// Contains try loop over the data check if the data includes the element.
// alias of the IsContains
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func Contains(data, elem any) bool {
_, found := checkfn.Contains(data, elem)
return found
}
// IsContains try loop over the data check if the data includes the element.
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func IsContains(data, elem any) bool {
_, found := checkfn.Contains(data, elem)
return found
}

View File

@@ -3,6 +3,7 @@ package cmdline
import (
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
@@ -24,6 +25,12 @@ func NewBuilder(binFile string, args ...string) *LineBuilder {
return b
}
// ResetGet value, will reset after get.
func (b *LineBuilder) ResetGet() string {
defer b.Reset()
return b.String()
}
// AddArg to builder
func (b *LineBuilder) AddArg(arg string) {
_, _ = b.WriteString(arg)
@@ -51,35 +58,10 @@ func (b *LineBuilder) AddAny(args ...any) {
// 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 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 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 not-first write.
if b.Len() != 0 {
_ = b.WriteByte(' ')
}
// no quote char OR not need quote
if quote == 0 {
return b.Builder.WriteString(a)
}
_ = b.WriteByte(quote) // add start quote
n, err := b.Builder.WriteString(a)
_ = b.WriteByte(quote) // add end quote
return n, err
return b.Builder.WriteString(comfunc.ShellQuote(a))
}

View File

@@ -1,12 +1,15 @@
// Package cmdline provide quick build and parse cmd line string.
package cmdline
import "github.com/gookit/goutil/internal/comfunc"
// 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()
}
func ParseLine(line string) []string { return NewParser(line).Parse() }
// Quote string in shell command env
func Quote(s string) string { return comfunc.ShellQuote(s) }

View File

@@ -1,14 +1,6 @@
// Package comdef provide some common type or constant definitions
package comdef
type (
// MarshalFunc define
MarshalFunc func(v any) ([]byte, error)
// UnmarshalFunc define
UnmarshalFunc func(bts []byte, ptr any) error
)
// ToTypeFunc convert value to defined type
type ToTypeFunc[T any] func(any) (T, error)

View File

@@ -4,7 +4,7 @@ import (
"bytes"
"io"
"github.com/gookit/goutil/stdio"
"github.com/gookit/goutil/x/stdio"
)
// DataFormatter interface
@@ -14,6 +14,13 @@ type DataFormatter interface {
}
// BaseFormatter struct
//
// Usage:
//
// type YourFormatter struct {
// comdef.BaseFormatter
// }
// // implement the DataFormatter interface...
type BaseFormatter struct {
ow ByteStringWriter
// Out formatted to the writer
@@ -41,7 +48,7 @@ func (f *BaseFormatter) SetOutput(out io.Writer) {
f.Out = out
}
// BsWriter build and get
// BsWriter warp the Out, build a ByteStringWriter
func (f *BaseFormatter) BsWriter() ByteStringWriter {
if f.ow == nil {
if f.Out == nil {
@@ -52,6 +59,5 @@ func (f *BaseFormatter) BsWriter() ByteStringWriter {
f.ow = stdio.NewWriteWrapper(f.Out)
}
}
return f.ow
}

View File

@@ -29,6 +29,9 @@ type Float64able interface {
Float64() (float64, error)
}
// MapFunc definition
type MapFunc func(val any) (any, error)
//
//
// Matcher type

27
vendor/github.com/gookit/goutil/comdef/serializer.go generated vendored Normal file
View File

@@ -0,0 +1,27 @@
package comdef
type (
// MarshalFunc define
MarshalFunc func(v any) ([]byte, error)
// UnmarshalFunc define
UnmarshalFunc func(bts []byte, ptr any) error
)
// Serializer interface definition
type Serializer interface {
Serialize(v any) ([]byte, error)
Deserialize(data []byte, v any) error
}
// GoSerializer interface definition
type GoSerializer interface {
Marshal(v any) ([]byte, error)
Unmarshal(v []byte, ptr any) error
}
// Codec interface definition
type Codec interface {
Decode(blob []byte, v any) (err error)
Encode(v any) (out []byte, err error)
}

View File

@@ -25,16 +25,28 @@ type Float interface {
~float32 | ~float64
}
// IntOrFloat interface type. all int and float types
// IntOrFloat interface type. all int and float types, but NOT uint types
type IntOrFloat interface {
Int | Float
}
// XintOrFloat interface type. all int, uint and float types
// Number interface type. contains all int, uint and float types
type Number interface {
Int | Uint | Float
}
// XintOrFloat interface type. all int, uint and float types. alias of Number
//
// Deprecated: use Number instead.
type XintOrFloat interface {
Int | Uint | Float
}
// NumberOrString interface type for (x)int, float, ~string types
type NumberOrString interface {
Int | Uint | Float | ~string
}
// SortedType interface type. same of constraints.Ordered
//
// it can be ordered, that supports the operators < <= >= >.
@@ -67,3 +79,12 @@ type SimpleType interface {
type ScalarType interface {
Int | Uint | Float | ~string | ~bool
}
// StrMap is alias of map[string]string
type StrMap map[string]string
// AnyMap is alias of map[string]any
type AnyMap map[string]any
// L2StrMap is alias of map[string]map[string]string
type L2StrMap map[string]map[string]string

View File

@@ -20,20 +20,16 @@ func Bool(v any) bool {
}
// ToBool try to convert type to bool
func ToBool(v any) (bool, error) {
return comfunc.ToBool(v)
}
func ToBool(v any) (bool, error) { return comfunc.ToBool(v) }
// String always convert value to string, will ignore error
// String func. always converts value to string, will ignore error
func String(v any) string {
s, _ := strutil.AnyToString(v, false)
return s
}
// ToString convert value to string, will return error on fail.
func ToString(v any) (string, error) {
return strutil.AnyToString(v, true)
}
func ToString(v any) (string, error) { return strutil.AnyToString(v, true) }
// Int convert value to int
func Int(v any) int {
@@ -42,9 +38,7 @@ func Int(v any) int {
}
// ToInt try to convert value to int
func ToInt(v any) (int, error) {
return mathutil.ToInt(v)
}
func ToInt(v any) (int, error) { return mathutil.ToInt(v) }
// Int64 convert value to int64
func Int64(v any) int64 {
@@ -53,9 +47,7 @@ func Int64(v any) int64 {
}
// ToInt64 try to convert value to int64
func ToInt64(v any) (int64, error) {
return mathutil.ToInt64(v)
}
func ToInt64(v any) (int64, error) { return mathutil.ToInt64(v) }
// Uint convert value to uint
func Uint(v any) uint {
@@ -64,9 +56,7 @@ func Uint(v any) uint {
}
// ToUint try to convert value to uint
func ToUint(v any) (uint, error) {
return mathutil.ToUint(v)
}
func ToUint(v any) (uint, error) { return mathutil.ToUint(v) }
// Uint64 convert value to uint64
func Uint64(v any) uint64 {
@@ -75,14 +65,10 @@ func Uint64(v any) uint64 {
}
// ToUint64 try to convert value to uint64
func ToUint64(v any) (uint64, error) {
return mathutil.ToUint64(v)
}
func ToUint64(v any) (uint64, error) { return mathutil.ToUint64(v) }
// BoolString convert bool to string
func BoolString(bl bool) string {
return strconv.FormatBool(bl)
}
func BoolString(bl bool) string { return strconv.FormatBool(bl) }
// BaseTypeVal convert custom type or intX,uintX,floatX to generic base type.
//
@@ -136,7 +122,7 @@ func ConvOrDefault(val any, kind reflect.Kind, defVal any) any {
//
// Examples:
//
// val, err := ToKind("123", reflect.Int) // 123
// val, err := ToKind("123", reflect.Int) // 123
func ToKind(val any, kind reflect.Kind, fbFunc func(val any) (any, error)) (newVal any, err error) {
switch kind {
case reflect.Int:

View File

@@ -3,7 +3,9 @@ package envutil
import (
"os"
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/internal/varexpr"
)
@@ -42,36 +44,23 @@ func ParseValue(val string) string {
}
// VarParse alias of the ParseValue
func VarParse(val string) string {
return varexpr.SafeParse(val)
}
func VarParse(val string) string { return varexpr.SafeParse(val) }
// ParseEnvValue alias of the ParseValue
func ParseEnvValue(val string) string {
return varexpr.SafeParse(val)
func ParseEnvValue(val string) string { return varexpr.SafeParse(val) }
// SplitText2map parse ENV text to map. Can use to parse .env file contents.
func SplitText2map(text string) map[string]string {
envMp, _ := comfunc.ParseEnvLines(text, comfunc.ParseEnvLineOption{
SkipOnErrorLine: true,
})
return envMp
}
// 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)
// SplitLineToKv parse ENV line to k-v. eg: 'DEBUG=true' => ['DEBUG', 'true']
func SplitLineToKv(line string) (string, string) {
if line = strings.TrimSpace(line); line == "" {
return "", ""
}
return comfunc.SplitLineToKv(line, "=")
}

View File

@@ -4,9 +4,9 @@ import (
"os"
"path/filepath"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
"github.com/gookit/goutil/x/basefn"
)
// Getenv get ENV value by key name, can with default value
@@ -18,6 +18,14 @@ func Getenv(name string, def ...string) string {
return val
}
// MustGet get ENV value by key name, if not exists or empty, will panic
func MustGet(name string) string {
if val := os.Getenv(name); val != "" {
return val
}
panic("ENV key '" + name + "' not exists")
}
// GetInt get int ENV value by key name, can with default value
func GetInt(name string, def ...int) int {
if val := os.Getenv(name); val != "" {
@@ -34,6 +42,20 @@ func GetBool(name string, def ...bool) bool {
return basefn.FirstOr(def, false)
}
// GetOne get one not empty ENV value by input names.
func GetOne(names []string, defVal ...string) string {
for _, name := range names {
if val := os.Getenv(name); val != "" {
return val
}
}
if len(defVal) > 0 {
return defVal[0]
}
return ""
}
// GetMulti ENV values by input names.
func GetMulti(names ...string) map[string]string {
valMap := make(map[string]string, len(names))
@@ -46,6 +68,15 @@ func GetMulti(names ...string) map[string]string {
return valMap
}
// OnExist check ENV value by key name, if exists call fn
func OnExist(name string, fn func(val string)) bool {
if val := os.Getenv(name); val != "" {
fn(val)
return true
}
return false
}
// EnvPaths get and split $PATH to []string
func EnvPaths() []string {
return filepath.SplitList(os.Getenv("PATH"))

View File

@@ -36,33 +36,6 @@ func IsMSys() bool {
return sysutil.IsMSys()
}
var detectedWSL bool
var detectedWSLContents string
// IsWSL system env
// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
func IsWSL() bool {
if !detectedWSL {
b := make([]byte, 1024)
// `cat /proc/version`
// on mac:
// !not the file!
// on linux(debian,ubuntu,alpine):
// Linux version 4.19.121-linuxkit (root@18b3f92ade35) (gcc version 9.2.0 (Alpine 9.2.0)) #1 SMP Thu Jan 21 15:36:34 UTC 2021
// on win git bash, conEmu:
// MINGW64_NT-10.0-19042 version 3.1.7-340.x86_64 (@WIN-N0G619FD3UK) (gcc version 9.3.0 (GCC) ) 2020-10-23 13:08 UTC
// on WSL:
// Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020
f, err := os.Open("/proc/version")
if err == nil {
_, _ = f.Read(b) // ignore error
f.Close()
detectedWSLContents = string(b)
}
detectedWSL = true
}
return strings.Contains(detectedWSLContents, "Microsoft")
}
// IsTerminal isatty check
//
@@ -114,7 +87,7 @@ var specialColorTerms = map[string]bool{
//
// Supported:
//
// linux, mac, or windows's ConEmu, Cmder, putty, git-bash.exe
// linux, mac, or Windows's ConEmu, Cmder, putty, git-bash.exe
//
// Not support:
//

52
vendor/github.com/gookit/goutil/envutil/set.go generated vendored Normal file
View File

@@ -0,0 +1,52 @@
package envutil
import (
"os"
"github.com/gookit/goutil/internal/comfunc"
)
// 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)
}
}
// LoadText parse multiline text to ENV. Can use to load .env file contents.
//
// Usage:
// envutil.LoadText(fsutil.ReadFile(".env"))
func LoadText(text string) {
envMp := SplitText2map(text)
for key, value := range envMp {
_ = os.Setenv(key, value)
}
}
// LoadString set line to ENV. e.g.: KEY=VALUE
func LoadString(line string) bool {
k, v := comfunc.SplitLineToKv(line, "=")
if len(k) > 0 {
return os.Setenv(k, v) == nil
}
return false
}

View File

@@ -6,34 +6,25 @@ import (
)
// E new a raw go error. alias of errors.New()
func E(msg string) error {
return errors.New(msg)
}
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)
}
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)
}
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...)
}
// Ef new a raw go error. alias of fmt.Errorf
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...)
}
// Errf new a raw go error. alias of fmt.Errorf
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...)
}
// Rf new a raw go error. alias of fmt.Errorf
func Rf(tpl string, vs ...any) error { return fmt.Errorf(tpl, vs...) }
// Rawf new a raw go error. alias of fmt.Errorf
func Rawf(tpl string, vs ...any) error { return fmt.Errorf(tpl, vs...) }
/*************************************************************
* helper func for error
@@ -74,7 +65,7 @@ func IsErrorX(err error) (ok bool) {
return
}
// ToErrorX convert check
// ToErrorX convert check. like errors.As()
func ToErrorX(err error) (ex *ErrorX, ok bool) {
ex, ok = err.(*ErrorX)
return

View File

@@ -14,6 +14,8 @@ go get github.com/gookit/goutil/fsutil
## Find files
More see [./finder](./finder)
```go
// find all files in dir
fsutil.FindInDir("./", func(filePath string, de fs.DirEntry) error {

View File

@@ -12,16 +12,16 @@ const (
MimeSniffLen = 512
)
// NameMatchFunc name match func, alias of comdef.StringMatchFunc
// NameMatchFunc name matches func, alias of comdef.StringMatchFunc
type NameMatchFunc = comdef.StringMatchFunc
// PathMatchFunc path match func. alias of comdef.StringMatchFunc
// PathMatchFunc path matches func. alias of comdef.StringMatchFunc
type PathMatchFunc = comdef.StringMatchFunc
// Entry extends fs.DirEntry, add some useful methods
type Entry interface {
fs.DirEntry
// Path get file/dir full path. eg: "/path/to/file.go"
// Path gets file/dir full path. eg: "/path/to/file.go"
Path() string
// Info get file info. like fs.DirEntry.Info(), but will cache result.
Info() (fs.FileInfo, error)
@@ -42,12 +42,12 @@ func NewEntry(fPath string, ent fs.DirEntry) Entry {
}
}
// Path get full file/dir path. eg: "/path/to/file.go"
// Path gets full file/dir path. eg: "/path/to/file.go"
func (e *entry) Path() string {
return e.path
}
// Info get file info, will cache result
// Info gets file info, will cache result
func (e *entry) Info() (fs.FileInfo, error) {
if e.stat == nil {
e.stat, e.sErr = e.DirEntry.Info()
@@ -63,7 +63,7 @@ func (e *entry) String() string {
// FileInfo extends fs.FileInfo, add some useful methods
type FileInfo interface {
fs.FileInfo
// Path get file full path. eg: "/path/to/file.go"
// Path gets file full path. eg: "/path/to/file.go"
Path() string
}
@@ -80,7 +80,7 @@ func NewFileInfo(fPath string, info fs.FileInfo) FileInfo {
}
}
// Path get file full path. eg: "/path/to/file.go"
// Path gets file full path. eg: "/path/to/file.go"
func (fi *fileInfo) Path() string {
return fi.fullPath
}

View File

@@ -11,19 +11,19 @@ import (
"github.com/gookit/goutil/strutil"
)
// FilePathInDirs get full file path in dirs.
// FilePathInDirs get full file path in dirs. return empty string if not found.
//
// Params:
// - file: can be relative path, file name, full path.
// - dirs: dir paths
func FilePathInDirs(file string, dirs ...string) string {
file = comfunc.ExpandHome(file)
if FileExists(file) {
return file
func FilePathInDirs(fPath string, dirs ...string) string {
fPath = comfunc.ExpandHome(fPath)
if FileExists(fPath) {
return fPath
}
for _, dirPath := range dirs {
fPath := JoinSubPaths(dirPath, file)
fPath := JoinSubPaths(dirPath, fPath)
if FileExists(fPath) {
return fPath
}
@@ -102,12 +102,12 @@ func SearchNameUpx(dirPath, name string) (string, bool) {
// WalkDir walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root.
//
// TIP: will recursively find in sub dirs.
// TIP: will recursively found in sub dirs.
func WalkDir(dir string, fn fs.WalkDirFunc) error {
return filepath.WalkDir(dir, fn)
}
// Glob find files by glob path pattern. alias of filepath.Glob()
// Glob finds files by glob path pattern. alias of filepath.Glob()
// and support filter matched files by name.
//
// Usage:
@@ -132,8 +132,6 @@ func Glob(pattern string, fls ...NameMatchFunc) []string {
}
// GlobWithFunc find files by glob path pattern, then 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 {
@@ -207,7 +205,7 @@ func ApplyFilters(fPath string, ent fs.DirEntry, filters []FilterFunc) bool {
// FindInDir code refer the go pkg: path/filepath.glob()
//
// - TIP: will be not find in subdir.
// - TIP: will be not found in sub-dir.
//
// filters: return false will skip the file.
func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error) {
@@ -235,9 +233,22 @@ func FindInDir(dir string, handleFn HandleFunc, filters ...FilterFunc) (e error)
continue
}
if err := handleFn(filePath, ent); err != nil {
return err
if err1 := handleFn(filePath, ent); err1 != nil {
return err1
}
}
return nil
}
// FileInDirs returns the first file path in the given dirs.
func FileInDirs(paths []string, names ...string) string {
for _, pathDir := range paths {
for _, name := range names {
file := pathDir + "/" + name
if IsFile(file) {
return file
}
}
}
return ""
}

View File

@@ -6,8 +6,8 @@ import (
"path/filepath"
"strings"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/x/basefn"
)
// PathSep alias of os.PathSeparator

View File

@@ -3,39 +3,66 @@ package fsutil
import (
"os"
"path/filepath"
"strings"
"github.com/gookit/goutil/internal/comfunc"
)
// DirPath get dir path from filepath, without last name.
func DirPath(fpath string) string { return filepath.Dir(fpath) }
// DirPath get dir path from filepath, without a last name.
func DirPath(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) }
// Dir get dir path from filepath, without a 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 filepath.Base(fpath) }
// PathName get file/dir name from a full path
func PathName(fPath string) string { return filepath.Base(fPath) }
// PathNoExt get path from full path, without ext.
//
// eg: path/to/main.go => "path/to/main"
func PathNoExt(fPath string) string {
ext := filepath.Ext(fPath)
if el := len(ext); el > 0 {
return fPath[:len(fPath)-el]
}
return fPath
}
// Name get file/dir name from full path.
//
// eg: path/to/main.go => main.go
func Name(fpath string) string {
if fpath == "" {
// eg: path/to/main.go => "main.go"
func Name(fPath string) string {
if fPath == "" {
return ""
}
return filepath.Base(fpath)
return filepath.Base(fPath)
}
// NameNoExt get file name from a full path, without an ext.
//
// eg: path/to/main.go => "main"
func NameNoExt(fPath string) string {
if fPath == "" {
return ""
}
fName := filepath.Base(fPath)
if pos := strings.LastIndexByte(fName, '.'); pos > 0 {
return fName[:pos]
}
return fName
}
// FileExt get filename ext. alias of filepath.Ext()
//
// eg: path/to/main.go => ".go"
func FileExt(fpath string) string { return filepath.Ext(fpath) }
func FileExt(fPath string) string { return filepath.Ext(fPath) }
// Extname get filename ext. alias of filepath.Ext()
//
// eg: path/to/main.go => "go"
func Extname(fpath string) string {
if ext := filepath.Ext(fpath); len(ext) > 0 {
func Extname(fPath string) string {
if ext := filepath.Ext(fPath); len(ext) > 0 {
return ext[1:]
}
return ""
@@ -44,14 +71,14 @@ func Extname(fpath string) string {
// Suffix get filename ext. alias of filepath.Ext()
//
// eg: path/to/main.go => ".go"
func Suffix(fpath string) string { return filepath.Ext(fpath) }
func Suffix(fPath string) string { return filepath.Ext(fPath) }
// Expand will parse first `~` as user home dir path.
// Expand will parse first `~` to user home dir path.
func Expand(pathStr string) string {
return comfunc.ExpandHome(pathStr)
}
// ExpandPath will parse `~` as user home dir path.
// ExpandPath will parse `~` to user home dir path.
func ExpandPath(pathStr string) string {
return comfunc.ExpandHome(pathStr)
}

View File

@@ -6,7 +6,7 @@ import (
"os"
)
// DetectMime detect file mime type. alias of MimeType()
// DetectMime detect a file mime type. alias of MimeType()
func DetectMime(path string) string {
return MimeType(path)
}

View File

@@ -9,7 +9,7 @@ import (
"path/filepath"
"strings"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/x/basefn"
)
// Mkdir alias of os.MkdirAll()
@@ -17,7 +17,7 @@ func Mkdir(dirPath string, perm os.FileMode) error {
return os.MkdirAll(dirPath, perm)
}
// MkDirs batch make multi dirs at once
// MkDirs batch makes multi dirs at once
func MkDirs(perm os.FileMode, dirPaths ...string) error {
for _, dirPath := range dirPaths {
if err := os.MkdirAll(dirPath, perm); err != nil {
@@ -27,7 +27,7 @@ func MkDirs(perm os.FileMode, dirPaths ...string) error {
return nil
}
// MkSubDirs batch make multi sub-dirs at once
// MkSubDirs batch makes multi sub-dirs at once
func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error {
for _, dirName := range subDirs {
dirPath := parentDir + "/" + dirName
@@ -38,7 +38,7 @@ func MkSubDirs(perm os.FileMode, parentDir string, subDirs ...string) error {
return nil
}
// MkParentDir quick create parent dir for given path.
// MkParentDir quickly create parent dir for a given path.
func MkParentDir(fpath string) error {
dirPath := filepath.Dir(fpath)
if !IsDir(dirPath) {
@@ -232,10 +232,10 @@ func SafeRemoveAll(path string) {
_ = os.RemoveAll(path)
}
// RmIfExist removes the named file or (empty) directory on exists.
// RmIfExist removes the named file or (empty) directory on existing.
func RmIfExist(fPath string) error { return DeleteIfExist(fPath) }
// DeleteIfExist removes the named file or (empty) directory on exists.
// DeleteIfExist removes the named file or (empty) directory on existing.
func DeleteIfExist(fPath string) error {
if PathExists(fPath) {
return os.Remove(fPath)
@@ -243,10 +243,10 @@ func DeleteIfExist(fPath string) error {
return nil
}
// RmFileIfExist removes the named file on exists.
// RmFileIfExist removes the named file on existing.
func RmFileIfExist(fPath string) error { return DeleteIfFileExist(fPath) }
// DeleteIfFileExist removes the named file on exists.
// DeleteIfFileExist removes the named file on existing.
func DeleteIfFileExist(fPath string) error {
if IsFile(fPath) {
return os.Remove(fPath)

View File

@@ -7,7 +7,7 @@ import (
"os"
"text/scanner"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/x/basefn"
)
// NewIOReader instance by input file path or io.Reader

View File

@@ -4,7 +4,7 @@ import (
"io"
"os"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/x/basefn"
)
// ************************************************************
@@ -74,9 +74,14 @@ func SaveFile(filePath string, data any, optFns ...OpenOptionFunc) error {
return WriteFile(filePath, data, opt.Perm, opt.Flag)
}
// PutContents create file and write contents to file at once.
// WriteData Quick write any data to file, alias of PutContents
func WriteData(filePath string, data any, fileFlag ...int) (int, error) {
return PutContents(filePath, data, fileFlag...)
}
// PutContents create file and write contents to file at once. Will auto create dir
//
// data type allow: string, []byte, io.Reader. will auto create dir.
// data type allows: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags (override write)
//
@@ -92,9 +97,9 @@ func PutContents(filePath string, data any, fileFlag ...int) (int, error) {
return WriteOSFile(f, data)
}
// WriteFile create file and write contents to file, can set perm for file.
// WriteFile create file and write contents to file, can set perm for a file.
//
// data type allow: string, []byte, io.Reader
// data type allows: string, []byte, io.Reader
//
// Tip: file flag default is FsCWTFlags (override write)
//
@@ -114,7 +119,7 @@ func WriteFile(filePath string, data any, perm os.FileMode, fileFlag ...int) err
// WriteOSFile write data to give os.File, then close file.
//
// data type allow: string, []byte, io.Reader
// data type allows: string, []byte, io.Reader
func WriteOSFile(f *os.File, data any) (n int, err error) {
switch typData := data.(type) {
case []byte:

View File

@@ -23,6 +23,14 @@ func CallOn(cond bool, fn ErrFunc) error {
return nil
}
// IfElseFn call okFunc() on condition is true, else call elseFn()
func IfElseFn(cond bool, okFn, elseFn ErrFunc) error {
if cond {
return okFn()
}
return elseFn()
}
// CallOrElse call okFunc() on condition is true, else call elseFn()
func CallOrElse(cond bool, okFn, elseFn ErrFunc) error {
if cond {
@@ -43,7 +51,7 @@ func SafeRun(fn func()) (err error) {
}
}()
fn()
return nil
return err
}
// SafeRunWithError sync run a func with error.

View File

@@ -1,13 +1,16 @@
// Package goutil 💪 Useful utils for Go: int, string, array/slice, map, error, time, format, CLI, ENV, filesystem,
// Package goutil 💪 Useful utils for Go: byte, int, string, array/slice, map, struct, reflect, error, time, format, CLI, ENV, filesystem,
// system, testing, debug and more.
package goutil
import (
"fmt"
"reflect"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/goinfo"
"github.com/gookit/goutil/internal/checkfn"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/x/basefn"
"github.com/gookit/goutil/x/goinfo"
)
// Value alias of structs.Value
@@ -18,7 +21,7 @@ func Panicf(format string, v ...any) {
panic(fmt.Sprintf(format, v...))
}
// PanicIf if cond = true, panics with error message
// PanicIf if cond = true, panics with an error message
func PanicIf(cond bool, fmtAndArgs ...any) {
basefn.PanicIf(cond, fmtAndArgs...)
}
@@ -74,21 +77,6 @@ func Must[T any](v T, err error) T {
return v
}
// FuncName get func name
func FuncName(f any) string {
return goinfo.FuncName(f)
}
// PkgName get current package name. alias of goinfo.PkgName()
//
// Usage:
//
// funcName := goutil.FuncName(fn)
// pgkName := goutil.PkgName(funcName)
func PkgName(funcName string) string {
return goinfo.PkgName(funcName)
}
// ErrOnFail return input error on cond is false, otherwise return nil
func ErrOnFail(cond bool, err error) error {
return OrError(cond, err)
@@ -102,7 +90,7 @@ func OrError(cond bool, err error) error {
return nil
}
// OrValue get
// OrValue get. like: if cond { okVal } else { elVal }
func OrValue[T any](cond bool, okVal, elVal T) T {
if cond {
return okVal
@@ -117,3 +105,105 @@ func OrReturn[T any](cond bool, okFn, elseFn func() T) T {
}
return elseFn()
}
//
// ------------------------- check functions -------------------------
//
// IsNil value check
func IsNil(v any) bool {
if v == nil {
return true
}
return reflects.IsNil(reflect.ValueOf(v))
}
// IsZero value check, alias of the IsEmpty()
var IsZero = IsEmpty
// IsEmpty value check
func IsEmpty(v any) bool {
if v == nil {
return true
}
return reflects.IsEmpty(reflect.ValueOf(v))
}
// IsZeroReal Alias of the IsEmptyReal()
var IsZeroReal = IsEmptyReal
// IsEmptyReal checks for empty given value and also real empty value if the passed value is a pointer
func IsEmptyReal(v any) bool {
if v == nil {
return true
}
return reflects.IsEmptyReal(reflect.ValueOf(v))
}
// IsFunc value
func IsFunc(val any) bool {
if val == nil {
return false
}
return reflect.TypeOf(val).Kind() == reflect.Func
}
// IsEqual determines if two objects are considered equal.
//
// TIP: cannot compare a function type
func IsEqual(src, dst any) bool {
if src == nil || dst == nil {
return src == dst
}
// cannot compare a function type
if IsFunc(src) || IsFunc(dst) {
return false
}
return reflects.IsEqual(src, dst)
}
// Contains try loop over the data check if the data includes the element.
// alias of the IsContains
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func Contains(data, elem any) bool {
_, found := checkfn.Contains(data, elem)
return found
}
// IsContains try loop over the data check if the data includes the element.
//
// TIP: only support types: string, map, array, slice
//
// map - check key exists
// string - check sub-string exists
// array,slice - check sub-element exists
func IsContains(data, elem any) bool {
_, found := checkfn.Contains(data, elem)
return found
}
//
// ------------------------- goinfo functions -------------------------
//
// FuncName get func name
func FuncName(f any) string {
return goinfo.FuncName(f)
}
// PkgName get the current package name. alias of goinfo.PkgName()
//
// Usage:
//
// funcName := goutil.FuncName(fn)
// pgkName := goutil.PkgName(funcName)
func PkgName(funcName string) string {
return goinfo.PkgName(funcName)
}

View File

@@ -11,12 +11,12 @@ import (
// are part of the same overall task.
type ErrGroup = syncs.ErrGroup
// NewCtxErrGroup instance
// NewCtxErrGroup instance. use for batch run tasks, can with context.
func NewCtxErrGroup(ctx context.Context, limit ...int) (*ErrGroup, context.Context) {
return syncs.NewCtxErrGroup(ctx, limit...)
}
// NewErrGroup instance
// NewErrGroup instance. use for batch run tasks
func NewErrGroup(limit ...int) *ErrGroup {
return syncs.NewErrGroup(limit...)
}
@@ -48,7 +48,6 @@ func (p *QuickRun) Add(fns ...RunFn) *QuickRun {
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
}

View File

@@ -123,3 +123,8 @@ var numReg = regexp.MustCompile(`^[-+]?\d*\.?\d+$`)
// IsNumeric returns true if the given string is a numeric, otherwise false.
func IsNumeric(s string) bool { return numReg.MatchString(s) }
// IsHttpURL check input is http/https url
func IsHttpURL(s string) bool {
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
}

View File

@@ -0,0 +1,40 @@
package checkfn
import (
"os"
"strings"
)
var detectedWSL bool
var detectedWSLContents string
// IsWSL system env
// https://github.com/Microsoft/WSL/issues/423#issuecomment-221627364
func IsWSL() bool {
// ENV:
// WSL_DISTRO_NAME=Debian
if len(os.Getenv("WSL_DISTRO_NAME")) > 0 {
return true
}
if !detectedWSL {
b := make([]byte, 1024)
// `cat /proc/version`
// on mac:
// !not the file!
// on linux(debian,ubuntu,alpine):
// Linux version 4.19.121-linuxkit (root@18b3f92ade35) (gcc version 9.2.0 (Alpine 9.2.0)) #1 SMP Thu Jan 21 15:36:34 UTC 2021
// on win git bash, conEmu:
// MINGW64_NT-10.0-19042 version 3.1.7-340.x86_64 (@WIN-N0G619FD3UK) (gcc version 9.3.0 (GCC) ) 2020-10-23 13:08 UTC
// on WSL:
// Linux version 4.4.0-19041-Microsoft (Microsoft@Microsoft.com) (gcc version 5.4.0 (GCC) ) #488-Microsoft Mon Sep 01 13:43:00 PST 2020
f, err := os.Open("/proc/version")
if err == nil {
_, _ = f.Read(b) // ignore error
f.Close()
detectedWSLContents = string(b)
}
detectedWSL = true
}
return strings.Contains(detectedWSLContents, "Microsoft")
}

View File

@@ -1,3 +1,3 @@
# common func for internal use
# Common func for internal use
- don't depend on other external packages

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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])
}

View File

@@ -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:

View File

@@ -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
}

View File

@@ -62,11 +62,15 @@ var std = New()
// ${var_name} Only var name
// ${var_name | default} With default value
// ${var_name | ?error} With error on value is empty.
//
// see Parser.Parse
func Parse(val string) (string, error) {
return std.Parse(val)
}
// SafeParse parse ENV var value from input string, support default value.
//
// see Parser.Parse
func SafeParse(val string) string {
s, _ := std.Parse(val)
return s
@@ -100,6 +104,7 @@ func New(optFns ...ParseOptFn) *Parser {
// ${var_name} Only var name
// ${var_name | default} With default value
// ${var_name | ?error} With error on value is empty.
// ${VAR_NAME1}/path/${VAR_NAME2} Allow multi var name.
func (p *Parser) Parse(val string) (newVal string, err error) {
if p.Regexp == nil {
p.useDefaultRegex()

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"io"
"os"
)
// MustString encode data to json string, will panic on error
@@ -62,3 +63,13 @@ func DecodeString(str string, ptr any) error {
func DecodeReader(r io.Reader, ptr any) error {
return json.NewDecoder(r).Decode(ptr)
}
// DecodeFile decode JSON from file, bind data to ptr.
func DecodeFile(file string, ptr any) error {
bs, err := os.ReadFile(file)
if err != nil {
return err
}
return json.Unmarshal(bs, ptr)
}

View File

@@ -11,8 +11,20 @@ import (
"github.com/gookit/goutil/strutil"
)
// alias functions
var (
// ToStrMap convert map[string]any to map[string]string
ToStrMap = ToStringMap
// ToL2StrMap convert map[string]any to map[string]map[string]string
ToL2StrMap = ToL2StringMap
)
// KeyToLower convert keys to lower case.
func KeyToLower(src map[string]string) map[string]string {
if len(src) == 0 {
return src
}
newMp := make(map[string]string, len(src))
for k, v := range src {
k = strings.ToLower(k)
@@ -21,7 +33,22 @@ func KeyToLower(src map[string]string) map[string]string {
return newMp
}
// ToStringMap convert map[string]any to map[string]string
// AnyToStrMap try convert any(map[string]any, map[string]string) to map[string]string
func AnyToStrMap(src any) map[string]string {
if src == nil {
return nil
}
if m, ok := src.(map[string]string); ok {
return m
}
if m, ok := src.(map[string]any); ok {
return ToStringMap(m)
}
return nil
}
// ToStringMap simple convert map[string]any to map[string]string
func ToStringMap(src map[string]any) map[string]string {
strMp := make(map[string]string, len(src))
for k, v := range src {
@@ -30,7 +57,25 @@ func ToStringMap(src map[string]any) map[string]string {
return strMp
}
// CombineToSMap combine two string-slice to SMap(map[string]string)
// ToL2StringMap convert map[string]any to map[string]map[string]string
func ToL2StringMap(groupsMap map[string]any) map[string]map[string]string {
if len(groupsMap) == 0 {
return nil
}
l2sMap := make(map[string]map[string]string, len(groupsMap))
for k, v := range groupsMap {
if mp, ok := v.(map[string]any); ok {
l2sMap[k] = ToStringMap(mp)
} else if smp, ok := v.(map[string]string); ok {
l2sMap[k] = smp
}
}
return l2sMap
}
// CombineToSMap combine two string-slices to SMap(map[string]string)
func CombineToSMap(keys, values []string) SMap {
return arrutil.CombineToSMap(keys, values)
}
@@ -40,6 +85,54 @@ func CombineToMap[K comdef.SortedType, V any](keys []K, values []V) map[K]V {
return arrutil.CombineToMap(keys, values)
}
// SliceToSMap convert string k-v pairs slice to map[string]string
// - eg: []string{k1,v1,k2,v2} -> map[string]string{k1:v1, k2:v2}
func SliceToSMap(kvPairs ...string) map[string]string {
ln := len(kvPairs)
// check kvPairs length must be even
if ln == 0 || ln%2 != 0 {
return nil
}
sMap := make(map[string]string, ln/2)
for i := 0; i < ln; i += 2 {
sMap[kvPairs[i]] = kvPairs[i+1]
}
return sMap
}
// SliceToMap convert any k-v pairs slice to map[string]any
func SliceToMap(kvPairs ...any) map[string]any {
ln := len(kvPairs)
// check kvPairs length must be even
if ln == 0 || ln%2 != 0 {
return nil
}
mp := make(map[string]any, ln/2)
for i := 0; i < ln; i += 2 {
kStr := strutil.SafeString(kvPairs[i])
mp[kStr] = kvPairs[i+1]
}
return mp
}
// SliceToTypeMap convert k-v pairs slice to map[string]T
func SliceToTypeMap[T any](valFunc func(any) T, kvPairs ...any) map[string]T {
ln := len(kvPairs)
// check kvPairs length must be even
if ln == 0 || ln%2 != 0 {
return nil
}
mp := make(map[string]T, ln/2)
for i := 0; i < ln; i += 2 {
kStr := strutil.SafeString(kvPairs[i])
mp[kStr] = valFunc(kvPairs[i+1])
}
return mp
}
// ToAnyMap convert map[TYPE1]TYPE2 to map[string]any
func ToAnyMap(mp any) map[string]any {
amp, _ := TryAnyMap(mp)
@@ -51,15 +144,23 @@ func TryAnyMap(mp any) (map[string]any, error) {
if aMp, ok := mp.(map[string]any); ok {
return aMp, nil
}
if sMp, ok := mp.(map[string]string); ok {
anyMp := make(map[string]any, len(sMp))
for k, v := range sMp {
anyMp[k] = v
}
return anyMp, nil
}
rv := reflect.Indirect(reflect.ValueOf(mp))
if rv.Kind() != reflect.Map {
return nil, errors.New("input is not a map value")
return nil, errors.New("input is not a map value type")
}
anyMp := make(map[string]any, rv.Len())
for _, key := range rv.MapKeys() {
anyMp[key.String()] = rv.MapIndex(key).Interface()
keyStr := strutil.SafeString(key.Interface())
anyMp[keyStr] = rv.MapIndex(key).Interface()
}
return anyMp, nil
}
@@ -125,9 +226,7 @@ func ToString(mp map[string]any) string {
}
// ToString2 simple and quickly convert a map to string.
func ToString2(mp any) string {
return NewFormatter(mp).Format()
}
func ToString2(mp any) string { return NewFormatter(mp).Format() }
// FormatIndent format map data to string with newline and indent.
func FormatIndent(mp any, indent string) string {

View File

@@ -4,6 +4,7 @@ import (
"strings"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/mathutil"
"github.com/gookit/goutil/strutil"
)
@@ -25,37 +26,10 @@ func (d Data) IsEmpty() bool {
return len(d) == 0
}
// Value get from the data map
func (d Data) Value(key string) (any, bool) {
val, ok := d.GetByPath(key)
return val, ok
}
// Get value from the data map.
// Supports dot syntax to get deep values. eg: top.sub
func (d Data) Get(key string) any {
if val, ok := d.GetByPath(key); ok {
return val
}
return nil
}
// GetByPath get value from the data map by path. eg: top.sub
// Supports dot syntax to get deep values.
func (d Data) GetByPath(path string) (any, bool) {
if val, ok := d[path]; ok {
return val, true
}
// is key path.
if strings.ContainsRune(path, '.') {
val, ok := GetByPath(path, d)
if ok {
return val, true
}
}
return nil, false
}
//
// endregion
// region T: set value(s)
//
// Set value to the data map
func (d Data) Set(key string, val any) {
@@ -65,7 +39,7 @@ func (d Data) Set(key string, val any) {
// SetByPath sets a value in the map.
// Supports dot syntax to set deep values.
//
// For example:
// Example:
//
// d.SetByPath("name.first", "Mat")
func (d Data) SetByPath(path string, value any) error {
@@ -78,7 +52,7 @@ func (d Data) SetByPath(path string, value any) error {
// SetByKeys sets a value in the map by path keys.
// Supports dot syntax to set deep values.
//
// For example:
// Example:
//
// d.SetByKeys([]string{"name", "first"}, "Mat")
func (d Data) SetByKeys(keys []string, value any) error {
@@ -102,6 +76,61 @@ func (d Data) SetByKeys(keys []string, value any) error {
// return SetByKeys((*map[string]any)(d), keys, value)
}
//
// endregion
// region T: read value(s)
//
// Value get from the data map
func (d Data) Value(key string) (any, bool) {
val, ok := d.GetByPath(key)
return val, ok
}
// Get value from the data map.
// Supports dot syntax to get deep values. eg: top.sub
func (d Data) Get(key string) any {
if val, ok := d.GetByPath(key); ok {
return val
}
return nil
}
// One get value from the data by multi paths. will return first founded value
func (d Data) One(keys ...string) any {
if val, ok := d.TryOne(keys...); ok {
return val
}
return nil
}
// TryOne get value from the data by multi paths. will return first founded value
func (d Data) TryOne(keys ...string) (any, bool) {
for _, path := range keys {
if val, ok := d.GetByPath(path); ok {
return val, true
}
}
return nil, false
}
// GetByPath get value from the data map by path. eg: top.sub
// Supports dot syntax to get deep values.
func (d Data) GetByPath(path string) (any, bool) {
if val, ok := d[path]; ok {
return val, true
}
// is a key path.
if strings.ContainsRune(path, '.') {
val, ok := GetByPath(path, d)
if ok {
return val, true
}
}
return nil, false
}
// Default get value from the data map with default value
func (d Data) Default(key string, def any) any {
if val, ok := d.GetByPath(key); ok {
@@ -142,10 +171,20 @@ func (d Data) Uint64(key string) uint64 {
return 0
}
// Str value get by key
// Str value gets by key
func (d Data) Str(key string) string {
if val, ok := d.GetByPath(key); ok {
return strutil.QuietString(val)
return strutil.SafeString(val)
}
return ""
}
// StrOne value gets by multi keys, will return first value
func (d Data) StrOne(keys ...string) string {
for _, key := range keys {
if val, ok := d.GetByPath(key); ok {
return strutil.SafeString(val)
}
}
return ""
}
@@ -156,45 +195,46 @@ func (d Data) Bool(key string) bool {
if !ok {
return false
}
switch tv := val.(type) {
case string:
return strutil.QuietBool(tv)
case bool:
return tv
default:
return false
}
return comfunc.Bool(val)
}
// Strings get []string value
func (d Data) Strings(key string) []string {
val, ok := d.GetByPath(key)
if !ok {
return nil
}
switch typVal := val.(type) {
case string:
return []string{typVal}
case []string:
return typVal
case []any:
return arrutil.SliceToStrings(typVal)
default:
return nil
// BoolOne value gets from multi keys, return first value
func (d Data) BoolOne(keys ...string) bool {
for _, key := range keys {
if val, ok := d.GetByPath(key); ok {
return comfunc.Bool(val)
}
}
return false
}
// StrSplit get strings by split key value
func (d Data) StrSplit(key, sep string) []string {
if val, ok := d.GetByPath(key); ok {
return strings.Split(strutil.QuietString(val), sep)
// StringsOne get []string value by multi keys, return first founded value
func (d Data) StringsOne(keys ...string) []string {
for _, key := range keys {
if val, ok := d.GetByPath(key); ok {
return arrutil.AnyToStrings(val)
}
}
return nil
}
// StringsByStr value get by key
// Strings get []string value by key
func (d Data) Strings(key string) []string {
if val, ok := d.GetByPath(key); ok {
return arrutil.AnyToStrings(val)
}
return nil
}
// StrSplit get strings by split string value
func (d Data) StrSplit(key, sep string) []string {
if val, ok := d.GetByPath(key); ok {
return strings.Split(strutil.SafeString(val), sep)
}
return nil
}
// StringsByStr value gets by key, will split string value by ","
func (d Data) StringsByStr(key string) []string {
return d.StrSplit(key, ",")
}
@@ -221,23 +261,40 @@ func (d Data) StringMap(key string) map[string]string {
}
}
// Sub get sub value as new Data
// Sub get sub value(map[string]any) as new Data
func (d Data) Sub(key string) Data {
if val, ok := d.GetByPath(key); ok {
if sub, ok := val.(map[string]any); ok {
return sub
}
return d.toAnyMap(val)
}
return nil
}
// AnyMap get sub value as map[string]any
func (d Data) AnyMap(key string) map[string]any {
if val, ok := d.GetByPath(key); ok {
return d.toAnyMap(val)
}
return nil
}
// AnyMap get sub value as map[string]any
func (d Data) toAnyMap(val any) map[string]any {
switch tv := val.(type) {
case map[string]string:
return ToAnyMap(tv)
case map[string]any:
return tv
default:
return nil
}
}
// Slice get []any value from data map
func (d Data) Slice(key string) ([]any, error) {
val, ok := d.GetByPath(key)
if !ok {
return nil, nil
}
return arrutil.AnyToSlice(val)
}

View File

@@ -202,6 +202,14 @@ func TypedKeys[K comdef.SimpleType, V any](mp map[K]V) (keys []K) {
return
}
// FirstKey returns the first key of the given map.
func FirstKey[T any](mp map[string]T) string {
for key := range mp {
return key
}
return ""
}
// Values get all values from the given map.
func Values(mp any) (values []any) {
rv := reflect.Indirect(reflect.ValueOf(mp))

View File

@@ -58,6 +58,11 @@ func MergeSMap(src, dst map[string]string, ignoreCase bool) map[string]string {
return MergeStringMap(src, dst, ignoreCase)
}
// MergeStrMap simple merge two string map. merge src to dst map
func MergeStrMap(src, dst map[string]string) map[string]string {
return MergeStringMap(src, dst, false)
}
// 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 {
@@ -87,6 +92,25 @@ func MergeMultiSMap(mps ...map[string]string) map[string]string {
return newMp
}
// MergeL2StrMap merge multi level2 string-map data. The back map covers the front.
func MergeL2StrMap(mps ...map[string]map[string]string) map[string]map[string]string {
newMp := make(map[string]map[string]string)
for _, mp := range mps {
for k, v := range mp {
// merge level 2 value
if oldV, ok := newMp[k]; ok {
for k1, v1 := range v {
oldV[k1] = v1
}
newMp[k] = oldV
} else {
newMp[k] = v
}
}
}
return newMp
}
// FilterSMap filter empty elem for the string map.
func FilterSMap(sm map[string]string) map[string]string {
for key, val := range sm {

View File

@@ -171,7 +171,7 @@ func setMapByKeys(rv reflect.Value, keys []string, nv reflect.Value) (err error)
if isSlice {
// key is slice index
if strutil.IsNumeric(key) {
if strutil.IsInt(key) {
idx, _ = strconv.Atoi(key)
}
@@ -231,7 +231,7 @@ func setMapByKeys(rv reflect.Value, keys []string, nv reflect.Value) (err error)
// next key is index number.
nxtKey := keys[i+1]
if strutil.IsNumeric(nxtKey) {
if strutil.IsInt(nxtKey) {
idx, _ = strconv.Atoi(nxtKey)
sliLen := tmpV.Len()
wantLen := idx + 1
@@ -275,7 +275,7 @@ func setMapByKeys(rv reflect.Value, keys []string, nv reflect.Value) (err error)
}
break
} else if isSlice && strutil.IsNumeric(key) { // (E). slice from ptr slice
} else if isSlice && strutil.IsInt(key) { // (E). slice from ptr slice
idx, _ = strconv.Atoi(key)
sliLen := rv.Len()
wantLen := idx + 1

View File

@@ -5,22 +5,23 @@ import (
"github.com/gookit/goutil/strutil"
)
// SMap is alias of map[string]string
type SMap map[string]string
// SMap and StrMap is alias of map[string]string
type SMap = StrMap
type StrMap map[string]string
// IsEmpty of the data map
func (m SMap) IsEmpty() bool {
func (m StrMap) IsEmpty() bool {
return len(m) == 0
}
// Has key on the data map
func (m SMap) Has(key string) bool {
func (m StrMap) Has(key string) bool {
_, ok := m[key]
return ok
}
// HasValue on the data map
func (m SMap) HasValue(val string) bool {
func (m StrMap) HasValue(val string) bool {
for _, v := range m {
if v == val {
return true
@@ -30,25 +31,25 @@ func (m SMap) HasValue(val string) bool {
}
// Load data to the map
func (m SMap) Load(data map[string]string) {
func (m StrMap) Load(data map[string]string) {
for k, v := range data {
m[k] = v
}
}
// Set value to the data map
func (m SMap) Set(key string, val any) {
func (m StrMap) Set(key string, val any) {
m[key] = strutil.MustString(val)
}
// Value get from the data map
func (m SMap) Value(key string) (string, bool) {
func (m StrMap) Value(key string) (string, bool) {
val, ok := m[key]
return val, ok
}
// Default get value by key. if not found, return defVal
func (m SMap) Default(key, defVal string) string {
func (m StrMap) Default(key, defVal string) string {
if val, ok := m[key]; ok {
return val
}
@@ -56,41 +57,51 @@ func (m SMap) Default(key, defVal string) string {
}
// Get value by key
func (m SMap) Get(key string) string {
func (m StrMap) Get(key string) string {
return m[key]
}
// Int value get
func (m SMap) Int(key string) int {
func (m StrMap) Int(key string) int {
if val, ok := m[key]; ok {
return mathutil.QuietInt(val)
return mathutil.SafeInt(val)
}
return 0
}
// Int64 value get
func (m SMap) Int64(key string) int64 {
func (m StrMap) Int64(key string) int64 {
if val, ok := m[key]; ok {
return mathutil.QuietInt64(val)
return mathutil.SafeInt64(val)
}
return 0
}
// Str value get
func (m SMap) Str(key string) string {
func (m StrMap) Str(key string) string {
return m[key]
}
// StrOne get first founded value by keys
func (m SMap) StrOne(keys ...string) string {
for _, key := range keys {
if val, ok := m[key]; ok {
return val
}
}
return ""
}
// Bool value get
func (m SMap) Bool(key string) bool {
func (m StrMap) Bool(key string) bool {
if val, ok := m[key]; ok {
return strutil.QuietBool(val)
return strutil.SafeBool(val)
}
return false
}
// Ints value to []int
func (m SMap) Ints(key string) []int {
func (m StrMap) Ints(key string) []int {
if val, ok := m[key]; ok {
return strutil.Ints(val, ValSepStr)
}
@@ -98,7 +109,7 @@ func (m SMap) Ints(key string) []int {
}
// Strings value to []string
func (m SMap) Strings(key string) (ss []string) {
func (m StrMap) Strings(key string) (ss []string) {
if val, ok := m[key]; ok {
return strutil.ToSlice(val, ValSepStr)
}
@@ -106,21 +117,21 @@ func (m SMap) Strings(key string) (ss []string) {
}
// IfExist key, then call the fn with value.
func (m SMap) IfExist(key string, fn func(val string)) {
func (m StrMap) IfExist(key string, fn func(val string)) {
if val, ok := m[key]; ok {
fn(val)
}
}
// IfValid value is not empty, then call the fn
func (m SMap) IfValid(key string, fn func(val string)) {
func (m StrMap) IfValid(key string, fn func(val string)) {
if val, ok := m[key]; ok && val != "" {
fn(val)
}
}
// Keys of the string-map
func (m SMap) Keys() []string {
func (m StrMap) Keys() []string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
@@ -129,7 +140,7 @@ func (m SMap) Keys() []string {
}
// Values of the string-map
func (m SMap) Values() []string {
func (m StrMap) Values() []string {
ss := make([]string, 0, len(m))
for _, v := range m {
ss = append(ss, v)
@@ -138,7 +149,7 @@ func (m SMap) Values() []string {
}
// ToKVPairs slice convert. eg: {k1:v1,k2:v2} => {k1,v1,k2,v2}
func (m SMap) ToKVPairs() []string {
func (m StrMap) ToKVPairs() []string {
pairs := make([]string, 0, len(m)*2)
for k, v := range m {
pairs = append(pairs, k, v)
@@ -147,6 +158,6 @@ func (m SMap) ToKVPairs() []string {
}
// String data to string
func (m SMap) String() string {
func (m StrMap) String() string {
return ToString2(m)
}

51
vendor/github.com/gookit/goutil/maputil/smap_l2.go generated vendored Normal file
View File

@@ -0,0 +1,51 @@
package maputil
import "github.com/gookit/goutil/strutil"
// L2StrMap is alias of map[string]map[string]string
type L2StrMap map[string]map[string]string
// Load data, merge new data to old
func (m L2StrMap) Load(mp map[string]map[string]string) {
for k, v := range mp {
if oldV, ok := m[k]; ok {
for k1, v1 := range v {
oldV[k1] = v1
}
m[k] = oldV
} else {
m[k] = v
}
}
}
// Value get by key path. eg: "top.sub"
func (m L2StrMap) Value(key string) (val string, ok bool) {
top, sub, found := strutil.Cut(key, KeySepStr)
if !found {
return "", false
}
if vals, ok1 := m[top]; ok1 {
val, ok = vals[sub]
return
}
return "", false
}
// Get value by key path. eg: "top.sub"
func (m L2StrMap) Get(key string) string {
val, _ := m.Value(key)
return val
}
// Exists check key path exists. eg: "top.sub"
func (m L2StrMap) Exists(key string) bool {
_, ok := m.Value(key)
return ok
}
// StrMap get by top key. eg: "top"
func (m L2StrMap) StrMap(top string) StrMap {
return m[top]
}

View File

@@ -56,7 +56,7 @@ func CompFloat[T comdef.Float](first, second T, op string) (ok bool) {
}
// CompValue compare intX,uintX,floatX value. returns `first op(=,!=,<,<=,>,>=) second`
func CompValue[T comdef.XintOrFloat](first, second T, op string) (ok bool) {
func CompValue[T comdef.Number](first, second T, op string) (ok bool) {
switch op {
case "<", "lt":
ok = first < second
@@ -75,12 +75,12 @@ func CompValue[T comdef.XintOrFloat](first, second T, op string) (ok bool) {
}
// InRange check if val in int/float range [min, max]
func InRange[T comdef.IntOrFloat](val, min, max T) bool {
func InRange[T comdef.Number](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 {
func OutRange[T comdef.Number](val, min, max T) bool {
return val < min || val > max
}

View File

@@ -7,7 +7,7 @@ import (
)
// Min compare two value and return max value
func Min[T comdef.XintOrFloat](x, y T) T {
func Min[T comdef.Number](x, y T) T {
if x < y {
return x
}
@@ -15,7 +15,7 @@ func Min[T comdef.XintOrFloat](x, y T) T {
}
// Max compare two value and return max value
func Max[T comdef.XintOrFloat](x, y T) T {
func Max[T comdef.Number](x, y T) T {
if x > y {
return x
}
@@ -23,7 +23,7 @@ func Max[T comdef.XintOrFloat](x, y T) T {
}
// SwapMin compare and always return [min, max] value
func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) {
func SwapMin[T comdef.Number](x, y T) (T, T) {
if x < y {
return x, y
}
@@ -31,7 +31,7 @@ func SwapMin[T comdef.XintOrFloat](x, y T) (T, T) {
}
// SwapMax compare and always return [max, min] value
func SwapMax[T comdef.XintOrFloat](x, y T) (T, T) {
func SwapMax[T comdef.Number](x, y T) (T, T) {
if x > y {
return x, y
}

View File

@@ -8,12 +8,12 @@ import (
)
// OrElse return default value on val is zero, else return val
func OrElse[T comdef.XintOrFloat](val, defVal T) T {
func OrElse[T comdef.Number](val, defVal T) T {
return ZeroOr(val, defVal)
}
// ZeroOr return default value on val is zero, else return val
func ZeroOr[T comdef.XintOrFloat](val, defVal T) T {
func ZeroOr[T comdef.Number](val, defVal T) T {
if val != 0 {
return val
}
@@ -27,7 +27,7 @@ func ZeroOr[T comdef.XintOrFloat](val, defVal T) T {
// LessOr(11, 10, 1) // 1
// LessOr(2, 10, 1) // 2
// LessOr(10, 10, 1) // 1
func LessOr[T comdef.XintOrFloat](val, max, devVal T) T {
func LessOr[T comdef.Number](val, max, devVal T) T {
if val < max {
return val
}
@@ -41,7 +41,7 @@ func LessOr[T comdef.XintOrFloat](val, max, devVal T) T {
// LteOr(11, 10, 1) // 11
// LteOr(2, 10, 1) // 2
// LteOr(10, 10, 1) // 10
func LteOr[T comdef.XintOrFloat](val, max, devVal T) T {
func LteOr[T comdef.Number](val, max, devVal T) T {
if val <= max {
return val
}
@@ -54,7 +54,7 @@ func LteOr[T comdef.XintOrFloat](val, max, devVal T) T {
//
// GreaterOr(23, 0, 2) // 23
// GreaterOr(0, 0, 2) // 2
func GreaterOr[T comdef.XintOrFloat](val, min, defVal T) T {
func GreaterOr[T comdef.Number](val, min, defVal T) T {
if val > min {
return val
}
@@ -67,15 +67,15 @@ func GreaterOr[T comdef.XintOrFloat](val, min, defVal T) T {
//
// GteOr(23, 0, 2) // 23
// GteOr(0, 0, 2) // 0
func GteOr[T comdef.XintOrFloat](val, min, defVal T) T {
func GteOr[T comdef.Number](val, min, defVal T) T {
if val >= min {
return val
}
return defVal
}
// Mul computes the a*b value, rounding the result.
func Mul[T1, T2 comdef.XintOrFloat](a T1, b T2) float64 {
// Mul computes the `a*b` value, rounding the result.
func Mul[T1, T2 comdef.Number](a T1, b T2) float64 {
return math.Round(SafeFloat(a) * SafeFloat(b))
}
@@ -84,8 +84,8 @@ func MulF2i(a, b float64) int {
return int(math.Round(a * b))
}
// Div computes the a/b value, result use round handle.
func Div[T1, T2 comdef.XintOrFloat](a T1, b T2) float64 {
// Div computes the `a/b` value, result uses a round handle.
func Div[T1, T2 comdef.Number](a T1, b T2) float64 {
return math.Round(SafeFloat(a) / SafeFloat(b))
}
@@ -100,7 +100,7 @@ func DivF2i(a, b float64) int {
return int(math.Round(a / b))
}
// Percent returns a values percent of the total
// Percent returns a value percentage of the total
func Percent(val, total int) float64 {
if total == 0 {
return float64(0)

View File

@@ -5,6 +5,24 @@ import (
"reflect"
)
// IsTimeType check is or alias of time.Time type
func IsTimeType(t reflect.Type) bool {
if t == nil || t.Kind() != reflect.Struct {
return false
}
// t == timeType - 无法判断自定义类型
return t == timeType || t.ConvertibleTo(timeType)
}
// IsDurationType check is or alias of time.Duration type
func IsDurationType(t reflect.Type) bool {
if t == nil || t.Kind() != reflect.Int64 {
return false
}
// t == durationType - 无法判断自定义类型
return t == durationType || t.ConvertibleTo(durationType)
}
// HasChild type check. eg: array, slice, map, struct
func HasChild(v reflect.Value) bool {
switch v.Kind() {
@@ -15,7 +33,7 @@ func HasChild(v reflect.Value) bool {
}
}
// IsArrayOrSlice check. eg: array, slice
// IsArrayOrSlice type check. eg: array, slice
func IsArrayOrSlice(k reflect.Kind) bool {
return k == reflect.Slice || k == reflect.Array
}
@@ -70,8 +88,9 @@ func CanBeNil(typ reflect.Type) bool {
return true
case reflect.Struct:
return typ == reflectValueType
default:
return false
}
return false
}
// IsFunc value
@@ -84,7 +103,7 @@ func IsFunc(val any) bool {
// IsEqual determines if two objects are considered equal.
//
// TIP: cannot compare function type
// TIP: cannot compare a function type
func IsEqual(src, dst any) bool {
if src == nil || dst == nil {
return src == dst
@@ -109,7 +128,7 @@ func IsEqual(src, dst any) bool {
// IsZero reflect value check, alias of the IsEmpty()
var IsZero = IsEmpty
// IsEmpty reflect value check
// IsEmpty reflect value check. if is ptr, check if is nil
func IsEmpty(v reflect.Value) bool {
switch v.Kind() {
case reflect.Invalid:
@@ -140,7 +159,7 @@ var IsEmptyValue = IsEmptyReal
//
// Note:
//
// Difference the IsEmpty(), if value is ptr or interface, will check real elem.
// Difference the IsEmpty(), if value is ptr or interface, will check real elem.
//
// From src/pkg/encoding/json/encode.go.
func IsEmptyReal(v reflect.Value) bool {

View File

@@ -50,44 +50,47 @@ func ConvToType(val any, typ reflect.Type) (rv reflect.Value, err error) {
// ValueByType create reflect.Value by give reflect.Type
func ValueByType(val any, typ reflect.Type) (rv reflect.Value, err error) {
// handle kind: string, bool, intX, uintX, floatX
if typ.Kind() == reflect.String || typ.Kind() <= reflect.Float64 {
return ConvToKind(val, typ.Kind())
}
var ok bool
var newRv reflect.Value
if newRv, ok = val.(reflect.Value); !ok {
newRv = reflect.ValueOf(val)
}
// try auto convert slice type
if IsArrayOrSlice(newRv.Kind()) && IsArrayOrSlice(typ.Kind()) {
return ConvSlice(newRv, typ.Elem())
// fix: check newRv is valid
if !newRv.IsValid() {
return rv, comdef.ErrConvType
}
// check type. like map
// check the same type. like map
if newRv.Type() == typ {
return newRv, nil
}
// handle kind: string, bool, intX, uintX, floatX
if typ.Kind() == reflect.String || typ.Kind() <= reflect.Float64 {
return ConvToKind(val, typ.Kind())
}
// try the auto convert slice type
if IsArrayOrSlice(newRv.Kind()) && IsArrayOrSlice(typ.Kind()) {
return ConvSlice(newRv, typ.Elem())
}
err = comdef.ErrConvType
return
}
// ValueByKind convert and create reflect.Value by give reflect.Kind
func ValueByKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
return ConvToKind(val, kind)
}
func ValueByKind(val any, kind reflect.Kind) (reflect.Value, error) { return ConvToKind(val, kind) }
// ConvToKind convert and create reflect.Value by give reflect.Kind
//
// TIPs:
//
// Only support kind: string, bool, intX, uintX, floatX
func ConvToKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
if rv, ok := val.(reflect.Value); ok {
val = rv.Interface()
func ConvToKind(val any, kind reflect.Kind, fallback ...ConvFunc) (rv reflect.Value, err error) {
if rv1, ok := val.(reflect.Value); ok {
val = rv1.Interface()
}
switch kind {
@@ -185,6 +188,12 @@ func ConvToKind(val any, kind reflect.Kind) (rv reflect.Value, err error) {
err = err1
}
default:
// call fallback func
if len(fallback) > 0 && fallback[0] != nil {
rv, err = fallback[0](val, kind)
} else {
err = comdef.ErrConvType
}
err = comdef.ErrConvType
}
return
@@ -254,3 +263,31 @@ func ValToString(rv reflect.Value, defaultAsErr bool) (str string, err error) {
}
return
}
// ToTimeOrDuration convert string to time.Time or time.Duration type
//
// If the target type is not match, return the input string.
func ToTimeOrDuration(str string, typ reflect.Type) (any, error) {
// datetime, time, duration string should not greater than 64
if len(str) > 64 {
return str, nil
}
var anyVal any = str
// time.Time date string
if len(str) > 5 && IsTimeType(typ) {
ttVal, err := strutil.ToTime(str)
if err != nil {
return nil, err
}
anyVal = ttVal
} else if IsDurationType(typ) {
dVal, err := strutil.ToDuration(str)
if err != nil {
return nil, err
}
anyVal = dVal
}
return anyVal, nil
}

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"reflect"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/x/basefn"
)
// FuncX wrap a go func. represent a function
@@ -86,7 +86,7 @@ func (f *FuncX) Call(args ...any) ([]any, error) {
// The function must return 1 result, or 2 results, the second of which is an error.
//
// - Only support func with 1 or 2 return values: (val) OR (val, err)
// - Will check args and try convert input args to func args type.
// - Will check args and try to convert input args to func args type.
func (f *FuncX) Call2(args ...any) (any, error) {
// convert args to []reflect.Value
argRvs := make([]reflect.Value, len(args))

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