From 403af5683bd43ed7b51cfbda4baa3f46f09cf6de Mon Sep 17 00:00:00 2001 From: Alexander Terp Date: Sat, 23 Nov 2024 15:37:54 +1100 Subject: [PATCH] WIP: 'rest' arg Unfortunately pflag does not let you get the ignored args, it just tosses them away. In order for this feature to work, I'd need to dump pflag as well and really do my own parsing. I don't really wanna do that. This feature is not important enough, especially because you can relatively easily have your own string[] 'rest' arg which you can then join with spaces and offer as passthrough, with the downside of expecting users to comma separate their args. Some pflag Github discussions: https://github.com/spf13/pflag/pull/160 https://github.com/spf13/pflag/pull/187 https://github.com/spf13/pflag/issues/186 --- core/arg_types.go | 6 ++++++ core/gen_arg_stmt.go | 16 ++++++++++++++++ core/generators/ast.go | 1 + core/interpreter_args.go | 4 ++++ core/parser.go | 9 +++++++++ core/runner.go | 6 +++--- core/runner_usage.go | 8 +++++++- core/script_meta.go | 23 ++++++++++++++++------- docs/thinking/rest_args.md | 2 +- 9 files changed, 63 insertions(+), 12 deletions(-) diff --git a/core/arg_types.go b/core/arg_types.go index 93f8a9c..d3960b1 100644 --- a/core/arg_types.go +++ b/core/arg_types.go @@ -123,3 +123,9 @@ func FromArgDecl(l *LiteralInterpreter, argDecl *ArgDeclaration) *ScriptArg { return scriptArg } + +type ScriptRestArg struct { + RestToken Token + Name string + Comment *ArgCommentToken +} diff --git a/core/gen_arg_stmt.go b/core/gen_arg_stmt.go index 5e75382..9befcdf 100644 --- a/core/gen_arg_stmt.go +++ b/core/gen_arg_stmt.go @@ -11,6 +11,7 @@ type ArgStmt interface { } type ArgStmtVisitor interface { VisitArgDeclarationArgStmt(ArgDeclaration) + VisitArgRestDeclArgStmt(ArgRestDecl) } type ArgDeclaration struct { Identifier Token @@ -36,3 +37,18 @@ func (e ArgDeclaration) String() string { parts = append(parts, fmt.Sprintf("Comment: %v", e.Comment)) return fmt.Sprintf("ArgDeclaration(%s)", strings.Join(parts, ", ")) } + +type ArgRestDecl struct { + Identifier Token + Comment *ArgCommentToken +} + +func (e ArgRestDecl) Accept(visitor ArgStmtVisitor) { + visitor.VisitArgRestDeclArgStmt(e) +} +func (e ArgRestDecl) String() string { + var parts []string + parts = append(parts, fmt.Sprintf("Identifier: %v", e.Identifier)) + parts = append(parts, fmt.Sprintf("Comment: %v", e.Comment)) + return fmt.Sprintf("ArgRestDecl(%s)", strings.Join(parts, ", ")) +} diff --git a/core/generators/ast.go b/core/generators/ast.go index 6e3e82d..0550a2d 100644 --- a/core/generators/ast.go +++ b/core/generators/ast.go @@ -92,6 +92,7 @@ func main() { defineAst(outputDir, "ArgStmt", "", []string{ "ArgDeclaration : Token Identifier, *Token Rename, *Token Flag, RslArgType ArgType, " + // todo rename 'Rename'? "bool IsOptional, *LiteralOrArray Default, *ArgCommentToken Comment", + "ArgRestDecl : Token Identifier, *ArgCommentToken Comment", }) defineAst(outputDir, "RadStmt", "", []string{ diff --git a/core/interpreter_args.go b/core/interpreter_args.go index f9a1ab2..617764c 100644 --- a/core/interpreter_args.go +++ b/core/interpreter_args.go @@ -12,6 +12,10 @@ func (a ArgBlockInterpreter) VisitArgDeclarationArgStmt(declaration ArgDeclarati // arg declarations already initialized in env, nothing to do on visit here, just pass } +func (a ArgBlockInterpreter) VisitArgRestDeclArgStmt(decl ArgRestDecl) { + // arg declarations already initialized in env, nothing to do on visit here, just pass +} + func (a ArgBlockInterpreter) Run(block ArgBlock) { for _, stmt := range block.Stmts { stmt.Accept(a) diff --git a/core/parser.go b/core/parser.go index 3c4d87e..07523c3 100644 --- a/core/parser.go +++ b/core/parser.go @@ -175,6 +175,15 @@ func (p *Parser) argStatement() ArgStmt { return p.argDeclaration(identifier) } + if identifier.GetLexeme() == "rest" { + var argComment *ArgCommentToken + if p.matchAny(ARG_COMMENT) { + argComment = p.previous().(*ArgCommentToken) + } + // todo syntax not great in that it doesn't declare a type. you just need to 'know' it's a string array + return &ArgRestDecl{Identifier: identifier, Comment: argComment} + } + if p.matchKeyword(ARGS_BLOCK_KEYWORDS, REQUIRES) { panic(NOT_IMPLEMENTED) } diff --git a/core/runner.go b/core/runner.go index 9da15ca..7d598c7 100644 --- a/core/runner.go +++ b/core/runner.go @@ -109,13 +109,13 @@ func (r *RadRunner) Run() error { // help not explicitly invoked, so let's try parsing other args - // re-enable erroring on unknown flags. note: maybe remove for 'catchall' args? + // re-enable erroring on unknown flags // todo if unknown flag passed, pflag handles the error & prints a kinda ugly msg (twice, bug). // continue allowing unknown flags and then detect ourselves? - pflag.CommandLine.ParseErrorsWhitelist.UnknownFlags = false + //pflag.CommandLine.ParseErrorsWhitelist.UnknownFlags = false - // todo apparently this is not recommended, I should be using flagsets? I THINK I DO, FOR TESTS? pflag.Parse() + fmt.Println(pflag.Args()) posArgsIndex := 0 if FlagStdinScriptName.Value == "" { diff --git a/core/runner_usage.go b/core/runner_usage.go index bb9c79a..1b37cb0 100644 --- a/core/runner_usage.go +++ b/core/runner_usage.go @@ -60,9 +60,15 @@ func (r *RadRunner) printScriptUsage() { cyan(buf, fmt.Sprintf(" <%s>", arg.ApiName)) } } + + if r.scriptMetadata.RestArg != nil { + cyan(buf, fmt.Sprintf(" [%s...]", r.scriptMetadata.RestArg.Name)) + // todo the rest arg's comment currently gets dropped, not displayed in help + } + fmt.Fprintf(buf, "\n\n") - greenBold(buf, "Script flags:\n") + greenBold(buf, "Script args:\n") flagUsage(buf, r.scriptArgs) fmt.Fprintf(buf, "\n") diff --git a/core/script_meta.go b/core/script_meta.go index beb9d8e..1e924ea 100644 --- a/core/script_meta.go +++ b/core/script_meta.go @@ -7,17 +7,19 @@ import ( type ScriptData struct { ScriptName string Args []ScriptArg + RestArg *ScriptRestArg OneLineDescription *string // todo not really using this atm, throw away? revisit this syntax BlockDescription *string Instructions []Stmt } func ExtractMetadata(instructions []Stmt) *ScriptData { - args := extractArgs(instructions) + args, restArg := extractArgs(instructions) oneLineDescription, blockDescription := extractDescriptions(instructions) return &ScriptData{ ScriptName: ScriptName, Args: args, + RestArg: restArg, OneLineDescription: oneLineDescription, BlockDescription: blockDescription, Instructions: instructions, @@ -40,7 +42,7 @@ func extractDescriptions(instructions []Stmt) (*string, *string) { return oneLiner, block } -func extractArgs(instructions []Stmt) []ScriptArg { +func extractArgs(instructions []Stmt) ([]ScriptArg, *ScriptRestArg) { var args []ScriptArg argBlockIfFound, ok := lo.Find(instructions, func(stmt Stmt) bool { _, ok := stmt.(*ArgBlock) @@ -48,18 +50,25 @@ func extractArgs(instructions []Stmt) []ScriptArg { }) if !ok { - return args + return args, nil } + var restArg *ScriptRestArg argBlock := argBlockIfFound.(*ArgBlock) for _, argStmt := range argBlock.Stmts { - argDecl, ok := argStmt.(*ArgDeclaration) - if ok { + switch coerced := argStmt.(type) { + case *ArgDeclaration: literalInterpreter := NewLiteralInterpreter(nil) // todo should probably not be nil, for erroring? - arg := FromArgDecl(literalInterpreter, argDecl) + arg := FromArgDecl(literalInterpreter, coerced) args = append(args, *arg) + case *ArgRestDecl: + restArg = &ScriptRestArg{ + RestToken: coerced.Identifier, + Name: coerced.Identifier.GetLexeme(), + Comment: coerced.Comment, + } } } - return args + return args, restArg } diff --git a/docs/thinking/rest_args.md b/docs/thinking/rest_args.md index 24d698b..2fe3ae5 100644 --- a/docs/thinking/rest_args.md +++ b/docs/thinking/rest_args.md @@ -41,7 +41,7 @@ rest = ["-t", "quiet", "-a", "30"] Option 2: -Skip unknown args/flags, use recognized flags, put the unrecognized into an array. +Skip unknown args/flags, use recognized flags, put the unrecognized ones into an array. ``` name = Alice