Add template functionality

This commit is contained in:
Lucas Bremgartner 2017-06-29 15:40:34 +02:00
parent 87f1a4e15c
commit a4b355ffbd
4 changed files with 173 additions and 12 deletions

View File

@ -223,10 +223,34 @@ shown below:
Homemaker will process the dependency tasks before processing the task itself.
In addition to creating links, Homemaker is capable of executing commands on a per-task basis. Commands should be
defined in an array called `cmds`, split into an item per each command line argument. All of the commands are executed
with `dest` as the working directory (as mentioned previously, this defaults to your home directory). If any command
returns a nonzero exit code, Homemaker will display an error message and prompt the user to determine if it should
Sometimes, just linking a config file is not enough, because the content of the configuration file needs to be adapted
to the target and we do not want to maintain several different versions of the same file. For such use cases, Homemaker
supports templates. The configuration syntax for templates is the same as for links.
```
[tasks.template]
templates = [
[".gitconfig"]
]
```
In the template file, the [go templating systax](https://godoc.org/text/template) is used for the customization of the
config file. With the `.Env` prefix, all environment variables are available. Template example:
```
[user]
name = John Doe
{{ if eq .Env.USER "john" }}
email = john@doe.me
{{ else }}
email = john.doe@work.com
{{ end }}
```
In addition to creating links and processing templates, Homemaker is capable of executing commands on a per-task basis.
Commands should be defined in an array called `cmds`, split into an item per each command line argument. All of the commands
are executed with `dest` as the working directory (as mentioned previously, this defaults to your home directory). If any
command returns a nonzero exit code, Homemaker will display an error message and prompt the user to determine if it should
*abort*, *retry*, or *cancel*. Additionally, if you must have explicit control of whether commands execute before or
after the linking phase, you can use the `cmdspre` and `cmdspost` arrays which have similar behavior.

View File

@ -36,6 +36,7 @@ const (
flagVerbose
flagNoCmds
flagNoLinks
flagNoTemplates
flagNoMacro
flagUnlink = flagNoCmds | (1 << iota)
)
@ -55,6 +56,7 @@ func main() {
verbose := flag.Bool("verbose", false, "verbose output")
nocmds := flag.Bool("nocmds", false, "don't execute commands")
nolinks := flag.Bool("nolinks", false, "don't create links")
notemplates := flag.Bool("notemplates", false, "don't process templates")
variant := flag.String("variant", "", "execution variant for tasks and macros")
unlink := flag.Bool("unlink", false, "remove existing links instead of creating them")
@ -77,6 +79,9 @@ func main() {
if *nolinks {
flags |= flagNoLinks
}
if *notemplates {
flags |= flagNoTemplates
}
if *unlink {
flags |= flagUnlink
}

25
task.go
View File

@ -28,14 +28,15 @@ import (
)
type task struct {
Deps []string
Links [][]string
CmdsPre [][]string
Cmds [][]string
CmdsPost [][]string
Envs [][]string
Accepts [][]string
Rejects [][]string
Deps []string
Links [][]string
CmdsPre [][]string
Cmds [][]string
CmdsPost [][]string
Envs [][]string
Accepts [][]string
Rejects [][]string
Templates [][]string
}
func (t *task) deps(conf *config) []string {
@ -85,6 +86,14 @@ func (t *task) process(conf *config) error {
}
}
if conf.flags&flagNoTemplates == 0 {
for _, currTmpl := range t.Templates {
if err := processTemplate(currTmpl, conf); err != nil {
return err
}
}
}
if conf.flags&flagNoCmds == 0 {
for _, currCmd := range t.CmdsPost {
if err := processCmd(currCmd, true, conf); err != nil {

123
template.go Normal file
View File

@ -0,0 +1,123 @@
/*
* Copyright (c) 2015 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>, Lucas Bremgartner <lucas@bremis.ch>
*
* 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.
*/
package main
import (
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"text/template"
)
type context struct {
}
func (c *context) Env() map[string]string {
env := make(map[string]string)
for _, i := range os.Environ() {
sep := strings.Index(i, "=")
env[i[0:sep]] = i[sep+1:]
}
return env
}
func parseTemplate(params []string) (srcPath, dstPath string, mode os.FileMode, err error) {
length := len(params)
if length < 1 || length > 3 {
err = fmt.Errorf("invalid template statement")
return
}
if length > 2 {
var parsed uint64
parsed, err = strconv.ParseUint(params[2], 0, 64)
if err != nil {
return
}
mode = os.FileMode(parsed)
} else {
mode = 0755
}
dstPath = os.ExpandEnv(params[0])
srcPath = dstPath
if length > 1 {
srcPath = os.ExpandEnv(params[1])
}
return
}
func processTemplate(params []string, conf *config) (err error) {
srcPath, dstPath, mode, err := parseTemplate(params)
if err != nil {
return err
}
srcPathAbs := srcPath
if !path.IsAbs(srcPathAbs) {
srcPathAbs = path.Join(conf.srcDir, srcPath)
}
dstPathAbs := dstPath
if !path.IsAbs(dstPathAbs) {
dstPathAbs = path.Join(conf.dstDir, dstPath)
}
if _, err = os.Stat(srcPathAbs); os.IsNotExist(err) {
return fmt.Errorf("source path %s does not exist in filesystem", srcPathAbs)
}
if err = try(func() error { return createPath(dstPathAbs, conf.flags, mode) }); err != nil {
return err
}
if err = try(func() error { return cleanPath(dstPathAbs, conf.flags) }); err != nil {
return err
}
if conf.flags&flagVerbose != 0 {
log.Printf("process template %s to %s", srcPathAbs, dstPathAbs)
}
t, err := template.New(srcPath).ParseFiles(srcPathAbs)
if err != nil {
return err
}
f, err := os.Create(dstPathAbs)
if err != nil {
return err
}
defer func() {
err = f.Close()
}()
return try(func() error {
return t.Execute(f, &context{})
})
}