diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d24b4fe..6d264e7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,7 +17,7 @@ on:
- '.github/**'
- '.dockerignore'
- 'docs/**'
- - 'etl/**'
+ - '../../card_data/**'
- '.gitignore'
- 'demo**'
- 'go.mod'
@@ -28,7 +28,7 @@ on:
- main
env:
- VERSION_NUMBER: 'v1.4.0'
+ VERSION_NUMBER: 'v1.5.0'
DOCKERHUB_REGISTRY_NAME: 'digitalghostdev/poke-cli'
AWS_REGION: 'us-west-2'
diff --git a/.gitignore b/.gitignore
index 33bfbea..43b0a1d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,4 +28,12 @@ go.work.sum
.env
# Python
-etl/.venv
\ No newline at end of file
+card_data/.venv
+__pycache__/
+
+# Terraform
+card_data/terraform/access-token
+/card_data/terraform/access-token
+card_data/terraform/secrets.tfvars
+card_data/terraform/terraform.tfstate
+/card_data/terraform/.terraform/
diff --git a/.goreleaser.yml b/.goreleaser.yml
index cf6446a..de0dbd5 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -14,7 +14,7 @@ builds:
- windows
- darwin
ldflags:
- - -s -w -X main.version=v1.4.0
+ - -s -w -X main.version=v1.5.0
archives:
- formats: [ 'zip' ]
@@ -45,7 +45,7 @@ homebrew_casks:
owner: digitalghost-dev
name: homebrew-tap
token: "{{.Env.GITHUB_TOKEN}}"
- homepage: "https://github.com/digitalghost-dev/poke-cli"
+ homepage: "https://docs.poke-cli.com/"
description: "A hybrid CLI/TUI tool written in Go for viewing Pokémon data from the terminal!"
license: "Apache License 2.0"
hooks:
@@ -53,4 +53,23 @@ homebrew_casks:
install: |
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
+ end
+
+winget:
+ - name: poke-cli
+ publisher: digitalghost-dev
+ license: MIT
+ homepage: "https://docs.poke-cli.com/"
+ short_description: "Pokémon CLI/TUI terminal tool."
+ repository:
+ owner: digitalghost-dev
+ name: winget-pkgs
+ token: "{{.Env.GITHUB_TOKEN}}"
+ branch: "poke-cli-{{.Version}}"
+ pull_request:
+ enabled: true
+ base:
+ owner: microsoft
+ name: winget-pkgs
+ branch: master
+ description: "A hybrid CLI/TUI tool written in Go for viewing Pokémon data from the terminal!"
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 58c0abc..b93908e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,12 +8,12 @@ RUN go mod download
COPY . .
-RUN go build -ldflags "-X main.version=v1.4.0" -o poke-cli .
+RUN go build -ldflags "-X main.version=v1.5.0" -o poke-cli .
# build 2
FROM --platform=$BUILDPLATFORM alpine:3.22
-# Install only necessary packages and remove them after use
+# Installing only necessary packages and remove them after use
RUN apk upgrade && \
apk add --no-cache shadow && \
addgroup -S poke_group && adduser -S poke_user -G poke_group && \
@@ -25,10 +25,8 @@ COPY --from=build /app/poke-cli /app/poke-cli
ENV TERM=xterm-256color
ENV COLOR_OUTPUT=true
-# Set correct permissions
RUN chown -R poke_user:poke_group /app
-# Switch to non-root user
USER poke_user
ENTRYPOINT ["/app/poke-cli"]
\ No newline at end of file
diff --git a/README.md b/README.md
index 7a8d474..4322495 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
Pokémon CLI
-
+
@@ -24,7 +24,7 @@ View future plans in the [Roadmap](#roadmap) section.
---
## Demo
-
+
---
## Installation
@@ -32,6 +32,7 @@ View future plans in the [Roadmap](#roadmap) section.
* [Binary](#binary)
* [Docker Image](#docker-image)
* [Homebrew](#homebrew)
+* [Winget](#winget)
* [Source](#source)
### Binary
@@ -77,11 +78,11 @@ 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.4.0 [subcommand] flag]
+ docker run --rm -it digitalghostdev/poke-cli:v1.5.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.4.0 -c "cd /app && exec sh"
+ docker run --rm -it --name poke-cli --entrypoint /bin/sh digitalghostdev/poke-cli:v1.5.0 -c "cd /app && exec sh"
# placed into the /app directory, run the program with './poke-cli'
# example: ./poke-cli ability swift-swim
```
@@ -91,7 +92,18 @@ View future plans in the [Roadmap](#roadmap) section.
```bash
brew install --cask digitalghost-dev/tap/poke-cli
````
-2. Verify install!
+2. Verify install:
+ ```bash
+ poke-cli -v
+ ```
+
+### Winget
+1. Install the package:
+ ```powershell
+ winget install poke-cli
+ ```
+
+2. Verify install:
```bash
poke-cli -v
```
@@ -108,35 +120,36 @@ View future plans in the [Roadmap](#roadmap) section.
## Usage
By running `poke-cli [-h | --help]`, it'll display information on how to use the tool.
```
-╭──────────────────────────────────────────────────────────╮
-│Welcome! This tool displays data related to Pokémon! │
-│ │
-│ USAGE: │
-│ poke-cli [flag] │
-│ poke-cli [flag] │
-│ poke-cli [flag] │
-│ │
-│ FLAGS: │
-│ -h, --help Shows the help menu │
-│ -l, --latest Prints the latest version available │
-│ -v, --version Prints the current version │
-│ │
-│ 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 │
-│ search Search for a resource │
-│ types Get details about a typing │
-│ │
-│ 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 │
-╰──────────────────────────────────────────────────────────╯
+╭───────────────────────────────────────────────────────────────╮
+│Welcome! This tool displays data related to Pokémon! │
+│ │
+│ USAGE: │
+│ poke-cli [flag] │
+│ poke-cli [flag] │
+│ poke-cli [flag] │
+│ │
+│ FLAGS: │
+│ -h, --help Shows the help menu │
+│ -l, --latest Prints the latest version available │
+│ -v, --version Prints the current version │
+│ │
+│ 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 │
+│ search Search for a resource │
+│ speed Calculate the speed of a Pokémon in battle │
+│ types Get details about a typing │
+│ │
+│ 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 │
+╰───────────────────────────────────────────────────────────────╯
```
---
@@ -163,7 +176,7 @@ Below is a list of the planned/completed commands and flags:
- [ ] `item`
- [x] `move`
- [x] `pokemon`
-- [ ] `speed`: compare speed stats between two Pokémon.
+- [x] `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 fc7b1a9..8c1d3cd 100644
--- a/cli.go
+++ b/cli.go
@@ -9,6 +9,7 @@ import (
"github.com/digitalghost-dev/poke-cli/cmd/natures"
"github.com/digitalghost-dev/poke-cli/cmd/pokemon"
"github.com/digitalghost-dev/poke-cli/cmd/search"
+ "github.com/digitalghost-dev/poke-cli/cmd/speed"
"github.com/digitalghost-dev/poke-cli/cmd/types"
"github.com/digitalghost-dev/poke-cli/cmd/utils"
"github.com/digitalghost-dev/poke-cli/flags"
@@ -72,6 +73,7 @@ func runCLI(args []string) int {
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"),
fmt.Sprintf("\n\t%-15s %s", "search", "Search for a resource"),
+ fmt.Sprintf("\n\t%-15s %s", "speed", "Calculate the speed of a Pokémon in battle"),
fmt.Sprintf("\n\t%-15s %s", "types", "Get details about a typing"),
"\n\n", styling.StyleItalic.Render("hint: when calling a resource with a space, use a hyphen"),
"\n", styling.StyleItalic.Render("example: poke-cli ability strong-jaw"),
@@ -105,6 +107,7 @@ func runCLI(args []string) int {
"move": utils.HandleCommandOutput(move.MoveCommand),
"natures": utils.HandleCommandOutput(natures.NaturesCommand),
"pokemon": utils.HandleCommandOutput(pokemon.PokemonCommand),
+ "speed": utils.HandleCommandOutput(speed.SpeedCommand),
"types": utils.HandleCommandOutput(types.TypesCommand),
"search": func() int {
search.SearchCommand()
@@ -141,6 +144,7 @@ func runCLI(args []string) int {
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"),
fmt.Sprintf("\n\t%-15s %s", "search", "Search for a resource"),
+ fmt.Sprintf("\n\t%-15s %s", "speed", "Calculate the speed of a Pokémon in battle"),
fmt.Sprintf("\n\t%-15s %s", "types", "Get details about a typing"),
fmt.Sprintf("\n\nAlso run %s for more info!", styling.StyleBold.Render("poke-cli -h")),
)
diff --git a/cli_test.go b/cli_test.go
index 2a330a9..2e9709c 100644
--- a/cli_test.go
+++ b/cli_test.go
@@ -101,6 +101,12 @@ func TestRunCLI(t *testing.T) {
expectedOutput: utils.LoadGolden(t, "cli_help.golden"),
expectedCode: 0,
},
+ {
+ name: "Non-valid command",
+ args: []string{"movesets"},
+ expectedOutput: utils.LoadGolden(t, "cli_incorrect_command.golden"),
+ expectedCode: 1,
+ },
}
for _, tt := range tests {
diff --git a/cmd/speed/speed.go b/cmd/speed/speed.go
new file mode 100644
index 0000000..0617d51
--- /dev/null
+++ b/cmd/speed/speed.go
@@ -0,0 +1,331 @@
+package speed
+
+import (
+ "errors"
+ "flag"
+ "fmt"
+ "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"
+ "github.com/digitalghost-dev/poke-cli/styling"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+ "log"
+ "math"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// DefaultSpeedStat is the default implementation of SpeedStatFunc
+var DefaultSpeedStat SpeedStatFunc = func(name string) (string, error) {
+ pokemonStruct, _, err := connections.PokemonApiCall("pokemon", name, connections.APIURL)
+ if err != nil {
+ return "", fmt.Errorf("API call failed: %w", err)
+ }
+
+ for _, stat := range pokemonStruct.Stats {
+ if stat.Stat.Name == "speed" {
+ return strconv.Itoa(stat.BaseStat), nil
+ }
+ }
+
+ return "", errors.New("speed stat not found")
+}
+
+var (
+ pokemon PokemonDetails
+ output strings.Builder
+ abilityMultipliers = map[string]float64{
+ "None": 1.0,
+ "Swift Swim": 2.0,
+ "Chlorophyll": 2.0,
+ "Sand Rush": 2.0,
+ "Slush Rush": 2.0,
+ "Unburden": 2.0,
+ "Quick Feet": 1.5,
+ "Surge Surfer": 2.0,
+ }
+ modifierMultipliers = map[string]float64{
+ "Choice Scarf": 1.5,
+ "Tailwind": 2.0,
+ }
+ stageMultipliers = map[int]float64{
+ -6: 0.25,
+ -5: 2.0 / 7.0,
+ -4: 1.0 / 3.0,
+ -3: 0.4,
+ -2: 0.5,
+ -1: 2.0 / 3.0,
+ 0: 1.0,
+ +1: 1.5,
+ +2: 2.0,
+ +3: 2.5,
+ +4: 3.0,
+ +5: 3.5,
+ +6: 4.0,
+ }
+ natureMultipliers = map[string]float64{
+ "+10%": 1.1,
+ "0%": 1.0,
+ "-10%": 0.9,
+ }
+)
+
+type PokemonDetails struct {
+ Name string
+ SpeedStage string
+ Nature string
+ Level string
+ Modifier []string
+ Ability string
+ SpeedEV string
+ SpeedIV string
+}
+
+// SpeedStatFunc is a function type for getting a Pokémon's base speed stat
+type SpeedStatFunc func(name string) (string, error)
+
+func SpeedCommand() (string, error) {
+ // Reset the output string builder
+ output.Reset()
+
+ flag.Usage = func() {
+ helpMessage := styling.HelpBorder.Render(
+ "Calculate the speed of a Pokémon.\n\n",
+ styling.StyleBold.Render("USAGE:"),
+ fmt.Sprintf("\n\t%s %s %s", "poke-cli", styling.StyleBold.Render("speed"), "[flag]"),
+ "\n\n",
+ styling.StyleBold.Render("FLAGS:"),
+ fmt.Sprintf("\n\t%-30s %s", "-h, --help", "Prints out the help menu"),
+ )
+ output.WriteString(helpMessage)
+ }
+
+ 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.ValidateSpeedArgs(os.Args); err != nil {
+ output.WriteString(err.Error())
+ return output.String(), err
+ }
+
+ form := form()
+
+ err := form.Run()
+ if err != nil {
+ return "", err
+ }
+
+ result, err := formula()
+ if err != nil {
+ return "", err
+ }
+
+ return result, nil
+}
+
+func form() *huh.Form {
+ form := huh.NewForm(
+ huh.NewGroup(huh.NewNote().
+ Title("Speed Calculator").
+ Description("This command will calculate the speed stat\nof a Pokémon during an in-game battle.").
+ Next(true).
+ NextLabel("Next"),
+ ),
+ huh.NewGroup(
+ huh.NewInput().
+ Value(&pokemon.Name).
+ Title("Enter the first Pokémon's name:").
+ Placeholder("incineroar").
+ Validate(func(s string) error {
+ if strings.TrimSpace(s) == "" {
+ return errors.New("input cannot be blank")
+ }
+ _, _, err := connections.PokemonApiCall("pokemon", s, connections.APIURL)
+ if err != nil {
+ return errors.New("not a valid Pokémon")
+ }
+ return nil
+ }),
+ huh.NewInput().
+ Value(&pokemon.Level).
+ Title("And its level:").
+ Placeholder("50").
+ Validate(func(s string) error {
+ num, err := strconv.Atoi(s)
+ if err != nil {
+ return errors.New("please enter a valid number")
+ }
+ if num < 1 || num > 100 {
+ return errors.New("level must be between 1 and 100")
+ }
+ return nil
+ }),
+ huh.NewInput().
+ Value(&pokemon.SpeedEV).
+ Title("EV amount:").
+ Description("Enter the Pokémon's EV level for the speed stat").
+ Placeholder("252").
+ Validate(func(s string) error {
+ num, err := strconv.Atoi(s)
+ if err != nil {
+ return errors.New("please enter a valid number")
+ }
+ if num < 0 || num > 252 {
+ return errors.New("level must be between 0 and 252")
+ }
+ return nil
+ }),
+ huh.NewInput().
+ Value(&pokemon.SpeedIV).
+ Title("IV amount").
+ Description("Enter the Pokémon's IV level for the speed stat").
+ Placeholder("31").
+ Validate(func(s string) error {
+ num, err := strconv.Atoi(s)
+ if err != nil {
+ return errors.New("please enter a valid number")
+ }
+ if num < 0 || num > 31 {
+ return errors.New("level must be between 0 and 31")
+ }
+ return nil
+ }),
+ huh.NewMultiSelect[string]().
+ Title("Modifiers").
+ Description("Select any amount of options").
+ Options(
+ huh.NewOption("Choice Scarf", "Choice Scarf"),
+ huh.NewOption("Tailwind", "Tailwind"),
+ ).Value(&pokemon.Modifier),
+ ),
+ // Page 3
+ huh.NewGroup(
+ huh.NewSelect[string]().
+ Title("Ability").
+ Description("Select an ability in play").
+ Value(&pokemon.Ability).
+ Options(
+ huh.NewOptions("None", "Swift Swim", "Chlorophyll", "Sand Rush", "Slush Rush", "Unburden", "Quick Feet", "Surge Surfer")...,
+ ),
+ huh.NewSelect[string]().
+ Options(
+ huh.NewOptions("+10%", "0%", "-10%")...,
+ ).
+ Title("Nature").
+ Description("Nature benefit/detriment 1").
+ Value(&pokemon.Nature).
+ Validate(func(value string) error {
+ if value == "" {
+ return errors.New("please select a nature")
+ }
+ return nil
+ }),
+ huh.NewInput().
+ Value(&pokemon.SpeedStage).
+ Title("Speed Stage").
+ Placeholder("ex: +6 or 0 or -3").
+ Validate(func(s string) error {
+ num, err := strconv.Atoi(s)
+ if err != nil {
+ return errors.New("please enter a whole number between -6 and +6")
+ }
+ if num < -6 || num > 6 {
+ return errors.New("level must be between -6 and +6")
+ }
+ return nil
+ }),
+ ),
+ ).WithTheme(styling.FormTheme())
+
+ return form
+}
+
+func formula() (string, error) {
+ modifierMultiplier := 1.0 // start with no change
+ for _, mod := range pokemon.Modifier {
+ if val, ok := modifierMultipliers[mod]; ok {
+ modifierMultiplier *= val
+ }
+ }
+
+ abilityMultiplier := abilityMultipliers[pokemon.Ability]
+
+ speedStageInt, err := strconv.Atoi(pokemon.SpeedStage)
+ if err != nil {
+ log.Fatalf("Invalid SpeedStage: %v", err)
+ }
+ stageMultiplier := stageMultipliers[speedStageInt]
+
+ // Get the Pokémon's base speed using the DefaultSpeedStat function
+ speedStr, err := DefaultSpeedStat(pokemon.Name)
+ if err != nil {
+ return "", err
+ }
+
+ baseSpeed, err := strconv.Atoi(speedStr)
+ if err != nil {
+ return "", fmt.Errorf("failed to convert speed to int: %w", err)
+ }
+
+ intIV, err := strconv.Atoi(pokemon.SpeedIV)
+ if err != nil {
+ return "", fmt.Errorf("failed to convert speed to int: %w", err)
+ }
+ intEV, err := strconv.Atoi(pokemon.SpeedEV)
+ if err != nil {
+ return "", fmt.Errorf("failed to convert speed to int: %w", err)
+ }
+
+ intLevel, err := strconv.Atoi(pokemon.Level)
+ if err != nil {
+ return "", fmt.Errorf("failed to convert speed to int: %w", err)
+ }
+
+ natureMultiplier := natureMultipliers[pokemon.Nature]
+
+ chosenPokemon := cases.Title(language.English).String(pokemon.Name)
+
+ // Calculate final speed using the formula:
+ // (((2 x base + IV + (EV / 4)) x level / 100 + 5) * nature * modifier * ability * stage
+ finalSpeed := float64(((2*baseSpeed+intIV+(intEV/4))*intLevel/100)+5) * natureMultiplier * abilityMultiplier * stageMultiplier * modifierMultiplier
+
+ // Round down the final speed
+ finalSpeedFloor := math.Floor(finalSpeed)
+ finalSpeedStr := fmt.Sprintf("%.0f", finalSpeedFloor)
+
+ header := fmt.Sprintf("%s at level %s with selected options has a current speed of %s.",
+ styling.YellowAdaptive(chosenPokemon),
+ styling.YellowAdaptive(pokemon.Level),
+ styling.YellowAdaptive(finalSpeedStr),
+ )
+ body := fmt.Sprintf("EVs: %s\nIVs: %s\nModifiers: %s\nNature: %s\nAbility: %s\nSpeed Stage: %s\nBase Speed: %s",
+ styling.YellowAdaptive(pokemon.SpeedEV),
+ styling.YellowAdaptive(pokemon.SpeedIV),
+ styling.YellowAdaptive(xstrings.EnglishJoin(pokemon.Modifier, true)),
+ styling.YellowAdaptive(pokemon.Nature),
+ styling.YellowAdaptive(pokemon.Ability),
+ styling.YellowAdaptive(pokemon.SpeedStage),
+ styling.YellowAdaptive(speedStr),
+ )
+
+ docStyle := lipgloss.NewStyle().
+ Padding(1, 2).
+ BorderStyle(lipgloss.ThickBorder()).
+ BorderForeground(lipgloss.AdaptiveColor{Light: "#444", Dark: "#EEE"}).
+ Width(32)
+
+ fullDoc := lipgloss.JoinVertical(lipgloss.Top, header, "---", body)
+ output.WriteString(docStyle.Render(fullDoc))
+
+ return output.String(), nil
+}
diff --git a/cmd/speed/speed_test.go b/cmd/speed/speed_test.go
new file mode 100644
index 0000000..16a604d
--- /dev/null
+++ b/cmd/speed/speed_test.go
@@ -0,0 +1,234 @@
+package speed
+
+import (
+ "github.com/digitalghost-dev/poke-cli/cmd/utils"
+ "github.com/digitalghost-dev/poke-cli/styling"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "os"
+ "testing"
+)
+
+func TestSpeedCommand(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ expectedOutput string
+ wantError bool
+ }{
+ {
+ name: "Speed help flag",
+ args: []string{"speed", "--help"},
+ expectedOutput: utils.LoadGolden(t, "speed_help.golden"),
+ },
+ {
+ name: "Speed help flag",
+ args: []string{"speed", "-h"},
+ expectedOutput: utils.LoadGolden(t, "speed_help.golden"),
+ },
+ }
+
+ 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, _ := SpeedCommand()
+ cleanOutput := styling.StripANSI(output)
+
+ assert.Equal(t, tt.expectedOutput, cleanOutput, "Output should match expected")
+ })
+ }
+}
+
+func TestFormula(t *testing.T) {
+ // Save the original DefaultSpeedStat function and restore it after the test
+ originalSpeedStat := DefaultSpeedStat
+ defer func() { DefaultSpeedStat = originalSpeedStat }()
+
+ // Create a mock SpeedStatFunc that always returns 90 (Pikachu's base speed)
+ DefaultSpeedStat = func(name string) (string, error) {
+ return "90", nil
+ }
+
+ tests := []struct {
+ name string
+ pokemonDetails PokemonDetails
+ expectedSpeed string
+ wantError bool
+ }{
+ {
+ name: "Basic calculation with default values",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "95",
+ wantError: false,
+ },
+ {
+ name: "With positive nature",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "+10%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "104",
+ wantError: false,
+ },
+ {
+ name: "With negative nature",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "-10%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "85",
+ wantError: false,
+ },
+ {
+ name: "With ability multiplier",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "Swift Swim",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "190",
+ wantError: false,
+ },
+ {
+ name: "With positive speed stage",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "2",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "190",
+ wantError: false,
+ },
+ {
+ name: "With negative speed stage",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "-2",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "47",
+ wantError: false,
+ },
+ {
+ name: "With modifier",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{"Choice Scarf"},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "142",
+ wantError: false,
+ },
+ {
+ name: "With multiple modifiers",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{"Choice Scarf", "Tailwind"},
+ Ability: "None",
+ SpeedEV: "0",
+ SpeedIV: "0",
+ },
+ expectedSpeed: "285",
+ wantError: false,
+ },
+ {
+ name: "With EVs and IVs",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "0",
+ Nature: "0%",
+ Level: "50",
+ Modifier: []string{},
+ Ability: "None",
+ SpeedEV: "252",
+ SpeedIV: "31",
+ },
+ expectedSpeed: "142",
+ wantError: false,
+ },
+ {
+ name: "Complex scenario",
+ pokemonDetails: PokemonDetails{
+ Name: "pikachu",
+ SpeedStage: "1",
+ Nature: "+10%",
+ Level: "100",
+ Modifier: []string{"Choice Scarf"},
+ Ability: "Quick Feet",
+ SpeedEV: "252",
+ SpeedIV: "31",
+ },
+ expectedSpeed: "1035",
+ wantError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pokemon = tt.pokemonDetails
+
+ result, err := formula()
+
+ if tt.wantError {
+ assert.Error(t, err)
+ } else {
+ require.NoError(t, err)
+
+ cleanOutput := styling.StripANSI(result)
+
+ t.Logf("Expected speed: %s", tt.expectedSpeed)
+
+ // Check if the output contains the expected speed
+ assert.Contains(t, cleanOutput, "current speed of "+tt.expectedSpeed,
+ "Expected speed "+tt.expectedSpeed+" not found in output: "+cleanOutput)
+ }
+ })
+ }
+}
diff --git a/cmd/types/types_test.go b/cmd/types/types_test.go
index ccef786..64d6be5 100644
--- a/cmd/types/types_test.go
+++ b/cmd/types/types_test.go
@@ -45,6 +45,7 @@ func TestTypesCommand(t *testing.T) {
})
}
}
+
func TestModelInit(t *testing.T) {
m := model{}
cmd := m.Init()
diff --git a/cmd/utils/validateargs.go b/cmd/utils/validateargs.go
index dce7cfe..791d763 100644
--- a/cmd/utils/validateargs.go
+++ b/cmd/utils/validateargs.go
@@ -163,6 +163,19 @@ func ValidateSearchArgs(args []string) error {
return nil
}
+// ValidateSpeedArgs validates the command line arguments
+func ValidateSpeedArgs(args []string) error {
+ if err := checkLength(args, 3); err != nil {
+ return err
+ }
+
+ if err := checkNoOtherOptions(args, 3, ""); err != nil {
+ return err
+ }
+
+ return nil
+}
+
// ValidateTypesArgs validates the command line arguments
func ValidateTypesArgs(args []string) error {
if err := checkLength(args, 3); err != nil {
diff --git a/demo.gif b/demo.gif
index adeeec4..c2b828f 100644
Binary files a/demo.gif and b/demo.gif differ
diff --git a/demo.tape b/demo.tape
index a040354..094d66c 100644
--- a/demo.tape
+++ b/demo.tape
@@ -61,18 +61,18 @@ Require echo
Set Shell "bash"
Set FontSize 32
-Set Width 2100
+Set Width 2300
Set Height 1300
-Set TypingSpeed 100ms
+Set TypingSpeed 95ms
Set Theme "tokyonight-storm"
Type "poke-cli pokemon charizard --abilities --stats --types"
-Sleep 3s
+Sleep 2s
Enter
-Sleep 6s
+Sleep 4s
Type clear
@@ -80,11 +80,11 @@ Enter
Type "poke-cli pokemon tyranitar --image=sm"
-Sleep 3s
+Sleep 2s
Enter
-Sleep 6s
+Sleep 4s
Type clear
@@ -92,7 +92,7 @@ Enter
Type "poke-cli types"
-Sleep 3s
+Sleep 2s
Enter
@@ -112,17 +112,17 @@ Type "poke-cli natures"
Enter
-Sleep 6s
+Sleep 4s
Type clear
Enter
-Type "poke-cli ability stench --pokemon"
+Type "poke-cli ability solar-power --pokemon"
Enter
-Sleep 6s
+Sleep 4s
Type clear
@@ -140,6 +140,18 @@ Type clear
Enter
+Type "poke-cli item choice-band"
+
+Sleep 2s
+
+Enter
+
+Sleep 3s
+
+Type clear
+
+Enter
+
Type "poke-cli search"
Sleep 2s
@@ -148,7 +160,7 @@ Enter
Sleep 2s
-Down@1s 2
+Down@500ms 2
Sleep 2s
@@ -162,6 +174,79 @@ Sleep 2s
Enter
-Sleep 4s
+Sleep 3s
+
+Escape
+
+Sleep 2s
+
+Type clear
+
+Enter
+
+Type "poke-cli speed"
+
+Sleep 2s
+
+Enter
+
+Sleep 5s
+
+Enter
+
+Sleep 1.5s
+
+Type "zapdos"
+
+Enter
+
+Sleep 1.5s
+
+Type "50"
+
+Enter
+
+Sleep 1.5s
+
+Type "252"
+
+Enter
+
+Sleep 1.5s
+
+Type "31"
+
+Enter
+
+Sleep 1.5s
+
+Down@500ms 1
+
+Type "x"
+
+Sleep 2s
+
+Enter
+
+Down@500ms 3
+
+Up@750ms 3
+
+Enter
+
+Sleep 1.5s
+
+Down@500ms 1
+
+Enter
+
+Sleep 1.5s
+
+Type "1"
+
+Sleep 2s
+
+Enter
+
+Sleep 6s
-Type esc
\ No newline at end of file
diff --git a/docs/assets/speed.gif b/docs/assets/speed.gif
new file mode 100644
index 0000000..892bd10
Binary files /dev/null and b/docs/assets/speed.gif differ
diff --git a/docs/commands.md b/docs/commands.md
index 2610a02..4ab9af0 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -130,6 +130,19 @@ Output:
---
+## `speed`
+* Calculate the speed of a Pokémon in battle.
+
+Example:
+```console
+$ poke-cli speed
+```
+Output:
+
+
+
+---
+
## `types`
* Retrieve details about a specific type and a damage relation table.
diff --git a/go.mod b/go.mod
index 124b266..6b963cc 100644
--- a/go.mod
+++ b/go.mod
@@ -3,40 +3,44 @@ module github.com/digitalghost-dev/poke-cli
go 1.24.4
require (
- github.com/charmbracelet/bubbles v0.20.0
- github.com/charmbracelet/bubbletea v1.3.5
+ github.com/charmbracelet/bubbles v0.21.0
+ github.com/charmbracelet/bubbletea v1.3.6
+ github.com/charmbracelet/huh v0.7.0
github.com/charmbracelet/lipgloss v1.1.0
+ github.com/charmbracelet/x/exp/strings v0.0.0-20250708181618-a60a724ba6c3
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.26.0
+ golang.org/x/text v0.27.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.3.1 // indirect
+ github.com/catppuccin/go v0.3.0 // 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/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.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.16 // 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
github.com/muesli/termenv v0.16.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
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.28.0 // indirect
- golang.org/x/sync v0.15.0 // indirect
- golang.org/x/sys v0.33.0 // indirect
+ golang.org/x/sync v0.16.0 // indirect
+ golang.org/x/sys v0.34.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index e6a6aa2..765af5d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,31 +1,51 @@
+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/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.3.5 h1:JAMNLTbqMOhSwoELIr0qyP4VidFq72/6E9j7HHmRKQc=
-github.com/charmbracelet/bubbletea v1.3.5/go.mod h1:TkCnmH+aBd4LrXhXcqrKiYwRs7qyQx5rBgH5fVY3v54=
+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.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
+github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
+github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
+github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
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/huh v0.7.0 h1:W8S1uyGETgj9Tuda3/JdVkc3x7DBLZYPZc4c+/rnRdc=
+github.com/charmbracelet/huh v0.7.0/go.mod h1:UGC3DZHlgOKHvHC07a5vHag41zzhpPFj34U92sOmyuk=
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.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/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
+github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
+github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
+github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/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/strings v0.0.0-20250708181618-a60a724ba6c3 h1:QTf5vyE6CO+zZiF83Je/r3zWol31h3sKRJ7Vt2PwJVg=
+github.com/charmbracelet/x/exp/strings v0.0.0-20250708181618-a60a724ba6c3/go.mod h1:Rgw3/F+xlcUc5XygUtimVSxAqCOsqyvJjqF5UHRvc5k=
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/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/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
+github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
+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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
@@ -36,6 +56,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+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=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
@@ -56,15 +78,15 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQz
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
+golang.org/x/sync v0.16.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
-golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
+golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
-golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
+golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
+golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
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/styling/styling.go b/styling/styling.go
index cdb1a68..8499d35 100644
--- a/styling/styling.go
+++ b/styling/styling.go
@@ -2,15 +2,19 @@ package styling
import (
"fmt"
+ "github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
"image/color"
"regexp"
)
var (
- Green = lipgloss.NewStyle().Foreground(lipgloss.Color("#38B000"))
- Red = lipgloss.NewStyle().Foreground(lipgloss.Color("#D00000"))
- Gray = lipgloss.Color("#777777")
+ Green = lipgloss.NewStyle().Foreground(lipgloss.Color("#38B000"))
+ Red = lipgloss.NewStyle().Foreground(lipgloss.Color("#D00000"))
+ Gray = lipgloss.Color("#777777")
+ YellowAdaptive = func(s string) string {
+ return lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#E1AD01", Dark: "#FFDE00"}).Render(s)
+ }
ColoredBullet = lipgloss.NewStyle().
SetString("•").
Foreground(lipgloss.Color("#FFCC00"))
@@ -69,7 +73,7 @@ func StripANSI(input string) string {
return ansiRegex.ReplaceAllString(input, "")
}
-// To avoid unnecessary dependencies, I adapted the MakeColor function from
+// Color To avoid unnecessary dependencies, I adapted the MakeColor function from
// "github.com/lucasb-eyer/go-colorful" and implemented it using only the
// standard library. Since I only needed this function, importing the entire
// library was unnecessary.
@@ -77,7 +81,7 @@ type Color struct {
R, G, B float64
}
-// Implement the Go color.Color interface.
+// RGBA Implement the Go color.Color interface.
func (col Color) RGBA() (uint32, uint32, uint32, uint32) {
return uint32(col.R*65535.0 + 0.5), uint32(col.G*65535.0 + 0.5), uint32(col.B*65535.0 + 0.5), 0xFFFF
}
@@ -102,3 +106,49 @@ 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("#FFDE00")
+ 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/cli_help.golden b/testdata/cli_help.golden
index 73ec9eb..3076d2f 100644
--- a/testdata/cli_help.golden
+++ b/testdata/cli_help.golden
@@ -1,29 +1,30 @@
-╭──────────────────────────────────────────────────────────╮
-│Welcome! This tool displays data related to Pokémon! │
-│ │
-│ USAGE: │
-│ poke-cli [flag] │
-│ poke-cli [flag] │
-│ poke-cli [flag] │
-│ │
-│ FLAGS: │
-│ -h, --help Shows the help menu │
-│ -l, --latest Prints the latest version available │
-│ -v, --version Prints the current version │
-│ │
-│ 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 │
-│ search Search for a resource │
-│ types Get details about a typing │
-│ │
-│ 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 │
-│ ]8;;https://docs.poke-cli.com\docs.poke-cli.com]8;;\ │
-╰──────────────────────────────────────────────────────────╯
+╭───────────────────────────────────────────────────────────────╮
+│Welcome! This tool displays data related to Pokémon! │
+│ │
+│ USAGE: │
+│ poke-cli [flag] │
+│ poke-cli [flag] │
+│ poke-cli [flag] │
+│ │
+│ FLAGS: │
+│ -h, --help Shows the help menu │
+│ -l, --latest Prints the latest version available │
+│ -v, --version Prints the current version │
+│ │
+│ 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 │
+│ search Search for a resource │
+│ speed Calculate the speed of a Pokémon in battle │
+│ types Get details about a typing │
+│ │
+│ 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 │
+│ ]8;;https://docs.poke-cli.com\docs.poke-cli.com]8;;\ │
+╰───────────────────────────────────────────────────────────────╯
diff --git a/testdata/cli_incorrect_command.golden b/testdata/cli_incorrect_command.golden
index 8603c98..f87d825 100644
--- a/testdata/cli_incorrect_command.golden
+++ b/testdata/cli_incorrect_command.golden
@@ -1,14 +1,16 @@
-╭──────────────────────────────────────────────────╮
-│Error! │
-│ 'movesets' is not a valid command. │
-│ │
-│Commands: │
-│ ability Get details about an ability │
-│ move Get details about a move │
-│ natures Get details about all natures │
-│ pokemon Get details about a Pokémon │
-│ search Search for a resource │
-│ types Get details about a typing │
-│ │
-│Also run poke-cli -h for more info! │
-╰──────────────────────────────────────────────────╯
\ No newline at end of file
+╭───────────────────────────────────────────────────────────────╮
+│Error! │
+│ 'movesets' is not a valid command. │
+│ │
+│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 │
+│ search Search for a resource │
+│ speed Calculate the speed of a Pokémon in battle │
+│ types Get details about a typing │
+│ │
+│Also run poke-cli -h for more info! │
+╰───────────────────────────────────────────────────────────────╯
diff --git a/testdata/speed_help.golden b/testdata/speed_help.golden
new file mode 100644
index 0000000..995d9f3
--- /dev/null
+++ b/testdata/speed_help.golden
@@ -0,0 +1,9 @@
+╭───────────────────────────────────────────────────────────╮
+│Calculate the speed of a Pokémon. │
+│ │
+│ USAGE: │
+│ poke-cli speed [flag] │
+│ │
+│ FLAGS: │
+│ -h, --help Prints out the help menu│
+╰───────────────────────────────────────────────────────────╯
\ No newline at end of file
diff --git a/testdata/speed_invalid_option.golden b/testdata/speed_invalid_option.golden
new file mode 100644
index 0000000..557a5b5
--- /dev/null
+++ b/testdata/speed_invalid_option.golden
@@ -0,0 +1,5 @@
+╭────────────────────────────────────╮
+│Error! │
+│The only available options after the│
+│ command are '-h' or '--help'│
+╰────────────────────────────────────╯
\ No newline at end of file