-
Notifications
You must be signed in to change notification settings - Fork 0
/
application.go
185 lines (161 loc) · 3.9 KB
/
application.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
// Package skapt provides a tiny interface
// to create and manage your command line applications
package skapt
import (
"bytes"
"fmt"
"io"
"os"
"regexp"
"strings"
"text/tabwriter"
"text/template"
"github.com/hoenirvili/skapt/flag"
)
// Context holds context specific information
// for the current running handler
type Context struct {
// Flags contains the parsed flags
flag.Flags
// Args additional command line arguments
Args []string
// Stdout writer to stdout
Stdout io.Writer
// Stdout writer to stderr
Stderr io.Writer
}
// Application will hold all the information for creating
// and parsing the command line
type Application struct {
// Name of the command line application
Name string
// Usage is the usage of the command line
Usage string
// Description holds the description of the command line
Description string
// Version is the version of the application
Version string
// Flags holds list of the root command
Flags flag.Flags
// NArgs minim required value args
NArgs int
// Handler is the root main handler
Handler func(ctx *Context) error
// Stdout place to write information that the user
// needs to know
Stdout io.Writer
// Stderr place where all error messages should be written
Stderr io.Writer
}
// validate if the holds valid information
// in order to be executed by Exec
func (a Application) validate() error {
if a.Name == "" {
return fmt.Errorf("empty application name")
}
if a.Handler == nil {
return fmt.Errorf("empty application handler")
}
if a.Flags != nil {
if err := a.Flags.Validate(); err != nil {
return err
}
}
return nil
}
// Exec executes the command line based on the args provided
func (a Application) Exec(args []string) (err error) {
if err := a.validate(); err != nil {
return err
}
if a.Stdout == nil {
a.Stdout = os.Stdout
}
if a.Stderr == nil {
a.Stderr = os.Stderr
}
// if there is any error, wrap it into a byte slice
// and output it to stderr
defer func() {
if err != nil {
_, errFpr := fmt.Fprintf(a.Stderr, err.Error())
if errFpr != nil {
err = errFpr
}
}
}()
if len(args) == 0 {
return fmt.Errorf("No arguments to execute")
}
a.Flags.AppendHelpIfNotPresent()
a.Flags.AppendVersionIfNotPreset()
args, err = a.Flags.Parse(args)
if err != nil {
return err
}
switch {
case a.Flags.Bool("help"):
return a.render(help)
case a.Flags.Bool("version"):
return a.render(version)
}
if len(args)-1 < a.NArgs {
return fmt.Errorf("Expecting at least %d additional arguments", a.NArgs)
}
if err := a.Flags.RequiredAreParsed(); err != nil {
return err
}
return a.Handler(&Context{
Flags: a.Flags,
Args: args,
Stdout: a.Stdout,
Stderr: a.Stderr,
})
}
var help = `
Usage:\t{{if .Usage}}{{wrap .Usage true}}{{else}}{{.Name}}\t[OPTIONS] [ARG...]
\t{{.Name}}\t[ --help | -h | -v | --version ]{{end}}
{{wrap .Description false}}
Options:
{{range .Flags}}
{{if.Short}}-{{.Short}}{{end}} {{if .Long}}--{{.Long}}{{end}}\t\t{{wrap .Description true}}{{end}}
`[1:]
var version = `
Version {{.Version}}
`[1:]
var re = regexp.MustCompile(`(?mi)\S+`)
// render renders the specified template to stdout
func (a *Application) render(templ string) error {
funcMap := template.FuncMap{
"wrap": func(description string, tab bool) string {
if len(description) < 90 {
return description
}
str := ""
words := re.FindAllString(description, -1)
for key, word := range words {
if key != 0 && key%10 == 0 {
if tab {
str += "\n\\t\\t"
} else {
str += "\n"
}
}
str += word + " "
}
return str
},
}
t := template.Must(template.New("t").Funcs(funcMap).Parse(templ))
buffer := &bytes.Buffer{}
if err := t.Execute(buffer, a); err != nil {
return err
}
str := buffer.String()
str = strings.Replace(str, "\\t", "\t", -1)
w := tabwriter.NewWriter(a.Stdout, 0, 0, 1, ' ', 0)
if _, err := fmt.Fprintf(w, str); err != nil {
return err
}
return w.Flush()
}