bee/cmd/commands/bale/bale.go

251 lines
5.2 KiB
Go

// Copyright 2013 bee authors
//
// 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 bale
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"github.com/beego/bee/v2/cmd/commands"
"github.com/beego/bee/v2/cmd/commands/version"
"github.com/beego/bee/v2/config"
beeLogger "github.com/beego/bee/v2/logger"
"github.com/beego/bee/v2/utils"
)
var CmdBale = &commands.Command{
UsageLine: "bale",
Short: "Transforms non-Go files to Go source files",
Long: `Bale command compress all the static files in to a single binary file.
This is useful to not have to carry static files including js, css, images and
views when deploying a Web application.
It will auto-generate an unpack function to the main package then run it during the runtime.
This is mainly used for zealots who are requiring 100% Go code.
`,
PreRun: func(cmd *commands.Command, args []string) { version.ShowShortVersionBanner() },
Run: runBale,
}
func init() {
commands.AvailableCommands = append(commands.AvailableCommands, CmdBale)
}
func runBale(cmd *commands.Command, args []string) int {
os.RemoveAll("bale")
os.Mkdir("bale", os.ModePerm)
// Pack and compress data
for _, p := range config.Conf.Bale.Dirs {
if !utils.IsExist(p) {
beeLogger.Log.Warnf("Skipped directory: %s", p)
continue
}
beeLogger.Log.Infof("Packaging directory: %s", p)
filepath.Walk(p, walkFn)
}
// Generate auto-uncompress function.
buf := new(bytes.Buffer)
buf.WriteString(fmt.Sprintf(BaleHeader, config.Conf.Bale.Import,
strings.Join(resFiles, "\",\n\t\t\""),
strings.Join(resFiles, ",\n\t\tbale.R")))
fw, err := os.Create("bale.go")
if err != nil {
beeLogger.Log.Fatalf("Failed to create file: %s", err)
}
defer fw.Close()
_, err = fw.Write(buf.Bytes())
if err != nil {
beeLogger.Log.Fatalf("Failed to write data: %s", err)
}
beeLogger.Log.Success("Baled resources successfully!")
return 0
}
const (
// BaleHeader ...
BaleHeader = `package main
import(
"os"
"strings"
"path"
"%s"
)
func isExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
func init() {
files := []string{
"%s",
}
funcs := []func() []byte{
bale.R%s,
}
for i, f := range funcs {
fp := getFilePath(files[i])
if !isExist(fp) {
saveFile(fp, f())
}
}
}
func getFilePath(name string) string {
name = strings.Replace(name, "_4_", "/", -1)
name = strings.Replace(name, "_3_", " ", -1)
name = strings.Replace(name, "_2_", "-", -1)
name = strings.Replace(name, "_1_", ".", -1)
name = strings.Replace(name, "_0_", "_", -1)
return name
}
func saveFile(filePath string, b []byte) (int, error) {
os.MkdirAll(path.Dir(filePath), os.ModePerm)
fw, err := os.Create(filePath)
if err != nil {
return 0, err
}
defer fw.Close()
return fw.Write(b)
}
`
)
var resFiles = make([]string, 0, 10)
func walkFn(resPath string, info os.FileInfo, _ error) error {
if info.IsDir() || filterSuffix(resPath) {
return nil
}
// Open resource files
fr, err := os.Open(resPath)
if err != nil {
beeLogger.Log.Fatalf("Failed to read file: %s", err)
}
// Convert path
resPath = strings.Replace(resPath, "_", "_0_", -1)
resPath = strings.Replace(resPath, ".", "_1_", -1)
resPath = strings.Replace(resPath, "-", "_2_", -1)
resPath = strings.Replace(resPath, " ", "_3_", -1)
sep := "/"
if runtime.GOOS == "windows" {
sep = "\\"
}
resPath = strings.Replace(resPath, sep, "_4_", -1)
// Create corresponding Go source files
os.MkdirAll(path.Dir(resPath), os.ModePerm)
fw, err := os.Create("bale/" + resPath + ".go")
if err != nil {
beeLogger.Log.Fatalf("Failed to create file: %s", err)
}
defer fw.Close()
// Write header
fmt.Fprintf(fw, Header, resPath)
// Copy and compress data
gz := gzip.NewWriter(&ByteWriter{Writer: fw})
io.Copy(gz, fr)
gz.Close()
// Write footer.
fmt.Fprint(fw, Footer)
resFiles = append(resFiles, resPath)
return nil
}
func filterSuffix(name string) bool {
for _, s := range config.Conf.Bale.IngExt {
if strings.HasSuffix(name, s) {
return true
}
}
return false
}
const (
// Header ...
Header = `package bale
import(
"bytes"
"compress/gzip"
"io"
)
func R%s() []byte {
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`
// Footer ...
Footer = `
}))
if err != nil {
panic("Unpack resources failed: " + err.Error())
}
var b bytes.Buffer
io.Copy(&b, gz)
gz.Close()
return b.Bytes()
}`
)
var newline = []byte{'\n'}
// ByteWriter ...
type ByteWriter struct {
io.Writer
c int
}
func (w *ByteWriter) Write(p []byte) (n int, err error) {
if len(p) == 0 {
return
}
for n = range p {
if w.c%12 == 0 {
w.Writer.Write(newline)
w.c = 0
}
fmt.Fprintf(w.Writer, "0x%02x,", p[n])
w.c++
}
n++
return
}