mirror of
https://git.sr.ht/~phw/scotty
synced 2025-04-16 01:59:29 +02:00
Implemented service edit command
This commit is contained in:
parent
c6c0723e27
commit
58a47a43e7
15 changed files with 213 additions and 57 deletions
39
cmd/add.go
39
cmd/add.go
|
@ -27,7 +27,6 @@ import (
|
|||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
"go.uploadedlobster.com/scotty/internal/backends"
|
||||
"go.uploadedlobster.com/scotty/internal/cli"
|
||||
"go.uploadedlobster.com/scotty/internal/config"
|
||||
)
|
||||
|
@ -39,12 +38,7 @@ var addCmd = &cobra.Command{
|
|||
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()
|
||||
backend, err := cli.SelectBackend("")
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Set service name
|
||||
|
@ -64,12 +58,13 @@ var addCmd = &cobra.Command{
|
|||
|
||||
// Prepare service config
|
||||
service := config.ServiceConfig{
|
||||
Name: name,
|
||||
Backend: backend,
|
||||
Name: name,
|
||||
Backend: backend,
|
||||
ConfigValues: make(map[string]any),
|
||||
}
|
||||
|
||||
// Additional options
|
||||
err = extraOptions(&service)
|
||||
service, err = cli.PromptExtraOptions(service)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Save the service config
|
||||
|
@ -92,27 +87,3 @@ func init() {
|
|||
// is called directly, e.g.:
|
||||
// addCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
||||
|
||||
func extraOptions(config *config.ServiceConfig) error {
|
||||
backend, err := backends.BackendByName(config.Backend)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts := backend.Options()
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
values := make(map[string]any, len(*opts))
|
||||
for _, opt := range *opts {
|
||||
val, err := cli.Prompt(opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
values[opt.Name] = val
|
||||
|
||||
}
|
||||
|
||||
config.ConfigValues = values
|
||||
return nil
|
||||
}
|
||||
|
|
68
cmd/edit.go
Normal file
68
cmd/edit.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// editCmd represents the add command
|
||||
var editCmd = &cobra.Command{
|
||||
Use: "edit",
|
||||
Short: "Edit existing service configuration",
|
||||
Long: `Edit an existing service configuration.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
service, err := cli.SelectService()
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Select backend
|
||||
backend, err := cli.SelectBackend(service.Backend)
|
||||
cobra.CheckErr(err)
|
||||
service.Backend = backend
|
||||
|
||||
// Additional options
|
||||
service, err = cli.PromptExtraOptions(service)
|
||||
cobra.CheckErr(err)
|
||||
|
||||
// Save the service config
|
||||
err = service.Save()
|
||||
cobra.CheckErr(err)
|
||||
fmt.Printf("Updated service %v using backend %v\n", service.Name, service.Backend)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serviceCmd.AddCommand(editCmd)
|
||||
|
||||
// Here you will define your flags and configuration settings.
|
||||
|
||||
// Cobra supports Persistent Flags which will work for this command
|
||||
// and all subcommands, e.g.:
|
||||
// editCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||
|
||||
// Cobra supports local flags which will only run when this command
|
||||
// is called directly, e.g.:
|
||||
// editCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||
}
|
|
@ -36,8 +36,8 @@ type DeezerApiBackend struct {
|
|||
|
||||
func (b *DeezerApiBackend) Name() string { return "deezer" }
|
||||
|
||||
func (b *DeezerApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *DeezerApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "client-id",
|
||||
Label: "Client ID",
|
||||
Type: models.String,
|
||||
|
|
|
@ -25,7 +25,7 @@ type DumpBackend struct{}
|
|||
|
||||
func (b *DumpBackend) Name() string { return "dump" }
|
||||
|
||||
func (b *DumpBackend) Options() *[]models.BackendOption { return nil }
|
||||
func (b *DumpBackend) Options() []models.BackendOption { return nil }
|
||||
|
||||
func (b *DumpBackend) FromConfig(config *config.ServiceConfig) models.Backend {
|
||||
return b
|
||||
|
|
|
@ -33,8 +33,8 @@ type FunkwhaleApiBackend struct {
|
|||
|
||||
func (b *FunkwhaleApiBackend) Name() string { return "funkwhale" }
|
||||
|
||||
func (b *FunkwhaleApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *FunkwhaleApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "server-url",
|
||||
Label: "Server URL",
|
||||
Type: models.String,
|
||||
|
|
|
@ -36,8 +36,8 @@ type JSPFBackend struct {
|
|||
|
||||
func (b *JSPFBackend) Name() string { return "jspf" }
|
||||
|
||||
func (b *JSPFBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *JSPFBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "file-path",
|
||||
Label: "File path",
|
||||
Type: models.String,
|
||||
|
|
|
@ -43,8 +43,8 @@ type LastfmApiBackend struct {
|
|||
|
||||
func (b *LastfmApiBackend) Name() string { return "lastfm" }
|
||||
|
||||
func (b *LastfmApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *LastfmApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "username",
|
||||
Label: "User name",
|
||||
Type: models.String,
|
||||
|
|
|
@ -34,8 +34,8 @@ type ListenBrainzApiBackend struct {
|
|||
|
||||
func (b *ListenBrainzApiBackend) Name() string { return "listenbrainz" }
|
||||
|
||||
func (b *ListenBrainzApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *ListenBrainzApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "username",
|
||||
Label: "User name",
|
||||
Type: models.String,
|
||||
|
|
|
@ -33,8 +33,8 @@ type MalojaApiBackend struct {
|
|||
|
||||
func (b *MalojaApiBackend) Name() string { return "maloja" }
|
||||
|
||||
func (b *MalojaApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *MalojaApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "server-url",
|
||||
Label: "Server URL",
|
||||
Type: models.String,
|
||||
|
|
|
@ -36,8 +36,8 @@ type ScrobblerLogBackend struct {
|
|||
|
||||
func (b *ScrobblerLogBackend) Name() string { return "scrobbler-log" }
|
||||
|
||||
func (b *ScrobblerLogBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *ScrobblerLogBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "file-path",
|
||||
Label: "File path",
|
||||
Type: models.String,
|
||||
|
|
|
@ -39,8 +39,8 @@ type SpotifyApiBackend struct {
|
|||
|
||||
func (b *SpotifyApiBackend) Name() string { return "spotify" }
|
||||
|
||||
func (b *SpotifyApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *SpotifyApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "client-id",
|
||||
Label: "Client ID",
|
||||
Type: models.String,
|
||||
|
|
|
@ -34,8 +34,8 @@ type SubsonicApiBackend struct {
|
|||
|
||||
func (b *SubsonicApiBackend) Name() string { return "subsonic" }
|
||||
|
||||
func (b *SubsonicApiBackend) Options() *[]models.BackendOption {
|
||||
return &[]models.BackendOption{{
|
||||
func (b *SubsonicApiBackend) Options() []models.BackendOption {
|
||||
return []models.BackendOption{{
|
||||
Name: "server-url",
|
||||
Label: "Server URL",
|
||||
Type: models.String,
|
||||
|
|
88
internal/cli/services.go
Normal file
88
internal/cli/services.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package cli
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"go.uploadedlobster.com/scotty/internal/backends"
|
||||
"go.uploadedlobster.com/scotty/internal/config"
|
||||
)
|
||||
|
||||
func SelectService() (config.ServiceConfig, error) {
|
||||
services := config.AllServicesAsList()
|
||||
if len(services) == 0 {
|
||||
err := errors.New("no existing service configurations")
|
||||
return config.ServiceConfig{}, err
|
||||
}
|
||||
sel := promptui.Select{
|
||||
Label: "Service",
|
||||
Items: services,
|
||||
Size: 10,
|
||||
}
|
||||
i, _, err := sel.Run()
|
||||
if err != nil {
|
||||
return config.ServiceConfig{}, err
|
||||
}
|
||||
return services[i], nil
|
||||
}
|
||||
|
||||
func SelectBackend(selected string) (string, error) {
|
||||
backendList := backends.GetBackends()
|
||||
i := slices.IndexFunc(backendList, func(b backends.BackendInfo) bool {
|
||||
return b.Name == selected
|
||||
})
|
||||
sel := promptui.Select{
|
||||
Label: "Backend",
|
||||
Items: backendList,
|
||||
CursorPos: i,
|
||||
Size: 10,
|
||||
}
|
||||
_, backend, err := sel.Run()
|
||||
return backend, err
|
||||
}
|
||||
|
||||
func PromptExtraOptions(config config.ServiceConfig) (config.ServiceConfig, error) {
|
||||
backend, err := backends.BackendByName(config.Backend)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
opts := backend.Options()
|
||||
if opts == nil {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
values := make(map[string]any, len(opts))
|
||||
for _, opt := range opts {
|
||||
// Use current value as default
|
||||
current, exists := config.ConfigValues[opt.Name]
|
||||
if exists {
|
||||
opt.Default = fmt.Sprintf("%v", current)
|
||||
}
|
||||
|
||||
val, err := Prompt(opt)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
values[opt.Name] = val
|
||||
}
|
||||
|
||||
config.ConfigValues = values
|
||||
return config, nil
|
||||
}
|
|
@ -17,6 +17,7 @@ package config
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -44,6 +45,10 @@ func NewServiceConfig(name string, config *viper.Viper) ServiceConfig {
|
|||
return service
|
||||
}
|
||||
|
||||
func (c ServiceConfig) String() string {
|
||||
return c.Name
|
||||
}
|
||||
|
||||
func (c *ServiceConfig) GetString(key string) string {
|
||||
return cast.ToString(c.ConfigValues[key])
|
||||
}
|
||||
|
@ -66,13 +71,27 @@ func (c *ServiceConfig) Save() error {
|
|||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
type ServiceList []ServiceConfig
|
||||
|
||||
func (l ServiceList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (l ServiceList) Less(i, j int) bool {
|
||||
return l[i].Name < l[j].Name
|
||||
}
|
||||
|
||||
func (l ServiceList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
func AllServices() map[string]ServiceConfig {
|
||||
services := make(map[string]ServiceConfig)
|
||||
config := viper.Sub("service")
|
||||
if config != nil {
|
||||
for k, v := range config.AllSettings() {
|
||||
s, ok := v.(*viper.Viper)
|
||||
if ok {
|
||||
for k := range config.AllSettings() {
|
||||
s := config.Sub(k)
|
||||
if s != nil {
|
||||
services[k] = NewServiceConfig(k, s)
|
||||
}
|
||||
}
|
||||
|
@ -80,6 +99,16 @@ func AllServices() map[string]ServiceConfig {
|
|||
return services
|
||||
}
|
||||
|
||||
func AllServicesAsList() ServiceList {
|
||||
services := AllServices()
|
||||
list := make(ServiceList, 0, len(services))
|
||||
for _, s := range services {
|
||||
list = append(list, s)
|
||||
}
|
||||
sort.Sort(list)
|
||||
return list
|
||||
}
|
||||
|
||||
func GetService(name string) (*ServiceConfig, error) {
|
||||
key := "service." + name
|
||||
config := viper.Sub(key)
|
||||
|
|
|
@ -35,7 +35,7 @@ type Backend interface {
|
|||
FromConfig(config *config.ServiceConfig) Backend
|
||||
|
||||
// Return configuration options
|
||||
Options() *[]BackendOption
|
||||
Options() []BackendOption
|
||||
}
|
||||
|
||||
type ImportBackend interface {
|
||||
|
|
Loading…
Add table
Reference in a new issue