diff --git a/cmd/cmd.go b/cmd/cmd.go index 7e43744..34e33fc 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,8 +1,7 @@ package cmd import ( - "errors" - "strings" + "context" tea "github.com/charmbracelet/bubbletea" "github.com/gabe565/cli-of-life/internal/config" @@ -22,52 +21,36 @@ func New() *cobra.Command { DisableAutoGenTag: true, } - cmd.Flags().StringP(config.FileFlag, "f", "", "Loads a pattern file on startup") - cmd.Flags().String(config.FileFormatFlag, "auto", "File format (one of: "+strings.Join(pattern.FormatStrings(), ", ")+")") - cmd.Flags().String(config.RuleStringFlag, pattern.GameOfLife().String(), "Rule string to use. This will be ignored if a pattern file is loaded.") - cmd.Flags().Bool(config.PlayFlag, false, "Play on startup") - cmd.Flags().String(config.CompletionFlag, "", "Output command-line completion code for the specified shell (one of: "+strings.Join(shells(), ", ")+")") - - if err := errors.Join( - cmd.RegisterFlagCompletionFunc(config.FileFlag, - func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{pattern.ExtRLE, pattern.ExtPlaintext}, cobra.ShellCompDirectiveFilterFileExt - }, - ), - cmd.RegisterFlagCompletionFunc(config.FileFormatFlag, - func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return pattern.FormatStrings(), cobra.ShellCompDirectiveNoFileComp - }, - ), - cmd.RegisterFlagCompletionFunc(config.RuleStringFlag, - func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{pattern.GameOfLife().String(), pattern.HighLife().String()}, cobra.ShellCompDirectiveNoFileComp - }, - ), - ); err != nil { + conf := config.New() + conf.RegisterFlags(cmd.Flags()) + if err := config.RegisterCompletion(cmd); err != nil { panic(err) } - + cmd.SetContext(config.NewContext(context.Background(), conf)) return cmd } func run(cmd *cobra.Command, _ []string) error { - if shell := cmd.Flag(config.CompletionFlag).Value.String(); shell != "" { - return completion(cmd, shell) + conf, ok := config.FromContext(cmd.Context()) + if !ok { + panic("command missing config") + } + + if conf.Completion != "" { + return completion(cmd, conf.Completion) } var rule pattern.Rule - if err := rule.UnmarshalText([]byte(cmd.Flag(config.RuleStringFlag).Value.String())); err != nil { + if err := rule.UnmarshalText([]byte(conf.RuleString)); err != nil { return err } pat := pattern.Pattern{ Rule: rule, } - if file := cmd.Flag(config.FileFlag).Value.String(); file != "" { - format := pattern.Format(cmd.Flag(config.FileFormatFlag).Value.String()) + if conf.File != "" { var err error - if pat, err = pattern.UnmarshalFile(file, format); err != nil { + if pat, err = pattern.UnmarshalFile(conf.File, pattern.Format(conf.FileFormat)); err != nil { return err } } @@ -75,7 +58,7 @@ func run(cmd *cobra.Command, _ []string) error { g := game.New( game.WithPattern(pat), game.WithDimensions(400, 400), - game.WithPlay(cmd.Flag(config.PlayFlag).Value.String() == "true"), + game.WithPlay(conf.Play), ) _, err := tea.NewProgram(g, tea.WithAltScreen(), tea.WithMouseAllMotion()).Run() diff --git a/cmd/completion.go b/cmd/completion.go index a9483eb..d13dc1c 100644 --- a/cmd/completion.go +++ b/cmd/completion.go @@ -4,31 +4,21 @@ import ( "errors" "fmt" + "github.com/gabe565/cli-of-life/internal/config" "github.com/spf13/cobra" ) -const ( - ShellBash = "bash" - ShellZsh = "zsh" - ShellFish = "fish" - ShellPowerShell = "powershell" -) - -func shells() []string { - return []string{ShellBash, ShellZsh, ShellFish, ShellPowerShell} -} - var ErrInvalidShell = errors.New("invalid shell") func completion(cmd *cobra.Command, shell string) error { switch shell { - case ShellBash: + case config.ShellBash: return cmd.Root().GenBashCompletion(cmd.OutOrStdout()) - case ShellZsh: + case config.ShellZsh: return cmd.Root().GenZshCompletion(cmd.OutOrStdout()) - case ShellFish: + case config.ShellFish: return cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true) - case ShellPowerShell: + case config.ShellPowerShell: return cmd.Root().GenPowerShellCompletionWithDesc(cmd.OutOrStdout()) default: return fmt.Errorf("%w: %s", ErrInvalidShell, shell) diff --git a/internal/config/completion.go b/internal/config/completion.go new file mode 100644 index 0000000..411bf3f --- /dev/null +++ b/internal/config/completion.go @@ -0,0 +1,39 @@ +package config + +import ( + "errors" + + "github.com/gabe565/cli-of-life/internal/pattern" + "github.com/spf13/cobra" +) + +const ( + ShellBash = "bash" + ShellZsh = "zsh" + ShellFish = "fish" + ShellPowerShell = "powershell" +) + +func shells() []string { + return []string{ShellBash, ShellZsh, ShellFish, ShellPowerShell} +} + +func RegisterCompletion(cmd *cobra.Command) error { + return errors.Join( + cmd.RegisterFlagCompletionFunc(FileFlag, + func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{pattern.ExtRLE, pattern.ExtPlaintext}, cobra.ShellCompDirectiveFilterFileExt + }, + ), + cmd.RegisterFlagCompletionFunc(FileFormatFlag, + func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return pattern.FormatStrings(), cobra.ShellCompDirectiveNoFileComp + }, + ), + cmd.RegisterFlagCompletionFunc(RuleStringFlag, + func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{pattern.GameOfLife().String(), pattern.HighLife().String()}, cobra.ShellCompDirectiveNoFileComp + }, + ), + ) +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..e301edf --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,19 @@ +package config + +import "github.com/gabe565/cli-of-life/internal/pattern" + +type Config struct { + File string + FileFormat string + RuleString string + Play bool + + Completion string +} + +func New() *Config { + return &Config{ + FileFormat: "auto", + RuleString: pattern.GameOfLife().String(), + } +} diff --git a/internal/config/context.go b/internal/config/context.go new file mode 100644 index 0000000..a2a7e94 --- /dev/null +++ b/internal/config/context.go @@ -0,0 +1,16 @@ +package config + +import "context" + +type ctxKey uint8 + +const configKey ctxKey = iota + +func NewContext(ctx context.Context, conf *Config) context.Context { + return context.WithValue(ctx, configKey, conf) +} + +func FromContext(ctx context.Context) (*Config, bool) { + conf, ok := ctx.Value(configKey).(*Config) + return conf, ok +} diff --git a/internal/config/flags.go b/internal/config/flags.go index 1ec425d..23990da 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -1,5 +1,12 @@ package config +import ( + "strings" + + "github.com/gabe565/cli-of-life/internal/pattern" + "github.com/spf13/pflag" +) + const ( FileFlag = "file" FileFormatFlag = "file-format" @@ -7,3 +14,11 @@ const ( PlayFlag = "play" CompletionFlag = "completion" ) + +func (c *Config) RegisterFlags(fs *pflag.FlagSet) { + fs.StringVarP(&c.File, FileFlag, "f", c.File, "Loads a pattern file on startup") + fs.StringVar(&c.FileFormat, FileFormatFlag, c.FileFormat, "File format (one of: "+strings.Join(pattern.FormatStrings(), ", ")+")") + fs.StringVar(&c.RuleString, RuleStringFlag, c.RuleString, "Rule string to use. This will be ignored if a pattern file is loaded.") + fs.BoolVar(&c.Play, PlayFlag, c.Play, "Play on startup") + fs.StringVar(&c.Completion, CompletionFlag, c.Completion, "Output command-line completion code for the specified shell (one of: "+strings.Join(shells(), ", ")+")") +}