diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1e58186..924f972 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ on:
- main
env:
- VERSION_NUMBER: 'v1.9.2'
+ VERSION_NUMBER: 'v1.9.3'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'
diff --git a/.gitignore b/.gitignore
index 2bf9bec..5d79543 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,6 +37,7 @@ web/.coverage
# Python
card_data/.venv
__pycache__/
+.ruff_cache/
# Terraform
.terraformrc
diff --git a/.goreleaser.yml b/.goreleaser.yml
index f5e6bac..475906b 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- - -s -w -X main.version=v1.9.2
+ - -s -w -X main.version=v1.9.3
archives:
- formats: [ 'zip' ]
diff --git a/Dockerfile b/Dockerfile
index 5480709..7f43ac5 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,7 +8,7 @@ RUN go mod download
COPY . .
-RUN go build -ldflags "-X main.version=v1.9.2" -o poke-cli .
+RUN go build -ldflags "-X main.version=v1.9.3" -o poke-cli .
# build 2
FROM --platform=$BUILDPLATFORM alpine:3.23
diff --git a/README.md b/README.md
index 50944d4..25f767e 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-
+
@@ -99,11 +99,11 @@ Cloudsmith is a fully cloud-based service that lets you easily create, store, an
3. Choose how to interact with the container:
* Run a single command and exit:
```bash
- docker run --rm -it digitalghostdev/poke-cli:v1.9.2
[subcommand] [flag]
+ docker run --rm -it digitalghostdev/poke-cli:v1.9.3 [subcommand] [flag]
```
* Enter the container and use its shell:
```bash
- docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.9.2 -c "cd /app && exec sh"
+ docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.9.3 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
@@ -112,13 +112,13 @@ Cloudsmith is a fully cloud-based service that lets you easily create, store, an
> The `card` command renders TCG card images using your terminal's graphics protocol. When running inside Docker, pass your terminal's environment variables so image rendering works correctly:
> ```bash
> # Kitty
-> docker run --rm -it -e TERM -e KITTY_WINDOW_ID digitalghostdev/poke-cli:v1.9.2 card
+> docker run --rm -it -e TERM -e KITTY_WINDOW_ID digitalghostdev/poke-cli:v1.9.3 card
>
> # WezTerm, iTerm2, Ghostty, Konsole, Rio, Tabby
-> docker run --rm -it -e TERM -e TERM_PROGRAM digitalghostdev/poke-cli:v1.9.2 card
+> docker run --rm -it -e TERM -e TERM_PROGRAM digitalghostdev/poke-cli:v1.9.3 card
>
> # Windows Terminal (Sixel)
-> docker run --rm -it -e WT_SESSION digitalghostdev/poke-cli:v1.9.2 card
+> docker run --rm -it -e WT_SESSION digitalghostdev/poke-cli:v1.9.3 card
> ```
> If your terminal is not listed above, image rendering is not supported inside Docker.
diff --git a/card_data/pipelines/poke_cli_dbt/dbt_project.yml b/card_data/pipelines/poke_cli_dbt/dbt_project.yml
index d684c53..b87090d 100644
--- a/card_data/pipelines/poke_cli_dbt/dbt_project.yml
+++ b/card_data/pipelines/poke_cli_dbt/dbt_project.yml
@@ -1,5 +1,5 @@
name: 'poke_cli_dbt'
-version: '1.9.2'
+version: '1.9.3'
profile: 'poke_cli_dbt'
diff --git a/cli.go b/cli.go
index d454fa6..19e7b66 100644
--- a/cli.go
+++ b/cli.go
@@ -159,14 +159,11 @@ func runCLI(args []string) int {
case exists:
return cmdFunc()
default:
- errMessage := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("✖ Error!"),
- fmt.Sprintf("\n\t%-15s", fmt.Sprintf("'%s' is not a valid command.\n", cmdArg)),
- styling.StyleBold.Render("\nCommands:"),
- renderCommandList(),
- fmt.Sprintf("\n\nAlso run %s for more info!", styling.StyleBold.Render("poke-cli -h")),
- )
- output.WriteString(errMessage)
+ msg := fmt.Sprintf("\t%-15s", fmt.Sprintf("'%s' is not a valid command.\n", cmdArg)) +
+ styling.StyleBold.Render("\nCommands:") +
+ renderCommandList() +
+ fmt.Sprintf("\n\nAlso run %s for more info!", styling.StyleBold.Render("poke-cli -h"))
+ output.WriteString(utils.FormatError(msg))
fmt.Println(output.String())
diff --git a/cli_test.go b/cli_test.go
index 80134c5..91400b3 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -132,14 +132,14 @@ func TestRunCLI_VariousCommands(t *testing.T) {
args []string
expected int
}{
- //{"Invalid command", []string{"foobar"}, 1},
+ {"Invalid command", []string{"foobar"}, 1},
{"Latest flag long", []string{"--latest"}, 0},
{"Latest flag short", []string{"-l"}, 0},
{"Version flag long", []string{"--version"}, 0},
{"Version flag short", []string{"-v"}, 0},
{"Search command with invalid args", []string{"search", "pokemon", "extra-arg"}, 1},
- //{"Missing Pokémon name", []string{"pokemon"}, 1},
- //{"Another invalid command", []string{"invalid"}, 1},
+ {"Missing Pokémon name", []string{"pokemon"}, 1},
+ {"Another invalid command", []string{"invalid"}, 1},
}
for _, tt := range tests {
diff --git a/cmd/ability/ability.go b/cmd/ability/ability.go
index df65bb2..e3a9c0e 100644
--- a/cmd/ability/ability.go
+++ b/cmd/ability/ability.go
@@ -1,6 +1,7 @@
package ability
import (
+ "errors"
"flag"
"fmt"
"os"
@@ -48,9 +49,10 @@ func AbilityCommand() (string, error) {
abilityName := strings.ToLower(args[2])
if err := af.FlagSet.Parse(args[3:]); err != nil {
+ if errors.Is(err, flag.ErrHelp) {
+ return output.String(), nil
+ }
fmt.Fprintf(&output, "error parsing flags: %v\n", err)
- af.FlagSet.Usage()
-
return output.String(), err
}
diff --git a/cmd/ability/ability_test.go b/cmd/ability/ability_test.go
index 1ce0496..dedfaa5 100644
--- a/cmd/ability/ability_test.go
+++ b/cmd/ability/ability_test.go
@@ -52,6 +52,12 @@ func TestAbilityCommand(t *testing.T) {
args: []string{"ability", "poison-point"},
expectedOutput: utils.LoadGolden(t, "ability_poison_point.golden"),
},
+ {
+ name: "Ability invalid flag",
+ args: []string{"ability", "clear-body", "--bogus"},
+ expectedOutput: utils.LoadGolden(t, "ability_invalid_flag.golden"),
+ wantError: true,
+ },
}
for _, tt := range tests {
diff --git a/cmd/berry/berry.go b/cmd/berry/berry.go
index 557591e..5b84a68 100644
--- a/cmd/berry/berry.go
+++ b/cmd/berry/berry.go
@@ -7,9 +7,9 @@ import (
"os"
"strings"
- "github.com/charmbracelet/bubbles/table"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/table"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/styling"
@@ -65,7 +65,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var bubbleCmd tea.Cmd
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "esc", "ctrl+c":
m.quitting = true
@@ -87,9 +87,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// View renders the current UI
-func (m model) View() string {
+func (m model) View() tea.View {
if m.quitting {
- return "\n Goodbye! \n"
+ return tea.NewView("\n Goodbye! \n")
}
selectedBerry := ""
@@ -100,7 +100,7 @@ func (m model) View() string {
leftPanel := styling.TypesTableBorder.Render(m.table.View())
rightPanel := lipgloss.NewStyle().
- Width(50).
+ Width(52).
Height(29).
Border(lipgloss.RoundedBorder()).
BorderForeground(styling.YellowColor).
@@ -109,18 +109,18 @@ func (m model) View() string {
screen := lipgloss.JoinHorizontal(lipgloss.Top, leftPanel, rightPanel)
- return fmt.Sprintf("Highlight a berry!\n%s\n%s",
+ return tea.NewView(fmt.Sprintf("Highlight a berry!\n%s\n%s",
screen,
- styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nctrl+c | esc (quit)"))
+ styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nctrl+c | esc (quit)")))
}
func tableGeneration() error {
namesList, err := connections.QueryBerryData(`
- SELECT
+ SELECT
UPPER(SUBSTR(name, 1, 1)) || SUBSTR(name, 2)
- FROM
- berries
- ORDER BY
+ FROM
+ berries
+ ORDER BY
name`)
if err != nil {
log.Fatalf("Failed to get berry names: %v", err)
@@ -136,6 +136,7 @@ func tableGeneration() error {
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(28),
+ table.WithWidth(16),
)
s := table.DefaultStyles()
diff --git a/cmd/berry/berry_test.go b/cmd/berry/berry_test.go
index d813416..105e014 100644
--- a/cmd/berry/berry_test.go
+++ b/cmd/berry/berry_test.go
@@ -6,10 +6,10 @@ import (
"testing"
"time"
- "github.com/charmbracelet/bubbles/table"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/charmbracelet/x/exp/teatest"
+ "charm.land/bubbles/v2/table"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
+ "github.com/charmbracelet/x/exp/teatest/v2"
"github.com/digitalghost-dev/poke-cli/styling"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -74,6 +74,7 @@ func TestModelUpdate(t *testing.T) {
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(5),
+ table.WithWidth(16),
)
m := model{
@@ -124,15 +125,15 @@ func TestModelView(t *testing.T) {
}
view := m.View()
- if view == "" {
+ if view.Content == "" {
t.Errorf("View() should not return empty string for normal state")
}
// Test quitting state
m.quitting = true
view = m.View()
- if !strings.Contains(view, "Goodbye") {
- t.Errorf("View() should contain 'Goodbye' when quitting, got %q", view)
+ if !strings.Contains(view.Content, "Goodbye") {
+ t.Errorf("View() should contain 'Goodbye' when quitting, got %q", view.Content)
}
}
@@ -145,6 +146,7 @@ func TestModelViewWithSelectedBerry(t *testing.T) {
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(5),
+ table.WithWidth(16),
)
m := model{
@@ -161,8 +163,8 @@ func TestModelViewWithSelectedBerry(t *testing.T) {
}
for _, element := range expectedElements {
- if !strings.Contains(view, element) {
- t.Errorf("View() should contain %q, got %q", element, view)
+ if !strings.Contains(view.Content, element) {
+ t.Errorf("View() should contain %q, got %q", element, view.Content)
}
}
}
@@ -182,6 +184,7 @@ func createTestModel() model {
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(5),
+ table.WithWidth(16),
)
s := table.DefaultStyles()
@@ -202,14 +205,14 @@ func TestTableNavigation(t *testing.T) {
testModel := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(100, 50))
// Navigate down twice
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown})
// Navigate back up once
- testModel.Send(tea.KeyMsg{Type: tea.KeyUp})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyUp})
// Quit the program
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -229,7 +232,7 @@ func TestTableQuitWithEscape(t *testing.T) {
testModel := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(100, 50))
// Quit with escape
- testModel.Send(tea.KeyMsg{Type: tea.KeyEsc})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEscape})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -244,7 +247,7 @@ func TestTableInitialSelection(t *testing.T) {
testModel := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(100, 50))
// Don't navigate, just quit immediately
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
diff --git a/cmd/berry/berryinfo.go b/cmd/berry/berryinfo.go
index 3b284df..4aef543 100644
--- a/cmd/berry/berryinfo.go
+++ b/cmd/berry/berryinfo.go
@@ -5,7 +5,7 @@ import (
"net/http"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/styling"
"github.com/disintegration/imaging"
@@ -95,7 +95,7 @@ func BerryImage(berryName string) string {
c2, _ := styling.MakeColor(img.At(x, heightCounter+1))
color2 := lipgloss.Color(c2.Hex())
- styleKey := string(color1) + "_" + string(color2)
+ styleKey := c1.Hex() + "_" + c2.Hex()
style, exists := styleCache[styleKey]
if !exists {
style = lipgloss.NewStyle().Foreground(color1).Background(color2)
diff --git a/cmd/card/card.go b/cmd/card/card.go
index 6881ed1..41e53b7 100644
--- a/cmd/card/card.go
+++ b/cmd/card/card.go
@@ -6,7 +6,7 @@ import (
"os"
"strings"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
)
@@ -37,7 +37,7 @@ func CardCommand() (string, error) {
}
// Program 1: Series selection
- finalModel, err := tea.NewProgram(SeriesList(), tea.WithAltScreen()).Run()
+ finalModel, err := tea.NewProgram(SeriesList()).Run()
if err != nil {
return "", fmt.Errorf("error running series selection program: %w", err)
}
@@ -54,7 +54,7 @@ func CardCommand() (string, error) {
return "", fmt.Errorf("error loading sets: %w", err)
}
- finalSetsModel, err := tea.NewProgram(setsMdl, tea.WithAltScreen()).Run()
+ finalSetsModel, err := tea.NewProgram(setsMdl).Run()
if err != nil {
return "", fmt.Errorf("error running sets selection program: %w", err)
}
@@ -76,7 +76,7 @@ func CardCommand() (string, error) {
}
for {
- finalCardsModel, err := tea.NewProgram(cardsMdl, tea.WithAltScreen()).Run()
+ finalCardsModel, err := tea.NewProgram(cardsMdl).Run()
if err != nil {
return "", fmt.Errorf("error running cards program: %w", err)
}
@@ -89,7 +89,7 @@ func CardCommand() (string, error) {
if cardsResult.ViewImage {
// Launch image viewer
imageURL := cardsResult.ImageMap[cardsResult.SelectedOption]
- _, err := tea.NewProgram(ImageRenderer(cardsResult.SelectedOption, imageURL), tea.WithAltScreen()).Run()
+ _, err := tea.NewProgram(ImageRenderer(cardsResult.SelectedOption, imageURL)).Run()
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: image viewer error: %v\n", err)
}
diff --git a/cmd/card/cardlist.go b/cmd/card/cardlist.go
index 65c457a..58a7aef 100644
--- a/cmd/card/cardlist.go
+++ b/cmd/card/cardlist.go
@@ -3,13 +3,14 @@ package card
import (
"encoding/json"
"fmt"
+ "image/color"
"strings"
- "github.com/charmbracelet/bubbles/spinner"
- "github.com/charmbracelet/bubbles/table"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/spinner"
+ "charm.land/bubbles/v2/table"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -47,11 +48,11 @@ type cardDataMsg struct {
}
var (
- activeTableSelectedBg = styling.YellowColor
- inactiveTableSelectedBg = lipgloss.Color("#808080")
+ activeTableSelectedBg color.Color = styling.YellowColor
+ inactiveTableSelectedBg color.Color = lipgloss.Color("#808080")
)
-func cardTableStyles(selectedBg lipgloss.Color) table.Styles {
+func cardTableStyles(selectedBg color.Color) table.Styles {
s := table.DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
@@ -137,7 +138,7 @@ func (m cardsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var bubbleCmd tea.Cmd
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c":
m.Quitting = true
@@ -185,14 +186,15 @@ func (m cardsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
ti.Placeholder = "type name..."
ti.Prompt = "🔎 "
ti.CharLimit = 24
- ti.Width = 30
+ ti.SetWidth(30)
ti.Blur()
t := table.New(
table.WithColumns([]table.Column{{Title: "Card Name", Width: 35}}),
table.WithRows(msg.allRows),
table.WithFocused(true),
- table.WithHeight(27),
+ table.WithHeight(25),
+ table.WithWidth(35),
)
styles := cardTableStyles(activeTableSelectedBg)
@@ -261,52 +263,55 @@ func applyFilter(m *cardsModel) {
m.Table.SetCursor(0)
}
-func (m cardsModel) View() string {
+func (m cardsModel) View() tea.View {
+ var content string
if m.Quitting {
- return "\n Quitting card search...\n\n"
- }
- if m.Error != nil {
- return styling.ApiErrorStyle.Render(
+ content = "\n Quitting card search...\n\n"
+ } else if m.Error != nil {
+ content = styling.ApiErrorStyle.Render(
"Error loading cards from Supabase:\n" +
m.Error.Error() + "\n\n" +
"Press ctrl+c or esc to exit.",
)
- }
- if m.Loading {
- return lipgloss.NewStyle().Padding(2).Render(
+ } else if m.Loading {
+ content = lipgloss.NewStyle().Padding(2).Render(
m.Spinner.View() + " Loading cards...",
)
- }
-
- selectedCard := ""
- if row := m.Table.SelectedRow(); len(row) > 0 {
- cardName := row[0]
- price := m.PriceMap[cardName]
- if price == "" {
- price = "Price: Not available"
+ } else {
+ selectedCard := ""
+ if row := m.Table.SelectedRow(); len(row) > 0 {
+ cardName := row[0]
+ price := m.PriceMap[cardName]
+ if price == "" {
+ price = "Price: Not available"
+ }
+ illustrator := m.IllustratorMap[cardName]
+ regulationMark := m.RegulationMarkMap[cardName]
+ selectedCard = cardName + "\n---\n" + price + "\n---\n" + illustrator + "\n---\n" + regulationMark
}
- illustrator := m.IllustratorMap[cardName]
- regulationMark := m.RegulationMarkMap[cardName]
- selectedCard = cardName + "\n---\n" + price + "\n---\n" + illustrator + "\n---\n" + regulationMark
- }
- leftContent := lipgloss.JoinVertical(lipgloss.Left, m.Search.View(), m.Table.View())
- leftPanel := styling.TypesTableBorder.Render(leftContent)
+ leftContent := lipgloss.JoinVertical(lipgloss.Left, m.Search.View(), m.Table.View())
+ leftPanel := styling.TypesTableBorder.Render(leftContent)
- rightPanel := lipgloss.NewStyle().
- Width(40).
- Height(29).
- Border(lipgloss.RoundedBorder()).
- BorderForeground(styling.YellowColor).
- Padding(1).
- Render(selectedCard)
+ rightPanel := lipgloss.NewStyle().
+ Width(42).
+ Height(29).
+ Border(lipgloss.RoundedBorder()).
+ BorderForeground(styling.YellowColor).
+ Padding(1).
+ Render(selectedCard)
- screen := lipgloss.JoinHorizontal(lipgloss.Top, leftPanel, rightPanel)
+ screen := lipgloss.JoinHorizontal(lipgloss.Top, leftPanel, rightPanel)
+
+ content = fmt.Sprintf(
+ "Highlight a card!\n\nNote: Prices are for normal variations of cards.\n%s\n%s",
+ screen,
+ styling.KeyMenu.Render("↑ (move up)\n↓ (move down)\n? (view image)\ntab (toggle search)\nctrl+c | esc (quit)"))
+ }
- return fmt.Sprintf(
- "Highlight a card!\n\nNote: Prices are for normal variations of cards.\n%s\n%s",
- screen,
- styling.KeyMenu.Render("↑ (move up)\n↓ (move down)\n? (view image)\ntab (toggle search)\nctrl+c | esc (quit)"))
+ v := tea.NewView(content)
+ v.AltScreen = true
+ return v
}
type cardData struct {
diff --git a/cmd/card/cardlist_test.go b/cmd/card/cardlist_test.go
index 9a982ac..e3a5552 100644
--- a/cmd/card/cardlist_test.go
+++ b/cmd/card/cardlist_test.go
@@ -5,9 +5,9 @@ import (
"strings"
"testing"
- "github.com/charmbracelet/bubbles/table"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/table"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
)
func TestCardsModel_Init(t *testing.T) {
@@ -31,6 +31,7 @@ func TestCardsModel_Update_EscKey(t *testing.T) {
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
model := cardsModel{
@@ -38,7 +39,7 @@ func TestCardsModel_Update_EscKey(t *testing.T) {
Quitting: false,
}
- msg := tea.KeyMsg{Type: tea.KeyEsc}
+ msg := tea.KeyPressMsg{Code: tea.KeyEscape}
newModel, cmd := model.Update(msg)
resultModel := newModel.(cardsModel)
@@ -63,6 +64,7 @@ func TestCardsModel_Update_CtrlC(t *testing.T) {
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
model := cardsModel{
@@ -70,7 +72,7 @@ func TestCardsModel_Update_CtrlC(t *testing.T) {
Quitting: false,
}
- msg := tea.KeyMsg{Type: tea.KeyCtrlC}
+ msg := tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl}
newModel, cmd := model.Update(msg)
resultModel := newModel.(cardsModel)
@@ -92,6 +94,7 @@ func TestCardsModel_Update_TabTogglesSearchFocusAndTableSelectedBackground(t *te
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
search := textinput.New()
@@ -107,7 +110,7 @@ func TestCardsModel_Update_TabTogglesSearchFocusAndTableSelectedBackground(t *te
}
// Tab into the search bar.
- newModel, _ := model.Update(tea.KeyMsg{Type: tea.KeyTab})
+ newModel, _ := model.Update(tea.KeyPressMsg{Code: tea.KeyTab})
m1 := newModel.(cardsModel)
if !m1.Search.Focused() {
t.Fatal("expected search to be focused after tab")
@@ -121,7 +124,7 @@ func TestCardsModel_Update_TabTogglesSearchFocusAndTableSelectedBackground(t *te
}
// Tab back to the table.
- newModel2, _ := m1.Update(tea.KeyMsg{Type: tea.KeyTab})
+ newModel2, _ := m1.Update(tea.KeyPressMsg{Code: tea.KeyTab})
m2 := newModel2.(cardsModel)
if m2.Search.Focused() {
t.Fatal("expected search to be blurred after tabbing back")
@@ -146,6 +149,7 @@ func TestCardsModel_Update_ViewImageKey_QuestionMark(t *testing.T) {
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
model := cardsModel{
@@ -153,7 +157,7 @@ func TestCardsModel_Update_ViewImageKey_QuestionMark(t *testing.T) {
ViewImage: false,
}
- msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}}
+ msg := tea.KeyPressMsg{Code: '?', Text: "?"}
newModel, cmd := model.Update(msg)
resultModel := newModel.(cardsModel)
@@ -175,6 +179,7 @@ func TestCardsModel_Update_ViewImageKey_DoesNotOverrideSearch(t *testing.T) {
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
search := textinput.New()
@@ -186,7 +191,7 @@ func TestCardsModel_Update_ViewImageKey_DoesNotOverrideSearch(t *testing.T) {
ViewImage: false,
}
- msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'?'}}
+ msg := tea.KeyPressMsg{Code: '?', Text: "?"}
newModel, _ := model.Update(msg)
resultModel := newModel.(cardsModel)
@@ -208,8 +213,8 @@ func TestCardsModel_View_Quitting(t *testing.T) {
result := model.View()
- if !strings.Contains(result, "Quitting card search") {
- t.Errorf("View() when quitting should contain 'Quitting card search', got: %s", result)
+ if !strings.Contains(result.Content, "Quitting card search") {
+ t.Errorf("View() when quitting should contain 'Quitting card search', got: %s", result.Content)
}
}
@@ -224,6 +229,7 @@ func TestCardsModel_View_PriceDisplay(t *testing.T) {
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
priceMap := map[string]string{
@@ -239,7 +245,7 @@ func TestCardsModel_View_PriceDisplay(t *testing.T) {
result := model.View()
// The view should include the card name
- if !strings.Contains(result, "001/198 - Bulbasaur") {
+ if !strings.Contains(result.Content, "001/198 - Bulbasaur") {
t.Error("View() should display selected card name")
}
}
@@ -255,6 +261,7 @@ func TestCardsModel_View_MissingPrice(t *testing.T) {
table.WithColumns(columns),
table.WithRows(rows),
table.WithFocused(true),
+ table.WithWidth(35),
)
// Empty price map - simulates missing price data
@@ -269,7 +276,7 @@ func TestCardsModel_View_MissingPrice(t *testing.T) {
result := model.View()
// Should show "Price: Not available" when price is missing
- if !strings.Contains(result, "Price: Not available") {
+ if !strings.Contains(result.Content, "Price: Not available") {
t.Error("View() should display 'Price: Not available' for cards without pricing")
}
}
@@ -290,8 +297,8 @@ func TestCardsList_ReturnsLoadingModel(t *testing.T) {
// View should show loading spinner
view := model.View()
- if !strings.Contains(view, "Loading cards") {
- t.Errorf("expected view to show loading state, got: %s", view)
+ if !strings.Contains(view.Content, "Loading cards") {
+ t.Errorf("expected view to show loading state, got: %s", view.Content)
}
}
diff --git a/cmd/card/imageviewer.go b/cmd/card/imageviewer.go
index 88ba82f..1de2277 100644
--- a/cmd/card/imageviewer.go
+++ b/cmd/card/imageviewer.go
@@ -1,9 +1,9 @@
package card
import (
- "github.com/charmbracelet/bubbles/spinner"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/spinner"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -63,7 +63,7 @@ func (m imageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.Spinner, cmd = m.Spinner.Update(msg)
return m, cmd
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "esc":
return m, tea.Quit
@@ -72,22 +72,26 @@ func (m imageModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
-func (m imageModel) View() string {
+func (m imageModel) View() tea.View {
+ var content string
if m.Loading {
- return lipgloss.NewStyle().Padding(2).Render(
+ content = lipgloss.NewStyle().Padding(2).Render(
m.Spinner.View() + "Loading image for \n" + m.CardName,
)
- }
- if m.Error != nil {
+ } else if m.Error != nil {
// Styling the error message with padding for better readability
- return lipgloss.NewStyle().
+ content = lipgloss.NewStyle().
Padding(2).
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(styling.YellowColor).
Render(styling.Red.Render(m.Error.Error()))
+ } else {
+ content = m.ImageData
}
- return m.ImageData
+ v := tea.NewView(content)
+ v.AltScreen = true
+ return v
}
func ImageRenderer(cardName string, imageURL string) imageModel {
diff --git a/cmd/card/imageviewer_test.go b/cmd/card/imageviewer_test.go
index e288c1b..d1dcc45 100644
--- a/cmd/card/imageviewer_test.go
+++ b/cmd/card/imageviewer_test.go
@@ -4,8 +4,8 @@ import (
"strings"
"testing"
- spinnerpkg "github.com/charmbracelet/bubbles/spinner"
- tea "github.com/charmbracelet/bubbletea"
+ spinnerpkg "charm.land/bubbles/v2/spinner"
+ tea "charm.land/bubbletea/v2"
)
func TestImageModel_Init(t *testing.T) {
@@ -24,7 +24,7 @@ func TestImageModel_Update_EscKey(t *testing.T) {
}
// Test ESC key
- msg := tea.KeyMsg{Type: tea.KeyEsc}
+ msg := tea.KeyPressMsg{Code: tea.KeyEscape}
newModel, cmd := model.Update(msg)
// Should return quit command
@@ -44,7 +44,7 @@ func TestImageModel_Update_CtrlC(t *testing.T) {
ImageURL: "test-sixel-data",
}
- msg := tea.KeyMsg{Type: tea.KeyCtrlC}
+ msg := tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl}
_, cmd := model.Update(msg)
if cmd == nil {
@@ -58,7 +58,7 @@ func TestImageModel_Update_DifferentKey(t *testing.T) {
ImageURL: "test-sixel-data",
}
- msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}}
+ msg := tea.KeyPressMsg{Code: 'a', Text: "a"}
_, cmd := model.Update(msg)
if cmd != nil {
@@ -72,11 +72,11 @@ func TestImageModel_View_Loading(t *testing.T) {
result := model.View()
// When loading, should show spinner and card name
- if result == "" {
+ if result.Content == "" {
t.Error("View() should not be empty when loading")
}
// Can't check exact spinner output as it's dynamic, but should contain card name
- if !strings.Contains(result, "001/198 - Pineco") {
+ if !strings.Contains(result.Content, "001/198 - Pineco") {
t.Error("View() should contain card name when loading")
}
}
@@ -92,8 +92,8 @@ func TestImageModel_View_Loaded(t *testing.T) {
result := model.View()
- if result != expectedData {
- t.Errorf("View() = %v, want %v", result, expectedData)
+ if result.Content != expectedData {
+ t.Errorf("View() = %v, want %v", result.Content, expectedData)
}
}
@@ -107,8 +107,8 @@ func TestImageModel_View_Empty(t *testing.T) {
result := model.View()
- if result != "" {
- t.Errorf("View() with empty ImageData should return empty string, got %v", result)
+ if result.Content != "" {
+ t.Errorf("View() with empty ImageData should return empty string, got %v", result.Content)
}
}
diff --git a/cmd/card/serieslist.go b/cmd/card/serieslist.go
index 9d7132e..139cf80 100644
--- a/cmd/card/serieslist.go
+++ b/cmd/card/serieslist.go
@@ -1,8 +1,8 @@
package card
import (
- "github.com/charmbracelet/bubbles/list"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/list"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -26,7 +26,7 @@ func (m seriesModel) Init() tea.Cmd {
func (m seriesModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.Quitting = true
@@ -50,15 +50,19 @@ func (m seriesModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}
-func (m seriesModel) View() string {
+func (m seriesModel) View() tea.View {
+ var content string
if m.Quitting {
- return "\n Quitting card search...\n\n"
- }
- if m.Choice != "" {
- return styling.QuitTextStyle.Render("Series selected:", m.Choice)
+ content = "\n Quitting card search...\n\n"
+ } else if m.Choice != "" {
+ content = styling.QuitTextStyle.Render("Series selected:", m.Choice)
+ } else {
+ content = "\n" + m.List.View()
}
- return "\n" + m.List.View()
+ v := tea.NewView(content)
+ v.AltScreen = true
+ return v
}
func SeriesList() seriesModel {
diff --git a/cmd/card/serieslist_test.go b/cmd/card/serieslist_test.go
index ff295af..edbc97d 100644
--- a/cmd/card/serieslist_test.go
+++ b/cmd/card/serieslist_test.go
@@ -4,9 +4,9 @@ import (
"testing"
"time"
- "github.com/charmbracelet/bubbles/list"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/x/exp/teatest"
+ "charm.land/bubbles/v2/list"
+ tea "charm.land/bubbletea/v2"
+ "github.com/charmbracelet/x/exp/teatest/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -37,7 +37,7 @@ func TestSeriesModelQuit(t *testing.T) {
testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(80, 24))
// Test ctrl+c quit
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(seriesModel)
@@ -59,7 +59,7 @@ func TestSeriesModelEscQuit(t *testing.T) {
testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(80, 24))
// Test esc quit
- testModel.Send(tea.KeyMsg{Type: tea.KeyEsc})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEscape})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(seriesModel)
@@ -81,8 +81,8 @@ func TestSeriesModelSelection(t *testing.T) {
testModel := teatest.NewTestModel(t, model, teatest.WithInitialTermSize(80, 24))
// Navigate and select
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown}) // Move to second item
- testModel.Send(tea.KeyMsg{Type: tea.KeyEnter}) // Select it
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown}) // Move to second item
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEnter}) // Select it
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(seriesModel)
@@ -124,22 +124,22 @@ func TestSeriesModelView(t *testing.T) {
// Test normal view
model := seriesModel{List: l}
view := model.View()
- if view == "" {
+ if view.Content == "" {
t.Errorf("Expected non-empty view, got empty string")
}
// Test quitting view
model.Quitting = true
view = model.View()
- if view != "\n Quitting card search...\n\n" {
- t.Errorf("Expected quitting message, got '%s'", view)
+ if view.Content != "\n Quitting card search...\n\n" {
+ t.Errorf("Expected quitting message, got '%s'", view.Content)
}
// Test choice made view
model.Quitting = false
model.Choice = "Scarlet & Violet"
view = model.View()
- if view == "" {
+ if view.Content == "" {
t.Errorf("Expected non-empty view for choice, got empty string")
}
}
diff --git a/cmd/card/setslist.go b/cmd/card/setslist.go
index 4c23080..6a2d60f 100644
--- a/cmd/card/setslist.go
+++ b/cmd/card/setslist.go
@@ -3,10 +3,10 @@ package card
import (
"encoding/json"
- "github.com/charmbracelet/bubbles/list"
- "github.com/charmbracelet/bubbles/spinner"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/list"
+ "charm.land/bubbles/v2/spinner"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -73,7 +73,7 @@ func (m setsModel) Init() tea.Cmd {
func (m setsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.Quitting = true
@@ -135,27 +135,29 @@ func (m setsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
-func (m setsModel) View() string {
+func (m setsModel) View() tea.View {
+ var content string
if m.Error != nil {
- return styling.ApiErrorStyle.Render(
+ content = styling.ApiErrorStyle.Render(
"Error loading sets from Supabase:\n" +
m.Error.Error() + "\n\n" +
"Press ctrl+c or esc to exit.",
)
- }
- if m.Choice != "" {
- return styling.QuitTextStyle.Render("Set selected:", m.Choice)
- }
- if m.Loading {
- return lipgloss.NewStyle().Padding(2).Render(
+ } else if m.Choice != "" {
+ content = styling.QuitTextStyle.Render("Set selected:", m.Choice)
+ } else if m.Loading {
+ content = lipgloss.NewStyle().Padding(2).Render(
m.Spinner.View() + "Loading sets...",
)
- }
- if m.Quitting {
- return "\n Quitting card search...\n\n"
+ } else if m.Quitting {
+ content = "\n Quitting card search...\n\n"
+ } else {
+ content = "\n" + m.List.View()
}
- return "\n" + m.List.View()
+ v := tea.NewView(content)
+ v.AltScreen = true
+ return v
}
type setData struct {
diff --git a/cmd/card/setslist_test.go b/cmd/card/setslist_test.go
index f823b0b..daa51e8 100644
--- a/cmd/card/setslist_test.go
+++ b/cmd/card/setslist_test.go
@@ -5,8 +5,8 @@ import (
"strings"
"testing"
- "github.com/charmbracelet/bubbles/list"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/list"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -32,7 +32,7 @@ func TestSetsModel_Update_EscKey(t *testing.T) {
Quitting: false,
}
- msg := tea.KeyMsg{Type: tea.KeyEsc}
+ msg := tea.KeyPressMsg{Code: tea.KeyEscape}
newModel, cmd := model.Update(msg)
resultModel, ok := newModel.(setsModel)
@@ -61,7 +61,7 @@ func TestSetsModel_Update_CtrlC(t *testing.T) {
Quitting: false,
}
- msg := tea.KeyMsg{Type: tea.KeyCtrlC}
+ msg := tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl}
newModel, cmd := model.Update(msg)
resultModel, ok := newModel.(setsModel)
@@ -119,8 +119,8 @@ func TestSetsModel_View_Quitting(t *testing.T) {
result := model.View()
- if !strings.Contains(result, "Quitting card search") {
- t.Errorf("View() when quitting should contain 'Quitting card search', got: %s", result)
+ if !strings.Contains(result.Content, "Quitting card search") {
+ t.Errorf("View() when quitting should contain 'Quitting card search', got: %s", result.Content)
}
}
@@ -137,8 +137,8 @@ func TestSetsModel_View_ChoiceMade(t *testing.T) {
result := model.View()
- if !strings.Contains(result, "Set selected: Scarlet & Violet") {
- t.Errorf("View() with choice should contain 'Set selected: Scarlet & Violet', got: %s", result)
+ if !strings.Contains(result.Content, "Set selected: Scarlet & Violet") {
+ t.Errorf("View() with choice should contain 'Set selected: Scarlet & Violet', got: %s", result.Content)
}
}
@@ -156,7 +156,7 @@ func TestSetsModel_View_Normal(t *testing.T) {
result := model.View()
- if result == "" {
+ if result.Content == "" {
t.Error("View() should not return empty string in normal state")
}
}
@@ -178,7 +178,7 @@ func TestSetsModel_Update_EnterKey(t *testing.T) {
SetsIDMap: setsIDMap,
}
- msg := tea.KeyMsg{Type: tea.KeyEnter}
+ msg := tea.KeyPressMsg{Code: tea.KeyEnter}
_, cmd := model.Update(msg)
if cmd == nil {
@@ -202,7 +202,7 @@ func TestSetsList_Success(t *testing.T) {
}
// View should show loading spinner
- if model.View() == "" {
+ if model.View().Content == "" {
t.Error("model view should render loading state")
}
}
diff --git a/cmd/item/item.go b/cmd/item/item.go
index e2cca5c..e2c0a2c 100644
--- a/cmd/item/item.go
+++ b/cmd/item/item.go
@@ -6,7 +6,7 @@ import (
"os"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/structs"
@@ -61,11 +61,13 @@ func itemInfoContainer(output *strings.Builder, itemStruct structs.ItemJSONStruc
itemCost := fmt.Sprintf("Cost: %d", itemStruct.Cost)
itemCategory := "Category: " + styling.CapitalizeResourceName(itemStruct.Category.Name)
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
docStyle := lipgloss.NewStyle().
Padding(1, 2).
BorderStyle(lipgloss.ThickBorder()).
- BorderForeground(lipgloss.AdaptiveColor{Light: "#444", Dark: "#EEE"}).
- Width(32)
+ BorderForeground(ld(lipgloss.Color("#444"), lipgloss.Color("#EEE"))).
+ Width(34)
var flavorTextEntry string
var missingData string
diff --git a/cmd/move/move.go b/cmd/move/move.go
index 7078812..1a3ae4c 100644
--- a/cmd/move/move.go
+++ b/cmd/move/move.go
@@ -7,7 +7,7 @@ import (
"strconv"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/structs"
@@ -66,7 +66,7 @@ func moveInfoContainer(output *strings.Builder, moveStruct structs.MoveJSONStruc
Padding(1, 2).
BorderStyle(lipgloss.ThickBorder()).
BorderForeground(lipgloss.Color(styling.GetTypeColor(moveStruct.Type.Name))).
- Width(32)
+ Width(34)
headerStyle := lipgloss.NewStyle().
Bold(true).
@@ -103,7 +103,7 @@ func moveEffectContainer(output *strings.Builder, moveStruct structs.MoveJSONStr
Padding(1, 2).
BorderStyle(lipgloss.ThickBorder()).
BorderForeground(lipgloss.Color(styling.GetTypeColor(moveStruct.Type.Name))).
- Width(32)
+ Width(34)
for _, entry := range moveStruct.FlavorTextEntries {
if entry.Language.Name != "en" {
diff --git a/cmd/natures/natures.go b/cmd/natures/natures.go
index aca4b33..f5242a9 100644
--- a/cmd/natures/natures.go
+++ b/cmd/natures/natures.go
@@ -5,8 +5,8 @@ import (
"os"
"strings"
- "github.com/charmbracelet/lipgloss"
- "github.com/charmbracelet/lipgloss/table"
+ "charm.land/lipgloss/v2"
+ "charm.land/lipgloss/v2/table"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/styling"
)
diff --git a/cmd/pokemon/pokemon.go b/cmd/pokemon/pokemon.go
index db6a974..51087d2 100644
--- a/cmd/pokemon/pokemon.go
+++ b/cmd/pokemon/pokemon.go
@@ -2,6 +2,7 @@ package pokemon
import (
"bytes"
+ "errors"
"flag"
"fmt"
"io"
@@ -10,7 +11,7 @@ import (
"sort"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/flags"
@@ -61,8 +62,10 @@ func PokemonCommand() (string, error) {
pokemonName := strings.ToLower(args[2])
if err := pf.FlagSet.Parse(args[3:]); err != nil {
- fmt.Printf("error parsing flags: %v\n", err)
- pf.FlagSet.Usage()
+ if errors.Is(err, flag.ErrHelp) {
+ return output.String(), nil
+ }
+ fmt.Fprintf(&output, "error parsing flags: %v\n", err)
return output.String(), err
}
diff --git a/cmd/pokemon/pokemon_test.go b/cmd/pokemon/pokemon_test.go
index 4b6740b..a2bc417 100644
--- a/cmd/pokemon/pokemon_test.go
+++ b/cmd/pokemon/pokemon_test.go
@@ -92,6 +92,12 @@ func TestPokemonCommand(t *testing.T) {
args: []string{"pokemon", "slowking-galar"},
expectedOutput: utils.LoadGolden(t, "pokemon_regional_form_2.golden"),
},
+ {
+ name: "Pokemon invalid flag",
+ args: []string{"pokemon", "pikachu", "--bogus"},
+ expectedOutput: utils.LoadGolden(t, "pokemon_invalid_flag.golden"),
+ expectedError: true,
+ },
}
for _, tt := range tests {
diff --git a/cmd/search/model_input.go b/cmd/search/model_input.go
index a73f0f4..2a3b67a 100644
--- a/cmd/search/model_input.go
+++ b/cmd/search/model_input.go
@@ -4,9 +4,10 @@ import (
"fmt"
"strings"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
+ "github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -14,7 +15,7 @@ import (
func UpdateInput(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
if m.ShowResults {
// If results are shown, pressing 'b' resets to search view
if msg.String() == "b" {
@@ -25,15 +26,14 @@ func UpdateInput(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
return m, textinput.Blink
}
} else {
- switch msg.Type {
+ switch msg.Code {
case tea.KeyEnter:
searchTerm := m.TextInput.Value()
_, endpoint := RenderInput(m)
// checking for blank queries
if strings.TrimSpace(searchTerm) == "" {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("✖ Error!"), "\nNo blank queries")
- m.WarningMessage = errMessage
+ m.WarningMessage = utils.FormatError("No blank queries")
return m, nil
}
diff --git a/cmd/search/model_input_test.go b/cmd/search/model_input_test.go
index 011e36f..f6df872 100644
--- a/cmd/search/model_input_test.go
+++ b/cmd/search/model_input_test.go
@@ -3,8 +3,8 @@ package search
import (
"testing"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
)
func TestUpdateInput(t *testing.T) {
@@ -16,7 +16,7 @@ func TestUpdateInput(t *testing.T) {
TextInput: ti,
}
- msg := tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'b'}}
+ msg := tea.KeyPressMsg{Code: 'b', Text: "b"}
mUpdated, _ := UpdateInput(msg, m)
updated := mUpdated.(model)
diff --git a/cmd/search/model_selection.go b/cmd/search/model_selection.go
index 0561194..ec53b63 100644
--- a/cmd/search/model_selection.go
+++ b/cmd/search/model_selection.go
@@ -3,15 +3,15 @@ package search
import (
"fmt"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
// UpdateSelection handles navigation in the selection menu.
func UpdateSelection(msg tea.Msg, m model) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "down":
m.Choice++
diff --git a/cmd/search/model_selection_test.go b/cmd/search/model_selection_test.go
index 4751bdf..255b0dc 100644
--- a/cmd/search/model_selection_test.go
+++ b/cmd/search/model_selection_test.go
@@ -4,19 +4,19 @@ import (
"testing"
"time"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/x/exp/teatest"
+ tea "charm.land/bubbletea/v2"
+ "github.com/charmbracelet/x/exp/teatest/v2"
)
func TestSelection(t *testing.T) {
m := initialModel()
testModel := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(500, 600))
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
- testModel.Send(tea.KeyMsg{Type: tea.KeyUp})
- testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyUp})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEnter})
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -40,17 +40,17 @@ func TestChoiceClamping(t *testing.T) {
testModel := teatest.NewTestModel(t, m)
// Move down twice, this should attempt to exceed max Choice
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown}) // 0 → 1
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown}) // 1 → 2, but should clamp to 1
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown}) // 0 → 1
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown}) // 1 → 2, but should clamp to 1
// Move up three times, this should attempt to go below 0
- testModel.Send(tea.KeyMsg{Type: tea.KeyUp}) // 1 → 0
- testModel.Send(tea.KeyMsg{Type: tea.KeyUp}) // 0 → -1, clamp to 0
- testModel.Send(tea.KeyMsg{Type: tea.KeyUp}) // stays at 0
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyUp}) // 1 → 0
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyUp}) // 0 → -1, clamp to 0
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyUp}) // stays at 0
// Simulate enter and quit to finish
- testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEnter})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t)
final := testModel.FinalModel(t).(model)
diff --git a/cmd/search/search.go b/cmd/search/search.go
index 66199f6..0de158f 100644
--- a/cmd/search/search.go
+++ b/cmd/search/search.go
@@ -5,8 +5,8 @@ import (
"os"
"strings"
- "github.com/charmbracelet/bubbles/textinput"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/textinput"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
)
@@ -58,7 +58,7 @@ func initialModel() model {
ti := textinput.New()
ti.Placeholder = "type name..."
ti.CharLimit = 20
- ti.Width = 20
+ ti.SetWidth(20)
return model{
TextInput: ti,
@@ -73,7 +73,7 @@ func (m model) Init() tea.Cmd {
// Update handles keypresses and updates the state.
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.Quitting = true
@@ -88,17 +88,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// View renders the correct UI screen.
-func (m model) View() string {
+func (m model) View() tea.View {
if m.Quitting {
- return "\n Quitting search...\n\n"
+ return tea.NewView("\n Quitting search...\n\n")
}
if m.ShowResults {
resultsView, _ := RenderInput(m) // Fetch results view
- return resultsView
+ return tea.NewView(resultsView)
}
if !m.Chosen {
- return RenderSelection(m)
+ return tea.NewView(RenderSelection(m))
}
inputView, _ := RenderInput(m)
- return inputView
+ return tea.NewView(inputView)
}
diff --git a/cmd/search/search_test.go b/cmd/search/search_test.go
index e0e8db5..bd1406e 100644
--- a/cmd/search/search_test.go
+++ b/cmd/search/search_test.go
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/styling"
"github.com/stretchr/testify/require"
@@ -66,7 +66,7 @@ func TestModelQuit(t *testing.T) {
m := model{}
// Simulate pressing Esc
- msg := tea.KeyMsg{Type: tea.KeyEsc}
+ msg := tea.KeyPressMsg{Code: tea.KeyEscape}
newModel, cmd := m.Update(msg)
assert.True(t, newModel.(model).Quitting, "Model should be set to quitting")
@@ -92,7 +92,7 @@ func TestSearchCommandValidationError(t *testing.T) {
func TestModelViewQuitting(t *testing.T) {
m := model{Quitting: true}
view := m.View()
- assert.Contains(t, view, "Quitting search", "View should show quitting message")
+ assert.Contains(t, view.Content, "Quitting search", "View should show quitting message")
}
func TestModelViewShowResults(t *testing.T) {
@@ -102,12 +102,12 @@ func TestModelViewShowResults(t *testing.T) {
}
view := m.View()
// View calls RenderInput when ShowResults is true
- assert.NotEmpty(t, view, "View should render results")
+ assert.NotEmpty(t, view.Content, "View should render results")
}
func TestModelViewNotChosen(t *testing.T) {
m := model{Chosen: false}
view := m.View()
// View calls RenderSelection when not chosen
- assert.Contains(t, view, "Search for a resource", "View should show selection prompt")
+ assert.Contains(t, view.Content, "Search for a resource", "View should show selection prompt")
}
diff --git a/cmd/speed/speed.go b/cmd/speed/speed.go
index 598ffb2..1f360bd 100644
--- a/cmd/speed/speed.go
+++ b/cmd/speed/speed.go
@@ -10,8 +10,8 @@ import (
"strconv"
"strings"
+ "charm.land/lipgloss/v2"
"github.com/charmbracelet/huh"
- "github.com/charmbracelet/lipgloss"
xstrings "github.com/charmbracelet/x/exp/strings"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
@@ -316,11 +316,13 @@ func formula() (string, error) {
styling.Yellow.Render(speedStr),
)
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
docStyle := lipgloss.NewStyle().
Padding(1, 2).
BorderStyle(lipgloss.ThickBorder()).
- BorderForeground(lipgloss.AdaptiveColor{Light: "#444", Dark: "#EEE"}).
- Width(32)
+ BorderForeground(ld(lipgloss.Color("#444"), lipgloss.Color("#EEE"))).
+ Width(34)
fullDoc := lipgloss.JoinVertical(lipgloss.Top, header, "---", body)
output.WriteString(docStyle.Render(fullDoc))
diff --git a/cmd/tcg/dashboard.go b/cmd/tcg/dashboard.go
index 4f9d195..390473d 100644
--- a/cmd/tcg/dashboard.go
+++ b/cmd/tcg/dashboard.go
@@ -2,19 +2,22 @@ package tcg
import (
"fmt"
+ "image/color"
+ "os"
"strings"
- "github.com/charmbracelet/bubbles/table"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/table"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
type styles struct {
- doc lipgloss.Style
- inactiveTab lipgloss.Style
- activeTab lipgloss.Style
- window lipgloss.Style
+ doc lipgloss.Style
+ inactiveTab lipgloss.Style
+ activeTab lipgloss.Style
+ window lipgloss.Style
+ highlightColor color.Color
}
func tabBorderWithBottom(left, middle, right string) lipgloss.Border {
@@ -28,7 +31,9 @@ func tabBorderWithBottom(left, middle, right string) lipgloss.Border {
func newStyles() *styles {
inactiveTabBorder := tabBorderWithBottom("┴", "─", "┴")
activeTabBorder := tabBorderWithBottom("┘", " ", "└")
- highlightColor := lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
+ highlightColor := ld(lipgloss.Color("#874BFD"), lipgloss.Color("#7D56F4"))
s := new(styles)
s.doc = lipgloss.NewStyle().
@@ -44,6 +49,7 @@ func newStyles() *styles {
Padding(2, 0).
Border(lipgloss.NormalBorder()).
UnsetBorderTop()
+ s.highlightColor = highlightColor
return s
}
@@ -73,7 +79,7 @@ func overviewView(m model, contentWidth int) string {
if len(m.standings) == 0 {
return " Loading..."
}
- return overviewContent(m.flag, m.tournament, m.tournamentType, m.tournamentDate, m.winner, m.winningDeck, m.totalPlayers, contentWidth)
+ return overviewContent(m.flag, m.tournament, m.tournamentType, m.tournamentDate, m.winner, m.winningDeck, m.totalPlayers, contentWidth, m.styles.highlightColor)
}
func countriesView(s []countryStats, width int) string {
@@ -86,7 +92,7 @@ func (m model) Init() tea.Cmd {
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch keypress := msg.String(); keypress {
case "ctrl+c", "esc":
return m, tea.Quit
@@ -151,9 +157,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
-func (m model) View() string {
+func (m model) View() tea.View {
if m.styles == nil {
- return ""
+ return tea.NewView("")
}
doc := strings.Builder{}
@@ -192,8 +198,8 @@ func (m model) View() string {
// Fill the gap between the tab row and the window's right edge so the top
// border line stretches the full width of the window.
- highlightColor := lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
- fillWidth := (windowWidth + 2) - lipgloss.Width(row)
+ highlightColor := m.styles.highlightColor
+ fillWidth := windowWidth - lipgloss.Width(row)
if fillWidth > 0 {
fill := lipgloss.NewStyle().Foreground(highlightColor).
Render(strings.Repeat("─", fillWidth-1) + "┐")
@@ -243,5 +249,7 @@ func (m model) View() string {
doc.WriteString("\n")
doc.WriteString(styling.KeyMenu.Render("← → (switch tab) • b (back) • ctrl+c | esc (quit)"))
- return s.doc.Render(doc.String())
+ v := tea.NewView(s.doc.Render(doc.String()))
+ v.AltScreen = true
+ return v
}
diff --git a/cmd/tcg/dashboard_test.go b/cmd/tcg/dashboard_test.go
index 24a36ab..c16995c 100644
--- a/cmd/tcg/dashboard_test.go
+++ b/cmd/tcg/dashboard_test.go
@@ -6,8 +6,8 @@ import (
"testing"
"time"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/x/exp/teatest"
+ tea "charm.land/bubbletea/v2"
+ "github.com/charmbracelet/x/exp/teatest/v2"
)
func newTestModel() model {
@@ -45,16 +45,16 @@ func TestDashboardModel_Init_ReturnsCmd(t *testing.T) {
func TestDashboardModel_Update_Quit(t *testing.T) {
tests := []struct {
name string
- key tea.KeyType
+ msg tea.KeyPressMsg
}{
- {"ctrl+c", tea.KeyCtrlC},
- {"esc", tea.KeyEsc},
+ {"ctrl+c", tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl}},
+ {"esc", tea.KeyPressMsg{Code: tea.KeyEscape}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := newTestModel()
tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(120, 40))
- tm.Send(tea.KeyMsg{Type: tt.key})
+ tm.Send(tt.msg)
tm.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
})
}
@@ -63,7 +63,7 @@ func TestDashboardModel_Update_Quit(t *testing.T) {
func TestDashboardModel_Update_Back(t *testing.T) {
m := newTestModel()
tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(120, 40))
- tm.Send(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'b'}})
+ tm.Send(tea.KeyPressMsg{Code: 'b', Text: "b"})
tm.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := tm.FinalModel(t).(model)
if !final.goBack {
@@ -74,12 +74,12 @@ func TestDashboardModel_Update_Back(t *testing.T) {
func TestDashboardModel_Update_TabNavigation(t *testing.T) {
m := newTestModel()
// right moves forward
- newM, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight})
+ newM, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
if newM.(model).activeTab != 1 {
t.Errorf("expected activeTab=1 after right, got %d", newM.(model).activeTab)
}
// left moves back
- newM2, _ := newM.(model).Update(tea.KeyMsg{Type: tea.KeyLeft})
+ newM2, _ := newM.(model).Update(tea.KeyPressMsg{Code: tea.KeyLeft})
if newM2.(model).activeTab != 0 {
t.Errorf("expected activeTab=0 after left, got %d", newM2.(model).activeTab)
}
@@ -88,13 +88,13 @@ func TestDashboardModel_Update_TabNavigation(t *testing.T) {
func TestDashboardModel_Update_TabNavigation_Clamps(t *testing.T) {
m := newTestModel()
// can't go below 0
- newM, _ := m.Update(tea.KeyMsg{Type: tea.KeyLeft})
+ newM, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyLeft})
if newM.(model).activeTab != 0 {
t.Errorf("expected activeTab to clamp at 0, got %d", newM.(model).activeTab)
}
// can't exceed last tab
m.activeTab = 3
- newM2, _ := m.Update(tea.KeyMsg{Type: tea.KeyRight})
+ newM2, _ := m.Update(tea.KeyPressMsg{Code: tea.KeyRight})
if newM2.(model).activeTab != 3 {
t.Errorf("expected activeTab to clamp at 3, got %d", newM2.(model).activeTab)
}
@@ -164,7 +164,7 @@ func TestDashboardModel_Update_WindowSize(t *testing.T) {
func TestDashboardModel_View_NilStyles(t *testing.T) {
m := model{}
- if m.View() != "" {
+ if m.View().Content != "" {
t.Error("expected empty string when styles is nil")
}
}
@@ -173,7 +173,7 @@ func TestDashboardModel_View_ContainsTabs(t *testing.T) {
m := newTestModel()
view := m.View()
for _, tab := range []string{"Overview", "Standings", "Decks", "Countries"} {
- if !strings.Contains(view, tab) {
+ if !strings.Contains(view.Content, tab) {
t.Errorf("expected view to contain tab %q", tab)
}
}
@@ -182,7 +182,7 @@ func TestDashboardModel_View_ContainsTabs(t *testing.T) {
func TestDashboardModel_View_LoadingState(t *testing.T) {
m := newTestModel()
view := m.View()
- if !strings.Contains(view, "Loading") {
+ if !strings.Contains(view.Content, "Loading") {
t.Error("expected loading message before data arrives")
}
}
@@ -191,8 +191,8 @@ func TestDashboardModel_View_FetchError(t *testing.T) {
m := newTestModel()
m.err = errors.New("network error")
view := m.View()
- if !strings.Contains(view, "fetch error") {
- t.Errorf("expected fetch error in view, got: %s", view)
+ if !strings.Contains(view.Content, "fetch error") {
+ t.Errorf("expected fetch error in view, got: %s", view.Content)
}
}
@@ -201,7 +201,7 @@ func TestDashboardModel_View_AllTabs(t *testing.T) {
for tab := 0; tab <= 3; tab++ {
m.activeTab = tab
view := m.View()
- if view == "" {
+ if view.Content == "" {
t.Errorf("expected non-empty view for tab %d", tab)
}
}
diff --git a/cmd/tcg/data.go b/cmd/tcg/data.go
index 73dc6d3..ebeeb3f 100644
--- a/cmd/tcg/data.go
+++ b/cmd/tcg/data.go
@@ -5,7 +5,7 @@ import (
"net/url"
"strings"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
)
type standingRows struct {
diff --git a/cmd/tcg/tab_overview.go b/cmd/tcg/tab_overview.go
index 725cac3..236d65d 100644
--- a/cmd/tcg/tab_overview.go
+++ b/cmd/tcg/tab_overview.go
@@ -2,10 +2,11 @@ package tcg
import (
"fmt"
+ "image/color"
"strconv"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
)
func formatInt(n int) string {
@@ -20,8 +21,7 @@ func formatInt(n int) string {
return result.String()
}
-func overviewContent(flag, tournament, tournamentType, tournamentDate, winner, winningDeck string, totalPlayers, contentWidth int) string {
- highlightColor := lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
+func overviewContent(flag, tournament, tournamentType, tournamentDate, winner, winningDeck string, totalPlayers, contentWidth int, highlightColor color.Color) string {
header := fmt.Sprintf("%s %s · %s · %s", flag, tournament, tournamentType, tournamentDate)
statBox := lipgloss.NewStyle().
diff --git a/cmd/tcg/tab_overview_test.go b/cmd/tcg/tab_overview_test.go
index 1bc5604..5b42142 100644
--- a/cmd/tcg/tab_overview_test.go
+++ b/cmd/tcg/tab_overview_test.go
@@ -3,6 +3,8 @@ package tcg
import (
"strings"
"testing"
+
+ "charm.land/lipgloss/v2"
)
func TestOverviewContent(t *testing.T) {
@@ -53,7 +55,7 @@ func TestOverviewContent(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- result := overviewContent(tt.flag, tt.tournament, tt.tType, tt.tDate, tt.winner, tt.winningDeck, tt.totalPlayers, tt.contentWidth)
+ result := overviewContent(tt.flag, tt.tournament, tt.tType, tt.tDate, tt.winner, tt.winningDeck, tt.totalPlayers, tt.contentWidth, lipgloss.Color("#7D56F4"))
if result == "" {
t.Fatal("expected non-empty output")
}
diff --git a/cmd/tcg/tab_standings.go b/cmd/tcg/tab_standings.go
index 0143039..31f8e51 100644
--- a/cmd/tcg/tab_standings.go
+++ b/cmd/tcg/tab_standings.go
@@ -3,8 +3,8 @@ package tcg
import (
"strconv"
- "github.com/charmbracelet/bubbles/table"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/table"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -39,6 +39,7 @@ func standingsTable(rows []standingRows, width, height int) table.Model {
}
tableHeight := max(height-14, 5)
+ tableWidth := fixedWidth + deckWidth + separators
s := table.DefaultStyles()
s.Header = s.Header.
@@ -55,6 +56,7 @@ func standingsTable(rows []standingRows, width, height int) table.Model {
table.WithRows(tableRows),
table.WithFocused(true),
table.WithHeight(tableHeight),
+ table.WithWidth(tableWidth),
)
t.SetStyles(s)
diff --git a/cmd/tcg/tcg.go b/cmd/tcg/tcg.go
index 4793632..1a96df3 100644
--- a/cmd/tcg/tcg.go
+++ b/cmd/tcg/tcg.go
@@ -1,12 +1,13 @@
package tcg
import (
+ "errors"
"flag"
"fmt"
"os"
"strings"
- tea "github.com/charmbracelet/bubbletea"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/flags"
@@ -33,8 +34,6 @@ func TcgCommand() (string, error) {
return output.String(), nil
}
- flag.Parse()
-
if err := utils.ValidateArgs(os.Args, utils.Validator{MaxArgs: 3, CmdName: "tcg", RequireName: false, HasFlags: true}); err != nil {
output.WriteString(err.Error())
return output.String(), err
@@ -42,6 +41,9 @@ func TcgCommand() (string, error) {
tf := flags.SetupTcgFlagSet()
if err := tf.FlagSet.Parse(os.Args[2:]); err != nil {
+ if errors.Is(err, flag.ErrHelp) {
+ return output.String(), nil
+ }
fmt.Fprintf(&output, "error parsing flags: %v\n", err)
return output.String(), err
}
@@ -58,7 +60,7 @@ func TcgCommand() (string, error) {
conn := connections.CallTCGData
runTournaments := func(m tournamentsModel) (tournamentsModel, error) {
- final, err := tea.NewProgram(m, tea.WithAltScreen()).Run()
+ final, err := tea.NewProgram(m).Run()
if err != nil {
return tournamentsModel{}, err
}
@@ -70,7 +72,7 @@ func TcgCommand() (string, error) {
}
runDashboard := func(m model) (model, error) {
- final, err := tea.NewProgram(m, tea.WithAltScreen()).Run()
+ final, err := tea.NewProgram(m).Run()
if err != nil {
return model{}, err
}
diff --git a/cmd/tcg/tcg_test.go b/cmd/tcg/tcg_test.go
index f949c50..20b47e0 100644
--- a/cmd/tcg/tcg_test.go
+++ b/cmd/tcg/tcg_test.go
@@ -107,6 +107,12 @@ func TestTcgCommand(t *testing.T) {
golden: "tcg_too_many_args.golden",
wantErr: true,
},
+ {
+ name: "invalid flag",
+ args: []string{"poke-cli", "tcg", "--bogus"},
+ golden: "tcg_invalid_flag.golden",
+ wantErr: true,
+ },
}
for _, tt := range tests {
diff --git a/cmd/tcg/tournamentslist.go b/cmd/tcg/tournamentslist.go
index a465011..53db148 100644
--- a/cmd/tcg/tournamentslist.go
+++ b/cmd/tcg/tournamentslist.go
@@ -3,9 +3,9 @@ package tcg
import (
"encoding/json"
- "github.com/charmbracelet/bubbles/list"
- "github.com/charmbracelet/bubbles/spinner"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/list"
+ "charm.land/bubbles/v2/spinner"
+ tea "charm.land/bubbletea/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -68,7 +68,7 @@ func (m tournamentsModel) Init() tea.Cmd {
func (m tournamentsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "ctrl+c", "esc":
m.quitting = true
@@ -131,23 +131,25 @@ func (m tournamentsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
-func (m tournamentsModel) View() string {
+func (m tournamentsModel) View() tea.View {
+ var content string
if m.quitting {
- return "\n Quitting...\n\n"
- }
- if m.error != nil {
- return styling.ApiErrorStyle.Render(
+ content = "\n Quitting...\n\n"
+ } else if m.error != nil {
+ content = styling.ApiErrorStyle.Render(
"Error loading tournaments from Supabase:\n" +
m.error.Error() + "\n\n" +
"Press ctrl+c or esc to exit.",
)
- }
- if m.loading {
- return "\n " + m.spinner.View() + " Loading tournaments...\n\n"
- }
- if m.selected != nil {
- return styling.QuitTextStyle.Render("Tournament selected:", m.selected.Location+" · "+m.selected.TextDate)
+ } else if m.loading {
+ content = "\n " + m.spinner.View() + " Loading tournaments...\n\n"
+ } else if m.selected != nil {
+ content = styling.QuitTextStyle.Render("Tournament selected:", m.selected.Location+" · "+m.selected.TextDate)
+ } else {
+ content = "\n" + m.list.View()
}
- return "\n" + m.list.View()
+ v := tea.NewView(content)
+ v.AltScreen = true
+ return v
}
diff --git a/cmd/tcg/tournamentslist_test.go b/cmd/tcg/tournamentslist_test.go
index 32b8ab4..4326904 100644
--- a/cmd/tcg/tournamentslist_test.go
+++ b/cmd/tcg/tournamentslist_test.go
@@ -6,9 +6,9 @@ import (
"testing"
"time"
- "github.com/charmbracelet/bubbles/list"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/x/exp/teatest"
+ "charm.land/bubbles/v2/list"
+ tea "charm.land/bubbletea/v2"
+ "github.com/charmbracelet/x/exp/teatest/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -111,17 +111,17 @@ func TestFetchTournaments_Success(t *testing.T) {
func TestTournamentsModel_Update_CtrlC(t *testing.T) {
tests := []struct {
name string
- key tea.KeyType
+ msg tea.KeyPressMsg
}{
- {name: "ctrl+c", key: tea.KeyCtrlC},
- {name: "esc", key: tea.KeyEsc},
+ {name: "ctrl+c", msg: tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl}},
+ {name: "esc", msg: tea.KeyPressMsg{Code: tea.KeyEscape}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
m := loadedModel()
tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))
- tm.Send(tea.KeyMsg{Type: tt.key})
+ tm.Send(tt.msg)
tm.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := tm.FinalModel(t).(tournamentsModel)
if !final.quitting {
@@ -134,7 +134,7 @@ func TestTournamentsModel_Update_CtrlC(t *testing.T) {
func TestTournamentsModel_Update_Enter_SetsSelected(t *testing.T) {
m := loadedModel()
tm := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(80, 24))
- tm.Send(tea.KeyMsg{Type: tea.KeyEnter})
+ tm.Send(tea.KeyPressMsg{Code: tea.KeyEnter})
tm.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := tm.FinalModel(t).(tournamentsModel)
if final.selected == nil {
@@ -207,8 +207,8 @@ func TestTournamentsModel_Update_WindowResize_WhenLoading(t *testing.T) {
func TestTournamentsModel_View_Loading(t *testing.T) {
m := tournamentsList(noopConn)
view := m.View()
- if !strings.Contains(view, "Loading tournaments") {
- t.Errorf("expected loading message, got %q", view)
+ if !strings.Contains(view.Content, "Loading tournaments") {
+ t.Errorf("expected loading message, got %q", view.Content)
}
}
@@ -217,8 +217,8 @@ func TestTournamentsModel_View_Error(t *testing.T) {
m.loading = false
m.error = errors.New("something went wrong")
view := m.View()
- if !strings.Contains(view, "something went wrong") {
- t.Errorf("expected error message in view, got %q", view)
+ if !strings.Contains(view.Content, "something went wrong") {
+ t.Errorf("expected error message in view, got %q", view.Content)
}
}
@@ -226,8 +226,8 @@ func TestTournamentsModel_View_Quitting(t *testing.T) {
m := tournamentsList(noopConn)
m.quitting = true
view := m.View()
- if !strings.Contains(view, "Quitting") {
- t.Errorf("expected quitting message, got %q", view)
+ if !strings.Contains(view.Content, "Quitting") {
+ t.Errorf("expected quitting message, got %q", view.Content)
}
}
@@ -236,15 +236,15 @@ func TestTournamentsModel_View_Selected(t *testing.T) {
td := m.tournaments[0]
m.selected = &td
view := m.View()
- if !strings.Contains(view, "London") {
- t.Errorf("expected selected tournament in view, got %q", view)
+ if !strings.Contains(view.Content, "London") {
+ t.Errorf("expected selected tournament in view, got %q", view.Content)
}
}
func TestTournamentsModel_View_Normal(t *testing.T) {
m := loadedModel()
view := m.View()
- if view == "" {
+ if view.Content == "" {
t.Error("expected non-empty view for loaded model")
}
}
diff --git a/cmd/types/damage_table.go b/cmd/types/damage_table.go
index f44e39f..572b3da 100644
--- a/cmd/types/damage_table.go
+++ b/cmd/types/damage_table.go
@@ -5,7 +5,7 @@ import (
"os"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
"github.com/charmbracelet/x/term"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/styling"
@@ -17,7 +17,9 @@ import (
func DamageTable(typesName string, endpoint string) {
// Setting up variables to style the list
var columnWidth = 11
- var subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
+ subtle := ld(lipgloss.Color("#383838"), lipgloss.Color("#D9DCCF"))
var list = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, true, false, false).BorderForeground(subtle).MarginRight(2).Height(8)
var listHeader = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).BorderForeground(subtle).MarginRight(2).Render
var listItem = lipgloss.NewStyle().Render
diff --git a/cmd/types/damage_table_test.go b/cmd/types/damage_table_test.go
index f0fab2d..e14a7db 100644
--- a/cmd/types/damage_table_test.go
+++ b/cmd/types/damage_table_test.go
@@ -5,6 +5,8 @@ import (
"os"
"strings"
"testing"
+
+ "github.com/digitalghost-dev/poke-cli/styling"
)
func TestDamageTable(t *testing.T) {
@@ -30,7 +32,7 @@ func TestDamageTable(t *testing.T) {
if err != nil {
t.Fatalf("Failed to read from pipe: %v", err)
}
- output := buf.String()
+ output := styling.StripANSI(buf.String())
// Step 7: Assert the output contains expected strings
if !strings.Contains(output, "You selected the Fire type.") {
diff --git a/cmd/types/types.go b/cmd/types/types.go
index d461a2b..e69b093 100644
--- a/cmd/types/types.go
+++ b/cmd/types/types.go
@@ -6,9 +6,9 @@ import (
"os"
"strings"
- "github.com/charmbracelet/bubbles/table"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/table"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -64,7 +64,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var bubbleCmd tea.Cmd
switch msg := msg.(type) {
- case tea.KeyMsg:
+ case tea.KeyPressMsg:
switch msg.String() {
case "esc", "ctrl+c":
m.quitting = true
@@ -82,20 +82,20 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
// View renders the current UI
-func (m model) View() string {
+func (m model) View() tea.View {
if m.quitting {
- return "\n Goodbye! \n"
+ return tea.NewView("\n Goodbye! \n")
}
// Don't render anything if a selection has been made
if m.selectedOption != "" {
- return ""
+ return tea.NewView("")
}
// Render the type selection table with instructions
- return fmt.Sprintf("Select a type!\n%s\n%s",
+ return tea.NewView(fmt.Sprintf("Select a type!\n%s\n%s",
styling.TypesTableBorder.Render(m.table.View()),
- styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nenter (select) • ctrl+c | esc (quit)"))
+ styling.KeyMenu.Render("↑ (move up) • ↓ (move down)\nenter (select) • ctrl+c | esc (quit)")))
}
func createTypeSelectionTable() model {
@@ -113,6 +113,7 @@ func createTypeSelectionTable() model {
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(10),
+ table.WithWidth(16),
)
s := table.DefaultStyles()
diff --git a/cmd/types/types_test.go b/cmd/types/types_test.go
index 52894ba..1af37df 100644
--- a/cmd/types/types_test.go
+++ b/cmd/types/types_test.go
@@ -5,10 +5,10 @@ import (
"testing"
"time"
- "github.com/charmbracelet/bubbles/table"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/charmbracelet/x/exp/teatest"
+ "charm.land/bubbles/v2/table"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
+ "github.com/charmbracelet/x/exp/teatest/v2"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/styling"
"github.com/stretchr/testify/assert"
@@ -68,6 +68,7 @@ func createTestModel() model {
table.WithRows(rows),
table.WithFocused(true),
table.WithHeight(7),
+ table.WithWidth(16),
)
// Set table styles
@@ -90,7 +91,7 @@ func TestUpdate(t *testing.T) {
testModel := teatest.NewTestModel(t, m)
// Send escape key
- testModel.Send(tea.KeyMsg{Type: tea.KeyEsc})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEscape})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -102,7 +103,7 @@ func TestUpdate(t *testing.T) {
testModel := teatest.NewTestModel(t, m)
// Send ctrl+c key
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -114,7 +115,7 @@ func TestUpdate(t *testing.T) {
testModel := teatest.NewTestModel(t, m)
// Send enter key
- testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEnter})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -126,10 +127,10 @@ func TestUpdate(t *testing.T) {
testModel := teatest.NewTestModel(t, m)
// Send down arrow key to select the second row
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown})
// Then send enter to select it
- testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEnter})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
@@ -143,7 +144,7 @@ func TestView(t *testing.T) {
m.quitting = true
view := m.View()
- assert.Equal(t, "\n Goodbye! \n", view, "View should return goodbye message when quitting")
+ assert.Equal(t, "\n Goodbye! \n", view.Content, "View should return goodbye message when quitting")
})
t.Run("View should return empty string when selectedOption is set", func(t *testing.T) {
@@ -155,9 +156,9 @@ func TestView(t *testing.T) {
m := createTestModel()
view := m.View()
- assert.Contains(t, view, "Select a type!", "View should contain the title")
- assert.Contains(t, view, "Type", "View should contain the table header")
- assert.Contains(t, view, "move up", "View should contain the key menu")
+ assert.Contains(t, view.Content, "Select a type!", "View should contain the title")
+ assert.Contains(t, view.Content, "Type", "View should contain the table header")
+ assert.Contains(t, view.Content, "move up", "View should contain the key menu")
})
}
@@ -166,10 +167,10 @@ func TestTypeSelection(t *testing.T) {
testModel := teatest.NewTestModel(t, m, teatest.WithInitialTermSize(300, 500))
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
- testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
- testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
- testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyDown})
+ testModel.Send(tea.KeyPressMsg{Code: tea.KeyEnter})
+ testModel.Send(tea.KeyPressMsg{Code: 'c', Mod: tea.ModCtrl})
testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
final := testModel.FinalModel(t).(model)
diff --git a/cmd/utils/errors.go b/cmd/utils/errors.go
new file mode 100644
index 0000000..3f439be
--- /dev/null
+++ b/cmd/utils/errors.go
@@ -0,0 +1,10 @@
+package utils
+
+import "github.com/digitalghost-dev/poke-cli/styling"
+
+func FormatError(message string) string {
+ return styling.ErrorBorder.Render(
+ styling.ErrorColor.Render("✖ Error!"),
+ "\n"+message,
+ )
+}
diff --git a/cmd/utils/validateargs.go b/cmd/utils/validateargs.go
index ee0e455..b2dc035 100644
--- a/cmd/utils/validateargs.go
+++ b/cmd/utils/validateargs.go
@@ -3,8 +3,6 @@ package utils
import (
"fmt"
"strings"
-
- "github.com/digitalghost-dev/poke-cli/styling"
)
type Validator struct {
@@ -17,10 +15,7 @@ type Validator struct {
// checkLength checks if the number of arguments is lower than the max value. Helper Function.
func checkLength(args []string, max int) error {
if len(args) > max {
- errMessage := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("✖ Error!") + "\nToo many arguments",
- )
- return fmt.Errorf("%s", errMessage)
+ return fmt.Errorf("%s", FormatError("Too many arguments"))
}
return nil
}
@@ -28,9 +23,7 @@ func checkLength(args []string, max int) error {
// checkNoOtherOptions checks if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
func checkNoOtherOptions(args []string, max int, commandName string) error {
if len(args) == max && args[2] != "-h" && args[2] != "--help" {
- errMsg := styling.ErrorColor.Render("✖ Error!") +
- "\nThe only available options after the\n" + "<" + commandName + "> command are '-h' or '--help'"
- return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
+ return fmt.Errorf("%s", FormatError(fmt.Sprintf("The only available options after the\n<%s> command are '-h' or '--help'", commandName)))
}
return nil
}
@@ -40,13 +33,10 @@ func ValidateArgs(args []string, v Validator) error {
return err
}
if v.RequireName && len(args) == 2 {
- errMessage := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("✖ Error!"),
- fmt.Sprintf("\nPlease declare a(n) %s's name after the <%s> command", v.CmdName, v.CmdName),
- fmt.Sprintf("\nRun 'poke-cli %s -h' for more details", v.CmdName),
- "\nerror: insufficient arguments",
- )
- return fmt.Errorf("%s", errMessage)
+ return fmt.Errorf("%s", FormatError(fmt.Sprintf(
+ "Please declare a(n) %s's name after the <%s> command\nRun 'poke-cli %s -h' for more details\nerror: insufficient arguments",
+ v.CmdName, v.CmdName, v.CmdName,
+ )))
}
if !v.HasFlags && !v.RequireName {
if err := checkNoOtherOptions(args, v.MaxArgs, v.CmdName); err != nil {
@@ -60,13 +50,9 @@ func ValidateArgs(args []string, v Validator) error {
func ValidatePokemonArgs(args []string) error {
// Check if the number of arguments is less than 3
if len(args) < 3 {
- errMessage := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("✖ Error!"),
- "\nPlease declare a Pokémon's name after the command",
- "\nRun 'poke-cli pokemon -h' for more details",
- "\nerror: insufficient arguments",
- )
- return fmt.Errorf("%s", errMessage)
+ return fmt.Errorf("%s", FormatError(
+ "Please declare a Pokémon's name after the command\nRun 'poke-cli pokemon -h' for more details\nerror: insufficient arguments",
+ ))
}
if err := checkLength(args, 8); err != nil {
@@ -74,11 +60,7 @@ func ValidatePokemonArgs(args []string) error {
}
printImageFlagError := func() error {
- msg := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("✖ Error!") +
- "\nThe image flag (-i or --image) requires a non-empty value.\nValid sizes are: lg, md, sm.",
- )
- return fmt.Errorf("%s", msg)
+ return fmt.Errorf("%s", FormatError("The image flag (-i or --image) requires a non-empty value.\nValid sizes are: lg, md, sm."))
}
for _, arg := range args {
@@ -97,26 +79,12 @@ func ValidatePokemonArgs(args []string) error {
for _, arg := range args[3:] {
// Check for an empty flag after Pokémon's name
if arg == "-" || arg == "--" {
- errorTitle := styling.ErrorColor.Render("✖ Error!")
- errorString := fmt.Sprintf(
- "\nEmpty flag '%s'.\nPlease specify valid flag(s).",
- arg,
- )
- finalErrorMessage := errorTitle + errorString
- renderedError := styling.ErrorBorder.Render(finalErrorMessage)
- return fmt.Errorf("%s", renderedError)
+ return fmt.Errorf("%s", FormatError(fmt.Sprintf("Empty flag '%s'.\nPlease specify valid flag(s).", arg)))
}
// Check if the argument after Pokémon's name is an attempted flag
if arg[0] != '-' {
- errorTitle := styling.ErrorColor.Render("✖ Error!")
- errorString := fmt.Sprintf(
- "\nInvalid argument '%s'.\nOnly flags are allowed after declaring a Pokémon's name",
- arg,
- )
- finalErrorMessage := errorTitle + errorString
- renderedError := styling.ErrorBorder.Render(finalErrorMessage)
- return fmt.Errorf("%s", renderedError)
+ return fmt.Errorf("%s", FormatError(fmt.Sprintf("Invalid argument '%s'.\nOnly flags are allowed after declaring a Pokémon's name", arg)))
}
}
}
diff --git a/connections/connection.go b/connections/connection.go
index 9a3242d..160ff0f 100644
--- a/connections/connection.go
+++ b/connections/connection.go
@@ -10,8 +10,8 @@ import (
"net/url"
"time"
+ "github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/structs"
- "github.com/digitalghost-dev/poke-cli/styling"
)
const APIURL = "https://pokeapi.co/api/v2/"
@@ -30,11 +30,7 @@ func FetchEndpoint[T EndpointResource](endpoint, resourceName, baseURL, resource
err := ApiCallSetup(fullURL, &result, false)
if err != nil {
- errMessage := styling.ErrorBorder.Render(
- styling.ErrorColor.Render("✖ Error!"),
- fmt.Sprintf("\n%s not found.\n• Perhaps a typo?\n• Missing a hyphen instead of a space?", resourceType),
- )
- return zero, "", fmt.Errorf("%s", errMessage)
+ return zero, "", fmt.Errorf("%s", utils.FormatError(resourceType+" not found.\n• Perhaps a typo?\n• Missing a hyphen instead of a space?"))
}
return result, result.GetResourceName(), nil
diff --git a/flags/abilityflagset.go b/flags/abilityflagset.go
index 0326c6a..8adfa15 100644
--- a/flags/abilityflagset.go
+++ b/flags/abilityflagset.go
@@ -20,7 +20,7 @@ type AbilityFlags struct {
func SetupAbilityFlagSet() *AbilityFlags {
af := &AbilityFlags{}
- af.FlagSet = flag.NewFlagSet("abilityFlags", flag.ExitOnError)
+ af.FlagSet = flag.NewFlagSet("abilityFlags", flag.ContinueOnError)
af.Pokemon = af.FlagSet.Bool("pokemon", false, "List all Pokémon with chosen ability")
af.ShortPokemon = af.FlagSet.Bool("p", false, "List all Pokémon with chosen ability")
diff --git a/flags/pokemonflagset.go b/flags/pokemonflagset.go
index 5c920a2..5c6b567 100644
--- a/flags/pokemonflagset.go
+++ b/flags/pokemonflagset.go
@@ -9,13 +9,15 @@ import (
"io"
"log"
"net/http"
+ "os"
"sort"
"strconv"
"strings"
"sync"
- "github.com/charmbracelet/lipgloss"
- "github.com/charmbracelet/lipgloss/table"
+ "charm.land/lipgloss/v2"
+ "charm.land/lipgloss/v2/table"
+ cmdutils "github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/connections"
"github.com/digitalghost-dev/poke-cli/structs"
"github.com/digitalghost-dev/poke-cli/styling"
@@ -57,7 +59,7 @@ func header(header string) string {
func SetupPokemonFlagSet() *PokemonFlags {
pf := &PokemonFlags{}
- pf.FlagSet = flag.NewFlagSet("pokeFlags", flag.ExitOnError)
+ pf.FlagSet = flag.NewFlagSet("pokeFlags", flag.ContinueOnError)
pf.Abilities = pf.FlagSet.Bool("abilities", false, "Print the Pokémon's abilities")
pf.ShortAbilities = pf.FlagSet.Bool("a", false, "Print the Pokémon's abilities")
@@ -347,7 +349,7 @@ func ImageFlag(w io.Writer, endpoint string, pokemonName string, size string) er
c2, _ := styling.MakeColor(img.At(x, heightCounter+1))
color2 := lipgloss.Color(c2.Hex())
- styleKey := string(color1) + "_" + string(color2)
+ styleKey := c1.Hex() + "_" + c2.Hex()
style, exists := styleCache[styleKey]
if !exists {
style = lipgloss.NewStyle().Foreground(color1).Background(color2)
@@ -386,8 +388,7 @@ func ImageFlag(w io.Writer, endpoint string, pokemonName string, size string) er
// Validate size
dimensions, exists := sizeMap[strings.ToLower(size)]
if !exists {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("✖ Error!"), "\nInvalid image size.\nValid sizes are: lg, md, sm")
- return fmt.Errorf("%s", errMessage)
+ return fmt.Errorf("%s", cmdutils.FormatError("Invalid image size.\nValid sizes are: lg, md, sm"))
}
imgStr := ToString(dimensions[0], dimensions[1], img)
@@ -510,7 +511,9 @@ func MovesFlag(w io.Writer, endpoint string, pokemonName string) error {
}
// Build and print table
- color := lipgloss.AdaptiveColor{Light: "#4B4B4B", Dark: "#D3D3D3"}
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
+ color := ld(lipgloss.Color("#4B4B4B"), lipgloss.Color("#D3D3D3"))
t := table.New().
Border(lipgloss.NormalBorder()).
diff --git a/flags/tcg_flagset.go b/flags/tcg_flagset.go
index ed8c6cf..93cba58 100644
--- a/flags/tcg_flagset.go
+++ b/flags/tcg_flagset.go
@@ -18,7 +18,7 @@ type TcgFlags struct {
func SetupTcgFlagSet() *TcgFlags {
tf := &TcgFlags{}
- tf.FlagSet = flag.NewFlagSet("tcgFlags", flag.ExitOnError)
+ tf.FlagSet = flag.NewFlagSet("tcgFlags", flag.ContinueOnError)
tf.Web = tf.FlagSet.Bool("web", false, "Opens a Streamlit dashboard of stats in the browser")
tf.ShortWeb = tf.FlagSet.Bool("w", false, "Opens a Streamlit dashboard of stats in the browser")
diff --git a/flags/version.go b/flags/version.go
index aa21351..91c6eaa 100644
--- a/flags/version.go
+++ b/flags/version.go
@@ -8,9 +8,10 @@ import (
"io"
"net/http"
"net/url"
+ "os"
"strings"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
"github.com/digitalghost-dev/poke-cli/styling"
)
@@ -75,11 +76,13 @@ func latestRelease(output *strings.Builder) error {
releaseString := "Latest available release on GitHub:"
releaseTag := styling.ColoredBullet.Render("") + release.TagName
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
docStyle := lipgloss.NewStyle().
Padding(1, 2).
BorderStyle(lipgloss.ThickBorder()).
- BorderForeground(lipgloss.AdaptiveColor{Light: "#444", Dark: "#EEE"}).
- Width(30)
+ BorderForeground(ld(lipgloss.Color("#444"), lipgloss.Color("#EEE"))).
+ Width(32)
fullDoc := lipgloss.JoinVertical(lipgloss.Top, releaseString, releaseTag)
diff --git a/go.mod b/go.mod
index b74cb0d..8d0334e 100644
--- a/go.mod
+++ b/go.mod
@@ -3,19 +3,21 @@ module github.com/digitalghost-dev/poke-cli
go 1.25.8
require (
- github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
- github.com/charmbracelet/bubbletea v1.3.10
+ charm.land/bubbles/v2 v2.1.0
+ charm.land/bubbletea/v2 v2.0.2
+ charm.land/lipgloss/v2 v2.0.2
github.com/charmbracelet/huh v0.8.0
github.com/charmbracelet/lipgloss v1.1.0
- github.com/charmbracelet/x/ansi v0.11.0
+ github.com/charmbracelet/x/ansi v0.11.6
github.com/charmbracelet/x/exp/strings v0.0.0-20251110210702-903592506081
- github.com/charmbracelet/x/exp/teatest v0.0.0-20251110210702-903592506081
+ github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20260413165052-6921c759c913
github.com/charmbracelet/x/term v0.2.2
github.com/disintegration/imaging v1.6.2
github.com/dolmen-go/kittyimg v0.0.0-20250610224728-874967bd8ea4
github.com/schollz/closestmatch v2.1.0+incompatible
github.com/stretchr/testify v1.11.1
golang.org/x/image v0.33.0
+ golang.org/x/term v0.42.0
golang.org/x/text v0.31.0
modernc.org/sqlite v1.39.1
)
@@ -23,15 +25,19 @@ require (
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
- github.com/aymanbagabas/go-udiff v0.3.1 // indirect
- github.com/bits-and-blooms/bitset v1.24.3 // indirect
+ github.com/aymanbagabas/go-udiff v0.4.1 // indirect
+ github.com/bits-and-blooms/bitset v1.24.4 // indirect
github.com/catppuccin/go v0.3.0 // indirect
- github.com/charmbracelet/colorprofile v0.3.3 // indirect
+ github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
+ github.com/charmbracelet/bubbletea v1.3.10 // indirect
+ github.com/charmbracelet/colorprofile v0.4.2 // indirect
+ github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
github.com/charmbracelet/x/cellbuf v0.0.14 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20251110210702-903592506081 // indirect
- github.com/clipperhouse/displaywidth v0.5.0 // indirect
- github.com/clipperhouse/stringish v0.1.1 // indirect
- github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
+ github.com/charmbracelet/x/termios v0.1.1 // indirect
+ github.com/charmbracelet/x/windows v0.2.2 // indirect
+ github.com/clipperhouse/displaywidth v0.11.0 // indirect
+ github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
@@ -39,7 +45,7 @@ require (
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
- github.com/mattn/go-runewidth v0.0.19 // indirect
+ github.com/mattn/go-runewidth v0.0.21 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
@@ -51,7 +57,8 @@ require (
github.com/sahilm/fuzzy v0.1.1 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20251023183803-a4bb9ffd2546 // indirect
- golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/sync v0.19.0 // indirect
+ golang.org/x/sys v0.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.66.10 // indirect
modernc.org/mathutil v1.7.1 // indirect
diff --git a/go.sum b/go.sum
index 2ee4fad..c4d181a 100644
--- a/go.sum
+++ b/go.sum
@@ -1,27 +1,35 @@
+charm.land/bubbles/v2 v2.1.0 h1:YSnNh5cPYlYjPxRrzs5VEn3vwhtEn3jVGRBT3M7/I0g=
+charm.land/bubbles/v2 v2.1.0/go.mod h1:l97h4hym2hvWBVfmJDtrEHHCtkIKeTEb3TTJ4ZOB3wY=
+charm.land/bubbletea/v2 v2.0.2 h1:4CRtRnuZOdFDTWSff9r8QFt/9+z6Emubz3aDMnf/dx0=
+charm.land/bubbletea/v2 v2.0.2/go.mod h1:3LRff2U4WIYXy7MTxfbAQ+AdfM3D8Xuvz2wbsOD9OHQ=
+charm.land/lipgloss/v2 v2.0.2 h1:xFolbF8JdpNkM2cEPTfXEcW1p6NRzOWTSamRfYEw8cs=
+charm.land/lipgloss/v2 v2.0.2/go.mod h1:KjPle2Qd3YmvP1KL5OMHiHysGcNwq6u83MUjYkFvEkM=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
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/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
-github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
-github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
-github.com/bits-and-blooms/bitset v1.24.3 h1:Bte86SlO3lwPQqww+7BE9ZuUCKIjfqnG5jtEyqA9y9Y=
-github.com/bits-and-blooms/bitset v1.24.3/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
+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/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
+github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
-github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI=
-github.com/charmbracelet/colorprofile v0.3.3/go.mod h1:nB1FugsAbzq284eJcjfah2nhdSLppN2NqvfotkfRYP4=
+github.com/charmbracelet/colorprofile v0.4.2 h1:BdSNuMjRbotnxHSfxy+PCSa4xAmz7szw70ktAtWRYrY=
+github.com/charmbracelet/colorprofile v0.4.2/go.mod h1:0rTi81QpwDElInthtrQ6Ni7cG0sDtwAd4C4le060fT8=
github.com/charmbracelet/huh v0.8.0 h1:Xz/Pm2h64cXQZn/Jvele4J3r7DDiqFCNIVteYukxDvY=
github.com/charmbracelet/huh v0.8.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
-github.com/charmbracelet/x/ansi v0.11.0 h1:uuIVK7GIplwX6UBIz8S2TF8nkr7xRlygSsBRjSJqIvA=
-github.com/charmbracelet/x/ansi v0.11.0/go.mod h1:uQt8bOrq/xgXjlGcFMc8U2WYbnxyjrKhnvTQluvfCaE=
+github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 h1:eyFRbAmexyt43hVfeyBofiGSEmJ7krjLOYt/9CF5NKA=
+github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8/go.mod h1:SQpCTRNBtzJkwku5ye4S3HEuthAlGy2n9VXZnWkEW98=
+github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
+github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
@@ -32,20 +40,20 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20251110210702-903592506081 h1:0pHM
github.com/charmbracelet/x/exp/golden v0.0.0-20251110210702-903592506081/go.mod h1:V8n/g3qVKNxr2FR37Y+otCsMySvZr601T0C7coEP0bw=
github.com/charmbracelet/x/exp/strings v0.0.0-20251110210702-903592506081 h1:pTHy/fb1lG8MTw0FizbBQV9HHXEO2+MtPXkcE0S44nM=
github.com/charmbracelet/x/exp/strings v0.0.0-20251110210702-903592506081/go.mod h1:/ehtMPNh9K4odGFkqYJKpIYyePhdp1hLBRvyY4bWkH8=
-github.com/charmbracelet/x/exp/teatest v0.0.0-20251110210702-903592506081 h1:4V7nggB2MvTMnI03immNNETBuRZHZE9N/awjP77IooY=
-github.com/charmbracelet/x/exp/teatest v0.0.0-20251110210702-903592506081/go.mod h1:aPVjFrBwbJgj5Qz1F0IXsnbcOVJcMKgu1ySUfTAxh7k=
+github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20260413165052-6921c759c913 h1:UMEoVjbcvT7AhKY++IA3sJJwwhLrHjLfP6acSW0N6f0=
+github.com/charmbracelet/x/exp/teatest/v2 v2.0.0-20260413165052-6921c759c913/go.mod h1:aRoQwQWmN9LBG2xi3sVByMFt2fdkPCagd0GAJ1qwOfw=
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/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
+github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
+github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
-github.com/clipperhouse/displaywidth v0.5.0 h1:AIG5vQaSL2EKqzt0M9JMnvNxOCRTKUc4vUnLWGgP89I=
-github.com/clipperhouse/displaywidth v0.5.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
-github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
-github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
-github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
-github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
+github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
+github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
+github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -70,8 +78,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
-github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
-github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
+github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
+github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
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/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
@@ -103,12 +111,14 @@ golang.org/x/image v0.33.0 h1:LXRZRnv1+zGd5XBUVRFmYEphyyKJjQjCRiOuAP3sZfQ=
golang.org/x/image v0.33.0/go.mod h1:DD3OsTYT9chzuzTQt+zMcOlBHgfoKQb1gry8p76Y1sc=
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
-golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
-golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
+golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
-golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
+golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
diff --git a/nfpm.yaml b/nfpm.yaml
index 16135db..c3b5afa 100644
--- a/nfpm.yaml
+++ b/nfpm.yaml
@@ -1,7 +1,7 @@
name: "poke-cli"
arch: "arm64"
platform: "linux"
-version: "v1.9.2"
+version: "v1.9.3"
section: "default"
version_schema: semver
maintainer: "Christian S"
diff --git a/styling/huh_theme.go b/styling/huh_theme.go
new file mode 100644
index 0000000..98fa6a4
--- /dev/null
+++ b/styling/huh_theme.go
@@ -0,0 +1,52 @@
+package styling
+
+import (
+ "github.com/charmbracelet/huh"
+ lipgloss "github.com/charmbracelet/lipgloss"
+)
+
+func FormTheme() *huh.Theme {
+ var (
+ yellow = lipgloss.Color(LightYellow)
+ blue = lipgloss.Color("#3B4CCA")
+ red = lipgloss.Color("#D00000")
+ black = lipgloss.Color("#000000")
+ normalFg = lipgloss.AdaptiveColor{Light: "235", Dark: "252"}
+ )
+ t := huh.ThemeBase()
+
+ t.Focused.Base = t.Focused.Base.BorderForeground(lipgloss.Color("238"))
+ t.Focused.Card = t.Focused.Base
+ t.Focused.Title = t.Focused.Title.Foreground(blue).Bold(true)
+ t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(blue).Bold(true).MarginBottom(1)
+ t.Focused.Directory = t.Focused.Directory.Foreground(blue)
+ t.Focused.Description = t.Focused.Description.Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"})
+ t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red)
+ t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red)
+ t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(red)
+ t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(yellow)
+ t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(yellow)
+ t.Focused.Option = t.Focused.Option.Foreground(normalFg)
+ t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(red)
+ t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(red)
+ t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(red).SetString("✓ ")
+ t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}).SetString("• ")
+ t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(normalFg)
+ t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(black).Background(yellow)
+ t.Focused.Next = t.Focused.FocusedButton
+
+ t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(yellow)
+ t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(lipgloss.AdaptiveColor{Light: "248", Dark: "238"})
+ t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(red)
+
+ t.Blurred = t.Focused
+ t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder())
+ t.Blurred.Card = t.Blurred.Base
+ t.Blurred.NextIndicator = lipgloss.NewStyle()
+ t.Blurred.PrevIndicator = lipgloss.NewStyle()
+
+ t.Group.Title = t.Focused.Title
+ t.Group.Description = t.Focused.Description
+
+ return t
+}
diff --git a/styling/list.go b/styling/list.go
index edd730b..a77b0e4 100644
--- a/styling/list.go
+++ b/styling/list.go
@@ -3,22 +3,32 @@ package styling
import (
"fmt"
"io"
+ "os"
"strings"
- "github.com/charmbracelet/bubbles/list"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/bubbles/v2/list"
+ tea "charm.land/bubbletea/v2"
+ "charm.land/lipgloss/v2"
)
var (
TitleStyle = lipgloss.NewStyle().MarginLeft(2)
ItemStyle = lipgloss.NewStyle().PaddingLeft(4)
- SelectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(YellowAdaptive)
- PaginationStyle = list.DefaultStyles().PaginationStyle.PaddingLeft(4)
- HelpStyle = list.DefaultStyles().HelpStyle.PaddingLeft(4).PaddingBottom(1)
+ SelectedItemStyle lipgloss.Style
+ PaginationStyle lipgloss.Style
+ HelpStyle lipgloss.Style
QuitTextStyle = lipgloss.NewStyle().Margin(1, 0, 2, 4)
)
+func init() {
+ isDark := lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ ld := lipgloss.LightDark(isDark)
+ defaults := list.DefaultStyles(isDark)
+ PaginationStyle = defaults.PaginationStyle.PaddingLeft(4)
+ HelpStyle = defaults.HelpStyle.PaddingLeft(4).PaddingBottom(1)
+ SelectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(ld(lipgloss.Color(DarkYellow), lipgloss.Color(LightYellow)))
+}
+
type Item string
func (i Item) FilterValue() string { return "" }
diff --git a/styling/list_test.go b/styling/list_test.go
index d43ce4d..0766cac 100644
--- a/styling/list_test.go
+++ b/styling/list_test.go
@@ -4,8 +4,8 @@ import (
"bytes"
"testing"
- "github.com/charmbracelet/bubbles/list"
- tea "github.com/charmbracelet/bubbletea"
+ "charm.land/bubbles/v2/list"
+ tea "charm.land/bubbletea/v2"
)
func TestItemFilterValue(t *testing.T) {
@@ -37,7 +37,7 @@ func TestItemDelegateSpacing(t *testing.T) {
func TestItemDelegateUpdate(t *testing.T) {
delegate := ItemDelegate{}
- cmd := delegate.Update(tea.KeyMsg{}, &list.Model{})
+ cmd := delegate.Update(tea.KeyPressMsg{}, &list.Model{})
if cmd != nil {
t.Error("Expected Update to return nil, got non-nil value")
diff --git a/styling/styling.go b/styling/styling.go
index d4cea25..882ee73 100644
--- a/styling/styling.go
+++ b/styling/styling.go
@@ -3,11 +3,12 @@ package styling
import (
"fmt"
"image/color"
+ "os"
"regexp"
"strings"
- "github.com/charmbracelet/huh"
- "github.com/charmbracelet/lipgloss"
+ "charm.land/lipgloss/v2"
+ "golang.org/x/term"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
@@ -22,31 +23,27 @@ const (
var (
YellowColor = lipgloss.Color(PrimaryYellow)
- YellowAdaptive = lipgloss.AdaptiveColor{Light: DarkYellow, Dark: LightYellow}
- YellowAdaptive2 = lipgloss.AdaptiveColor{Light: DarkYellow, Dark: PrimaryYellow}
+ YellowAdaptive color.Color
+ YellowAdaptive2 color.Color
)
var (
Green = lipgloss.NewStyle().Foreground(lipgloss.Color("#38B000"))
Red = lipgloss.NewStyle().Foreground(lipgloss.Color("#D00000"))
Gray = lipgloss.Color("#777777")
- Yellow = lipgloss.NewStyle().Foreground(YellowAdaptive)
- ColoredBullet = lipgloss.NewStyle().
- SetString("•").
- Foreground(YellowColor)
- CheckboxStyle = lipgloss.NewStyle().Foreground(YellowColor)
+ Yellow lipgloss.Style
+ ColoredBullet lipgloss.Style
+ CheckboxStyle = lipgloss.NewStyle().Foreground(lipgloss.Color(PrimaryYellow))
KeyMenu = lipgloss.NewStyle().Foreground(lipgloss.Color("#777777"))
- DocsLink = lipgloss.NewStyle().
- Foreground(YellowAdaptive2).
- Render("\x1b]8;;https://docs.poke-cli.com\x1b\\docs.poke-cli.com\x1b]8;;\x1b\\")
+ DocsLink string
StyleBold = lipgloss.NewStyle().Bold(true)
StyleItalic = lipgloss.NewStyle().Italic(true)
StyleUnderline = lipgloss.NewStyle().Underline(true)
HelpBorder = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
- BorderForeground(YellowColor)
+ BorderForeground(lipgloss.Color(PrimaryYellow))
ErrorColor = lipgloss.NewStyle().Foreground(lipgloss.Color("#F2055C"))
ErrorBorder = lipgloss.NewStyle().
BorderStyle(lipgloss.RoundedBorder()).
@@ -61,7 +58,7 @@ var (
BorderForeground(lipgloss.Color("#FF8C00"))
TypesTableBorder = lipgloss.NewStyle().
BorderStyle(lipgloss.NormalBorder()).
- BorderForeground(YellowColor)
+ BorderForeground(lipgloss.Color(PrimaryYellow))
ColorMap = map[string]string{
"normal": "#B7B7A9",
"fire": "#FF4422",
@@ -84,6 +81,23 @@ var (
}
)
+func init() {
+ isDark := true
+ if term.IsTerminal(int(os.Stdin.Fd())) && term.IsTerminal(int(os.Stdout.Fd())) {
+ isDark = lipgloss.HasDarkBackground(os.Stdin, os.Stdout)
+ }
+ ld := lipgloss.LightDark(isDark)
+ YellowAdaptive = ld(lipgloss.Color(DarkYellow), lipgloss.Color(LightYellow))
+ YellowAdaptive2 = ld(lipgloss.Color(DarkYellow), lipgloss.Color(PrimaryYellow))
+ Yellow = lipgloss.NewStyle().Foreground(YellowAdaptive)
+ ColoredBullet = lipgloss.NewStyle().
+ SetString("•").
+ Foreground(lipgloss.Color(PrimaryYellow))
+ DocsLink = lipgloss.NewStyle().
+ Foreground(YellowAdaptive2).
+ Render("\x1b]8;;https://docs.poke-cli.com\x1b\\docs.poke-cli.com\x1b]8;;\x1b\\")
+}
+
// GetTypeColor Helper function to get color for a given type name from colorMap
func GetTypeColor(typeName string) string {
typeColor := ColorMap[typeName]
@@ -157,49 +171,3 @@ func (col Color) Hex() string {
return fmt.Sprintf("#%02x%02x%02x",
uint8(col.R*255.0+0.5), uint8(col.G*255.0+0.5), uint8(col.B*255.0+0.5))
}
-
-func FormTheme() *huh.Theme {
- var (
- yellow = lipgloss.Color(LightYellow)
- blue = lipgloss.Color("#3B4CCA")
- red = lipgloss.Color("#D00000")
- black = lipgloss.Color("#000000")
- normalFg = lipgloss.AdaptiveColor{Light: "235", Dark: "252"}
- )
- t := huh.ThemeBase()
-
- t.Focused.Base = t.Focused.Base.BorderForeground(lipgloss.Color("238"))
- t.Focused.Card = t.Focused.Base
- t.Focused.Title = t.Focused.Title.Foreground(blue).Bold(true)
- t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(blue).Bold(true).MarginBottom(1)
- t.Focused.Directory = t.Focused.Directory.Foreground(blue)
- t.Focused.Description = t.Focused.Description.Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"})
- t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red)
- t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red)
- t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(red)
- t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(yellow)
- t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(yellow)
- t.Focused.Option = t.Focused.Option.Foreground(normalFg)
- t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(red)
- t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(red)
- t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(red).SetString("✓ ")
- t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}).SetString("• ")
- t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(normalFg)
- t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(black).Background(yellow)
- t.Focused.Next = t.Focused.FocusedButton
-
- t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(yellow)
- t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(lipgloss.AdaptiveColor{Light: "248", Dark: "238"})
- t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(red)
-
- t.Blurred = t.Focused
- t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder())
- t.Blurred.Card = t.Blurred.Base
- t.Blurred.NextIndicator = lipgloss.NewStyle()
- t.Blurred.PrevIndicator = lipgloss.NewStyle()
-
- t.Group.Title = t.Focused.Title
- t.Group.Description = t.Focused.Description
-
- return t
-}
diff --git a/testdata/ability_invalid_flag.golden b/testdata/ability_invalid_flag.golden
new file mode 100644
index 0000000..a7c394a
--- /dev/null
+++ b/testdata/ability_invalid_flag.golden
@@ -0,0 +1 @@
+error parsing flags: flag provided but not defined: -bogus
diff --git a/testdata/main_latest_flag.golden b/testdata/main_latest_flag.golden
index d29afa4..5d9163c 100644
--- a/testdata/main_latest_flag.golden
+++ b/testdata/main_latest_flag.golden
@@ -2,6 +2,6 @@
┃ ┃
┃ Latest available release ┃
┃ on GitHub: ┃
-┃ • v1.9.1 ┃
+┃ • v1.9.2 ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
diff --git a/testdata/pokemon_invalid_flag.golden b/testdata/pokemon_invalid_flag.golden
new file mode 100644
index 0000000..a7c394a
--- /dev/null
+++ b/testdata/pokemon_invalid_flag.golden
@@ -0,0 +1 @@
+error parsing flags: flag provided but not defined: -bogus
diff --git a/testdata/tcg_invalid_flag.golden b/testdata/tcg_invalid_flag.golden
new file mode 100644
index 0000000..a7c394a
--- /dev/null
+++ b/testdata/tcg_invalid_flag.golden
@@ -0,0 +1 @@
+error parsing flags: flag provided but not defined: -bogus
diff --git a/web/pyproject.toml b/web/pyproject.toml
index 7718174..8f1d0a3 100644
--- a/web/pyproject.toml
+++ b/web/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "web"
-version = "1.9.2"
+version = "1.9.3"
description = "Streamlit dashboard for browsing and visualizing Pokémon TCG tournament standings and results."
readme = "README.md"
requires-python = ">=3.12"