storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/ellipses/ellipses.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2018 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package ellipses
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"regexp"
    23  	"strconv"
    24  	"strings"
    25  )
    26  
    27  var (
    28  	// Regex to extract ellipses syntax inputs.
    29  	regexpEllipses = regexp.MustCompile(`(.*)({[0-9a-z]*\.\.\.[0-9a-z]*})(.*)`)
    30  
    31  	// Ellipses constants
    32  	openBraces  = "{"
    33  	closeBraces = "}"
    34  	ellipses    = "..."
    35  )
    36  
    37  // Parses an ellipses range pattern of following style
    38  // `{1...64}`
    39  // `{33...64}`
    40  func parseEllipsesRange(pattern string) (seq []string, err error) {
    41  	if !strings.Contains(pattern, openBraces) {
    42  		return nil, errors.New("Invalid argument")
    43  	}
    44  	if !strings.Contains(pattern, closeBraces) {
    45  		return nil, errors.New("Invalid argument")
    46  	}
    47  
    48  	pattern = strings.TrimPrefix(pattern, openBraces)
    49  	pattern = strings.TrimSuffix(pattern, closeBraces)
    50  
    51  	ellipsesRange := strings.Split(pattern, ellipses)
    52  	if len(ellipsesRange) != 2 {
    53  		return nil, errors.New("Invalid argument")
    54  	}
    55  
    56  	var hexadecimal bool
    57  	var start, end uint64
    58  	if start, err = strconv.ParseUint(ellipsesRange[0], 10, 64); err != nil {
    59  		// Look for hexadecimal conversions if any.
    60  		start, err = strconv.ParseUint(ellipsesRange[0], 16, 64)
    61  		if err != nil {
    62  			return nil, err
    63  		}
    64  		hexadecimal = true
    65  	}
    66  
    67  	if end, err = strconv.ParseUint(ellipsesRange[1], 10, 64); err != nil {
    68  		// Look for hexadecimal conversions if any.
    69  		end, err = strconv.ParseUint(ellipsesRange[1], 16, 64)
    70  		if err != nil {
    71  			return nil, err
    72  		}
    73  		hexadecimal = true
    74  	}
    75  
    76  	if start > end {
    77  		return nil, fmt.Errorf("Incorrect range start %d cannot be bigger than end %d", start, end)
    78  	}
    79  
    80  	for i := start; i <= end; i++ {
    81  		if strings.HasPrefix(ellipsesRange[0], "0") && len(ellipsesRange[0]) > 1 || strings.HasPrefix(ellipsesRange[1], "0") {
    82  			if hexadecimal {
    83  				seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dx", len(ellipsesRange[1])), i))
    84  			} else {
    85  				seq = append(seq, fmt.Sprintf(fmt.Sprintf("%%0%dd", len(ellipsesRange[1])), i))
    86  			}
    87  		} else {
    88  			if hexadecimal {
    89  				seq = append(seq, fmt.Sprintf("%x", i))
    90  			} else {
    91  				seq = append(seq, fmt.Sprintf("%d", i))
    92  			}
    93  		}
    94  	}
    95  
    96  	return seq, nil
    97  }
    98  
    99  // Pattern - ellipses pattern, describes the range and also the
   100  // associated prefix and suffixes.
   101  type Pattern struct {
   102  	Prefix string
   103  	Suffix string
   104  	Seq    []string
   105  }
   106  
   107  // argExpander - recursively expands labels into its respective forms.
   108  func argExpander(labels [][]string) (out [][]string) {
   109  	if len(labels) == 1 {
   110  		for _, v := range labels[0] {
   111  			out = append(out, []string{v})
   112  		}
   113  		return out
   114  	}
   115  	for _, lbl := range labels[0] {
   116  		rs := argExpander(labels[1:])
   117  		for _, rlbls := range rs {
   118  			r := append(rlbls, []string{lbl}...)
   119  			out = append(out, r)
   120  		}
   121  	}
   122  	return out
   123  }
   124  
   125  // ArgPattern contains a list of patterns provided in the input.
   126  type ArgPattern []Pattern
   127  
   128  // Expand - expands all the ellipses patterns in
   129  // the given argument.
   130  func (a ArgPattern) Expand() [][]string {
   131  	labels := make([][]string, len(a))
   132  	for i := range labels {
   133  		labels[i] = a[i].Expand()
   134  	}
   135  	return argExpander(labels)
   136  }
   137  
   138  // Expand - expands a ellipses pattern.
   139  func (p Pattern) Expand() []string {
   140  	var labels []string
   141  	for i := range p.Seq {
   142  		switch {
   143  		case p.Prefix != "" && p.Suffix == "":
   144  			labels = append(labels, fmt.Sprintf("%s%s", p.Prefix, p.Seq[i]))
   145  		case p.Suffix != "" && p.Prefix == "":
   146  			labels = append(labels, fmt.Sprintf("%s%s", p.Seq[i], p.Suffix))
   147  		case p.Suffix == "" && p.Prefix == "":
   148  			labels = append(labels, p.Seq[i])
   149  		default:
   150  			labels = append(labels, fmt.Sprintf("%s%s%s", p.Prefix, p.Seq[i], p.Suffix))
   151  		}
   152  	}
   153  	return labels
   154  }
   155  
   156  // HasEllipses - returns true if input arg has ellipses type pattern.
   157  func HasEllipses(args ...string) bool {
   158  	var ok = true
   159  	for _, arg := range args {
   160  		ok = ok && (strings.Count(arg, ellipses) > 0 || (strings.Count(arg, openBraces) > 0 && strings.Count(arg, closeBraces) > 0))
   161  	}
   162  	return ok
   163  }
   164  
   165  // ErrInvalidEllipsesFormatFn error returned when invalid ellipses format is detected.
   166  var ErrInvalidEllipsesFormatFn = func(arg string) error {
   167  	return fmt.Errorf("Invalid ellipsis format in (%s), Ellipsis range must be provided in format {N...M} where N and M are positive integers, M must be greater than N,  with an allowed minimum range of 4", arg)
   168  }
   169  
   170  // FindEllipsesPatterns - finds all ellipses patterns, recursively and parses the ranges numerically.
   171  func FindEllipsesPatterns(arg string) (ArgPattern, error) {
   172  	var patterns []Pattern
   173  	parts := regexpEllipses.FindStringSubmatch(arg)
   174  	if len(parts) == 0 {
   175  		// We throw an error if arg doesn't have any recognizable ellipses pattern.
   176  		return nil, ErrInvalidEllipsesFormatFn(arg)
   177  	}
   178  
   179  	parts = parts[1:]
   180  	patternFound := regexpEllipses.MatchString(parts[0])
   181  	for patternFound {
   182  		seq, err := parseEllipsesRange(parts[1])
   183  		if err != nil {
   184  			return patterns, err
   185  		}
   186  		patterns = append(patterns, Pattern{
   187  			Prefix: "",
   188  			Suffix: parts[2],
   189  			Seq:    seq,
   190  		})
   191  		parts = regexpEllipses.FindStringSubmatch(parts[0])
   192  		if len(parts) > 0 {
   193  			parts = parts[1:]
   194  			patternFound = HasEllipses(parts[0])
   195  			continue
   196  		}
   197  		break
   198  	}
   199  
   200  	if len(parts) > 0 {
   201  		seq, err := parseEllipsesRange(parts[1])
   202  		if err != nil {
   203  			return patterns, err
   204  		}
   205  
   206  		patterns = append(patterns, Pattern{
   207  			Prefix: parts[0],
   208  			Suffix: parts[2],
   209  			Seq:    seq,
   210  		})
   211  	}
   212  
   213  	// Check if any of the prefix or suffixes now have flower braces
   214  	// left over, in such a case we generally think that there is
   215  	// perhaps a typo in users input and error out accordingly.
   216  	for _, pattern := range patterns {
   217  		if strings.Count(pattern.Prefix, openBraces) > 0 || strings.Count(pattern.Prefix, closeBraces) > 0 {
   218  			return nil, ErrInvalidEllipsesFormatFn(arg)
   219  		}
   220  		if strings.Count(pattern.Suffix, openBraces) > 0 || strings.Count(pattern.Suffix, closeBraces) > 0 {
   221  			return nil, ErrInvalidEllipsesFormatFn(arg)
   222  		}
   223  	}
   224  
   225  	return patterns, nil
   226  }