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

happy-sdk / happy / 13612669089

02 Mar 2025 06:05AM UTC coverage: 50.097% (-0.07%) from 50.162%
13612669089

push

github

mkungla
feat: add branding color palette

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

0 of 7 new or added lines in 2 files covered. (0.0%)

14 existing lines in 1 file now uncovered.

8040 of 16049 relevant lines covered (50.1%)

104006.17 hits per line

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

0.0
/sdk/cli/cli.go
1
// SPDX-License-Identifier: Apache-2.0
2
//
3
// Copyright © 2022 The Happy Authors
4

5
// Package cli provides utilities for happy command line interfaces.
6
package cli
7

8
import (
9
        "bufio"
10
        "bytes"
11
        "errors"
12
        "fmt"
13
        "log/slog"
14
        "os"
15
        "os/exec"
16
        "strings"
17

18
        "github.com/happy-sdk/happy/pkg/settings"
19
        "github.com/happy-sdk/happy/pkg/vars/varflag"
20
        "github.com/happy-sdk/happy/sdk/app/session"
21
        "github.com/happy-sdk/happy/sdk/logging"
22
)
23

24
var (
25
        ErrCommandInvalid = errors.New("invalid command definition")
26
        ErrCommandArgs    = errors.New("command arguments error")
27
        ErrCommandFlags   = errors.New("command flags error")
28
        ErrPanic          = errors.New("there was panic, check logs for more info")
29
)
30

31
// Common CLI flags which are automatically attached to the CLI ubnless disabled ins settings.
32
// You still can manually add them to your CLI if you want to.
33
var (
34
        FlagVersion     = varflag.BoolFunc("version", false, "print application version")
35
        FlagHelp        = varflag.BoolFunc("help", false, "display help or help for the command. [...command --help]", "h")
36
        FlagX           = varflag.BoolFunc("x", false, "the -x flag prints all the cli commands as they are executed.")
37
        FlagSystemDebug = varflag.BoolFunc("system-debug", false, "enable system debug log level (very verbose)")
38
        FlagDebug       = varflag.BoolFunc("debug", false, "enable debug log level")
39
        FlagVerbose     = varflag.BoolFunc("verbose", false, "enable verbose log level", "v")
40
)
41

42
type Settings struct {
43
        Name               settings.String `default:"" desc:"Name of executable file"`
44
        MainMinArgs        settings.Uint   `default:"0" desc:"Minimum number of arguments for a application main"`
45
        MainMaxArgs        settings.Uint   `default:"0" desc:"Maximum number of arguments for a application main"`
46
        WithoutConfigCmd   settings.Bool   `default:"false" desc:"Do not include the config command in the CLI"`
47
        WithoutGlobalFlags settings.Bool   `default:"false" desc:"Do not include the global flags automatically in the CLI"`
48
}
49

50
func (s Settings) Blueprint() (*settings.Blueprint, error) {
×
51
        b, err := settings.New(s)
×
52
        if err != nil {
×
53
                return nil, err
×
54
        }
×
55

56
        return b, nil
×
57
}
58

59
// AskForConfirmation gets (y/Y)es or (n/N)o from cli input.
60
func AskForConfirmation(q string) bool {
×
61
        var response string
×
62
        fmt.Fprintln(os.Stdout, q, "(y/Y)es or (n/N)o?")
×
63

×
64
        if _, err := fmt.Scanln(&response); err != nil {
×
65
                return false
×
66
        }
×
67

68
        switch strings.ToLower(response) {
×
69
        case "y", "Y", "yes":
×
70
                return true
×
71
        case "n", "N", "no":
×
72
                return false
×
73
        default:
×
74
                fmt.Fprintln(
×
75
                        os.Stdout,
×
76
                        "I'm sorry but I didn't get what you meant, please type (y/Y)es or (n/N)o and then press enter:")
×
77

×
78
                return AskForConfirmation(q)
×
79
        }
80
}
81

82
func AskForInput(q string) string {
×
83
        fmt.Fprintln(os.Stdout, q)
×
84
        reader := bufio.NewReader(os.Stdin)
×
85
        response, err := reader.ReadString('\n')
×
86
        if err != nil {
×
87
                return ""
×
88
        }
×
89
        return strings.TrimSpace(response)
×
90
}
91

92
// Exec wraps ExecRaw to return output as string.
93
func Exec(sess *session.Context, cmd *exec.Cmd) (string, error) {
×
94
        out, err := ExecRaw(sess, cmd)
×
95
        return string(bytes.TrimSpace(out)), err
×
96
}
×
97

98
// ExecRaw wraps and executes provided command and returns its
99
// CombinedOutput. It ensures that -x flag is taken into account and
100
// Command is Session Context aware.
101
func ExecRaw(sess *session.Context, cmd *exec.Cmd) ([]byte, error) {
×
102
        return execCommandRaw(sess, cmd)
×
103
}
×
104

105
// Run wraps and executes provided command and writes
106
// its Stdout and Stderr. It ensures that -x flag is taken
107
// into account and Command is Session Context aware.
108
func Run(sess *session.Context, cmd *exec.Cmd) error {
×
109
        return run(sess, cmd)
×
110
}
×
111

112
func run(sess *session.Context, cmd *exec.Cmd) error {
×
113
        sess.Log().Debug("exec: ", slog.String("cmd", cmd.String()))
×
114

×
115
        if sess.Get("app.main.exec.x").Bool() {
×
116
                sess.Log().LogDepth(4, logging.LevelAlways, cmd.String())
×
117
        }
×
118

119
        scmd := exec.CommandContext(sess, cmd.Path, cmd.Args[1:]...) //nolint: gosec
×
120
        scmd.Env = cmd.Env
×
121
        scmd.Dir = cmd.Dir
×
122
        scmd.Stdin = cmd.Stdin
×
123
        scmd.Stdout = cmd.Stdout
×
124
        scmd.Stderr = cmd.Stderr
×
125
        scmd.ExtraFiles = cmd.ExtraFiles
×
126
        cmd = scmd
×
127

×
128
        stderr, err := cmd.StderrPipe()
×
129
        if err != nil {
×
130
                return err
×
131
        }
×
132

133
        stdout, err := cmd.StdoutPipe()
×
134
        if err != nil {
×
135
                return err
×
136
        }
×
137

138
        stdopipe := bufio.NewScanner(stdout)
×
139
        go func() {
×
140
                for stdopipe.Scan() {
×
141
                        fmt.Fprintln(os.Stdout, stdopipe.Text())
×
142
                }
×
143
        }()
144
        stdepipe := bufio.NewScanner(stderr)
×
145
        go func() {
×
146
                for stdepipe.Scan() {
×
147
                        fmt.Fprintln(os.Stderr, stdepipe.Text())
×
148
                }
×
149
        }()
150

151
        if err := cmd.Start(); err != nil {
×
152
                return err
×
153
        }
×
154

155
        if err := cmd.Wait(); err != nil {
×
156
                //nolint: forbidigo
×
157
                fmt.Println("")
×
158
                var ee *exec.ExitError
×
159
                if errors.As(err, &ee) {
×
160
                        fmt.Println(string(ee.Stderr))
×
161
                        sess.Log().Error(ee.Error())
×
162
                }
×
163

164
                return err
×
165
        }
166
        sess.Log().Debug(cmd.String(), slog.Int("exit", 0))
×
167
        return nil
×
168
}
169

170
func execCommandRaw(sess *session.Context, cmd *exec.Cmd) ([]byte, error) {
×
171
        sess.Log().Debug("exec: ", slog.String("cmd", cmd.String()))
×
172

×
173
        if sess.Get("app.main.exec.x").Bool() {
×
174
                sess.Log().LogDepth(4, logging.LevelAlways, cmd.String())
×
175
        }
×
176

177
        scmd := exec.CommandContext(sess, cmd.Path, cmd.Args[1:]...) //nolint: gosec
×
178
        scmd.Env = cmd.Env
×
179
        scmd.Dir = cmd.Dir
×
180

×
181
        // Create buffers to capture stdout and stderr separately
×
182
        var stdoutBuf, stderrBuf bytes.Buffer
×
183

×
184
        // Always redirect stdout and stderr to our buffers, ignoring any previously set values
×
185
        // This ensures we don't write to the original stdout/stderr
×
186
        scmd.Stdout = &stdoutBuf
×
187
        scmd.Stderr = &stderrBuf
×
188

×
189
        scmd.ExtraFiles = cmd.ExtraFiles
×
190
        cmd = scmd
×
191

×
192
        // Execute command
×
193
        err := cmd.Run()
×
194

×
195
        // Get stdout content
×
196
        stdoutBytes := stdoutBuf.Bytes()
×
197

×
UNCOV
198
        // If no error, just return the stdout
×
UNCOV
199
        if err == nil {
×
UNCOV
200
                return stdoutBytes, nil
×
UNCOV
201
        }
×
202

203
        // On error, clean stderr by replacing newlines with spaces
UNCOV
204
        cleanedStderr := strings.ReplaceAll(stderrBuf.String(), "\n", " ")
×
UNCOV
205

×
UNCOV
206
        var ee *exec.ExitError
×
UNCOV
207
        if errors.As(err, &ee) {
×
UNCOV
208
                // Create new error with original error and cleaned stderr appended
×
UNCOV
209
                enhancedErr := fmt.Errorf("%w: %s", err, cleanedStderr)
×
UNCOV
210
                return stdoutBytes, enhancedErr
×
UNCOV
211
        }
×
212

213
        // For other types of errors, also append cleaned stderr
UNCOV
214
        enhancedErr := fmt.Errorf("%w: %s", err, cleanedStderr)
×
UNCOV
215
        return stdoutBytes, enhancedErr
×
216
}
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