All checks were successful
Go Action / goaction (push) Successful in 29s
Reviewed-on: #12
265 lines
4.6 KiB
Go
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
|
|
}
|