-
Notifications
You must be signed in to change notification settings - Fork 5
/
sub.go
131 lines (116 loc) · 3.59 KB
/
sub.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
/*
Purpose : Simple alterntive to subenvst
Author : Ky-Anh Huynh
License : MIT
Features:
- [x] Only process variable name encapsulated by ${}, e.g, ${foo}.
- [x] Option `-u` to raise error when some variable is unset.
*/
package main
import "fmt"
import "regexp"
import "os"
import "io"
import "bufio"
import "flag"
/* internal global controllers */
var regVarname = regexp.MustCompile(`.+`)
var allVarSet = true
var lastProcessedVar = ""
var errors = 0
/* users' controllers */
var setMinusU bool
var scanOnly bool
var varPrefix = ""
// Internal function that replaces ${VAR_NAME} with environment value.
func repl_func(in []byte) []byte {
in_st := string(in)
// Ensure that input data is long enough
if 2 > len(in_st)-1 {
var_set := false
allVarSet = allVarSet && var_set
var_val := fmt.Sprintf("<%s::error::invalid_input_length>", in_st)
fmt.Fprintf(os.Stderr, "%s\n", var_val)
return []byte(var_val)
}
// FIXME: Expecting variable in the form `${FOO_BAR}`.
// FIXME: That means, there is no way to support user form e.g `$<FOO_BAR>`
// FIXME: (recall `sed` style?)
// When user provides some regexp with `-p prefix`, this can be tricky.
if in_st[0:2] != "${" || in_st[len(in_st)-1:len(in_st)] != "}" {
var_set := false
allVarSet = allVarSet && var_set
var_val := fmt.Sprintf("<%s::error::invalid_input_data>", in_st)
fmt.Fprintf(os.Stderr, "%s\n", var_val)
return []byte(var_val)
}
var_name := in_st[2 : len(in_st)-1]
var_set, var_val := lookUpEnv(var_name)
allVarSet = allVarSet && var_set
lastProcessedVar = var_name
return []byte(var_val)
}
func lookUpEnv(var_name string) (bool, string) {
var_val, var_set := os.LookupEnv(var_name)
if !var_set {
var_val = fmt.Sprintf("<%s::error::variable_unset>", var_name)
fmt.Fprintf(os.Stderr, "%s\n", var_val)
}
return var_set, var_val
}
// https://github.com/jprichardson/readline-go/blob/master/readline.go
// Invoke function `f` on each each line from the reader.
func eachLine(reader io.Reader, f func(string)) {
buf := bufio.NewReader(reader)
line, err := buf.ReadBytes('\n')
for err == nil {
f(string(line))
line, err = buf.ReadBytes('\n')
}
f(string(line))
}
func replLine(input string) []byte {
output := regVarname.ReplaceAllFunc([]byte(input), repl_func)
return output
}
/*
Scan the line and find all variable names match our regular expression.
Only used when `scanOnly` option is instructed.
*/
func scanLine(input string) [][]byte {
output := regVarname.FindAll([]byte(input), -1)
return output
}
func doLine(line string) {
if scanOnly {
if found := scanLine(line); found != nil {
for _, v := range found {
var_name := string(v[2 : len(v)-1])
var_set, _ := lookUpEnv(var_name)
fmt.Printf("%s\n", var_name)
if !var_set {
errors += 1
}
}
}
} else {
fmt.Printf("%s", replLine(line))
if setMinusU && !allVarSet {
fmt.Fprintf(os.Stderr, ":: Some environment variable is not set. The last processed variable is '%s'.\n", lastProcessedVar)
os.Exit(1)
}
}
}
func main() {
flag.BoolVar(&setMinusU, "u", false, "Raise error when some variable is not set.")
flag.BoolVar(&scanOnly, "v", false, "Output ocurrences of variables in input.")
flag.StringVar(&varPrefix, "p", "[^}]+", "Limit substitution to variables that match this prefix.")
flag.CommandLine.Parse(os.Args[1:])
// FIXME: Does this generate any serious issue at runtime?
regVarname = regexp.MustCompile(fmt.Sprintf(`\${(%s)}`, varPrefix))
fmt.Fprintf(os.Stderr, ":: genvsub is reading from STDIN and looking for variables with regexp '%s'\n", regVarname)
eachLine(os.Stdin, doLine)
if setMinusU && errors > 0 {
os.Exit(1)
}
}