github.com/influx6/npkg@v0.8.8/npattrn/pattern.go (about)

     1  // Package npattrn provides a simple regexp npattrn matching library majorly
     2  // for constructing URL matchers.
     3  //  Patterns in this package follow the follow approach in declaring custom match
     4  // segments.
     5  //
     6  // 		npattrn: /name/{id:[/\d+/]}/log/{date:[/\w+\W+/]}
     7  // 		npattrn: /name/:id
     8  //
     9  //
    10  package npattrn
    11  
    12  import (
    13  	"regexp"
    14  	"strings"
    15  )
    16  
    17  // Params defines a map of stringed keys and values.
    18  type Params map[string]string
    19  
    20  // Matchable defines an interface for matchers.
    21  type Matchable interface {
    22  	IsParam() bool
    23  	HasHash() bool
    24  	Segment() string
    25  	Validate(string) bool
    26  }
    27  
    28  // Matchers defines a list of machers for validating patterns with.
    29  type Matchers []Matchable
    30  
    31  // URIMatcher defines an interface for a URI matcher.
    32  type URIMatcher interface {
    33  	Validate(string) (Params, string, bool)
    34  	Pattern() string
    35  	Priority() int
    36  }
    37  
    38  // matchProvider provides a class array-path matcher
    39  type matchProvider struct {
    40  	pattern  string
    41  	matchers Matchers
    42  	endless  bool
    43  	priority int
    44  }
    45  
    46  // New returns a new instance of a URIMatcher.
    47  func New(pattern string) URIMatcher {
    48  	pattern = addSlash(pattern)
    49  
    50  	pm := SegmentList(pattern)
    51  
    52  	m := matchProvider{
    53  		priority: CheckPriority(pattern),
    54  		pattern:  pattern,
    55  		matchers: pm,
    56  		endless:  IsEndless(pattern),
    57  	}
    58  
    59  	return &m
    60  }
    61  
    62  // Priority returns the priority status of this giving npattrn.
    63  func (m *matchProvider) Priority() int {
    64  	return m.priority
    65  }
    66  
    67  // Pattern returns the npattrn string for this matcher.
    68  func (m *matchProvider) Pattern() string {
    69  	return m.pattern
    70  }
    71  
    72  // Validate returns true/false if the giving string matches the npattrn, returning
    73  // a map of parameters match against segments of the npattrn.
    74  func (m *matchProvider) Validate(path string) (Params, string, bool) {
    75  	path = addSlash(path)
    76  	var hashedSrc = stripAndCleanButHash(strings.Replace(path, "#", "/#", 1))
    77  	src := splitPattern(hashedSrc)
    78  
    79  	srclen := len(src)
    80  	total := len(m.matchers)
    81  
    82  	if !m.endless && total != srclen {
    83  		return nil, "", false
    84  	}
    85  
    86  	if m.endless && total > srclen {
    87  		return nil, "", false
    88  	}
    89  
    90  	var state bool
    91  
    92  	param := make(Params)
    93  
    94  	var lastIndex int
    95  	var doneHash bool
    96  
    97  	for index, v := range m.matchers {
    98  		lastIndex = index
    99  
   100  		if v.HasHash() {
   101  			doneHash = true
   102  		}
   103  
   104  		if v.Validate(src[index]) {
   105  
   106  			if v.IsParam() {
   107  				param[v.Segment()] = src[index]
   108  			}
   109  
   110  			state = true
   111  			continue
   112  		} else {
   113  			state = false
   114  			break
   115  		}
   116  	}
   117  
   118  	if lastIndex+1 == srclen {
   119  		return param, "", state
   120  	}
   121  
   122  	remPath := strings.Join(src[lastIndex+1:], "/")
   123  	if doneHash || !strings.Contains(hashedSrc, "#") {
   124  		return param, addSlash(remPath), state
   125  	}
   126  	return param, addSlash(remPath), state
   127  }
   128  
   129  //==============================================================================
   130  
   131  // SegmentList returns list of SegmentMatcher which implements the Matchable
   132  // interface, with each made of each segment of the npattrn.
   133  func SegmentList(pattern string) Matchers {
   134  	pattern = stripAndCleanButHash(pattern)
   135  
   136  	var set Matchers
   137  
   138  	if hashIndex := strings.Index(pattern, "#"); hashIndex != -1 {
   139  		if hashIndex == 0 {
   140  			pattern = strings.Join([]string{"/", pattern}, "")
   141  		} else {
   142  			last := pattern[hashIndex-1 : hashIndex]
   143  			if string(last[0]) != "/" {
   144  				splits := strings.Split(pattern, "#")
   145  				pattern = strings.Join([]string{splits[0], "/#", splits[1]}, "")
   146  			}
   147  		}
   148  	}
   149  
   150  	for _, val := range splitPattern(pattern) {
   151  		set = append(set, Segment(val))
   152  	}
   153  
   154  	return set
   155  }
   156  
   157  //==============================================================================
   158  
   159  // SegmentMatcher defines a single piece of npattrn to be matched against.
   160  type SegmentMatcher struct {
   161  	*regexp.Regexp
   162  	original string
   163  	param    bool
   164  	hashed   bool
   165  }
   166  
   167  // Segment returns a Matchable for a specific part of a npattrn eg. :name, age,
   168  // {id:[\\d+]}.
   169  func Segment(segment string) Matchable {
   170  	if segment == "*" {
   171  		segment = "/*"
   172  	}
   173  
   174  	hashed := strings.HasPrefix(segment, "#")
   175  	if hashed {
   176  		segment = segment[1:]
   177  	}
   178  
   179  	id, rx, b := YankSpecial(segment)
   180  	mrk := regexp.MustCompile(rx)
   181  
   182  	sm := SegmentMatcher{
   183  		Regexp:   mrk,
   184  		original: id,
   185  		param:    b,
   186  		hashed:   hashed,
   187  	}
   188  
   189  	return &sm
   190  }
   191  
   192  // HasHash returns true/false if this segment hash the hash.
   193  func (s *SegmentMatcher) HasHash() bool {
   194  	return s.hashed
   195  }
   196  
   197  // IsParam returns true/false if the segment is also a paramter.
   198  func (s *SegmentMatcher) IsParam() bool {
   199  	return s.param
   200  }
   201  
   202  // Segment returns the original string that makes up this segment matcher.
   203  func (s *SegmentMatcher) Segment() string {
   204  	return s.original
   205  }
   206  
   207  // Validate validates the value against the matcher.
   208  func (s *SegmentMatcher) Validate(m string) bool {
   209  	return s.MatchString(m)
   210  }
   211  
   212  //==============================================================================