tools/go/gozip/main.go (180 lines of code) (raw):

package main import ( "archive/zip" "bufio" "flag" "fmt" "io" "io/ioutil" "os" "path" "runtime" "strings" ) type executableList []string func (l *executableList) Set(value string) error { *l = append(*l, value) return nil } func (l *executableList) String() string { return strings.Join(*l, ", ") } var ( fbaseDir string fdir string finputFile string foutput string fquiet bool executables executableList executablesMap map[string]struct{} ) const pathSeparator = string(os.PathSeparator) func init() { flag.StringVar(&fbaseDir, "base-dir", "", "Base dir for the zip archive") flag.StringVar(&fdir, "dir", "", "dir to zip") flag.StringVar(&finputFile, "input-file", "", "a file containing a list of files to archive") flag.StringVar(&foutput, "output", "", "Output file name") flag.BoolVar(&fquiet, "quiet", false, "no output") flag.Var(&executables, "set-executable", "Set these files as executabled in the zip file") } func validateFlags() { if fdir == "" && finputFile == "" && fbaseDir == "" && foutput == "" { flag.Usage() os.Exit(0) } if foutput == "" { flag.Usage() panic("output file name is required") } if fdir == "" && finputFile == "" { flag.Usage() panic("file and path can't both be empty") } if fdir != "" && finputFile != "" { flag.Usage() panic("file and path are mutually exclusive") } if fbaseDir == "" && fdir != "" { fbaseDir = fdir } if !strings.HasSuffix(fbaseDir, pathSeparator) { fbaseDir = fbaseDir + pathSeparator } executablesMap = make(map[string]struct{}, len(executables)) for _, v := range executables { executablesMap[v] = struct{}{} } } func zipDir(filename, dir string) error { newZipFile, err := os.Create(filename) if err != nil { return err } defer newZipFile.Close() zipWriter := zip.NewWriter(newZipFile) defer zipWriter.Close() if err = addFiles(zipWriter, dir, dir); err != nil { return err } return nil } func zipFiles(filename string, files []string, baseInZip string) error { newZipFile, err := os.Create(filename) if err != nil { return err } defer newZipFile.Close() zipWriter := zip.NewWriter(newZipFile) defer zipWriter.Close() for _, file := range files { if err = addFileToZip(zipWriter, file, baseInZip); err != nil { return err } } return nil } func addFileToZip(zipWriter *zip.Writer, filePath, baseInZip string) error { fileToZip, err := os.Open(filePath) if err != nil { return err } defer fileToZip.Close() info, err := fileToZip.Stat() if err != nil { return err } header, err := zip.FileInfoHeader(info) if err != nil { return err } baseLength := len(baseInZip) if !strings.HasSuffix(baseInZip, pathSeparator) { baseLength++ } header.Name = filePath[baseLength:] if runtime.GOOS == "windows" { header.Name = strings.ReplaceAll(header.Name, "\\", "/") } if _, ok := executablesMap[header.Name]; ok { mod := header.Mode() | 0111 header.SetMode(mod) } header.Method = zip.Deflate if !fquiet { fmt.Printf("%s => %s\n", filePath, header.Name) } writer, err := zipWriter.CreateHeader(header) if err != nil { return err } _, err = io.Copy(writer, fileToZip) return err } func addFiles(w *zip.Writer, dirPath, baseInZip string) error { files, err := ioutil.ReadDir(dirPath) if err != nil { return err } for _, file := range files { if !file.IsDir() { err := addFileToZip(w, path.Join(dirPath, file.Name()), baseInZip) if err != nil { return err } } else if file.IsDir() { newBase := path.Join(dirPath, file.Name()) addFiles(w, newBase, baseInZip) } } return nil } // https://stackoverflow.com/a/18479916/3234163 func readAllLines(path string) ([]string, error) { file, err := os.Open(path) if err != nil { return nil, err } defer file.Close() var lines []string scanner := bufio.NewScanner(file) for scanner.Scan() { lines = append(lines, scanner.Text()) } return lines, scanner.Err() } func main() { flag.Parse() validateFlags() var err error if fdir != "" { err = zipDir(foutput, fdir) } else { var lines []string lines, err = readAllLines(finputFile) if err == nil { err = zipFiles(foutput, lines, fbaseDir) } } if err != nil { panic(err) } }