Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

timestamp flag #987

Merged
merged 15 commits into from
Dec 20, 2019
4 changes: 2 additions & 2 deletions flag_float64.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ type Float64Flag struct {
Value float64
DefaultText string
Destination *float64
HasBeenSet bool
HasBeenSet bool
}

// IsSet returns whether or not the flag has been set through env or file
func (f *Float64Flag)IsSet() bool {
func (f *Float64Flag) IsSet() bool {
return f.HasBeenSet
}

Expand Down
55 changes: 55 additions & 0 deletions flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1678,3 +1678,58 @@ func TestInt64Slice_Serialized_Set(t *testing.T) {
t.Fatalf("pre and post serialization do not match: %v != %v", sl0, sl1)
}
}

func TestTimestamp_set(t *testing.T) {
ts := Timestamp{
timestamp: nil,
hasBeenSet: false,
layout: "Jan 2, 2006 at 3:04pm (MST)",
}

time1 := "Feb 3, 2013 at 7:54pm (PST)"
if err := ts.Set(time1); err != nil {
t.Fatalf("Failed to parse time %s with layout %s", time1, ts.layout)
}
if ts.hasBeenSet == false {
t.Fatalf("hasBeenSet is not true after setting a time")
}

ts.hasBeenSet = false
ts.SetLayout(time.RFC3339)
time2 := "2006-01-02T15:04:05Z"
if err := ts.Set(time2); err != nil {
t.Fatalf("Failed to parse time %s with layout %s", time2, ts.layout)
}
if ts.hasBeenSet == false {
t.Fatalf("hasBeenSet is not true after setting a time")
}
}

func TestTimestampFlagApply(t *testing.T) {
expectedResult, _ := time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: time.RFC3339}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)

err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
expect(t, err, nil)
expect(t, *fl.Value.timestamp, expectedResult)
}

func TestTimestampFlagApply_Fail_Parse_Wrong_Layout(t *testing.T) {
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: "randomlayout"}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)

err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"randomlayout\": cannot parse \"2006-01-02T15:04:05Z\" as \"randomlayout\""))
}

func TestTimestampFlagApply_Fail_Parse_Wrong_Time(t *testing.T) {
fl := TimestampFlag{Name: "time", Aliases: []string{"t"}, Layout: "Jan 2, 2006 at 3:04pm (MST)"}
set := flag.NewFlagSet("test", 0)
_ = fl.Apply(set)

err := set.Parse([]string{"--time", "2006-01-02T15:04:05Z"})
expect(t, err, fmt.Errorf("invalid value \"2006-01-02T15:04:05Z\" for flag -time: parsing time \"2006-01-02T15:04:05Z\" as \"Jan 2, 2006 at 3:04pm (MST)\": cannot parse \"2006-01-02T15:04:05Z\" as \"Jan\""))
}
152 changes: 152 additions & 0 deletions flag_timestamp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package cli

import (
"flag"
"fmt"
"time"
)

// Timestamp wrap to satisfy golang's flag interface.
type Timestamp struct {
timestamp *time.Time
hasBeenSet bool
layout string
}

// Timestamp constructor
func NewTimestamp(timestamp time.Time) *Timestamp {
return &Timestamp{timestamp: &timestamp}
}

drov0 marked this conversation as resolved.
Show resolved Hide resolved
// Set the timestamp value directly
func (t *Timestamp) SetTimestamp(value time.Time) {
if !t.hasBeenSet {
t.timestamp = &value
t.hasBeenSet = true
}
}

// Set the timestamp string layout for future parsing
func (t *Timestamp) SetLayout(layout string) {
t.layout = layout
}

// Parses the string value to timestamp
func (t *Timestamp) Set(value string) error {
timestamp, err := time.Parse(t.layout, value)
if err != nil {
return err
}

t.timestamp = &timestamp
t.hasBeenSet = true
return nil
}

// String returns a readable representation of this value (for usage defaults)
func (t *Timestamp) String() string {
return fmt.Sprintf("%#v", t.timestamp)
}

// Value returns the timestamp value stored in the flag
func (t *Timestamp) Value() *time.Time {
return t.timestamp
}

// Get returns the flag structure
func (t *Timestamp) Get() interface{} {
return *t
}

// TimestampFlag is a flag with type time
type TimestampFlag struct {
Name string
Aliases []string
Usage string
EnvVars []string
FilePath string
Required bool
Hidden bool
Layout string
Value *Timestamp
DefaultText string
HasBeenSet bool
}

// IsSet returns whether or not the flag has been set through env or file
func (f *TimestampFlag) IsSet() bool {
return f.HasBeenSet
}

// String returns a readable representation of this value
// (for usage defaults)
func (f *TimestampFlag) String() string {
return FlagStringer(f)
}

// Names returns the names of the flag
func (f *TimestampFlag) Names() []string {
return flagNames(f)
}

// IsRequired returns whether or not the flag is required
func (f *TimestampFlag) IsRequired() bool {
return f.Required
}

// TakesValue returns true of the flag takes a value, otherwise false
func (f *TimestampFlag) TakesValue() bool {
return true
}

// GetUsage returns the usage string for the flag
func (f *TimestampFlag) GetUsage() string {
return f.Usage
}

// GetValue returns the flags value as string representation and an empty
// string if the flag takes no value at all.
func (f *TimestampFlag) GetValue() string {
if f.Value != nil {
return f.Value.timestamp.String()
}
return ""
}

// Apply populates the flag given the flag set and environment
func (f *TimestampFlag) Apply(set *flag.FlagSet) error {
if f.Layout == "" {
return fmt.Errorf("timestamp Layout is required")
}
f.Value = &Timestamp{}
f.Value.SetLayout(f.Layout)

if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok {
if err := f.Value.Set(val); err != nil {
return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err)
}
f.HasBeenSet = true
}

for _, name := range f.Names() {
set.Var(f.Value, name, f.Usage)
}
return nil
}

// Timestamp gets the timestamp from a flag name
func (c *Context) Timestamp(name string) *time.Time {
if fs := lookupFlagSet(name, c); fs != nil {
return lookupTimestamp(name, fs)
}
return nil
}

// Fetches the timestamp value from the local timestampWrap
func lookupTimestamp(name string, set *flag.FlagSet) *time.Time {
f := set.Lookup(name)
if f != nil {
return (f.Value.(*Timestamp)).Value()
}
return nil
}