• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

happy-sdk / happy / 15980077914

30 Jun 2025 05:53PM UTC coverage: 46.685% (-5.3%) from 51.943%
15980077914

push

github

mkungla
wip: gohappy cmd

Signed-off-by: Marko Kungla <marko.kungla@gmail.com>

0 of 281 new or added lines in 9 files covered. (0.0%)

2059 existing lines in 30 files now uncovered.

7943 of 17014 relevant lines covered (46.69%)

97527.29 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

37.35
/sdk/cli/command/cmd.go
1
// SPDX-License-Identifier: Apache-2.0
2
//
3
// Copyright © 2024 The Happy Authors
4

5
package command
6

7
import (
8
        "errors"
9
        "fmt"
10
        "log/slog"
11
        "maps"
12
        "os"
13
        "sync"
14

15
        "github.com/happy-sdk/happy/pkg/logging"
16
        "github.com/happy-sdk/happy/pkg/settings"
17
        "github.com/happy-sdk/happy/pkg/vars/varflag"
18
        "github.com/happy-sdk/happy/sdk/action"
19
        "github.com/happy-sdk/happy/sdk/internal"
20
        "github.com/happy-sdk/happy/sdk/session"
21
)
22

23
// Command is building command chain from provided root command.
24
func Compile(root *Command) (*Cmd, *logging.QueueLogger, error) {
9✔
25

9✔
26
        if err := root.verify(); err != nil {
9✔
UNCOV
27
                return nil, root.cnflog, err
×
UNCOV
28
        }
×
29

30
        if !root.tryLock("Compile") {
9✔
UNCOV
31
                return nil, root.cnflog, fmt.Errorf("%w: failed to compile the command %s", Error, root.name)
×
UNCOV
32
        }
×
33
        defer root.mu.Unlock()
9✔
34

9✔
35
        if err := root.flags.Parse(os.Args); err != nil {
9✔
UNCOV
36
                return nil, root.cnflog, err
×
UNCOV
37
        }
×
38

39
        acmd, err := root.getActiveCommand()
9✔
40
        if err != nil {
9✔
UNCOV
41
                return nil, root.cnflog, err
×
UNCOV
42
        }
×
43

44
        cmd := &Cmd{}
9✔
45

9✔
46
        if acmd == root {
18✔
47
                cmd.isRoot = true
9✔
48
                cmd.globalFlags = root.getGlobalFlags().Flags()
9✔
49
        } else {
9✔
50
                cmd.globalFlags = root.getGlobalFlags().Flags()
×
51
                cmd.ownFlags = acmd.flags.Flags()
×
52

×
53
                for _, flag := range cmd.globalFlags {
×
54
                        if err := acmd.flags.Add(flag); err != nil {
×
UNCOV
55
                                return nil, root.cnflog, fmt.Errorf("%w: %s: %s", Error, acmd.name, err.Error())
×
56
                        }
×
57
                }
58
                sharedf, err := acmd.getSharedFlags()
×
59
                if err != nil && !errors.Is(err, ErrHasNoParent) {
×
60
                        return nil, root.cnflog, fmt.Errorf("%w: %s: %s", Error, acmd.name, err.Error())
×
61
                }
×
62
                cmd.sharedFlags = sharedf.Flags()
×
63
                for _, flag := range cmd.sharedFlags {
×
64
                        if err := acmd.flags.Add(flag); err != nil {
×
UNCOV
65
                                return nil, root.cnflog, fmt.Errorf("%w: %s: %s", Error, acmd.name, err.Error())
×
66
                        }
×
67
                }
UNCOV
68
                acmd.mu.Lock()
×
UNCOV
69
                defer acmd.mu.Unlock()
×
70
        }
71

72
        cmd.cnf = acmd.cnf
9✔
73
        cmd.flags = acmd.flags
9✔
74

9✔
75
        cmd.parents = acmd.parents
9✔
76
        cmd.isWrapperCommand = acmd.isWrapperCommand
9✔
77

9✔
78
        cmd.usage = acmd.usage
9✔
79
        cmd.info = acmd.info
9✔
80

9✔
81
        cmd.hideAction = acmd.hideAction
9✔
82
        cmd.beforeAction = acmd.beforeAction
9✔
83
        cmd.doAction = acmd.doAction
9✔
84
        cmd.afterSuccessAction = acmd.afterSuccessAction
9✔
85
        cmd.afterFailureAction = acmd.afterFailureAction
9✔
86
        cmd.afterAlwaysAction = acmd.afterAlwaysAction
9✔
87

9✔
88
        var catdesc = make(map[string]string)
9✔
89
        if acmd.parent != nil {
9✔
90
                cmd.parent = compileParent(acmd.parent)
×
91
                if acmd.parent.catdesc != nil {
×
UNCOV
92
                        maps.Copy(catdesc, acmd.parent.catdesc)
×
UNCOV
93
                }
×
94
        }
95

96
        maps.Copy(catdesc, cmd.catdesc)
9✔
97

9✔
98
        for _, scmd := range acmd.subCommands {
9✔
UNCOV
99
                cmd.subcmds = append(cmd.subcmds, &SubCmdInfo{
×
UNCOV
100
                        Name:        scmd.name,
×
UNCOV
101
                        Description: scmd.cnf.Get("description").String(),
×
UNCOV
102
                        Category:    scmd.cnf.Get("category").String(),
×
UNCOV
103
                        Hidden:      scmd.cnf.Get("hidden").Value().Bool(),
×
UNCOV
104
                        hideAction:  scmd.hideAction,
×
105
                })
×
106
                maps.Copy(catdesc, scmd.catdesc)
×
UNCOV
107
        }
×
108
        cmd.catdesc = catdesc
9✔
109

9✔
110
        return cmd, root.cnflog, nil
9✔
111
}
112

113
func compileParent(cmd *Command) *Cmd {
×
114
        c := &Cmd{
×
115
                name:             cmd.name,
×
116
                cnf:              cmd.cnf,
×
117
                parents:          cmd.parents,
×
118
                isWrapperCommand: cmd.isWrapperCommand,
×
119
                usage:            cmd.usage,
×
120
                info:             cmd.info,
×
121
                catdesc:          cmd.catdesc,
×
122
                flags:            cmd.flags,
×
123
        }
×
124

×
125
        if c.cnf.Get("shared_before_action").Value().Bool() {
×
126
                c.hideAction = cmd.hideAction
×
UNCOV
127
                c.beforeAction = cmd.beforeAction
×
128
        }
×
129

130
        if cmd.parent != nil {
×
131
                c.parent = compileParent(cmd.parent)
×
UNCOV
132
        }
×
UNCOV
133
        return c
×
134
}
135

136
type SubCmdInfo struct {
137
        Name        string
138
        Description string
139
        Category    string
140
        Hidden      bool
141
        hideAction  action.Action
142
}
143

144
type Cmd struct {
145
        mu    sync.Mutex
146
        name  string
147
        cnf   *settings.Profile
148
        flags varflag.Flags
149

150
        isRoot           bool
151
        sharedCalled     bool
152
        parents          []string
153
        isWrapperCommand bool
154
        catdesc          map[string]string
155
        usage            []string
156
        info             []string
157

158
        hideAction         action.Action
159
        beforeAction       action.WithArgs
160
        doAction           action.WithArgs
161
        afterSuccessAction action.Action
162
        afterFailureAction action.WithPrevErr
163
        afterAlwaysAction  action.WithPrevErr
164

165
        parent *Cmd
166

167
        // used in help menu
168
        globalFlags []varflag.Flag
169
        sharedFlags []varflag.Flag
170
        ownFlags    []varflag.Flag
171

172
        subcmds []*SubCmdInfo
173

174
        err error
175
}
176

177
func (c *Cmd) IsRoot() bool {
9✔
178
        return c.isRoot
9✔
179
}
9✔
180

181
func (c *Cmd) Name() string {
×
182
        return c.name
×
183
}
×
184

UNCOV
185
func (c *Cmd) Usage() []string {
×
UNCOV
186
        return c.usage
×
UNCOV
187
}
×
188
func (c *Cmd) Hidden() bool {
9✔
189
        return c.cnf.Get("hidden").Value().Bool()
9✔
190
}
9✔
191

192
func (c *Cmd) CheckHidden(sess *session.Context) bool {
9✔
193
        if c.hideAction != nil {
9✔
UNCOV
194
                var hidden bool
×
UNCOV
195
                if err := c.hideAction(sess); err != nil {
×
UNCOV
196
                        internal.LogInit(sess.Log(), fmt.Sprintf("hide(%s): %s", c.name, err.Error()))
×
197
                        hidden = true
×
198
                        c.err = err
×
199
                }
×
UNCOV
200
                if err := c.cnf.Set("hidden", hidden); err != nil {
×
UNCOV
201
                        sess.Log().Error(err.Error())
×
UNCOV
202
                }
×
203
        }
204

205
        for _, scmd := range c.subcmds {
9✔
206
                if scmd.hideAction != nil {
×
207

×
UNCOV
208
                        if err := scmd.hideAction(sess); err != nil {
×
209
                                internal.LogInit(sess.Log(), fmt.Sprintf("hide-cmd(%s): %s", scmd.Name, err.Error()))
×
210
                                scmd.Hidden = true
×
211
                        }
×
212
                }
213
        }
214
        return c.Hidden()
9✔
215
}
216

217
func (c *Cmd) Info() []string {
×
218
        return c.info
×
219
}
×
220

221
// Flag looks up flag with given name and returns flags.Interface.
222
// If no flag was found empty bool flag will be returned.
223
// Instead of returning error you can check returned flags .IsPresent.
224
func (c *Cmd) Flag(name string) varflag.Flag {
72✔
225

72✔
226
        f, err := c.flags.Get(name)
72✔
227
        if err != nil {
144✔
228
                f, _ = varflag.Bool(name, false, "")
72✔
229
        }
72✔
230
        return f
72✔
231
}
232

UNCOV
233
func (c *Cmd) Flags() []varflag.Flag {
×
UNCOV
234
        return c.ownFlags
×
235
}
×
236

237
func (c *Cmd) GetFlagSet() varflag.Flags {
6✔
238
        return c.flags
6✔
239
}
6✔
240

241
func (c *Cmd) SharedFlags() []varflag.Flag {
×
UNCOV
242
        return c.sharedFlags
×
243
}
×
244

UNCOV
245
func (c *Cmd) GlobalFlags() []varflag.Flag {
×
UNCOV
246
        return c.globalFlags
×
UNCOV
247
}
×
248

UNCOV
249
func (c *Cmd) SubCommands() []*SubCmdInfo {
×
250
        return c.subcmds
×
251
}
×
252

253
func (c *Cmd) Categories() map[string]string {
×
254
        return c.catdesc
×
255
}
×
256

257
func (c *Cmd) IsImmediate() bool {
9✔
258
        return c.cnf.Get("immediate").Value().Bool()
9✔
259
}
9✔
260

261
func (c *Cmd) IsWrapper() bool {
9✔
262
        return c.isWrapperCommand
9✔
263
}
9✔
264

265
func (c *Cmd) ExecBefore(sess *session.Context) (err error) {
9✔
266
        c.mu.Lock()
9✔
267
        defer c.mu.Unlock()
9✔
268

9✔
269
        if c.parent != nil && !c.sharedCalled && !c.cnf.Get("skip_shared_before").Value().Bool() {
9✔
UNCOV
270
                if err := c.parent.callSharedBeforeAction(sess); err != nil {
×
271
                        return err
×
272
                }
×
273
                // dereference parent
UNCOV
274
                c.parent = nil
×
275
        }
276

277
        if c.CheckHidden(sess) {
9✔
278
                if c.cnf.Get("fail_hidden").Value().Bool() {
×
279
                        if c.err != nil {
×
280
                                return c.err
×
UNCOV
281
                        }
×
UNCOV
282
                        return fmt.Errorf("%w: %s", ErrCommandNotAllowed, c.name)
×
UNCOV
283
                } else {
×
UNCOV
284
                        internal.Log(sess.Log(), fmt.Sprintf("%s: %s", ErrCommandNotAllowed, c.name))
×
UNCOV
285
                        return nil
×
UNCOV
286
                }
×
287
        }
288
        if c.beforeAction == nil {
15✔
289
                return nil
6✔
290
        }
6✔
291

292
        args, err := c.getArgs()
3✔
293
        if err != nil {
3✔
294
                return err
×
295
        }
×
296

297
        if err := c.beforeAction(sess, args); err != nil {
3✔
298
                internal.Log(
×
299
                        sess.Log(),
×
300
                        "before action",
×
UNCOV
301
                        slog.String("cmd", c.name),
×
302
                        slog.String("err", err.Error()),
×
303
                )
×
UNCOV
304
                return err
×
UNCOV
305
        }
×
306
        // dereference before action
307
        c.beforeAction = nil
3✔
308
        return nil
3✔
309
}
310

311
func (c *Cmd) ExecDo(sess *session.Context) (err error) {
9✔
312
        c.mu.Lock()
9✔
313
        defer c.mu.Unlock()
9✔
314

9✔
315
        if c.doAction == nil {
9✔
316
                return nil
×
317
        }
×
318

319
        args, err := c.getArgs()
9✔
320
        if err != nil {
9✔
UNCOV
321
                return err
×
UNCOV
322
        }
×
323

324
        if err := c.doAction(sess, args); err != nil {
9✔
UNCOV
325
                internal.Log(
×
UNCOV
326
                        sess.Log(),
×
UNCOV
327
                        "do action",
×
UNCOV
328
                        slog.String("cmd", c.name),
×
UNCOV
329
                        slog.String("err", err.Error()),
×
UNCOV
330
                )
×
UNCOV
331
                return err
×
UNCOV
332
        }
×
333

334
        // dereference do action
335
        c.doAction = nil
9✔
336
        return err
9✔
337
}
338

339
func (c *Cmd) ExecAfterFailure(sess *session.Context, err error) error {
×
340
        c.mu.Lock()
×
UNCOV
341
        defer c.mu.Unlock()
×
UNCOV
342
        if c.afterFailureAction == nil {
×
UNCOV
343
                return nil
×
UNCOV
344
        }
×
345

UNCOV
346
        if err := c.afterFailureAction(sess, err); err != nil {
×
347
                internal.Log(
×
348
                        sess.Log(),
×
349
                        "after failure action",
×
350
                        slog.String("cmd", c.name),
×
351
                        slog.String("err", err.Error()),
×
UNCOV
352
                )
×
353
                return err
×
UNCOV
354
        }
×
355
        // dereference after failure action
356
        c.afterFailureAction = nil
×
357
        return nil
×
358
}
359

360
func (c *Cmd) ExecAfterSuccess(sess *session.Context) error {
9✔
361
        c.mu.Lock()
9✔
362
        defer c.mu.Unlock()
9✔
363
        if c.afterSuccessAction == nil {
15✔
364
                return nil
6✔
365
        }
6✔
366

367
        if err := c.afterSuccessAction(sess); err != nil {
3✔
368
                internal.Log(sess.Log(), "after success action",
×
UNCOV
369
                        slog.String("cmd", c.name),
×
370
                        slog.String("err", err.Error()),
×
UNCOV
371
                )
×
UNCOV
372
                return err
×
UNCOV
373
        }
×
374

375
        // dereference after success action
376
        c.afterSuccessAction = nil
3✔
377
        return nil
3✔
378
}
379

380
func (c *Cmd) ExecAfterAlways(sess *session.Context, err error) error {
9✔
381
        c.mu.Lock()
9✔
382
        defer c.mu.Unlock()
9✔
383

9✔
384
        if c.afterAlwaysAction == nil {
15✔
385
                return nil
6✔
386
        }
6✔
387

388
        if err := c.afterAlwaysAction(sess, err); err != nil {
3✔
389
                internal.Log(sess.Log(), "after always action",
×
UNCOV
390
                        slog.String("cmd", c.name),
×
UNCOV
391
                        slog.String("err", err.Error()),
×
392
                )
×
393
                return err
×
394
        }
×
395

396
        // dereference after always action
397
        c.afterAlwaysAction = nil
3✔
398
        return nil
3✔
399
}
400

401
func (c *Cmd) callSharedBeforeAction(sess *session.Context) error {
×
UNCOV
402
        if c.parent != nil {
×
UNCOV
403
                if err := c.parent.callSharedBeforeAction(sess); err != nil {
×
UNCOV
404
                        return err
×
UNCOV
405
                }
×
406
                // dereference parent
UNCOV
407
                c.parent = nil
×
408
        }
UNCOV
409
        if c.beforeAction == nil {
×
UNCOV
410
                return nil
×
UNCOV
411
        }
×
412

413
        // Is before action shared with sub commands
UNCOV
414
        if c.cnf.Get("shared_before_action").Value().Bool() {
×
UNCOV
415
                // Check is caller parent hidden and should fail.
×
UNCOV
416
                // Even if caller parent is hidden but fail_hidden for this parent
×
UNCOV
417
                // is not set the call it.
×
UNCOV
418
                fmt.Printf("%q.shared_before_action\n", c.name)
×
UNCOV
419
                if c.cnf.Get("fail_hidden").Value().Bool() && c.CheckHidden(sess) {
×
UNCOV
420
                        if c.err != nil {
×
UNCOV
421
                                return c.err
×
UNCOV
422
                        }
×
UNCOV
423
                        return fmt.Errorf("%w: %s", ErrCommandNotAllowed, c.name)
×
424
                }
UNCOV
425
                c.sharedCalled = true
×
UNCOV
426
                if err := c.beforeAction(sess, action.NewArgs(c.flags)); err != nil {
×
UNCOV
427
                        internal.Log(sess.Log(), "shared before action",
×
UNCOV
428
                                slog.String("cmd", c.name),
×
UNCOV
429
                                slog.String("err", err.Error()))
×
UNCOV
430
                        return err
×
UNCOV
431
                }
×
432
                // dereference before action
UNCOV
433
                c.beforeAction = nil
×
434
        }
UNCOV
435
        return nil
×
436
}
437

438
func (c *Cmd) SkipSharedBeforeAction() bool {
6✔
439
        return c.cnf.Get("skip_shared_before").Value().Bool()
6✔
440
}
6✔
441

UNCOV
442
func (c *Cmd) HasBefore() bool {
×
UNCOV
443
        return c.beforeAction != nil
×
UNCOV
444
}
×
445

446
func (c *Cmd) getArgs() (action.Args, error) {
12✔
447
        args := action.NewArgs(c.flags)
12✔
448
        argnmin := c.cnf.Get("min_args").Value().Uint()
12✔
449
        argnmax := c.cnf.Get("max_args").Value().Uint()
12✔
450
        name := c.name
12✔
451

12✔
452
        if argnmin == 0 && argnmax == 0 && args.Argn() > 0 {
12✔
UNCOV
453
                return args, fmt.Errorf("%w: %s does not accept arguments", Error, name)
×
UNCOV
454
        }
×
455

456
        if args.Argn() < argnmin {
12✔
UNCOV
457
                if err := c.cnf.Get("min_args_err").Value(); !err.Empty() {
×
UNCOV
458
                        return args, errors.New(err.String())
×
UNCOV
459
                }
×
UNCOV
460
                return args, fmt.Errorf("%w: %s: requires min %d arguments, %d provided", Error, name, argnmin, args.Argn())
×
461
        }
462
        if args.Argn() > argnmax {
12✔
UNCOV
463
                if err := c.cnf.Get("max_args_err").Value(); !err.Empty() {
×
UNCOV
464
                        return args, errors.New(err.String())
×
UNCOV
465
                }
×
UNCOV
466
                return args, fmt.Errorf("%w: %s: accepts max %d arguments, %d provided, extra %v", Error, name, argnmax, args.Argn(), args.Args()[argnmax:args.Argn()])
×
467
        }
468

469
        return args, nil
12✔
470
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc