parser API

parser

package

API reference for the parser package.

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

name
string
description
string

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-32
func Parse(name string, root any) (*CommandNode, error)

{
	val := reflect.ValueOf(root)
	if val.Kind() == reflect.Ptr {
		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

Returns

error
pkg/parser/parser.go:41-222
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:225-243
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
}