mirror of
https://gitee.com/IrisVega/frp.git
synced 2024-11-01 22:31:29 +08:00
204 lines
4.5 KiB
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)
|
||
|
}
|