├── test ├── bar_baz.txt ├── foo.txt └── sub │ └── foo.txt ├── go.mod ├── Makefile ├── README.md └── main.go /test/bar_baz.txt: -------------------------------------------------------------------------------- 1 | bar baz 2 | -------------------------------------------------------------------------------- /test/foo.txt: -------------------------------------------------------------------------------- 1 | foo 2 | foo 3 | foo 4 | -------------------------------------------------------------------------------- /test/sub/foo.txt: -------------------------------------------------------------------------------- 1 | foo 2 | foo 3 | foo 4 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/256dpi/embed 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | check: 2 | go fmt ./... 3 | go vet ./... 4 | golint ./... 5 | go test ./... 6 | 7 | install: 8 | go install . 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # embed 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/256dpi/embed)](https://goreportcard.com/report/github.com/256dpi/embed) 4 | 5 | **A small tool for embedding files in a Go source file.** 6 | 7 | ## Install 8 | 9 | ``` 10 | go get github.com/256dpi/embed 11 | ``` 12 | 13 | ## Example 14 | 15 | ```go 16 | //go:generate go run github.com/embed -pkg frontend -strings ./assets 17 | ``` 18 | 19 | ## Usage 20 | 21 | ``` 22 | Usage of embed: 23 | -const 24 | Whether to use constants instead of a map. 25 | -export 26 | Whether to export variables. 27 | -name string 28 | The map name. (default "files") 29 | -out string 30 | The output filename. (default "files.go") 31 | -pkg string 32 | The package name. (default "main") 33 | -strings 34 | Whether to use strings instead of byte slices. 35 | -strip 36 | Whether to strip names of path and extension. 37 | ``` 38 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "go/format" 8 | "io/ioutil" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | "text/template" 15 | ) 16 | 17 | const tmpl = ` 18 | // Code generated by github.com/256dpi/embed. DO NOT EDIT. 19 | 20 | package {{.Package}} 21 | 22 | {{if .Const}} 23 | {{- range .Files}} 24 | // {{.Comment}} 25 | const {{.Name}} = {{if $.Strings}}{{.String}}{{else}}[]byte{ {{range .Bytes}}{{.}},{{end}} }{{end}} 26 | {{- end}} 27 | {{else}} 28 | // {{.Name}} holds a collection of static files. 29 | var {{.Name}} = map[string]{{if .Strings}}string{{else}}[]byte{{end}}{ 30 | {{- range .Files}} 31 | "{{.Name}}": {{if $.Strings}}{{.String}}{{else}}{ {{range .Bytes}}{{.}},{{end}} }{{end}}, 32 | {{- end}} 33 | } 34 | {{end}} 35 | ` 36 | 37 | type file struct { 38 | Name string 39 | Comment string 40 | Bytes []byte 41 | String string 42 | } 43 | 44 | type data struct { 45 | Package string 46 | Export bool 47 | Strings bool 48 | Const bool 49 | Name string 50 | Files []file 51 | } 52 | 53 | var output = flag.String("out", "files.go", "The output filename.") 54 | var pkgName = flag.String("pkg", "main", "The package name.") 55 | var export = flag.Bool("export", false, "Whether to export variables.") 56 | var useStrings = flag.Bool("strings", false, "Whether to use strings instead of byte slices.") 57 | var stripName = flag.Bool("strip", false, "Whether to strip names of path and extension.") 58 | var useConst = flag.Bool("const", false, "Whether to use constants instead of a map.") 59 | var mapName = flag.String("name", "files", "The map name.") 60 | 61 | func main() { 62 | // parse flags 63 | flag.Parse() 64 | 65 | // check args 66 | if len(flag.Args()) == 0 { 67 | flag.Usage() 68 | return 69 | } 70 | 71 | // constants imply strings 72 | if *useConst { 73 | *useStrings = true 74 | } 75 | 76 | // titleize map name 77 | if *export { 78 | *mapName = titleize(*mapName) 79 | } 80 | 81 | // get paths 82 | var paths []string 83 | for _, pth := range flag.Args() { 84 | paths = append(paths, strings.TrimSuffix(pth, "/")) 85 | } 86 | 87 | // collect files 88 | var files []file 89 | for _, basePath := range paths { 90 | err := filepath.Walk(basePath, func(pth string, info os.FileInfo, err error) error { 91 | // check error 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | // ignore directories 97 | if info.IsDir() { 98 | return nil 99 | } 100 | 101 | // read file 102 | contents, err := ioutil.ReadFile(pth) 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | // prepare name 108 | name := filepath.ToSlash(pth) 109 | 110 | // remove base path 111 | name = strings.TrimPrefix(name, basePath+"/") 112 | 113 | // strip if requested 114 | if *stripName || *useConst { 115 | name = filepath.Base(name) 116 | name = strings.TrimSuffix(name, path.Ext(name)) 117 | } 118 | 119 | // make uppercase if exported 120 | if *useConst { 121 | name = titleize(name) 122 | } 123 | 124 | // store file 125 | files = append(files, file{ 126 | Name: name, 127 | Comment: fmt.Sprintf("%s holds the contents of %s.", name, pth), 128 | Bytes: contents, 129 | String: strconv.QuoteToASCII(string(contents)), 130 | }) 131 | 132 | return nil 133 | }) 134 | if err != nil { 135 | panic(err) 136 | } 137 | } 138 | 139 | // prepare template 140 | tpl, err := template.New("").Parse(tmpl) 141 | if err != nil { 142 | panic(err) 143 | } 144 | 145 | // prepare input 146 | input := data{ 147 | Package: *pkgName, 148 | Export: *export, 149 | Strings: *useStrings, 150 | Const: *useConst, 151 | Files: files, 152 | Name: *mapName, 153 | } 154 | 155 | // execute template 156 | buf := bytes.Buffer{} 157 | err = tpl.Execute(&buf, input) 158 | if err != nil { 159 | panic(err) 160 | } 161 | 162 | // format output 163 | formatted, err := format.Source(buf.Bytes()) 164 | if err != nil { 165 | panic(err) 166 | } 167 | 168 | // create file 169 | f, err := os.Create(*output) 170 | if err != nil { 171 | panic(err) 172 | } 173 | 174 | // write output 175 | _, err = f.Write(formatted) 176 | if err != nil { 177 | panic(err) 178 | } 179 | 180 | // close file 181 | err = f.Close() 182 | if err != nil { 183 | panic(err) 184 | } 185 | } 186 | 187 | func titleize(input string) string { 188 | // prepare flag 189 | upNext := *export 190 | 191 | // prepare string 192 | var titleized string 193 | 194 | // process all runes 195 | for _, v := range input { 196 | // check if next is up or is already up 197 | if upNext { 198 | titleized += strings.ToUpper(string(v)) 199 | upNext = false 200 | continue 201 | } 202 | 203 | // check if next should be up 204 | if v == '-' || v == '_' || v == ' ' { 205 | upNext = true 206 | continue 207 | } 208 | 209 | // add rune 210 | titleized += string(v) 211 | } 212 | 213 | return titleized 214 | } 215 | --------------------------------------------------------------------------------