Skip to content

Commit

Permalink
Merge pull request #34 from choria-io/33
Browse files Browse the repository at this point in the history
(#33) Experimental command plugins
  • Loading branch information
ripienaar authored Apr 25, 2023
2 parents f95ab53 + c3640d2 commit 4b9c8b7
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 45 deletions.
1 change: 1 addition & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ func New(name, help string) *Application {
a.Flag("completion-bash", "Output possible completions for the given args.").Hidden().UnNegatableBoolVar(&a.completion)
a.Flag("completion-script-bash", "Generate completion script for bash.").Hidden().PreAction(a.generateBashCompletionScript).UnNegatableBool()
a.Flag("completion-script-zsh", "Generate completion script for ZSH.").Hidden().PreAction(a.generateZSHCompletionScript).UnNegatableBool()
a.Flag("fisk-introspect", "Introspect the application model").Hidden().Action(a.introspectAction).UnNegatableBool()

return a
}
Expand Down
167 changes: 167 additions & 0 deletions app_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package fisk

import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
)

type pluginDelegator struct {
command string
flags map[string]*string
boolFlags map[string]*bool
unNegBoolFlags map[string]*bool
args map[string]*string
}

func (a *Application) introspectModel() *ApplicationModel {
model := a.Model()
var nf []*FlagModel
for _, flag := range model.Flags {
if flag.Name == "help" || strings.HasPrefix(flag.Name, "help-") || strings.HasPrefix(flag.Name, "completion-") || strings.HasPrefix(flag.Name, "fisk-") {
continue
}

nf = append(nf, flag)
}
model.Flags = nf

var nc []*CmdModel
for _, cmd := range model.Commands {
if cmd.Name == "help" {
continue
}
nc = append(nc, cmd)
}
model.Commands = nc

return model
}

func (a *Application) introspectAction(c *ParseContext) error {
a.Writer(os.Stdout)

j, err := json.Marshal(a.introspectModel())
if err != nil {
return err
}

fmt.Println(string(j))
return nil
}

func (c *CmdClause) addArgsFromModel(model *ArgGroupModel) {
for _, arg := range model.Args {
a := c.Arg(arg.Name, arg.Help)
a.placeholder = arg.PlaceHolder
a.required = arg.Required
a.hidden = arg.Hidden
a.defaultValues = arg.Default
a.envar = arg.Envar
c.pluginDelegator.args[arg.Name] = a.String()
}
}

func (c *CmdClause) addFlagsFromModel(model *FlagGroupModel) {
for _, flag := range model.Flags {
f := c.Flag(flag.Name, flag.Help)
f.shorthand = flag.Short
f.defaultValues = flag.Default
f.envar = flag.Envar
f.placeholder = flag.PlaceHolder
f.required = flag.Required
f.hidden = flag.Hidden

switch {
case flag.Boolean && flag.Negatable:
c.pluginDelegator.boolFlags[flag.Name] = f.Bool()

case flag.Boolean:
c.pluginDelegator.unNegBoolFlags[flag.Name] = f.UnNegatableBool()

default:
c.pluginDelegator.flags[flag.Name] = f.String()
}
}
}

func (c *CmdClause) addCommandsFromModel(model *CmdGroupModel) {
for _, cmd := range model.Commands {
cm := c.Command(cmd.Name, cmd.Help)
cm.pluginDelegator = c.pluginDelegator
cm.aliases = cmd.Aliases
cm.helpLong = cmd.HelpLong
cm.hidden = cmd.Hidden
cm.isDefault = cmd.Default
cm.addArgsFromModel(cmd.ArgGroupModel)
cm.addFlagsFromModel(cmd.FlagGroupModel)
cm.addCommandsFromModel(cmd.CmdGroupModel)
cm.Action(func(pc *ParseContext) error {
var args []string
for k, v := range cm.pluginDelegator.args {
if v != nil {
args = append(args, fmt.Sprintf("%s=%s", k, *v))
}
}

for k, v := range cm.pluginDelegator.flags {
if v != nil {
args = append(args, fmt.Sprintf("--%s=%s", k, *v))
}
}

for k, v := range cm.pluginDelegator.boolFlags {
if *v {
args = append(args, fmt.Sprintf("--%s", k))
} else {
args = append(args, fmt.Sprintf("--no-%s", k))
}
}

for k, v := range cm.pluginDelegator.unNegBoolFlags {
if *v {
args = append(args, fmt.Sprintf("--%s", k))
}
}

return exec.Command(cm.pluginDelegator.command, args...).Run()
})
}
}

func (a *Application) registerPluginModel(command string, model *ApplicationModel) (*CmdClause, error) {
cmd := a.Command(model.Name, model.Help)
cmd.pluginDelegator = &pluginDelegator{
command: command,
flags: map[string]*string{},
args: map[string]*string{},
boolFlags: map[string]*bool{},
unNegBoolFlags: map[string]*bool{},
}

cmd.addArgsFromModel(model.ArgGroupModel)
cmd.addFlagsFromModel(model.FlagGroupModel)
cmd.addCommandsFromModel(model.CmdGroupModel)

return cmd, nil
}

// ExternalPluginJSON extends the application using a plugin and a model describing the application
func (a *Application) ExternalPluginJSON(command string, model json.RawMessage) (*CmdClause, error) {
var m ApplicationModel
err := json.Unmarshal(model, &m)
if err != nil {
return nil, err
}

if m.Name == "" {
return nil, fmt.Errorf("plugin declared no name")
}
if m.Help == "" {
return nil, fmt.Errorf("plugin declared no help")
}

return a.registerPluginModel(command, &m)
}
19 changes: 10 additions & 9 deletions cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,15 +225,16 @@ type CmdClauseValidator func(*CmdClause) error
// and either subcommands or positional arguments.
type CmdClause struct {
cmdMixin
app *Application
name string
aliases []string
help string
helpLong string
isDefault bool
validator CmdClauseValidator
hidden bool
completionAlts []string
app *Application
name string
aliases []string
help string
helpLong string
isDefault bool
validator CmdClauseValidator
hidden bool
completionAlts []string
pluginDelegator *pluginDelegator
}

func newCommand(app *Application, name, help string) *CmdClause {
Expand Down
82 changes: 46 additions & 36 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var (
)

type FlagGroupModel struct {
Flags []*FlagModel
Flags []*FlagModel `json:"flags"`
}

func (f *FlagGroupModel) FlagSummary() string {
Expand Down Expand Up @@ -51,15 +51,20 @@ func (f *FlagGroupModel) FlagSummary() string {
}

type FlagModel struct {
Name string
Help string
Short rune
Default []string
Envar string
PlaceHolder string
Required bool
Hidden bool
Value Value
Name string `json:"name"`
Help string `json:"help"`
Short rune `json:"short"`
Default []string `json:"default"`
Envar string `json:"envar"`
PlaceHolder string `json:"place_holder"`
Required bool `json:"required"`
Hidden bool `json:"hidden"`

// used by plugin model
Boolean bool `json:"boolean"`
Negatable bool `json:"negatable"`

Value Value `json:"-"`
}

func (f *FlagModel) String() string {
Expand Down Expand Up @@ -103,7 +108,7 @@ func (f *FlagModel) HelpWithEnvar() string {
}

type ArgGroupModel struct {
Args []*ArgModel
Args []*ArgModel `json:"args"`
}

func (a *ArgGroupModel) ArgSummary() string {
Expand Down Expand Up @@ -134,14 +139,14 @@ func (a *ArgModel) HelpWithEnvar() string {
}

type ArgModel struct {
Name string
Help string
Default []string
Envar string
PlaceHolder string
Required bool
Hidden bool
Value Value
Name string `json:"name"`
Help string `json:"help"`
Default []string `json:"default"`
Envar string `json:"envar"`
PlaceHolder string `json:"place_holder"`
Required bool `json:"required"`
Hidden bool `json:"hidden"`
Value Value `json:"-"`
}

func (a *ArgModel) String() string {
Expand All @@ -153,7 +158,7 @@ func (a *ArgModel) String() string {
}

type CmdGroupModel struct {
Commands []*CmdModel
Commands []*CmdModel `json:"commands"`
}

func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
Expand All @@ -167,14 +172,14 @@ func (c *CmdGroupModel) FlattenedCommands() (out []*CmdModel) {
}

type CmdModel struct {
Name string
Aliases []string
Help string
HelpLong string
FullCommand string
Depth int
Hidden bool
Default bool
Name string `json:"name"`
Aliases []string `json:"aliases"`
Help string `json:"help"`
HelpLong string `json:"help_long"`
FullCommand string `json:"-"`
Depth int `json:"-"`
Hidden bool `json:"hidden"`
Default bool `json:"default"`

*FlagGroupModel
*ArgGroupModel
Expand All @@ -186,13 +191,13 @@ func (c *CmdModel) String() string {
}

type ApplicationModel struct {
Name string
Help string
Cheat string
Version string
Author string
Cheats map[string]string
CheatTags []string
Name string `json:"name"`
Help string `json:"help"`
Cheat string `json:"cheat"`
Version string `json:"version"`
Author string `json:"author"`
Cheats map[string]string `json:"cheats"`
CheatTags []string `json:"cheat_tags"`

*ArgGroupModel
*CmdGroupModel
Expand Down Expand Up @@ -243,7 +248,7 @@ func (f *flagGroup) Model() *FlagGroupModel {
}

func (f *FlagClause) Model() *FlagModel {
return &FlagModel{
m := &FlagModel{
Name: f.name,
Help: f.help,
Short: f.shorthand,
Expand All @@ -254,6 +259,11 @@ func (f *FlagClause) Model() *FlagModel {
Hidden: f.hidden,
Value: f.value,
}

m.Boolean = m.IsBoolFlag()
m.Negatable = m.IsNegatable()

return m
}

func (c *cmdGroup) Model() *CmdGroupModel {
Expand Down

0 comments on commit 4b9c8b7

Please sign in to comment.