diff --git a/main.go b/main.go index 4f88ff5..14ffe4e 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,13 @@ package main import ( "bufio" - "context" - "encoding/json" "fmt" "maps" - "net/http" "os" "slices" "time" + + "codeflow.dananglin.me.uk/apollo/pokedex/internal/pokeclient" ) const ( @@ -27,40 +26,22 @@ var state State type command struct { name string description string - callback func() error + callback callbackFunc } +type callbackFunc func() error + func main() { run() } func run() { - fmt.Print("pokedex > ") + client := pokeclient.NewClient( + 5*time.Minute, + 10*time.Second, + ) - scanner := bufio.NewScanner(os.Stdin) - - for scanner.Scan() { - command := scanner.Text() - - cmdMap := getCommandMap() - if _, ok := cmdMap[command]; !ok { - fmt.Println("ERROR: Unrecognised command.") - - fmt.Print("\npokedex > ") - - continue - } - - if err := cmdMap[command].callback(); err != nil { - fmt.Printf("ERROR: %v.\n", err) - } - - fmt.Print("pokedex > ") - } -} - -func getCommandMap() map[string]command { - return map[string]command{ + commandMap := map[string]command{ "exit": { name: "exit", description: "Exit the Pokedex", @@ -69,41 +50,81 @@ func getCommandMap() map[string]command { "help": { name: "help", description: "Displays a help message", - callback: commandHelp, + callback: nil, }, "map": { name: "map", description: "Displays the next 20 locations in the Pokemon world", - callback: commandMap, + callback: commandMap(client), }, "mapb": { name: "map back", description: "Displays the previous 20 locations in the Pokemon world", - callback: commandMapB, + callback: commandMapB(client), }, } + + summaries := summaryMap(commandMap) + + commandMap["help"] = command{ + name: "help", + description: "Displays a help message", + callback: commandHelp(summaries), + } + + fmt.Printf("\nWelcome to the Pokedex!\n") + fmt.Print("\npokedex > ") + + scanner := bufio.NewScanner(os.Stdin) + + for scanner.Scan() { + command := scanner.Text() + + cmd, ok := commandMap[command] + if !ok { + fmt.Println("ERROR: Unrecognised command.") + + fmt.Print("\npokedex > ") + + continue + } + + if cmd.callback == nil { + fmt.Println("ERROR: This command is defined but does not have a callback function.") + + fmt.Print("\npokedex > ") + + continue + } + + if err := commandMap[command].callback(); err != nil { + fmt.Printf("ERROR: %v.\n", err) + } + + fmt.Print("pokedex > ") + } } -func commandHelp() error { - cmdMap := getCommandMap() +func commandHelp(summaries map[string]string) callbackFunc { + return func() error { + keys := []string{} - keys := []string{} + for key := range maps.All(summaries) { + keys = append(keys, key) + } - for key := range maps.All(cmdMap) { - keys = append(keys, key) + slices.Sort(keys) + + fmt.Printf("\nCommands:\n") + + for _, key := range slices.All(keys) { + fmt.Printf("\n%s: %s", key, summaries[key]) + } + + fmt.Printf("\n\n") + + return nil } - - slices.Sort(keys) - - fmt.Printf("\nWelcome to the Pokedex!\nUsage:\n") - - for _, key := range slices.All(keys) { - fmt.Printf("\n%s: %s", key, cmdMap[key].description) - } - - fmt.Println("\n") - - return nil } func commandExit() error { @@ -112,63 +133,51 @@ func commandExit() error { return nil } -func commandMap() error { - url := state.Next - if url == nil { - url = new(string) - *url = baseURL + locationAreaEndpoint - } +func commandMap(client *pokeclient.Client) callbackFunc { + return func() error { + url := state.Next + if url == nil { + url = new(string) + *url = baseURL + locationAreaEndpoint + } - return printMap(*url) + return printResourceList(client, *url) + } } -func commandMapB() error { - url := state.Previous - if url == nil { - return fmt.Errorf("no previous locations available") - } +func commandMapB(client *pokeclient.Client) callbackFunc { + return func() error { + url := state.Previous + if url == nil { + return fmt.Errorf("no previous locations available") + } - return printMap(*url) + return printResourceList(client, *url) + } } -func printMap(url string) error { - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) +func printResourceList(client *pokeclient.Client, url string) error { + list, err := client.GetNamedAPIResourceList(url) if err != nil { - return fmt.Errorf("error creating the HTTP request: %w", err) + return fmt.Errorf("unable to get the list of resources: %w", err) } - client := http.Client{} + state.Next = list.Next + state.Previous = list.Previous - resp, err := client.Do(request) - if err != nil { - return fmt.Errorf("error getting the response from the server: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode >= 400 { - return fmt.Errorf( - "received a bad status from %s: (%d) %s", - url, - resp.StatusCode, - resp.Status, - ) - } - - var result NamedAPIResourceList - - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return fmt.Errorf("unable to decode the JSON response: %w", err) - } - - state.Next = result.Next - state.Previous = result.Previous - - for _, location := range slices.All(result.Results) { + for _, location := range slices.All(list.Results) { fmt.Println(location.Name) } return nil } + +func summaryMap(commandMap map[string]command) map[string]string { + summaries := make(map[string]string) + + for key, value := range maps.All(commandMap) { + summaries[key] = value.description + } + + return summaries +}