From 9561b6cd0ee48bce65e8d6721e88c6a86ee9883a Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:00:07 -0800 Subject: [PATCH 1/3] Overhaul Command --- README.md | 12 +-- Taskfile.yml | 25 ++++- cmd/backup.go | 248 +++++++++++++++++++++++-------------------------- cmd/exclude.go | 28 ------ cmd/info.go | 24 ++--- cmd/list.go | 30 ++---- cmd/root.go | 63 +++++++++---- docs/index.md | 16 +--- go.mod | 8 +- go.sum | 16 ++-- 10 files changed, 221 insertions(+), 249 deletions(-) delete mode 100644 cmd/exclude.go diff --git a/README.md b/README.md index c1357b3..adb27f0 100644 --- a/README.md +++ b/README.md @@ -94,25 +94,19 @@ _Note: Docker requires you to mount the target bin directory._ Specify `source` and `destination`. ```shell -bup backup [source] [destination] -``` - -Use the `b` alias, current directory, and saved `destination`. - -```shell -bup b +bup [source] [destination] ``` The `list` command list backups by name. ```shell -bup l +bup -l ``` The `info` command prints the configuration. ```shell -bup i +bup -i ``` [![View Documentation](https://img.shields.io/badge/view_documentation-blue?style=for-the-badge&logo=googledocs&logoColor=white)](https://smashedr.github.io/bup/) diff --git a/Taskfile.yml b/Taskfile.yml index 7feb5a5..96ac226 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -6,7 +6,7 @@ env: tasks: goup: - aliases: [goget, tidy] + aliases: [go, tidy] cmd: | go get -u go mod tidy @@ -54,11 +54,13 @@ tasks: unzip dist/pathmgr.zip -d dist/PathMgr install: + vars: + TARGET_NAME: bup cmd: | go build - mv bup "${HOME}/bin/bup" - which bup - bup -V + mv {{.TARGET_NAME}} "${HOME}/bin/{{.TARGET_NAME}}" + which {{.TARGET_NAME}} + {{.TARGET_NAME}} -V vhs:*: desc: Create VHS Tape @@ -88,3 +90,18 @@ tasks: - out - '*.exe' EOF + + build:*:*: + desc: Build OS:ARCH + vars: + OS: "{{index .MATCH 0}}" + ARCH: "{{index .MATCH 1}}" + requires: + vars: + - name: OS + enum: [darwin, linux, windows] + - name: ARCH + enum: [amd64, arm64, "386"] + cmd: | + echo "Building: {{.OS}} / {{.ARCH}}" + GOOS={{.OS}} GOARCH={{.ARCH}} goreleaser build --snapshot --clean --single-target {{.CLI_ARGS}} diff --git a/cmd/backup.go b/cmd/backup.go index a773bab..b30b9e6 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -14,144 +14,137 @@ import ( "time" ) -var backupCmd = &cobra.Command{ - Use: "backup [source] [destination]", - Aliases: []string{"b", "bu", "bup"}, - Short: "Backup source to destination as zip", - Long: "Creates a zip archive of the source in the destination with a timestamp filename.", - Args: cobra.RangeArgs(0, 2), - Run: func(cmd *cobra.Command, args []string) { - log.Debug("backupCmd:", "args", args) - - var source, destination string - if len(args) == 2 { - source = args[0] - destination = args[1] - } else if len(args) == 1 { - source = args[0] - destination = viper.GetString("destination") - } else { - source = "." - destination = viper.GetString("destination") - } - //fmt.Printf("source: %s\n", source) - //fmt.Printf("destination: %s\n", destination) - - excludes := viper.GetStringSlice("excludes") - exclude, _ := cmd.Flags().GetStringSlice("exclude") - excludes = append(excludes, exclude...) - //exclude, _ := cmd.Flags().GetString("exclude") - //if exclude != "" { - // parts := strings.Split(exclude, ",") - // for _, part := range parts { - // excludes = append(excludes, strings.TrimSpace(part)) - // } - //} - log.Infof("Excludes: %v", excludes) - - if destination == "" { - destination = promptForDestination() - } - - sourceInfo, err := os.Stat(source) - if err != nil || !sourceInfo.IsDir() { - log.Errorf("Error: inalid source: %v", source) - return - } - destInfo, err := os.Stat(destination) - if err != nil || !destInfo.IsDir() { - log.Errorf("Error: inalid destination: %v", destination) - return - } +func backupCmd(cmd *cobra.Command, args []string) { + log.Debug("backupCmd:", "args", args) + + var source, destination string + if len(args) == 2 { + source = args[0] + destination = args[1] + } else if len(args) == 1 { + source = args[0] + destination = viper.GetString("destination") + } else { + source = "." + destination = viper.GetString("destination") + } + //fmt.Printf("source: %s\n", source) + //fmt.Printf("destination: %s\n", destination) + + excludes := viper.GetStringSlice("excludes") + exclude, _ := cmd.Flags().GetStringSlice("exclude") + excludes = append(excludes, exclude...) + //exclude, _ := cmd.Flags().GetString("exclude") + //if exclude != "" { + // parts := strings.Split(exclude, ",") + // for _, part := range parts { + // excludes = append(excludes, strings.TrimSpace(part)) + // } + //} + log.Infof("Excludes: %v", excludes) + + if destination == "" { + destination = promptForDestination() + } - //if err := validateDirectory(source, "Source"); err != nil { - // fmt.Println(err) - // return - //} - //if err := validateDirectory(destination, "Destination"); err != nil { - // fmt.Println(err) - // return - //} - - sourcePath, _ := filepath.Abs(source) - destPath, _ := filepath.Abs(destination) - sourceName := filepath.Base(sourcePath) - - //viper.SetDefault("destination", destination) - //viper.WriteConfig() - if viper.GetString("destination") == "" { - viper.Set("destination", destPath) - err := viper.WriteConfig() - if err != nil { - log.Warnf("Error Saving Config: %v", err) - } else { - fmt.Printf("Set Default Destination: %v\n", destPath) - } - } + sourceInfo, err := os.Stat(source) + if err != nil || !sourceInfo.IsDir() { + log.Errorf("Error: inalid source: %v", source) + return + } + destInfo, err := os.Stat(destination) + if err != nil || !destInfo.IsDir() { + log.Errorf("Error: inalid destination: %v", destination) + return + } - noConfirm, _ := cmd.Flags().GetBool("yes") - log.Infof("Skip Confirmation: %v", noConfirm) - - fmt.Printf("Source: %s\n", sourcePath) - fmt.Printf("Destination: %s\n", destPath) - fmt.Printf("Name: %s\n", sourceName) - - if !noConfirm { - var confirm = true - form := huh.NewConfirm(). - Title("Proceed?"). - Affirmative("Yes."). - Negative("No!"). - Value(&confirm). - WithTheme(huh.ThemeDracula()) - err := form.Run() - if err != nil { - fmt.Printf("prompt error: %v\n", err) - os.Exit(1) - } - if !confirm { - fmt.Printf("Operation cancelled\n") - os.Exit(0) - } + //if err := validateDirectory(source, "Source"); err != nil { + // fmt.Println(err) + // return + //} + //if err := validateDirectory(destination, "Destination"); err != nil { + // fmt.Println(err) + // return + //} + + sourcePath, _ := filepath.Abs(source) + destPath, _ := filepath.Abs(destination) + sourceName := filepath.Base(sourcePath) + + //viper.SetDefault("destination", destination) + //viper.WriteConfig() + if viper.GetString("destination") == "" { + viper.Set("destination", destPath) + err := viper.WriteConfig() + if err != nil { + log.Warnf("Error Saving Config: %v", err) + } else { + fmt.Printf("Set Default Destination: %v\n", destPath) } + } - fullDestPath := filepath.Join(destPath, sourceName) - if err := os.MkdirAll(fullDestPath, 0755); err != nil { - log.Errorf("Error creating directory: %v", err) + noConfirm, _ := cmd.Flags().GetBool("yes") + log.Infof("Skip Confirmation: %v", noConfirm) + + fmt.Printf("Source: %s\n", sourcePath) + fmt.Printf("Destination: %s\n", destPath) + fmt.Printf("Name: %s\n", sourceName) + + if !noConfirm { + var confirm = true + form := huh.NewConfirm(). + Title("Proceed?"). + Affirmative("Yes."). + Negative("No!"). + Value(&confirm). + WithTheme(huh.ThemeDracula()) + err := form.Run() + if err != nil { + fmt.Printf("prompt error: %v\n", err) os.Exit(1) } - fmt.Printf("Directory: %s\n", fullDestPath) + if !confirm { + fmt.Printf("Operation cancelled\n") + os.Exit(0) + } + } - // Create timestamp filename - timestamp := time.Now().Format("06-01-02-15-04-05") // YY-MM-DD-HH-MM-SS - zipFileName := timestamp + ".zip" - fmt.Printf("File Name: %s\n", zipFileName) - zipFilePath := filepath.Join(fullDestPath, zipFileName) - fmt.Printf("File Path: %s\n", zipFilePath) + fullDestPath := filepath.Join(destPath, sourceName) + if err := os.MkdirAll(fullDestPath, 0755); err != nil { + log.Errorf("Error creating directory: %v", err) + os.Exit(1) + } + fmt.Printf("Directory: %s\n", fullDestPath) - if err := archive.CreateZipArchive(excludes, sourcePath, zipFilePath); err != nil { - log.Fatalf("Error creating archive: %v", err) - } + // Create timestamp filename + timestamp := time.Now().Format("06-01-02-15-04-05") // YY-MM-DD-HH-MM-SS + zipFileName := timestamp + ".zip" + fmt.Printf("File Name: %s\n", zipFileName) + zipFilePath := filepath.Join(fullDestPath, zipFileName) + fmt.Printf("File Path: %s\n", zipFilePath) - copyToClipboard := viper.GetBool("clipboard") - log.Infof("copyToClipboard: %v", copyToClipboard) - if copyToClipboard { - if err := clipboard.Init(); err != nil { - log.Warnf("Clipboard not available.") - } else { - clipboard.Write(clipboard.FmtText, []byte(zipFilePath)) - } - } + if err := archive.CreateZipArchive(excludes, sourcePath, zipFilePath); err != nil { + log.Fatalf("Error creating archive: %v", err) + } - fileInfo, err := os.Stat(zipFilePath) - if err != nil { - log.Warnf("Error getting archive info: %v", err) + copyToClipboard := viper.GetBool("clipboard") + log.Infof("copyToClipboard: %v", copyToClipboard) + if copyToClipboard { + if err := clipboard.Init(); err != nil { + log.Warnf("Clipboard not available.") } else { - fmt.Printf("Archive Size: %s\n", formatBytes(fileInfo.Size())) + clipboard.Write(clipboard.FmtText, []byte(zipFilePath)) } + } + + fileInfo, err := os.Stat(zipFilePath) + if err != nil { + log.Warnf("Error getting archive info: %v", err) + } else { + fmt.Printf("Archive Size: %s\n", formatBytes(fileInfo.Size())) + } - fmt.Printf("Archive File: %s\nSuccess!\n", zipFilePath) - }, + fmt.Printf("Archive File: %s\nSuccess!\n", zipFilePath) } func promptForDestination() string { @@ -209,8 +202,3 @@ func formatBytes(bytes int64) string { } return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) } - -func init() { - rootCmd.AddCommand(backupCmd) - backupCmd.Flags().StringSliceP("exclude", "e", []string{}, "inline pattern to exclude") -} diff --git a/cmd/exclude.go b/cmd/exclude.go deleted file mode 100644 index e0f9130..0000000 --- a/cmd/exclude.go +++ /dev/null @@ -1,28 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/charmbracelet/log" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -var excludeCmd = &cobra.Command{ - Use: "exclude add/remove name", - Aliases: []string{"e", "ex", "exc"}, - Short: "Show, add or remove excludes", - Long: "Show, add or remove excludes.", - Run: func(cmd *cobra.Command, args []string) { - log.Debug("excludeCmd:", "args", args) - - destination := viper.GetString("destination") - fmt.Printf("destination: %s\n", destination) - fmt.Printf("excludes: %s\n", viper.GetStringSlice("excludes")) - - log.Fatal("INOP - this command only list excludes. Update the Config File to edit them.") - }, -} - -func init() { - rootCmd.AddCommand(excludeCmd) -} diff --git a/cmd/info.go b/cmd/info.go index d813330..169ae93 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -7,22 +7,12 @@ import ( "github.com/spf13/viper" ) -var infoCmd = &cobra.Command{ - Use: "info", - Aliases: []string{"i", "in", "inf"}, - Short: "Show information about application", - Long: "Show information about application.", - Run: func(cmd *cobra.Command, args []string) { - log.Debug("infoCmd:", "args", args) +func infoCmd(cmd *cobra.Command, args []string) { + log.Debug("infoCmd:", "args", args) - fmt.Printf("cfgFile: %s\n", cfgFile) - fmt.Printf("viper.ConfigFileUsed: %s\n", viper.ConfigFileUsed()) - destination := viper.GetString("destination") - fmt.Printf("destination: %s\n", destination) - fmt.Printf("excludes: %s\n", viper.GetStringSlice("excludes")) - }, -} - -func init() { - rootCmd.AddCommand(infoCmd) + fmt.Printf("cfgFile: %s\n", cfgFile) + fmt.Printf("viper.ConfigFileUsed: %s\n", viper.ConfigFileUsed()) + destination := viper.GetString("destination") + fmt.Printf("destination: %s\n", destination) + fmt.Printf("excludes: %s\n", viper.GetStringSlice("excludes")) } diff --git a/cmd/list.go b/cmd/list.go index c109b52..1a5dc65 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -11,24 +11,18 @@ import ( "path/filepath" ) -var listCmd = &cobra.Command{ - Use: "list [name]", - Aliases: []string{"l", "li", "lis", "ls"}, - Short: "List backups for a given name", - Long: "List all backups or backups for specified name.", - Run: func(cmd *cobra.Command, args []string) { - log.Debug("listCmd:", "args", args) +func listCmd(cmd *cobra.Command, args []string) { + log.Debug("listCmd:", "args", args) - destination := viper.GetString("destination") - if len(args) == 0 { - listDir(destination, "All Backups") - } else { - for i := 0; i < len(args); i++ { - path := filepath.Join(destination, args[i]) - listDir(path, args[i]) - } + destination := viper.GetString("destination") + if len(args) == 0 { + listDir(destination, "All Backups") + } else { + for i := 0; i < len(args); i++ { + path := filepath.Join(destination, args[i]) + listDir(path, args[i]) } - }, + } } func listDir(path, header string) { @@ -72,7 +66,3 @@ func renderTable(rows [][]string, headers ...string) { Rows(rows...) fmt.Println(t) } - -func init() { - rootCmd.AddCommand(listCmd) -} diff --git a/cmd/root.go b/cmd/root.go index 3411825..63d993c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -15,11 +15,29 @@ var ( ) var rootCmd = &cobra.Command{ - Use: "bup", + Use: "bup [source] [destination]", Short: "Easily backup directories to destination with excludes.", Long: "Easily create a timestamped archive of the current directory or [source] to a [destination] or saved destination with excludes.", - // Consider moving backup to the top level command - // Run: func(cmd *cobra.Command, args []string) { }, + Args: cobra.ArbitraryArgs, + Run: func(cmd *cobra.Command, args []string) { + log.Debug("rootCmd:", "args", args) + infoFlag, _ := cmd.Flags().GetBool("info") + log.Info("Flags:", "infoFlag", infoFlag) + listFlag, _ := cmd.Flags().GetBool("list") + log.Info("Flags:", "listFlag", listFlag) + + if infoFlag { + infoCmd(cmd, args) + return + } + + if listFlag { + listCmd(cmd, args) + return + } + + backupCmd(cmd, args) + }, } func SetVersionInfo(version, commit, date string) { @@ -38,14 +56,16 @@ func init() { rootCmd.PersistentFlags().BoolP("yes", "y", false, "answer yes to confirmations") rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file [default: ~/.config/bup.yaml]") rootCmd.PersistentFlags().CountVarP(&verbose, "verbose", "v", "verbose output (-vvv debug)") + rootCmd.Flags().BoolP("list", "l", false, "list backups") + rootCmd.Flags().BoolP("info", "i", false, "information about bup") rootCmd.Flags().BoolP("version", "V", false, "version for bup") } func onInitialize() { - initLogger(verbose) + initLogger() log.Info("Log Level", "verbose", verbose) - //viper.SetEnvPrefix("bup") + // Default Config viper.SetDefault("clipboard", true) viper.SetDefault("excludes", []string{ ".*cache", @@ -58,7 +78,7 @@ func onInitialize() { "*.exe", }) - // Provided Config + // User Provided Config log.Debug("onInitialize", "cfgFile", cfgFile) if cfgFile != "" { viper.SetConfigFile(cfgFile) @@ -71,8 +91,10 @@ func onInitialize() { } // Find Config + configName := "bup" + viper.SetConfigName(configName) viper.SetConfigType("yaml") - viper.SetConfigName("bup") + viper.SetEnvPrefix("bup") viper.AddConfigPath(".") viper.AddConfigPath("$HOME") @@ -84,32 +106,37 @@ func onInitialize() { if err := viper.ReadInConfig(); err != nil { homeDir, err := os.UserHomeDir() if err != nil { - homeDir = "." + homeDir = "." // NOTE: improve fallback method } log.Debugf("homeDir: %v", homeDir) configPath := filepath.Join(homeDir, ".config") log.Debugf("configPath: %v", configPath) - _ = os.MkdirAll(configPath, 0755) - configFile := filepath.Join(configPath, "bup.yaml") - log.Debugf("configFile: %v", configFile) + if err := os.MkdirAll(configPath, 0755); err != nil { + log.Fatalf("Creating config directory: %v: %v", configPath, err) + } + configFile := filepath.Join(configPath, configName+".yaml") + log.Infof("Config File: %v", configFile) viper.SetConfigFile(configFile) - _ = viper.SafeWriteConfigAs(configFile) + if err := viper.SafeWriteConfigAs(configFile); err != nil { + // NOTE: This will error if the config file exist + log.Debugf("SafeWriteConfigAs: %v: %v", configFile, err) + } if err := viper.ReadInConfig(); err != nil { - log.Fatalf("Error reading config: %s\nUsing Default Config!", configFile) + log.Fatalf("Reading config: %v: %v", configFile, err) } - log.Infof("Config File: %v", configFile) } else { log.Infof("Config File: %v", viper.ConfigFileUsed()) } } -func initLogger(verbosity int) { - log.SetReportCaller(verbosity >= 3) - log.SetReportTimestamp(verbosity >= 3) +func initLogger() { + log.Debug("initLogger", "verbose", verbose) + log.SetReportCaller(verbose >= 3) + log.SetReportTimestamp(verbose >= 3) log.SetTimeFormat("15:04:05") //log.SetPrefix("bup") - switch verbosity { + switch verbose { case 0: log.SetLevel(log.WarnLevel) // Default case 1: diff --git a/docs/index.md b/docs/index.md index ef55f2e..02f0141 100644 --- a/docs/index.md +++ b/docs/index.md @@ -64,25 +64,19 @@ If you run into any issues or have any questions, [support](support.md) is avail Specify `source` and `destination`. ```shell -bup backup [source] [destination] +bup [source] [destination] ``` -Use the `b` alias, current directory, and saved `destination`. +The `--list` flag list backups by name. ```shell -bup b +bup -l ``` -The `list` command list backups by name. +The `--info` flag prints the configuration. ```shell -bup l -``` - -The `info` command prints the configuration. - -```shell -bup i +bup -i ```   diff --git a/go.mod b/go.mod index 51e0be8..e8e664f 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/catppuccin/go v0.3.0 // indirect github.com/charmbracelet/bubbles v1.0.0 // indirect github.com/charmbracelet/bubbletea v1.3.10 // indirect - github.com/charmbracelet/colorprofile v0.4.1 // indirect + github.com/charmbracelet/colorprofile v0.4.2 // indirect github.com/charmbracelet/x/ansi v0.11.6 // indirect github.com/charmbracelet/x/cellbuf v0.0.15 // indirect github.com/charmbracelet/x/exp/strings v0.1.0 // indirect @@ -47,10 +47,10 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/exp v0.0.0-20260209203927-2842357ff358 // indirect - golang.org/x/exp/shiny v0.0.0-20260209203927-2842357ff358 // indirect + golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a // indirect + golang.org/x/exp/shiny v0.0.0-20260212183809-81e46e3db34a // indirect golang.org/x/image v0.36.0 // indirect - golang.org/x/mobile v0.0.0-20260209203831-923679eb55af // indirect + golang.org/x/mobile v0.0.0-20260211191516-dcd2a3258864 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect diff --git a/go.sum b/go.sum index 60a7cfa..dd03f67 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5f github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk= -github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk= +github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY= +github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8= github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY= github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4= github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= @@ -115,14 +115,14 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.design/x/clipboard v0.7.1 h1:OEG3CmcYRBNnRwpDp7+uWLiZi3hrMRJpE9JkkkYtz2c= golang.design/x/clipboard v0.7.1/go.mod h1:i5SiIqj0wLFw9P/1D7vfILFK0KHMk7ydE72HRrUIgkg= -golang.org/x/exp v0.0.0-20260209203927-2842357ff358 h1:kpfSV7uLwKJbFSEgNhWzGSL47NDSF/5pYYQw1V0ub6c= -golang.org/x/exp v0.0.0-20260209203927-2842357ff358/go.mod h1:R3t0oliuryB5eenPWl3rrQxwnNM3WTwnsRZZiXLAAW8= -golang.org/x/exp/shiny v0.0.0-20260209203927-2842357ff358 h1:cPCrmjVzlx/yVDBr1S7Pl3TFo5vEAFrus5a139vIg6Q= -golang.org/x/exp/shiny v0.0.0-20260209203927-2842357ff358/go.mod h1:NJvadzWdysRnMQ01yaqBdZglv1MjdIvnn5KrwzzrYp8= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a h1:ovFr6Z0MNmU7nH8VaX5xqw+05ST2uO1exVfZPVqRC5o= +golang.org/x/exp v0.0.0-20260212183809-81e46e3db34a/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA= +golang.org/x/exp/shiny v0.0.0-20260212183809-81e46e3db34a h1:pCkykR/N0jWVZoY0vl/3zjgys3YxHlrcTlJ/xL9ZMps= +golang.org/x/exp/shiny v0.0.0-20260212183809-81e46e3db34a/go.mod h1:zxsA7NyDTOUjcveVwAMFI/YIErWwayTW/4RGqB/RzKk= golang.org/x/image v0.36.0 h1:Iknbfm1afbgtwPTmHnS2gTM/6PPZfH+z2EFuOkSbqwc= golang.org/x/image v0.36.0/go.mod h1:YsWD2TyyGKiIX1kZlu9QfKIsQ4nAAK9bdgdrIsE7xy4= -golang.org/x/mobile v0.0.0-20260209203831-923679eb55af h1:VqXrZNyqFISxo0rNDFZQlRDRIp7RXSJDeh/LbrK+W1k= -golang.org/x/mobile v0.0.0-20260209203831-923679eb55af/go.mod h1:tbwefIr7RlQD1OpZ0KEZ9nux/uiihAOGdafgZfJkmII= +golang.org/x/mobile v0.0.0-20260211191516-dcd2a3258864 h1:cTVynMSsMYgbUrtia2HB1jrhdUwQNtQti91vUCyjMp4= +golang.org/x/mobile v0.0.0-20260211191516-dcd2a3258864/go.mod h1:4OGHIUSBiIqyFAQDaX1tpY0BVnO20DvNDeATBu8aeFQ= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= From 30542074591d643b11ed33067fb0a986f5c08f8f Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:00:56 -0800 Subject: [PATCH 2/3] Add Styles, Fixes, Cleanup --- README.md | 12 ++++-- cmd/backup.go | 84 ++++++++++++++------------------------- cmd/list.go | 28 +------------ cmd/root.go | 35 +++++++++------- docs/index.md | 12 ++++-- go.mod | 3 +- go.sum | 2 + internal/styles/styles.go | 73 ++++++++++++++++++++++++++++++++++ 8 files changed, 147 insertions(+), 102 deletions(-) create mode 100644 internal/styles/styles.go diff --git a/README.md b/README.md index adb27f0..77cf5d2 100644 --- a/README.md +++ b/README.md @@ -97,18 +97,24 @@ Specify `source` and `destination`. bup [source] [destination] ``` -The `list` command list backups by name. +Use `--list` to list backups. ```shell -bup -l +bup -l [name] ``` -The `info` command prints the configuration. +Use `--info` to prints the configuration. ```shell bup -i ``` +Use `--edit` to open the config in an editor. + +```shell +bup -e +``` + [![View Documentation](https://img.shields.io/badge/view_documentation-blue?style=for-the-badge&logo=googledocs&logoColor=white)](https://smashedr.github.io/bup/) # Development diff --git a/cmd/backup.go b/cmd/backup.go index b30b9e6..d4eae6d 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -4,7 +4,9 @@ import ( "fmt" "github.com/charmbracelet/huh" "github.com/charmbracelet/log" + "github.com/dustin/go-humanize" "github.com/smashedr/bup/internal/archive" + "github.com/smashedr/bup/internal/styles" "github.com/spf13/cobra" "github.com/spf13/viper" "golang.design/x/clipboard" @@ -17,6 +19,7 @@ import ( func backupCmd(cmd *cobra.Command, args []string) { log.Debug("backupCmd:", "args", args) + // Parse Source/Destination var source, destination string if len(args) == 2 { source = args[0] @@ -28,51 +31,29 @@ func backupCmd(cmd *cobra.Command, args []string) { source = "." destination = viper.GetString("destination") } - //fmt.Printf("source: %s\n", source) - //fmt.Printf("destination: %s\n", destination) - - excludes := viper.GetStringSlice("excludes") - exclude, _ := cmd.Flags().GetStringSlice("exclude") - excludes = append(excludes, exclude...) - //exclude, _ := cmd.Flags().GetString("exclude") - //if exclude != "" { - // parts := strings.Split(exclude, ",") - // for _, part := range parts { - // excludes = append(excludes, strings.TrimSpace(part)) - // } - //} - log.Infof("Excludes: %v", excludes) - if destination == "" { destination = promptForDestination() } + log.Debug("Args", "source", source, "destination", destination) + // Validate Source sourceInfo, err := os.Stat(source) + sourcePath, _ := filepath.Abs(source) + sourceName := filepath.Base(sourcePath) if err != nil || !sourceInfo.IsDir() { - log.Errorf("Error: inalid source: %v", source) - return + log.Fatalf("Inalid source: %v", source) } + log.Debug("Source", "sourcePath", sourcePath, "sourceName", sourceName) + + // Validate Destination destInfo, err := os.Stat(destination) + destPath, _ := filepath.Abs(destination) if err != nil || !destInfo.IsDir() { - log.Errorf("Error: inalid destination: %v", destination) - return + log.Fatalf("Inalid destination: %v", destination) } + log.Debug("Destination", "destPath", destPath) - //if err := validateDirectory(source, "Source"); err != nil { - // fmt.Println(err) - // return - //} - //if err := validateDirectory(destination, "Destination"); err != nil { - // fmt.Println(err) - // return - //} - - sourcePath, _ := filepath.Abs(source) - destPath, _ := filepath.Abs(destination) - sourceName := filepath.Base(sourcePath) - - //viper.SetDefault("destination", destination) - //viper.WriteConfig() + // Ensure Default Destination is Set if viper.GetString("destination") == "" { viper.Set("destination", destPath) err := viper.WriteConfig() @@ -83,12 +64,18 @@ func backupCmd(cmd *cobra.Command, args []string) { } } + // Process Excludes + excludes := viper.GetStringSlice("excludes") + exclude, _ := cmd.Flags().GetStringSlice("exclude") + excludes = append(excludes, exclude...) + log.Infof("Excludes: %v", excludes) + noConfirm, _ := cmd.Flags().GetBool("yes") log.Infof("Skip Confirmation: %v", noConfirm) - fmt.Printf("Source: %s\n", sourcePath) - fmt.Printf("Destination: %s\n", destPath) - fmt.Printf("Name: %s\n", sourceName) + styles.PrintKV("Source", sourcePath) + styles.PrintKV("Destination", destPath) + styles.PrintKV("Name", sourceName) if !noConfirm { var confirm = true @@ -114,14 +101,14 @@ func backupCmd(cmd *cobra.Command, args []string) { log.Errorf("Error creating directory: %v", err) os.Exit(1) } - fmt.Printf("Directory: %s\n", fullDestPath) + styles.PrintKV("Directory", fullDestPath) // Create timestamp filename timestamp := time.Now().Format("06-01-02-15-04-05") // YY-MM-DD-HH-MM-SS zipFileName := timestamp + ".zip" - fmt.Printf("File Name: %s\n", zipFileName) + styles.PrintKV("File Name", zipFileName) zipFilePath := filepath.Join(fullDestPath, zipFileName) - fmt.Printf("File Path: %s\n", zipFilePath) + styles.PrintKV("File Path", zipFilePath) if err := archive.CreateZipArchive(excludes, sourcePath, zipFilePath); err != nil { log.Fatalf("Error creating archive: %v", err) @@ -141,10 +128,10 @@ func backupCmd(cmd *cobra.Command, args []string) { if err != nil { log.Warnf("Error getting archive info: %v", err) } else { - fmt.Printf("Archive Size: %s\n", formatBytes(fileInfo.Size())) + styles.PrintKV("File Size", humanize.Bytes(uint64(fileInfo.Size()))) } - fmt.Printf("Archive File: %s\nSuccess!\n", zipFilePath) + styles.PrintS("Backup Successful", "") } func promptForDestination() string { @@ -189,16 +176,3 @@ func promptForDestination() string { log.Infof("absPath %q", absPath) return absPath } - -func formatBytes(bytes int64) string { - const unit = 1024 - if bytes < unit { - return fmt.Sprintf("%d B", bytes) - } - div, exp := int64(unit), 0 - for n := bytes / unit; n >= unit; n /= unit { - div *= unit - exp++ - } - return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) -} diff --git a/cmd/list.go b/cmd/list.go index 1a5dc65..6d860ce 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -1,10 +1,8 @@ package cmd import ( - "fmt" - "github.com/charmbracelet/lipgloss" - "github.com/charmbracelet/lipgloss/table" "github.com/charmbracelet/log" + "github.com/smashedr/bup/internal/styles" "github.com/spf13/cobra" "github.com/spf13/viper" "os" @@ -42,27 +40,5 @@ func listDir(path, header string) { rows = append(rows, []string{e.Name()}) } log.Debugf("rows: %v", rows) - renderTable(rows, header) -} - -func renderTable(rows [][]string, headers ...string) { - headerStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#bd93f9")). - Bold(true). - Align(lipgloss.Center) - borderStyle := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#7571f9")). - Bold(true) - t := table.New(). - Border(lipgloss.RoundedBorder()). - BorderStyle(borderStyle). - StyleFunc(func(row, col int) lipgloss.Style { - if row == table.HeaderRow { - return headerStyle - } - return lipgloss.NewStyle() - }). - Headers(headers...). - Rows(rows...) - fmt.Println(t) + styles.RenderTable(rows, header) } diff --git a/cmd/root.go b/cmd/root.go index 63d993c..4ed5b88 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "github.com/charmbracelet/log" + "github.com/confluentinc/go-editor" "github.com/spf13/cobra" "github.com/spf13/viper" "os" @@ -21,22 +22,26 @@ var rootCmd = &cobra.Command{ Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { log.Debug("rootCmd:", "args", args) + editFlag, _ := cmd.Flags().GetBool("edit") infoFlag, _ := cmd.Flags().GetBool("info") - log.Info("Flags:", "infoFlag", infoFlag) listFlag, _ := cmd.Flags().GetBool("list") - log.Info("Flags:", "listFlag", listFlag) + log.Info("Flags:", "edit", editFlag, "info", infoFlag, "list", listFlag) - if infoFlag { + switch { + case infoFlag: infoCmd(cmd, args) - return - } - - if listFlag { + case listFlag: listCmd(cmd, args) - return + case editFlag: + file := viper.ConfigFileUsed() + log.Infof("viper.ConfigFileUsed(): %v", file) + edit := editor.NewEditor() + if err := edit.Launch(file); err != nil { + log.Fatal(err) + } + default: + backupCmd(cmd, args) } - - backupCmd(cmd, args) }, } @@ -53,11 +58,13 @@ func Execute() { func init() { cobra.OnInitialize(onInitialize) - rootCmd.PersistentFlags().BoolP("yes", "y", false, "answer yes to confirmations") - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file [default: ~/.config/bup.yaml]") - rootCmd.PersistentFlags().CountVarP(&verbose, "verbose", "v", "verbose output (-vvv debug)") + rootCmd.Flags().BoolP("yes", "y", false, "answer yes to confirmations") + rootCmd.Flags().StringVar(&cfgFile, "config", "", "config file [default: ~/.config/bup.yaml]") + rootCmd.Flags().CountVarP(&verbose, "verbose", "v", "verbose output (-vvv debug)") + rootCmd.Flags().StringSliceP("exclude", "e", []string{}, "inline pattern to exclude") + rootCmd.Flags().BoolP("edit", "E", false, "edit config in default editor") + rootCmd.Flags().BoolP("info", "I", false, "information about bup") rootCmd.Flags().BoolP("list", "l", false, "list backups") - rootCmd.Flags().BoolP("info", "i", false, "information about bup") rootCmd.Flags().BoolP("version", "V", false, "version for bup") } diff --git a/docs/index.md b/docs/index.md index 02f0141..84fb25b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -67,18 +67,24 @@ Specify `source` and `destination`. bup [source] [destination] ``` -The `--list` flag list backups by name. +Use `--list` to list backups. ```shell -bup -l +bup -l [name] ``` -The `--info` flag prints the configuration. +Use `--info` to prints the configuration. ```shell bup -i ``` +Use `--edit` to open the config in an editor. + +```shell +bup -e +``` +   !!! question diff --git a/go.mod b/go.mod index e8e664f..d81cc76 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/charmbracelet/huh v0.8.0 github.com/charmbracelet/lipgloss v1.1.0 github.com/charmbracelet/log v0.4.2 + github.com/confluentinc/go-editor v0.11.0 + github.com/dustin/go-humanize v1.0.1 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 golang.design/x/clipboard v0.7.1 @@ -24,7 +26,6 @@ require ( github.com/charmbracelet/x/term v0.2.2 // indirect github.com/clipperhouse/displaywidth v0.10.0 // indirect github.com/clipperhouse/uax29/v2 v2.6.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect diff --git a/go.sum b/go.sum index dd03f67..fb5dcd0 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/clipperhouse/displaywidth v0.10.0 h1:GhBG8WuerxjFQQYeuZAeVTuyxuX+Urai github.com/clipperhouse/displaywidth v0.10.0/go.mod h1:XqJajYsaiEwkxOj4bowCTMcT1SgvHo9flfF3jQasdbs= github.com/clipperhouse/uax29/v2 v2.6.0 h1:z0cDbUV+aPASdFb2/ndFnS9ts/WNXgTNNGFoKXuhpos= github.com/clipperhouse/uax29/v2 v2.6.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= +github.com/confluentinc/go-editor v0.11.0 h1:fcEALYHj7xV/fRSp54/IHi2DS4GlZMJWVgrYvi/llvU= +github.com/confluentinc/go-editor v0.11.0/go.mod h1:nEjwqdqx8S7ZGjXsDvRgawsA04Fu2P/KAtA8fa5afMI= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= diff --git a/internal/styles/styles.go b/internal/styles/styles.go new file mode 100644 index 0000000..2f6abe7 --- /dev/null +++ b/internal/styles/styles.go @@ -0,0 +1,73 @@ +package styles + +import ( + "fmt" + "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/table" +) + +var Success = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#00ff00")) +var Warning = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#ffff00")) +var Failure = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#ff0000")) + +var Key = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#00ADD8")). + Width(12) +var Value = lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("#73FA91")) + +var Command = lipgloss.NewStyle(). + Bold(true). + Background(lipgloss.Color("#00ADD8")). + Foreground(lipgloss.Color("#F7F7F7")) + +var Head = lipgloss.NewStyle(). + Bold(true). + Background(lipgloss.Color("#006bb0")). + Foreground(lipgloss.Color("#00ff00")). + Padding(1). + Align(lipgloss.Center) + +var TableBorder = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#bc94f7")). + Bold(true) +var TableHeader = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#00ADD8")). + Bold(true). + Align(lipgloss.Center) +var TableRow = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#F8F8F8")). + PaddingLeft(1).PaddingRight(1) + +func PrintS(key, format string, args ...interface{}) { + fmt.Println(Success.Render(key) + " " + fmt.Sprintf(format, args...)) +} +func PrintF(key, format string, args ...interface{}) { + fmt.Println(Failure.Render(key) + " " + fmt.Sprintf(format, args...)) +} +func PrintKV(key, value string) { + fmt.Println(Key.Render(key) + " " + Value.Render(value)) +} + +func RenderTable(rows [][]string, headers ...string) { + t := table.New(). + Border(lipgloss.RoundedBorder()). + BorderStyle(TableBorder). + StyleFunc(func(row, col int) lipgloss.Style { + if row == table.HeaderRow { + return TableHeader + } + return TableRow + }). + Headers(headers...). + Rows(rows...) + fmt.Println(t) +} From f4bc38b6a147a8020d089ff8810e87d0115b31d7 Mon Sep 17 00:00:00 2001 From: Shane <6071159+smashedr@users.noreply.github.com> Date: Thu, 12 Feb 2026 22:29:08 -0800 Subject: [PATCH 3/3] Update Tapes --- Taskfile.yml | 2 +- assets/banner.tape | 6 +++--- assets/main.tape | 12 +++++++++--- cmd/info.go | 11 +++++++---- cmd/root.go | 11 ++++++----- 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/Taskfile.yml b/Taskfile.yml index 96ac226..63c070f 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -58,7 +58,7 @@ tasks: TARGET_NAME: bup cmd: | go build - mv {{.TARGET_NAME}} "${HOME}/bin/{{.TARGET_NAME}}" + mv -f {{.TARGET_NAME}} "${HOME}/bin/{{.TARGET_NAME}}" which {{.TARGET_NAME}} {{.TARGET_NAME}} -V diff --git a/assets/banner.tape b/assets/banner.tape index 4a1cf14..0fa50f3 100644 --- a/assets/banner.tape +++ b/assets/banner.tape @@ -15,10 +15,10 @@ Set Height 640 Set FontSize 24 Hide -#Type "bup --help | head -n 16 && echo ' ...'" -Type "bup --help" +Type "bup --help | head -n 14" +#Type "bup --help" Enter Show Sleep 1.5s -Source assets/main.tape +#Source assets/main.tape diff --git a/assets/main.tape b/assets/main.tape index da066d1..00f798b 100644 --- a/assets/main.tape +++ b/assets/main.tape @@ -1,6 +1,6 @@ # Source assets/main.tape -Type "bup backup ." +Type "bup ." Sleep 0.5s Enter Wait /Yes\./ @@ -12,19 +12,25 @@ Sleep 0.4s Enter Sleep 3s -Type "bup list" +Type "bup --list" Sleep 0.5s Enter Wait #Screenshot dist/bup.png Sleep 2s -Type "bup l bup" +Type "bup -l bup" Sleep 0.5s Enter Wait Sleep 2s +Type "bup --info" +Sleep 0.5s +Enter +Wait +Sleep 1.5s + Type "clear" Sleep 0.3s Enter diff --git a/cmd/info.go b/cmd/info.go index 169ae93..e5d3e3d 100644 --- a/cmd/info.go +++ b/cmd/info.go @@ -3,6 +3,7 @@ package cmd import ( "fmt" "github.com/charmbracelet/log" + "github.com/smashedr/bup/internal/styles" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -10,9 +11,11 @@ import ( func infoCmd(cmd *cobra.Command, args []string) { log.Debug("infoCmd:", "args", args) - fmt.Printf("cfgFile: %s\n", cfgFile) - fmt.Printf("viper.ConfigFileUsed: %s\n", viper.ConfigFileUsed()) + styles.PrintKV("Config Flag", fmt.Sprintf("%q", cfgFile)) + styles.PrintKV("Config Used", viper.ConfigFileUsed()) + destination := viper.GetString("destination") - fmt.Printf("destination: %s\n", destination) - fmt.Printf("excludes: %s\n", viper.GetStringSlice("excludes")) + styles.PrintKV("Destination", destination) + + styles.PrintKV("Excludes", fmt.Sprintf("%v", viper.GetStringSlice("excludes"))) } diff --git a/cmd/root.go b/cmd/root.go index 4ed5b88..028cb3c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -58,14 +58,15 @@ func Execute() { func init() { cobra.OnInitialize(onInitialize) - rootCmd.Flags().BoolP("yes", "y", false, "answer yes to confirmations") - rootCmd.Flags().StringVar(&cfgFile, "config", "", "config file [default: ~/.config/bup.yaml]") - rootCmd.Flags().CountVarP(&verbose, "verbose", "v", "verbose output (-vvv debug)") + rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "config file [default: ~/.config/bup.yaml]") rootCmd.Flags().StringSliceP("exclude", "e", []string{}, "inline pattern to exclude") + rootCmd.Flags().CountVarP(&verbose, "verbose", "v", "verbose output (-vvv debug)") + rootCmd.Flags().BoolP("version", "V", false, "version for bup") + rootCmd.Flags().BoolP("yes", "y", false, "answer yes to confirmations") + // Flag Commands rootCmd.Flags().BoolP("edit", "E", false, "edit config in default editor") - rootCmd.Flags().BoolP("info", "I", false, "information about bup") + rootCmd.Flags().BoolP("info", "i", false, "information about bup") rootCmd.Flags().BoolP("list", "l", false, "list backups") - rootCmd.Flags().BoolP("version", "V", false, "version for bup") } func onInitialize() {