mirror of
https://github.com/onyx-and-iris/lottery-cli.git
synced 2026-08-17 00:34:25 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a8cb55e84 | ||
|
|
dc1f6409d7 | ||
| 1282da4e0e | |||
|
|
98c94b4947 | ||
| f6435fcd88 | |||
| 38975cd9d6 | |||
| 18f56b4ecb | |||
| dd49700eb1 | |||
| af06fab1f1 | |||
|
|
c55ab96a4f | ||
| 2259676027 | |||
| 12ca7d8fc2 | |||
| 1310447664 | |||
| 3f0af6e5ec |
@@ -17,6 +17,7 @@ before:
|
|||||||
|
|
||||||
builds:
|
builds:
|
||||||
- main: ./cmd/lottery/
|
- main: ./cmd/lottery/
|
||||||
|
binary: lottery
|
||||||
env:
|
env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
goos:
|
goos:
|
||||||
|
|||||||
44
README.md
44
README.md
@@ -20,30 +20,64 @@ go install github.com/onyx-and-iris/lottery-cli/cmd/lottery@latest
|
|||||||
|
|
||||||
*flags*
|
*flags*
|
||||||
|
|
||||||
|
- --kind/-k: The kind of lottery.
|
||||||
- --count/-c: Number of draws to generate.
|
- --count/-c: Number of draws to generate.
|
||||||
- --count-prompt/-C: Prompt for the number of draws to generate.
|
- --count-prompt/-C: Prompt for the number of draws to generate.
|
||||||
> Note. If both --count and --count-prompt are passed the count prompt will win.
|
> Note. --count and --count-prompt flags are mutually exclusive.
|
||||||
|
|
||||||
*environment variables*
|
*environment variables*
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export LOTTERY_KIND=lotto
|
||||||
export LOTTERY_COUNT=3
|
export LOTTERY_COUNT=3
|
||||||
export LOTTERY_COUNT_PROMPT=false
|
export LOTTERY_COUNT_PROMPT=false
|
||||||
```
|
```
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
There are no subcommands, just run the CLI directly passing any desired flags:
|
### RootCmd
|
||||||
|
|
||||||
|
Run with the selection prompt without prompting for a count:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
lottery --count=3
|
lottery
|
||||||
```
|
```
|
||||||
|
|
||||||
You will then be entered into the selection prompt.
|
Run with the selection prompt but also prompt for a count:
|
||||||
|
|
||||||
|
```console
|
||||||
|
lottery --count-prompt
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with the selection prompt but pass in the count directly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
lottery --count=4
|
||||||
|
```
|
||||||
|
|
||||||
|
Run a single draw directly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
lottery --kind=euromillions
|
||||||
|
```
|
||||||
|
|
||||||
|
Run multiple draws directly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
lottery --kind=euromillions --count=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### ListCmd
|
||||||
|
|
||||||
|
List the available lotteries:
|
||||||
|
|
||||||
|
```console
|
||||||
|
lottery list
|
||||||
|
```
|
||||||
|
|
||||||
## Special Thanks
|
## Special Thanks
|
||||||
|
|
||||||
- [spf13](https://github.com/spf13) for the [cobra](https://github.com/spf13/cobra) package.
|
- [spf13](https://github.com/spf13) for the [cobra](https://github.com/spf13/cobra) and [viper](https://github.com/spf13/viper) packages.
|
||||||
- [Charm](https://github.com/charmbracelet) developers for the [fang](https://github.com/charmbracelet/fang), [lipgloss](https://github.com/charmbracelet/lipgloss) and [huh](https://github.com/charmbracelet/huh) packages.
|
- [Charm](https://github.com/charmbracelet) developers for the [fang](https://github.com/charmbracelet/fang), [lipgloss](https://github.com/charmbracelet/lipgloss) and [huh](https://github.com/charmbracelet/huh) packages.
|
||||||
24
cmd/lottery/list.go
Normal file
24
cmd/lottery/list.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/onyx-and-iris/lottery-cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
// listCmd represents the list command.
|
||||||
|
var listCmd = &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "List all available lottery kinds.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
fmt.Println(renderKindList(lottery.AllKinds()))
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(listCmd)
|
||||||
|
}
|
||||||
@@ -29,76 +29,125 @@ func versionFromBuild() string {
|
|||||||
return strings.Split(info.Main.Version, "-")[0]
|
return strings.Split(info.Main.Version, "-")[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
var cmd = &cobra.Command{
|
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 {
|
||||||
|
// Fail fast if the count is invalid when the count-prompt flag is not set.
|
||||||
|
return validateNonPromptCount()
|
||||||
|
},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
var selected string
|
kindStr := viper.GetString("kind")
|
||||||
|
if kindStr == "" {
|
||||||
form := huh.NewForm(
|
if err := huh.NewSelect[string]().
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewSelect[string]().
|
|
||||||
Title("Pick a lottery.").
|
Title("Pick a lottery.").
|
||||||
Options(
|
Options(kindPromptOptions()...).
|
||||||
huh.NewOption("Lotto", "lotto"),
|
Value(&kindStr).Run(); err != nil {
|
||||||
huh.NewOption("EuroMillions", "euromillions"),
|
return err
|
||||||
huh.NewOption("Set For Life", "setforlife"),
|
}
|
||||||
huh.NewOption("Thunderball", "thunderball"),
|
}
|
||||||
huh.NewOption("Powerball", "powerball"),
|
|
||||||
).
|
kind, err := lottery.ParseKind(kindStr)
|
||||||
Value(&selected),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
err := form.Run()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
kind, err := lottery.ParseKind(selected)
|
count, err := resolveCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := lottery.New(kind)
|
return runLottery(kind, count)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if countPrompt := viper.GetBool("count-prompt"); countPrompt {
|
|
||||||
var count string
|
|
||||||
countPrompt := huh.NewForm(
|
|
||||||
huh.NewGroup(
|
|
||||||
huh.NewInput().
|
|
||||||
Title("How many draws would you like to generate?").
|
|
||||||
Value(&count),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
err := countPrompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
viper.Set("count", count)
|
|
||||||
}
|
|
||||||
|
|
||||||
count := viper.GetInt("count")
|
|
||||||
|
|
||||||
for range count {
|
|
||||||
l.Draw()
|
|
||||||
fmt.Println(renderDraw(l))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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().
|
||||||
|
Title("How many draws would you like to generate?").
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
includeDrawHeading := count > 1
|
||||||
|
renders := make([]string, 0, count)
|
||||||
|
drawTitle := "Lottery"
|
||||||
|
|
||||||
|
for i := range count {
|
||||||
|
selectedLottery.Draw()
|
||||||
|
title, entry := renderDrawEntry(selectedLottery, i+1, includeDrawHeading)
|
||||||
|
drawTitle = title
|
||||||
|
renders = append(renders, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(renders) > 0 {
|
||||||
|
fmt.Println(renderDrawCollection(drawTitle, renders))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// kindPromptLabel returns a user-friendly label for the given lottery kind.
|
||||||
|
func kindPromptLabel(kind lottery.Kind) string {
|
||||||
|
switch kind {
|
||||||
|
case lottery.KindLotto:
|
||||||
|
return "Lotto"
|
||||||
|
case lottery.KindEuroMillions:
|
||||||
|
return "EuroMillions"
|
||||||
|
case lottery.KindSetForLife:
|
||||||
|
return "Set For Life"
|
||||||
|
case lottery.KindThunderball:
|
||||||
|
return "Thunderball"
|
||||||
|
case lottery.KindPowerball:
|
||||||
|
return "Powerball"
|
||||||
|
default:
|
||||||
|
return string(kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// kindPromptOptions returns a slice of options for the lottery kind prompt.
|
||||||
|
func kindPromptOptions() []huh.Option[string] {
|
||||||
|
kinds := lottery.AllKinds()
|
||||||
|
options := make([]huh.Option[string], 0, len(kinds))
|
||||||
|
for _, kind := range kinds {
|
||||||
|
options = append(options, huh.NewOption(kindPromptLabel(kind), string(kind)))
|
||||||
|
}
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cmd.Flags().IntP("count", "c", 1, "Number of draws to generate.")
|
rootCmd.Flags().StringP("kind", "k", "", "Lottery kind to generate draws for.")
|
||||||
cmd.Flags().BoolP("count-prompt", "C", false, "Prompt for the 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.MarkFlagsMutuallyExclusive("count", "count-prompt")
|
||||||
|
|
||||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||||
viper.SetEnvPrefix("LOTTERY")
|
viper.SetEnvPrefix("LOTTERY")
|
||||||
viper.AutomaticEnv()
|
viper.AutomaticEnv()
|
||||||
if err := viper.BindPFlags(cmd.Flags()); err != nil {
|
if err := viper.BindPFlags(rootCmd.Flags()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +155,7 @@ func init() {
|
|||||||
func main() {
|
func main() {
|
||||||
if err := fang.Execute(
|
if err := fang.Execute(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
cmd,
|
rootCmd,
|
||||||
fang.WithVersion(versionFromBuild()),
|
fang.WithVersion(versionFromBuild()),
|
||||||
); err != nil {
|
); err != nil {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|||||||
@@ -9,6 +9,24 @@ import (
|
|||||||
"github.com/onyx-and-iris/lottery-cli"
|
"github.com/onyx-and-iris/lottery-cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// nolint:misspell
|
||||||
|
var (
|
||||||
|
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12"))
|
||||||
|
drawHeadingStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true).
|
||||||
|
Underline(true).
|
||||||
|
Foreground(lipgloss.Color("14"))
|
||||||
|
labelStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
||||||
|
numbersStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("10"))
|
||||||
|
specialStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("11"))
|
||||||
|
separatorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
||||||
|
cardStyle = lipgloss.NewStyle().
|
||||||
|
BorderStyle(lipgloss.RoundedBorder()).
|
||||||
|
BorderForeground(lipgloss.Color("8")).
|
||||||
|
Padding(0, 1).
|
||||||
|
MarginTop(1)
|
||||||
|
)
|
||||||
|
|
||||||
func formatNumberList(numbers []int) string {
|
func formatNumberList(numbers []int) string {
|
||||||
parts := make([]string, len(numbers))
|
parts := make([]string, len(numbers))
|
||||||
for i, n := range numbers {
|
for i, n := range numbers {
|
||||||
@@ -17,13 +35,7 @@ func formatNumberList(numbers []int) string {
|
|||||||
return strings.Join(parts, " ")
|
return strings.Join(parts, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:misspell
|
func drawTitleAndLines(l lottery.Lottery) (string, []string) {
|
||||||
func renderDraw(l lottery.Lottery) string {
|
|
||||||
titleStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("12"))
|
|
||||||
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
|
|
||||||
numbersStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("10"))
|
|
||||||
specialStyle := lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("11"))
|
|
||||||
|
|
||||||
var title string
|
var title string
|
||||||
var lines []string
|
var lines []string
|
||||||
|
|
||||||
@@ -91,12 +103,72 @@ func renderDraw(l lottery.Lottery) string {
|
|||||||
lines = append(lines, "Unknown lottery type")
|
lines = append(lines, "Unknown lottery type")
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.Join(lines, "\n")
|
return title, lines
|
||||||
card := lipgloss.NewStyle().
|
}
|
||||||
BorderStyle(lipgloss.RoundedBorder()).
|
|
||||||
BorderForeground(lipgloss.Color("8")).
|
func renderDrawEntry(l lottery.Lottery, drawNumber int, includeHeading bool) (string, string) {
|
||||||
Padding(0, 1).
|
title, lines := drawTitleAndLines(l)
|
||||||
MarginTop(1)
|
|
||||||
|
entry := make([]string, 0, len(lines)+1)
|
||||||
return card.Render(titleStyle.Render(title+" draw") + "\n" + body)
|
if includeHeading {
|
||||||
|
entry = append(entry, drawHeadingStyle.Render("Draw "+strconv.Itoa(drawNumber)))
|
||||||
|
}
|
||||||
|
entry = append(entry, lines...)
|
||||||
|
|
||||||
|
return title, strings.Join(entry, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func maxLineWidth(blocks []string) int {
|
||||||
|
maxWidth := 0
|
||||||
|
for _, block := range blocks {
|
||||||
|
for line := range strings.SplitSeq(block, "\n") {
|
||||||
|
width := lipgloss.Width(line)
|
||||||
|
if width > maxWidth {
|
||||||
|
maxWidth = width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if maxWidth < 12 {
|
||||||
|
return 12
|
||||||
|
}
|
||||||
|
return maxWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderDrawCollection renders a collection of draw entries into a single string with a title and separator lines.
|
||||||
|
func renderDrawCollection(title string, entries []string) string {
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
separator := separatorStyle.Render(strings.Repeat("─", maxLineWidth(entries)))
|
||||||
|
body := strings.Join(entries, "\n"+separator+"\n")
|
||||||
|
if len(entries) == 1 {
|
||||||
|
title = titleStyle.Render(title + " draw")
|
||||||
|
} else {
|
||||||
|
title = titleStyle.Render(title + " draws")
|
||||||
|
}
|
||||||
|
|
||||||
|
return cardStyle.Render(title + "\n" + body)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderKindList renders a list of lottery kinds into a formatted string.
|
||||||
|
func renderKindList(kinds []lottery.Kind) string {
|
||||||
|
lines := make([]string, 0, len(kinds)+1)
|
||||||
|
lines = append(
|
||||||
|
lines,
|
||||||
|
labelStyle.Render("Total:")+" "+numbersStyle.Render(strconv.Itoa(len(kinds))),
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, kind := range kinds {
|
||||||
|
lines = append(
|
||||||
|
lines,
|
||||||
|
specialStyle.Render("•")+
|
||||||
|
" "+numbersStyle.Render(kindPromptLabel(kind))+
|
||||||
|
" "+labelStyle.Render("("+string(kind)+")"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cardStyle.Render(
|
||||||
|
titleStyle.Render("Available lottery kinds") + "\n" + strings.Join(lines, "\n"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
41
cmd/lottery/validate.go
Normal file
41
cmd/lottery/validate.go
Normal 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
|
||||||
|
}
|
||||||
45
go.mod
45
go.mod
@@ -4,51 +4,50 @@ go 1.26.1
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
charm.land/huh/v2 v2.0.3
|
charm.land/huh/v2 v2.0.3
|
||||||
charm.land/lipgloss/v2 v2.0.5
|
charm.land/lipgloss/v2 v2.0.6
|
||||||
github.com/charmbracelet/fang v1.0.0
|
github.com/charmbracelet/fang v1.0.0
|
||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
charm.land/bubbles/v2 v2.0.0 // indirect
|
charm.land/bubbles/v2 v2.1.1 // indirect
|
||||||
charm.land/bubbletea/v2 v2.0.2 // indirect
|
charm.land/bubbletea/v2 v2.0.8 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/catppuccin/go v0.2.0 // indirect
|
github.com/catppuccin/go v0.3.0 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
|
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 // indirect
|
github.com/charmbracelet/x/ansi v0.11.8 // indirect
|
||||||
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect
|
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260816001655-68d539dca504 // indirect
|
||||||
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
github.com/charmbracelet/x/exp/strings v0.1.0 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
github.com/mattn/go-runewidth v0.0.27 // indirect
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/mango v0.1.0 // indirect
|
github.com/muesli/mango v0.2.0 // indirect
|
||||||
github.com/muesli/mango-cobra v1.2.0 // indirect
|
github.com/muesli/mango-cobra v1.3.0 // indirect
|
||||||
github.com/muesli/mango-pflag v0.1.0 // indirect
|
github.com/muesli/mango-pflag v0.2.0 // indirect
|
||||||
github.com/muesli/roff v0.1.0 // indirect
|
github.com/muesli/roff v0.1.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v1.0.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
golang.org/x/sys v0.46.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.41.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
91
go.sum
91
go.sum
@@ -1,39 +1,39 @@
|
|||||||
charm.land/bubbles/v2 v2.0.0 h1:tE3eK/pHjmtrDiRdoC9uGNLgpopOd8fjhEe31B/ai5s=
|
charm.land/bubbles/v2 v2.1.1 h1:7r55WzBxpo/R3z98hGmY7KKPd3ET6vsf0Fb9sDHOV60=
|
||||||
charm.land/bubbles/v2 v2.0.0/go.mod h1:rCHoleP2XhU8um45NTuOWBPNVHxnkXKTiZqcclL/qOI=
|
charm.land/bubbles/v2 v2.1.1/go.mod h1:GE6M31gaWZVXzGw73OeuTTgy4lX+OtkH0E5ymnNsHxo=
|
||||||
charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
|
charm.land/bubbletea/v2 v2.0.8 h1:SxTJMhCAI3lbPmy4SgX5LWZ24AdINr4I6UEqzZvYJuY=
|
||||||
charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
|
charm.land/bubbletea/v2 v2.0.8/go.mod h1:2SkdgoTXluXJHOUwAoRlRXF/28vklb1rFl6GcgV1/ss=
|
||||||
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
|
charm.land/huh/v2 v2.0.3 h1:2cJsMqEPwSywGHvdlKsJyQKPtSJLVnFKyFbsYZTlLkU=
|
||||||
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
|
charm.land/huh/v2 v2.0.3/go.mod h1:93eEveeeqn47MwiC3tf+2atZ2l7Is88rAtmZNZ8x9Wc=
|
||||||
charm.land/lipgloss/v2 v2.0.5 h1:kbNxgeeUOYv5J0YdpxFjfvf3dFvqH8Aci4zB6xqFtrY=
|
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
|
||||||
charm.land/lipgloss/v2 v2.0.5/go.mod h1:9oqhxt4yxIMe6q5A4kHr44DremZk7J9UNh74GlWa5nc=
|
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||||
github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA=
|
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||||
github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||||
github.com/charmbracelet/fang v1.0.0 h1:jESBY40agJOlLYnnv9jE0mLqDGTxEk0hkOnx7YGyRlQ=
|
github.com/charmbracelet/fang v1.0.0 h1:jESBY40agJOlLYnnv9jE0mLqDGTxEk0hkOnx7YGyRlQ=
|
||||||
github.com/charmbracelet/fang v1.0.0/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
|
github.com/charmbracelet/fang v1.0.0/go.mod h1:P5/DNb9DddQ0Z0dbc0P3ol4/ix5Po7Ofr2KMBfAqoCo=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
|
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be h1:qEvkJy1sjJXP+yf8IH2o13NV+Rh4nIUHjaLNJ+pWxpI=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
|
github.com/charmbracelet/ultraviolet v0.0.0-20260812204455-68fa937c71be/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
|
||||||
github.com/charmbracelet/x/ansi v0.11.7 h1:kzv1kJvjg2S3r9KHo8hDdHFQLEqn4RBCb39dAYC84jI=
|
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
|
||||||
github.com/charmbracelet/x/ansi v0.11.7/go.mod h1:9qGpnAVYz+8ACONkZBUWPtL7lulP9No6p1epAihUZwQ=
|
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
|
||||||
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
|
github.com/charmbracelet/x/conpty v0.1.1 h1:s1bUxjoi7EpqiXysVtC+a8RrvPPNcNvAjfi4jxsAuEs=
|
||||||
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
|
github.com/charmbracelet/x/conpty v0.1.1/go.mod h1:OmtR77VODEFbiTzGE9G1XiRJAga6011PIm4u5fTNZpk=
|
||||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||||
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 h1:IJDiTgVE56gkAGfq0lBEloWgkXMk4hl/bmuPoicI4R0=
|
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260816001655-68d539dca504 h1:vXmc9iOQFML+9lTrD8Bzvysnpl5I0MjGqPe9B4j6AHM=
|
||||||
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444/go.mod h1:T9jr8CzFpjhFVHjNjKwbAD7KwBNyFnj2pntAO7F2zw0=
|
github.com/charmbracelet/x/exp/charmtone v0.0.0-20260816001655-68d539dca504/go.mod h1:nsExn0DGyX0lh9LwLHTn2Gg+hafdzfSXnC+QmEJTZFY=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
||||||
github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
|
github.com/charmbracelet/x/exp/ordered v0.1.0 h1:55/qLwjIh0gL0Vni+QAWk7T/qRVP6sBf+2agPBgnOFE=
|
||||||
github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
|
github.com/charmbracelet/x/exp/ordered v0.1.0/go.mod h1:5UHwmG+is5THxMyCJHNPCn2/ecI07aKNrW+LcResjJ8=
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
github.com/charmbracelet/x/exp/strings v0.1.0 h1:i69S2XI7uG1u4NLGeJPSYU++Nmjvpo9nwd6aoEm7gkA=
|
||||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
github.com/charmbracelet/x/exp/strings v0.1.0/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||||
@@ -55,10 +55,10 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -67,24 +67,24 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4=
|
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
|
||||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
|
github.com/muesli/mango v0.2.0 h1:iNNc0c5VLQ6fsMgAqGQofByNUBH2Q2nEbD6TaI+5yyQ=
|
||||||
github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
|
github.com/muesli/mango v0.2.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
|
||||||
github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg=
|
github.com/muesli/mango-cobra v1.3.0 h1:vQy5GvPg3ndOSpduxutqFoINhWk3vD5K2dXo5E8pqec=
|
||||||
github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA=
|
github.com/muesli/mango-cobra v1.3.0/go.mod h1:Cj1ZrBu3806Qw7UjxnAUgE+7tllUBj1NCLQDwwGx19E=
|
||||||
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
|
github.com/muesli/mango-pflag v0.2.0 h1:QViokgKDZQCzKhYe1zH8D+UlPJzBSGoP9yx0hBG0t5k=
|
||||||
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
|
github.com/muesli/mango-pflag v0.2.0/go.mod h1:X9LT1p/pbGA1wjvEbtwnixujKErkP0jVmrxwrw3fL0Y=
|
||||||
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
|
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
|
||||||
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@@ -92,10 +92,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
@@ -111,20 +109,19 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
56
kinds.go
56
kinds.go
@@ -1,6 +1,10 @@
|
|||||||
package lottery
|
package lottery
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type Kind string
|
type Kind string
|
||||||
|
|
||||||
@@ -12,19 +16,43 @@ const (
|
|||||||
KindPowerball Kind = "powerball"
|
KindPowerball Kind = "powerball"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var allKinds = []Kind{
|
||||||
|
KindLotto,
|
||||||
|
KindEuroMillions,
|
||||||
|
KindSetForLife,
|
||||||
|
KindThunderball,
|
||||||
|
KindPowerball,
|
||||||
|
}
|
||||||
|
|
||||||
|
var allKindsText = []string{
|
||||||
|
string(KindLotto),
|
||||||
|
string(KindEuroMillions),
|
||||||
|
string(KindSetForLife),
|
||||||
|
string(KindThunderball),
|
||||||
|
string(KindPowerball),
|
||||||
|
}
|
||||||
|
|
||||||
|
var allKindsCSV = strings.Join(allKindsText, ", ")
|
||||||
|
|
||||||
|
// AllKinds returns the complete, ordered list of supported lottery kinds.
|
||||||
|
//
|
||||||
|
// The returned slice is a copy and can be modified safely by callers.
|
||||||
|
func AllKinds() []Kind {
|
||||||
|
kinds := make([]Kind, len(allKinds))
|
||||||
|
copy(kinds, allKinds)
|
||||||
|
return kinds
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseKind parses a string into a Kind, returning an error if the string is not a valid kind.
|
||||||
func ParseKind(kind string) (Kind, error) {
|
func ParseKind(kind string) (Kind, error) {
|
||||||
switch kind {
|
parsed := Kind(kind)
|
||||||
case "lotto":
|
if slices.Contains(allKinds, parsed) {
|
||||||
return KindLotto, nil
|
return parsed, nil
|
||||||
case "euromillions":
|
|
||||||
return KindEuroMillions, nil
|
|
||||||
case "setforlife":
|
|
||||||
return KindSetForLife, nil
|
|
||||||
case "thunderball":
|
|
||||||
return KindThunderball, nil
|
|
||||||
case "powerball":
|
|
||||||
return KindPowerball, nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("invalid lottery kind: %s", kind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"invalid lottery kind: %s, must be one of: %s",
|
||||||
|
kind,
|
||||||
|
allKindsCSV,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
71
kinds_test.go
Normal file
71
kinds_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package lottery
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAllKindsReturnsExpectedOrder(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
got := AllKinds()
|
||||||
|
want := []Kind{
|
||||||
|
KindLotto,
|
||||||
|
KindEuroMillions,
|
||||||
|
KindSetForLife,
|
||||||
|
KindThunderball,
|
||||||
|
KindPowerball,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(got) != len(want) {
|
||||||
|
t.Fatalf("expected %d kinds, got %d", len(want), len(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range want {
|
||||||
|
if got[i] != want[i] {
|
||||||
|
t.Fatalf("kind at index %d: expected %q, got %q", i, want[i], got[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAllKindsReturnsCopy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
first := AllKinds()
|
||||||
|
first[0] = "mutated"
|
||||||
|
|
||||||
|
again := AllKinds()
|
||||||
|
if again[0] != KindLotto {
|
||||||
|
t.Fatalf("expected AllKinds to return a defensive copy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKindAcceptsAllKinds(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, kind := range AllKinds() {
|
||||||
|
parsed, err := ParseKind(string(kind))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error for %q, got %v", kind, err)
|
||||||
|
}
|
||||||
|
if parsed != kind {
|
||||||
|
t.Fatalf("expected %q, got %q", kind, parsed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseKindIncludesAllKindsInError(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
_, err := ParseKind("invalid")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected an error for invalid kind")
|
||||||
|
}
|
||||||
|
|
||||||
|
message := err.Error()
|
||||||
|
for _, kind := range AllKinds() {
|
||||||
|
if !strings.Contains(message, string(kind)) {
|
||||||
|
t.Fatalf("expected error message to contain kind %q: %s", kind, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
util.go
4
util.go
@@ -8,8 +8,8 @@ import (
|
|||||||
// drawUnique returns count unique numbers in the range [1, max].
|
// drawUnique returns count unique numbers in the range [1, max].
|
||||||
// It uses the rand.Perm function to generate a random permutation of numbers and selects the first count numbers from it.
|
// It uses the rand.Perm function to generate a random permutation of numbers and selects the first count numbers from it.
|
||||||
// The result is sorted before being returned.
|
// The result is sorted before being returned.
|
||||||
func drawUnique(count, max int) []int {
|
func drawUnique(count, maxNum int) []int {
|
||||||
perm := rand.Perm(max)
|
perm := rand.Perm(maxNum)
|
||||||
result := make([]int, count)
|
result := make([]int, count)
|
||||||
for i := range result {
|
for i := range result {
|
||||||
result[i] = perm[i] + 1
|
result[i] = perm[i] + 1
|
||||||
|
|||||||
40
util_test.go
Normal file
40
util_test.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package lottery
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestDrawUnique(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
count int
|
||||||
|
maxNum int
|
||||||
|
}{
|
||||||
|
{"small", 3, 10},
|
||||||
|
{"exact", 5, 5},
|
||||||
|
{"larger", 10, 50},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
for range 100 {
|
||||||
|
nums := drawUnique(tc.count, tc.maxNum)
|
||||||
|
if len(nums) != tc.count {
|
||||||
|
t.Fatalf("got len=%d, want %d", len(nums), tc.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[int]struct{}, len(nums))
|
||||||
|
prev := 0
|
||||||
|
for _, n := range nums {
|
||||||
|
if n < 1 || n > tc.maxNum {
|
||||||
|
t.Fatalf("value %d out of range [1,%d]", n, tc.maxNum)
|
||||||
|
}
|
||||||
|
if n < prev {
|
||||||
|
t.Fatalf("values not sorted: %v", nums)
|
||||||
|
}
|
||||||
|
if _, ok := seen[n]; ok {
|
||||||
|
t.Fatalf("duplicate value %d in %v", n, nums)
|
||||||
|
}
|
||||||
|
prev = n
|
||||||
|
seen[n] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user