├── LICENSE ├── README.md ├── assets.go ├── example_test.go ├── file.go ├── filesystem.go └── generate.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Jesse van den Kieboom. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following disclaimer 10 | in the documentation and/or other materials provided with the 11 | distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived from 14 | this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 20 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-assets 2 | go-assets is a simple embedding asset generator and consumer library for go. 3 | The main use of the library is to generate and embed small in-memory file 4 | systems ready to be integrated in webservers or other services which have 5 | a small amount of assets used at runtime. This is great for being able to do 6 | single binary deployments with assets. 7 | 8 | The [Generator](http://godoc.org/github.com/jessevdk/go-assets#Generator) type 9 | can be used to generate a go file containing an in-memory 10 | file tree from files and directories on disk. The file data can be optionally 11 | compressed using gzip to reduce file size. Afterwards, the generated file 12 | can be included into your application and the assets can be directly accessed 13 | without having to load them from disk. The generated assets variable is of 14 | type [FileSystem](http://godoc.org/github.com/jessevdk/go-assets#FileSystem) and 15 | implements the os.FileInfo and http.FileSystem interfaces so that they can be 16 | directly used with http.FileHandler. 17 | 18 | See also [go-assets-builder](https://github.com/jessevdk/go-assets-builder) for 19 | a simple builder program using go-assets and exposing the generator as a command 20 | line application. 21 | 22 | See for more information. 23 | -------------------------------------------------------------------------------- /assets.go: -------------------------------------------------------------------------------- 1 | // go-assets is a simple embedding asset generator and consumer library for go. 2 | // The main use of the library is to generate and embed small in-memory file 3 | // systems ready to be integrated in webservers or other services which have 4 | // a small amount of assets used at runtime. This is great for being able to do 5 | // single binary deployments with assets. 6 | // 7 | // The Generator type can be used to generate a go file containing an in-memory 8 | // file tree from files and directories on disk. The file data can be optionally 9 | // compressed using gzip to reduce file size. Afterwards, the generated file 10 | // can be included into your application and the assets can be directly accessed 11 | // without having to load them from disk. The generated assets variable is of 12 | // type FileSystem and implements the os.FileInfo and http.FileSystem interfaces 13 | // so that they can be directly used with http.FileHandler. 14 | package assets 15 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | func ExampleGenerator() { 8 | g := Generator{} 9 | 10 | if err := g.Add("."); err != nil { 11 | panic(err) 12 | } 13 | 14 | // This will write a go file to standard out. The generated go file 15 | // will reside in the g.PackageName package and will contain a 16 | // single variable g.VariableName of type assets.FileSystem containing 17 | // the whole file system. 18 | g.Write(os.Stdout) 19 | } 20 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "path" 7 | "time" 8 | ) 9 | 10 | // An asset file. 11 | type File struct { 12 | // The full asset file path 13 | Path string 14 | 15 | // The asset file mode 16 | FileMode os.FileMode 17 | 18 | // The asset modification time 19 | Mtime time.Time 20 | 21 | // The asset data. Note that this data might be in gzip compressed form. 22 | Data []byte 23 | 24 | fs *FileSystem 25 | buf *bytes.Reader 26 | dirIndex int 27 | } 28 | 29 | // Implementation of os.FileInfo 30 | 31 | func (f *File) Name() string { 32 | return path.Base(f.Path) 33 | } 34 | 35 | func (f *File) Mode() os.FileMode { 36 | return f.FileMode 37 | } 38 | 39 | func (f *File) ModTime() time.Time { 40 | return f.Mtime 41 | } 42 | 43 | func (f *File) IsDir() bool { 44 | return f.FileMode.IsDir() 45 | } 46 | 47 | func (f *File) Size() int64 { 48 | return int64(len(f.Data)) 49 | } 50 | 51 | func (f *File) Sys() interface{} { 52 | return nil 53 | } 54 | 55 | // Implementation of http.File 56 | 57 | func (f *File) Close() error { 58 | f.buf = nil 59 | f.dirIndex = 0 60 | 61 | return nil 62 | } 63 | 64 | func (f *File) Stat() (os.FileInfo, error) { 65 | return f, nil 66 | } 67 | 68 | func (f *File) Readdir(count int) ([]os.FileInfo, error) { 69 | if f.IsDir() { 70 | ret, err := f.fs.readDir(f.Path, f.dirIndex, count) 71 | f.dirIndex += len(ret) 72 | 73 | return ret, err 74 | } else { 75 | return nil, os.ErrInvalid 76 | } 77 | } 78 | 79 | func (f *File) Read(data []byte) (int, error) { 80 | if f.buf == nil { 81 | f.buf = bytes.NewReader(f.Data) 82 | } 83 | 84 | return f.buf.Read(data) 85 | } 86 | 87 | func (f *File) Seek(offset int64, whence int) (int64, error) { 88 | if f.buf == nil { 89 | f.buf = bytes.NewReader(f.Data) 90 | } 91 | 92 | return f.buf.Seek(offset, whence) 93 | } 94 | -------------------------------------------------------------------------------- /filesystem.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "bytes" 5 | "net/http" 6 | "os" 7 | "path" 8 | "time" 9 | ) 10 | 11 | // An in-memory asset file system. The file system implements the 12 | // http.FileSystem interface. 13 | type FileSystem struct { 14 | // A map of directory paths to the files in those directories. 15 | Dirs map[string][]string 16 | 17 | // A map of file/directory paths to assets.File types. 18 | Files map[string]*File 19 | 20 | // Override loading assets from local path. Useful for development. 21 | LocalPath string 22 | } 23 | 24 | func NewFileSystem(dirs map[string][]string, files map[string]*File, localPath string) *FileSystem { 25 | fs := &FileSystem{ 26 | Dirs: dirs, 27 | Files: files, 28 | LocalPath: localPath, 29 | } 30 | 31 | for _, f := range fs.Files { 32 | f.fs = fs 33 | } 34 | 35 | return fs 36 | } 37 | 38 | func (f *FileSystem) NewFile(path string, filemode os.FileMode, mtime time.Time, data []byte) *File { 39 | return &File{ 40 | Path: path, 41 | FileMode: filemode, 42 | Mtime: mtime, 43 | Data: data, 44 | 45 | fs: f, 46 | } 47 | } 48 | 49 | // Implementation of http.FileSystem 50 | func (f *FileSystem) Open(p string) (http.File, error) { 51 | p = path.Clean(p) 52 | 53 | if len(f.LocalPath) != 0 { 54 | return http.Dir(f.LocalPath).Open(p) 55 | } 56 | 57 | if fi, ok := f.Files[p]; ok { 58 | if !fi.IsDir() { 59 | // Make a copy for reading 60 | ret := fi 61 | ret.buf = bytes.NewReader(ret.Data) 62 | 63 | return ret, nil 64 | } 65 | 66 | return fi, nil 67 | } 68 | 69 | return nil, os.ErrNotExist 70 | } 71 | 72 | func (f *FileSystem) readDir(p string, index int, count int) ([]os.FileInfo, error) { 73 | if d, ok := f.Dirs[p]; ok { 74 | maxl := index + count 75 | 76 | if maxl > len(d) { 77 | maxl = len(d) 78 | } 79 | 80 | ret := make([]os.FileInfo, 0, maxl-index) 81 | 82 | for i := index; i < maxl; i++ { 83 | ret = append(ret, f.Files[path.Join(p, d[i])]) 84 | } 85 | 86 | return ret, nil 87 | } 88 | 89 | return nil, os.ErrNotExist 90 | } 91 | -------------------------------------------------------------------------------- /generate.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha1" 6 | "fmt" 7 | "go/format" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "path" 12 | "strings" 13 | ) 14 | 15 | type file struct { 16 | info os.FileInfo 17 | path string 18 | } 19 | 20 | // An asset generator. The generator can be used to generate an asset go file 21 | // with all the assets that were added to the generator embedded into it. 22 | // The generated assets are made available by the specified go variable 23 | // VariableName which is of type assets.FileSystem. 24 | type Generator struct { 25 | // The package name to generate assets in, 26 | PackageName string 27 | 28 | // The variable name containing the asset filesystem (defaults to Assets), 29 | VariableName string 30 | 31 | // Strip the specified prefix from all paths, 32 | StripPrefix string 33 | 34 | fsDirsMap map[string][]string 35 | fsFilesMap map[string]file 36 | } 37 | 38 | func (x *Generator) addPath(parent string, prefix string, info os.FileInfo) error { 39 | p := path.Join(parent, info.Name()) 40 | 41 | f := file{ 42 | info: info, 43 | path: path.Join(prefix, p), 44 | } 45 | 46 | x.fsFilesMap[p] = f 47 | 48 | if info.IsDir() { 49 | f, err := os.Open(f.path) 50 | fi, err := f.Readdir(-1) 51 | f.Close() 52 | if err != nil { 53 | return err 54 | } 55 | 56 | x.fsDirsMap[p] = make([]string, 0, len(fi)) 57 | 58 | for _, f := range fi { 59 | if err := x.addPath(p, prefix, f); err != nil { 60 | return err 61 | } 62 | } 63 | } else { 64 | x.appendFileInDir(parent, info.Name()) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func (x *Generator) appendFileInDir(dir string, file string) { 71 | for _, v := range x.fsDirsMap[dir] { 72 | if v == file { 73 | return 74 | } 75 | } 76 | 77 | x.fsDirsMap[dir] = append(x.fsDirsMap[dir], file) 78 | } 79 | 80 | func (x *Generator) addParents(p string, prefix string) error { 81 | dname, fname := path.Split(p) 82 | 83 | if len(dname) == 0 { 84 | return nil 85 | } 86 | 87 | wosep := dname[0 : len(dname)-1] 88 | 89 | if err := x.addParents(wosep, prefix); err != nil { 90 | return err 91 | } 92 | 93 | if len(wosep) == 0 { 94 | wosep = "/" 95 | } 96 | 97 | x.appendFileInDir(wosep, fname) 98 | 99 | if _, ok := x.fsFilesMap[wosep]; !ok { 100 | pp := path.Join(prefix, wosep) 101 | s, err := os.Stat(pp) 102 | 103 | if err != nil { 104 | return err 105 | } 106 | 107 | x.fsFilesMap[wosep] = file{ 108 | info: s, 109 | path: pp, 110 | } 111 | } 112 | 113 | return nil 114 | } 115 | 116 | func (x *Generator) splitRelPrefix(p string) (string, string) { 117 | i := 0 118 | relp := "../" 119 | 120 | for strings.HasPrefix(p[i:], relp) { 121 | i += len(relp) 122 | } 123 | 124 | return path.Join(p[0:i], "."), path.Join("/", p[i:]) 125 | } 126 | 127 | // Add a file or directory asset to the generator. Added directories will be 128 | // recursed automatically. 129 | func (x *Generator) Add(p string) error { 130 | if x.fsFilesMap == nil { 131 | x.fsFilesMap = make(map[string]file) 132 | } 133 | 134 | if x.fsDirsMap == nil { 135 | x.fsDirsMap = make(map[string][]string) 136 | } 137 | 138 | p = path.Clean(p) 139 | 140 | info, err := os.Stat(p) 141 | 142 | if err != nil { 143 | return err 144 | } 145 | 146 | prefix, p := x.splitRelPrefix(p) 147 | 148 | if err := x.addParents(p, prefix); err != nil { 149 | return err 150 | } 151 | 152 | return x.addPath(path.Dir(p), prefix, info) 153 | } 154 | 155 | func (x *Generator) stripPrefix(p string) (string, bool) { 156 | if len(x.StripPrefix) == 0 { 157 | return p, true 158 | } 159 | 160 | if strings.HasPrefix(p, x.StripPrefix) { 161 | return p[len(x.StripPrefix):], true 162 | } else { 163 | return p, false 164 | } 165 | } 166 | 167 | // Write the asset tree specified in the generator to the given writer. The 168 | // written asset tree is a valid, standalone go file with the assets 169 | // embedded into it. 170 | func (x *Generator) Write(wr io.Writer) error { 171 | p := x.PackageName 172 | 173 | if len(p) == 0 { 174 | p = "main" 175 | } 176 | 177 | variableName := x.VariableName 178 | 179 | if len(variableName) == 0 { 180 | variableName = "Assets" 181 | } 182 | 183 | writer := &bytes.Buffer{} 184 | 185 | // Write package and import 186 | fmt.Fprintf(writer, "package %s\n\n", p) 187 | fmt.Fprintln(writer, "import (") 188 | fmt.Fprintln(writer, "\t\"time\"") 189 | fmt.Fprintln(writer) 190 | fmt.Fprintln(writer, "\t\"github.com/jessevdk/go-assets\"") 191 | fmt.Fprintln(writer, ")") 192 | fmt.Fprintln(writer) 193 | 194 | vnames := make(map[string]string) 195 | 196 | // Write file contents as const strings 197 | if x.fsFilesMap != nil { 198 | // Create mapping from full file path to asset variable name. 199 | // This also reads the file and writes the contents as a const 200 | // string 201 | for k, v := range x.fsFilesMap { 202 | if v.info.IsDir() { 203 | continue 204 | } 205 | 206 | f, err := os.Open(v.path) 207 | 208 | if err != nil { 209 | return err 210 | } 211 | 212 | data, err := ioutil.ReadAll(f) 213 | 214 | f.Close() 215 | 216 | if err != nil { 217 | return err 218 | } 219 | 220 | s := sha1.New() 221 | io.WriteString(s, k) 222 | 223 | vname := fmt.Sprintf("_%s%x", variableName, s.Sum(nil)) 224 | vnames[k] = vname 225 | 226 | fmt.Fprintf(writer, "var %s = %#v\n", vname, string(data)) 227 | } 228 | 229 | fmt.Fprintln(writer) 230 | } 231 | 232 | fmt.Fprintf(writer, "// %s returns go-assets FileSystem\n", variableName) 233 | fmt.Fprintf(writer, "var %s = assets.NewFileSystem(", variableName) 234 | 235 | if x.fsDirsMap == nil { 236 | x.fsDirsMap = make(map[string][]string) 237 | } 238 | 239 | if x.fsFilesMap == nil { 240 | x.fsFilesMap = make(map[string]file) 241 | } 242 | 243 | dirmap := make(map[string][]string) 244 | 245 | for k, v := range x.fsDirsMap { 246 | if kk, ok := x.stripPrefix(k); ok { 247 | if len(kk) == 0 { 248 | kk = "/" 249 | } 250 | 251 | dirmap[kk] = v 252 | } 253 | } 254 | 255 | fmt.Fprintf(writer, "%#v, ", dirmap) 256 | fmt.Fprintf(writer, "map[string]*assets.File{\n") 257 | 258 | // Write files 259 | for k, v := range x.fsFilesMap { 260 | kk, ok := x.stripPrefix(k) 261 | 262 | if !ok { 263 | continue 264 | } 265 | 266 | if len(kk) == 0 { 267 | kk = "/" 268 | } 269 | 270 | mt := v.info.ModTime() 271 | 272 | var dt string 273 | 274 | if !v.info.IsDir() { 275 | dt = "[]byte(" + vnames[k] + ")" 276 | } else { 277 | dt = "nil" 278 | } 279 | 280 | fmt.Fprintf(writer, "\t\t%#v: &assets.File{\n", kk) 281 | fmt.Fprintf(writer, "\t\t\tPath: %#v,\n", kk) 282 | fmt.Fprintf(writer, "\t\t\tFileMode: %#v,\n", v.info.Mode()) 283 | fmt.Fprintf(writer, "\t\t\tMtime: time.Unix(%#v, %#v),\n", mt.Unix(), mt.UnixNano()) 284 | fmt.Fprintf(writer, "\t\t\tData: %s,\n", dt) 285 | fmt.Fprintf(writer, "\t\t},") 286 | } 287 | 288 | fmt.Fprintln(writer, "\t}, \"\")") 289 | 290 | ret, err := format.Source(writer.Bytes()) 291 | 292 | if err != nil { 293 | return err 294 | } 295 | 296 | wr.Write(ret) 297 | return nil 298 | } 299 | --------------------------------------------------------------------------------