package main import ( "bytes" "fmt" "github.com/bmatcuk/doublestar/v4" "golang.org/x/mod/module" "golang.org/x/mod/zip" "io" "log" "os" "path" "path/filepath" "slices" "strings" ) type dirFile struct { filePath, slashPath string info os.FileInfo } func (f dirFile) Path() string { return f.slashPath } func (f dirFile) Lstat() (os.FileInfo, error) { return f.info, nil } func (f dirFile) Open() (io.ReadCloser, error) { return os.Open(f.filePath) } func parseGitignore(wd string) []string { f := make([]string, 0) file, err := os.ReadFile(path.Join(wd, ".gitignore")) if err == nil { str := bytes.NewBuffer(file).String() for _, rline := range strings.Split(str, "\n") { line := strings.TrimSpace(rline) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "!") { continue } if strings.HasPrefix(line, "*") { f = append(f, line) f = append(f, fmt.Sprintf("**/%s", line)) } else if strings.HasSuffix(line, "/") { f = append(f, fmt.Sprintf("%s/**", line)) f = append(f, fmt.Sprintf("%s/**/*", line)) } } } fmt.Println(f) return f } func doWalk(wd string, exGlobs []string) []zip.File { f := make([]zip.File, 0) filepath.Walk(wd, func(p string, info os.FileInfo, err error) error { slashed := filepath.ToSlash(p) rel, _ := filepath.Rel(wd, slashed) rel = filepath.ToSlash(rel) if info.IsDir() { if p == wd { return nil } switch filepath.Base(p) { case ".git", ".bzr", ".svn", ".hg": return filepath.SkipDir } if goModInfo, err := os.Lstat(filepath.Join(p, "go.mod")); err == nil && !goModInfo.IsDir() { return filepath.SkipDir } for _, pat := range exGlobs { ok, _ := doublestar.Match(pat, rel) if ok { return filepath.SkipDir } } return nil } if !info.Mode().IsRegular() { return nil } ok, err := doublestar.Match(fmt.Sprintf("{%s}", strings.Join(exGlobs, ",")), rel) if err != nil { log.Fatal(err) } if !ok { if !slices.Contains(maps(f, func(t zip.File) string { return t.Path() }), slashed) { f = append(f, dirFile{ filePath: p, info: info, slashPath: rel, }) } } return nil }) return f } func maps[T any, R any](slice []T, fn func(t T) R) []R { ret := make([]R, 0) for _, e := range slice { ret = append(ret, fn(e)) } return ret } func main() { args := os.Args[1:] if len(args) < 3 { log.Fatal("usage: PACKAGE-NAME VERSION PATH [EXCLUSION-GLOBS...]") } version := args[1] if !strings.HasPrefix(version, "v") { version = fmt.Sprintf("v%s", version) } var absPath string absPath, _ = filepath.Abs(args[2]) excludeGlobs := make([]string, 0) excludeGlobs = append(excludeGlobs, ".*/*", "build/**") excludeGlobs = append(excludeGlobs, parseGitignore(absPath)...) if len(args) >= 4 { excludeGlobs = append(excludeGlobs, args[3:]...) } buildFile := fmt.Sprintf(filepath.Join(absPath, "build", "%s.zip"), version) walked := doWalk(absPath, excludeGlobs) _ = os.Remove(buildFile) f, _ := os.Create(buildFile) err := zip.Create(f, module.Version{ Version: version, Path: args[0], }, walked) if err != nil { log.Fatal(err) } }