70 Commits

Author SHA1 Message Date
a9af1ee6fa Merge pull request 'update command generate' (#44) from generate into master
All checks were successful
Go Action / goaction (push) Successful in 36s
Reviewed-on: #44
2026-01-06 00:18:43 +03:00
9ef447d96a update command generate
All checks were successful
Go Action / goaction (pull_request) Successful in 3m7s
2026-01-06 00:15:09 +03:00
bba135f061 Merge pull request 'add providers interface' (#43) from providers into master
All checks were successful
Go Action / goaction (push) Successful in 56s
Reviewed-on: #43
2026-01-05 14:07:11 +03:00
0cf039a3bc add providers interface
All checks were successful
Go Action / goaction (pull_request) Successful in 1m20s
2026-01-05 14:02:56 +03:00
963a2f1b6c Merge pull request 'add atr dump style' (#42) from style into master
All checks were successful
Go Action / goaction (push) Successful in 1m1s
Reviewed-on: #42
2026-01-04 22:18:30 +03:00
46280e5377 add atr dump style
All checks were successful
Go Action / goaction (pull_request) Successful in 1m5s
2026-01-04 22:16:19 +03:00
a23a2d8eb5 Merge pull request 'move option position to param' (#41) from def into master
All checks were successful
Go Action / goaction (push) Successful in 56s
Reviewed-on: #41
2026-01-04 18:56:20 +03:00
d2cc93a97e move option position to param
All checks were successful
Go Action / goaction (pull_request) Successful in 1m15s
2026-01-04 18:53:12 +03:00
07bded1e6a Merge pull request 'move default from option to param' (#40) from def into master
All checks were successful
Go Action / goaction (push) Successful in 1m2s
Reviewed-on: #40
2026-01-04 18:04:32 +03:00
ab536ad876 move default from option to param
All checks were successful
Go Action / goaction (pull_request) Successful in 1m15s
2026-01-04 18:02:19 +03:00
3ec9ee48ab Merge pull request 'dump-reference' (#39) from dump-reference into master
All checks were successful
Go Action / goaction (push) Successful in 1m0s
Reviewed-on: #39
2026-01-04 17:49:02 +03:00
f9f4f90dc8 add dump reference arg provider
All checks were successful
Go Action / goaction (pull_request) Successful in 1m5s
2026-01-04 17:46:38 +03:00
28e03c727b move helper to file 2026-01-04 17:45:58 +03:00
7fcde79266 Merge pull request 'update config options' (#38) from dump-reference into master
All checks were successful
Go Action / goaction (push) Successful in 1m1s
Reviewed-on: #38
2026-01-03 17:32:29 +03:00
23de85f8a1 update create def by vars
All checks were successful
Go Action / goaction (pull_request) Successful in 1m19s
2026-01-03 17:30:34 +03:00
a52e470906 update config options
Some checks failed
Go Action / goaction (pull_request) Failing after 47s
2026-01-03 17:27:37 +03:00
9a189aad47 Merge pull request 'add test arg bind' (#37) from dump-reference into master
All checks were successful
Go Action / goaction (push) Successful in 1m1s
Reviewed-on: #37
2026-01-03 17:26:34 +03:00
8b2f2ea660 add test arg bind
All checks were successful
Go Action / goaction (pull_request) Successful in 1m18s
2026-01-03 17:08:45 +03:00
3054acf0c9 Merge pull request 'dump-reference' (#36) from dump-reference into master
All checks were successful
Go Action / goaction (push) Successful in 56s
Reviewed-on: #36
2026-01-03 14:53:12 +03:00
deca96cf6b add dump refereence env provider
All checks were successful
Go Action / goaction (pull_request) Successful in 1m17s
2026-01-03 14:51:00 +03:00
973aa29ccd set parallel test 2026-01-03 14:50:00 +03:00
39043f1304 move description param from option to param 2026-01-03 14:09:02 +03:00
87c0106ee5 upadate check wild key 2026-01-03 14:08:17 +03:00
f277358d9a Merge pull request 'update string value' (#35) from value into master
All checks were successful
Go Action / goaction (push) Successful in 57s
Reviewed-on: #35
2026-01-03 00:22:47 +03:00
deb258aa42 update string value
All checks were successful
Go Action / goaction (pull_request) Successful in 1m7s
2026-01-03 00:21:06 +03:00
6eabe7c28c Merge pull request 'format' (#34) from format into master
All checks were successful
Go Action / goaction (push) Successful in 54s
Reviewed-on: #34
2026-01-02 23:53:48 +03:00
88bc251577 add json processor
All checks were successful
Go Action / goaction (pull_request) Successful in 56s
2026-01-02 23:52:05 +03:00
74d8cad719 update pointer value 2026-01-02 23:51:35 +03:00
1169e25504 Merge pull request 'provider add format' (#33) from format into master
All checks were successful
Go Action / goaction (push) Successful in 57s
Reviewed-on: #33
2026-01-02 23:21:19 +03:00
a191e82526 provider add format
All checks were successful
Go Action / goaction (pull_request) Successful in 57s
2026-01-02 23:19:41 +03:00
2ce5efdcdd Merge pull request 'processor' (#32) from processor into master
All checks were successful
Go Action / goaction (push) Successful in 53s
Reviewed-on: #32
2026-01-02 22:44:19 +03:00
ef591885eb add provider processor
All checks were successful
Go Action / goaction (pull_request) Successful in 1m9s
2026-01-02 22:42:20 +03:00
d3418959d1 add csv processor 2026-01-02 22:42:02 +03:00
e60c0e25d5 value add helper parse slice string 2026-01-02 22:41:17 +03:00
dac9a87743 param add helper get rune 2026-01-02 22:40:23 +03:00
e7ac06a61c update key map, set priority find by name 2026-01-02 22:39:20 +03:00
b190ccd34e Merge pull request 'add key processor' (#31) from key into master
All checks were successful
Go Action / goaction (push) Successful in 44s
Reviewed-on: #31
2026-01-01 20:40:46 +03:00
01d707ccbf add key processor
All checks were successful
Go Action / goaction (pull_request) Successful in 2m15s
2026-01-01 19:33:45 +03:00
bb03532d83 Merge pull request 'update env processor' (#30) from processor into master
All checks were successful
Go Action / goaction (push) Successful in 50s
Reviewed-on: #30
2025-12-31 23:09:23 +03:00
57c035d72e update env processor
All checks were successful
Go Action / goaction (pull_request) Successful in 1m3s
2025-12-31 23:03:30 +03:00
85618ce0bd Merge pull request 'add env processor' (#29) from processor into master
All checks were successful
Go Action / goaction (push) Successful in 49s
Reviewed-on: #29
2025-12-31 20:32:54 +03:00
81eb902a54 add env processor
All checks were successful
Go Action / goaction (pull_request) Successful in 53s
2025-12-31 20:31:09 +03:00
8d54d7dbae Merge pull request 'update factory provider' (#28) from factory into master
All checks were successful
Go Action / goaction (push) Successful in 45s
Reviewed-on: #28
2025-12-30 22:55:08 +03:00
382bd117c1 update factory provider
All checks were successful
Go Action / goaction (pull_request) Successful in 56s
2025-12-30 22:53:06 +03:00
6e1192772a Merge pull request 'fix typo' (#27) from parse into master
All checks were successful
Go Action / goaction (push) Successful in 42s
Reviewed-on: #27
2025-12-30 21:57:21 +03:00
0054ebf7e6 fix typo
All checks were successful
Go Action / goaction (pull_request) Successful in 51s
2025-12-30 21:55:25 +03:00
783b4dd3b5 Merge pull request 'group' (#26) from group into master
All checks were successful
Go Action / goaction (push) Successful in 36s
Reviewed-on: #26
2025-12-29 22:25:31 +03:00
8d15b51248 add generate heper config
All checks were successful
Go Action / goaction (pull_request) Successful in 4m52s
2025-12-29 22:19:11 +03:00
302af61012 set definition as option 2025-12-29 22:18:13 +03:00
360ee322f2 update definition group 2025-12-29 21:45:47 +03:00
3aa8a30f3f Merge pull request 'update pos argument' (#25) from arg into master
All checks were successful
Go Action / goaction (push) Successful in 51s
Reviewed-on: #25
2025-12-27 19:31:02 +03:00
1fb591bb22 update pos argument
All checks were successful
Go Action / goaction (pull_request) Successful in 52s
2025-12-27 19:29:30 +03:00
4547adab23 Merge pull request 'update dasel provider' (#24) from dasel into master
All checks were successful
Go Action / goaction (push) Successful in 45s
Reviewed-on: #24
2025-12-27 16:31:34 +03:00
7ca860c127 update dasel provider
All checks were successful
Go Action / goaction (pull_request) Successful in 38s
2025-12-27 16:30:24 +03:00
b6aa83b53e Merge pull request 'add dasel provider' (#23) from dasel into master
All checks were successful
Go Action / goaction (push) Successful in 53s
Reviewed-on: #23
2025-12-27 16:12:16 +03:00
e5637b2a49 add dasel provider
All checks were successful
Go Action / goaction (pull_request) Successful in 58s
2025-12-27 16:10:10 +03:00
1be25e67a3 Merge pull request 'update duration jbytes' (#22) from jbytes into master
All checks were successful
Go Action / goaction (push) Successful in 46s
Reviewed-on: #22
2025-12-27 15:05:58 +03:00
c81d6ee010 update duration jbytes
All checks were successful
Go Action / goaction (pull_request) Successful in 58s
2025-12-27 15:04:07 +03:00
5e0e1f5c65 Merge pull request 'update yaml provider' (#21) from yaml into master
All checks were successful
Go Action / goaction (push) Successful in 33s
Reviewed-on: #21
2025-12-27 12:12:22 +03:00
3cdadd465b update yaml provider
All checks were successful
Go Action / goaction (pull_request) Successful in 40s
2025-12-27 12:10:58 +03:00
be629c70f1 Merge pull request 'update workflows' (#20) from workflow into master
All checks were successful
Go Action / goaction (push) Successful in 32s
Reviewed-on: #20
2025-12-27 11:53:55 +03:00
bf901485ce add ignore change workflow provider
All checks were successful
Go Action / goaction (pull_request) Successful in 31s
2025-12-27 11:53:12 +03:00
7990d1b4ff update workflows
All checks were successful
Go Action / goaction (pull_request) Successful in 33s
2025-12-27 11:48:55 +03:00
d5250f2c4e Merge pull request 'update provider vault' (#19) from vault into master
All checks were successful
Go Action / goaction (push) Successful in 36s
Reviewed-on: #19
2025-12-27 11:47:01 +03:00
3fc7f6259b update provider vault
All checks were successful
Go Action / goaction (push) Successful in 29s
Go Action / goaction (pull_request) Successful in 1m21s
2025-12-27 11:38:43 +03:00
de35df477b Merge pull request 'update toml' (#18) from toml into master
All checks were successful
Go Action / goaction (push) Successful in 29s
Reviewed-on: #18
2025-12-27 11:25:57 +03:00
0e7f303c2d update toml workflows
All checks were successful
Go Action / goaction (push) Successful in 26s
Go Action / goaction (pull_request) Successful in 35s
2025-12-27 11:21:52 +03:00
90f6f65c5f update toml
All checks were successful
Go Action / goaction (push) Successful in 28s
Go Action / goaction (pull_request) Successful in 26s
2025-12-27 10:50:32 +03:00
cc252cc858 Merge pull request 'update test' (#17) from toml into master
All checks were successful
Go Action / goaction (push) Successful in 28s
Reviewed-on: #17
2025-12-27 10:44:11 +03:00
1e0f4490ba update test
All checks were successful
Go Action / goaction (push) Successful in 27s
Go Action / goaction (pull_request) Successful in 28s
2025-12-27 10:43:31 +03:00
104 changed files with 3464 additions and 894 deletions

View File

@@ -0,0 +1,38 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
pull_request:
paths:
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/dasel
- name: Run go test
run: go test ./...
working-directory: ./provider/dasel

View File

@@ -1,6 +1,16 @@
name: Go Action
on: [push, pull_request]
on:
push:
branches:
- master
paths:
- 'provider/etcd/**'
- '.gitea/workflows/etcd.yml'
pull_request:
paths:
- 'provider/etcd/**'
- '.gitea/workflows/etcd.yml'
jobs:
goaction:

View File

@@ -1,6 +1,27 @@
name: Go Action
on: [push, pull_request]
on:
push:
branches:
- master
pull_request:
paths-ignore:
- 'provider/etcd/**'
- '.gitea/workflows/etcd.yml'
- 'provider/ini/**'
- '.gitea/workflows/ini.yml'
- 'provider/json/**'
- '.gitea/workflows/json.yml'
- 'provider/toml/**'
- '.gitea/workflows/toml.yml'
- 'provider/vault/**'
- '.gitea/workflows/vault.yml'
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
jobs:
goaction:

View File

@@ -1,6 +1,16 @@
name: Go Action
on: [push, pull_request]
on:
push:
branches:
- master
paths:
- 'provider/ini/**'
- '.gitea/workflows/ini.yml'
pull_request:
paths:
- 'provider/ini/**'
- '.gitea/workflows/ini.yml'
jobs:
goaction:

View File

@@ -1,6 +1,16 @@
name: Go Action
on: [push, pull_request]
on:
push:
branches:
- master
paths:
- 'provider/json/**'
- '.gitea/workflows/json.yml'
pull_request:
paths:
- 'provider/json/**'
- '.gitea/workflows/json.yml'
jobs:
goaction:

38
.gitea/workflows/toml.yml Normal file
View File

@@ -0,0 +1,38 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/toml/**'
- '.gitea/workflows/toml.yml'
pull_request:
paths:
- 'provider/toml/**'
- '.gitea/workflows/toml.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/toml
- name: Run go test
run: go test ./...
working-directory: ./provider/toml

View File

@@ -0,0 +1,46 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/vault/**'
- '.gitea/workflows/vault.yml'
pull_request:
paths:
- 'provider/vault/**'
- '.gitea/workflows/vault.yml'
jobs:
goaction:
services:
vault-server:
image: vault:1.13.3
env:
VAULT_DEV_ROOT_TOKEN_ID: "dev"
env:
VAULT_DEV_LISTEN_ADDRESS: 'http://vault-server:8200'
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/vault
- name: Run go test
run: go test ./...
working-directory: ./provider/vault

38
.gitea/workflows/yaml.yml Normal file
View File

@@ -0,0 +1,38 @@
name: Go Action
on:
push:
branches:
- master
paths:
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
pull_request:
paths:
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
jobs:
goaction:
runs-on: ubuntu-latest # Use a Gitea Actions runner label
steps:
- name: Check out repository code
uses: actions/checkout@v4 # Action to clone the repo
- name: Set up Go
uses: actions/setup-go@v5 # Action to install a specific Go version
with:
go-version: '1.25.5' # Specify your required Go version
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v8 # Use the golangci-lint action
with:
version: v2.7.2 # Specify the linter version
# Optional: additional arguments
args: --verbose
working-directory: ./provider/yaml
- name: Run go test
run: go test ./...
working-directory: ./provider/yaml

View File

@@ -6,6 +6,8 @@ import (
"fmt"
)
var _ Providers = (*Client)(nil)
func Must(providers ...any) *Client {
client, err := New(providers...)
if err != nil {
@@ -18,17 +20,28 @@ func Must(providers ...any) *Client {
func New(providers ...any) (*Client, error) {
client := &Client{
providers: make([]Provider, len(providers)),
name: make(map[string]int),
chain: make([]Providers, 0, len(providers)),
}
for idx, prov := range providers {
var name string
switch current := prov.(type) {
case Provider:
client.providers[idx] = current
name = current.Name()
case Factory:
client.providers[idx] = WrapFactory(current, client)
name = current.Name()
default:
return nil, fmt.Errorf("provier[%d]: %w %T", idx, ErrUnknowType, prov)
}
client.name[name] = idx
if current, ok := prov.(Providers); ok {
client.chain = append(client.chain, current)
}
}
return client, nil
@@ -36,6 +49,8 @@ func New(providers ...any) (*Client, error) {
type Client struct {
providers []Provider
name map[string]int
chain []Providers
}
func (c *Client) Name() string {
@@ -97,3 +112,30 @@ func (c *Client) Bind(ctx context.Context, data Variables) error {
return nil
}
func (c *Client) Provider(name string) (Provider, error) {
if idx, ok := c.name[name]; ok {
return c.providers[idx], nil
}
for _, prov := range c.chain {
if cprov, err := prov.Provider(name); err == nil {
return cprov, nil
}
}
return nil, fmt.Errorf("provider[%v]:%w", c.Name(), ErrNotFound)
}
func (c *Client) Names() []string {
names := make([]string, 0, len(c.providers))
for name := range c.name {
names = append(names, name)
}
for _, prov := range c.chain {
names = append(names, prov.Names()...)
}
return names
}

View File

@@ -11,6 +11,7 @@ import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/provider/env"
"gitoa.ru/go-4devs/config/provider/factory"
"gitoa.ru/go-4devs/config/provider/watcher"
"gitoa.ru/go-4devs/config/test"
)
@@ -127,7 +128,7 @@ func ExampleClient_Value_factory() {
config, err := config.New(
arg.New(),
config.Factory(func(ctx context.Context, cfg config.Provider) (config.Provider, error) {
factory.New("factory:env", func(ctx context.Context, cfg config.Provider) (config.Provider, error) {
val, err := cfg.Value(ctx, "env")
if err != nil {
return nil, fmt.Errorf("failed read config file:%w", err)

32
cmd/config/main.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"context"
"os"
"os/signal"
"syscall"
"gitoa.ru/go-4devs/config/definition/generate/command"
"gitoa.ru/go-4devs/console"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := make(chan os.Signal, 1)
defer close(ch)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-ch
cancel()
}()
console.
New().
Add(
command.Command(),
).
Execute(ctx)
}

View File

@@ -2,15 +2,21 @@ package definition
import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
)
var _ config.Options = (*Definition)(nil)
func New(opts ...config.Option) *Definition {
return &Definition{
options: opts,
Params: param.New(),
}
}
type Definition struct {
param.Params
options []config.Option
}
@@ -21,3 +27,14 @@ func (d *Definition) Add(opts ...config.Option) {
func (d *Definition) Options() []config.Option {
return d.options
}
func (d *Definition) With(params param.Params) *Definition {
def := &Definition{
options: make([]config.Option, len(d.options)),
Params: param.Chain(params, d.Params),
}
copy(def.options, d.options)
return def
}

View File

@@ -16,14 +16,13 @@ var tpls embed.FS
type Boot struct {
Config
*pkg.Packages
imp *pkg.Imports
Configure []string
OutName string
}
func (b Boot) Imports() []pkg.Import {
return b.imp.Imports()
func (b Boot) Pkg() string {
return pkg.Pkg(b.FullPkg())
}
type Config interface {
@@ -33,13 +32,12 @@ type Config interface {
Prefix() string
Suffix() string
FullPkg() string
Pkg() string
}
func Bootstrap(ctx context.Context, cfg Config) (string, error) {
fInfo, err := os.Stat(cfg.File())
if err != nil {
return "", fmt.Errorf("stat:%w", err)
return "", fmt.Errorf("stat[%v]:%w", cfg.File(), err)
}
pkgPath, err := pkg.ByPath(ctx, cfg.File(), fInfo.IsDir())
@@ -61,17 +59,19 @@ func Bootstrap(ctx context.Context, cfg Config) (string, error) {
Adds(
"context",
"gitoa.ru/go-4devs/config/definition",
"gitoa.ru/go-4devs/config",
"gitoa.ru/go-4devs/config/definition/generate/view",
"gitoa.ru/go-4devs/config/param",
"gitoa.ru/go-4devs/config/definition/generate",
"os",
"io",
"fmt",
"go/format",
pkgPath,
)
data := Boot{
imp: imports,
Packages: imports,
Configure: cfg.Methods(),
OutName: fInfo.Name()[0:len(fInfo.Name())-3] + "_config.go",
Config: cfg,
}

View File

@@ -10,50 +10,34 @@ import (
)
func main() {
if err := run(); err != nil {
if err := run(os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
func run(f io.Writer) error {
ctx := context.Background()
f, err := os.Create("{{.OutName}}")
if err != nil {
return err
}
defs:=make([]generate.Input,0)
defs:=make([]config.Options,0)
{{ range .Configure }}
def{{.}} := definition.New()
params{{.}} := param.New(
{{- if $.SkipContext }}view.WithSkipContext,{{ end }}
view.WithStructName("{{$.Prefix}}_{{.}}_{{$.Suffix}}"),
view.WithStructPrefix("{{$.Prefix}}"),
view.WithStructSuffix("{{$.Suffix}}"),
)
def{{.}} := definition.New().With(params{{.}})
if err := {{$.Pkg}}.{{.}}(ctx, def{{.}}); err != nil {
return err
}
defs = append(defs,generate.NewInput("{{.}}",def{{.}}))
defs = append(defs,def{{.}})
{{ end }}
opts := make([]generate.Option,0)
{{ if .SkipContext }}opts = append(opts, generate.WithSkipContext){{ end }}
opts = append(opts,
generate.WithPrefix("{{.Prefix}}"),
generate.WithSuffix("{{.Suffix}}"),
generate.WithFullPkg("{{.FullPkg}}"),
)
if gerr := generate.Run(ctx,generate.NewConfig(opts...),f, defs...);gerr != nil {
if gerr := generate.Run(ctx,"{{.FullPkg}}",f, defs...);gerr != nil {
return gerr
}
in, err := os.ReadFile(f.Name())
if err != nil {
return err
}
out, err := format.Source(in)
if err != nil {
return err
}
return os.WriteFile(f.Name(), out, 0644)
return nil
}

View File

@@ -0,0 +1,64 @@
package command
import (
"context"
"fmt"
"os"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate"
"gitoa.ru/go-4devs/config/provider/chain"
"gitoa.ru/go-4devs/console/command"
"gitoa.ru/go-4devs/console/output"
)
const Name = "config:generate"
func Handle(ctx context.Context, in config.Provider, out output.Output, next command.ExecuteFn) error {
var name string
value, err := in.Value(ctx, generate.OptionFile)
if err == nil {
name = value.String()
}
if name == "" {
name = os.Getenv("GOFILE")
}
parser, err := generate.Parse(ctx, name)
if err != nil {
return fmt.Errorf("parse:%w", err)
}
mem, merr := generate.NewMemoryProvider(name,
generate.WithOutName(parser.OutName()),
generate.WithFullPkg(parser.FullPkg()),
generate.WithMethods(parser.Methods()...),
)
if merr != nil {
return fmt.Errorf("mem provider:%w", merr)
}
return next(ctx, chain.New(in, mem), out)
}
func Execute(ctx context.Context, in config.Provider, _ output.Output) error {
cfg := generate.NewConfigure(ctx, in)
if err := generate.Generate(ctx, cfg); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
func Command() command.Command {
return command.New(
Name,
"generate helper for configure command",
Execute,
command.Configure(generate.Configure),
command.Handle(Handle),
)
}

View File

@@ -0,0 +1,134 @@
package generate
import (
"context"
"fmt"
"strconv"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/provider/memory"
)
const (
OptionFile = "file"
optionPrefix = "prefix"
optionSuffix = "suffix"
optionSkipContext = "skip-context"
optionBuildTags = "build-tags"
optionOutName = "out-name"
optionMethod = "method"
optionFullPkg = "full-pkg"
)
func WithPrefix(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionPrefix)
if err != nil {
return fmt.Errorf("append %v:%w", optionPrefix, err)
}
return nil
}
}
func WithSuffix(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionSuffix)
if err != nil {
return fmt.Errorf("append %v:%w", optionSuffix, err)
}
return nil
}
}
func WithSkipContext(in bool) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(strconv.FormatBool(in), optionSkipContext)
if err != nil {
return fmt.Errorf("append %v:%w", optionSkipContext, err)
}
return nil
}
}
func WithBuildTags(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionBuildTags)
if err != nil {
return fmt.Errorf("append %v:%w", optionBuildTags, err)
}
return nil
}
}
func WithOutName(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionOutName)
if err != nil {
return fmt.Errorf("append %v:%w", optionOutName, err)
}
return nil
}
}
func WithMethods(methods ...string) func(*memory.Map) error {
return func(m *memory.Map) error {
for _, method := range methods {
err := m.AppendOption(method, optionMethod)
if err != nil {
return fmt.Errorf("append %v:%w", optionMethod, err)
}
}
return nil
}
}
func WithFullPkg(in string) func(*memory.Map) error {
return func(m *memory.Map) error {
err := m.AppendOption(in, optionFullPkg)
if err != nil {
return fmt.Errorf("append %v:%w", optionFullPkg, err)
}
return nil
}
}
func NewMemoryProvider(file string, opts ...func(*memory.Map) error) (*memory.Map, error) {
cfg := new(memory.Map)
err := cfg.AppendOption(file, OptionFile)
if err != nil {
return nil, fmt.Errorf("append %v:%w", OptionFile, err)
}
for idx, opt := range opts {
err = opt(cfg)
if err != nil {
return nil, fmt.Errorf("opt[%d]:%w", idx, err)
}
}
return cfg, nil
}
func Configure(_ context.Context, def config.Definition) error {
def.Add(
option.String(OptionFile, "set file", option.Required),
option.String(optionPrefix, "struct prefix"),
option.String(optionSuffix, "struct suffix", option.Default("Config")),
option.Bool(optionSkipContext, "skip contect to method"),
option.String(optionBuildTags, "add build tags"),
option.String(optionOutName, "set out name"),
option.String(optionMethod, "set method", option.Slice),
option.String(optionFullPkg, "set full pkg"),
)
return nil
}

View File

@@ -0,0 +1,231 @@
package generate
import (
"context"
"errors"
"fmt"
"log"
"gitoa.ru/go-4devs/config"
)
func NewConfigure(ctx context.Context, prov config.Provider) ConfigureConfig {
return ConfigureConfig{
Provider: prov,
ctx: ctx,
handle: func(err error) {
if !errors.Is(err, config.ErrNotFound) {
log.Print(err)
}
},
}
}
type ConfigureConfig struct {
config.Provider
ctx context.Context //nolint:containedctx
handle func(err error)
}
func (i ConfigureConfig) ReadBuildTags() (string, error) {
val, verr := i.Value(i.ctx, optionBuildTags)
if verr != nil {
return "", fmt.Errorf("get %v:%w", optionBuildTags, verr)
}
data, err := val.ParseString()
if err != nil {
return "", fmt.Errorf("parse %v:%w", optionBuildTags, err)
}
return data, nil
}
func (i ConfigureConfig) BuildTags() string {
data, err := i.ReadBuildTags()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadOutName() (string, error) {
val, err := i.Value(i.ctx, optionOutName)
if err != nil {
return "", fmt.Errorf("get %v:%w", optionOutName, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionOutName, derr)
}
return data, nil
}
func (i ConfigureConfig) OutName() string {
data, err := i.ReadOutName()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadFile() (string, error) {
val, err := i.Value(i.ctx, OptionFile)
if err != nil {
return "", fmt.Errorf("get %v:%w", OptionFile, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", OptionFile, derr)
}
return data, nil
}
func (i ConfigureConfig) File() string {
data, err := i.ReadFile()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadMethods() ([]string, error) {
val, err := i.Value(i.ctx, optionMethod)
if err != nil {
return nil, fmt.Errorf("get %v:%w", optionMethod, err)
}
var data []string
perr := val.Unmarshal(&data)
if perr != nil {
return nil, fmt.Errorf("unmarshal %v:%w", optionMethod, perr)
}
return data, nil
}
func (i ConfigureConfig) Methods() []string {
data, err := i.ReadMethods()
if err != nil {
i.handle(err)
return nil
}
return data
}
func (i ConfigureConfig) ReadSkipContext() (bool, error) {
val, err := i.Value(i.ctx, optionSkipContext)
if err != nil {
return false, fmt.Errorf("get %v:%w", optionSkipContext, err)
}
data, derr := val.ParseBool()
if derr != nil {
return false, fmt.Errorf("parse %v:%w", optionSkipContext, derr)
}
return data, nil
}
func (i ConfigureConfig) SkipContext() bool {
data, err := i.ReadSkipContext()
if err != nil {
i.handle(err)
return false
}
return data
}
func (i ConfigureConfig) ReadPrefix() (string, error) {
val, err := i.Value(i.ctx, optionPrefix)
if err != nil {
return "", fmt.Errorf("get %v: %w", optionPrefix, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionPrefix, derr)
}
return data, nil
}
func (i ConfigureConfig) Prefix() string {
val, err := i.ReadPrefix()
if err != nil {
i.handle(err)
return ""
}
return val
}
func (i ConfigureConfig) ReadSuffix() (string, error) {
val, err := i.Value(i.ctx, optionSuffix)
if err != nil {
return "", fmt.Errorf("get %v:%w", optionSuffix, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionSuffix, derr)
}
return data, nil
}
func (i ConfigureConfig) Suffix() string {
data, err := i.ReadSuffix()
if err != nil {
i.handle(err)
return ""
}
return data
}
func (i ConfigureConfig) ReadFullPkg() (string, error) {
val, err := i.Value(i.ctx, optionFullPkg)
if err != nil {
return "", fmt.Errorf("get %v:%w", optionFullPkg, err)
}
data, derr := val.ParseString()
if derr != nil {
return "", fmt.Errorf("parse %v:%w", optionFullPkg, derr)
}
return data, nil
}
func (i ConfigureConfig) FullPkg() string {
data, err := i.ReadFullPkg()
if err != nil {
i.handle(err)
return ""
}
return data
}

View File

@@ -1,206 +0,0 @@
// Code generated gitoa.ru/go-4devs/config DO NOT EDIT.
package example
import (
context "context"
fmt "fmt"
config "gitoa.ru/go-4devs/config"
)
func WithInputConfigLog(log func(context.Context, string, ...any)) func(*InputConfig) {
return func(ci *InputConfig) {
ci.log = log
}
}
func NewInputConfig(prov config.Provider, opts ...func(*InputConfig)) InputConfig {
i := InputConfig{
Provider: prov,
log: func(_ context.Context, format string, args ...any) {
fmt.Printf(format, args...)
},
}
for _, opt := range opts {
opt(&i)
}
return i
}
type InputConfig struct {
config.Provider
log func(context.Context, string, ...any)
}
// readTest test string.
func (i InputConfig) readTest(ctx context.Context) (v string, e error) {
val, err := i.Value(ctx, "test")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"test"}, err)
}
return val.ParseString()
}
// ReadTest test string.
func (i InputConfig) ReadTest() (string, error) {
return i.readTest(context.Background())
}
// Test test string.
func (i InputConfig) Test() string {
val, err := i.readTest(context.Background())
if err != nil {
i.log(context.Background(), "get [%v]: %v", []string{"test"}, err)
}
return val
}
type InputConfigUser struct {
InputConfig
}
// User configure user.
func (i InputConfig) User() InputConfigUser {
return InputConfigUser{i}
}
// readName name.
func (i InputConfigUser) readName(ctx context.Context) (v string, e error) {
val, err := i.Value(ctx, "user", "name")
if err != nil {
i.log(context.Background(), "read [%v]: %v", []string{"user", "name"}, err)
return "4devs", nil
}
return val.ParseString()
}
// ReadName name.
func (i InputConfigUser) ReadName(ctx context.Context) (string, error) {
return i.readName(ctx)
}
// Name name.
func (i InputConfigUser) Name(ctx context.Context) string {
val, err := i.readName(ctx)
if err != nil {
i.log(ctx, "get [%v]: %v", []string{"user", "name"}, err)
}
return val
}
// readPass password.
func (i InputConfigUser) readPass(ctx context.Context) (v string, e error) {
val, err := i.Value(ctx, "user", "pass")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"user", "pass"}, err)
}
return val.ParseString()
}
// ReadPass password.
func (i InputConfigUser) ReadPass(ctx context.Context) (string, error) {
return i.readPass(ctx)
}
// Pass password.
func (i InputConfigUser) Pass(ctx context.Context) string {
val, err := i.readPass(ctx)
if err != nil {
i.log(ctx, "get [%v]: %v", []string{"user", "pass"}, err)
}
return val
}
type InputConfigLog struct {
InputConfig
}
// Log configure logger.
func (i InputConfig) Log() InputConfigLog {
return InputConfigLog{i}
}
// readLevel log level.
func (i InputConfigLog) readLevel(ctx context.Context) (v Level, e error) {
val, err := i.Value(ctx, "log", "level")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", "level"}, err)
}
pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", "level"}, perr)
}
return v, v.UnmarshalText([]byte(pval))
}
// ReadLevel log level.
func (i InputConfigLog) ReadLevel(ctx context.Context) (Level, error) {
return i.readLevel(ctx)
}
// Level log level.
func (i InputConfigLog) Level(ctx context.Context) Level {
val, err := i.readLevel(ctx)
if err != nil {
i.log(ctx, "get [%v]: %v", []string{"log", "level"}, err)
}
return val
}
type InputConfigLogService struct {
InputConfigLog
service string
}
// Service servise logger.
func (i InputConfigLog) Service(key string) InputConfigLogService {
return InputConfigLogService{i, key}
}
// readLevel log level.
func (i InputConfigLogService) readLevel(ctx context.Context) (v Level, e error) {
val, err := i.Value(ctx, "log", i.service, "level")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", i.service, "level"}, err)
}
pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", i.service, "level"}, perr)
}
return v, v.UnmarshalText([]byte(pval))
}
// ReadLevel log level.
func (i InputConfigLogService) ReadLevel(ctx context.Context) (Level, error) {
return i.readLevel(ctx)
}
// Level log level.
func (i InputConfigLogService) Level(ctx context.Context) Level {
val, err := i.readLevel(ctx)
if err != nil {
i.log(ctx, "get [%v]: %v", []string{"log", i.service, "level"}, err)
}
return val
}

View File

@@ -3,13 +3,15 @@ package example
import (
"context"
"gitoa.ru/go-4devs/config/definition"
configs "gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
)
//go:generate go run ../../../cmd/config/main.go config:generate
type Level string
func (l *Level) UnmarshalText(in []byte) error {
@@ -19,13 +21,13 @@ func (l *Level) UnmarshalText(in []byte) error {
return nil
}
func Config(_ context.Context, def *definition.Definition) error {
func Example(_ context.Context, def configs.Definition) error {
def.Add(
option.String("test", "test string", view.WithSkipContext),
group.New("user", "configure user",
option.String("name", "name", option.Default("4devs")),
option.String("pass", "password"),
),
).With(view.WithContext),
group.New("log", "configure logger",
option.New("level", "log level", Level("")),

View File

@@ -0,0 +1,153 @@
// Code generated gitoa.ru/go-4devs/config DO NOT EDIT.
package example
import (
"context"
"fmt"
"gitoa.ru/go-4devs/config"
)
func WithExampleConfigHandle(fn func(context.Context, error)) func(*ExampleConfig) {
return func(ci *ExampleConfig) {
ci.handle = fn
}
}
func NewExampleConfig(ctx context.Context, prov config.Provider, opts ...func(*ExampleConfig)) ExampleConfig {
i := ExampleConfig{
Provider: prov,
handle: func(_ context.Context, err error) {
fmt.Printf("ExampleConfig:%v", err)
},
ctx: ctx,
}
for _, opt := range opts {
opt(&i)
}
return i
}
type ExampleConfig struct {
config.Provider
handle func(context.Context, error)
ctx context.Context
}
// readTest test string.
func (i ExampleConfig) readTest(ctx context.Context) (v string, e error) {
val, err := i.Value(ctx, "test")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"test"}, err)
}
return val.ParseString()
}
// ReadTest test string.
func (i ExampleConfig) ReadTest() (string, error) {
return i.readTest(i.ctx)
}
// Test test string.
func (i ExampleConfig) Test() string {
val, err := i.readTest(i.ctx)
if err != nil {
i.handle(i.ctx, err)
}
return val
}
type UserConfig struct {
ExampleConfig
}
// User configure user.
func (i ExampleConfig) User() UserConfig {
return UserConfig{i}
}
type LogConfig struct {
ExampleConfig
}
// Log configure logger.
func (i ExampleConfig) Log() LogConfig {
return LogConfig{i}
}
// readLevel log level.
func (i LogConfig) readLevel(ctx context.Context) (v Level, e error) {
val, err := i.Value(ctx, "log", "level")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", "level"}, err)
}
pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("parse [%v]:%w", []string{"log", "level"}, perr)
}
return v, v.UnmarshalText([]byte(pval))
}
// ReadLevel log level.
func (i LogConfig) ReadLevel(ctx context.Context) (Level, error) {
return i.readLevel(ctx)
}
// Level log level.
func (i LogConfig) Level(ctx context.Context) Level {
val, err := i.readLevel(ctx)
if err != nil {
i.handle(ctx, err)
}
return val
}
type LogServiceConfig struct {
LogConfig
service string
}
// Service servise logger.
func (i LogConfig) Service(key string) LogServiceConfig {
return LogServiceConfig{i, key}
}
// readLevel log level.
func (i LogServiceConfig) readLevel(ctx context.Context) (v Level, e error) {
val, err := i.Value(ctx, "log", i.service, "level")
if err != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"log", i.service, "level"}, err)
}
pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("parse [%v]:%w", []string{"log", i.service, "level"}, perr)
}
return v, v.UnmarshalText([]byte(pval))
}
// ReadLevel log level.
func (i LogServiceConfig) ReadLevel(ctx context.Context) (Level, error) {
return i.readLevel(ctx)
}
// Level log level.
func (i LogServiceConfig) Level(ctx context.Context) Level {
val, err := i.readLevel(ctx)
if err != nil {
i.handle(ctx, err)
}
return val
}

View File

@@ -1,59 +0,0 @@
package example_test
import (
"context"
"os"
"testing"
"gitoa.ru/go-4devs/config/definition"
"gitoa.ru/go-4devs/config/definition/generate"
"gitoa.ru/go-4devs/config/definition/generate/bootstrap"
"gitoa.ru/go-4devs/config/definition/generate/example"
)
func TestGenerate_Bootstrap(t *testing.T) {
t.SkipNow()
t.Parallel()
ctx := context.Background()
options := definition.New()
_ = example.Config(ctx, options)
cfg, _ := generate.NewGConfig("./config.go",
generate.WithMethods("Config"),
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate/example"),
)
path, err := bootstrap.Bootstrap(ctx, cfg)
if err != nil {
t.Error(err)
t.FailNow()
}
os.Remove(path)
t.Log(path)
t.FailNow()
}
func TestGenerate_Genereate(t *testing.T) {
t.SkipNow()
t.Parallel()
ctx := context.Background()
options := definition.New()
_ = example.Config(ctx, options)
cfg, _ := generate.NewGConfig("./config.go",
generate.WithMethods("Config"),
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate/example"),
)
err := generate.Generate(ctx, cfg)
if err != nil {
t.Error(err)
t.FailNow()
}
t.FailNow()
}

View File

@@ -11,14 +11,6 @@ import (
"gitoa.ru/go-4devs/config/definition/generate/bootstrap"
)
func NewGConfig(fname string, opts ...Option) (Config, error) {
opts = append([]Option{
WithFile(fname),
}, opts...)
return NewConfig(opts...), nil
}
type GConfig interface {
BuildTags() string
OutName() string

View File

@@ -14,137 +14,23 @@ import (
"gitoa.ru/go-4devs/config/definition/generate/pkg"
"gitoa.ru/go-4devs/config/definition/generate/render"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/param"
)
type Option func(*Config)
func WithSkipContext(c *Config) {
view.WithSkipContext(c.Params)
}
func WithPrefix(name string) Option {
return func(c *Config) {
c.prefix = name
}
}
func WithSuffix(name string) Option {
return func(c *Config) {
c.suffix = name
}
}
// WithMethods set methosd.
//
// generate.WithMethods(runtime.FuncForPC(reflect.ValueOf(configure).Pointer()).Name()).
func WithMethods(in ...string) Option {
return func(c *Config) {
c.methods = in
}
}
func WithOutName(in string) Option {
return func(c *Config) {
c.outName = in
}
}
func WithFile(in string) Option {
return func(c *Config) {
c.file = in
}
}
func WithFullPkg(in string) Option {
return func(c *Config) {
c.fullPkg = in
}
}
func NewConfig(opts ...Option) Config {
var cfg Config
cfg.Params = param.New()
cfg.prefix = "Input"
for _, opt := range opts {
opt(&cfg)
}
return cfg
}
type Config struct {
param.Params
methods []string
prefix string
suffix string
fullPkg string
pkg string
file string
buildTags string
outName string
}
func (c Config) BuildTags() string {
return c.buildTags
}
func (c Config) Pkg() string {
if c.pkg == "" {
if idx := strings.LastIndex(c.fullPkg, "/"); idx != -1 {
c.pkg = c.fullPkg[idx+1:]
}
}
return c.pkg
}
func (c Config) FullPkg() string {
return c.fullPkg
}
func (c Config) SkipContext() bool {
return view.IsSkipContext(c.Params)
}
func (c Config) Methods() []string {
return c.methods
}
func (c Config) Prefix() string {
return c.prefix
}
func (c Config) Suffix() string {
return c.suffix
}
func (c Config) File() string {
return c.file
}
func (c Config) OutName() string {
return c.outName
}
//go:embed tpl/*
var tpls embed.FS
var initTpl = template.Must(template.New("tpls").ParseFS(tpls, "tpl/*.tpl")).Lookup("init.go.tpl")
func Run(_ context.Context, cfg Config, w io.Writer, inputs ...Input) error {
func Run(_ context.Context, fullPkg string, w io.Writer, defs ...config.Options) error {
data := Data{
Config: cfg,
imp: pkg.NewImports(cfg.FullPkg()).Adds("fmt", "context", "gitoa.ru/go-4devs/config"),
Packages: pkg.NewImports(fullPkg).
Adds("fmt", "context", "gitoa.ru/go-4devs/config"),
}
var buff bytes.Buffer
for _, in := range inputs {
vi := view.NewViews(in.Method(), data.Params, in.Options(), view.WithKeys())
for _, in := range defs {
vi := view.NewViews(in)
if err := render.Render(&buff, vi, data); err != nil {
return fmt.Errorf("render:%w", err)
@@ -163,32 +49,17 @@ func Run(_ context.Context, cfg Config, w io.Writer, inputs ...Input) error {
}
type Data struct {
Config
imp *pkg.Imports
}
func (f Data) Imports() []pkg.Import {
return f.imp.Imports()
*pkg.Packages
}
func (f Data) StructName(name string) string {
return f.Prefix() + FuncName(name) + f.Suffix()
return FuncName(name)
}
func (f Data) FuncName(in string) string {
return FuncName(in)
}
func (f Data) AddType(pkg string) (string, error) {
short, err := f.imp.AddType(pkg)
if err != nil {
return "", fmt.Errorf("data: %w", err)
}
return short, nil
}
func FuncName(name string) string {
data := strings.Builder{}
toUp := true
@@ -211,23 +82,3 @@ func FuncName(name string) string {
return data.String()
}
func NewInput(method string, options config.Options) Input {
return Input{
method: method,
options: options,
}
}
type Input struct {
options config.Options
method string
}
func (i Input) Method() string {
return i.method
}
func (i Input) Options() config.Options {
return i.options
}

View File

@@ -1,11 +1,18 @@
package generate_test
import (
"context"
"os"
"testing"
"gitoa.ru/go-4devs/config/definition"
"gitoa.ru/go-4devs/config/definition/generate"
"gitoa.ru/go-4devs/config/definition/generate/bootstrap"
"gitoa.ru/go-4devs/config/definition/generate/view"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/test/require"
)
type LogLevel string
@@ -17,7 +24,7 @@ func (l *LogLevel) UnmarshalText(in []byte) error {
return nil
}
func Configure(def *definition.Definition) {
func Configure(_ context.Context, def *definition.Definition) error {
def.Add(
option.String("test", "test string", view.WithSkipContext),
group.New("user", "configure user",
@@ -30,4 +37,52 @@ func Configure(def *definition.Definition) {
proto.New("service", "servise logger", option.New("level", "log level", LogLevel(""))),
),
)
return nil
}
func TestGenerate_Bootstrap(t *testing.T) {
t.SkipNow()
t.Parallel()
ctx := context.Background()
options := definition.New()
err := Configure(ctx, options)
require.NoError(t, err)
cfg, _ := generate.NewMemoryProvider("generate_test.go",
generate.WithMethods("Config"),
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate_test"),
)
path, err := bootstrap.Bootstrap(ctx, generate.NewConfigure(ctx, cfg))
if err != nil {
t.Error(err)
t.FailNow()
}
os.Remove(path)
t.Log(path)
t.FailNow()
}
func TestGenerate_Genereate(t *testing.T) {
t.SkipNow()
t.Parallel()
ctx := context.Background()
options := definition.New()
err := Configure(ctx, options)
require.NoError(t, err)
cfg, _ := generate.NewMemoryProvider("generate_test.go",
generate.WithMethods("Config"),
generate.WithFullPkg("gitoa.ru/go-4devs/config/definition/generate_test"),
)
err = generate.Generate(ctx, generate.NewConfigure(ctx, cfg))
require.NoError(t, err)
t.FailNow()
}

View File

@@ -0,0 +1,152 @@
package generate
import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"reflect"
"slices"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/generate/pkg"
)
const NameSuffix = "_config.go"
func Parse(ctx context.Context, name string) (Parser, error) {
var parse Parser
parse.file = name
stats, err := os.Stat(name)
if err != nil {
return parse, fmt.Errorf("stats:%w", err)
}
parse.fullPkg, err = pkg.ByPath(ctx, name, stats.IsDir())
if err != nil {
return parse, fmt.Errorf("get pkg:%w", err)
}
parse.methods, err = NewParseMethods(
name,
[]reflect.Type{
reflect.TypeFor[context.Context](),
reflect.TypeFor[config.Definition](),
},
[]reflect.Type{reflect.TypeFor[error]()},
)
if err != nil {
return parse, fmt.Errorf("parse methods:%w", err)
}
return parse, nil
}
func NewParseMethods(file string, params []reflect.Type, results []reflect.Type) ([]string, error) {
pfile, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ParseComments)
if err != nil {
return nil, fmt.Errorf("parse:%w", err)
}
resultAlias := importAlias(pfile, results)
paramsAlias := importAlias(pfile, params)
var methods []string
ast.Inspect(pfile, func(anode ast.Node) bool {
if fn, ok := anode.(*ast.FuncDecl); ok &&
fn.Recv == nil &&
fn.Type != nil &&
(fn.Type.Params != nil && len(params) == len(fn.Type.Params.List) || len(params) == 0 && fn.Type.Params == nil) &&
(fn.Type.Results != nil && len(results) == len(fn.Type.Results.List) || len(results) == 0 && fn.Type.Results == nil) {
if hasFields(fn.Type.Params, params, paramsAlias) && hasFields(fn.Type.Results, results, resultAlias) {
methods = append(methods, fn.Name.String())
}
}
return true
})
return methods, nil
}
func importAlias(file *ast.File, params []reflect.Type) map[int][]string {
paramsAlias := make(map[int][]string, len(params))
for idx := range params {
name := params[idx].Name()
if pkgPath := params[idx].PkgPath(); pkgPath != "" {
name = pkg.Pkg(pkgPath)
}
paramsAlias[idx] = append(paramsAlias[idx], name)
}
ast.Inspect(file, func(anode ast.Node) bool {
if exp, ok := anode.(*ast.ImportSpec); ok {
pathName := strings.Trim(exp.Path.Value, "\"")
pname := pkg.Pkg(pathName)
if exp.Name != nil {
pname = exp.Name.String()
}
for idx, param := range params {
if pathName == param.PkgPath() {
paramsAlias[idx] = append(paramsAlias[idx], pname)
}
}
}
return true
})
return paramsAlias
}
func hasFields(fields *ast.FieldList, params []reflect.Type, alias map[int][]string) bool {
for idx, one := range fields.List {
iparam := params[idx]
if ident, iok := one.Type.(*ast.Ident); iok && iparam.String() == ident.String() {
return true
}
selector, sok := one.Type.(*ast.SelectorExpr)
if !sok {
return false
}
if iparam.Name() != selector.Sel.String() {
return false
}
salias, saok := selector.X.(*ast.Ident)
if iparam.PkgPath() != "" && saok && !slices.Contains(alias[idx], salias.String()) {
return false
}
}
return true
}
type Parser struct {
file string
fullPkg string
methods []string
}
func (p Parser) OutName() string {
return strings.ReplaceAll(p.file, ".go", NameSuffix)
}
func (p Parser) FullPkg() string {
return p.fullPkg
}
func (p Parser) Methods() []string {
return p.methods
}

View File

@@ -6,8 +6,8 @@ import (
"strings"
)
func NewImports(pkg string) *Imports {
imp := Imports{
func NewImports(pkg string) *Packages {
imp := Packages{
data: make(map[string]string),
pkg: pkg,
}
@@ -15,12 +15,12 @@ func NewImports(pkg string) *Imports {
return &imp
}
type Imports struct {
type Packages struct {
data map[string]string
pkg string
}
func (i *Imports) Imports() []Import {
func (i *Packages) Imports() []Import {
imports := make([]Import, 0, len(i.data))
for name, alias := range i.data {
imports = append(imports, Import{
@@ -32,7 +32,7 @@ func (i *Imports) Imports() []Import {
return imports
}
func (i *Imports) Short(fullType string) (string, error) {
func (i *Packages) Short(fullType string) (string, error) {
idx := strings.LastIndexByte(fullType, '.')
if idx == -1 {
return "", fmt.Errorf("%w: expect package.Type", ErrWrongFormat)
@@ -45,7 +45,7 @@ func (i *Imports) Short(fullType string) (string, error) {
return "", fmt.Errorf("%w alias for pkg %v", ErrNotFound, fullType[:idx])
}
func (i *Imports) AddType(fullType string) (string, error) {
func (i *Packages) AddType(fullType string) (string, error) {
idx := strings.LastIndexByte(fullType, '.')
if idx == -1 {
return "", fmt.Errorf("%w: expect pckage.Type got %v", ErrWrongFormat, fullType)
@@ -60,7 +60,7 @@ func (i *Imports) AddType(fullType string) (string, error) {
return imp.Alias + fullType[idx:], nil
}
func (i *Imports) Adds(pkgs ...string) *Imports {
func (i *Packages) Adds(pkgs ...string) *Packages {
for _, pkg := range pkgs {
i.Add(pkg)
}
@@ -68,7 +68,7 @@ func (i *Imports) Adds(pkgs ...string) *Imports {
return i
}
func (i *Imports) Add(pkg string) Import {
func (i *Packages) Add(pkg string) Import {
if pkg == i.pkg {
return Import{
Alias: "",
@@ -100,7 +100,31 @@ func (i *Imports) Add(pkg string) Import {
}
}
func (i *Packages) Pkg() string {
return Pkg(i.pkg)
}
type Import struct {
Alias string
Package string
}
func (i Import) Pkg() string {
return Pkg(i.Package)
}
func (i Import) String() string {
if i.Alias == i.Pkg() {
return fmt.Sprintf("%q", i.Package)
}
return fmt.Sprintf("%s %q", i.Alias, i.Package)
}
func Pkg(fullPkg string) string {
if idx := strings.LastIndex(fullPkg, "/"); idx != -1 {
return fullPkg[idx+1:]
}
return fullPkg
}

View File

@@ -117,7 +117,7 @@ func getPkgPathFromGOPATH(fname string, isDir bool) (string, error) {
gopath = build.Default.GOPATH
}
for _, p := range strings.Split(gopath, string(filepath.ListSeparator)) {
for p := range strings.SplitSeq(gopath, string(filepath.ListSeparator)) {
prefix := filepath.Join(p, "src") + string(filepath.Separator)
rel, err := filepath.Rel(prefix, fname)

View File

@@ -18,15 +18,15 @@ type ViewData struct {
}
func (d ViewData) StructName() string {
return d.Rendering.StructName(d.View.ParentName() + "_" + d.View.Name())
return d.Rendering.StructName(d.View.StructName())
}
func (d ViewData) FuncName() string {
return d.Rendering.FuncName(d.View.FuncName())
}
func (d ViewData) ParentName() string {
name := d.View.ParentName()
func (d ViewData) ParentStruct() string {
name := d.View.ParentStruct()
if name == "" {
name = d.Name()
}
@@ -43,7 +43,7 @@ func (d ViewData) Type() string {
}
func (d ViewData) Keys(parent string) string {
return Keys(append(d.View.Keys(), d.Name()), parent)
return Keys(d.View.Keys(), parent)
}
func (d ViewData) Value(name, val string) string {

View File

@@ -16,7 +16,7 @@ type Execute func(w io.Writer, vi view.View, rnd Rendering) error
var randders = map[reflect.Type]Execute{
reflect.TypeFor[*definition.Definition](): Template(defTpl),
reflect.TypeFor[group.Group](): Template(groupTpl),
reflect.TypeFor[*group.Group](): Template(groupTpl),
reflect.TypeFor[option.Option](): Template(optTpl),
reflect.TypeFor[proto.Proto](): Template(protoTpl),
}

View File

@@ -1,8 +1,8 @@
{{ block "FlagValue" . -}}
pval, perr := {{.ValName}}.ParseString()
if perr != nil {
return {{.Value}}, fmt.Errorf("read [%v]:%w",[]string{ {{- .Keys "i" -}} }, perr)
return {{.Value}}, fmt.Errorf("parse [%v]:%w",[]string{ {{- .Keys "i" -}} }, perr)
}
return {{.Value}}, {{.Value}}.Set(pval)
{{- end }}
{{- end -}}

View File

@@ -1,8 +1,8 @@
{{block "UnmarshalJSON" . -}}
pval, perr := {{.ValName}}.ParseString()
if perr != nil {
return {{.Value}}, fmt.Errorf("read [%v]:%w", []string{ {{- .Keys "i" -}} }, perr)
return {{.Value}}, fmt.Errorf("parse [%v]:%w", []string{ {{- .Keys "i" -}} }, perr)
}
return {{.Value}}, {{.Value}}.UnmarshalJSON([]byte(pval))
{{- end }}
{{- end -}}

View File

@@ -1,8 +1,8 @@
{{ block "UnmarshalText" . -}}
pval, perr := {{.ValName}}.ParseString()
if perr != nil {
return {{.Value}}, fmt.Errorf("read [%v]:%w", []string{ {{- .Keys "i" -}} }, perr)
return {{.Value}}, fmt.Errorf("parse [%v]:%w", []string{ {{- .Keys "i" -}} }, perr)
}
return {{.Value}}, {{.Value}}.UnmarshalText([]byte(pval))
{{- end }}
{{- end -}}

View File

@@ -1,15 +1,16 @@
func With{{.StructName}}Log(log func(context.Context, string, ...any)) func(*{{.StructName}}) {
func With{{.StructName}}Handle(fn func(context.Context, error)) func(*{{.StructName}}) {
return func(ci *{{.StructName}}) {
ci.log = log
ci.handle = fn
}
}
func New{{.StructName}}(prov config.Provider, opts ...func(*{{.StructName}})) {{.StructName}} {
func New{{.StructName}}({{if or .SkipContext .ClildSkipContext }} ctx context.Context,{{end}}prov config.Provider, opts ...func(*{{.StructName}})) {{.StructName}} {
i := {{.StructName}}{
Provider: prov,
log: func(_ context.Context, format string, args ...any) {
fmt.Printf(format, args...)
handle: func(_ context.Context, err error) {
fmt.Printf("{{.StructName}}:%v",err)
},
{{if or .SkipContext .ClildSkipContext }} ctx: ctx, {{end}}
}
for _, opt := range opts {
@@ -21,5 +22,6 @@ func New{{.StructName}}(prov config.Provider, opts ...func(*{{.StructName}})) {{
type {{.StructName}} struct {
config.Provider
log func(context.Context, string, ...any)
handle func(context.Context, error)
{{if or .SkipContext .ClildSkipContext}}ctx context.Context {{end}}
}

View File

@@ -1,8 +1,8 @@
type {{.StructName}} struct {
{{.ParentName}}
{{.ParentStruct}}
}
// {{.FuncName}} {{.Description}}.
func (i {{.ParentName}}) {{.FuncName}}() {{.StructName}} {
func (i {{.ParentStruct}}) {{.FuncName}}() {{.StructName}} {
return {{.StructName}}{i}
}

View File

@@ -1,9 +1,9 @@
// read{{.FuncName}} {{.Description}}.
func (i {{.ParentName}}) read{{.FuncName}}(ctx context.Context) (v {{.Type}},e error) {
func (i {{.ParentStruct}}) read{{.FuncName}}(ctx context.Context) (v {{.Type}},e error) {
val, err := i.Value(ctx, {{ .Keys "i" }})
if err != nil {
{{- if .HasDefault }}
i.log({{ if not .SkipContext }}context.Background(){{else}}ctx{{ end }}, "read [%v]: %v",[]string{ {{- .Keys "i" -}} }, err)
i.handle(ctx, err)
{{ .Default "val" -}}
{{ else }}
@@ -15,15 +15,15 @@ func (i {{.ParentName}}) read{{.FuncName}}(ctx context.Context) (v {{.Type}},e e
}
// Read{{.FuncName}} {{.Description}}.
func (i {{.ParentName}}) Read{{.FuncName}}({{if not .SkipContext}} ctx context.Context {{end}}) ({{.Type}}, error) {
return i.read{{.FuncName}}({{if .SkipContext}}context.Background(){{else}}ctx{{end}})
func (i {{.ParentStruct}}) Read{{.FuncName}}({{if not .SkipContext}} ctx context.Context {{end}}) ({{.Type}}, error) {
return i.read{{.FuncName}}({{if .SkipContext}}i.ctx{{else}}ctx{{end}})
}
// {{.FuncName}} {{.Description}}.
func (i {{.ParentName}}) {{.FuncName}}({{if not .SkipContext}} ctx context.Context {{end}}) {{.Type}} {
val, err := i.read{{.FuncName}}({{ if .SkipContext }}context.Background(){{else}}ctx{{ end }})
func (i {{.ParentStruct}}) {{.FuncName}}({{if not .SkipContext}} ctx context.Context {{end}}) {{.Type}} {
val, err := i.read{{.FuncName}}({{ if .SkipContext }}i.ctx{{else}}ctx{{ end }})
if err != nil {
i.log({{ if .SkipContext }}context.Background(){{else}}ctx{{ end }}, "get [%v]: %v",[]string{ {{- .Keys "i" -}} }, err)
i.handle({{ if .SkipContext }}i.ctx{{else}}ctx{{ end }}, err)
}
return val

View File

@@ -1,9 +1,9 @@
type {{.StructName}} struct {
{{.ParentName}}
{{.ParentStruct}}
{{ .Name }} string
}
// {{.FuncName}} {{.Description}}.
func (i {{.ParentName}}) {{.FuncName}}(key string) {{.StructName}} {
func (i {{.ParentStruct}}) {{.FuncName}}(key string) {{.StructName}} {
return {{.StructName}}{i,key}
}

View File

@@ -111,7 +111,7 @@ func dataRender(view ViewData) DataRender {
return h
}
if vtype.Kind() != reflect.Interface && vtype.Kind() != reflect.Ptr {
if vtype.Kind() != reflect.Interface && vtype.Kind() != reflect.Pointer {
vtype = reflect.PointerTo(vtype)
}

View File

@@ -32,12 +32,12 @@ func TestValue_FlagType(t *testing.T) {
const ex = `pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("read [%v]:%w",[]string{"flagValue"}, perr)
return v, fmt.Errorf("parse [%v]:%w",[]string{"flag_value"}, perr)
}
return v, v.Set(pval)`
viewData := render.NewViewData(nil, view.NewView(option.New("flag_value", "flag desc", flagValue(0)), nil))
viewData := render.NewViewData(nil, view.NewView(option.New("flag_value", "flag desc", flagValue(0))))
result := render.Value("val", "v", viewData)
if result != ex {
@@ -50,7 +50,7 @@ func TestData_Flag(t *testing.T) {
const ex = `return {{.val}}, {{.val}}.Set("42")`
viewData := render.NewViewData(nil, view.NewView(option.New("flag_value", "flag desc", flagValue(0)), nil))
viewData := render.NewViewData(nil, view.NewView(option.New("flag_value", "flag desc", flagValue(0))))
result := render.Data(flagValue(42), "val", viewData)
if result != ex {
@@ -78,7 +78,7 @@ func TestValue_Scan(t *testing.T) {
const ex = `return v, v.Scan(val.Any())`
viewData := render.NewViewData(nil, view.NewView(option.New("scan_value", "scan desc", scanValue(42)), nil))
viewData := render.NewViewData(nil, view.NewView(option.New("scan_value", "scan desc", scanValue(42))))
result := render.Value("val", "v", viewData)
if result != ex {
@@ -102,7 +102,7 @@ func TestData_UnmarshalText(t *testing.T) {
const ex = `return {{.val}}, {{.val}}.UnmarshalText("4devs")`
data := textData("4devs")
viewData := render.NewViewData(nil, view.NewView(option.New("tvalue", "unmarshal text desc", textData("")), nil))
viewData := render.NewViewData(nil, view.NewView(option.New("tvalue", "unmarshal text desc", textData(""))))
result := render.Data(data, "val", viewData)
if result != ex {
@@ -115,12 +115,12 @@ func TestValue_UnmarshalText(t *testing.T) {
const ex = `pval, perr := val.ParseString()
if perr != nil {
return v, fmt.Errorf("read [%v]:%w", []string{"tvalue"}, perr)
return v, fmt.Errorf("parse [%v]:%w", []string{"tvalue"}, perr)
}
return v, v.UnmarshalText([]byte(pval))`
viewData := render.NewViewData(nil, view.NewView(option.New("tvalue", "unmarshal text desc", textData("")), nil))
viewData := render.NewViewData(nil, view.NewView(option.New("tvalue", "unmarshal text desc", textData(""))))
result := render.Value("val", "v", viewData)
if result != ex {

View File

@@ -3,6 +3,6 @@ package {{.Pkg}}
import (
{{range .Imports}}
{{- .Alias }} "{{ .Package }}"
{{- . }}
{{end}}
)

View File

@@ -3,6 +3,7 @@ package view
import (
"fmt"
"reflect"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
@@ -14,8 +15,29 @@ type key int
const (
viewParamFunctName key = iota + 1
viewParamSkipContext
viewParamStructName
viewParamStructPrefix
viewParamStructSuffix
)
func WithStructName(name string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, viewParamStructName, name)
}
}
func WithStructPrefix(prefix string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, viewParamStructPrefix, prefix)
}
}
func WithStructSuffix(suffix string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, viewParamStructSuffix, suffix)
}
}
func WithSkipContext(p param.Params) param.Params {
return param.With(p, viewParamSkipContext, true)
}
@@ -38,39 +60,27 @@ func IsSkipContext(p param.Params) bool {
type Option func(*View)
func WithParent(name string) Option {
func WithParent(in *View) Option {
return func(v *View) {
v.parent = name
v.parent = in
}
}
func WithKeys(keys ...string) Option {
return func(v *View) {
v.keys = keys
}
}
func NewViews(name string, get param.Params, option config.Options, opts ...Option) View {
view := newView(name, get, option, opts...)
func NewViews(option config.Options, opts ...Option) View {
view := newView(option, opts...)
for _, op := range option.Options() {
view.children = append(view.children, NewView(op, get, WithParent(name)))
view.children = append(view.children, NewView(op, WithParent(&view)))
}
return view
}
type IOption any
func newView(name string, get param.Params, in any, opts ...Option) View {
func newView(params param.Params, opts ...Option) View {
vi := View{
kind: reflect.TypeOf(in),
name: name,
Params: get,
dtype: param.Type(get),
Params: params,
parent: nil,
children: nil,
keys: nil,
parent: "",
}
for _, opt := range opts {
@@ -80,17 +90,12 @@ func newView(name string, get param.Params, in any, opts ...Option) View {
return vi
}
func NewView(opt config.Option, get param.Params, opts ...Option) View {
vi := newView(opt.Name(), param.Chain(get, opt), opt, opts...)
func NewView(opt config.Option, opts ...Option) View {
vi := newView(opt, opts...)
if data, ok := opt.(config.Group); ok {
for _, chi := range data.Options() {
vi.children = append(vi.children, NewView(
chi,
param.Chain(vi.Params, chi),
WithParent(vi.ParentName()+"_"+opt.Name()),
WithKeys(append(vi.keys, opt.Name())...),
))
vi.children = append(vi.children, NewView(chi, WithParent(&vi)))
}
}
@@ -101,17 +106,13 @@ type View struct {
param.Params
children []View
keys []string
kind reflect.Type
name string
parent string
dtype any
parent *View
}
func (v View) Types() []any {
types := make([]any, 0)
if v.dtype != nil {
types = append(types, v.dtype)
if v.Type() != "" {
types = append(types, v.Type())
}
for _, child := range v.children {
@@ -122,7 +123,7 @@ func (v View) Types() []any {
}
func (v View) Kind() reflect.Type {
return v.kind
return reflect.TypeOf(v.Params)
}
func (v View) Views() []View {
@@ -130,32 +131,63 @@ func (v View) Views() []View {
}
func (v View) Param(key any) string {
data, ok := v.Params.Param(key)
if !ok {
return ""
data, has := v.Params.Param(key)
if has {
return fmt.Sprintf("%v", data)
}
if res, ok := data.(string); ok {
return res
if v.parent != nil {
return v.parent.Param(key)
}
return fmt.Sprintf("%v", data)
return ""
}
func (v View) ClildSkipContext() bool {
for _, child := range v.children {
if child.SkipContext() {
return true
}
}
return false
}
func (v View) SkipContext() bool {
return IsSkipContext(v.Params)
if IsSkipContext(v.Params) {
return true
}
if v.parent != nil {
return v.parent.SkipContext()
}
return false
}
func (v View) Name() string {
return v.name
if data, ok := v.Params.(interface{ Name() string }); ok {
return data.Name()
}
return ""
}
func (v View) Keys() []string {
return v.keys
keys := make([]string, 0, 1)
if v.parent != nil {
keys = append(keys, v.parent.Keys()...)
}
if name := v.Name(); name != "" {
keys = append(keys, name)
}
return keys
}
func (v View) Type() any {
return v.dtype
return param.Type(v.Params)
}
func (v View) FuncName() string {
@@ -163,22 +195,49 @@ func (v View) FuncName() string {
name, valid := data.(string)
if !ok || !valid {
return v.name
return v.Name()
}
return name
}
func (v View) ParentName() string {
return v.parent
func (v View) StructName() string {
name, ok := param.String(v.Params, viewParamStructName)
if ok {
return name
}
keys := make([]string, 0, len(v.Keys())+2) //nolint:mnd
prefix := v.Param(viewParamStructPrefix)
if prefix != "" {
keys = append(keys, prefix)
}
keys = append(keys, v.Keys()...)
suffix := v.Param(viewParamStructSuffix)
if suffix != "" {
keys = append(keys, suffix)
}
return strings.Join(keys, "_")
}
func (v View) ParentStruct() string {
if v.parent == nil {
return ""
}
return v.parent.StructName()
}
func (v View) Description() string {
return option.DataDescription(v.Params)
return param.Description(v.Params)
}
func (v View) Default() any {
data, ok := option.DataDefaut(v.Params)
data, ok := param.Default(v.Params)
if !ok {
return nil
}

View File

@@ -2,20 +2,19 @@ package group
import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
)
var _ config.Group = New("", "")
func New(name, desc string, opts ...config.Option) Group {
func New(name, desc string, opts ...config.Option) *Group {
group := Group{
name: name,
opts: opts,
Params: param.New(option.Description(desc)),
Params: param.New(param.WithDescription(desc)),
}
return group
return &group
}
type Group struct {
@@ -25,10 +24,21 @@ type Group struct {
opts []config.Option
}
func (g Group) Name() string {
func (g *Group) Name() string {
return g.name
}
func (g Group) Options() []config.Option {
func (g *Group) Options() []config.Option {
return g.opts
}
func (g *Group) Add(opts ...config.Option) {
g.opts = append(g.opts, opts...)
}
func (g *Group) With(opts ...param.Option) *Group {
group := New(g.Name(), "")
group.Params = param.Chain(g.Params, param.New(opts...))
return group
}

View File

@@ -0,0 +1,29 @@
package group_test
import (
"testing"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/test/require"
)
func TestGroupAdd(t *testing.T) {
t.Parallel()
var gr group.Group
gr.Add(group.New("test", "test"))
require.Truef(t, len(gr.Options()) == 1, "len(%v) != 1", len(gr.Options()))
}
func TestGroupWith(t *testing.T) {
t.Parallel()
const descrition = "group description"
gr := group.New("test", "test desc")
gr = gr.With(param.WithDescription(descrition))
require.Equal(t, descrition, param.Description(gr))
}

View File

@@ -10,7 +10,7 @@ import (
var _ config.Option = New("", "", nil)
func New(name, desc string, vtype any, opts ...param.Option) Option {
opts = append(opts, Description(desc), WithType(vtype))
opts = append(opts, param.WithDescription(desc), WithType(vtype))
res := Option{
name: name,
Params: param.New(opts...),

View File

@@ -8,12 +8,9 @@ type key int
const (
paramHidden key = iota + 1
paramDefault
paramDesc
paramRequired
paramSlice
paramBool
paramPos
paramShort
)
@@ -49,9 +46,7 @@ func WithType(in any) param.Option {
}
func Position(pos uint64) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramPos, pos)
}
return param.WithPostition(pos)
}
func Hidden(v param.Params) param.Params {
@@ -67,31 +62,30 @@ func Slice(v param.Params) param.Params {
}
func Default(in any) param.Option {
return func(v param.Params) param.Params {
return param.With(v, paramDefault, in)
}
return param.WithDefault(in)
}
// Deprecated: use param.WithDescription.
func Description(in string) param.Option {
return func(v param.Params) param.Params {
return param.With(v, paramDesc, in)
}
return param.WithDescription(in)
}
func HasDefaut(fn param.Params) bool {
_, ok := fn.Param(paramDefault)
_, ok := param.Default(fn)
return ok
}
// Deprecated: use param.Position.
func DataPosition(fn param.Params) (uint64, bool) {
return param.Uint64(paramPos, fn)
pos := param.Position(fn)
return pos, pos != 0
}
// Deprecated: use param.Default.
func DataDefaut(fn param.Params) (any, bool) {
data, ok := fn.Param(paramDefault)
return data, ok
return param.Default(fn)
}
func IsSlice(fn param.Params) bool {
@@ -118,8 +112,7 @@ func IsRequired(fn param.Params) bool {
return ok && data
}
// Deprecated: use param.Description.
func DataDescription(fn param.Params) string {
data, _ := param.String(fn, paramDesc)
return data
return param.Description(fn)
}

View File

@@ -2,7 +2,6 @@ package proto
import (
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
)
@@ -13,7 +12,7 @@ func New(name string, desc string, opts ...config.Option) Proto {
return Proto{
name: key.Wild(name),
opts: opts,
Params: param.New(option.Description(desc)),
Params: param.New(param.WithDescription(desc)),
}
}

View File

@@ -10,11 +10,12 @@ import (
func WrapFactory(fn Factory, prov Provider) *WrapProvider {
return &WrapProvider{
factory: func(ctx context.Context) (Provider, error) {
return fn(ctx, prov)
return fn.Create(ctx, prov)
},
mu: sync.Mutex{},
done: 0,
provider: nil,
name: fn.Name(),
}
}
@@ -23,6 +24,7 @@ type WrapProvider struct {
done uint32
provider Provider
factory func(ctx context.Context) (Provider, error)
name string
}
func (p *WrapProvider) Watch(ctx context.Context, callback WatchCallback, path ...string) error {
@@ -56,11 +58,7 @@ func (p *WrapProvider) Value(ctx context.Context, path ...string) (Value, error)
}
func (p *WrapProvider) Name() string {
if err := p.init(context.Background()); err != nil {
return fmt.Sprintf("%T", p.provider)
}
return p.provider.Name()
return p.name
}
func (p *WrapProvider) Bind(ctx context.Context, data Variables) error {

4
go.mod
View File

@@ -1,3 +1,5 @@
module gitoa.ru/go-4devs/config
go 1.23
go 1.24.0
require gitoa.ru/go-4devs/console v0.3.0

2
go.sum
View File

@@ -0,0 +1,2 @@
gitoa.ru/go-4devs/console v0.3.0 h1:8A8UZXrDAlBDWGWUsWckyEeYE3lowreZANCSRYjzBdM=
gitoa.ru/go-4devs/console v0.3.0/go.mod h1:PG/Zyj1dLh7eFlj9bgnV58+Ys6I/MTrS0q9W7oD7z4U=

View File

@@ -6,11 +6,12 @@ import (
const (
prefixByPath = "byPath"
wrongIDx = -1
)
func newMap() *Map {
return &Map{
idx: 0,
idx: wrongIDx,
wild: nil,
children: nil,
}
@@ -69,31 +70,30 @@ func (m *Map) add(path []string) *Map {
}
func (m *Map) byPath(path, sep string) (*Map, bool) {
if len(path) == 0 {
return m, m.isValid()
}
for name := range m.children {
if after, ok := strings.CutPrefix(path, name); ok {
data := m.children[name]
if len(after) == 0 {
return data, true
if len(after) == 0 || len(after) == len(sep) {
return data, data.isValid()
}
after, ok = strings.CutPrefix(after, sep)
if !ok {
return data, false
}
if data.wild == nil {
return data.byPath(after, sep)
}
if idx := strings.Index(after, sep); idx != -1 {
return data.wild.byPath(after[idx+1:], sep)
}
return data, false
return data.byPath(after[len(sep):], sep)
}
}
return m, false
if m.wild == nil {
return m, m.isValid()
}
if idx := strings.Index(path, sep); idx != -1 {
return m.wild.byPath(path[idx+1:], sep)
}
return m, m.isValid()
}
func (m *Map) find(path []string) (*Map, bool) {
@@ -104,18 +104,22 @@ func (m *Map) find(path []string) (*Map, bool) {
path = path[1:]
}
if m.wild != nil {
data, ok := m.children[name]
if !ok && m.wild != nil {
return m.wild.find(path)
}
data, ok := m.children[name]
if !ok {
return data, false
}
if last {
return data, data.children == nil
return data, data.isValid()
}
return data.find(path)
}
func (m *Map) isValid() bool {
return m.idx != wrongIDx
}

View File

@@ -11,26 +11,33 @@ func TestMap_ByPath(t *testing.T) {
const (
expID int = 1
othID int = 0
newID int = 42
grpID int = 27
)
data := key.Map{}
data.Add(expID, []string{"test", "data", "three"})
data.Add(expID, []string{"test", "other"})
data.Add(othID, []string{"test", "other"})
data.Add(newID, []string{"new", "{data}", "test"})
data.Add(grpID, []string{"new", "group"})
idx, ok := data.Index(key.ByPath("test-other", "-"))
if !ok {
t.Error("key not found")
}
if idx != expID {
t.Errorf("idx exp:%v got:%v", expID, idx)
if idx != othID {
t.Errorf("idx exp:%v got:%v", othID, idx)
}
if nidx, nok := data.Index(key.ByPath("new-service-test", "-")); !nok && nidx != newID {
t.Errorf("idx exp:%v got:%v", newID, nidx)
}
if gidx, gok := data.Index(key.ByPath("new-group", "-")); !gok && gidx != grpID {
t.Errorf("idx %v exp:%v got:%v", gok, grpID, gidx)
}
}
func TestMap_Add(t *testing.T) {
@@ -65,11 +72,13 @@ func TestMap_Wild(t *testing.T) {
const (
expID int = 1
grpID int = 27
newID int = 42
)
data := key.Map{}
data.Add(expID, []string{"test", "{data}", "id"})
data.Add(grpID, []string{"test", "group"})
data.Add(newID, []string{"new", "data"})
idx, ok := data.Index([]string{"test", "data", "id"})
@@ -80,4 +89,13 @@ func TestMap_Wild(t *testing.T) {
if idx != expID {
t.Errorf("idx exp:%v got:%v", expID, idx)
}
gidx, gok := data.Index([]string{"test", "group"})
if !gok {
t.Error("key not found")
}
if gidx != grpID {
t.Errorf("idx exp:%v got:%v", grpID, idx)
}
}

View File

@@ -1,15 +1,21 @@
package key
import "slices"
const minWildCount = 3
func IsWild(name string) bool {
func IsWild(keys ...string) bool {
return slices.ContainsFunc(keys, isWild)
}
func Wild(name string) string {
return "{" + name + "}"
}
func isWild(name string) bool {
if len(name) < minWildCount {
return false
}
return name[0] == '{' && name[len(name)-1] == '}'
}
func Wild(name string) string {
return "{" + name + "}"
}

16
key/wild_test.go Normal file
View File

@@ -0,0 +1,16 @@
package key_test
import (
"testing"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/test/require"
)
func TestWild(t *testing.T) {
t.Parallel()
require.True(t, key.IsWild(key.Wild("test")))
require.True(t, !key.IsWild("test"))
require.True(t, key.IsWild("test", key.Wild("test"), "key"))
}

View File

@@ -11,6 +11,17 @@ func String(fn Params, key any) (string, bool) {
return data, ok
}
func Rune(fn Params, key any) (rune, bool) {
val, ok := fn.Param(key)
if !ok {
return '0', false
}
data, dok := val.(rune)
return data, dok
}
func Bool(key any, fn Params) (bool, bool) {
val, ok := fn.Param(key)
if !ok {

View File

@@ -5,6 +5,9 @@ type key int
const (
paramTimeFormat key = iota + 1
paramType
paramDescription
paramDefault
paramPosition
)
func WithTimeFormat(format string) Option {
@@ -28,3 +31,39 @@ func Type(fn Params) any {
return param
}
func WithDescription(in string) Option {
return func(p Params) Params {
return With(p, paramDescription, in)
}
}
func Description(fn Params) string {
data, _ := String(fn, paramDescription)
return data
}
func WithDefault(in any) Option {
return func(p Params) Params {
return With(p, paramDefault, in)
}
}
func Default(p Params) (any, bool) {
data, ok := p.Param(paramDefault)
return data, ok
}
func WithPostition(in uint64) Option {
return func(p Params) Params {
return With(p, paramPosition, in)
}
}
func Position(in Params) uint64 {
pos, _ := Uint64(paramPosition, in)
return pos
}

123
processor/csv/processor.go Normal file
View File

@@ -0,0 +1,123 @@
package csv
import (
"bytes"
"context"
"encoding/csv"
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
type pkey int
const (
paramDelimiter pkey = iota + 1
paramParse
)
const defaultDelimiter = ','
func WithDelimiter(in rune) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramDelimiter, in)
}
}
func WithInt(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.Atoi)
})
}
func WithInt64(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseInt64)
})
}
func WithFloat(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseFloat)
})
}
func WithBool(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseBool)
})
}
func WithUint(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseUint)
})
}
func WithUint64(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseUint64)
})
}
func WithDuration(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseDuration)
})
}
func WithTime(p param.Params) param.Params {
return param.With(p, paramParse, func(data []string) (config.Value, error) {
return value.ParseSlice(data, value.ParseTime)
})
}
func WithParse(fn func(data []string) config.Value) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramParse, fn)
}
}
func Csv(_ context.Context, in config.Value, opts ...param.Option) (config.Value, error) {
sval, serr := in.ParseString()
if serr != nil {
return in, nil //nolint:nilerr
}
params := param.New(opts...)
reader := csv.NewReader(bytes.NewBufferString(sval))
reader.Comma = getDelimiter(params)
data, rerr := reader.Read()
if rerr != nil {
return nil, fmt.Errorf("read csv:%w", rerr)
}
return csvValue(params, data)
}
func csvValue(params param.Params, data []string) (config.Value, error) {
fn, ok := params.Param(paramParse)
if !ok {
return stringsValue(data)
}
parse, _ := fn.(func([]string) (config.Value, error))
return parse(data)
}
func stringsValue(data []string) (config.Value, error) {
return value.New(data), nil
}
func getDelimiter(params param.Params) rune {
if name, ok := param.Rune(params, paramDelimiter); ok {
return name
}
return defaultDelimiter
}

View File

@@ -0,0 +1,54 @@
package csv_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/csv"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
func TestCsv(t *testing.T) {
t.Parallel()
ctx := context.Background()
val := value.String("test2,test3,other")
data, derr := csv.Csv(ctx, val)
require.NoError(t, derr)
var resString []string
require.NoError(t, data.Unmarshal(&resString))
require.Equal(t, []string{"test2", "test3", "other"}, resString)
}
func TestCsv_int(t *testing.T) {
t.Parallel()
ctx := context.Background()
val := value.String("42,0,1")
data, derr := csv.Csv(ctx, val, csv.WithInt)
require.NoError(t, derr)
var resInt []int
require.NoError(t, data.Unmarshal(&resInt))
require.Equal(t, []int{42, 0, 1}, resInt)
}
func TestCsv_invalidValue(t *testing.T) {
t.Parallel()
ctx := context.Background()
val := value.String("42,0.1,1")
data, derr := csv.Csv(ctx, val, csv.WithInt)
require.ErrorIs(t, derr, config.ErrInvalidValue)
require.Equal(t, nil, data)
}

27
processor/env/processor.go vendored Normal file
View File

@@ -0,0 +1,27 @@
package env
import (
"context"
"fmt"
"os"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
var _ config.ProcessFunc = Env
func Env(_ context.Context, in config.Value, _ ...param.Option) (config.Value, error) {
key, err := in.ParseString()
if err != nil {
return in, fmt.Errorf("process[env]:%w", err)
}
res, ok := os.LookupEnv(key)
if !ok {
return nil, fmt.Errorf("%w", config.ErrNotFound)
}
return value.String(res), nil
}

View File

@@ -0,0 +1,19 @@
package json //nolint:revive
import (
"context"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
//nolint:revive
func Json(_ context.Context, in config.Value, _ ...param.Option) (config.Value, error) {
data, err := in.ParseString()
if err != nil {
return in, nil //nolint:nilerr
}
return value.JString(data), nil
}

View File

@@ -0,0 +1,48 @@
package json_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/json"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
func TestJson(t *testing.T) {
t.Parallel()
ctx := context.Background()
val, err := json.Json(ctx, value.String("42"))
require.NoError(t, err)
var res int
require.NoError(t, val.Unmarshal(&res))
require.Equal(t, 42, res)
sval, serr := json.Json(ctx, value.String("\"test data\""))
require.NoError(t, serr)
var sres string
require.NoError(t, sval.Unmarshal(&sres))
require.Equal(t, "test data", sres)
slval, slerr := json.Json(ctx, value.String("[\"test\",\"test2 data\",\"test3\"]"))
require.NoError(t, slerr)
var slres []string
require.NoError(t, slval.Unmarshal(&slres))
require.Equal(t, []string{"test", "test2 data", "test3"}, slres)
}
func TestJson_invalidValue(t *testing.T) {
t.Parallel()
ctx := context.Background()
val, err := json.Json(ctx, value.New("42"))
require.NoError(t, err)
var data string
require.ErrorIs(t, val.Unmarshal(&data), config.ErrInvalidValue)
}

View File

@@ -0,0 +1,49 @@
package key
import (
"context"
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
type pkey int
const paramKey pkey = iota
func WithKey(in string) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramKey, in)
}
}
func Key(_ context.Context, in config.Value, opts ...param.Option) (config.Value, error) {
data := make(map[string]any, 0)
if err := in.Unmarshal(&data); err != nil {
return nil, fmt.Errorf("unmarshal:%w", err)
}
key, ok := getKey(opts...)
if !ok {
return nil, fmt.Errorf("key is %w", config.ErrRequired)
}
val, vok := data[key]
if !vok {
return nil, fmt.Errorf("value by key[%v]: %w", key, config.ErrNotFound)
}
return value.New(val), nil
}
func getKey(opts ...param.Option) (string, bool) {
params := param.New(opts...)
if name, ok := param.String(params, paramKey); ok {
return name, ok
}
return "", false
}

View File

@@ -0,0 +1,58 @@
package key_test
import (
"context"
"encoding/json"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/key"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
func TestKey(t *testing.T) {
t.Parallel()
ctx := context.Background()
res, rerr := key.Key(ctx, keyData(t), key.WithKey("key"))
require.NoError(t, rerr)
require.Equal(t, "value", res.String())
}
func TestKey_required(t *testing.T) {
t.Parallel()
ctx := context.Background()
res, rerr := key.Key(ctx, keyData(t))
require.ErrorIs(t, rerr, config.ErrRequired)
require.Equal(t, res, nil)
}
func TestKey_notFound(t *testing.T) {
t.Parallel()
ctx := context.Background()
res, rerr := key.Key(ctx, keyData(t), key.WithKey("wrong"))
require.ErrorIs(t, rerr, config.ErrNotFound)
require.Equal(t, res, nil)
}
func keyData(t *testing.T) config.Value {
t.Helper()
data := map[string]string{
"key": "value",
}
jdata, err := json.Marshal(data)
require.NoError(t, err)
return value.JBytes(jdata)
}

View File

@@ -2,6 +2,7 @@ package config
import (
"context"
"io"
"gitoa.ru/go-4devs/config/param"
)
@@ -17,7 +18,10 @@ type WatchProvider interface {
Watch(ctx context.Context, callback WatchCallback, path ...string) error
}
type Factory func(ctx context.Context, cfg Provider) (Provider, error)
type Factory interface {
Name() string
Create(ctx context.Context, prov Provider) (Provider, error)
}
type Option interface {
Name() string
@@ -31,6 +35,7 @@ type Group interface {
type Options interface {
Options() []Option
param.Params
}
type BindProvider interface {
@@ -39,6 +44,19 @@ type BindProvider interface {
Bind(ctx context.Context, data Variables) error
}
type DunpProvider interface {
Provider
DumpRefernce(ctx context.Context, w io.Writer, opts Options) error
}
type Providers interface {
Provider
Provider(name string) (Provider, error)
Names() []string
}
type Variables interface {
ByName(name ...string) (Variable, error)
ByParam(filter param.Has) (Variable, error)
@@ -48,3 +66,13 @@ type Variables interface {
type Definition interface {
Add(opts ...Option)
}
type ProcessFunc func(ctx context.Context, in Value, opts ...param.Option) (Value, error)
func (o ProcessFunc) Process(ctx context.Context, in Value, opts ...param.Option) (Value, error) {
return o(ctx, in, opts...)
}
type Processor interface {
Process(ctx context.Context, in Value, opts ...param.Option) (Value, error)
}

419
provider/arg/dump.go Normal file
View File

@@ -0,0 +1,419 @@
package arg
import (
"bytes"
"encoding"
"encoding/json"
"errors"
"fmt"
"io"
"sort"
"strings"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
)
const (
defaultSpace = 2
)
func ResolveStyle(params param.Params) ViewStyle {
var vs ViewStyle
data, ok := params.Param(paramDumpReferenceView)
if ok {
vs, _ = data.(ViewStyle)
}
return vs
}
func WithStyle(comment, info Style) param.Option {
return func(p param.Params) param.Params {
return param.With(p, paramDumpReferenceView, ViewStyle{
Comment: comment,
Info: info,
MLen: 0,
})
}
}
type ViewStyle struct {
Comment Style
Info Style
MLen int
}
func (v ViewStyle) ILen() int {
return v.Info.Len() + v.MLen
}
type Style struct {
Start string
End string
}
func (s Style) Len() int {
return len(s.End) + len(s.Start)
}
func NewDump() Dump {
return Dump{
sep: dash,
space: defaultSpace,
}
}
type Dump struct {
sep string
space int
}
func (d Dump) Reference(w io.Writer, opt config.Options) error {
views := NewViews(opt, nil)
style := ResolveStyle(opt)
style.MLen = d.keyMaxLen(views...)
if args := views.Arguments(); len(args) > 0 {
if aerr := d.writeArguments(w, style, args...); aerr != nil {
return fmt.Errorf("write arguments:%w", aerr)
}
}
if opts := views.Options(); len(opts) > 0 {
if oerr := d.writeOptions(w, style, opts...); oerr != nil {
return fmt.Errorf("write option:%w", oerr)
}
}
return nil
}
//nolint:mnd
func (d Dump) keyMaxLen(views ...View) int {
var maxLen int
for _, vi := range views {
vlen := len(vi.Name(d.sep)) + d.space
if !vi.IsArgument() {
if !vi.IsBool() {
vlen = vlen*2 + 1
}
if def := vi.Default(); def != "" {
vlen += d.space
}
vlen += 4 + d.space
}
if vlen > maxLen {
maxLen = vlen
}
}
return maxLen
}
func (d Dump) writeArguments(w io.Writer, style ViewStyle, args ...View) error {
_, err := fmt.Fprintf(w, "\n%sArguments:%s\n",
style.Comment.Start,
style.Comment.End,
)
for _, arg := range args {
alen, ierr := fmt.Fprintf(w, "%s%s%s%s",
strings.Repeat(" ", d.space),
style.Info.Start,
arg.Name(d.sep),
style.Info.End,
)
if ierr != nil {
err = errors.Join(err, ierr)
}
_, ierr = fmt.Fprint(w, strings.Repeat(" ", style.ILen()+d.space-alen))
if ierr != nil {
err = errors.Join(err, ierr)
}
_, ierr = fmt.Fprint(w, arg.Description())
if ierr != nil {
err = errors.Join(err, ierr)
}
if def := arg.Default(); def != "" {
ierr := d.writeDefault(w, style, def)
if ierr != nil {
err = errors.Join(err, ierr)
}
}
_, ierr = fmt.Fprint(w, "\n")
if ierr != nil {
err = errors.Join(err, ierr)
}
}
if err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
//nolint:gocognit,gocyclo,cyclop
func (d Dump) writeOptions(w io.Writer, style ViewStyle, opts ...View) error {
_, err := fmt.Fprintf(w, "\n%sOptions:%s\n",
style.Comment.Start,
style.Comment.End,
)
for _, opt := range opts {
if opt.IsHidden() {
continue
}
var op bytes.Buffer
_, oerr := fmt.Fprintf(&op, "%s%s", strings.Repeat(" ", d.space), style.Info.Start)
if oerr != nil {
err = errors.Join(err, oerr)
}
if short := opt.Short(); short != "" {
op.WriteString("-")
op.WriteString(short)
op.WriteString(", ")
} else {
op.WriteString(" ")
}
op.WriteString("--")
op.WriteString(opt.Name(d.sep))
if !opt.IsBool() {
if !opt.IsRequired() {
op.WriteString("[")
}
op.WriteString("=")
op.WriteString(strings.ToUpper(opt.Name(d.sep)))
if !opt.IsRequired() {
op.WriteString("]")
}
}
_, oerr = fmt.Fprintf(&op, "%s", style.Info.End)
if oerr != nil {
err = errors.Join(err, oerr)
}
olen, oerr := w.Write(op.Bytes())
if oerr != nil {
err = errors.Join(err, oerr)
}
_, oerr = fmt.Fprintf(w, "%s%s",
strings.Repeat(" ", style.ILen()+d.space-olen),
opt.Description(),
)
if oerr != nil {
err = errors.Join(err, oerr)
}
if def := opt.Default(); def != "" {
oerr = d.writeDefault(w, style, def)
if oerr != nil {
err = errors.Join(err, oerr)
}
}
if opt.IsSlice() {
_, oerr = fmt.Fprintf(w, "%s (multiple values allowed)%s", style.Comment.Start, style.Comment.End)
if oerr != nil {
err = errors.Join(err, oerr)
}
}
_, oerr = fmt.Fprint(w, "\n")
if oerr != nil {
err = errors.Join(err, oerr)
}
}
if err != nil {
return fmt.Errorf("write options:%w", err)
}
return nil
}
func (d Dump) writeDefault(w io.Writer, style ViewStyle, data string) error {
_, err := fmt.Fprintf(w, " %s[default:%s]%s",
style.Comment.Start,
data,
style.Comment.End,
)
if err != nil {
return fmt.Errorf("default:%w", err)
}
return nil
}
func NewViews(opts config.Options, parent *View) Views {
views := make(Views, 0, len(opts.Options()))
for _, opt := range opts.Options() {
views = append(views, newViews(opt, parent)...)
}
return views
}
func newViews(opt config.Option, parent *View) []View {
view := NewView(opt, parent)
switch one := opt.(type) {
case config.Group:
return NewViews(one, &view)
default:
return []View{view}
}
}
type Views []View
func (v Views) Arguments() []View {
args := make([]View, 0, len(v))
for _, view := range v {
if view.IsArgument() {
args = append(args, view)
}
}
sort.Slice(args, func(i, j int) bool {
return args[i].Pos() < args[j].Pos()
})
return args
}
func (v Views) Options() []View {
opts := make([]View, 0, len(v))
for _, view := range v {
if !view.IsArgument() {
opts = append(opts, view)
}
}
sort.Slice(opts, func(i, j int) bool {
return opts[i].Pos() < opts[j].Pos()
})
return opts
}
func NewView(params config.Option, parent *View) View {
pos, ok := ParamArgument(params)
keys := make([]string, 0)
if parent != nil {
keys = append(keys, parent.Keys()...)
}
if !ok {
pos = param.Position(params)
}
if name := params.Name(); name != "" {
keys = append(keys, name)
}
return View{
pos: pos,
isArgument: ok,
keys: keys,
parent: parent,
Params: params,
}
}
type View struct {
param.Params
keys []string
pos uint64
isArgument bool
parent *View
}
func (v View) Name(delimiter string) string {
return strings.Join(v.keys, delimiter)
}
func (v View) IsArgument() bool {
return v.isArgument
}
func (v View) Keys() []string {
return v.keys
}
func (v View) Pos() uint64 {
return v.pos
}
func (v View) Default() string {
data, ok := param.Default(v.Params)
if !ok {
return ""
}
switch dt := data.(type) {
case time.Time:
return dt.Format(time.RFC3339)
case encoding.TextMarshaler:
if res, err := dt.MarshalText(); err == nil {
return string(res)
}
case json.Marshaler:
if res, err := dt.MarshalJSON(); err == nil {
return string(res)
}
}
return fmt.Sprintf("%v", data)
}
func (v View) Description() string {
return param.Description(v.Params)
}
func (v View) IsHidden() bool {
return option.IsHidden(v.Params)
}
func (v View) Short() string {
short, _ := option.ParamShort(v.Params)
return short
}
func (v View) IsRequired() bool {
return option.IsHidden(v.Params)
}
func (v View) IsBool() bool {
return option.IsBool(v.Params)
}
func (v View) IsSlice() bool {
return option.IsSlice(v.Params)
}

35
provider/arg/dump_test.go Normal file
View File

@@ -0,0 +1,35 @@
package arg_test
import (
"bytes"
"testing"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/test/require"
)
func TestDumpReference(t *testing.T) {
t.Parallel()
//nolint:dupword
const expect = `
Arguments:
config config [default:config.hcl]
user-name username
Options:
-l, --listen[=LISTEN] listen [default:8080]
-p, --user-password[=USER-PASSWORD] user pass
-u, --url[=URL] url (multiple values allowed)
-t, --timeout[=TIMEOUT] timeout (multiple values allowed)
--start-at[=START-AT] start at [default:2010-01-02T15:04:05Z]
--end-after[=END-AFTER] after (multiple values allowed)
--end-{service}-after[=END-{SERVICE}-AFTER] after
`
dump := arg.NewDump()
var buff bytes.Buffer
require.NoError(t, dump.Reference(&buff, testOptions(t)))
require.Equal(t, expect, buff.String())
}

View File

@@ -0,0 +1,38 @@
package arg_test
import (
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/test"
)
func testOptions(t *testing.T) config.Options {
t.Helper()
def := definition.New()
def.Add(
option.Int("listen", "listen", option.Short('l'), option.Default(8080)),
option.String("config", "config", arg.Argument, option.Default("config.hcl")),
group.New("user", "configure user",
option.String("name", "username", arg.Argument),
option.String("password", "user pass", option.Short('p')),
),
option.String("url", "url", option.Short('u'), option.Slice),
option.Duration("timeout", "timeout", option.Short('t'), option.Slice),
option.Time("start-at", "start at", option.Default(test.Time("2010-01-02T15:04:05Z"))),
group.New("end", "end at",
option.Time("after", "after", option.Slice),
proto.New("service", "service after",
option.Time("after", "after"),
),
),
)
return def
}

View File

@@ -10,13 +10,14 @@ type keyParam int
const (
paramArgument keyParam = iota + 1
paramDumpReferenceView
)
//nolint:gochecknoglobals
var argNum uint64
func Argument(v param.Params) param.Params {
return param.With(v, paramArgument, atomic.AddUint64(&argNum, 1))
return param.With(v, paramArgument, atomic.AddUint64(&argNum, 1)-1)
}
func ParamArgument(fn param.Params) (uint64, bool) {

View File

@@ -3,6 +3,7 @@ package arg
import (
"context"
"fmt"
"io"
"os"
"strings"
@@ -19,6 +20,7 @@ const (
dash = `-`
)
// Deprecated: use WithArgs.
func WithSkip(skip int) func(*Argv) {
return func(ar *Argv) {
res := 2
@@ -34,24 +36,27 @@ func WithSkip(skip int) func(*Argv) {
res = 1
}
ar.skip = res
ar.args = os.Args[res:]
}
}
func WithArgs(args []string) func(*Argv) {
return func(a *Argv) {
a.args = args
}
}
func New(opts ...func(*Argv)) *Argv {
arg := &Argv{
args: nil,
args: os.Args[1:],
pos: 0,
Map: memory.Map{},
skip: 1,
}
for _, opt := range opts {
opt(arg)
}
arg.args = os.Args[arg.skip:]
return arg
}
@@ -60,7 +65,6 @@ type Argv struct {
args []string
pos uint64
skip int
}
func (i *Argv) Value(ctx context.Context, key ...string) (config.Value, error) {
@@ -70,7 +74,7 @@ func (i *Argv) Value(ctx context.Context, key ...string) (config.Value, error) {
data, err := i.Map.Value(ctx, key...)
if err != nil {
return nil, fmt.Errorf("%w", err)
return nil, fmt.Errorf("map: %w", err)
}
return data, nil
@@ -101,17 +105,21 @@ func (i *Argv) Bind(ctx context.Context, def config.Variables) error {
}
if err != nil {
return fmt.Errorf("%w", err)
return fmt.Errorf("arg bind:%w", err)
}
}
if err := i.Map.Bind(ctx, def); err != nil {
return fmt.Errorf("%w", err)
return fmt.Errorf("arg map:%w", err)
}
return nil
}
func (i *Argv) DumpRefernce(_ context.Context, w io.Writer, opt config.Options) error {
return NewDump().Reference(w, opt)
}
func (i *Argv) parseLongOption(arg string, def config.Variables) error {
var value *string
@@ -164,7 +172,7 @@ func (i *Argv) parseShortOption(arg string, def config.Variables) error {
var value string
if len(name) > 1 {
name, value = arg[0:1], arg[1:]
name, value = arg[0:1], strings.TrimSpace(arg[1:])
}
opt, err := def.ByParam(option.HasShort(name))

View File

@@ -1,27 +1,22 @@
package arg_test
import (
"context"
"fmt"
"os"
"strings"
"testing"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/provider/arg"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
func TestProvider(t *testing.T) {
t.Parallel()
args := os.Args
defer func() {
os.Args = args
}()
os.Args = []string{
"main.go",
args := []string{
"--listen=8080",
"--config=config.hcl",
"--url=http://4devs.io",
@@ -44,11 +39,50 @@ func TestProvider(t *testing.T) {
}, &[]time.Time{}, "end-after"),
}
prov := arg.New()
prov := arg.New(arg.WithArgs(args))
test.Run(t, prov, read)
}
func TestProviderBind(t *testing.T) {
t.Parallel()
args := []string{
"-l 8080",
"--config=config.hcl",
"-u http://4devs.io",
"--url=https://4devs.io",
"-t 1m",
"--timeout=1h",
"--start-at=2010-01-02T15:04:05Z",
"--end-after=2009-01-02T15:04:05Z",
"--end-after=2008-01-02T15:04:05+03:00",
}
read := []test.Read{
test.NewRead(8080, "listen"),
test.NewRead(test.Time("2010-01-02T15:04:05Z"), "start-at"),
test.NewReadUnmarshal(&[]string{"http://4devs.io", "https://4devs.io"}, &[]string{}, "url"),
test.NewReadUnmarshal(&[]Duration{{time.Minute}, {time.Hour}}, &[]Duration{}, "timeout"),
test.NewReadUnmarshal(&[]time.Time{
test.Time("2009-01-02T15:04:05Z"),
test.Time("2008-01-02T15:04:05+03:00"),
}, &[]time.Time{}, "end", "after"),
}
ctx := context.Background()
prov := arg.New(arg.WithArgs(args))
require.NoError(t, prov.Bind(ctx, testVariables(t)))
test.Run(t, prov, read)
}
func testVariables(t *testing.T) config.Variables {
t.Helper()
return config.NewVars(testOptions(t).Options()...)
}
type Duration struct {
time.Duration
}

View File

@@ -9,7 +9,12 @@ import (
const Name = "chain"
func New(c ...config.Provider) config.BindProvider {
type Providers interface {
config.BindProvider
config.Providers
}
func New(c ...config.Provider) Providers {
return chain(c)
}
@@ -40,3 +45,39 @@ func (c chain) Bind(ctx context.Context, def config.Variables) error {
func (c chain) Name() string {
return Name
}
func (c chain) Provider(name string) (config.Provider, error) {
if c.Name() == name {
return c, nil
}
for _, prov := range c {
if prov.Name() == name {
return prov, nil
}
cprov, ok := prov.(config.Providers)
if !ok {
continue
}
if in, err := cprov.Provider(name); err == nil {
return in, nil
}
}
return nil, fmt.Errorf("prov[%v]:%w", c.Name(), config.ErrNotFound)
}
func (c chain) Names() []string {
names := make([]string, 0, len(c))
for _, prov := range c {
names = append(names, prov.Name())
if cprov, ok := prov.(config.Providers); ok {
names = append(names, cprov.Names()...)
}
}
return names
}

21
provider/dasel/go.mod Normal file
View File

@@ -0,0 +1,21 @@
module gitoa.ru/go-4devs/config/provider/dasel
go 1.25.5
require (
github.com/tomwright/dasel/v3 v3.2.0
gitoa.ru/go-4devs/config v0.0.6
)
require (
github.com/agext/levenshtein v1.2.1 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/hashicorp/hcl/v2 v2.24.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
golang.org/x/mod v0.26.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/tools v0.35.0 // indirect
)

30
provider/dasel/go.sum Normal file
View File

@@ -0,0 +1,30 @@
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/tomwright/dasel/v3 v3.2.0 h1:wyF2A4jVADx10E0kjbzPTiaF26D0OkK2OPuQRPJRDCo=
github.com/tomwright/dasel/v3 v3.2.0/go.mod h1:XyAl6LidZuWOISIeUmKlCqJDz4IWEDp83epNngZgOQA=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
gitoa.ru/go-4devs/config v0.0.6 h1:BbOH2KHBRMWtSwcFyHKzMXzxYbJpSwZpA8LGnnzBwk8=
gitoa.ru/go-4devs/config v0.0.6/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=

View File

@@ -0,0 +1,18 @@
app {
name {
var = [
"name"
]
title = "config title"
timeout = "1m"
success = true
}
}
cfg {
duration = 1260000000000
enabled = true
type = "json"
}

View File

@@ -0,0 +1,13 @@
package hcl
import (
"github.com/tomwright/dasel/v3/parsing/hcl"
"gitoa.ru/go-4devs/config/provider/dasel"
)
const Name = "dasel:hcl"
//nolint:wrapcheck
func New(data []byte) (dasel.Provider, error) {
return dasel.New(data, hcl.HCL, dasel.WithName(Name))
}

View File

@@ -0,0 +1,35 @@
package hcl_test
import (
"embed"
"testing"
"time"
dhcl "gitoa.ru/go-4devs/config/provider/dasel/hcl"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
//go:embed fixture/*
var fixture embed.FS
func TestProvider(t *testing.T) {
t.Parallel()
js, err := fixture.ReadFile("fixture/config.hcl")
require.NoError(t, err)
prov, derr := dhcl.New(js)
require.NoError(t, derr)
sl := []string{}
read := []test.Read{
test.NewRead("config title", "app", "name", "title"),
test.NewRead(time.Minute, "app.name.timeout"),
test.NewReadUnmarshal(&[]string{"name"}, &sl, "app.name.var"),
test.NewReadConfig("cfg"),
test.NewRead(true, "app", "name", "success"),
}
test.Run(t, prov, read)
}

View File

@@ -0,0 +1,17 @@
{
"app": {
"name": {
"var": [
"name"
],
"title": "config title",
"timeout": "1m",
"success": true
}
},
"cfg": {
"duration": 1260000000000,
"enabled": true,
"type":"json"
}
}

View File

@@ -0,0 +1,13 @@
package json //nolint:revive
import (
"github.com/tomwright/dasel/v3/parsing/json"
"gitoa.ru/go-4devs/config/provider/dasel"
)
const Name = "dasel:json"
//nolint:wrapcheck
func New(data []byte) (dasel.Provider, error) {
return dasel.New(data, json.JSON, dasel.WithName(Name))
}

View File

@@ -0,0 +1,35 @@
package json_test
import (
"embed"
"testing"
"time"
djson "gitoa.ru/go-4devs/config/provider/dasel/json"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
//go:embed fixture/*
var fixture embed.FS
func TestProvider(t *testing.T) {
t.Parallel()
js, err := fixture.ReadFile("fixture/config.json")
require.NoError(t, err)
prov, derr := djson.New(js)
require.NoError(t, derr)
sl := []string{}
read := []test.Read{
test.NewRead("config title", "app.name.title"),
test.NewRead(time.Minute, "app.name.timeout"),
test.NewReadUnmarshal(&[]string{"name"}, &sl, "app.name.var"),
test.NewReadConfig("cfg"),
test.NewRead(true, "app", "name", "success"),
}
test.Run(t, prov, read)
}

View File

@@ -0,0 +1,91 @@
package dasel
import (
"context"
"encoding/json"
"fmt"
"strings"
dasel "github.com/tomwright/dasel/v3"
"github.com/tomwright/dasel/v3/model"
"github.com/tomwright/dasel/v3/parsing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/value"
)
var _ config.Provider = Provider{} //nolint:exhaustruct
const Name = "dasel"
type Option func(*Provider)
func WithName(in string) Option {
return func(p *Provider) {
p.name = in
}
}
func New(in []byte, format parsing.Format, opts ...Option) (Provider, error) {
reader, err := format.NewReader(parsing.DefaultReaderOptions())
if err != nil {
return Provider{}, fmt.Errorf("%w:%w", config.ErrInitFactory, err)
}
data, verr := reader.Read(in)
if verr != nil {
return Provider{}, fmt.Errorf("%w:%w", config.ErrInitFactory, verr)
}
prov := Provider{
data: data,
key: func(path ...string) string {
return strings.Join(path, ".")
},
name: Name,
}
for _, opt := range opts {
opt(&prov)
}
return prov, nil
}
type Provider struct {
data *model.Value
key func(path ...string) string
name string
}
func (p Provider) Value(ctx context.Context, path ...string) (config.Value, error) {
selector := p.key(path...)
data, cnt, err := dasel.Query(ctx, p.data, selector)
if err != nil {
return nil, fmt.Errorf("query: %w:%w", config.ErrInvalidValue, err)
}
if cnt > 1 {
return nil, fmt.Errorf("count: %v:%w", cnt, config.ErrToManyArgs)
}
if cnt == 0 {
return value.EmptyValue(), nil
}
val, verr := data[0].GoValue()
if verr != nil {
return nil, fmt.Errorf("go value: %w:%w", config.ErrInvalidValue, verr)
}
res, merr := json.Marshal(val)
if merr != nil {
return nil, fmt.Errorf("marshal: %w:%w", config.ErrInvalidValue, merr)
}
return value.JBytes(res), nil
}
func (p Provider) Name() string {
return p.name
}

View File

@@ -3,10 +3,13 @@ package env
import (
"context"
"fmt"
"io"
"os"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/value"
)
@@ -42,15 +45,68 @@ type Provider struct {
prefix string
}
func (p *Provider) Key(path ...string) string {
return p.prefix + p.key(path...)
}
func (p *Provider) Name() string {
return p.name
}
func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error) {
name := p.prefix + p.key(path...)
if val, ok := os.LookupEnv(name); ok {
if val, ok := os.LookupEnv(p.Key(path...)); ok {
return value.JString(val), nil
}
return nil, fmt.Errorf("%v:%w", p.Name(), config.ErrNotFound)
}
func (p *Provider) DumpReference(_ context.Context, w io.Writer, opt config.Options) error {
return p.writeOptions(w, opt)
}
func (p *Provider) writeOptions(w io.Writer, opt config.Options, key ...string) error {
for idx, option := range opt.Options() {
if err := p.writeOption(w, option, key...); err != nil {
return fmt.Errorf("option[%d]:%w", idx, err)
}
}
return nil
}
func (p *Provider) writeOption(w io.Writer, opt config.Option, keys ...string) error {
if desc := param.Description(opt); desc != "" {
if _, derr := fmt.Fprintf(w, "# %v.\n", desc); derr != nil {
return fmt.Errorf("write description:%w", derr)
}
}
var err error
switch one := opt.(type) {
case config.Group:
err = p.writeOptions(w, one, append(keys, one.Name())...)
case config.Options:
err = p.writeOptions(w, one, keys...)
default:
def, dok := param.Default(opt)
prefix := ""
if !dok || key.IsWild(keys...) {
prefix = "#"
}
if !dok {
def = ""
}
_, err = fmt.Fprintf(w, "%s%s=%v\n", prefix, p.Key(append(keys, one.Name())...), def)
}
if err != nil {
return fmt.Errorf("%w", err)
}
return nil
}

View File

@@ -1,10 +1,17 @@
package env_test
import (
"bytes"
"context"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/provider/env"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
)
func TestProvider(t *testing.T) {
@@ -19,3 +26,35 @@ func TestProvider(t *testing.T) {
}
test.Run(t, provider, read)
}
func TestProvider_DumpReference(t *testing.T) {
t.Parallel()
const expect = `# configure log.
# level.
FDEVS_CONFIG_LOG_LEVEL=info
# configure log service.
# level.
#FDEVS_CONFIG_LOG_{SERVICE}_LEVEL=
`
ctx := context.Background()
prov := env.New("fdevs", "config")
buf := bytes.NewBuffer(nil)
require.NoError(t, prov.DumpReference(ctx, buf, testOptions(t)))
require.Equal(t, buf.String(), expect)
}
func testOptions(t *testing.T) config.Options {
t.Helper()
return group.New("test", "test",
group.New("log", "configure log",
option.String("level", "level", option.Default("info")),
proto.New("service", "configure log service",
option.String("level", "level"),
),
),
)
}

View File

@@ -0,0 +1,31 @@
package factory
import (
"context"
"gitoa.ru/go-4devs/config"
)
var _ config.Factory = New("", nil)
type Create func(ctx context.Context, prov config.Provider) (config.Provider, error)
func New(name string, fn Create) Factory {
return Factory{
create: fn,
name: name,
}
}
type Factory struct {
create Create
name string
}
func (f Factory) Name() string {
return f.name
}
func (f Factory) Create(ctx context.Context, prov config.Provider) (config.Provider, error) {
return f.create(ctx, prov)
}

74
provider/handler/env.go Normal file
View File

@@ -0,0 +1,74 @@
package handler
import (
"context"
"fmt"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/processor/env"
"gitoa.ru/go-4devs/config/value"
)
const (
envPreffix = "%env("
envSuffix = ")%"
)
type EnvOption func(*EnvHandler)
func WithEnvName(in string) EnvOption {
return func(eh *EnvHandler) {
eh.name = in
}
}
func WithEnvProcessor(proc config.Processor) EnvOption {
return func(eh *EnvHandler) {
eh.Processor = proc
}
}
func Env(parent config.Provider, opts ...EnvOption) EnvHandler {
handler := EnvHandler{
Provider: parent,
Processor: config.ProcessFunc(env.Env),
name: "env:" + parent.Name(),
}
for _, opt := range opts {
opt(&handler)
}
return handler
}
type EnvHandler struct {
config.Provider
config.Processor
name string
}
func (e EnvHandler) Name() string {
return e.name
}
func (e EnvHandler) Value(ctx context.Context, key ...string) (config.Value, error) {
val, err := e.Provider.Value(ctx, key...)
if err != nil {
return nil, fmt.Errorf("get %v:%w", e.Name(), err)
}
data, serr := val.ParseString()
if serr != nil || !strings.HasPrefix(data, envPreffix) || !strings.HasSuffix(data, envSuffix) {
return val, nil //nolint:nilerr
}
pval, perr := e.Process(ctx, value.String(data[len(envPreffix):len(data)-len(envSuffix)]))
if perr != nil {
return nil, fmt.Errorf("process[%v]:%w", e.Name(), perr)
}
return pval, nil
}

View File

@@ -0,0 +1,34 @@
package handler_test
import (
"context"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/provider/handler"
"gitoa.ru/go-4devs/config/test/require"
"gitoa.ru/go-4devs/config/value"
)
type provider struct {
value config.Value
}
func (p provider) Value(context.Context, ...string) (config.Value, error) {
return p.value, nil
}
func (p provider) Name() string {
return "test"
}
func TestEnvValue(t *testing.T) {
const except = "env value"
t.Setenv("APP_ENV", except)
ctx := context.Background()
process := handler.Env(provider{value: value.String("%env(APP_ENV)%")})
data, err := process.Value(ctx, "any")
require.NoError(t, err)
require.Equal(t, except, data.String())
}

View File

@@ -0,0 +1,107 @@
package handler
import (
"context"
"fmt"
"log"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
)
type pkey uint8
const (
processorKey pkey = iota + 1
)
func FormatFn(fn config.ProcessFunc, opts ...param.Option) param.Option {
return Process(config.ProcessFunc(func(ctx context.Context, in config.Value, _ ...param.Option) (config.Value, error) {
return fn(ctx, in, opts...)
}))
}
func Process(fn config.Processor) param.Option {
return func(p param.Params) param.Params {
return param.With(p, processorKey, fn)
}
}
func getProcess(in param.Params) (config.Processor, bool) {
p, ok := in.Param(processorKey)
if !ok {
return nil, false
}
data, tok := p.(config.Processor)
return data, tok
}
func Processor(parent config.Provider) *ProcessHandler {
handler := &ProcessHandler{
Provider: parent,
name: "process:" + parent.Name(),
idx: key.Map{},
process: nil,
}
return handler
}
type ProcessHandler struct {
config.Provider
idx key.Map
process []config.Processor
name string
}
func (p *ProcessHandler) Name() string {
return p.name
}
func (p *ProcessHandler) Bind(ctx context.Context, vars config.Variables) error {
for _, one := range vars.Variables() {
process, ok := getProcess(one)
if !ok {
continue
}
p.idx.Add(len(p.process), one.Key())
p.process = append(p.process, process)
}
log.Print(p.idx.Index([]string{"group", "int"}))
if bind, bok := p.Provider.(config.BindProvider); bok {
berr := bind.Bind(ctx, vars)
if berr != nil {
return fmt.Errorf("%w", berr)
}
}
return nil
}
func (p *ProcessHandler) Value(ctx context.Context, key ...string) (config.Value, error) {
pval, perr := p.Provider.Value(ctx, key...)
if perr != nil {
return nil, fmt.Errorf("%w", perr)
}
idx, iok := p.idx.Index(key)
if !iok {
return pval, nil
}
prov := p.process[idx]
val, err := prov.Process(ctx, pval)
if err != nil {
return nil, fmt.Errorf("process[%v]:%w", p.Name(), err)
}
return val, nil
}

View File

@@ -0,0 +1,111 @@
package handler_test
import (
"context"
"testing"
"time"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/group"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/definition/proto"
"gitoa.ru/go-4devs/config/processor/csv"
"gitoa.ru/go-4devs/config/provider/handler"
"gitoa.ru/go-4devs/config/provider/memory"
"gitoa.ru/go-4devs/config/test/require"
)
var (
testKey = []string{"test"}
testBool = []string{"group", "service", "bool"}
testInt = []string{"group", "int"}
testTime = []string{"group", "time"}
testUint64 = []string{"uint64"}
)
func TestProcessor(t *testing.T) {
t.Parallel()
ctx := context.Background()
prov := handler.Processor(&memory.Default{})
require.NoError(t, prov.Bind(ctx, testVariables(t)))
tval, terr := prov.Value(ctx, testKey...)
require.NoError(t, terr)
var tdata []string
require.NoError(t, tval.Unmarshal(&tdata))
require.Equal(t, []string{"test1", "test2 data", "test3"}, tdata)
bval, berr := prov.Value(ctx, testBool...)
require.NoError(t, berr)
var bdata []bool
require.NoError(t, bval.Unmarshal(&bdata))
require.Equal(t, []bool{true, false, true}, bdata)
ival, ierr := prov.Value(ctx, testInt...)
require.NoError(t, ierr)
var idata []int
require.NoError(t, ival.Unmarshal(&idata))
require.Equal(t, []int{-42, 0, 42}, idata)
tival, tierr := prov.Value(ctx, testTime...)
require.NoError(t, tierr)
var tidata []time.Time
require.NoError(t, tival.Unmarshal(&tidata))
require.Equal(t, []time.Time{time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC)}, tidata)
uval, uerr := prov.Value(ctx, testUint64...)
require.NoError(t, uerr)
var udata []uint64
require.NoError(t, uval.Unmarshal(&udata))
require.Equal(t, []uint64{42}, udata)
}
func testVariables(t *testing.T) config.Variables {
t.Helper()
vars := config.NewVars(
option.String("test", "test",
option.Slice,
option.Default("test1,\"test2 data\",test3"),
handler.Process(config.ProcessFunc(csv.Csv)),
),
group.New("group", "group",
proto.New("proto", "proto",
option.Bool("bool", "bool",
option.Slice,
option.Default("true|false|true"),
handler.FormatFn(csv.Csv, csv.WithBool, csv.WithDelimiter('|')),
),
),
option.Int("int", "int",
option.Slice,
option.Default("-42,0,42"),
handler.FormatFn(csv.Csv, csv.WithInt),
),
option.Time("time", "time",
option.Slice,
option.Default("2006-01-02T15:04:05Z"),
handler.FormatFn(csv.Csv, csv.WithTime),
),
),
option.Uint64("uint64", "uint64",
option.Slice,
option.Default("42"),
handler.FormatFn(csv.Csv, csv.WithUint64),
),
)
return vars
}

View File

@@ -5,7 +5,7 @@ import (
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/param"
)
const NameDefault = "default"
@@ -27,7 +27,7 @@ func (a *Default) Value(ctx context.Context, key ...string) (config.Value, error
func (a *Default) Bind(_ context.Context, def config.Variables) error {
for _, opt := range def.Variables() {
if data, ok := option.DataDefaut(opt); ok {
if data, ok := param.Default(opt); ok {
a.data.SetOption(data, opt.Key()...)
}
}

View File

@@ -1,8 +1,8 @@
module gitoa.ru/go-4devs/config/provider/toml
go 1.22
go 1.23
require (
github.com/pelletier/go-toml v1.9.5
gitoa.ru/go-4devs/config v0.0.1
github.com/pelletier/go-toml/v2 v2.2.4
gitoa.ru/go-4devs/config v0.0.5
)

View File

@@ -1,4 +1,6 @@
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
gitoa.ru/go-4devs/config v0.0.1 h1:9KrOO09YbIMO8qL8aVn/G74DurGdOIW5y3O02bays4I=
gitoa.ru/go-4devs/config v0.0.1/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
gitoa.ru/go-4devs/config v0.0.4 h1:mTD0w34PcmVHWaOr/4+zkhYSvcuHiJaBa/bJBuUHcLI=
gitoa.ru/go-4devs/config v0.0.4/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
gitoa.ru/go-4devs/config v0.0.5 h1:9GnQ1G6ebpkXe1tAeb3va/GIx7eBrMuJBYra47OXl+w=
gitoa.ru/go-4devs/config v0.0.5/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=

View File

@@ -2,10 +2,10 @@ package toml
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/pelletier/go-toml"
toml "github.com/pelletier/go-toml/v2"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/value"
)
@@ -17,23 +17,22 @@ const (
var _ config.Provider = (*Provider)(nil)
func NewFile(file string, opts ...Option) (*Provider, error) {
tree, err := toml.LoadFile(file)
if err != nil {
return nil, fmt.Errorf("toml: failed load file: %w", err)
}
return configure(tree, opts...), nil
}
type Option func(*Provider)
func configure(tree *toml.Tree, opts ...Option) *Provider {
func WithName(in string) Option {
return func(p *Provider) {
p.name = in
}
}
func New(in []byte, opts ...Option) (*Provider, error) {
var data Data
if err := toml.Unmarshal(in, &data); err != nil {
return nil, fmt.Errorf("toml failed load data: %w", err)
}
prov := &Provider{
tree: tree,
key: func(s []string) string {
return strings.Join(s, Separator)
},
data: data,
name: Name,
}
@@ -41,21 +40,11 @@ func configure(tree *toml.Tree, opts ...Option) *Provider {
opt(prov)
}
return prov
}
func New(data []byte, opts ...Option) (*Provider, error) {
tree, err := toml.LoadBytes(data)
if err != nil {
return nil, fmt.Errorf("toml failed load data: %w", err)
}
return configure(tree, opts...), nil
return prov, nil
}
type Provider struct {
tree *toml.Tree
key func([]string) string
data Data
name string
}
@@ -64,9 +53,39 @@ func (p *Provider) Name() string {
}
func (p *Provider) Value(_ context.Context, path ...string) (config.Value, error) {
if k := p.key(path); p.tree.Has(k) {
return Value{Value: value.Value{Val: p.tree.Get(k)}}, nil
val, err := p.data.Value(path...)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return nil, config.ErrValueNotFound
data, merr := json.Marshal(val)
if merr != nil {
return nil, fmt.Errorf("toml:%w", merr)
}
return value.JBytes(data), nil
}
type Data map[string]any
func (d Data) Value(path ...string) (any, error) {
if len(path) == 1 {
val, ok := d[path[0]]
if !ok {
return "", config.ErrValueNotFound
}
return val, nil
}
key, path := path[0], path[1:]
val, ok := d[key]
if !ok {
return nil, config.ErrValueNotFound
}
data, _ := val.(map[string]any)
return Data(data).Value(path...)
}

View File

@@ -4,6 +4,7 @@ import (
"embed"
"testing"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/provider/toml"
"gitoa.ru/go-4devs/config/test"
"gitoa.ru/go-4devs/config/test/require"
@@ -24,12 +25,14 @@ func TestProvider(t *testing.T) {
m := []int{}
read := []test.Read{
test.NewRead("192.168.1.1", "database.server"),
test.NewRead("192.168.1.1", "database", "server"),
test.NewRead("TOML Example", "title"),
test.NewRead("10.0.0.1", "servers.alpha.ip"),
test.NewRead(true, "database.enabled"),
test.NewRead(5000, "database.connection_max"),
test.NewRead("10.0.0.1", "servers", "alpha", "ip"),
test.NewRead(true, "database", "enabled"),
test.NewRead(5000, "database", "connection_max"),
test.NewReadUnmarshal(&[]int{8001, 8001, 8002}, &m, "database", "ports"),
test.NewErrorIs(config.ErrValueNotFound, "typo"),
test.NewErrorIs(config.ErrValueNotFound, "database.server"),
}
test.Run(t, prov, read)

View File

@@ -1,41 +0,0 @@
package toml
import (
"encoding/json"
"fmt"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/value"
)
type Value struct {
value.Value
}
func (s Value) Int() int {
v, _ := s.ParseInt()
return v
}
func (s Value) ParseInt() (int, error) {
v, err := s.ParseInt64()
if err != nil {
return 0, fmt.Errorf("toml failed parce int: %w", err)
}
return int(v), nil
}
func (s Value) Unmarshal(target any) error {
b, err := json.Marshal(s.Raw())
if err != nil {
return fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
}
if err := json.Unmarshal(b, target); err != nil {
return fmt.Errorf("%w: %w", config.ErrInvalidValue, err)
}
return nil
}

View File

@@ -1,29 +1,29 @@
module gitoa.ru/go-4devs/config/provider/vault
go 1.22
go 1.23.0
require (
github.com/hashicorp/vault/api v1.11.0
gitoa.ru/go-4devs/config v0.0.2
github.com/hashicorp/vault/api v1.22.0
gitoa.ru/go-4devs/config v0.0.5
)
require (
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.6.6 // indirect
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
golang.org/x/crypto v0.17.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/time v0.12.0 // indirect
)

View File

@@ -1,92 +1,61 @@
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw=
github.com/go-test/deep v1.0.2/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.16.2 h1:K4ev2ib4LdQETX5cSZBG0DVLk1jwGqSPXBjdah3veNs=
github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ=
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc=
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/vault/api v1.11.0 h1:AChWByeHf4/P9sX3Y1B7vFsQhZO2BgQiCMQ2SA1P1UY=
github.com/hashicorp/vault/api v1.11.0/go.mod h1:si+lJCYO7oGkIoNPAN8j3azBLTn9SjMGS+jFaHd1Cck=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0=
github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gitoa.ru/go-4devs/config v0.0.2 h1:bkTxW57kDDMf4cj/8W7fxPSN7JCPWEqlhCmL6LP3Vzg=
gitoa.ru/go-4devs/config v0.0.2/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 h1:NusfzzA6yGQ+ua51ck7E3omNUX/JuqbFSaRGqU8CcLI=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gitoa.ru/go-4devs/config v0.0.5 h1:9GnQ1G6ebpkXe1tAeb3va/GIx7eBrMuJBYra47OXl+w=
gitoa.ru/go-4devs/config v0.0.5/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -33,7 +33,7 @@ func (p *provider) Value(context.Context, ...string) (config.Value, error) {
func TestWatcher(t *testing.T) {
t.Parallel()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer func() {
cancel()
@@ -41,7 +41,7 @@ func TestWatcher(t *testing.T) {
prov := &provider{}
w := watcher.New(time.Second, prov)
w := watcher.New(time.Second/3, prov)
wg := sync.WaitGroup{}
wg.Add(2)

View File

@@ -1,8 +1,8 @@
module gitoa.ru/go-4devs/config/provider/yaml
go 1.22
go 1.23
require (
gitoa.ru/go-4devs/config v0.0.1
gitoa.ru/go-4devs/config v0.0.5
gopkg.in/yaml.v3 v3.0.1
)

View File

@@ -1,5 +1,7 @@
gitoa.ru/go-4devs/config v0.0.1 h1:9KrOO09YbIMO8qL8aVn/G74DurGdOIW5y3O02bays4I=
gitoa.ru/go-4devs/config v0.0.1/go.mod h1:xfEC2Al9xnMLJUuekYs3KhJ5BIzWAseNwkMwbN6/xss=
gitoa.ru/go-4devs/config v0.0.5 h1:9GnQ1G6ebpkXe1tAeb3va/GIx7eBrMuJBYra47OXl+w=
gitoa.ru/go-4devs/config v0.0.5/go.mod h1:UINWnObZA0nLiJro+TtavUBBvN0cSt17aRHOk20pP74=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -134,7 +134,7 @@ func NewErrorIs(exErr error, path ...string) Read {
Assert: func(*testing.T, config.Value) {},
Error: func(t *testing.T, err error) {
t.Helper()
require.ErrorIsf(t, exErr, err, "except err %v != %v", exErr, err)
require.ErrorIs(t, err, exErr)
},
}
}

View File

@@ -12,3 +12,13 @@ func Truef(t *testing.T, value bool, msg string, args ...any) {
t.FailNow()
}
}
func True(t *testing.T, value bool, args ...any) {
t.Helper()
if !value {
t.Errorf("require:true got:%v", value)
t.Error(args...)
t.FailNow()
}
}

View File

@@ -89,8 +89,22 @@ func JUnmarshal(b []byte, v any) error {
return nil
}
func JParce[T any](b []byte) (T, error) {
func JParse[T any](b []byte) (T, error) {
var data T
return data, JUnmarshal(b, &data)
}
func ParseSlice[T any](in []string, fn func(string) (T, error)) (config.Value, error) {
out := make([]T, 0, len(in))
for idx, one := range in {
data, err := fn(one)
if err != nil {
return nil, fmt.Errorf("parse[%T][%d]:%w", out, idx, err)
}
out = append(out, data)
}
return New(out), nil
}

View File

@@ -1,6 +1,8 @@
package value
import (
"errors"
"fmt"
"time"
"gitoa.ru/go-4devs/config"
@@ -21,35 +23,45 @@ func (s JBytes) ParseString() (string, error) {
}
func (s JBytes) ParseInt() (int, error) {
return JParce[int](s.Bytes())
return JParse[int](s.Bytes())
}
func (s JBytes) ParseInt64() (int64, error) {
return JParce[int64](s.Bytes())
return JParse[int64](s.Bytes())
}
func (s JBytes) ParseUint() (uint, error) {
return JParce[uint](s.Bytes())
return JParse[uint](s.Bytes())
}
func (s JBytes) ParseUint64() (uint64, error) {
return JParce[uint64](s.Bytes())
return JParse[uint64](s.Bytes())
}
func (s JBytes) ParseFloat64() (float64, error) {
return JParce[float64](s.Bytes())
return JParse[float64](s.Bytes())
}
func (s JBytes) ParseBool() (bool, error) {
return JParce[bool](s.Bytes())
return JParse[bool](s.Bytes())
}
func (s JBytes) ParseDuration() (time.Duration, error) {
return JParce[time.Duration](s.Bytes())
jdata, jerr := JParse[time.Duration](s.Bytes())
if jerr == nil {
return jdata, nil
}
ustr, serr := s.ParseString()
if serr != nil {
return 0, errors.Join(jerr, fmt.Errorf("parse duration:%w", serr))
}
return ParseDuration(ustr)
}
func (s JBytes) ParseTime() (time.Time, error) {
return JParce[time.Time](s.Bytes())
return JParse[time.Time](s.Bytes())
}
func (s JBytes) Bytes() []byte {

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