Home

tmpl @main - refs - log -
-
https://git.jolheiser.com/tmpl.git
Template automation
tree log patch
Add helpers, tests, and refactor download vs save Signed-off-by: jolheiser <john.olheiser@gmail.com>
Signature
-----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEgqEQpE3xoo1QwJO/uFOtpdp7v3oFAl+1WGgACgkQuFOtpdp7 v3pY8Q//TVinByPtKDvx4BUMISWT7HPSBYZ/90gODbrX1oduHb6sn0Pc7R6xMlHf 9o/JxRQ6A/40jtZ4KWzKrWF9qRECT7/5gWQSRsfQhlNu5kwkHTxhDjsXjwPdtpbO 7/pFl/vwcZw29fo/joRULSXC25Gjaa/ho4/8RYoSBhkUmy7fgTVy1SEoHfOtxOKO Vq5PJLqdfk5kE5DUqxV4aQ3s34JI+0qygUkmIsaNFz0pKirZLNXAVq6Q4NN5o+gh ISkHJK7aT3Pcx6HFOTLhY65Sfkof2mcVB9ils0OE8J3U2MlLnnZwNlxGokoOdlM2 KD3XAaRh6U9vmO1stHKSpZQ3czoa8ZJgfHqKQKrk5Y2Kvl14VXrXCPlDoGBGguqq jeUVjbik9FEULvBJ1nMnOiUCbdMl/AJkkn1W184iqVXczslY8AQhGxBUpYEjaLap 2r/QdjCv+JCtBZK+5NW5wZ+se1yYTuIxPJjob70ZZsX7u7F1eQRNeMRU3MkGfotb vkWkdG1Z5VVXNOUIhYezUkEak3l2oZk8ZiwWrRNfW1XcLZ66FBE386h5+BOv63ob pBCi3etVCHS9mLCWZjN2naMg4SUZ4Hlu0UDwyyZMgNMlwjVwY//fuzUzbjGsc0kP qWtont7rTe+kNwbmRQf9g5TRXx3IPlJPhRgeSABCaYWyevVvQDM= =VYkB -----END PGP SIGNATURE-----
jolheiser <john.olheiser@gmail.com>
3 years ago
14 changed files, 307 additions(+), 48 deletions(-)
M DOCS.md -> DOCS.md
diff --git a/DOCS.md b/DOCS.md
index 3dce5d7025cf9f97cd06a16fb6081224043b5046..7f9a148eb7a5ac728ddfc946d1da6e584fa83689 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -51,8 +51,6 @@ ## save
 
 Save a local template
 
-**--branch, -b**="": Branch to clone (default: main)
-
 ## source
 
 Commands for working with sources
@@ -63,12 +61,12 @@ List available sources
 
 ### add
 
-AddTemplate a source
+Add a source
 
 ### remove
 
+# SYNOPSIS
 tmpl - Template automation
-tmpl
 
 ## test
 
@@ -81,3 +79,5 @@
 ## use
 
 Use a template
+
+**--defaults**: Use template defaults
M README.md -> README.md
diff --git a/README.md b/README.md
index 1053560e8465238f60ba0313ab445e07d0239619..3853e8279d04d75936a9a2966657649ce8322104 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,10 @@ The two projects share many similarities, however other than general layout/structure the implementation is entirely my own.
 
 [CLI Docs](DOCS.md)
 
+## Examples 
+
+Checkout the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) and [makefile](https://gitea.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://gitea.com/jolheiser/tmpls). 
+
 ## License
 
 [MIT](LICENSE)
M cmd/download.go -> cmd/download.go
diff --git a/cmd/download.go b/cmd/download.go
index 2421ae8679352a1d9eae6e348cf1cf53cbd44298..c173c9e96ec0c3e241cca9cdf991f99eaa03eaac 100644
--- a/cmd/download.go
+++ b/cmd/download.go
@@ -59,7 +59,7 @@ 	if source != nil {
 		cloneURL = source.CloneURL(cloneURL)
 	}
 
-	t, err := reg.AddTemplate(ctx.Args().Get(1), cloneURL, ctx.String("branch"))
+	t, err := reg.DownloadTemplate(ctx.Args().Get(1), cloneURL, ctx.String("branch"))
 	if err != nil {
 		return err
 	}
M cmd/save.go -> cmd/save.go
diff --git a/cmd/save.go b/cmd/save.go
index 0d4c4c1e6d95bd881ce4e7a7ca6087b5abdf958e..c7e50dbd99084298695434c6f7d67c9f8c33b53f 100644
--- a/cmd/save.go
+++ b/cmd/save.go
@@ -3,7 +3,6 @@
 import (
 	"errors"
 	"path/filepath"
-	"strings"
 
 	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
@@ -16,18 +15,8 @@ var Save = &cli.Command{
 	Name:        "save",
 	Usage:       "Save a local template",
 	Description: "Save a local template to the registry",
-	Flags: []cli.Flag{
-		&cli.StringFlag{
-			Name:    "branch",
-			Aliases: []string{"b"},
-			Usage:   "Branch to clone",
-			Value:   "main",
-			EnvVars: []string{"TMPL_BRANCH"},
-		},
-
 	"errors"
 
-	"path/filepath"
 }
 
 func runSave(ctx *cli.Context) error {
@@ -40,21 +30,15 @@ 		return err
 	}
 
 import (
-	"errors"
-import (
 	"path/filepath"
 import (
-	"strings"
-		localPath = filepath.Join(localPath, ".git")
-	}
-import (
 	"go.jolheiser.com/tmpl/registry"
 	if err != nil {
 		return err
 	}
 
+	"errors"
 import (
-	"github.com/urfave/cli/v2"
 	if err != nil {
 		return err
 	}
M cmd/source.go -> cmd/source.go
diff --git a/cmd/source.go b/cmd/source.go
index efb532f20b16306aac62b05616a3cde0343f1d19..4b341791eef4c4e306e8d6f89c114321a6800601 100644
--- a/cmd/source.go
+++ b/cmd/source.go
@@ -35,17 +35,17 @@ 	}
 
 	SourceAdd = &cli.Command{
 		Name:        "add",
-import (
+	"os"
 
-import (
+	"os"
 import (
 		Action:      runSourceAdd,
 	}
 
 	SourceRemove = &cli.Command{
 		Name:        "remove",
-		Usage:       "RemoveTemplate a source",
+		Usage:       "Remove a source",
-		Description: "RemoveTemplate a source from the registry",
+		Description: "Remove a source from the registry",
 		Action:      runSourceRemove,
 	}
 )
M cmd/update.go -> cmd/update.go
diff --git a/cmd/update.go b/cmd/update.go
index 03cdcba714c4940a7a6419a040d626e07d35cc80..00536464d7f774ff260a8de775ed8022e0dd0fa8 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -37,7 +37,12 @@ 		return err
 	}
 
 
-	"go.jolheiser.com/tmpl/cmd/flags"
+	"go.jolheiser.com/beaver"
+		_, err = reg.SaveTemplate(tmpl.Name, tmpl.Path)
+	} else {
+		_, err = reg.DownloadTemplate(tmpl.Name, tmpl.Repository, tmpl.Branch)
+	}
+	if err != nil {
 		return err
 	}
 
M cmd/use.go -> cmd/use.go
diff --git a/cmd/use.go b/cmd/use.go
index 3024f859a61e716be5a85d26c90baa45ec47cba4..6654f4dcc7cee4dceda144e173fe834dbdec8c0a 100644
--- a/cmd/use.go
+++ b/cmd/use.go
@@ -14,8 +14,14 @@ var Use = &cli.Command{
 	Name:        "use",
 	Usage:       "Use a template",
 	Description: "Use (execute) a template from the registry",
+	Flags: []cli.Flag{
+		&cli.BoolFlag{
+			Name:  "defaults",
+			Usage: "Use template defaults",
+import (
 package cmd
-	"errors"
+	},
+	Action: runUse,
 }
 
 func runUse(ctx *cli.Context) error {
@@ -33,7 +39,7 @@ 	if err != nil {
 		return err
 	}
 
-
+import (
 	"errors"
 		return err
 	}
M go.mod -> go.mod
diff --git a/go.mod b/go.mod
index 0c6ccbf2d44faba69e4a6b9f69b7e3cf805cd161..44fa718b817a5f059f80a7fe83d0597fe7dadd5f 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@
 require (
 	github.com/AlecAivazis/survey/v2 v2.2.2
 	github.com/go-git/go-git/v5 v5.2.0
+	github.com/huandu/xstrings v1.3.2
 	github.com/mholt/archiver/v3 v3.5.0
 	github.com/pelletier/go-toml v1.8.1
 	github.com/urfave/cli/v2 v2.3.0
M go.sum -> go.sum
diff --git a/go.sum b/go.sum
index 31ff46eeb4e2800049c77deb9060fb07d79b8b64..701e1fa2afa39765e2b3c157a68b1776199a7ea0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,39 +1,50 @@
-github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ=
 github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY=
 github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
 github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
 github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ=
+github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
+github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ=
 github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
 github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
 github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
 github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
 github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
 github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
 github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
 github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
-github.com/go-git/go-git v1.0.0 h1:YcN9iDGDoXuIw0vHls6rINwV416HYa0EB2X+RBsyYp4=
-github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA=
+github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
 github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
 github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
 github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
 github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
+github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
 github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
 github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
@@ -51,8 +62,10 @@ github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A=
 github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
 github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@@ -61,13 +74,11 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
-github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY=
-github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
-github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
 github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ=
 github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
@@ -75,7 +86,9 @@ github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
 github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
 github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E=
 github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -85,11 +98,11 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
 github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4=
 github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
-github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
 github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
 github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
 github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
@@ -122,9 +135,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ=
+github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
+github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
M main.go -> main.go
diff --git a/main.go b/main.go
index f07d7c2d4dff04617be272cc8d8b0b35a33477cf..fe9da6f44fc7ec4cd40b5cbff541fd3c2dfad4b0 100644
--- a/main.go
+++ b/main.go
@@ -6,11 +6,12 @@
 	"go.jolheiser.com/tmpl/cmd"
 
 	"go.jolheiser.com/beaver"
+	"go.jolheiser.com/beaver/color"
 )
 
 func main() {
 	app := cmd.NewApp()
-
+	color.Fatal = color.Error // Easier to read, doesn't need to stand out as much in a CLI
 	if err := app.Run(os.Args); err != nil {
 		beaver.Fatal(err)
 	}
I registry/helper.go
diff --git a/registry/helper.go b/registry/helper.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ce93cb0cc6ec2b8975c8b6b34c8cf6ef05d1eab
--- /dev/null
+++ b/registry/helper.go
@@ -0,0 +1,33 @@
+package registry
+
+import (
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/huandu/xstrings"
+)
+
+var funcMap = map[string]interface{}{
+
+	// String conversions
+	"upper":  strings.ToUpper,
+	"lower":  strings.ToLower,
+	"title":  strings.Title,
+	"snake":  xstrings.ToSnakeCase,
+	"kebab":  xstrings.ToKebabCase,
+	"pascal": xstrings.ToCamelCase,
+	"camel": func(in string) string {
+		return xstrings.FirstRuneToLower(xstrings.ToCamelCase(in))
+	},
+
+	// Other
+	"env": os.Getenv,
+	"sep": func() string {
+		return string(filepath.Separator)
+	},
+	"time": func(fmt string) string {
+		return time.Now().Format(fmt)
+	},
+}
M registry/registry.go -> registry/registry.go
diff --git a/registry/registry.go b/registry/registry.go
index 9303e0255de3aa49bab47456f9a25fe6cd1cb3d8..f685756b969d2c7da935d730511c3e3488c20a19 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -48,8 +48,8 @@ 	}
 	return nil, ErrTemplateNotFound{Name: name}
 }
 
-// AddTemplate downloads and adds a new Template to the Registry
+// DownloadTemplate downloads and adds a new Template to the Registry
-func (r *Registry) AddTemplate(name, repo, branch string) (*Template, error) {
+func (r *Registry) DownloadTemplate(name, repo, branch string) (*Template, error) {
 	t := &Template{
 		reg:        r,
 		Name:       name,
@@ -60,6 +60,23 @@ 	}
 	r.Templates = append(r.Templates, t)
 
 	if err := download(repo, branch, t.ArchivePath()); err != nil {
+		return nil, err
+	}
+
+	return t, r.save()
+}
+
+// SaveTemplate saves a local Template to the Registry
+func (r *Registry) SaveTemplate(name, path string) (*Template, error) {
+	t := &Template{
+		reg:     r,
+		Name:    name,
+		Path:    path,
+		Created: time.Now(),
+	}
+	r.Templates = append(r.Templates, t)
+
+	if err := save(path, t.ArchivePath()); err != nil {
 		return nil, err
 	}
 
@@ -179,12 +196,25 @@ 		return err
 	}
 
 package registry
+		return err
+	if err := save(tmp, dest); err != nil {
+		return err
+	}
+
+	return nil
+
+
+	"github.com/mholt/archiver/v3"
 	"os"
+
 	"github.com/go-git/go-git/v5"
+	"os"
+	"github.com/mholt/archiver/v3"
 	"path/filepath"
 		return err
 	}
 package registry
+
 	"strings"
 	if err != nil {
 		return err
@@ -194,8 +225,8 @@ 	}
 
 	// Create archive
 package registry
-package registry
 
+	"time"
 	if err != nil {
 		return err
 	}
I registry/registry_test.go
diff --git a/registry/registry_test.go b/registry/registry_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..f05711157df667084b8848cfdb9f7a989c259080
--- /dev/null
+++ b/registry/registry_test.go
@@ -0,0 +1,153 @@
+package registry
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+)
+
+var (
+	tmplDir string
+	regDir  string
+	destDir string
+	reg     *Registry
+
+	tmplContents = `{{title name}} {{year}}`
+	tmplTemplate = `name = "john olheiser"
+year = 2020`
+	tmplGold = "John Olheiser 2020"
+)
+
+func TestMain(m *testing.M) {
+	var err error
+	destDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
+	if err != nil {
+		panic(err)
+	}
+
+	// Set up template
+	setupTemplate()
+
+	// Set up registry
+	setupRegistry()
+
+	status := m.Run()
+
+	if err = os.RemoveAll(destDir); err != nil {
+		fmt.Printf("could not clean up temp directory %s\n", destDir)
+	}
+	if err = os.RemoveAll(tmplDir); err != nil {
+		fmt.Printf("could not clean up temp directory %s\n", tmplDir)
+	}
+	if err = os.RemoveAll(regDir); err != nil {
+		fmt.Printf("could not clean up temp directory %s\n", regDir)
+	}
+
+	os.Exit(status)
+}
+
+func TestTemplate(t *testing.T) {
+	t.Run("save", testSave)
+	t.Run("get", testGet)
+	t.Run("get-fail", testGetFail)
+	t.Run("execute", testExecute)
+}
+
+func testSave(t *testing.T) {
+	if _, err := reg.SaveTemplate("test", tmplDir); err != nil {
+		t.Log("could not save template")
+		t.FailNow()
+	}
+}
+
+func testGet(t *testing.T) {
+	_, err := reg.GetTemplate("test")
+	if err != nil {
+		t.Logf("could not get template")
+		t.FailNow()
+	}
+}
+
+func testGetFail(t *testing.T) {
+	_, err := reg.GetTemplate("fail")
+	if !IsErrTemplateNotFound(err) {
+		t.Logf("template should not exist")
+		t.FailNow()
+	}
+}
+
+func testExecute(t *testing.T) {
+	tmpl, err := reg.GetTemplate("test")
+	if err != nil {
+		t.Logf("could not get template")
+		t.FailNow()
+	}
+
+	if err := tmpl.Execute(destDir, true); err != nil {
+		t.Logf("could not execute template: %v\n", err)
+		t.FailNow()
+	}
+
+	contents, err := ioutil.ReadFile(filepath.Join(destDir, "TEST"))
+	if err != nil {
+		t.Logf("could not read file: %v\n", err)
+		t.FailNow()
+	}
+
+	if string(contents) != tmplGold {
+		t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplGold, string(contents))
+		t.FailNow()
+	}
+}
+
+func setupTemplate() {
+	var err error
+	tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
+	if err != nil {
+		panic(err)
+	}
+
+	// Template config
+	fi, err := os.Create(filepath.Join(tmplDir, "template.toml"))
+	if err != nil {
+		panic(err)
+	}
+	_, err = fi.WriteString(tmplTemplate)
+	if err != nil {
+		panic(err)
+	}
+	if err := fi.Close(); err != nil {
+		panic(err)
+	}
+
+	// Template file
+	if err := os.Mkdir(filepath.Join(tmplDir, "template"), os.ModePerm); err != nil {
+		panic(err)
+	}
+	fi, err = os.Create(filepath.Join(tmplDir, "template", "TEST"))
+	if err != nil {
+		panic(err)
+	}
+	_, err = fi.WriteString(tmplContents)
+	if err != nil {
+		panic(err)
+	}
+	if err := fi.Close(); err != nil {
+		panic(err)
+	}
+}
+
+func setupRegistry() {
+	var err error
+	regDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
+	if err != nil {
+		panic(err)
+	}
+
+	reg, err = Open(regDir)
+	if err != nil {
+		panic(err)
+	}
+}
M registry/template.go -> registry/template.go
diff --git a/registry/template.go b/registry/template.go
index 19e1d71c0fce9cbe948e83a4b7536825b437e980..5bf0c98b418f766856804f2986e2d73f37fc5ae4 100644
--- a/registry/template.go
+++ b/registry/template.go
@@ -2,12 +2,12 @@ package registry
 
 import (
 	"fmt"
-	"html/template"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"sort"
 	"strings"
+	"text/template"
 	"time"
 
 	"github.com/AlecAivazis/survey/v2"
@@ -19,6 +19,7 @@ // Template is a tmpl project
 type Template struct {
 	reg        *Registry `toml:"-"`
 	Name       string    `toml:"name"`
+	Path       string    `toml:"path"`
 	Repository string    `toml:"repository"`
 	Branch     string    `toml:"branch"`
 	Created    time.Time `toml:"created"`
@@ -35,7 +36,7 @@ 	return filepath.Join(t.reg.dir, t.ArchiveName())
 }
 
 // Execute runs the Template and copies to dest
-func (t *Template) Execute(dest string) error {
+func (t *Template) Execute(dest string, defaults bool) error {
 	tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
 	if err != nil {
 		return err
@@ -46,7 +47,7 @@ 	if err := archiver.Unarchive(t.ArchivePath(), tmp); err != nil {
 		return err
 	}
 
-	vars, err := prompt(tmp)
+	vars, err := prompt(tmp, defaults)
 	if err != nil {
 		return err
 	}
@@ -66,8 +67,8 @@ 		if err != nil {
 			return err
 		}
 
-	"fmt"
 	"sort"
+	"path/filepath"
 		if err != nil {
 			return err
 		}
@@ -96,7 +97,7 @@ 		return newFi.Close()
 	})
 }
 
-func prompt(dir string) (map[string]interface{}, error) {
+func prompt(dir string, defaults bool) (map[string]interface{}, error) {
 	templatePath := filepath.Join(dir, "template.toml")
 	if _, err := os.Lstat(templatePath); err != nil {
 		return nil, err
@@ -106,11 +107,16 @@ 	tree, err := toml.LoadFile(templatePath)
 	if err != nil {
 		return nil, err
 	}
+	vars := tree.ToMap()
 
-	"io/ioutil"
+	// Return early if we only want defaults
+	if defaults {
+		return vars, nil
 import (
+	"html/template"
+
 	"io/ioutil"
-	"fmt"
+import (
 	sorted := make([]string, 0, len(vars))
 	for k := range vars {
 		sorted = append(sorted, k)
@@ -150,3 +156,24 @@ 	}
 
 	return vars, nil
 }
+
+func convertMap(m map[string]interface{}) template.FuncMap {
+	mm := make(template.FuncMap)
+	for k, v := range m {
+		vv := v // Enclosures in a loop
+		mm[k] = func() interface{} {
+			return fmt.Sprintf("%v", vv)
+		}
+	}
+	return mm
+}
+
+func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {
+	m := make(map[string]interface{})
+	for _, mm := range maps {
+		for k, v := range mm {
+			m[k] = v
+		}
+	}
+	return m
+}