add simple zip example
This commit is contained in:
		
							parent
							
								
									0a5f44eca7
								
							
						
					
					
						commit
						da2fc01c62
					
				
							
								
								
									
										34
									
								
								zip/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								zip/README.md
									
									
									
									
									
										Normal file
									
								
							@ -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
 | 
			
		||||
							
								
								
									
										45
									
								
								zip/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								zip/main.go
									
									
									
									
									
										Normal file
									
								
							@ -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")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										113
									
								
								zip/zip.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										113
									
								
								zip/zip.go
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user