mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2025-02-27 00:53:03 +08:00
434 lines
9.4 KiB
Go
434 lines
9.4 KiB
Go
// Package embed implements all file embedding logic for github.com/mjibson/esc.
|
|
package embed
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// Config contains all information needed to run esc.
|
|
type Config struct {
|
|
// OutputFile is the file name to write output, else stdout.
|
|
OutputFile string
|
|
// Package name for the generated file.
|
|
Package string
|
|
// Prefix is stripped from filenames.
|
|
Prefix string
|
|
// Ignore is the regexp for files we should ignore (for example `\.DS_Store`).
|
|
Ignore string
|
|
// Include is the regexp for files to include. If provided, only files that
|
|
// match will be included.
|
|
Include string
|
|
// ModTime is the Unix timestamp to override as modification time for all files.
|
|
ModTime string
|
|
// Private, if true, causes autogenerated functions to be unexported.
|
|
Private bool
|
|
// NoCompression, if true, stores the files without compression.
|
|
NoCompression bool
|
|
|
|
// Files is the list of files or directories to embed.
|
|
Files []string
|
|
}
|
|
|
|
var modTime *int64
|
|
|
|
type headerTemplateParams struct {
|
|
Invocation string
|
|
PackageName string
|
|
FunctionPrefix string
|
|
}
|
|
|
|
type _escFile struct {
|
|
data []byte
|
|
local string
|
|
fileinfo os.FileInfo
|
|
}
|
|
|
|
// Run executes a Config.
|
|
func Run(conf *Config) error {
|
|
var err error
|
|
if conf.ModTime != "" {
|
|
i, err := strconv.ParseInt(conf.ModTime, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("modtime must be an integer: %v", err)
|
|
}
|
|
modTime = &i
|
|
}
|
|
var fnames, dirnames []string
|
|
content := make(map[string]_escFile)
|
|
prefix := filepath.ToSlash(conf.Prefix)
|
|
var ignoreRegexp *regexp.Regexp
|
|
if conf.Ignore != "" {
|
|
ignoreRegexp, err = regexp.Compile(conf.Ignore)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
var includeRegexp *regexp.Regexp
|
|
if conf.Include != "" {
|
|
includeRegexp, err = regexp.Compile(conf.Include)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, base := range conf.Files {
|
|
files := []string{base}
|
|
for len(files) > 0 {
|
|
fname := files[0]
|
|
files = files[1:]
|
|
if ignoreRegexp != nil && ignoreRegexp.MatchString(fname) {
|
|
continue
|
|
}
|
|
f, err := os.Open(fname)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fi.IsDir() {
|
|
fis, err := f.Readdir(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, fi := range fis {
|
|
files = append(files, filepath.Join(fname, fi.Name()))
|
|
}
|
|
} else if includeRegexp == nil || includeRegexp.MatchString(fname) {
|
|
b, err := ioutil.ReadAll(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fpath := filepath.ToSlash(fname)
|
|
n := strings.TrimPrefix(fpath, prefix)
|
|
n = path.Join("/", n)
|
|
if _, ok := content[n]; ok {
|
|
return fmt.Errorf("%s, %s: duplicate name after prefix removal", n, fpath)
|
|
}
|
|
content[n] = _escFile{data: b, local: fpath, fileinfo: fi}
|
|
fnames = append(fnames, n)
|
|
}
|
|
f.Close()
|
|
}
|
|
}
|
|
sort.Strings(fnames)
|
|
w := new(bytes.Buffer)
|
|
headerText, err := header(conf.Package, !(conf.Private))
|
|
if nil != err {
|
|
return fmt.Errorf("failed to expand autogenerated code: %s", err)
|
|
}
|
|
if _, err := w.Write(headerText); err != nil {
|
|
return fmt.Errorf("failed to write output: %s", err)
|
|
}
|
|
dirs := map[string]bool{"/": true}
|
|
gzipLevel := gzip.BestCompression
|
|
if conf.NoCompression {
|
|
gzipLevel = gzip.NoCompression
|
|
}
|
|
for _, fname := range fnames {
|
|
f := content[fname]
|
|
for b := path.Dir(fname); b != "/"; b = path.Dir(b) {
|
|
dirs[b] = true
|
|
}
|
|
var buf bytes.Buffer
|
|
gw, err := gzip.NewWriterLevel(&buf, gzipLevel)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := gw.Write(f.data); err != nil {
|
|
return err
|
|
}
|
|
if err := gw.Close(); err != nil {
|
|
return err
|
|
}
|
|
t := f.fileinfo.ModTime().Unix()
|
|
if modTime != nil {
|
|
t = *modTime
|
|
}
|
|
fmt.Fprintf(w, `
|
|
%q: {
|
|
local: %q,
|
|
size: %v,
|
|
modtime: %v,
|
|
compressed: %s,
|
|
},%s`, fname, f.local, len(f.data), t, segment(&buf), "\n")
|
|
}
|
|
for d := range dirs {
|
|
dirnames = append(dirnames, d)
|
|
}
|
|
sort.Strings(dirnames)
|
|
for _, dir := range dirnames {
|
|
local := path.Join(prefix, dir)
|
|
if len(local) == 0 {
|
|
local = "."
|
|
}
|
|
if local[0] == '/' {
|
|
// Read dirs relative to the go proc's cwd vs system's
|
|
// fs root.
|
|
local = local[1:]
|
|
}
|
|
fmt.Fprintf(w, `
|
|
%q: {
|
|
isDir: true,
|
|
local: %q,
|
|
},%s`, dir, local, "\n")
|
|
}
|
|
w.WriteString(footer)
|
|
out := os.Stdout
|
|
if conf.OutputFile != "" {
|
|
if out, err = os.Create(conf.OutputFile); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if _, err := w.WriteTo(out); err != nil {
|
|
return err
|
|
}
|
|
if conf.OutputFile != "" {
|
|
return out.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func segment(s *bytes.Buffer) string {
|
|
var b bytes.Buffer
|
|
b64 := base64.NewEncoder(base64.StdEncoding, &b)
|
|
b64.Write(s.Bytes())
|
|
b64.Close()
|
|
res := "`\n"
|
|
chunk := make([]byte, 80)
|
|
for n, _ := b.Read(chunk); n > 0; n, _ = b.Read(chunk) {
|
|
res += string(chunk[0:n]) + "\n"
|
|
}
|
|
return res + "`"
|
|
}
|
|
|
|
func header(packageName string, enableExports bool) ([]byte, error) {
|
|
functionPrefix := ""
|
|
if !enableExports {
|
|
functionPrefix = "_esc"
|
|
}
|
|
headerParams := headerTemplateParams{
|
|
Invocation: strings.Join(os.Args[1:], " "),
|
|
PackageName: packageName,
|
|
FunctionPrefix: functionPrefix,
|
|
}
|
|
tmpl, err := template.New("").Parse(headerTemplate)
|
|
if nil != err {
|
|
return nil, err
|
|
}
|
|
var b bytes.Buffer
|
|
err = tmpl.Execute(&b, headerParams)
|
|
if nil != err {
|
|
return nil, err
|
|
}
|
|
return b.Bytes(), nil
|
|
}
|
|
|
|
const (
|
|
headerTemplate = `// Code generated by "esc {{.Invocation}}"; DO NOT EDIT.
|
|
|
|
package {{.PackageName}}
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/base64"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type _escLocalFS struct{}
|
|
|
|
var _escLocal _escLocalFS
|
|
|
|
type _escStaticFS struct{}
|
|
|
|
var _escStatic _escStaticFS
|
|
|
|
type _escDirectory struct {
|
|
fs http.FileSystem
|
|
name string
|
|
}
|
|
|
|
type _escFile struct {
|
|
compressed string
|
|
size int64
|
|
modtime int64
|
|
local string
|
|
isDir bool
|
|
|
|
once sync.Once
|
|
data []byte
|
|
name string
|
|
}
|
|
|
|
func (_escLocalFS) Open(name string) (http.File, error) {
|
|
f, present := _escData[path.Clean(name)]
|
|
if !present {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
return os.Open(f.local)
|
|
}
|
|
|
|
func (_escStaticFS) prepare(name string) (*_escFile, error) {
|
|
f, present := _escData[path.Clean(name)]
|
|
if !present {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
var err error
|
|
f.once.Do(func() {
|
|
f.name = path.Base(name)
|
|
if f.size == 0 {
|
|
return
|
|
}
|
|
var gr *gzip.Reader
|
|
b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed))
|
|
gr, err = gzip.NewReader(b64)
|
|
if err != nil {
|
|
return
|
|
}
|
|
f.data, err = ioutil.ReadAll(gr)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func (fs _escStaticFS) Open(name string) (http.File, error) {
|
|
f, err := fs.prepare(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f.File()
|
|
}
|
|
|
|
func (dir _escDirectory) Open(name string) (http.File, error) {
|
|
return dir.fs.Open(dir.name + name)
|
|
}
|
|
|
|
func (f *_escFile) File() (http.File, error) {
|
|
type httpFile struct {
|
|
*bytes.Reader
|
|
*_escFile
|
|
}
|
|
return &httpFile{
|
|
Reader: bytes.NewReader(f.data),
|
|
_escFile: f,
|
|
}, nil
|
|
}
|
|
|
|
func (f *_escFile) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (f *_escFile) Stat() (os.FileInfo, error) {
|
|
return f, nil
|
|
}
|
|
|
|
func (f *_escFile) Name() string {
|
|
return f.name
|
|
}
|
|
|
|
func (f *_escFile) Size() int64 {
|
|
return f.size
|
|
}
|
|
|
|
func (f *_escFile) Mode() os.FileMode {
|
|
return 0
|
|
}
|
|
|
|
func (f *_escFile) ModTime() time.Time {
|
|
return time.Unix(f.modtime, 0)
|
|
}
|
|
|
|
func (f *_escFile) IsDir() bool {
|
|
return f.isDir
|
|
}
|
|
|
|
func (f *_escFile) Sys() interface{} {
|
|
return f
|
|
}
|
|
|
|
// {{.FunctionPrefix}}FS returns a http.Filesystem for the embedded assets. If useLocal is true,
|
|
// the filesystem's contents are instead used.
|
|
func {{.FunctionPrefix}}FS(useLocal bool) http.FileSystem {
|
|
if useLocal {
|
|
return _escLocal
|
|
}
|
|
return _escStatic
|
|
}
|
|
|
|
// {{.FunctionPrefix}}Dir returns a http.Filesystem for the embedded assets on a given prefix dir.
|
|
// If useLocal is true, the filesystem's contents are instead used.
|
|
func {{.FunctionPrefix}}Dir(useLocal bool, name string) http.FileSystem {
|
|
if useLocal {
|
|
return _escDirectory{fs: _escLocal, name: name}
|
|
}
|
|
return _escDirectory{fs: _escStatic, name: name}
|
|
}
|
|
|
|
// {{.FunctionPrefix}}FSByte returns the named file from the embedded assets. If useLocal is
|
|
// true, the filesystem's contents are instead used.
|
|
func {{.FunctionPrefix}}FSByte(useLocal bool, name string) ([]byte, error) {
|
|
if useLocal {
|
|
f, err := _escLocal.Open(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b, err := ioutil.ReadAll(f)
|
|
_ = f.Close()
|
|
return b, err
|
|
}
|
|
f, err := _escStatic.prepare(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f.data, nil
|
|
}
|
|
|
|
// {{.FunctionPrefix}}FSMustByte is the same as {{.FunctionPrefix}}FSByte, but panics if name is not present.
|
|
func {{.FunctionPrefix}}FSMustByte(useLocal bool, name string) []byte {
|
|
b, err := {{.FunctionPrefix}}FSByte(useLocal, name)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// {{.FunctionPrefix}}FSString is the string version of {{.FunctionPrefix}}FSByte.
|
|
func {{.FunctionPrefix}}FSString(useLocal bool, name string) (string, error) {
|
|
b, err := {{.FunctionPrefix}}FSByte(useLocal, name)
|
|
return string(b), err
|
|
}
|
|
|
|
// {{.FunctionPrefix}}FSMustString is the string version of {{.FunctionPrefix}}FSMustByte.
|
|
func {{.FunctionPrefix}}FSMustString(useLocal bool, name string) string {
|
|
return string({{.FunctionPrefix}}FSMustByte(useLocal, name))
|
|
}
|
|
|
|
var _escData = map[string]*_escFile{
|
|
`
|
|
footer = `}
|
|
`
|
|
)
|