1
1
mirror of https://github.com/goreleaser/nfpm synced 2025-04-20 04:18:00 +02:00
nfpm/internal/glob/glob.go
2025-03-29 08:57:48 -03:00

110 lines
2.7 KiB
Go

// Package glob provides file globbing for use in nfpm.Packager implementations
package glob
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/goreleaser/fileglob"
)
// longestCommonPrefix returns the longest prefix of all strings the argument
// slice. If the slice is empty the empty string is returned.
func longestCommonPrefix(strs []string) string {
if len(strs) == 0 {
return ""
}
lcp := strs[0]
for _, str := range strs {
lcp = strlcp(
filepath.ToSlash(lcp),
filepath.ToSlash(str),
)
}
return lcp
}
func strlcp(a, b string) string {
minlen := min(len(a), len(b))
for i := range minlen {
if a[i] != b[i] {
return a[0:i]
}
}
return a[0:minlen]
}
// ErrGlobNoMatch happens when no files matched the given glob.
type ErrGlobNoMatch struct {
glob string
}
func (e ErrGlobNoMatch) Error() string {
return fmt.Sprintf("glob failed: %s: no matching files", e.glob)
}
// Glob returns a map with source file path as keys and destination as values.
// First the longest common prefix (lcp) of all globbed files is found. The destination
// for each globbed file is then dst joined with src with the lcp trimmed off.
func Glob(pattern, dst string, ignoreMatchers bool) (map[string]string, error) {
options := []fileglob.OptFunc{fileglob.MatchDirectoryIncludesContents}
if ignoreMatchers {
options = append(options, fileglob.QuoteMeta)
}
if strings.HasPrefix(pattern, "../") {
p, err := filepath.Abs(pattern)
if err != nil {
return nil, fmt.Errorf("failed to resolve pattern: %s: %w", pattern, err)
}
pattern = filepath.ToSlash(p)
}
matches, err := fileglob.Glob(pattern, append(options, fileglob.MaybeRootFS)...)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, err
}
return nil, fmt.Errorf("glob failed: %s: %w", pattern, err)
}
if len(matches) == 0 {
return nil, ErrGlobNoMatch{pattern}
}
files := make(map[string]string)
prefix := pattern
// the prefix may not be a complete path or may use glob patterns, in that case use the parent directory
if _, err := os.Stat(prefix); errors.Is(err, fs.ErrNotExist) || (fileglob.ContainsMatchers(pattern) && !ignoreMatchers) {
prefix = filepath.Dir(longestCommonPrefix(matches))
}
for _, src := range matches {
// only include files
if f, err := os.Stat(src); err == nil && f.Mode().IsDir() {
continue
}
if strings.HasSuffix(dst, "/") {
files[src] = filepath.Join(dst, filepath.Base(src))
continue
}
relpath, err := filepath.Rel(prefix, src)
if err != nil {
// since prefix is a prefix of src a relative path should always be found
return nil, err
}
globdst := filepath.ToSlash(filepath.Join(dst, relpath))
files[src] = globdst
}
return files, nil
}