// Copyright 2014 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package contains a program that generates code to register // a directory and its contents as zip data for statik file system. package main import ( "archive/zip" "bytes" "flag" "fmt" "io" "io/ioutil" "os" "path" "path/filepath" "strings" ) const ( namePackage = "statik" nameSourceFile = "statik.go" ) var ( flagSrc = flag.String("src", path.Join(".", "public"), "The path of the source directory.") flagDest = flag.String("dest", ".", "The destination path of the generated package.") ) func main() { flag.Parse() file, err := generateSource(*flagSrc) if err != nil { exitWithError(err) } destDir := path.Join(*flagDest, namePackage) err = os.MkdirAll(destDir, 0755) if err != nil { exitWithError(err) } err = rename(file.Name(), path.Join(destDir, nameSourceFile)) if err != nil { exitWithError(err) } } // rename tries to os.Rename, but fall backs to copying from src // to dest and unlink the source if os.Rename fails. func rename(src, dest string) error { // Try to rename generated source. if err := os.Rename(src, dest); err == nil { return nil } // If the rename failed (might do so due to temporary file residing on a // different device), try to copy byte by byte. rc, err := os.Open(src) if err != nil { return err } defer func() { rc.Close() os.Remove(src) // ignore the error, source is in tmp. }() if _, err = os.Stat(dest); !os.IsNotExist(err) { return fmt.Errorf("file %q already exists", dest) } wc, err := os.Create(dest) if err != nil { return err } defer wc.Close() if _, err = io.Copy(wc, rc); err != nil { // Delete remains of failed copy attempt. os.Remove(dest) } return err } // Walks on the source path and generates source code // that contains source directory's contents as zip contents. // Generates source registers generated zip contents data to // be read by the statik/fs HTTP file system. func generateSource(srcPath string) (file *os.File, err error) { var ( buffer bytes.Buffer zipWriter io.Writer ) zipWriter = &buffer f, err := ioutil.TempFile("", namePackage) if err != nil { return } zipWriter = io.MultiWriter(zipWriter, f) defer f.Close() w := zip.NewWriter(zipWriter) if err = filepath.Walk(srcPath, func(path string, fi os.FileInfo, err error) error { if err != nil { return err } // Ignore directories and hidden files. // No entry is needed for directories in a zip file. // Each file is represented with a path, no directory // entities are required to build the hierarchy. if fi.IsDir() || strings.HasPrefix(fi.Name(), ".") { return nil } relPath, err := filepath.Rel(srcPath, path) if err != nil { return err } b, err := ioutil.ReadFile(path) if err != nil { return err } fHeader, err := zip.FileInfoHeader(fi) if err != nil { return err } fHeader.Name = filepath.ToSlash(relPath) f, err := w.CreateHeader(fHeader) if err != nil { return err } _, err = f.Write(b) return err }); err != nil { return } if err = w.Close(); err != nil { return } // then embed it as a quoted string var qb bytes.Buffer fmt.Fprintf(&qb, `package %s import ( "github.com/rakyll/statik/fs" ) func init() { data := "`, namePackage) FprintZipData(&qb, buffer.Bytes()) fmt.Fprint(&qb, `" fs.Register(data) } `) if err = ioutil.WriteFile(f.Name(), qb.Bytes(), 0644); err != nil { return } return f, nil } // Converts zip binary contents to a string literal. func FprintZipData(dest *bytes.Buffer, zipData []byte) { for _, b := range zipData { if b == '\n' { dest.WriteString(`\n`) continue } if b == '\\' { dest.WriteString(`\\`) continue } if b == '"' { dest.WriteString(`\"`) continue } if (b >= 32 && b <= 126) || b == '\t' { dest.WriteByte(b) continue } fmt.Fprintf(dest, "\\x%02x", b) } } // Prints out the error message and exists with a non-success signal. func exitWithError(err error) { fmt.Println(err) os.Exit(1) }