├── 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 [![Circle CI](https://circleci.com/gh/UnnoTed/fileb0x.svg?style=svg)](https://circleci.com/gh/UnnoTed/fileb0x) [![GoDoc](https://godoc.org/github.com/UnnoTed/fileb0x?status.svg)](https://godoc.org/github.com/UnnoTed/fileb0x) [![GoReportCard](https://goreportcard.com/badge/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 | --------------------------------------------------------------------------------