| // Copyright 2014 The Go Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style | 
 | // license that can be found in the LICENSE file. | 
 |  | 
 | // Mkzip creates a zip file from a 'proto' file describing the contents. | 
 | // | 
 | // The proto file is inspired by the Plan 9 mkfs prototype file format. | 
 | // It describes a file tree, one directory per line, with leading tab | 
 | // indentation marking the tree structure. Each line contains a leading | 
 | // name field giving the name of the file to copy into the zip file, | 
 | // and then a sequence of optional key=value attributes to control | 
 | // the copy. The only known attribute is src=foo, meaning copy the | 
 | // actual data for the file (or directory) from an alternate location. | 
 | package main | 
 |  | 
 | import ( | 
 | 	"archive/zip" | 
 | 	"bufio" | 
 | 	"flag" | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"io/ioutil" | 
 | 	"log" | 
 | 	"os" | 
 | 	"path" | 
 | 	"path/filepath" | 
 | 	"strings" | 
 | ) | 
 |  | 
 | func usage() { | 
 | 	fmt.Fprintf(os.Stderr, "usage: mkzip [-r root] src.proto out.zip\n") | 
 | 	os.Exit(2) | 
 | } | 
 |  | 
 | func sysfatal(format string, args ...interface{}) { | 
 | 	fmt.Fprintf(os.Stderr, "mkzip: %s\n", fmt.Sprintf(format, args...)) | 
 | 	os.Exit(2) | 
 | } | 
 |  | 
 | var ( | 
 | 	root      = flag.String("r", ".", "interpret source paths relative to this directory") | 
 | 	gopackage = flag.String("p", "", "write Go source file in this package") | 
 | ) | 
 |  | 
 | type stack struct { | 
 | 	name  string | 
 | 	src   string | 
 | 	depth int | 
 | } | 
 |  | 
 | func main() { | 
 | 	log.SetFlags(0) | 
 | 	flag.Usage = usage | 
 | 	flag.Parse() | 
 |  | 
 | 	args := flag.Args() | 
 | 	if len(args) != 2 { | 
 | 		usage() | 
 | 	} | 
 |  | 
 | 	rf, err := os.Open(args[0]) | 
 | 	if err != nil { | 
 | 		sysfatal("%v", err) | 
 | 	} | 
 | 	r := bufio.NewScanner(rf) | 
 |  | 
 | 	zf, err := os.Create(args[1]) | 
 | 	if err != nil { | 
 | 		sysfatal("%v", err) | 
 | 	} | 
 |  | 
 | 	var w io.Writer = zf | 
 | 	if *gopackage != "" { | 
 | 		fmt.Fprintf(zf, `package %s | 
 | import "sync" | 
 | func init() { | 
 | 	var once sync.Once | 
 | 	fsinit = func() { | 
 | 		once.Do(func() { | 
 | 			unzip("`, *gopackage) | 
 | 		gw := &goWriter{b: bufio.NewWriter(w)} | 
 | 		defer func() { | 
 | 			if err := gw.Close(); err != nil { | 
 | 				sysfatal("finishing Go output: %v", err) | 
 | 			} | 
 | 		}() | 
 | 		w = gw | 
 | 	} | 
 | 	z := zip.NewWriter(w) | 
 |  | 
 | 	lineno := 0 | 
 |  | 
 | 	addfile := func(info os.FileInfo, dst string, src string) { | 
 | 		zh, err := zip.FileInfoHeader(info) | 
 | 		if err != nil { | 
 | 			sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) | 
 | 		} | 
 | 		zh.Name = dst | 
 | 		zh.Method = zip.Deflate | 
 | 		if info.IsDir() && !strings.HasSuffix(dst, "/") { | 
 | 			zh.Name += "/" | 
 | 		} | 
 | 		w, err := z.CreateHeader(zh) | 
 | 		if err != nil { | 
 | 			sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) | 
 | 		} | 
 | 		if info.IsDir() { | 
 | 			return | 
 | 		} | 
 | 		r, err := os.Open(src) | 
 | 		if err != nil { | 
 | 			sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) | 
 | 		} | 
 | 		defer r.Close() | 
 | 		if _, err := io.Copy(w, r); err != nil { | 
 | 			sysfatal("%s:%d: %s: %v", args[0], lineno, src, err) | 
 | 		} | 
 | 	} | 
 |  | 
 | 	var stk []stack | 
 |  | 
 | 	for r.Scan() { | 
 | 		line := r.Text() | 
 | 		lineno++ | 
 | 		s := strings.TrimLeft(line, "\t") | 
 | 		prefix, line := line[:len(line)-len(s)], s | 
 | 		if i := strings.Index(line, "#"); i >= 0 { | 
 | 			line = line[:i] | 
 | 		} | 
 | 		f := strings.Fields(line) | 
 | 		if len(f) == 0 { | 
 | 			continue | 
 | 		} | 
 | 		if strings.HasPrefix(line, " ") { | 
 | 			sysfatal("%s:%d: must use tabs for indentation", args[0], lineno) | 
 | 		} | 
 | 		depth := len(prefix) | 
 | 		for len(stk) > 0 && depth <= stk[len(stk)-1].depth { | 
 | 			stk = stk[:len(stk)-1] | 
 | 		} | 
 | 		parent := "" | 
 | 		psrc := *root | 
 | 		if len(stk) > 0 { | 
 | 			parent = stk[len(stk)-1].name | 
 | 			psrc = stk[len(stk)-1].src | 
 | 		} | 
 | 		if strings.Contains(f[0], "/") { | 
 | 			sysfatal("%s:%d: destination name cannot contain slash", args[0], lineno) | 
 | 		} | 
 | 		name := path.Join(parent, f[0]) | 
 | 		src := filepath.Join(psrc, f[0]) | 
 | 		for _, attr := range f[1:] { | 
 | 			i := strings.Index(attr, "=") | 
 | 			if i < 0 { | 
 | 				sysfatal("%s:%d: malformed attribute %q", args[0], lineno, attr) | 
 | 			} | 
 | 			key, val := attr[:i], attr[i+1:] | 
 | 			switch key { | 
 | 			case "src": | 
 | 				src = val | 
 | 			default: | 
 | 				sysfatal("%s:%d: unknown attribute %q", args[0], lineno, attr) | 
 | 			} | 
 | 		} | 
 |  | 
 | 		stk = append(stk, stack{name: name, src: src, depth: depth}) | 
 |  | 
 | 		if f[0] == "*" || f[0] == "+" { | 
 | 			if f[0] == "*" { | 
 | 				dir, err := ioutil.ReadDir(psrc) | 
 | 				if err != nil { | 
 | 					sysfatal("%s:%d: %v", args[0], lineno, err) | 
 | 				} | 
 | 				for _, d := range dir { | 
 | 					addfile(d, path.Join(parent, d.Name()), filepath.Join(psrc, d.Name())) | 
 | 				} | 
 | 			} else { | 
 | 				err := filepath.Walk(psrc, func(src string, info os.FileInfo, err error) error { | 
 | 					if err != nil { | 
 | 						return err | 
 | 					} | 
 | 					if src == psrc { | 
 | 						return nil | 
 | 					} | 
 | 					if psrc == "." { | 
 | 						psrc = "" | 
 | 					} | 
 | 					name := path.Join(parent, filepath.ToSlash(src[len(psrc):])) | 
 | 					addfile(info, name, src) | 
 | 					return nil | 
 | 				}) | 
 | 				if err != nil { | 
 | 					sysfatal("%s:%d: %v", args[0], lineno, err) | 
 | 				} | 
 | 			} | 
 | 			continue | 
 | 		} | 
 |  | 
 | 		fi, err := os.Stat(src) | 
 | 		if err != nil { | 
 | 			sysfatal("%s:%d: %v", args[0], lineno, err) | 
 | 		} | 
 | 		addfile(fi, name, src) | 
 | 	} | 
 |  | 
 | 	if err := z.Close(); err != nil { | 
 | 		sysfatal("finishing zip file: %v", err) | 
 | 	} | 
 | } | 
 |  | 
 | type goWriter struct { | 
 | 	b *bufio.Writer | 
 | } | 
 |  | 
 | func (w *goWriter) Write(b []byte) (int, error) { | 
 | 	for _, c := range b { | 
 | 		fmt.Fprintf(w.b, "\\x%02x", c) | 
 | 	} | 
 | 	return len(b), nil | 
 | } | 
 |  | 
 | func (w *goWriter) Close() error { | 
 | 	fmt.Fprintf(w.b, "\")\n\t\t})\n\t}\n}") | 
 | 	w.b.Flush() | 
 | 	return nil | 
 | } |