From ae5f1c5f269b8380af55be3fc7bb7c987efcf97b Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Tue, 5 Dec 2023 17:41:15 +0100 Subject: [PATCH] Basic TUI to add new service configuration --- cmd/add.go | 88 ++++++++++++++++++++++++++++++++++ cmd/config.go | 48 +++++++++++++++++++ go.mod | 2 + go.sum | 4 ++ internal/backends/backends.go | 24 +++++++++- internal/config/config.go | 13 +++++ internal/config/config_test.go | 32 +++++++++++++ internal/config/services.go | 33 +++++++++++++ 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 cmd/add.go create mode 100644 cmd/config.go create mode 100644 internal/config/config_test.go create mode 100644 internal/config/services.go diff --git a/cmd/add.go b/cmd/add.go new file mode 100644 index 0000000..1803e9b --- /dev/null +++ b/cmd/add.go @@ -0,0 +1,88 @@ +/* +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/manifoldco/promptui" + "github.com/spf13/cobra" + "go.uploadedlobster.com/scotty/internal/backends" + "go.uploadedlobster.com/scotty/internal/config" +) + +// addCmd represents the add command +var addCmd = &cobra.Command{ + Use: "add", + Short: "Add a service configuration", + Long: `Add a service configuration.`, + Run: func(cmd *cobra.Command, args []string) { + // Select backend + sel := promptui.Select{ + Label: "Backend", + Items: backends.GetBackends(), + Size: 10, + } + _, backend, err := sel.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + // Set service name + prompt := promptui.Prompt{ + Label: "Service name", + Validate: config.ValidateKey, + Default: backend, + } + + name, err := prompt.Run() + if err != nil { + fmt.Printf("Prompt failed %v\n", err) + return + } + + // Save the service config + service := config.ServiceConfig{ + Name: name, + Backend: backend, + } + err = service.Save() + cobra.CheckErr(err) + + fmt.Printf("Saved service %v using backend %v\n", service.Name, service.Backend) + }, +} + +func init() { + configCmd.AddCommand(addCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // addCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/config.go b/cmd/config.go new file mode 100644 index 0000000..ced8585 --- /dev/null +++ b/cmd/config.go @@ -0,0 +1,48 @@ +/* +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 ( + "github.com/spf13/cobra" +) + +// configCmd represents the config command +var configCmd = &cobra.Command{ + Use: "config", + Short: "Manage the configuration", + Long: `Manage the scotty configuration using the subcommands to add, remove +or edit services.`, +} + +func init() { + rootCmd.AddCommand(configCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // configCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // configCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/go.mod b/go.mod index e8feb7c..4404fda 100644 --- a/go.mod +++ b/go.mod @@ -23,6 +23,7 @@ require ( require ( github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect @@ -35,6 +36,7 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect diff --git a/go.sum b/go.sum index 51d651e..7716b50 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,7 @@ github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpH github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo= @@ -186,6 +187,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= @@ -376,6 +379,7 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/internal/backends/backends.go b/internal/backends/backends.go index 02f29ae..3f4e2c9 100644 --- a/internal/backends/backends.go +++ b/internal/backends/backends.go @@ -19,6 +19,7 @@ package backends import ( "fmt" "reflect" + "sort" "strings" "github.com/spf13/viper" @@ -41,6 +42,24 @@ type BackendInfo struct { ImportCapabilities []Capability } +func (b BackendInfo) String() string { + return b.Name +} + +type BackendList []BackendInfo + +func (l BackendList) Len() int { + return len(l) +} + +func (l BackendList) Less(i, j int) bool { + return l[i].Name < l[j].Name +} + +func (l BackendList) Swap(i, j int) { + l[i], l[j] = l[j], l[i] +} + type Capability = string func ResolveBackend[T interface{}](config *viper.Viper) (T, error) { @@ -59,8 +78,8 @@ func ResolveBackend[T interface{}](config *viper.Viper) (T, error) { return result, err } -func GetBackends() []BackendInfo { - backends := make([]BackendInfo, 0) +func GetBackends() BackendList { + backends := make(BackendList, 0) for name, backendFunc := range knownBackends { backend := backendFunc() info := BackendInfo{ @@ -71,6 +90,7 @@ func GetBackends() []BackendInfo { backends = append(backends, info) } + sort.Sort(backends) return backends } diff --git a/internal/config/config.go b/internal/config/config.go index ece3544..746e79c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,9 +16,11 @@ Scotty. If not, see . package config import ( + "fmt" "os" "path" "path/filepath" + "regexp" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -74,6 +76,17 @@ func DatabasePath() string { return filepath.Join(getConfigDir(), path) } +func ValidateKey(key string) error { + found, err := regexp.MatchString("^[A-Za-z0-9_-]+$", key) + if err != nil { + return err + } else if found { + return nil + } else { + return fmt.Errorf("key must only consist of A-Za-z0-9_-") + } +} + func setDefaults() { viper.SetDefault("database", defaultDatabase) viper.SetDefault("oauth-host", defaultOAuthHost) diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..4616857 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,32 @@ +/* +Copyright © 2023 Philipp Wolfer + +Scotty is free software: you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later version. + +Scotty is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +Scotty. If not, see . +*/ + +package config_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uploadedlobster.com/scotty/internal/config" +) + +func TestTimestampUpdate(t *testing.T) { + assert := assert.New(t) + assert.Nil(config.ValidateKey("foo")) + assert.Nil(config.ValidateKey("foo123")) + assert.Nil(config.ValidateKey("foo_bar-123")) + assert.NotNil(config.ValidateKey("foo/bar")) + assert.NotNil(config.ValidateKey("bär")) +} diff --git a/internal/config/services.go b/internal/config/services.go new file mode 100644 index 0000000..e3a15e8 --- /dev/null +++ b/internal/config/services.go @@ -0,0 +1,33 @@ +/* +Copyright © 2023 Philipp Wolfer + +Scotty is free software: you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation, either version 3 of the License, or (at your option) any later version. + +Scotty is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +Scotty. If not, see . +*/ + +package config + +import "github.com/spf13/viper" + +type ServiceConfig struct { + Name string + Backend string + ConfigValues map[string]any +} + +func (c *ServiceConfig) Save() error { + key := "service." + c.Name + viper.Set(key+".backend", c.Backend) + for k, v := range c.ConfigValues { + viper.Set(key+"."+k, v) + } + return viper.WriteConfig() +}