Skip to content

Commit ae07492

Browse files
committed
Day30: XKCD cli wrapper
1 parent 5c7ca6f commit ae07492

4 files changed

Lines changed: 185 additions & 1 deletion

File tree

Day30/go-grab-xkcd/client/xkcd.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package client
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"github.com/canro91/30DaysOfGo/Day30/go-grab-xkcd/model"
7+
"io"
8+
"net/http"
9+
"os"
10+
"path"
11+
"path/filepath"
12+
"time"
13+
)
14+
15+
const (
16+
BaseUrl string = "https://xkcd.com"
17+
DefaultTimeout time.Duration = 30 * time.Second
18+
LatestComic ComicNumber = 0
19+
)
20+
21+
type ComicNumber int
22+
23+
type XKCDClient struct {
24+
client *http.Client
25+
baseUrl string
26+
}
27+
28+
func NewXKCDClient() *XKCDClient {
29+
return &XKCDClient{
30+
client: &http.Client{
31+
Timeout: DefaultTimeout,
32+
},
33+
baseUrl: BaseUrl,
34+
}
35+
}
36+
37+
func (c *XKCDClient) SetTimeout(d time.Duration) {
38+
c.client.Timeout = d
39+
}
40+
41+
func (c *XKCDClient) Fetch(n ComicNumber, save bool) (model.Comic, error) {
42+
response, err := c.client.Get(c.BuildUrl(n))
43+
if err != nil {
44+
return model.Comic{}, err
45+
}
46+
defer response.Body.Close()
47+
48+
var comicResponse model.ComicResponse
49+
if err := json.NewDecoder(response.Body).Decode(&comicResponse); err != nil {
50+
return model.Comic{}, err
51+
}
52+
53+
if save {
54+
if err := c.SaveToDisk(comicResponse.Img, "."); err != nil {
55+
fmt.Println("Failed to saved image")
56+
}
57+
}
58+
59+
return comicResponse.MapToComic(), nil
60+
}
61+
62+
func (c *XKCDClient) SaveToDisk(url, savePath string) error {
63+
response, err := http.Get(url)
64+
if err != nil {
65+
return err
66+
}
67+
defer response.Body.Close()
68+
69+
absolutePath, err := filepath.Abs(savePath)
70+
filePath := fmt.Sprintf("%s/%s", absolutePath, path.Base(url))
71+
file, err := os.Create(filePath)
72+
if err != nil {
73+
return err
74+
}
75+
defer file.Close()
76+
77+
_, err = io.Copy(file, response.Body)
78+
if err != nil {
79+
return err
80+
}
81+
82+
return nil
83+
}
84+
85+
func (c *XKCDClient) BuildUrl(n ComicNumber) string {
86+
var url string
87+
if n == LatestComic {
88+
url = fmt.Sprintf("%s/info.0.json", BaseUrl)
89+
} else {
90+
url = fmt.Sprintf("%s/%d/info.0.json", BaseUrl, n)
91+
}
92+
return url
93+
}

Day30/go-grab-xkcd/main.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"github.com/canro91/30DaysOfGo/Day30/go-grab-xkcd/client"
7+
"log"
8+
"time"
9+
)
10+
11+
func main() {
12+
comicNumber := flag.Int("n", int(client.LatestComic), "Comic number")
13+
clientTimeout := flag.Int64("t", int64(client.DefaultTimeout.Seconds()), "Client timeout in seconds")
14+
saveImage := flag.Bool("s", false, "Save image to current directory")
15+
outputType := flag.String("o", "text", "Print output in format: text/json")
16+
flag.Parse()
17+
18+
xkcdClient := client.NewXKCDClient()
19+
xkcdClient.SetTimeout(time.Duration(*clientTimeout) * time.Second)
20+
21+
comic, err := xkcdClient.Fetch(client.ComicNumber(*comicNumber), *saveImage)
22+
if err != nil {
23+
log.Println(err)
24+
}
25+
26+
if *outputType == "json" {
27+
fmt.Println(comic.ToJSON())
28+
} else {
29+
fmt.Println(comic.PrettyPrint())
30+
}
31+
}

Day30/go-grab-xkcd/model/comic.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package model
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
type ComicResponse struct {
9+
Month string `json:"month"`
10+
Num int `json:"num"`
11+
Link string `json:"link"`
12+
Year string `json:"year"`
13+
News string `json:"news"`
14+
SafeTitle string `json:"safe_title"`
15+
Transcript string `json:"transcript"`
16+
Alt string `json:"alt"`
17+
Img string `json:"img"`
18+
Title string `json:"title"`
19+
Day string `json:"day"`
20+
}
21+
type Comic struct {
22+
Title string `json:"title"`
23+
Number int `json:"number"`
24+
Date string `json:"date"`
25+
Description string `json:"description"`
26+
Image string `json:"image"`
27+
}
28+
29+
func (r ComicResponse) FormattedDate() string {
30+
return fmt.Sprintf("%s-%s-%s", r.Year, r.Month, r.Day)
31+
}
32+
33+
func (r ComicResponse) MapToComic() Comic {
34+
return Comic{
35+
Title: r.Title,
36+
Number: r.Num,
37+
Date: r.FormattedDate(),
38+
Description: r.Alt,
39+
Image: r.Img,
40+
}
41+
}
42+
43+
func (c Comic) PrettyPrint() string {
44+
return fmt.Sprintf(
45+
"Title: %s\nComic No: %d\nDate: %s\nDescription: %s\nImage: %s\n",
46+
c.Title, c.Number, c.Date, c.Description, c.Image)
47+
}
48+
49+
func (c Comic) ToJSON() string {
50+
json, err := json.Marshal(c)
51+
if err != nil {
52+
return ""
53+
}
54+
55+
return string(json)
56+
}

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ This is my jounery to learn Go in 30 days throught projects and hand-on tutorial
3131
* Day 26: [Sync](https://github.com/quii/learn-go-with-tests/blob/master/sync.md)
3232
* Day 27: [Context](https://github.com/quii/learn-go-with-tests/blob/master/context.md)
3333
* Day 28: [Roman numerals kata](https://github.com/quii/learn-go-with-tests/blob/master/roman-numerals.md)
34-
* Day 29: [API Client](https://blog.gopheracademy.com/advent-2019/api-clients-humans/)
34+
* Day 29: [API Client](https://blog.gopheracademy.com/advent-2019/api-clients-humans/) for Day 23 API
35+
36+
***
37+
38+
* Day 30: [Create a CLI wrapper for XKCD API](https://eryb.space/2020/05/27/diving-into-go-by-building-a-cli-application.html)

0 commit comments

Comments
 (0)