Skip to content

Commit

Permalink
WIP: 'rest' arg
Browse files Browse the repository at this point in the history
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:

spf13/pflag#160
spf13/pflag#187
spf13/pflag#186
  • Loading branch information
amterp committed Nov 23, 2024
1 parent 6108c21 commit 403af56
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 12 deletions.
6 changes: 6 additions & 0 deletions core/arg_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,9 @@ func FromArgDecl(l *LiteralInterpreter, argDecl *ArgDeclaration) *ScriptArg {

return scriptArg
}

type ScriptRestArg struct {
RestToken Token
Name string
Comment *ArgCommentToken
}
16 changes: 16 additions & 0 deletions core/gen_arg_stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ArgStmt interface {
}
type ArgStmtVisitor interface {
VisitArgDeclarationArgStmt(ArgDeclaration)
VisitArgRestDeclArgStmt(ArgRestDecl)
}
type ArgDeclaration struct {
Identifier Token
Expand All @@ -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, ", "))
}
1 change: 1 addition & 0 deletions core/generators/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
4 changes: 4 additions & 0 deletions core/interpreter_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions core/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
6 changes: 3 additions & 3 deletions core/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand Down
8 changes: 7 additions & 1 deletion core/runner_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
23 changes: 16 additions & 7 deletions core/script_meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -40,26 +42,33 @@ 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)
return ok
})

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
}
2 changes: 1 addition & 1 deletion docs/thinking/rest_args.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 403af56

Please sign in to comment.