Build commands with both human-readable and structured output
Dual commands implement both BareCommand and GlazeCommand interfaces, switching between human-readable and structured output based on the --with-glaze-output flag.
type StatusCommand struct {
*cmds.CommandDescription
}
type StatusSettings struct {
Verbose bool `glazed:"verbose"`
}
// BareCommand: human-readable output
func (c *StatusCommand) Run(ctx context.Context, vals *values.Values) error {
s := &StatusSettings{}
if err := vals.DecodeSectionInto(schema.DefaultSlug, s); err != nil {
return err
}
fmt.Println("Status: Healthy")
if s.Verbose {
fmt.Println("Version: 1.0.0")
}
return nil
}
// GlazeCommand: structured output
func (c *StatusCommand) RunIntoGlazeProcessor(ctx context.Context, vals *values.Values, gp middlewares.Processor) error {
s := &StatusSettings{}
if err := vals.DecodeSectionInto(schema.DefaultSlug, s); err != nil {
return err
}
row := types.NewRow(
types.MRP("status", "healthy"),
)
if s.Verbose {
row.Set("version", "1.0.0")
}
return gp.AddRow(ctx, row)
}
var _ cmds.BareCommand = &StatusCommand{}
var _ cmds.GlazeCommand = &StatusCommand{}
cobraCmd, err := cli.BuildCobraCommand(cmd,
cli.WithDualMode(true), // enable dual-mode toggle
cli.WithGlazeToggleFlag("with-glaze-output"), // flag name
cli.WithDefaultToGlaze(), // default to glaze mode (optional)
cli.WithHiddenGlazeFlags("template", "select"), // hide specific glaze flags
)
| Option | Description |
|---|---|
WithDualMode(true) | Enable toggle between BareCommand and GlazeCommand |
WithGlazeToggleFlag | Set the flag name (default: with-glaze-output) |
WithDefaultToGlaze | Start in glaze mode instead of classic mode |
WithHiddenGlazeFlags | Hide specific glaze output flags |
Set the default output format for glaze mode using WithOutputSectionOptions:
glazedSection, err := settings.NewGlazedSection(
settings.WithOutputSectionOptions(
schema.WithDefaults(map[string]interface{}{
"output": "json", // default to JSON
}),
),
)
When multiple commands output similar data, use consistent field names:
// Common fields across cloud commands
types.MRP("id", node.Id()),
types.MRP("name", node.Name()),
types.MRP("type", node.Document.Type),
types.MRP("is_dir", node.IsDirectory()),
types.MRP("path", buildPathFromParents(node)),
When iterating (e.g., WalkTree), gp.AddRow() returns error, not bool:
// ❌ Wrong: returns error, not bool
Visit: func(node *model.Node, _ []string) bool {
gp.AddRow(ctx, row) // ignores error
return err // compile error
}
// ✅ Correct: handle or ignore error
Visit: func(node *model.Node, _ []string) bool {
if err := gp.AddRow(ctx, row); err != nil {
// log or handle
}
return false // continue walking
}
Share the same settings struct between both interface implementations:
type FindSettings struct {
AuthSettings
Compact bool `glazed:"compact"`
Start string `glazed:"start"`
Pattern string `glazed:"pattern"`
}
// Used by both Run() and RunIntoGlazeProcessor()
func (c *FindCommand) Run(ctx context.Context, vals *values.Values) error {
s := &FindSettings{}
if err := vals.DecodeSectionInto(schema.DefaultSlug, s); err != nil {
return err
}
// ...
}
func (c *FindCommand) RunIntoGlazeProcessor(ctx context.Context, vals *values.Values, gp middlewares.Processor) error {
s := &FindSettings{}
if err := vals.DecodeSectionInto(schema.DefaultSlug, s); err != nil {
return err
}
// ...
}
# Classic mode (default)
./remarquee cloud ls
# Glaze mode - JSON (now default if configured)
./remarquee cloud ls --with-glaze-output
# Override format
./remarquee cloud ls --with-glaze-output --output yaml
./remarquee cloud ls --with-glaze-output --output csv --fields name,path
glazed/pkg/doc/tutorials/05-build-first-command.mdglazed/pkg/doc/topics/commands-reference.mdcmd/examples/new-api-dual-mode/main.go