Fail fast if count value is invalid when count-prompt is not set.

move lottery draw logic into runLottery()

move MarkFlagsMutuallyExclusive into init()
This commit is contained in:
2026-08-03 04:46:00 +01:00
parent 98c94b4947
commit 1282da4e0e
2 changed files with 101 additions and 42 deletions

View File

@@ -33,23 +33,16 @@ var rootCmd = &cobra.Command{
Use: "lottery", Use: "lottery",
Short: "A CLI for National Lottery games.", Short: "A CLI for National Lottery games.",
PreRunE: func(cmd *cobra.Command, args []string) error { PreRunE: func(cmd *cobra.Command, args []string) error {
cmd.MarkFlagsMutuallyExclusive("count", "count-prompt") // Fail fast if the count is invalid when the count-prompt flag is not set.
return validateNonPromptCount()
return nil
}, },
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
kindStr := viper.GetString("kind") kindStr := viper.GetString("kind")
if kindStr == "" { if kindStr == "" {
form := huh.NewForm( if err := huh.NewSelect[string]().
huh.NewGroup(
huh.NewSelect[string]().
Title("Pick a lottery."). Title("Pick a lottery.").
Options(kindPromptOptions()...). Options(kindPromptOptions()...).
Value(&kindStr), Value(&kindStr).Run(); err != nil {
),
)
err := form.Run()
if err != nil {
return err return err
} }
} }
@@ -59,30 +52,53 @@ var rootCmd = &cobra.Command{
return err return err
} }
l, err := lottery.New(kind) count, err := resolveCount()
if err != nil { if err != nil {
return err return err
} }
if countPrompt := viper.GetBool("count-prompt"); countPrompt { return runLottery(kind, count)
var count string },
}
// resolveCount resolves the count of draws to generate, either from the command line flag or by prompting the user.
func resolveCount() (int, error) {
if viper.GetBool("count-prompt") {
var count int
if err := huh.NewInput(). if err := huh.NewInput().
Title("How many draws would you like to generate?"). Title("How many draws would you like to generate?").
Value(&count).Run(); err != nil { Validate(func(s string) error {
parsedCount, err := parseCount(s)
if err != nil {
return err
}
count = parsedCount
return nil
}).
Run(); err != nil {
return 0, err
}
return count, nil
}
return viper.GetInt("count"), nil
}
// runLottery runs the lottery draw for the specified kind and count.
func runLottery(kind lottery.Kind, count int) error {
selectedLottery, err := lottery.New(kind)
if err != nil {
return err return err
} }
viper.Set("count", count)
}
count := viper.GetInt("count")
includeDrawHeading := count > 1 includeDrawHeading := count > 1
renders := make([]string, 0, count) renders := make([]string, 0, count)
drawTitle := "Lottery" drawTitle := "Lottery"
for i := range count { for i := range count {
l.Draw() selectedLottery.Draw()
title, entry := renderDrawEntry(l, i+1, includeDrawHeading) title, entry := renderDrawEntry(selectedLottery, i+1, includeDrawHeading)
drawTitle = title drawTitle = title
renders = append(renders, entry) renders = append(renders, entry)
} }
@@ -92,9 +108,9 @@ var rootCmd = &cobra.Command{
} }
return nil return nil
},
} }
// kindPromptLabel returns a user-friendly label for the given lottery kind.
func kindPromptLabel(kind lottery.Kind) string { func kindPromptLabel(kind lottery.Kind) string {
switch kind { switch kind {
case lottery.KindLotto: case lottery.KindLotto:
@@ -112,6 +128,7 @@ func kindPromptLabel(kind lottery.Kind) string {
} }
} }
// kindPromptOptions returns a slice of options for the lottery kind prompt.
func kindPromptOptions() []huh.Option[string] { func kindPromptOptions() []huh.Option[string] {
kinds := lottery.AllKinds() kinds := lottery.AllKinds()
options := make([]huh.Option[string], 0, len(kinds)) options := make([]huh.Option[string], 0, len(kinds))
@@ -125,6 +142,7 @@ func init() {
rootCmd.Flags().StringP("kind", "k", "", "Lottery kind to generate draws for.") rootCmd.Flags().StringP("kind", "k", "", "Lottery kind to generate draws for.")
rootCmd.Flags().IntP("count", "c", 1, "Number of draws to generate.") rootCmd.Flags().IntP("count", "c", 1, "Number of draws to generate.")
rootCmd.Flags().BoolP("count-prompt", "C", false, "Prompt for the number of draws to generate.") rootCmd.Flags().BoolP("count-prompt", "C", false, "Prompt for the number of draws to generate.")
rootCmd.MarkFlagsMutuallyExclusive("count", "count-prompt")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.SetEnvPrefix("LOTTERY") viper.SetEnvPrefix("LOTTERY")

41
cmd/lottery/validate.go Normal file
View File

@@ -0,0 +1,41 @@
package main
import (
"fmt"
"strconv"
"strings"
"github.com/spf13/viper"
)
// validateNonPromptCount checks if the count is valid when the count-prompt flag is not set.
func validateNonPromptCount() error {
if viper.GetBool("count-prompt") {
return nil
}
return validateCount(viper.GetInt("count"))
}
// validateCount checks if the count is valid.
func validateCount(count int) error {
if count < 1 {
return fmt.Errorf("count must be greater than 0")
}
return nil
}
// parseCount parses the count from a string and validates it.
func parseCount(raw string) (int, error) {
count, err := strconv.Atoi(strings.TrimSpace(raw))
if err != nil {
return 0, fmt.Errorf("count must be a whole number")
}
if err := validateCount(count); err != nil {
return 0, err
}
return count, nil
}