This repository has been archived by the owner on Jun 12, 2018. It is now read-only.
/
flagconfig.go
131 lines (118 loc) · 3.1 KB
/
flagconfig.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
// Package flagconfig provides a flag to specifiy a config file which
// will in turn be used to read unspecified flag values.
//
// Default configuration file location is $HOME/.config/{executable_name}/config
// or /etc/conf.d/{executable_name}
//
// Lines in the configuration file should be in flag-name=value format.
// Comments are allowed in the configuration file.
package flagconfig
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"strings"
)
var configFile = flag.String(
"c", defaultConfig(), "Config file to read flags from.")
// Usage prints the usage information for the application including all flags
// and their values after parsing the configuration file
func Usage() {
Parse()
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flag.VisitAll(func(f *flag.Flag) {
fmt.Fprintf(os.Stderr, " -%s=%s: %s\n", f.Name, f.Value.String(), f.Usage)
})
}
func defaultConfig() string {
home := os.Getenv("HOME")
basename := filepath.Base(os.Args[0])
path := filepath.Join(home, ".config", basename, "config")
_, err := os.Open(path)
if err == nil {
return path
}
path = filepath.Join("/", "etc", "conf.d", basename)
_, err = os.Open(path)
if err == nil {
return path
}
return ""
}
func contains(list []*flag.Flag, f *flag.Flag) bool {
for _, i := range list {
if i == f {
return true
}
}
return false
}
func readConfig(filename string) map[string]string {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalf("Failed to read config file %s: %s", *configFile, err)
}
lines := strings.Split(string(bytes), "\n")
result := make(map[string]string, len(lines))
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if trimmed == "" || trimmed[0] == '#' {
continue
}
parts := strings.Split(line, "=")
if len(parts) != 2 {
log.Fatalf("Invalid config line: %s", line)
}
result[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
return result
}
// Parse parses the default configuration file and populates the global flags
// based on the contents of the file.
func Parse() {
ParseSet(flag.CommandLine)
}
// ParseSet parses the default configuraiton file and populates the flags in
// the flag.FlagSet based on the contents of the file.
func ParseSet(flags *flag.FlagSet) {
ParseFile(flags, *configFile)
}
// ParseFile parses the specified configuration file and populates the flags
// in the flag.FlagSet based on the contents of the file.
func ParseFile(flags *flag.FlagSet, filename string) {
if filename == "" {
return
}
var (
explicit []*flag.Flag
all []*flag.Flag
)
config := readConfig(filename)
flags.Visit(func(f *flag.Flag) {
explicit = append(explicit, f)
})
flags.VisitAll(func(f *flag.Flag) {
all = append(all, f)
if !contains(explicit, f) {
val := config[f.Name]
if val != "" {
err := f.Value.Set(val)
if err != nil {
log.Fatalf("Failed to set flag %s with value %s", f.Name, val)
}
}
}
})
Outer:
for name, val := range config {
for _, f := range all {
if f.Name == name {
continue Outer
}
}
log.Fatalf("Unknown flag %s=%s in config file.", name, val)
}
}