Skip to content

Commit

Permalink
Add new flag -d to not replace variables that start with a digit. (#39)
Browse files Browse the repository at this point in the history
* feat: new param -d to skip the replacement of environment variables which start with a digit

* feat: changed flag to -no-digit, updated readme

* fix: indentation
  • Loading branch information
Xalag authored Nov 6, 2022
1 parent cbd1443 commit dffd717
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ The flags and their restrictions are:
| ------------| -------------- | ------------ | ------------ |
|`-i` | input file | ```string | stdin``` | `stdin`
|`-o` | output file | ```string | stdout``` | `stdout`
|`-no-digit` | do not replace variables starting with a digit, e.g. $1 and ${1} | `flag` | `false`
|`-no-unset` | fail if a variable is not set | `flag` | `false`
|`-no-empty` | fail if a variable is set but empty | `flag` | `false`
|`-fail-fast` | fails at first occurence of an error, if `-no-empty` or `-no-unset` flags were **not** specified this is ignored | `flag` | `false`
Expand Down
4 changes: 3 additions & 1 deletion cmd/envsubst/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
var (
input = flag.String("i", "", "")
output = flag.String("o", "", "")
noDigit = flag.Bool("no-digit", false, "")
noUnset = flag.Bool("no-unset", false, "")
noEmpty = flag.Bool("no-empty", false, "")
failFast = flag.Bool("fail-fast", false, "")
Expand All @@ -24,6 +25,7 @@ Options:
-i Specify file input, otherwise use last argument as input file.
If no input file is specified, read from stdin.
-o Specify file output. If none is specified, write to stdout.
-no-digit Do not replace variables starting with a digit. e.g. $1 and ${1}
-no-unset Fail if a variable is not set.
-no-empty Fail if a variable is set but empty.
-fail-fast Fail on first error otherwise display all failures if restrictions are set.
Expand Down Expand Up @@ -79,7 +81,7 @@ func main() {
if *failFast {
parserMode = parse.Quick
}
restrictions := &parse.Restrictions{*noUnset, *noEmpty}
restrictions := &parse.Restrictions{*noUnset, *noEmpty, *noDigit}
result, err := (&parse.Parser{Name: "string", Env: os.Environ(), Restrict: restrictions, Mode: parserMode}).Parse(data)
if err != nil {
errorAndExit(err)
Expand Down
21 changes: 18 additions & 3 deletions envsubst.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ func String(s string) (string, error) {
// an error describing the failure.
// Errors on first failure or returns a collection of failures if failOnFirst is false
func StringRestricted(s string, noUnset, noEmpty bool) (string, error) {
return StringRestrictedNoDigit(s, noUnset, noEmpty , false)
}

// Like StringRestricted but additionally allows to ignore env variables which start with a digit.
func StringRestrictedNoDigit(s string, noUnset, noEmpty bool, noDigit bool) (string, error) {
return parse.New("string", os.Environ(),
&parse.Restrictions{noUnset, noEmpty}).Parse(s)
&parse.Restrictions{noUnset, noEmpty, noDigit}).Parse(s)
}

// Bytes returns the bytes represented by the parsed template after processing it.
Expand All @@ -32,8 +37,13 @@ func Bytes(b []byte) ([]byte, error) {
// If the parser encounters invalid input, or a restriction is violated, it returns
// an error describing the failure.
func BytesRestricted(b []byte, noUnset, noEmpty bool) ([]byte, error) {
return BytesRestrictedNoDigit(b, noUnset, noEmpty, false)
}

// Like BytesRestricted but additionally allows to ignore env variables which start with a digit.
func BytesRestrictedNoDigit(b []byte, noUnset, noEmpty bool, noDigit bool) ([]byte, error) {
s, err := parse.New("bytes", os.Environ(),
&parse.Restrictions{noUnset, noEmpty}).Parse(string(b))
&parse.Restrictions{noUnset, noEmpty, noDigit}).Parse(string(b))
if err != nil {
return nil, err
}
Expand All @@ -51,9 +61,14 @@ func ReadFile(filename string) ([]byte, error) {
// If the call to io.ReadFile failed it returns the error; otherwise it will
// call envsubst.Bytes with the returned content.
func ReadFileRestricted(filename string, noUnset, noEmpty bool) ([]byte, error) {
return ReadFileRestrictedNoDigit(filename, noUnset, noEmpty, false)
}

// Like ReadFileRestricted but additionally allows to ignore env variables which start with a digit.
func ReadFileRestrictedNoDigit(filename string, noUnset, noEmpty bool, noDigit bool) ([]byte, error) {
b, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return BytesRestricted(b, noUnset, noEmpty)
return BytesRestrictedNoDigit(b, noUnset, noEmpty, noDigit)
}
15 changes: 14 additions & 1 deletion parse/lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type lexer struct {
lastPos Pos // position of most recent item returned by nextItem
items chan item // channel of lexed items
subsDepth int // depth of substitution
noDigit bool // if the lexer skips variables that start with a digit
}

// next returns the next rune in the input.
Expand Down Expand Up @@ -120,10 +121,11 @@ func (l *lexer) nextItem() item {
}

// lex creates a new scanner for the input string.
func lex(input string) *lexer {
func lex(input string, noDigit bool) *lexer {
l := &lexer{
input: input,
items: make(chan item),
noDigit: noDigit,
}
go l.run()
return l
Expand All @@ -150,13 +152,24 @@ Loop:
}
l.pos++
switch r := l.peek(); {
case l.noDigit && unicode.IsDigit(r):
// ignore variable starting with digit like $1.
l.next()
l.emit(itemText)
case r == '$':
// ignore the previous '$'.
l.ignore()
l.next()
l.emit(itemText)
case r == '{':
l.next()
r2 := l.peek()
if l.noDigit && unicode.IsDigit(r2) {
// ignore variable starting with digit like ${1}.
l.next()
l.emit(itemText)
break
}
l.subsDepth++
l.emit(itemLeftDelim)
return lexSubstitution
Expand Down
27 changes: 26 additions & 1 deletion parse/lex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package parse

import (
"testing"
"strings"
)

type lexTest struct {
Expand Down Expand Up @@ -93,6 +94,29 @@ var lexTests = []lexTest{
{itemText, 8, "{HOME}"},
tEOF,
}},
{"no digit $1", "hello $1", []item{
{itemText, 0, "hello "},
{itemText, 7, "$1"},
tEOF,
}},
{"no digit $1ABC", "hello $1ABC", []item{
{itemText, 0, "hello "},
{itemText, 7, "$1"},
{itemText, 9, "ABC"},
tEOF,
}},
{"no digit ${2}", "hello ${2}", []item{
{itemText, 0, "hello "},
{itemText, 7, "${2"},
{itemText, 10, "}"},
tEOF,
}},
{"no digit ${2ABC}", "hello ${2ABC}", []item{
{itemText, 0, "hello "},
{itemText, 7, "${2"},
{itemText, 10, "ABC}"},
tEOF,
}},
}

func TestLex(t *testing.T) {
Expand All @@ -106,7 +130,8 @@ func TestLex(t *testing.T) {

// collect gathers the emitted items into a slice.
func collect(t *lexTest) (items []item) {
l := lex(t.input)
noDigit := strings.HasPrefix(t.name, "no digit")
l := lex(t.input, noDigit)
for {
item := l.nextItem()
items = append(items, item)
Expand Down
11 changes: 6 additions & 5 deletions parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ const (
type Restrictions struct {
NoUnset bool
NoEmpty bool
NoDigit bool
}

// Restrictions specifier
var (
Relaxed = &Restrictions{false, false}
NoEmpty = &Restrictions{false, true}
NoUnset = &Restrictions{true, false}
Strict = &Restrictions{true, true}
Relaxed = &Restrictions{false, false, false}
NoEmpty = &Restrictions{false, true, false}
NoUnset = &Restrictions{true, false, false}
Strict = &Restrictions{true, true, false}
)

// Parser type initializer
Expand All @@ -53,7 +54,7 @@ func New(name string, env []string, r *Restrictions) *Parser {

// Parse parses the given string.
func (p *Parser) Parse(text string) (string, error) {
p.lex = lex(text)
p.lex = lex(text, p.Restrict.NoDigit)
// Build internal array of all unset or empty vars here
var errs []error
// clean parse state
Expand Down

0 comments on commit dffd717

Please sign in to comment.