package globimport ()// TODO: Use native path separators instead of always using /.// PathInfo keeps a path resulting from glob expansion and its FileInfo. The// FileInfo is useful for efficiently determining if a given pathname satisfies// a particular constraint without doing an extra stat.typePathInfostruct {// The generated path, consistent with the original glob pattern. It cannot // be replaced by Info.Name(), which is just the final path component.PathstringInfoos.FileInfo}// Glob returns a list of file names satisfying the given pattern.func ( string, func(PathInfo) bool) bool {returnParse().Glob()}// Glob returns a list of file names satisfying the Pattern.func ( Pattern) ( func(PathInfo) bool) bool { := .Segments := ""// TODO(xiaq): This is a hack solely for supporting globs that start with // ~ (tilde) in the eval package.if .DirOverride != "" { = .DirOverride }iflen() > 0 && IsSlash([0]) { = [1:] += "/" } elseifruntime.GOOS == "windows" && len() > 1 && IsLiteral([0]) && IsSlash([1]) {// TODO: Handle UNC. := [0].(Literal).DataifisDrive() { = [2:] = + "/" } }returnglob(, , )}func ( string) bool {returnlen() == 2 && [1] == ':' && (('a' <= [0] && [1] <= 'z') || ('A' <= [0] && [0] <= 'Z'))}// glob finds all filenames matching the given Segments in the given dir, and// calls the callback on all of them. If the callback returns false, globbing is// interrupted, and glob returns false. Otherwise it returns true.func ( []Segment, string, func(PathInfo) bool) bool {// Consume non-wildcard path elements simply by following the path. This may // seem like an optimization, but is actually required for "." and ".." to // be used as path elements, as they do not appear in the result of ReadDir.forlen() > 1 && IsLiteral([0]) && IsSlash([1]) { := [0].(Literal).Data = [2:] += + "/"if , := os.Stat(); != nil || !.IsDir() {returntrue } }iflen() == 0 {if , := os.Stat(); == nil {return (PathInfo{, }) }returntrue } elseiflen() == 1 && IsLiteral([0]) { := + [0].(Literal).Dataif , := os.Stat(); == nil {return (PathInfo{, }) }returntrue } , := readDir()if != nil {// TODO(xiaq): Silently drop the error.returntrue } := -1// nexti moves i to the next index in segs that is either / or ** (in other // words, something that matches /). := func() {for ++; < len(); ++ {ifIsSlash([]) || IsWild1([], StarStar) {break } } } ()// Enumerate the position of the first slash. In the presence of multiple // **'s in the pattern, the first slash may be in any of those. // // For instance, in x**y**z, the first slash may be in the first ** or the // second: // 1) If it is in the first, then pattern is equivalent to x*/**y**z. We // match directories with x* and recurse in each subdirectory with the // pattern **y**z. // 2) If it is the in the second, we know that since the first ** can no // longer contain any slashes, we treat it as * (this is done in // matchElement). The pattern is now equivalent to x*y*/**z. We match // directories with x*y* and recurse in each subdirectory with the // pattern **z. // // The rules are: // 1) For each **, we treat it as */** and all previous ones as *. We match // subdirectories with the part before /, and recurse in subdirectories // with the pattern after /. // 2) If a literal / is encountered, we return after recursing in the // subdirectories.for < len() { := IsSlash([])var , []Segmentif {// segs = x/y. Match dir with x, recurse on y. , = [:], [+1:] } else {// segs = x**y. Match dir with x*, recurse on **y. , = [:+1], [:] }for , := range { := .Name()ifmatchElement(, ) && .IsDir() {if !(, ++"/", ) {returnfalse } } }if {// First slash cannot appear later than a slash in the pattern.returntrue } () }// If we reach here, it is possible to have no slashes at all. Simply match // the entire pattern with all files.for , := range { := .Name()ifmatchElement(, ) { := + , := os.Stat()if != nil {returntrue }if !(PathInfo{, }) {returnfalse } } }returntrue}// readDir is just like ioutil.ReadDir except that it treats an argument of ""// as ".".func ( string) ([]os.FileInfo, error) {if == "" { = "." }returnioutil.ReadDir()}// matchElement matches a path element against segments, which may not contain// any Slash segments. It treats StarStar segments as they are Star segments.func ( []Segment, string) bool {iflen() == 0 {return == "" }// If the name start with "." and the first segment is a Wild, only match // when MatchHidden is true.iflen() > 0 && [0] == '.' && IsWild([0]) && ![0].(Wild).MatchHidden {returnfalse }:forlen() > 0 {// Find a chunk. A chunk is an optional Star followed by a run of // fixed-length segments (Literal and Question).varintfor = 1; < len(); ++ {ifIsWild2([], Star, StarStar) {break } } := [:] := IsWild2([0], Star, StarStar)varWildif { = [0].(Wild) = [1:] } = [:]// NOTE A quick path when len(segs) == 0 can be implemented: match // backwards.// Match at the current position. If this is the last chunk, we need to // make sure name is exhausted by the matching. , := matchFixedLength(, )if && ( == "" || len() > 0) { = continue }if {// NOTE An optimization is to make the upper bound not len(names), // but rather len(names) - LB(# bytes segs can match)for , := range { := + len(string())// Match name[:j] with the starting *, and the rest with chunk.if !.Match() {break } , := matchFixedLength(, [:])if && ( == "" || len() > 0) { = continue } } }returnfalse }return == ""}// matchFixedLength returns whether a run of fixed-length segments (Literal and// Question) matches a prefix of name. It returns whether the match is// successful and if if it is, the remaining part of name.func ( []Segment, string) (bool, string) {for , := range {if == "" {returnfalse, "" }switch seg := .(type) {caseLiteral: := len(.Data)iflen() < || [:] != .Data {returnfalse, "" } = [:]caseWild:if .Type == Question { , := utf8.DecodeRuneInString()if !.Match() {returnfalse, "" } = [:] } else {panic("matchFixedLength given non-question wild segment") }default:panic("matchFixedLength given non-literal non-wild segment") } }returntrue, }
The pages are generated with Goldsv0.2.8-preview. (GOOS=darwin GOARCH=arm64)