From f691a743b0defe80e8824b3f12c98e50fc08de64 Mon Sep 17 00:00:00 2001 From: Danny Kopping Date: Thu, 20 Nov 2025 10:57:13 +0200 Subject: [PATCH] feat: support days/weeks in serpent.Duration Signed-off-by: Danny Kopping --- go.mod | 1 + go.sum | 2 + values.go | 12 ++++- values_test.go | 143 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 values_test.go diff --git a/go.mod b/go.mod index 1c2880c..c27e47b 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/pion/udp v0.1.4 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.8.4 + github.com/xhit/go-str2duration/v2 v2.1.0 golang.org/x/crypto v0.19.0 golang.org/x/exp v0.0.0-20240213143201-ec583247a57a golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 diff --git a/go.sum b/go.sum index a1106fc..0f3edc4 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs= go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= diff --git a/values.go b/values.go index 79c8e2c..aa98bec 100644 --- a/values.go +++ b/values.go @@ -13,6 +13,7 @@ import ( "time" "github.com/spf13/pflag" + str2duration "github.com/xhit/go-str2duration/v2" "golang.org/x/xerrors" "gopkg.in/yaml.v3" ) @@ -262,7 +263,16 @@ func DurationOf(d *time.Duration) *Duration { } func (d *Duration) Set(v string) error { - dd, err := time.ParseDuration(v) + // Try [str2duration.ParseDuration] first, which supports days and weeks. + // If it fails, fall back to [time.ParseDuration] for backward compatibility. + dd, err := str2duration.ParseDuration(v) + if err == nil { + *d = Duration(dd) + return nil + } + + // Fallback to standard [time.ParseDuration]. + dd, err = time.ParseDuration(v) *d = Duration(dd) return err } diff --git a/values_test.go b/values_test.go new file mode 100644 index 0000000..a67aac4 --- /dev/null +++ b/values_test.go @@ -0,0 +1,143 @@ +package serpent_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/require" + + serpent "github.com/coder/serpent" +) + +func TestDuration(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input string + expected time.Duration + wantErr bool + }{ + // Standard time.Duration formats (should still work) + { + name: "Nanoseconds", + input: "100ns", + expected: 100 * time.Nanosecond, + }, + { + name: "Microseconds", + input: "100us", + expected: 100 * time.Microsecond, + }, + { + name: "Milliseconds", + input: "100ms", + expected: 100 * time.Millisecond, + }, + { + name: "Seconds", + input: "30s", + expected: 30 * time.Second, + }, + { + name: "Minutes", + input: "5m", + expected: 5 * time.Minute, + }, + { + name: "Hours", + input: "2h", + expected: 2 * time.Hour, + }, + { + name: "Combined", + input: "1h30m", + expected: 90 * time.Minute, + }, + // New formats with days and weeks support + { + name: "Days", + input: "1d", + expected: 24 * time.Hour, + }, + { + name: "MultipleDays", + input: "7d", + expected: 7 * 24 * time.Hour, + }, + { + name: "Weeks", + input: "1w", + expected: 7 * 24 * time.Hour, + }, + { + name: "MultipleWeeks", + input: "2w", + expected: 14 * 24 * time.Hour, + }, + { + name: "CombinedWithDays", + input: "1d12h", + expected: 36 * time.Hour, + }, + { + name: "CombinedWithWeeks", + input: "1w2d", + expected: (7 + 2) * 24 * time.Hour, + }, + { + name: "ComplexCombination", + input: "2w3d4h5m6s", + expected: (14 + 3) * 24 * time.Hour + 4*time.Hour + 5*time.Minute + 6*time.Second, + }, + // Error cases + { + name: "Invalid", + input: "invalid", + wantErr: true, + }, + { + name: "Empty", + input: "", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var d serpent.Duration + err := d.Set(tt.input) + + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expected, d.Value()) + + // Verify String() returns a parseable value + str := d.String() + var d2 serpent.Duration + err = d2.Set(str) + require.NoError(t, err) + require.Equal(t, d.Value(), d2.Value(), "String() should return a parseable value") + }) + } +} + +func TestDurationOf(t *testing.T) { + t.Parallel() + + td := 5 * time.Minute + d := serpent.DurationOf(&td) + require.NotNil(t, d) + require.Equal(t, td, d.Value()) + + // Test modification through pointer + newVal := 10 * time.Minute + *d = serpent.Duration(newVal) + require.Equal(t, newVal, time.Duration(td)) +}