add simple zip example
This commit is contained in:
parent
0a5f44eca7
commit
da2fc01c62
|
@ -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