Merge pull request #16 from breml/template

Add template functionality
This commit is contained in:
Alex Yatskov 2017-06-30 09:10:05 -07:00 committed by GitHub
commit 92b243ad1f
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. 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 Sometimes, just linking a config file is not enough, because the content of the configuration file needs to be adapted
defined in an array called `cmds`, split into an item per each command line argument. All of the commands are executed to the target and we do not want to maintain several different versions of the same file. For such use cases, Homemaker
with `dest` as the working directory (as mentioned previously, this defaults to your home directory). If any command supports templates. The configuration syntax for templates is the same as for links.
returns a nonzero exit code, Homemaker will display an error message and prompt the user to determine if it should
```
[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 *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. after the linking phase, you can use the `cmdspre` and `cmdspost` arrays which have similar behavior.

View File

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

View File

@ -37,6 +37,7 @@ type task struct {
Envs [][]string Envs [][]string
Accepts [][]string Accepts [][]string
Rejects [][]string Rejects [][]string
Templates [][]string
} }
func (t *task) deps(conf *config) []string { func (t *task) deps(conf *config) []string {
@ -87,6 +88,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 { if conf.flags&flagNoCmds == 0 {
for _, currCmd := range t.CmdsPost { for _, currCmd := range t.CmdsPost {
if err := processCmd(currCmd, true, conf); err != nil { 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{})
})
}