456 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			456 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package doublestar
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"unicode/utf8"
 | 
						|
)
 | 
						|
 | 
						|
var ErrBadPattern = path.ErrBadPattern
 | 
						|
 | 
						|
// Split a path on the given separator, respecting escaping.
 | 
						|
func splitPathOnSeparator(path string, separator rune) []string {
 | 
						|
	// if the separator is '\\', then we can just split...
 | 
						|
	if separator == '\\' {
 | 
						|
		return strings.Split(path, string(separator))
 | 
						|
	}
 | 
						|
 | 
						|
	// otherwise, we need to be careful of situations where the separator was escaped
 | 
						|
	cnt := strings.Count(path, string(separator))
 | 
						|
	if cnt == 0 {
 | 
						|
		return []string{path}
 | 
						|
	}
 | 
						|
	ret := make([]string, cnt+1)
 | 
						|
	pathlen := len(path)
 | 
						|
	separatorLen := utf8.RuneLen(separator)
 | 
						|
	idx := 0
 | 
						|
	for start := 0; start < pathlen; {
 | 
						|
		end := indexRuneWithEscaping(path[start:], separator)
 | 
						|
		if end == -1 {
 | 
						|
			end = pathlen
 | 
						|
		} else {
 | 
						|
			end += start
 | 
						|
		}
 | 
						|
		ret[idx] = path[start:end]
 | 
						|
		start = end + separatorLen
 | 
						|
		idx++
 | 
						|
	}
 | 
						|
	return ret[:idx]
 | 
						|
}
 | 
						|
 | 
						|
// Find the first index of a rune in a string,
 | 
						|
// ignoring any times the rune is escaped using "\".
 | 
						|
func indexRuneWithEscaping(s string, r rune) int {
 | 
						|
	end := strings.IndexRune(s, r)
 | 
						|
	if end == -1 {
 | 
						|
		return -1
 | 
						|
	}
 | 
						|
	if end > 0 && s[end-1] == '\\' {
 | 
						|
		start := end + utf8.RuneLen(r)
 | 
						|
		end = indexRuneWithEscaping(s[start:], r)
 | 
						|
		if end != -1 {
 | 
						|
			end += start
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return end
 | 
						|
}
 | 
						|
 | 
						|
// Match returns true if name matches the shell file name pattern.
 | 
						|
// The pattern syntax is:
 | 
						|
//
 | 
						|
//  pattern:
 | 
						|
//    { term }
 | 
						|
//  term:
 | 
						|
//    '*'         matches any sequence of non-path-separators
 | 
						|
//              '**'        matches any sequence of characters, including
 | 
						|
//                          path separators.
 | 
						|
//    '?'         matches any single non-path-separator character
 | 
						|
//    '[' [ '^' ] { character-range } ']'
 | 
						|
//          character class (must be non-empty)
 | 
						|
//    '{' { term } [ ',' { term } ... ] '}'
 | 
						|
//    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 path-separator defaults to the '/' character. The only possible
 | 
						|
// returned error is ErrBadPattern, when pattern is malformed.
 | 
						|
//
 | 
						|
// Note: this is meant as a drop-in replacement for path.Match() which
 | 
						|
// always uses '/' as the path separator. If you want to support systems
 | 
						|
// which use a different path separator (such as Windows), what you want
 | 
						|
// is the PathMatch() function below.
 | 
						|
//
 | 
						|
func Match(pattern, name string) (bool, error) {
 | 
						|
	return matchWithSeparator(pattern, name, '/')
 | 
						|
}
 | 
						|
 | 
						|
// PathMatch is like Match except that it uses your system's path separator.
 | 
						|
// For most systems, this will be '/'. However, for Windows, it would be '\\'.
 | 
						|
// Note that for systems where the path separator is '\\', escaping is
 | 
						|
// disabled.
 | 
						|
//
 | 
						|
// Note: this is meant as a drop-in replacement for filepath.Match().
 | 
						|
//
 | 
						|
func PathMatch(pattern, name string) (bool, error) {
 | 
						|
	return matchWithSeparator(pattern, name, os.PathSeparator)
 | 
						|
}
 | 
						|
 | 
						|
// Match returns true if name matches the shell file name pattern.
 | 
						|
// The pattern syntax is:
 | 
						|
//
 | 
						|
//  pattern:
 | 
						|
//    { term }
 | 
						|
//  term:
 | 
						|
//    '*'         matches any sequence of non-path-separators
 | 
						|
//              '**'        matches any sequence of characters, including
 | 
						|
//                          path separators.
 | 
						|
//    '?'         matches any single non-path-separator character
 | 
						|
//    '[' [ '^' ] { character-range } ']'
 | 
						|
//          character class (must be non-empty)
 | 
						|
//    '{' { term } [ ',' { term } ... ] '}'
 | 
						|
//    c           matches character c (c != '*', '?', '\\', '[')
 | 
						|
//    '\\' c      matches character c
 | 
						|
//
 | 
						|
//  character-range:
 | 
						|
//    c           matches character c (c != '\\', '-', ']')
 | 
						|
//    '\\' c      matches character c, unless separator is '\\'
 | 
						|
//    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.
 | 
						|
//
 | 
						|
func matchWithSeparator(pattern, name string, separator rune) (bool, error) {
 | 
						|
	patternComponents := splitPathOnSeparator(pattern, separator)
 | 
						|
	nameComponents := splitPathOnSeparator(name, separator)
 | 
						|
	return doMatching(patternComponents, nameComponents)
 | 
						|
}
 | 
						|
 | 
						|
func doMatching(patternComponents, nameComponents []string) (matched bool, err error) {
 | 
						|
	// check for some base-cases
 | 
						|
	patternLen, nameLen := len(patternComponents), len(nameComponents)
 | 
						|
	if patternLen == 0 && nameLen == 0 {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
	if patternLen == 0 || nameLen == 0 {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	patIdx, nameIdx := 0, 0
 | 
						|
	for patIdx < patternLen && nameIdx < nameLen {
 | 
						|
		if patternComponents[patIdx] == "**" {
 | 
						|
			// if our last pattern component is a doublestar, we're done -
 | 
						|
			// doublestar will match any remaining name components, if any.
 | 
						|
			if patIdx++; patIdx >= patternLen {
 | 
						|
				return true, nil
 | 
						|
			}
 | 
						|
 | 
						|
			// otherwise, try matching remaining components
 | 
						|
			for ; nameIdx < nameLen; nameIdx++ {
 | 
						|
				if m, _ := doMatching(patternComponents[patIdx:], nameComponents[nameIdx:]); m {
 | 
						|
					return true, nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return false, nil
 | 
						|
		} else {
 | 
						|
			// try matching components
 | 
						|
			matched, err = matchComponent(patternComponents[patIdx], nameComponents[nameIdx])
 | 
						|
			if !matched || err != nil {
 | 
						|
				return
 | 
						|
			}
 | 
						|
		}
 | 
						|
		patIdx++
 | 
						|
		nameIdx++
 | 
						|
	}
 | 
						|
	return patIdx >= patternLen && nameIdx >= nameLen, nil
 | 
						|
}
 | 
						|
 | 
						|
// Glob returns the names of all files matching pattern or nil
 | 
						|
// if there is no matching file. The syntax of pattern is the same
 | 
						|
// as in Match. The pattern may describe hierarchical names such as
 | 
						|
// /usr/*/bin/ed (assuming the Separator is '/').
 | 
						|
//
 | 
						|
// Glob ignores file system errors such as I/O errors reading directories.
 | 
						|
// The only possible returned error is ErrBadPattern, when pattern
 | 
						|
// is malformed.
 | 
						|
//
 | 
						|
// Your system path separator is automatically used. This means on
 | 
						|
// systems where the separator is '\\' (Windows), escaping will be
 | 
						|
// disabled.
 | 
						|
//
 | 
						|
// Note: this is meant as a drop-in replacement for filepath.Glob().
 | 
						|
//
 | 
						|
func Glob(pattern string) (matches []string, err error) {
 | 
						|
	patternComponents := splitPathOnSeparator(filepath.ToSlash(pattern), '/')
 | 
						|
	if len(patternComponents) == 0 {
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// On Windows systems, this will return the drive name ('C:'), on others,
 | 
						|
	// it will return an empty string.
 | 
						|
	volumeName := filepath.VolumeName(pattern)
 | 
						|
 | 
						|
	// If the first pattern component is equal to the volume name, then the
 | 
						|
	// pattern is an absolute path.
 | 
						|
	if patternComponents[0] == volumeName {
 | 
						|
		return doGlob(fmt.Sprintf("%s%s", volumeName, string(os.PathSeparator)), patternComponents[1:], matches)
 | 
						|
	}
 | 
						|
 | 
						|
	// otherwise, it's a relative pattern
 | 
						|
	return doGlob(".", patternComponents, matches)
 | 
						|
}
 | 
						|
 | 
						|
// Perform a glob
 | 
						|
func doGlob(basedir string, components, matches []string) (m []string, e error) {
 | 
						|
	m = matches
 | 
						|
	e = nil
 | 
						|
 | 
						|
	// figure out how many components we don't need to glob because they're
 | 
						|
	// just names without patterns - we'll use os.Lstat below to check if that
 | 
						|
	// path actually exists
 | 
						|
	patLen := len(components)
 | 
						|
	patIdx := 0
 | 
						|
	for ; patIdx < patLen; patIdx++ {
 | 
						|
		if strings.IndexAny(components[patIdx], "*?[{\\") >= 0 {
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if patIdx > 0 {
 | 
						|
		basedir = filepath.Join(basedir, filepath.Join(components[0:patIdx]...))
 | 
						|
	}
 | 
						|
 | 
						|
	// Lstat will return an error if the file/directory doesn't exist
 | 
						|
	fi, err := os.Lstat(basedir)
 | 
						|
	if err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// if there are no more components, we've found a match
 | 
						|
	if patIdx >= patLen {
 | 
						|
		m = append(m, basedir)
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// otherwise, we need to check each item in the directory...
 | 
						|
	// first, if basedir is a symlink, follow it...
 | 
						|
	if (fi.Mode() & os.ModeSymlink) != 0 {
 | 
						|
		fi, err = os.Stat(basedir)
 | 
						|
		if err != nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// confirm it's a directory...
 | 
						|
	if !fi.IsDir() {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// read directory
 | 
						|
	dir, err := os.Open(basedir)
 | 
						|
	if err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
	defer dir.Close()
 | 
						|
 | 
						|
	files, _ := dir.Readdir(-1)
 | 
						|
	lastComponent := (patIdx + 1) >= patLen
 | 
						|
	if components[patIdx] == "**" {
 | 
						|
		// if the current component is a doublestar, we'll try depth-first
 | 
						|
		for _, file := range files {
 | 
						|
			// if symlink, we may want to follow
 | 
						|
			if (file.Mode() & os.ModeSymlink) != 0 {
 | 
						|
				file, err = os.Stat(filepath.Join(basedir, file.Name()))
 | 
						|
				if err != nil {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if file.IsDir() {
 | 
						|
				// recurse into directories
 | 
						|
				if lastComponent {
 | 
						|
					m = append(m, filepath.Join(basedir, file.Name()))
 | 
						|
				}
 | 
						|
				m, e = doGlob(filepath.Join(basedir, file.Name()), components[patIdx:], m)
 | 
						|
			} else if lastComponent {
 | 
						|
				// if the pattern's last component is a doublestar, we match filenames, too
 | 
						|
				m = append(m, filepath.Join(basedir, file.Name()))
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if lastComponent {
 | 
						|
			return // we're done
 | 
						|
		}
 | 
						|
		patIdx++
 | 
						|
		lastComponent = (patIdx + 1) >= patLen
 | 
						|
	}
 | 
						|
 | 
						|
	// check items in current directory and recurse
 | 
						|
	var match bool
 | 
						|
	for _, file := range files {
 | 
						|
		match, e = matchComponent(components[patIdx], file.Name())
 | 
						|
		if e != nil {
 | 
						|
			return
 | 
						|
		}
 | 
						|
		if match {
 | 
						|
			if lastComponent {
 | 
						|
				m = append(m, filepath.Join(basedir, file.Name()))
 | 
						|
			} else {
 | 
						|
				m, e = doGlob(filepath.Join(basedir, file.Name()), components[patIdx+1:], m)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// Attempt to match a single pattern component with a path component
 | 
						|
func matchComponent(pattern, name string) (bool, error) {
 | 
						|
	// check some base cases
 | 
						|
	patternLen, nameLen := len(pattern), len(name)
 | 
						|
	if patternLen == 0 && nameLen == 0 {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
	if patternLen == 0 {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
	if nameLen == 0 && pattern != "*" {
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// check for matches one rune at a time
 | 
						|
	patIdx, nameIdx := 0, 0
 | 
						|
	for patIdx < patternLen && nameIdx < nameLen {
 | 
						|
		patRune, patAdj := utf8.DecodeRuneInString(pattern[patIdx:])
 | 
						|
		nameRune, nameAdj := utf8.DecodeRuneInString(name[nameIdx:])
 | 
						|
		if patRune == '\\' {
 | 
						|
			// handle escaped runes
 | 
						|
			patIdx += patAdj
 | 
						|
			patRune, patAdj = utf8.DecodeRuneInString(pattern[patIdx:])
 | 
						|
			if patRune == utf8.RuneError {
 | 
						|
				return false, ErrBadPattern
 | 
						|
			} else if patRune == nameRune {
 | 
						|
				patIdx += patAdj
 | 
						|
				nameIdx += nameAdj
 | 
						|
			} else {
 | 
						|
				return false, nil
 | 
						|
			}
 | 
						|
		} else if patRune == '*' {
 | 
						|
			// handle stars
 | 
						|
			if patIdx += patAdj; patIdx >= patternLen {
 | 
						|
				// a star at the end of a pattern will always
 | 
						|
				// match the rest of the path
 | 
						|
				return true, nil
 | 
						|
			}
 | 
						|
 | 
						|
			// check if we can make any matches
 | 
						|
			for ; nameIdx < nameLen; nameIdx += nameAdj {
 | 
						|
				if m, _ := matchComponent(pattern[patIdx:], name[nameIdx:]); m {
 | 
						|
					return true, nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return false, nil
 | 
						|
		} else if patRune == '[' {
 | 
						|
			// handle character sets
 | 
						|
			patIdx += patAdj
 | 
						|
			endClass := indexRuneWithEscaping(pattern[patIdx:], ']')
 | 
						|
			if endClass == -1 {
 | 
						|
				return false, ErrBadPattern
 | 
						|
			}
 | 
						|
			endClass += patIdx
 | 
						|
			classRunes := []rune(pattern[patIdx:endClass])
 | 
						|
			classRunesLen := len(classRunes)
 | 
						|
			if classRunesLen > 0 {
 | 
						|
				classIdx := 0
 | 
						|
				matchClass := false
 | 
						|
				if classRunes[0] == '^' {
 | 
						|
					classIdx++
 | 
						|
				}
 | 
						|
				for classIdx < classRunesLen {
 | 
						|
					low := classRunes[classIdx]
 | 
						|
					if low == '-' {
 | 
						|
						return false, ErrBadPattern
 | 
						|
					}
 | 
						|
					classIdx++
 | 
						|
					if low == '\\' {
 | 
						|
						if classIdx < classRunesLen {
 | 
						|
							low = classRunes[classIdx]
 | 
						|
							classIdx++
 | 
						|
						} else {
 | 
						|
							return false, ErrBadPattern
 | 
						|
						}
 | 
						|
					}
 | 
						|
					high := low
 | 
						|
					if classIdx < classRunesLen && classRunes[classIdx] == '-' {
 | 
						|
						// we have a range of runes
 | 
						|
						if classIdx++; classIdx >= classRunesLen {
 | 
						|
							return false, ErrBadPattern
 | 
						|
						}
 | 
						|
						high = classRunes[classIdx]
 | 
						|
						if high == '-' {
 | 
						|
							return false, ErrBadPattern
 | 
						|
						}
 | 
						|
						classIdx++
 | 
						|
						if high == '\\' {
 | 
						|
							if classIdx < classRunesLen {
 | 
						|
								high = classRunes[classIdx]
 | 
						|
								classIdx++
 | 
						|
							} else {
 | 
						|
								return false, ErrBadPattern
 | 
						|
							}
 | 
						|
						}
 | 
						|
					}
 | 
						|
					if low <= nameRune && nameRune <= high {
 | 
						|
						matchClass = true
 | 
						|
					}
 | 
						|
				}
 | 
						|
				if matchClass == (classRunes[0] == '^') {
 | 
						|
					return false, nil
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				return false, ErrBadPattern
 | 
						|
			}
 | 
						|
			patIdx = endClass + 1
 | 
						|
			nameIdx += nameAdj
 | 
						|
		} else if patRune == '{' {
 | 
						|
			// handle alternatives such as {alt1,alt2,...}
 | 
						|
			patIdx += patAdj
 | 
						|
			endOptions := indexRuneWithEscaping(pattern[patIdx:], '}')
 | 
						|
			if endOptions == -1 {
 | 
						|
				return false, ErrBadPattern
 | 
						|
			}
 | 
						|
			endOptions += patIdx
 | 
						|
			options := splitPathOnSeparator(pattern[patIdx:endOptions], ',')
 | 
						|
			patIdx = endOptions + 1
 | 
						|
			for _, o := range options {
 | 
						|
				m, e := matchComponent(o+pattern[patIdx:], name[nameIdx:])
 | 
						|
				if e != nil {
 | 
						|
					return false, e
 | 
						|
				}
 | 
						|
				if m {
 | 
						|
					return true, nil
 | 
						|
				}
 | 
						|
			}
 | 
						|
			return false, nil
 | 
						|
		} else if patRune == '?' || patRune == nameRune {
 | 
						|
			// handle single-rune wildcard
 | 
						|
			patIdx += patAdj
 | 
						|
			nameIdx += nameAdj
 | 
						|
		} else {
 | 
						|
			return false, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if patIdx >= patternLen && nameIdx >= nameLen {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
	if nameIdx >= nameLen && pattern[patIdx:] == "*" || pattern[patIdx:] == "**" {
 | 
						|
		return true, nil
 | 
						|
	}
 | 
						|
	return false, nil
 | 
						|
}
 |