github.com/spetr/go-zglob@v0.0.2/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/spetr/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 {
    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  			c := cc[i]
    87  			if c == '/' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || 255 < c {
    88  				filemask += string(c)
    89  			} else {
    90  				filemask += fmt.Sprintf("[\\x%02X]", c)
    91  			}
    92  			if c == '/' && dirmask == "" && strings.Index(filemask, "*") != -1 {
    93  				dirmask = filemask
    94  			}
    95  		}
    96  	}
    97  	if dirmask == "" {
    98  		dirmask = filemask
    99  	}
   100  	if len(filemask) > 0 && filemask[len(filemask)-1] == '/' {
   101  		if root == "" {
   102  			root = filemask
   103  		}
   104  		filemask += "[^/]*"
   105  	}
   106  	if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
   107  		dirmask = "(?i:" + dirmask + ")"
   108  		filemask = "(?i:" + filemask + ")"
   109  	}
   110  	return &zenv{
   111  		dre:     regexp.MustCompile("^" + dirmask),
   112  		fre:     regexp.MustCompile("^" + filemask + "$"),
   113  		pattern: pattern,
   114  		root:    filepath.Clean(root),
   115  	}, nil
   116  }
   117  
   118  func Glob(pattern string) ([]string, error) {
   119  	return glob(pattern, false)
   120  }
   121  
   122  func GlobFollowSymlinks(pattern string) ([]string, error) {
   123  	return glob(pattern, true)
   124  }
   125  
   126  func glob(pattern string, followSymlinks bool) ([]string, error) {
   127  	zenv, err := New(pattern)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	if zenv.root == "" {
   132  		_, err := os.Stat(pattern)
   133  		if err != nil {
   134  			return nil, os.ErrNotExist
   135  		}
   136  		return []string{pattern}, nil
   137  	}
   138  	relative := !filepath.IsAbs(pattern)
   139  	matches := []string{}
   140  
   141  	fastwalk.FastWalk(zenv.root, func(path string, info os.FileMode) error {
   142  		if zenv.root == "." && len(zenv.root) < len(path) {
   143  			path = path[len(zenv.root)+1:]
   144  		}
   145  		path = filepath.ToSlash(path)
   146  
   147  		if followSymlinks && info == os.ModeSymlink {
   148  			followedPath, err := filepath.EvalSymlinks(path)
   149  			if err == nil {
   150  				fi, err := os.Lstat(followedPath)
   151  				if err == nil && fi.IsDir() {
   152  					return fastwalk.TraverseLink
   153  				}
   154  			}
   155  		}
   156  
   157  		if info.IsDir() {
   158  			if path == "." || len(path) <= len(zenv.root) {
   159  				return nil
   160  			}
   161  			if zenv.fre.MatchString(path) {
   162  				mu.Lock()
   163  				matches = append(matches, path)
   164  				mu.Unlock()
   165  				return nil
   166  			}
   167  			if !zenv.dre.MatchString(path + "/") {
   168  				return filepath.SkipDir
   169  			}
   170  		}
   171  
   172  		if zenv.fre.MatchString(path) {
   173  			if relative && filepath.IsAbs(path) {
   174  				path = path[len(zenv.root)+1:]
   175  			}
   176  			mu.Lock()
   177  			matches = append(matches, path)
   178  			mu.Unlock()
   179  		}
   180  		return nil
   181  	})
   182  	return matches, nil
   183  }
   184  
   185  func Match(pattern, name string) (matched bool, err error) {
   186  	zenv, err := New(pattern)
   187  	if err != nil {
   188  		return false, err
   189  	}
   190  	return zenv.Match(name), nil
   191  }
   192  
   193  func (z *zenv) Match(name string) bool {
   194  	if z.root == "" {
   195  		return z.pattern == name
   196  	}
   197  
   198  	name = filepath.ToSlash(name)
   199  
   200  	if name == "." || len(name) <= len(z.root) {
   201  		return false
   202  	}
   203  
   204  	if z.fre.MatchString(name) {
   205  		return true
   206  	}
   207  	return false
   208  }