-
Notifications
You must be signed in to change notification settings - Fork 4
/
cobra-parser.go
209 lines (179 loc) · 6.61 KB
/
cobra-parser.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package cli
import (
"github.com/pkg/errors"
"strings"
"github.com/go-go-golems/glazed/pkg/cmds"
"github.com/go-go-golems/glazed/pkg/cmds/layers"
cmd_middlewares "github.com/go-go-golems/glazed/pkg/cmds/middlewares"
"github.com/go-go-golems/glazed/pkg/cmds/parameters"
"github.com/spf13/cobra"
)
// CobraMiddlewaresFunc is a function that returns a list of middlewares for a cobra command.
// It can be used to overload the default middlewares for cobra commands.
// It is mostly used to add a "load from json" layer set in the GlazedCommandSettings.
type CobraMiddlewaresFunc func(
commandSettings *GlazedCommandSettings,
cmd *cobra.Command,
args []string,
) ([]cmd_middlewares.Middleware, error)
// CobraCommandDefaultMiddlewares is the default implementation for creating
// the middlewares used in a Cobra command. It handles parsing parameters
// from Cobra flags, command line arguments, environment variables, and
// default values. The middlewares gather all these parameters into a
// ParsedParameters object.
//
// If the commandSettings specify parameters to be loaded from a file, this gets added as a middleware.
func CobraCommandDefaultMiddlewares(
commandSettings *GlazedCommandSettings,
cmd *cobra.Command,
args []string,
) ([]cmd_middlewares.Middleware, error) {
middlewares_ := []cmd_middlewares.Middleware{
cmd_middlewares.ParseFromCobraCommand(cmd,
parameters.WithParseStepSource("cobra"),
),
cmd_middlewares.GatherArguments(args,
parameters.WithParseStepSource("arguments"),
),
}
if commandSettings.LoadParametersFromFile != "" {
middlewares_ = append(middlewares_,
cmd_middlewares.LoadParametersFromFile(commandSettings.LoadParametersFromFile))
}
middlewares_ = append(middlewares_,
cmd_middlewares.SetFromDefaults(parameters.WithParseStepSource(parameters.SourceDefaults)),
)
return middlewares_, nil
}
// CobraParser takes a CommandDescription, and hooks it up to a cobra command.
// It can then be used to parse the cobra flags and arguments back into a
// set of ParsedLayer and a map[string]interface{} for the lose stuff.
//
// That command however doesn't have a Run* method, which is left to the caller to implement.
//
// This returns a CobraParser that can be used to parse the registered Layers
// from the description.
type CobraParser struct {
Layers *layers.ParameterLayers
// middlewaresFunc is called after the command has been executed, once the
// GlazedCommandSettings struct has been filled. At this point, cobra has done the parsing
// of CLI flags and arguments, but these haven't yet been parsed into ParsedLayers
// by the glazed framework.
//
// This hooks allows the implementor to specify additional ways of loading parameters
// (for example, sqleton loads the dbt and sql connection parameters from env and viper as well).
middlewaresFunc CobraMiddlewaresFunc
// List of layers to be shown in short help, empty: always show all
shortHelpLayers []string
}
type CobraParserOption func(*CobraParser) error
func WithCobraMiddlewaresFunc(middlewaresFunc CobraMiddlewaresFunc) CobraParserOption {
return func(c *CobraParser) error {
c.middlewaresFunc = middlewaresFunc
return nil
}
}
func WithCobraShortHelpLayers(layers ...string) CobraParserOption {
return func(c *CobraParser) error {
c.shortHelpLayers = append(c.shortHelpLayers, layers...)
return nil
}
}
func NewCobraCommandFromCommandDescription(
description *cmds.CommandDescription,
) *cobra.Command {
cmd := &cobra.Command{
Use: description.Name,
Short: description.Short,
Long: description.Long,
}
return cmd
}
// NewCobraParserFromLayers creates a new CobraParser instance from a
// CommandDescription, initializes the underlying cobra.Command, and adds all the
// parameters specified in the Layers CommandDescription to the cobra command.
func NewCobraParserFromLayers(
layers *layers.ParameterLayers,
options ...CobraParserOption,
) (*CobraParser, error) {
ret := &CobraParser{
Layers: layers,
middlewaresFunc: CobraCommandDefaultMiddlewares,
}
for _, option := range options {
err := option(ret)
if err != nil {
return nil, err
}
}
// NOTE(manuel, 2023-12-30) I actually think we always want to have the glazed-command layer
glazedCommandLayer, err := NewGlazedCommandLayer()
if err != nil {
return nil, err
}
ret.Layers.Set(glazedCommandLayer.GetSlug(), glazedCommandLayer)
return ret, nil
}
func (c *CobraParser) AddToCobraCommand(cmd *cobra.Command) error {
// NOTE(manuel, 2024-01-03) Maybe add some middleware functionality to whitelist/blacklist the Layers/parameters that get added to the CLI
// If we want to remove some parameters from the CLI args (for example some output settings or so)
err := c.Layers.ForEachE(func(_ string, layer layers.ParameterLayer) error {
// check that layer is a CobraParameterLayer
// if not, return an error
cobraLayer, ok := layer.(layers.CobraParameterLayer)
if !ok {
return errors.Errorf("layer %s is not a CobraParameterLayer", layer.GetName())
}
err := cobraLayer.AddLayerToCobraCommand(cmd)
if err != nil {
return err
}
return nil
})
if err != nil {
return err
}
if len(c.shortHelpLayers) > 0 {
shortHelperLayer := strings.Join(c.shortHelpLayers, ",")
cmd.Annotations["shortHelpLayers"] = shortHelperLayer
}
return nil
}
func (c *CobraParser) Parse(
cmd *cobra.Command,
args []string,
) (*layers.ParsedLayers, error) {
parsedLayers := layers.NewParsedLayers()
// We parse the glazed command settings first, since they will influence the following parsing
// steps.
glazedCommandLayer, err := NewGlazedCommandLayer()
if err != nil {
return nil, err
}
glazedCommandLayers := layers.NewParameterLayers(layers.WithLayers(glazedCommandLayer))
// Parse the glazed command settings from the cobra command and config file
middlewares_ := []cmd_middlewares.Middleware{
cmd_middlewares.ParseFromCobraCommand(cmd, parameters.WithParseStepSource("cobra")),
cmd_middlewares.GatherFlagsFromViper(parameters.WithParseStepSource("viper")),
}
err = cmd_middlewares.ExecuteMiddlewares(glazedCommandLayers, parsedLayers, middlewares_...)
if err != nil {
return nil, err
}
commandSettings := &GlazedCommandSettings{}
err = parsedLayers.InitializeStruct(GlazedCommandSlug, commandSettings)
if err != nil {
return nil, err
}
// Create the middlewares by invoking the passed in middlewares constructor.
// This is where applications can specify their own middlewares.
middlewares_, err = c.middlewaresFunc(commandSettings, cmd, args)
if err != nil {
return nil, err
}
err = cmd_middlewares.ExecuteMiddlewares(c.Layers, parsedLayers, middlewares_...)
if err != nil {
return nil, err
}
return parsedLayers, nil
}