spry/main.go

276 lines
6.3 KiB
Go

package main
import (
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"text/template"
"time"
sprig "github.com/Masterminds/sprig/v3"
s "gitlab.com/CRThaze/sugar"
htl "gitlab.com/CRThaze/helm-tmpl-lang"
)
var Version string = "0.0.0"
type valFileSlice []string
func (v *valFileSlice) String() string {
return fmt.Sprintf("%v", *v)
}
func (v *valFileSlice) Set(value string) error {
*v = append(*v, value)
return nil
}
type valEnt struct {
address []string
v string
}
type valSlice []valEnt
func (v *valSlice) String() string {
return fmt.Sprintf("%v", *v)
}
func (v *valSlice) Set(value string) error {
pieces := strings.Split(value, "=")
if len(pieces) < 2 {
return fmt.Errorf("Invalid value setting.")
}
*v = append(*v, valEnt{
strings.Split(pieces[0], "."),
pieces[1],
})
return nil
}
var flags struct {
valuesFiles valFileSlice
values valSlice
outputDelimiter string
helm bool
helmStrict bool
helmDNSFunc bool
helmLint bool
helmNestValues bool
outputFilenamePrefix string
outputFilenameSTDERR bool
outputDelimiterSTDERR bool
}
func init() {
flag.Var(&flags.values, "set", "Set a specific value (foo.bar=spam)")
flag.Var(&flags.valuesFiles, "f", "Specify a values file (JSON or YAML)")
flag.StringVar(
&flags.outputDelimiter,
"d", "---",
"Output delimiter between template files rendered",
)
flag.BoolVar(
&flags.helm,
"helm", false,
"Use the Helm Templating Language (HTL) (a superset of Sprig).",
)
flag.BoolVar(
&flags.helmStrict,
"helmStrict", false,
"When using HTL, use strict rendering.",
)
flag.BoolVar(
&flags.helmDNSFunc,
"helmDNS", true,
"When using HTL, support DNS resolution.",
)
flag.BoolVar(
&flags.helmLint,
"helmLint", false,
"When using HTL, enable the Linting Mode.",
)
flag.BoolVar(
&flags.helmNestValues,
"helmNestValues", true,
"When using HTL, Nest provided values under '.Values'",
)
flag.StringVar(
&flags.outputFilenamePrefix,
"fp", "# ",
"Prefix for the filename in the output.",
)
flag.BoolVar(
&flags.outputDelimiterSTDERR,
"delimErr", false,
"Whether to print output delimiter to STDERR.",
)
flag.BoolVar(
&flags.outputFilenameSTDERR,
"filenameErr", true,
"Whether to print filename to STDERR.",
)
shortVersion := flag.Bool(
"v", false,
"Print the version and exit.",
)
longVersion := flag.Bool(
"version", false,
"Print the version and exit.",
)
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s [Options] [Template Files]:\n", os.Args[0])
flag.PrintDefaults()
fmt.Fprintf(flag.CommandLine.Output(), "\n")
fmt.Fprintf(
flag.CommandLine.Output(),
"Template Files can also be directories to recurse for templates.\n",
)
fmt.Fprintf(flag.CommandLine.Output(), "\n")
fmt.Fprintf(
flag.CommandLine.Output(),
"If no template files are provided it will attempt read from STDIN.\n",
)
fmt.Fprintf(
flag.CommandLine.Output(),
"If no input is available from STDIN it will print this usage message instead.\n",
)
fmt.Fprintf(
flag.CommandLine.Output(),
"Pass only '-' to Template Files to force waiting for input.\n",
)
fmt.Fprintf(flag.CommandLine.Output(), "\n")
fmt.Fprintf(
flag.CommandLine.Output(),
"Helm Mode: use the Helm Templating Language/Engine\n"+
"with the following caveats:\n"+
"\t- 'lookup' function unavailable.\n",
)
fmt.Fprintf(flag.CommandLine.Output(), "\n")
}
flag.Parse()
if *shortVersion || *longVersion {
fmt.Fprintf(
flag.CommandLine.Output(),
"%s - Version %s\n\n", os.Args[0], Version,
)
os.Exit(0)
}
}
func main() {
var err error
var ht *htl.Templater
lo := &htl.LangOpts{
Strict: flags.helmStrict,
LintMode: flags.helmLint,
EnableDNS: flags.helmDNSFunc,
}
tpl := template.New("base").Funcs(sprig.FuncMap())
values := mergeValues()
if len(flag.Args()) == 0 {
// With no provided arguments read from STDIN if anything is immediately available through it.
// otherwise print the usage and exit.
ch := make(chan struct{})
go func() {
select {
case <-ch:
return
case <-time.After(100 * time.Millisecond):
flag.Usage()
os.Exit(2)
}
}()
var data []byte
data, err = io.ReadAll(os.Stdin)
// Tell the waiting routine that we got input on STDIN.
ch <- struct{}{}
s.CheckExit(err)
if flags.helm {
ht = htl.NewTemplaterWithBytes(data, nil)
fmt.Fprintf(os.Stderr, "WARN: Helm Compatibility only partially implemented.\n")
tpl, err = tpl.Parse(string(data))
} else {
tpl, err = tpl.Parse(string(data))
}
s.CheckExit(err)
} else if len(flag.Args()) == 1 && flag.Args()[0] == "-" {
// If only a single non-flag argument is provided and it's a -, wait for STDIN indefinitely.
var data []byte
data, err = io.ReadAll(os.Stdin)
s.CheckExit(err)
if flags.helm {
ht = htl.NewTemplaterWithBytes(data, lo)
} else {
tpl, err = tpl.Parse(string(data))
}
s.CheckExit(err)
} else {
// Otherwise use the provided tplFiles/directories for templates to render.
tplFiles := expandFiles(flag.Args())
if flags.helm {
ht, err = htl.NewTemplaterWithFiles(tplFiles, lo)
s.CheckExit(err)
rendered, err := ht.Render(values)
s.CheckExit(err)
first := true
for filename, content := range rendered {
if first {
first = false
} else {
out := os.Stdout
if flags.outputDelimiterSTDERR {
out = os.Stderr
}
fmt.Fprintf(out, "%s\n", flags.outputDelimiter)
out = os.Stdout
if flags.outputFilenameSTDERR {
out = os.Stderr
}
fmt.Fprintf(out, "%s%s\n", flags.outputFilenamePrefix, filename)
}
fmt.Fprint(os.Stdout, content)
}
} else {
tpl, err = tpl.ParseFiles(tplFiles...)
s.CheckExit(err)
for i, file := range tplFiles {
if i > 0 {
out := os.Stdout
if flags.outputDelimiterSTDERR {
out = os.Stderr
}
fmt.Fprintf(out, "%s\n", flags.outputDelimiter)
out = os.Stdout
if flags.outputFilenameSTDERR {
out = os.Stderr
}
fmt.Fprintf(out, "%s%s\n", flags.outputFilenamePrefix, file)
}
s.CheckExit(tpl.ExecuteTemplate(os.Stdout, filepath.Base(file), values))
}
}
return
}
if ht != nil {
rendered, err := ht.Render(values)
s.CheckExit(err)
fmt.Fprintln(os.Stdout, rendered)
} else {
s.CheckExit(tpl.Execute(os.Stdout, values))
}
}