Files
config/provider/arg/provider.go
andrey f9a0411192
All checks were successful
Go Action / goaction (push) Successful in 29s
def (#12)
Reviewed-on: #12
2025-12-26 14:55:42 +03:00

265 lines
4.6 KiB
Go

package arg
import (
"context"
"fmt"
"os"
"strings"
"gitoa.ru/go-4devs/config"
"gitoa.ru/go-4devs/config/definition/option"
"gitoa.ru/go-4devs/config/key"
"gitoa.ru/go-4devs/config/param"
"gitoa.ru/go-4devs/config/provider/memory"
)
const (
doubleDash = `--`
defaultLenLognOption = 2
dash = `-`
)
func WithSkip(skip int) func(*Argv) {
return func(ar *Argv) {
res := 2
switch {
case skip > 0 && len(os.Args) > skip:
res = skip
case skip > 0:
res = len(os.Args)
case len(os.Args) == 1:
res = 1
case len(os.Args) > 1 && os.Args[1][0] == '-':
res = 1
}
ar.skip = res
}
}
func New(opts ...func(*Argv)) *Argv {
arg := &Argv{
args: nil,
pos: 0,
Map: memory.Map{},
skip: 1,
}
for _, opt := range opts {
opt(arg)
}
arg.args = os.Args[arg.skip:]
return arg
}
type Argv struct {
memory.Map
args []string
pos uint64
skip int
}
func (i *Argv) Value(ctx context.Context, key ...string) (config.Value, error) {
if err := i.parse(); err != nil {
return nil, fmt.Errorf("parse:%w", err)
}
data, err := i.Map.Value(ctx, key...)
if err != nil {
return nil, fmt.Errorf("%w", err)
}
return data, nil
}
func (i *Argv) Bind(ctx context.Context, def config.Variables) error {
options := true
for len(i.args) > 0 {
var err error
arg := i.args[0]
i.args = i.args[1:]
switch {
case options && arg == doubleDash:
options = false
case options && len(arg) > 2 && arg[0:2] == doubleDash:
err = i.parseLongOption(arg[2:], def)
case options && arg[0:1] == "-":
if len(arg) == 1 {
return fmt.Errorf("%w: option name required given '-'", config.ErrInvalidName)
}
err = i.parseShortOption(arg[1:], def)
default:
err = i.parseArgument(arg, def)
}
if err != nil {
return fmt.Errorf("%w", err)
}
}
if err := i.Map.Bind(ctx, def); err != nil {
return fmt.Errorf("%w", err)
}
return nil
}
func (i *Argv) parseLongOption(arg string, def config.Variables) error {
var value *string
name := arg
if strings.Contains(arg, "=") {
vals := strings.SplitN(arg, "=", defaultLenLognOption)
name = vals[0]
value = &vals[1]
}
opt, err := def.ByName(key.ByPath(name, dash)...)
if err != nil {
return Err(err, name)
}
return i.appendOption(value, opt)
}
func (i *Argv) appendOption(data *string, opt config.Variable) error {
if i.HasOption(opt.Key()...) && !option.IsSlice(opt) {
return fmt.Errorf("%w: got: array, expect: %T", config.ErrUnexpectedType, param.Type(opt))
}
var val string
switch {
case data != nil:
val = *data
case option.IsBool(opt):
val = "true"
case len(i.args) > 0 && len(i.args[0]) > 0 && i.args[0][0:1] != "-":
val = i.args[0]
i.args = i.args[1:]
default:
return Err(config.ErrRequired, opt.Key()...)
}
err := i.AppendOption(val, opt.Key()...)
if err != nil {
return Err(err, opt.Key()...)
}
return nil
}
func (i *Argv) parseShortOption(arg string, def config.Variables) error {
name := arg
var value string
if len(name) > 1 {
name, value = arg[0:1], arg[1:]
}
opt, err := def.ByParam(option.HasShort(name))
if err != nil {
return fmt.Errorf("%w", err)
}
if option.IsBool(opt) && value != "" {
err := i.parseShortOption(value, def)
if err != nil {
return err
}
value = ""
}
if value == "" {
return i.appendOption(nil, opt)
}
return i.appendOption(&value, opt)
}
func (i *Argv) parseArgument(arg string, def config.Variables) error {
opt, err := def.ByParam(PosArgument(i.pos))
if err != nil {
return fmt.Errorf("%w", err)
}
i.pos++
if err := i.AppendOption(arg, opt.Key()...); err != nil {
return Err(err, opt.Key()...)
}
return nil
}
func (i *Argv) parse() error {
if i.Len() > 0 {
return nil
}
for _, arg := range i.args {
name, value, err := i.parseOne(arg)
if err != nil {
return err
}
if name != "" {
if err := i.AppendOption(value, name); err != nil {
return fmt.Errorf("append %v: %w", name, err)
}
}
}
return nil
}
// parseOne return name, value, error.
func (i *Argv) parseOne(arg string) (string, string, error) {
if arg[0] != '-' {
return "", "", nil
}
numMinuses := 1
if arg[1] == '-' {
numMinuses++
}
name := strings.TrimSpace(arg[numMinuses:])
if len(name) == 0 {
return name, "", nil
}
if name[0] == '-' || name[0] == '=' {
return "", "", fmt.Errorf("%w: bad flag syntax: %s", config.ErrInvalidValue, arg)
}
var val string
for idx := 1; idx < len(name); idx++ {
if name[idx] == '=' || name[idx] == ' ' {
val = strings.TrimSpace(name[idx+1:])
name = name[0:idx]
break
}
}
if val == "" && numMinuses == 1 && len(arg) > 2 {
name, val = name[:1], name[1:]
}
return name, val, nil
}