This repository has been archived by the owner on Feb 1, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcfgFlags.go
147 lines (135 loc) · 4.35 KB
/
cfgFlags.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
// The MIT License (MIT)
//
// Copyright (c) 2015 Arnaud Vazard
//
// See LICENSE file.
// Package cfgFlags implements an "hybrid" configuration management, using an INI configuration file alongside the "flag" package
package cfgFlags
import (
"flag"
"fmt"
"io/ioutil"
"log"
"strconv"
"strings"
)
var configFlag *string
func init() {
// Flag to set the configuration file path via command line
configFlag = flag.String("config", "", "Configuration file path")
}
// Parse is the equivalent of flag.Parse() except that it also retrieve configuration values fron an INI file.
func Parse() error {
// Parse the command line flags
flag.Parse()
if *configFlag == "" {
log.Println("cfgFlags: No configuration file was given")
return nil
}
// Get the flags from the configuration file
valuesFromFile, err := getValuesFromFile(*configFlag)
if err != nil {
return err
}
// Get the missing flags (That is the flags that have not been set via the command line)
missingFlags := getMissingFlags()
for key, value := range valuesFromFile {
// Look up the flag from the configuration file in the flag list
f := flag.Lookup(key)
// If no flag matching "key" was found, return false
if f == nil {
return fmt.Errorf("cfgFlags: Unknow flag found in the configuration file (%q)\n", key)
}
// Iterate over the list of flags that are not yet set
for _, v := range missingFlags {
// If the flag from the file is found in the "missing" slice
if f.Name == v {
// If the value from the file is different from the default value for the flag, we set the value for this flag
if f.Value.String() != value {
// If an error happen, return false
if err := f.Value.Set(value); err != nil {
return fmt.Errorf("cfgFlags: Error while parsing flag %q (error: %q)\n", key, err)
}
}
// if we found the flag there is no need to continue to loop
break
}
}
}
return nil
}
func getValuesFromFile(configFile string) (map[string]string, error) {
// Read the file to a byte slice
content, err := ioutil.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("cfgFlags: Error while reading configuration file %q: %s\n", configFile, err)
}
valuesFromFile := make(map[string]string)
// Iterate over the file content (split on "\n")
for i, line := range strings.Split(string(content), "\n") {
line = strings.TrimSpace(line)
// If the line is empty, start with a comment or is a section, we do nothing
if line == "" || line[0] == ';' || line[0] == '#' || line[0] == '[' {
continue
}
// Split the string on the "=" character
fields := strings.Split(line, "=")
if len(fields) > 2 {
return nil, fmt.Errorf("cfgFlags: There is more than one \"=\" in the following line from the configuration file: %q (line %d)\n", line, i)
} else if len(fields) < 2 {
return nil, fmt.Errorf("cfgFlags: There is no \"=\" in the following line from the configuration file: %q (line %d)\n", line, i)
}
err := cleanString(&fields[1])
if err != nil {
return nil, fmt.Errorf("cfgFlags: Error while processing line %d from the configuration file: %s", i, err)
}
// Return map: The key is the first field with leading and trailing spaces removed, the value is the second field "cleaned"
valuesFromFile[strings.TrimSpace(fields[0])] = fields[1]
}
return valuesFromFile, nil
}
func getMissingFlags() []string {
var (
set, missing []string
found = false
)
// Visit only the flags that have been set
flag.Visit(func(f *flag.Flag) {
set = append(set, f.Name)
})
// Visit all the flags, even those not set
flag.VisitAll(func(f *flag.Flag) {
for _, v := range set {
if v == f.Name {
found = true
break
}
}
// If we don't find the flag in the slice of already set flags, we add it to the missing slice
if !found {
missing = append(missing, f.Name)
}
found = false
})
return missing
}
func cleanString(str *string) error {
// Trim the spaces from the string and return if the resulting string is empty
tmp := strings.TrimSpace(*str)
if len(tmp) == 0 {
return nil
}
// If the string is not quoted, we remove the trailing comments (Beginning with # or ;)
// If the string is quoted, we unquote it
if tmp[0] != '"' {
tmp = strings.Split(strings.Split(tmp, "#")[0], ";")[0]
} else {
result, err := strconv.Unquote(tmp)
if err != nil {
return err
}
tmp = result
}
*str = strings.TrimSpace(tmp)
return nil
}