go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/text/pattern/pattern.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     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  // Package pattern implements lightweight parsable string patterns.
    16  package pattern
    17  
    18  import (
    19  	"fmt"
    20  	"regexp"
    21  	"strings"
    22  )
    23  
    24  // Pattern can either match or not match a string.
    25  type Pattern interface {
    26  	// String returns the definition of the pattern parsable by Parse.
    27  	String() string
    28  	// Match returns true if s matches this pattern, otherwise false.
    29  	Match(s string) bool
    30  }
    31  
    32  // Parse parses a pattern.
    33  //
    34  // Ordered by precedence, s can be:
    35  //
    36  //   - "": matches nothing
    37  //   - "*": matches anything
    38  //   - "<S>" where S does not have a colon: same as "exact:<S>"
    39  //   - "exact:<S>": matches only string S
    40  //   - "text:<S>": same as "exact:<S>" for backward compatibility
    41  //   - "regex:<E>": matches all strings matching regular expression E. If E
    42  //     does not start/end with ^/$, they are added automatically.
    43  //
    44  // Anything else will cause an error.
    45  func Parse(s string) (Pattern, error) {
    46  	switch s {
    47  	case "":
    48  		return None, nil
    49  	case "*":
    50  		return Any, nil
    51  	}
    52  
    53  	parts := strings.SplitN(s, ":", 2)
    54  	if len(parts) < 2 {
    55  		return Exact(s), nil
    56  	}
    57  
    58  	kind, value := parts[0], parts[1]
    59  	switch kind {
    60  	case "exact", "text":
    61  		return Exact(value), nil
    62  	case "regex":
    63  		switch value {
    64  		case ".", ".*":
    65  			return Any, nil
    66  		case "^$":
    67  			return None, nil
    68  		default:
    69  			if !strings.HasPrefix(value, "^") {
    70  				value = "^" + value
    71  			}
    72  			if !strings.HasSuffix(value, "$") {
    73  				value = value + "$"
    74  			}
    75  			r, err := regexp.Compile(value)
    76  			if err != nil {
    77  				return nil, err
    78  			}
    79  			return Regexp(r), nil
    80  		}
    81  	default:
    82  		return nil, fmt.Errorf("unknown pattern kind: %q", kind)
    83  	}
    84  }
    85  
    86  // MustParse parses the pattern according to the specification
    87  // of Parse. In addition, it panics if there is an error in parsing the
    88  // given string as a pattern.
    89  //
    90  // See Parse for more details.
    91  func MustParse(s string) Pattern {
    92  	pattern, err := Parse(s)
    93  	if err != nil {
    94  		panic(err)
    95  	}
    96  	return pattern
    97  }