diff --git a/cmd/liquid/main.go b/cmd/liquid/main.go index 2b59d00..74ec7ae 100644 --- a/cmd/liquid/main.go +++ b/cmd/liquid/main.go @@ -9,10 +9,10 @@ package main import ( - "bytes" + "errors" + "flag" "fmt" "io" - "io/ioutil" "os" "strings" @@ -21,59 +21,79 @@ import ( // for testing var ( - stderr = os.Stderr - stdout io.Writer = os.Stdout - stdin io.Reader = os.Stdin - exit = os.Exit + stderr io.Writer = os.Stderr + stdout io.Writer = os.Stdout + stdin io.Reader = os.Stdin + exit func(int) = os.Exit + env func() []string = os.Environ + bindings map[string]interface{} = map[string]interface{}{} ) func main() { - if err := run(os.Args[1:]); err != nil { - fmt.Fprintln(stderr, err) // nolint: gas - os.Exit(1) - } -} + var err error -func run(args []string) error { - switch { - case len(args) == 0: - buf := new(bytes.Buffer) - if _, err := io.Copy(buf, stdin); err != nil { - return err + cmdLine := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + cmdLine.Usage = func() { + fmt.Fprintf(stderr, "usage: %s [OPTIONS] [FILE]\n", cmdLine.Name()) + fmt.Fprint(stderr, "\nOPTIONS\n") + cmdLine.PrintDefaults() + } + + var bindEnvs bool + cmdLine.BoolVar(&bindEnvs, "env", false, "bind environment variables") + + err = cmdLine.Parse(os.Args[1:]) + if err != nil { + if err == flag.ErrHelp { + exit(0) + return } - return render(buf.Bytes(), "") - case args[0] == "-h" || args[0] == "--help": - usage() - case strings.HasPrefix(args[0], "-"): - // undefined flag - usage() + fmt.Fprintln(stderr, err) exit(1) - case len(args) == 1: - s, err := ioutil.ReadFile(args[0]) - if err != nil { - return err + return + } + + if bindEnvs { + for _, e := range env() { + pair := strings.SplitN(e, "=", 2) + bindings[pair[0]] = pair[1] } - return render(s, args[0]) + } + + args := cmdLine.Args() + switch len(args) { + case 0: + // use stdin + case 1: + stdin, err = os.Open(args[0]) default: - usage() + err = errors.New("too many arguments") + } + + if err == nil { + err = render() + } + + if err != nil { + fmt.Fprintln(stderr, err) exit(1) } - return nil } -func render(b []byte, filename string) (err error) { - tpl, err := liquid.NewEngine().ParseTemplate(b) +func render() error { + buf, err := io.ReadAll(stdin) if err != nil { return err } - out, err := tpl.Render(map[string]interface{}{}) + + tpl, err := liquid.NewEngine().ParseTemplate(buf) + if err != nil { + return err + } + out, err := tpl.Render(bindings) if err != nil { return err } _, err = stdout.Write(out) return err } - -func usage() { - fmt.Fprintf(stdout, "usage: %s [FILE]\n", os.Args[0]) // nolint: gas -} diff --git a/cmd/liquid/main_test.go b/cmd/liquid/main_test.go index 15caf6a..266aade 100644 --- a/cmd/liquid/main_test.go +++ b/cmd/liquid/main_test.go @@ -2,47 +2,112 @@ package main import ( "bytes" + "os" "testing" "github.com/stretchr/testify/require" ) func TestMain(t *testing.T) { - exit = func(n int) { t.Fatalf("exit called") } + oldArgs := os.Args + + defer func() { + os.Args = oldArgs + stderr = os.Stderr + stdout = os.Stdout + stdin = os.Stdin + exit = os.Exit + env = os.Environ + bindings = map[string]interface{}{} + }() + + exit = func(n int) { + t.Fatalf("exit called") + } + + os.Args = []string{"liquid"} // stdin src := `{{ "Hello World" | downcase | split: " " | first | append: "!"}}` - buf := new(bytes.Buffer) + buf := &bytes.Buffer{} stdin = bytes.NewBufferString(src) stdout = buf - require.NoError(t, run([]string{})) + main() require.Equal(t, "hello!", buf.String()) - // filename - buf = new(bytes.Buffer) - stdin = bytes.NewBufferString("") + // environment binding + var envCalled bool + env = func() []string { + envCalled = true + return []string{"TARGET=World"} + } + src = `Hello, {{ TARGET }}!` + // without -e + stdin = bytes.NewBufferString(src) + buf = &bytes.Buffer{} stdout = buf - require.NoError(t, run([]string{"testdata/source.txt"})) + os.Args = []string{"liquid"} + main() + require.False(t, envCalled) + require.Equal(t, "Hello, !", buf.String()) + // with -e + stdin = bytes.NewBufferString(src) + buf = &bytes.Buffer{} + stdout = buf + os.Args = []string{"liquid", "--env"} + main() + require.True(t, envCalled) + require.Equal(t, "Hello, World!", buf.String()) + + // filename + stdin = os.Stdin + buf = &bytes.Buffer{} + stdout = buf + os.Args = []string{"liquid", "testdata/source.txt"} + main() require.Contains(t, buf.String(), "file system") - // missing file - require.Error(t, run([]string{"testdata/missing_file"})) + // following tests test the exit code + var exitCalled bool + exitCode := 0 + exit = func(n int) { exitCalled = true; exitCode = n } + os.Args = []string{"liquid", "testdata/source.txt"} + main() + require.Equal(t, 0, exitCode) + + exitCode = 0 + // missing file + buf = &bytes.Buffer{} + stderr = buf + os.Args = []string{"liquid", "testdata/missing_file"} + main() + require.Equal(t, 1, exitCode) + require.Contains(t, buf.String(), "no such") + + exitCalled = false // --help - buf = new(bytes.Buffer) - stdout = buf - require.NoError(t, run([]string{"--help"})) + buf = &bytes.Buffer{} + stderr = buf + os.Args = []string{"liquid", "--help"} + main() require.Contains(t, buf.String(), "usage:") + require.True(t, exitCalled) + require.Equal(t, 0, exitCode) // --undefined-flag - exitCode := 0 - exit = func(n int) { exitCode = n } - require.NoError(t, run([]string{"--undefined-flag"})) + buf = &bytes.Buffer{} + stderr = buf + os.Args = []string{"liquid", "--undefined-flag"} + main() require.Equal(t, 1, exitCode) + require.Contains(t, buf.String(), "defined") // multiple args - exitCode = 0 - exit = func(n int) { exitCode = n } - require.NoError(t, run([]string{"file1", "file2"})) + os.Args = []string{"liquid", "testdata/source.txt", "file2"} + buf = &bytes.Buffer{} + stderr = buf + main() + require.Contains(t, buf.String(), "too many") require.Equal(t, 1, exitCode) }