From 9ef61b2626c17c1fc5cbc4de07ff7e700d405063 Mon Sep 17 00:00:00 2001 From: Marcus Kok <47163063+Marcusk19@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:03:15 -0400 Subject: [PATCH] Work (#13) * use config for dotfile path, add some makefile scripts * update README * add unit test for link command * fixing ci --- .github/workflows/ci.yml | 2 +- Makefile | 10 ++++++++ README.md | 29 ++++++++++++++++++--- cmd/add.go | 11 +++++--- cmd/init.go | 10 ++++++-- cmd/link.go | 30 ++++++++++++++++------ cmd/root.go | 30 +++------------------- test/init_test.go | 4 +-- test/link_test.go | 54 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 133 insertions(+), 47 deletions(-) create mode 100644 Makefile create mode 100644 test/link_test.go 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/Makefile b/Makefile new file mode 100644 index 0000000..c03d245 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +clean: + rm -rf test/bender_test 2> /dev/null + rm -rf tmp 2> /dev/null + +sandbox: + 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/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 +``` + + 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..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()) @@ -68,7 +71,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 && viper.Get("testing") != true { + log.Fatalf("Unable to write config on init: %s\n", err) + } if (viper.Get("testing") != "true"){ _, err = git.PlainInit(DotfilePath, false) @@ -77,5 +83,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..b233d78 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 { @@ -19,18 +20,25 @@ func init() { } func runLinkCommand(cmd *cobra.Command, args []string) { - fs := UseFilesystem() + fs := FileSystem 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) + log.Fatalf("Could not read dotfiles directory: %s\n",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.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 if(DryRun) { @@ -40,10 +48,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..073107a 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'") } @@ -88,29 +89,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/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()) } diff --git a/test/link_test.go b/test/link_test.go new file mode 100644 index 0000000..d04a5c9 --- /dev/null +++ b/test/link_test.go @@ -0,0 +1,54 @@ +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("dotfile-path", filepath.Join(homedir, ".dotfiles")) + viper.Set("someconfig", filepath.Join(homedir, ".config/someconfig/")) + viper.Set("testing", true) + +} + +func tearDownTesting() { + fs := cmd.FileSystem + fs.RemoveAll("bender_test/") +}