From cdae99f0d883e3ac061ea58a2c0964a613b86ccc Mon Sep 17 00:00:00 2001 From: Mike Cohen Date: Thu, 12 Mar 2026 12:16:25 +1000 Subject: [PATCH] Standardise the way to access functions with kw args Some functions require specific args but also allow any additional keywords. This PR adds a standard way to extract such args: ``` kwargs, err := arg_parser.ExtractKWArgsWithContext(ctx, scope, args, arg) ``` The free form args are returned in the kwargs dict while the specific args are parsed as usual into the arg target. --- arg_parser/args.go | 22 ++++++++++++++++++++++ arg_parser/parser.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/arg_parser/args.go b/arg_parser/args.go index e7b536c..1602473 100644 --- a/arg_parser/args.go +++ b/arg_parser/args.go @@ -77,6 +77,28 @@ func ExtractArgsWithContext( return err } +func ExtractKWArgsWithContext( + ctx context.Context, scope types.Scope, + args *ordereddict.Dict, target interface{}) (*ordereddict.Dict, error) { + + args = NormalizeArgs(args) + + v := reflect.ValueOf(target) + if v.Type().Kind() == reflect.Ptr { + v = v.Elem() + } + + parser, err := GetParser(v) + if err != nil { + scope.Explainer().ParseArgs(args, target, err) + return nil, err + } + + kw, err := parser.ParseAsFreeForm(ctx, scope, args, v) + scope.Explainer().ParseArgs(args, target, err) + return kw, err +} + // Try to retrieve an arg name from the Dict of args. Coerce the arg // into something resembling a list of strings. func _ExtractStringArray( diff --git a/arg_parser/parser.go b/arg_parser/parser.go index 2620532..d1d9de7 100644 --- a/arg_parser/parser.go +++ b/arg_parser/parser.go @@ -94,6 +94,49 @@ func (self *Parser) Parse( return nil } +// Parses the args as free form args: +// * Extract known fields into the target struct. +// * Other fields will be returns as the kwargs dict. +func (self *Parser) ParseAsFreeForm( + ctx context.Context, scope types.Scope, args *ordereddict.Dict, target reflect.Value) (*ordereddict.Dict, error) { + parsed := make([]string, 0, args.Len()) + kwargs := ordereddict.NewDict() + + for _, parser := range self.Fields { + value, pres := args.Get(parser.Field) + if !pres { + if parser.Required { + return nil, fmt.Errorf("Field %s is required", parser.Field) + } + continue + } + + // Keep track of the fields we parsed. + parsed = append(parsed, parser.Field) + + // Convert the value using the parser + new_value, err := parser.Parser(ctx, scope, args, value) + if err != nil { + return nil, fmt.Errorf("Field %s %w", parser.Field, err) + } + + // Now set the field on the struct. + field_value := target.Field(parser.FieldIdx) + field_value.Set(reflect.ValueOf(new_value)) + } + + // Remaining fields are collected into the kwargs + if len(parsed) != args.Len() { + for _, item := range args.Items() { + if !utils.InString(&parsed, item.Key) { + kwargs.Set(item.Key, item.Value) + } + } + } + + return dict.RowToDict(ctx, scope, kwargs), nil +} + // The plugin may specify the arg as being a LazyExpr, in which case // it is completely up to it to evaluate the expression (if at all). // Note: Reducing the lazy expression may yield a StoredQuery - it is