File: string_pattern.go

package info (click to toggle)
golang-github-zenazn-goji 1.0.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 464 kB
  • sloc: makefile: 3
file content (137 lines) | stat: -rw-r--r-- 2,901 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package web

import (
	"fmt"
	"net/http"
	"regexp"
	"strings"
)

// stringPattern is a struct describing
type stringPattern struct {
	raw      string
	pats     []string
	breaks   []byte
	literals []string
	wildcard bool
}

func (s stringPattern) Prefix() string {
	return s.literals[0]
}
func (s stringPattern) Match(r *http.Request, c *C) bool {
	return s.match(r, c, true)
}
func (s stringPattern) Run(r *http.Request, c *C) {
	s.match(r, c, false)
}
func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool {
	path := r.URL.Path
	var matches map[string]string
	if !dryrun {
		if s.wildcard {
			matches = make(map[string]string, len(s.pats)+1)
		} else if len(s.pats) != 0 {
			matches = make(map[string]string, len(s.pats))
		}
	}
	for i, pat := range s.pats {
		sli := s.literals[i]
		if !strings.HasPrefix(path, sli) {
			return false
		}
		path = path[len(sli):]

		m := 0
		bc := s.breaks[i]
		for ; m < len(path); m++ {
			if path[m] == bc || path[m] == '/' {
				break
			}
		}
		if m == 0 {
			// Empty strings are not matches, otherwise routes like
			// "/:foo" would match the path "/"
			return false
		}
		if !dryrun {
			matches[pat] = path[:m]
		}
		path = path[m:]
	}
	// There's exactly one more literal than pat.
	tail := s.literals[len(s.pats)]
	if s.wildcard {
		if !strings.HasPrefix(path, tail) {
			return false
		}
		if !dryrun {
			matches["*"] = path[len(tail)-1:]
		}
	} else if path != tail {
		return false
	}

	if c == nil || dryrun {
		return true
	}

	if c.URLParams == nil {
		c.URLParams = matches
	} else {
		for k, v := range matches {
			c.URLParams[k] = v
		}
	}
	return true
}

func (s stringPattern) String() string {
	return fmt.Sprintf("stringPattern(%q)", s.raw)
}

func (s stringPattern) Raw() string {
	return s.raw
}

// "Break characters" are characters that can end patterns. They are not allowed
// to appear in pattern names. "/" was chosen because it is the standard path
// separator, and "." was chosen because it often delimits file extensions. ";"
// and "," were chosen because Section 3.3 of RFC 3986 suggests their use.
const bc = "/.;,"

var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`)

func parseStringPattern(s string) stringPattern {
	raw := s
	var wildcard bool
	if strings.HasSuffix(s, "/*") {
		s = s[:len(s)-1]
		wildcard = true
	}

	matches := patternRe.FindAllStringSubmatchIndex(s, -1)
	pats := make([]string, len(matches))
	breaks := make([]byte, len(matches))
	literals := make([]string, len(matches)+1)
	n := 0
	for i, match := range matches {
		a, b := match[2], match[3]
		literals[i] = s[n : a-1] // Need to leave off the colon
		pats[i] = s[a:b]
		if b == len(s) {
			breaks[i] = '/'
		} else {
			breaks[i] = s[b]
		}
		n = b
	}
	literals[len(matches)] = s[n:]
	return stringPattern{
		raw:      raw,
		pats:     pats,
		breaks:   breaks,
		literals: literals,
		wildcard: wildcard,
	}
}