1 | package msg |
||
2 | |||
3 | import ( |
||
4 | "bufio" |
||
5 | "fmt" |
||
6 | "io" |
||
7 | "os" |
||
8 | "strings" |
||
9 | "sync" |
||
10 | |||
11 | "github.com/Masterminds/vcs" |
||
12 | ) |
||
13 | |||
14 | // Messenger provides the underlying implementation that displays output to |
||
15 | // users. |
||
16 | type Messenger struct { |
||
17 | sync.Mutex |
||
18 | |||
19 | // Quiet, if true, suppresses chatty levels, like Info. |
||
20 | Quiet bool |
||
21 | |||
22 | // IsDebugging, if true, shows Debug. |
||
23 | IsDebugging bool |
||
24 | |||
25 | // NoColor, if true, will not use color in the output. |
||
26 | NoColor bool |
||
27 | |||
28 | // Stdout is the location where this prints output. |
||
29 | Stdout io.Writer |
||
30 | |||
31 | // Stderr is the location where this prints logs. |
||
32 | Stderr io.Writer |
||
33 | |||
34 | // Stdin is the location where input is read. |
||
35 | Stdin io.Reader |
||
36 | |||
37 | // PanicOnDie if true Die() will panic instead of exiting. |
||
38 | PanicOnDie bool |
||
39 | |||
40 | // The default exit code to use when dyping |
||
41 | ecode int |
||
42 | |||
43 | // If an error was been sent. |
||
44 | hasErrored bool |
||
45 | } |
||
46 | |||
47 | // NewMessenger creates a default Messenger to display output. |
||
48 | func NewMessenger() *Messenger { |
||
49 | m := &Messenger{ |
||
50 | Quiet: false, |
||
51 | IsDebugging: false, |
||
52 | NoColor: false, |
||
53 | Stdout: os.Stdout, |
||
54 | Stderr: os.Stderr, |
||
55 | Stdin: os.Stdin, |
||
56 | PanicOnDie: false, |
||
57 | ecode: 1, |
||
58 | } |
||
59 | |||
60 | return m |
||
61 | } |
||
62 | |||
63 | // Default contains a default Messenger used by package level functions |
||
64 | var Default = NewMessenger() |
||
65 | |||
66 | // Info logs information |
||
67 | func (m *Messenger) Info(msg string, args ...interface{}) { |
||
68 | if m.Quiet { |
||
69 | return |
||
70 | } |
||
71 | prefix := m.Color(Green, "[INFO]\t") |
||
72 | m.Msg(prefix+msg, args...) |
||
73 | } |
||
74 | |||
75 | // Info logs information using the Default Messenger |
||
76 | func Info(msg string, args ...interface{}) { |
||
77 | Default.Info(msg, args...) |
||
78 | } |
||
79 | |||
80 | // Debug logs debug information |
||
81 | func (m *Messenger) Debug(msg string, args ...interface{}) { |
||
82 | if m.Quiet || !m.IsDebugging { |
||
83 | return |
||
84 | } |
||
85 | prefix := "[DEBUG]\t" |
||
86 | m.Msg(prefix+msg, args...) |
||
87 | } |
||
88 | |||
89 | // Debug logs debug information using the Default Messenger |
||
90 | func Debug(msg string, args ...interface{}) { |
||
91 | Default.Debug(msg, args...) |
||
92 | } |
||
93 | |||
94 | // Warn logs a warning |
||
95 | func (m *Messenger) Warn(msg string, args ...interface{}) { |
||
96 | prefix := m.Color(Yellow, "[WARN]\t") |
||
97 | m.Msg(prefix+msg, args...) |
||
98 | } |
||
99 | |||
100 | // Warn logs a warning using the Default Messenger |
||
101 | func Warn(msg string, args ...interface{}) { |
||
102 | Default.Warn(msg, args...) |
||
103 | } |
||
104 | |||
105 | // Err logs an error. |
||
106 | func (m *Messenger) Err(msg string, args ...interface{}) { |
||
107 | prefix := m.Color(Red, "[ERROR]\t") |
||
108 | m.Msg(prefix+msg, args...) |
||
109 | m.hasErrored = true |
||
110 | } |
||
111 | |||
112 | // Err logs anderror using the Default Messenger |
||
113 | func Err(msg string, args ...interface{}) { |
||
114 | Default.Err(msg, args...) |
||
115 | } |
||
116 | |||
117 | // Die prints an error message and immediately exits the application. |
||
118 | // If PanicOnDie is set to true a panic will occur instead of os.Exit being |
||
119 | // called. |
||
120 | func (m *Messenger) Die(msg string, args ...interface{}) { |
||
121 | m.Err(msg, args...) |
||
122 | if m.PanicOnDie { |
||
123 | panic("trapped a Die() call") |
||
124 | } |
||
125 | os.Exit(m.ecode) |
||
126 | } |
||
127 | |||
128 | // Die prints an error message and immediately exits the application using the |
||
129 | // Default Messenger. If PanicOnDie is set to true a panic will occur instead of |
||
130 | // os.Exit being called. |
||
131 | func Die(msg string, args ...interface{}) { |
||
132 | Default.Die(msg, args...) |
||
133 | } |
||
134 | |||
135 | // ExitCode sets the exit code used by Die. |
||
136 | // |
||
137 | // The default is 1. |
||
138 | // |
||
139 | // Returns the old error code. |
||
140 | func (m *Messenger) ExitCode(e int) int { |
||
141 | m.Lock() |
||
142 | old := m.ecode |
||
143 | m.ecode = e |
||
144 | m.Unlock() |
||
145 | return old |
||
146 | } |
||
147 | |||
148 | // ExitCode sets the exit code used by Die using the Default Messenger. |
||
149 | // |
||
150 | // The default is 1. |
||
151 | // |
||
152 | // Returns the old error code. |
||
153 | func ExitCode(e int) int { |
||
154 | return Default.ExitCode(e) |
||
155 | } |
||
156 | |||
157 | // Msg prints a message with optional arguments, that can be printed, of |
||
158 | // varying types. |
||
159 | func (m *Messenger) Msg(msg string, args ...interface{}) { |
||
160 | // When operations in Glide are happening concurrently messaging needs to be |
||
161 | // locked to avoid displaying one message in the middle of another one. |
||
162 | m.Lock() |
||
163 | defer m.Unlock() |
||
164 | // Get rid of the annoying fact that messages need \n at the end, but do |
||
165 | // it in a backward compatible way. |
||
166 | if !strings.HasSuffix(msg, "\n") { |
||
167 | msg += "\n" |
||
168 | } |
||
169 | |||
170 | if len(args) == 0 { |
||
171 | fmt.Fprint(m.Stderr, msg) |
||
172 | } else { |
||
173 | fmt.Fprintf(m.Stderr, msg, args...) |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
174 | } |
||
175 | |||
176 | // If an arg is a vcs error print the output if in debug mode. This is |
||
177 | // capured here rather than calling Debug because concurrent operations |
||
178 | // could cause other messages to appear between the initial error and the |
||
179 | // debug output by unlocking and calling Debug. |
||
180 | if len(args) != 0 && !m.Quiet && m.IsDebugging { |
||
181 | if err, ok := args[len(args)-1].(error); ok { |
||
182 | switch t := err.(type) { |
||
183 | case *vcs.LocalError: |
||
184 | fmt.Fprintf(m.Stderr, "[DEBUG]\tOutput was: %s", strings.TrimSpace(t.Out())) |
||
185 | case *vcs.RemoteError: |
||
186 | fmt.Fprintf(m.Stderr, "[DEBUG]\tOutput was: %s", strings.TrimSpace(t.Out())) |
||
187 | } |
||
188 | } |
||
189 | } |
||
190 | } |
||
191 | |||
192 | // Msg prints a message with optional arguments, that can be printed, of |
||
193 | // varying types using the Default Messenger. |
||
194 | func Msg(msg string, args ...interface{}) { |
||
195 | Default.Msg(msg, args...) |
||
196 | } |
||
197 | |||
198 | // Puts formats a message and then prints to Stdout. |
||
199 | // |
||
200 | // It does not prefix the message, does not color it, or otherwise decorate it. |
||
201 | // |
||
202 | // It does add a line feed. |
||
203 | func (m *Messenger) Puts(msg string, args ...interface{}) { |
||
204 | // When operations in Glide are happening concurrently messaging needs to be |
||
205 | // locked to avoid displaying one message in the middle of another one. |
||
206 | m.Lock() |
||
207 | defer m.Unlock() |
||
208 | |||
209 | fmt.Fprintf(m.Stdout, msg, args...) |
||
0 ignored issues
–
show
|
|||
210 | fmt.Fprintln(m.Stdout) |
||
211 | } |
||
212 | |||
213 | // Puts formats a message and then prints to Stdout using the Default Messenger. |
||
214 | // |
||
215 | // It does not prefix the message, does not color it, or otherwise decorate it. |
||
216 | // |
||
217 | // It does add a line feed. |
||
218 | func Puts(msg string, args ...interface{}) { |
||
219 | Default.Puts(msg, args...) |
||
220 | } |
||
221 | |||
222 | // Print prints exactly the string given. |
||
223 | // |
||
224 | // It prints to Stdout. |
||
225 | func (m *Messenger) Print(msg string) { |
||
226 | // When operations in Glide are happening concurrently messaging needs to be |
||
227 | // locked to avoid displaying one message in the middle of another one. |
||
228 | m.Lock() |
||
229 | defer m.Unlock() |
||
230 | |||
231 | fmt.Fprint(m.Stdout, msg) |
||
232 | } |
||
233 | |||
234 | // Print prints exactly the string given using the Default Messenger. |
||
235 | // |
||
236 | // It prints to Stdout. |
||
237 | func Print(msg string) { |
||
238 | Default.Print(msg) |
||
239 | } |
||
240 | |||
241 | // HasErrored returns if Error has been called. |
||
242 | // |
||
243 | // This is useful if you want to known if Error was called to exit with a |
||
244 | // non-zero exit code. |
||
245 | func (m *Messenger) HasErrored() bool { |
||
246 | return m.hasErrored |
||
247 | } |
||
248 | |||
249 | // HasErrored returns if Error has been called on the Default Messenger. |
||
250 | // |
||
251 | // This is useful if you want to known if Error was called to exit with a |
||
252 | // non-zero exit code. |
||
253 | func HasErrored() bool { |
||
254 | return Default.HasErrored() |
||
255 | } |
||
256 | |||
257 | // Color returns a string in a certain color if colors are enabled and |
||
258 | // available on that platform. |
||
259 | func Color(code, msg string) string { |
||
260 | return Default.Color(code, msg) |
||
261 | } |
||
262 | |||
263 | // PromptUntil provides a prompt until one of the passed in strings has been |
||
264 | // entered and return is hit. Note, the comparisons are case insensitive meaning |
||
265 | // Y == y. The returned value is the one from the passed in options (same case). |
||
266 | func (m *Messenger) PromptUntil(opts []string) (string, error) { |
||
267 | reader := bufio.NewReader(os.Stdin) |
||
268 | for { |
||
269 | text, err := reader.ReadString('\n') |
||
270 | if err != nil { |
||
271 | return "", err |
||
272 | } |
||
273 | |||
274 | for _, c := range opts { |
||
275 | if strings.EqualFold(c, strings.TrimSpace(text)) { |
||
276 | return c, nil |
||
277 | } |
||
278 | } |
||
279 | } |
||
280 | } |
||
281 | |||
282 | // PromptUntil provides a prompt until one of the passed in strings has been |
||
283 | // entered and return is hit. Note, the comparisons are case insensitive meaning |
||
284 | // Y == y. The returned value is the one from the passed in options (same case). |
||
285 | // Uses the default setup. |
||
286 | func PromptUntil(opts []string) (string, error) { |
||
287 | return Default.PromptUntil(opts) |
||
288 | } |
||
289 | |||
290 | // PromptUntilYorN provides a prompt until the user chooses yes or no. This is |
||
291 | // not case sensitive and they can input other options such as Y or N. |
||
292 | // In the response Yes is bool true and No is bool false. |
||
293 | func (m *Messenger) PromptUntilYorN() bool { |
||
294 | res, err := m.PromptUntil([]string{"y", "yes", "n", "no"}) |
||
295 | if err != nil { |
||
296 | m.Die("Error processing response: %s", err) |
||
297 | } |
||
298 | |||
299 | if res == "y" || res == "yes" { |
||
300 | return true |
||
301 | } |
||
302 | |||
303 | return false |
||
304 | } |
||
305 | |||
306 | // PromptUntilYorN provides a prompt until the user chooses yes or no. This is |
||
307 | // not case sensitive and they can input other options such as Y or N. |
||
308 | // In the response Yes is bool true and No is bool false. |
||
309 | // Uses the default setup. |
||
310 | func PromptUntilYorN() bool { |
||
311 | return Default.PromptUntilYorN() |
||
312 | } |
||
313 |