From bf92ff7c37ceb51e2cc99e3eab87bc5d44eed299 Mon Sep 17 00:00:00 2001 From: Marcusk19 Date: Fri, 22 Mar 2024 12:37:38 -0400 Subject: [PATCH 1/4] use config for dotfile path, add some makefile scripts --- Makefile | 7 +++++++ cmd/add.go | 11 ++++++++--- cmd/init.go | 7 +++++-- cmd/link.go | 23 +++++++++++++++++------ cmd/root.go | 5 +++-- test/init_test.go | 4 ++-- 6 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3a5a6f9 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +clean: + rm -r test/bender_test + rm -r tmp + +sandbox: + mkdir -p ./tmp/ + cp -r ~/.config/ ./tmp/config diff --git a/cmd/add.go b/cmd/add.go index 13546d1..7646376 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -34,16 +34,21 @@ func runAddCommand(cmd *cobra.Command, args []string) { dirs := strings.Split(configSrc, "/") name := dirs[len(dirs) - 1] viper.Set(name, configSrc) - viper.WriteConfig() + err := viper.WriteConfig() + if err != nil { + fmt.Printf("Problem updating bender config %s", err) + } + + dotfilePath := viper.Get("dotfile-path").(string) - dotfileDest := filepath.Join(DotfilePath, name) + dotfileDest := filepath.Join(dotfilePath, name) if DryRun { fmt.Printf("Will copy %s -> %s \n", configSrc, dotfileDest) return } - err := tools.CopyDir(fs, configSrc, dotfileDest) + err = tools.CopyDir(fs, configSrc, dotfileDest) if err != nil { log.Fatal(err) } diff --git a/cmd/init.go b/cmd/init.go index e95cdb9..5506815 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -68,7 +68,10 @@ func runInitCommand(cmd *cobra.Command, args []string) { panic(fmt.Errorf("Unable to create config file %w", err)) } - viper.WriteConfig() + err = viper.WriteConfig() + if err != nil { + fmt.Printf("Unable to write config on init: %s\n", err) + } if (viper.Get("testing") != "true"){ _, err = git.PlainInit(DotfilePath, false) @@ -77,5 +80,5 @@ func runInitCommand(cmd *cobra.Command, args []string) { } } - fmt.Fprintf(cmd.OutOrStdout(), "Successfully created dotfiles repository\n") + fmt.Fprintf(cmd.OutOrStdout(), "Successfully created dotfiles repository at %s\n", DotfilePath) } diff --git a/cmd/link.go b/cmd/link.go index 8f5a5db..2ccc513 100644 --- a/cmd/link.go +++ b/cmd/link.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var linkCommand = &cobra.Command { @@ -21,16 +22,20 @@ func init() { func runLinkCommand(cmd *cobra.Command, args []string) { fs := UseFilesystem() fmt.Println("Symlinking dotfiles...") - entries, err := afero.ReadDir(fs, DotfilePath) + dotfileRoot := viper.Get("dotfile-path").(string) + entries, err := afero.ReadDir(fs, dotfileRoot) if err != nil { log.Fatal(err) } for _, entry := range(entries) { - if entry.Name() == ".git" { + configName := entry.Name() + if configName == ".git" || configName == "bender" { continue } - dotPath := filepath.Join(DotfilePath, entry.Name()) - configPath := filepath.Join(ConfigPath, entry.Name()) + dotPath := filepath.Join(dotfileRoot, entry.Name()) + + configPath := viper.Get(configName).(string) + // destination needs to be removed before symlink if(DryRun) { @@ -40,10 +45,16 @@ func runLinkCommand(cmd *cobra.Command, args []string) { fs.RemoveAll(configPath) } + testing := viper.Get("testing") + if(DryRun) { - log.Printf("Will link %s -> %s\n", dotPath, configPath) + log.Printf("Will link %s -> %s\n", configPath, dotPath) } else { - err = afero.OsFs.SymlinkIfPossible(afero.OsFs{}, dotPath, configPath) + if(testing == "true") { + fmt.Fprintf(cmd.OutOrStdout(), "%s,%s", configPath, dotPath) + } else { + err = afero.OsFs.SymlinkIfPossible(afero.OsFs{}, dotPath, configPath) + } } if err != nil { log.Fatalf("Cannot symlink %s: %s", entry.Name(), err.Error()) diff --git a/cmd/root.go b/cmd/root.go index ec99e4d..9564e06 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -66,10 +66,11 @@ func init() { viper.SetConfigName("config") viper.SetConfigType("yaml") - viper.AddConfigPath(filepath.Join(defaultDotPath, "bender")) - viper.AddConfigPath("./bender") + viper.AddConfigPath("./tmp/dotfiles/bender") + viper.AddConfigPath(filepath.Join(DotfilePath, "bender")) err := viper.ReadInConfig() + if err != nil { fmt.Println("No config detected. You can generate one by using 'bender init'") } diff --git a/test/init_test.go b/test/init_test.go index e8b0c51..736fe07 100644 --- a/test/init_test.go +++ b/test/init_test.go @@ -20,13 +20,13 @@ func TestInitCommand(t *testing.T) { bender.SetOut(actual) bender.SetErr(actual) - bender.SetArgs([]string{"init", "--dotfile-path=bender_test/.dotfiles"}) + bender.SetArgs([]string{"init", "--dotfile-path=bender_test/dotfiles"}) bender.Execute() homedir := "bender_test/" - _, err := afero.ReadFile(fs, filepath.Join(homedir, ".dotfiles/bender/config")) + _, err := afero.ReadFile(fs, filepath.Join(homedir, "dotfiles/bender/config")) if err != nil { t.Error(err.Error()) } From 38af0d5ad72746452beaf7852dd0fcf909ef7415 Mon Sep 17 00:00:00 2001 From: Marcusk19 Date: Fri, 22 Mar 2024 12:48:10 -0400 Subject: [PATCH 2/4] update README --- README.md | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 71df99e..61abe57 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,36 @@ # Bender ![](assets/bender.png) -A general purpose CLI tool. +A cli tool to manage your dotfiles +## About +Bender is a tool to help you easily manage your dotfiles and sync them across separate machines using +git. It aims to abstract away the manual effort of symlinking your dotfiles to config directories and +updating them with git. ## Installation - TBD ## Usage -use pretty command to get whitespace for special characters like `\n` and `\t` -e.g. ```bash -bender pretty example.txt +# init sets up the config file and directory to hold all dotfiles +bender init --dotfile-path=/path/to/dotfile/repo +# add a config directory for bender to track +bender add /.config/nvim +# create symlinks +bender link ``` + +## Development +It's preferable to create a temporary directory and copy your system's config +directory over to avoid making undesirable changes to your system. +A couple of useful makefile scripts exist to set up and tear down this. +It will create a testing directory in `./tmp/config` and copy your system configs +over. + +```bash +make sandbox # creates the directory and copies over from ~/.config +make clean # removes directory +``` + + From 843385b004919a886a6bd802137f0e49fce04bbf Mon Sep 17 00:00:00 2001 From: Marcusk19 Date: Fri, 22 Mar 2024 16:47:29 -0400 Subject: [PATCH 3/4] add unit test for link command --- Makefile | 11 ++++++---- cmd/init.go | 7 +++++-- cmd/link.go | 9 +++++--- cmd/root.go | 26 +----------------------- test/link_test.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 34 deletions(-) create mode 100644 test/link_test.go diff --git a/Makefile b/Makefile index 3a5a6f9..c03d245 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ clean: - rm -r test/bender_test - rm -r tmp + rm -rf test/bender_test 2> /dev/null + rm -rf tmp 2> /dev/null sandbox: - mkdir -p ./tmp/ - cp -r ~/.config/ ./tmp/config + mkdir -p ./tmp/ 2> /dev/null + cp -r ~/.config/ ./tmp/config 2> /dev/null + +unit-test: + TESTING=true go test -v ./test diff --git a/cmd/init.go b/cmd/init.go index 5506815..025c12d 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -53,6 +53,9 @@ var initCommand = &cobra.Command { func runInitCommand(cmd *cobra.Command, args []string) { fs := FileSystem + // if user has passed a dotfile path flag need to add it to + // viper's search path for a config file + viper.AddConfigPath(filepath.Join(DotfilePath, "bender")) if(viper.Get("testing") == true && fs.Name() != "MemMapFS") { log.Fatalf("wrong filesystem, got %s", fs.Name()) @@ -69,8 +72,8 @@ func runInitCommand(cmd *cobra.Command, args []string) { } err = viper.WriteConfig() - if err != nil { - fmt.Printf("Unable to write config on init: %s\n", err) + if err != nil && viper.Get("testing") != true { + log.Fatalf("Unable to write config on init: %s\n", err) } if (viper.Get("testing") != "true"){ diff --git a/cmd/link.go b/cmd/link.go index 2ccc513..8fe259a 100644 --- a/cmd/link.go +++ b/cmd/link.go @@ -20,12 +20,12 @@ func init() { } func runLinkCommand(cmd *cobra.Command, args []string) { - fs := UseFilesystem() + fs := FileSystem fmt.Println("Symlinking dotfiles...") dotfileRoot := viper.Get("dotfile-path").(string) entries, err := afero.ReadDir(fs, dotfileRoot) if err != nil { - log.Fatal(err) + log.Fatalf("Could not read dotfiles directory: %s\n",err) } for _, entry := range(entries) { configName := entry.Name() @@ -34,7 +34,10 @@ func runLinkCommand(cmd *cobra.Command, args []string) { } dotPath := filepath.Join(dotfileRoot, entry.Name()) - configPath := viper.Get(configName).(string) + configPath := viper.GetString(configName) + if configPath == ""{ + fmt.Fprintf(cmd.OutOrStdout(), "Warning: could not find config for %s\n", entry.Name()) + } // destination needs to be removed before symlink diff --git a/cmd/root.go b/cmd/root.go index 9564e06..0517c89 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -67,6 +67,7 @@ func init() { viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath("./tmp/dotfiles/bender") + fmt.Printf("dotfile path is %s\n", DotfilePath) viper.AddConfigPath(filepath.Join(DotfilePath, "bender")) err := viper.ReadInConfig() @@ -89,29 +90,4 @@ func UseFilesystem() afero.Fs { } // TODO: this can probably be removed -func SetUpForTesting() afero.Fs { - viper.Set("testing", true) - fs := UseFilesystem() - - homedir := "bender_test/" - fs.MkdirAll(filepath.Join(homedir, ".config/"), 0755) - fs.MkdirAll(filepath.Join(homedir, ".dotfiles/"), 0755) - - fs.Mkdir("bin/", 0755) - fs.Create("bin/alacritty") - fs.Create("bin/nvim") - fs.Create("bin/tmux") - - fs.Mkdir(filepath.Join(homedir, ".config/alacritty"), 0755) - fs.Mkdir(filepath.Join(homedir, ".config/nvim"), 0755) - fs.Mkdir(filepath.Join(homedir, ".config/tmux"), 0755) - - fs.Create(filepath.Join(homedir, ".config/alacritty/alacritty.conf")) - fs.Create(filepath.Join(homedir, ".config/nvim/nvim.conf")) - fs.Create(filepath.Join(homedir, ".config/tmux/tmux.conf")) - - FileSystem = fs - - return fs -} diff --git a/test/link_test.go b/test/link_test.go new file mode 100644 index 0000000..723ae04 --- /dev/null +++ b/test/link_test.go @@ -0,0 +1,52 @@ +package test + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/Marcusk19/bender/cmd" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + + +func TestLinkCommand(t *testing.T) { + setUpTesting() + bender := cmd.RootCmd + actual := new(bytes.Buffer) + + bender.SetOut(actual) + bender.SetErr(actual) + bender.SetArgs([]string{"link"}) + + bender.Execute() + + homedir := os.Getenv("HOME") + someconfig := filepath.Join(homedir, ".config/someconfig/") + somedot := filepath.Join(homedir, ".dotfiles/someconfig/") + + expected := fmt.Sprintf("%s,%s", someconfig, somedot) + + assert.Equal(t, expected, actual.String(), "actual differs from expected") + + tearDownTesting() +} + +func setUpTesting() { + fs := cmd.FileSystem + homedir := os.Getenv("HOME") + fs.MkdirAll(filepath.Join(homedir, ".dotfiles/bender"), 0755) + fs.Create(filepath.Join(homedir, ".dotfiles/bender/config")) + fs.MkdirAll(filepath.Join(homedir, ".dotfiles/someconfig/"), 0755) + + viper.Set("someconfig", filepath.Join(homedir, ".config/someconfig/")) + +} + +func tearDownTesting() { + fs := cmd.FileSystem + fs.RemoveAll("bender_test/") +} From 4ffe7e0344d0b521ca4079810500e50ef310bd53 Mon Sep 17 00:00:00 2001 From: Marcusk19 Date: Fri, 22 Mar 2024 17:01:01 -0400 Subject: [PATCH 4/4] fixing ci --- .github/workflows/ci.yml | 2 +- cmd/link.go | 2 +- cmd/root.go | 1 - test/link_test.go | 2 ++ 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ffe45e..140dfde 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,4 +15,4 @@ jobs: - name: Install dependencies run: go get . - name: Test with Go CLI - run: TESTING=true go test -v ./test + run: make unit-test diff --git a/cmd/link.go b/cmd/link.go index 8fe259a..b233d78 100644 --- a/cmd/link.go +++ b/cmd/link.go @@ -53,7 +53,7 @@ func runLinkCommand(cmd *cobra.Command, args []string) { if(DryRun) { log.Printf("Will link %s -> %s\n", configPath, dotPath) } else { - if(testing == "true") { + if(testing == true) { fmt.Fprintf(cmd.OutOrStdout(), "%s,%s", configPath, dotPath) } else { err = afero.OsFs.SymlinkIfPossible(afero.OsFs{}, dotPath, configPath) diff --git a/cmd/root.go b/cmd/root.go index 0517c89..073107a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -67,7 +67,6 @@ func init() { viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath("./tmp/dotfiles/bender") - fmt.Printf("dotfile path is %s\n", DotfilePath) viper.AddConfigPath(filepath.Join(DotfilePath, "bender")) err := viper.ReadInConfig() diff --git a/test/link_test.go b/test/link_test.go index 723ae04..d04a5c9 100644 --- a/test/link_test.go +++ b/test/link_test.go @@ -42,7 +42,9 @@ func setUpTesting() { fs.Create(filepath.Join(homedir, ".dotfiles/bender/config")) fs.MkdirAll(filepath.Join(homedir, ".dotfiles/someconfig/"), 0755) + viper.Set("dotfile-path", filepath.Join(homedir, ".dotfiles")) viper.Set("someconfig", filepath.Join(homedir, ".config/someconfig/")) + viper.Set("testing", true) }