@@ -0,0 +1,106 @@
1+package config
2+3+import (
4+"errors"
5+"fmt"
6+"regexp"
7+"strconv"
8+"time"
9+10+"github.com/kr/pretty"
11+"github.com/zrepl/yaml-config"
12+)
13+14+type Duration struct{ d time.Duration }
15+16+func (d Duration) Duration() time.Duration { return d.d }
17+18+var _ yaml.Unmarshaler = &Duration{}
19+20+func (d *Duration) UnmarshalYAML(unmarshal func(v interface{}, not_strict bool) error) error {
21+var s string
22+err := unmarshal(&s, false)
23+if err != nil {
24+return err
25+ }
26+d.d, err = parseDuration(s)
27+if err != nil {
28+d.d = 0
29+return &yaml.TypeError{Errors: []string{fmt.Sprintf("cannot parse value %q: %s", s, err)}}
30+ }
31+return nil
32+}
33+34+type PositiveDuration struct{ d Duration }
35+36+var _ yaml.Unmarshaler = &PositiveDuration{}
37+38+func (d PositiveDuration) Duration() time.Duration { return d.d.Duration() }
39+40+func (d *PositiveDuration) UnmarshalYAML(unmarshal func(v interface{}, not_strict bool) error) error {
41+err := d.d.UnmarshalYAML(unmarshal)
42+if err != nil {
43+return err
44+ }
45+if d.d.Duration() <= 0 {
46+return fmt.Errorf("duration must be positive, got %s", d.d.Duration())
47+ }
48+return nil
49+}
50+51+func parsePositiveDuration(e string) (time.Duration, error) {
52+d, err := parseDuration(e)
53+if err != nil {
54+return d, err
55+ }
56+if d <= 0 {
57+return 0, errors.New("duration must be positive integer")
58+ }
59+return d, err
60+}
61+62+var durationStringRegex *regexp.Regexp = regexp.MustCompile(`^\s*([\+-]?\d+)\s*(|s|m|h|d|w)\s*$`)
63+64+func parseDuration(e string) (d time.Duration, err error) {
65+comps := durationStringRegex.FindStringSubmatch(e)
66+if comps == nil {
67+err = fmt.Errorf("must match %s", durationStringRegex)
68+return
69+ }
70+if len(comps) != 3 {
71+panic(pretty.Sprint(comps))
72+ }
73+74+durationFactor, err := strconv.ParseInt(comps[1], 10, 64)
75+if err != nil {
76+return 0, err
77+ }
78+79+var durationUnit time.Duration
80+switch comps[2] {
81+case "":
82+if durationFactor != 0 {
83+err = fmt.Errorf("missing time unit")
84+return
85+ } else {
86+// It's the case where user specified '0'.
87+// We want to allow this, just like time.ParseDuration.
88+ }
89+case "s":
90+durationUnit = time.Second
91+case "m":
92+durationUnit = time.Minute
93+case "h":
94+durationUnit = time.Hour
95+case "d":
96+durationUnit = 24 * time.Hour
97+case "w":
98+durationUnit = 24 * 7 * time.Hour
99+default:
100+err = fmt.Errorf("contains unknown time unit '%s'", comps[2])
101+return
102+ }
103+104+d = time.Duration(durationFactor) * durationUnit
105+return
106+}