parser
API
parser
packageAPI reference for the parser
package.
Imports
(5)
S
struct
FlagMetadata
FlagMetadata holds information about a flag.
pkg/parser/node.go:8-16
type FlagMetadata struct
Fields
| Name | Type | Description |
|---|---|---|
| Name | string | |
| Short | string | |
| Description | string | |
| Default | string | |
| Env | string | |
| Required | bool | |
| Field | reflect.Value |
S
struct
ArgMetadata
ArgMetadata holds information about a positional argument.
pkg/parser/node.go:19-24
type ArgMetadata struct
Fields
| Name | Type | Description |
|---|---|---|
| Description | string | |
| Required | bool | |
| IsGreedy | bool | |
| Field | reflect.Value |
S
struct
CommandNode
CommandNode represents a node in the command tree.
pkg/parser/node.go:27-37
type CommandNode struct
Fields
| Name | Type | Description |
|---|---|---|
| Name | string | |
| Description | string | |
| Aliases | []string | |
| Flags | map[string]*FlagMetadata | |
| ShortFlags | map[string]string | |
| Args | []*ArgMetadata | |
| Children | map[string]*CommandNode | |
| Value | reflect.Value | |
| Type | reflect.Type |
F
function
NewCommandNode
NewCommandNode creates a new CommandNode with initialized maps.
Parameters
Returns
pkg/parser/node.go:44-54
func NewCommandNode(name, description string, val reflect.Value) *CommandNode
{
return &CommandNode{
Name: name,
Description: description,
Flags: make(map[string]*FlagMetadata),
ShortFlags: make(map[string]string),
Children: make(map[string]*CommandNode),
Value: val,
Type: val.Type(),
}
}
Example
node := parser.NewCommandNode("init", "Initialize project", reflect.ValueOf(&InitCmd{}))
F
function
Parse
Parse parses a struct and returns a CommandNode tree.
Parameters
name
string
root
any
Returns
error
pkg/parser/parser.go:19-38
func Parse(name string, root any) (*CommandNode, error)
{
val := reflect.ValueOf(root)
if !val.IsValid() {
return nil, fmt.Errorf("invalid root value (nil)")
}
if val.Kind() == reflect.Ptr {
if val.IsNil() {
return nil, fmt.Errorf("root value is a nil pointer")
}
val = val.Elem()
}
node := NewCommandNode(name, "", val)
if err := ParseStruct(node, val); err != nil {
return nil, err
}
return node, nil
}
Example
root := &RootCmd{}
node, err := parser.Parse("apx", root)
F
function
ParseStruct
ParseStruct recursively parses the struct fields to build the command tree.
Parameters
node
val
Returns
error
pkg/parser/parser.go:47-228
func ParseStruct(node *CommandNode, val reflect.Value) error
{
v := val
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return nil
}
v = v.Elem()
}
if v.Kind() != reflect.Struct {
return nil
}
typ := v.Type()
for i := 0; i < v.NumField(); i++ {
field := typ.Field(i)
fieldVal := v.Field(i)
if field.Tag.Get("internal") == "ignore" {
continue
}
cmdTag, ok := field.Tag.Lookup("cmd")
// Pre-process for map merging
if ok && cmdTag == "*" {
fVal := fieldVal
for fVal.Kind() == reflect.Ptr {
if fVal.IsNil() {
break
}
fVal = fVal.Elem()
}
if fVal.Kind() == reflect.Map {
if !fVal.IsNil() {
if node.Children == nil {
node.Children = make(map[string]*CommandNode)
}
iter := fVal.MapRange()
for iter.Next() {
cmdName := iter.Key().String()
val := iter.Value()
// Unwrap pointer to reach the command struct
startVal := val
for startVal.Kind() == reflect.Ptr {
if startVal.IsNil() {
break
}
startVal = startVal.Elem()
}
if startVal.Kind() == reflect.Ptr && startVal.IsNil() {
continue
}
helpPrefix := field.Tag.Get("help")
description := ""
if helpPrefix != "" {
description = fmt.Sprintf("pr:%s.%s", helpPrefix, cmdName)
}
childNode := NewCommandNode(cmdName, description, startVal)
node.Children[cmdName] = childNode
if err := ParseStruct(childNode, startVal); err != nil {
return err
}
}
}
}
continue
}
if ok {
startVal := fieldVal
for startVal.Kind() == reflect.Ptr {
if startVal.IsNil() {
if startVal.CanSet() {
startVal.Set(reflect.New(startVal.Type().Elem()))
} else {
break
}
}
startVal = startVal.Elem()
}
cmdName := strings.ToLower(field.Name)
if cmdTag != "" {
cmdName = cmdTag
}
var aliases []string
if aliasTag, ok := field.Tag.Lookup("aliases"); ok {
aliases = strings.Split(aliasTag, ",")
}
description := field.Tag.Get("help")
childNode := NewCommandNode(cmdName, description, startVal)
childNode.Aliases = aliases
node.Children[cmdName] = childNode
for _, alias := range aliases {
node.Children[alias] = childNode
}
if err := ParseStruct(childNode, startVal); err != nil {
return err
}
continue
}
if cliTag, ok := field.Tag.Lookup("cli"); ok {
parts := strings.Split(cliTag, ",")
name := parts[0]
short := ""
if len(parts) > 1 {
short = parts[1]
}
required := false
if reqTag, ok := field.Tag.Lookup("required"); ok && reqTag == "true" {
required = true
}
flagMeta := &FlagMetadata{
Name: name,
Short: short,
Description: field.Tag.Get("help"),
Default: field.Tag.Get("default"),
Env: field.Tag.Get("env"),
Required: required,
Field: fieldVal,
}
node.Flags[name] = flagMeta
if short != "" {
node.ShortFlags[short] = name
}
continue
}
if flagTag, ok := field.Tag.Lookup("flag"); ok {
meta := parseFlagTag(flagTag, fieldVal)
name := meta.Name
if name == "" {
name = strings.ToLower(field.Name)
}
node.Flags[name] = meta
if meta.Short != "" {
node.ShortFlags[meta.Short] = name
}
continue
}
if _, ok := field.Tag.Lookup("arg"); ok {
required := false
if reqTag, ok := field.Tag.Lookup("required"); ok && reqTag == "true" {
required = true
}
isGreedy := field.Type.Kind() == reflect.Slice && field.Type.Elem().Kind() == reflect.String
argMeta := &ArgMetadata{
Description: field.Tag.Get("help"),
Required: required,
IsGreedy: isGreedy,
Field: fieldVal,
}
node.Args = append(node.Args, argMeta)
continue
}
}
return nil
}
Example
node := parser.NewCommandNode("init", "Initialize project", reflect.ValueOf(&InitCmd{}))
val := reflect.ValueOf(&InitCmd{})
err := parser.ParseStruct(node, val)
F
function
parseFlagTag
parseFlagTag parses the flag:“short:x, long:y, name:z” format.
Parameters
tag
string
fieldVal
Returns
pkg/parser/parser.go:231-249
func parseFlagTag(tag string, fieldVal reflect.Value) *FlagMetadata
{
meta := &FlagMetadata{Field: fieldVal}
parsed := flagTagParser.Parse(tag)
if vals, ok := parsed["short"]; ok && len(vals) > 0 {
meta.Short = vals[0]
}
if vals, ok := parsed["long"]; ok && len(vals) > 0 {
meta.Name = vals[0]
}
if vals, ok := parsed["name"]; ok && len(vals) > 0 {
meta.Description = vals[0]
}
return meta
}
S
struct
rootNoSub
pkg/parser/parser_test.go:8-8
type rootNoSub struct
S
struct
rootWithSub
pkg/parser/parser_test.go:10-12
type rootWithSub struct
Fields
| Name | Type | Description |
|---|---|---|
| Add | AddCmd | cmd:"" help:"Add a new item" |
Uses
S
struct
AddCmd
pkg/parser/parser_test.go:14-16
type AddCmd struct
Fields
| Name | Type | Description |
|---|---|---|
| Item | string | arg:"" required:"true" help:"Item to add" |
S
struct
rootWithFlags
pkg/parser/parser_test.go:18-22
type rootWithFlags struct
Fields
| Name | Type | Description |
|---|---|---|
| Verbose | bool | cli:"verbose,v" help:"Enable verbose" env:"VERBOSE" |
| Name | string | cli:"name" help:"Your name" default:"world" |
| Count | int | cli:"count" help:"Count" required:"true" |
S
struct
rootFlagTag
pkg/parser/parser_test.go:24-26
type rootFlagTag struct
Fields
| Name | Type | Description |
|---|---|---|
| Verbose | bool | flag:"short:v,long:verbose,name:Enable verbose output" |
S
struct
rootAliases
pkg/parser/parser_test.go:28-30
type rootAliases struct
Fields
| Name | Type | Description |
|---|---|---|
| Add | AddCmd | cmd:"" aliases:"a,ad" help:"Add a new item" |
Uses
S
struct
rootWildcard
pkg/parser/parser_test.go:32-34
type rootWildcard struct
Fields
| Name | Type | Description |
|---|---|---|
| Commands | map[string]*WildcardCmd | cmd:"*" help:"dynamic" |
S
struct
WildcardCmd
pkg/parser/parser_test.go:36-38
type WildcardCmd struct
Fields
| Name | Type | Description |
|---|---|---|
| Arg | string | arg:"" help:"arg" |
S
struct
rootNested
pkg/parser/parser_test.go:40-44
type rootNested struct
Fields
| Name | Type | Description |
|---|---|---|
| Config | struct { Show ShowCmd `cmd:"" help:"Show config"` } | cmd:"config" help:"Config commands" |
S
struct
ShowCmd
pkg/parser/parser_test.go:46-48
type ShowCmd struct
Fields
| Name | Type | Description |
|---|---|---|
| All | bool | cli:"all" help:"Show all" |
S
struct
rootInternal
pkg/parser/parser_test.go:50-53
type rootInternal struct
Fields
| Name | Type | Description |
|---|---|---|
| Verbose | bool | cli:"verbose,v" help:"Enable verbose" |
| Hidden | string | internal:"ignore" |
F
function
TestParse_RootOnly
Parameters
t
pkg/parser/parser_test.go:55-69
func TestParse_RootOnly(t *testing.T)
{
node, err := Parse("app", &rootNoSub{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if node.Name != "app" {
t.Errorf("got name %q, want %q", node.Name, "app")
}
if len(node.Children) != 0 {
t.Errorf("expected no children, got %d", len(node.Children))
}
if len(node.Flags) != 0 {
t.Errorf("expected no flags, got %d", len(node.Flags))
}
}
F
function
TestParse_Subcommand
Parameters
t
pkg/parser/parser_test.go:71-86
func TestParse_Subcommand(t *testing.T)
{
node, err := Parse("app", &rootWithSub{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if len(node.Children) != 1 {
t.Fatalf("expected 1 child, got %d", len(node.Children))
}
child, ok := node.Children["add"]
if !ok {
t.Fatal("expected child named 'add'")
}
if child.Description == "" {
t.Error("expected non-empty description for add")
}
}
F
function
TestParse_CLIFlags
Parameters
t
pkg/parser/parser_test.go:88-118
func TestParse_CLIFlags(t *testing.T)
{
node, err := Parse("app", &rootWithFlags{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if len(node.Flags) != 3 {
t.Fatalf("expected 3 flags, got %d", len(node.Flags))
}
verbose, ok := node.Flags["verbose"]
if !ok {
t.Fatal("expected flag 'verbose'")
}
if verbose.Short != "v" {
t.Errorf("got short %q, want %q", verbose.Short, "v")
}
if verbose.Env != "VERBOSE" {
t.Errorf("got env %q, want %q", verbose.Env, "VERBOSE")
}
if verbose.Default != "" {
t.Errorf("expected empty default for verbose")
}
count, ok := node.Flags["count"]
if !ok {
t.Fatal("expected flag 'count'")
}
if !count.Required {
t.Error("count should be required")
}
}
F
function
TestParse_FlagTag
Parameters
t
pkg/parser/parser_test.go:120-135
func TestParse_FlagTag(t *testing.T)
{
node, err := Parse("app", &rootFlagTag{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if len(node.Flags) != 1 {
t.Fatalf("expected 1 flag, got %d", len(node.Flags))
}
flag, ok := node.Flags["verbose"]
if !ok {
t.Fatal("expected flag 'verbose'")
}
if flag.Short != "v" {
t.Errorf("got short %q, want %q", flag.Short, "v")
}
}
F
function
TestParse_PositionalArgs
Parameters
t
pkg/parser/parser_test.go:137-152
func TestParse_PositionalArgs(t *testing.T)
{
node, err := Parse("add", &AddCmd{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if len(node.Args) != 1 {
t.Fatalf("expected 1 arg, got %d", len(node.Args))
}
arg := node.Args[0]
if !arg.Required {
t.Error("arg should be required")
}
if arg.IsGreedy {
t.Error("arg should not be greedy")
}
}
F
function
TestParse_Aliases
Parameters
t
pkg/parser/parser_test.go:154-178
func TestParse_Aliases(t *testing.T)
{
node, err := Parse("app", &rootAliases{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if len(node.Children) < 1 {
t.Fatal("expected at least 1 child")
}
add, ok := node.Children["add"]
if !ok {
t.Fatal("expected child 'add'")
}
if len(add.Aliases) != 2 {
t.Fatalf("expected 2 aliases, got %d", len(add.Aliases))
}
aChild, ok := node.Children["a"]
if !ok {
t.Fatal("expected alias 'a' to map to same node")
}
if aChild != add {
t.Error("alias 'a' should point to same CommandNode as 'add'")
}
}
F
function
TestParse_DynamicCommands
Parameters
t
pkg/parser/parser_test.go:180-209
func TestParse_DynamicCommands(t *testing.T)
{
cmds := map[string]*WildcardCmd{
"start": {Arg: "start-val"},
"stop": {Arg: "stop-val"},
}
root := &rootWildcard{Commands: cmds}
node, err := Parse("app", root)
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
start, ok := node.Children["start"]
if !ok {
t.Fatal("expected dynamic child 'start'")
}
if start.Name != "start" {
t.Errorf("got name %q, want %q", start.Name, "start")
}
stop, ok := node.Children["stop"]
if !ok {
t.Fatal("expected dynamic child 'stop'")
}
if stop.Name != "stop" {
t.Errorf("got name %q, want %q", stop.Name, "stop")
}
if len(start.Args) != 1 {
t.Fatalf("expected 1 arg in start child, got %d", len(start.Args))
}
}
F
function
TestParse_NestedCommands
Parameters
t
pkg/parser/parser_test.go:211-231
func TestParse_NestedCommands(t *testing.T)
{
node, err := Parse("app", &rootNested{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
config, ok := node.Children["config"]
if !ok {
t.Fatal("expected child 'config'")
}
show, ok := config.Children["show"]
if !ok {
t.Fatal("expected child 'show' under config")
}
if show.Description == "" {
t.Error("expected non-empty description for show")
}
_, hasAll := show.Flags["all"]
if !hasAll {
t.Error("expected flag 'all' on show")
}
}
F
function
TestParse_InternalIgnore
Parameters
t
pkg/parser/parser_test.go:233-244
func TestParse_InternalIgnore(t *testing.T)
{
node, err := Parse("app", &rootInternal{})
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
if len(node.Flags) != 1 {
t.Fatalf("expected 1 flag (verbose), got %d", len(node.Flags))
}
if node.Value.Type().Kind() != reflect.Struct {
t.Fatal("expected struct value")
}
}
F
function
TestParse_NonStructType
Parameters
t
pkg/parser/parser_test.go:246-254
func TestParse_NonStructType(t *testing.T)
{
node, err := Parse("app", "string-root")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if node == nil {
t.Fatal("expected non-nil node")
}
}
F
function
TestParse_NilPointer
Parameters
t
pkg/parser/parser_test.go:256-262
func TestParse_NilPointer(t *testing.T)
{
var cmd *AddCmd
_, err := Parse("add", cmd)
if err == nil {
t.Error("expected error for nil pointer")
}
}