gitee.com/mirrors_u-root/u-root@v7.0.0+incompatible/cmds/core/elvish/glob/glob.go (about)

     1  // Package glob implements globbing for elvish.
     2  package glob
     3  
     4  import (
     5  	"io/ioutil"
     6  	"os"
     7  	"runtime"
     8  	"unicode/utf8"
     9  )
    10  
    11  // TODO: Use native path separators instead of always using /.
    12  
    13  // Glob returns a list of file names satisfying the given pattern.
    14  func Glob(p string, cb func(string) bool) bool {
    15  	return Parse(p).Glob(cb)
    16  }
    17  
    18  // Glob returns a list of file names satisfying the Pattern.
    19  func (p Pattern) Glob(cb func(string) bool) bool {
    20  	segs := p.Segments
    21  	dir := ""
    22  
    23  	// XXX: This is a hack solely for supporting globs that start with ~ in the
    24  	// eval package.
    25  	if p.DirOverride != "" {
    26  		dir = p.DirOverride
    27  	}
    28  
    29  	if len(segs) > 0 && IsSlash(segs[0]) {
    30  		segs = segs[1:]
    31  		dir += "/"
    32  	} else if runtime.GOOS == "windows" && len(segs) > 1 && IsLiteral(segs[0]) && IsSlash(segs[1]) {
    33  		// TODO: Handle UNC.
    34  		elem := segs[0].(Literal).Data
    35  		if isDrive(elem) {
    36  			segs = segs[2:]
    37  			dir = elem + "/"
    38  		}
    39  	}
    40  
    41  	return glob(segs, dir, cb)
    42  }
    43  
    44  func isDrive(s string) bool {
    45  	return len(s) == 2 && s[1] == ':' &&
    46  		(('a' <= s[0] && s[1] <= 'z') || ('A' <= s[0] && s[0] <= 'Z'))
    47  }
    48  
    49  // glob finds all filenames matching the given Segments in the given dir, and
    50  // calls the callback on all of them. If the callback returns false, globbing is
    51  // interrupted, and glob returns false. Otherwise it returns true.
    52  func glob(segs []Segment, dir string, cb func(string) bool) bool {
    53  	// Consume non-wildcard path elements simply by following the path. This may
    54  	// seem like an optimization, but is actually required for "." and ".." to
    55  	// be used as path elements, as they do not appear in the result of ReadDir.
    56  	for len(segs) > 1 && IsLiteral(segs[0]) && IsSlash(segs[1]) {
    57  		elem := segs[0].(Literal).Data
    58  		segs = segs[2:]
    59  		dir += elem + "/"
    60  		if info, err := os.Stat(dir); err != nil || !info.IsDir() {
    61  			return true
    62  		}
    63  	}
    64  
    65  	if len(segs) == 0 {
    66  		return cb(dir)
    67  	} else if len(segs) == 1 && IsLiteral(segs[0]) {
    68  		path := dir + segs[0].(Literal).Data
    69  		if _, err := os.Stat(path); err == nil {
    70  			return cb(path)
    71  		}
    72  		return true
    73  	}
    74  
    75  	infos, err := readDir(dir)
    76  	if err != nil {
    77  		// XXX Silently drop the error
    78  		return true
    79  	}
    80  
    81  	i := -1
    82  	// nexti moves i to the next index in segs that is either / or ** (in other
    83  	// words, something that matches /).
    84  	nexti := func() {
    85  		for i++; i < len(segs); i++ {
    86  			if IsSlash(segs[i]) || IsWild1(segs[i], StarStar) {
    87  				break
    88  			}
    89  		}
    90  	}
    91  	nexti()
    92  
    93  	// Enumerate the position of the first slash. In the presence of multiple
    94  	// **'s in the pattern, the first slash may be in any of those.
    95  	//
    96  	// For instance, in x**y**z, the first slash may be in the first ** or the
    97  	// second:
    98  	// 1) If it is in the first, then pattern is equivalent to x*/**y**z. We
    99  	//    match directories with x* and recurse in each subdirectory with the
   100  	//    pattern **y**z.
   101  	// 2) If it is the in the second, we know that since the first ** can no
   102  	//    longer contain any slashes, we treat it as * (this is done in
   103  	//    matchElement). The pattern is now equivalent to x*y*/**z. We match
   104  	//    directories with x*y* and recurse in each subdirectory with the
   105  	//    pattern **z.
   106  	//
   107  	// The rules are:
   108  	// 1) For each **, we treat it as */** and all previous ones as *. We match
   109  	//    subdirectories with the part before /, and recurse in subdirectories
   110  	//    with the pattern after /.
   111  	// 2) If a literal / is encountered, we return after recursing in the
   112  	//    subdirectories.
   113  	for i < len(segs) {
   114  		slash := IsSlash(segs[i])
   115  		var first, rest []Segment
   116  		if slash {
   117  			// segs = x/y. Match dir with x, recurse on y.
   118  			first, rest = segs[:i], segs[i+1:]
   119  		} else {
   120  			// segs = x**y. Match dir with x*, recurse on **y.
   121  			first, rest = segs[:i+1], segs[i:]
   122  		}
   123  
   124  		for _, info := range infos {
   125  			name := info.Name()
   126  			if matchElement(first, name) && info.IsDir() {
   127  				if !glob(rest, dir+name+"/", cb) {
   128  					return false
   129  				}
   130  			}
   131  		}
   132  
   133  		if slash {
   134  			// First slash cannot appear later than a slash in the pattern.
   135  			return true
   136  		}
   137  		nexti()
   138  	}
   139  
   140  	// If we reach here, it is possible to have no slashes at all. Simply match
   141  	// the entire pattern with all files.
   142  	for _, info := range infos {
   143  		name := info.Name()
   144  		if matchElement(segs, name) {
   145  			if !cb(dir + name) {
   146  				return false
   147  			}
   148  		}
   149  	}
   150  	return true
   151  }
   152  
   153  // readDir is just like ioutil.ReadDir except that it treats an argument of ""
   154  // as ".".
   155  func readDir(dir string) ([]os.FileInfo, error) {
   156  	if dir == "" {
   157  		dir = "."
   158  	}
   159  	return ioutil.ReadDir(dir)
   160  }
   161  
   162  // matchElement matches a path element against segments, which may not contain
   163  // any Slash segments. It treats StarStar segments as they are Star segments.
   164  func matchElement(segs []Segment, name string) bool {
   165  	if len(segs) == 0 {
   166  		return name == ""
   167  	}
   168  	// If the name start with "." and the first segment is a Wild, only match
   169  	// when MatchHidden is true.
   170  	if len(name) > 0 && name[0] == '.' && IsWild(segs[0]) && !segs[0].(Wild).MatchHidden {
   171  		return false
   172  	}
   173  segs:
   174  	for len(segs) > 0 {
   175  		// Find a chunk. A chunk is an optional Star followed by a run of
   176  		// fixed-length segments (Literal and Question).
   177  		var i int
   178  		for i = 1; i < len(segs); i++ {
   179  			if IsWild2(segs[i], Star, StarStar) {
   180  				break
   181  			}
   182  		}
   183  
   184  		chunk := segs[:i]
   185  		startsWithStar := IsWild2(chunk[0], Star, StarStar)
   186  		var startingStar Wild
   187  		if startsWithStar {
   188  			startingStar = chunk[0].(Wild)
   189  			chunk = chunk[1:]
   190  		}
   191  		segs = segs[i:]
   192  
   193  		// NOTE A quick path when len(segs) == 0 can be implemented: match
   194  		// backwards.
   195  
   196  		// Match at the current position. If this is the last chunk, we need to
   197  		// make sure name is exhausted by the matching.
   198  		ok, rest := matchFixedLength(chunk, name)
   199  		if ok && (rest == "" || len(segs) > 0) {
   200  			name = rest
   201  			continue
   202  		}
   203  
   204  		if startsWithStar {
   205  			// NOTE An optimization is to make the upper bound not len(names),
   206  			// but rather len(names) - LB(# bytes segs can match)
   207  			for i, r := range name {
   208  				j := i + len(string(r))
   209  				// Match name[:j] with the starting *, and the rest with chunk.
   210  				if !startingStar.Match(r) {
   211  					break
   212  				}
   213  				ok, rest := matchFixedLength(chunk, name[j:])
   214  				if ok && (rest == "" || len(segs) > 0) {
   215  					name = rest
   216  					continue segs
   217  				}
   218  			}
   219  		}
   220  		return false
   221  	}
   222  	return name == ""
   223  }
   224  
   225  // matchFixedLength returns whether a run of fixed-length segments (Literal and
   226  // Question) matches a prefix of name. It returns whether the match is
   227  // successful and if if it is, the remaining part of name.
   228  func matchFixedLength(segs []Segment, name string) (bool, string) {
   229  	for _, seg := range segs {
   230  		if name == "" {
   231  			return false, ""
   232  		}
   233  		switch seg := seg.(type) {
   234  		case Literal:
   235  			n := len(seg.Data)
   236  			if len(name) < n || name[:n] != seg.Data {
   237  				return false, ""
   238  			}
   239  			name = name[n:]
   240  		case Wild:
   241  			if seg.Type == Question {
   242  				r, n := utf8.DecodeRuneInString(name)
   243  				if !seg.Match(r) {
   244  					return false, ""
   245  				}
   246  				name = name[n:]
   247  			} else {
   248  				panic("matchFixedLength given non-question wild segment")
   249  			}
   250  		default:
   251  			panic("matchFixedLength given non-literal non-wild segment")
   252  		}
   253  	}
   254  	return true, name
   255  }