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  }