|  | // 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 | 
|  | } |