diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..19ccad6 --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,68 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "go.uploadedlobster.com/scotty/internal/cli" +) + +// deleteCmd represents the add command +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete existing service configuration", + Long: `Delete an existing service configuration.`, + Run: func(cmd *cobra.Command, args []string) { + service, err := cli.SelectService() + cobra.CheckErr(err) + + // Prompt for deletion + delete, err := cli.PromptYesNo(fmt.Sprintf("Delete the service configuration \"%v\"?", service)) + cobra.CheckErr(err) + + if !delete { + fmt.Println("Aborted") + return + } + + // Delete the service config + err = service.Delete() + cobra.CheckErr(err) + fmt.Printf("Service \"%v\" deleted\n", service.Name) + }, +} + +func init() { + serviceCmd.AddCommand(deleteCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // deleteCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // deleteCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/go.mod b/go.mod index 04b84d4..a827f0f 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/go-resty/resty/v2 v2.10.0 github.com/jarcoal/httpmock v1.3.1 github.com/manifoldco/promptui v0.9.0 + github.com/pelletier/go-toml/v2 v2.1.0 github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 github.com/spf13/cast v1.5.1 github.com/spf13/cobra v1.8.0 @@ -42,7 +43,6 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.4 // indirect diff --git a/internal/cli/prompt.go b/internal/cli/prompt.go index 0fbe979..19038e9 100644 --- a/internal/cli/prompt.go +++ b/internal/cli/prompt.go @@ -59,8 +59,12 @@ func PromptSecret(opt models.BackendOption) (string, error) { } func PromptBool(opt models.BackendOption) (bool, error) { + return PromptYesNo(opt.Label) +} + +func PromptYesNo(label string) (bool, error) { sel := promptui.Select{ - Label: opt.Label, + Label: label, Items: []string{"Yes", "No"}, } _, val, err := sel.Run() diff --git a/internal/config/config.go b/internal/config/config.go index 746e79c..6d68d00 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,12 +16,15 @@ Scotty. If not, see . package config import ( + "errors" "fmt" "os" "path" "path/filepath" "regexp" + "strings" + "github.com/pelletier/go-toml/v2" "github.com/spf13/cobra" "github.com/spf13/viper" "go.uploadedlobster.com/scotty/internal/version" @@ -30,6 +33,7 @@ import ( const ( defaultDatabase = "scotty.sqlite3" defaultOAuthHost = "127.0.0.1:2369" + fileMode = 0640 ) func DefaultConfigDir() string { @@ -48,7 +52,7 @@ func InitConfig(cfgFile string) error { viper.AddConfigPath(configDir) viper.SetConfigType("toml") viper.SetConfigName(version.AppName) - viper.SetConfigPermissions(0640) + viper.SetConfigPermissions(fileMode) } setDefaults() @@ -67,6 +71,43 @@ func InitConfig(cfgFile string) error { return viper.ReadInConfig() } +// Write the configuration except for removedKeys +func WriteConfig(removedKeys ...string) error { + file := viper.ConfigFileUsed() + if len(file) == 0 { + return errors.New("no configuration file defined, cannot write config") + } + + configMap := viper.AllSettings() + for _, key := range removedKeys { + c := configMap + ok := true + subKeys := strings.Split(key, ".") + keyLen := len(subKeys) + // Deep search the key in the config and delete the deepest key, if it exists + for i, s := range subKeys { + if i == keyLen-1 { + // This is the final key, delete it from the map + delete(c, s) + } else { + // Use the child for next iteration if it is a map + c, ok = c[s].(map[string]any) + if !ok { + // Child is not a map, can't search deeper + break + } + } + } + } + + content, err := toml.Marshal(configMap) + if err != nil { + return err + } + + return os.WriteFile(file, content, fileMode) +} + func DatabasePath() string { path := viper.GetString("database") if filepath.IsAbs(path) { diff --git a/internal/config/services.go b/internal/config/services.go index eed2678..8a191f3 100644 --- a/internal/config/services.go +++ b/internal/config/services.go @@ -68,7 +68,13 @@ func (c *ServiceConfig) Save() error { for k, v := range c.ConfigValues { viper.Set(key+"."+k, v) } - return viper.WriteConfig() + return WriteConfig() +} + +// Deletes the service configuration from the config file +func (c *ServiceConfig) Delete() error { + key := "service." + c.Name + return WriteConfig(key) } type ServiceList []ServiceConfig