384 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			384 lines
		
	
	
		
			8.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"bytes"
 | 
						|
	"crypto/md5"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"go/format"
 | 
						|
	"io/ioutil"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path"
 | 
						|
	"path/filepath"
 | 
						|
	"runtime"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/UnnoTed/fileb0x/compression"
 | 
						|
	"github.com/UnnoTed/fileb0x/config"
 | 
						|
	"github.com/UnnoTed/fileb0x/custom"
 | 
						|
	"github.com/UnnoTed/fileb0x/dir"
 | 
						|
	"github.com/UnnoTed/fileb0x/file"
 | 
						|
	"github.com/UnnoTed/fileb0x/template"
 | 
						|
	"github.com/UnnoTed/fileb0x/updater"
 | 
						|
	"github.com/UnnoTed/fileb0x/utils"
 | 
						|
 | 
						|
	// just to install automatically
 | 
						|
	_ "github.com/labstack/echo"
 | 
						|
	_ "golang.org/x/net/webdav"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	err     error
 | 
						|
	cfg     *config.Config
 | 
						|
	files   = make(map[string]*file.File)
 | 
						|
	dirs    = new(dir.Dir)
 | 
						|
	cfgPath string
 | 
						|
 | 
						|
	fUpdate   string
 | 
						|
	startTime = time.Now()
 | 
						|
 | 
						|
	hashStart = []byte("// modification hash(")
 | 
						|
	hashEnd   = []byte(")")
 | 
						|
 | 
						|
	modTimeStart = []byte("// modified(")
 | 
						|
	modTimeEnd   = []byte(")")
 | 
						|
)
 | 
						|
 | 
						|
func main() {
 | 
						|
	runtime.GOMAXPROCS(runtime.NumCPU())
 | 
						|
 | 
						|
	// check for updates
 | 
						|
	flag.StringVar(&fUpdate, "update", "", "-update=http(s)://host:port - default port: 8041")
 | 
						|
	flag.Parse()
 | 
						|
	var (
 | 
						|
		update = fUpdate != ""
 | 
						|
		up     *updater.Updater
 | 
						|
	)
 | 
						|
 | 
						|
	// create config and try to get b0x file from args
 | 
						|
	f := new(config.File)
 | 
						|
	err = f.FromArg(true)
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	// load b0x file's config
 | 
						|
	cfg, err = f.Load()
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = cfg.Defaults()
 | 
						|
	if err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	cfgPath = f.FilePath
 | 
						|
 | 
						|
	if err := cfg.Updater.CheckInfo(); err != nil {
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	cfg.Updater.IsUpdating = update
 | 
						|
 | 
						|
	// creates a config that can be inserTed into custom
 | 
						|
	// without causing a import cycle
 | 
						|
	sharedConfig := new(custom.SharedConfig)
 | 
						|
	sharedConfig.Output = cfg.Output
 | 
						|
	sharedConfig.Updater = cfg.Updater
 | 
						|
	sharedConfig.Compression = compression.NewGzip()
 | 
						|
	sharedConfig.Compression.Options = cfg.Compression
 | 
						|
 | 
						|
	// loop through b0x's [custom] objects
 | 
						|
	for _, c := range cfg.Custom {
 | 
						|
		err = c.Parse(&files, &dirs, sharedConfig)
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// builds remap's list
 | 
						|
	var (
 | 
						|
		remap    string
 | 
						|
		modHash  string
 | 
						|
		mods     []string
 | 
						|
		lastHash string
 | 
						|
	)
 | 
						|
 | 
						|
	for _, f := range files {
 | 
						|
		remap += f.GetRemap()
 | 
						|
		mods = append(mods, f.Modified)
 | 
						|
	}
 | 
						|
 | 
						|
	// sorts modification time list and create a md5 of it
 | 
						|
	sort.Strings(mods)
 | 
						|
	modHash = stringMD5Hex(strings.Join(mods, "")) + "." + stringMD5Hex(string(f.Data))
 | 
						|
	exists := fileExists(cfg.Dest + cfg.Output)
 | 
						|
 | 
						|
	if exists {
 | 
						|
		// gets the modification hash from the main b0x file
 | 
						|
		lastHash, err = getModification(cfg.Dest+cfg.Output, hashStart, hashEnd)
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !exists || lastHash != modHash {
 | 
						|
		// create files template and exec it
 | 
						|
		t := new(template.Template)
 | 
						|
		t.Set("files")
 | 
						|
		t.Variables = struct {
 | 
						|
			ConfigFile       string
 | 
						|
			Now              string
 | 
						|
			Pkg              string
 | 
						|
			Files            map[string]*file.File
 | 
						|
			Tags             string
 | 
						|
			Spread           bool
 | 
						|
			Remap            string
 | 
						|
			DirList          []string
 | 
						|
			Compression      *compression.Options
 | 
						|
			Debug            bool
 | 
						|
			Updater          updater.Config
 | 
						|
			ModificationHash string
 | 
						|
		}{
 | 
						|
			ConfigFile:       filepath.Base(cfgPath),
 | 
						|
			Now:              time.Now().String(),
 | 
						|
			Pkg:              cfg.Pkg,
 | 
						|
			Files:            files,
 | 
						|
			Tags:             cfg.Tags,
 | 
						|
			Remap:            remap,
 | 
						|
			Spread:           cfg.Spread,
 | 
						|
			DirList:          dirs.Clean(),
 | 
						|
			Compression:      cfg.Compression,
 | 
						|
			Debug:            cfg.Debug,
 | 
						|
			Updater:          cfg.Updater,
 | 
						|
			ModificationHash: modHash,
 | 
						|
		}
 | 
						|
 | 
						|
		tmpl, err := t.Exec()
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
 | 
						|
		if err := os.MkdirAll(cfg.Dest, 0770); err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// gofmt
 | 
						|
		if cfg.Fmt {
 | 
						|
			tmpl, err = format.Source(tmpl)
 | 
						|
			if err != nil {
 | 
						|
				panic(err)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// write final execuTed template into the destination file
 | 
						|
		err = ioutil.WriteFile(cfg.Dest+cfg.Output, tmpl, 0640)
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// write spread files
 | 
						|
	var (
 | 
						|
		finalList   []string
 | 
						|
		changedList []string
 | 
						|
	)
 | 
						|
	if cfg.Spread {
 | 
						|
		a := strings.Split(path.Dir(cfg.Dest), "/")
 | 
						|
		dirName := a[len(a)-1:][0]
 | 
						|
 | 
						|
		for _, f := range files {
 | 
						|
			a := strings.Split(path.Dir(f.Path), "/")
 | 
						|
			fileDirName := a[len(a)-1:][0]
 | 
						|
 | 
						|
			if dirName == fileDirName {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			// transform / to _ and some other chars...
 | 
						|
			customName := "b0xfile_" + utils.FixName(f.Path) + ".go"
 | 
						|
			finalList = append(finalList, customName)
 | 
						|
 | 
						|
			exists := fileExists(cfg.Dest + customName)
 | 
						|
			var mth string
 | 
						|
			if exists {
 | 
						|
				mth, err = getModification(cfg.Dest+customName, modTimeStart, modTimeEnd)
 | 
						|
				if err != nil {
 | 
						|
					panic(err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			changed := mth != f.Modified
 | 
						|
			if changed {
 | 
						|
				changedList = append(changedList, f.OriginalPath)
 | 
						|
			}
 | 
						|
 | 
						|
			if !exists || changed {
 | 
						|
				// creates file template and exec it
 | 
						|
				t := new(template.Template)
 | 
						|
				t.Set("file")
 | 
						|
				t.Variables = struct {
 | 
						|
					ConfigFile   string
 | 
						|
					Now          string
 | 
						|
					Pkg          string
 | 
						|
					Path         string
 | 
						|
					Name         string
 | 
						|
					Dir          [][]string
 | 
						|
					Tags         string
 | 
						|
					Data         string
 | 
						|
					Compression  *compression.Options
 | 
						|
					Modified     string
 | 
						|
					OriginalPath string
 | 
						|
				}{
 | 
						|
					ConfigFile:   filepath.Base(cfgPath),
 | 
						|
					Now:          time.Now().String(),
 | 
						|
					Pkg:          cfg.Pkg,
 | 
						|
					Path:         f.Path,
 | 
						|
					Name:         f.Name,
 | 
						|
					Dir:          dirs.List,
 | 
						|
					Tags:         f.Tags,
 | 
						|
					Data:         f.Data,
 | 
						|
					Compression:  cfg.Compression,
 | 
						|
					Modified:     f.Modified,
 | 
						|
					OriginalPath: f.OriginalPath,
 | 
						|
				}
 | 
						|
				tmpl, err := t.Exec()
 | 
						|
				if err != nil {
 | 
						|
					panic(err)
 | 
						|
				}
 | 
						|
 | 
						|
				// gofmt
 | 
						|
				if cfg.Fmt {
 | 
						|
					tmpl, err = format.Source(tmpl)
 | 
						|
					if err != nil {
 | 
						|
						panic(err)
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				// write final execuTed template into the destination file
 | 
						|
				if err := ioutil.WriteFile(cfg.Dest+customName, tmpl, 0640); err != nil {
 | 
						|
					panic(err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// remove b0xfiles when [clean] is true
 | 
						|
	// it doesn't clean destination's folders
 | 
						|
	if cfg.Clean {
 | 
						|
		matches, err := filepath.Glob(cfg.Dest + "b0xfile_*.go")
 | 
						|
		if err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// remove matched file if they aren't in the finalList
 | 
						|
		// which contains the list of all files written by the
 | 
						|
		// spread option
 | 
						|
		for _, f := range matches {
 | 
						|
			var found bool
 | 
						|
			for _, name := range finalList {
 | 
						|
				if strings.HasSuffix(f, name) {
 | 
						|
					found = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			if !found {
 | 
						|
				err = os.Remove(f)
 | 
						|
				if err != nil {
 | 
						|
					panic(err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// main b0x
 | 
						|
	if lastHash != modHash {
 | 
						|
		log.Printf("fileb0x: took [%dms] to write [%s] from config file [%s] at [%s]",
 | 
						|
			time.Since(startTime).Nanoseconds()/1e6, cfg.Dest+cfg.Output,
 | 
						|
			filepath.Base(cfgPath), time.Now().String())
 | 
						|
	} else {
 | 
						|
		log.Printf("fileb0x: no changes detected")
 | 
						|
	}
 | 
						|
 | 
						|
	// log changed files
 | 
						|
	if cfg.Lcf && len(changedList) > 0 {
 | 
						|
		log.Printf("fileb0x: list of changed files [%s]", strings.Join(changedList, " | "))
 | 
						|
	}
 | 
						|
 | 
						|
	if update {
 | 
						|
		if !cfg.Updater.Enabled {
 | 
						|
			panic("fileb0x: The updater is disabled, enable it in your config file!")
 | 
						|
		}
 | 
						|
 | 
						|
		// includes port when not present
 | 
						|
		if !strings.HasSuffix(fUpdate, ":"+strconv.Itoa(cfg.Updater.Port)) {
 | 
						|
			fUpdate += ":" + strconv.Itoa(cfg.Updater.Port)
 | 
						|
		}
 | 
						|
 | 
						|
		up = &updater.Updater{
 | 
						|
			Server: fUpdate,
 | 
						|
			Auth: updater.Auth{
 | 
						|
				Username: cfg.Updater.Username,
 | 
						|
				Password: cfg.Updater.Password,
 | 
						|
			},
 | 
						|
			Workers: cfg.Updater.Workers,
 | 
						|
		}
 | 
						|
 | 
						|
		// get file hashes from server
 | 
						|
		if err := up.Init(); err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
 | 
						|
		// check if an update is available, then updates...
 | 
						|
		if err := up.UpdateFiles(files); err != nil {
 | 
						|
			panic(err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func getModification(path string, start []byte, end []byte) (string, error) {
 | 
						|
	file, err := os.Open(path)
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	defer file.Close()
 | 
						|
 | 
						|
	reader := bufio.NewReader(file)
 | 
						|
	var data []byte
 | 
						|
	for {
 | 
						|
		line, _, err := reader.ReadLine()
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
 | 
						|
		if !bytes.HasPrefix(line, start) || !bytes.HasSuffix(line, end) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		data = line
 | 
						|
		break
 | 
						|
	}
 | 
						|
 | 
						|
	hash := bytes.TrimPrefix(data, start)
 | 
						|
	hash = bytes.TrimSuffix(hash, end)
 | 
						|
 | 
						|
	return string(hash), nil
 | 
						|
}
 | 
						|
 | 
						|
func fileExists(filename string) bool {
 | 
						|
	_, err := os.Stat(filename)
 | 
						|
	return err == nil
 | 
						|
}
 | 
						|
 | 
						|
func stringMD5Hex(data string) string {
 | 
						|
	hash := md5.New()
 | 
						|
	hash.Write([]byte(data))
 | 
						|
	return fmt.Sprintf("%x", hash.Sum(nil))
 | 
						|
}
 |