feat: drop github.com/a8m/envsubst in favor of gookit/config (#9028)

This commit is contained in:
Thomas Müller
2024-04-30 18:05:58 +02:00
committed by GitHub
parent e1a259e2aa
commit 4f7480d322
16 changed files with 64 additions and 819 deletions

View File

@@ -3,7 +3,8 @@ directives:
- '''self'''
connect-src:
- '''self'''
- 'https://${KEYCLOAK_DOMAIN:-keycloak.owncloud.test}/'
# In contrary to bash and docker the default is given after the | character
- 'https://${KEYCLOAK_DOMAIN|keycloak.owncloud.test}/'
default-src:
- '''none'''
font-src:

View File

@@ -12,14 +12,16 @@ directives:
frame-src:
- '''self'''
- 'https://embed.diagrams.net/'
- 'https://${ONLYOFFICE_DOMAIN:-onlyoffice.owncloud.test}/'
- 'https://${COLLABORA_DOMAIN:-collabora.owncloud.test}/'
# In contrary to bash and docker the default is given after the | character
- 'https://${ONLYOFFICE_DOMAIN|onlyoffice.owncloud.test}/'
- 'https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'
img-src:
- '''self'''
- 'data:'
- 'blob:'
- 'https://${ONLYOFFICE_DOMAIN:-onlyoffice.owncloud.test}/'
- 'https://${COLLABORA_DOMAIN:-collabora.owncloud.test}/'
# In contrary to bash and docker the default is given after the | character
- 'https://${ONLYOFFICE_DOMAIN|onlyoffice.owncloud.test}/'
- 'https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'
manifest-src:
- '''self'''
media-src:

View File

@@ -44,7 +44,7 @@ Let's explore with examples this approach.
- docker images: `/etc/ocis/`
- binary releases: `$HOME/.ocis/config/`
followed by the `<extension name>.yaml`, eg `proxy.yaml` for the extension configuration. You also can put an `ocis.yaml` config file to the expected loading location to use a single config file.
followed by the `<extension name>.yaml`, e.g. `proxy.yaml` for the extension configuration. You also can put an `ocis.yaml` config file to the expected loading location to use a single config file.
You can set another directory as config path in the environment variable `OCIS_CONFIG_DIR`. It will then pick the same file names, but from the folder you configured.
@@ -99,6 +99,21 @@ log:
level: info
```
#### Substitute ENV variables in configuration files
Environment variables can be used in the configurations files and will be replaced by oCIS when loading these.
Default values can be specified after a `|` character - see below.
```yaml
proxy:
http:
addr: ${PROXY_HTTP_ADDR|localhost:4321}
log:
pretty: true
color: true
level: info
```
### Workflows
Since one can run an extension using the runtime (supervised) or not (unsupervised), we ensure correct behavior in both modes, expecting the same outputs.

1
go.mod
View File

@@ -9,7 +9,6 @@ require (
github.com/Masterminds/semver v1.5.0
github.com/MicahParks/keyfunc v1.9.0
github.com/Nerzal/gocloak/v13 v13.9.0
github.com/a8m/envsubst v1.4.2
github.com/bbalet/stopwords v1.0.0
github.com/beevik/etree v1.3.0
github.com/blevesearch/bleve/v2 v2.4.0

2
go.sum
View File

@@ -826,8 +826,6 @@ github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVO
github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6 h1:ws0yvsikTQdmheKINP16tBzAHdttrHwbz/q3Fgl9X1Y=
github.com/aduffeck/gowebdav v0.0.0-20231215102054-212d4a4374f6/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=

View File

@@ -1,11 +1,11 @@
package middleware
import (
"github.com/a8m/envsubst"
gofig "github.com/gookit/config/v2"
"github.com/gookit/config/v2/yaml"
"github.com/owncloud/ocis/v2/services/proxy/pkg/config"
"github.com/unrolled/secure"
"github.com/unrolled/secure/cspbuilder"
"gopkg.in/yaml.v2"
"net/http"
"os"
)
@@ -16,15 +16,23 @@ func LoadCSPConfig(proxyCfg *config.Config) (*config.CSP, error) {
if err != nil {
return nil, err
}
// replace env vars ..
yamlContent, err = envsubst.Bytes(yamlContent)
return loadCSPConfig(yamlContent)
}
// LoadCSPConfig loads CSP header configuration from a yaml file.
func loadCSPConfig(yamlContent []byte) (*config.CSP, error) {
// substitute env vars and load to struct
gofig.WithOptions(gofig.ParseEnv)
gofig.AddDriver(yaml.Driver)
err := gofig.LoadSources("yaml", yamlContent)
if err != nil {
return nil, err
}
// read yaml
cspConfig := config.CSP{}
err = yaml.Unmarshal(yamlContent, &cspConfig)
err = gofig.BindStruct("", &cspConfig)
if err != nil {
return nil, err
}

View File

@@ -0,0 +1,27 @@
package middleware
import (
"gotest.tools/v3/assert"
"testing"
)
func TestLoadCSPConfig(t *testing.T) {
// setup test env
yaml := `
directives:
frame-src:
- '''self'''
- 'https://embed.diagrams.net/'
- 'https://${ONLYOFFICE_DOMAIN|onlyoffice.owncloud.test}/'
- 'https://${COLLABORA_DOMAIN|collabora.owncloud.test}/'
`
config, err := loadCSPConfig([]byte(yaml))
if err != nil {
t.Error(err)
}
assert.Equal(t, config.Directives["frame-src"][0], "'self'")
assert.Equal(t, config.Directives["frame-src"][1], "https://embed.diagrams.net/")
assert.Equal(t, config.Directives["frame-src"][2], "https://onlyoffice.owncloud.test/")
assert.Equal(t, config.Directives["frame-src"][3], "https://collabora.owncloud.test/")
}

View File

@@ -1,7 +0,0 @@
language: go
go:
- 1.13
- 1.14
- tip
scripts:
- go test

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 envsubst contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,107 +0,0 @@
# envsubst
[![GoDoc][godoc-img]][godoc-url]
[![License][license-image]][license-url]
[![Build status][travis-image]][travis-url]
[![Github All Releases][releases-image]][releases]
> Environment variables substitution for Go. see docs [below](#docs)
#### Installation:
##### From binaries
Latest stable `envsubst` [prebuilt binaries for 64-bit Linux, or Mac OS X][releases] are available via Github releases.
###### Linux and MacOS
```console
curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst-`uname -s`-`uname -m` -o envsubst
chmod +x envsubst
sudo mv envsubst /usr/local/bin
```
###### Windows
Download the latest prebuilt binary from [releases page][releases], or if you have curl installed:
```console
curl -L https://github.com/a8m/envsubst/releases/download/v1.2.0/envsubst.exe
```
##### With go
You can install via `go get` (provided you have installed go):
```console
go get github.com/a8m/envsubst/cmd/envsubst
```
#### Using via cli
```sh
envsubst < input.tmpl > output.text
echo 'welcome $HOME ${USER:=a8m}' | envsubst
envsubst -help
```
#### Imposing restrictions
There are three command line flags with which you can cause the substitution to stop with an error code, should the restriction associated with the flag not be met. This can be handy if you want to avoid creating e.g. configuration files with unset or empty parameters.
Setting a `-fail-fast` flag in conjunction with either no-unset or no-empty or both will result in a faster feedback loop, this can be especially useful when running through a large file or byte array input, otherwise a list of errors is returned.
The flags and their restrictions are:
|__Option__ | __Meaning__ | __Type__ | __Default__ |
| ------------| -------------- | ------------ | ------------ |
|`-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`
These flags can be combined to form tighter restrictions.
#### Using `envsubst` programmatically ?
You can take a look on [`_example/main`](https://github.com/a8m/envsubst/blob/master/_example/main.go) or see the example below.
```go
package main
import (
"fmt"
"github.com/a8m/envsubst"
)
func main() {
input := "welcom $HOME"
str, err := envsubst.String(input)
// ...
buf, err := envsubst.Bytes([]byte(input))
// ...
buf, err := envsubst.ReadFile("filename")
}
```
### Docs
> api docs here: [![GoDoc][godoc-img]][godoc-url]
|__Expression__ | __Meaning__ |
| ----------------- | -------------- |
|`${var}` | Value of var (same as `$var`)
|`${var-$DEFAULT}` | If var not set, evaluate expression as $DEFAULT
|`${var:-$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT
|`${var=$DEFAULT}` | If var not set, evaluate expression as $DEFAULT
|`${var:=$DEFAULT}` | If var not set or is empty, evaluate expression as $DEFAULT
|`${var+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
|`${var:+$OTHER}` | If var set, evaluate expression as $OTHER, otherwise as empty string
|`$$var` | Escape expressions. Result will be `$var`.
<sub>Most of the rows in this table were taken from [here](http://www.tldp.org/LDP/abs/html/refcards.html#AEN22728)</sub>
### See also
* `os.ExpandEnv(s string) string` - only supports `$var` and `${var}` notations
#### License
MIT
[releases]: https://github.com/a8m/envsubst/releases
[releases-image]: https://img.shields.io/github/downloads/a8m/envsubst/total.svg?style=for-the-badge
[godoc-url]: https://godoc.org/github.com/a8m/envsubst
[godoc-img]: https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge
[license-image]: https://img.shields.io/badge/license-MIT-blue.svg?style=for-the-badge
[license-url]: LICENSE
[travis-image]: https://img.shields.io/travis/a8m/envsubst.svg?style=for-the-badge
[travis-url]: https://travis-ci.org/a8m/envsubst

View File

@@ -1,74 +0,0 @@
package envsubst
import (
"io/ioutil"
"os"
"github.com/a8m/envsubst/parse"
)
// String returns the parsed template string after processing it.
// If the parser encounters invalid input, it returns an error describing the failure.
func String(s string) (string, error) {
return StringRestricted(s, false, false)
}
// StringRestricted returns the parsed template string after processing it.
// If the parser encounters invalid input, or a restriction is violated, it returns
// 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, noDigit}).Parse(s)
}
// Bytes returns the bytes represented by the parsed template after processing it.
// If the parser encounters invalid input, it returns an error describing the failure.
func Bytes(b []byte) ([]byte, error) {
return BytesRestricted(b, false, false)
}
// BytesRestricted returns the bytes represented by the parsed template after processing it.
// 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, noDigit}).Parse(string(b))
if err != nil {
return nil, err
}
return []byte(s), nil
}
// ReadFile call io.ReadFile with the given file name.
// If the call to io.ReadFile failed it returns the error; otherwise it will
// call envsubst.Bytes with the returned content.
func ReadFile(filename string) ([]byte, error) {
return ReadFileRestricted(filename, false, false)
}
// ReadFileRestricted calls io.ReadFile with the given file name.
// 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 BytesRestrictedNoDigit(b, noUnset, noEmpty, noDigit)
}

View File

@@ -1,27 +0,0 @@
package parse
import (
"strings"
)
type Env []string
func (e Env) Get(name string) string {
v, _ := e.Lookup(name)
return v
}
func (e Env) Has(name string) bool {
_, ok := e.Lookup(name)
return ok
}
func (e Env) Lookup(name string) (string, bool) {
prefix := name + "="
for _, pair := range e {
if strings.HasPrefix(pair, prefix) {
return pair[len(prefix):], true
}
}
return "", false
}

View File

@@ -1,269 +0,0 @@
package parse
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// itemType identifies the type of lex items.
type itemType int
// Pos represents a byte position in the original input text from which
// this template was parsed.
type Pos int
// item represents a token or text string returned from the scanner.
type item struct {
typ itemType // The type of this item.
pos Pos // The starting position, in bytes, of this item in the input string.
val string // The value of this item.
}
func (i item) String() string {
typ := "OP"
if t, ok := tokens[i.typ]; ok {
typ = t
}
return fmt.Sprintf("%s: %.40q", typ, i.val)
}
const (
eof = -1
itemError itemType = iota // error occurred; value is text of error
itemEOF
itemText // plain text
itemPlus // plus('+')
itemDash // dash('-')
itemEquals // equals
itemColonEquals // colon-equals (':=')
itemColonDash // colon-dash(':-')
itemColonPlus // colon-plus(':+')
itemVariable // variable starting with '$', such as '$hello' or '$1'
itemLeftDelim // left action delimiter '${'
itemRightDelim // right action delimiter '}'
)
var tokens = map[itemType]string{
itemEOF: "EOF",
itemError: "ERROR",
itemText: "TEXT",
itemVariable: "VAR",
itemLeftDelim: "START EXP",
itemRightDelim: "END EXP",
}
// stateFn represents the state of the lexer as a function that returns the next state.
type stateFn func(*lexer) stateFn
// lexer holds the state of the scanner
type lexer struct {
input string // the string being lexed
state stateFn // the next lexing function to enter
pos Pos // current position in the input
start Pos // start position of this item
width Pos // width of last rune read from input
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.
func (l *lexer) next() rune {
if int(l.pos) >= len(l.input) {
l.width = 0
return eof
}
r, w := utf8.DecodeRuneInString(l.input[l.pos:])
l.width = Pos(w)
l.pos += l.width
return r
}
// peek returns but does not consume the next rune in the input.
func (l *lexer) peek() rune {
r := l.next()
l.backup()
return r
}
// backup steps back one rune. Can only be called once per call of next.
func (l *lexer) backup() {
l.pos -= l.width
}
// emit passes an item back to the client.
func (l *lexer) emit(t itemType) {
l.items <- item{t, l.start, l.input[l.start:l.pos]}
l.lastPos = l.start
l.start = l.pos
}
// ignore skips over the pending input before this point.
func (l *lexer) ignore() {
l.start = l.pos
}
// errorf returns an error token and terminates the scan by passing
// back a nil pointer that will be the next state, terminating l.nextItem.
func (l *lexer) errorf(format string, args ...interface{}) stateFn {
l.items <- item{itemError, l.start, fmt.Sprintf(format, args...)}
return nil
}
// nextItem returns the next item from the input.
// Called by the parser, not in the lexing goroutine.
func (l *lexer) nextItem() item {
item := <-l.items
return item
}
// lex creates a new scanner for the input string.
func lex(input string, noDigit bool) *lexer {
l := &lexer{
input: input,
items: make(chan item),
noDigit: noDigit,
}
go l.run()
return l
}
// run runs the state machine for the lexer.
func (l *lexer) run() {
for l.state = lexText; l.state != nil; {
l.state = l.state(l)
}
close(l.items)
}
// lexText scans until encountering with "$" or an opening action delimiter, "${".
func lexText(l *lexer) stateFn {
Loop:
for {
switch r := l.next(); r {
case '$':
l.pos--
// emit the text we've found until here, if any.
if l.pos > l.start {
l.emit(itemText)
}
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 lexSubstitutionOperator
case isAlphaNumeric(r):
return lexVariable
}
case eof:
break Loop
}
}
// Correctly reached EOF.
if l.pos > l.start {
l.emit(itemText)
}
l.emit(itemEOF)
return nil
}
// lexVariable scans a Variable: $Alphanumeric.
// The $ has been scanned.
func lexVariable(l *lexer) stateFn {
var r rune
for {
r = l.next()
if !isAlphaNumeric(r) {
l.backup()
break
}
}
if v := l.input[l.start:l.pos]; v == "_" || v == "$_" {
return lexText
}
l.emit(itemVariable)
if l.subsDepth > 0 {
return lexSubstitutionOperator
}
return lexText
}
// lexSubstitutionOperator scans a starting substitution operator (if any) and continues with lexSubstitution
func lexSubstitutionOperator(l *lexer) stateFn {
switch r := l.next(); {
case r == '}':
l.subsDepth--
l.emit(itemRightDelim)
return lexText
case r == eof || isEndOfLine(r):
return l.errorf("closing brace expected")
case isAlphaNumeric(r) && strings.HasPrefix(l.input[l.lastPos:], "${"):
return lexVariable
case r == '+':
l.emit(itemPlus)
case r == '-':
l.emit(itemDash)
case r == '=':
l.emit(itemEquals)
case r == ':':
switch l.next() {
case '-':
l.emit(itemColonDash)
case '=':
l.emit(itemColonEquals)
case '+':
l.emit(itemColonPlus)
}
}
return lexSubstitution
}
// lexSubstitution scans the elements inside substitution delimiters.
func lexSubstitution(l *lexer) stateFn {
switch r := l.next(); {
case r == '}':
l.subsDepth--
l.emit(itemRightDelim)
return lexText
case r == eof || isEndOfLine(r):
return l.errorf("closing brace expected")
case isAlphaNumeric(r) && strings.HasPrefix(l.input[l.lastPos:], "${"):
fallthrough
case r == '$':
return lexVariable
default:
l.emit(itemText)
}
return lexSubstitution
}
// isEndOfLine reports whether r is an end-of-line character.
func isEndOfLine(r rune) bool {
return r == '\r' || r == '\n'
}
// isAlphaNumeric reports whether r is an alphabetic, digit, or underscore.
func isAlphaNumeric(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}

View File

@@ -1,107 +0,0 @@
package parse
import (
"fmt"
)
type Node interface {
Type() NodeType
String() (string, error)
}
// NodeType identifies the type of a node.
type NodeType int
// Type returns itself and provides an easy default implementation
// for embedding in a Node. Embedded in all non-trivial Nodes.
func (t NodeType) Type() NodeType {
return t
}
const (
NodeText NodeType = iota
NodeSubstitution
NodeVariable
)
type TextNode struct {
NodeType
Text string
}
func NewText(text string) *TextNode {
return &TextNode{NodeText, text}
}
func (t *TextNode) String() (string, error) {
return t.Text, nil
}
type VariableNode struct {
NodeType
Ident string
Env Env
Restrict *Restrictions
}
func NewVariable(ident string, env Env, restrict *Restrictions) *VariableNode {
return &VariableNode{NodeVariable, ident, env, restrict}
}
func (t *VariableNode) String() (string, error) {
if err := t.validateNoUnset(); err != nil {
return "", err
}
value := t.Env.Get(t.Ident)
if err := t.validateNoEmpty(value); err != nil {
return "", err
}
return value, nil
}
func (t *VariableNode) isSet() bool {
return t.Env.Has(t.Ident)
}
func (t *VariableNode) validateNoUnset() error {
if t.Restrict.NoUnset && !t.isSet() {
return fmt.Errorf("variable ${%s} not set", t.Ident)
}
return nil
}
func (t *VariableNode) validateNoEmpty(value string) error {
if t.Restrict.NoEmpty && value == "" && t.isSet() {
return fmt.Errorf("variable ${%s} set but empty", t.Ident)
}
return nil
}
type SubstitutionNode struct {
NodeType
ExpType itemType
Variable *VariableNode
Default Node // Default could be variable or text
}
func (t *SubstitutionNode) String() (string, error) {
if t.ExpType >= itemPlus && t.Default != nil {
switch t.ExpType {
case itemColonDash, itemColonEquals:
if s, _ := t.Variable.String(); s != "" {
return s, nil
}
return t.Default.String()
case itemPlus, itemColonPlus:
if t.Variable.isSet() {
return t.Default.String()
}
return "", nil
default:
if !t.Variable.isSet() {
return t.Default.String()
}
}
}
return t.Variable.String()
}

View File

@@ -1,189 +0,0 @@
// Most of the code in this package taken from golang/text/template/parse
package parse
import (
"errors"
"strings"
)
// A mode value is a set of flags (or 0). They control parser behavior.
type Mode int
// Mode for parser behaviour
const (
Quick Mode = iota // stop parsing after first error encoutered and return
AllErrors // report all errors
)
// The restrictions option controls the parsring restriction.
type Restrictions struct {
NoUnset bool
NoEmpty bool
NoDigit bool
}
// Restrictions specifier
var (
Relaxed = &Restrictions{false, false, false}
NoEmpty = &Restrictions{false, true, false}
NoUnset = &Restrictions{true, false, false}
Strict = &Restrictions{true, true, false}
)
// Parser type initializer
type Parser struct {
Name string // name of the processing template
Env Env
Restrict *Restrictions
Mode Mode
// parsing state;
lex *lexer
token [3]item // three-token lookahead
peekCount int
nodes []Node
}
// New allocates a new Parser with the given name.
func New(name string, env []string, r *Restrictions) *Parser {
return &Parser{
Name: name,
Env: Env(env),
Restrict: r,
}
}
// Parse parses the given string.
func (p *Parser) Parse(text string) (string, error) {
p.lex = lex(text, p.Restrict.NoDigit)
// Build internal array of all unset or empty vars here
var errs []error
// clean parse state
p.nodes = make([]Node, 0)
p.peekCount = 0
if err := p.parse(); err != nil {
switch p.Mode {
case Quick:
return "", err
case AllErrors:
errs = append(errs, err)
}
}
var out string
for _, node := range p.nodes {
s, err := node.String()
if err != nil {
switch p.Mode {
case Quick:
return "", err
case AllErrors:
errs = append(errs, err)
}
}
out += s
}
if len(errs) > 0 {
var b strings.Builder
for i, err := range errs {
if i > 0 {
b.WriteByte('\n')
}
b.WriteString(err.Error())
}
return "", errors.New(b.String())
}
return out, nil
}
// parse is the top-level parser for the template.
// It runs to EOF and return an error if something isn't right.
func (p *Parser) parse() error {
Loop:
for {
switch t := p.next(); t.typ {
case itemEOF:
break Loop
case itemError:
return p.errorf(t.val)
case itemVariable:
varNode := NewVariable(strings.TrimPrefix(t.val, "$"), p.Env, p.Restrict)
p.nodes = append(p.nodes, varNode)
case itemLeftDelim:
if p.peek().typ == itemVariable {
n, err := p.action()
if err != nil {
return err
}
p.nodes = append(p.nodes, n)
continue
}
fallthrough
default:
textNode := NewText(t.val)
p.nodes = append(p.nodes, textNode)
}
}
return nil
}
// Parse substitution. first item is a variable.
func (p *Parser) action() (Node, error) {
var expType itemType
var defaultNode Node
varNode := NewVariable(p.next().val, p.Env, p.Restrict)
Loop:
for {
switch t := p.next(); t.typ {
case itemRightDelim:
break Loop
case itemError:
return nil, p.errorf(t.val)
case itemVariable:
defaultNode = NewVariable(strings.TrimPrefix(t.val, "$"), p.Env, p.Restrict)
case itemText:
n := NewText(t.val)
Text:
for {
switch p.peek().typ {
case itemRightDelim, itemError, itemEOF:
break Text
default:
// patch to accept all kind of chars
n.Text += p.next().val
}
}
defaultNode = n
default:
expType = t.typ
}
}
return &SubstitutionNode{NodeSubstitution, expType, varNode, defaultNode}, nil
}
func (p *Parser) errorf(s string) error {
return errors.New(s)
}
// next returns the next token.
func (p *Parser) next() item {
if p.peekCount > 0 {
p.peekCount--
} else {
p.token[0] = p.lex.nextItem()
}
return p.token[p.peekCount]
}
// backup backs the input stream up one token.
func (p *Parser) backup() {
p.peekCount++
}
// peek returns but does not consume the next token.
func (p *Parser) peek() item {
if p.peekCount > 0 {
return p.token[p.peekCount-1]
}
p.peekCount = 1
p.token[0] = p.lex.nextItem()
return p.token[0]
}

4
vendor/modules.txt vendored
View File

@@ -69,10 +69,6 @@ github.com/ProtonMail/go-crypto/openpgp/s2k
github.com/RoaringBitmap/roaring
github.com/RoaringBitmap/roaring/internal
github.com/RoaringBitmap/roaring/roaring64
# github.com/a8m/envsubst v1.4.2
## explicit; go 1.17
github.com/a8m/envsubst
github.com/a8m/envsubst/parse
# github.com/agnivade/levenshtein v1.1.1
## explicit; go 1.13
github.com/agnivade/levenshtein