AJ ONeal
пре 5 година
3 измењених фајлова са 192 додато и 0 уклоњено
@ -0,0 +1,34 @@ |
|||
# Go Zip Example |
|||
|
|||
An example of how to zip a directory in Go. |
|||
|
|||
- Utilizes `filepath.Walk` to traverse a directory (or single file) |
|||
- Handles each of |
|||
- Files (deflated, compressed) |
|||
- Directories (empty, not compressed) |
|||
- Symlinks (not compressed) |
|||
- Skips irregular files (pipes, sockets, devices, chars) |
|||
- Names zip file after the name of the directory |
|||
- Trims path prefix |
|||
|
|||
```bash |
|||
git clone https://git.coolaj86.com/coolaj86/go-examples.git |
|||
pushd go-examples/zip |
|||
``` |
|||
|
|||
```bash |
|||
go run . path/to/whatever |
|||
``` |
|||
|
|||
```txt |
|||
wrote whatever.zip |
|||
``` |
|||
|
|||
Separates concerns into functions for readability: |
|||
|
|||
- func main() |
|||
- func Zip(w io.Writer, src string, trim string) error |
|||
- zipOne |
|||
- zipDirectory |
|||
- zipFile |
|||
- zipSymlink |
@ -0,0 +1,45 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"os" |
|||
"path/filepath" |
|||
"strings" |
|||
) |
|||
|
|||
func usage() { |
|||
fmt.Println("Usage: go run go-zip.go <path/to/things>") |
|||
} |
|||
|
|||
func main() { |
|||
if len(os.Args) < 2 || len(os.Args) > 3 { |
|||
usage() |
|||
os.Exit(1) |
|||
} |
|||
|
|||
dir := strings.TrimSuffix(os.Args[1], string(filepath.Separator)) |
|||
base := filepath.Base(dir) |
|||
// ../foo/whatever => ../foo/
|
|||
trim := strings.TrimSuffix(dir, base) |
|||
|
|||
// ./ => error
|
|||
// ../../ => error
|
|||
if "" == base || "." == base || ".." == base { |
|||
// TODO also don't allow ../self
|
|||
fmt.Println("Error: Cannot zip the directory containing the output file") |
|||
os.Exit(1) |
|||
} |
|||
|
|||
f, err := os.OpenFile(base+".zip", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) |
|||
if nil != err { |
|||
panic(err) |
|||
} |
|||
defer f.Close() |
|||
|
|||
// ./whatever => whatever.zip
|
|||
// ./path/to/whatever => whatever.zip
|
|||
if err := Zip(f, dir, trim); nil != err { |
|||
panic(err) |
|||
} |
|||
fmt.Println("wrote", base+".zip") |
|||
} |
@ -0,0 +1,113 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"archive/zip" |
|||
"fmt" |
|||
"io" |
|||
"os" |
|||
"path/filepath" |
|||
"strings" |
|||
) |
|||
|
|||
// Zip walks `src`, omitting `trim`, writing to `w`
|
|||
func Zip(w io.Writer, src string, trim string) error { |
|||
zw := zip.NewWriter(w) |
|||
defer zw.Close() |
|||
|
|||
return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { |
|||
// path includes fi.Name() already
|
|||
if nil != err { |
|||
fmt.Println("warning: skipped", path+": ", err) |
|||
return nil |
|||
} |
|||
|
|||
zipOne(zw, path, fi, trim) |
|||
return nil |
|||
}) |
|||
} |
|||
|
|||
func zipOne(zw *zip.Writer, path string, fi os.FileInfo, trim string) error { |
|||
h, err := zip.FileInfoHeader(fi) |
|||
if nil != err { |
|||
return err |
|||
} |
|||
h.Name = strings.TrimPrefix(strings.TrimPrefix(path, trim), string(filepath.Separator)) |
|||
|
|||
if fi.IsDir() { |
|||
fmt.Printf("directory: %s\n\t%q\n", path, h.Name) |
|||
return zipDirectory(zw, h) |
|||
} |
|||
|
|||
// Allow zipping a single file
|
|||
if "" == h.Name { |
|||
h.Name = path |
|||
} |
|||
if fi.Mode().IsRegular() { |
|||
fmt.Printf("file: %s\n\t%q\n", path, h.Name) |
|||
return zipFile(zw, h, path) |
|||
} |
|||
|
|||
if os.ModeSymlink == (fi.Mode() & os.ModeType) { |
|||
fmt.Printf("symlink: %s\n\t%q\n", path, h.Name) |
|||
return zipSymlink(zw, h, path) |
|||
} |
|||
|
|||
fmt.Printf("skipping: %s\n\t(irregular file type)\n", path) |
|||
return nil |
|||
} |
|||
|
|||
func zipDirectory(zw *zip.Writer, h *zip.FileHeader) error { |
|||
// directories must end in / for go
|
|||
h.Name = strings.TrimPrefix(h.Name+"/", "/") |
|||
|
|||
// skip top-level, trimmed directory
|
|||
if "" == h.Name { |
|||
return nil |
|||
} |
|||
|
|||
if _, err := zw.CreateHeader(h); nil != err { |
|||
return err |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func zipFile(zw *zip.Writer, h *zip.FileHeader, path string) error { |
|||
r, err := os.Open(path) |
|||
if nil != err { |
|||
return err |
|||
} |
|||
defer r.Close() |
|||
|
|||
// Files should be zipped (not dirs, and symlinks... meh)
|
|||
// TODO investigate if files below a certain size shouldn't be deflated
|
|||
h.Method = zip.Deflate |
|||
w, err := zw.CreateHeader(h) |
|||
if nil != err { |
|||
return err |
|||
} |
|||
|
|||
if _, err := io.Copy(w, r); nil != err { |
|||
return err |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func zipSymlink(zw *zip.Writer, h *zip.FileHeader, path string) error { |
|||
w, err := zw.CreateHeader(h) |
|||
if nil != err { |
|||
return err |
|||
} |
|||
|
|||
// TODO make sure that this is within the root directory
|
|||
targetpath, err := os.Readlink(path) |
|||
if nil != err { |
|||
return err |
|||
} |
|||
if _, err := w.Write([]byte(targetpath)); nil != err { |
|||
return err |
|||
} |
|||
|
|||
return nil |
|||
} |
Loading…
Reference in new issue