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

happy-sdk / happy / 15987602525

01 Jul 2025 01:35AM UTC coverage: 46.009% (-0.7%) from 46.673%
15987602525

push

github

mkungla
feat: add prj info, test and lint cmds

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

0 of 130 new or added lines in 3 files covered. (0.0%)

433 existing lines in 8 files now uncovered.

7954 of 17288 relevant lines covered (46.01%)

96150.19 hits per line

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

36.71
/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✔
27
                return nil, root.cnflog, err
×
28
        }
×
29

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

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

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

44
        cmd := &Cmd{
9✔
45
                name:    acmd.name,
9✔
46
                sources: acmd.sources,
9✔
47
        }
9✔
48

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

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

75
        cmd.cnf = acmd.cnf
9✔
76
        cmd.flags = acmd.flags
9✔
77

9✔
78
        cmd.parents = acmd.parents
9✔
79
        cmd.isWrapperCommand = acmd.isWrapperCommand
9✔
80

9✔
81
        cmd.usage = acmd.usage
9✔
82
        cmd.info = acmd.info
9✔
83

9✔
84
        cmd.disableAction = acmd.disableAction
9✔
85
        cmd.beforeAction = acmd.beforeAction
9✔
86
        cmd.doAction = acmd.doAction
9✔
87
        cmd.afterSuccessAction = acmd.afterSuccessAction
9✔
88
        cmd.afterFailureAction = acmd.afterFailureAction
9✔
89
        cmd.afterAlwaysAction = acmd.afterAlwaysAction
9✔
90

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

99
        maps.Copy(catdesc, cmd.catdesc)
9✔
100

9✔
101
        for _, scmd := range acmd.subCommands {
9✔
102
                cmd.subcmds = append(cmd.subcmds, &SubCmdInfo{
×
103
                        Name:          scmd.name,
×
104
                        Description:   scmd.cnf.Get("description").String(),
×
105
                        Category:      scmd.cnf.Get("category").String(),
×
106
                        Disabled:      scmd.cnf.Get("disabled").Value().Bool(),
×
107
                        disableAction: scmd.disableAction,
×
UNCOV
108
                })
×
UNCOV
109
                maps.Copy(catdesc, scmd.catdesc)
×
UNCOV
110
        }
×
111
        cmd.catdesc = catdesc
9✔
112

9✔
113
        return cmd, root.cnflog, nil
9✔
114
}
115

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

×
UNCOV
129
        if c.cnf.Get("shared_before_action").Value().Bool() {
×
130
                c.disableAction = cmd.disableAction
×
131
                c.beforeAction = cmd.beforeAction
×
132
        }
×
133

UNCOV
134
        if cmd.parent != nil {
×
UNCOV
135
                c.parent = compileParent(cmd.parent)
×
UNCOV
136
        }
×
UNCOV
137
        return c
×
138
}
139

140
type SubCmdInfo struct {
141
        Name          string
142
        Description   string
143
        Category      string
144
        Disabled      bool
145
        disableAction action.Action
146
}
147

148
type Cmd struct {
149
        mu    sync.Mutex
150
        name  string
151
        cnf   *settings.Profile
152
        flags varflag.Flags
153

154
        isRoot           bool
155
        sharedCalled     bool
156
        parents          []string
157
        isWrapperCommand bool
158
        catdesc          map[string]string
159
        usage            []string
160
        info             []string
161

162
        disableAction      action.Action
163
        beforeAction       action.WithArgs
164
        doAction           action.WithArgs
165
        afterSuccessAction action.Action
166
        afterFailureAction action.WithPrevErr
167
        afterAlwaysAction  action.WithPrevErr
168

169
        parent *Cmd
170

171
        // used in help menu
172
        globalFlags []varflag.Flag
173
        sharedFlags []varflag.Flag
174
        ownFlags    []varflag.Flag
175

176
        subcmds []*SubCmdInfo
177

178
        err     error
179
        sources originalSource
180
}
181

182
func (c *Cmd) IsRoot() bool {
9✔
183
        return c.isRoot
9✔
184
}
9✔
185

186
func (c *Cmd) Name() string {
×
187
        return c.name
×
UNCOV
188
}
×
189

UNCOV
190
func (c *Cmd) Parent() *Cmd {
×
UNCOV
191
        return c.parent
×
UNCOV
192
}
×
193

194
func (c *Cmd) Usage() []string {
×
195
        return c.usage
×
196
}
×
197

198
func (c *Cmd) Disabled() bool {
9✔
199
        return c.cnf.Get("disabled").Value().Bool()
9✔
200
}
9✔
201

202
func (c *Cmd) CheckDisabled(sess *session.Context) bool {
9✔
203
        if c.cnf.Get("disabled").Value().Bool() {
9✔
UNCOV
204
                return true
×
UNCOV
205
        }
×
206
        if c.disableAction != nil {
9✔
207
                var disabled bool
×
208
                if err := c.disableAction(sess); err != nil {
×
209
                        internal.LogInit(sess.Log(), fmt.Sprintf("hide(%s): %s", c.name, err.Error()))
×
210
                        disabled = true
×
211
                        c.err = err
×
UNCOV
212
                }
×
UNCOV
213
                if err := c.cnf.Set("disabled", disabled); err != nil {
×
UNCOV
214
                        sess.Log().Error(err.Error())
×
UNCOV
215
                }
×
216
        }
217

218
        for _, scmd := range c.subcmds {
9✔
219
                if scmd.disableAction != nil {
×
UNCOV
220

×
UNCOV
221
                        if err := scmd.disableAction(sess); err != nil {
×
UNCOV
222
                                internal.LogInit(sess.Log(), fmt.Sprintf("disable-cmd(%s): %s", scmd.Name, err.Error()))
×
UNCOV
223
                                scmd.Disabled = true
×
UNCOV
224
                        }
×
225
                }
226
        }
227
        return c.Disabled()
9✔
228
}
229

UNCOV
230
func (c *Cmd) Info() []string {
×
UNCOV
231
        return c.info
×
UNCOV
232
}
×
233

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

72✔
239
        f, err := c.flags.Get(name)
72✔
240
        if err != nil {
144✔
241
                f, _ = varflag.Bool(name, false, "")
72✔
242
        }
72✔
243
        return f
72✔
244
}
245

246
func (c *Cmd) Flags() []varflag.Flag {
×
247
        return c.ownFlags
×
UNCOV
248
}
×
249

250
func (c *Cmd) GetFlagSet() varflag.Flags {
6✔
251
        return c.flags
6✔
252
}
6✔
253

254
func (c *Cmd) SharedFlags() []varflag.Flag {
×
255
        return c.sharedFlags
×
UNCOV
256
}
×
257

UNCOV
258
func (c *Cmd) GlobalFlags() []varflag.Flag {
×
UNCOV
259
        return c.globalFlags
×
UNCOV
260
}
×
261

UNCOV
262
func (c *Cmd) SubCommands() []*SubCmdInfo {
×
UNCOV
263
        return c.subcmds
×
UNCOV
264
}
×
265

UNCOV
266
func (c *Cmd) Categories() map[string]string {
×
UNCOV
267
        return c.catdesc
×
UNCOV
268
}
×
269

270
func (c *Cmd) IsImmediate() bool {
9✔
271
        return c.cnf.Get("immediate").Value().Bool()
9✔
272
}
9✔
273

274
func (c *Cmd) IsWrapper() bool {
9✔
275
        return c.isWrapperCommand
9✔
276
}
9✔
277

278
func (c *Cmd) ExecBefore(sess *session.Context) (err error) {
9✔
279
        c.mu.Lock()
9✔
280
        defer c.mu.Unlock()
9✔
281

9✔
282
        if c.parent != nil {
9✔
283
                disabledParent := c.parent.CheckDisabled(sess)
×
284
                if disabledParent {
×
285
                        _ = c.cnf.Set("disabled", true)
×
286
                }
×
UNCOV
287
                if !disabledParent && !c.sharedCalled && !c.cnf.Get("skip_shared_before").Value().Bool() {
×
UNCOV
288
                        if err := c.parent.callSharedBeforeAction(sess); err != nil {
×
UNCOV
289
                                return err
×
UNCOV
290
                        }
×
291
                }
292
        }
293

294
        if c.CheckDisabled(sess) {
9✔
295
                if c.cnf.Get("fail_disabled").Value().Bool() {
×
UNCOV
296
                        if c.err != nil {
×
UNCOV
297
                                return c.err
×
298
                        }
×
299
                        return fmt.Errorf("%w: %s", ErrCommandNotAllowed, c.name)
×
300
                } else {
×
301
                        internal.Log(sess.Log(), fmt.Sprintf("%s: %s", ErrCommandNotAllowed, c.name))
×
302
                        return nil
×
303
                }
×
304
        }
305
        // dereference parent
306
        c.parent = nil
9✔
307

9✔
308
        if c.beforeAction == nil {
15✔
309
                return nil
6✔
310
        }
6✔
311

312
        args, err := c.getArgs()
3✔
313
        if err != nil {
3✔
UNCOV
314
                return err
×
UNCOV
315
        }
×
316

317
        if err := c.beforeAction(sess, args); err != nil {
3✔
UNCOV
318
                internal.Log(
×
UNCOV
319
                        sess.Log(),
×
UNCOV
320
                        "before action",
×
321
                        slog.String("cmd", c.name),
×
322
                        slog.String("err", err.Error()),
×
UNCOV
323
                )
×
UNCOV
324
                return err
×
325
        }
×
326
        // dereference before action
327
        c.beforeAction = nil
3✔
328
        return nil
3✔
329
}
330

331
func (c *Cmd) ExecDo(sess *session.Context) (string, error) {
9✔
332
        c.mu.Lock()
9✔
333
        defer c.mu.Unlock()
9✔
334

9✔
335
        if c.doAction == nil {
9✔
UNCOV
336
                return "", nil
×
UNCOV
337
        }
×
338

339
        args, err := c.getArgs()
9✔
340
        if err != nil {
9✔
341
                return "", err
×
342
        }
×
343

344
        if err := c.doAction(sess, args); err != nil {
9✔
UNCOV
345
                internal.Log(
×
346
                        sess.Log(),
×
347
                        "do action",
×
348
                        slog.String("cmd", c.name),
×
349
                        slog.String("err", err.Error()),
×
350
                )
×
351

×
352
                return c.sources.Do, err
×
353
        }
×
354

355
        // dereference do action
356
        c.doAction = nil
9✔
357
        return "", nil
9✔
358
}
359

UNCOV
360
func (c *Cmd) ExecAfterFailure(sess *session.Context, err error) error {
×
UNCOV
361
        c.mu.Lock()
×
UNCOV
362
        defer c.mu.Unlock()
×
UNCOV
363
        if c.afterFailureAction == nil {
×
UNCOV
364
                return nil
×
UNCOV
365
        }
×
366

UNCOV
367
        if err := c.afterFailureAction(sess, err); err != nil {
×
368
                internal.Log(
×
369
                        sess.Log(),
×
370
                        "after failure action",
×
371
                        slog.String("cmd", c.name),
×
372
                        slog.String("err", err.Error()),
×
373
                )
×
UNCOV
374
                return err
×
UNCOV
375
        }
×
376
        // dereference after failure action
UNCOV
377
        c.afterFailureAction = nil
×
UNCOV
378
        return nil
×
379
}
380

381
func (c *Cmd) ExecAfterSuccess(sess *session.Context) error {
9✔
382
        c.mu.Lock()
9✔
383
        defer c.mu.Unlock()
9✔
384
        if c.afterSuccessAction == nil {
15✔
385
                return nil
6✔
386
        }
6✔
387

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

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

401
func (c *Cmd) ExecAfterAlways(sess *session.Context, err error) error {
9✔
402
        c.mu.Lock()
9✔
403
        defer c.mu.Unlock()
9✔
404

9✔
405
        if c.afterAlwaysAction == nil {
15✔
406
                return nil
6✔
407
        }
6✔
408

409
        if err := c.afterAlwaysAction(sess, err); err != nil {
3✔
410
                internal.Log(sess.Log(), "after always action",
×
411
                        slog.String("cmd", c.name),
×
UNCOV
412
                        slog.String("err", err.Error()),
×
UNCOV
413
                )
×
414
                return err
×
415
        }
×
416

417
        // dereference after always action
418
        c.afterAlwaysAction = nil
3✔
419
        return nil
3✔
420
}
421

422
func (c *Cmd) callSharedBeforeAction(sess *session.Context) error {
×
423
        if c.parent != nil {
×
UNCOV
424
                if err := c.parent.callSharedBeforeAction(sess); err != nil {
×
425
                        return err
×
426
                }
×
427
                // dereference parent
428
                c.parent = nil
×
429
        }
430
        if c.beforeAction == nil {
×
431
                return nil
×
UNCOV
432
        }
×
433

434
        // Is before action shared with sub commands
435
        if c.cnf.Get("shared_before_action").Value().Bool() {
×
UNCOV
436
                // Check is caller parent disabled and should fail.
×
UNCOV
437
                // Even if caller parent is disabled but fail_disabled for this parent
×
UNCOV
438
                // is not set the call it.
×
UNCOV
439
                if c.cnf.Get("fail_disabled").Value().Bool() && c.CheckDisabled(sess) {
×
UNCOV
440
                        if c.err != nil {
×
UNCOV
441
                                return c.err
×
442
                        }
×
443
                        return fmt.Errorf("%w: %s", ErrCommandNotAllowed, c.name)
×
444
                }
UNCOV
445
                c.sharedCalled = true
×
UNCOV
446
                if err := c.beforeAction(sess, action.NewArgs(c.flags)); err != nil {
×
UNCOV
447
                        internal.Log(sess.Log(), "shared before action",
×
UNCOV
448
                                slog.String("cmd", c.name),
×
UNCOV
449
                                slog.String("err", err.Error()))
×
UNCOV
450
                        return err
×
UNCOV
451
                }
×
452
                // dereference before action
453
                c.beforeAction = nil
×
454
        }
UNCOV
455
        return nil
×
456
}
457

458
func (c *Cmd) SkipSharedBeforeAction() bool {
6✔
459
        return c.cnf.Get("skip_shared_before").Value().Bool()
6✔
460
}
6✔
461

UNCOV
462
func (c *Cmd) HasBefore() bool {
×
463
        return c.beforeAction != nil
×
464
}
×
465

466
func (c *Cmd) Err() error {
×
UNCOV
467
        return c.err
×
UNCOV
468
}
×
469

UNCOV
470
func (c *Cmd) Config() *settings.Profile {
×
UNCOV
471
        return c.cnf
×
UNCOV
472
}
×
473

474
func (c *Cmd) getArgs() (action.Args, error) {
12✔
475
        args := action.NewArgs(c.flags)
12✔
476
        argnmin := c.cnf.Get("min_args").Value().Uint()
12✔
477
        argnmax := c.cnf.Get("max_args").Value().Uint()
12✔
478
        name := c.name
12✔
479

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

484
        if args.Argn() < argnmin {
12✔
UNCOV
485
                if err := c.cnf.Get("min_args_err").Value(); !err.Empty() {
×
UNCOV
486
                        return args, errors.New(err.String())
×
UNCOV
487
                }
×
UNCOV
488
                return args, fmt.Errorf("%w: %s: requires min %d arguments, %d provided", Error, name, argnmin, args.Argn())
×
489
        }
490
        if args.Argn() > argnmax {
12✔
UNCOV
491
                if err := c.cnf.Get("max_args_err").Value(); !err.Empty() {
×
UNCOV
492
                        return args, errors.New(err.String())
×
UNCOV
493
                }
×
UNCOV
494
                return args, fmt.Errorf("%w: %s: accepts max %d arguments, %d provided, extra %v", Error, name, argnmax, args.Argn(), args.Args()[argnmax:args.Argn()])
×
495
        }
496

497
        return args, nil
12✔
498
}
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