1// Copyright 2015 Brian J. Downs
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//
16// weather.go
17//
18// This application will go out and get the weather for the given
19// location and display it in the given data units (fahrenheit,
20// celcius, or kelvin).  If the string "here" is provided as an
21// argument to the -l flag, the app will try to figure out where
22// it's being executed from based on geolocation from the IP address.
23//
24// Examples:
25//          go run weather.go --help
26//          go run weather.go -w Philadelphia -u f -l en  # fahrenheit, English
27//          go run weather.go -w here -u f -l ru          # fahrenheit, Russian
28//          go run weather.go -w Dublin -u c -l fi        # celcius, Finnish
29//          go run weather.go -w "Las Vegas" -u k -l es   # kelvin, Spanish
30package main
31
32import (
33	"encoding/json"
34	"flag"
35	owm "github.com/briandowns/openweathermap" // "owm" for easier use
36	"io/ioutil"
37	"log"
38	"net/http"
39	"os"
40	"strings"
41	"text/template"
42)
43
44// URL is a constant that contains where to find the IP locale info
45const URL = "http://ip-api.com/json"
46
47// template used for output
48const weatherTemplate = `Current weather for {{.Name}}:
49    Conditions: {{range .Weather}} {{.Description}} {{end}}
50    Now:         {{.Main.Temp}} {{.Unit}}
51    High:        {{.Main.TempMax}} {{.Unit}}
52    Low:         {{.Main.TempMin}} {{.Unit}}
53`
54
55const forecastTemplate = `Weather Forecast for {{.City.Name}}:
56{{range .List}}Date & Time: {{.DtTxt}}
57Conditions:  {{range .Weather}}{{.Main}} {{.Description}}{{end}}
58Temp:        {{.Main.Temp}}
59High:        {{.Main.TempMax}}
60Low:         {{.Main.TempMin}}
61
62{{end}}
63`
64
65// Pointers to hold the contents of the flag args.
66var (
67	whereFlag = flag.String("w", "", "Location to get weather.  If location has a space, wrap the location in double quotes.")
68	unitFlag  = flag.String("u", "", "Unit of measure to display temps in")
69	langFlag  = flag.String("l", "", "Language to display temps in")
70	whenFlag  = flag.String("t", "current", "current | forecast")
71)
72
73// Data will hold the result of the query to get the IP
74// address of the caller.
75type Data struct {
76	Status      string  `json:"status"`
77	Country     string  `json:"country"`
78	CountryCode string  `json:"countryCode"`
79	Region      string  `json:"region"`
80	RegionName  string  `json:"regionName"`
81	City        string  `json:"city"`
82	Zip         string  `json:"zip"`
83	Lat         float64 `json:"lat"`
84	Lon         float64 `json:"lon"`
85	Timezone    string  `json:"timezone"`
86	ISP         string  `json:"isp"`
87	ORG         string  `json:"org"`
88	AS          string  `json:"as"`
89	Message     string  `json:"message"`
90	Query       string  `json:"query"`
91}
92
93// getLocation will get the location details for where this
94// application has been run from.
95func getLocation() (*Data, error) {
96	response, err := http.Get(URL)
97	if err != nil {
98		return nil, err
99	}
100	defer response.Body.Close()
101
102	result, err := ioutil.ReadAll(response.Body)
103	if err != nil {
104		return nil, err
105	}
106
107	r := &Data{}
108	if err = json.Unmarshal(result, &r); err != nil {
109		return nil, err
110	}
111	return r, nil
112}
113
114// getCurrent gets the current weather for the provided
115// location in the units provided.
116func getCurrent(location, units, lang string) (*owm.CurrentWeatherData, error) {
117	w, err := owm.NewCurrent(units, lang, os.Getenv("OWM_API_KEY"))
118	if err != nil {
119		return nil, err
120	}
121	w.CurrentByName(location)
122	return w, nil
123}
124func getForecast5(location, units, lang string) (*owm.Forecast5WeatherData, error) {
125	w, err := owm.NewForecast("5", units, lang, os.Getenv("OWM_API_KEY"))
126	if err != nil {
127		return nil, err
128	}
129	w.DailyByName(location, 5)
130	forecast := w.ForecastWeatherJson.(*owm.Forecast5WeatherData)
131	return forecast, err
132}
133
134func main() {
135	flag.Parse()
136
137	// If there's any funkiness with cli args, tuck and roll...
138	if len(*whereFlag) <= 1 || len(*unitFlag) != 1 || len(*langFlag) != 2 || len(*whenFlag) <= 1 {
139		flag.Usage()
140		os.Exit(1)
141	}
142
143	// Process request for location of "here"
144	if strings.ToLower(*whereFlag) == "here" {
145		loc, err := getLocation()
146		if err != nil {
147			log.Fatalln(err)
148		}
149		w, err := getCurrent(loc.City, *unitFlag, *langFlag)
150		if err != nil {
151			log.Fatalln(err)
152		}
153		tmpl, err := template.New("weather").Parse(weatherTemplate)
154		if err != nil {
155			log.Fatalln(err)
156		}
157
158		// Render the template and display
159		err = tmpl.Execute(os.Stdout, w)
160		if err != nil {
161			log.Fatalln(err)
162		}
163		os.Exit(0)
164	}
165
166	if *whenFlag == "current" {
167		// Process request for the given location
168		w, err := getCurrent(*whereFlag, *unitFlag, *langFlag)
169		if err != nil {
170			log.Fatalln(err)
171		}
172		tmpl, err := template.New("weather").Parse(weatherTemplate)
173		if err != nil {
174			log.Fatalln(err)
175		}
176		// Render the template and display
177		if err := tmpl.Execute(os.Stdout, w); err != nil {
178			log.Fatalln(err)
179		}
180	} else { //forecast
181		w, err := getForecast5(*whereFlag, *unitFlag, *langFlag)
182		if err != nil {
183			log.Fatalln(err)
184		}
185		tmpl, err := template.New("forecast").Parse(forecastTemplate)
186		if err != nil {
187			log.Fatalln(err)
188		}
189		// Render the template and display
190		if err := tmpl.Execute(os.Stdout, w); err != nil {
191			log.Fatalln(err)
192		}
193	}
194
195	os.Exit(0)
196}
197