generated from bitfield/weather
-
Notifications
You must be signed in to change notification settings - Fork 0
/
weather.go
129 lines (109 loc) · 3.26 KB
/
weather.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
// Package weather makes it easy to fetch the current weather conditions
// of cities from all around the world.
package weather
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
const apiUrl = "https://api.openweathermap.org/data/2.5/weather"
// OpenWeatherClient is an API client for fetching the current weather conditions
// from the Open Weather service.
type OpenWeatherClient struct {
token string
BaseURL string
HttpClient *http.Client
}
// NewOpenWeatherClient returns a new instance of the Client configured with the given auth token.
func NewOpenWeatherClient(token string) *OpenWeatherClient {
client := &OpenWeatherClient{
token: token,
BaseURL: apiUrl,
HttpClient: &http.Client{},
}
return client
}
// FormatURL returns a URL for fetching the current weather of the given location.
func (c *OpenWeatherClient) FormatURL(location string) string {
return fmt.Sprintf("%s?q=%s&appid=%s", c.BaseURL, url.QueryEscape(location), c.token)
}
// Current fetches the present weather conditions at the given location.
func (c *OpenWeatherClient) Current(location string) (Conditions, error) {
url := c.FormatURL(location)
resp, err := c.HttpClient.Get(url)
if err != nil {
return Conditions{}, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return Conditions{}, errors.New(resp.Status)
}
conditions, err := ParseJSON(resp.Body)
if err != nil {
return Conditions{}, err
}
return conditions, nil
}
// Conditions holds the weather summary and temperature of a particular location.
type Conditions struct {
Summary string
Temperature Temperature
}
// String formats the weather conditions as a string.
func (c Conditions) String() string {
return fmt.Sprintf("%s %.1fºC", c.Summary, c.Temperature.Celsius())
}
type jsonResponse struct {
Weather []struct {
Main string `json:"main"`
} `json:"weather"`
Main struct {
Temp float64 `json:"temp"`
} `json:"main"`
}
// ParseJSON decodes the JSON response provided by the given io.Reader into
// the struct for weather conditions.
func ParseJSON(r io.Reader) (Conditions, error) {
decoder := json.NewDecoder(r)
response := jsonResponse{}
if err := decoder.Decode(&response); err != nil {
return Conditions{}, err
}
if len(response.Weather) < 1 {
return Conditions{}, errors.New("invalid response: missing weather data")
}
summary := response.Weather[0].Main
temperature := Temperature(response.Main.Temp)
return Conditions{summary, temperature}, nil
}
// Current returns the present weather conditions of the given location using
// the Open Weather API.
func Current(token, location string) (Conditions, error) {
client := NewOpenWeatherClient(token)
return client.Current(location)
}
// RunCLI runs the command-line interface of the weather package.
func RunCLI() int {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "location not provided")
return 1
}
location := strings.Join(os.Args[1:], " ")
token := os.Getenv("OPENWEATHER_API_TOKEN")
if token == "" {
fmt.Fprintln(os.Stderr, "missing api token")
return 1
}
conditions, err := Current(token, location)
if err != nil {
fmt.Fprintf(os.Stderr, "couldn't fetch weather conditions for location %q: %s\n", location, err)
return 1
}
fmt.Println(conditions)
return 0
}