├── test.bat
├── _example
├── gin
│ ├── .gitignore
│ ├── run
│ ├── css
│ │ └── main.css
│ ├── templates
│ │ └── index.html
│ ├── main.go
│ ├── go.mod
│ ├── b0x.yml
│ └── go.sum
├── simple
│ ├── run.bat
│ ├── b0x.bat
│ ├── run
│ ├── public
│ │ ├── assets
│ │ │ └── data
│ │ │ │ ├── exclude_me.txt
│ │ │ │ ├── test2.json
│ │ │ │ └── test1.json
│ │ ├── this is just some random stuff!.txt
│ │ ├── secrets.txt
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── reinstall.sh
│ ├── main.go
│ ├── go.mod
│ ├── go.sum
│ ├── b0x.toml
│ ├── b0x.yaml
│ └── b0x.json
└── echo
│ ├── run
│ ├── main.go
│ ├── ufo.html
│ └── b0x.yml
├── bench.bat
├── circle.yml
├── .gitignore
├── run
├── run.bat
├── custom
├── replacer.go
├── custom_test.go
└── custom.go
├── lint.bat
├── config
├── regexp.go
├── regexp_test.go
├── config_test.go
├── config.go
├── file.go
└── file_test.go
├── file
├── file.go
├── methods.go
└── methods_windows.go
├── compression
├── options.go
└── gzip.go
├── bench.txt
├── template
├── files_test.go
├── template.go
├── funcs_test.go
├── file.go
├── funcs.go
├── template_test.go
└── files.go
├── utils
├── utils.go
└── utils_test.go
├── updater
├── config.go
├── updater_test.go
├── server_test.go
└── updater.go
├── go.mod
├── LICENSE
├── CHANGELOG.md
├── dir
├── dir_test.go
└── dir.go
├── main_test.go
├── go.sum
├── main.go
└── README.md
/test.bat:
--------------------------------------------------------------------------------
1 | go test ./... -v
--------------------------------------------------------------------------------
/_example/gin/.gitignore:
--------------------------------------------------------------------------------
1 | myEmbeddedFiles
--------------------------------------------------------------------------------
/_example/simple/run.bat:
--------------------------------------------------------------------------------
1 | go run main.go
--------------------------------------------------------------------------------
/bench.bat:
--------------------------------------------------------------------------------
1 | go test -bench=. -benchmem -v
--------------------------------------------------------------------------------
/_example/simple/b0x.bat:
--------------------------------------------------------------------------------
1 | fileb0x b0x.json
--------------------------------------------------------------------------------
/_example/echo/run:
--------------------------------------------------------------------------------
1 | go generate
2 | go run main.go
3 |
--------------------------------------------------------------------------------
/_example/gin/run:
--------------------------------------------------------------------------------
1 | go generate
2 | go run main.go
3 |
--------------------------------------------------------------------------------
/_example/simple/run:
--------------------------------------------------------------------------------
1 | go generate
2 | go run main.go
3 |
--------------------------------------------------------------------------------
/_example/simple/public/assets/data/exclude_me.txt:
--------------------------------------------------------------------------------
1 | i like turtles
--------------------------------------------------------------------------------
/_example/simple/public/this is just some random stuff!.txt:
--------------------------------------------------------------------------------
1 | yeah
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | test:
2 | override:
3 | - go test ./... -v
4 |
--------------------------------------------------------------------------------
/_example/simple/public/secrets.txt:
--------------------------------------------------------------------------------
1 | jet fuel can't melt steel beams
--------------------------------------------------------------------------------
/_example/simple/public/assets/data/test2.json:
--------------------------------------------------------------------------------
1 | {
2 | "email": "{EMAIL}"
3 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _example/simple/static/
2 | _example/echo/myEmbeddedFiles/
3 | fileb0x
4 |
--------------------------------------------------------------------------------
/_example/simple/reinstall.sh:
--------------------------------------------------------------------------------
1 | cd ../..
2 | go install
3 | cd _example/simple
4 |
5 |
--------------------------------------------------------------------------------
/run:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | go install
4 | cd ./_example/simple/
5 |
6 | ./run
7 | cd ../../
8 |
--------------------------------------------------------------------------------
/_example/simple/public/assets/data/test1.json:
--------------------------------------------------------------------------------
1 | {
2 | "he": "llo",
3 | "replace_test": "{world}"
4 | }
--------------------------------------------------------------------------------
/_example/gin/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: red;
3 | color: white;
4 | font-size: 3em;
5 | }
--------------------------------------------------------------------------------
/_example/simple/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/UnnoTed/fileb0x/HEAD/_example/simple/public/favicon.ico
--------------------------------------------------------------------------------
/run.bat:
--------------------------------------------------------------------------------
1 | go install
2 |
3 | @echo off
4 | cd .\_example\simple\
5 | @echo on
6 |
7 | call b0x.bat
8 |
9 | @echo off
10 | cd ..\..\
11 | @echo on
--------------------------------------------------------------------------------
/custom/replacer.go:
--------------------------------------------------------------------------------
1 | package custom
2 |
3 | // Replacer strings in a file
4 | type Replacer struct {
5 | File string
6 | Replace map[string]string
7 | }
8 |
--------------------------------------------------------------------------------
/lint.bat:
--------------------------------------------------------------------------------
1 | golint ./...
2 |
3 | @echo off
4 | cd .\_example\simple\
5 | @echo on
6 |
7 | call golint ./...
8 |
9 | @echo off
10 | cd ..\..\
11 | @echo on
--------------------------------------------------------------------------------
/config/regexp.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import "regexp"
4 |
5 | var (
6 | // used to remove comments from json
7 | regexComments = regexp.MustCompile(`\/\/([\w\s\'].*)`)
8 |
9 | // SafeVarName is used to remove special chars from paths
10 | SafeVarName = regexp.MustCompile(`[^a-zA-Z0-9]`)
11 | )
12 |
--------------------------------------------------------------------------------
/_example/gin/templates/index.html:
--------------------------------------------------------------------------------
1 | {{define "index"}}
2 |
3 |
4 |
5 |
6 | Test
7 |
8 |
9 |
10 | CSS files are available under /css.
11 |
12 |
13 | {{end}}
--------------------------------------------------------------------------------
/file/file.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | // File holds file's data
4 | type File struct {
5 | OriginalPath string
6 | Name string
7 | Path string
8 | Data string
9 | Bytes []byte
10 | ReplacedText bool
11 | Tags string
12 | Base string
13 | Prefix string
14 | Modified string
15 | }
16 |
17 | // NewFile creates a new File
18 | func NewFile() *File {
19 | f := new(File)
20 | return f
21 | }
22 |
--------------------------------------------------------------------------------
/file/methods.go:
--------------------------------------------------------------------------------
1 | // +build !windows
2 |
3 | package file
4 |
5 | // GetRemap returns a map's params with
6 | // info required to load files directly
7 | // from the hard drive when using prefix
8 | // and base while debug mode is activaTed
9 | func (f *File) GetRemap() string {
10 | if f.Base == "" && f.Prefix == "" {
11 | return ""
12 | }
13 |
14 | return `"` + f.Path + `": {
15 | "prefix": "` + f.Prefix + `",
16 | "base": "` + f.Base + `",
17 | },`
18 | }
19 |
--------------------------------------------------------------------------------
/file/methods_windows.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import "strings"
4 |
5 | // GetRemap returns a map's params with
6 | // info required to load files directly
7 | // from the hard drive when using prefix
8 | // and base while debug mode is activaTed
9 | func (f *File) GetRemap() string {
10 | if f.Base == "" && f.Prefix == "" {
11 | return ""
12 | }
13 |
14 | return `"` + strings.Replace(f.Path, `\`, `\\`, -1) + `": {
15 | "prefix": "` + f.Prefix + `",
16 | "base": "` + f.Base + `",
17 | },`
18 | }
19 |
--------------------------------------------------------------------------------
/config/regexp_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestRegexpComments(t *testing.T) {
10 | j := `{
11 | // wat
12 | }`
13 |
14 | j = regexComments.ReplaceAllString(j, "")
15 |
16 | assert.Equal(t, `{
17 |
18 | }`, j)
19 | }
20 |
21 | func TestRegexpSafeVarName(t *testing.T) {
22 | v := `hi_i have a cat, my cat's name is cat, cat is a cat!`
23 |
24 | v = SafeVarName.ReplaceAllString(v, "")
25 |
26 | assert.Equal(t, `hiihaveacatmycatsnameiscatcatisacat`, v)
27 | }
28 |
--------------------------------------------------------------------------------
/compression/options.go:
--------------------------------------------------------------------------------
1 | package compression
2 |
3 | // Options for compression
4 | type Options struct {
5 | // activates the compression
6 | // default: false
7 | Compress bool
8 |
9 | // valid values are:
10 | // -> "NoCompression"
11 | // -> "BestSpeed"
12 | // -> "BestCompression"
13 | // -> "DefaultCompression"
14 | //
15 | // default: "DefaultCompression" // when: Compress == true && Method == ""
16 | Method string
17 |
18 | // true = do it yourself (the file is written as gzip into the memory file system)
19 | // false = decompress at run time (while writing file into memory file system)
20 | // default: false
21 | Keep bool
22 | }
23 |
--------------------------------------------------------------------------------
/bench.txt:
--------------------------------------------------------------------------------
1 | ./_example/echo/ufo.html (1.4kb)
2 | BenchmarkOldConvert-4 50000 37127 ns/op 31200 B/op 11 allocs/op
3 | BenchmarkNewConvert-4 300000 5847 ns/op 12288 B/op 2 allocs/op
4 |
5 | gitkraken's binary (80mb)
6 | BenchmarkOldConvert-4 1 1777277402 ns/op 1750946416 B/op 30 allocs/op
7 | BenchmarkNewConvert-4 5 236663214 ns/op 643629056 B/op 2 allocs/op
8 |
9 | https://www.youtube.com/watch?v=fT4lDU-QLUY (232mb)
10 | BenchmarkOldConvert-4 1 5089024416 ns/op 4071281120 B/op 28 allocs/op
11 | BenchmarkNewConvert-4 2 712384868 ns/op 1856667696 B/op 2 allocs/op
12 |
--------------------------------------------------------------------------------
/template/files_test.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestDebugRemapPath(t *testing.T) {
11 | remap := map[string]map[string]string{
12 | "public/README.md": {
13 | "prefix": "public/",
14 | "base": "../../",
15 | },
16 | }
17 |
18 | open := func(path string) string {
19 | path = strings.TrimPrefix(path, "/")
20 |
21 | for current, f := range remap {
22 | if path == current {
23 | path = f["base"] + strings.TrimPrefix(path, f["prefix"])
24 | break
25 | }
26 | }
27 |
28 | return path
29 | }
30 |
31 | tests := map[string]string{
32 | "/public/README.md": "../../README.md",
33 | }
34 |
35 | for actual, expected := range tests {
36 | assert.Equal(t, expected, open(actual))
37 | t.Log(expected, actual, open(actual))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/_example/gin/main.go:
--------------------------------------------------------------------------------
1 | //go:generate fileb0x b0x.yml
2 | package main
3 |
4 | import (
5 | "github.com/gin-gonic/gin"
6 | "html/template"
7 | "net/http"
8 | // your embedded files import here ...
9 | "github.com/UnnoTed/fileb0x/_example/gin/myEmbeddedFiles"
10 | )
11 |
12 | func main() {
13 | indexData, err := myEmbeddedFiles.ReadFile("templates/index.html")
14 | if err != nil {
15 | panic(err)
16 | }
17 |
18 | tmpl := template.New("")
19 | tmpl, err = tmpl.Parse(string(indexData))
20 | if err != nil {
21 | panic(err)
22 | }
23 |
24 | r := gin.Default()
25 | r.SetHTMLTemplate(tmpl)
26 |
27 | // Note that thanks to the prefix you can't access templates/index.html here
28 | r.StaticFS("/css", &myEmbeddedFiles.HTTPFS{Prefix: "css"})
29 |
30 | r.GET("/", func(c *gin.Context) {
31 | c.HTML(http.StatusOK, "index", nil)
32 | })
33 | r.Run() // listen and serve on 0.0.0.0:8080
34 | }
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "os"
5 | "path/filepath"
6 | "strings"
7 | )
8 |
9 | // FixPath converts \ and \\ to /
10 | func FixPath(path string) string {
11 | a := filepath.Clean(path)
12 | b := strings.Replace(a, `\`, "/", -1)
13 | c := strings.Replace(b, `\\`, "/", -1)
14 | return c
15 | }
16 |
17 | // FixName converts [/ to _](1), [ to -](2) and [, to __](3)
18 | func FixName(path string) string {
19 | a := FixPath(path)
20 | b := strings.Replace(a, "/", "_", -1) // / to _
21 | c := strings.Replace(b, " ", "-", -1) // {space} to -
22 | return strings.Replace(c, ",", "__", -1) // , to __
23 | }
24 |
25 | // GetCurrentDir gets the directory where the application was run
26 | func GetCurrentDir() (string, error) {
27 | d, err := filepath.Abs(filepath.Dir(os.Args[0]))
28 | return d, err
29 | }
30 |
31 | // Exists returns true when a folder/file exists
32 | func Exists(path string) bool {
33 | _, err := os.Stat(path)
34 | return !os.IsNotExist(err)
35 | }
36 |
--------------------------------------------------------------------------------
/updater/config.go:
--------------------------------------------------------------------------------
1 | package updater
2 |
3 | import (
4 | "errors"
5 | "os"
6 | )
7 |
8 | type Config struct {
9 | IsUpdating bool
10 | Username string
11 | Password string
12 | Enabled bool
13 | Workers int
14 | Empty bool
15 | Port int
16 | }
17 |
18 | func (u Config) CheckInfo() error {
19 | if !u.Enabled {
20 | return nil
21 | }
22 |
23 | if u.Username == "{FROM_ENV}" || u.Username == "" {
24 | u.Username = os.Getenv("fileb0x_username")
25 | }
26 |
27 | if u.Password == "{FROM_ENV}" || u.Password == "" {
28 | u.Password = os.Getenv("fileb0x_password")
29 | }
30 |
31 | // check for empty username and password
32 | if u.Username == "" {
33 | return errors.New("fileb0x: You must provide an username in the config file or through an env var: fileb0x_username")
34 |
35 | } else if u.Password == "" {
36 | return errors.New("fileb0x: You must provide an password in the config file or through an env var: fileb0x_password")
37 | }
38 |
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/config/config_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestConfigDefaults(t *testing.T) {
11 | var err error
12 | cfgList := []*Config{
13 | {Dest: "", Pkg: "", Output: ""},
14 | {Dest: "hello", Pkg: "static", Output: "ssets"},
15 | {Dest: "hello", Pkg: "static", Output: "amissingEnd"},
16 | {Dest: "hello", Pkg: "static", Output: "compiled", NoPrefix: true},
17 | }
18 |
19 | expecTedList := []*Config{
20 | {Dest: "/", Pkg: "main", Output: "ab0x.go"},
21 | {Dest: "hello/", Pkg: "static", Output: "assets.go"},
22 | {Dest: "hello/", Pkg: "static", Output: "amissingEnd.go"},
23 | {Dest: "hello/", Pkg: "static", Output: "compiled.go", NoPrefix: true},
24 | }
25 |
26 | for i, cfg := range cfgList {
27 | err = cfg.Defaults()
28 | assert.NoError(t, err)
29 |
30 | err = expecTedList[i].Defaults()
31 | assert.NoError(t, err)
32 |
33 | eq := reflect.DeepEqual(cfg, expecTedList[i])
34 | assert.True(t, eq, "NOT EQUAL:", cfg, expecTedList[i])
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/_example/gin/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/UnnoTed/fileb0x/_example/gin
2 |
3 | require (
4 | github.com/davecgh/go-spew v1.1.1 // indirect
5 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
6 | github.com/gin-gonic/gin v1.3.0
7 | github.com/golang/protobuf v1.2.0 // indirect
8 | github.com/json-iterator/go v1.1.5 // indirect
9 | github.com/mattn/go-isatty v0.0.4 // indirect
10 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
11 | github.com/modern-go/reflect2 v1.0.1 // indirect
12 | github.com/pmezard/go-difflib v1.0.0 // indirect
13 | github.com/stretchr/testify v1.2.2 // indirect
14 | github.com/ugorji/go/codec v0.0.0-20181018023622-b30ce92d500b // indirect
15 | golang.org/x/net v0.0.0-20181017193950-04a2e542c03f
16 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
17 | golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 // indirect
18 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
19 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
20 | gopkg.in/yaml.v2 v2.2.1 // indirect
21 | )
22 |
--------------------------------------------------------------------------------
/_example/echo/main.go:
--------------------------------------------------------------------------------
1 | //go:generate fileb0x b0x.yml
2 | package main
3 |
4 | import (
5 | "log"
6 | "net/http"
7 |
8 | "github.com/labstack/echo"
9 |
10 | // your embedded files import here ...
11 | "github.com/UnnoTed/fileb0x/_example/echo/myEmbeddedFiles"
12 | "github.com/UnnoTed/open-golang/open"
13 | )
14 |
15 | func main() {
16 | e := echo.New()
17 | e.Debug = true
18 |
19 | // enable any filename to be loaded from in-memory file system
20 | e.GET("/*", echo.WrapHandler(myEmbeddedFiles.Handler))
21 |
22 | // read ufo.html from in-memory file system
23 | htmlb, err := myEmbeddedFiles.ReadFile("ufo.html")
24 | if err != nil {
25 | log.Fatal(err)
26 | }
27 |
28 | // convert to string
29 | html := string(htmlb)
30 |
31 | // serve ufo.html through "/"
32 | e.GET("/", func(c echo.Context) error {
33 |
34 | // serve it
35 | return c.HTML(http.StatusOK, html)
36 | })
37 |
38 | // try it -> http://localhost:1337/
39 | // http://localhost:1337/ufo.html
40 | // http://localhost:1337/public/README.md
41 | open.Run("http://localhost:1337/")
42 | e.Start(":1337")
43 | }
44 |
--------------------------------------------------------------------------------
/_example/simple/main.go:
--------------------------------------------------------------------------------
1 | //go:generate go run github.com/UnnoTed/fileb0x b0x.yaml
2 | package main
3 |
4 | import (
5 | "log"
6 | "net/http"
7 |
8 | // "example.com/foo/simple" represents your package (as per go.mod)
9 | // package static is created by `go generate` according to b0x.yaml (as per the comment above)
10 | "example.com/foo/simple/static"
11 | )
12 |
13 | func main() {
14 | files, err := static.WalkDirs("", false)
15 | if err != nil {
16 | log.Fatal(err)
17 | }
18 |
19 | log.Println("ALL FILES", files)
20 |
21 | b, err := static.ReadFile("public/README.md")
22 | if err != nil {
23 | log.Fatal(err)
24 | }
25 |
26 | _ = b
27 | //log.Println(string(b))
28 | log.Println("try it -> http://localhost:8080/public/secrets.txt")
29 |
30 | // false = file system
31 | // true = handler
32 | as := false
33 |
34 | // try it -> http://localhost:8080/public/secrets.txt
35 | if as {
36 | // as Handler
37 | panic(http.ListenAndServe(":8080", static.Handler))
38 | } else {
39 | // as File System
40 | panic(http.ListenAndServe(":8080", http.FileServer(static.HTTP)))
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/UnnoTed/fileb0x
2 |
3 | require (
4 | github.com/BurntSushi/toml v0.3.1
5 | github.com/bmatcuk/doublestar v1.1.1
6 | github.com/davecgh/go-spew v1.1.1 // indirect
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
8 | github.com/gizak/termui/v3 v3.1.0
9 | github.com/karrick/godirwalk v1.7.8
10 | github.com/labstack/echo v3.2.1+incompatible
11 | github.com/labstack/gommon v0.2.7 // indirect
12 | github.com/mattn/go-colorable v0.0.9 // indirect
13 | github.com/mattn/go-isatty v0.0.4 // indirect
14 | github.com/mattn/go-runewidth v0.0.3 // indirect
15 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect
16 | github.com/pmezard/go-difflib v1.0.0 // indirect
17 | github.com/stretchr/testify v1.2.2
18 | github.com/valyala/bytebufferpool v1.0.0 // indirect
19 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
20 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b // indirect
21 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
22 | golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 // indirect
23 | gopkg.in/yaml.v2 v2.2.1
24 | )
25 |
26 | go 1.13
27 |
--------------------------------------------------------------------------------
/_example/simple/go.mod:
--------------------------------------------------------------------------------
1 | module example.com/foo/simple
2 |
3 | require (
4 | github.com/BurntSushi/toml v0.3.1 // indirect
5 | github.com/UnnoTed/fileb0x v0.0.0-20180803232343-279e1dc62738 // indirect
6 | github.com/airking05/termui v2.2.0+incompatible // indirect
7 | github.com/bmatcuk/doublestar v1.1.1 // indirect
8 | github.com/karrick/godirwalk v1.7.3 // indirect
9 | github.com/labstack/echo v3.2.1+incompatible // indirect
10 | github.com/labstack/gommon v0.2.7 // indirect
11 | github.com/maruel/panicparse v1.1.1 // indirect
12 | github.com/mattn/go-colorable v0.0.9 // indirect
13 | github.com/mattn/go-isatty v0.0.4 // indirect
14 | github.com/mattn/go-runewidth v0.0.3 // indirect
15 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect
16 | github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e // indirect
17 | github.com/valyala/bytebufferpool v1.0.0 // indirect
18 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
19 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b // indirect
20 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
21 | gopkg.in/yaml.v2 v2.2.1 // indirect
22 | )
23 |
--------------------------------------------------------------------------------
/template/template.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "text/template"
7 | )
8 |
9 | // Template holds b0x and file template
10 | type Template struct {
11 | template string
12 |
13 | name string
14 | Variables interface{}
15 | }
16 |
17 | // Set the template to be used
18 | // "files" or "file"
19 | func (t *Template) Set(name string) error {
20 | t.name = name
21 | if name != "files" && name != "file" {
22 | return errors.New(`Error: Template must be "files" or "file"`)
23 | }
24 |
25 | if name == "files" {
26 | t.template = filesTemplate
27 | } else if name == "file" {
28 | t.template = fileTemplate
29 | }
30 |
31 | return nil
32 | }
33 |
34 | // Exec the template and return the final data as byte array
35 | func (t *Template) Exec() ([]byte, error) {
36 | tmpl, err := template.New(t.name).Funcs(funcsTemplate).Parse(t.template)
37 | if err != nil {
38 | return nil, err
39 | }
40 |
41 | // exec template
42 | buff := bytes.NewBufferString("")
43 | err = tmpl.Execute(buff, t.Variables)
44 | if err != nil {
45 | return nil, err
46 | }
47 |
48 | return buff.Bytes(), nil
49 | }
50 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 UnnoTed (UnnoTedx@gmail.com)
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | To update simply run:
5 | ```bash
6 | go get -u github.com/UnnoTed/fileb0x
7 | ```
8 |
9 | ## 2018-04-17
10 | ### Changed
11 | - Improved file processing's speed
12 | - Improved walk speed with [godirwalk](https://github.com/karrick/godirwalk)
13 | - Fixed updater's progressbar
14 |
15 | ## 2018-03-17
16 | ### Added
17 | - Added condition to files' template to avoid creating error variable when not required.
18 |
19 | ## 2018-03-14
20 | ### Removed
21 | - [go-dry](https://github.com/ungerik/go-dry) dependency.
22 |
23 | ## 2018-02-22
24 | ### Added
25 | - Avoid rewriting the main b0x file by checking a MD5 hash of the (file's modification time + cfg).
26 | - Avoid rewriting unchanged files by comparing the Timestamp of the b0x's file and the file's modification time.
27 | - Config option `lcf` which when enabled along with `spread` **l**ogs the list of **c**hanged **f**iles to the console.
28 | - Message to inform that no file or cfg changes have been detecTed (not an error).
29 | ### Changed
30 | - Config option `clean` to only remove unused b0x files instead of everything.
31 |
--------------------------------------------------------------------------------
/dir/dir_test.go:
--------------------------------------------------------------------------------
1 | package dir
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | var d = new(Dir)
10 |
11 | func TestDirInsert(t *testing.T) {
12 | folder := "public/assets/img/png/"
13 | d.Insert(folder)
14 |
15 | assert.NotEmpty(t, d.List)
16 | assert.NotEmpty(t, d.Blacklist)
17 |
18 | // exists
19 | assert.True(t, d.Exists("public/"))
20 | assert.True(t, d.Exists("public/assets/"))
21 | assert.True(t, d.Exists("public/assets/img/"))
22 | assert.True(t, d.Exists("public/assets/img/png/"))
23 |
24 | expecTed := [][]string{
25 | {"public/", "public/assets/", "public/assets/img/", "public/assets/img/png/"},
26 | }
27 |
28 | ebl := []string{
29 | "public/assets/img/png/", // it should be removed on d.Clean()
30 | "public/",
31 | "public/assets/",
32 | "public/assets/img/",
33 | "public/assets/img/png/", // duplicaTed
34 | }
35 |
36 | assert.EqualValues(t, expecTed, d.List)
37 | assert.EqualValues(t, ebl, d.Blacklist)
38 | }
39 |
40 | func TestDirClean(t *testing.T) {
41 | clean := d.Clean()
42 |
43 | expecTed := []string{
44 | "public/",
45 | "public/assets/",
46 | "public/assets/img/",
47 | "public/assets/img/png/",
48 | }
49 |
50 | assert.EqualValues(t, expecTed, clean)
51 | }
52 |
--------------------------------------------------------------------------------
/template/funcs_test.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 | )
8 |
9 | func TestExported(t *testing.T) {
10 | SetUnexported(true)
11 | e := exported("HELLO")
12 | assert.Equal(t, `hello`, e)
13 |
14 | SetUnexported(false)
15 | e = exported("HELLO")
16 | assert.Equal(t, `HELLO`, e)
17 | }
18 |
19 | func TestExportedTitle(t *testing.T) {
20 | SetUnexported(true)
21 | e := exportedTitle("HELLO")
22 | assert.Equal(t, `hELLO`, e)
23 |
24 | SetUnexported(false)
25 | e = exportedTitle("hello")
26 | assert.Equal(t, `Hello`, e)
27 | }
28 |
29 | func TestVarName(t *testing.T) {
30 | var s string
31 | s = buildSafeVarName(`a/safe/variable.name`)
32 | assert.Equal(t, `ASafeVariableName`, s)
33 |
34 | s = buildSafeVarName(`a/safe/variable.html`)
35 | assert.Equal(t, `ASafeVariableHTML`, s)
36 |
37 | s = buildSafeVarName(`a/safe/variable.json`)
38 | assert.Equal(t, `ASafeVariableJSON`, s)
39 |
40 | s = buildSafeVarName(`a/safe/variable.url`)
41 | assert.Equal(t, `ASafeVariableURL`, s)
42 |
43 | s = buildSafeVarName(`a/sql/variable.name`)
44 | assert.Equal(t, `ASQLVariableName`, s)
45 |
46 | s = buildSafeVarName(`a/sql/_variable.name`)
47 | assert.Equal(t, `ASQLVariableName2`, s)
48 | }
49 |
--------------------------------------------------------------------------------
/compression/gzip.go:
--------------------------------------------------------------------------------
1 | package compression
2 |
3 | import (
4 | "bytes"
5 | "compress/flate"
6 | "compress/gzip"
7 | )
8 |
9 | // Gzip compression support
10 | type Gzip struct {
11 | *Options
12 | }
13 |
14 | // NewGzip creates a Gzip + Options variable
15 | func NewGzip() *Gzip {
16 | gz := new(Gzip)
17 | gz.Options = new(Options)
18 | return gz
19 | }
20 |
21 | // Compress to gzip
22 | func (gz *Gzip) Compress(content []byte) ([]byte, error) {
23 | if !gz.Options.Compress {
24 | return content, nil
25 | }
26 |
27 | // method
28 | var m int
29 | switch gz.Options.Method {
30 | case "NoCompression":
31 | m = flate.NoCompression
32 | break
33 | case "BestSpeed":
34 | m = flate.BestSpeed
35 | break
36 | case "BestCompression":
37 | m = flate.BestCompression
38 | break
39 | default:
40 | m = flate.DefaultCompression
41 | break
42 | }
43 |
44 | // compress
45 | var b bytes.Buffer
46 | w, err := gzip.NewWriterLevel(&b, m)
47 | if err != nil {
48 | return nil, err
49 | }
50 |
51 | // insert content
52 | _, err = w.Write(content)
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | err = w.Close()
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | // compressed content
63 | return b.Bytes(), nil
64 | }
65 |
--------------------------------------------------------------------------------
/template/file.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var fileTemplate = `{{buildTags .Tags}}// Code generaTed by fileb0x at "{{.Now}}" from config file "{{.ConfigFile}}" DO NOT EDIT.
4 | // modified({{.Modified}})
5 | // original path: {{.OriginalPath}}
6 |
7 | package {{.Pkg}}
8 |
9 | import (
10 | {{if .Compression.Compress}}
11 | {{if not .Compression.Keep}}
12 | "bytes"
13 | "compress/gzip"
14 | "io"
15 | {{end}}
16 | {{end}}
17 | "os"
18 | )
19 |
20 | // {{exportedTitle "File"}}{{buildSafeVarName .Path}} is "{{.Path}}"
21 | var {{exportedTitle "File"}}{{buildSafeVarName .Path}} = {{.Data}}
22 |
23 | func init() {
24 | {{if .Compression.Compress}}
25 | {{if not .Compression.Keep}}
26 | rb := bytes.NewReader({{exportedTitle "File"}}{{buildSafeVarName .Path}})
27 | r, err := gzip.NewReader(rb)
28 | if err != nil {
29 | panic(err)
30 | }
31 |
32 | err = r.Close()
33 | if err != nil {
34 | panic(err)
35 | }
36 | {{end}}
37 | {{end}}
38 |
39 | f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, "{{.Path}}", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
40 | if err != nil {
41 | panic(err)
42 | }
43 |
44 | {{if .Compression.Compress}}
45 | {{if not .Compression.Keep}}
46 | _, err = io.Copy(f, r)
47 | if err != nil {
48 | panic(err)
49 | }
50 | {{end}}
51 | {{else}}
52 | _, err = f.Write({{exportedTitle "File"}}{{buildSafeVarName .Path}})
53 | if err != nil {
54 | panic(err)
55 | }
56 | {{end}}
57 |
58 | err = f.Close()
59 | if err != nil {
60 | panic(err)
61 | }
62 | }
63 |
64 | `
65 |
--------------------------------------------------------------------------------
/dir/dir.go:
--------------------------------------------------------------------------------
1 | package dir
2 |
3 | import "strings"
4 |
5 | // Dir holds directory information to insert into templates
6 | type Dir struct {
7 | List [][]string
8 | Blacklist []string
9 | }
10 |
11 | // Exists checks if a directory exists or not
12 | func (d *Dir) Exists(newDir string) bool {
13 | for _, dir := range d.Blacklist {
14 | if dir == newDir {
15 | return true
16 | }
17 | }
18 |
19 | return false
20 | }
21 |
22 | // Parse a directory to build a list of directories to be made at b0x.go
23 | func (d *Dir) Parse(newDir string) []string {
24 | list := strings.Split(newDir, "/")
25 |
26 | var dirWalk []string
27 |
28 | for indx := range list {
29 | dirList := ""
30 | for i := -1; i < indx; i++ {
31 | dirList += list[i+1] + "/"
32 | }
33 |
34 | if !d.Exists(dirList) {
35 | if strings.HasSuffix(dirList, "//") {
36 | dirList = dirList[:len(dirList)-1]
37 | }
38 |
39 | dirWalk = append(dirWalk, dirList)
40 | d.Blacklist = append(d.Blacklist, dirList)
41 | }
42 | }
43 |
44 | return dirWalk
45 | }
46 |
47 | // Insert a new folder to the list
48 | func (d *Dir) Insert(newDir string) {
49 | if !d.Exists(newDir) {
50 | d.Blacklist = append(d.Blacklist, newDir)
51 | d.List = append(d.List, d.Parse(newDir))
52 | }
53 | }
54 |
55 | // Clean dupes
56 | func (d *Dir) Clean() []string {
57 | var cleanList []string
58 |
59 | for _, dirs := range d.List {
60 | for _, dir := range dirs {
61 | if dir == "./" || dir == "/" || dir == "." || dir == "" {
62 | continue
63 | }
64 |
65 | cleanList = append(cleanList, dir)
66 | }
67 | }
68 |
69 | return cleanList
70 | }
71 |
--------------------------------------------------------------------------------
/_example/echo/ufo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ayy
6 |
52 |
53 |
54 |
55 |
67 |
68 |
--------------------------------------------------------------------------------
/_example/simple/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ayy
6 |
52 |
53 |
54 |
55 |
67 |
68 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/UnnoTed/fileb0x/compression"
7 | "github.com/UnnoTed/fileb0x/custom"
8 | "github.com/UnnoTed/fileb0x/updater"
9 | )
10 |
11 | // Config holds the json/yaml/toml data
12 | type Config struct {
13 | Dest string
14 | NoPrefix bool
15 |
16 | Pkg string
17 | Fmt bool // gofmt
18 | Compression *compression.Options
19 | Tags string
20 |
21 | Output string
22 |
23 | Custom []custom.Custom
24 |
25 | Spread bool
26 | Unexported bool
27 | Clean bool
28 | Debug bool
29 | Updater updater.Config
30 | Lcf bool
31 | }
32 |
33 | // Defaults set the default value for some variables
34 | func (cfg *Config) Defaults() error {
35 | // default destination
36 | if cfg.Dest == "" {
37 | cfg.Dest = "/"
38 | }
39 |
40 | // insert "/" at end of dest when it's not found
41 | if !strings.HasSuffix(cfg.Dest, "/") {
42 | cfg.Dest += "/"
43 | }
44 |
45 | // default file name
46 | if cfg.Output == "" {
47 | cfg.Output = "b0x.go"
48 | }
49 |
50 | // inserts .go at the end of file name
51 | if !strings.HasSuffix(cfg.Output, ".go") {
52 | cfg.Output += ".go"
53 | }
54 |
55 | // inserts an A before the output file's name so it can
56 | // run init() before b0xfile's
57 | if !cfg.NoPrefix && !strings.HasPrefix(cfg.Output, "a") {
58 | cfg.Output = "a" + cfg.Output
59 | }
60 |
61 | // default package
62 | if cfg.Pkg == "" {
63 | cfg.Pkg = "main"
64 | }
65 |
66 | if cfg.Compression == nil {
67 | cfg.Compression = &compression.Options{
68 | Compress: false,
69 | Method: "DefaultCompression",
70 | Keep: false,
71 | }
72 | }
73 |
74 | return nil
75 | }
76 |
--------------------------------------------------------------------------------
/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/hex"
6 | "io/ioutil"
7 | "log"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | const (
14 | tfile = "./_example/echo/ufo.html"
15 | hextable = "0123456789abcdef"
16 | )
17 |
18 | var (
19 | xx = []byte(`\x`)
20 | content []byte
21 | )
22 |
23 | func Method1(data []byte) string {
24 | var buf bytes.Buffer
25 | h := hex.EncodeToString(data)
26 |
27 | // loop through hex string, at each 2 chars
28 | // it's added into a byte array -> []byte("\x09\x11...")
29 | for i := 0; i < len(h); i += 2 {
30 | buf.Write(xx)
31 | buf.WriteString(h[i : i+2])
32 | }
33 |
34 | return buf.String()
35 | }
36 |
37 | func Method2(data []byte) string {
38 | dst := make([]byte, len(data)*4)
39 |
40 | for i := 0; i < len(data); i++ {
41 | dst[i*4] = byte('\\')
42 | dst[i*4+1] = byte('x')
43 | dst[i*4+2] = hextable[data[i]>>4]
44 | dst[i*4+3] = hextable[data[i]&0x0f]
45 | }
46 |
47 | return string(dst)
48 | }
49 |
50 | func BenchmarkInit(b *testing.B) {
51 | if len(content) > 0 {
52 | return
53 | }
54 |
55 | c, err := ioutil.ReadFile(tfile)
56 | if err != nil {
57 | log.Fatal(err)
58 | }
59 |
60 | content = c
61 | }
62 |
63 | func BenchmarkOldConvert(b *testing.B) {
64 | for a := 0; a < b.N; a++ {
65 | _ = Method1(content)
66 | }
67 | }
68 |
69 | func BenchmarkNewConvert(b *testing.B) {
70 | for a := 0; a < b.N; a++ {
71 | _ = Method2(content)
72 | }
73 | }
74 |
75 | func TestMethodsEqual(t *testing.T) {
76 | if len(content) > 0 {
77 | return
78 | }
79 |
80 | c, err := ioutil.ReadFile(tfile)
81 | if err != nil {
82 | log.Fatal(err)
83 | }
84 |
85 | content = c
86 |
87 | assert.Equal(t, Method1(content), Method2(content))
88 | }
89 |
--------------------------------------------------------------------------------
/_example/gin/b0x.yml:
--------------------------------------------------------------------------------
1 | # all folders and files are relative to the path
2 | # where fileb0x was run at!
3 |
4 | # default: main
5 | pkg: myEmbeddedFiles
6 |
7 | # destination
8 | dest: "./myEmbeddedFiles/"
9 |
10 | # gofmt
11 | # type: bool
12 | # default: false
13 | fmt: true
14 |
15 | # compress files
16 | # at the moment, only supports gzip
17 | #
18 | # type: object
19 | compression:
20 | # activates the compression
21 | #
22 | # type: bool
23 | # default: false
24 | compress: false
25 |
26 | # valid values are:
27 | # -> "NoCompression"
28 | # -> "BestSpeed"
29 | # -> "BestCompression"
30 | # -> "DefaultCompression" or ""
31 | #
32 | # type: string
33 | # default: "DefaultCompression" # when: Compress == true && Method == ""
34 | method: ""
35 |
36 | # true = do it yourself (the file is written as gzip compressed file into the memory file system)
37 | # false = decompress files at run time (while writing file into memory file system)
38 | #
39 | # type: bool
40 | # default: false
41 | keep: false
42 |
43 | # ---------------
44 | # -- DANGEROUS --
45 | # ---------------
46 | #
47 | # cleans the destination folder (only b0xfiles)
48 | # you should use this when using the spread function
49 | # type: bool
50 | # default: false
51 | clean: true
52 |
53 | # default: ab0x.go
54 | output: "ab0x.go"
55 |
56 | # [unexporTed] builds non-exporTed functions, variables and types...
57 | # type: bool
58 | # default: false
59 | unexporTed: false
60 |
61 | # [spread] means it will make a file to hold all fileb0x data
62 | # and each file into a separaTed .go file
63 | #
64 | # example:
65 | # theres 2 files in the folder assets, they're: hello.json and world.txt
66 | # when spread is activaTed, fileb0x will make a file:
67 | # b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
68 | #
69 | #
70 | # type: bool
71 | # default: false
72 | spread: true
73 |
74 | # [lcf] log changed files when spread is active
75 | lcf: true
76 |
77 | # type: array of objects
78 | custom:
79 | - files:
80 | - "css/*"
81 | - "templates/*"
82 |
--------------------------------------------------------------------------------
/updater/updater_test.go:
--------------------------------------------------------------------------------
1 | package updater
2 |
3 | import (
4 | "io"
5 | "os"
6 | "testing"
7 |
8 | "time"
9 |
10 | "github.com/UnnoTed/fileb0x/file"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestUpdater(t *testing.T) {
15 | svr := Server{}
16 | type filef struct {
17 | filename string
18 | data []byte
19 | }
20 |
21 | go svr.Init()
22 | time.Sleep(500 * time.Millisecond)
23 |
24 | var files []filef
25 |
26 | files = append(files, filef{
27 | filename: "whoDid911.txt",
28 | data: []byte("notMe"),
29 | })
30 |
31 | for _, fileData := range files {
32 | f, err := FS.OpenFile(CTX, fileData.filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0777)
33 | assert.NoError(t, err)
34 |
35 | n, err := f.Write(fileData.data)
36 | if err == nil && n < len(fileData.data) {
37 | err = io.ErrShortWrite
38 | }
39 | if err1 := f.Close(); err == nil {
40 | err = err1
41 | }
42 |
43 | assert.NoError(t, err)
44 | svr.Files = append(svr.Files, fileData.filename)
45 | }
46 |
47 | up := &Updater{
48 | Server: "http://localhost:8041",
49 | Auth: Auth{
50 | Username: "user",
51 | Password: "pass",
52 | },
53 | }
54 |
55 | assert.Empty(t, up.RemoteHashes)
56 |
57 | err := up.Get()
58 | assert.NoError(t, err)
59 | assert.NotEmpty(t, up.RemoteHashes)
60 | assert.Equal(t, "62e37ff222ec1ca377bb41ffb3fdf08860263e9754f26392c0745765af7397c3", up.RemoteHashes["whoDid911.txt"])
61 |
62 | fileList := map[string]*file.File{
63 | "whoDid911.txt": {
64 | Name: "whoDid911.txt",
65 | Path: "whoDid911.txt",
66 | Bytes: []byte("obsama"),
67 | },
68 | }
69 |
70 | updatable, err := up.Updatable(fileList)
71 | assert.NoError(t, err)
72 | assert.True(t, updatable)
73 |
74 | err = up.UpdateFiles(fileList)
75 | assert.NoError(t, err)
76 |
77 | //
78 | err = up.Get()
79 | assert.NoError(t, err)
80 | assert.NotEmpty(t, up.RemoteHashes)
81 | assert.Equal(t, "98b9640c89068b3639679512d12fd48b4ccb40c1e3fd6b3e0bd4d575cef75cd6", up.RemoteHashes["whoDid911.txt"])
82 |
83 | updatable, err = up.Updatable(fileList)
84 | assert.NoError(t, err)
85 | assert.False(t, updatable)
86 | }
87 |
--------------------------------------------------------------------------------
/utils/utils_test.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestFixPath(t *testing.T) {
11 | p := `C:\test\a\b\c\d\e\f`
12 | fixedPath := FixPath(p)
13 |
14 | assert.Equal(t, `C:/test/a/b/c/d/e/f`, fixedPath)
15 | }
16 |
17 | func TestFixName(t *testing.T) {
18 | name := `notporn/empty folder/ufo, porno.flv`
19 | fixedName := FixName(name)
20 |
21 | assert.Equal(t, `notporn_empty-folder_ufo__-porno.flv`, fixedName)
22 | }
23 |
24 | // https://github.com/UnnoTed/fileb0x/issues/8
25 | func TestIssue8(t *testing.T) {
26 | type replacer struct {
27 | file string
28 | base string
29 | prefix string
30 | result string
31 | }
32 |
33 | list := []replacer{
34 | {"./main.go", "./", "_bug?_", "_bug?_main.go"},
35 | {"./fileb0x.test.yaml", "./", "_bug?_", "_bug?_fileb0x.test.yaml"},
36 | {"./static/test.txt", "", "test_prefix/", "test_prefix/static/test.txt"},
37 | {"./static/test.txt", "./static/", "", "test.txt"},
38 | }
39 |
40 | fixit := func(r replacer) string {
41 | r.file = FixPath(r.file)
42 |
43 | var fixedPath string
44 | if r.prefix != "" || r.base != "" {
45 | if strings.HasPrefix(r.base, "./") {
46 | r.base = r.base[2:]
47 | }
48 |
49 | if strings.HasPrefix(r.file, r.base) {
50 | fixedPath = r.prefix + r.file[len(r.base):]
51 | } else {
52 | if r.base != "" {
53 | fixedPath = r.prefix + r.file
54 | }
55 | }
56 |
57 | fixedPath = FixPath(fixedPath)
58 | } else {
59 | fixedPath = FixPath(r.file)
60 | }
61 |
62 | return fixedPath
63 | }
64 |
65 | for _, r := range list {
66 | assert.Equal(t, r.result, fixit(r))
67 | }
68 | }
69 |
70 | func TestGetCurrentDir(t *testing.T) {
71 | dir, err := GetCurrentDir()
72 | assert.NoError(t, err)
73 | assert.NotEmpty(t, dir)
74 |
75 | assert.Contains(t, dir, "fileb0x")
76 | assert.Contains(t, dir, "utils")
77 | }
78 |
79 | func TestExists(t *testing.T) {
80 | dir, err := GetCurrentDir()
81 | assert.NoError(t, err)
82 | assert.NotEmpty(t, dir)
83 |
84 | exists := Exists(dir + "/_testmain.go")
85 | assert.True(t, exists)
86 |
87 | exists = Exists("wat")
88 | assert.False(t, exists)
89 | }
90 |
--------------------------------------------------------------------------------
/_example/echo/b0x.yml:
--------------------------------------------------------------------------------
1 | # all folders and files are relative to the path
2 | # where fileb0x was run at!
3 |
4 | # default: main
5 | pkg: myEmbeddedFiles
6 |
7 | # destination
8 | dest: "./myEmbeddedFiles/"
9 |
10 | # gofmt
11 | # type: bool
12 | # default: false
13 | fmt: true
14 |
15 | # compress files
16 | # at the moment, only supports gzip
17 | #
18 | # type: object
19 | compression:
20 | # activates the compression
21 | #
22 | # type: bool
23 | # default: false
24 | compress: false
25 |
26 | # valid values are:
27 | # -> "NoCompression"
28 | # -> "BestSpeed"
29 | # -> "BestCompression"
30 | # -> "DefaultCompression" or ""
31 | #
32 | # type: string
33 | # default: "DefaultCompression" # when: Compress == true && Method == ""
34 | method: ""
35 |
36 | # true = do it yourself (the file is written as gzip compressed file into the memory file system)
37 | # false = decompress files at run time (while writing file into memory file system)
38 | #
39 | # type: bool
40 | # default: false
41 | keep: false
42 |
43 | # ---------------
44 | # -- DANGEROUS --
45 | # ---------------
46 | #
47 | # cleans the destination folder (only b0xfiles)
48 | # you should use this when using the spread function
49 | # type: bool
50 | # default: false
51 | clean: true
52 |
53 | # default: ab0x.go
54 | output: "ab0x.go"
55 |
56 | # [unexporTed] builds non-exporTed functions, variables and types...
57 | # type: bool
58 | # default: false
59 | unexporTed: false
60 |
61 | # [spread] means it will make a file to hold all fileb0x data
62 | # and each file into a separaTed .go file
63 | #
64 | # example:
65 | # theres 2 files in the folder assets, they're: hello.json and world.txt
66 | # when spread is activaTed, fileb0x will make a file:
67 | # b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
68 | #
69 | #
70 | # type: bool
71 | # default: false
72 | spread: true
73 |
74 | # [lcf] log changed files when spread is active
75 | lcf: true
76 |
77 | # type: array of objects
78 | custom:
79 |
80 | # type: array of strings
81 | - files:
82 | - "../../README.md"
83 | - "../../bench.bat"
84 |
85 | # base is the path that will be removed from all files' path
86 | # type: string
87 | base: "../../"
88 |
89 | # prefix is the path that will be added to all files' path
90 | # type: string
91 | prefix: "public/"
92 | # end: files
93 | - files:
94 | - "ufo.html"
95 |
--------------------------------------------------------------------------------
/custom/custom_test.go:
--------------------------------------------------------------------------------
1 | package custom
2 |
3 | import (
4 | "encoding/hex"
5 | "log"
6 | "runtime"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/UnnoTed/fileb0x/compression"
11 | "github.com/UnnoTed/fileb0x/dir"
12 | "github.com/UnnoTed/fileb0x/file"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func TestCustomParse(t *testing.T) {
17 | c := new(Custom)
18 | c.Files = []string{
19 | "../_example/simple/public/",
20 | }
21 |
22 | c.Base = "../_example/simple/"
23 | c.Prefix = "prefix_test/"
24 | c.Exclude = []string{
25 | "public/assets/data/exclude_me.txt",
26 | }
27 |
28 | c.Replace = []Replacer{
29 | {
30 | File: "public/assets/data/test*.json",
31 | Replace: map[string]string{
32 | "{world}": "earth",
33 | "{EMAIL}": "aliens@nasa.com",
34 | },
35 | },
36 | }
37 |
38 | files := make(map[string]*file.File)
39 | dirs := new(dir.Dir)
40 |
41 | oldFiles := c.Files
42 | c.Files = []string{"../sa8vuj948127498/*"}
43 |
44 | defaultCompression := compression.NewGzip()
45 |
46 | sharedConfig := new(SharedConfig)
47 | sharedConfig.Output = "124g98j13409b1341"
48 | sharedConfig.Compression = defaultCompression
49 |
50 | err := c.Parse(&files, &dirs, sharedConfig)
51 | assert.Error(t, err)
52 |
53 | c.Files = oldFiles
54 | err = c.Parse(&files, &dirs, sharedConfig)
55 | assert.NoError(t, err)
56 | assert.NotNil(t, files)
57 | assert.NotNil(t, dirs)
58 |
59 | // insert \r on windows
60 | var isWindows string
61 | if runtime.GOOS == "windows" {
62 | isWindows = "\r"
63 | }
64 |
65 | for _, f := range files {
66 | assert.True(t, strings.HasPrefix(f.Path, c.Prefix))
67 | assert.NotEqual(t, "exclude_me.txt", f.Name)
68 |
69 | if f.Name == "test1.json" {
70 | e := "{" + isWindows + "\n \"he\": \"llo\"," + isWindows +
71 | "\n \"replace_test\": \"earth\"" + isWindows + "\n}"
72 |
73 | assert.Equal(t, e, data2str(f.Data))
74 |
75 | } else if f.Name == "test2.json" {
76 | e := "{" + isWindows + "\n \"email\": \"aliens@nasa.com\"" + isWindows + "\n}"
77 | assert.Equal(t, e, data2str(f.Data))
78 | }
79 | }
80 |
81 | ds := dirs.Clean()
82 | var blacklist []string
83 | for _, d := range ds {
84 | assert.True(t, strings.HasPrefix(d, c.Prefix))
85 | assert.NotContains(t, blacklist, d)
86 | blacklist = append(blacklist, d)
87 | }
88 | }
89 |
90 | func data2str(h string) string {
91 | h = strings.TrimPrefix(h, `[]byte("`)
92 | h = strings.TrimSuffix(h, `")`)
93 | h = strings.Replace(h, `\x`, "", -1)
94 |
95 | b, err := hex.DecodeString(h)
96 | if err != nil {
97 | log.Fatal(err)
98 | }
99 |
100 | return string(b)
101 | }
102 |
--------------------------------------------------------------------------------
/template/funcs.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "regexp"
5 | "strconv"
6 | "strings"
7 | "text/template"
8 |
9 | "github.com/UnnoTed/fileb0x/config"
10 | )
11 |
12 | var safeNameBlacklist = map[string]string{}
13 | var blacklist = map[string]int{}
14 |
15 | // taken from golint @ https://github.com/golang/lint/blob/master/lint.go#L702
16 | var commonInitialisms = map[string]bool{
17 | "API": true,
18 | "ASCII": true,
19 | "CPU": true,
20 | "CSS": true,
21 | "DNS": true,
22 | "EOF": true,
23 | "GUID": true,
24 | "HTML": true,
25 | "HTTP": true,
26 | "HTTPS": true,
27 | "ID": true,
28 | "IP": true,
29 | "JSON": true,
30 | "LHS": true,
31 | "QPS": true,
32 | "RAM": true,
33 | "RHS": true,
34 | "RPC": true,
35 | "SLA": true,
36 | "SMTP": true,
37 | "SQL": true,
38 | "SSH": true,
39 | "TCP": true,
40 | "TLS": true,
41 | "TTL": true,
42 | "UDP": true,
43 | "UI": true,
44 | "UID": true,
45 | "UUID": true,
46 | "URI": true,
47 | "URL": true,
48 | "UTF8": true,
49 | "VM": true,
50 | "XML": true,
51 | "XSRF": true,
52 | "XSS": true,
53 | }
54 |
55 | var r = regexp.MustCompile(`[^a-zA-Z0-9]`)
56 |
57 | var funcsTemplate = template.FuncMap{
58 | "exported": exported,
59 | "buildTags": buildTags,
60 | "exportedTitle": exportedTitle,
61 | "buildSafeVarName": buildSafeVarName,
62 | }
63 |
64 | var unexported bool
65 |
66 | // SetUnexported variables, functions and types
67 | func SetUnexported(e bool) {
68 | unexported = e
69 | }
70 |
71 | func exported(field string) string {
72 | if !unexported {
73 | return strings.ToUpper(field)
74 | }
75 |
76 | return strings.ToLower(field)
77 | }
78 |
79 | func exportedTitle(field string) string {
80 | if !unexported {
81 | return strings.Title(field)
82 | }
83 |
84 | return strings.ToLower(field[0:1]) + field[1:]
85 | }
86 |
87 | func buildSafeVarName(path string) string {
88 | name, exists := safeNameBlacklist[path]
89 | if exists {
90 | return name
91 | }
92 |
93 | n := config.SafeVarName.ReplaceAllString(path, "$")
94 | words := strings.Split(n, "$")
95 |
96 | name = ""
97 | // check for uppercase words
98 | for _, word := range words {
99 | upper := strings.ToUpper(word)
100 |
101 | if commonInitialisms[upper] {
102 | name += upper
103 | } else {
104 | name += strings.Title(word)
105 | }
106 | }
107 |
108 | // avoid redeclaring variables
109 | //
110 | // _file.txt
111 | // file.txt
112 | _, blacklisted := blacklist[name]
113 |
114 | if blacklisted {
115 | blacklist[name]++
116 | name += strconv.Itoa(blacklist[name])
117 | }
118 |
119 | safeNameBlacklist[path] = name
120 | blacklist[name]++
121 | return name
122 | }
123 |
124 | func buildTags(tags string) string {
125 | if tags != "" {
126 | tags = "// +build " + tags + "\n"
127 | }
128 |
129 | return tags
130 | }
131 |
--------------------------------------------------------------------------------
/config/file.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "io/ioutil"
7 | "os"
8 | "path"
9 |
10 | "github.com/UnnoTed/fileb0x/utils"
11 |
12 | "github.com/BurntSushi/toml"
13 | "gopkg.in/yaml.v2"
14 | "fmt"
15 | )
16 |
17 | // File holds config file info
18 | type File struct {
19 | FilePath string
20 | Data []byte
21 | Mode string // "json" || "yaml" || "yml" || "toml"
22 | }
23 |
24 | // FromArg gets the json/yaml/toml file from args
25 | func (f *File) FromArg(read bool) error {
26 | // (length - 1)
27 | arg := os.Args[len(os.Args)-1:][0]
28 |
29 | // get extension
30 | ext := path.Ext(arg)
31 | if len(ext) > 1 {
32 | ext = ext[1:] // remove dot
33 | }
34 |
35 | // when json/yaml/toml file isn't found on last arg
36 | // it searches for a ".json", ".yaml", ".yml" or ".toml" string in all args
37 | if ext != "json" && ext != "yaml" && ext != "yml" && ext != "toml" {
38 | // loop through args
39 | for _, a := range os.Args {
40 | // get extension
41 | ext := path.Ext(a)
42 |
43 | // check for valid extensions
44 | if ext == ".json" || ext == ".yaml" || ext == ".yml" || ext == ".toml" {
45 | f.Mode = ext[1:] // remove dot
46 | ext = f.Mode
47 | arg = a
48 | break
49 | }
50 | }
51 | } else {
52 | f.Mode = ext
53 | }
54 |
55 | // check if extension is json, yaml or toml
56 | // then get it's absolute path
57 | if ext == "json" || ext == "yaml" || ext == "yml" || ext == "toml" {
58 | f.FilePath = arg
59 |
60 | // so we can test without reading a file
61 | if read {
62 | if !utils.Exists(f.FilePath) {
63 | return errors.New("Error: I Can't find the config file at [" + f.FilePath + "]")
64 | }
65 | }
66 | } else {
67 | return errors.New("Error: You must specify a json, yaml or toml file")
68 | }
69 |
70 | return nil
71 | }
72 |
73 | // Parse gets the config file's content from File.Data
74 | func (f *File) Parse() (*Config, error) {
75 | // remove comments
76 | f.RemoveJSONComments()
77 |
78 | to := &Config{}
79 | switch f.Mode {
80 | case "json":
81 | return to, json.Unmarshal(f.Data, to)
82 | case "yaml", "yml":
83 | return to, yaml.Unmarshal(f.Data, to)
84 | case "toml":
85 | return to, toml.Unmarshal(f.Data, to)
86 | default:
87 | return nil, fmt.Errorf("unknown mode '%s'", f.Mode)
88 | }
89 | }
90 |
91 | // Load the json/yaml file that was specified from args
92 | // and transform it into a config struct
93 | func (f *File) Load() (*Config, error) {
94 | var err error
95 | if !utils.Exists(f.FilePath) {
96 | return nil, errors.New("Error: I Can't find the config file at [" + f.FilePath + "]")
97 | }
98 |
99 | // read file
100 | f.Data, err = ioutil.ReadFile(f.FilePath)
101 | if err != nil {
102 | return nil, err
103 | }
104 |
105 | // parse file
106 | return f.Parse()
107 | }
108 |
109 | // RemoveJSONComments from the file
110 | func (f *File) RemoveJSONComments() {
111 | if f.Mode == "json" {
112 | // remove inline comments
113 | f.Data = []byte(regexComments.ReplaceAllString(string(f.Data), ""))
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/config/file_test.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "io/ioutil"
5 | "os"
6 | "reflect"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | var (
14 | f = new(File)
15 | yamlCfg *Config
16 | jsonCfg *Config
17 | confYAML []byte
18 | confJSON []byte
19 |
20 | yamlPath = "/../_example/simple/b0x.yaml"
21 | jsonPath = "/../_example/simple/b0x.json"
22 | )
23 |
24 | func TestFileFromArg(t *testing.T) {
25 | loadFromArgs(t, "yaml")
26 |
27 | // remove "b0x.*" from last arg
28 | os.Args = os.Args[:len(os.Args)-1]
29 |
30 | err := f.FromArg(false)
31 | assert.Error(t, err)
32 | assert.Equal(t, "Error: You must specify a json, yaml or toml file", err.Error())
33 |
34 | err = f.FromArg(true)
35 | assert.Error(t, err)
36 | assert.Equal(t, "Error: You must specify a json, yaml or toml file", err.Error())
37 |
38 | loadFromArgs(t, "json")
39 | }
40 |
41 | func TestJsonRemoveComments(t *testing.T) {
42 | f.Data = []byte(`{
43 | // hey
44 | // wat
45 | }`)
46 |
47 | f.RemoveJSONComments()
48 | assert.Equal(t, `{
49 |
50 |
51 | }`, string(f.Data))
52 | }
53 |
54 | func TestJsonLoad(t *testing.T) {
55 | var err error
56 | f.FilePath, err = os.Getwd()
57 | assert.NoError(t, err)
58 | assert.NotEmpty(t, f.FilePath)
59 | f.FilePath += jsonPath
60 |
61 | cfg, err := f.Load()
62 | assert.NoError(t, err)
63 | assert.NotNil(t, cfg)
64 |
65 | assert.NotEqual(t, `{
66 |
67 |
68 | }`, string(f.Data))
69 |
70 | cfg.Defaults()
71 | jsonCfg = cfg
72 | }
73 |
74 | func TestYamlLoad(t *testing.T) {
75 | loadFromArgs(t, "yaml")
76 |
77 | var err error
78 | f.FilePath, err = os.Getwd()
79 | assert.NoError(t, err)
80 | assert.NotEmpty(t, f.FilePath)
81 |
82 | cfg, err := f.Load()
83 | assert.Error(t, err)
84 | assert.Nil(t, cfg)
85 | assert.Empty(t, string(f.Data))
86 |
87 | f.FilePath += yamlPath
88 | cfg, err = f.Load()
89 | assert.NoError(t, err)
90 | assert.NotNil(t, cfg)
91 | assert.NotEmpty(t, string(f.Data))
92 |
93 | cfg.Defaults()
94 | yamlCfg = cfg
95 | }
96 |
97 | func TestYamlParse(t *testing.T) {
98 | var err error
99 | f.Data = nil
100 | f.Mode = "yaml"
101 |
102 | f.Data, err = ioutil.ReadFile("." + yamlPath)
103 | assert.NoError(t, err)
104 | assert.NotNil(t, f.Data)
105 |
106 | cfg, err := f.Parse()
107 | assert.NoError(t, err)
108 | assert.NotNil(t, cfg)
109 | }
110 |
111 | func TestJsonParse(t *testing.T) {
112 | var err error
113 | f.Data = nil
114 | f.Mode = "json"
115 |
116 | f.Data, err = ioutil.ReadFile("." + jsonPath)
117 | assert.NoError(t, err)
118 | assert.NotNil(t, f.Data)
119 |
120 | cfg, err := f.Parse()
121 | assert.NoError(t, err)
122 | assert.NotNil(t, cfg)
123 | }
124 |
125 | func TestComparison(t *testing.T) {
126 | assert.True(t, reflect.DeepEqual(yamlCfg, jsonCfg))
127 | }
128 |
129 | func loadFromArgs(t *testing.T, ext string) {
130 | // insert "b0x.ext" to args
131 | os.Args = append(os.Args, "b0x."+ext)
132 |
133 | err := f.FromArg(false)
134 | assert.NoError(t, err)
135 |
136 | err = f.FromArg(true)
137 | assert.Error(t, err)
138 | assert.Equal(t, "Error: I Can't find the config file at ["+f.FilePath+"]", err.Error())
139 |
140 | // test b0x.ext from args
141 | err = f.FromArg(false)
142 | assert.NoError(t, err)
143 | assert.True(t, strings.HasSuffix(f.FilePath, "b0x."+ext))
144 | }
145 |
--------------------------------------------------------------------------------
/_example/gin/go.sum:
--------------------------------------------------------------------------------
1 | github.com/UnnoTed/fileb0x v1.0.0 h1:Bm2whQ7/+s2bqngaPZ4zNCLWfwRb2xOux1mD3rXHAhE=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
5 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
6 | github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
7 | github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
8 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
10 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
11 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
12 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
13 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
14 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
15 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
16 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
17 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
18 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
19 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
20 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
21 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
22 | github.com/ugorji/go/codec v0.0.0-20181018023622-b30ce92d500b h1:ap7PshyiINMYdtIz5+sdpUbypfSPWdTC4SaJhhtLtpU=
23 | github.com/ugorji/go/codec v0.0.0-20181018023622-b30ce92d500b/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
24 | golang.org/x/net v0.0.0-20181017193950-04a2e542c03f h1:4pRM7zYwpBjCnfA1jRmhItLxYJkaEnsmuAcRtA347DA=
25 | golang.org/x/net v0.0.0-20181017193950-04a2e542c03f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
26 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
27 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
28 | golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 h1:R91KX5nmbbvEd7w370cbVzKC+EzCTGqZq63Zad5IcLM=
29 | golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
32 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
33 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
34 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
35 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
36 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
37 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
38 |
--------------------------------------------------------------------------------
/template/template_test.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | import (
4 | "strings"
5 | "testing"
6 |
7 | "github.com/UnnoTed/fileb0x/compression"
8 | "github.com/UnnoTed/fileb0x/dir"
9 | "github.com/UnnoTed/fileb0x/file"
10 | "github.com/UnnoTed/fileb0x/updater"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestTemplate(t *testing.T) {
15 | var err error
16 | files := make(map[string]*file.File)
17 | files["test_file.txt"] = &file.File{
18 | Name: "test_file.txt",
19 | Path: "static/test_file.txt",
20 | Data: `[]byte("\x12\x34\x56\x78\x10")`,
21 | }
22 |
23 | dirs := new(dir.Dir)
24 | dirs.Insert("static/")
25 |
26 | tp := new(Template)
27 |
28 | err = tp.Set("ayy lmao")
29 | assert.Error(t, err)
30 | assert.Equal(t, `Error: Template must be "files" or "file"`, err.Error())
31 |
32 | err = tp.Set("files")
33 | assert.NoError(t, err)
34 | assert.Equal(t, "files", tp.name)
35 |
36 | defaultCompression := compression.NewGzip()
37 |
38 | tp.Variables = struct {
39 | ConfigFile string
40 | Now string
41 | Pkg string
42 | Files map[string]*file.File
43 | Spread bool
44 | DirList []string
45 | Compression *compression.Options
46 | Tags string
47 | Debug bool
48 | Remap string
49 | Updater updater.Config
50 | ModificationHash string
51 | }{
52 | Pkg: "main",
53 | Files: files,
54 | Spread: false,
55 | DirList: dirs.Clean(),
56 | Compression: defaultCompression.Options,
57 | ModificationHash: "asdasdasd",
58 | }
59 |
60 | tp.template = "wrong {{.Err pudding"
61 | tmpl, err := tp.Exec()
62 | assert.Error(t, err)
63 | assert.Empty(t, tmpl)
64 |
65 | tp.template = "wrong{{if .Error}} pudding {{end}}"
66 | tmpl, err = tp.Exec()
67 | assert.Error(t, err)
68 | assert.Empty(t, tmpl)
69 |
70 | err = tp.Set("files")
71 | tmpl, err = tp.Exec()
72 | assert.NoError(t, err)
73 | assert.NotEmpty(t, tmpl)
74 |
75 | s := string(tmpl)
76 |
77 | assert.True(t, strings.Contains(s, `var FileStaticTestFileTxt = []byte("\x12\x34\x56\x78\x10")`))
78 | assert.True(t, strings.Contains(s, `err = FS.Mkdir(CTX, "static/", 0777)`))
79 | assert.True(t, strings.Contains(s, `f, err = FS.OpenFile(CTX, "static/test_file.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)`))
80 |
81 | // now with spread
82 | err = tp.Set("file")
83 | assert.NoError(t, err)
84 | assert.Equal(t, "file", tp.name)
85 |
86 | defaultCompression = compression.NewGzip()
87 |
88 | tp.Variables = struct {
89 | ConfigFile string
90 | Now string
91 | Pkg string
92 | Path string
93 | Name string
94 | Dir [][]string
95 | Tags string
96 | Remap string
97 | Data string
98 | Compression *compression.Options
99 | Updater updater.Config
100 | Modified string
101 | OriginalPath string
102 | }{
103 | Pkg: "main",
104 | Path: files["test_file.txt"].Path,
105 | Name: files["test_file.txt"].Name,
106 | Dir: dirs.List,
107 | Data: files["test_file.txt"].Data,
108 | Compression: defaultCompression.Options,
109 | Modified: "11901",
110 | OriginalPath: "",
111 | }
112 |
113 | tmpl, err = tp.Exec()
114 | assert.NoError(t, err)
115 | assert.NotEmpty(t, tmpl)
116 |
117 | s = string(tmpl)
118 |
119 | assert.True(t, strings.Contains(s, `var FileStaticTestFileTxt = []byte("\x12\x34\x56\x78\x10")`))
120 | assert.True(t, strings.Contains(s, `f, err := FS.OpenFile(CTX, "static/test_file.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)`))
121 | }
122 |
--------------------------------------------------------------------------------
/_example/simple/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/UnnoTed/fileb0x v0.0.0-20180803232343-279e1dc62738 h1:yEbjyPVTqIoYLISRIBUn0KMIscP6+64ta/rJM1PiU9Q=
4 | github.com/UnnoTed/fileb0x v0.0.0-20180803232343-279e1dc62738/go.mod h1:2zx0uDhw6cpa5CFvIzZa8CWKs6OKemzECJZCOzopISw=
5 | github.com/airking05/termui v2.2.0+incompatible h1:S3j2WJzr70u8KjUktaQ0Cmja+R0edOXChltFoQSGG8I=
6 | github.com/airking05/termui v2.2.0+incompatible/go.mod h1:B/M5sgOwSZlvGm3TsR98s1BSzlSH4wPQzUUNwZG+uUM=
7 | github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
8 | github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
9 | github.com/karrick/godirwalk v1.7.3 h1:e5iv87oxunQtG7S9MB10jrINLmF7HecFSjiTYKO7P2c=
10 | github.com/karrick/godirwalk v1.7.3/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
11 | github.com/labstack/echo v3.2.1+incompatible h1:J2M7YArHx4gi8p/3fDw8tX19SXhBCoRpviyAZSN3I88=
12 | github.com/labstack/echo v3.2.1+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
13 | github.com/labstack/gommon v0.2.7 h1:2qOPq/twXDrQ6ooBGrn3mrmVOC+biLlatwgIu8lbzRM=
14 | github.com/labstack/gommon v0.2.7/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
15 | github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc=
16 | github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI=
17 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
18 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
19 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
20 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
21 | github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
22 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
23 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
24 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
25 | github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
26 | github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
27 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
28 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
29 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
30 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
31 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
32 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
33 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ=
34 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
35 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f h1:QM2QVxvDoW9PFSPp/zy9FgxJLfaWTZlS61KEPtBwacM=
36 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
37 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
38 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
39 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
40 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ=
4 | github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
8 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
9 | github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
10 | github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
11 | github.com/karrick/godirwalk v1.7.8 h1:VfG72pyIxgtC7+3X9CMHI0AOl4LwyRAg98WAgsvffi8=
12 | github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34=
13 | github.com/labstack/echo v3.2.1+incompatible h1:J2M7YArHx4gi8p/3fDw8tX19SXhBCoRpviyAZSN3I88=
14 | github.com/labstack/echo v3.2.1+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
15 | github.com/labstack/gommon v0.2.7 h1:2qOPq/twXDrQ6ooBGrn3mrmVOC+biLlatwgIu8lbzRM=
16 | github.com/labstack/gommon v0.2.7/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
17 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
18 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
19 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
20 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
21 | github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
22 | github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
23 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
24 | github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
25 | github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
26 | github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
27 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
28 | github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
29 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
30 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
33 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
34 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
35 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
36 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
37 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
38 | github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
39 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ=
40 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
41 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f h1:QM2QVxvDoW9PFSPp/zy9FgxJLfaWTZlS61KEPtBwacM=
42 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
43 | golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 h1:R91KX5nmbbvEd7w370cbVzKC+EzCTGqZq63Zad5IcLM=
44 | golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
45 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
46 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
47 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
48 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
49 |
--------------------------------------------------------------------------------
/_example/simple/b0x.toml:
--------------------------------------------------------------------------------
1 | # all folders and files are relative to the path
2 | # where fileb0x was run at!
3 |
4 | # default: main
5 | pkg = "static"
6 |
7 | # destination
8 | dest = "./static/"
9 |
10 | # gofmt
11 | # type: bool
12 | # default: false
13 | fmt = false
14 |
15 | # build tags for the main b0x.go file
16 | tags = ""
17 |
18 | # updater allows you to update a b0x in a running server
19 | # without having to restart it
20 | [updater]
21 | # disabled by default
22 | enabled = false
23 |
24 | # empty mode creates a empty b0x file with just the
25 | # server and the filesystem, then you'll have to upload
26 | # the files later using the cmd:
27 | # fileb0x -update=http://server.com:port b0x.toml
28 | #
29 | # it avoids long compile time
30 | empty = false
31 |
32 | # amount of uploads at the same time
33 | workers = 3
34 |
35 | # to get a username and password from a env variable
36 | # leave username and password blank (username = "")
37 | # then set your username and password in the env vars
38 | # (no caps) -> fileb0x_username and fileb0x_password
39 | username = "user"
40 | password = "pass"
41 | port = 8041
42 |
43 | # compress files
44 | # at the moment, only supports gzip
45 | #
46 | # type: object
47 | [compression]
48 | # activates the compression
49 | #
50 | # type: bool
51 | # default: false
52 | compress = false
53 |
54 | # valid values are:
55 | # -> "NoCompression"
56 | # -> "BestSpeed"
57 | # -> "BestCompression"
58 | # -> "DefaultCompression" or ""
59 | #
60 | # type: string
61 | # default: "DefaultCompression" // when: Compress == true && Method == ""
62 | method = ""
63 |
64 | # true = do it yourself (the file is written as gzip compressed file into the memory file system)
65 | # false = decompress files at run time (while writing file into memory file system)
66 | #
67 | # type: bool
68 | # default: false
69 | keep = false
70 |
71 | # ---------------
72 | # -- DANGEROUS --
73 | # ---------------
74 | #
75 | # cleans the destination folder (only b0xfiles)
76 | # you should use this when using the spread function
77 | # type: bool
78 | # default: false
79 | clean = false
80 |
81 | # default: ab0x.go
82 | output = "ab0x.go"
83 |
84 | # [noprefix] disables adding "a" prefix to output
85 | # type: bool
86 | # default: false
87 | noprefix = false
88 |
89 | # [unexporTed] builds non-exporTed functions, variables and types...
90 | # type: bool
91 | # default: false
92 | unexporTed = false
93 |
94 | # [spread] means it will make a file to hold all fileb0x data
95 | # and each file into a separaTed .go file
96 | #
97 | # example:
98 | # theres 2 files in the folder assets, they're: hello.json and world.txt
99 | # when spread is activaTed, fileb0x will make a file:
100 | # b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
101 | #
102 | #
103 | # type: bool
104 | # default: false
105 | spread = false
106 |
107 | # [lcf] log changed files when spread is active
108 | lcf = true
109 |
110 | # [debug] is a debug mode where the files are read directly from the file
111 | # sytem. Useful for web dev when files change when the server is running.
112 | # type: bool
113 | # default: false
114 | debug = false
115 |
116 | # type: array of objects
117 | [[custom]]
118 |
119 | # type: array of strings
120 | files = ["../../README.md", "../../*.bat"]
121 |
122 | # base is the path that will be removed from all files' path
123 | # type: string
124 | base = "../../"
125 |
126 | # prefix is the path that will be added to all files' path
127 | # type: string
128 | prefix = "public/"
129 |
130 | # build tags for this set of files
131 | # it will only work if spread mode is enabled
132 | tags = ""
133 | # end: custom
134 |
135 | [[custom]]
136 | # everything inside the folder
137 | # type: array of strings
138 | files = ["./public/"]
139 |
140 | # base is the path that will be removed from all files' path
141 | # type: string
142 | base = ""
143 |
144 | # prefix is the path that will be added to all files' path
145 | # type: string
146 | prefix = ""
147 |
148 | # build tags for this set of files
149 | # it will only work if spread mode is enabled
150 | tags = ""
151 |
152 | # if you have difficulty to understand what base and prefix is
153 | # think about it like this: [prefix] will replace [base]
154 |
155 | # accetps glob
156 | # type: array of strings
157 | exclude = ["public/assets/data/exclude_me.txt"]
158 |
159 | # replace strings in the file
160 | # type: array of objects
161 | [[custom.replace]]
162 |
163 | # accepts glob
164 | # type: string
165 | file = "public/assets/data/*.json"
166 |
167 | # case sensitive
168 | # type: object with strings
169 | [custom.replace.replace]
170 | "{world}" = "hello world"
171 | "{EMAIL}" = "contact@company.com"
172 | # end: replace
173 | # end: custom
174 |
--------------------------------------------------------------------------------
/_example/simple/b0x.yaml:
--------------------------------------------------------------------------------
1 | # all folders and files are relative to the path
2 | # where fileb0x was run at!
3 |
4 | # default: main
5 | pkg: static
6 |
7 | # destination
8 | dest: "./static/"
9 |
10 | # gofmt
11 | # type: bool
12 | # default: false
13 | fmt: false
14 |
15 | # build tags for the main b0x.go file
16 | tags: ""
17 |
18 | # updater allows you to update a b0x in a running server
19 | # without having to restart it
20 | updater:
21 | # disabled by default
22 | enabled: false
23 |
24 | # empty mode creates a empty b0x file with just the
25 | # server and the filesystem, then you'll have to upload
26 | # the files later using the cmd:
27 | # fileb0x -update=http://server.com:port b0x.yaml
28 | #
29 | # it avoids long compile time
30 | empty: false
31 |
32 | # amount of uploads at the same time
33 | workers: 3
34 |
35 | # to get a username and password from a env variable
36 | # leave username and password blank (username: "")
37 | # then set your username and password in the env vars
38 | # (no caps) -> fileb0x_username and fileb0x_password
39 | username: "user"
40 | password: "pass"
41 | port: 8041
42 |
43 | # compress files
44 | # at the moment, only supports gzip
45 | #
46 | # type: object
47 | compression:
48 | # activates the compression
49 | #
50 | # type: bool
51 | # default: false
52 | compress: false
53 |
54 | # valid values are:
55 | # -> "NoCompression"
56 | # -> "BestSpeed"
57 | # -> "BestCompression"
58 | # -> "DefaultCompression" or ""
59 | #
60 | # type: string
61 | # default: "DefaultCompression" # when: Compress == true && Method == ""
62 | method: ""
63 |
64 | # true = do it yourself (the file is written as gzip compressed file into the memory file system)
65 | # false = decompress files at run time (while writing file into memory file system)
66 | #
67 | # type: bool
68 | # default: false
69 | keep: false
70 |
71 | # ---------------
72 | # -- DANGEROUS --
73 | # ---------------
74 | #
75 | # cleans the destination folder (only b0xfiles)
76 | # you should use this when using the spread function
77 | # type: bool
78 | # default: false
79 | clean: false
80 |
81 | # default: ab0x.go
82 | output: "ab0x.go"
83 |
84 | # [noprefix] disables adding "a" prefix to output
85 | # type: bool
86 | # default: false
87 | noprefix: false
88 |
89 | # [unexporTed] builds non-exporTed functions, variables and types...
90 | # type: bool
91 | # default: false
92 | unexporTed: false
93 |
94 | # [spread] means it will make a file to hold all fileb0x data
95 | # and each file into a separaTed .go file
96 | #
97 | # example:
98 | # theres 2 files in the folder assets, they're: hello.json and world.txt
99 | # when spread is activaTed, fileb0x will make a file:
100 | # b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
101 | #
102 | #
103 | # type: bool
104 | # default: false
105 | spread: false
106 |
107 | # [lcf] log changed files when spread is active
108 | lcf: true
109 |
110 | # [debug] is a debug mode where the files are read directly from the file
111 | # sytem. Useful for web dev when files change when the server is running.
112 | # type: bool
113 | # default: false
114 | debug: false
115 |
116 | # type: array of objects
117 | custom:
118 |
119 | # type: array of strings
120 | - files:
121 | - "../../README.md"
122 | # - "../../bench.bat"
123 | - "../../*.bat"
124 |
125 | # base is the path that will be removed from all files' path
126 | # type: string
127 | base: "../../"
128 |
129 | # prefix is the path that will be added to all files' path
130 | # type: string
131 | prefix: "public/"
132 |
133 | # build tags for this set of files
134 | # it will only work if spread mode is enabled
135 | tags: ""
136 |
137 | - files:
138 | # everything inside the folder
139 | # type: array of strings
140 | - "./public/"
141 |
142 | # base is the path that will be removed from all files' path
143 | # type: string
144 | base: ""
145 |
146 | # prefix is the path that will be added to all files' path
147 | # type: string
148 | prefix: ""
149 |
150 | # build tags for this set of files
151 | # it will only work if spread mode is enabled
152 | tags: ""
153 |
154 | # if you have difficulty to understand what base and prefix is
155 | # think about it like this: [prefix] will replace [base]
156 |
157 | # accetps glob
158 | # type: array of strings
159 | exclude:
160 | - "public/assets/data/exclude_me.txt"
161 |
162 | # replace strings in the file
163 | # type: array of objects
164 | replace:
165 |
166 | # accepts glob
167 | # type: string
168 | - file: "public/assets/data/*.json"
169 |
170 | # case sensitive
171 | # type: object with strings
172 | replace:
173 | "{world}": "hello world"
174 | "{EMAIL}": "contact@company.com"
175 | # end: replace
176 | # end: files
177 |
--------------------------------------------------------------------------------
/_example/simple/b0x.json:
--------------------------------------------------------------------------------
1 | {
2 | // in-line comments in json are supporTed by fileb0x!
3 | // a comment must have a space after the double slash
4 | //
5 | // all folders and files are relative to the path
6 | // where fileb0x was run at!
7 |
8 | // default: main
9 | "pkg": "static",
10 |
11 | // destination
12 | "dest": "./static/",
13 |
14 | // gofmt
15 | // type: bool
16 | // default: false
17 | "fmt": false,
18 |
19 | // build tags for the main b0x.go file
20 | "tags": "",
21 |
22 | // updater allows you to update a b0x in a running server
23 | // without having to restart it
24 | "updater": {
25 | // disabled by default
26 | "enabled": false,
27 |
28 | // empty mode creates a empty b0x file with just the
29 | // server and the filesystem, then you'll have to upload
30 | // the files later using the cmd:
31 | // fileb0x -update=http://server.com:port b0x.json
32 | //
33 | // it avoids long compile time
34 | "empty": false,
35 |
36 | // amount of uploads at the same time
37 | "workers": 3,
38 |
39 | // to get a username and password from a env variable
40 | // leave username and password blank ("username": "")
41 | // then set your username and password in the env vars
42 | // (no caps) -> fileb0x_username and fileb0x_password
43 | "username": "user",
44 | "password": "pass",
45 | "port": 8041
46 | },
47 |
48 | // compress files
49 | // at the moment, only supports gzip
50 | //
51 | // type: object
52 | "compression": {
53 | // activates the compression
54 | //
55 | // type: bool
56 | // default: false
57 | "compress": false,
58 |
59 | // valid values are:
60 | // -> "NoCompression"
61 | // -> "BestSpeed"
62 | // -> "BestCompression"
63 | // -> "DefaultCompression" or ""
64 | //
65 | // type: string
66 | // default: "DefaultCompression" // when: Compress == true && Method == ""
67 | "method": "",
68 |
69 | // true = do it yourself (the file is written as gzip compressed file into the memory file system)
70 | // false = decompress files at run time (before writing file into memory file system)
71 | //
72 | // type: bool
73 | // default: false
74 | "keep": false
75 | },
76 |
77 | // ---------------
78 | // -- DANGEROUS --
79 | // ---------------
80 | //
81 | // cleans the destination folder (only b0xfiles)
82 | // you should use this when using the spread function
83 | // type: bool
84 | // default: false
85 | "clean": false,
86 |
87 | // default: ab0x.go
88 | "output": "ab0x.go",
89 |
90 | // [noprefix] disables adding "a" prefix to output
91 | // type: bool
92 | // default: false
93 | "noprefix": false,
94 |
95 | // [unexporTed] builds non-exporTed functions, variables and types...
96 | // type: bool
97 | // default: false
98 | "unexporTed": false,
99 |
100 | // [spread] means it will make a file to hold all fileb0x data
101 | // and each file into a separaTed .go file
102 | //
103 | // example:
104 | // theres 2 files in the folder assets, they're: hello.json and world.txt
105 | // when spread is activaTed, fileb0x will make a file:
106 | // b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
107 | //
108 | //
109 | // type: bool
110 | // default: false
111 | "spread": false,
112 |
113 | // [lcf] log changed files when spread is active
114 | "lcf": true,
115 |
116 | // [debug] is a debug mode where the files are read directly from the file
117 | // sytem. Useful for web dev when files change when the server is running.
118 | // type: bool
119 | // default: false
120 | "debug": false,
121 |
122 | // type: array of objects
123 | "custom": [
124 | {
125 | // type: array of strings
126 | "files": [
127 | "../../README.md",
128 | "../../*.bat"
129 | ],
130 |
131 | // base is the path that will be removed from all files' path
132 | // type: string
133 | "base": "../../",
134 |
135 | // prefix is the path that will be added to all files' path
136 | // type: string
137 | "prefix": "public/",
138 |
139 | // build tags for this set of files
140 | // it will only work if spread mode is enabled
141 | "tags": ""
142 | },
143 | {
144 | // everything inside the folder
145 | // type: array of strings
146 | "files": ["./public/"],
147 |
148 | // base is the path that will be removed from all files' path
149 | // type: string
150 | "base": "",
151 |
152 | // prefix is the path that will be added to all files' path
153 | // type: string
154 | "prefix": "",
155 |
156 | // if you have difficulty to understand what base and prefix is
157 | // think about it like this: [prefix] will replace [base]
158 |
159 | // accetps glob
160 | // type: array of strings
161 | "exclude": [
162 | "public/assets/data/exclude_me.txt"
163 | ],
164 |
165 | // build tags for this set of files
166 | // it will only work if spread mode is enabled
167 | "tags": "",
168 |
169 | // replace strings in the file
170 | // type: array of objects
171 | "replace": [
172 | {
173 | // accepts glob
174 | // type: string
175 | "file": "public/assets/data/*.json",
176 |
177 | // case sensitive
178 | // type: object with strings
179 | "replace": {
180 | "{world}": "hello world",
181 | "{EMAIL}": "contact@company.com"
182 | }
183 | }
184 | ]
185 | }
186 | ]
187 | }
188 |
--------------------------------------------------------------------------------
/custom/custom.go:
--------------------------------------------------------------------------------
1 | package custom
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "os"
9 | "path"
10 | "strings"
11 |
12 | "github.com/UnnoTed/fileb0x/compression"
13 | "github.com/UnnoTed/fileb0x/dir"
14 | "github.com/UnnoTed/fileb0x/file"
15 | "github.com/UnnoTed/fileb0x/updater"
16 | "github.com/UnnoTed/fileb0x/utils"
17 | "github.com/bmatcuk/doublestar"
18 | "github.com/karrick/godirwalk"
19 | )
20 |
21 | const hextable = "0123456789abcdef"
22 |
23 | // SharedConfig holds needed data from config package
24 | // without causing import cycle
25 | type SharedConfig struct {
26 | Output string
27 | Compression *compression.Gzip
28 | Updater updater.Config
29 | }
30 |
31 | // Custom is a set of files with dedicaTed customization
32 | type Custom struct {
33 | Files []string
34 | Base string
35 | Prefix string
36 | Tags string
37 |
38 | Exclude []string
39 | Replace []Replacer
40 | }
41 |
42 | var (
43 | xx = []byte(`\x`)
44 | start = []byte(`[]byte("`)
45 | )
46 |
47 | const lowerhex = "0123456789abcdef"
48 |
49 | // Parse the files transforming them into a byte string and inserting the file
50 | // into a map of files
51 | func (c *Custom) Parse(files *map[string]*file.File, dirs **dir.Dir, config *SharedConfig) error {
52 | to := *files
53 | dirList := *dirs
54 |
55 | var newList []string
56 | for _, customFile := range c.Files {
57 | // get files from glob
58 | list, err := doublestar.Glob(customFile)
59 | if err != nil {
60 | return err
61 | }
62 |
63 | // insert files from glob into the new list
64 | newList = append(newList, list...)
65 | }
66 |
67 | // copy new list
68 | c.Files = newList
69 |
70 | // 0 files in the list
71 | if len(c.Files) == 0 {
72 | return errors.New("No files found")
73 | }
74 |
75 | // loop through files from glob
76 | for _, customFile := range c.Files {
77 | // gives error when file doesn't exist
78 | if !utils.Exists(customFile) {
79 | return fmt.Errorf("File [%s] doesn't exist", customFile)
80 | }
81 |
82 | cb := func(fpath string, d *godirwalk.Dirent) error {
83 | if config.Updater.Empty && !config.Updater.IsUpdating {
84 | log.Println("empty mode")
85 | return nil
86 | }
87 |
88 | // only files will be processed
89 | if d != nil && d.IsDir() {
90 | return nil
91 | }
92 |
93 | originalPath := fpath
94 | fpath = utils.FixPath(fpath)
95 |
96 | var fixedPath string
97 | if c.Prefix != "" || c.Base != "" {
98 | c.Base = strings.TrimPrefix(c.Base, "./")
99 |
100 | if strings.HasPrefix(fpath, c.Base) {
101 | fixedPath = c.Prefix + fpath[len(c.Base):]
102 | } else {
103 | if c.Base != "" {
104 | fixedPath = c.Prefix + fpath
105 | }
106 | }
107 |
108 | fixedPath = utils.FixPath(fixedPath)
109 | } else {
110 | fixedPath = utils.FixPath(fpath)
111 | }
112 |
113 | // check for excluded files
114 | for _, excludedFile := range c.Exclude {
115 | m, err := doublestar.Match(c.Prefix+excludedFile, fixedPath)
116 | if err != nil {
117 | return err
118 | }
119 |
120 | if m {
121 | return nil
122 | }
123 | }
124 |
125 | info, err := os.Stat(fpath)
126 | if err != nil {
127 | return err
128 | }
129 |
130 | if info.Name() == config.Output {
131 | return nil
132 | }
133 |
134 | // get file's content
135 | content, err := ioutil.ReadFile(fpath)
136 | if err != nil {
137 | return err
138 | }
139 |
140 | replaced := false
141 |
142 | // loop through replace list
143 | for _, r := range c.Replace {
144 | // check if path matches the pattern from property: file
145 | matched, err := doublestar.Match(c.Prefix+r.File, fixedPath)
146 | if err != nil {
147 | return err
148 | }
149 |
150 | if matched {
151 | for pattern, word := range r.Replace {
152 | content = []byte(strings.Replace(string(content), pattern, word, -1))
153 | replaced = true
154 | }
155 | }
156 | }
157 |
158 | // compress the content
159 | if config.Compression.Options != nil {
160 | content, err = config.Compression.Compress(content)
161 | if err != nil {
162 | return err
163 | }
164 | }
165 |
166 | dst := make([]byte, len(content)*4)
167 | for i := 0; i < len(content); i++ {
168 | dst[i*4] = byte('\\')
169 | dst[i*4+1] = byte('x')
170 | dst[i*4+2] = hextable[content[i]>>4]
171 | dst[i*4+3] = hextable[content[i]&0x0f]
172 | }
173 |
174 | f := file.NewFile()
175 | f.OriginalPath = originalPath
176 | f.ReplacedText = replaced
177 | f.Data = `[]byte("` + string(dst) + `")`
178 | f.Name = info.Name()
179 | f.Path = fixedPath
180 | f.Tags = c.Tags
181 | f.Base = c.Base
182 | f.Prefix = c.Prefix
183 | f.Modified = info.ModTime().String()
184 |
185 | if _, ok := to[fixedPath]; ok {
186 | f.Tags = to[fixedPath].Tags
187 | }
188 |
189 | // insert dir to dirlist so it can be created on b0x's init()
190 | dirList.Insert(path.Dir(fixedPath))
191 |
192 | // insert file into file list
193 | to[fixedPath] = f
194 | return nil
195 | }
196 |
197 | customFile = utils.FixPath(customFile)
198 |
199 | // unlike filepath.walk, godirwalk will only walk dirs
200 | f, err := os.Open(customFile)
201 | if err != nil {
202 | return err
203 | }
204 |
205 | defer f.Close()
206 |
207 | fs, err := f.Stat()
208 | if err != nil {
209 | return err
210 | }
211 |
212 | if fs.IsDir() {
213 | if err := godirwalk.Walk(customFile, &godirwalk.Options{
214 | Unsorted: true,
215 | Callback: cb,
216 | }); err != nil {
217 | return err
218 | }
219 |
220 | } else {
221 | if err := cb(customFile, nil); err != nil {
222 | return err
223 | }
224 | }
225 | }
226 |
227 | return nil
228 | }
229 |
--------------------------------------------------------------------------------
/updater/server_test.go:
--------------------------------------------------------------------------------
1 | // Code generated by fileb0x at "2017-05-06 21:06:04.400581277 -0300 ADT" from config file "b0x.toml" DO NOT EDIT.
2 |
3 | package updater
4 |
5 | import (
6 | "bytes"
7 |
8 | "io"
9 | "log"
10 | "net/http"
11 | "os"
12 | "path"
13 | "path/filepath"
14 | "strings"
15 |
16 | "context"
17 | "golang.org/x/net/webdav"
18 |
19 | "crypto/sha256"
20 | "encoding/hex"
21 |
22 | "github.com/labstack/echo"
23 | "github.com/labstack/echo/middleware"
24 | )
25 |
26 | var (
27 | // CTX is a context for webdav vfs
28 | CTX = context.Background()
29 |
30 | // FS is a virtual memory file system
31 | FS = webdav.NewMemFS()
32 |
33 | // Handler is used to server files through a http handler
34 | Handler *webdav.Handler
35 |
36 | // HTTP is the http file system
37 | HTTP http.FileSystem = new(HTTPFS)
38 | )
39 |
40 | // HTTPFS implements http.FileSystem
41 | type HTTPFS struct{}
42 |
43 | func init() {
44 | if CTX.Err() != nil {
45 | log.Fatal(CTX.Err())
46 | }
47 |
48 | Handler = &webdav.Handler{
49 | FileSystem: FS,
50 | LockSystem: webdav.NewMemLS(),
51 | }
52 | }
53 |
54 | // Open a file
55 | func (hfs *HTTPFS) Open(path string) (http.File, error) {
56 | f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
57 | if err != nil {
58 | return nil, err
59 | }
60 |
61 | return f, nil
62 | }
63 |
64 | // ReadFile is adapTed from ioutil
65 | func ReadFile(path string) ([]byte, error) {
66 | f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644)
67 | if err != nil {
68 | return nil, err
69 | }
70 |
71 | buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
72 |
73 | // If the buffer overflows, we will get bytes.ErrTooLarge.
74 | // Return that as an error. Any other panic remains.
75 | defer func() {
76 | e := recover()
77 | if e == nil {
78 | return
79 | }
80 | if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
81 | err = panicErr
82 | } else {
83 | panic(e)
84 | }
85 | }()
86 | _, err = buf.ReadFrom(f)
87 | return buf.Bytes(), err
88 | }
89 |
90 | // WriteFile is adapTed from ioutil
91 | func WriteFile(filename string, data []byte, perm os.FileMode) error {
92 | f, err := FS.OpenFile(CTX, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
93 | if err != nil {
94 | return err
95 | }
96 | n, err := f.Write(data)
97 | if err == nil && n < len(data) {
98 | err = io.ErrShortWrite
99 | }
100 | if err1 := f.Close(); err == nil {
101 | err = err1
102 | }
103 | return err
104 | }
105 |
106 | // WalkDirs looks for files in the given dir and returns a list of files in it
107 | // usage for all files in the b0x: WalkDirs("", false)
108 | func WalkDirs(name string, includeDirsInList bool, files ...string) ([]string, error) {
109 | f, err := FS.OpenFile(CTX, name, os.O_RDONLY, 0)
110 | if err != nil {
111 | return nil, err
112 | }
113 |
114 | fileInfos, err := f.Readdir(0)
115 | f.Close()
116 | if err != nil {
117 | return nil, err
118 | }
119 |
120 | for _, info := range fileInfos {
121 | filename := path.Join(name, info.Name())
122 |
123 | if includeDirsInList || !info.IsDir() {
124 | files = append(files, filename)
125 | }
126 |
127 | if info.IsDir() {
128 | files, err = WalkDirs(filename, includeDirsInList, files...)
129 | if err != nil {
130 | return nil, err
131 | }
132 | }
133 | }
134 |
135 | return files, nil
136 | }
137 |
138 | // Server holds information about the http server
139 | // used to update files remotely
140 | type Server struct {
141 | Auth Auth
142 | Files []string
143 | }
144 |
145 | // Init sets the routes and basic http auth
146 | // before starting the http server
147 | func (s *Server) Init() {
148 | s.Auth = Auth{
149 | Username: "user",
150 | Password: "pass",
151 | }
152 |
153 | e := echo.New()
154 | e.Use(middleware.Recover())
155 | e.Use(s.BasicAuth())
156 | e.POST("/", s.Post)
157 | e.GET("/", s.Get)
158 |
159 | if err := e.Start(":8041"); err != nil {
160 | panic(err)
161 | }
162 | }
163 |
164 | // Get gives a list of file names and hashes
165 | func (s *Server) Get(c echo.Context) error {
166 | log.Println("[fileb0x.Server]: Hashing server files...")
167 |
168 | // file:hash
169 | hashes := map[string]string{}
170 |
171 | // get all files in the virtual memory file system
172 | var err error
173 | s.Files, err = WalkDirs("", false)
174 | if err != nil {
175 | return err
176 | }
177 |
178 | // get a hash for each file
179 | for _, filePath := range s.Files {
180 | f, err := FS.OpenFile(CTX, filePath, os.O_RDONLY, 0644)
181 | if err != nil {
182 | return err
183 | }
184 |
185 | hash := sha256.New()
186 | _, err = io.Copy(hash, f)
187 | if err != nil {
188 | return err
189 | }
190 |
191 | hashes[filePath] = hex.EncodeToString(hash.Sum(nil))
192 | }
193 |
194 | log.Println("[fileb0x.Server]: Done hashing files")
195 | return c.JSON(http.StatusOK, &ResponseInit{
196 | Success: true,
197 | Hashes: hashes,
198 | })
199 | }
200 |
201 | // Post is used to upload a file and replace
202 | // it in the virtual memory file system
203 | func (s *Server) Post(c echo.Context) error {
204 | file, err := c.FormFile("file")
205 | if err != nil {
206 | return err
207 | }
208 |
209 | log.Println("[fileb0x.Server]:", file.Filename, "Found request to upload a file")
210 |
211 | src, err := file.Open()
212 | if err != nil {
213 | return err
214 | }
215 | defer src.Close()
216 |
217 | newDir := filepath.Dir(file.Filename)
218 | _, err = FS.Stat(CTX, newDir)
219 | if err == os.ErrNotExist {
220 | list := strings.Split(newDir, "/")
221 | var tree string
222 |
223 | for _, dir := range list {
224 | if dir == "" || dir == "." || dir == "/" || dir == "./" {
225 | continue
226 | }
227 |
228 | tree += dir + "/"
229 | log.Println("dir", tree)
230 | err = FS.Mkdir(CTX, tree, 0777)
231 | if err != nil && err != os.ErrExist {
232 | log.Println("failed", err)
233 | return err
234 | }
235 | }
236 | }
237 |
238 | log.Println("[fileb0x.Server]:", file.Filename, "Opening file...")
239 | f, err := FS.OpenFile(CTX, file.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
240 | if err != nil {
241 | return err
242 | }
243 |
244 | log.Println("[fileb0x.Server]:", file.Filename, "Writing file into Virutal Memory FileSystem...")
245 | if _, err = io.Copy(f, src); err != nil {
246 | return err
247 | }
248 |
249 | log.Println("[fileb0x.Server]:", file.Filename, "Done writing file")
250 | return c.String(http.StatusOK, "ok")
251 | }
252 |
253 | // BasicAuth is a middleware to check if
254 | // the username and password are valid
255 | // echo's middleware isn't used because of golint issues
256 | func (s *Server) BasicAuth() echo.MiddlewareFunc {
257 | return func(next echo.HandlerFunc) echo.HandlerFunc {
258 | return func(c echo.Context) error {
259 | u, p, _ := c.Request().BasicAuth()
260 | if u != s.Auth.Username || p != s.Auth.Password {
261 | return echo.ErrUnauthorized
262 | }
263 |
264 | return next(c)
265 | }
266 | }
267 | }
268 |
--------------------------------------------------------------------------------
/updater/updater.go:
--------------------------------------------------------------------------------
1 | package updater
2 |
3 | import (
4 | "bytes"
5 | "crypto/sha256"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "log"
11 | "mime/multipart"
12 | "net/http"
13 | "os"
14 | "strings"
15 |
16 | "encoding/hex"
17 |
18 | "encoding/json"
19 |
20 | "github.com/UnnoTed/fileb0x/file"
21 | termui "github.com/gizak/termui/v3"
22 | "github.com/gizak/termui/v3/widgets"
23 | )
24 |
25 | // Auth holds authentication for the http basic auth
26 | type Auth struct {
27 | Username string
28 | Password string
29 | }
30 |
31 | // ResponseInit holds a list of hashes from the server
32 | // to be sent to the client so it can check if there
33 | // is a new file or a changed file
34 | type ResponseInit struct {
35 | Success bool
36 | Hashes map[string]string
37 | }
38 |
39 | // ProgressReader implements a io.Reader with a Read
40 | // function that lets a callback report how much
41 | // of the file was read
42 | type ProgressReader struct {
43 | io.Reader
44 | Reporter func(r int64)
45 | }
46 |
47 | func (pr *ProgressReader) Read(p []byte) (n int, err error) {
48 | n, err = pr.Reader.Read(p)
49 | pr.Reporter(int64(n))
50 | return
51 | }
52 |
53 | // Updater sends files that should be update to the b0x server
54 | type Updater struct {
55 | Server string
56 | Auth Auth
57 | ui []termui.Drawable
58 |
59 | RemoteHashes map[string]string
60 | LocalHashes map[string]string
61 | ToUpdate []string
62 | Workers int
63 | }
64 |
65 | // Init gets the list of file hash from the server
66 | func (up *Updater) Init() error {
67 | return up.Get()
68 | }
69 |
70 | // Get gets the list of file hash from the server
71 | func (up *Updater) Get() error {
72 | log.Println("Creating hash list request...")
73 | req, err := http.NewRequest("GET", up.Server, nil)
74 | if err != nil {
75 | return err
76 | }
77 |
78 | req.SetBasicAuth(up.Auth.Username, up.Auth.Password)
79 |
80 | log.Println("Sending hash list request...")
81 | client := &http.Client{}
82 | resp, err := client.Do(req)
83 | if err != nil {
84 | return err
85 | }
86 |
87 | if resp.StatusCode == http.StatusUnauthorized {
88 | return errors.New("Error Unautorized")
89 | }
90 |
91 | log.Println("Reading hash list response's body...")
92 | var buf bytes.Buffer
93 | _, err = buf.ReadFrom(resp.Body)
94 | if err != nil {
95 | return err
96 | }
97 |
98 | log.Println("Parsing hash list response's body...")
99 | ri := &ResponseInit{}
100 | err = json.Unmarshal(buf.Bytes(), &ri)
101 | if err != nil {
102 | log.Println("Body is", buf.Bytes())
103 | return err
104 | }
105 | resp.Body.Close()
106 |
107 | // copy hash list
108 | if ri.Success {
109 | log.Println("Copying hash list...")
110 | up.RemoteHashes = ri.Hashes
111 | up.LocalHashes = map[string]string{}
112 | log.Println("Done")
113 | }
114 |
115 | return nil
116 | }
117 |
118 | // Updatable checks if there is any file that should be updaTed
119 | func (up *Updater) Updatable(files map[string]*file.File) (bool, error) {
120 | hasUpdates := !up.EqualHashes(files)
121 |
122 | if hasUpdates {
123 | log.Println("----------------------------------------")
124 | log.Println("-- Found files that should be updated --")
125 | log.Println("----------------------------------------")
126 | } else {
127 | log.Println("-----------------------")
128 | log.Println("-- Nothing to update --")
129 | log.Println("-----------------------")
130 | }
131 |
132 | return hasUpdates, nil
133 | }
134 |
135 | // EqualHash checks if a local file hash equals a remote file hash
136 | // it returns false when a remote file hash isn't found (new files)
137 | func (up *Updater) EqualHash(name string) bool {
138 | hash, existsLocally := up.LocalHashes[name]
139 | _, existsRemotely := up.RemoteHashes[name]
140 | if !existsRemotely || !existsLocally || hash != up.RemoteHashes[name] {
141 | if hash != up.RemoteHashes[name] {
142 | log.Println("Found changes in file: ", name)
143 |
144 | } else if !existsRemotely && existsLocally {
145 | log.Println("Found new file: ", name)
146 | }
147 |
148 | return false
149 | }
150 |
151 | return true
152 | }
153 |
154 | // EqualHashes builds the list of local hashes before
155 | // checking if there is any that should be updated
156 | func (up *Updater) EqualHashes(files map[string]*file.File) bool {
157 | for _, f := range files {
158 | log.Println("Checking file for changes:", f.Path)
159 |
160 | if len(f.Bytes) == 0 && !f.ReplacedText {
161 | data, err := ioutil.ReadFile(f.OriginalPath)
162 | if err != nil {
163 | panic(err)
164 | }
165 |
166 | f.Bytes = data
167 |
168 | // removes the []byte("") from the string
169 | // when the data isn't in the Bytes variable
170 | } else if len(f.Bytes) == 0 && f.ReplacedText && len(f.Data) > 0 {
171 | f.Data = strings.TrimPrefix(f.Data, `[]byte("`)
172 | f.Data = strings.TrimSuffix(f.Data, `")`)
173 | f.Data = strings.Replace(f.Data, "\\x", "", -1)
174 |
175 | var err error
176 | f.Bytes, err = hex.DecodeString(f.Data)
177 | if err != nil {
178 | log.Println("SHIT", err)
179 | return false
180 | }
181 |
182 | f.Data = ""
183 | }
184 |
185 | sha := sha256.New()
186 | if _, err := sha.Write(f.Bytes); err != nil {
187 | panic(err)
188 | return false
189 | }
190 |
191 | up.LocalHashes[f.Path] = hex.EncodeToString(sha.Sum(nil))
192 | }
193 |
194 | // check if there is any file to update
195 | update := false
196 | for k := range up.LocalHashes {
197 | if !up.EqualHash(k) {
198 | up.ToUpdate = append(up.ToUpdate, k)
199 | update = true
200 | }
201 | }
202 |
203 | return !update
204 | }
205 |
206 | type job struct {
207 | current int
208 | files *file.File
209 | total int
210 | }
211 |
212 | // UpdateFiles sends all files that should be updated to the server
213 | // the limit is 3 concurrent files at once
214 | func (up *Updater) UpdateFiles(files map[string]*file.File) error {
215 | updatable, err := up.Updatable(files)
216 | if err != nil {
217 | return err
218 | }
219 |
220 | if !updatable {
221 | return nil
222 | }
223 |
224 | // everything's height
225 | height := 3
226 | err = termui.Init()
227 | if err != nil {
228 | panic(err)
229 | }
230 | defer termui.Close()
231 |
232 | // info text
233 | p := widgets.NewParagraph()
234 | p.Text = "fileb0x updater - PRESS q TO QUIT"
235 | p.TitleStyle.Fg = termui.ColorWhite
236 | termWidth, _ := termui.TerminalDimensions()
237 | p.SetRect(0, 0, termWidth, height)
238 | up.ui = append(up.ui, p)
239 |
240 | doneTotal := 0
241 | total := len(up.ToUpdate)
242 | jobs := make(chan *job, total)
243 | done := make(chan bool, total)
244 |
245 | if up.Workers <= 0 {
246 | up.Workers = 1
247 | }
248 |
249 | // listen to events
250 | go func() {
251 | uiEvents := termui.PollEvents()
252 | for {
253 | e := <-uiEvents
254 | switch e.ID {
255 | case "q", "":
256 | termui.Close()
257 | os.Exit(1)
258 | }
259 | }
260 | }()
261 |
262 | // stops rendering when total is reached
263 | go func(upp *Updater, d *int) {
264 | for {
265 | if *d >= total {
266 | break
267 | }
268 |
269 | termui.Render(upp.ui...)
270 | }
271 | }(up, &doneTotal)
272 |
273 | for i := 0; i < up.Workers; i++ {
274 | // creates a progress bar
275 | g := widgets.NewGauge()
276 | top := height + (height * i)
277 | g.SetRect(0, top, termWidth, top+height)
278 | g.BarColor = termui.ColorBlue
279 | up.ui = append(up.ui, g)
280 |
281 | go up.worker(jobs, done, g)
282 | }
283 |
284 | for i, name := range up.ToUpdate {
285 | jobs <- &job{
286 | current: i + 1,
287 | files: files[name],
288 | total: total,
289 | }
290 | }
291 | close(jobs)
292 |
293 | for i := 0; i < total; i++ {
294 | <-done
295 | doneTotal++
296 | }
297 |
298 | return nil
299 | }
300 |
301 | func (up *Updater) worker(jobs <-chan *job, done chan<- bool, g *widgets.Gauge) {
302 | for job := range jobs {
303 | f := job.files
304 | fr := bytes.NewReader(f.Bytes)
305 | g.Title = fmt.Sprintf("%d/%d %s", job.current, job.total, f.Path)
306 |
307 | // updates progress bar's percentage
308 | var total int64
309 | pr := &ProgressReader{fr, func(r int64) {
310 | total += r
311 | g.Percent = int(float64(total) / float64(fr.Size()) * 100)
312 | }}
313 |
314 | r, w := io.Pipe()
315 | writer := multipart.NewWriter(w)
316 |
317 | // copy the file into the form
318 | go func(fr *ProgressReader) {
319 | defer w.Close()
320 | part, err := writer.CreateFormFile("file", f.Path)
321 | if err != nil {
322 | panic(err)
323 | }
324 |
325 | _, err = io.Copy(part, fr)
326 | if err != nil {
327 | panic(err)
328 | }
329 |
330 | err = writer.Close()
331 | if err != nil {
332 | panic(err)
333 | }
334 | }(pr)
335 |
336 | // create a post request with basic auth
337 | // and the file included in a form
338 | req, err := http.NewRequest("POST", up.Server, r)
339 | if err != nil {
340 | panic(err)
341 | }
342 |
343 | req.Header.Set("Content-Type", writer.FormDataContentType())
344 | req.SetBasicAuth(up.Auth.Username, up.Auth.Password)
345 |
346 | // sends the request
347 | client := &http.Client{}
348 | resp, err := client.Do(req)
349 | if err != nil {
350 | panic(err)
351 | }
352 |
353 | body := &bytes.Buffer{}
354 | _, err = body.ReadFrom(resp.Body)
355 | if err != nil {
356 | panic(err)
357 | }
358 |
359 | if err := resp.Body.Close(); err != nil {
360 | panic(err)
361 | }
362 |
363 | if body.String() != "ok" {
364 | panic(body.String())
365 | }
366 |
367 | done <- true
368 | }
369 | }
370 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "crypto/md5"
7 | "flag"
8 | "fmt"
9 | "go/format"
10 | "io/ioutil"
11 | "log"
12 | "os"
13 | "path"
14 | "path/filepath"
15 | "runtime"
16 | "sort"
17 | "strconv"
18 | "strings"
19 | "time"
20 |
21 | "github.com/UnnoTed/fileb0x/compression"
22 | "github.com/UnnoTed/fileb0x/config"
23 | "github.com/UnnoTed/fileb0x/custom"
24 | "github.com/UnnoTed/fileb0x/dir"
25 | "github.com/UnnoTed/fileb0x/file"
26 | "github.com/UnnoTed/fileb0x/template"
27 | "github.com/UnnoTed/fileb0x/updater"
28 | "github.com/UnnoTed/fileb0x/utils"
29 |
30 | // just to install automatically
31 | _ "github.com/labstack/echo"
32 | _ "golang.org/x/net/webdav"
33 | )
34 |
35 | var (
36 | err error
37 | cfg *config.Config
38 | files = make(map[string]*file.File)
39 | dirs = new(dir.Dir)
40 | cfgPath string
41 |
42 | fUpdate string
43 | startTime = time.Now()
44 |
45 | hashStart = []byte("// modification hash(")
46 | hashEnd = []byte(")")
47 |
48 | modTimeStart = []byte("// modified(")
49 | modTimeEnd = []byte(")")
50 | )
51 |
52 | func main() {
53 | runtime.GOMAXPROCS(runtime.NumCPU())
54 |
55 | // check for updates
56 | flag.StringVar(&fUpdate, "update", "", "-update=http(s)://host:port - default port: 8041")
57 | flag.Parse()
58 | var (
59 | update = fUpdate != ""
60 | up *updater.Updater
61 | )
62 |
63 | // create config and try to get b0x file from args
64 | f := new(config.File)
65 | err = f.FromArg(true)
66 | if err != nil {
67 | panic(err)
68 | }
69 |
70 | // load b0x file's config
71 | cfg, err = f.Load()
72 | if err != nil {
73 | panic(err)
74 | }
75 |
76 | err = cfg.Defaults()
77 | if err != nil {
78 | panic(err)
79 | }
80 |
81 | cfgPath = f.FilePath
82 |
83 | if err := cfg.Updater.CheckInfo(); err != nil {
84 | panic(err)
85 | }
86 |
87 | cfg.Updater.IsUpdating = update
88 |
89 | // creates a config that can be inserTed into custom
90 | // without causing a import cycle
91 | sharedConfig := new(custom.SharedConfig)
92 | sharedConfig.Output = cfg.Output
93 | sharedConfig.Updater = cfg.Updater
94 | sharedConfig.Compression = compression.NewGzip()
95 | sharedConfig.Compression.Options = cfg.Compression
96 |
97 | // loop through b0x's [custom] objects
98 | for _, c := range cfg.Custom {
99 | err = c.Parse(&files, &dirs, sharedConfig)
100 | if err != nil {
101 | panic(err)
102 | }
103 | }
104 |
105 | // builds remap's list
106 | var (
107 | remap string
108 | modHash string
109 | mods []string
110 | lastHash string
111 | )
112 |
113 | for _, f := range files {
114 | remap += f.GetRemap()
115 | mods = append(mods, f.Modified)
116 | }
117 |
118 | // sorts modification time list and create a md5 of it
119 | sort.Strings(mods)
120 | modHash = stringMD5Hex(strings.Join(mods, "")) + "." + stringMD5Hex(string(f.Data))
121 | exists := fileExists(cfg.Dest + cfg.Output)
122 |
123 | if exists {
124 | // gets the modification hash from the main b0x file
125 | lastHash, err = getModification(cfg.Dest+cfg.Output, hashStart, hashEnd)
126 | if err != nil {
127 | panic(err)
128 | }
129 | }
130 |
131 | if !exists || lastHash != modHash {
132 | // create files template and exec it
133 | t := new(template.Template)
134 | t.Set("files")
135 | t.Variables = struct {
136 | ConfigFile string
137 | Now string
138 | Pkg string
139 | Files map[string]*file.File
140 | Tags string
141 | Spread bool
142 | Remap string
143 | DirList []string
144 | Compression *compression.Options
145 | Debug bool
146 | Updater updater.Config
147 | ModificationHash string
148 | }{
149 | ConfigFile: filepath.Base(cfgPath),
150 | Now: time.Now().String(),
151 | Pkg: cfg.Pkg,
152 | Files: files,
153 | Tags: cfg.Tags,
154 | Remap: remap,
155 | Spread: cfg.Spread,
156 | DirList: dirs.Clean(),
157 | Compression: cfg.Compression,
158 | Debug: cfg.Debug,
159 | Updater: cfg.Updater,
160 | ModificationHash: modHash,
161 | }
162 |
163 | tmpl, err := t.Exec()
164 | if err != nil {
165 | panic(err)
166 | }
167 |
168 | if err := os.MkdirAll(cfg.Dest, 0770); err != nil {
169 | panic(err)
170 | }
171 |
172 | // gofmt
173 | if cfg.Fmt {
174 | tmpl, err = format.Source(tmpl)
175 | if err != nil {
176 | panic(err)
177 | }
178 | }
179 |
180 | // write final execuTed template into the destination file
181 | err = ioutil.WriteFile(cfg.Dest+cfg.Output, tmpl, 0640)
182 | if err != nil {
183 | panic(err)
184 | }
185 | }
186 |
187 | // write spread files
188 | var (
189 | finalList []string
190 | changedList []string
191 | )
192 | if cfg.Spread {
193 | a := strings.Split(path.Dir(cfg.Dest), "/")
194 | dirName := a[len(a)-1:][0]
195 |
196 | for _, f := range files {
197 | a := strings.Split(path.Dir(f.Path), "/")
198 | fileDirName := a[len(a)-1:][0]
199 |
200 | if dirName == fileDirName {
201 | continue
202 | }
203 |
204 | // transform / to _ and some other chars...
205 | customName := "b0xfile_" + utils.FixName(f.Path) + ".go"
206 | finalList = append(finalList, customName)
207 |
208 | exists := fileExists(cfg.Dest + customName)
209 | var mth string
210 | if exists {
211 | mth, err = getModification(cfg.Dest+customName, modTimeStart, modTimeEnd)
212 | if err != nil {
213 | panic(err)
214 | }
215 | }
216 |
217 | changed := mth != f.Modified
218 | if changed {
219 | changedList = append(changedList, f.OriginalPath)
220 | }
221 |
222 | if !exists || changed {
223 | // creates file template and exec it
224 | t := new(template.Template)
225 | t.Set("file")
226 | t.Variables = struct {
227 | ConfigFile string
228 | Now string
229 | Pkg string
230 | Path string
231 | Name string
232 | Dir [][]string
233 | Tags string
234 | Data string
235 | Compression *compression.Options
236 | Modified string
237 | OriginalPath string
238 | }{
239 | ConfigFile: filepath.Base(cfgPath),
240 | Now: time.Now().String(),
241 | Pkg: cfg.Pkg,
242 | Path: f.Path,
243 | Name: f.Name,
244 | Dir: dirs.List,
245 | Tags: f.Tags,
246 | Data: f.Data,
247 | Compression: cfg.Compression,
248 | Modified: f.Modified,
249 | OriginalPath: f.OriginalPath,
250 | }
251 | tmpl, err := t.Exec()
252 | if err != nil {
253 | panic(err)
254 | }
255 |
256 | // gofmt
257 | if cfg.Fmt {
258 | tmpl, err = format.Source(tmpl)
259 | if err != nil {
260 | panic(err)
261 | }
262 | }
263 |
264 | // write final execuTed template into the destination file
265 | if err := ioutil.WriteFile(cfg.Dest+customName, tmpl, 0640); err != nil {
266 | panic(err)
267 | }
268 | }
269 | }
270 | }
271 |
272 | // remove b0xfiles when [clean] is true
273 | // it doesn't clean destination's folders
274 | if cfg.Clean {
275 | matches, err := filepath.Glob(cfg.Dest + "b0xfile_*.go")
276 | if err != nil {
277 | panic(err)
278 | }
279 |
280 | // remove matched file if they aren't in the finalList
281 | // which contains the list of all files written by the
282 | // spread option
283 | for _, f := range matches {
284 | var found bool
285 | for _, name := range finalList {
286 | if strings.HasSuffix(f, name) {
287 | found = true
288 | }
289 | }
290 |
291 | if !found {
292 | err = os.Remove(f)
293 | if err != nil {
294 | panic(err)
295 | }
296 | }
297 | }
298 | }
299 |
300 | // main b0x
301 | if lastHash != modHash {
302 | log.Printf("fileb0x: took [%dms] to write [%s] from config file [%s] at [%s]",
303 | time.Since(startTime).Nanoseconds()/1e6, cfg.Dest+cfg.Output,
304 | filepath.Base(cfgPath), time.Now().String())
305 | } else {
306 | log.Printf("fileb0x: no changes detected")
307 | }
308 |
309 | // log changed files
310 | if cfg.Lcf && len(changedList) > 0 {
311 | log.Printf("fileb0x: list of changed files [%s]", strings.Join(changedList, " | "))
312 | }
313 |
314 | if update {
315 | if !cfg.Updater.Enabled {
316 | panic("fileb0x: The updater is disabled, enable it in your config file!")
317 | }
318 |
319 | // includes port when not present
320 | if !strings.HasSuffix(fUpdate, ":"+strconv.Itoa(cfg.Updater.Port)) {
321 | fUpdate += ":" + strconv.Itoa(cfg.Updater.Port)
322 | }
323 |
324 | up = &updater.Updater{
325 | Server: fUpdate,
326 | Auth: updater.Auth{
327 | Username: cfg.Updater.Username,
328 | Password: cfg.Updater.Password,
329 | },
330 | Workers: cfg.Updater.Workers,
331 | }
332 |
333 | // get file hashes from server
334 | if err := up.Init(); err != nil {
335 | panic(err)
336 | }
337 |
338 | // check if an update is available, then updates...
339 | if err := up.UpdateFiles(files); err != nil {
340 | panic(err)
341 | }
342 | }
343 | }
344 |
345 | func getModification(path string, start []byte, end []byte) (string, error) {
346 | file, err := os.Open(path)
347 | if err != nil {
348 | return "", err
349 | }
350 | defer file.Close()
351 |
352 | reader := bufio.NewReader(file)
353 | var data []byte
354 | for {
355 | line, _, err := reader.ReadLine()
356 | if err != nil {
357 | return "", err
358 | }
359 |
360 | if !bytes.HasPrefix(line, start) || !bytes.HasSuffix(line, end) {
361 | continue
362 | }
363 |
364 | data = line
365 | break
366 | }
367 |
368 | hash := bytes.TrimPrefix(data, start)
369 | hash = bytes.TrimSuffix(hash, end)
370 |
371 | return string(hash), nil
372 | }
373 |
374 | func fileExists(filename string) bool {
375 | _, err := os.Stat(filename)
376 | return err == nil
377 | }
378 |
379 | func stringMD5Hex(data string) string {
380 | hash := md5.New()
381 | hash.Write([]byte(data))
382 | return fmt.Sprintf("%x", hash.Sum(nil))
383 | }
384 |
--------------------------------------------------------------------------------
/template/files.go:
--------------------------------------------------------------------------------
1 | package template
2 |
3 | var filesTemplate = `{{buildTags .Tags}}// Code generated by fileb0x at "{{.Now}}" from config file "{{.ConfigFile}}" DO NOT EDIT.
4 | // modification hash({{.ModificationHash}})
5 |
6 | package {{.Pkg}}
7 | {{$Compression := .Compression}}
8 |
9 | import (
10 | "bytes"
11 | {{if not .Spread}}{{if and $Compression.Compress (not .Debug)}}{{if not $Compression.Keep}}"compress/gzip"{{end}}{{end}}{{end}}
12 | "context"
13 | "io"
14 | "net/http"
15 | "os"
16 | "path"
17 | {{if or .Updater.Enabled .Debug}}
18 | "strings"
19 | {{end}}
20 |
21 | "golang.org/x/net/webdav"
22 |
23 | {{if .Updater.Enabled}}
24 | "crypto/sha256"
25 | "encoding/hex"
26 | "log"
27 | "path/filepath"
28 |
29 | "github.com/labstack/echo"
30 | "github.com/labstack/echo/middleware"
31 | {{end}}
32 | )
33 |
34 | var (
35 | // CTX is a context for webdav vfs
36 | {{exported "CTX"}} = context.Background()
37 |
38 | {{if .Debug}}
39 | {{exported "FS"}} = webdav.Dir(".")
40 | {{else}}
41 | // FS is a virtual memory file system
42 | {{exported "FS"}} = webdav.NewMemFS()
43 | {{end}}
44 |
45 | // Handler is used to server files through a http handler
46 | {{exportedTitle "Handler"}} *webdav.Handler
47 |
48 | // HTTP is the http file system
49 | {{exportedTitle "HTTP"}} http.FileSystem = new({{exported "HTTPFS"}})
50 | )
51 |
52 | // HTTPFS implements http.FileSystem
53 | type {{exported "HTTPFS"}} struct {
54 | // Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/*
55 | Prefix string
56 | }
57 |
58 | {{if (and (not .Spread) (not .Debug))}}
59 | {{range .Files}}
60 | // {{exportedTitle "File"}}{{buildSafeVarName .Path}} is "{{.Path}}"
61 | var {{exportedTitle "File"}}{{buildSafeVarName .Path}} = {{.Data}}
62 | {{end}}
63 | {{end}}
64 |
65 | func init() {
66 | err := {{exported "CTX"}}.Err()
67 | if err != nil {
68 | panic(err)
69 | }
70 |
71 | {{ $length := len .DirList }}
72 | {{ $fLength := len .Files }}
73 | {{ $noDirsButFiles := (and (not .Spread) (eq $length 0) (gt $fLength 0)) }}
74 | {{if not .Debug}}
75 | {{range $index, $dir := .DirList}}
76 | {{if and (ne $dir "./") (ne $dir "/") (ne $dir ".") (ne $dir "")}}
77 | err = {{exported "FS"}}.Mkdir({{exported "CTX"}}, "{{$dir}}", 0777)
78 | if err != nil && err != os.ErrExist {
79 | panic(err)
80 | }
81 | {{end}}
82 | {{end}}
83 | {{end}}
84 |
85 | {{if (and (not .Spread) (not .Debug))}}
86 | {{if not .Updater.Empty}}
87 | var f webdav.File
88 | {{end}}
89 |
90 | {{if $Compression.Compress}}
91 | {{if not $Compression.Keep}}
92 | var rb *bytes.Reader
93 | var r *gzip.Reader
94 | {{end}}
95 | {{end}}
96 |
97 | {{range .Files}}
98 | {{if $Compression.Compress}}
99 | {{if not $Compression.Keep}}
100 | rb = bytes.NewReader({{exportedTitle "File"}}{{buildSafeVarName .Path}})
101 | r, err = gzip.NewReader(rb)
102 | if err != nil {
103 | panic(err)
104 | }
105 |
106 | err = r.Close()
107 | if err != nil {
108 | panic(err)
109 | }
110 | {{end}}
111 | {{end}}
112 |
113 | f, err = {{exported "FS"}}.OpenFile({{exported "CTX"}}, "{{.Path}}", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
114 | if err != nil {
115 | panic(err)
116 | }
117 |
118 | {{if $Compression.Compress}}
119 | {{if not $Compression.Keep}}
120 | _, err = io.Copy(f, r)
121 | if err != nil {
122 | panic(err)
123 | }
124 | {{end}}
125 | {{else}}
126 | _, err = f.Write({{exportedTitle "File"}}{{buildSafeVarName .Path}})
127 | if err != nil {
128 | panic(err)
129 | }
130 | {{end}}
131 |
132 | err = f.Close()
133 | if err != nil {
134 | panic(err)
135 | }
136 | {{end}}
137 | {{end}}
138 |
139 | {{exportedTitle "Handler"}} = &webdav.Handler{
140 | FileSystem: FS,
141 | LockSystem: webdav.NewMemLS(),
142 | }
143 |
144 | {{if .Updater.Enabled}}
145 | go func() {
146 | svr := &{{exportedTitle "Server"}}{}
147 | svr.Init()
148 | }()
149 | {{end}}
150 | }
151 |
152 | {{if .Debug}}
153 | var remap = map[string]map[string]string{
154 | {{.Remap}}
155 | }
156 | {{end}}
157 |
158 | // Open a file
159 | func (hfs *{{exported "HTTPFS"}}) Open(path string) (http.File, error) {
160 | path = hfs.Prefix + path
161 |
162 | {{if .Debug}}
163 | path = strings.TrimPrefix(path, "/")
164 |
165 | for current, f := range remap {
166 | if path == current {
167 | path = f["base"] + strings.TrimPrefix(path, f["prefix"])
168 | break
169 | }
170 | }
171 |
172 | {{end}}
173 | f, err := {{if .Debug}}os{{else}}{{exported "FS"}}{{end}}.OpenFile({{if not .Debug}}{{exported "CTX"}}, {{end}}path, os.O_RDONLY, 0644)
174 | if err != nil {
175 | return nil, err
176 | }
177 |
178 | return f, nil
179 | }
180 |
181 | // ReadFile is adapTed from ioutil
182 | func {{exportedTitle "ReadFile"}}(path string) ([]byte, error) {
183 | f, err := {{if .Debug}}os{{else}}{{exported "FS"}}{{end}}.OpenFile({{if not .Debug}}{{exported "CTX"}}, {{end}}path, os.O_RDONLY, 0644)
184 | if err != nil {
185 | return nil, err
186 | }
187 |
188 | buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead))
189 |
190 | // If the buffer overflows, we will get bytes.ErrTooLarge.
191 | // Return that as an error. Any other panic remains.
192 | defer func() {
193 | e := recover()
194 | if e == nil {
195 | return
196 | }
197 | if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge {
198 | err = panicErr
199 | } else {
200 | panic(e)
201 | }
202 | }()
203 | _, err = buf.ReadFrom(f)
204 | return buf.Bytes(), err
205 | }
206 |
207 | // WriteFile is adapTed from ioutil
208 | func {{exportedTitle "WriteFile"}}(filename string, data []byte, perm os.FileMode) error {
209 | f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
210 | if err != nil {
211 | return err
212 | }
213 | n, err := f.Write(data)
214 | if err == nil && n < len(data) {
215 | err = io.ErrShortWrite
216 | }
217 | if err1 := f.Close(); err == nil {
218 | err = err1
219 | }
220 | return err
221 | }
222 |
223 | // WalkDirs looks for files in the given dir and returns a list of files in it
224 | // usage for all files in the b0x: WalkDirs("", false)
225 | func {{exportedTitle "WalkDirs"}}(name string, includeDirsInList bool, files ...string) ([]string, error) {
226 | f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, name, os.O_RDONLY, 0)
227 | if err != nil {
228 | return nil, err
229 | }
230 |
231 | fileInfos, err := f.Readdir(0)
232 | if err != nil {
233 | return nil, err
234 | }
235 |
236 | err = f.Close()
237 | if err != nil {
238 | return nil, err
239 | }
240 |
241 | for _, info := range fileInfos {
242 | filename := path.Join(name, info.Name())
243 |
244 | if includeDirsInList || !info.IsDir() {
245 | files = append(files, filename)
246 | }
247 |
248 | if info.IsDir() {
249 | files, err = {{exportedTitle "WalkDirs"}}(filename, includeDirsInList, files...)
250 | if err != nil {
251 | return nil, err
252 | }
253 | }
254 | }
255 |
256 | return files, nil
257 | }
258 |
259 | {{if .Updater.Enabled}}
260 | // Auth holds information for a http basic auth
261 | type {{exportedTitle "Auth"}} struct {
262 | Username string
263 | Password string
264 | }
265 |
266 | // ResponseInit holds a list of hashes from the server
267 | // to be sent to the client so it can check if there
268 | // is a new file or a changed file
269 | type {{exportedTitle "ResponseInit"}} struct {
270 | Success bool
271 | Hashes map[string]string
272 | }
273 |
274 | // Server holds information about the http server
275 | // used to update files remotely
276 | type {{exportedTitle "Server"}} struct {
277 | Auth {{exportedTitle "Auth"}}
278 | Files []string
279 | }
280 |
281 | // Init sets the routes and basic http auth
282 | // before starting the http server
283 | func (s *{{exportedTitle "Server"}}) Init() {
284 | s.Auth = {{exportedTitle "Auth"}}{
285 | Username: "{{.Updater.Username}}",
286 | Password: "{{.Updater.Password}}",
287 | }
288 |
289 | e := echo.New()
290 | e.Use(middleware.Recover())
291 | e.Use(s.BasicAuth())
292 | e.POST("/", s.Post)
293 | e.GET("/", s.Get)
294 |
295 | log.Println("fileb0x updater server is running at port 0.0.0.0:{{.Updater.Port}}")
296 | if err := e.Start(":{{.Updater.Port}}"); err != nil {
297 | panic(err)
298 | }
299 | }
300 |
301 | // Get gives a list of file names and hashes
302 | func (s *{{exportedTitle "Server"}}) Get(c echo.Context) error {
303 | log.Println("[fileb0x.Server]: Hashing server files...")
304 |
305 | // file:hash
306 | hashes := map[string]string{}
307 |
308 | // get all files in the virtual memory file system
309 | var err error
310 | s.Files, err = {{exportedTitle "WalkDirs"}}("", false)
311 | if err != nil {
312 | return err
313 | }
314 |
315 | // get a hash for each file
316 | for _, filePath := range s.Files {
317 | f, err := FS.OpenFile(CTX, filePath, os.O_RDONLY, 0644)
318 | if err != nil {
319 | return err
320 | }
321 |
322 | hash := sha256.New()
323 | _, err = io.Copy(hash, f)
324 | if err != nil {
325 | return err
326 | }
327 |
328 | hashes[filePath] = hex.EncodeToString(hash.Sum(nil))
329 | }
330 |
331 | log.Println("[fileb0x.Server]: Done hashing files")
332 | return c.JSON(http.StatusOK, &ResponseInit{
333 | Success: true,
334 | Hashes: hashes,
335 | })
336 | }
337 |
338 | // Post is used to upload a file and replace
339 | // it in the virtual memory file system
340 | func (s *{{exportedTitle "Server"}}) Post(c echo.Context) error {
341 | file, err := c.FormFile("file")
342 | if err != nil {
343 | return err
344 | }
345 |
346 | log.Println("[fileb0x.Server]:", file.Filename, "Found request to upload a file")
347 |
348 | src, err := file.Open()
349 | if err != nil {
350 | return err
351 | }
352 | defer src.Close()
353 |
354 |
355 | newDir := filepath.Dir(file.Filename)
356 | _, err = {{exported "FS"}}.Stat({{exported "CTX"}}, newDir)
357 | if err != nil && strings.HasSuffix(err.Error(), os.ErrNotExist.Error()) {
358 | log.Println("[fileb0x.Server]: Creating dir tree", newDir)
359 | list := strings.Split(newDir, "/")
360 | var tree string
361 |
362 | for _, dir := range list {
363 | if dir == "" || dir == "." || dir == "/" || dir == "./" {
364 | continue
365 | }
366 |
367 | tree += dir + "/"
368 | err = {{exported "FS"}}.Mkdir({{exported "CTX"}}, tree, 0777)
369 | if err != nil && err != os.ErrExist {
370 | log.Println("failed", err)
371 | return err
372 | }
373 | }
374 | }
375 |
376 | log.Println("[fileb0x.Server]:", file.Filename, "Opening file...")
377 | f, err := {{exported "FS"}}.OpenFile({{exported "CTX"}}, file.Filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
378 | if err != nil && !strings.HasSuffix(err.Error(), os.ErrNotExist.Error()) {
379 | return err
380 | }
381 |
382 | log.Println("[fileb0x.Server]:", file.Filename, "Writing file into Virutal Memory FileSystem...")
383 | if _, err = io.Copy(f, src); err != nil {
384 | return err
385 | }
386 |
387 | if err = f.Close(); err != nil {
388 | return err
389 | }
390 |
391 | log.Println("[fileb0x.Server]:", file.Filename, "Done writing file")
392 | return c.String(http.StatusOK, "ok")
393 | }
394 |
395 | // BasicAuth is a middleware to check if
396 | // the username and password are valid
397 | // echo's middleware isn't used because of golint issues
398 | func (s *{{exportedTitle "Server"}}) BasicAuth() echo.MiddlewareFunc {
399 | return func(next echo.HandlerFunc) echo.HandlerFunc {
400 | return func(c echo.Context) error {
401 | u, p, _ := c.Request().BasicAuth()
402 | if u != s.Auth.Username || p != s.Auth.Password {
403 | return echo.ErrUnauthorized
404 | }
405 |
406 | return next(c)
407 | }
408 | }
409 | }
410 | {{end}}
411 | `
412 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | fileb0x [](https://circleci.com/gh/UnnoTed/fileb0x) [](https://godoc.org/github.com/UnnoTed/fileb0x) [](https://goreportcard.com/report/unnoted/fileb0x)
2 | -------
3 |
4 | ### What is fileb0x?
5 | A better customizable tool to embed files in go.
6 |
7 | It is an alternative to `go-bindata` that have better features and organized configuration.
8 |
9 | ###### TL;DR
10 | a better `go-bindata`
11 |
12 | -------
13 | ### How does it compare to `go-bindata`?
14 | Feature | fileb0x | go-bindata
15 | --------------------- | ------------- | ------------------
16 | gofmt | yes (optional) | no
17 | golint | safe | unsafe
18 | gzip compression | yes | yes
19 | gzip decompression | yes (optional: runtime) | yes (on read)
20 | gzip compression levels | yes | no
21 | separated prefix / base for each file | yes | no (all files only)
22 | different build tags for each file | yes | no
23 | exclude / ignore files | yes (glob) | yes (regex)
24 | spread files | yes | no (single file only)
25 | unexported vars/funcs | yes (optional) | no
26 | virtual memory file system | yes | no
27 | http file system / handler | yes | no
28 | replace text in files | yes | no
29 | glob support | yes | no (walk folders only)
30 | regex support | no | yes (ignore files only)
31 | config file | yes (config file only) | no (cmd args only)
32 | update files remotely | yes | no
33 |
34 | -------
35 | ### What are the benefits of using a Virtual Memory File System?
36 | By using a virtual memory file system you can have access to files like when they're stored in a hard drive instead of a `map[string][]byte` you would be able to use IO writer and reader.
37 | This means you can `read`, `write`, `remove`, `stat` and `rename` files also `make`, `remove` and `stat` directories.
38 |
39 | ###### TL;DR
40 | Virtual Memory File System has similar functions as a hdd stored files would have.
41 |
42 |
43 |
44 | ### Features
45 |
46 | - [x] golint safe code output
47 |
48 | - [x] optional: gzip compression (with optional run-time decompression)
49 |
50 | - [x] optional: formatted code (gofmt)
51 |
52 | - [x] optional: spread files
53 |
54 | - [x] optional: unexporTed variables, functions and types
55 |
56 | - [x] optional: include multiple files and folders
57 |
58 | - [x] optional: exclude files or/and folders
59 |
60 | - [x] optional: replace text in files
61 |
62 | - [x] optional: custom base and prefix path
63 |
64 | - [x] Virtual Memory FileSystem - [webdav](https://godoc.org/golang.org/x/net/webdav)
65 |
66 | - [x] HTTP FileSystem and Handler
67 |
68 | - [x] glob support - [doublestar](https://github.com/bmatcuk/doublestar)
69 |
70 | - [x] json / yaml / toml support
71 |
72 | - [x] optional: Update files remotely
73 |
74 | - [x] optional: Build tags for each file
75 |
76 |
77 | ### License
78 | MIT
79 |
80 |
81 | ### Get Started
82 |
83 | ###### TL;DR QuickStart™
84 |
85 | Here's the get-you-going in 30 seconds or less:
86 |
87 | ```bash
88 | git clone https://github.com/UnnoTed/fileb0x.git
89 | cd fileb0x
90 | cd _example/simple
91 | go generate
92 | go build
93 | ./simple
94 | ```
95 |
96 | * `mod.go` defines the package as `example.com/foo/simple`
97 | * `b0x.yaml` defines the sub-package `static` from the folder `public`
98 | * `main.go` includes the comment `//go:generate go run github.com/UnnoTed/fileb0x b0x.yaml`
99 | * `main.go` also includes the import `example.com/foo/simple/static`
100 | * `go generate` locally installs `fileb0x` which generates `./static` according to `bax.yaml`
101 | * `go build` creates the binary `simple` from `package main` in the current folder
102 | * `./simple` runs the self-contained standalone webserver with built-in files from `public`
103 |
104 |
105 |
106 | How to use it?
107 |
108 | ##### 1. Download
109 |
110 | ```bash
111 | go get -u github.com/UnnoTed/fileb0x
112 | ```
113 |
114 | ##### 2. Create a config file
115 | First you need to create a config file, it can be `*.json`, `*.yaml` or `*.toml`. (`*` means any file name)
116 |
117 | Now write into the file the configuration you wish, you can use the example files as a start.
118 |
119 | json config file example [b0x.json](https://raw.githubusercontent.com/UnnoTed/fileb0x/master/_example/simple/b0x.json)
120 |
121 | yaml config file example [b0x.yaml](https://github.com/UnnoTed/fileb0x/blob/master/_example/simple/b0x.yaml)
122 |
123 | toml config file example [b0x.toml](https://github.com/UnnoTed/fileb0x/blob/master/_example/simple/b0x.toml)
124 |
125 | ##### 3. Run
126 | if you prefer to use it from the `cmd or terminal` edit and run the command below.
127 |
128 | ```bash
129 | fileb0x YOUR_CONFIG_FILE.yaml
130 | ```
131 |
132 | or if you wish to generate the embedded files through `go generate` just add and edit the line below into your `main.go`.
133 | ```go
134 | //go:generate fileb0x YOUR_CONFIG_FILE.yaml
135 | ```
136 |
137 |
138 |
139 |
140 | What functions and variables fileb0x let me access and what are they for?
141 |
142 | #### HTTP
143 | ```go
144 | var HTTP http.FileSystem
145 | ```
146 |
147 | ##### Type
148 | [`http.FileSystem`](https://golang.org/pkg/net/http/#FileSystem)
149 |
150 | ##### What is it?
151 |
152 | A In-Memory HTTP File System.
153 |
154 | ##### What it does?
155 |
156 | Serve files through a HTTP FileServer.
157 |
158 | ##### How to use it?
159 | ```go
160 | // http.ListenAndServe will create a server at the port 8080
161 | // it will take http.FileServer() as a param
162 | //
163 | // http.FileServer() will use HTTP as a file system so all your files
164 | // can be avialable through the port 8080
165 | http.ListenAndServe(":8080", http.FileServer(myEmbeddedFiles.HTTP))
166 | ```
167 |
168 |
169 | How to use it with `echo`?
170 |
171 | ```go
172 | package main
173 |
174 | import (
175 | "github.com/labstack/echo"
176 | "github.com/labstack/echo/engine/standard"
177 | // your embedded files import here ...
178 | "github.com/UnnoTed/fileb0x/_example/echo/myEmbeddedFiles"
179 | )
180 |
181 | func main() {
182 | e := echo.New()
183 |
184 | // enable any filename to be loaded from in-memory file system
185 | e.GET("/*", echo.WrapHandler(myEmbeddedFiles.Handler))
186 |
187 | // http://localhost:1337/public/README.md
188 | e.Start(":1337")
189 | }
190 | ```
191 |
192 | ##### How to serve a single file through `echo`?
193 | ```go
194 | package main
195 |
196 | import (
197 | "github.com/labstack/echo"
198 |
199 | // your embedded files import here ...
200 | "github.com/UnnoTed/fileb0x/_example/echo/myEmbeddedFiles"
201 | )
202 |
203 | func main() {
204 | e := echo.New()
205 |
206 | // read ufo.html from in-memory file system
207 | htmlb, err := myEmbeddedFiles.ReadFile("ufo.html")
208 | if err != nil {
209 | log.Fatal(err)
210 | }
211 |
212 | // convert to string
213 | html := string(htmlb)
214 |
215 | // serve ufo.html through "/"
216 | e.GET("/", func(c echo.Context) error {
217 |
218 | // serve as html
219 | return c.HTML(http.StatusOK, html)
220 | })
221 |
222 | e.Start(":1337")
223 | }
224 | ```
225 |
226 |
227 |
228 |
229 | Examples
230 |
231 | [simple example](https://github.com/UnnoTed/fileb0x/tree/master/_example/simple) -
232 | [main.go](https://github.com/UnnoTed/fileb0x/blob/master/_example/simple/main.go)
233 |
234 | [echo example](https://github.com/UnnoTed/fileb0x/tree/master/_example/echo) -
235 | [main.go](https://github.com/UnnoTed/fileb0x/blob/master/_example/echo/main.go)
236 |
237 | ```go
238 | package main
239 |
240 | import (
241 | "log"
242 | "net/http"
243 |
244 | // your generaTed package
245 | "github.com/UnnoTed/fileb0x/_example/simple/static"
246 | )
247 |
248 | func main() {
249 | files, err := static.WalkDirs("", false)
250 | if err != nil {
251 | log.Fatal(err)
252 | }
253 |
254 | log.Println("ALL FILES", files)
255 |
256 | // here we'll read the file from the virtual file system
257 | b, err := static.ReadFile("public/README.md")
258 | if err != nil {
259 | log.Fatal(err)
260 | }
261 |
262 | // byte to str
263 | s := string(b)
264 | s += "#hello"
265 |
266 | // write file back into the virtual file system
267 | err := static.WriteFile("public/README.md", []byte(s), 0644)
268 | if err != nil {
269 | log.Fatal(err)
270 | }
271 |
272 |
273 | log.Println(string(b))
274 |
275 | // true = handler
276 | // false = file system
277 | as := false
278 |
279 | // try it -> http://localhost:1337/public/secrets.txt
280 | if as {
281 | // as Handler
282 | panic(http.ListenAndServe(":1337", static.Handler))
283 | } else {
284 | // as File System
285 | panic(http.ListenAndServe(":1337", http.FileServer(static.HTTP)))
286 | }
287 | }
288 | ```
289 |
290 |
291 |
292 | Update files remotely
293 |
294 | Having to upload an entire binary just to update some files in a b0x and restart a server isn't something that i like to do...
295 |
296 | ##### How it works?
297 | By enabling the updater option, the next time that you generate a b0x, it will include a http server, this http server will use a http basic auth and it contains 1 endpoint `/` that accepts 2 methods: `GET, POST`.
298 |
299 | The `GET` method responds with a list of file names and sha256 hash of each file.
300 | The `POST` method is used to upload files, it creates the directory tree of a new file and then creates the file or it updates an existing file from the virtual memory file system... it responds with a `ok` string when the upload is successful.
301 |
302 | ##### How to update files remotely?
303 |
304 | 1. First enable the updater option in your config file:
305 | ```yaml
306 | ##################
307 | ## yaml example ##
308 | ##################
309 |
310 | # updater allows you to update a b0x in a running server
311 | # without having to restart it
312 | updater:
313 | # disabled by default
314 | enabled: false
315 |
316 | # empty mode creates a empty b0x file with just the
317 | # server and the filesystem, then you'll have to upload
318 | # the files later using the cmd:
319 | # fileb0x -update=http://server.com:port b0x.yaml
320 | #
321 | # it avoids long compile time
322 | empty: false
323 |
324 | # amount of uploads at the same time
325 | workers: 3
326 |
327 | # to get a username and password from a env variable
328 | # leave username and password blank (username: "")
329 | # then set your username and password in the env vars
330 | # (no caps) -> fileb0x_username and fileb0x_password
331 | #
332 | # when using env vars, set it before generating a b0x
333 | # so it can be applied to the updater server.
334 | username: "user" # username: ""
335 | password: "pass" # password: ""
336 | port: 8041
337 | ```
338 | 2. Generate a b0x with the updater option enabled, don't forget to set the username and password for authentication.
339 | 3. When your files update, just run `fileb0x -update=http://yourServer.com:8041 b0x.toml` to update the files in the running server.
340 |
341 |
342 |
343 | Build Tags
344 |
345 | To use build tags for a b0x package just add the tags to the `tags` property in the main object of your config file
346 | ```yaml
347 | # default: main
348 | pkg: static
349 |
350 | # destination
351 | dest: "./static/"
352 |
353 | # build tags for the main b0x.go file
354 | tags: "!linux"
355 | ```
356 |
357 | You can also have different build tags for a list of files, you must enable the `spread` property in the main object of your config file, then at the `custom` list, choose the set of files which you want a different build tag
358 | ```yaml
359 | # default: main
360 | pkg: static
361 |
362 | # destination
363 | dest: "./static/"
364 |
365 | # build tags for the main b0x.go file
366 | tags: "windows darwin"
367 |
368 | # [spread] means it will make a file to hold all fileb0x data
369 | # and each file into a separaTed .go file
370 | #
371 | # example:
372 | # theres 2 files in the folder assets, they're: hello.json and world.txt
373 | # when spread is activaTed, fileb0x will make a file:
374 | # b0x.go or [output]'s data, assets_hello.json.go and assets_world.txt.go
375 | #
376 | #
377 | # type: bool
378 | # default: false
379 | spread: true
380 |
381 | # type: array of objects
382 | custom:
383 | # type: array of strings
384 | - files:
385 | - "start_space_ship.exe"
386 |
387 | # build tags for this set of files
388 | # it will only work if spread mode is enabled
389 | tags: "windows"
390 |
391 | # type: array of strings
392 | - files:
393 | - "ufo.dmg"
394 |
395 | # build tags for this set of files
396 | # it will only work if spread mode is enabled
397 | tags: "darwin"
398 | ```
399 |
400 | the config above will make:
401 | ```yaml
402 | ab0x.go # // +build windows darwin
403 |
404 | b0xfile_ufo.exe.go # // +build windows
405 | b0xfile_start_space_ship.bat.go # // +build darwin
406 | ```
407 |
408 |
409 | ### Functions and Variables
410 |
411 |
412 | FS (File System)
413 |
414 | ```go
415 | var FS webdav.FileSystem
416 | ```
417 |
418 | ##### Type
419 | [`webdav.FileSystem`](https://godoc.org/golang.org/x/net/webdav#FileSystem)
420 |
421 | ##### What is it?
422 |
423 | In-Memory File System.
424 |
425 | ##### What it does?
426 |
427 | Lets you `read, write, remove, stat and rename` files and `make, remove and stat` directories...
428 |
429 | ##### How to use it?
430 | ```go
431 | func main() {
432 |
433 | // you have the following functions available
434 | // they all control files/dirs from/to the in-memory file system!
435 | func Mkdir(name string, perm os.FileMode) error
436 | func OpenFile(name string, flag int, perm os.FileMode) (File, error)
437 | func RemoveAll(name string) error
438 | func Rename(oldName, newName string) error
439 | func Stat(name string) (os.FileInfo, error)
440 | // you should remove those lines ^
441 |
442 | // 1. creates a directory
443 | err := myEmbeddedFiles.FS.Mkdir(myEmbeddedFiles.CTX, "assets", 0777)
444 | if err != nil {
445 | log.Fatal(err)
446 | }
447 |
448 | // 2. creates a file into the directory we created before and opens it
449 | // with fileb0x you can use ReadFile and WriteFile instead of this complicaTed thing
450 | f, err := myEmbeddedFiles.FS.OpenFile(myEmbeddedFiles.CTX, "assets/memes.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
451 | if err != nil {
452 | log.Fatal(err)
453 | }
454 |
455 | data := []byte("I are programmer I make computer beep boop beep beep boop")
456 |
457 | // write the data into the file
458 | n, err := f.Write(data)
459 | if err == nil && n < len(data) {
460 | err = io.ErrShortWrite
461 | }
462 |
463 | // close the file
464 | if err1 := f.Close(); err == nil {
465 | log.Fatal(err1)
466 | }
467 |
468 | // 3. rename a file
469 | // can also move files
470 | err = myEmbeddedFiles.FS.Rename(myEmbeddedFiles.CTX, "assets/memes.txt", "assets/programmer_memes.txt")
471 | if err != nil {
472 | log.Fatal(err)
473 | }
474 |
475 | // 4. checks if the file we renamed exists
476 | if _, err = myEmbeddedFiles.FS.Stat(myEmbeddedFiles.CTX, "assets/programmer_memes.txt"); os.IsExist(err) {
477 | // exists!
478 |
479 | // tries to remove the /assets/ directory
480 | // from the in-memory file system
481 | err = myEmbeddedFiles.FS.RemoveAll(myEmbeddedFiles.CTX, "assets")
482 | if err != nil {
483 | log.Fatal(err)
484 | }
485 | }
486 |
487 | // 5. checks if the dir we removed exists
488 | if _, err = myEmbeddedFiles.FS.Stat(myEmbeddedFiles.CTX, "public/"); os.IsNotExist(err) {
489 | // doesn't exists!
490 | log.Println("works!")
491 | }
492 | }
493 | ```
494 |
495 |
496 | Handler
497 |
498 | ```go
499 | var Handler *webdav.Handler
500 | ```
501 |
502 | ##### Type
503 | [`webdav.Handler`](https://godoc.org/golang.org/x/net/webdav#Handler)
504 |
505 | ##### What is it?
506 |
507 | A HTTP Handler implementation.
508 |
509 | ##### What it does?
510 |
511 | Serve your embedded files.
512 |
513 | ##### How to use it?
514 | ```go
515 | // ListenAndServer will create a http server at port 8080
516 | // and use Handler as a http handler to serve your embedded files
517 | http.ListenAndServe(":8080", myEmbeddedFiles.Handler)
518 | ```
519 |
520 |
521 |
522 | ReadFile
523 |
524 | ```go
525 | func ReadFile(filename string) ([]byte, error)
526 | ```
527 |
528 | ##### Type
529 | [`ioutil.ReadFile`](https://godoc.org/io/ioutil#ReadFile)
530 |
531 | ##### What is it?
532 |
533 | A Helper function to read your embedded files.
534 |
535 | ##### What it does?
536 |
537 | Reads the specified file from the in-memory file system and return it as a byte slice.
538 |
539 | ##### How to use it?
540 | ```go
541 | // it works the same way that ioutil.ReadFile does.
542 | // but it will read the file from the in-memory file system
543 | // instead of the hard disk!
544 | //
545 | // the file name is passwords.txt
546 | // topSecretFile is a byte slice ([]byte)
547 | topSecretFile, err := myEmbeddedFiles.ReadFile("passwords.txt")
548 | if err != nil {
549 | log.Fatal(err)
550 | }
551 |
552 | log.Println(string(topSecretFile))
553 | ```
554 |
555 |
556 |
557 | WriteFile
558 |
559 | ```go
560 | func WriteFile(filename string, data []byte, perm os.FileMode) error
561 | ```
562 |
563 | ##### Type
564 | [`ioutil.WriteFile`](https://godoc.org/io/ioutil#WriteFile)
565 |
566 | ##### What is it?
567 |
568 | A Helper function to write a file into the in-memory file system.
569 |
570 | ##### What it does?
571 |
572 | Writes the `data` into the specified `filename` in the in-memory file system, meaning you embedded a file!
573 |
574 | -- IMPORTANT --
575 | IT WON'T WRITE THE FILE INTO THE .GO GENERATED FILE, IT WILL BE TEMPORARY, WHILE YOUR APP IS RUNNING THE FILE WILL BE AVAILABLE,
576 | AFTER IT SHUTDOWN, IT IS GONE.
577 |
578 | ##### How to use it?
579 | ```go
580 | // it works the same way that ioutil.WriteFile does.
581 | // but it will write the file into the in-memory file system
582 | // instead of the hard disk!
583 | //
584 | // the file name is secret.txt
585 | // data should be a byte slice ([]byte)
586 | // 0644 is a unix file permission
587 |
588 | data := []byte("jet fuel can't melt steel beams")
589 | err := myEmbeddedFiles.WriteFile("secret.txt", data, 0644)
590 | if err != nil {
591 | log.Fatal(err)
592 | }
593 | ```
594 |
595 |
596 |
597 | WalkDirs
598 |
599 | ```go
600 | func WalkDirs(name string, includeDirsInList bool, files ...string) ([]string, error) {
601 | ```
602 |
603 | ##### Type
604 | `[]string`
605 |
606 | ##### What is it?
607 |
608 | A Helper function to walk dirs from the in-memory file system.
609 |
610 | ##### What it does?
611 |
612 | Returns a list of files (with option to include dirs) that are currently in the in-memory file system.
613 |
614 | ##### How to use it?
615 | ```go
616 | includeDirsInTheList := false
617 |
618 | // WalkDirs returns a string slice with all file paths
619 | files, err := myEmbeddedFiles.WalkDirs("", includeDirsInTheList)
620 | if err != nil {
621 | log.Fatal(err)
622 | }
623 |
624 | log.Println("List of all my files", files)
625 | ```
626 |
627 |
628 |
--------------------------------------------------------------------------------