Skip to content

Commit

Permalink
remove config command and add a 'setup' command using a fancy huh form
Browse files Browse the repository at this point in the history
  • Loading branch information
RobBrazier committed Nov 2, 2024
1 parent 2f1fb81 commit 8c13557
Show file tree
Hide file tree
Showing 16 changed files with 350 additions and 262 deletions.
26 changes: 0 additions & 26 deletions cmd/config/get.go

This file was deleted.

25 changes: 0 additions & 25 deletions cmd/config/root.go

This file was deleted.

53 changes: 0 additions & 53 deletions cmd/config/set.go

This file was deleted.

47 changes: 0 additions & 47 deletions cmd/login.go

This file was deleted.

35 changes: 25 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package cmd

import (
"errors"
"fmt"
"log/slog"
"maps"
"os"
"slices"

"github.com/RobBrazier/readflow/internal"
"github.com/RobBrazier/readflow/source"
"github.com/RobBrazier/readflow/target"
"github.com/adrg/xdg"
"github.com/spf13/cobra"
Expand All @@ -14,24 +18,33 @@ import (

var cfgFile string
var verbose bool
var availableSources []string

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: internal.NAME,
Short: "Track your Kobo reads on Anilist.co and Hardcover.app using Calibre-Web and Calibre databases",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
level := slog.LevelInfo
if verbose {
level = slog.LevelDebug
}
slog.SetLogLoggerLevel(level)

if availableSources == nil {
availableSources = slices.Collect(maps.Keys(source.GetSources()))
}
if slices.Contains(availableSources, viper.GetString(internal.CONFIG_SOURCE)) {
return nil
}
return errors.New(fmt.Sprintf("Invalid source. Available sources: %v", availableSources))
},
}

// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := RootCmd.Execute()
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
Expand All @@ -40,20 +53,22 @@ func Execute() {
func init() {
cobra.OnInitialize(initConfig)

defaultConfigFile, err := xdg.SearchConfigFile(fmt.Sprintf("%s/config.yaml", RootCmd.Name()))
defaultConfigFile, err := xdg.SearchConfigFile(fmt.Sprintf("%s/config.yaml", rootCmd.Name()))
cobra.CheckErr(err)

RootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", defaultConfigFile, "config file")
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", defaultConfigFile, "config file")

availableTargets := []string{}
for _, target := range target.GetTargets() {
name := target.GetName()
availableTargets = append(availableTargets, name)
}
RootCmd.PersistentFlags().StringSliceP("targets", "t", availableTargets, "Active targets to sync reading status with")
RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")
rootCmd.PersistentFlags().StringSliceP("targets", "t", availableTargets, "Active targets to sync reading status with")
rootCmd.PersistentFlags().StringP("source", "s", "database", "Active source to retrieve reading data from")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging")

viper.BindPFlag("targets", RootCmd.PersistentFlags().Lookup("targets"))
viper.BindPFlag("targets", rootCmd.PersistentFlags().Lookup("targets"))
viper.BindPFlag("source", rootCmd.PersistentFlags().Lookup("source"))
}

// initConfig reads in config file and ENV variables if set.
Expand All @@ -62,7 +77,7 @@ func initConfig() {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
configPath, err := xdg.SearchConfigFile(RootCmd.Name())
configPath, err := xdg.SearchConfigFile(rootCmd.Name())
cobra.CheckErr(err)

// Search config in xdg config directory with name "readflow/config.yaml".
Expand Down
147 changes: 147 additions & 0 deletions cmd/setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package cmd

import (
"fmt"
"log/slog"
"slices"

"github.com/RobBrazier/readflow/internal"
"github.com/RobBrazier/readflow/internal/form"
"github.com/RobBrazier/readflow/target"
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/huh"
"github.com/cli/browser"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// setupCmd represents the setup command
var setupCmd = &cobra.Command{
Use: "setup",
Short: "Setup configuration options and login to services",
RunE: func(cmd *cobra.Command, args []string) error {
source := viper.GetString(internal.CONFIG_SOURCE)
targets := viper.GetStringSlice(internal.CONFIG_TARGETS)
calibreDb := viper.GetString(internal.CONFIG_CALIBRE_DB)
calibreWebDb := viper.GetString(internal.CONFIG_CALIBREWEB_DB)
chaptersColumn := viper.GetString(internal.CONFIG_CHAPTERS_COLUMN)

anilistTokenExists := viper.GetString(internal.CONFIG_TOKENS_ANILIST) != ""
hardcoverTokenExists := viper.GetString(internal.CONFIG_TOKENS_HARDCOVER) != ""

fetchAnilistToken := !anilistTokenExists
fetchHardcoverToken := !hardcoverTokenExists

var anilistToken string
var hardcoverToken string

// Initial config form
form := huh.NewForm(
// Source/Target
huh.NewGroup(
form.SourceSelect(&source),
form.TargetSelect(&targets),
),
// Databases
huh.NewGroup(
huh.NewInput().
Title("Calibre datatabase location").
Description("e.g. /path/to/metadata.db").
Value(&calibreDb),
huh.NewInput().
Title("Calibre-Web datatabase location").
Description("e.g. /path/to/app.db").
Value(&calibreWebDb),
).WithHideFunc(func() bool {
return source != "database"
}),
// Chapters Column
huh.NewGroup(
huh.NewInput().
Title("Calibre database 'chapters' custom column").
Description("e.g. custom_column_15 (if not specified, it'll be searched for in the calibre db)\nNOTE: only used for anilist").
Value(&chaptersColumn),
).WithHideFunc(func() bool {
return !slices.Contains(targets, "anilist") || source != "database"
}),
// Prompt for Re-Authentication
huh.NewGroup(
form.Confirm("Token already exists in config for Anilist, Re-authenticate", &fetchAnilistToken),
).WithHideFunc(func() bool {
return !slices.Contains(targets, "anilist") || fetchAnilistToken
}),
huh.NewGroup(
form.Confirm("Token already exists in config for Hardcover, Re-authenticate", &fetchHardcoverToken),
).WithHideFunc(func() bool {
return !slices.Contains(targets, "hardcover") || fetchHardcoverToken
}),
// Re-Authorize if requested
huh.NewGroup(
huh.NewInput().
Title("Authenticating with Anilist").
DescriptionFunc(func() string {
url, _ := getTarget("anilist").Login()
browser.OpenURL(url)
return fmt.Sprintf(
"Please open the following URL in your browser if it hasn't already opened:\n%s",
url,
)
}, nil).
EchoMode(huh.EchoMode(textinput.EchoPassword)).
Value(&anilistToken),
).WithHideFunc(func() bool {
return !slices.Contains(targets, "anilist") || !fetchAnilistToken
}),
huh.NewGroup(
huh.NewInput().
Title("Authenticating with Hardcover").
DescriptionFunc(func() string {
url, _ := getTarget("hardcover").Login()
browser.OpenURL(url)
return fmt.Sprintf(
"Please open the following URL in your browser if it hasn't already opened:\n%s",
url,
)
}, nil).
EchoMode(huh.EchoMode(textinput.EchoPassword)).
Value(&hardcoverToken),
).WithHideFunc(func() bool {
return !slices.Contains(targets, "hardcover") || !fetchHardcoverToken
}),
)
err := form.Run()
if err != nil {
return err
}
viper.Set(internal.CONFIG_SOURCE, source)
viper.Set(internal.CONFIG_TARGETS, targets)
viper.Set(internal.CONFIG_CALIBRE_DB, calibreDb)
viper.Set(internal.CONFIG_CALIBREWEB_DB, calibreWebDb)
viper.Set(internal.CONFIG_CHAPTERS_COLUMN, chaptersColumn)
if fetchAnilistToken && anilistToken != "" {
viper.Set(internal.CONFIG_TOKENS_ANILIST, anilistToken)
}
if fetchHardcoverToken && hardcoverToken != "" {
viper.Set(internal.CONFIG_TOKENS_HARDCOVER, hardcoverToken)
}
err = viper.WriteConfig()
if err != nil {
return err
}
slog.Info("Saved settings to config")
return nil
},
}

func getTarget(name string) target.SyncTarget {
for _, target := range target.GetTargets() {
if target.GetName() == name {
return target
}
}
return nil
}

func init() {
rootCmd.AddCommand(setupCmd)
}
Loading

0 comments on commit 8c13557

Please sign in to comment.