frp/vendor/github.com/rakyll/statik/statik.go

204 lines
4.5 KiB
Go

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