/
config.go
136 lines (125 loc) · 3.79 KB
/
config.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
package main
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"time"
"github.com/kballard/go-shellquote"
flag "github.com/ogier/pflag"
)
type Config struct {
command []string
source string
regexes []string
globs []string
inverseRegexes []string
inverseGlobs []string
subSymbol string
startService bool
shutdownTimeout time.Duration
onlyFiles bool
onlyDirs bool
allFiles bool
}
func (c *Config) registerFlags(f *flag.FlagSet) {
f.VarP(newMultiString(nil, &c.regexes), "regex", "r", `
A regular expression to match filenames. (May be repeated.)`)
f.VarP(newMultiString(nil, &c.inverseRegexes), "inverse-regex", "R", `
A regular expression to exclude matching filenames.
(May be repeated.)`)
f.VarP(newMultiString(nil, &c.globs), "glob", "g", `
A shell glob expression to match filenames. (May be repeated.)`)
f.VarP(newMultiString(nil, &c.inverseGlobs), "inverse-glob", "G", `
A shell glob expression to exclude matching filenames.
(May be repeated.)`)
f.StringVar(&c.subSymbol, "substitute", defaultSubSymbol, `
The substitution symbol that is replaced with the filename
in a command.`)
f.BoolVarP(&c.startService, "start-service", "s", false, `
Indicates that the command is a long-running process to be
restarted on matching changes.`)
f.DurationVarP(&c.shutdownTimeout, "shutdown-timeout", "t", 500*time.Millisecond, `
Allow services this long to shut down.`)
f.BoolVar(&c.onlyFiles, "only-files", false, `
Only match files (not directories).`)
f.BoolVar(&c.onlyDirs, "only-dirs", false, `
Only match directories (not files).`)
f.BoolVar(&c.allFiles, "all", false, `
Include normally ignored files (VCS and editor special files).`)
}
// ReadConfigs reads configurations from either a file or, as a special case,
// stdin if "-" is given for path.
func ReadConfigs(path string) ([]*Config, error) {
var r io.Reader
name := path
if path == "-" {
r = os.Stdin
name = "standard input"
} else {
f, err := os.Open(flagConf)
if err != nil {
return nil, err
}
defer f.Close()
r = f
}
return readConfigsFromReader(r, name)
}
func readConfigsFromReader(r io.Reader, name string) ([]*Config, error) {
scanner := bufio.NewScanner(r)
lineNo := 0
var configs []*Config
for scanner.Scan() {
lineNo++
errorf := fmt.Sprintf("error on line %d of %s: %%s", lineNo, name)
c := &Config{}
flags := flag.NewFlagSet("", flag.ContinueOnError)
flags.SetOutput(ioutil.Discard)
c.registerFlags(flags)
parts, err := shellquote.Split(scanner.Text())
if err != nil {
return nil, fmt.Errorf(errorf, err)
}
// Skip empty lines and comments (lines starting with #).
if len(parts) == 0 || strings.HasPrefix(parts[0], "#") {
continue
}
if err := flags.Parse(parts); err != nil {
return nil, fmt.Errorf(errorf, err)
}
c.command = flags.Args()
c.source = fmt.Sprintf("%s, line %d", name, lineNo)
configs = append(configs, c)
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading config from %s: %s", name, err)
}
return configs, nil
}
// A multiString is a flag.Getter which collects repeated string flags.
type multiString struct {
vals *[]string
set bool // If false, then vals contains the defaults.
}
func newMultiString(vals []string, p *[]string) *multiString {
*p = vals
return &multiString{vals: p}
}
func (s *multiString) Set(val string) error {
if s.set {
*s.vals = append(*s.vals, val)
} else {
*s.vals = []string{val}
s.set = true
}
return nil
}
func (s *multiString) Get() interface{} {
return s.vals
}
func (s *multiString) String() string {
return fmt.Sprintf("[%s]", strings.Join(*s.vals, " "))
}