Skip to content

Commit c4175b0

Browse files
Merge pull request #63 from digitalghost-dev/0.7.0
0.7.0
2 parents 73da8da + aa5fe04 commit c4175b0

File tree

8 files changed

+247
-77
lines changed

8 files changed

+247
-77
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ on:
2121
branches:
2222
- main
2323
env:
24-
VERSION_NUMBER: 'v0.6.5'
24+
VERSION_NUMBER: 'v0.7.0'
2525
REGISTRY_NAME: digitalghostdev/poke-cli
2626

2727
jobs:

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<img height="250" width="350" src="https://cdn.simpleicons.org/pokemon/FFCC00" alt="pokemon-logo"/>
33
<h1>Pokémon CLI</h1>
44
<img src="https://img.shields.io/github/v/release/digitalghost-dev/poke-cli?style=flat-square&logo=git&logoColor=FFCC00&label=Release%20Version&labelColor=EEE&color=FFCC00" alt="version-label">
5-
<img src="https://img.shields.io/docker/image-size/digitalghostdev/poke-cli/v0.6.5?arch=arm64&style=flat-square&logo=docker&logoColor=FFCC00&labelColor=EEE&color=FFCC00" alt="docker-image-size">
5+
<img src="https://img.shields.io/docker/image-size/digitalghostdev/poke-cli/v0.7.0?arch=arm64&style=flat-square&logo=docker&logoColor=FFCC00&labelColor=EEE&color=FFCC00" alt="docker-image-size">
66
<img src="https://img.shields.io/github/actions/workflow/status/digitalghost-dev/poke-cli/ci.yml?branch=main&style=flat-square&logo=github&logoColor=FFCC00&label=CI&labelColor=EEE&color=FFCC00">
77
</div>
88

@@ -40,7 +40,7 @@ _Taskfile can build the executable for you_
4040
_Use a Docker Image_
4141

4242
```bash
43-
docker run --rm -it digitalghostdev/poke-cli:v0.6.5 [command] [subcommand] [flag]
43+
docker run --rm -it digitalghostdev/poke-cli:v0.7.0 [command] [subcommand] [flag]
4444
```
4545

4646
### Go Build

cli_test.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func stripANSI(input string) string {
1717
}
1818

1919
func TestMainFunction(t *testing.T) {
20-
version := "v0.6.4"
20+
version := "v0.6.5"
2121

2222
// Backup the original exit function and stdout/stderr
2323
originalExit := exit
@@ -81,6 +81,26 @@ func TestMainFunction(t *testing.T) {
8181
"╰──────────────────────────────────────────────────────╯\n",
8282
expectedExit: 0,
8383
},
84+
{
85+
args: []string{"pokemon", "kingambit"},
86+
expectedOutput: "Your selected Pokémon: Kingambit\nNational Pokédex #: 983\n",
87+
expectedExit: 0,
88+
},
89+
{
90+
args: []string{"pokemon", "cradily", "--types"},
91+
expectedOutput: "Your selected Pokémon: Cradily\nNational Pokédex #: 346\n──────\nTyping\nType 1: rock\nType 2: grass\n",
92+
expectedExit: 0,
93+
},
94+
{
95+
args: []string{"pokemon", "giratina-altered", "--abilities"},
96+
expectedOutput: "Your selected Pokémon: Giratina-Altered\nNational Pokédex #: 487\n─────────\nAbilities\nAbility 1: pressure\nHidden Ability: telepathy\n",
97+
expectedExit: 0,
98+
},
99+
{
100+
args: []string{"pokemon", "coPPeraJAH", "-t", "-a"},
101+
expectedOutput: "Your selected Pokémon: Copperajah\nNational Pokédex #: 879\n──────\nTyping\nType 1: steel\n─────────\nAbilities\nAbility 1: sheer-force\nHidden Ability: heavy-metal\n",
102+
expectedExit: 0,
103+
},
84104
}
85105

86106
for _, test := range tests {

cmd/types.go

Lines changed: 108 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/charmbracelet/bubbles/table"
77
tea "github.com/charmbracelet/bubbletea"
88
"github.com/charmbracelet/lipgloss"
9+
"github.com/charmbracelet/x/term"
910
"github.com/digitalghost-dev/poke-cli/connections"
1011
"golang.org/x/text/cases"
1112
"golang.org/x/text/language"
@@ -26,8 +27,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
2627
case tea.KeyMsg:
2728
switch msg.String() {
2829
case "q", "ctrl+c":
29-
// Fully quiting the program while
30-
// the table is in selection mode
30+
// Quit the program when in selection mode
3131
m.selectedOption = "quit"
3232
return m, tea.Quit
3333
case "enter":
@@ -51,30 +51,108 @@ func (m model) View() string {
5151
return "Select a type! Hit 'Q' or 'CTRL-C' to quit.\n" + typesTableBorder.Render(m.table.View()) + "\n"
5252
}
5353

54-
// Creating a separate function that handles all the logic for building the table.
55-
func tableGeneration(endpoint string) table.Model {
56-
columns := []table.Column{
57-
{Title: "Type", Width: 16},
54+
// Helper function to get color for a given type name from colorMap
55+
func getTypeColor(typeName string) string {
56+
color := colorMap[typeName]
57+
58+
return color
59+
}
60+
61+
// Function to display type details after a type is selected
62+
func displayTypeDetails(typesName string, endpoint string) {
63+
64+
// Setting up variables to style the list
65+
var columnWidth = 13
66+
var subtle = lipgloss.AdaptiveColor{Light: "#D9DCCF", Dark: "#383838"}
67+
var list = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, true, false, false).BorderForeground(subtle).MarginRight(2).Height(8).Width(columnWidth + 1)
68+
var listHeader = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderBottom(true).BorderForeground(subtle).MarginRight(2).Render
69+
var listItem = lipgloss.NewStyle().Render
70+
var docStyle = lipgloss.NewStyle().Padding(1, 1, 1, 1)
71+
72+
baseURL := "https://pokeapi.co/api/v2/"
73+
typesStruct, typeName, _ := connections.TypesApiCall(endpoint, typesName, baseURL)
74+
75+
// Format selected type
76+
selectedType := cases.Title(language.English).String(typeName)
77+
coloredType := lipgloss.NewStyle().Foreground(lipgloss.Color(getTypeColor(typeName))).Render(selectedType)
78+
79+
fmt.Printf("You selected the %s type.\nNumber of Pokémon with type: %d\n", coloredType, len(typesStruct.Pokemon))
80+
fmt.Println("----------")
81+
fmt.Println(styleBold.Render("Damage Chart:"))
82+
83+
physicalWidth, _, _ := term.GetSize(uintptr(int(os.Stdout.Fd())))
84+
doc := strings.Builder{}
85+
86+
// Helper function to build list items
87+
buildListItems := func(items []struct{ Name, URL string }) string {
88+
var itemList []string
89+
for _, item := range items {
90+
color := getTypeColor(item.Name)
91+
coloredStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(color))
92+
coloredItem := coloredStyle.Render(cases.Title(language.English).String(item.Name))
93+
itemList = append(itemList, listItem(coloredItem))
94+
}
95+
return lipgloss.JoinVertical(lipgloss.Left, itemList...)
5896
}
5997

98+
// Render lists based on Damage Relations
99+
lists := lipgloss.JoinHorizontal(lipgloss.Top,
100+
list.Render(
101+
lipgloss.JoinVertical(lipgloss.Left,
102+
listHeader("Weakness"),
103+
buildListItems([]struct{ Name, URL string }(typesStruct.DamageRelations.DoubleDamageFrom)),
104+
),
105+
),
106+
list.Width(columnWidth).Render(
107+
lipgloss.JoinVertical(lipgloss.Left,
108+
listHeader("x2 Damage"),
109+
buildListItems([]struct{ Name, URL string }(typesStruct.DamageRelations.DoubleDamageTo)),
110+
),
111+
),
112+
list.Width(columnWidth).Render(
113+
lipgloss.JoinVertical(lipgloss.Left,
114+
listHeader("Resists"),
115+
buildListItems([]struct{ Name, URL string }(typesStruct.DamageRelations.HalfDamageFrom)),
116+
),
117+
),
118+
list.Width(columnWidth).Render(
119+
lipgloss.JoinVertical(lipgloss.Left,
120+
listHeader("x0.5 Damage"),
121+
buildListItems([]struct{ Name, URL string }(typesStruct.DamageRelations.HalfDamageTo)),
122+
),
123+
),
124+
list.Width(columnWidth).Render(
125+
lipgloss.JoinVertical(lipgloss.Left,
126+
listHeader("Immune"),
127+
buildListItems([]struct{ Name, URL string }(typesStruct.DamageRelations.NoDamageFrom)),
128+
),
129+
),
130+
list.Width(columnWidth).Render(
131+
lipgloss.JoinVertical(lipgloss.Left,
132+
listHeader("x0 Damage"),
133+
buildListItems([]struct{ Name, URL string }(typesStruct.DamageRelations.NoDamageTo)),
134+
),
135+
),
136+
)
137+
138+
// Append lists to document
139+
doc.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, lists))
140+
141+
if physicalWidth > 0 {
142+
docStyle = docStyle.MaxWidth(physicalWidth)
143+
}
144+
145+
// Print the rendered document
146+
fmt.Println(docStyle.Render(doc.String()))
147+
}
148+
149+
// Function that generates and handles the type selection table
150+
func tableGeneration(endpoint string) table.Model {
151+
columns := []table.Column{{Title: "Type", Width: 16}}
60152
rows := []table.Row{
61-
{"Normal"},
62-
{"Fire"},
63-
{"Water"},
64-
{"Electric"},
65-
{"Grass"},
66-
{"Ice"},
67-
{"Fighting"},
68-
{"Poison"},
69-
{"Ground"},
70-
{"Flying"},
71-
{"Psychic"},
72-
{"Bug"},
73-
{"Rock"},
74-
{"Ghost"},
75-
{"Dragon"},
76-
{"Steel"},
77-
{"Fairy"},
153+
{"Normal"}, {"Fire"}, {"Water"}, {"Electric"}, {"Grass"}, {"Ice"},
154+
{"Fighting"}, {"Poison"}, {"Ground"}, {"Flying"}, {"Psychic"}, {"Bug"},
155+
{"Rock"}, {"Ghost"}, {"Dragon"}, {"Steel"}, {"Fairy"},
78156
}
79157

80158
t := table.New(
@@ -85,15 +163,8 @@ func tableGeneration(endpoint string) table.Model {
85163
)
86164

87165
s := table.DefaultStyles()
88-
s.Header = s.Header.
89-
BorderStyle(lipgloss.NormalBorder()).
90-
BorderForeground(lipgloss.Color("#FFCC00")).
91-
BorderBottom(true).
92-
Bold(false)
93-
s.Selected = s.Selected.
94-
Foreground(lipgloss.Color("#000")).
95-
Background(lipgloss.Color("#FFCC00")).
96-
Bold(false)
166+
s.Header = s.Header.BorderStyle(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("#FFCC00")).BorderBottom(true)
167+
s.Selected = s.Selected.Foreground(lipgloss.Color("#000")).Background(lipgloss.Color("#FFCC00"))
97168
t.SetStyles(s)
98169

99170
m := model{table: t}
@@ -103,40 +174,22 @@ func tableGeneration(endpoint string) table.Model {
103174
os.Exit(1)
104175
}
105176

106-
// Type assert to model and access the selected option
177+
// Access the selected option from the model
107178
finalModel, ok := programModel.(model)
108179
if !ok {
109180
fmt.Println("Error: could not retrieve final model")
110181
os.Exit(1)
111182
}
112183

113-
// Check if the user quit the program by pressing 'Q' or 'CTRL-C'
114-
if finalModel.selectedOption == "quit" {
115-
// Return early to prevent further execution
116-
return t
184+
if finalModel.selectedOption != "quit" {
185+
typesName := strings.ToLower(finalModel.selectedOption)
186+
displayTypeDetails(typesName, endpoint) // Call function to display type details
117187
}
118188

119-
// Proceed with the API call only if a type was selected
120-
typesName := strings.ToLower(finalModel.selectedOption)
121-
122-
baseURL := "https://pokeapi.co/api/v2/"
123-
typeResponse, typeName, _ := connections.TypesApiCall(endpoint, typesName, baseURL)
124-
125-
selectedType := cases.Title(language.English).String(typeName)
126-
127-
pokemonCount := len(typeResponse.Pokemon)
128-
129-
// Picking the type's color from the colorMap map in cmd/styles.go:
130-
coloredStyle := lipgloss.NewStyle().Foreground(lipgloss.Color(colorMap[typeName]))
131-
coloredType := coloredStyle.Render(selectedType)
132-
133-
fmt.Printf("You selected Type: %s\nNumber of Pokémon with type: %d\n", coloredType, pokemonCount)
134189
return t
135190
}
136191

137192
func TypesCommand() {
138-
styleBold = lipgloss.NewStyle().Bold(true)
139-
styleItalic = lipgloss.NewStyle().Italic(true)
140193

141194
flag.Usage = func() {
142195
helpMessage := helpBorder.Render(
@@ -153,13 +206,11 @@ func TypesCommand() {
153206

154207
flag.Parse()
155208

156-
err := ValidateTypesArgs(os.Args)
157-
if err != nil {
209+
if err := ValidateTypesArgs(os.Args); err != nil {
158210
fmt.Println(err.Error())
159211
os.Exit(1)
160212
}
161213

162214
endpoint := strings.ToLower(os.Args[1])[0:4]
163-
164215
tableGeneration(endpoint)
165216
}

connections/connection.go

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"github.com/charmbracelet/lipgloss"
77
"io"
8-
"log"
98
"net/http"
109
)
1110

@@ -39,46 +38,73 @@ type TypesJSONStruct struct {
3938
} `json:"pokemon"`
4039
Slot int `json:"slot"`
4140
} `json:"pokemon"`
41+
DamageRelations struct {
42+
DoubleDamageFrom []struct {
43+
Name string `json:"name"`
44+
URL string `json:"url"`
45+
} `json:"double_damage_from"`
46+
DoubleDamageTo []struct {
47+
Name string `json:"name"`
48+
URL string `json:"url"`
49+
} `json:"double_damage_to"`
50+
HalfDamageFrom []struct {
51+
Name string `json:"name"`
52+
URL string `json:"url"`
53+
} `json:"half_damage_from"`
54+
HalfDamageTo []struct {
55+
Name string `json:"name"`
56+
URL string `json:"ul"`
57+
} `json:"half_damage_to"`
58+
NoDamageFrom []struct {
59+
Name string `json:"name"`
60+
URL string `json:"url"`
61+
} `json:"no_damage_from"`
62+
NoDamageTo []struct {
63+
Name string `json:"name"`
64+
URL string `json:"url"`
65+
} `json:"no_damage_to"`
66+
} `json:"damage_relations"`
4267
}
4368

4469
var httpGet = http.Get
4570
var red = lipgloss.Color("#F2055C")
4671
var errorColor = lipgloss.NewStyle().Foreground(red)
4772

4873
// ApiCallSetup Helper function to handle API calls and JSON unmarshalling
49-
func ApiCallSetup(url string, target interface{}) {
74+
func ApiCallSetup(url string, target interface{}) error {
5075
res, err := httpGet(url)
5176
if err != nil {
52-
log.Fatalf("Error making GET request: %v", err)
77+
return fmt.Errorf("error making GET request: %w", err)
5378
}
54-
defer func(Body io.ReadCloser) {
55-
err := Body.Close()
56-
if err != nil {
57-
log.Printf("Failed to close body: %v", err)
58-
}
59-
}(res.Body)
79+
defer res.Body.Close()
6080

6181
if res.StatusCode == http.StatusNotFound {
6282
fmt.Println(errorColor.Render("Page not found. 404 error."))
83+
return fmt.Errorf("page not found: 404 error")
6384
}
6485

6586
body, err := io.ReadAll(res.Body)
6687
if err != nil {
67-
log.Fatalf("Error reading response body: %v", err)
88+
return fmt.Errorf("error reading response body: %w", err)
6889
}
6990

7091
err = json.Unmarshal(body, target)
7192
if err != nil {
72-
log.Fatalf("Error unmarshalling JSON: %v", err)
93+
return fmt.Errorf("error unmarshalling JSON: %w", err)
7394
}
95+
96+
return nil
7497
}
7598

7699
func PokemonApiCall(endpoint string, pokemonName string, baseURL string) (PokemonJSONStruct, string, int) {
77100

78101
url := baseURL + endpoint + "/" + pokemonName
79102
var pokemonStruct PokemonJSONStruct
80103

81-
ApiCallSetup(url, &pokemonStruct)
104+
err := ApiCallSetup(url, &pokemonStruct)
105+
if err != nil {
106+
return PokemonJSONStruct{}, "", 0
107+
}
82108

83109
return pokemonStruct, pokemonStruct.Name, pokemonStruct.ID
84110
}
@@ -88,7 +114,10 @@ func TypesApiCall(endpoint string, typesName string, baseURL string) (TypesJSONS
88114
url := baseURL + endpoint + "/" + typesName
89115
var typesStruct TypesJSONStruct
90116

91-
ApiCallSetup(url, &typesStruct)
117+
err := ApiCallSetup(url, &typesStruct)
118+
if err != nil {
119+
return TypesJSONStruct{}, "", 0
120+
}
92121

93122
return typesStruct, typesStruct.Name, typesStruct.ID
94123
}

0 commit comments

Comments
 (0)