diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c86a98e..d24b4fe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,7 +28,7 @@ on:
- main
env:
- VERSION_NUMBER: 'v1.3.3'
+ VERSION_NUMBER: 'v1.4.0'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'
diff --git a/.goreleaser.yaml b/.goreleaser.yml
similarity index 88%
rename from .goreleaser.yaml
rename to .goreleaser.yml
index 557e2c2..cf6446a 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yml
@@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- - -s -w -X main.version=v1.3.3
+ - -s -w -X main.version=v1.4.0
archives:
- formats: [ 'zip' ]
@@ -28,7 +28,7 @@ archives:
# use zip for windows archives
format_overrides:
- goos: windows
- - formats: [ 'zip' ]
+ formats: [ 'zip' ]
changelog:
sort: asc
@@ -51,6 +51,6 @@ homebrew_casks:
hooks:
post:
install: |
- if system_command("/usr/bin/xattr", args: ["-h"]).exit_status == 0
+ if OS.mac? && system_command("/usr/bin/xattr", args: ["-h"]).exit_status == 0
system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/poke-cli"]
end
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index cc68419..58c0abc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# build 1
-FROM golang:1.24.4-alpine3.21 AS build
+FROM golang:1.24.4-alpine3.22 AS build
WORKDIR /app
@@ -8,13 +8,14 @@ RUN go mod download
COPY . .
-RUN go build -ldflags "-X main.version=v1.3.3" -o poke-cli .
+RUN go build -ldflags "-X main.version=v1.4.0" -o poke-cli .
# build 2
-FROM --platform=$BUILDPLATFORM alpine:latest
+FROM --platform=$BUILDPLATFORM alpine:3.22
# Install only necessary packages and remove them after use
-RUN apk add --no-cache shadow && \
+RUN apk upgrade && \
+ apk add --no-cache shadow && \
addgroup -S poke_group && adduser -S poke_user -G poke_group && \
sed -i 's/^root:.*/root:!*:0:0:root:\/root:\/sbin\/nologin/' /etc/passwd && \
apk del shadow
diff --git a/README.md b/README.md
index 7b1541d..7a8d474 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Pokémon CLI
-
+
@@ -77,22 +77,24 @@ View future plans in the [Roadmap](#roadmap) section.
3. Choose how to interact with the container:
* Run a single command and exit:
```bash
- docker run --rm -it digitalghostdev/poke-cli:v1.3.3 [subcommand] flag]
+ docker run --rm -it digitalghostdev/poke-cli:v1.4.0 [subcommand] flag]
```
* Enter the container and use its shell:
```bash
- docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.3.3 -c "cd /app && exec sh"
+ docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.4.0 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
### Homebrew
-```bash
-brew install --cask digitalghost-dev/tap/poke-cli
-
-# verify
-poke-cli -v
-```
+1. Install the Cask:
+ ```bash
+ brew install --cask digitalghost-dev/tap/poke-cli
+ ````
+2. Verify install!
+ ```bash
+ poke-cli -v
+ ```
### Source
@@ -121,6 +123,7 @@ By running `poke-cli [-h | --help]`, it'll display information on how to use the
│ │
│ COMMANDS: │
│ ability Get details about an ability │
+│ item Get details about an item │
│ move Get details about a move │
│ natures Get details about all natures │
│ pokemon Get details about a Pokémon │
@@ -130,6 +133,9 @@ By running `poke-cli [-h | --help]`, it'll display information on how to use the
│ hint: when calling a resource with a space, use a hyphen │
│ example: poke-cli ability strong-jaw │
│ example: poke-cli pokemon flutter-mane │
+│ │
+│ ↓ ctrl/cmd + click for docs/guides │
+│ docs.poke-cli.com │
╰──────────────────────────────────────────────────────────╯
```
@@ -138,20 +144,25 @@ By running `poke-cli [-h | --help]`, it'll display information on how to use the
## Roadmap
Below is a list of the planned/completed commands and flags:
-- [x] `ability`: get data about a specific ability.
+- [x] `ability`: get data about an ability.
- [x] `-p | --pokemon`: display Pokémon that learn this ability.
-- [ ] `berry`: get data about a specific berry.
-- [ ] `item`: get data about a specific item.
-- [x] `move`: get data about a specific move.
+- [ ] `berry`: get data about a berry.
+- [x] `item`: get data about an item.
+- [x] `move`: get data about a move.
- [ ] `-p | --pokemon`: display Pokémon that learn this move.
- [x] `natures`: get data about natures.
-- [x] `pokemon`: get data about a specific Pokémon.
+- [x] `pokemon`: get data about a Pokémon.
- [x] `-a | --abilities`: display the Pokémon's abilities.
- [x] `-i | --image`: display a pixel image of the Pokémon.
- [x] `-s | --stats`: display the Pokémon's base stats.
- [x] `-t | --types`: display the Pokémon's typing.
- [x] `-m | --moves`: display learnable moves.
-- [x] `search`: search for a resource (`ability`, `berry`, `pokemon`, `move`)
+- [ ] `search`: search for a resource
+ - [x] `ability`
+ - [ ] `berry`
+ - [ ] `item`
+ - [x] `move`
+ - [x] `pokemon`
- [ ] `speed`: compare speed stats between two Pokémon.
- [x] `types`: get data about a specific typing.
diff --git a/cli.go b/cli.go
index 9a489c0..fc7b1a9 100644
--- a/cli.go
+++ b/cli.go
@@ -4,6 +4,7 @@ import (
"flag"
"fmt"
"github.com/digitalghost-dev/poke-cli/cmd/ability"
+ "github.com/digitalghost-dev/poke-cli/cmd/item"
"github.com/digitalghost-dev/poke-cli/cmd/move"
"github.com/digitalghost-dev/poke-cli/cmd/natures"
"github.com/digitalghost-dev/poke-cli/cmd/pokemon"
@@ -66,6 +67,7 @@ func runCLI(args []string) int {
fmt.Sprintf("\n\t%-15s %s", "-v, --version", "Prints the current version"),
"\n\n", styling.StyleBold.Render("COMMANDS:"),
fmt.Sprintf("\n\t%-15s %s", "ability", "Get details about an ability"),
+ fmt.Sprintf("\n\t%-15s %s", "item", "Get details about an item"),
fmt.Sprintf("\n\t%-15s %s", "move", "Get details about a move"),
fmt.Sprintf("\n\t%-15s %s", "natures", "Get details about all natures"),
fmt.Sprintf("\n\t%-15s %s", "pokemon", "Get details about a Pokémon"),
@@ -99,6 +101,7 @@ func runCLI(args []string) int {
commands := map[string]func() int{
"ability": utils.HandleCommandOutput(ability.AbilityCommand),
+ "item": utils.HandleCommandOutput(item.ItemCommand),
"move": utils.HandleCommandOutput(move.MoveCommand),
"natures": utils.HandleCommandOutput(natures.NaturesCommand),
"pokemon": utils.HandleCommandOutput(pokemon.PokemonCommand),
@@ -133,6 +136,7 @@ func runCLI(args []string) int {
fmt.Sprintf("\n\t%-15s", fmt.Sprintf("'%s' is not a valid command.\n", cmdArg)),
styling.StyleBold.Render("\nCommands:"),
fmt.Sprintf("\n\t%-15s %s", "ability", "Get details about an ability"),
+ fmt.Sprintf("\n\t%-15s %s", "item", "Get details about an item"),
fmt.Sprintf("\n\t%-15s %s", "move", "Get details about a move"),
fmt.Sprintf("\n\t%-15s %s", "natures", "Get details about all natures"),
fmt.Sprintf("\n\t%-15s %s", "pokemon", "Get details about a Pokémon"),
diff --git a/cmd/item/item.go b/cmd/item/item.go
new file mode 100644
index 0000000..ca2cb0a
--- /dev/null
+++ b/cmd/item/item.go
@@ -0,0 +1,96 @@
+package item
+
+import (
+ "flag"
+ "fmt"
+ "github.com/charmbracelet/lipgloss"
+ "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"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+ "os"
+ "strings"
+)
+
+func ItemCommand() (string, error) {
+ var output strings.Builder
+
+ flag.Usage = func() {
+ helpMessage := styling.HelpBorder.Render(
+ "Get details about a specific item.\n\n",
+ styling.StyleBold.Render("USAGE:"),
+ fmt.Sprintf("\n\t%s %s %s %s", "poke-cli", styling.StyleBold.Render("item"), "", "[flag]"),
+ fmt.Sprintf("\n\t%-30s", styling.StyleItalic.Render("Use a hyphen when typing a name with a space.")),
+ "\n\n",
+ styling.StyleBold.Render("FLAGS:"),
+ fmt.Sprintf("\n\t%-30s %s", "-h, --help", "Prints the help menu."),
+ )
+ output.WriteString(helpMessage)
+ }
+
+ args := os.Args
+
+ flag.Parse()
+
+ if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
+ flag.Usage()
+ return output.String(), nil
+ }
+
+ if err := utils.ValidateItemArgs(os.Args); err != nil {
+ output.WriteString(err.Error())
+ return output.String(), err
+ }
+
+ endpoint := strings.ToLower(args[1])
+ itemName := strings.ToLower(args[2])
+
+ itemStruct, itemName, err := connections.ItemApiCall(endpoint, itemName, connections.APIURL)
+ if err != nil {
+ if os.Getenv("GO_TESTING") != "1" {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ return err.Error(), nil
+ }
+
+ itemInfoContainer(&output, itemStruct, itemName)
+
+ return output.String(), nil
+}
+
+func itemInfoContainer(output *strings.Builder, itemStruct structs.ItemJSONStruct, itemName string) {
+ capitalizedItem := styling.StyleBold.Render(cases.Title(language.English).String(strings.ReplaceAll(itemName, "-", " ")))
+ itemCost := fmt.Sprintf("Cost: %d", itemStruct.Cost)
+ itemCategory := "Category: " + cases.Title(language.English).String(strings.ReplaceAll(itemStruct.Category.Name, "-", " "))
+
+ docStyle := lipgloss.NewStyle().
+ Padding(1, 2).
+ BorderStyle(lipgloss.ThickBorder()).
+ BorderForeground(lipgloss.AdaptiveColor{Light: "#444", Dark: "#EEE"}).
+ Width(32)
+
+ var flavorTextEntry string
+ var missingData string
+ var fullDoc string
+
+ if len(itemStruct.FlavorTextEntries) == 0 {
+ missingData = styling.StyleItalic.Render("Missing data from API")
+ fullDoc = lipgloss.JoinVertical(lipgloss.Top, capitalizedItem, itemCost, itemCategory, "---", "Description:", missingData)
+ } else {
+ for _, entry := range itemStruct.FlavorTextEntries {
+ if entry.Language.Name == "en" && entry.VersionGroup.Name == "sword-shield" {
+ if entry.Text != "" {
+ flavorTextEntry = entry.Text
+ fullDoc = lipgloss.JoinVertical(lipgloss.Top, capitalizedItem, itemCost, itemCategory, "---", "Description:", flavorTextEntry)
+ break
+ }
+ }
+ }
+ }
+
+ output.WriteString(docStyle.Render(fullDoc))
+ output.WriteString("\n")
+}
diff --git a/cmd/item/item_test.go b/cmd/item/item_test.go
new file mode 100644
index 0000000..dccc619
--- /dev/null
+++ b/cmd/item/item_test.go
@@ -0,0 +1,70 @@
+package item
+
+import (
+ "github.com/digitalghost-dev/poke-cli/cmd/utils"
+ "github.com/digitalghost-dev/poke-cli/styling"
+ "github.com/stretchr/testify/assert"
+ "os"
+ "testing"
+)
+
+func TestItemCommand(t *testing.T) {
+ err := os.Setenv("GO_TESTING", "1")
+ if err != nil {
+ t.Fatalf("Failed to set GO_TESTING env var: %v", err)
+ }
+
+ defer func() {
+ err := os.Unsetenv("GO_TESTING")
+ if err != nil {
+ t.Logf("Warning: failed to unset GO_TESTING: %v", err)
+ }
+ }()
+
+ tests := []struct {
+ name string
+ args []string
+ expectedOutput string
+ expectedError bool
+ }{
+ {
+ name: "Item help flag",
+ args: []string{"item", "--help"},
+ expectedOutput: utils.LoadGolden(t, "item_help.golden"),
+ },
+ {
+ name: "Item help flag",
+ args: []string{"item", "-h"},
+ expectedOutput: utils.LoadGolden(t, "item_help.golden"),
+ },
+ {
+ name: "Select 'choice-band' as item",
+ args: []string{"item", "choice-band"},
+ expectedOutput: utils.LoadGolden(t, "item.golden"),
+ },
+ {
+ name: "Select 'clear-amulet' as item with missing data",
+ args: []string{"item", "clear-amulet"},
+ expectedOutput: utils.LoadGolden(t, "item_missing_data.golden"),
+ },
+ {
+ name: "Too many arguments",
+ args: []string{"item", "dubious-disc", "--help"},
+ expectedOutput: utils.LoadGolden(t, "item_too_many_args.golden"),
+ expectedError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ originalArgs := os.Args
+ os.Args = append([]string{"poke-cli"}, tt.args...)
+ defer func() { os.Args = originalArgs }()
+
+ output, _ := ItemCommand()
+ cleanOutput := styling.StripANSI(output)
+
+ assert.Equal(t, tt.expectedOutput, cleanOutput, "Output should match expected")
+ })
+ }
+}
diff --git a/cmd/move/move_test.go b/cmd/move/move_test.go
index 23459eb..837d798 100644
--- a/cmd/move/move_test.go
+++ b/cmd/move/move_test.go
@@ -38,7 +38,7 @@ func TestMoveCommand(t *testing.T) {
expectedOutput: utils.LoadGolden(t, "move_help.golden"),
},
{
- name: "Select 'Shadow-Ball' as move",
+ name: "Select 'shadow-ball' as move",
args: []string{"move", "shadow-ball"},
expectedOutput: utils.LoadGolden(t, "move.golden"),
},
diff --git a/cmd/natures/natures.go b/cmd/natures/natures.go
index 7ef7677..bdd16e5 100644
--- a/cmd/natures/natures.go
+++ b/cmd/natures/natures.go
@@ -14,7 +14,6 @@ import (
func NaturesCommand() (string, error) {
var output strings.Builder
- // Define the usage function
flag.Usage = func() {
helpMessage := styling.HelpBorder.Render(
"Get details about all natures.\n\n",
diff --git a/cmd/pokemon/pokemon.go b/cmd/pokemon/pokemon.go
index b6bd1bd..b9eade9 100644
--- a/cmd/pokemon/pokemon.go
+++ b/cmd/pokemon/pokemon.go
@@ -104,8 +104,8 @@ func PokemonCommand() (string, error) {
// Call the ImageFlag function with the specified size
if err := flags.ImageFlag(&output, endpoint, pokemonName, size); err != nil {
- output.WriteString(fmt.Sprintf("error parsing flags: %v\n", err))
- return "", fmt.Errorf("error parsing flags: %w", err)
+ output.WriteString(fmt.Sprintf("%v\n", err))
+ return output.String(), fmt.Errorf("%w", err)
}
}
diff --git a/cmd/pokemon/pokemon_test.go b/cmd/pokemon/pokemon_test.go
index 5222430..5a3ceae 100644
--- a/cmd/pokemon/pokemon_test.go
+++ b/cmd/pokemon/pokemon_test.go
@@ -25,7 +25,7 @@ func TestPokemonCommand(t *testing.T) {
name string
args []string
expectedOutput string
- wantError bool
+ expectedError bool
}{
{
name: "Pokemon help flag",
@@ -43,10 +43,22 @@ func TestPokemonCommand(t *testing.T) {
expectedOutput: utils.LoadGolden(t, "pokemon_image.golden"),
},
{
- name: "Pokemon invalid image flag",
+ name: "Pokemon image flag missing size",
args: []string{"pokemon", "tryanitar", "--image="},
- expectedOutput: utils.LoadGolden(t, "pokemon_invalid_image_flag.golden"),
- wantError: true,
+ expectedOutput: utils.LoadGolden(t, "pokemon_image_flag_missing_size.golden"),
+ expectedError: true,
+ },
+ {
+ name: "Pokemon image flag non-valid size",
+ args: []string{"pokemon", "floatzel", "--image=xl"},
+ expectedOutput: utils.LoadGolden(t, "pokemon_image_flag_non-valid_size.golden"),
+ expectedError: true,
+ },
+ {
+ name: "Pokemon image flag empty flag",
+ args: []string{"pokemon", "gastly", "--"},
+ expectedOutput: utils.LoadGolden(t, "pokemon_image_flag_empty_flag.golden"),
+ expectedError: true,
},
{
name: "Pokemon stats flag",
diff --git a/cmd/search/search.go b/cmd/search/search.go
index 6849904..1f5efbc 100644
--- a/cmd/search/search.go
+++ b/cmd/search/search.go
@@ -10,9 +10,7 @@ import (
"os"
)
-// SearchCommand is the main function for the "search" command.
func SearchCommand() {
- // Define your custom usage function first
flag.Usage = func() {
helpMessage := styling.HelpBorder.Render(
"Search for a resource by name or partial match.\n\n",
@@ -33,7 +31,6 @@ func SearchCommand() {
return
}
- // Validate arguments before launching TUI
if err := utils.ValidateSearchArgs(os.Args); err != nil {
fmt.Println(err.Error())
os.Exit(1)
diff --git a/cmd/types/types.go b/cmd/types/types.go
index 2d7caef..99cf401 100644
--- a/cmd/types/types.go
+++ b/cmd/types/types.go
@@ -29,11 +29,13 @@ func TypesCommand() (string, error) {
flag.Parse()
+ // Handle help flag
if len(os.Args) == 3 && (os.Args[2] == "-h" || os.Args[2] == "--help") {
flag.Usage()
return output.String(), nil
}
+ // Validate arguments
if err := utils.ValidateTypesArgs(os.Args); err != nil {
output.WriteString(err.Error())
return output.String(), err
@@ -46,43 +48,59 @@ func TypesCommand() (string, error) {
}
type model struct {
+ quitting bool
table table.Model
selectedOption string
}
-func (m model) Init() tea.Cmd { return nil }
+// Init initializes the model
+func (m model) Init() tea.Cmd {
+ return nil
+}
+// Update handles user input and updates the model state
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var bubbleCmd tea.Cmd
+
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "esc", "ctrl+c":
+ m.quitting = true
return m, tea.Quit
case "enter":
+ // User selected a type
m.selectedOption = m.table.SelectedRow()[0]
return m, tea.Quit
}
}
+ // Handle other updates (like navigation)
m.table, bubbleCmd = m.table.Update(msg)
return m, bubbleCmd
}
+// View renders the current UI
func (m model) View() string {
+ if m.quitting {
+ return "\n Goodbye! \n"
+ }
+
+ // Don't render anything if a selection has been made
if m.selectedOption != "" {
return ""
}
+ // Render the type selection table with instructions
return 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)"))
}
// Function that generates and handles the type selection table
-func tableGeneration(endpoint string) table.Model {
+func tableGeneration(endpoint string) {
types := []string{"Normal", "Fire", "Water", "Electric", "Grass", "Ice",
- "Fighting", "Poison", "Ground", "Flying", "Psychic", "Bug",
+ "Fighting", "Poison", "Ground", "Flying", "Psychic", "Bug", "Dark",
"Rock", "Ghost", "Dragon", "Steel", "Fairy"}
rows := make([]table.Row, len(types))
@@ -90,6 +108,7 @@ func tableGeneration(endpoint string) table.Model {
rows[i] = []string{t}
}
+ // Initialize table with configuration
t := table.New(
table.WithColumns([]table.Column{{Title: "Type", Width: 16}}),
table.WithRows(rows),
@@ -97,6 +116,7 @@ func tableGeneration(endpoint string) table.Model {
table.WithHeight(7),
)
+ // Set table styles
s := table.DefaultStyles()
s.Header = s.Header.
BorderStyle(lipgloss.NormalBorder()).
@@ -108,12 +128,15 @@ func tableGeneration(endpoint string) table.Model {
t.SetStyles(s)
m := model{table: t}
- if programModel, err := tea.NewProgram(m).Run(); err != nil {
+ programModel, err := tea.NewProgram(m).Run()
+
+ if err != nil {
fmt.Println("Error running program:", err)
os.Exit(1)
- } else if finalModel, ok := programModel.(model); ok && finalModel.selectedOption != "quit" {
- DamageTable(strings.ToLower(finalModel.selectedOption), endpoint)
}
- return t
+ // Only show damage table if a type was actually selected (not when quitting)
+ if finalModel, ok := programModel.(model); ok && finalModel.selectedOption != "" {
+ DamageTable(strings.ToLower(finalModel.selectedOption), endpoint)
+ }
}
diff --git a/cmd/types/types_test.go b/cmd/types/types_test.go
index 928404b..ccef786 100644
--- a/cmd/types/types_test.go
+++ b/cmd/types/types_test.go
@@ -1,11 +1,16 @@
package types
import (
+ "github.com/charmbracelet/bubbles/table"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/charmbracelet/x/exp/teatest"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/styling"
"github.com/stretchr/testify/assert"
"os"
"testing"
+ "time"
)
func TestTypesCommand(t *testing.T) {
@@ -45,3 +50,110 @@ func TestModelInit(t *testing.T) {
cmd := m.Init()
assert.Nil(t, cmd, "Init() should return nil")
}
+
+// createTestModel creates a model with a table for testing
+func createTestModel() model {
+ // Create a simple table with a few types
+ types := []string{"Normal", "Fire", "Water"}
+ rows := make([]table.Row, len(types))
+ for i, t := range types {
+ rows[i] = []string{t}
+ }
+
+ t := table.New(
+ table.WithColumns([]table.Column{{Title: "Type", Width: 16}}),
+ table.WithRows(rows),
+ table.WithFocused(true),
+ table.WithHeight(7),
+ )
+
+ // Set table styles
+ s := table.DefaultStyles()
+ s.Header = s.Header.
+ BorderStyle(lipgloss.NormalBorder()).
+ BorderForeground(lipgloss.Color("#FFCC00")).
+ BorderBottom(true)
+ s.Selected = s.Selected.
+ Foreground(lipgloss.Color("#000")).
+ Background(lipgloss.Color("#FFCC00"))
+ t.SetStyles(s)
+
+ return model{table: t}
+}
+
+func TestUpdate(t *testing.T) {
+ t.Run("Escape key should set quitting to true", func(t *testing.T) {
+ m := createTestModel()
+ testModel := teatest.NewTestModel(t, m)
+
+ // Send escape key
+ testModel.Send(tea.KeyMsg{Type: tea.KeyEsc})
+ testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
+
+ final := testModel.FinalModel(t).(model)
+ assert.True(t, final.quitting, "quitting should be true after pressing escape")
+ })
+
+ t.Run("Ctrl+C key should set quitting to true", func(t *testing.T) {
+ m := createTestModel()
+ testModel := teatest.NewTestModel(t, m)
+
+ // Send ctrl+c key
+ testModel.Send(tea.KeyMsg{Type: tea.KeyCtrlC})
+ testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
+
+ final := testModel.FinalModel(t).(model)
+ assert.True(t, final.quitting, "quitting should be true after pressing ctrl+c")
+ })
+
+ t.Run("Enter key should set selectedOption", func(t *testing.T) {
+ m := createTestModel()
+ testModel := teatest.NewTestModel(t, m)
+
+ // Send enter key
+ testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
+ testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
+
+ final := testModel.FinalModel(t).(model)
+ assert.Equal(t, "Normal", final.selectedOption, "selectedOption should be set to the selected row")
+ })
+
+ t.Run("Arrow keys should update table selection", func(t *testing.T) {
+ m := createTestModel()
+ testModel := teatest.NewTestModel(t, m)
+
+ // Send down arrow key to select the second row
+ testModel.Send(tea.KeyMsg{Type: tea.KeyDown})
+
+ // Then send enter to select it
+ testModel.Send(tea.KeyMsg{Type: tea.KeyEnter})
+ testModel.WaitFinished(t, teatest.WithFinalTimeout(300*time.Millisecond))
+
+ final := testModel.FinalModel(t).(model)
+ assert.Equal(t, "Fire", final.selectedOption, "selectedOption should be updated after arrow navigation")
+ })
+}
+
+func TestView(t *testing.T) {
+ t.Run("View should return goodbye message when quitting", func(t *testing.T) {
+ m := createTestModel()
+ m.quitting = true
+
+ view := m.View()
+ assert.Equal(t, "\n Goodbye! \n", view, "View should return goodbye message when quitting")
+ })
+
+ t.Run("View should return empty string when selectedOption is set", func(t *testing.T) {
+ m := createTestModel()
+ m.selectedOption = "Fire"
+ })
+
+ t.Run("View should render table in normal state", func(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")
+ })
+}
diff --git a/cmd/utils/validateargs.go b/cmd/utils/validateargs.go
index 3df37ae..dce7cfe 100644
--- a/cmd/utils/validateargs.go
+++ b/cmd/utils/validateargs.go
@@ -6,7 +6,7 @@ import (
"strings"
)
-// checkLength checks if the number of arguments is lower than the max value
+// 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(
@@ -17,6 +17,16 @@ func checkLength(args []string, max int) error {
return nil
}
+// 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 nil
+}
+
// ValidateAbilityArgs validates the command line arguments
func ValidateAbilityArgs(args []string) error {
if err := checkLength(args, 4); err != nil {
@@ -31,6 +41,20 @@ func ValidateAbilityArgs(args []string) error {
return nil
}
+// ValidateItemArgs validates the command line arguments
+func ValidateItemArgs(args []string) error {
+ if err := checkLength(args, 3); err != nil {
+ return err
+ }
+
+ if len(args) == 2 {
+ errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nPlease specify an item ")
+ return fmt.Errorf("%s", errMessage)
+ }
+
+ return nil
+}
+
// ValidateMoveArgs validates the command line arguments
func ValidateMoveArgs(args []string) error {
if err := checkLength(args, 3); err != nil {
@@ -38,7 +62,7 @@ func ValidateMoveArgs(args []string) error {
}
if len(args) == 2 {
- errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nPlease specify a move")
+ errMessage := styling.ErrorBorder.Render(styling.ErrorColor.Render("Error!"), "\nPlease specify a move ")
return fmt.Errorf("%s", errMessage)
}
@@ -51,12 +75,8 @@ func ValidateNaturesArgs(args []string) error {
return err
}
- // Check if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
- // If true, return an error message since only '-h' and '--help' are allowed after 'types'
- if len(args) == 3 && args[2] != "-h" && args[2] != "--help" {
- errMsg := styling.ErrorColor.Render("Error!") +
- "\nThe only currently available options\nafter command are '-h' or '--help'"
- return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
+ if err := checkNoOtherOptions(args, 3, ""); err != nil {
+ return err
}
return nil
@@ -101,11 +121,11 @@ func ValidatePokemonArgs(args []string) error {
// Validate each argument after the Pokémon's name
if len(args) > 3 {
for _, arg := range args[3:] {
- // Check for single `-` or `--` which are invalid
+ // Check for an empty flag after Pokémon's name
if arg == "-" || arg == "--" {
errorTitle := styling.ErrorColor.Render("Error!")
errorString := fmt.Sprintf(
- "\nInvalid argument '%s'. Single '-' or '--' is not allowed.\nPlease use valid flags.",
+ "\nEmpty flag '%s'.\nPlease specify valid flag(s).",
arg,
)
finalErrorMessage := errorTitle + errorString
@@ -113,7 +133,7 @@ func ValidatePokemonArgs(args []string) error {
return fmt.Errorf("%s", renderedError)
}
- // Check if the argument starts with a flag prefix but is invalid
+ // Check if the argument after Pokémon's name is an attempted flag
if arg[0] != '-' {
errorTitle := styling.ErrorColor.Render("Error!")
errorString := fmt.Sprintf(
@@ -136,12 +156,8 @@ func ValidateSearchArgs(args []string) error {
return err
}
- // Check if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
- // If true, return an error message since only '-h' and '--help' are allowed after
- if len(args) == 3 && args[2] != "-h" && args[2] != "--help" {
- errMsg := styling.ErrorColor.Render("Error!") +
- "\nThe only currently available options\nafter command are '-h' or '--help'"
- return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
+ if err := checkNoOtherOptions(args, 3, ""); err != nil {
+ return err
}
return nil
@@ -153,12 +169,8 @@ func ValidateTypesArgs(args []string) error {
return err
}
- // Check if there are exactly 3 arguments and the third argument is neither '-h' nor '--help'
- // If true, return an error message since only '-h' and '--help' are allowed after
- if len(args) == 3 && args[2] != "-h" && args[2] != "--help" {
- errMsg := styling.ErrorColor.Render("Error!") +
- "\nThe only currently available options\nafter command are '-h' or '--help'"
- return fmt.Errorf("%s", styling.ErrorBorder.Render(errMsg))
+ if err := checkNoOtherOptions(args, 3, ""); err != nil {
+ return err
}
return nil
diff --git a/connections/connection.go b/connections/connection.go
index 9bf726c..f4bca9e 100644
--- a/connections/connection.go
+++ b/connections/connection.go
@@ -66,6 +66,24 @@ func AbilityApiCall(endpoint string, abilityName string, baseURL string) (struct
return abilityStruct, abilityStruct.Name, nil
}
+// ItemApiCall function for calling the item endpoint of the pokeAPI
+func ItemApiCall(endpoint string, itemName string, baseURL string) (structs.ItemJSONStruct, string, error) {
+ fullURL := baseURL + endpoint + "/" + itemName
+
+ var itemStruct structs.ItemJSONStruct
+ err := ApiCallSetup(fullURL, &itemStruct, false)
+
+ if err != nil {
+ errMessage := styling.ErrorBorder.Render(
+ styling.ErrorColor.Render("Error!"),
+ "\nItem not found.\n\u2022 Perhaps a typo?\n\u2022 Missing a hyphen instead of a space?",
+ )
+ return structs.ItemJSONStruct{}, "", fmt.Errorf("%s", errMessage)
+ }
+
+ return itemStruct, itemStruct.Name, nil
+}
+
// MoveApiCall function for calling the move endpoint of the pokeAPI
func MoveApiCall(endpoint string, moveName string, baseURL string) (structs.MoveJSONStruct, string, error) {
fullURL := baseURL + endpoint + "/" + moveName
diff --git a/connections/connection_test.go b/connections/connection_test.go
index fcb19d4..dc4b186 100644
--- a/connections/connection_test.go
+++ b/connections/connection_test.go
@@ -95,6 +95,43 @@ func TestAbilityApiCall(t *testing.T) {
})
}
+func TestItemApiCall(t *testing.T) {
+ t.Run("Successful API call returns expected item", func(t *testing.T) {
+ expectedItem := structs.ItemJSONStruct{
+ Name: "choice-band",
+ }
+
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ err := json.NewEncoder(w).Encode(expectedItem)
+ assert.NoError(t, err, "Expected no error for encoding response")
+ }))
+ defer ts.Close()
+
+ item, name, err := ItemApiCall("/item", "choice-band", ts.URL)
+
+ require.NoError(t, err, "Expected no error on successful API call")
+ assert.Equal(t, expectedItem, item, "Expected item struct does not match")
+ assert.Equal(t, "choice-band", name, "Expected item name does not match")
+ })
+
+ t.Run("Failed API call returns styled error", func(t *testing.T) {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ // Simulate API failure (e.g., 404 Not Found)
+ http.Error(w, "Not Found", http.StatusNotFound)
+ }))
+ defer ts.Close()
+
+ item, _, err := ItemApiCall("/item", "non-existent-item", ts.URL)
+
+ require.Error(t, err, "Expected an error for invalid item")
+ assert.Equal(t, structs.ItemJSONStruct{}, item, "Expected empty item struct on error")
+
+ assert.Contains(t, err.Error(), "Item not found", "Expected 'Item not found' in error message")
+ assert.Contains(t, err.Error(), "Perhaps a typo?", "Expected helpful suggestion in error message")
+ })
+}
+
func TestMoveApiCall(t *testing.T) {
t.Run("Successful API call returns expected move", func(t *testing.T) {
expectedMove := structs.MoveJSONStruct{
diff --git a/docs/assets/item.gif b/docs/assets/item.gif
new file mode 100644
index 0000000..d5b6146
Binary files /dev/null and b/docs/assets/item.gif differ
diff --git a/docs/commands.md b/docs/commands.md
index 04008cc..2610a02 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -31,6 +31,20 @@ Output:
---
+## `item`
+* Retrieve information about a specific item, including its cost, category and description.
+
+Example:
+```console
+$ poke-cli item poke-ball
+```
+
+Output:
+
+
+
+---
+
## `move`
* Retrieve information about a specific move, including its type, power, PP, accuracy, category, etc.,
and the move's effect.
diff --git a/flags/pokemonflagset_test.go b/flags/pokemonflagset_test.go
index 91e34ed..cbc3d85 100644
--- a/flags/pokemonflagset_test.go
+++ b/flags/pokemonflagset_test.go
@@ -80,6 +80,55 @@ Hidden Ability: Chlorophyll
assert.Equal(t, expectedOutput, actualOutput, "Output should contain data for the abilities flag")
}
+func TestMovesFlag(t *testing.T) {
+ // Capture standard output
+ var output bytes.Buffer
+ stdout := os.Stdout
+ r, w, _ := os.Pipe()
+ os.Stdout = w
+
+ // Call the MovesFlag function with a valid Pokémon
+ err := MovesFlag(&output, "pokemon", "bulbasaur")
+
+ // Close and restore stdout
+ if closeErr := w.Close(); closeErr != nil {
+ t.Fatalf("Failed to close pipe writer: %v", closeErr)
+ }
+ os.Stdout = stdout
+
+ _, readErr := output.ReadFrom(r)
+ if readErr != nil {
+ t.Fatalf("Failed to read from pipe: %v", readErr)
+ }
+
+ // Assert no errors occurred
+ require.NoError(t, err, "MovesFlag should not return an error for a valid Pokémon")
+
+ // Get the actual output and strip ANSI codes
+ actualOutput := styling.StripANSI(output.String())
+
+ // Check for expected header
+ assert.Contains(t, actualOutput, "Learnable Moves", "Output should contain the header 'Learnable Moves'")
+
+ // Check for table headers
+ assert.Contains(t, actualOutput, "Name", "Output should contain the 'Name' column header")
+ assert.Contains(t, actualOutput, "Level", "Output should contain the 'Level' column header")
+ assert.Contains(t, actualOutput, "Type", "Output should contain the 'Type' column header")
+ assert.Contains(t, actualOutput, "Accuracy", "Output should contain the 'Accuracy' column header")
+ assert.Contains(t, actualOutput, "Power", "Output should contain the 'Power' column header")
+
+ // Since the actual moves might change with API updates, we'll just check that the output is not empty
+ assert.NotEmpty(t, actualOutput, "Output should not be empty")
+
+ // Check that the output contains some common moves for Bulbasaur
+ // Note: These are common moves, but if they change in the API, this test might need updating
+ assert.True(t,
+ strings.Contains(actualOutput, "Tackle") ||
+ strings.Contains(actualOutput, "Vine Whip") ||
+ strings.Contains(actualOutput, "Growl"),
+ "Output should contain at least one of Bulbasaur's common moves")
+}
+
func TestImageFlag(t *testing.T) {
// Capture standard output
var output bytes.Buffer
diff --git a/go.mod b/go.mod
index 18cf049..124b266 100644
--- a/go.mod
+++ b/go.mod
@@ -4,23 +4,23 @@ go 1.24.4
require (
github.com/charmbracelet/bubbles v0.20.0
- github.com/charmbracelet/bubbletea v1.2.4
+ github.com/charmbracelet/bubbletea v1.3.5
github.com/charmbracelet/lipgloss v1.1.0
- github.com/charmbracelet/x/exp/teatest v0.0.0-20250317102001-c803e5cafd0b
+ github.com/charmbracelet/x/exp/teatest v0.0.0-20250702191427-5bdfc8f2e4ff
github.com/charmbracelet/x/term v0.2.1
github.com/disintegration/imaging v1.6.2
github.com/stretchr/testify v1.10.0
- golang.org/x/text v0.21.0
+ golang.org/x/text v0.26.0
)
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.2.0 // indirect
- github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
- github.com/charmbracelet/x/ansi v0.8.0 // indirect
- github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
- github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f // indirect
+ github.com/aymanbagabas/go-udiff v0.3.1 // indirect
+ github.com/charmbracelet/colorprofile v0.3.1 // indirect
+ github.com/charmbracelet/x/ansi v0.9.3 // indirect
+ github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
+ github.com/charmbracelet/x/exp/golden v0.0.0-20250702191427-5bdfc8f2e4ff // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
@@ -34,8 +34,12 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
- golang.org/x/image v0.23.0 // indirect
- golang.org/x/sync v0.10.0 // indirect
- golang.org/x/sys v0.30.0 // indirect
+ golang.org/x/image v0.28.0 // indirect
+ golang.org/x/sync v0.15.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
+
+// v1.3.4 was pushed as test and not a real version.
+// The release was then deleted on GitHub. v1.4.0 follows.
+retract v1.3.4
diff --git a/go.sum b/go.sum
index c0fbb3b..e6a6aa2 100644
--- a/go.sum
+++ b/go.sum
@@ -2,24 +2,24 @@ github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z
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.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8=
-github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
+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/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
-github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
-github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
-github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
-github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
+github.com/charmbracelet/bubbletea v1.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
+github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
+github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
+github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
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.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
-github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
-github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
-github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
-github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f h1:UytXHv0UxnsDFmL/7Z9Q5SBYPwSuRLXHbwx+6LycZ2w=
-github.com/charmbracelet/x/exp/golden v0.0.0-20241212170349-ad4b7ae0f25f/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
-github.com/charmbracelet/x/exp/teatest v0.0.0-20250317102001-c803e5cafd0b h1:stRYpFhKYr6u2DrdyXHabpXQdRbNs8REQ4RB/Kc22t0=
-github.com/charmbracelet/x/exp/teatest v0.0.0-20250317102001-c803e5cafd0b/go.mod h1:ag+SpTUkiN/UuUGYPX3Ci4fR1oF3XX97PpGhiXK7i6U=
+github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
+github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
+github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
+github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
+github.com/charmbracelet/x/exp/golden v0.0.0-20250702191427-5bdfc8f2e4ff h1:mEllIwjDs9aKqnzckYmNZqxoULwp4afFLVgH9x9QAGA=
+github.com/charmbracelet/x/exp/golden v0.0.0-20250702191427-5bdfc8f2e4ff/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
+github.com/charmbracelet/x/exp/teatest v0.0.0-20250702191427-5bdfc8f2e4ff h1:DKxPeQDSnQPDxD27Bq8nUDwiSecy2Yf+nT3U/TlPXs8=
+github.com/charmbracelet/x/exp/teatest v0.0.0-20250702191427-5bdfc8f2e4ff/go.mod h1:RXbDhep1qKL/SEz2IuOhOUrsNHDKGqRmGks1nZStKyU=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -54,17 +54,17 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJu
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
-golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
-golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
-golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
+golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
+golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
-golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
-golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
+golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/structs/structs.go b/structs/structs.go
index 5a650bd..6cb083c 100644
--- a/structs/structs.go
+++ b/structs/structs.go
@@ -35,6 +35,31 @@ type AbilityJSONStruct struct {
} `json:"pokemon"`
}
+// ItemJSONStruct item endpoint from API
+type ItemJSONStruct struct {
+ Name string `json:"name"`
+ ID int `json:"id"`
+ Cost int `json:"cost"`
+ Category struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"category"`
+ FlavorTextEntries []struct {
+ Language struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"language"`
+ Text string `json:"text"`
+ VersionGroup struct {
+ Name string `json:"name"`
+ URL string `json:"url"`
+ } `json:"version_group"`
+ } `json:"flavor_text_entries"`
+ Sprites struct {
+ Default string `json:"default"`
+ } `json:"sprites"`
+}
+
// MoveJSONStruct move endpoint from API
type MoveJSONStruct struct {
Name string `json:"name"`
diff --git a/testdata/cli_help.golden b/testdata/cli_help.golden
index 43bc33d..73ec9eb 100644
--- a/testdata/cli_help.golden
+++ b/testdata/cli_help.golden
@@ -13,6 +13,7 @@
│ │
│ COMMANDS: │
│ ability Get details about an ability │
+│ item Get details about an item │
│ move Get details about a move │
│ natures Get details about all natures │
│ pokemon Get details about a Pokémon │
diff --git a/testdata/item.golden b/testdata/item.golden
new file mode 100644
index 0000000..f9438c5
--- /dev/null
+++ b/testdata/item.golden
@@ -0,0 +1,14 @@
+┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ ┃
+┃ Choice Band ┃
+┃ Cost: 4000 ┃
+┃ Category: Choice ┃
+┃ --- ┃
+┃ Description: ┃
+┃ An item to be held by a ┃
+┃ Pokémon. This curious ┃
+┃ headband boosts Attack but ┃
+┃ only allows the use of ┃
+┃ one move. ┃
+┃ ┃
+┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
diff --git a/testdata/item_help.golden b/testdata/item_help.golden
new file mode 100644
index 0000000..c197516
--- /dev/null
+++ b/testdata/item_help.golden
@@ -0,0 +1,10 @@
+╭────────────────────────────────────────────────────────╮
+│Get details about a specific item. │
+│ │
+│ USAGE: │
+│ poke-cli item [flag] │
+│ Use a hyphen when typing a name with a space. │
+│ │
+│ FLAGS: │
+│ -h, --help Prints the help menu.│
+╰────────────────────────────────────────────────────────╯
\ No newline at end of file
diff --git a/testdata/item_missing_data.golden b/testdata/item_missing_data.golden
new file mode 100644
index 0000000..58aca70
--- /dev/null
+++ b/testdata/item_missing_data.golden
@@ -0,0 +1,10 @@
+┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
+┃ ┃
+┃ Clear Amulet ┃
+┃ Cost: 30000 ┃
+┃ Category: Held Items ┃
+┃ --- ┃
+┃ Description: ┃
+┃ Missing data from API ┃
+┃ ┃
+┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
diff --git a/testdata/item_too_many_args.golden b/testdata/item_too_many_args.golden
new file mode 100644
index 0000000..093717c
--- /dev/null
+++ b/testdata/item_too_many_args.golden
@@ -0,0 +1,4 @@
+╭──────────────────╮
+│Error! │
+│Too many arguments│
+╰──────────────────╯
\ No newline at end of file
diff --git a/testdata/natures_invalid_extra_arg.golden b/testdata/natures_invalid_extra_arg.golden
index de50ee1..93450ba 100644
--- a/testdata/natures_invalid_extra_arg.golden
+++ b/testdata/natures_invalid_extra_arg.golden
@@ -1,5 +1,5 @@
-╭────────────────────────────────────────────╮
-│Error! │
-│The only currently available options │
-│after command are '-h' or '--help'│
-╰────────────────────────────────────────────╯
\ No newline at end of file
+╭──────────────────────────────────────╮
+│Error! │
+│The only available options after the │
+│ command are '-h' or '--help'│
+╰──────────────────────────────────────╯
\ No newline at end of file
diff --git a/testdata/pokemon_image_flag_empty_flag.golden b/testdata/pokemon_image_flag_empty_flag.golden
new file mode 100644
index 0000000..ec831c7
--- /dev/null
+++ b/testdata/pokemon_image_flag_empty_flag.golden
@@ -0,0 +1,5 @@
+╭─────────────────────────────╮
+│Error! │
+│Empty flag '--'. │
+│Please specify valid flag(s).│
+╰─────────────────────────────╯
\ No newline at end of file
diff --git a/testdata/pokemon_invalid_image_flag.golden b/testdata/pokemon_image_flag_missing_size.golden
similarity index 100%
rename from testdata/pokemon_invalid_image_flag.golden
rename to testdata/pokemon_image_flag_missing_size.golden
diff --git a/testdata/pokemon_image_flag_non-valid_size.golden b/testdata/pokemon_image_flag_non-valid_size.golden
new file mode 100644
index 0000000..d74aaea
--- /dev/null
+++ b/testdata/pokemon_image_flag_non-valid_size.golden
@@ -0,0 +1,11 @@
+Your selected Pokémon: Floatzel
+• National Pokédex #: 419
+• Weight: 33.5kg (73.9 lbs)
+• Height: 3.6m (3′07″)
+─────
+Image
+╭───────────────────────────╮
+│Error! │
+│Invalid image size. │
+│Valid sizes are: lg, md, sm│
+╰───────────────────────────╯