aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/cli/internal/globby/globby.go
diff options
context:
space:
mode:
author简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
committer简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
commitdd84b9d64fb98746a230cd24233ff50a562c39c9 (patch)
treeb583261ef00b3afe72ec4d6dacb31e57779a6faf /cli/internal/globby/globby.go
parent0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff)
downloadHydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz
HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip
Diffstat (limited to 'cli/internal/globby/globby.go')
-rw-r--r--cli/internal/globby/globby.go187
1 files changed, 187 insertions, 0 deletions
diff --git a/cli/internal/globby/globby.go b/cli/internal/globby/globby.go
new file mode 100644
index 0000000..14c40d9
--- /dev/null
+++ b/cli/internal/globby/globby.go
@@ -0,0 +1,187 @@
+package globby
+
+import (
+ "fmt"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ iofs "io/fs"
+
+ "github.com/vercel/turbo/cli/internal/fs"
+
+ "github.com/vercel/turbo/cli/internal/doublestar"
+ "github.com/vercel/turbo/cli/internal/util"
+)
+
+// GlobAll returns an array of files and folders that match the specified set of glob patterns.
+// The returned files and folders are absolute paths, assuming that basePath is an absolute path.
+func GlobAll(basePath string, includePatterns []string, excludePatterns []string) ([]string, error) {
+ fsys := fs.CreateDirFSAtRoot(basePath)
+ fsysRoot := fs.GetDirFSRootPath(fsys)
+ output, err := globAllFs(fsys, fsysRoot, basePath, includePatterns, excludePatterns)
+
+ // Because this is coming out of a map output is in no way ordered.
+ // Sorting will put the files in a depth-first order.
+ sort.Strings(output)
+ return output, err
+}
+
+// GlobFiles returns an array of files that match the specified set of glob patterns.
+// The return files are absolute paths, assuming that basePath is an absolute path.
+func GlobFiles(basePath string, includePatterns []string, excludePatterns []string) ([]string, error) {
+ fsys := fs.CreateDirFSAtRoot(basePath)
+ fsysRoot := fs.GetDirFSRootPath(fsys)
+ output, err := globFilesFs(fsys, fsysRoot, basePath, includePatterns, excludePatterns)
+
+ // Because this is coming out of a map output is in no way ordered.
+ // Sorting will put the files in a depth-first order.
+ sort.Strings(output)
+ return output, err
+}
+
+// checkRelativePath ensures that the the requested file path is a child of `from`.
+func checkRelativePath(from string, to string) error {
+ relativePath, err := filepath.Rel(from, to)
+
+ if err != nil {
+ return err
+ }
+
+ if strings.HasPrefix(relativePath, "..") {
+ return fmt.Errorf("the path you are attempting to specify (%s) is outside of the root", to)
+ }
+
+ return nil
+}
+
+// globFilesFs searches the specified file system to enumerate all files to include.
+func globFilesFs(fsys iofs.FS, fsysRoot string, basePath string, includePatterns []string, excludePatterns []string) ([]string, error) {
+ return globWalkFs(fsys, fsysRoot, basePath, includePatterns, excludePatterns, false)
+}
+
+// globAllFs searches the specified file system to enumerate all files to include.
+func globAllFs(fsys iofs.FS, fsysRoot string, basePath string, includePatterns []string, excludePatterns []string) ([]string, error) {
+ return globWalkFs(fsys, fsysRoot, basePath, includePatterns, excludePatterns, true)
+}
+
+// globWalkFs searches the specified file system to enumerate all files and folders to include.
+func globWalkFs(fsys iofs.FS, fsysRoot string, basePath string, includePatterns []string, excludePatterns []string, includeDirs bool) ([]string, error) {
+ var processedIncludes []string
+ var processedExcludes []string
+ result := make(util.Set)
+
+ for _, includePattern := range includePatterns {
+ includePath := filepath.Join(basePath, includePattern)
+ err := checkRelativePath(basePath, includePath)
+
+ if err != nil {
+ return nil, err
+ }
+
+ // fs.FS paths may not include leading separators. Calculate the
+ // correct path for this relative to the filesystem root.
+ // This will not error as it follows the call to checkRelativePath.
+ iofsRelativePath, _ := fs.IofsRelativePath(fsysRoot, includePath)
+
+ // Includes only operate on files.
+ processedIncludes = append(processedIncludes, iofsRelativePath)
+ }
+
+ for _, excludePattern := range excludePatterns {
+ excludePath := filepath.Join(basePath, excludePattern)
+ err := checkRelativePath(basePath, excludePath)
+
+ if err != nil {
+ return nil, err
+ }
+
+ // fs.FS paths may not include leading separators. Calculate the
+ // correct path for this relative to the filesystem root.
+ // This will not error as it follows the call to checkRelativePath.
+ iofsRelativePath, _ := fs.IofsRelativePath(fsysRoot, excludePath)
+
+ // In case this is a file pattern and not a directory, add the exact pattern.
+ // In the event that the user has already specified /**,
+ if !strings.HasSuffix(iofsRelativePath, string(filepath.Separator)+"**") {
+ processedExcludes = append(processedExcludes, iofsRelativePath)
+ }
+ // TODO: we need to either document or change this behavior
+ // Excludes operate on entire folders, so we also exclude everything under this in case it represents a directory
+ processedExcludes = append(processedExcludes, filepath.Join(iofsRelativePath, "**"))
+ }
+
+ // We start from a naive includePattern
+ includePattern := ""
+ includeCount := len(processedIncludes)
+
+ // Do not use alternation if unnecessary.
+ if includeCount == 1 {
+ includePattern = processedIncludes[0]
+ } else if includeCount > 1 {
+ // We use alternation from the very root of the path. This avoids fs.Stat of the basePath.
+ includePattern = "{" + strings.Join(processedIncludes, ",") + "}"
+ }
+
+ // We start with an empty string excludePattern which we only use if excludeCount > 0.
+ excludePattern := ""
+ excludeCount := len(processedExcludes)
+
+ // Do not use alternation if unnecessary.
+ if excludeCount == 1 {
+ excludePattern = processedExcludes[0]
+ } else if excludeCount > 1 {
+ // We use alternation from the very root of the path. This avoids fs.Stat of the basePath.
+ excludePattern = "{" + strings.Join(processedExcludes, ",") + "}"
+ }
+
+ // GlobWalk expects that everything uses Unix path conventions.
+ includePattern = filepath.ToSlash(includePattern)
+ excludePattern = filepath.ToSlash(excludePattern)
+
+ err := doublestar.GlobWalk(fsys, includePattern, func(path string, dirEntry iofs.DirEntry) error {
+ if !includeDirs && dirEntry.IsDir() {
+ return nil
+ }
+
+ // All files that are returned by doublestar.GlobWalk are relative to
+ // the fsys root. Go, however, has decided that `fs.FS` filesystems do
+ // not address the root of the file system using `/` and instead use
+ // paths without leading separators.
+ //
+ // We need to track where the `fsys` root is so that when we hand paths back
+ // we hand them back as the path addressable in the actual OS filesystem.
+ //
+ // As a consequence, when processing, we need to *restore* the original
+ // root to the file path after returning. This works because when we create
+ // the `os.dirFS` filesystem we do so at the root of the current volume.
+ if excludeCount == 0 {
+ // Reconstruct via string concatenation since the root is already pre-composed.
+ result.Add(fsysRoot + path)
+ return nil
+ }
+
+ isExcluded, err := doublestar.Match(excludePattern, filepath.ToSlash(path))
+ if err != nil {
+ return err
+ }
+
+ if !isExcluded {
+ // Reconstruct via string concatenation since the root is already pre-composed.
+ result.Add(fsysRoot + path)
+ }
+
+ return nil
+ })
+
+ // GlobWalk threw an error.
+ if err != nil {
+ return nil, err
+ }
+
+ // Never actually capture the root folder.
+ // This is a risk because of how we rework the globs.
+ result.Delete(strings.TrimSuffix(basePath, "/"))
+
+ return result.UnsafeListOfStrings(), nil
+}