-
Notifications
You must be signed in to change notification settings - Fork 70
/
cmd.go
148 lines (135 loc) · 4.47 KB
/
cmd.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// Copyright (c) 2023, Cogent Core. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cli
import (
"fmt"
"strings"
"cogentcore.org/core/base/strcase"
"cogentcore.org/core/types"
)
// Cmd represents a runnable command with configuration options.
// The type constraint is the type of the configuration
// information passed to the command.
type Cmd[T any] struct {
// Func is the actual function that runs the command.
// It takes configuration information and returns an error.
Func func(T) error
// Name is the name of the command.
Name string
// Doc is the documentation for the command.
Doc string
// Root is whether the command is the root command
// (what is called when no subcommands are passed)
Root bool
// Icon is the icon of the command in the tool bar
// when running in the GUI via cliview
Icon string
// SepBefore is whether to add a separator before the
// command in the tool bar when running in the GUI via cliview
SepBefore bool
// SepAfter is whether to add a separator after the
// command in the tool bar when running in the GUI via cliview
SepAfter bool
}
// CmdOrFunc is a generic type constraint that represents either
// a [*Cmd] with the given config type or a command function that
// takes the given config type and returns an error.
type CmdOrFunc[T any] interface {
*Cmd[T] | func(T) error
}
// CmdFromFunc returns a new [Cmd] object from the given function
// and any information specified on it using comment directives,
// which requires the use of [types].
func CmdFromFunc[T any](fun func(T) error) (*Cmd[T], error) {
cmd := &Cmd[T]{
Func: fun,
}
fn := types.FuncName(fun)
// we need to get rid of package name and then convert to kebab
strs := strings.Split(fn, ".")
cfn := strs[len(strs)-1] // camel function name
cmd.Name = strcase.ToKebab(cfn)
if f := types.FuncByName(fn); f != nil {
cmd.Doc = f.Doc
for _, dir := range f.Directives {
if dir.Tool != "cli" {
continue
}
if dir.Directive != "cmd" {
return cmd, fmt.Errorf("unrecognized comment directive %q (from comment %q)", dir.Directive, dir.String())
}
_, err := SetFromArgs(cmd, dir.Args, ErrNotFound)
if err != nil {
return cmd, fmt.Errorf("error setting command from directive arguments (from comment %q): %w", dir.String(), err)
}
}
// we format the doc after the directives so that we have the up-to-date documentation and name
cmd.Doc = types.FormatDoc(cmd.Doc, cfn, strcase.ToSentence(cmd.Name))
}
return cmd, nil
}
// CmdFromCmdOrFunc returns a new [Cmd] object from the given
// [CmdOrFunc] object, using [CmdFromFunc] if it is a function.
func CmdFromCmdOrFunc[T any, C CmdOrFunc[T]](cmd C) (*Cmd[T], error) {
switch c := any(cmd).(type) {
case *Cmd[T]:
return c, nil
case func(T) error:
return CmdFromFunc(c)
default:
panic(fmt.Errorf("internal/programmer error: cli.CmdFromCmdOrFunc: impossible type %T for command %v", cmd, cmd))
}
}
// CmdsFromFuncs is a helper function that returns a slice
// of command objects from the given slice of command functions,
// using [CmdFromFunc].
func CmdsFromFuncs[T any](funcs []func(T) error) ([]*Cmd[T], error) {
res := make([]*Cmd[T], len(funcs))
for i, fun := range funcs {
cmd, err := CmdFromFunc(fun)
if err != nil {
return nil, err
}
res[i] = cmd
}
return res, nil
}
// CmdsFromCmdOrFuncs is a helper function that returns a slice
// of command objects from the given slice of [CmdOrFunc] objects,
// using [CmdFromCmdOrFunc].
func CmdsFromCmdOrFuncs[T any, C CmdOrFunc[T]](cmds []C) ([]*Cmd[T], error) {
res := make([]*Cmd[T], len(cmds))
for i, cmd := range cmds {
cmd, err := CmdFromCmdOrFunc[T, C](cmd)
if err != nil {
return nil, err
}
res[i] = cmd
}
return res, nil
}
// AddCmd adds the given command to the given set of commands
// if there is not already a command with the same name in the
// set of commands. Also, if [Cmd.Root] is set to true on the
// passed command, and there are no other root commands in the
// given set of commands, the passed command will be made the
// root command; otherwise, it will be made not the root command.
func AddCmd[T any](cmds []*Cmd[T], cmd *Cmd[T]) []*Cmd[T] {
hasCmd := false
hasRoot := false
for _, c := range cmds {
if c.Name == cmd.Name {
hasCmd = true
}
if c.Root {
hasRoot = true
}
}
if hasCmd {
return cmds
}
cmd.Root = cmd.Root && !hasRoot // we must both want root and be able to take root
cmds = append(cmds, cmd)
return cmds
}