Files
ana-ledger/cmd/cli/flag.go
Bel LaPointe ca365ad39c
All checks were successful
cicd / ci (push) Successful in 1m51s
can pass duration to period
2026-03-04 08:53:02 -07:00

147 lines
3.3 KiB
Go

package cli
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
"time"
)
type FlagStringArray []string
func (array *FlagStringArray) String() string {
return strings.Join(*array, ", ")
}
func (array *FlagStringArray) Set(s string) error {
*array = append(*array, s)
return nil
}
type FileList FlagStringArray
func (fileList FileList) Strings() []string {
return ([]string)(fileList)
}
func (fileList *FileList) String() string {
return (*FlagStringArray)(fileList).String()
}
func (fileList *FileList) Set(s string) error {
if _, err := os.Stat(s); os.IsNotExist(err) {
return fmt.Errorf("file does not exist: %s", s)
}
return (*FlagStringArray)(fileList).Set(s)
}
type Period struct {
Start time.Time
Stop time.Time
}
func (period Period) Empty() bool {
return period.Stop.Sub(period.Start) == 0
}
func (period *Period) String() string {
return fmt.Sprintf("%s..%s", period.Start, period.Stop)
}
func (period *Period) Set(s string) error {
ss := strings.Split(s, "..")
if err := period.setStartStop(ss[0]); err != nil {
return err
}
if len(ss) != 2 {
} else if err := period.setStop(ss[1]); err != nil {
return err
}
return nil
}
func (period *Period) setStartStop(s string) error {
stop, err := period.setT(time.Now(), s, &period.Start)
period.Stop = stop
return err
}
func (period *Period) setStop(s string) error {
_, err := period.setT(time.Now(), s, &period.Stop)
return err
}
func (*Period) setT(now time.Time, s string, t *time.Time) (time.Time, error) {
if s == "" {
*t = time.Unix(0, 0)
return time.Now().AddDate(100, 0, 0), nil
}
if submatches := regexp.MustCompile(`[+-]?(?P<years>[0-9]+y)?(?P<months>[0-9]+mo)?(?P<weeks>[0-9]+w)?(?P<days>[0-9]+d)?([0-9]+[a-z])?`).FindStringSubmatch(s); len(submatches) > 0 {
s2 := s
result := now
scalar := time.Duration(1)
if strings.HasPrefix(s2, "-") {
scalar = -1
}
for i, submatch := range submatches[1 : len(submatches)-1] {
if len(submatch) == 0 {
continue
}
unit := submatch[len(submatch)-1]
if submatch[len(submatch)-1] == 'o' { // mo
submatch = submatch[:len(submatch)-1]
}
n, err := strconv.Atoi(submatch[:len(submatch)-1])
if err != nil {
return time.Time{}, err
}
for i := 0; i < n; i++ {
switch unit {
case 'y':
result = result.AddDate(int(scalar*1), 0, 0)
case 'o':
result = result.AddDate(0, int(scalar*1), 0)
case 'w':
result = result.AddDate(0, 0, int(scalar*7))
case 'd':
result = result.AddDate(0, 0, int(scalar*1))
}
}
s2 = strings.ReplaceAll(s2, submatches[i+1], "")
}
if stdDuration := strings.TrimLeft(s2, "+-"); stdDuration != "" {
if d, err := time.ParseDuration(stdDuration); err == nil {
result = result.Add(scalar * d)
}
}
if result != now {
*t = result
return result.AddDate(100, 0, 0), nil
}
}
if result, err := time.Parse("2006", s); err == nil {
*t = result
return result.AddDate(1, 0, 0).Add(-1 * time.Minute), nil
}
if result, err := time.Parse("2006-01", s); err == nil {
*t = result
return result.AddDate(0, 1, 0).Add(-1 * time.Minute), nil
}
if result, err := time.Parse("2006-01-02", s); err == nil {
*t = result
return result.AddDate(0, 0, 1).Add(-1 * time.Minute), nil
}
return time.Time{}, fmt.Errorf("unimplemented format: %s", s)
}