github.com/goreleaser/nfpm/v2@v2.44.0/internal/glob/glob.go (about) 1 // Package glob provides file globbing for use in nfpm.Packager implementations 2 package glob 3 4 import ( 5 "errors" 6 "fmt" 7 "io/fs" 8 "os" 9 "path/filepath" 10 "strings" 11 12 "github.com/goreleaser/fileglob" 13 ) 14 15 // longestCommonPrefix returns the longest prefix of all strings the argument 16 // slice. If the slice is empty the empty string is returned. 17 func longestCommonPrefix(strs []string) string { 18 if len(strs) == 0 { 19 return "" 20 } 21 lcp := strs[0] 22 for _, str := range strs { 23 lcp = strlcp( 24 filepath.ToSlash(lcp), 25 filepath.ToSlash(str), 26 ) 27 } 28 return lcp 29 } 30 31 func strlcp(a, b string) string { 32 minlen := min(len(a), len(b)) 33 for i := range minlen { 34 if a[i] != b[i] { 35 return a[0:i] 36 } 37 } 38 return a[0:minlen] 39 } 40 41 // ErrGlobNoMatch happens when no files matched the given glob. 42 type ErrGlobNoMatch struct { 43 glob string 44 } 45 46 func (e ErrGlobNoMatch) Error() string { 47 return fmt.Sprintf("glob failed: %s: no matching files", e.glob) 48 } 49 50 // Glob returns a map with source file path as keys and destination as values. 51 // First the longest common prefix (lcp) of all globbed files is found. The destination 52 // for each globbed file is then dst joined with src with the lcp trimmed off. 53 func Glob(pattern, dst string, ignoreMatchers bool) (map[string]string, error) { 54 options := []fileglob.OptFunc{fileglob.MatchDirectoryIncludesContents} 55 if ignoreMatchers { 56 options = append(options, fileglob.QuoteMeta) 57 } 58 59 if strings.HasPrefix(pattern, "../") { 60 p, err := filepath.Abs(pattern) 61 if err != nil { 62 return nil, fmt.Errorf("failed to resolve pattern: %s: %w", pattern, err) 63 } 64 pattern = filepath.ToSlash(p) 65 } 66 67 matches, err := fileglob.Glob(pattern, append(options, fileglob.MaybeRootFS)...) 68 if err != nil { 69 if errors.Is(err, os.ErrNotExist) { 70 return nil, err 71 } 72 73 return nil, fmt.Errorf("glob failed: %s: %w", pattern, err) 74 } 75 76 if len(matches) == 0 { 77 return nil, ErrGlobNoMatch{pattern} 78 } 79 80 files := make(map[string]string) 81 prefix := pattern 82 // the prefix may not be a complete path or may use glob patterns, in that case use the parent directory 83 if _, err := os.Stat(prefix); errors.Is(err, fs.ErrNotExist) || (fileglob.ContainsMatchers(pattern) && !ignoreMatchers) { 84 prefix = filepath.Dir(longestCommonPrefix(matches)) 85 } 86 87 for _, src := range matches { 88 // only include files 89 if f, err := os.Stat(src); err == nil && f.Mode().IsDir() { 90 continue 91 } 92 93 if strings.HasSuffix(dst, "/") { 94 files[src] = filepath.Join(dst, filepath.Base(src)) 95 continue 96 } 97 98 relpath, err := filepath.Rel(prefix, src) 99 if err != nil { 100 // since prefix is a prefix of src a relative path should always be found 101 return nil, err 102 } 103 104 globdst := filepath.ToSlash(filepath.Join(dst, relpath)) 105 files[src] = globdst 106 } 107 108 return files, nil 109 }