go.arsenm.dev/pcre@v0.0.0-20220530205550-74594f6c8b0e/glob.go (about)

     1  package pcre
     2  
     3  import (
     4  	"io/fs"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"unsafe"
     9  
    10  	"go.arsenm.dev/pcre/lib"
    11  	"modernc.org/libc"
    12  )
    13  
    14  // ConvertGlob converts the given glob into a
    15  // pcre regular expression, and then returns
    16  // the result.
    17  func ConvertGlob(glob string) (string, error) {
    18  	tls := libc.NewTLS()
    19  	defer tls.Close()
    20  
    21  	// Get C string from given glob
    22  	cGlob, err := libc.CString(glob)
    23  	if err != nil {
    24  		return "", err
    25  	}
    26  	defer libc.Xfree(tls, cGlob)
    27  	// Convert length to size_t
    28  	cGlobLen := lib.Tsize_t(len(glob))
    29  
    30  	// Create null pointer
    31  	outPtr := uintptr(0)
    32  	// Get pointer to pointer
    33  	cOutPtr := uintptr(unsafe.Pointer(&outPtr))
    34  
    35  	// Create 0 size_t
    36  	outLen := lib.Tsize_t(0)
    37  	// Get pointer to size_t
    38  	cOutLen := uintptr(unsafe.Pointer(&outLen))
    39  
    40  	// Convert glob to regular expression
    41  	ret := lib.Xpcre2_pattern_convert_8(
    42  		tls,
    43  		cGlob,
    44  		cGlobLen,
    45  		lib.DPCRE2_CONVERT_GLOB,
    46  		cOutPtr,
    47  		cOutLen,
    48  		0,
    49  	)
    50  	if ret != 0 {
    51  		return "", codeToError(tls, ret)
    52  	}
    53  	defer lib.Xpcre2_converted_pattern_free_8(tls, outPtr)
    54  
    55  	// Get output as byte slice
    56  	out := unsafe.Slice((*byte)(unsafe.Pointer(outPtr)), outLen)
    57  	// Convert output to string
    58  	// This copies the data, so it's safe for later use
    59  	return string(out), nil
    60  }
    61  
    62  // CompileGlob is a convenience function that converts
    63  // a glob to a pcre regular expression and then compiles
    64  // it.
    65  func CompileGlob(glob string) (*Regexp, error) {
    66  	pattern, err := ConvertGlob(glob)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	// Compile converted glob and return results
    71  	return Compile(pattern)
    72  }
    73  
    74  // Glob returns a list of matches for the given glob pattern.
    75  // It returns nil if there was no match. If the glob contains
    76  // "**", it will recurse through the directory, which may be
    77  // extremely slow depending on which directory is being searched.
    78  func Glob(glob string) ([]string, error) {
    79  	// If glob is empty, return nil
    80  	if glob == "" {
    81  		return nil, nil
    82  	}
    83  
    84  	// If the glob is a file path, return the file
    85  	_, err := os.Lstat(glob)
    86  	if err == nil {
    87  		return []string{glob}, nil
    88  	}
    89  
    90  	// If the glob has no glob characters, return nil
    91  	if !hasGlobChars(glob) {
    92  		return nil, nil
    93  	}
    94  
    95  	// Split glob by filepath separator
    96  	paths := strings.Split(glob, string(filepath.Separator))
    97  
    98  	var splitDir []string
    99  	// For every path in split list
   100  	for _, path := range paths {
   101  		// If glob characters forund, stop
   102  		if hasGlobChars(path) {
   103  			break
   104  		}
   105  		// Add path to splitDir
   106  		splitDir = append(splitDir, path)
   107  	}
   108  
   109  	// Join splitDir and add filepath separator. This is the directory that will be searched.
   110  	dir := filepath.Join(splitDir...)
   111  	
   112  	if filepath.IsAbs(glob) {
   113  		dir = string(filepath.Separator) + dir
   114  	}
   115  
   116  	// If the directory is not accessible, return error
   117  	_, err = os.Lstat(dir)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// Compile glob pattern
   123  	r, err := CompileGlob(glob)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	defer r.Close()
   128  
   129  	var matches []string
   130  	// If glob contains "**" (starstar), walk recursively. Otherwise, only search dir.
   131  	if strings.Contains(glob, "**") {
   132  		err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
   133  			if r.MatchString(path) {
   134  				matches = append(matches, path)
   135  			}
   136  			return nil
   137  		})
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  	} else {
   142  		files, err := os.ReadDir(dir)
   143  		if err != nil {
   144  			return nil, err
   145  		}
   146  		for _, file := range files {
   147  			// Get full path of file
   148  			path := filepath.Join(dir, file.Name())
   149  			if r.MatchString(path) {
   150  				matches = append(matches, path)
   151  			}
   152  		}
   153  	}
   154  
   155  	return matches, nil
   156  }
   157  
   158  // hasGlobChars checks if the string has any
   159  // characters that are part of a glob.
   160  func hasGlobChars(s string) bool {
   161  	return strings.ContainsAny(s, "*[]?")
   162  }