github.com/nats-io/nsc/v2@v2.8.7-0.20240307184528-efd7023c6896/cmd/timeparams.go (about) 1 /* 2 * Copyright 2018-2023 The NATS Authors 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package cmd 17 18 import ( 19 "fmt" 20 "regexp" 21 "strconv" 22 "strings" 23 "time" 24 25 cli "github.com/nats-io/cliprompts/v2" 26 "github.com/spf13/cobra" 27 ) 28 29 type TimeParams struct { 30 Start string 31 Expiry string 32 } 33 34 func (p *TimeParams) BindFlags(cmd *cobra.Command) { 35 cmd.Flags().StringVarP(&p.Start, "start", "", "", "valid from ('0' is always, '3d' is three days) - yyyy-mm-dd, #m(inutes), #h(ours), #d(ays), #w(eeks), #M(onths), #y(ears)") 36 cmd.Flags().StringVarP(&p.Expiry, "expiry", "", "", "valid until ('0' is always, '2M' is two months) - yyyy-mm-dd, #m(inutes), #h(ours), #d(ays), #w(eeks), #M(onths), #y(ears)") 37 } 38 39 func (p *TimeParams) valid(value string, label string, oldOK bool) error { 40 now := time.Now().Unix() 41 when, err := ParseExpiry(value) 42 if err != nil { 43 return fmt.Errorf("%s %q is invalid: %v", label, value, err) 44 } 45 if InteractiveFlag && !oldOK && when != 0 && now > when { 46 m := fmt.Sprintf("%s %q is in the past (%s) - are you sure?", label, value, HumanizedDate(when)) 47 ok, err := cli.Confirm(m, false) 48 if err != nil { 49 return err 50 } 51 if !ok { 52 return fmt.Errorf("%s %q is in the past (%s)", label, value, HumanizedDate(when)) 53 } 54 } 55 return nil 56 } 57 58 func (p *TimeParams) ValidateStart() error { 59 if err := p.valid(p.Start, "start", true); err != nil { 60 return err 61 } 62 return nil 63 } 64 65 func (p *TimeParams) ValidateExpiry() error { 66 if err := p.valid(p.Expiry, "expiry", false); err != nil { 67 return err 68 } 69 return nil 70 } 71 72 func (p *TimeParams) IsStartChanged() bool { 73 return p.Start != "" 74 } 75 76 func (p *TimeParams) IsExpiryChanged() bool { 77 return p.Expiry != "" 78 } 79 80 func (p *TimeParams) Validate() error { 81 if err := p.ValidateStart(); err != nil { 82 return err 83 } 84 if err := p.ValidateExpiry(); err != nil { 85 return err 86 } 87 return nil 88 } 89 90 func (p *TimeParams) canParse(s string) error { 91 _, err := ParseExpiry(s) 92 if err != nil { 93 return fmt.Errorf("%s is invalid: %v", s, err) 94 } 95 return nil 96 } 97 98 func (p *TimeParams) Edit() error { 99 var err error 100 format := "valid from ('0' is always) - yyyy-mm-dd, #m(inutes), #h(ours), #d(ays), #w(eeks), #M(onths), #y(ears)" 101 p.Start, err = cli.Prompt("valid", p.Start, cli.Val(p.canParse), cli.Help(format)) 102 if err != nil { 103 return err 104 } 105 106 p.Expiry, err = cli.Prompt("valid until (0 is always)", p.Expiry, cli.Val(p.canParse), cli.Help(format)) 107 return err 108 } 109 110 func (p *TimeParams) StartDate() (int64, error) { 111 return ParseExpiry(p.Start) 112 } 113 114 func (p *TimeParams) ExpiryDate() (int64, error) { 115 return ParseExpiry(p.Expiry) 116 } 117 118 // parse expiration argument - supported are YYYY-MM-DD for absolute, and relative 119 // (m)inute, (h)our, (d)ay, (w)week, (M)onth, (y)ear expressions 120 func ParseExpiry(s string) (int64, error) { 121 if s == "" || s == "0" { 122 return 0, nil 123 } 124 if strings.Contains(s, ".") { 125 return 0, fmt.Errorf("start/expiry must be an integer value: %v", s) 126 } 127 // try to parse directly 128 t, err := time.Parse("2006-01-02 15:04:05 UTC", s) 129 if err == nil { 130 return t.Unix(), nil 131 } 132 133 re := regexp.MustCompile(`(\d){4}-(\d){2}-(\d){2}`) 134 if re.MatchString(s) { 135 t, err := time.Parse("2006-01-02", s) 136 if err != nil { 137 return 0, err 138 } 139 return t.Unix(), nil 140 } 141 142 re = regexp.MustCompile(`(?P<count>-?\d+)(?P<qualifier>[mhdMyw])`) 143 m := re.FindStringSubmatch(s) 144 if m != nil { 145 v, err := strconv.ParseInt(m[1], 10, 64) 146 if err != nil { 147 return 0, err 148 } 149 count := int(v) 150 if count == 0 { 151 return 0, nil 152 } 153 dur := time.Duration(count) 154 now := time.Now() 155 switch m[2] { 156 case "m": 157 return now.Add(dur * time.Minute).Unix(), nil 158 case "h": 159 return now.Add(dur * time.Hour).Unix(), nil 160 case "d": 161 return now.AddDate(0, 0, count).Unix(), nil 162 case "w": 163 return now.AddDate(0, 0, 7*count).Unix(), nil 164 case "M": 165 return now.AddDate(0, count, 0).Unix(), nil 166 case "y": 167 return now.AddDate(count, 0, 0).Unix(), nil 168 default: 169 return 0, fmt.Errorf("unknown interval %q in %q", m[2], m[0]) 170 } 171 } 172 return 0, fmt.Errorf("couldn't parse expiry: %v", s) 173 }