Skip to content

Commit

Permalink
Switch to using cobra for CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
riley-martine committed Feb 18, 2023
1 parent 90fa264 commit e7c063f
Show file tree
Hide file tree
Showing 84 changed files with 14,047 additions and 172 deletions.
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
.DEFAULT_TARGET: all

GO_FILES := $(wildcard *.go)
GO_FILES := $(shell find . -type f -name '*.go')

all: static/cities.csv sundial
all: internal/core/cities.csv sundial

static/cities.csv: scripts/makecsv.sh scripts/trim_csv.py
internal/core/cities.csv: scripts/makecsv.sh scripts/trim_csv.py
scripts/makecsv.sh

sundial: $(GO_FILES) static/cities.csv
Expand Down
81 changes: 81 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package cmd

import (
"fmt"
"os"
"strings"
"time"

"github.com/riley-martine/sundial/internal/core"
"github.com/spf13/cobra"
)

// Set by goreleaser:
// https://goreleaser.com/cookbooks/using-main.version/?h=version
var version = "dev"

var (
debug bool
cityName string
countryCode string
fipsCode string
givenTime string
)

var rootCmd = &cobra.Command{
Use: "sundial --city CITY",
Short: "Print the percent through the day or night.",
Version: version,
Long: `Sundial is a program to print the percent through the day or night.
https://github.com/riley-martine/sundial`,
Run: func(cmd *cobra.Command, args []string) {
city, err := core.FindCity(cityName, countryCode, fipsCode)
if err != nil {
fmt.Fprintln(os.Stderr, err)
if strings.HasPrefix(err.Error(), "could not narrow") {
fmt.Fprintln(os.Stderr, "You may need to be more specific about which city you're in. Try specifying a country code (second field) and a fips code (third field).")
fmt.Fprintln(os.Stderr, " e.g. sundial -city Washington -country US -fipscode IL")
}
os.Exit(1)
}

// This probably doesn't need ParseInLocation
// Adding that data back to the cities CSV balloons the size
t := time.Now()
if givenTime != "" {
t, err = time.Parse(time.UnixDate, givenTime)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

out, err := core.GetPeriodPercent(city, t, debug)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println(out)
},
}

func init() {
rootCmd.Flags().BoolVar(&debug, "debug", false, "Print debug logging. Default: false")

rootCmd.Flags().StringVar(&cityName, "city", "", "Name of city you're in. Required.")
rootCmd.MarkFlagRequired("city")
rootCmd.Flags().StringVar(&countryCode, "country", "", "Two-letter country code, e.g. 'US'. Not required if only one city with name.")
rootCmd.Flags().StringVar(&fipsCode, "fipscode", "", `Fipscode of region you're in. In the US, this is the two-letter state abbreviation.
Otherwise, search http://download.geonames.org/export/dump/admin1CodesASCII.txt
for '$countryCode.' and select the value after the period for the region you're in.
Not required if only one city in country with name.`)

rootCmd.Flags().StringVar(&givenTime, "time", "", "Time to convert, in time.UnixDate format. Defaults to now.")
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
// fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
10 changes: 9 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,12 @@ module github.com/riley-martine/sundial

go 1.19

require github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5
require (
github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5
github.com/spf13/cobra v1.6.1
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)
11 changes: 11 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5 h1:ouekCqYkMw4QXFCaLyYqjBe99/MUW4Qf3DJhCRh1G18=
github.com/kelvins/sunrisesunset v0.0.0-20210220141756-39fa1bd816d5/go.mod h1:3oZ7G+fb8Z8KF+KPHxeDO3GWpEjgvk/f+d/yaxmDRT4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
File renamed without changes.
151 changes: 151 additions & 0 deletions internal/core/core.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package core

import (
"embed"
"encoding/csv"
"fmt"
"github.com/kelvins/sunrisesunset"
"io"
"strconv"
"time"
)

/*
What I want to display: percent through the apparent solar day we are
*/

//go:embed cities.csv
var fs embed.FS

type CityInfo struct {
Name string
CountryCode string
FipsCode string
Latitude float64
Longitude float64
}

func (c *CityInfo) String() string {
return fmt.Sprintf("{%s, %s, %s, %0.2f, %0.2f}", c.Name, c.CountryCode, c.FipsCode, c.Latitude, c.Longitude)
}

func (c *CityInfo) GetSunriseSunset(at time.Time) (sunrise time.Time, sunset time.Time, err error) {
_, secondsOffset := at.Zone()
hoursOffset := float64(secondsOffset) / 60 / 60
p := sunrisesunset.Parameters{
Latitude: c.Latitude,
Longitude: c.Longitude,
UtcOffset: hoursOffset,
Date: at,
}

return p.GetSunriseSunset()
}

func GetPeriodPercent(c *CityInfo, at time.Time, debug bool) (string, error) {
if debug {
fmt.Printf("City: %#v\n", c)
}

sunrise, sunset, err := c.GetSunriseSunset(at)
if err != nil {
return "", err
}

if debug {
fmt.Println("Sunrise:", sunrise.Format("15:04:05")) // Sunrise: 06:11:44
fmt.Println("Sunset:", sunset.Format("15:04:05")) // Sunset: 18:14:27
fmt.Println("Length of apparent solar time in mean solar time:", sunset.Sub(sunrise))
fmt.Println("Solar noon:", sunrise.Add(sunset.Sub(sunrise)/2).Format("15:04:05"))
}

dayDuration := sunset.Sub(sunrise)
if at.After(sunrise) && at.Before(sunset) {
hasPassed := at.Sub(sunrise)
if debug {
fmt.Println("Time passed since sunrise:", hasPassed)
}
fractionPassed := hasPassed.Seconds() / dayDuration.Seconds()
return fmt.Sprintf("%.0f%% ☉", fractionPassed*100), nil
}

// https://glossary.ametsoc.org/wiki/Mean_solar_day
meanDayDuration, err := time.ParseDuration("86400s")
if err != nil {
return "", err
}
nightDuration := meanDayDuration - dayDuration

if at.Before(sunrise) {
nightEnd := sunrise
nightStart := nightEnd.Add(-nightDuration)
hasPassedNight := at.Sub(nightStart)
fractionPassedNight := hasPassedNight.Seconds() / nightDuration.Seconds()
if debug {
fmt.Println("Time passed since sunset:", hasPassedNight)
}
return fmt.Sprintf("%.0f%% ☾", fractionPassedNight*100), nil
}

if at.After(sunset) {
nightStart := sunset
hasPassedNight := at.Sub(nightStart)
fractionPassedNight := hasPassedNight.Seconds() / nightDuration.Seconds()
if debug {
fmt.Println("Time passed since sunset:", hasPassedNight)
}
return fmt.Sprintf("%.0f%% ☾", fractionPassedNight*100), nil
}

panic("This should never happen")
}

func FindCity(name, countryCode, fipsCode string) (*CityInfo, error) {
// TODO cache successful runs and don't bother with opening this
file, err := fs.Open("cities.csv")
if err != nil {
return nil, err
}

r := csv.NewReader(file)
r.Comma = '\t'
r.ReuseRecord = true

cities := []*CityInfo{}
for {
record, err := r.Read()
if err == io.EOF {
break
} else if err != nil {
return nil, err
}

if record[0] != name {
continue
}

if countryCode != "" && record[1] != countryCode {
continue
}

if fipsCode != "" && record[2] != fipsCode {
continue
}
lat, err := strconv.ParseFloat(record[3], 64)
if err != nil {
return nil, err
}
long, err := strconv.ParseFloat(record[4], 64)
if err != nil {
return nil, err
}
cities = append(cities, &CityInfo{Name: record[0], CountryCode: record[1], FipsCode: record[2], Latitude: lat, Longitude: long})
}
if len(cities) == 0 {
return nil, fmt.Errorf("unable to find city '%s' in country '%s' with fips code '%s'", name, countryCode, fipsCode)
}
if len(cities) > 1 {
return nil, fmt.Errorf("could not narrow down between cities: %+v", cities)
}
return cities[0], nil
}
Loading

0 comments on commit e7c063f

Please sign in to comment.