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