55 Commits

Author SHA1 Message Date
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
86 changed files with 3126 additions and 720 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

@@ -18,6 +18,9 @@ on:
- '.gitea/workflows/vault.yml'
- 'provider/yaml/**'
- '.gitea/workflows/yaml.yml'
- 'provider/dasel/**'
- '.gitea/workflows/dasel.yml'
jobs:

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,68 @@
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"
"gitoa.ru/go-4devs/console/output"
)
const Name = "config:generate"
func Handle(ctx context.Context, in config.Provider, out output.Output, next console.Action) 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() *console.Command {
return &console.Command{
Description: "",
Help: "",
Version: "v0.0.1",
Hidden: false,
Prepare: nil,
Handle: Handle,
Name: Name,
Execute: Execute,
Configure: generate.Configure,
}
}

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.2.0

2
go.sum
View File

@@ -0,0 +1,2 @@
gitoa.ru/go-4devs/console v0.2.0 h1:6lsbArs99GA8vGdnwNDThZNKjFNctNtTlSCUjhgwIpU=
gitoa.ru/go-4devs/console v0.2.0/go.mod h1:xi4Svw7T+lylckAQiJQS/2qwDwF4YbIanlhcbQrBAiI=

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

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

@@ -23,31 +23,31 @@ 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) {
jdata, jerr := JParce[time.Duration](s.Bytes())
jdata, jerr := JParse[time.Duration](s.Bytes())
if jerr == nil {
return jdata, nil
}
@@ -61,7 +61,7 @@ func (s JBytes) ParseDuration() (time.Duration, error) {
}
func (s JBytes) ParseTime() (time.Time, error) {
return JParce[time.Time](s.Bytes())
return JParse[time.Time](s.Bytes())
}
func (s JBytes) Bytes() []byte {

View File

@@ -2,7 +2,6 @@ package value
import (
"fmt"
"strconv"
"time"
"gitoa.ru/go-4devs/config"
@@ -32,12 +31,7 @@ func (s String) Any() any {
}
func (s String) ParseInt() (int, error) {
v, err := strconv.Atoi(string(s))
if err != nil {
return 0, fmt.Errorf("string int:%w", err)
}
return v, nil
return Atoi(s.String())
}
func (s String) Int64() int64 {
@@ -47,63 +41,31 @@ func (s String) Int64() int64 {
}
func (s String) ParseInt64() (int64, error) {
v, err := strconv.ParseInt(string(s), 10, 64)
if err != nil {
return 0, fmt.Errorf("string int64:%w", err)
}
return v, nil
return ParseInt64(s.String())
}
func (s String) ParseUint() (uint, error) {
uout, err := s.ParseUint64()
return uint(uout), err
return ParseUint(s.String())
}
func (s String) ParseUint64() (uint64, error) {
uout, err := strconv.ParseUint(string(s), 10, 64)
if err != nil {
return 0, fmt.Errorf("string uint:%w", err)
}
return uout, nil
return ParseUint64(s.String())
}
func (s String) ParseFloat64() (float64, error) {
fout, err := strconv.ParseFloat(string(s), 64)
if err != nil {
return 0, fmt.Errorf("string float64:%w", err)
}
return fout, nil
return ParseFloat(s.String())
}
func (s String) ParseBool() (bool, error) {
v, err := strconv.ParseBool(string(s))
if err != nil {
return false, fmt.Errorf("string bool:%w", err)
}
return v, nil
return ParseBool(s.String())
}
func (s String) ParseDuration() (time.Duration, error) {
v, err := time.ParseDuration(string(s))
if err != nil {
return 0, fmt.Errorf("string duration:%w", err)
}
return v, nil
return ParseDuration(s.String())
}
func (s String) ParseTime() (time.Time, error) {
v, err := time.Parse(time.RFC3339, string(s))
if err != nil {
return time.Time{}, fmt.Errorf("string time:%w", err)
}
return v, nil
return ParseTime(s.String())
}
func (s String) IsEquals(val config.Value) bool {

View File

@@ -171,7 +171,7 @@ func typeAssert(source, target any) error {
}
valTarget := reflect.ValueOf(target)
if !valTarget.IsValid() || valTarget.Kind() != reflect.Ptr {
if !valTarget.IsValid() || valTarget.Kind() != reflect.Pointer {
return fmt.Errorf("ptr target:%w", config.ErrInvalidValue)
}
@@ -245,7 +245,7 @@ func directTypeAssert(source, target any) bool {
}
func deReference(v reflect.Value) reflect.Value {
if (v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface) && !v.IsNil() {
if (v.Kind() == reflect.Pointer || v.Kind() == reflect.Interface) && !v.IsNil() {
return v.Elem()
}

View File

@@ -8,9 +8,14 @@ import (
)
func NewVar(opt Option) Variable {
names := make([]string, 0, 1)
if name := opt.Name(); name != "" {
names = append(names, name)
}
return Variable{
param: opt,
names: []string{opt.Name()},
names: names,
}
}
@@ -58,6 +63,12 @@ func newVars(opts ...Option) []Variable {
one := NewVar(opt)
switch data := opt.(type) {
case Group:
if data.Name() == "" {
vars = append(vars, newVars(data.Options()...)...)
continue
}
vars = append(vars, groupVars(one, data.Options()...)...)
default:
vars = append(vars, one)