3 Commits

5 changed files with 141 additions and 44 deletions

View File

@@ -20,6 +20,7 @@ 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. If both --count and --count-prompt are passed the count prompt will win.
@@ -29,21 +30,44 @@ go install github.com/onyx-and-iris/lottery-cli/cmd/lottery@latest
```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: 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 -k=euromillions
```
Run multiple draws directly:
```console
lottery -k=euromillions -c=3
```
## 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.

View File

@@ -32,9 +32,19 @@ func versionFromBuild() string {
var cmd = &cobra.Command{ var cmd = &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 {
kindStr := viper.GetString("kind")
if kindStr != "" {
_, err := lottery.ParseKind(kindStr)
if err != nil {
return err
}
}
return nil
},
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( form := huh.NewForm(
huh.NewGroup( huh.NewGroup(
huh.NewSelect[string](). huh.NewSelect[string]().
@@ -46,15 +56,16 @@ var cmd = &cobra.Command{
huh.NewOption("Thunderball", "thunderball"), huh.NewOption("Thunderball", "thunderball"),
huh.NewOption("Powerball", "powerball"), huh.NewOption("Powerball", "powerball"),
). ).
Value(&selected), Value(&kindStr),
), ),
) )
err := form.Run() err := form.Run()
if err != nil { if err != nil {
return err return err
} }
}
kind, err := lottery.ParseKind(selected) kind, err := lottery.ParseKind(kindStr)
if err != nil { if err != nil {
return err return err
} }
@@ -81,10 +92,19 @@ var cmd = &cobra.Command{
} }
count := viper.GetInt("count") count := viper.GetInt("count")
includeDrawHeading := count > 1
renders := make([]string, 0, count)
drawTitle := "Lottery"
for range count { for i := range count {
l.Draw() l.Draw()
fmt.Println(renderDraw(l)) title, entry := renderDrawEntry(l, i+1, includeDrawHeading)
drawTitle = title
renders = append(renders, entry)
}
if len(renders) > 0 {
fmt.Println(renderDrawCollection(drawTitle, renders))
} }
return nil return nil
@@ -92,6 +112,7 @@ var cmd = &cobra.Command{
} }
func init() { func init() {
cmd.Flags().StringP("kind", "k", "", "Lottery kind to generate draws for.")
cmd.Flags().IntP("count", "c", 1, "Number of draws to generate.") cmd.Flags().IntP("count", "c", 1, "Number of draws to generate.")
cmd.Flags().BoolP("count-prompt", "C", false, "Prompt for the number of draws to generate.") cmd.Flags().BoolP("count-prompt", "C", false, "Prompt for the number of draws to generate.")

View File

@@ -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,49 @@ 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
}
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)
} }

View File

@@ -25,6 +25,9 @@ func ParseKind(kind string) (Kind, error) {
case "powerball": case "powerball":
return KindPowerball, nil return KindPowerball, nil
default: default:
return "", fmt.Errorf("invalid lottery kind: %s", kind) return "", fmt.Errorf(
"invalid lottery kind: %s, must be one of: lotto, euromillions, setforlife, thunderball, powerball",
kind,
)
} }
} }

View File

@@ -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