Skip to content

Commit

Permalink
Adding new functions bool and getenv
Browse files Browse the repository at this point in the history
Two new functions:
- `getenv` - a more forgiving way to get an environment variable
- `bool` - converts a string into a boolean

Also enabled multi-line templates by reading the entire input before
applying the template, instead of applying the template one line at a
time.

Signed-off-by: Dave Henderson <dhenderson@gmail.com>
  • Loading branch information
hairyhenderson committed Feb 20, 2016
1 parent e96a78e commit fc17cc1
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 13 deletions.
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,72 @@ $ echo "Hello, {{.Env.USER}}" | gomplate
Hello, hairyhenderson
```

#### About `.Env`

You can easily access environment variables with `.Env`, but there's a catch:
if you try to reference an environment variable that doesn't exist, parsing
will fail and `gomplate` will exit with an error condition.

Sometimes, this behaviour is desired; if the output is unusable without certain strings, this is a sure way to know that variables are missing!

If you want different behaviour, try `getenv` (below).

### Built-in functions

In addition to all of the functions and operators that the [Go template](https://golang.org/pkg/text/template/)
language provides (`if`, `else`, `eq`, `and`, `or`, `range`, etc...), there are
some additional functions baked in to `gomplate`:

#### `getenv`

Exposes the [os.Getenv](https://golang.org/pkg/os/#Getenv) function.

This is a more forgiving alternative to using `.Env`, since missing keys will
return an empty string.

##### Example

```console
$ echo 'Hello, {{getenv "USER"}}' | gomplate
Hello, hairyhenderson
```
#### `bool`

Converts a true-ish string to a boolean. Can be used to simplify conditional statements based on environment variables or other text input.

##### Example

_`input.tmpl`:_
```
{{if bool (getenv "FOO")}}foo{{else}}bar{{end}}
```

```console
$ gomplate < input.tmpl
bar
$ FOO=true gomplate < input.tmpl
foo
```

### Some more complex examples

##### Variable assignment and `if`/`else`

_`input.tmpl`:_
```
{{ $u := getenv "USER" }}
{{ if eq $u "root" }}You are root!{{else}}You are not root :({{end}}
```

```console
$ gomplate < input.tmpl
You are not root :(
$ sudo gomplate < input.tmpl
You are root!
```

_Note:_ it's important for the `if`/`else`/`end` keywords to appear on the same line, or else `gomplate` will not be able to parse the pipeline properly

## License

[The MIT License](http://opensource.org/licenses/MIT)
Expand Down
51 changes: 38 additions & 13 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package main

import (
"bufio"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strconv"

"text/template"
)
Expand All @@ -23,25 +24,49 @@ func init() {
}
}

// Getenv retrieves the value of the environment variable named by the key.
// It returns the value, which will be empty if the variable is not present.
func Getenv(key string) string {
return os.Getenv(key)
}

// Bool converts a string to a boolean value, using strconv.ParseBool under the covers.
// Possible true values are: 1, t, T, TRUE, true, True
// All other values are considered false.
func Bool(in string) bool {
if b, err := strconv.ParseBool(in); err == nil {
return b
}
return false
}

var funcMap = template.FuncMap{
"Getenv": Getenv,
"getenv": Getenv,
"Bool": Bool,
"bool": Bool,
}

func createTemplate() *template.Template {
return template.New("template").Option("missingkey=error")
return template.New("template").Funcs(funcMap).Option("missingkey=error")
}

// RunTemplate -
func RunTemplate(in io.Reader, out io.Writer) {
s := bufio.NewScanner(in)
context := &Context{}
for s.Scan() {
tmpl, err := createTemplate().Parse(s.Text())
if err != nil {
log.Fatalf("Line %q: %v\n", s.Text(), err)
}

if err := tmpl.Execute(out, context); err != nil {
panic(err)
}
out.Write([]byte("\n"))
text, err := ioutil.ReadAll(in)
if err != nil {
log.Fatalf("Read failed!\n%v\n", err)
}
tmpl, err := createTemplate().Parse(string(text))
if err != nil {
log.Fatalf("Line %q: %v\n", string(text), err)
}

if err := tmpl.Execute(out, context); err != nil {
panic(err)
}
out.Write([]byte("\n"))
}

func main() {
Expand Down
40 changes: 40 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"bytes"
"os"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func testTemplate(template string) string {
in := strings.NewReader(template)
var out bytes.Buffer
RunTemplate(in, &out)
return strings.TrimSpace(out.String())
}

func TestGetenv(t *testing.T) {
assert.Empty(t, Getenv("FOOBARBAZ"))
assert.Empty(t, testTemplate(`{{getenv "BLAHBLAHBLAH"}}`))
assert.Equal(t, Getenv("USER"), os.Getenv("USER"))
assert.Equal(t, os.Getenv("USER"), testTemplate(`{{getenv "USER"}}`))
}

func TestBool(t *testing.T) {
assert.False(t, Bool(""))
assert.False(t, Bool("asdf"))
assert.False(t, Bool("1234"))
assert.False(t, Bool("False"))
assert.False(t, Bool("0"))
assert.False(t, Bool("false"))
assert.False(t, Bool("F"))
assert.False(t, Bool("f"))
assert.True(t, Bool("true"))
assert.True(t, Bool("True"))
assert.True(t, Bool("t"))
assert.True(t, Bool("T"))
assert.True(t, Bool("1"))
}

0 comments on commit fc17cc1

Please sign in to comment.