1 // Package compiler offers a regexp compiler which compiles regex templates to regexp.Regexp 2 // 3 // reg, err := compiler.CompileRegex("foo:bar.baz:<[0-9]{2,10}>", '<', '>') 4 // // if err != nil ... 5 // reg.MatchString("foo:bar.baz:123") 6 // 7 // reg, err := compiler.CompileRegex("/foo/bar/url/{[a-z]+}", '{', '}') 8 // // if err != nil ... 9 // reg.MatchString("/foo/bar/url/abz") 10 // 11 // This package is adapts github.com/gorilla/mux/regexp.go 12 13 package templatex 14 15 // Copyright 2012 The Gorilla Authors. All rights reserved. 16 // Use of this source code is governed by a BSD-style 17 // license as follows: 18 19 //Copyright (c) 2012 Rodrigo Moraes. All rights reserved. 20 // 21 //Redistribution and use in source and binary forms, with or without 22 //modification, are permitted provided that the following conditions are 23 //met: 24 // 25 //* Redistributions of source code must retain the above copyright 26 //notice, this list of conditions and the following disclaimer. 27 //* Redistributions in binary form must reproduce the above 28 //copyright notice, this list of conditions and the following disclaimer 29 //in the documentation and/or other materials provided with the 30 //distribution. 31 //* Neither the name of Google Inc. nor the names of its 32 //contributors may be used to endorse or promote products derived from 33 //this software without specific prior written permission. 34 // 35 //THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 //"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 //LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 //A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 //OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 //SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 //LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 //DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 //THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 //(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 //OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 46 47 import ( 48 "bytes" 49 "fmt" 50 "regexp" 51 52 "github.com/pkg/errors" 53 ) 54 55 // delimiterIndices returns the first level delimiter indices from a string. 56 // It returns an error in case of unbalanced delimiters. 57 func delimiterIndices(s string, delimiterStart, delimiterEnd byte) ([]int, error) { 58 var level, idx int 59 idxs := make([]int, 0) 60 for i := 0; i < len(s); i++ { 61 switch s[i] { 62 case delimiterStart: 63 if level++; level == 1 { 64 idx = i 65 } 66 case delimiterEnd: 67 if level--; level == 0 { 68 idxs = append(idxs, idx, i+1) 69 } else if level < 0 { 70 return nil, errors.Errorf("unbalanced braces in: %s", s) 71 } 72 } 73 } 74 75 if level != 0 { 76 return nil, errors.Errorf("unbalanced braces in: %s", s) 77 } 78 79 return idxs, nil 80 } 81 82 // CompileRegex parses a template and returns a Regexp. 83 // 84 // You can define your own delimiters. It is e.g. common to use curly braces {} but I recommend using characters 85 // which have no special meaning in Regex, e.g.: <, > 86 // 87 // reg, err := templatex.CompileRegex("foo:bar.baz:<[0-9]{2,10}>", '<', '>') 88 // // if err != nil ... 89 // reg.MatchString("foo:bar.baz:123") 90 func CompileRegex(tpl string, delimiterStart, delimiterEnd byte) (*regexp.Regexp, error) { 91 // Check if it is well-formed. 92 idxs, errBraces := delimiterIndices(tpl, delimiterStart, delimiterEnd) 93 if errBraces != nil { 94 return nil, errBraces 95 } 96 varsR := make([]*regexp.Regexp, len(idxs)/2) 97 pattern := bytes.NewBufferString("") 98 if err := pattern.WriteByte('^'); err != nil { 99 return nil, errors.WithStack(err) 100 } 101 102 var end int 103 var err error 104 for i := 0; i < len(idxs); i += 2 { 105 // Set all values we are interested in. 106 raw := tpl[end:idxs[i]] 107 end = idxs[i+1] 108 patt := tpl[idxs[i]+1 : end-1] 109 // Build the regexp pattern. 110 varIdx := i / 2 111 fmt.Fprintf(pattern, "%s(%s)", regexp.QuoteMeta(raw), patt) 112 varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) 113 if err != nil { 114 return nil, errors.WithStack(err) 115 } 116 } 117 118 // Add the remaining. 119 raw := tpl[end:] 120 if _, err := pattern.WriteString(regexp.QuoteMeta(raw)); err != nil { 121 return nil, errors.WithStack(err) 122 } 123 if err := pattern.WriteByte('$'); err != nil { 124 return nil, errors.WithStack(err) 125 } 126 127 // Compile full regexp. 128 reg, errCompile := regexp.Compile(pattern.String()) 129 if errCompile != nil { 130 return nil, errors.WithStack(errCompile) 131 } 132 133 return reg, nil 134 } 135