-
-
Notifications
You must be signed in to change notification settings - Fork 328
/
pattern.go
152 lines (147 loc) · 3.61 KB
/
pattern.go
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information
package syntax
import (
"bytes"
"fmt"
"regexp"
"strings"
)
func charClass(s string) (string, error) {
if strings.HasPrefix(s, "[[.") || strings.HasPrefix(s, "[[=") {
return "", fmt.Errorf("collating features not available")
}
if !strings.HasPrefix(s, "[[:") {
return "", nil
}
name := s[3:]
end := strings.Index(name, ":]]")
if end < 0 {
return "", fmt.Errorf("[[: was not matched with a closing :]]")
}
name = name[:end]
switch name {
case "alnum", "alpha", "ascii", "blank", "cntrl", "digit", "graph",
"lower", "print", "punct", "space", "upper", "word", "xdigit":
default:
return "", fmt.Errorf("invalid character class: %q", name)
}
return s[:len(name)+6], nil
}
// TranslatePattern turns a shell pattern expression into a regular
// expression that can be used with regexp.Compile. It will return an
// error if the input pattern was incorrect. Otherwise, the returned
// expression can be passed to regexp.MustCompile.
//
// For example, TranslatePattern(`foo*bar?`, true) returns `foo.*bar.`.
//
// Note that this function (and QuotePattern) should not be directly
// used with file paths if Windows is supported, as the path separator
// on that platform is the same character as the escaping character for
// shell patterns.
func TranslatePattern(pattern string, greedy bool) (string, error) {
any := false
loop:
for _, r := range pattern {
switch r {
// including those that need escaping since they are
// special chars in regexes
case '*', '?', '[', '\\', '.', '+', '(', ')', '|',
']', '{', '}', '^', '$':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pattern, nil
}
var buf bytes.Buffer
for i := 0; i < len(pattern); i++ {
switch c := pattern[i]; c {
case '*':
buf.WriteString(".*")
if !greedy {
buf.WriteByte('?')
}
case '?':
buf.WriteString(".")
case '\\':
if i++; i >= len(pattern) {
return "", fmt.Errorf(`\ at end of pattern`)
}
buf.WriteString(regexp.QuoteMeta(string(pattern[i])))
case '[':
name, err := charClass(pattern[i:])
if err != nil {
return "", err
}
if name != "" {
buf.WriteString(name)
i += len(name) - 1
break
}
buf.WriteByte(c)
if i++; i >= len(pattern) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
switch c = pattern[i]; c {
case '!', '^':
buf.WriteByte('^')
i++
c = pattern[i]
}
buf.WriteByte(c)
last := c
rangeStart := byte(0)
for {
if i++; i >= len(pattern) {
return "", fmt.Errorf("[ was not matched with a closing ]")
}
last, c = c, pattern[i]
buf.WriteByte(c)
if c == ']' {
break
}
if rangeStart != 0 && rangeStart > c {
return "", fmt.Errorf("invalid range: %c-%c", rangeStart, c)
}
if c == '-' {
rangeStart = last
} else {
rangeStart = 0
}
}
default:
buf.WriteString(regexp.QuoteMeta(string(c)))
}
}
return buf.String(), nil
}
// QuotePattern returns a string that quotes all special characters in
// the given pattern. The returned string is a pattern that matches the
// literal string.
//
// For example, QuotePattern(`foo*bar?`) returns `foo\*bar\?`.
func QuotePattern(pattern string) string {
any := false
loop:
for _, r := range pattern {
switch r {
case '*', '?', '[', '\\':
any = true
break loop
}
}
if !any { // short-cut without a string copy
return pattern
}
var buf bytes.Buffer
for _, r := range pattern {
switch r {
case '*', '?', '[', '\\':
buf.WriteByte('\\')
}
buf.WriteRune(r)
}
return buf.String()
}