Clean up matching/globbing code

getEsc, scanChunk and matchChunk were unused leftovers from the old
implementation, removed in 4f3e725e88.

isPathSeparator contained a useless cast to string.

The documentation (copied from path/filepath) referred to a Separator
constant, which is not present in the package.

Join, Split and Match are now explicitly marked as aliases for standard
library functions and refer to stdlib docs for details.
This commit is contained in:
greatroar 2021-03-12 13:42:32 +01:00
parent 5b7da38a9c
commit 413dd37571
1 changed files with 20 additions and 177 deletions

197
match.go
View File

@ -3,198 +3,39 @@ package sftp
import ( import (
"path" "path"
"strings" "strings"
"unicode/utf8"
) )
// ErrBadPattern indicates a globbing pattern was malformed. // ErrBadPattern indicates a globbing pattern was malformed.
var ErrBadPattern = path.ErrBadPattern var ErrBadPattern = path.ErrBadPattern
// Unix separator // Match reports whether name matches the shell pattern.
const separator = "/"
// Match reports whether name matches the shell file name pattern.
// The pattern syntax is:
//
// pattern:
// { term }
// term:
// '*' matches any sequence of non-Separator characters
// '?' matches any single non-Separator character
// '[' [ '^' ] { character-range } ']'
// character class (must be non-empty)
// c matches character c (c != '*', '?', '\\', '[')
// '\\' c matches character c
//
// character-range:
// c matches character c (c != '\\', '-', ']')
// '\\' c matches character c
// lo '-' hi matches character c for lo <= c <= hi
//
// Match requires pattern to match all of name, not just a substring.
// The only possible returned error is ErrBadPattern, when pattern
// is malformed.
//
// //
// This is an alias for path.Match from the standard library,
// offered so that callers need not import the path package.
// For details, see https://golang.org/pkg/path/#Match.
func Match(pattern, name string) (matched bool, err error) { func Match(pattern, name string) (matched bool, err error) {
return path.Match(pattern, name) return path.Match(pattern, name)
} }
// detect if byte(char) is path separator // detect if byte(char) is path separator
func isPathSeparator(c byte) bool { func isPathSeparator(c byte) bool {
return string(c) == "/" return c == '/'
} }
// scanChunk gets the next segment of pattern, which is a non-star string // Split splits the path p immediately following the final slash,
// possibly preceded by a star.
func scanChunk(pattern string) (star bool, chunk, rest string) {
for len(pattern) > 0 && pattern[0] == '*' {
pattern = pattern[1:]
star = true
}
inrange := false
var i int
Scan:
for i = 0; i < len(pattern); i++ {
switch pattern[i] {
case '\\':
// error check handled in matchChunk: bad pattern.
if i+1 < len(pattern) {
i++
}
case '[':
inrange = true
case ']':
inrange = false
case '*':
if !inrange {
break Scan
}
}
}
return star, pattern[0:i], pattern[i:]
}
// matchChunk checks whether chunk matches the beginning of s.
// If so, it returns the remainder of s (after the match).
// Chunk is all single-character operators: literals, char classes, and ?.
func matchChunk(chunk, s string) (rest string, ok bool, err error) {
for len(chunk) > 0 {
if len(s) == 0 {
return
}
switch chunk[0] {
case '[':
// character class
r, n := utf8.DecodeRuneInString(s)
s = s[n:]
chunk = chunk[1:]
// We can't end right after '[', we're expecting at least
// a closing bracket and possibly a caret.
if len(chunk) == 0 {
err = ErrBadPattern
return
}
// possibly negated
negated := chunk[0] == '^'
if negated {
chunk = chunk[1:]
}
// parse all ranges
match := false
nrange := 0
for {
if len(chunk) > 0 && chunk[0] == ']' && nrange > 0 {
chunk = chunk[1:]
break
}
var lo, hi rune
if lo, chunk, err = getEsc(chunk); err != nil {
return
}
hi = lo
if chunk[0] == '-' {
if hi, chunk, err = getEsc(chunk[1:]); err != nil {
return
}
}
if lo <= r && r <= hi {
match = true
}
nrange++
}
if match == negated {
return
}
case '?':
if isPathSeparator(s[0]) {
return
}
_, n := utf8.DecodeRuneInString(s)
s = s[n:]
chunk = chunk[1:]
case '\\':
chunk = chunk[1:]
if len(chunk) == 0 {
err = ErrBadPattern
return
}
fallthrough
default:
if chunk[0] != s[0] {
return
}
s = s[1:]
chunk = chunk[1:]
}
}
return s, true, nil
}
// getEsc gets a possibly-escaped character from chunk, for a character class.
func getEsc(chunk string) (r rune, nchunk string, err error) {
if len(chunk) == 0 || chunk[0] == '-' || chunk[0] == ']' {
err = ErrBadPattern
return
}
if chunk[0] == '\\' {
chunk = chunk[1:]
if len(chunk) == 0 {
err = ErrBadPattern
return
}
}
r, n := utf8.DecodeRuneInString(chunk)
if r == utf8.RuneError && n == 1 {
err = ErrBadPattern
}
nchunk = chunk[n:]
if len(nchunk) == 0 {
err = ErrBadPattern
}
return
}
// Split splits path immediately following the final Separator,
// separating it into a directory and file name component. // separating it into a directory and file name component.
// If there is no Separator in path, Split returns an empty dir //
// and file set to path. // This is an alias for path.Split from the standard library,
// The returned values have the property that path = dir+file. // offered so that callers need not import the path package.
func Split(path string) (dir, file string) { // For details, see https://golang.org/pkg/path/#Split.
i := len(path) - 1 func Split(p string) (dir, file string) {
for i >= 0 && !isPathSeparator(path[i]) { return path.Split(p)
i--
}
return path[:i+1], path[i+1:]
} }
// Glob returns the names of all files matching pattern or nil // Glob returns the names of all files matching pattern or nil
// if there is no matching file. The syntax of patterns is the same // if there is no matching file. The syntax of patterns is the same
// as in Match. The pattern may describe hierarchical names such as // as in Match. The pattern may describe hierarchical names such as
// /usr/*/bin/ed (assuming the Separator is '/'). // /usr/*/bin/ed.
// //
// Glob ignores file system errors such as I/O errors reading directories. // Glob ignores file system errors such as I/O errors reading directories.
// The only possible returned error is ErrBadPattern, when pattern // The only possible returned error is ErrBadPattern, when pattern
@ -241,8 +82,7 @@ func cleanGlobPath(path string) string {
switch path { switch path {
case "": case "":
return "." return "."
case string(separator): case "/":
// do nothing to the path
return path return path
default: default:
return path[0 : len(path)-1] // chop off trailing separator return path[0 : len(path)-1] // chop off trailing separator
@ -280,9 +120,12 @@ func (c *Client) glob(dir, pattern string, matches []string) (m []string, e erro
return return
} }
// Join joins any number of path elements into a single path, adding // Join joins any number of path elements into a single path, separating
// a Separator if necessary. // them with slashes.
// all empty strings are ignored. //
// This is an alias for path.Join from the standard library,
// offered so that callers need not import the path package.
// For details, see https://golang.org/pkg/path/#Join.
func Join(elem ...string) string { func Join(elem ...string) string {
return path.Join(elem...) return path.Join(elem...)
} }