gopkg.in/mattn/go-zglob.v0@v0.0.3/zglob.go (about)

     1  package zglob
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"regexp"
     8  	"runtime"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/mattn/go-zglob/fastwalk"
    13  )
    14  
    15  var (
    16  	envre = regexp.MustCompile(`^(\$[a-zA-Z][a-zA-Z0-9_]+|\$\([a-zA-Z][a-zA-Z0-9_]+\))$`)
    17  	mu    sync.Mutex
    18  )
    19  
    20  type zenv struct {
    21  	dre     *regexp.Regexp
    22  	fre     *regexp.Regexp
    23  	pattern string
    24  	root    string
    25  }
    26  
    27  func New(pattern string) (*zenv, error) {
    28  	globmask := ""
    29  	root := ""
    30  	for n, i := range strings.Split(filepath.ToSlash(pattern), "/") {
    31  		if root == "" && (strings.Index(i, "*") != -1 || strings.Index(i, "{") != -1) {
    32  			if globmask == "" {
    33  				root = "."
    34  			} else {
    35  				root = filepath.ToSlash(globmask)
    36  			}
    37  		}
    38  		if n == 0 && i == "~" {
    39  			if runtime.GOOS == "windows" {
    40  				i = os.Getenv("USERPROFILE")
    41  			} else {
    42  				i = os.Getenv("HOME")
    43  			}
    44  		}
    45  		if envre.MatchString(i) {
    46  			i = strings.Trim(strings.Trim(os.Getenv(i[1:]), "()"), `"`)
    47  		}
    48  
    49  		globmask = filepath.Join(globmask, i)
    50  		if n == 0 {
    51  			if runtime.GOOS == "windows" && filepath.VolumeName(i) != "" {
    52  				globmask = i + "/"
    53  			} else if len(globmask) == 0 {
    54  				globmask = "/"
    55  			}
    56  		}
    57  	}
    58  	if root == "" {
    59  		return &zenv{
    60  			dre:     nil,
    61  			fre:     nil,
    62  			pattern: pattern,
    63  			root:    "",
    64  		}, nil
    65  	}
    66  	if globmask == "" {
    67  		globmask = "."
    68  	}
    69  	globmask = filepath.ToSlash(filepath.Clean(globmask))
    70  
    71  	cc := []rune(globmask)
    72  	dirmask := ""
    73  	filemask := ""
    74  	for i := 0; i < len(cc); i++ {
    75  		if cc[i] == '*' {
    76  			if i < len(cc)-2 && cc[i+1] == '*' && cc[i+2] == '/' {
    77  				filemask += "(.*/)?"
    78  				if dirmask == "" {
    79  					dirmask = filemask
    80  				}
    81  				i += 2
    82  			} else {
    83  				filemask += "[^/]*"
    84  			}
    85  		} else {
    86  			if cc[i] == '{' {
    87  				pattern := ""
    88  				for j := i + 1; j < len(cc); j++ {
    89  					if cc[j] == ',' {
    90  						pattern += "|"
    91  					} else if cc[j] == '}' {
    92  						i = j
    93  						break
    94  					} else {
    95  						c := cc[j]
    96  						if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c {
    97  							pattern += string(c)
    98  						} else {
    99  							pattern += fmt.Sprintf("[\\x%02X]", c)
   100  						}
   101  					}
   102  				}
   103  				if pattern != "" {
   104  					filemask += "(" + pattern + ")"
   105  					continue
   106  				}
   107  			}
   108  			c := cc[i]
   109  			if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c {
   110  				filemask += string(c)
   111  			} else {
   112  				filemask += fmt.Sprintf("[\\x%02X]", c)
   113  			}
   114  			if c == '/' && dirmask == "" && strings.Index(filemask, "*") != -1 {
   115  				dirmask = filemask
   116  			}
   117  		}
   118  	}
   119  	if dirmask == "" {
   120  		dirmask = filemask
   121  	}
   122  	if len(filemask) > 0 && filemask[len(filemask)-1] == '/' {
   123  		if root == "" {
   124  			root = filemask
   125  		}
   126  		filemask += "[^/]*"
   127  	}
   128  	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
   129  		dirmask = "(?i:" + dirmask + ")"
   130  		filemask = "(?i:" + filemask + ")"
   131  	}
   132  	return &zenv{
   133  		dre:     regexp.MustCompile("^" + dirmask),
   134  		fre:     regexp.MustCompile("^" + filemask + "$"),
   135  		pattern: pattern,
   136  		root:    filepath.Clean(root),
   137  	}, nil
   138  }
   139  
   140  func Glob(pattern string) ([]string, error) {
   141  	return glob(pattern, false)
   142  }
   143  
   144  func GlobFollowSymlinks(pattern string) ([]string, error) {
   145  	return glob(pattern, true)
   146  }
   147  
   148  func glob(pattern string, followSymlinks bool) ([]string, error) {
   149  	zenv, err := New(pattern)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	if zenv.root == "" {
   154  		_, err := os.Stat(pattern)
   155  		if err != nil {
   156  			return nil, os.ErrNotExist
   157  		}
   158  		return []string{pattern}, nil
   159  	}
   160  	relative := !filepath.IsAbs(pattern)
   161  	matches := []string{}
   162  
   163  	fastwalk.FastWalk(zenv.root, func(path string, info os.FileMode) error {
   164  		if zenv.root == "." && len(zenv.root) < len(path) {
   165  			path = path[len(zenv.root)+1:]
   166  		}
   167  		path = filepath.ToSlash(path)
   168  
   169  		if followSymlinks && info == os.ModeSymlink {
   170  			followedPath, err := filepath.EvalSymlinks(path)
   171  			if err == nil {
   172  				fi, err := os.Lstat(followedPath)
   173  				if err == nil && fi.IsDir() {
   174  					return fastwalk.TraverseLink
   175  				}
   176  			}
   177  		}
   178  
   179  		if info.IsDir() {
   180  			if path == "." || len(path) <= len(zenv.root) {
   181  				return nil
   182  			}
   183  			if zenv.fre.MatchString(path) {
   184  				mu.Lock()
   185  				matches = append(matches, path)
   186  				mu.Unlock()
   187  				return nil
   188  			}
   189  			if !zenv.dre.MatchString(path + "/") {
   190  				return filepath.SkipDir
   191  			}
   192  		}
   193  
   194  		if zenv.fre.MatchString(path) {
   195  			if relative && filepath.IsAbs(path) {
   196  				path = path[len(zenv.root)+1:]
   197  			}
   198  			mu.Lock()
   199  			matches = append(matches, path)
   200  			mu.Unlock()
   201  		}
   202  		return nil
   203  	})
   204  	return matches, nil
   205  }
   206  
   207  func Match(pattern, name string) (matched bool, err error) {
   208  	zenv, err := New(pattern)
   209  	if err != nil {
   210  		return false, err
   211  	}
   212  	return zenv.Match(name), nil
   213  }
   214  
   215  func (z *zenv) Match(name string) bool {
   216  	if z.root == "" {
   217  		return z.pattern == name
   218  	}
   219  
   220  	name = filepath.ToSlash(name)
   221  
   222  	if name == "." || len(name) <= len(z.root) {
   223  		return false
   224  	}
   225  
   226  	if z.fre.MatchString(name) {
   227  		return true
   228  	}
   229  	return false
   230  }