diff --git a/dot_test.go b/dot_test.go index 5a5edd0..6adc7d0 100644 --- a/dot_test.go +++ b/dot_test.go @@ -6,6 +6,7 @@ import ( "gopkg.in/yaml.v3" "os" "testing" + "runtime" ) func isSymlink(path string) bool { @@ -72,6 +73,27 @@ func TestDoCopy(t *testing.T) { toContents, err = os.ReadFile(m.To) assert.Nil(t, err) assert.Equal(t, fromContents, toContents) + + // copies a file with template + m = FileMapping{ + From: "fixtures/gpg-agent.conf.input", + To: "out/gpg-agent.conf", + With: map[string]string { + "PinentryPath": "/foo/bar", + }, + } + + err = m.doCopy() + assert.Nil(t, err) + assert.False(t, isSymlink(m.To)) + + // correct contents + + fromContents, err = os.ReadFile("fixtures/gpg-agent.conf.output") + assert.Nil(t, err) + toContents, err = os.ReadFile(m.To) + assert.Nil(t, err) + assert.Equal(t, fromContents, toContents) } func TestUnmap(t *testing.T) { @@ -341,3 +363,31 @@ func TestGetHomeDir(t *testing.T) { assert.Equal(t, got, want) } } + +func TestEvalTemplateString(t *testing.T) { + cases := map[string]string { + "{{ .v1 }}": "a value", + "{{ .v2 }}": "", + } + env := map[string]string { + "v1": "a value", + } + for templ, want := range cases { + got := evalTemplateString(templ, env) + assert.Equal(t, want, got) + } +} + +func TestEvalTemplate(t *testing.T) { + curOs := runtime.GOOS + with := map[string]string { + "t1": "{{if eq .Os \"" + curOs + "\"}}it works{{end}}", + "t2": "{{if eq .Os \"linux\"}}must not be this{{end}}", + "t3": "{{if eq .Os \"linux\"}}must not be this{{else}}else{{end}}", + } + + res := evalTemplate(with) + assert.Equal(t, "it works", res["t1"]) + assert.Equal(t, "", res["t2"], "") + assert.Equal(t, "else", res["t3"]) +} diff --git a/examples/04-dots-copy-templating.yml b/examples/04-dots-copy-templating.yml new file mode 100644 index 0000000..a84a1d6 --- /dev/null +++ b/examples/04-dots-copy-templating.yml @@ -0,0 +1,20 @@ +# to, os, as + +map: + gitconfig: + to: out/gitconfig + os: all # all, linux, macos + zshrc: + to: out/zshrc + as: copy + os: linux + gpg-agent.conf: + to: out/gpg-agent.conf + as: copy + os: macos + with: + PinentryPath: '{{if eq .Os "darwin"}}/opt/homebrew/bin{{else}}/usr/bin{{end}}' + +opt: + cd: examples/ + diff --git a/examples/gpg-agent.conf b/examples/gpg-agent.conf new file mode 100644 index 0000000..4b338f6 --- /dev/null +++ b/examples/gpg-agent.conf @@ -0,0 +1,4 @@ +default-cache-ttl 1800 +max-cache-ttl 3600 +enable-ssh-support +pinentry-program {{ .PinentryPath }}/pinentry-tty diff --git a/fixtures/gpg-agent.conf.input b/fixtures/gpg-agent.conf.input new file mode 100644 index 0000000..4b338f6 --- /dev/null +++ b/fixtures/gpg-agent.conf.input @@ -0,0 +1,4 @@ +default-cache-ttl 1800 +max-cache-ttl 3600 +enable-ssh-support +pinentry-program {{ .PinentryPath }}/pinentry-tty diff --git a/fixtures/gpg-agent.conf.output b/fixtures/gpg-agent.conf.output new file mode 100644 index 0000000..ce3f3e4 --- /dev/null +++ b/fixtures/gpg-agent.conf.output @@ -0,0 +1,4 @@ +default-cache-ttl 1800 +max-cache-ttl 3600 +enable-ssh-support +pinentry-program /foo/bar/pinentry-tty diff --git a/main.go b/main.go index 19b257b..e58b585 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( goversion "github.com/caarlos0/go-version" "gopkg.in/yaml.v3" + "text/template" ) /* @@ -81,6 +82,7 @@ type FileMapping struct { To string As string Os string + With map[string]string } func (m FileMapping) doLink() error { @@ -92,11 +94,24 @@ func (m FileMapping) doLink() error { } func (m FileMapping) doCopy() error { - fin, err := os.Open(m.From) - if err != nil { - return err + var inReader io.Reader + if len(m.With) > 0 { + for v, m := range m.With { + logger.Printf("%s %s\n", v, m) + } + in, err := os.ReadFile(m.From) + if err != nil { + panic(err) + } + inReader = strings.NewReader(evalTemplateString(string(in), m.With)) + } else { + fin, err := os.Open(m.From) + if err != nil { + return err + } + defer fin.Close() + inReader = fin } - defer fin.Close() fout, err := os.Create(m.To) if err != nil { @@ -104,7 +119,7 @@ func (m FileMapping) doCopy() error { } defer fout.Close() - _, err = io.Copy(fout, fin) + _, err = io.Copy(fout, inReader) if err != nil { return err } @@ -196,6 +211,10 @@ func (dots Dots) validate() []error { } else if isDirectory(mapping.From) && mapping.As == "copy" { errs = append(errs, fmt.Errorf("%s: cannot use copy type with directory", mapping.From)) } + + if mapping.As != "copy" && len(mapping.With) > 0 { + errs = append(errs, fmt.Errorf("%s: templating is only supported in `copy` mode ]", mapping.From)); + } } return errs } @@ -208,6 +227,28 @@ func inferDestination(file string) string { } } +func evalTemplateString(templStr string, env map[string]string) string { + templ, err := template.New("template").Parse(templStr) + if err != nil { + logger.Fatalf("failed creating template from %s, %v", templStr, err) + } + var templOut bytes.Buffer + err = templ.Execute(&templOut, env) + if err != nil { + logger.Fatalf("failed executing template, %v", err) + } + return templOut.String() +} + +func evalTemplate(with map[string]string) map[string]string { + newMap := make(map[string]string, len(with)) + for variable, templ := range with { + env := map[string]string{ "Os": runtime.GOOS } + newMap[variable] = evalTemplateString(templ, env) + } + return newMap +} + func (dots Dots) transform() Dots { opts := dots.Opts mappings := dots.FileMappings @@ -226,6 +267,10 @@ func (dots Dots) transform() Dots { mapping.To = inferDestination(mapping.From) } + if len(mapping.With) > 0 { + mapping.With = evalTemplate(mapping.With) + } + if len(opts.Cd) > 0 { // Cd set: add prefix to From mapping.From = path.Join(opts.Cd, mapping.From)