Compare commits

..

11 Commits

Author SHA1 Message Date
0682a0d249 Update README 2023-12-04 20:02:59 -08:00
59fadc5961 Update README 2022-07-14 14:15:40 -07:00
8c887ca0e7 Move to foosoft.net 2022-07-03 21:07:02 -07:00
61a139d47f Add build script, site metadata, update binary page 2021-12-14 17:32:37 -08:00
Alex Yatskov
0a90c2e21c
Merge pull request #34 from albertdev/bugfix/use-filepath-package
Use filepath package throughout for Windows support
2021-12-14 15:07:59 -08:00
Bert Jacobs
edb42a0e3b Use filepath package throughout for Windows support
The "path" package supports only forward slashes in paths, like those
found in *nix operating systems or URLs.

Meanwhile Windows with its CP/M and QDOS ancestry requires drive letters
and backslashes, and thus the "filepath" package needs to be used.
2021-12-14 23:40:23 +01:00
Alex Yatskov
72ff78498b
Merge pull request #33 from albertdev/bugfix/platform-indedependent-homedir
Use platform independent os.UserHomeDir() function
2021-12-14 14:18:07 -08:00
Alex Yatskov
ddbd7c428a
Merge pull request #32 from albertdev/bugfix/rework-clobber-prompt
Continue to next file / task when pressing 'n' at clobber prompt
2021-12-14 14:17:39 -08:00
Bert Jacobs
d4087be780 Move shared *Path functions to util.go 2021-09-13 18:27:47 +02:00
Bert Jacobs
0ba821eb7e Pass result of "clobber path" prompt to caller, stop task
The end user would be prompted if a file is going to be overwritten,
but homemaker would still attempt to do so no matter the answer.

Move the retry-functionality of cleanPath inside this function so that
the function itself can return a boolean to indicate if the calling task
is free to continue. If the path is not clean then homemaker can just
move on to the next file or next task.
2021-09-13 18:23:39 +02:00
Bert Jacobs
8d703e95ae Use platform independent os.UserHomeDir() function
Pull request #7 replaced some code blocking cross-compilation. However,
the "HOME" environment variable is not available on Windows.

For this reason, Go introduced a dedicated method in the os package
starting from version 1.12.
2021-09-13 17:23:35 +02:00
11 changed files with 146 additions and 75 deletions

1
.gitignore vendored
View File

@ -4,6 +4,7 @@
*.so
*.dylib
homemaker
build
# Test binary, build with `go test -c`
*.test

50
Makefile Normal file
View 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)

View File

@ -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
configuring a new system for use.
![](https://foosoft.net/projects/homemaker/img/demo.gif)
![](img/demo.gif)
## 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:
```
$ 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:
* [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)
Otherwise, you can use the [pre-built binaries](https://github.com/FooSoft/homemaker/releases) from the project page.
## Configuration

View File

@ -26,7 +26,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"path"
"path/filepath"
"github.com/naoina/toml"
"gopkg.in/yaml.v2"
@ -50,7 +50,7 @@ func newConfig(filename string) (*config, error) {
}
conf := &config{handled: make(map[string]bool)}
switch path.Ext(filename) {
switch filepath.Ext(filename) {
case ".json":
if err := json.Unmarshal(bytes, &conf); err != nil {
return nil, err

12
go.mod
View File

@ -1,7 +1,13 @@
module github.com/FooSoft/homemaker
module foosoft.net/projects/homemaker
go 1.18
require (
github.com/naoina/go-stringutil v0.1.0 // indirect
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
View File

@ -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/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/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/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -27,7 +27,8 @@ import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"strings"
)
const (
@ -42,7 +43,7 @@ const (
)
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, "Parameters:\n")
flag.PrintDefaults()
@ -50,7 +51,7 @@ func usage() {
func main() {
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")
clobber := flag.Bool("clobber", false, "delete files and directories at target")
verbose := flag.Bool("verbose", false, "verbose output")
@ -94,6 +95,10 @@ func main() {
log.Fatal(err)
}
if strings.TrimSpace(*dstDir) == "" {
*dstDir, _ = os.UserHomeDir()
}
conf.srcDir = makeAbsPath(flag.Arg(1))
conf.dstDir = makeAbsPath(*dstDir)
conf.variant = *variant

BIN
img/demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

60
link.go
View File

@ -26,51 +26,10 @@ import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"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) {
length := len(params)
if length < 1 || length > 3 {
@ -106,13 +65,13 @@ func processLink(params []string, conf *config) error {
}
srcPathAbs := srcPath
if !path.IsAbs(srcPathAbs) {
srcPathAbs = path.Join(conf.srcDir, srcPath)
if !filepath.IsAbs(srcPathAbs) {
srcPathAbs = filepath.Join(conf.srcDir, srcPath)
}
dstPathAbs := dstPath
if !path.IsAbs(dstPathAbs) {
dstPathAbs = path.Join(conf.dstDir, dstPath)
if !filepath.IsAbs(dstPathAbs) {
dstPathAbs = filepath.Join(conf.dstDir, dstPath)
}
if conf.flags&flagUnlink != flagUnlink {
@ -124,9 +83,13 @@ func processLink(params []string, conf *config) error {
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
}
if !pathCleaned {
return nil
}
if conf.flags&flagVerbose != 0 {
log.Printf("linking %s to %s", srcPathAbs, dstPathAbs)
@ -141,6 +104,7 @@ func processLink(params []string, conf *config) error {
return nil
}
return try(func() error { return cleanPath(dstPathAbs, conf.flags) })
_, err = cleanPath(dstPathAbs, conf.flags)
return err
}
}

View File

@ -26,7 +26,7 @@ import (
"fmt"
"log"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"text/template"
@ -79,13 +79,13 @@ func processTemplate(params []string, conf *config) (err error) {
}
srcPathAbs := srcPath
if !path.IsAbs(srcPathAbs) {
srcPathAbs = path.Join(conf.srcDir, srcPath)
if !filepath.IsAbs(srcPathAbs) {
srcPathAbs = filepath.Join(conf.srcDir, srcPath)
}
dstPathAbs := dstPath
if !path.IsAbs(dstPathAbs) {
dstPathAbs = path.Join(conf.dstDir, dstPath)
if !filepath.IsAbs(dstPathAbs) {
dstPathAbs = filepath.Join(conf.dstDir, dstPath)
}
if _, err = os.Stat(srcPathAbs); os.IsNotExist(err) {
@ -96,9 +96,13 @@ func processTemplate(params []string, conf *config) (err error) {
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
}
if !pathCleaned {
return nil
}
if conf.flags&flagVerbose != 0 {
log.Printf("process template %s to %s", srcPathAbs, dstPathAbs)

46
util.go
View File

@ -47,6 +47,52 @@ func makeAbsPath(path string) string {
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 {
if nameParts := strings.Split(name, "__"); len(nameParts) > 1 {
variant = nameParts[len(nameParts)-1]