aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Makefile14
-rw-r--r--cmd/metar.go223
-rw-r--r--cmd/root.go23
-rw-r--r--go.mod7
-rw-r--r--go.sum36
-rw-r--r--main.go21
7 files changed, 303 insertions, 22 deletions
diff --git a/.gitignore b/.gitignore
index 3086b1d..0286619 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
wxcli
+wx
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b9a7829
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+SOURCES=$(wildcard *.go) $(wildcard */*.go)
+NAME=wx
+BINDIR=/usr/bin
+
+$(NAME): $(SOURCES)
+ go build -o $(NAME) .
+
+.PHONY: clean
+clean:
+ rm -f $(NAME)
+
+.PHONY: install
+install:
+ install -m755 $(NAME) $(BINDIR)
diff --git a/cmd/metar.go b/cmd/metar.go
new file mode 100644
index 0000000..6355105
--- /dev/null
+++ b/cmd/metar.go
@@ -0,0 +1,223 @@
+package cmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "strings"
+ "time"
+
+ "bnbl.io/wx/metar"
+ "github.com/spf13/cobra"
+)
+
+// wx metar --past 1h kbos
+// wx metar --past 3h kbos kbed
+// wx metar --past 1h @MA
+// wx metar --since yesterday --until now kbos
+// wx metar --since yesterday --until now --rect 0,0,1,1
+// wx metar --past 1h --rect 0,0,1,1
+// wx metar --past 1h --radial 90,0,0
+// wx metar --past 6h --most-recent-by-station=constraint --path-dist 50 --waypoint ksea --waypoint kbos --min-dist 3
+
+type relTime struct {
+ t time.Time
+}
+
+func (t *relTime) String() string {
+ return t.t.String()
+}
+
+func (t *relTime) Set(s string) (err error) {
+ // first, see if s is a duration that we can use relative to now, e.g.
+ // "--since 3d" means since 3 days ago.
+ if d, err := time.ParseDuration(s); err == nil {
+ t.t = time.Now().Add(-1 * d)
+ return nil
+ }
+
+ // next, try to parse it in a series of formats
+ t.t, err = time.Parse("2006-01-02T15:04:05", s)
+ return
+}
+
+func (t *relTime) Type() string {
+ return "relative time"
+}
+
+type rectangle struct {
+ minLat, minLon, maxLat, maxLon float64
+}
+
+func (r *rectangle) String() string {
+ return fmt.Sprintf("%f,%f,%f,%f", r.minLat, r.minLon, r.maxLat, r.maxLon)
+}
+
+func (r *rectangle) Set(s string) error {
+ n, err := fmt.Sscanf(s, "%f,%f,%f,%f", &r.minLat, &r.minLon, &r.maxLat, &r.maxLon)
+ if err != nil {
+ return err
+ }
+ if n != 4 {
+ return fmt.Errorf("excpected 4 values but got %d", n)
+ }
+ return nil
+}
+
+func (r *rectangle) Type() string {
+ return "rect"
+}
+
+func (r *rectangle) IsZero() bool {
+ return r.minLat == 0 && r.minLon == 0 && r.maxLat == 0 && r.maxLon == 0
+}
+
+type ring struct {
+ radius, lat, lon float64
+}
+
+func (r *ring) String() string {
+ return fmt.Sprintf("%f,%f,%f", r.radius, r.lat, r.lon)
+}
+
+func (r *ring) Set(s string) error {
+ n, err := fmt.Sscanf(s, "%f,%f,%f", &r.radius, &r.lat, &r.lon)
+ if err != nil {
+ return err
+ }
+ if n != 3 {
+ return fmt.Errorf("expected a radius, lat, and lon, but got %d values", n)
+ }
+ return nil
+}
+
+func (r *ring) Type() string {
+ return "radial"
+}
+
+func (r *ring) IsZero() bool {
+ return r.radius == 0 && r.lat == 0 && r.lon == 0
+}
+
+var (
+ since, until relTime
+ past time.Duration
+ mostRecentByStation string
+ mostRecent bool
+ rect rectangle
+ radial ring
+ pathDist float64
+ waypoints []string
+ minDist float64
+)
+
+func init() {
+ rootCmd.AddCommand(metarCmd)
+
+ // time constraints
+ metarCmd.Flags().Var(
+ &since, "since",
+ `Starting time for METARs (e.g. "2020-01-01T01:31:00" or "3h"), used in
+conjunction with --until`)
+ metarCmd.Flags().Var(
+ &until, "until",
+ `Ending time for METARs (e.g. "2020-01-01T01:31:00" or "3h"), used in
+conjunction with --since`)
+ metarCmd.Flags().DurationVar(
+ &past, "past", 0,
+ `Duration to retrieve METARs since (e.g. "3h")`)
+
+ // location constraints
+ metarCmd.Flags().Var(
+ &rect, "rect",
+ `A lat-lon rect to search within (minLat,minLon,maxLat,maxLon) e.g.
+"40,-71,41,-70"`)
+ metarCmd.Flags().Var(
+ &radial, "radial",
+ `A distance around a radius to search within (dist,centerLat,centerLon)
+e.g. "3.5,40,-71"`)
+ metarCmd.Flags().Float64Var(
+ &pathDist, "path-dist", 0,
+ "The distance along the flight path to search METARs for")
+ metarCmd.Flags().StringSliceVar(
+ &waypoints, "waypoint", []string{},
+ `Multiple waypoint arguments specify the waypoints that make up a flight
+path, ordered from origin to destination. They may be specified as ICAO
+identifiers e.g. "KBOS", or as lon-lat pairs, e.g. "-71,40".`)
+ metarCmd.Flags().Float64Var(
+ &minDist, "min-dist", 0,
+ `The minimum distance in degrees latitude or longitude between stations
+reported. A higher value results in less dense results.`)
+
+ // result count limiting
+ metarCmd.Flags().StringVar(
+ &mostRecentByStation, "most-recent-by-station", "constraint",
+ `Method for fetching most recent for each station ("constraint",
+"postfilter", "false")`)
+ metarCmd.Flags().BoolVarP(
+ &mostRecent, "one", "1", false,
+ `Only fetch the single most recently-issued METAR of all the selected
+stations`)
+}
+
+var metarCmd = &cobra.Command{
+ Use: "metar",
+ Short: "Fetch METARs",
+ Long: "Fetch METARs matching the supplied criteria from the TDS",
+ Run: func(cmd *cobra.Command, args []string) {
+ reqArgs := []func(*metar.Request){}
+
+ if len(args) > 0 {
+ reqArgs = append(reqArgs, metar.StationString(strings.Join(args, " ")))
+ }
+
+ if !since.t.IsZero() || !until.t.IsZero() {
+ reqArgs = append(reqArgs, metar.TimeRange(since.t, until.t))
+ }
+
+ dur, _ := cmd.Flags().GetDuration("past")
+ if dur > 0 {
+ reqArgs = append(reqArgs, metar.HoursBeforeNow(dur.Hours()))
+ }
+
+ if mostRecentByStation != "" {
+ reqArgs = append(reqArgs, metar.MostRecentForEachStation(mostRecentByStation))
+ }
+
+ if mostRecent {
+ reqArgs = append(reqArgs, metar.MostRecent(true))
+ }
+
+ if !rect.IsZero() {
+ reqArgs = append(reqArgs, metar.LonLatRect(rect.minLat, rect.minLon, rect.maxLat, rect.maxLon))
+ }
+
+ if !radial.IsZero() {
+ reqArgs = append(reqArgs, metar.RadialDistance(radial.radius, radial.lat, radial.lon))
+ }
+
+ if pathDist > 0 && len(waypoints) > 0 {
+ reqArgs = append(reqArgs, metar.FlightPath(pathDist, waypoints...))
+ }
+
+ if minDist > 0 {
+ reqArgs = append(reqArgs, metar.MinDegreeDistance(minDist))
+ }
+
+ var mc metar.Client
+ res, err := mc.GetMETARs(context.Background(), metar.NewRequest(reqArgs...))
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "could not get metar: %v\n", err)
+ os.Exit(1)
+ }
+ if len(res.Errors) > 0 {
+ for _, err := range res.Errors {
+ fmt.Fprintf(os.Stderr, "error: %s\n", err)
+ }
+ os.Exit(2)
+ }
+ for _, m := range res.Data.METARs {
+ fmt.Printf("%s\n", strings.TrimSpace(m.RawText))
+ }
+ },
+}
diff --git a/cmd/root.go b/cmd/root.go
new file mode 100644
index 0000000..09660f0
--- /dev/null
+++ b/cmd/root.go
@@ -0,0 +1,23 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+)
+
+func Execute() {
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+}
+
+var rootCmd = &cobra.Command{
+ Use: "wx",
+ Short: "wx is a front-end for NOAA's Aviation Weather Center",
+ Long: `A front-end for NOAA's Aviation Weather Center.
+Capable of fetching weather information from the Center's Text Data Server
+(TDS) and displaying it in various formats.`,
+}
diff --git a/go.mod b/go.mod
index efffbca..66e8c2c 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,9 @@ module bnbl.io/wxcli
go 1.13
-require bnbl.io/wx v0.1.0
+require (
+ bnbl.io/wx v0.1.1
+ github.com/spf13/cobra v0.0.5
+)
+
+// replace bnbl.io/wx => ../wx
diff --git a/go.sum b/go.sum
index 5bc1432..c831e52 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,34 @@
-bnbl.io/wx v0.1.0 h1:FNrthzkLcsbqupDmcBNil3cd+KA3TRmo+6fkjbXac3Y=
-bnbl.io/wx v0.1.0/go.mod h1:0oU9MUdR+IKQRitmtKTLG1bVQvaI00iVvmzqWlIhi0c=
+bnbl.io/wx v0.1.1 h1:iY8XIY+z5rWCTY/rWNyCDpKpOrvA/jVKiyBNt7GKq+Q=
+bnbl.io/wx v0.1.1/go.mod h1:0oU9MUdR+IKQRitmtKTLG1bVQvaI00iVvmzqWlIhi0c=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
+github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/main.go b/main.go
index fe8637f..4802d4c 100644
--- a/main.go
+++ b/main.go
@@ -1,26 +1,9 @@
package main
import (
- "context"
- "fmt"
- "strings"
-
- "bnbl.io/wx/metar"
+ "bnbl.io/wxcli/cmd"
)
func main() {
- var mc metar.Client
- req := metar.NewRequest(
- metar.StationString("KBOS KBED KOWD KORH"),
- metar.HoursBeforeNow(4.0),
- metar.MostRecentForEachStation(metar.MostRecentConstraint),
- )
- res, err := mc.GetMETARs(context.Background(), req)
- if err != nil {
- fmt.Printf("could not get metar: %v\n", err)
- return
- }
- for _, m := range res.Data.METARs {
- fmt.Printf("%s\n", strings.TrimSpace(m.RawText))
- }
+ cmd.Execute()
}