From 0b77cb149519e07407167cb6b3ff245e1173c679 Mon Sep 17 00:00:00 2001 From: Adrian Reber Date: Fri, 1 Aug 2025 15:27:02 +0000 Subject: [PATCH] feat(config): add --no-confirm flag to automatically create config files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add --no-confirm persistent flag to config command that automatically creates config files and parent directories without prompting for confirmation. Implementation details: - Add --no-confirm flag to config command as persistent flag - Update askToCreate function to accept noConfirm parameter - When noConfirm is true, askToCreate returns true without prompting - Simplify handleFileCreation to pass flag directly to askToCreate - Centralize all noConfirm logic within askToCreate function - Maintain backward compatibility with existing askToCreate behavior - Add test case for noConfirm functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude Signed-off-by: Adrian Reber --- cmd/config-cluster-set.go | 19 ++----------------- cmd/config-set.go | 19 ++----------------- cmd/config.go | 1 + cmd/lib.go | 37 ++++++++++++++++++++++++++++++++++--- cmd/lib_test.go | 33 +++++++++++++++++++++++++++++---- 5 files changed, 68 insertions(+), 41 deletions(-) diff --git a/cmd/config-cluster-set.go b/cmd/config-cluster-set.go index 0f957fe0..81f57cb7 100644 --- a/cmd/config-cluster-set.go +++ b/cmd/config-cluster-set.go @@ -61,23 +61,8 @@ See ochami-config(5) for details on the configuration options.`, fileToModify = config.UserConfigFile } - // Ask to create file if it doesn't exist - if create, err := ios.askToCreate(fileToModify); err != nil { - if err != FileExistsError { - log.Logger.Error().Err(err).Msg("error asking to create file") - logHelpError(cmd) - os.Exit(1) - } - } else if create { - if err := createIfNotExists(fileToModify); err != nil { - log.Logger.Error().Err(err).Msg("error creating file") - logHelpError(cmd) - os.Exit(1) - } - } else { - log.Logger.Error().Msg("user declined to create file, not modifying") - os.Exit(0) - } + // Handle file creation based on --no-confirm flag + handleFileCreation(cmd, fileToModify) // Perform modification dflt, err := cmd.Flags().GetBool("default") diff --git a/cmd/config-set.go b/cmd/config-set.go index 94d7a7a8..e35bd622 100644 --- a/cmd/config-set.go +++ b/cmd/config-set.go @@ -58,23 +58,8 @@ See ochami-config(5) for details on the configuration options.`, os.Exit(1) } - // Ask to create file if it doesn't exist. - if create, err := ios.askToCreate(fileToModify); err != nil { - if err != FileExistsError { - log.Logger.Error().Err(err).Msg("error asking to create file") - logHelpError(cmd) - os.Exit(1) - } - } else if create { - if err := createIfNotExists(fileToModify); err != nil { - log.Logger.Error().Err(err).Msg("error creating file") - logHelpError(cmd) - os.Exit(1) - } - } else { - log.Logger.Error().Msg("user declined to create file, not modifying") - os.Exit(0) - } + // Handle file creation based on --no-confirm flag + handleFileCreation(cmd, fileToModify) // Perform modification if err := config.ModifyConfig(fileToModify, args[0], args[1]); err != nil { diff --git a/cmd/config.go b/cmd/config.go index e1e2a8da..c11d26e9 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -39,6 +39,7 @@ See ochami-config(5) for details on the configuration options.`, func init() { configCmd.PersistentFlags().Bool("system", false, "modify system config") configCmd.PersistentFlags().Bool("user", true, "modify user config") + configCmd.PersistentFlags().Bool("no-confirm", false, "create config file and parent directories without confirmation") rootCmd.AddCommand(configCmd) } diff --git a/cmd/lib.go b/cmd/lib.go index 4e2a4262..39c44637 100644 --- a/cmd/lib.go +++ b/cmd/lib.go @@ -61,12 +61,15 @@ func newIOStream(stdin io.Reader, stdout, stderr io.Writer) ioStream { // askToCreate prompts the user to, if path does not exist, to create a blank // file at path. If it exists, nil is returned. If the user declines, a // UserDeclinedError is returned. If an error occurs during creation, an error -// is returned. -func (i ioStream) askToCreate(path string) (bool, error) { +// is returned. If noConfirm is true, it automatically returns true without prompting. +func (i ioStream) askToCreate(path string, noConfirm bool) (bool, error) { if path == "" { return false, fmt.Errorf("path cannot be empty") } if _, err := os.Stat(path); os.IsNotExist(err) { + if noConfirm { + return true, nil + } respConfigCreate, err2 := i.loopYesNo(fmt.Sprintf("%s does not exist. Create it?", path)) if err2 != nil { return false, fmt.Errorf("error fetching user input: %w", err2) @@ -116,7 +119,7 @@ func initConfig(cmd *cobra.Command, create bool) error { if configFile != "" { if create { // Try to create config file with default values if it doesn't exist - if cr, err := ios.askToCreate(configFile); err != nil { + if cr, err := ios.askToCreate(configFile, false); err != nil { // Only return error if error is not one that the file // already exists. if !errors.Is(err, FileExistsError) { @@ -216,6 +219,34 @@ func createIfNotExists(path string) error { return nil } +// handleFileCreation checks if a file should be created based on the --no-confirm flag. +// The flag is passed to askToCreate which handles the logic. +func handleFileCreation(cmd *cobra.Command, fileToModify string) { + noConfirmFlag, err := cmd.Flags().GetBool("no-confirm") + if err != nil { + log.Logger.Error().Err(err).Msg("failed to retrieve \"no-confirm\" flag") + logHelpError(cmd) + os.Exit(1) + } + + if create, err := ios.askToCreate(fileToModify, noConfirmFlag); err != nil { + if err != FileExistsError { + log.Logger.Error().Err(err).Msg("error asking to create file") + logHelpError(cmd) + os.Exit(1) + } + } else if create { + if err := createIfNotExists(fileToModify); err != nil { + log.Logger.Error().Err(err).Msg("error creating file") + logHelpError(cmd) + os.Exit(1) + } + } else { + log.Logger.Error().Msg("user declined to create file, not modifying") + os.Exit(0) + } +} + // checkToken takes a pointer to a Cobra command and checks to see if --token // was set. If not, an error is printed and the program exits. func checkToken(cmd *cobra.Command) { diff --git a/cmd/lib_test.go b/cmd/lib_test.go index d5946cd4..97895802 100644 --- a/cmd/lib_test.go +++ b/cmd/lib_test.go @@ -21,7 +21,7 @@ func TestIOStream_askToCreate(t *testing.T) { errBuf := &bytes.Buffer{} ios := newIOStream(inBuf, outBuf, errBuf) - got, err := ios.askToCreate("") + got, err := ios.askToCreate("", false) if got != false { t.Errorf("askToCreate(\"\") = %v, want false", got) } @@ -49,7 +49,7 @@ func TestIOStream_askToCreate(t *testing.T) { errBuf := &bytes.Buffer{} ios := newIOStream(inBuf, outBuf, errBuf) - got, err := ios.askToCreate(f) + got, err := ios.askToCreate(f, false) if got != false { t.Errorf("askToCreate(%q) = %v, want false", f, got) } @@ -74,7 +74,7 @@ func TestIOStream_askToCreate(t *testing.T) { errBuf := &bytes.Buffer{} ios := newIOStream(inBuf, outBuf, errBuf) - got, err := ios.askToCreate(path) + got, err := ios.askToCreate(path, false) if got != false { t.Errorf("askToCreate(%q) decline = %v, want false", path, got) } @@ -100,7 +100,7 @@ func TestIOStream_askToCreate(t *testing.T) { errBuf := &bytes.Buffer{} ios := newIOStream(inBuf, outBuf, errBuf) - got, err := ios.askToCreate(path) + got, err := ios.askToCreate(path, false) if got != true { t.Errorf("askToCreate(%q) accept = %v, want true", path, got) } @@ -115,6 +115,31 @@ func TestIOStream_askToCreate(t *testing.T) { t.Errorf("stdout = %q, want empty", outBuf.String()) } }) + + t.Run("nonexistent file, no-confirm flag", func(t *testing.T) { + t.Parallel() + tmp := t.TempDir() + path := filepath.Join(tmp, "noexist3") + + inBuf := &bytes.Buffer{} + outBuf := &bytes.Buffer{} + errBuf := &bytes.Buffer{} + ios := newIOStream(inBuf, outBuf, errBuf) + + got, err := ios.askToCreate(path, true) + if got != true { + t.Errorf("askToCreate(%q) noConfirm = %v, want true", path, got) + } + if err != nil { + t.Errorf("askToCreate(%q) noConfirm error = %v, want nil", path, err) + } + if errBuf.Len() != 0 { + t.Errorf("stderr = %q, want empty (no prompt should be shown)", errBuf.String()) + } + if outBuf.Len() != 0 { + t.Errorf("stdout = %q, want empty", outBuf.String()) + } + }) } func TestIOStream_loopYesNo(t *testing.T) {