iperf3exporter/vendor/github.com/goreleaser/fileglob/glob.go
Marvin Preuss 2343c9588a
Some checks failed
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is failing
first commit
2021-10-20 10:08:56 +02:00

231 lines
5.8 KiB
Go

package fileglob
import (
"errors"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/gobwas/glob"
)
const (
separatorRune = '/'
separatorString = string(separatorRune)
)
type globOptions struct {
fs fs.FS
// if matchDirectories directly is set to true a matching directory will
// be treated just like a matching file. If set to false, a matching directory
// will auto-match all files inside instead of the directory itself.
matchDirectoriesDirectly bool
prefix string
pattern string
}
// OptFunc is a function that allow to customize Glob.
type OptFunc func(opts *globOptions)
// WithFs allows to provide another fs.FS implementation to Glob.
func WithFs(f fs.FS) OptFunc {
return func(opts *globOptions) {
opts.fs = f
}
}
// MaybeRootFS setups fileglob to walk from the root directory (/) or
// volume (on windows) if the given pattern is an absolute path.
//
// Result will also be prepended with the root path or volume.
func MaybeRootFS(opts *globOptions) {
if !filepath.IsAbs(opts.pattern) {
return
}
prefix := ""
if strings.HasPrefix(opts.pattern, separatorString) {
prefix = separatorString
}
if vol := filepath.VolumeName(opts.pattern); vol != "" {
prefix = vol + "/"
}
if prefix != "" {
opts.prefix = prefix
opts.fs = os.DirFS(prefix)
}
}
// WriteOptions write the current options to the given writer.
func WriteOptions(w io.Writer) OptFunc {
return func(opts *globOptions) {
_, _ = fmt.Fprintf(w, "%+v", opts)
}
}
// MatchDirectoryIncludesContents makes a match on a directory match all
// files inside it as well.
//
// This is the default behavior.
//
// Also check MatchDirectoryAsFile.
func MatchDirectoryIncludesContents(opts *globOptions) {
opts.matchDirectoriesDirectly = false
}
// MatchDirectoryAsFile makes a match on a directory match its name only.
//
// Also check MatchDirectoryIncludesContents.
func MatchDirectoryAsFile(opts *globOptions) {
opts.matchDirectoriesDirectly = true
}
// QuoteMeta quotes all glob pattern meta characters inside the argument text.
// For example, QuoteMeta for a pattern `{foo*}` sets the pattern to `\{foo\*\}`.
func QuoteMeta(opts *globOptions) {
opts.pattern = glob.QuoteMeta(opts.pattern)
}
// toNixPath converts the path to the nix style path
// Windows style path separators are escape characters so cause issues with the compiled glob.
func toNixPath(s string) string {
return filepath.ToSlash(filepath.Clean(s))
}
// Glob returns all files that match the given pattern in the current directory.
// If the given pattern indicates an absolute path, it will glob from `/`.
// If the given pattern starts with `../`, it will resolve to its absolute path and glob from `/`.
func Glob(pattern string, opts ...OptFunc) ([]string, error) { // nolint:funlen,cyclop
var matches []string
if strings.HasPrefix(pattern, "../") {
p, err := filepath.Abs(pattern)
if err != nil {
return matches, fmt.Errorf("failed to resolve pattern: %s: %w", pattern, err)
}
pattern = filepath.ToSlash(p)
}
options := compileOptions(opts, pattern)
pattern = strings.TrimSuffix(strings.TrimPrefix(options.pattern, options.prefix), separatorString)
matcher, err := glob.Compile(pattern, separatorRune)
if err != nil {
return matches, fmt.Errorf("compile glob pattern: %w", err)
}
prefix, err := staticPrefix(pattern)
if err != nil {
return nil, fmt.Errorf("cannot determine static prefix: %w", err)
}
prefixInfo, err := fs.Stat(options.fs, prefix)
if errors.Is(err, fs.ErrNotExist) {
if !ContainsMatchers(pattern) {
// glob contains no dynamic matchers so prefix is the file name that
// the glob references directly. When the glob explicitly references
// a single non-existing file, return an error for the user to check.
return []string{}, fmt.Errorf(`matching "%s%s": %w`, options.prefix, prefix, fs.ErrNotExist)
}
return []string{}, nil
}
if err != nil {
return nil, fmt.Errorf("stat static prefix %s%s: %w", options.prefix, prefix, err)
}
if !prefixInfo.IsDir() {
// if the prefix is a file, it either has to be
// the only match, or nothing matches at all
if matcher.Match(prefix) {
return cleanFilepaths([]string{prefix}, options.prefix), nil
}
return []string{}, nil
}
if err := fs.WalkDir(options.fs, prefix, func(path string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
// The glob ast from github.com/gobwas/glob only works properly with linux paths
path = toNixPath(path)
if !matcher.Match(path) {
return nil
}
if info.IsDir() {
if options.matchDirectoriesDirectly {
matches = append(matches, path)
return nil
}
// a direct match on a directory implies that all files inside
// match if options.matchFolders is false
filesInDir, err := filesInDirectory(options, path)
if err != nil {
return err
}
matches = append(matches, filesInDir...)
return fs.SkipDir
}
matches = append(matches, path)
return nil
}); err != nil {
return nil, fmt.Errorf("glob failed: %w", err)
}
return cleanFilepaths(matches, options.prefix), nil
}
func compileOptions(optFuncs []OptFunc, pattern string) *globOptions {
opts := &globOptions{
fs: os.DirFS("."),
prefix: "./",
pattern: pattern,
}
for _, apply := range optFuncs {
apply(opts)
}
return opts
}
func filesInDirectory(options *globOptions, dir string) ([]string, error) {
var files []string
return files, fs.WalkDir(options.fs, dir, func(path string, info fs.DirEntry, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
path = toNixPath(path)
files = append(files, path)
return nil
})
}
func cleanFilepaths(paths []string, prefix string) []string {
if prefix == "./" {
// if prefix is relative, no prefix and ./ is the same thing, ignore
return paths
}
result := make([]string, len(paths))
for i, p := range paths {
result[i] = prefix + p
}
return result
}