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  }