Introduction #
In this series, we’re going to build a simple wttr client in golang. Wttr is a simple API that returns the weather
as a pretty string. For example calling curl https://wttr.in/honolulu
will return something like the following
Weather report: honolulu
\ / Partly cloudy
_ /"".-. +23(25) °C
\_( ). ↑ 11 km/h
/(___(__) 16 km
0.1 mm
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Fri 14 Feb ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ _`/"".-. Patchy rain ne…│ _`/"".-. Patchy rain ne…│ _`/"".-. Patchy rain ne…│ \ / Clear │
│ ,\_( ). +24(26) °C │ ,\_( ). +25(26) °C │ ,\_( ). +24(26) °C │ .-. +23(25) °C │
│ /(___(__) ↑ 12-16 km/h │ /(___(__) ↑ 11-13 km/h │ /(___(__) ← 3 km/h │ ― ( ) ― ↙ 9-14 km/h │
│ ‘ ‘ ‘ ‘ 10 km │ ‘ ‘ ‘ ‘ 10 km │ ‘ ‘ ‘ ‘ 10 km │ `-’ 10 km │
│ ‘ ‘ ‘ ‘ 0.0 mm | 72% │ ‘ ‘ ‘ ‘ 0.0 mm | 72% │ ‘ ‘ ‘ ‘ 0.1 mm | 100% │ / \ 0.0 mm | 0% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Sat 15 Feb ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ \ / Sunny │ \ / Sunny │ \ / Sunny │ \ / Clear │
│ .-. +24(25) °C │ .-. +25(26) °C │ .-. +24(26) °C │ .-. +23(25) °C │
│ ― ( ) ― ↙ 19-24 km/h │ ― ( ) ― ← 22-25 km/h │ ― ( ) ― ← 18-24 km/h │ ― ( ) ― ↙ 11-16 km/h │
│ `-’ 10 km │ `-’ 10 km │ `-’ 10 km │ `-’ 10 km │
│ / \ 0.0 mm | 0% │ / \ 0.0 mm | 0% │ / \ 0.0 mm | 0% │ / \ 0.0 mm | 0% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Sun 16 Feb ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ \ / Sunny │ _`/"".-. Patchy rain ne…│ _`/"".-. Light rain sho…│ _`/"".-. Patchy light d…│
│ .-. +24(25) °C │ ,\_( ). +25(27) °C │ ,\_( ). +24(26) °C │ ,\_( ). +24(26) °C │
│ ― ( ) ― ← 2-3 km/h │ /(___(__) ↑ 6-7 km/h │ /(___(__) ↑ 3-4 km/h │ /(___(__) ↖ 2-3 km/h │
│ `-’ 10 km │ ‘ ‘ ‘ ‘ 10 km │ ‘ ‘ ‘ ‘ 10 km │ ‘ ‘ ‘ ‘ 5 km │
│ / \ 0.0 mm | 0% │ ‘ ‘ ‘ ‘ 0.0 mm | 68% │ ‘ ‘ ‘ ‘ 0.6 mm | 100% │ ‘ ‘ ‘ ‘ 0.3 mm | 100% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
Location: Honolulu, Honolulu County, Hawaii, United States of America [21.304547,-157.8556763]
Follow @igor_chubin for wttr.in updates
To see all possible options call the help url with curl https://wttr.in/:help
. At the time of writing the following
options are available:
Usage:
$ curl wttr.in # current location
$ curl wttr.in/muc # weather in the Munich airport
Supported location types:
/paris # city name
/~Eiffel+tower # any location (+ for spaces)
/Москва # Unicode name of any location in any language
/muc # airport code (3 letters)
/@stackoverflow.com # domain name
/94107 # area codes
/-78.46,106.79 # GPS coordinates
Moon phase information:
/moon # Moon phase (add ,+US or ,+France for these cities)
/moon@2016-10-25 # Moon phase for the date (@2016-10-25)
Units:
m # metric (SI) (used by default everywhere except US)
u # USCS (used by default in US)
M # show wind speed in m/s
View options:
0 # only current weather
1 # current weather + today's forecast
2 # current weather + today's + tomorrow's forecast
A # ignore User-Agent and force ANSI output format (terminal)
d # restrict output to standard console font glyphs
F # do not show the "Follow" line
n # narrow version (only day and night)
q # quiet version (no "Weather report" text)
Q # superquiet version (no "Weather report", no city name)
T # switch terminal sequences off (no colors)
PNG options:
/paris.png # generate a PNG file
p # add frame around the output
t # transparency 150
transparency=... # transparency from 0 to 255 (255 = not transparent)
background=... # background color in form RRGGBB, e.g. 00aaaa
Options can be combined:
/Paris?0pq
/Paris?0pq&lang=fr
/Paris_0pq.png # in PNG the file mode are specified after _
/Rome_0pq_lang=it.png # long options are separated with underscore
Localization:
$ curl fr.wttr.in/Paris
$ curl wttr.in/paris?lang=fr
$ curl -H "Accept-Language: fr" wttr.in/paris
Supported languages:
am ar af be bn ca da de el et fr fa gl hi hu ia id it lt mg nb nl oc pl pt-br ro ru ta tr th uk vi zh-cn zh-tw (supported)
az bg bs cy cs eo es eu fi ga hi hr hy is ja jv ka kk ko ky lv mk ml mr nl fy nn pt pt-br sk sl sr sr-lat sv sw te uz zh zu he (in progress)
Special URLs:
/:help # show this page
/:bash.function # show recommended bash function wttr()
/:translation # show the information about the translators
In this post, I’ll go by the steps I took. The result should be available on GitHub.
First draft #
main.go #
Let’s keep things simple and output to the user what we actually know at the moment, absolutely nothing!
package main
import (
"fmt"
)
func main() {
fmt.Println("I have no idea")
}
main_test.go #
Of course, we have to test that I have no idea
is returned:
package main
import (
"bytes"
"github.com/stretchr/testify/assert"
"io"
"os"
"testing"
)
func TestCmdOutput(t *testing.T) {
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
main()
_ = w.Close()
os.Stdout = oldStdout
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
got := buf.String()
want := "I have no idea\n"
assert.Equal(t, want, got)
}
First, we use the os package to get stout data from the command. When we’re done we set os.Stdout
to the old one to prevent weirdness.
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
main()
_ = w.Close()
os.Stdout = oldStdout
Afterward, we convert the output to a string we can do a comparison with
var buf bytes.Buffer
_, _ = io.Copy(&buf, r)
got := buf.String()
For unit tests, I like to use testify as I personally don’t like rewriting the same assertion functions over and over again. I understand that there is some contention about this in the Go community, personally I choose not to care.
want := "I have no idea\n"
assert.Equal(t, want, got)
Source code #
This version of the code can be found here.
Proof of concept go wttr client