Compare commits
11 Commits
0066db5487
...
0682a0d249
Author | SHA1 | Date | |
---|---|---|---|
0682a0d249 | |||
59fadc5961 | |||
8c887ca0e7 | |||
61a139d47f | |||
|
0a90c2e21c | ||
|
edb42a0e3b | ||
|
72ff78498b | ||
|
ddbd7c428a | ||
|
d4087be780 | ||
|
0ba821eb7e | ||
|
8d703e95ae |
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,6 +4,7 @@
|
|||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
homemaker
|
homemaker
|
||||||
|
build
|
||||||
|
|
||||||
# Test binary, build with `go test -c`
|
# Test binary, build with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
50
Makefile
Normal file
50
Makefile
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
appname := homemaker
|
||||||
|
sources := $(wildcard *.go)
|
||||||
|
|
||||||
|
build = GOOS=$(1) GOARCH=$(2) go build -o build/$(appname)$(3)
|
||||||
|
tar = cd build && tar -cvzf $(appname)_$(1)_$(2).tar.gz $(appname)$(3) && rm $(appname)$(3)
|
||||||
|
zip = cd build && zip $(appname)_$(1)_$(2).zip $(appname)$(3) && rm $(appname)$(3)
|
||||||
|
|
||||||
|
.PHONY: all windows darwin linux clean
|
||||||
|
|
||||||
|
all: windows darwin linux
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf build/
|
||||||
|
|
||||||
|
# linux builds
|
||||||
|
linux: build/$(appname)_linux_arm.tar.gz build/$(appname)_linux_arm64.tar.gz build/$(appname)_linux_386.tar.gz build/$(appname)_linux_amd64.tar.gz
|
||||||
|
|
||||||
|
build/$(appname)_linux_386.tar.gz: $(sources)
|
||||||
|
$(call build,linux,386,)
|
||||||
|
$(call tar,linux,386)
|
||||||
|
|
||||||
|
build/$(appname)_linux_amd64.tar.gz: $(sources)
|
||||||
|
$(call build,linux,amd64,)
|
||||||
|
$(call tar,linux,amd64)
|
||||||
|
|
||||||
|
build/$(appname)_linux_arm.tar.gz: $(sources)
|
||||||
|
$(call build,linux,arm,)
|
||||||
|
$(call tar,linux,arm)
|
||||||
|
|
||||||
|
build/$(appname)_linux_arm64.tar.gz: $(sources)
|
||||||
|
$(call build,linux,arm64,)
|
||||||
|
$(call tar,linux,arm64)
|
||||||
|
|
||||||
|
# darwin builds
|
||||||
|
darwin: build/$(appname)_darwin_amd64.tar.gz
|
||||||
|
|
||||||
|
build/$(appname)_darwin_amd64.tar.gz: $(sources)
|
||||||
|
$(call build,darwin,amd64,)
|
||||||
|
$(call tar,darwin,amd64)
|
||||||
|
|
||||||
|
# windows builds
|
||||||
|
windows: build/$(appname)_windows_386.zip build/$(appname)_windows_amd64.zip
|
||||||
|
|
||||||
|
build/$(appname)_windows_386.zip: $(sources)
|
||||||
|
$(call build,windows,386,.exe)
|
||||||
|
$(call zip,windows,386,.exe)
|
||||||
|
|
||||||
|
build/$(appname)_windows_amd64.zip: $(sources)
|
||||||
|
$(call build,windows,amd64,.exe)
|
||||||
|
$(call zip,windows,amd64,.exe)
|
14
README.md
14
README.md
@ -7,7 +7,7 @@ installation, has no dependencies and makes use of simple configuration file str
|
|||||||
[make](https://en.wikipedia.org/wiki/Make_%28software%29) to generate symlinks and execute system commands to aid in
|
[make](https://en.wikipedia.org/wiki/Make_%28software%29) to generate symlinks and execute system commands to aid in
|
||||||
configuring a new system for use.
|
configuring a new system for use.
|
||||||
|
|
||||||
![](https://foosoft.net/projects/homemaker/img/demo.gif)
|
![](img/demo.gif)
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@ -68,18 +68,10 @@ your needs.
|
|||||||
If you already have the Go environment and toolchain set up, you can get the latest version by running:
|
If you already have the Go environment and toolchain set up, you can get the latest version by running:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ go get github.com/FooSoft/homemaker
|
go install foosoft.net/projects/homemaker@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Otherwise, you can use the pre-built binaries for the platforms below:
|
Otherwise, you can use the [pre-built binaries](https://github.com/FooSoft/homemaker/releases) from the project page.
|
||||||
|
|
||||||
* [homemaker\_darwin\_386.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_darwin_386.tar.gz)
|
|
||||||
* [homemaker\_darwin\_amd64.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_darwin_amd64.tar.gz)
|
|
||||||
* [homemaker\_linux\_386.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_linux_386.tar.gz)
|
|
||||||
* [homemaker\_linux\_amd64.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_linux_amd64.tar.gz)
|
|
||||||
* [homemaker\_linux\_arm.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_linux_arm.tar.gz)
|
|
||||||
* [homemaker\_windows\_386.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_windows_386.tar.gz)
|
|
||||||
* [homemaker\_windows\_amd64.tar.gz](https://foosoft.net/projects/homemaker/dl/homemaker_windows_amd64.tar.gz)
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/naoina/toml"
|
"github.com/naoina/toml"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
@ -50,7 +50,7 @@ func newConfig(filename string) (*config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
conf := &config{handled: make(map[string]bool)}
|
conf := &config{handled: make(map[string]bool)}
|
||||||
switch path.Ext(filename) {
|
switch filepath.Ext(filename) {
|
||||||
case ".json":
|
case ".json":
|
||||||
if err := json.Unmarshal(bytes, &conf); err != nil {
|
if err := json.Unmarshal(bytes, &conf); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
12
go.mod
12
go.mod
@ -1,7 +1,13 @@
|
|||||||
module github.com/FooSoft/homemaker
|
module foosoft.net/projects/homemaker
|
||||||
|
|
||||||
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/naoina/go-stringutil v0.1.0 // indirect
|
|
||||||
github.com/naoina/toml v0.1.1
|
github.com/naoina/toml v0.1.1
|
||||||
gopkg.in/yaml.v2 v2.2.2
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
|
github.com/naoina/go-stringutil v0.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
7
go.sum
7
go.sum
@ -1,7 +1,10 @@
|
|||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
|
github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hzifhks=
|
||||||
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
|
||||||
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
github.com/naoina/toml v0.1.1 h1:PT/lllxVVN0gzzSqSlHEmP8MJB4MY2U7STGxiouV4X8=
|
||||||
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
11
homemaker.go
11
homemaker.go
@ -27,7 +27,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,7 +43,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintf(os.Stderr, "Usage: %s [options] conf src\n", path.Base(os.Args[0]))
|
fmt.Fprintf(os.Stderr, "Usage: %s [options] conf src\n", filepath.Base(os.Args[0]))
|
||||||
fmt.Fprintf(os.Stderr, "https://foosoft.net/projects/homemaker/\n\n")
|
fmt.Fprintf(os.Stderr, "https://foosoft.net/projects/homemaker/\n\n")
|
||||||
fmt.Fprintf(os.Stderr, "Parameters:\n")
|
fmt.Fprintf(os.Stderr, "Parameters:\n")
|
||||||
flag.PrintDefaults()
|
flag.PrintDefaults()
|
||||||
@ -50,7 +51,7 @@ func usage() {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
taskName := flag.String("task", "default", "name of task to execute")
|
taskName := flag.String("task", "default", "name of task to execute")
|
||||||
dstDir := flag.String("dest", os.Getenv("HOME"), "target directory for tasks")
|
dstDir := flag.String("dest", "", "target directory for tasks")
|
||||||
force := flag.Bool("force", true, "create parent directories to target")
|
force := flag.Bool("force", true, "create parent directories to target")
|
||||||
clobber := flag.Bool("clobber", false, "delete files and directories at target")
|
clobber := flag.Bool("clobber", false, "delete files and directories at target")
|
||||||
verbose := flag.Bool("verbose", false, "verbose output")
|
verbose := flag.Bool("verbose", false, "verbose output")
|
||||||
@ -94,6 +95,10 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(*dstDir) == "" {
|
||||||
|
*dstDir, _ = os.UserHomeDir()
|
||||||
|
}
|
||||||
|
|
||||||
conf.srcDir = makeAbsPath(flag.Arg(1))
|
conf.srcDir = makeAbsPath(flag.Arg(1))
|
||||||
conf.dstDir = makeAbsPath(*dstDir)
|
conf.dstDir = makeAbsPath(*dstDir)
|
||||||
conf.variant = *variant
|
conf.variant = *variant
|
||||||
|
BIN
img/demo.gif
Normal file
BIN
img/demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 647 KiB |
60
link.go
60
link.go
@ -26,51 +26,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func cleanPath(loc string, flags int) error {
|
|
||||||
if info, _ := os.Lstat(loc); info != nil {
|
|
||||||
if info.Mode()&os.ModeSymlink == 0 {
|
|
||||||
if flags&flagClobber != 0 || prompt("clobber path", loc) {
|
|
||||||
if flags&flagVerbose != 0 {
|
|
||||||
log.Printf("clobbering path: %s", loc)
|
|
||||||
}
|
|
||||||
if err := os.RemoveAll(loc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if flags&flagVerbose != 0 {
|
|
||||||
log.Printf("removing symlink: %s", loc)
|
|
||||||
}
|
|
||||||
if err := os.Remove(loc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func createPath(loc string, flags int, mode os.FileMode) error {
|
|
||||||
parentDir := path.Dir(loc)
|
|
||||||
|
|
||||||
if _, err := os.Stat(parentDir); os.IsNotExist(err) {
|
|
||||||
if flags&flagForce != 0 || prompt("force create path", parentDir) {
|
|
||||||
if flags&flagVerbose != 0 {
|
|
||||||
log.Printf("force creating path: %s", parentDir)
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(parentDir, mode); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLink(params []string) (srcPath, dstPath string, mode os.FileMode, err error) {
|
func parseLink(params []string) (srcPath, dstPath string, mode os.FileMode, err error) {
|
||||||
length := len(params)
|
length := len(params)
|
||||||
if length < 1 || length > 3 {
|
if length < 1 || length > 3 {
|
||||||
@ -106,13 +65,13 @@ func processLink(params []string, conf *config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srcPathAbs := srcPath
|
srcPathAbs := srcPath
|
||||||
if !path.IsAbs(srcPathAbs) {
|
if !filepath.IsAbs(srcPathAbs) {
|
||||||
srcPathAbs = path.Join(conf.srcDir, srcPath)
|
srcPathAbs = filepath.Join(conf.srcDir, srcPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
dstPathAbs := dstPath
|
dstPathAbs := dstPath
|
||||||
if !path.IsAbs(dstPathAbs) {
|
if !filepath.IsAbs(dstPathAbs) {
|
||||||
dstPathAbs = path.Join(conf.dstDir, dstPath)
|
dstPathAbs = filepath.Join(conf.dstDir, dstPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.flags&flagUnlink != flagUnlink {
|
if conf.flags&flagUnlink != flagUnlink {
|
||||||
@ -124,9 +83,13 @@ func processLink(params []string, conf *config) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := try(func() error { return cleanPath(dstPathAbs, conf.flags) }); err != nil {
|
pathCleaned, err := cleanPath(dstPathAbs, conf.flags)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !pathCleaned {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if conf.flags&flagVerbose != 0 {
|
if conf.flags&flagVerbose != 0 {
|
||||||
log.Printf("linking %s to %s", srcPathAbs, dstPathAbs)
|
log.Printf("linking %s to %s", srcPathAbs, dstPathAbs)
|
||||||
@ -141,6 +104,7 @@ func processLink(params []string, conf *config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return try(func() error { return cleanPath(dstPathAbs, conf.flags) })
|
_, err = cleanPath(dstPathAbs, conf.flags)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
16
template.go
16
template.go
@ -26,7 +26,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
@ -79,13 +79,13 @@ func processTemplate(params []string, conf *config) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
srcPathAbs := srcPath
|
srcPathAbs := srcPath
|
||||||
if !path.IsAbs(srcPathAbs) {
|
if !filepath.IsAbs(srcPathAbs) {
|
||||||
srcPathAbs = path.Join(conf.srcDir, srcPath)
|
srcPathAbs = filepath.Join(conf.srcDir, srcPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
dstPathAbs := dstPath
|
dstPathAbs := dstPath
|
||||||
if !path.IsAbs(dstPathAbs) {
|
if !filepath.IsAbs(dstPathAbs) {
|
||||||
dstPathAbs = path.Join(conf.dstDir, dstPath)
|
dstPathAbs = filepath.Join(conf.dstDir, dstPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = os.Stat(srcPathAbs); os.IsNotExist(err) {
|
if _, err = os.Stat(srcPathAbs); os.IsNotExist(err) {
|
||||||
@ -96,9 +96,13 @@ func processTemplate(params []string, conf *config) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = try(func() error { return cleanPath(dstPathAbs, conf.flags) }); err != nil {
|
pathCleaned, err := cleanPath(dstPathAbs, conf.flags)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !pathCleaned {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if conf.flags&flagVerbose != 0 {
|
if conf.flags&flagVerbose != 0 {
|
||||||
log.Printf("process template %s to %s", srcPathAbs, dstPathAbs)
|
log.Printf("process template %s to %s", srcPathAbs, dstPathAbs)
|
||||||
|
46
util.go
46
util.go
@ -47,6 +47,52 @@ func makeAbsPath(path string) string {
|
|||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cleanPath(loc string, flags int) (bool, error) {
|
||||||
|
if info, _ := os.Lstat(loc); info != nil {
|
||||||
|
if info.Mode()&os.ModeSymlink == 0 {
|
||||||
|
shouldContinue := false
|
||||||
|
if flags&flagClobber == 0 {
|
||||||
|
shouldContinue = prompt("clobber path", loc)
|
||||||
|
}
|
||||||
|
if flags&flagClobber != 0 || shouldContinue {
|
||||||
|
if flags&flagVerbose != 0 {
|
||||||
|
log.Printf("clobbering path: %s", loc)
|
||||||
|
}
|
||||||
|
if err := try(func() error { return os.RemoveAll(loc) }) ; err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if flags&flagVerbose != 0 {
|
||||||
|
log.Printf("removing symlink: %s", loc)
|
||||||
|
}
|
||||||
|
if err := try(func() error { return os.Remove(loc) }); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPath(loc string, flags int, mode os.FileMode) error {
|
||||||
|
parentDir := filepath.Dir(loc)
|
||||||
|
|
||||||
|
if _, err := os.Stat(parentDir); os.IsNotExist(err) {
|
||||||
|
if flags&flagForce != 0 || prompt("force create path", parentDir) {
|
||||||
|
if flags&flagVerbose != 0 {
|
||||||
|
log.Printf("force creating path: %s", parentDir)
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(parentDir, mode); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func makeVariantNames(name, variant string) []string {
|
func makeVariantNames(name, variant string) []string {
|
||||||
if nameParts := strings.Split(name, "__"); len(nameParts) > 1 {
|
if nameParts := strings.Split(name, "__"); len(nameParts) > 1 {
|
||||||
variant = nameParts[len(nameParts)-1]
|
variant = nameParts[len(nameParts)-1]
|
||||||
|
Loading…
Reference in New Issue
Block a user