├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── cmd.go ├── example ├── cmd.go ├── internal │ └── site │ │ ├── data.s │ │ ├── index.go │ │ ├── index_386.s │ │ ├── index_amd64.s │ │ ├── index_arm.s │ │ ├── index_arm64.s │ │ ├── index_mips64x.s │ │ ├── index_mipsx.s │ │ ├── index_ppc64x.s │ │ ├── index_s390x.s │ │ └── index_test.go ├── site │ ├── 404.html │ ├── css │ │ └── style.css │ ├── images │ │ └── a-nice-picture.jpg │ └── index.html └── source │ ├── Makefile │ ├── archetypes │ └── default.md │ ├── config.toml │ ├── content │ ├── credits-thanks-and-license.md │ ├── getting-started.md │ ├── go-imbed.md │ ├── license.md │ ├── nice-picture.md │ └── other-tools.md │ ├── static │ └── images │ │ └── a-nice-picture.jpg │ └── themes │ └── simple │ ├── archetypes │ └── default.md │ ├── layouts │ ├── 404.html │ └── index.html │ └── static │ └── css │ └── style.css ├── go.mod └── imbed ├── _templates ├── index.go ├── index_386.s ├── index_amd64.s ├── index_arm.s ├── index_arm64.s ├── index_mips64x.s ├── index_mipsx.s ├── index_ppc64x.s ├── index_s390x.s └── index_test.go ├── flags.go ├── imbed.go ├── imbed_test.go ├── internal └── templates │ ├── data.s │ ├── index.go │ ├── index_386.s │ ├── index_amd64.s │ ├── index_arm.s │ ├── index_arm64.s │ ├── index_mips64x.s │ ├── index_mipsx.s │ ├── index_ppc64x.s │ ├── index_s390x.s │ └── index_test.go ├── templates.go └── templates_bootstrap.go /.gitattributes: -------------------------------------------------------------------------------- 1 | imbed/internal/* linguist-generated=true 2 | example/* linguist-documentation 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12.x 5 | - tip 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2018 Alexey Naidyonov `` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-imbed 2 | 3 | [![Build Status](https://travis-ci.org/growler/go-imbed.svg?branch=master)](https://travis-ci.org/growler/go-imbed) 4 | 5 | go-imbed is a simple tool for embedding binary assets into Go executable. 6 | 7 | ## Why 8 | 9 | `go-imbed` came up as a holiday side project for a very simple case of embedding 10 | a REST API documentation into the executable image. There are 11 | [plenty of tools](#other-similar-tools) for embedding binary assets into executable 12 | (which clearly shows demand for something what Go lacks at the moment), but hey, 13 | why not invent another wheel? Besides, stuffing binary asset into Go 14 | source file seemed too unaesthetic to me. 15 | 16 | `go-imbed`: 17 | 18 | - produces go-gettable go and go assembly sources with `go generate`, 19 | - keeps data in read-only section of the binary, 20 | - compress compressible files with `gzip`, 21 | - provides [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc) handler 22 | (unless requested otherwise), 23 | - provides [http.FileSystem](https://golang.org/pkg/net/http/#FileSystem) API 24 | (if requested), 25 | - provides a simple FileSystem abstraction (if requested), 26 | - provides a union FileSystem abstraction with a real file system directory 27 | overlaid embedded (if requested), 28 | - generates a test code as well to keep those having OCD regarding test coverage happy. 29 | 30 | 31 | ## Installation 32 | 33 | ```text 34 | $ go get -u github.com/growler/go-imbed 35 | ``` 36 | 37 | ## Usage 38 | 39 | 1. Install `go-imbed`: 40 | ``` 41 | go get -u github.com/growler/go-imbed 42 | ``` 43 | 2. Add a static content tree to target package: 44 | ``` 45 | src 46 | └── yourpackage 47 | ├── code.go 48 | └── site 49 | ├── static 50 | │ └── style.css 51 | ├── index.html 52 | └── 404.html 53 | ``` 54 | 3. Add a go-generate comment to `code.go` (or any other Go file in `yourpackage`): 55 | ```go 56 | //go:generate go-imbed site internal/site 57 | ``` 58 | 4. Run `go generate yourpackage` 59 | 5. Start using it: 60 | ```go 61 | package main 62 | 63 | import ( 64 | "net/http" 65 | "fmt" 66 | "yourpackage/internal/site" 67 | ) 68 | 69 | func main() { 70 | http.HandleFunc("/", site.ServeHTTP) 71 | if err := http.ListenAndServe(":9091", nil); err != nil{ 72 | fmt.Println(err) 73 | } 74 | } 75 | 76 | ``` 77 | 78 | ## Options 79 | 80 | ```bash 81 | go-imbed [options] 82 | ``` 83 | 84 | ### `-pkg` 85 | 86 | Sets the resulting package name. If not present, the base name (i.e. last item) of the `target-package-path` 87 | will be used. 88 | 89 | ### `-no-compresssion` 90 | 91 | `go-imbed` compresses all the text resources with [gzip](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding#Directives). 92 | Supplied HTTP helper function will decompress resource if HTTP client does not 93 | support compression. `-no-compression` disables compression for all files. 94 | 95 | ### `-no-http-handler` 96 | 97 | `-no-http-handler` disables generation of [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc) 98 | API. 99 | 100 | ### `-fs` 101 | 102 | `-fs` generates a virtual filesystem API similar to [http.FileSystem](https://golang.org/pkg/net/http/#FileSystem). 103 | 104 | ### `-union-fs` 105 | 106 | `-union-fs` generates union filesystem API with a real file system directory overlaid 107 | embedded filesystem (implies `-fs`) 108 | 109 | ### `-http-fs` 110 | 111 | `-http-fs` generates [http.FileSystem](https://golang.org/pkg/net/http/#FileSystem) 112 | interface (implies `-fs`). 113 | 114 | ### `-raw-bytes` 115 | 116 | `-raw-bytes` enables direct access to stored binary asset as a []byte slice. Please note 117 | that changing data will result in segmentation fault. 118 | 119 | ### `-binary` 120 | 121 | `-binary` produces an executable image with embedded content instead of a source package. The image 122 | could serve as a self-extracting archive or self-contained HTTP server: 123 | 124 | ```bash 125 | $ ./site --help 126 | Usage of ./site: 127 | -extract directory 128 | extract content to the target directory and exit 129 | -listen address 130 | socket address to listen (default ":8080") 131 | -tls-cert file 132 | TLS certificate file to use 133 | -tls-key file 134 | TLS key file to use 135 | ``` 136 | 137 | ## Generated code API 138 | 139 | ### Asset 140 | 141 | ```go 142 | type Asset struct { 143 | // contains filtered or unexported fields 144 | } 145 | ``` 146 | 147 | Asset represents binary resource stored within Go executable. Asset implements 148 | [fmt.Stringer](https://golang.org/pkg/fmt/#Stringer) and 149 | [io.WriterTo](https://golang.org/pkg/io/#WriterTo) interfaces, decompressing 150 | binary data if necessary. 151 | 152 | ### Get 153 | 154 | ```go 155 | func Get(name string) *Asset 156 | ``` 157 | 158 | `Get` returns pointer to Asset structure, or nil if no asset found. Asset name 159 | should not contain leading slash, i.e. `css/style.css`, not `/css/style.css`. 160 | 161 | ### Must 162 | 163 | ```go 164 | func Must(name string) *Asset 165 | ``` 166 | 167 | `Must` returns pointer to Asset structure, or panics if no asset found. 168 | 169 | ### Asset.Name 170 | 171 | ```go 172 | func (*Asset) Name() string 173 | ``` 174 | 175 | Returns base file name of the asset. 176 | 177 | ### Asset.MimeType 178 | 179 | ```go 180 | func (*Asset) MimeType() string 181 | ``` 182 | 183 | Returns MIME Type (computed from the file extension during compilation) of the asset. 184 | 185 | ### Asset.IsCompressed 186 | 187 | ```go 188 | func (*Asset) IsCompressed() bool 189 | ``` 190 | 191 | Returns true if resource has been compressed. Present only if compression was not disabled. 192 | 193 | ### Asset.Reader 194 | 195 | ```go 196 | func (*Asset) Reader() io.ReaderCloser 197 | ``` 198 | 199 | Returns an io.ReaderCloser interface to read asset data. 200 | 201 | ### Asset os.FileInfo interface 202 | 203 | ```go 204 | func (*Asset) Size() int64 205 | func (*Asset) ModTime() time.Time 206 | func (*Asset) Mode() os.FileMode 207 | func (*Asset) Sys() interface{} 208 | func (*Asset) IsDir() bool 209 | ``` 210 | 211 | These functions implement [os.FileInfo](https://golang.org/pkg/os/#FileInfo) interface. 212 | Note that `Size()` returns real (uncompressed) size of the asset. 213 | 214 | ### Asset.String 215 | 216 | ```go 217 | func (*Asset) String() string 218 | ``` 219 | 220 | Returns asset content as `string`. If asset was not compressed, then 221 | string will hold a direct pointer to RO section of the binary, otherwise `String()` 222 | will return uncompressed asset. 223 | 224 | ### Asset.Bytes 225 | 226 | ```go 227 | func (*Asset) Bytes() []byte 228 | ``` 229 | 230 | Returns asset content as `[]byte`, uncompressing it if necessary. Note that 231 | `Bytes()` will return a copy of the embedded content even if it was not compressed. 232 | To get a direct reference to RO data, use [Asset.RawBytes](#asset.rawbytes) 233 | 234 | ### Asset.RawBytes 235 | 236 | ```go 237 | func (*Asset) RawBytes() []byte 238 | ``` 239 | 240 | Present only if `-raw-bytes` option was enabled and returns direct pointer to RO section 241 | of the binary. Any write to the slice will result in segmentation fault. 242 | 243 | ### Asset.WriteTo 244 | 245 | ```go 246 | func (*Asset) WriteTo(io.Writer) (int64, error) 247 | ``` 248 | 249 | Writes full content of the asset to supplied `io.Writer`, decompressing asset content if 250 | necessary. 251 | 252 | ### FileSystem 253 | 254 | ```go 255 | type FileSystem interface { 256 | Open(name string) (File, error) 257 | Stat(name string) (os.FileInfo, error) 258 | Walk(root string, walkFunc filepath.WalkFunc) error 259 | HttpFileSystem() http.FileSystem 260 | } 261 | ``` 262 | 263 | Virtual filesystem abstraction, present only if one of `-*fs` options were enabled. 264 | `Walk` methods behave the same way as [filepath.Walk](https://golang.org/pkg/path/filepath/#Walk). 265 | `HttpFileSystem()` method present only if `-http-fs` option was enabled and returns [http.FileSystem](https://golang.org/pkg/net/http/#FileSystem) 266 | interface to serve content with standard http server (but take a look at builtin [http handler](#httphandlerwithprefix) first). 267 | 268 | ### File 269 | 270 | ```go 271 | type File interface { 272 | io.Closer 273 | io.Reader 274 | io.Seeker 275 | Readdir(count int) ([]os.FileInfo, error) 276 | Stat() (os.FileInfo, error) 277 | } 278 | ``` 279 | 280 | Virtual filesystem File interface. Methods behave similar to *os.File methods 281 | 282 | ### Open 283 | 284 | ```go 285 | func Open(name string) (io.ReadCloser, error) 286 | 287 | func Open(name string) (File, error) 288 | ``` 289 | 290 | `Open` returns [io.ReadCloser](https://golang.org/pkg/io/#ReadCloser) or [File](#file) if `-*fs` option 291 | was set, to read asset content from. If no asset was found, [os.ErrNotExist](https://golang.org/pkg/os/#ErrNotExist) 292 | will be returned. 293 | 294 | Note that with virtual filesystem enabled it is possible to open directories and list assets with Readdir. 295 | 296 | ### CopyTo 297 | 298 | ```go 299 | func CopyTo(target string, mode os.FileMode, overwrite bool, files ...string) error 300 | ``` 301 | 302 | The CopyTo method extracts all mentioned files to a specified location, keeping directory structure. 303 | If supplied file is a directory, than it will be extracted recursively. CopyTo with no file mentioned 304 | will extract the whole content of the embedded filesystem. CopyTo returns error if there is a file with 305 | the same name at the target location, unless overwrite is set to true, or file has the same size and 306 | modification file as the extracted file. 307 | 308 | Following code 309 | 310 | ```go 311 | pkg.CopyTo(".", 0640, false) 312 | ``` 313 | 314 | will effectively extract content of the filesystem to the current directory (which 315 | makes it the most space-wise inefficient self-extracting archive ever). 316 | 317 | ### NewUnionFs 318 | 319 | ```go 320 | func NewUnionFs(path string) (FileSystem, error) 321 | ``` 322 | 323 | Present only if `-union-fs` option was enabled and returns a union fs, a real file system 324 | directory starting `path`, which overlaid embedded filesystem. 325 | 326 | ### HttpFileSystem 327 | 328 | ```go 329 | func HttpFileSystem() http.FileSystem 330 | ``` 331 | 332 | Present only if `-http-fs` option was enabled. Returns assets directory as [http.FileSystem](https://golang.org/pkg/net/http/#FileSystem). 333 | A convenience shortcut for `FS().HttpFileSystem()` 334 | 335 | ### HTTPHandlerWithPrefix 336 | 337 | ```go 338 | func HTTPHandlerWithPrefix(prefix string) func(w http.ResponseWriter, req *http.Request) 339 | ``` 340 | 341 | Present only unless `-no-http-handler` option was set. 342 | `HTTPHandlerWithPrefix` provides a simple way to serve embedded content via 343 | Go standard HTTP server and returns an http handler function. The `prefix` 344 | will be stripped from the request URL to serve embedded content from non-root URI. 345 | Note that handler sends already compressed content if client supports compression, and 346 | also it sends `Etag` with precomputed asset hash and supports conditional requests 347 | with `If-None-Match`, which makes it more efficient than `http.FileSystem` 348 | API in most real life cases. 349 | 350 | ```go 351 | func main() { 352 | ... 353 | http.HandleFunc("/api/help/", site.HTTPHandlerWithPrefix("/api/help/")) 354 | ... 355 | http.ListenAndServe(address, nil) 356 | ... 357 | } 358 | ``` 359 | 360 | If the source tree had `404.html` file, handler function will employ it in 361 | case of absent resource, otherwise a standard Go `http.NotFound` response will 362 | be used. 363 | 364 | ### ServeHTTP 365 | 366 | ```go 367 | var ServeHTTP = HTTPHandlerWithPrefix("/") 368 | ``` 369 | 370 | ServeHTTP provides a convenience handler whenever embedded content should be served from the root URI. 371 | 372 | ## Caveats 373 | 374 | - Tested well only for amd64 and 386. Other architectures should work, though. 375 | - Once again, `asset.RawBytes` points directly to data, located in read-only data section 376 | of the executable image, so any attempt to modify it will result in page 377 | protection fault. Hopefully, Go will have [read-only](https://docs.google.com/document/d/1-NzIYu0qnnsshMBpMPmuO21qd8unlimHgKjRD9qwp2A) 378 | [slices](https://github.com/golang/go/issues/20443) one day, so this will be no longer an issue. 379 | - UnionFs abstraction do not allow to _delete_ file, only to add or replace content to the embedded 380 | filesystem. 381 | - Even a minor change in one of the resources will result in totally new `data.s` file, which feels 382 | a bit inconvenient from VCS point of view. 383 | 384 | ## License 385 | 386 | The MIT License, see [LICENSE.md](LICENSE.md). 387 | 388 | ## Other similar tools 389 | 390 | - [go.rice](https://github.com/GeertJohan/go.rice) 391 | - [go-bindata](https://github.com/jteeuwen/go-bindata) 392 | - [statik](https://github.com/rakyll/statik) 393 | - [esc](https://github.com/mjibson/esc) 394 | - [packr](https://github.com/gobuffalo/packr) 395 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexey Naidyonov. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE.md file. 4 | 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "os" 11 | "mime" 12 | "bytes" 13 | "text/template" 14 | "github.com/growler/go-imbed/imbed" 15 | "path/filepath" 16 | "io/ioutil" 17 | "os/exec" 18 | "io" 19 | ) 20 | 21 | var usage = template.Must(template.New("").Parse( 22 | `A simple source generator to embed resources into Go executable 23 | 24 | Usage: 25 | {{.Binary}} [options] 26 | 27 | Options: 28 | {{.Options}} 29 | 30 | Generator will build a golang assembly file along with assets access APIs. 31 | 32 | All the generated sources will be placed into relative to the current 33 | working directory (so generator is convenient to use with go:generate). It is recommended to 34 | use internal package (i.e., "internal/site") 35 | 36 | The typical usage would be: 37 | 38 | // go:generate go-imbed site-source internal/site 39 | package main 40 | 41 | import ( 42 | "net/http" 43 | "fmt" 44 | "internal/site" 45 | ) 46 | 47 | func main() { 48 | http.HandleFunc("/", site.ServeHTTP) 49 | if err := http.ListenAndServe(":8080", nil); err != nil{ 50 | fmt.Println(err) 51 | } 52 | } 53 | `)) 54 | 55 | var cli *flag.FlagSet 56 | 57 | var ( 58 | disableCompression bool 59 | disableHTTPHandler bool 60 | enableFS bool 61 | enableUnionFS bool 62 | enableHTTPFS bool 63 | enableRawBytes bool 64 | pkgName string 65 | makeBinary bool 66 | help bool 67 | ) 68 | 69 | func init() { 70 | 71 | cli = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) 72 | cli.BoolVar(&help, "help", false, "prints help") 73 | cli.StringVar(&pkgName, "pkg", "", "package name (if not set, the basename of the will be used)") 74 | cli.BoolVar(&disableCompression, "no-compression", false, "disable compression even for compressible files") 75 | cli.BoolVar(&disableHTTPHandler, "no-http-handler", false, "disable http handler API") 76 | cli.BoolVar(&enableFS, "fs", false, "enable embedded filesystem API") 77 | cli.BoolVar(&enableUnionFS, "union-fs", false, "enable union filesystem API (real fs over embedded, implies -fs)") 78 | cli.BoolVar(&enableHTTPFS, "http-fs", false, "enable http.FileSystem API (implies -fs") 79 | cli.BoolVar(&enableRawBytes, "raw-bytes", false, "enable raw bytes access API") 80 | cli.BoolVar(&makeBinary, "binary", false, "produce self-contained http server binary ( will become the binary name then)") 81 | mimeTypes := [][2]string{ 82 | {".go", "text/x-golang"}, // Golang extension is due to get into apache /etc/mime.types 83 | } 84 | for i := range mimeTypes { 85 | mime.AddExtensionType(mimeTypes[i][0], mimeTypes[i][1]) 86 | } 87 | } 88 | 89 | func main() { 90 | err := cli.Parse(os.Args[1:]) 91 | if err != nil || cli.NArg() != 2 || help { 92 | var opts bytes.Buffer 93 | cli.SetOutput(&opts) 94 | cli.PrintDefaults() 95 | usage.Execute(os.Stdout, map[string]string{ 96 | "Binary": os.Args[0], 97 | "Options": opts.String(), 98 | }) 99 | if !help { 100 | os.Exit(2) 101 | } else { 102 | return 103 | } 104 | } 105 | source := cli.Arg(0) 106 | target := cli.Arg(1) 107 | if err = do(source, target); err != nil { 108 | fmt.Fprintln(os.Stderr, err.Error()) 109 | os.Exit(1) 110 | } 111 | } 112 | 113 | func do(source, target string) error { 114 | var ( 115 | targetDir string 116 | buildDir string 117 | flags imbed.ImbedFlag 118 | err error 119 | ) 120 | if makeBinary { 121 | buildDir, err = ioutil.TempDir(os.TempDir(), ".go-imbed") 122 | if err != nil { 123 | fmt.Fprintln(os.Stderr, err.Error()) 124 | os.Exit(1) 125 | } 126 | defer rmtree(buildDir) 127 | targetDir = filepath.Join(buildDir, "src", "main") 128 | pkgName = "main" 129 | flags = imbed.BuildMain | imbed.BuildFsAPI | imbed.BuildHttpHandlerAPI | imbed.CompressAssets 130 | } else { 131 | targetDir = target 132 | if pkgName == "" { 133 | pkgName = filepath.Base(target) 134 | } 135 | flags = imbed.ImbedFlag(0).Set(imbed.CompressAssets, !disableCompression). 136 | Set(imbed.BuildHttpHandlerAPI, !disableHTTPHandler). 137 | Set(imbed.BuildFsAPI, enableFS). 138 | Set(imbed.BuildHttpFsAPI, enableHTTPFS). 139 | Set(imbed.BuildUnionFsAPI, enableUnionFS). 140 | Set(imbed.BuildRawBytesAPI, enableRawBytes) 141 | } 142 | err = imbed.Imbed(source, targetDir, pkgName, flags) 143 | if err != nil { 144 | return err 145 | } 146 | if makeBinary { 147 | cmd := exec.Command("go", "install", "main") 148 | cmd.Env = append(os.Environ(), "GOPATH="+buildDir) 149 | cmd.Dir = buildDir 150 | cmd.Stderr = os.Stderr 151 | cmd.Stdout = os.Stdout 152 | err = cmd.Run() 153 | if err != nil { 154 | return err 155 | } 156 | srcBin, err := os.Open(filepath.Join(buildDir, "bin", "main")) 157 | if err != nil { 158 | return err 159 | } 160 | srcBinStat, err := srcBin.Stat() 161 | if err != nil { 162 | return err 163 | } 164 | defer srcBin.Close() 165 | dstBin, err := os.OpenFile(target, os.O_CREATE | os.O_WRONLY, srcBinStat.Mode()) 166 | if err != nil { 167 | return err 168 | } 169 | defer dstBin.Close() 170 | _, err = io.Copy(dstBin, srcBin) 171 | if err != nil { 172 | return err 173 | } 174 | } 175 | return nil 176 | } 177 | 178 | func rmtree(name string) { 179 | var files []string 180 | var dirs []string 181 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 182 | if err != nil { 183 | return nil 184 | } 185 | if info.IsDir() { 186 | dirs = append(dirs, path) 187 | } else { 188 | files = append(files, path) 189 | } 190 | return nil 191 | }) 192 | for j := len(files) - 1; j >= 0; j-- { 193 | os.Remove(files[j]) 194 | } 195 | for j := len(dirs) - 1; j >= 0; j-- { 196 | os.Remove(dirs[j]) 197 | } 198 | } -------------------------------------------------------------------------------- /example/cmd.go: -------------------------------------------------------------------------------- 1 | //go:generate go run ../cmd.go --http-fs --union-fs --raw-bytes site internal/site 2 | 3 | package main 4 | 5 | import ( 6 | "net/http" 7 | "fmt" 8 | "github.com/growler/go-imbed/example/internal/site" 9 | "flag" 10 | "os" 11 | ) 12 | 13 | var ( 14 | listenAddr string 15 | cert string 16 | key string 17 | ) 18 | 19 | func init() { 20 | flag.StringVar(&listenAddr, "listen", ":8080", "socket address to listen") 21 | flag.StringVar(&cert, "cert", "", "TLS certificate file to use") 22 | flag.StringVar(&key, "key", "", "TLS key file to use") 23 | } 24 | 25 | func main() { 26 | var tls bool 27 | var err error 28 | flag.Parse() 29 | if cert != "" && key != "" { 30 | tls = true 31 | } else if cert != "" || key != "" { 32 | fmt.Fprintln(os.Stderr, "both cert and key must be supplied for HTTPS") 33 | os.Exit(1) 34 | } 35 | http.Handle("/", http.FileServer(site.HttpFileSystem())) 36 | http.HandleFunc("/site/", site.HTTPHandlerWithPrefix("/site/")) 37 | if tls { 38 | err = http.ListenAndServeTLS(listenAddr, cert, key, nil) 39 | } else { 40 | err = http.ListenAndServe(listenAddr, nil) 41 | } 42 | if err != nil { 43 | fmt.Fprintln(os.Stderr, err.Error()) 44 | os.Exit(1) 45 | } 46 | } -------------------------------------------------------------------------------- /example/internal/site/index.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // Package site holds binary resources embedded into Go executable 4 | package site 5 | 6 | import ( 7 | "os" 8 | "io" 9 | "bytes" 10 | "path/filepath" 11 | "strconv" 12 | "sort" 13 | "net/http" 14 | "path" 15 | "strings" 16 | "compress/gzip" 17 | "io/ioutil" 18 | "time" 19 | ) 20 | 21 | func blob_bytes(uint32) []byte 22 | func blob_string(uint32) string 23 | 24 | // Asset represents binary resource stored within Go executable. Asset implements 25 | // fmt.Stringer and io.WriterTo interfaces, decompressing binary data if necessary. 26 | type Asset struct { 27 | name string // File name 28 | size int32 // File size (uncompressed) 29 | blob []byte // Resource blob []byte 30 | str_blob string // Resource blob as string 31 | isCompressed bool // true if resources was compressed with gzip 32 | mime string // MIME Type 33 | tag string // Tag is essentially a Tag of resource content and can be used as a value for "Etag" HTTP header 34 | } 35 | 36 | // Name returns the base name of the asset 37 | func (a *Asset) Name() string { return a.name } 38 | // MimeType returns MIME Type of the asset 39 | func (a *Asset) MimeType() string { return a.mime } 40 | // IsCompressed returns true of asset has been compressed 41 | func (a *Asset) IsCompressed() bool { return a.isCompressed } 42 | // String returns (uncompressed, if necessary) content of asset as a string 43 | func (a *Asset) String() string { 44 | if a.isCompressed { 45 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 46 | ret, _ := ioutil.ReadAll(ungzip) 47 | ungzip.Close() 48 | return string(ret) 49 | } 50 | return a.str_blob 51 | } 52 | 53 | // Bytes returns (uncompressed) content of asset as a []byte 54 | func (a *Asset) Bytes() []byte { 55 | if a.isCompressed { 56 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 57 | ret, _ := ioutil.ReadAll(ungzip) 58 | ungzip.Close() 59 | return ret 60 | } 61 | ret := make([]byte, len(a.blob)) 62 | copy(ret, a.blob) 63 | return ret 64 | } 65 | // RawBytes returns a raw byte slice of the asset. Changing content of slice will result into segfault. 66 | func (a *Asset) RawBytes() []byte { 67 | return a.blob 68 | } 69 | 70 | // Size implements os.FileInfo and returns the size of the asset (uncompressed, if asset has been compressed) 71 | func (a *Asset) Size() int64 { return int64(a.size) } 72 | // Mode implements os.FileInfo and always returns 0444 73 | func (a *Asset) Mode() os.FileMode { return 0444 } 74 | // ModTime implements os.FileInfo and returns the time stamp when this package has been produced (the same value for all the assets) 75 | func (a *Asset) ModTime() time.Time { return stamp } 76 | // IsDir implements os.FileInfo and returns false 77 | func (a *Asset) IsDir() bool { return false } 78 | // Sys implements os.FileInfo and returns nil 79 | func (a *Asset) Sys() interface{} { return a } 80 | 81 | // WriteTo implements io.WriterTo interface and writes content of the asset to w 82 | func (a *Asset) WriteTo(w io.Writer) (int64, error) { 83 | if a.isCompressed { 84 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 85 | n, err := io.Copy(w, ungzip) 86 | ungzip.Close() 87 | return n, err 88 | } 89 | n, err := w.Write(a.blob) 90 | return int64(n), err 91 | } 92 | 93 | type assetReader struct { 94 | bytes.Reader 95 | } 96 | 97 | func (r *assetReader) Close() error { 98 | r.Reset(nil) 99 | return nil 100 | } 101 | 102 | // Returns content of the asset as io.ReaderCloser. 103 | func (a *Asset) Reader() io.ReadCloser { 104 | if a.isCompressed { 105 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 106 | return ungzip 107 | } else { 108 | ret := &assetReader{} 109 | ret.Reset(a.blob) 110 | return ret 111 | } 112 | } 113 | 114 | func cleanPath(path string) string { 115 | path = filepath.Clean(path) 116 | if filepath.IsAbs(path) { 117 | path = path[len(filepath.VolumeName(path)):] 118 | if len(path) > 0 || os.IsPathSeparator(path[0]) { 119 | path = path[1:] 120 | } 121 | } else if path == "." { 122 | return "" 123 | } 124 | return filepath.ToSlash(path) 125 | } 126 | 127 | // Opens asset as an io.ReadCloser. Returns os.ErrNotExist if no asset is found. 128 | func Open(name string) (File, error) { 129 | return FS().Open(name) 130 | } 131 | 132 | // Gets asset by name. Returns nil if no asset found. 133 | func Get(name string) *Asset { 134 | if entry, ok := fidx[name]; ok { 135 | return entry 136 | } else { 137 | return nil 138 | } 139 | } 140 | 141 | // Get asset by name. Panics if no asset found. 142 | func Must(name string) *Asset { 143 | if entry, ok := fidx[name]; ok { 144 | return entry 145 | } else { 146 | panic("asset " + name + " not found") 147 | } 148 | } 149 | 150 | type directoryAsset struct { 151 | name string 152 | dirs []directoryAsset 153 | files []Asset 154 | } 155 | 156 | var root *directoryAsset 157 | 158 | // A simple FileSystem abstraction 159 | type FileSystem interface { 160 | Open(name string) (File, error) 161 | Stat(name string) (os.FileInfo, error) 162 | // As in filepath.Walk 163 | Walk(root string, walkFunc filepath.WalkFunc) error 164 | // Returns http.FileSystem interface to use with http.Server 165 | HttpFileSystem() http.FileSystem 166 | } 167 | 168 | // The CopyTo method extracts all mentioned files 169 | // to a specified location, keeping directory structure. 170 | // If supplied file is a directory, than it will be extracted 171 | // recursively. CopyTo with no file mentioned will extract 172 | // the whole content of the embedded filesystem. 173 | // CopyTo returns error if there is a file with the same name 174 | // at the target location, unless overwrite is set to true, or 175 | // file has the same size and modification file as the extracted 176 | // file. 177 | // site.CopyTo(".", mode, false) will effectively 178 | // extract content of the filesystem to the current directory (which 179 | // makes it the most space-wise inefficient self-extracting archive 180 | // ever). 181 | func CopyTo(target string, mode os.FileMode, overwrite bool, files ...string) error { 182 | mode = mode&0777 183 | dirmode := os.ModeDir|((mode&0444)>>2)|mode 184 | if len(files) == 0 { 185 | files = []string{""} 186 | } 187 | for _, file := range files { 188 | file = cleanPath(file) 189 | err := FS().Walk(file, func(path string, info os.FileInfo, err error) error { 190 | if err != nil { 191 | return err 192 | } 193 | targetPath := filepath.Join(target, path) 194 | fi, err := os.Stat(targetPath) 195 | if err == nil { 196 | if info.IsDir() && fi.IsDir() { 197 | return nil 198 | } else if info.IsDir() != fi.IsDir() { 199 | return os.ErrExist 200 | } else if !overwrite { 201 | if info.Size() == fi.Size() && info.ModTime().Equal(fi.ModTime()) { 202 | return nil 203 | } else { 204 | return os.ErrExist 205 | } 206 | } 207 | } 208 | if info.IsDir() { 209 | return os.MkdirAll(targetPath, dirmode) 210 | } 211 | asset := Get(path) 212 | if asset == nil { 213 | return os.ErrNotExist 214 | } 215 | targetPathDir := filepath.Dir(targetPath) 216 | if err = os.MkdirAll(targetPathDir, dirmode); err != nil { 217 | return err 218 | } 219 | dst, err := ioutil.TempFile(targetPathDir, ".imbed") 220 | if err != nil { 221 | return err 222 | } 223 | defer func() { 224 | dst.Close() 225 | os.Remove(dst.Name()) 226 | }() 227 | _, err = asset.WriteTo(dst) 228 | if err != nil { 229 | return err 230 | } 231 | dst.Close() 232 | os.Chtimes(dst.Name(), info.ModTime(), info.ModTime()) 233 | os.Chmod(dst.Name(), mode) 234 | return os.Rename(dst.Name(), targetPath) 235 | }) 236 | if err != nil { 237 | return err 238 | } 239 | } 240 | return nil 241 | } 242 | 243 | type fileInfoSlice []os.FileInfo 244 | func (fis *fileInfoSlice) Len() int { return len(*fis) } 245 | func (fis *fileInfoSlice) Less(i, j int) bool { return (*fis)[i].Name() < (*fis)[j].Name() } 246 | func (fis *fileInfoSlice) Swap(i, j int) { 247 | s := (*fis)[i] 248 | (*fis)[i] = (*fis)[j] 249 | (*fis)[j] = s 250 | } 251 | 252 | func walkRec(fs FileSystem, info os.FileInfo, p string, walkFn filepath.WalkFunc) error { 253 | var ( 254 | dir File 255 | fis fileInfoSlice 256 | err error 257 | ) 258 | err = walkFn(p, info, nil) 259 | if err != nil { 260 | if info.IsDir() && err == filepath.SkipDir { 261 | return nil 262 | } 263 | return err 264 | } 265 | if !info.IsDir() { 266 | return nil 267 | } 268 | dir, err = fs.Open(p) 269 | if err != nil { 270 | return walkFn(p, info, err) 271 | } 272 | fis, err = dir.Readdir(-1) 273 | if err != nil { 274 | return walkFn(p, info, err) 275 | } 276 | sort.Sort(&fis) 277 | for i := range fis { 278 | fn := path.Join(p, fis[i].Name()) 279 | err = walkRec(fs, fis[i], fn, walkFn) 280 | if err != nil { 281 | if !fis[i].IsDir() || err != filepath.SkipDir { 282 | return err 283 | } 284 | } 285 | } 286 | return nil 287 | } 288 | 289 | func walk(fs FileSystem, name string, walkFunc filepath.WalkFunc) error { 290 | var r os.FileInfo 291 | var err error 292 | name = cleanPath(name) 293 | r, err = fs.Stat(name) 294 | if err != nil { 295 | return err 296 | } 297 | return walkRec(fs, r, name, walkFunc) 298 | } 299 | 300 | type assetFs struct{} 301 | 302 | // Returns embedded FileSystem 303 | func FS() FileSystem { 304 | return &assetFs{} 305 | } 306 | 307 | func (fs *assetFs) Walk(root string, walkFunc filepath.WalkFunc) error { 308 | return walk(fs, root, walkFunc) 309 | } 310 | 311 | func (fs *assetFs) Stat(name string) (os.FileInfo, error) { 312 | name = cleanPath(name) 313 | if name == "" { 314 | return root, nil 315 | } 316 | if dir, ok := didx[name]; ok { 317 | return dir, nil 318 | } 319 | if asset, ok := fidx[name]; ok { 320 | return asset, nil 321 | } 322 | return nil, os.ErrNotExist 323 | } 324 | 325 | func (fs *assetFs) Open(name string) (File, error) { 326 | name = cleanPath(name) 327 | if name == "" { 328 | return root.open(""), nil 329 | } 330 | if dir, ok := didx[name]; ok { 331 | return dir.open(name), nil 332 | } 333 | if asset, ok := fidx[name]; ok { 334 | return asset.open(name), nil 335 | } 336 | return nil, os.ErrNotExist 337 | } 338 | func (fs *assetFs) HttpFileSystem() http.FileSystem { 339 | return &httpFileSystem{fs: fs} 340 | } 341 | 342 | 343 | // A File is returned by virtual FileSystem's Open method. 344 | // The methods should behave the same as those on an *os.File. 345 | type File interface { 346 | io.Closer 347 | io.Reader 348 | io.Seeker 349 | Readdir(count int) ([]os.FileInfo, error) 350 | Stat() (os.FileInfo, error) 351 | } 352 | 353 | func (a *Asset) open(name string) File { 354 | if a.isCompressed { 355 | ret := &assetCompressedFile{ 356 | asset: a, 357 | name: name, 358 | } 359 | ret.Reset(bytes.NewReader(a.blob)) 360 | return ret 361 | } else { 362 | ret := &assetFile{ 363 | asset: a, 364 | name: name, 365 | } 366 | ret.Reset(a.blob) 367 | return ret 368 | } 369 | } 370 | 371 | func (d *directoryAsset) open(name string) File { 372 | return &directoryAssetFile{ 373 | dir: d, 374 | name: name, 375 | pos: 0, 376 | } 377 | } 378 | 379 | type directoryAssetFile struct { 380 | dir *directoryAsset 381 | name string 382 | pos int 383 | } 384 | 385 | func (d *directoryAssetFile) Name() string { 386 | return d.name 387 | } 388 | 389 | func (d *directoryAssetFile) checkClosed() error { 390 | if d.pos < 0 { 391 | return os.ErrClosed 392 | } 393 | return nil 394 | } 395 | 396 | func (d *directoryAssetFile) Close() error { 397 | if err := d.checkClosed(); err != nil { 398 | return err 399 | } 400 | d.pos = -1 401 | return nil 402 | } 403 | 404 | func (d *directoryAssetFile) Read([]byte) (int, error) { 405 | if err := d.checkClosed(); err != nil { 406 | return 0, err 407 | } 408 | return 0, io.EOF 409 | } 410 | 411 | func (d *directoryAssetFile) Stat() (os.FileInfo, error) { 412 | if err := d.checkClosed(); err != nil { 413 | return nil, err 414 | } 415 | return d.dir, nil 416 | } 417 | 418 | func (d *directoryAssetFile) Seek(pos int64, whence int) (int64, error) { 419 | if err := d.checkClosed(); err != nil { 420 | return 0, err 421 | } 422 | return 0, os.ErrInvalid 423 | } 424 | 425 | func (d *directoryAssetFile) Readdir(count int) ([]os.FileInfo, error) { 426 | if err := d.checkClosed(); err != nil { 427 | return nil, err 428 | } 429 | var ( 430 | last int 431 | total = len(d.dir.dirs) + len(d.dir.files) 432 | ) 433 | if d.pos > total { 434 | if count > 0 { 435 | return nil, io.EOF 436 | } else { 437 | return nil, nil 438 | } 439 | } 440 | if count <= 0 || (d.pos + count) <= total { 441 | last = total 442 | } else { 443 | last = d.pos + count 444 | } 445 | ret := make([]os.FileInfo, 0, last - d.pos) 446 | if d.pos < len(d.dir.dirs) { 447 | var stop int 448 | if last > len(d.dir.dirs) { 449 | stop = len(d.dir.dirs) 450 | } else { 451 | stop = last 452 | } 453 | for i := d.pos; i < stop; i++ { 454 | ret = append(ret, &d.dir.dirs[i]) 455 | } 456 | d.pos = stop 457 | } 458 | var start, stop int 459 | start = d.pos - len(d.dir.dirs) 460 | stop = last - len(d.dir.dirs) 461 | for i := start; i < stop; i++ { 462 | ret = append(ret, &d.dir.files[i]) 463 | } 464 | d.pos = last 465 | return ret, nil 466 | } 467 | 468 | func (d *directoryAsset) Name() string { return d.name } 469 | func (d *directoryAsset) Size() int64 { return 0 } 470 | func (d *directoryAsset) Mode() os.FileMode { return os.ModeDir | 0555 } 471 | func (d *directoryAsset) ModTime() time.Time { return stamp } 472 | func (d *directoryAsset) IsDir() bool { return true } 473 | func (d *directoryAsset) Sys() interface{} { return d } 474 | 475 | type assetFile struct { 476 | assetReader 477 | name string 478 | asset *Asset 479 | } 480 | 481 | func (a *assetFile) Name() string { 482 | return a.name 483 | } 484 | 485 | func (a *assetFile) Stat() (os.FileInfo, error) { 486 | return a.asset, nil 487 | } 488 | 489 | func (a *assetFile) Readdir(int) ([]os.FileInfo, error) { 490 | return nil, os.ErrInvalid 491 | } 492 | type assetCompressedFile struct { 493 | gzip.Reader 494 | name string 495 | asset *Asset 496 | } 497 | 498 | func (a *assetCompressedFile) Name() string { 499 | return a.name 500 | } 501 | 502 | func (a *assetCompressedFile) Stat() (os.FileInfo, error) { 503 | return a.asset, nil 504 | } 505 | 506 | func (a *assetCompressedFile) Seek(int64, int) (int64, error) { 507 | return 0, os.ErrInvalid 508 | } 509 | 510 | func (a *assetCompressedFile) Readdir(count int) ([]os.FileInfo, error) { 511 | return nil, os.ErrInvalid 512 | } 513 | 514 | type unionFs struct { 515 | root string 516 | } 517 | 518 | func NewUnionFS(src string) (FileSystem, error) { 519 | abs, err := filepath.Abs(src) 520 | if err != nil { 521 | return nil, err 522 | } 523 | return &unionFs{ 524 | root: abs, 525 | }, nil 526 | } 527 | 528 | func (fs *unionFs) Stat(name string) (os.FileInfo, error) { 529 | name = cleanPath(name) 530 | fname := filepath.Join(fs.root, filepath.FromSlash(name)) 531 | fi, err := os.Stat(fname) 532 | if err == nil { 533 | return fi, nil 534 | } 535 | return FS().Stat(name) 536 | } 537 | 538 | func (fs *unionFs) Open(name string) (File, error) { 539 | name = cleanPath(name) 540 | fname := filepath.Join(fs.root, filepath.FromSlash(name)) 541 | fi, err := os.Stat(fname) 542 | if err == nil { 543 | file, err := os.OpenFile(fname, os.O_RDONLY, 0) 544 | if err == nil { 545 | if !fi.IsDir() { 546 | return &unionFsFile{ 547 | name: name, 548 | file: file, 549 | }, nil 550 | } else { 551 | dir, _ := didx[name] 552 | return &unionFsDirectoryFile{ 553 | name: name, 554 | dir: dir, 555 | fsDir: file, 556 | pos: 0, 557 | }, nil 558 | } 559 | } 560 | } 561 | return FS().Open(name) 562 | } 563 | 564 | func (fs *unionFs) Walk(root string, walkFunc filepath.WalkFunc) error { 565 | return walk(fs, root, walkFunc) 566 | } 567 | func (fs *unionFs) HttpFileSystem() http.FileSystem { 568 | return &httpFileSystem{fs: fs} 569 | } 570 | 571 | type unionFsFile struct { 572 | name string 573 | file *os.File 574 | } 575 | 576 | func (f *unionFsFile) Name() string { return f.name } 577 | func (f *unionFsFile) Close() error { return f.file.Close() } 578 | func (f *unionFsFile) Read(d []byte) (int, error) { return f.file.Read(d) } 579 | func (f *unionFsFile) Stat() (os.FileInfo, error) { return f.file.Stat() } 580 | func (f *unionFsFile) Seek(pos int64, whence int) (int64, error) { return f.file.Seek(pos, whence) } 581 | func (f *unionFsFile) Readdir(count int) ([]os.FileInfo, error) { return f.file.Readdir(count) } 582 | 583 | type unionFsDirectoryFile struct { 584 | name string 585 | dir *directoryAsset 586 | fsDir *os.File 587 | pos int 588 | } 589 | 590 | func (d *unionFsDirectoryFile) Name() string { return d.name } 591 | func (d *unionFsDirectoryFile) Close() error { 592 | if d.fsDir == nil { 593 | return os.ErrClosed 594 | } 595 | err := d.fsDir.Close() 596 | d.fsDir = nil 597 | return err 598 | } 599 | 600 | func (d *unionFsDirectoryFile) Read([]byte) (int, error) { 601 | if d.fsDir == nil { 602 | return 0, os.ErrClosed 603 | } 604 | return 0, io.EOF 605 | } 606 | 607 | func (d *unionFsDirectoryFile) Stat() (os.FileInfo, error) { 608 | if d.fsDir == nil { 609 | return nil, os.ErrClosed 610 | } 611 | return d.fsDir.Stat() 612 | } 613 | 614 | func (d *unionFsDirectoryFile) Seek(pos int64, whence int) (int64, error) { 615 | if d.fsDir == nil { 616 | return 0, os.ErrClosed 617 | } 618 | return 0, os.ErrInvalid 619 | } 620 | func (d *unionFsDirectoryFile) Readdir(count int) ([]os.FileInfo, error) { 621 | if d.fsDir == nil { 622 | return nil, os.ErrClosed 623 | } 624 | if d.pos < 0 { 625 | if count > 0 { 626 | return nil, io.EOF 627 | } else { 628 | return nil, nil 629 | } 630 | } 631 | if d.dir == nil { 632 | return d.fsDir.Readdir(count) 633 | } 634 | ret, err := d.fsDir.Readdir(count) 635 | if count > 0 && err == nil { 636 | return ret, err 637 | } 638 | embedded := make([]os.FileInfo, 0, len(d.dir.dirs) + len(d.dir.files)) 639 | for i := range d.dir.dirs { 640 | embedded = append(embedded, &d.dir.dirs[i]) 641 | } 642 | for i := range d.dir.files { 643 | embedded = append(embedded, &d.dir.files[i]) 644 | } 645 | for _, fi := range embedded[d.pos:] { 646 | if count > 0 && len(ret) >= count { 647 | return ret, nil 648 | } 649 | d.pos++ 650 | if _, err := os.Stat(filepath.Join(d.fsDir.Name(), fi.Name())); err == nil { 651 | continue 652 | } 653 | ret = append(ret, fi) 654 | } 655 | d.pos = -1 656 | return ret, nil 657 | } 658 | type httpFileSystem struct { 659 | fs FileSystem 660 | } 661 | func (fs *httpFileSystem) Open(name string) (http.File, error) { 662 | return fs.fs.Open(name) 663 | } 664 | func HttpFileSystem() http.FileSystem { 665 | return FS().HttpFileSystem() 666 | } 667 | 668 | var fidx = make(map[string]*Asset) 669 | var didx = make(map[string]*directoryAsset) 670 | var stamp time.Time 671 | 672 | func init() { 673 | stamp = time.Unix(1515954503, 733095000) 674 | bb := blob_bytes(66432) 675 | bs := blob_string(66432) 676 | root = &directoryAsset{ 677 | dirs: []directoryAsset{ 678 | { 679 | name: "css", 680 | files: []Asset{ 681 | { 682 | name: "style.css", 683 | blob: bb[264:1348], 684 | str_blob: bs[264:1348], 685 | mime: "text/css; charset=utf-8", 686 | tag: "zlyzclmjepcnm", 687 | size: 3213, 688 | isCompressed: true, 689 | }, 690 | }, 691 | }, 692 | { 693 | name: "images", 694 | files: []Asset{ 695 | { 696 | name: "a-nice-picture.jpg", 697 | blob: bb[1352:63866], 698 | str_blob: bs[1352:63866], 699 | mime: "image/jpeg", 700 | tag: "ahaszqrnqpm2a", 701 | size: 62514, 702 | isCompressed: false, 703 | }, 704 | }, 705 | }, 706 | }, 707 | files: []Asset{ 708 | { 709 | name: "404.html", 710 | blob: bb[0:258], 711 | str_blob: bs[0:258], 712 | mime: "text/html; charset=utf-8", 713 | tag: "hrlex6jrmr43u", 714 | size: 359, 715 | isCompressed: true, 716 | }, 717 | { 718 | name: "index.html", 719 | blob: bb[63872:66426], 720 | str_blob: bs[63872:66426], 721 | mime: "text/html; charset=utf-8", 722 | tag: "kqf5n5qf7i6vu", 723 | size: 7752, 724 | isCompressed: true, 725 | }, 726 | }, 727 | } 728 | didx[""] = root 729 | didx["css"] = &root.dirs[0] 730 | fidx["css/style.css"] = &root.dirs[0].files[0] 731 | didx["images"] = &root.dirs[1] 732 | fidx["images/a-nice-picture.jpg"] = &root.dirs[1].files[0] 733 | fidx["404.html"] = &root.files[0] 734 | http404Asset = &root.files[0] 735 | fidx["index.html"] = &root.files[1] 736 | } 737 | var http404Asset *Asset 738 | // ServeHTTP provides a convenience handler whenever embedded content should be served from the root URI. 739 | var ServeHTTP = HTTPHandlerWithPrefix("") 740 | 741 | // HTTPHandlerWithPrefix provides a simple way to serve embedded content via 742 | // Go standard HTTP server and returns an http handler function. The "prefix" 743 | // will be stripped from the request URL to serve embedded content from non-root URI 744 | func HTTPHandlerWithPrefix(prefix string) func(http.ResponseWriter, *http.Request) { 745 | return func(w http.ResponseWriter, req *http.Request) { 746 | if req.Method != "GET" && req.Method != "HEAD" { 747 | w.WriteHeader(http.StatusMethodNotAllowed) 748 | return 749 | } 750 | if !strings.HasPrefix(req.URL.Path, prefix) { 751 | http.NotFound(w, req) 752 | return 753 | } 754 | reqPath := req.URL.Path[len(prefix):] 755 | if strings.HasPrefix(reqPath, "/") { 756 | reqPath = reqPath[1:] 757 | } 758 | var status = http.StatusOK 759 | asset, ok := fidx[reqPath] 760 | if !ok { 761 | asset, ok = fidx[path.Join(reqPath, "index.html")] 762 | } 763 | if !ok { 764 | asset = http404Asset 765 | status = http.StatusNotFound 766 | } 767 | if tag := req.Header.Get("If-None-Match"); tag != "" { 768 | if strings.HasPrefix("W/", tag) || strings.HasPrefix("w/", tag) { 769 | tag = tag[2:] 770 | } 771 | if tag, err := strconv.Unquote(tag); err == nil && tag == asset.tag { 772 | w.WriteHeader(http.StatusNotModified) 773 | return 774 | } 775 | } 776 | if mtime := req.Header.Get("If-Modified-Since"); mtime != "" { 777 | if ts, err := time.Parse(time.RFC1123, mtime); err == nil && !ts.Before(stamp) { 778 | w.WriteHeader(http.StatusNotModified) 779 | return 780 | } 781 | } 782 | var deflate = asset.isCompressed 783 | if encs, ok := req.Header["Accept-Encoding"]; ok { 784 | for _, enc := range encs { 785 | if strings.Contains(enc, "gzip") { 786 | if deflate { 787 | w.Header().Set("Content-Encoding", "gzip") 788 | } 789 | deflate = false 790 | break 791 | } 792 | } 793 | } 794 | if !deflate { 795 | w.Header().Set("Content-Length", strconv.FormatInt(int64(len(asset.blob)), 10)) 796 | } 797 | w.Header().Set("Content-Type", asset.mime) 798 | w.Header().Set("Etag", strconv.Quote(asset.tag)) 799 | w.Header().Set("Last-Modified", stamp.Format(time.RFC1123)) 800 | w.WriteHeader(status) 801 | if req.Method != "HEAD" { 802 | if deflate { 803 | ungzip, _ := gzip.NewReader(bytes.NewReader(asset.blob)) 804 | defer ungzip.Close() 805 | io.Copy(w, ungzip) 806 | } else { 807 | w.Write(asset.blob) 808 | } 809 | } 810 | } 811 | } 812 | -------------------------------------------------------------------------------- /example/internal/site/index_386.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAL ·d(SB), AX 7 | MOVL AX, ret+4(FP) 8 | MOVL len+0(FP), AX 9 | MOVL AX, ret+8(FP) 10 | MOVL AX, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | LEAL ·d(SB), AX 15 | MOVL AX, ret+4(FP) 16 | MOVL len+0(FP), AX 17 | MOVL AX, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /example/internal/site/index_amd64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAQ ·d(SB), AX 7 | MOVQ AX, ret+8(FP) 8 | MOVL len+0(FP), AX 9 | MOVLQSX AX, AX 10 | MOVQ AX, ret+16(FP) 11 | MOVQ AX, ret+24(FP) 12 | RET 13 | 14 | TEXT ·blob_string(SB),NOSPLIT,$0-4 15 | LEAQ ·d(SB), AX 16 | MOVQ AX, ret+8(FP) 17 | MOVL len+0(FP), AX 18 | MOVLQSX AX, AX 19 | MOVQ AX, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /example/internal/site/index_arm.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | MOVW $·d(SB), R0 7 | MOVW R0, ret+4(FP) 8 | MOVW len+0(FP), R0 9 | MOVW R0, ret+8(FP) 10 | MOVW R0, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | MOVW $·d(SB), R0 15 | MOVW R0, ret+4(FP) 16 | MOVW len+0(FP), R0 17 | MOVW R0, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /example/internal/site/index_arm64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 6 | MOVD $·d(SB), R0 7 | MOVD R0, ret+8(FP) 8 | MOVW len+0(FP), R0 9 | MOVD R0, ret+16(FP) 10 | MOVD R0, ret+24(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-8 14 | MOVD $·d(SB), R0 15 | MOVD R0, ret+8(FP) 16 | MOVD len+0(FP), R0 17 | MOVD R0, ret+16(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /example/internal/site/index_mips64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips64 mips64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVV $·d(SB), R1 9 | MOVV R1, ret+8(FP) 10 | MOVV len+0(FP), R1 11 | MOVV R1, ret+16(FP) 12 | MOVV R1, ret+24(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVV $·d(SB), R1 17 | MOVV R1, ret+8(FP) 18 | MOVV len+0(FP), R1 19 | MOVV R1, ret+16(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /example/internal/site/index_mipsx.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips mipsle 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 8 | MOVW $·d(SB), R1 9 | MOVW R1, ret+4(FP) 10 | MOVW len+0(FP), R1 11 | MOVW R1, ret+8(FP) 12 | MOVW R1, ret+12(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-4 16 | MOVW $·d(SB), R1 17 | MOVW R1, ret+4(FP) 18 | MOVW len+0(FP), R1 19 | MOVW R1, ret+8(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /example/internal/site/index_ppc64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build ppc64 ppc64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVD $·d(SB), R3 9 | MOVD R3, ret+8(FP) 10 | MOVD len+0(FP), R3 11 | MOVD R3, ret+16(FP) 12 | MOVD R3, ret+24(FP) 13 | RET 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVD $·d(SB), R3 17 | MOVD R3, ret+8(FP) 18 | MOVD len+0(FP), R3 19 | MOVD R3, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /example/internal/site/index_s390x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT|NOFRAME,$0-8 6 | MOVD $·d(SB), R0 7 | MOVW len+0(FP), R1 8 | MOVD R1, R2 9 | STMG R0, R2, ret+8(FP) 10 | JMP R14 11 | 12 | TEXT ·blob_string(SB),NOSPLIT|NOFRAME,$0-8 13 | MOVD $·d(SB), R0 14 | MOVW len+0(FP), R1 15 | STMG R0, R1, ret+8(FP) 16 | JMP R14 17 | -------------------------------------------------------------------------------- /example/internal/site/index_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | package site 4 | 5 | import ( 6 | "encoding/base32" 7 | "encoding/binary" 8 | "hash/crc64" 9 | "testing" 10 | "math/rand" 11 | "os" 12 | "io/ioutil" 13 | "path/filepath" 14 | "bytes" 15 | "fmt" 16 | "net/http" 17 | "net/http/httptest" 18 | "path" 19 | "errors" 20 | ) 21 | 22 | var randomName = func() string { 23 | var buf [16]byte 24 | binary.LittleEndian.PutUint64(buf[:8], rand.Uint64()) 25 | binary.LittleEndian.PutUint64(buf[8:], rand.Uint64()) 26 | return b32Enc.EncodeToString(buf[:]) 27 | }() 28 | 29 | var b32Enc = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) 30 | 31 | func getTag(data []byte) string { 32 | var crcBuf [8]byte 33 | binary.LittleEndian.PutUint64(crcBuf[:], crc64.Checksum(data, crc64.MakeTable(crc64.ECMA))) 34 | return b32Enc.EncodeToString(crcBuf[:]) 35 | } 36 | 37 | func TestBytes(t *testing.T) { 38 | for n, a := range fidx { 39 | if getTag(a.Bytes()) != a.tag { 40 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 41 | } 42 | } 43 | } 44 | 45 | func TestString(t *testing.T) { 46 | for n, a := range fidx { 47 | if getTag([]byte(a.String())) != a.tag { 48 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 49 | } 50 | } 51 | } 52 | func TestWalkOpen(t *testing.T) { 53 | FS().Walk("", func(path string, info os.FileInfo, err error) error { 54 | if err != nil || info.IsDir() { 55 | return nil 56 | } 57 | asset := Get(path) 58 | if asset == nil { 59 | return fmt.Errorf("asset %s nout found", path) 60 | } 61 | if getTag(asset.Bytes()) != asset.tag { 62 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 63 | } 64 | if getTag([]byte(asset.String())) != asset.tag { 65 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 66 | } 67 | rdr := asset.Reader() 68 | data, err := ioutil.ReadAll(rdr) 69 | rdr.Close() 70 | if err != nil { 71 | return err 72 | } 73 | rdr, err = FS().Open(path) 74 | if err != nil { 75 | return err 76 | } 77 | data, err = ioutil.ReadAll(rdr) 78 | rdr.Close() 79 | if err != nil { 80 | return err 81 | } 82 | if getTag(data) != asset.tag { 83 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 84 | } 85 | rdr.Close() 86 | return nil 87 | }) 88 | } 89 | 90 | func rmtree(name string) { 91 | var files []string 92 | var dirs []string 93 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 94 | if err != nil { 95 | return nil 96 | } 97 | if info.IsDir() { 98 | dirs = append(dirs, path) 99 | } else { 100 | files = append(files, path) 101 | } 102 | return nil 103 | }) 104 | for j := len(files) - 1; j >= 0; j-- { 105 | os.Remove(files[j]) 106 | } 107 | for j := len(dirs) - 1; j >= 0; j-- { 108 | os.Remove(dirs[j]) 109 | } 110 | } 111 | 112 | func TestCopyTo(t *testing.T) { 113 | tmp, err := ioutil.TempDir(os.TempDir(), ".test-test") 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | defer rmtree(tmp) 118 | // a whole tree 119 | if err = CopyTo(tmp, 0640, false); err != nil { 120 | t.Fatal(err) 121 | } 122 | var testDir string 123 | var testFile string 124 | var testData []byte 125 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 126 | if err != nil { 127 | return err 128 | } 129 | if info.IsDir() { 130 | if testDir == "" { 131 | testDir = path 132 | } 133 | return nil 134 | } 135 | f, err := os.OpenFile(filepath.Join(tmp, filepath.FromSlash(path)), os.O_RDONLY, 0) 136 | if err != nil { 137 | return err 138 | } 139 | data, err := ioutil.ReadAll(f) 140 | f.Close() 141 | if err != nil { 142 | return err 143 | } 144 | if testFile == "" { 145 | testFile = path 146 | testData = Must(path).Bytes() 147 | } 148 | if bytes.Compare(data, Must(path).Bytes()) != 0 { 149 | return fmt.Errorf("data differs for %s", path) 150 | } 151 | return nil 152 | }) 153 | if err != nil { 154 | t.Fatal(err) 155 | } 156 | if testFile == "" { 157 | // an empty archive? 158 | return 159 | } 160 | // single file 161 | targetTestFile := filepath.Join(tmp, testFile) 162 | os.Remove(targetTestFile) 163 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 164 | t.Fatal(err) 165 | } 166 | f, err := os.OpenFile(targetTestFile, os.O_RDONLY, 0) 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | data, err := ioutil.ReadAll(f) 171 | f.Close() 172 | if err != nil { 173 | t.Fatal(err) 174 | } 175 | if bytes.Compare(testData, data) != 0 { 176 | t.Fatalf("data differs for single file extract") 177 | } 178 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 179 | t.Fatalf("expected no error, got %v", err) 180 | } 181 | f, err = os.OpenFile(targetTestFile, os.O_WRONLY | os.O_APPEND, 0600) 182 | if err != nil { 183 | t.Fatal(err) 184 | } 185 | _, err = f.WriteString(randomName) 186 | f.Close() 187 | if err != nil { 188 | t.Fatal(err) 189 | } 190 | if err = CopyTo(tmp, 0644, false, testFile); err != os.ErrExist { 191 | t.Fatalf("expected os.ErrExist, got %v", err) 192 | } 193 | if err = CopyTo(tmp, 0644, true, testFile); err != nil { 194 | t.Fatal(err) 195 | } 196 | f, err = os.OpenFile(targetTestFile, os.O_RDONLY, 0) 197 | if err != nil { 198 | t.Fatal(err) 199 | } 200 | data, err = ioutil.ReadAll(f) 201 | f.Close() 202 | if err != nil { 203 | t.Fatal(err) 204 | } 205 | if bytes.Compare(testData, data) != 0 { 206 | t.Fatalf("data differs for single file extract") 207 | } 208 | os.Remove(targetTestFile) 209 | if testDir == "" { 210 | // no directories in archive 211 | return 212 | } 213 | targetTestFile = filepath.Join(tmp, randomName) 214 | if err = CopyTo(targetTestFile, 0644, false, testDir); err != nil { 215 | t.Fatal(err) 216 | } 217 | err = FS().Walk(testDir, func(path string, info os.FileInfo, err error) error { 218 | if err != nil || info.IsDir() { 219 | return err 220 | } 221 | testData := Must(path).Bytes() 222 | f, err := os.OpenFile(filepath.Join(targetTestFile, filepath.FromSlash(path)), os.O_RDONLY, 0) 223 | if err != nil { 224 | return err 225 | } 226 | data, err := ioutil.ReadAll(f) 227 | f.Close() 228 | if err != nil { 229 | return err 230 | } 231 | if bytes.Compare(data, testData) != 0 { 232 | return fmt.Errorf("trees differ after partial extract") 233 | } 234 | return nil 235 | }) 236 | if err != nil { 237 | t.Fatal(err) 238 | } 239 | } 240 | func TestHttpHandler(t *testing.T) { 241 | for p := range fidx { 242 | asset := Get(p) 243 | if asset == nil { 244 | t.Fatalf("asset %s nout found", p) 245 | } 246 | handler := http.HandlerFunc(HTTPHandlerWithPrefix("/")) 247 | req, err := http.NewRequest("GET", path.Join("/", p), nil) 248 | if err != nil { 249 | t.Fatal(err) 250 | } 251 | rr := httptest.NewRecorder() 252 | handler.ServeHTTP(rr, req) 253 | if status := rr.Code; status != http.StatusOK { 254 | t.Fatalf("handler returned wrong status code for %s: got %v want %v", p, status, http.StatusOK) 255 | } 256 | if getTag(rr.Body.Bytes()) != asset.tag { 257 | t.Fatalf("handler returned content doesn't match with recorded for %s", p) 258 | } 259 | if rr.Header().Get("Content-Type") != asset.mime { 260 | t.Fatalf("handler returned content type doesn't match with recorded for %s: %s %s", p, rr.Header().Get("Content-Type"), asset.mime) 261 | } 262 | req, err = http.NewRequest("GET", path.Join("/", p), nil) 263 | if err != nil { 264 | t.Fatal(err) 265 | } 266 | req.Header.Set("If-None-Match", fmt.Sprintf("\"%s\"", asset.tag)) 267 | rr = httptest.NewRecorder() 268 | handler.ServeHTTP(rr, req) 269 | if status := rr.Code; status != http.StatusNotModified { 270 | t.Fatalf("handler returned wrong status code for %s: got %v want %v", p, status, http.StatusNotModified) 271 | } 272 | } 273 | req, err := http.NewRequest("GET", path.Join("/", randomName), nil) 274 | if err != nil { 275 | t.Fatal(err) 276 | } 277 | rr := httptest.NewRecorder() 278 | handler := http.HandlerFunc(HTTPHandlerWithPrefix("/")) 279 | handler.ServeHTTP(rr, req) 280 | if status := rr.Code; status != http.StatusNotFound { 281 | t.Fatalf("handler returned wrong status code: got %v want %v", status, http.StatusNotFound) 282 | } 283 | } 284 | func TestHttpFileSystem(t *testing.T) { 285 | FS().Walk("", func(p string, info os.FileInfo, err error) error { 286 | if err != nil || info.IsDir() { 287 | return nil 288 | } 289 | asset := Get(p) 290 | if asset == nil { 291 | return fmt.Errorf("asset %s nout found", p) 292 | } 293 | req, err := http.NewRequest("GET", path.Join("/", p), nil) 294 | if err != nil { 295 | return err 296 | } 297 | rr := httptest.NewRecorder() 298 | handler := http.FileServer(FS().HttpFileSystem()) 299 | handler.ServeHTTP(rr, req) 300 | if status := rr.Code; status != http.StatusOK { 301 | return fmt.Errorf("handler returned wrong status code for %s: got %v want %v", p, status, http.StatusOK) 302 | } 303 | if getTag(rr.Body.Bytes()) != asset.tag { 304 | return fmt.Errorf("handler returned content doesn't match with recorded for %s", p) 305 | } 306 | if rr.Header().Get("Content-Type") != asset.mime { 307 | return fmt.Errorf("handler returned content type doesn't match with recorded for %s: %s %s", p, rr.Header().Get("Content-Type"), asset.mime) 308 | } 309 | return nil 310 | }) 311 | } 312 | 313 | var contentDiffers = errors.New("asset content differs") 314 | 315 | func checkFileContent(fs FileSystem, name string, content []byte) error { 316 | file, err := fs.Open(name) 317 | if err != nil { 318 | return err 319 | } 320 | newContent, err := ioutil.ReadAll(file) 321 | file.Close() 322 | if err != nil { 323 | return err 324 | } 325 | if bytes.Compare(content, newContent) != 0 { 326 | return contentDiffers 327 | } 328 | return nil 329 | } 330 | 331 | func TestUnionFs(t *testing.T) { 332 | tmp, err := ioutil.TempDir(os.TempDir(), ".site-test") 333 | if err != nil { 334 | t.Fatal(err) 335 | } 336 | defer rmtree(tmp) 337 | fs, err := NewUnionFS(tmp) 338 | if err != nil { 339 | t.Fatal(err) 340 | } 341 | var testFile string 342 | var testFileData []byte 343 | cnt := 0 344 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 345 | if err != nil { 346 | return err 347 | } 348 | cnt++ 349 | if testFile == "" && !info.IsDir() { 350 | testFile = path 351 | testFileData = Must(path).Bytes() 352 | } 353 | return nil 354 | }) 355 | if err != nil { 356 | t.Fatal(err) 357 | } 358 | if err = CopyTo(tmp, 0640, false); err != nil { 359 | t.Fatal(err) 360 | } 361 | if err = checkFileContent(fs, testFile, testFileData); err != nil { 362 | t.Fatal(err) 363 | } 364 | testTarget := filepath.Join(tmp, filepath.FromSlash(testFile)) 365 | file, err := os.OpenFile(testTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 366 | if err != nil { 367 | t.Fatal(err) 368 | } 369 | _, err = file.WriteString(randomName) 370 | file.Close() 371 | if err != nil { 372 | t.Fatal(err) 373 | } 374 | if err = checkFileContent(fs, testFile, testFileData); err == nil { 375 | t.Fatalf("content is not changed") 376 | } else if err != contentDiffers { 377 | t.Fatal(err) 378 | } 379 | if err = os.Remove(testTarget); err != nil { 380 | t.Fatal(err) 381 | } 382 | if err = checkFileContent(fs, testFile, testFileData); err != nil { 383 | t.Fatal(err) 384 | } 385 | testTarget = filepath.Join(tmp, randomName) 386 | file, err = os.OpenFile(testTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 387 | if err != nil { 388 | t.Fatal(err) 389 | } 390 | _, err = file.Write(testFileData) 391 | file.Close() 392 | if err != nil { 393 | t.Fatal(err) 394 | } 395 | if err = checkFileContent(fs, randomName, testFileData); err != nil { 396 | t.Fatal(err) 397 | } 398 | if err = os.Remove(testTarget); err != nil { 399 | t.Fatal(err) 400 | } 401 | if _, err = fs.Stat(randomName); err != os.ErrNotExist { 402 | t.Fatalf("temp file is not removed") 403 | } 404 | rcnt := 0 405 | err = fs.Walk("", func(path string, info os.FileInfo, err error) error { 406 | if err != nil { 407 | return err 408 | } 409 | rcnt++ 410 | if !info.IsDir() { 411 | if err = checkFileContent(fs, path, Must(path).Bytes()); err != nil { 412 | return err 413 | } 414 | } 415 | return nil 416 | }) 417 | if err != nil { 418 | t.Fatal(err) 419 | } 420 | if cnt != rcnt { 421 | t.Fatalf("number of walked items differ (%d != %d)", cnt, rcnt) 422 | } 423 | } -------------------------------------------------------------------------------- /example/site/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Page not found 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | Requested page not found 14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /example/site/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | @import url(https://cdn.rawgit.com/tonsky/FiraCode/1.204/distr/fira_code.css); 3 | 4 | body { 5 | font-size: 16px; 6 | line-height: 1.6; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 8 | color: #242424; 9 | max-width: 800px; 10 | margin: 0% auto; 11 | } 12 | footer { 13 | text-align: center; 14 | } 15 | h1, h2, h3, h4, h5, h1 a { 16 | color: #263A48; 17 | font-weight: 500; 18 | text-decoration: none; 19 | } 20 | h1, h2 { 21 | font-size: 26px; 22 | border-bottom: 1px solid #eee 23 | } 24 | h2 { 25 | font-size: 22px; 26 | padding-bottom: 0.6em; 27 | } 28 | h3 { 29 | font-size: 18px; 30 | margin-bottom: 0.3em; 31 | } 32 | h1 small a { 33 | color: #98999C; 34 | font-size: 15px; 35 | font-weight: normal; 36 | float: right; 37 | position: absolute; 38 | top: 15px; 39 | right: 20px; 40 | } 41 | section { 42 | background: #fff; 43 | position: relative; 44 | } 45 | #license { 46 | font-size: 10pt; 47 | } 48 | 49 | .centered-picture { 50 | text-align: center; 51 | } 52 | 53 | blockquote { 54 | border-left: 3px solid #d54e21; 55 | font-size: 16px; 56 | padding: 0 0 0 20px; 57 | color: #d54e21; 58 | } 59 | blockquote a { 60 | color: #d54e21; 61 | font-weight: 500; 62 | } 63 | blockquote code { 64 | color: #d54e21; 65 | } 66 | pre { 67 | background-color: #f0f3f3; 68 | padding: 1em; 69 | margin-bottom: 1em; 70 | } 71 | code, cite { 72 | font-family: "Fira Code", "Lucida Console", Monaco, monospace; 73 | line-height: 1.0; 74 | } 75 | a { 76 | color: #1e8cbe; 77 | text-decoration: underline; 78 | } 79 | a:hover { 80 | color: #d54e21; 81 | } 82 | ul { 83 | list-style: none; 84 | padding-left: 0; 85 | } 86 | ol { 87 | list-style: number; 88 | } 89 | ol li { 90 | margin-left: -1em; 91 | } 92 | ol li:last-child { 93 | margin-bottom: 0; 94 | } 95 | ul ul { 96 | padding-top: 0; 97 | margin-bottom: 0; 98 | margin-left: 4%; 99 | } 100 | ul ul li:before { 101 | content: '-'; 102 | display: inline-block; 103 | padding-right: 2%; 104 | } 105 | 106 | ul.col-2 { 107 | color: #98999C; 108 | -webkit-column-count: 2; 109 | -moz-column-count: 2; 110 | column-count: 2; 111 | -webkit-column-gap: 20px; 112 | -moz-column-gap: 20px; 113 | column-gap: 20px; 114 | } 115 | 116 | @media screen and (min-width: 500px) { 117 | ul.col-2 { 118 | -webkit-column-count: 3; 119 | -moz-column-count: 3; 120 | column-count: 3; 121 | -webkit-column-gap: 20px; 122 | -moz-column-gap: 20px; 123 | column-gap: 20px; 124 | } 125 | } 126 | nav { 127 | background: #F0F1F3; 128 | min-width: 215px; 129 | margin-bottom: 5px; 130 | margin-top: 15px; 131 | } 132 | nav:first-of-type a { 133 | color: #d54e21; 134 | border-radius: 0; 135 | } 136 | nav:first-of-type a:hover { 137 | color: #d54e21; 138 | } 139 | nav:first-of-type a:before { 140 | background-color: #d54e21; 141 | } 142 | nav.affix { 143 | position: fixed; 144 | top: 20px; 145 | } 146 | nav.affix-bottom { 147 | position: absolute; 148 | } 149 | nav a { 150 | border-radius: 3px; 151 | font-size: 15px; 152 | display: block; 153 | cursor: pointer; 154 | font-weight: 500; 155 | position: relative; 156 | text-decoration: none; 157 | padding: 10px 12px; 158 | width: 100%; 159 | padding-right: 3px; 160 | border-bottom: 2px solid #fff; 161 | } 162 | nav a:before { 163 | content: ''; 164 | width: 4px; 165 | display: block; 166 | left: 0; 167 | position: absolute; 168 | height: 100%; 169 | display: none; 170 | background: #1e8cbe; 171 | top: 0; 172 | } 173 | nav a:hover { 174 | background-color: #E6E8EA; 175 | color: #1e8cbe; 176 | text-decoration: underline; 177 | } 178 | nav a:hover:before { 179 | display: block; 180 | } 181 | nav a:last-of-type { 182 | border-bottom: none; 183 | } 184 | -------------------------------------------------------------------------------- /example/site/images/a-nice-picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-imbed/4d4ce97ddf87ffa4bee0f45789ea2f6826f4c4c6/example/site/images/a-nice-picture.jpg -------------------------------------------------------------------------------- /example/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | go-imbed demo 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 40 | 41 | 42 | 43 |
44 | 45 |

go-imbedBack to Top

46 | 47 | 48 | 49 |

go-imbed is a simple Go source generator to embed binary resources into Go executable.

50 | 51 |

Why

52 | 53 |

go-imbed came up as a holiday side project for a very simple case of embedding a REST API documentation into the executable image. There are plenty of tools for embedding binary assets into executable (which clearly shows demand for something what Go lacks at the moment), but hey, why not invent another wheel? Besides, stuffing binary asset into Go source file seemed too unaesthetic to me.

54 | 55 | 56 |
57 | 58 | 59 | 60 |
61 | 62 |

Getting StartedBack to Top

63 | 64 | 65 | 66 |

Installation

67 | 68 |
$ go get -u github.com/growler/go-imbed
 69 | 
70 | 71 |

Usage

72 | 73 |
    74 |
  1. Install go-imbed:

    75 | 76 |
    go get -u github.com/growler/go-imbed
     77 | 
     78 | 
  2. 79 | 80 |
  3. Add a static content tree to target package:

    81 | 82 |
    src
     83 | └── yourpackage
     84 |     ├── code.go
     85 |     └── site
     86 |         ├── static
     87 |         │   └── style.css
     88 |         ├── index.html
     89 |         └── 404.html
     90 | 
  4. 91 | 92 |
  5. Add a go-generate comment to code.go (or any other Go file in yourpackage):

    93 | 94 |
        //go:generate go-imbed site internal/site
     95 |     
     96 |     package ...
     97 |     
  6. 98 | 99 |
  7. Run go generate yourpackage

  8. 100 | 101 |
  9. Start using it:

    102 | 103 |
        package main 
    104 | 
    105 |     import (
    106 |         "net/http"
    107 |         "fmt"
    108 |         "yourpackage/internal/site"
    109 |     )
    110 | 
    111 |     func main() {
    112 |     	http.HandleFunc("/", site.ServeHTTP)
    113 |     	if err := http.ListenAndServe(":9091", nil); err != nil {
    114 |     		fmt.Println(err)   
    115 |  		}
    116 |     }
    117 |     
  10. 118 |
119 | 120 | 121 |
122 | 123 | 124 | 125 |
126 | 127 |

Other embedding toolsBack to Top

128 | 129 |

There are plenty of other tools to consider before starting using go-imbed:

130 | 131 | 138 | 139 | 140 |
141 | 142 | 143 | 144 |
145 | 146 |

Credits and ThanksBack to Top

147 | 148 |

Many thanks go to:

149 | 150 | 156 | 157 | 158 |
159 | 160 | 161 | 162 |
163 | 164 |

Just a nice, peaceful pictureBack to Top

165 | 166 | 167 |
168 | 169 | Croatia, 2013 170 | 171 | 172 |
173 |

174 | Croatia, 2013 175 | 176 | 177 | 178 |

179 |
180 | 181 |
182 | 183 | 184 | 185 |
186 | 187 | 188 | 189 |
190 | 191 |

LicenseBack to Top

192 | 193 |

The MIT License (MIT)

194 | 195 |

Copyright © 2017 Alexey Naidyonov

196 | 197 |

Permission is hereby granted, free of charge, to any person obtaining a copy of 198 | this software and associated documentation files (the “Software”), to deal in 199 | the Software without restriction, including without limitation the rights to 200 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 201 | the Software, and to permit persons to whom the Software is furnished to do so, 202 | subject to the following conditions:

203 | 204 |

The above copyright notice and this permission notice shall be included in all 205 | copies or substantial portions of the Software.

206 | 207 |

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 208 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 209 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 210 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 211 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 212 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

213 | 214 | 215 |
216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /example/source/Makefile: -------------------------------------------------------------------------------- 1 | TOP := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) 2 | 3 | HUGO := $(shell which hugo) 4 | 5 | GO := $(shell which go) 6 | 7 | ifeq ($(GO),) 8 | $(error no Go found) 9 | endif 10 | 11 | ifeq ($(HUGO),) 12 | $(error hugo is not found, please install hugo (see https://gohugo.io/getting-started/installing/)) 13 | endif 14 | 15 | ifeq ($(GOPATH),) 16 | $(error no GOPATH set) 17 | endif 18 | 19 | GOBIN := $(shell echo $${GOPATH%%:*})/bin 20 | 21 | export PATH := $(GOBIN):$(PATH) 22 | 23 | all: clean 24 | @mkdir -p $(realpath $(TOP)/..)/site 25 | @$(HUGO) -d $(realpath $(TOP)/..)/site --disableKinds RSS,sitemap 26 | @go install github.com/growler/go-imbed 27 | @go generate github.com/growler/go-imbed/example/... 28 | 29 | clean: 30 | @rm -rf $(realpath $(TOP)/..)/site 31 | 32 | .PHONY: all clean -------------------------------------------------------------------------------- /example/source/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .TranslationBaseName "-" " " | title }}" 3 | date: {{ .Date }} 4 | anchor: "{{ replace .TranslationBaseName "-" " " | title | urlize }}" 5 | weight: 6 | --- 7 | -------------------------------------------------------------------------------- /example/source/config.toml: -------------------------------------------------------------------------------- 1 | baseURL = "/" 2 | title = "go-imbed demo" 3 | theme = "simple" 4 | staticDir = "static" 5 | 6 | [[menu.top]] 7 | name = "go-imbed sources" 8 | weight = 10 9 | url = "https://github.com/growler/go-imbed" 10 | -------------------------------------------------------------------------------- /example/source/content/credits-thanks-and-license.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Credits and Thanks" 3 | anchor: "credits-and-thanks" 4 | weight: 50 5 | --- 6 | Many thanks go to: 7 | 8 | - [Nikita Prokopov](https://github.com/tonsky) for the [best free font for programmers](https://github.com/tonsky/FiraCode) 9 | - [JetBrains](https://www.jetbrains.com) for the [best Go IDE](https://www.jetbrains.com/go/) 10 | - [Hugo](https://github.com/gohugoio/hugo) for static site generating engine 11 | - [Pavel Kanyshev](https://github.com/aerohub) for [hugo-simpledoc-theme](https://github.com/aerohub/hugo-simpledoc-theme) 12 | -------------------------------------------------------------------------------- /example/source/content/getting-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Getting Started" 3 | anchor: "getting-started" 4 | weight: 20 5 | --- 6 | ## Installation 7 | 8 | ```text 9 | $ go get -u github.com/growler/go-imbed 10 | ``` 11 | 12 | ## Usage 13 | 14 | 1. Install `go-imbed`: 15 | 16 | ``` 17 | go get -u github.com/growler/go-imbed 18 | 19 | ``` 20 | 2. Add a static content tree to target package: 21 | 22 | ``` 23 | src 24 | └── yourpackage 25 | ├── code.go 26 | └── site 27 | ├── static 28 | │ └── style.css 29 | ├── index.html 30 | └── 404.html 31 | ``` 32 | 33 | 3. Add a go-generate comment to `code.go` (or any other Go file in `yourpackage`): 34 | 35 | {{< highlight go >}} 36 | //go:generate go-imbed site internal/site 37 | 38 | package ... 39 | {{< /highlight >}} 40 | 41 | 4. Run `go generate yourpackage` 42 | 43 | 5. Start using it: 44 | 45 | {{< highlight go >}} 46 | package main 47 | 48 | import ( 49 | "net/http" 50 | "fmt" 51 | "yourpackage/internal/site" 52 | ) 53 | 54 | func main() { 55 | http.HandleFunc("/", site.ServeHTTP) 56 | if err := http.ListenAndServe(":9091", nil); err != nil { 57 | fmt.Println(err) 58 | } 59 | } 60 | {{< /highlight >}} 61 | -------------------------------------------------------------------------------- /example/source/content/go-imbed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "go-imbed" 3 | anchor: "go-imbed" 4 | weight: 10 5 | --- 6 | `go-imbed` is a simple Go source generator to embed binary resources into Go executable. 7 | 8 | ## Why 9 | 10 | `go-imbed` came up as a holiday side project for a very simple case of embedding a REST API documentation into the executable image. There are [plenty of tools](#other-tools) for embedding binary assets into executable (which clearly shows demand for something what Go lacks at the moment), but hey, why not invent another wheel? Besides, stuffing binary asset into Go source file seemed too unaesthetic to me. 11 | -------------------------------------------------------------------------------- /example/source/content/license.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "License" 3 | anchor: "license" 4 | weight: 60 5 | --- 6 | The MIT License (MIT) 7 | 8 | Copyright (c) 2017 Alexey Naidyonov 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | this software and associated documentation files (the "Software"), to deal in 12 | the Software without restriction, including without limitation the rights to 13 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 14 | the Software, and to permit persons to whom the Software is furnished to do so, 15 | subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 23 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 24 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 25 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /example/source/content/nice-picture.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Just a nice, peaceful picture" 3 | anchor: "a-nice-picture" 4 | weight: 50 5 | --- 6 | 7 | {{
}} 8 | -------------------------------------------------------------------------------- /example/source/content/other-tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Other embedding tools" 3 | anchor: "other-tools" 4 | weight: 40 5 | --- 6 | There are plenty of other tools to consider before starting using `go-imbed`: 7 | 8 | - [go-bindata](https://github.com/jteeuwen/go-bindata) 9 | - [statik](https://github.com/rakyll/statik) 10 | - [esc](https://github.com/mjibson/esc) 11 | - [go.rice](https://github.com/GeertJohan/go.rice) 12 | - [packr](https://github.com/gobuffalo/packr) 13 | -------------------------------------------------------------------------------- /example/source/static/images/a-nice-picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/growler/go-imbed/4d4ce97ddf87ffa4bee0f45789ea2f6826f4c4c6/example/source/static/images/a-nice-picture.jpg -------------------------------------------------------------------------------- /example/source/themes/simple/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .TranslationBaseName "-" " " | title }}" 3 | anchor: "{{ replace .TranslationBaseName "-" " " | title | urlize }}" 4 | weight: 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /example/source/themes/simple/layouts/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 404 Page not found 6 | 7 | {{ .Hugo.Generator }} 8 | 9 | 10 | 11 |
12 | 13 | Requested page not found 14 | 15 |
16 | 17 | -------------------------------------------------------------------------------- /example/source/themes/simple/layouts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {{ .Site.Title }} 7 | 8 | 9 | 10 | {{ .Hugo.Generator }} 11 | 12 | 13 | 14 | 30 | 31 | {{ range .Data.Pages.ByWeight }} 32 | 33 |
34 | 35 |

{{ .Title }}{{ with .Site.Params.bttButton }}{{ . | markdownify }}{{ else }}Back to Top{{ end }}

36 | 37 | {{ .Content }} 38 | 39 |
40 | 41 | {{ end }} 42 | 43 | -------------------------------------------------------------------------------- /example/source/themes/simple/static/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | @import url(https://cdn.rawgit.com/tonsky/FiraCode/1.204/distr/fira_code.css); 3 | 4 | body { 5 | font-size: 16px; 6 | line-height: 1.6; 7 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 8 | color: #242424; 9 | max-width: 800px; 10 | margin: 0% auto; 11 | } 12 | footer { 13 | text-align: center; 14 | } 15 | h1, h2, h3, h4, h5, h1 a { 16 | color: #263A48; 17 | font-weight: 500; 18 | text-decoration: none; 19 | } 20 | h1, h2 { 21 | font-size: 26px; 22 | border-bottom: 1px solid #eee 23 | } 24 | h2 { 25 | font-size: 22px; 26 | padding-bottom: 0.6em; 27 | } 28 | h3 { 29 | font-size: 18px; 30 | margin-bottom: 0.3em; 31 | } 32 | h1 small a { 33 | color: #98999C; 34 | font-size: 15px; 35 | font-weight: normal; 36 | float: right; 37 | position: absolute; 38 | top: 15px; 39 | right: 20px; 40 | } 41 | section { 42 | background: #fff; 43 | position: relative; 44 | } 45 | #license { 46 | font-size: 10pt; 47 | } 48 | 49 | .centered-picture { 50 | text-align: center; 51 | } 52 | 53 | blockquote { 54 | border-left: 3px solid #d54e21; 55 | font-size: 16px; 56 | padding: 0 0 0 20px; 57 | color: #d54e21; 58 | } 59 | blockquote a { 60 | color: #d54e21; 61 | font-weight: 500; 62 | } 63 | blockquote code { 64 | color: #d54e21; 65 | } 66 | pre { 67 | background-color: #f0f3f3; 68 | padding: 1em; 69 | margin-bottom: 1em; 70 | } 71 | code, cite { 72 | font-family: "Fira Code", "Lucida Console", Monaco, monospace; 73 | line-height: 1.0; 74 | } 75 | a { 76 | color: #1e8cbe; 77 | text-decoration: underline; 78 | } 79 | a:hover { 80 | color: #d54e21; 81 | } 82 | ul { 83 | list-style: none; 84 | padding-left: 0; 85 | } 86 | ol { 87 | list-style: number; 88 | } 89 | ol li { 90 | margin-left: -1em; 91 | } 92 | ol li:last-child { 93 | margin-bottom: 0; 94 | } 95 | ul ul { 96 | padding-top: 0; 97 | margin-bottom: 0; 98 | margin-left: 4%; 99 | } 100 | ul ul li:before { 101 | content: '-'; 102 | display: inline-block; 103 | padding-right: 2%; 104 | } 105 | 106 | ul.col-2 { 107 | color: #98999C; 108 | -webkit-column-count: 2; 109 | -moz-column-count: 2; 110 | column-count: 2; 111 | -webkit-column-gap: 20px; 112 | -moz-column-gap: 20px; 113 | column-gap: 20px; 114 | } 115 | 116 | @media screen and (min-width: 500px) { 117 | ul.col-2 { 118 | -webkit-column-count: 3; 119 | -moz-column-count: 3; 120 | column-count: 3; 121 | -webkit-column-gap: 20px; 122 | -moz-column-gap: 20px; 123 | column-gap: 20px; 124 | } 125 | } 126 | nav { 127 | background: #F0F1F3; 128 | min-width: 215px; 129 | margin-bottom: 5px; 130 | margin-top: 15px; 131 | } 132 | nav:first-of-type a { 133 | color: #d54e21; 134 | border-radius: 0; 135 | } 136 | nav:first-of-type a:hover { 137 | color: #d54e21; 138 | } 139 | nav:first-of-type a:before { 140 | background-color: #d54e21; 141 | } 142 | nav.affix { 143 | position: fixed; 144 | top: 20px; 145 | } 146 | nav.affix-bottom { 147 | position: absolute; 148 | } 149 | nav a { 150 | border-radius: 3px; 151 | font-size: 15px; 152 | display: block; 153 | cursor: pointer; 154 | font-weight: 500; 155 | position: relative; 156 | text-decoration: none; 157 | padding: 10px 12px; 158 | width: 100%; 159 | padding-right: 3px; 160 | border-bottom: 2px solid #fff; 161 | } 162 | nav a:before { 163 | content: ''; 164 | width: 4px; 165 | display: block; 166 | left: 0; 167 | position: absolute; 168 | height: 100%; 169 | display: none; 170 | background: #1e8cbe; 171 | top: 0; 172 | } 173 | nav a:hover { 174 | background-color: #E6E8EA; 175 | color: #1e8cbe; 176 | text-decoration: underline; 177 | } 178 | nav a:hover:before { 179 | display: block; 180 | } 181 | nav a:last-of-type { 182 | border-bottom: none; 183 | } 184 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/growler/go-imbed 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /imbed/_templates/index.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // Package {{.Pkg}} holds binary resources embedded into Go executable 4 | package {{.Pkg}} 5 | 6 | import ( 7 | "os" 8 | "io" 9 | "bytes" 10 | "path/filepath" 11 | {{- if .Params.BuildHttpHandlerAPI }} 12 | "strconv" 13 | {{- end }} 14 | {{- if .Params.BuildFsAPI }} 15 | "sort" 16 | {{- end }} 17 | {{- if or .Params.BuildHttpFsAPI .Params.BuildHttpHandlerAPI }} 18 | "net/http" 19 | {{- end }} 20 | {{- if or .Params.BuildHttpHandlerAPI .Params.BuildFsAPI }} 21 | "path" 22 | {{- end }} 23 | {{- if .Params.BuildHttpHandlerAPI }} 24 | "strings" 25 | {{- end }} 26 | {{- if .Params.CompressAssets }} 27 | "compress/gzip" 28 | {{- end }} 29 | {{- if or .Params.BuildFsAPI .Params.CompressAssets }} 30 | "io/ioutil" 31 | {{- end }} 32 | {{- if .Params.BuildMain }} 33 | "flag" 34 | {{- end }} 35 | "time" 36 | ) 37 | 38 | func blob_bytes(uint32) []byte 39 | func blob_string(uint32) string 40 | 41 | // Asset represents binary resource stored within Go executable. Asset implements 42 | // fmt.Stringer and io.WriterTo interfaces, decompressing binary data if necessary. 43 | type Asset struct { 44 | name string // File name 45 | size int32 // File size (uncompressed) 46 | blob []byte // Resource blob []byte 47 | str_blob string // Resource blob as a string 48 | {{- if .Params.CompressAssets }} 49 | isCompressed bool // true if resources was compressed with gzip 50 | {{- end}} 51 | mime string // MIME Type 52 | tag string // Tag is essentially a Tag of resource content and can be used as a value for "Etag" HTTP header 53 | } 54 | 55 | // Name returns the base name of the asset 56 | func (a *Asset) Name() string { return a.name } 57 | // MimeType returns MIME Type of the asset 58 | func (a *Asset) MimeType() string { return a.mime } 59 | // Tag returns a string which can serve as an unique version identifier for the asset (i.e., "Etag") 60 | func (a *Asset) Tag() string { return a.tag } 61 | {{- if .Params.CompressAssets }} 62 | // IsCompressed returns true of asset has been compressed 63 | func (a *Asset) IsCompressed() bool { return a.isCompressed } 64 | {{- end }} 65 | // String returns (uncompressed, if necessary) content of asset as a string 66 | func (a *Asset) String() string { 67 | {{- if .Params.CompressAssets }} 68 | if a.isCompressed { 69 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 70 | ret, _ := ioutil.ReadAll(ungzip) 71 | ungzip.Close() 72 | return string(ret) 73 | } 74 | {{- end }} 75 | return a.str_blob 76 | } 77 | 78 | // Bytes returns (uncompressed) content of asset as a []byte 79 | func (a *Asset) Bytes() []byte { 80 | {{- if .Params.CompressAssets }} 81 | if a.isCompressed { 82 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 83 | ret, _ := ioutil.ReadAll(ungzip) 84 | ungzip.Close() 85 | return ret 86 | } 87 | {{- end }} 88 | ret := make([]byte, len(a.blob)) 89 | copy(ret, a.blob) 90 | return ret 91 | } 92 | {{- if .Params.BuildRawBytesAPI }} 93 | // RawBytes returns a raw byte slice of the asset. Changing content of slice will result into segfault. 94 | func (a *Asset) RawBytes() []byte { 95 | return a.blob 96 | } 97 | {{- end }} 98 | 99 | // Size implements os.FileInfo and returns the size of the asset (uncompressed, if asset has been compressed) 100 | func (a *Asset) Size() int64 { return int64(a.size) } 101 | // Mode implements os.FileInfo and always returns 0444 102 | func (a *Asset) Mode() os.FileMode { return 0444 } 103 | // ModTime implements os.FileInfo and returns the time stamp when this package has been produced (the same value for all the assets) 104 | func (a *Asset) ModTime() time.Time { return stamp } 105 | // IsDir implements os.FileInfo and returns false 106 | func (a *Asset) IsDir() bool { return false } 107 | // Sys implements os.FileInfo and returns nil 108 | func (a *Asset) Sys() interface{} { return a } 109 | 110 | // WriteTo implements io.WriterTo interface and writes content of the asset to w 111 | func (a *Asset) WriteTo(w io.Writer) (int64, error) { 112 | {{- if .Params.CompressAssets }} 113 | if a.isCompressed { 114 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 115 | n, err := io.Copy(w, ungzip) 116 | ungzip.Close() 117 | return n, err 118 | } 119 | {{- end }} 120 | n, err := w.Write(a.blob) 121 | return int64(n), err 122 | } 123 | 124 | type assetReader struct { 125 | bytes.Reader 126 | } 127 | 128 | func (r *assetReader) Close() error { 129 | r.Reset(nil) 130 | return nil 131 | } 132 | 133 | // Returns content of the asset as io.ReaderCloser. 134 | func (a *Asset) Reader() io.ReadCloser { 135 | {{- if .Params.CompressAssets }} 136 | if a.isCompressed { 137 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 138 | return ungzip 139 | } else { 140 | {{- end }} 141 | ret := &assetReader{} 142 | ret.Reset(a.blob) 143 | return ret 144 | {{- if .Params.CompressAssets }} 145 | } 146 | {{- end }} 147 | } 148 | 149 | func cleanPath(path string) string { 150 | path = filepath.Clean(path) 151 | if filepath.IsAbs(path) { 152 | path = path[len(filepath.VolumeName(path)):] 153 | if len(path) > 0 || os.IsPathSeparator(path[0]) { 154 | path = path[1:] 155 | } 156 | } else if path == "." { 157 | return "" 158 | } 159 | return filepath.ToSlash(path) 160 | } 161 | 162 | // Opens asset as an io.ReadCloser. Returns os.ErrNotExist if no asset is found. 163 | {{- if .Params.BuildFsAPI }} 164 | func Open(name string) (File, error) { 165 | return FS().Open(name) 166 | } 167 | {{- else }} 168 | func Open(name string) (io.ReadCloser, error) { 169 | name = cleanPath(name) 170 | if asset, ok := fidx[name]; !ok { 171 | return nil, os.ErrNotExist 172 | } else { 173 | return asset.Reader(), nil 174 | } 175 | } 176 | {{- end }} 177 | 178 | // Gets asset by name. Returns nil if no asset found. 179 | func Get(name string) *Asset { 180 | if entry, ok := fidx[name]; ok { 181 | return entry 182 | } else { 183 | return nil 184 | } 185 | } 186 | 187 | // Get asset by name. Panics if no asset found. 188 | func Must(name string) *Asset { 189 | if entry, ok := fidx[name]; ok { 190 | return entry 191 | } else { 192 | panic("asset " + name + " not found") 193 | } 194 | } 195 | 196 | type directoryAsset struct { 197 | name string 198 | dirs []directoryAsset 199 | files []Asset 200 | } 201 | 202 | var root *directoryAsset 203 | 204 | {{- if or .Params.BuildFsAPI }} 205 | 206 | // A simple FileSystem abstraction 207 | type FileSystem interface { 208 | Open(name string) (File, error) 209 | Stat(name string) (os.FileInfo, error) 210 | // As in filepath.Walk 211 | Walk(root string, walkFunc filepath.WalkFunc) error 212 | {{- if .Params.BuildHttpFsAPI }} 213 | // Returns http.FileSystem interface to use with http.Server 214 | HttpFileSystem() http.FileSystem 215 | {{- end }} 216 | } 217 | 218 | // The CopyTo method extracts all mentioned files 219 | // to a specified location, keeping directory structure. 220 | // If supplied file is a directory, than it will be extracted 221 | // recursively. CopyTo with no file mentioned will extract 222 | // the whole content of the embedded filesystem. 223 | // CopyTo returns error if there is a file with the same name 224 | // at the target location, unless overwrite is set to true, or 225 | // file has the same size and modification file as the extracted 226 | // file. 227 | // {{.Pkg}}.CopyTo(".", mode, false) will effectively 228 | // extract content of the filesystem to the current directory (which 229 | // makes it the most space-wise inefficient self-extracting archive 230 | // ever). 231 | func CopyTo(target string, mode os.FileMode, overwrite bool, files ...string) error { 232 | mode = mode&0777 233 | dirmode := os.ModeDir|((mode&0444)>>2)|mode 234 | if len(files) == 0 { 235 | files = []string{""} 236 | } 237 | for _, file := range files { 238 | file = cleanPath(file) 239 | err := FS().Walk(file, func(path string, info os.FileInfo, err error) error { 240 | if err != nil { 241 | return err 242 | } 243 | targetPath := filepath.Join(target, path) 244 | fi, err := os.Stat(targetPath) 245 | if err == nil { 246 | if info.IsDir() && fi.IsDir() { 247 | return nil 248 | } else if info.IsDir() != fi.IsDir() { 249 | return os.ErrExist 250 | } else if !overwrite { 251 | if info.Size() == fi.Size() && info.ModTime().Equal(fi.ModTime()) { 252 | return nil 253 | } else { 254 | return os.ErrExist 255 | } 256 | } 257 | } 258 | if info.IsDir() { 259 | return os.MkdirAll(targetPath, dirmode) 260 | } 261 | asset := Get(path) 262 | if asset == nil { 263 | return os.ErrNotExist 264 | } 265 | targetPathDir := filepath.Dir(targetPath) 266 | if err = os.MkdirAll(targetPathDir, dirmode); err != nil { 267 | return err 268 | } 269 | dst, err := ioutil.TempFile(targetPathDir, ".imbed") 270 | if err != nil { 271 | return err 272 | } 273 | defer func() { 274 | dst.Close() 275 | os.Remove(dst.Name()) 276 | }() 277 | _, err = asset.WriteTo(dst) 278 | if err != nil { 279 | return err 280 | } 281 | dst.Close() 282 | os.Chtimes(dst.Name(), info.ModTime(), info.ModTime()) 283 | os.Chmod(dst.Name(), mode) 284 | return os.Rename(dst.Name(), targetPath) 285 | }) 286 | if err != nil { 287 | return err 288 | } 289 | } 290 | return nil 291 | } 292 | 293 | type fileInfoSlice []os.FileInfo 294 | func (fis *fileInfoSlice) Len() int { return len(*fis) } 295 | func (fis *fileInfoSlice) Less(i, j int) bool { return (*fis)[i].Name() < (*fis)[j].Name() } 296 | func (fis *fileInfoSlice) Swap(i, j int) { 297 | s := (*fis)[i] 298 | (*fis)[i] = (*fis)[j] 299 | (*fis)[j] = s 300 | } 301 | 302 | func walkRec(fs FileSystem, info os.FileInfo, p string, walkFn filepath.WalkFunc) error { 303 | var ( 304 | dir File 305 | fis fileInfoSlice 306 | err error 307 | ) 308 | err = walkFn(p, info, nil) 309 | if err != nil { 310 | if info.IsDir() && err == filepath.SkipDir { 311 | return nil 312 | } 313 | return err 314 | } 315 | if !info.IsDir() { 316 | return nil 317 | } 318 | dir, err = fs.Open(p) 319 | if err != nil { 320 | return walkFn(p, info, err) 321 | } 322 | fis, err = dir.Readdir(-1) 323 | if err != nil { 324 | return walkFn(p, info, err) 325 | } 326 | sort.Sort(&fis) 327 | for i := range fis { 328 | fn := path.Join(p, fis[i].Name()) 329 | err = walkRec(fs, fis[i], fn, walkFn) 330 | if err != nil { 331 | if !fis[i].IsDir() || err != filepath.SkipDir { 332 | return err 333 | } 334 | } 335 | } 336 | return nil 337 | } 338 | 339 | func walk(fs FileSystem, name string, walkFunc filepath.WalkFunc) error { 340 | var r os.FileInfo 341 | var err error 342 | name = cleanPath(name) 343 | r, err = fs.Stat(name) 344 | if err != nil { 345 | return err 346 | } 347 | return walkRec(fs, r, name, walkFunc) 348 | } 349 | 350 | type assetFs struct{} 351 | 352 | // Returns embedded FileSystem 353 | func FS() FileSystem { 354 | return &assetFs{} 355 | } 356 | 357 | func (fs *assetFs) Walk(root string, walkFunc filepath.WalkFunc) error { 358 | return walk(fs, root, walkFunc) 359 | } 360 | 361 | func (fs *assetFs) Stat(name string) (os.FileInfo, error) { 362 | name = cleanPath(name) 363 | if name == "" { 364 | return root, nil 365 | } 366 | if dir, ok := didx[name]; ok { 367 | return dir, nil 368 | } 369 | if asset, ok := fidx[name]; ok { 370 | return asset, nil 371 | } 372 | return nil, os.ErrNotExist 373 | } 374 | 375 | func (fs *assetFs) Open(name string) (File, error) { 376 | name = cleanPath(name) 377 | if name == "" { 378 | return root.open(""), nil 379 | } 380 | if dir, ok := didx[name]; ok { 381 | return dir.open(name), nil 382 | } 383 | if asset, ok := fidx[name]; ok { 384 | return asset.open(name), nil 385 | } 386 | return nil, os.ErrNotExist 387 | } 388 | 389 | {{- if .Params.BuildHttpFsAPI }} 390 | func (fs *assetFs) HttpFileSystem() http.FileSystem { 391 | return &httpFileSystem{fs: fs} 392 | } 393 | {{- end }} 394 | 395 | 396 | // A File is returned by virtual FileSystem's Open method. 397 | // The methods should behave the same as those on an *os.File. 398 | type File interface { 399 | io.Closer 400 | io.Reader 401 | io.Seeker 402 | Readdir(count int) ([]os.FileInfo, error) 403 | Stat() (os.FileInfo, error) 404 | } 405 | 406 | func (a *Asset) open(name string) File { 407 | {{- if .Params.CompressAssets }} 408 | if a.isCompressed { 409 | ret := &assetCompressedFile{ 410 | asset: a, 411 | name: name, 412 | } 413 | ret.Reset(bytes.NewReader(a.blob)) 414 | return ret 415 | } else { 416 | {{- end }} 417 | ret := &assetFile{ 418 | asset: a, 419 | name: name, 420 | } 421 | ret.Reset(a.blob) 422 | return ret 423 | {{- if .Params.CompressAssets }} 424 | } 425 | {{- end }} 426 | } 427 | 428 | func (d *directoryAsset) open(name string) File { 429 | return &directoryAssetFile{ 430 | dir: d, 431 | name: name, 432 | pos: 0, 433 | } 434 | } 435 | 436 | type directoryAssetFile struct { 437 | dir *directoryAsset 438 | name string 439 | pos int 440 | } 441 | 442 | func (d *directoryAssetFile) Name() string { 443 | return d.name 444 | } 445 | 446 | func (d *directoryAssetFile) checkClosed() error { 447 | if d.pos < 0 { 448 | return os.ErrClosed 449 | } 450 | return nil 451 | } 452 | 453 | func (d *directoryAssetFile) Close() error { 454 | if err := d.checkClosed(); err != nil { 455 | return err 456 | } 457 | d.pos = -1 458 | return nil 459 | } 460 | 461 | func (d *directoryAssetFile) Read([]byte) (int, error) { 462 | if err := d.checkClosed(); err != nil { 463 | return 0, err 464 | } 465 | return 0, io.EOF 466 | } 467 | 468 | func (d *directoryAssetFile) Stat() (os.FileInfo, error) { 469 | if err := d.checkClosed(); err != nil { 470 | return nil, err 471 | } 472 | return d.dir, nil 473 | } 474 | 475 | func (d *directoryAssetFile) Seek(pos int64, whence int) (int64, error) { 476 | if err := d.checkClosed(); err != nil { 477 | return 0, err 478 | } 479 | return 0, os.ErrInvalid 480 | } 481 | 482 | func (d *directoryAssetFile) Readdir(count int) ([]os.FileInfo, error) { 483 | if err := d.checkClosed(); err != nil { 484 | return nil, err 485 | } 486 | var ( 487 | last int 488 | total = len(d.dir.dirs) + len(d.dir.files) 489 | ) 490 | if d.pos > total { 491 | if count > 0 { 492 | return nil, io.EOF 493 | } else { 494 | return nil, nil 495 | } 496 | } 497 | if count <= 0 || (d.pos + count) <= total { 498 | last = total 499 | } else { 500 | last = d.pos + count 501 | } 502 | ret := make([]os.FileInfo, 0, last - d.pos) 503 | if d.pos < len(d.dir.dirs) { 504 | var stop int 505 | if last > len(d.dir.dirs) { 506 | stop = len(d.dir.dirs) 507 | } else { 508 | stop = last 509 | } 510 | for i := d.pos; i < stop; i++ { 511 | ret = append(ret, &d.dir.dirs[i]) 512 | } 513 | d.pos = stop 514 | } 515 | var start, stop int 516 | start = d.pos - len(d.dir.dirs) 517 | stop = last - len(d.dir.dirs) 518 | for i := start; i < stop; i++ { 519 | ret = append(ret, &d.dir.files[i]) 520 | } 521 | d.pos = last 522 | return ret, nil 523 | } 524 | 525 | func (d *directoryAsset) Name() string { return d.name } 526 | func (d *directoryAsset) Size() int64 { return 0 } 527 | func (d *directoryAsset) Mode() os.FileMode { return os.ModeDir | 0555 } 528 | func (d *directoryAsset) ModTime() time.Time { return stamp } 529 | func (d *directoryAsset) IsDir() bool { return true } 530 | func (d *directoryAsset) Sys() interface{} { return d } 531 | 532 | type assetFile struct { 533 | assetReader 534 | name string 535 | asset *Asset 536 | } 537 | 538 | func (a *assetFile) Name() string { 539 | return a.name 540 | } 541 | 542 | func (a *assetFile) Stat() (os.FileInfo, error) { 543 | return a.asset, nil 544 | } 545 | 546 | func (a *assetFile) Readdir(int) ([]os.FileInfo, error) { 547 | return nil, os.ErrInvalid 548 | } 549 | 550 | {{- if .Params.CompressAssets }} 551 | type assetCompressedFile struct { 552 | gzip.Reader 553 | name string 554 | asset *Asset 555 | } 556 | 557 | func (a *assetCompressedFile) Name() string { 558 | return a.name 559 | } 560 | 561 | func (a *assetCompressedFile) Stat() (os.FileInfo, error) { 562 | return a.asset, nil 563 | } 564 | 565 | func (a *assetCompressedFile) Seek(int64, int) (int64, error) { 566 | return 0, os.ErrInvalid 567 | } 568 | 569 | func (a *assetCompressedFile) Readdir(count int) ([]os.FileInfo, error) { 570 | return nil, os.ErrInvalid 571 | } 572 | 573 | {{- end }} 574 | {{- end }} 575 | 576 | {{- if .Params.BuildUnionFsAPI }} 577 | 578 | type unionFs struct { 579 | root string 580 | } 581 | 582 | func NewUnionFS(src string) (FileSystem, error) { 583 | abs, err := filepath.Abs(src) 584 | if err != nil { 585 | return nil, err 586 | } 587 | return &unionFs{ 588 | root: abs, 589 | }, nil 590 | } 591 | 592 | func (fs *unionFs) Stat(name string) (os.FileInfo, error) { 593 | name = cleanPath(name) 594 | fname := filepath.Join(fs.root, filepath.FromSlash(name)) 595 | fi, err := os.Stat(fname) 596 | if err == nil { 597 | return fi, nil 598 | } 599 | return FS().Stat(name) 600 | } 601 | 602 | func (fs *unionFs) Open(name string) (File, error) { 603 | name = cleanPath(name) 604 | fname := filepath.Join(fs.root, filepath.FromSlash(name)) 605 | fi, err := os.Stat(fname) 606 | if err == nil { 607 | file, err := os.OpenFile(fname, os.O_RDONLY, 0) 608 | if err == nil { 609 | if !fi.IsDir() { 610 | return &unionFsFile{ 611 | name: name, 612 | file: file, 613 | }, nil 614 | } else { 615 | dir, _ := didx[name] 616 | return &unionFsDirectoryFile{ 617 | name: name, 618 | dir: dir, 619 | fsDir: file, 620 | pos: 0, 621 | }, nil 622 | } 623 | } 624 | } 625 | return FS().Open(name) 626 | } 627 | 628 | func (fs *unionFs) Walk(root string, walkFunc filepath.WalkFunc) error { 629 | return walk(fs, root, walkFunc) 630 | } 631 | 632 | {{- if .Params.BuildHttpFsAPI }} 633 | func (fs *unionFs) HttpFileSystem() http.FileSystem { 634 | return &httpFileSystem{fs: fs} 635 | } 636 | {{- end }} 637 | 638 | type unionFsFile struct { 639 | name string 640 | file *os.File 641 | } 642 | 643 | func (f *unionFsFile) Name() string { return f.name } 644 | func (f *unionFsFile) Close() error { return f.file.Close() } 645 | func (f *unionFsFile) Read(d []byte) (int, error) { return f.file.Read(d) } 646 | func (f *unionFsFile) Stat() (os.FileInfo, error) { return f.file.Stat() } 647 | func (f *unionFsFile) Seek(pos int64, whence int) (int64, error) { return f.file.Seek(pos, whence) } 648 | func (f *unionFsFile) Readdir(count int) ([]os.FileInfo, error) { return f.file.Readdir(count) } 649 | 650 | type unionFsDirectoryFile struct { 651 | name string 652 | dir *directoryAsset 653 | fsDir *os.File 654 | pos int 655 | } 656 | 657 | func (d *unionFsDirectoryFile) Name() string { return d.name } 658 | func (d *unionFsDirectoryFile) Close() error { 659 | if d.fsDir == nil { 660 | return os.ErrClosed 661 | } 662 | err := d.fsDir.Close() 663 | d.fsDir = nil 664 | return err 665 | } 666 | 667 | func (d *unionFsDirectoryFile) Read([]byte) (int, error) { 668 | if d.fsDir == nil { 669 | return 0, os.ErrClosed 670 | } 671 | return 0, io.EOF 672 | } 673 | 674 | func (d *unionFsDirectoryFile) Stat() (os.FileInfo, error) { 675 | if d.fsDir == nil { 676 | return nil, os.ErrClosed 677 | } 678 | return d.fsDir.Stat() 679 | } 680 | 681 | func (d *unionFsDirectoryFile) Seek(pos int64, whence int) (int64, error) { 682 | if d.fsDir == nil { 683 | return 0, os.ErrClosed 684 | } 685 | return 0, os.ErrInvalid 686 | } 687 | func (d *unionFsDirectoryFile) Readdir(count int) ([]os.FileInfo, error) { 688 | if d.fsDir == nil { 689 | return nil, os.ErrClosed 690 | } 691 | if d.pos < 0 { 692 | if count > 0 { 693 | return nil, io.EOF 694 | } else { 695 | return nil, nil 696 | } 697 | } 698 | if d.dir == nil { 699 | return d.fsDir.Readdir(count) 700 | } 701 | ret, err := d.fsDir.Readdir(count) 702 | if count > 0 && err == nil { 703 | return ret, err 704 | } 705 | embedded := make([]os.FileInfo, 0, len(d.dir.dirs) + len(d.dir.files)) 706 | for i := range d.dir.dirs { 707 | embedded = append(embedded, &d.dir.dirs[i]) 708 | } 709 | for i := range d.dir.files { 710 | embedded = append(embedded, &d.dir.files[i]) 711 | } 712 | for _, fi := range embedded[d.pos:] { 713 | if count > 0 && len(ret) >= count { 714 | return ret, nil 715 | } 716 | d.pos++ 717 | if _, err := os.Stat(filepath.Join(d.fsDir.Name(), fi.Name())); err == nil { 718 | continue 719 | } 720 | ret = append(ret, fi) 721 | } 722 | d.pos = -1 723 | return ret, nil 724 | } 725 | 726 | {{- end }} 727 | 728 | {{- if .Params.BuildHttpFsAPI }} 729 | type httpFileSystem struct { 730 | fs FileSystem 731 | } 732 | func (fs *httpFileSystem) Open(name string) (http.File, error) { 733 | return fs.fs.Open(name) 734 | } 735 | func HttpFileSystem() http.FileSystem { 736 | return FS().HttpFileSystem() 737 | } 738 | {{- end }} 739 | 740 | var fidx = make(map[string]*Asset) 741 | var didx = make(map[string]*directoryAsset) 742 | var stamp time.Time 743 | 744 | func init() { 745 | stamp = time.Unix({{.Date}}).UTC() 746 | bb := blob_bytes({{.Size}}) 747 | bs := blob_string({{.Size}}) 748 | {{ .DirectoryCode -}} 749 | {{ .IndexCode -}} 750 | } 751 | 752 | {{- if .Params.BuildHttpHandlerAPI }} 753 | {{- if .Has404Asset }} 754 | var http404Asset *Asset 755 | {{- end }} 756 | // ServeHTTP provides a convenience handler whenever embedded content should be served from the root URI. 757 | var ServeHTTP = HTTPHandlerWithPrefix("") 758 | 759 | // HTTPHandlerWithPrefix provides a simple way to serve embedded content via 760 | // Go standard HTTP server and returns an http handler function. The "prefix" 761 | // will be stripped from the request URL to serve embedded content from non-root URI 762 | func HTTPHandlerWithPrefix(prefix string) func(http.ResponseWriter, *http.Request) { 763 | return func(w http.ResponseWriter, req *http.Request) { 764 | if req.Method != "GET" && req.Method != "HEAD" { 765 | w.WriteHeader(http.StatusMethodNotAllowed) 766 | return 767 | } 768 | if !strings.HasPrefix(req.URL.Path, prefix) { 769 | http.NotFound(w, req) 770 | return 771 | } 772 | reqPath := req.URL.Path[len(prefix):] 773 | if strings.HasPrefix(reqPath, "/") { 774 | reqPath = reqPath[1:] 775 | } 776 | var status = http.StatusOK 777 | asset, ok := fidx[reqPath] 778 | if !ok { 779 | asset, ok = fidx[path.Join(reqPath, "index.html")] 780 | } 781 | {{- if .Has404Asset }} 782 | if !ok { 783 | asset = http404Asset 784 | status = http.StatusNotFound 785 | } 786 | {{- else }} 787 | if !ok { 788 | http.NotFound(w, req) 789 | return 790 | } 791 | {{- end }} 792 | if tag := req.Header.Get("If-None-Match"); tag != "" { 793 | if strings.HasPrefix("W/", tag) || strings.HasPrefix("w/", tag) { 794 | tag = tag[2:] 795 | } 796 | if tag, err := strconv.Unquote(tag); err == nil && tag == asset.tag { 797 | w.WriteHeader(http.StatusNotModified) 798 | return 799 | } 800 | } 801 | if mtime := req.Header.Get("If-Modified-Since"); mtime != "" { 802 | if ts, err := http.ParseTime(mtime); err == nil && !ts.Before(stamp) { 803 | w.WriteHeader(http.StatusNotModified) 804 | return 805 | } 806 | } 807 | {{- if .Params.CompressAssets }} 808 | var deflate = asset.isCompressed 809 | if encs, ok := req.Header["Accept-Encoding"]; ok { 810 | for _, enc := range encs { 811 | if strings.Contains(enc, "gzip") { 812 | if deflate { 813 | w.Header().Set("Content-Encoding", "gzip") 814 | } 815 | deflate = false 816 | break 817 | } 818 | } 819 | } 820 | if !deflate { 821 | w.Header().Set("Content-Length", strconv.FormatInt(int64(len(asset.blob)), 10)) 822 | } 823 | {{- else }} 824 | w.Header().Set("Content-Length", strconv.FormatInt(int64(asset.size), 10)) 825 | {{- end }} 826 | w.Header().Set("Content-Type", asset.mime) 827 | w.Header().Set("Etag", strconv.Quote(asset.tag)) 828 | w.Header().Set("Last-Modified", stamp.Format(http.TimeFormat)) 829 | w.WriteHeader(status) 830 | if req.Method != "HEAD" { 831 | {{- if .Params.CompressAssets }} 832 | if deflate { 833 | ungzip, _ := gzip.NewReader(bytes.NewReader(asset.blob)) 834 | defer ungzip.Close() 835 | io.Copy(w, ungzip) 836 | } else { 837 | {{- end }} 838 | w.Write(asset.blob) 839 | {{- if .Params.CompressAssets }} 840 | } 841 | {{- end }} 842 | } 843 | } 844 | } 845 | {{- end}} 846 | 847 | {{- if .Params.BuildMain }} 848 | var ( 849 | listenAddr string 850 | cert string 851 | key string 852 | extract string 853 | list bool 854 | help bool 855 | ) 856 | 857 | func init() { 858 | flag.BoolVar(&help, "help", false, "prints help") 859 | flag.BoolVar(&list, "list", false, "list contents and exit") 860 | flag.StringVar(&extract, "extract", "", "extract contents to the target `directory` and exit") 861 | flag.StringVar(&listenAddr, "listen", ":8080", "socket `address` to listen") 862 | flag.StringVar(&cert, "tls-cert", "", "TLS certificate `file` to use") 863 | flag.StringVar(&key, "tls-key", "", "TLS key `file` to use") 864 | } 865 | 866 | func main() { 867 | var tls bool 868 | var err error 869 | flag.Parse() 870 | if help { 871 | flag.Usage() 872 | return 873 | } 874 | if list { 875 | FS().Walk("", func(path string, info os.FileInfo, err error) error { 876 | if info.IsDir() { 877 | return nil 878 | } 879 | os.Stdout.WriteString(path) 880 | os.Stdout.WriteString("\n") 881 | return nil 882 | }) 883 | return 884 | } 885 | if extract != "" { 886 | if err = CopyTo(extract, 0640, false); err != nil { 887 | os.Stderr.WriteString("error extracting content: ") 888 | os.Stderr.WriteString(err.Error()) 889 | os.Stderr.WriteString("\n") 890 | os.Exit(1) 891 | } 892 | return 893 | } 894 | if cert != "" && key != "" { 895 | tls = true 896 | } else if cert != "" || key != "" { 897 | os.Stderr.WriteString("both cert and key must be supplied for HTTPS\n") 898 | os.Exit(1) 899 | } 900 | http.HandleFunc("/", HTTPHandlerWithPrefix("/")) 901 | if tls { 902 | err = http.ListenAndServeTLS(listenAddr, cert, key, nil) 903 | } else { 904 | err = http.ListenAndServe(listenAddr, nil) 905 | } 906 | if err != nil { 907 | os.Stderr.WriteString(err.Error()) 908 | os.Stderr.WriteString("\n") 909 | os.Exit(1) 910 | } 911 | } 912 | {{ end }} 913 | -------------------------------------------------------------------------------- /imbed/_templates/index_386.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAL ·d(SB), AX 7 | MOVL AX, ret+4(FP) 8 | MOVL len+0(FP), AX 9 | MOVL AX, ret+8(FP) 10 | MOVL AX, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | LEAL ·d(SB), AX 15 | MOVL AX, ret+4(FP) 16 | MOVL len+0(FP), AX 17 | MOVL AX, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /imbed/_templates/index_amd64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAQ ·d(SB), AX 7 | MOVQ AX, ret+8(FP) 8 | MOVL len+0(FP), AX 9 | MOVLQSX AX, AX 10 | MOVQ AX, ret+16(FP) 11 | MOVQ AX, ret+24(FP) 12 | RET 13 | 14 | TEXT ·blob_string(SB),NOSPLIT,$0-4 15 | LEAQ ·d(SB), AX 16 | MOVQ AX, ret+8(FP) 17 | MOVL len+0(FP), AX 18 | MOVLQSX AX, AX 19 | MOVQ AX, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /imbed/_templates/index_arm.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | MOVW $·d(SB), R0 7 | MOVW R0, ret+4(FP) 8 | MOVW len+0(FP), R0 9 | MOVW R0, ret+8(FP) 10 | MOVW R0, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | MOVW $·d(SB), R0 15 | MOVW R0, ret+4(FP) 16 | MOVW len+0(FP), R0 17 | MOVW R0, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /imbed/_templates/index_arm64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 6 | MOVD $·d(SB), R0 7 | MOVD R0, ret+8(FP) 8 | MOVW len+0(FP), R0 9 | MOVD R0, ret+16(FP) 10 | MOVD R0, ret+24(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-8 14 | MOVD $·d(SB), R0 15 | MOVD R0, ret+8(FP) 16 | MOVD len+0(FP), R0 17 | MOVD R0, ret+16(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /imbed/_templates/index_mips64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips64 mips64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVV $·d(SB), R1 9 | MOVV R1, ret+8(FP) 10 | MOVV len+0(FP), R1 11 | MOVV R1, ret+16(FP) 12 | MOVV R1, ret+24(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVV $·d(SB), R1 17 | MOVV R1, ret+8(FP) 18 | MOVV len+0(FP), R1 19 | MOVV R1, ret+16(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /imbed/_templates/index_mipsx.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips mipsle 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 8 | MOVW $·d(SB), R1 9 | MOVW R1, ret+4(FP) 10 | MOVW len+0(FP), R1 11 | MOVW R1, ret+8(FP) 12 | MOVW R1, ret+12(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-4 16 | MOVW $·d(SB), R1 17 | MOVW R1, ret+4(FP) 18 | MOVW len+0(FP), R1 19 | MOVW R1, ret+8(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /imbed/_templates/index_ppc64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build ppc64 ppc64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVD $·d(SB), R3 9 | MOVD R3, ret+8(FP) 10 | MOVD len+0(FP), R3 11 | MOVD R3, ret+16(FP) 12 | MOVD R3, ret+24(FP) 13 | RET 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVD $·d(SB), R3 17 | MOVD R3, ret+8(FP) 18 | MOVD len+0(FP), R3 19 | MOVD R3, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /imbed/_templates/index_s390x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT|NOFRAME,$0-8 6 | MOVD $·d(SB), R0 7 | MOVW len+0(FP), R1 8 | MOVD R1, R2 9 | STMG R0, R2, ret+8(FP) 10 | JMP R14 11 | 12 | TEXT ·blob_string(SB),NOSPLIT|NOFRAME,$0-8 13 | MOVD $·d(SB), R0 14 | MOVW len+0(FP), R1 15 | STMG R0, R1, ret+8(FP) 16 | JMP R14 17 | -------------------------------------------------------------------------------- /imbed/_templates/index_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | package {{.Pkg}} 4 | 5 | import ( 6 | "encoding/base32" 7 | "encoding/binary" 8 | "hash/crc64" 9 | "testing" 10 | "math/rand" 11 | {{- if .Params.BuildFsAPI }} 12 | "os" 13 | "io/ioutil" 14 | "path/filepath" 15 | "bytes" 16 | {{- end }} 17 | {{- if or .Params.BuildFsAPI .Params.BuildHttpHandlerAPI }} 18 | "fmt" 19 | {{- end }} 20 | {{- if or .Params.BuildHttpHandlerAPI .Params.BuildHttpFsAPI }} 21 | "net/http" 22 | "net/http/httptest" 23 | "path" 24 | {{- end }} 25 | {{- if .Params.BuildUnionFsAPI }} 26 | "errors" 27 | {{- end }} 28 | ) 29 | 30 | var randomName = func() string { 31 | var buf [16]byte 32 | binary.LittleEndian.PutUint64(buf[:8], rand.Uint64()) 33 | binary.LittleEndian.PutUint64(buf[8:], rand.Uint64()) 34 | return b32Enc.EncodeToString(buf[:]) 35 | }() 36 | 37 | var b32Enc = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) 38 | 39 | func getTag(data []byte) string { 40 | var crcBuf [8]byte 41 | binary.LittleEndian.PutUint64(crcBuf[:], crc64.Checksum(data, crc64.MakeTable(crc64.ECMA))) 42 | return b32Enc.EncodeToString(crcBuf[:]) 43 | } 44 | 45 | func TestBytes(t *testing.T) { 46 | for n, a := range fidx { 47 | if getTag(a.Bytes()) != a.tag { 48 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 49 | } 50 | } 51 | } 52 | 53 | func TestString(t *testing.T) { 54 | for n, a := range fidx { 55 | if getTag([]byte(a.String())) != a.tag { 56 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 57 | } 58 | } 59 | } 60 | 61 | {{- if .Params.BuildFsAPI }} 62 | func TestWalkOpen(t *testing.T) { 63 | FS().Walk("", func(path string, info os.FileInfo, err error) error { 64 | if err != nil || info.IsDir() { 65 | return nil 66 | } 67 | asset := Get(path) 68 | if asset == nil { 69 | return fmt.Errorf("asset %s nout found", path) 70 | } 71 | if getTag(asset.Bytes()) != asset.tag { 72 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 73 | } 74 | if getTag([]byte(asset.String())) != asset.tag { 75 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 76 | } 77 | rdr := asset.Reader() 78 | data, err := ioutil.ReadAll(rdr) 79 | rdr.Close() 80 | if err != nil { 81 | return err 82 | } 83 | rdr, err = FS().Open(path) 84 | if err != nil { 85 | return err 86 | } 87 | data, err = ioutil.ReadAll(rdr) 88 | rdr.Close() 89 | if err != nil { 90 | return err 91 | } 92 | if getTag(data) != asset.tag { 93 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 94 | } 95 | rdr.Close() 96 | return nil 97 | }) 98 | } 99 | 100 | func rmtree(name string) { 101 | var files []string 102 | var dirs []string 103 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 104 | if err != nil { 105 | return nil 106 | } 107 | if info.IsDir() { 108 | dirs = append(dirs, path) 109 | } else { 110 | files = append(files, path) 111 | } 112 | return nil 113 | }) 114 | for j := len(files) - 1; j >= 0; j-- { 115 | os.Remove(files[j]) 116 | } 117 | for j := len(dirs) - 1; j >= 0; j-- { 118 | os.Remove(dirs[j]) 119 | } 120 | } 121 | 122 | func TestCopyTo(t *testing.T) { 123 | tmp, err := ioutil.TempDir(os.TempDir(), ".test-test") 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | defer rmtree(tmp) 128 | // a whole tree 129 | if err = CopyTo(tmp, 0640, false); err != nil { 130 | t.Fatal(err) 131 | } 132 | var testDir string 133 | var testFile string 134 | var testData []byte 135 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 136 | if err != nil { 137 | return err 138 | } 139 | if info.IsDir() { 140 | if testDir == "" { 141 | testDir = path 142 | } 143 | return nil 144 | } 145 | f, err := os.OpenFile(filepath.Join(tmp, filepath.FromSlash(path)), os.O_RDONLY, 0) 146 | if err != nil { 147 | return err 148 | } 149 | data, err := ioutil.ReadAll(f) 150 | f.Close() 151 | if err != nil { 152 | return err 153 | } 154 | if testFile == "" { 155 | testFile = path 156 | testData = Must(path).Bytes() 157 | } 158 | if bytes.Compare(data, Must(path).Bytes()) != 0 { 159 | return fmt.Errorf("data differs for %s", path) 160 | } 161 | return nil 162 | }) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | if testFile == "" { 167 | // an empty archive? 168 | return 169 | } 170 | // single file 171 | targetTestFile := filepath.Join(tmp, testFile) 172 | os.Remove(targetTestFile) 173 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 174 | t.Fatal(err) 175 | } 176 | f, err := os.OpenFile(targetTestFile, os.O_RDONLY, 0) 177 | if err != nil { 178 | t.Fatal(err) 179 | } 180 | data, err := ioutil.ReadAll(f) 181 | f.Close() 182 | if err != nil { 183 | t.Fatal(err) 184 | } 185 | if bytes.Compare(testData, data) != 0 { 186 | t.Fatalf("data differs for single file extract") 187 | } 188 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 189 | t.Fatalf("expected no error, got %v", err) 190 | } 191 | f, err = os.OpenFile(targetTestFile, os.O_WRONLY | os.O_APPEND, 0600) 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | _, err = f.WriteString(randomName) 196 | f.Close() 197 | if err != nil { 198 | t.Fatal(err) 199 | } 200 | if err = CopyTo(tmp, 0644, false, testFile); err != os.ErrExist { 201 | t.Fatalf("expected os.ErrExist, got %v", err) 202 | } 203 | if err = CopyTo(tmp, 0644, true, testFile); err != nil { 204 | t.Fatal(err) 205 | } 206 | f, err = os.OpenFile(targetTestFile, os.O_RDONLY, 0) 207 | if err != nil { 208 | t.Fatal(err) 209 | } 210 | data, err = ioutil.ReadAll(f) 211 | f.Close() 212 | if err != nil { 213 | t.Fatal(err) 214 | } 215 | if bytes.Compare(testData, data) != 0 { 216 | t.Fatalf("data differs for single file extract") 217 | } 218 | os.Remove(targetTestFile) 219 | if testDir == "" { 220 | // no directories in archive 221 | return 222 | } 223 | targetTestFile = filepath.Join(tmp, randomName) 224 | if err = CopyTo(targetTestFile, 0644, false, testDir); err != nil { 225 | t.Fatal(err) 226 | } 227 | err = FS().Walk(testDir, func(path string, info os.FileInfo, err error) error { 228 | if err != nil || info.IsDir() { 229 | return err 230 | } 231 | testData := Must(path).Bytes() 232 | f, err := os.OpenFile(filepath.Join(targetTestFile, filepath.FromSlash(path)), os.O_RDONLY, 0) 233 | if err != nil { 234 | return err 235 | } 236 | data, err := ioutil.ReadAll(f) 237 | f.Close() 238 | if err != nil { 239 | return err 240 | } 241 | if bytes.Compare(data, testData) != 0 { 242 | return fmt.Errorf("trees differ after partial extract") 243 | } 244 | return nil 245 | }) 246 | if err != nil { 247 | t.Fatal(err) 248 | } 249 | } 250 | {{- end }} 251 | 252 | {{- if .Params.BuildHttpHandlerAPI }} 253 | func TestHttpHandler(t *testing.T) { 254 | for p := range fidx { 255 | asset := Get(p) 256 | if asset == nil { 257 | t.Fatalf("asset %s nout found", p) 258 | } 259 | handler := http.HandlerFunc(HTTPHandlerWithPrefix("/")) 260 | req, err := http.NewRequest("GET", path.Join("/", p), nil) 261 | if err != nil { 262 | t.Fatal(err) 263 | } 264 | rr := httptest.NewRecorder() 265 | handler.ServeHTTP(rr, req) 266 | if status := rr.Code; status != http.StatusOK { 267 | t.Fatalf("handler returned wrong status code for %s: got %v want %v", p, status, http.StatusOK) 268 | } 269 | if getTag(rr.Body.Bytes()) != asset.tag { 270 | t.Fatalf("handler returned content doesn't match with recorded for %s", p) 271 | } 272 | if rr.Header().Get("Content-Type") != asset.mime { 273 | t.Fatalf("handler returned content type doesn't match with recorded for %s: %s %s", p, rr.Header().Get("Content-Type"), asset.mime) 274 | } 275 | req, err = http.NewRequest("GET", path.Join("/", p), nil) 276 | if err != nil { 277 | t.Fatal(err) 278 | } 279 | req.Header.Set("If-None-Match", fmt.Sprintf("\"%s\"", asset.tag)) 280 | rr = httptest.NewRecorder() 281 | handler.ServeHTTP(rr, req) 282 | if status := rr.Code; status != http.StatusNotModified { 283 | t.Fatalf("handler returned wrong status code for %s: got %v want %v", p, status, http.StatusNotModified) 284 | } 285 | } 286 | req, err := http.NewRequest("GET", path.Join("/", randomName), nil) 287 | if err != nil { 288 | t.Fatal(err) 289 | } 290 | rr := httptest.NewRecorder() 291 | handler := http.HandlerFunc(HTTPHandlerWithPrefix("/")) 292 | handler.ServeHTTP(rr, req) 293 | if status := rr.Code; status != http.StatusNotFound { 294 | t.Fatalf("handler returned wrong status code: got %v want %v", status, http.StatusNotFound) 295 | } 296 | } 297 | {{- end }} 298 | 299 | {{- if .Params.BuildHttpFsAPI }} 300 | func TestHttpFileSystem(t *testing.T) { 301 | FS().Walk("", func(p string, info os.FileInfo, err error) error { 302 | if err != nil || info.IsDir() { 303 | return nil 304 | } 305 | asset := Get(p) 306 | if asset == nil { 307 | return fmt.Errorf("asset %s nout found", p) 308 | } 309 | req, err := http.NewRequest("GET", path.Join("/", p), nil) 310 | if err != nil { 311 | return err 312 | } 313 | rr := httptest.NewRecorder() 314 | handler := http.FileServer(FS().HttpFileSystem()) 315 | handler.ServeHTTP(rr, req) 316 | if status := rr.Code; status != http.StatusOK { 317 | return fmt.Errorf("handler returned wrong status code for %s: got %v want %v", p, status, http.StatusOK) 318 | } 319 | if getTag(rr.Body.Bytes()) != asset.tag { 320 | return fmt.Errorf("handler returned content doesn't match with recorded for %s", p) 321 | } 322 | if rr.Header().Get("Content-Type") != asset.mime { 323 | return fmt.Errorf("handler returned content type doesn't match with recorded for %s: %s %s", p, rr.Header().Get("Content-Type"), asset.mime) 324 | } 325 | return nil 326 | }) 327 | } 328 | {{- end }} 329 | 330 | {{- if .Params.BuildUnionFsAPI }} 331 | 332 | var contentDiffers = errors.New("asset content differs") 333 | 334 | func checkFileContent(fs FileSystem, name string, content []byte) error { 335 | file, err := fs.Open(name) 336 | if err != nil { 337 | return err 338 | } 339 | newContent, err := ioutil.ReadAll(file) 340 | file.Close() 341 | if err != nil { 342 | return err 343 | } 344 | if bytes.Compare(content, newContent) != 0 { 345 | return contentDiffers 346 | } 347 | return nil 348 | } 349 | 350 | func TestUnionFs(t *testing.T) { 351 | tmp, err := ioutil.TempDir(os.TempDir(), ".{{.Pkg}}-test") 352 | if err != nil { 353 | t.Fatal(err) 354 | } 355 | defer rmtree(tmp) 356 | fs, err := NewUnionFS(tmp) 357 | if err != nil { 358 | t.Fatal(err) 359 | } 360 | var testFile string 361 | var testFileData []byte 362 | cnt := 0 363 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 364 | if err != nil { 365 | return err 366 | } 367 | cnt++ 368 | if testFile == "" && !info.IsDir() { 369 | testFile = path 370 | testFileData = Must(path).Bytes() 371 | } 372 | return nil 373 | }) 374 | if err != nil { 375 | t.Fatal(err) 376 | } 377 | if err = CopyTo(tmp, 0640, false); err != nil { 378 | t.Fatal(err) 379 | } 380 | if err = checkFileContent(fs, testFile, testFileData); err != nil { 381 | t.Fatal(err) 382 | } 383 | testTarget := filepath.Join(tmp, filepath.FromSlash(testFile)) 384 | file, err := os.OpenFile(testTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 385 | if err != nil { 386 | t.Fatal(err) 387 | } 388 | _, err = file.WriteString(randomName) 389 | file.Close() 390 | if err != nil { 391 | t.Fatal(err) 392 | } 393 | if err = checkFileContent(fs, testFile, testFileData); err == nil { 394 | t.Fatalf("content is not changed") 395 | } else if err != contentDiffers { 396 | t.Fatal(err) 397 | } 398 | if err = os.Remove(testTarget); err != nil { 399 | t.Fatal(err) 400 | } 401 | if err = checkFileContent(fs, testFile, testFileData); err != nil { 402 | t.Fatal(err) 403 | } 404 | testTarget = filepath.Join(tmp, randomName) 405 | file, err = os.OpenFile(testTarget, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) 406 | if err != nil { 407 | t.Fatal(err) 408 | } 409 | _, err = file.Write(testFileData) 410 | file.Close() 411 | if err != nil { 412 | t.Fatal(err) 413 | } 414 | if err = checkFileContent(fs, randomName, testFileData); err != nil { 415 | t.Fatal(err) 416 | } 417 | if err = os.Remove(testTarget); err != nil { 418 | t.Fatal(err) 419 | } 420 | if _, err = fs.Stat(randomName); err != os.ErrNotExist { 421 | t.Fatalf("temp file is not removed") 422 | } 423 | rcnt := 0 424 | err = fs.Walk("", func(path string, info os.FileInfo, err error) error { 425 | if err != nil { 426 | return err 427 | } 428 | rcnt++ 429 | if !info.IsDir() { 430 | if err = checkFileContent(fs, path, Must(path).Bytes()); err != nil { 431 | return err 432 | } 433 | } 434 | return nil 435 | }) 436 | if err != nil { 437 | t.Fatal(err) 438 | } 439 | if cnt != rcnt { 440 | t.Fatalf("number of walked items differ (%d != %d)", cnt, rcnt) 441 | } 442 | } 443 | {{- end }} -------------------------------------------------------------------------------- /imbed/flags.go: -------------------------------------------------------------------------------- 1 | package imbed 2 | 3 | import "bytes" 4 | 5 | type ImbedFlag int 6 | 7 | const ( 8 | // Compress assets whenever possible 9 | CompressAssets ImbedFlag = 1 << iota 10 | 11 | // Build FileSystem API 12 | BuildFsAPI 13 | 14 | // Build union FileSystem API (implies BuildFsAPI) 15 | BuildUnionFsAPI 16 | 17 | // Build http.FileSystem API (implies BuildFsAPI) 18 | BuildHttpFsAPI 19 | 20 | // Build http.Server handler API 21 | BuildHttpHandlerAPI 22 | 23 | // Build raw data access API (dangerous) 24 | BuildRawBytesAPI 25 | 26 | // Build main function (implies BuildHttpHandlerAPI and BuildFsAPI) 27 | BuildMain 28 | 29 | maxFlag uint = iota 30 | ) 31 | 32 | func (f ImbedFlag) has(flag ImbedFlag) bool { 33 | return (f&flag)!=0 34 | } 35 | 36 | func (f ImbedFlag) CompressAssets() bool { return f.has(CompressAssets) } 37 | func (f ImbedFlag) BuildFsAPI() bool { return f.has(BuildFsAPI) } 38 | func (f ImbedFlag) BuildUnionFsAPI() bool { return f.has(BuildUnionFsAPI) } 39 | func (f ImbedFlag) BuildHttpFsAPI() bool { return f.has(BuildHttpFsAPI) } 40 | func (f ImbedFlag) BuildHttpHandlerAPI() bool { return f.has(BuildHttpHandlerAPI) } 41 | func (f ImbedFlag) BuildRawBytesAPI() bool { return f.has(BuildRawBytesAPI) } 42 | func (f ImbedFlag) BuildMain() bool { return f.has(BuildMain) } 43 | 44 | func (f ImbedFlag) Set(s ImbedFlag, c bool) ImbedFlag { 45 | if c { 46 | return f | s 47 | } else { 48 | return f 49 | } 50 | } 51 | 52 | func (f ImbedFlag) String() string { 53 | var buf bytes.Buffer 54 | var add = func(s string) { 55 | if buf.Len() > 0 { 56 | buf.WriteByte(',') 57 | } 58 | buf.WriteString(s) 59 | } 60 | if f.has(CompressAssets) { 61 | add("Compress") 62 | } 63 | if f.has(BuildHttpHandlerAPI) { 64 | add("Http") 65 | } 66 | if f.has(BuildFsAPI) { 67 | add("FS") 68 | } 69 | if f.has(BuildHttpFsAPI) { 70 | add("HttpFS") 71 | } 72 | if f.has(BuildUnionFsAPI) { 73 | add("UnionFS") 74 | } 75 | if f.has(BuildMain) { 76 | add("Main") 77 | } 78 | return buf.String() 79 | } 80 | 81 | -------------------------------------------------------------------------------- /imbed/imbed.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexey Naidyonov. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE.md file. 4 | 5 | /* 6 | Package github.com/growler/go-imbed/imbed 7 | */ 8 | package imbed 9 | 10 | //go:generate go run -tags bootstrap ../cmd.go --no-http-handler --fs _templates internal/templates 11 | 12 | import ( 13 | "bytes" 14 | "compress/gzip" 15 | "encoding/base32" 16 | "encoding/binary" 17 | "fmt" 18 | "go/format" 19 | "hash/crc64" 20 | "io" 21 | "io/ioutil" 22 | "mime" 23 | "os" 24 | "path" 25 | "path/filepath" 26 | "strconv" 27 | "strings" 28 | "time" 29 | ) 30 | 31 | 32 | type directoryAsset struct { 33 | name string 34 | dirs []directoryAsset 35 | files []*fileAsset 36 | } 37 | 38 | type fileAsset struct { 39 | name string 40 | mimeType string 41 | tag string 42 | size int64 43 | isCompressed bool 44 | offStart int 45 | offStop int 46 | } 47 | 48 | func buildIndex(d *directoryAsset, flags ImbedFlag) (string, string, bool) { 49 | var dir bytes.Buffer 50 | var index bytes.Buffer 51 | addIndent(&dir, 1) 52 | dir.WriteString("root = &directoryAsset") 53 | addIndent(&index, 1) 54 | index.WriteString("didx[\"\"] = root\n") 55 | has404Asset := buildDirIndex(flags, &dir, &index, d, "", "root", 1) 56 | dir.WriteRune('\n') 57 | return dir.String(), index.String(), has404Asset 58 | } 59 | 60 | func addIndent(buf *bytes.Buffer, n int) { 61 | for i := 0; i < n; i++ { 62 | buf.WriteByte('\t') 63 | } 64 | } 65 | 66 | func buildDirIndex(flags ImbedFlag, dir *bytes.Buffer, index *bytes.Buffer, d *directoryAsset, p, indexPrefix string, indent int) bool { 67 | var has404Asset bool 68 | dir.WriteString("{\n") 69 | if d.name != "" { 70 | addIndent(dir, indent+1) 71 | fmt.Fprintf(dir, "name: \"%s\",\n", d.name) 72 | } 73 | if len(d.dirs) > 0 { 74 | addIndent(dir, indent+1) 75 | dir.WriteString("dirs: []directoryAsset{\n") 76 | for i := range d.dirs { 77 | addIndent(dir, indent+2) 78 | addIndent(index, 1) 79 | fmt.Fprintf(index, "didx[\"%s\"] = &%s.dirs[%d]\n", path.Join(p, d.dirs[i].name), indexPrefix, i) 80 | buildDirIndex(flags, dir, index, &d.dirs[i], path.Join(p, d.dirs[i].name), fmt.Sprintf("%s.dirs[%d]", indexPrefix, i), indent+2) 81 | dir.WriteString(",\n") 82 | } 83 | addIndent(dir, indent+1) 84 | dir.WriteString("},\n") 85 | } 86 | if len(d.files) > 0 { 87 | addIndent(dir, indent+1) 88 | dir.WriteString("files: []Asset{\n") 89 | for i := range d.files { 90 | fn := d.files[i].name 91 | addIndent(dir, indent+2) 92 | d.files[i].writeDefinition(dir, indent+2, flags) 93 | dir.WriteString(",\n") 94 | addIndent(index, 1) 95 | fmt.Fprintf(index, "fidx[\"%s\"] = &%s.files[%d]\n", path.Join(p, fn), indexPrefix, i) 96 | if flags.has(BuildHttpHandlerAPI) && p == "" && fn == "404.html" { 97 | addIndent(index, 1) 98 | fmt.Fprintf(index, "http404Asset = &%s.files[%d]\n", indexPrefix, i) 99 | has404Asset = true 100 | } 101 | } 102 | addIndent(dir, indent+1) 103 | dir.WriteString("},\n") 104 | } 105 | addIndent(dir, indent) 106 | dir.WriteString("}") 107 | return has404Asset 108 | } 109 | 110 | func (f *fileAsset) writeDefinition(w *bytes.Buffer, ind int, flags ImbedFlag) { 111 | fmt.Fprint(w, "{\n") 112 | addIndent(w, ind+1) 113 | fmt.Fprintf(w, "name: \"%s\",\n", f.name) 114 | addIndent(w, ind+1) 115 | fmt.Fprintf(w, "blob: bb[%d:%d],\n", f.offStart, f.offStop) 116 | addIndent(w, ind+1) 117 | fmt.Fprintf(w, "str_blob: bs[%d:%d],\n", f.offStart, f.offStop) 118 | addIndent(w, ind+1) 119 | fmt.Fprintf(w, "mime: \"%s\",\n", f.mimeType) 120 | addIndent(w, ind+1) 121 | fmt.Fprintf(w, "tag: \"%s\",\n", f.tag) 122 | addIndent(w, ind+1) 123 | fmt.Fprintf(w, "size: %d,\n", f.size) 124 | if flags.has(CompressAssets) { 125 | addIndent(w, ind+1) 126 | fmt.Fprintf(w, "isCompressed: %v,\n", f.isCompressed) 127 | } 128 | addIndent(w, ind) 129 | fmt.Fprint(w, "}") 130 | } 131 | 132 | func (d *directoryAsset) addDirectory(name string) { 133 | if name == "." { 134 | return 135 | } 136 | elts := strings.Split(name, "/") 137 | if len(elts) == 1 { 138 | d.dirs = append(d.dirs, directoryAsset{ 139 | name: elts[0], 140 | }) 141 | } else { 142 | for i := range d.dirs { 143 | if d.dirs[i].name == elts[0] { 144 | d.dirs[i].addDirectory(path.Join(elts[1:]...)) 145 | return 146 | } 147 | } 148 | panic("directory not found") 149 | } 150 | } 151 | 152 | func (d *directoryAsset) addFile(name string, file *fileAsset) { 153 | elts := strings.Split(name, "/") 154 | if len(elts) == 1 { 155 | d.files = append(d.files, file) 156 | } else { 157 | for i := range d.dirs { 158 | if d.dirs[i].name == elts[0] { 159 | d.dirs[i].addFile(path.Join(elts[1:]...), file) 160 | return 161 | } 162 | } 163 | panic("directory not found") 164 | } 165 | } 166 | 167 | var b32Enc = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) 168 | 169 | const objectFileHeaderTemplate = `// Code generated by go-imbed. DO NOT EDIT. 170 | 171 | #include "textflag.h" 172 | 173 | ` 174 | 175 | const objectFileFooterTemplate = `GLOBL ·d(SB),RODATA,$%d 176 | ` 177 | 178 | func writeObjectFileHeader(file *os.File) error { 179 | _, err := file.WriteString(objectFileHeaderTemplate) 180 | return err 181 | } 182 | 183 | func writeObjectFileFooter(file *os.File, size int) error { 184 | _, err := fmt.Fprintf(file, objectFileFooterTemplate, size) 185 | return err 186 | } 187 | 188 | func (a *fileAsset) writeObject(input *os.File, output *os.File, start int, flags ImbedFlag) (int, error) { 189 | var compressor *gzip.Writer 190 | var err error 191 | if _, err := input.Seek(0, 0); err != nil { 192 | return 0, err 193 | } 194 | crc := uint64(0) 195 | crcTable := crc64.MakeTable(crc64.ECMA) 196 | pipeIn, pipeOut := io.Pipe() 197 | if a.isCompressed { 198 | compressor, _ = gzip.NewWriterLevel(pipeOut, gzip.BestCompression) 199 | } 200 | go func() { 201 | buf := make([]byte, 8192) 202 | for { 203 | n, err := input.Read(buf) 204 | if err == io.EOF { 205 | if a.isCompressed { 206 | compressor.Close() 207 | } 208 | break 209 | } else if err != nil { 210 | pipeOut.CloseWithError(err) 211 | return 212 | } 213 | crc = crc64.Update(crc, crcTable, buf[:n]) 214 | if a.isCompressed { 215 | _, err = compressor.Write(buf[:n]) 216 | } else { 217 | _, err = pipeOut.Write(buf[:n]) 218 | } 219 | if err != nil { 220 | pipeOut.CloseWithError(err) 221 | return 222 | } 223 | } 224 | pipeOut.Close() 225 | }() 226 | defer pipeIn.Close() 227 | var buf [8]byte 228 | var _sbuf [32]byte 229 | addr := start 230 | size := 0 231 | read := 0 232 | for { 233 | if read, err = io.ReadFull(pipeIn, buf[:]); err != nil { 234 | if err == io.EOF { 235 | break 236 | } else if err != io.ErrUnexpectedEOF { 237 | return 0, err 238 | } 239 | } 240 | for i := read; i < 8; i++ { 241 | buf[i] = 0 242 | } 243 | var sbuf = _sbuf[0:0] 244 | for i := range buf { 245 | sbuf = append(sbuf, []byte("\\x")...) 246 | if buf[i] < 0x10 { 247 | sbuf = append(sbuf, '0') 248 | } 249 | sbuf = strconv.AppendUint(sbuf, uint64(buf[i]), 16) 250 | } 251 | _, err = fmt.Fprintf(output, "DATA ·d+%d(SB)/8,$\"%s\"\n", addr, string(sbuf)) 252 | if err != nil { 253 | return 0, err 254 | } 255 | size += read 256 | addr += 8 257 | } 258 | var crcBuf [8]byte 259 | binary.LittleEndian.PutUint64(crcBuf[:], crc) 260 | a.tag = b32Enc.EncodeToString(crcBuf[:]) 261 | a.offStart = start 262 | a.offStop = start + size 263 | return addr, nil 264 | } 265 | 266 | func writeGoIndex(file io.Writer, testFile io.Writer, pkg string, root *directoryAsset, addr int, flags ImbedFlag) error { 267 | timestamp := time.Now() 268 | dir, index, has404Asset := buildIndex(root, flags) 269 | buf := bytes.Buffer{} 270 | params := map[string]interface{}{ 271 | "Pkg": pkg, 272 | "Size": addr, 273 | "IndexCode": index, 274 | "DirectoryCode": dir, 275 | "Date": fmt.Sprintf("%d, %d", timestamp.Unix(), timestamp.Nanosecond()), 276 | "Params": flags, 277 | "Has404Asset": flags.BuildHttpHandlerAPI() && has404Asset, 278 | "BuildMain": pkg == "main" && flags.has(BuildMain), 279 | } 280 | err := iMustHazTemplate("index.go").Execute(&buf, params) 281 | if err != nil { 282 | return err 283 | } 284 | format.Source(buf.Bytes()) 285 | if err != nil { 286 | return err 287 | } 288 | _, err = file.Write(buf.Bytes()) 289 | if err != nil { 290 | return err 291 | } 292 | buf.Reset() 293 | err = iMustHazTemplate("index_test.go").Execute(&buf, params) 294 | if err != nil { 295 | return err 296 | } 297 | _, err = testFile.Write(buf.Bytes()) 298 | return err 299 | } 300 | 301 | func writeAsmIndex(target string) error { 302 | for _, file := range iMustHazAsmList() { 303 | src := iMustHazFile(file) 304 | targetFile, err := ioutil.TempFile(target, "indexasm") 305 | if err != nil { 306 | return err 307 | } 308 | if _, err = targetFile.WriteString(src); err != nil { 309 | targetFile.Close() 310 | os.Remove(targetFile.Name()) 311 | return err 312 | } 313 | if err = targetFile.Close(); err != nil { 314 | os.Remove(targetFile.Name()) 315 | return err 316 | } 317 | if err = os.Rename(targetFile.Name(), filepath.Join(target, file)); err != nil { 318 | os.Remove(targetFile.Name()) 319 | return err 320 | } 321 | } 322 | return nil 323 | } 324 | 325 | // Creates a Go package `pkgName` from `source` directory contents and puts code 326 | // into `target` location. 327 | func Imbed(source, target, pkgName string, flags ImbedFlag) error { 328 | if flags.has(BuildHttpFsAPI|BuildUnionFsAPI) { 329 | flags |= BuildFsAPI 330 | } 331 | if pkgName == "main" && flags.has(BuildMain) { 332 | flags |= BuildFsAPI|BuildHttpHandlerAPI 333 | } 334 | err := os.MkdirAll(target, 0755) 335 | if err != nil { 336 | return err 337 | } 338 | dataFile, err := ioutil.TempFile(target, "data") 339 | if err != nil { 340 | return err 341 | } 342 | defer os.Remove(dataFile.Name()) 343 | err = writeObjectFileHeader(dataFile) 344 | if err != nil { 345 | return err 346 | } 347 | indexFile, err := ioutil.TempFile(target, "index") 348 | if err != nil { 349 | return err 350 | } 351 | defer func() { 352 | indexFile.Close() 353 | os.Remove(indexFile.Name()) 354 | }() 355 | testFile, err := ioutil.TempFile(target, "index_test") 356 | if err != nil { 357 | return err 358 | } 359 | defer func() { 360 | testFile.Close() 361 | os.Remove(testFile.Name()) 362 | }() 363 | addr := 0 364 | root := &directoryAsset{} 365 | err = filepath.Walk(source, func(asset string, info os.FileInfo, err error) error { 366 | assetName, _ := filepath.Rel(source, asset) 367 | assetName = filepath.ToSlash(assetName) 368 | if err != nil { 369 | return err 370 | } 371 | if info.IsDir() { 372 | root.addDirectory(assetName) 373 | return nil 374 | } 375 | file, err := os.OpenFile(asset, os.O_RDONLY, 0) 376 | if err != nil { 377 | return err 378 | } 379 | defer file.Close() 380 | fstat, _ := file.Stat() 381 | m := mime.TypeByExtension(path.Ext(strings.ToLower(asset))) 382 | if m == "" { 383 | m = "application/binary" 384 | } 385 | var compressed = false 386 | if flags.CompressAssets() && (strings.HasPrefix(m, "text/") || strings.HasSuffix(m, "+xml") || 387 | strings.Contains(m, "javascript") || m == "application/xml") { 388 | compressed = true 389 | } 390 | var entry = fileAsset{ 391 | name: path.Base(assetName), 392 | mimeType: m, 393 | size: fstat.Size(), 394 | isCompressed: compressed, 395 | } 396 | addr, err = entry.writeObject(file, dataFile, addr, flags) 397 | if err != nil { 398 | return err 399 | } 400 | root.addFile(assetName, &entry) 401 | return nil 402 | }) 403 | if err != nil { 404 | return err 405 | } 406 | err = writeObjectFileFooter(dataFile, addr) 407 | if err != nil { 408 | return err 409 | } 410 | err = writeGoIndex(indexFile, testFile, pkgName, root, addr, flags) 411 | if err != nil { 412 | return err 413 | } 414 | dataFile.Close() 415 | indexFile.Close() 416 | testFile.Close() 417 | err = os.Rename(dataFile.Name(), filepath.Join(target, "data.s")) 418 | if err != nil { 419 | return err 420 | } 421 | err = os.Rename(indexFile.Name(), filepath.Join(target, "index.go")) 422 | if err != nil { 423 | return err 424 | } 425 | err = os.Rename(testFile.Name(), filepath.Join(target, "index_test.go")) 426 | if err != nil { 427 | return err 428 | } 429 | return writeAsmIndex(target) 430 | } 431 | -------------------------------------------------------------------------------- /imbed/imbed_test.go: -------------------------------------------------------------------------------- 1 | package imbed 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "testing" 10 | ) 11 | 12 | func rmtree(name string) { 13 | var files []string 14 | var dirs []string 15 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 16 | if err != nil { 17 | return nil 18 | } 19 | if info.IsDir() { 20 | dirs = append(dirs, path) 21 | } else { 22 | files = append(files, path) 23 | } 24 | return nil 25 | }) 26 | for j := len(files) - 1; j >= 0; j-- { 27 | os.Remove(files[j]) 28 | } 29 | for j := len(dirs) - 1; j >= 0; j-- { 30 | os.Remove(dirs[j]) 31 | } 32 | } 33 | 34 | func TestGenerateNoMain(t *testing.T) { 35 | tmp, err := ioutil.TempDir(os.TempDir(), "go-imbed-test") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | defer rmtree(tmp) 40 | pkgDir := filepath.Join(tmp, "src", "pkg") 41 | err = os.MkdirAll(filepath.Join(pkgDir, "internal"), 0700) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | mainf, err := os.OpenFile(filepath.Join(pkgDir, "main.go"), os.O_WRONLY|os.O_CREATE, 0644) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | _, err = io.WriteString(mainf, `package main 50 | 51 | import _ "pkg/internal/data" 52 | 53 | func main() { 54 | } 55 | `) 56 | mainf.Close() 57 | if err != nil { 58 | t.Fatal(err) 59 | } 60 | targetPkg := filepath.Join(tmp, "src", "pkg", "internal", "data") 61 | for i := 0; i < (1 << maxFlag); i++ { 62 | if ImbedFlag(i).has(BuildMain) { 63 | continue 64 | } 65 | cmd := exec.Command("go", "install", "pkg") 66 | cmd.Env = append(os.Environ(), "GOPATH="+tmp) 67 | cmd.Dir = tmp 68 | flags := ImbedFlag(i) 69 | err := Imbed("../example/site", targetPkg, "data", flags) 70 | if err != nil { 71 | t.Fatalf("error embedding with flags %s: %s", flags.String(), err) 72 | } 73 | cmd.Stderr = os.Stderr 74 | cmd.Stdout = os.Stdout 75 | err = cmd.Run() 76 | if err != nil { 77 | t.Fatalf("error compiling target with flags %s\n", flags.String()) 78 | } 79 | cmd = exec.Command("go", "test", "-v", "pkg/internal/data") 80 | cmd.Env = append(os.Environ(), "GOPATH="+tmp) 81 | cmd.Dir = tmp 82 | cmd.Stderr = os.Stderr 83 | cmd.Stdout = os.Stdout 84 | err = cmd.Run() 85 | if err != nil { 86 | t.Fatalf("error testing target with flags %s\n", flags.String()) 87 | } 88 | } 89 | } 90 | 91 | func TestGenerateMain(t *testing.T) { 92 | tmp, err := ioutil.TempDir(os.TempDir(), "go-imbed-test") 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | // defer rmtree(tmp) 97 | pkgDir := filepath.Join(tmp, "src", "main") 98 | err = os.MkdirAll(pkgDir, 0700) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | for i := 0; i < (1 << maxFlag); i++ { 103 | if !ImbedFlag(i).has(BuildMain) { 104 | continue 105 | } 106 | cmd := exec.Command("go", "install", "main") 107 | cmd.Env = append(os.Environ(), "GOPATH="+tmp) 108 | cmd.Dir = tmp 109 | flags := ImbedFlag(i) 110 | err := Imbed("../example/site", pkgDir, "main", flags) 111 | if err != nil { 112 | t.Fatalf("error embedding with flags %s: %s", flags.String(), err) 113 | } 114 | cmd.Stderr = os.Stderr 115 | cmd.Stdout = os.Stdout 116 | err = cmd.Run() 117 | if err != nil { 118 | t.Fatalf("error compiling target with flags %s\n", flags.String()) 119 | } 120 | cmd = exec.Command(filepath.Join(tmp, "bin", "main"), "-list") 121 | cmd.Dir = tmp 122 | err = cmd.Run() 123 | if err != nil { 124 | t.Fatalf("error running target with flags %s\n", flags.String()) 125 | } 126 | } 127 | rmtree(filepath.Join(tmp, "src")) 128 | 129 | } 130 | 131 | -------------------------------------------------------------------------------- /imbed/internal/templates/index.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // Package templates holds binary resources embedded into Go executable 4 | package templates 5 | 6 | import ( 7 | "os" 8 | "io" 9 | "bytes" 10 | "path/filepath" 11 | "sort" 12 | "path" 13 | "compress/gzip" 14 | "io/ioutil" 15 | "time" 16 | ) 17 | 18 | func blob_bytes(uint32) []byte 19 | func blob_string(uint32) string 20 | 21 | // Asset represents binary resource stored within Go executable. Asset implements 22 | // fmt.Stringer and io.WriterTo interfaces, decompressing binary data if necessary. 23 | type Asset struct { 24 | name string // File name 25 | size int32 // File size (uncompressed) 26 | blob []byte // Resource blob []byte 27 | str_blob string // Resource blob as a string 28 | isCompressed bool // true if resources was compressed with gzip 29 | mime string // MIME Type 30 | tag string // Tag is essentially a Tag of resource content and can be used as a value for "Etag" HTTP header 31 | } 32 | 33 | // Name returns the base name of the asset 34 | func (a *Asset) Name() string { return a.name } 35 | // MimeType returns MIME Type of the asset 36 | func (a *Asset) MimeType() string { return a.mime } 37 | // Tag returns a string which can serve as an unique version identifier for the asset (i.e., "Etag") 38 | func (a *Asset) Tag() string { return a.tag } 39 | // IsCompressed returns true of asset has been compressed 40 | func (a *Asset) IsCompressed() bool { return a.isCompressed } 41 | // String returns (uncompressed, if necessary) content of asset as a string 42 | func (a *Asset) String() string { 43 | if a.isCompressed { 44 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 45 | ret, _ := ioutil.ReadAll(ungzip) 46 | ungzip.Close() 47 | return string(ret) 48 | } 49 | return a.str_blob 50 | } 51 | 52 | // Bytes returns (uncompressed) content of asset as a []byte 53 | func (a *Asset) Bytes() []byte { 54 | if a.isCompressed { 55 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 56 | ret, _ := ioutil.ReadAll(ungzip) 57 | ungzip.Close() 58 | return ret 59 | } 60 | ret := make([]byte, len(a.blob)) 61 | copy(ret, a.blob) 62 | return ret 63 | } 64 | 65 | // Size implements os.FileInfo and returns the size of the asset (uncompressed, if asset has been compressed) 66 | func (a *Asset) Size() int64 { return int64(a.size) } 67 | // Mode implements os.FileInfo and always returns 0444 68 | func (a *Asset) Mode() os.FileMode { return 0444 } 69 | // ModTime implements os.FileInfo and returns the time stamp when this package has been produced (the same value for all the assets) 70 | func (a *Asset) ModTime() time.Time { return stamp } 71 | // IsDir implements os.FileInfo and returns false 72 | func (a *Asset) IsDir() bool { return false } 73 | // Sys implements os.FileInfo and returns nil 74 | func (a *Asset) Sys() interface{} { return a } 75 | 76 | // WriteTo implements io.WriterTo interface and writes content of the asset to w 77 | func (a *Asset) WriteTo(w io.Writer) (int64, error) { 78 | if a.isCompressed { 79 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 80 | n, err := io.Copy(w, ungzip) 81 | ungzip.Close() 82 | return n, err 83 | } 84 | n, err := w.Write(a.blob) 85 | return int64(n), err 86 | } 87 | 88 | type assetReader struct { 89 | bytes.Reader 90 | } 91 | 92 | func (r *assetReader) Close() error { 93 | r.Reset(nil) 94 | return nil 95 | } 96 | 97 | // Returns content of the asset as io.ReaderCloser. 98 | func (a *Asset) Reader() io.ReadCloser { 99 | if a.isCompressed { 100 | ungzip, _ := gzip.NewReader(bytes.NewReader(a.blob)) 101 | return ungzip 102 | } else { 103 | ret := &assetReader{} 104 | ret.Reset(a.blob) 105 | return ret 106 | } 107 | } 108 | 109 | func cleanPath(path string) string { 110 | path = filepath.Clean(path) 111 | if filepath.IsAbs(path) { 112 | path = path[len(filepath.VolumeName(path)):] 113 | if len(path) > 0 || os.IsPathSeparator(path[0]) { 114 | path = path[1:] 115 | } 116 | } else if path == "." { 117 | return "" 118 | } 119 | return filepath.ToSlash(path) 120 | } 121 | 122 | // Opens asset as an io.ReadCloser. Returns os.ErrNotExist if no asset is found. 123 | func Open(name string) (File, error) { 124 | return FS().Open(name) 125 | } 126 | 127 | // Gets asset by name. Returns nil if no asset found. 128 | func Get(name string) *Asset { 129 | if entry, ok := fidx[name]; ok { 130 | return entry 131 | } else { 132 | return nil 133 | } 134 | } 135 | 136 | // Get asset by name. Panics if no asset found. 137 | func Must(name string) *Asset { 138 | if entry, ok := fidx[name]; ok { 139 | return entry 140 | } else { 141 | panic("asset " + name + " not found") 142 | } 143 | } 144 | 145 | type directoryAsset struct { 146 | name string 147 | dirs []directoryAsset 148 | files []Asset 149 | } 150 | 151 | var root *directoryAsset 152 | 153 | // A simple FileSystem abstraction 154 | type FileSystem interface { 155 | Open(name string) (File, error) 156 | Stat(name string) (os.FileInfo, error) 157 | // As in filepath.Walk 158 | Walk(root string, walkFunc filepath.WalkFunc) error 159 | } 160 | 161 | // The CopyTo method extracts all mentioned files 162 | // to a specified location, keeping directory structure. 163 | // If supplied file is a directory, than it will be extracted 164 | // recursively. CopyTo with no file mentioned will extract 165 | // the whole content of the embedded filesystem. 166 | // CopyTo returns error if there is a file with the same name 167 | // at the target location, unless overwrite is set to true, or 168 | // file has the same size and modification file as the extracted 169 | // file. 170 | // templates.CopyTo(".", mode, false) will effectively 171 | // extract content of the filesystem to the current directory (which 172 | // makes it the most space-wise inefficient self-extracting archive 173 | // ever). 174 | func CopyTo(target string, mode os.FileMode, overwrite bool, files ...string) error { 175 | mode = mode&0777 176 | dirmode := os.ModeDir|((mode&0444)>>2)|mode 177 | if len(files) == 0 { 178 | files = []string{""} 179 | } 180 | for _, file := range files { 181 | file = cleanPath(file) 182 | err := FS().Walk(file, func(path string, info os.FileInfo, err error) error { 183 | if err != nil { 184 | return err 185 | } 186 | targetPath := filepath.Join(target, path) 187 | fi, err := os.Stat(targetPath) 188 | if err == nil { 189 | if info.IsDir() && fi.IsDir() { 190 | return nil 191 | } else if info.IsDir() != fi.IsDir() { 192 | return os.ErrExist 193 | } else if !overwrite { 194 | if info.Size() == fi.Size() && info.ModTime().Equal(fi.ModTime()) { 195 | return nil 196 | } else { 197 | return os.ErrExist 198 | } 199 | } 200 | } 201 | if info.IsDir() { 202 | return os.MkdirAll(targetPath, dirmode) 203 | } 204 | asset := Get(path) 205 | if asset == nil { 206 | return os.ErrNotExist 207 | } 208 | targetPathDir := filepath.Dir(targetPath) 209 | if err = os.MkdirAll(targetPathDir, dirmode); err != nil { 210 | return err 211 | } 212 | dst, err := ioutil.TempFile(targetPathDir, ".imbed") 213 | if err != nil { 214 | return err 215 | } 216 | defer func() { 217 | dst.Close() 218 | os.Remove(dst.Name()) 219 | }() 220 | _, err = asset.WriteTo(dst) 221 | if err != nil { 222 | return err 223 | } 224 | dst.Close() 225 | os.Chtimes(dst.Name(), info.ModTime(), info.ModTime()) 226 | os.Chmod(dst.Name(), mode) 227 | return os.Rename(dst.Name(), targetPath) 228 | }) 229 | if err != nil { 230 | return err 231 | } 232 | } 233 | return nil 234 | } 235 | 236 | type fileInfoSlice []os.FileInfo 237 | func (fis *fileInfoSlice) Len() int { return len(*fis) } 238 | func (fis *fileInfoSlice) Less(i, j int) bool { return (*fis)[i].Name() < (*fis)[j].Name() } 239 | func (fis *fileInfoSlice) Swap(i, j int) { 240 | s := (*fis)[i] 241 | (*fis)[i] = (*fis)[j] 242 | (*fis)[j] = s 243 | } 244 | 245 | func walkRec(fs FileSystem, info os.FileInfo, p string, walkFn filepath.WalkFunc) error { 246 | var ( 247 | dir File 248 | fis fileInfoSlice 249 | err error 250 | ) 251 | err = walkFn(p, info, nil) 252 | if err != nil { 253 | if info.IsDir() && err == filepath.SkipDir { 254 | return nil 255 | } 256 | return err 257 | } 258 | if !info.IsDir() { 259 | return nil 260 | } 261 | dir, err = fs.Open(p) 262 | if err != nil { 263 | return walkFn(p, info, err) 264 | } 265 | fis, err = dir.Readdir(-1) 266 | if err != nil { 267 | return walkFn(p, info, err) 268 | } 269 | sort.Sort(&fis) 270 | for i := range fis { 271 | fn := path.Join(p, fis[i].Name()) 272 | err = walkRec(fs, fis[i], fn, walkFn) 273 | if err != nil { 274 | if !fis[i].IsDir() || err != filepath.SkipDir { 275 | return err 276 | } 277 | } 278 | } 279 | return nil 280 | } 281 | 282 | func walk(fs FileSystem, name string, walkFunc filepath.WalkFunc) error { 283 | var r os.FileInfo 284 | var err error 285 | name = cleanPath(name) 286 | r, err = fs.Stat(name) 287 | if err != nil { 288 | return err 289 | } 290 | return walkRec(fs, r, name, walkFunc) 291 | } 292 | 293 | type assetFs struct{} 294 | 295 | // Returns embedded FileSystem 296 | func FS() FileSystem { 297 | return &assetFs{} 298 | } 299 | 300 | func (fs *assetFs) Walk(root string, walkFunc filepath.WalkFunc) error { 301 | return walk(fs, root, walkFunc) 302 | } 303 | 304 | func (fs *assetFs) Stat(name string) (os.FileInfo, error) { 305 | name = cleanPath(name) 306 | if name == "" { 307 | return root, nil 308 | } 309 | if dir, ok := didx[name]; ok { 310 | return dir, nil 311 | } 312 | if asset, ok := fidx[name]; ok { 313 | return asset, nil 314 | } 315 | return nil, os.ErrNotExist 316 | } 317 | 318 | func (fs *assetFs) Open(name string) (File, error) { 319 | name = cleanPath(name) 320 | if name == "" { 321 | return root.open(""), nil 322 | } 323 | if dir, ok := didx[name]; ok { 324 | return dir.open(name), nil 325 | } 326 | if asset, ok := fidx[name]; ok { 327 | return asset.open(name), nil 328 | } 329 | return nil, os.ErrNotExist 330 | } 331 | 332 | 333 | // A File is returned by virtual FileSystem's Open method. 334 | // The methods should behave the same as those on an *os.File. 335 | type File interface { 336 | io.Closer 337 | io.Reader 338 | io.Seeker 339 | Readdir(count int) ([]os.FileInfo, error) 340 | Stat() (os.FileInfo, error) 341 | } 342 | 343 | func (a *Asset) open(name string) File { 344 | if a.isCompressed { 345 | ret := &assetCompressedFile{ 346 | asset: a, 347 | name: name, 348 | } 349 | ret.Reset(bytes.NewReader(a.blob)) 350 | return ret 351 | } else { 352 | ret := &assetFile{ 353 | asset: a, 354 | name: name, 355 | } 356 | ret.Reset(a.blob) 357 | return ret 358 | } 359 | } 360 | 361 | func (d *directoryAsset) open(name string) File { 362 | return &directoryAssetFile{ 363 | dir: d, 364 | name: name, 365 | pos: 0, 366 | } 367 | } 368 | 369 | type directoryAssetFile struct { 370 | dir *directoryAsset 371 | name string 372 | pos int 373 | } 374 | 375 | func (d *directoryAssetFile) Name() string { 376 | return d.name 377 | } 378 | 379 | func (d *directoryAssetFile) checkClosed() error { 380 | if d.pos < 0 { 381 | return os.ErrClosed 382 | } 383 | return nil 384 | } 385 | 386 | func (d *directoryAssetFile) Close() error { 387 | if err := d.checkClosed(); err != nil { 388 | return err 389 | } 390 | d.pos = -1 391 | return nil 392 | } 393 | 394 | func (d *directoryAssetFile) Read([]byte) (int, error) { 395 | if err := d.checkClosed(); err != nil { 396 | return 0, err 397 | } 398 | return 0, io.EOF 399 | } 400 | 401 | func (d *directoryAssetFile) Stat() (os.FileInfo, error) { 402 | if err := d.checkClosed(); err != nil { 403 | return nil, err 404 | } 405 | return d.dir, nil 406 | } 407 | 408 | func (d *directoryAssetFile) Seek(pos int64, whence int) (int64, error) { 409 | if err := d.checkClosed(); err != nil { 410 | return 0, err 411 | } 412 | return 0, os.ErrInvalid 413 | } 414 | 415 | func (d *directoryAssetFile) Readdir(count int) ([]os.FileInfo, error) { 416 | if err := d.checkClosed(); err != nil { 417 | return nil, err 418 | } 419 | var ( 420 | last int 421 | total = len(d.dir.dirs) + len(d.dir.files) 422 | ) 423 | if d.pos > total { 424 | if count > 0 { 425 | return nil, io.EOF 426 | } else { 427 | return nil, nil 428 | } 429 | } 430 | if count <= 0 || (d.pos + count) <= total { 431 | last = total 432 | } else { 433 | last = d.pos + count 434 | } 435 | ret := make([]os.FileInfo, 0, last - d.pos) 436 | if d.pos < len(d.dir.dirs) { 437 | var stop int 438 | if last > len(d.dir.dirs) { 439 | stop = len(d.dir.dirs) 440 | } else { 441 | stop = last 442 | } 443 | for i := d.pos; i < stop; i++ { 444 | ret = append(ret, &d.dir.dirs[i]) 445 | } 446 | d.pos = stop 447 | } 448 | var start, stop int 449 | start = d.pos - len(d.dir.dirs) 450 | stop = last - len(d.dir.dirs) 451 | for i := start; i < stop; i++ { 452 | ret = append(ret, &d.dir.files[i]) 453 | } 454 | d.pos = last 455 | return ret, nil 456 | } 457 | 458 | func (d *directoryAsset) Name() string { return d.name } 459 | func (d *directoryAsset) Size() int64 { return 0 } 460 | func (d *directoryAsset) Mode() os.FileMode { return os.ModeDir | 0555 } 461 | func (d *directoryAsset) ModTime() time.Time { return stamp } 462 | func (d *directoryAsset) IsDir() bool { return true } 463 | func (d *directoryAsset) Sys() interface{} { return d } 464 | 465 | type assetFile struct { 466 | assetReader 467 | name string 468 | asset *Asset 469 | } 470 | 471 | func (a *assetFile) Name() string { 472 | return a.name 473 | } 474 | 475 | func (a *assetFile) Stat() (os.FileInfo, error) { 476 | return a.asset, nil 477 | } 478 | 479 | func (a *assetFile) Readdir(int) ([]os.FileInfo, error) { 480 | return nil, os.ErrInvalid 481 | } 482 | type assetCompressedFile struct { 483 | gzip.Reader 484 | name string 485 | asset *Asset 486 | } 487 | 488 | func (a *assetCompressedFile) Name() string { 489 | return a.name 490 | } 491 | 492 | func (a *assetCompressedFile) Stat() (os.FileInfo, error) { 493 | return a.asset, nil 494 | } 495 | 496 | func (a *assetCompressedFile) Seek(int64, int) (int64, error) { 497 | return 0, os.ErrInvalid 498 | } 499 | 500 | func (a *assetCompressedFile) Readdir(count int) ([]os.FileInfo, error) { 501 | return nil, os.ErrInvalid 502 | } 503 | 504 | var fidx = make(map[string]*Asset) 505 | var didx = make(map[string]*directoryAsset) 506 | var stamp time.Time 507 | 508 | func init() { 509 | stamp = time.Unix(1571908442, 807852000) 510 | bb := blob_bytes(9816) 511 | bs := blob_string(9816) 512 | root = &directoryAsset{ 513 | files: []Asset{ 514 | { 515 | name: "index.go", 516 | blob: bb[0:5775], 517 | str_blob: bs[0:5775], 518 | mime: "text/x-golang; charset=utf-8", 519 | tag: "xomyf7cjnm7zq", 520 | size: 21780, 521 | isCompressed: true, 522 | }, 523 | { 524 | name: "index_386.s", 525 | blob: bb[5776:5973], 526 | str_blob: bs[5776:5973], 527 | mime: "text/x-asm; charset=utf-8", 528 | tag: "ihibzfvzsneuc", 529 | size: 327, 530 | isCompressed: true, 531 | }, 532 | { 533 | name: "index_amd64.s", 534 | blob: bb[5976:6184], 535 | str_blob: bs[5976:6184], 536 | mime: "text/x-asm; charset=utf-8", 537 | tag: "dcfwghvd5ccho", 538 | size: 361, 539 | isCompressed: true, 540 | }, 541 | { 542 | name: "index_arm.s", 543 | blob: bb[6184:6377], 544 | str_blob: bs[6184:6377], 545 | mime: "text/x-asm; charset=utf-8", 546 | tag: "2kykitl4umpta", 547 | size: 329, 548 | isCompressed: true, 549 | }, 550 | { 551 | name: "index_arm64.s", 552 | blob: bb[6384:6584], 553 | str_blob: bs[6384:6584], 554 | mime: "text/x-asm; charset=utf-8", 555 | tag: "xsewb4p6f52nu", 556 | size: 331, 557 | isCompressed: true, 558 | }, 559 | { 560 | name: "index_mips64x.s", 561 | blob: bb[6584:6806], 562 | str_blob: bs[6584:6806], 563 | mime: "text/x-asm; charset=utf-8", 564 | tag: "srpac746plm5g", 565 | size: 370, 566 | isCompressed: true, 567 | }, 568 | { 569 | name: "index_mipsx.s", 570 | blob: bb[6808:7027], 571 | str_blob: bs[6808:7027], 572 | mime: "text/x-asm; charset=utf-8", 573 | tag: "itgddt77jfjk6", 574 | size: 364, 575 | isCompressed: true, 576 | }, 577 | { 578 | name: "index_ppc64x.s", 579 | blob: bb[7032:7247], 580 | str_blob: bs[7032:7247], 581 | mime: "text/x-asm; charset=utf-8", 582 | tag: "aatq7i5ygiqoy", 583 | size: 356, 584 | isCompressed: true, 585 | }, 586 | { 587 | name: "index_s390x.s", 588 | blob: bb[7248:7466], 589 | str_blob: bs[7248:7466], 590 | mime: "text/x-asm; charset=utf-8", 591 | tag: "ml3wgknpogqt6", 592 | size: 313, 593 | isCompressed: true, 594 | }, 595 | { 596 | name: "index_test.go", 597 | blob: bb[7472:9810], 598 | str_blob: bs[7472:9810], 599 | mime: "text/x-golang; charset=utf-8", 600 | tag: "3nfezi3r4tdnc", 601 | size: 10851, 602 | isCompressed: true, 603 | }, 604 | }, 605 | } 606 | didx[""] = root 607 | fidx["index.go"] = &root.files[0] 608 | fidx["index_386.s"] = &root.files[1] 609 | fidx["index_amd64.s"] = &root.files[2] 610 | fidx["index_arm.s"] = &root.files[3] 611 | fidx["index_arm64.s"] = &root.files[4] 612 | fidx["index_mips64x.s"] = &root.files[5] 613 | fidx["index_mipsx.s"] = &root.files[6] 614 | fidx["index_ppc64x.s"] = &root.files[7] 615 | fidx["index_s390x.s"] = &root.files[8] 616 | fidx["index_test.go"] = &root.files[9] 617 | } 618 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_386.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAL ·d(SB), AX 7 | MOVL AX, ret+4(FP) 8 | MOVL len+0(FP), AX 9 | MOVL AX, ret+8(FP) 10 | MOVL AX, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | LEAL ·d(SB), AX 15 | MOVL AX, ret+4(FP) 16 | MOVL len+0(FP), AX 17 | MOVL AX, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_amd64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | LEAQ ·d(SB), AX 7 | MOVQ AX, ret+8(FP) 8 | MOVL len+0(FP), AX 9 | MOVLQSX AX, AX 10 | MOVQ AX, ret+16(FP) 11 | MOVQ AX, ret+24(FP) 12 | RET 13 | 14 | TEXT ·blob_string(SB),NOSPLIT,$0-4 15 | LEAQ ·d(SB), AX 16 | MOVQ AX, ret+8(FP) 17 | MOVL len+0(FP), AX 18 | MOVLQSX AX, AX 19 | MOVQ AX, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_arm.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 6 | MOVW $·d(SB), R0 7 | MOVW R0, ret+4(FP) 8 | MOVW len+0(FP), R0 9 | MOVW R0, ret+8(FP) 10 | MOVW R0, ret+12(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-4 14 | MOVW $·d(SB), R0 15 | MOVW R0, ret+4(FP) 16 | MOVW len+0(FP), R0 17 | MOVW R0, ret+8(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_arm64.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 6 | MOVD $·d(SB), R0 7 | MOVD R0, ret+8(FP) 8 | MOVW len+0(FP), R0 9 | MOVD R0, ret+16(FP) 10 | MOVD R0, ret+24(FP) 11 | RET 12 | 13 | TEXT ·blob_string(SB),NOSPLIT,$0-8 14 | MOVD $·d(SB), R0 15 | MOVD R0, ret+8(FP) 16 | MOVD len+0(FP), R0 17 | MOVD R0, ret+16(FP) 18 | RET 19 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_mips64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips64 mips64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVV $·d(SB), R1 9 | MOVV R1, ret+8(FP) 10 | MOVV len+0(FP), R1 11 | MOVV R1, ret+16(FP) 12 | MOVV R1, ret+24(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVV $·d(SB), R1 17 | MOVV R1, ret+8(FP) 18 | MOVV len+0(FP), R1 19 | MOVV R1, ret+16(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_mipsx.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build mips mipsle 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-4 8 | MOVW $·d(SB), R1 9 | MOVW R1, ret+4(FP) 10 | MOVW len+0(FP), R1 11 | MOVW R1, ret+8(FP) 12 | MOVW R1, ret+12(FP) 13 | JMP (R31) 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-4 16 | MOVW $·d(SB), R1 17 | MOVW R1, ret+4(FP) 18 | MOVW len+0(FP), R1 19 | MOVW R1, ret+8(FP) 20 | JMP (R31) 21 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_ppc64x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | // +build ppc64 ppc64le 4 | 5 | #include "textflag.h" 6 | 7 | TEXT ·blob_bytes(SB),NOSPLIT,$0-8 8 | MOVD $·d(SB), R3 9 | MOVD R3, ret+8(FP) 10 | MOVD len+0(FP), R3 11 | MOVD R3, ret+16(FP) 12 | MOVD R3, ret+24(FP) 13 | RET 14 | 15 | TEXT ·blob_string(SB),NOSPLIT,$0-8 16 | MOVD $·d(SB), R3 17 | MOVD R3, ret+8(FP) 18 | MOVD len+0(FP), R3 19 | MOVD R3, ret+16(FP) 20 | RET 21 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_s390x.s: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | #include "textflag.h" 4 | 5 | TEXT ·blob_bytes(SB),NOSPLIT|NOFRAME,$0-8 6 | MOVD $·d(SB), R0 7 | MOVW len+0(FP), R1 8 | MOVD R1, R2 9 | STMG R0, R2, ret+8(FP) 10 | JMP R14 11 | 12 | TEXT ·blob_string(SB),NOSPLIT|NOFRAME,$0-8 13 | MOVD $·d(SB), R0 14 | MOVW len+0(FP), R1 15 | STMG R0, R1, ret+8(FP) 16 | JMP R14 17 | -------------------------------------------------------------------------------- /imbed/internal/templates/index_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by go-imbed. DO NOT EDIT. 2 | 3 | package templates 4 | 5 | import ( 6 | "encoding/base32" 7 | "encoding/binary" 8 | "hash/crc64" 9 | "testing" 10 | "math/rand" 11 | "os" 12 | "io/ioutil" 13 | "path/filepath" 14 | "bytes" 15 | "fmt" 16 | ) 17 | 18 | var randomName = func() string { 19 | var buf [16]byte 20 | binary.LittleEndian.PutUint64(buf[:8], rand.Uint64()) 21 | binary.LittleEndian.PutUint64(buf[8:], rand.Uint64()) 22 | return b32Enc.EncodeToString(buf[:]) 23 | }() 24 | 25 | var b32Enc = base32.NewEncoding("abcdefghijklmnopqrstuvwxyz234567").WithPadding(base32.NoPadding) 26 | 27 | func getTag(data []byte) string { 28 | var crcBuf [8]byte 29 | binary.LittleEndian.PutUint64(crcBuf[:], crc64.Checksum(data, crc64.MakeTable(crc64.ECMA))) 30 | return b32Enc.EncodeToString(crcBuf[:]) 31 | } 32 | 33 | func TestBytes(t *testing.T) { 34 | for n, a := range fidx { 35 | if getTag(a.Bytes()) != a.tag { 36 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 37 | } 38 | } 39 | } 40 | 41 | func TestString(t *testing.T) { 42 | for n, a := range fidx { 43 | if getTag([]byte(a.String())) != a.tag { 44 | t.Fatalf("checksum for asset %s doesn't match recorded", n) 45 | } 46 | } 47 | } 48 | func TestWalkOpen(t *testing.T) { 49 | FS().Walk("", func(path string, info os.FileInfo, err error) error { 50 | if err != nil || info.IsDir() { 51 | return nil 52 | } 53 | asset := Get(path) 54 | if asset == nil { 55 | return fmt.Errorf("asset %s nout found", path) 56 | } 57 | if getTag(asset.Bytes()) != asset.tag { 58 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 59 | } 60 | if getTag([]byte(asset.String())) != asset.tag { 61 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 62 | } 63 | rdr := asset.Reader() 64 | data, err := ioutil.ReadAll(rdr) 65 | rdr.Close() 66 | if err != nil { 67 | return err 68 | } 69 | rdr, err = FS().Open(path) 70 | if err != nil { 71 | return err 72 | } 73 | data, err = ioutil.ReadAll(rdr) 74 | rdr.Close() 75 | if err != nil { 76 | return err 77 | } 78 | if getTag(data) != asset.tag { 79 | return fmt.Errorf("checksum for asset %s doesn't match recorded", path) 80 | } 81 | rdr.Close() 82 | return nil 83 | }) 84 | } 85 | 86 | func rmtree(name string) { 87 | var files []string 88 | var dirs []string 89 | filepath.Walk(name, func(path string, info os.FileInfo, err error) error { 90 | if err != nil { 91 | return nil 92 | } 93 | if info.IsDir() { 94 | dirs = append(dirs, path) 95 | } else { 96 | files = append(files, path) 97 | } 98 | return nil 99 | }) 100 | for j := len(files) - 1; j >= 0; j-- { 101 | os.Remove(files[j]) 102 | } 103 | for j := len(dirs) - 1; j >= 0; j-- { 104 | os.Remove(dirs[j]) 105 | } 106 | } 107 | 108 | func TestCopyTo(t *testing.T) { 109 | tmp, err := ioutil.TempDir(os.TempDir(), ".test-test") 110 | if err != nil { 111 | t.Fatal(err) 112 | } 113 | defer rmtree(tmp) 114 | // a whole tree 115 | if err = CopyTo(tmp, 0640, false); err != nil { 116 | t.Fatal(err) 117 | } 118 | var testDir string 119 | var testFile string 120 | var testData []byte 121 | err = FS().Walk("", func(path string, info os.FileInfo, err error) error { 122 | if err != nil { 123 | return err 124 | } 125 | if info.IsDir() { 126 | if testDir == "" { 127 | testDir = path 128 | } 129 | return nil 130 | } 131 | f, err := os.OpenFile(filepath.Join(tmp, filepath.FromSlash(path)), os.O_RDONLY, 0) 132 | if err != nil { 133 | return err 134 | } 135 | data, err := ioutil.ReadAll(f) 136 | f.Close() 137 | if err != nil { 138 | return err 139 | } 140 | if testFile == "" { 141 | testFile = path 142 | testData = Must(path).Bytes() 143 | } 144 | if bytes.Compare(data, Must(path).Bytes()) != 0 { 145 | return fmt.Errorf("data differs for %s", path) 146 | } 147 | return nil 148 | }) 149 | if err != nil { 150 | t.Fatal(err) 151 | } 152 | if testFile == "" { 153 | // an empty archive? 154 | return 155 | } 156 | // single file 157 | targetTestFile := filepath.Join(tmp, testFile) 158 | os.Remove(targetTestFile) 159 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 160 | t.Fatal(err) 161 | } 162 | f, err := os.OpenFile(targetTestFile, os.O_RDONLY, 0) 163 | if err != nil { 164 | t.Fatal(err) 165 | } 166 | data, err := ioutil.ReadAll(f) 167 | f.Close() 168 | if err != nil { 169 | t.Fatal(err) 170 | } 171 | if bytes.Compare(testData, data) != 0 { 172 | t.Fatalf("data differs for single file extract") 173 | } 174 | if err = CopyTo(tmp, 0644, false, testFile); err != nil { 175 | t.Fatalf("expected no error, got %v", err) 176 | } 177 | f, err = os.OpenFile(targetTestFile, os.O_WRONLY | os.O_APPEND, 0600) 178 | if err != nil { 179 | t.Fatal(err) 180 | } 181 | _, err = f.WriteString(randomName) 182 | f.Close() 183 | if err != nil { 184 | t.Fatal(err) 185 | } 186 | if err = CopyTo(tmp, 0644, false, testFile); err != os.ErrExist { 187 | t.Fatalf("expected os.ErrExist, got %v", err) 188 | } 189 | if err = CopyTo(tmp, 0644, true, testFile); err != nil { 190 | t.Fatal(err) 191 | } 192 | f, err = os.OpenFile(targetTestFile, os.O_RDONLY, 0) 193 | if err != nil { 194 | t.Fatal(err) 195 | } 196 | data, err = ioutil.ReadAll(f) 197 | f.Close() 198 | if err != nil { 199 | t.Fatal(err) 200 | } 201 | if bytes.Compare(testData, data) != 0 { 202 | t.Fatalf("data differs for single file extract") 203 | } 204 | os.Remove(targetTestFile) 205 | if testDir == "" { 206 | // no directories in archive 207 | return 208 | } 209 | targetTestFile = filepath.Join(tmp, randomName) 210 | if err = CopyTo(targetTestFile, 0644, false, testDir); err != nil { 211 | t.Fatal(err) 212 | } 213 | err = FS().Walk(testDir, func(path string, info os.FileInfo, err error) error { 214 | if err != nil || info.IsDir() { 215 | return err 216 | } 217 | testData := Must(path).Bytes() 218 | f, err := os.OpenFile(filepath.Join(targetTestFile, filepath.FromSlash(path)), os.O_RDONLY, 0) 219 | if err != nil { 220 | return err 221 | } 222 | data, err := ioutil.ReadAll(f) 223 | f.Close() 224 | if err != nil { 225 | return err 226 | } 227 | if bytes.Compare(data, testData) != 0 { 228 | return fmt.Errorf("trees differ after partial extract") 229 | } 230 | return nil 231 | }) 232 | if err != nil { 233 | t.Fatal(err) 234 | } 235 | } -------------------------------------------------------------------------------- /imbed/templates.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexey Naidyonov. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE.md file. 4 | 5 | //+build !bootstrap 6 | 7 | package imbed 8 | 9 | import ( 10 | "text/template" 11 | 12 | "github.com/growler/go-imbed/imbed/internal/templates" 13 | "path/filepath" 14 | ) 15 | 16 | func iMustHazAsmList() []string { 17 | root, _ := templates.Open("") 18 | fis, _ := root.Readdir(-1) 19 | var list = make([]string, 0, len(fis)) 20 | for i := range fis { 21 | if filepath.Ext(fis[i].Name()) == ".s" { 22 | list = append(list, fis[i].Name()) 23 | } 24 | } 25 | return list 26 | } 27 | 28 | func iMustHazFile(name string) string { 29 | return templates.Must(name).String() 30 | } 31 | 32 | func iMustHazTemplate(name string) *template.Template { 33 | return template.Must(template.New("").Parse(iMustHazFile(name))) 34 | } 35 | -------------------------------------------------------------------------------- /imbed/templates_bootstrap.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Alexey Naidyonov. All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE.md file. 4 | 5 | //+build bootstrap 6 | 7 | package imbed 8 | 9 | import ( 10 | "text/template" 11 | "os" 12 | "io/ioutil" 13 | "path/filepath" 14 | ) 15 | 16 | func iMustHazAsmList() []string { 17 | fis, err := ioutil.ReadDir("_templates") 18 | if err != nil { 19 | panic(err) 20 | } 21 | var list = make([]string, 0, len(fis)) 22 | for i := range fis { 23 | if filepath.Ext(fis[i].Name()) == ".s" { 24 | list = append(list, fis[i].Name()) 25 | } 26 | } 27 | return list 28 | } 29 | 30 | func iMustHazFile(name string) string { 31 | file, err := os.OpenFile(filepath.Join("_templates", name), os.O_RDONLY, 0) 32 | if err != nil { 33 | panic(err) 34 | } 35 | bytes, err := ioutil.ReadAll(file) 36 | if err != nil { 37 | panic(err) 38 | } 39 | return string(bytes) 40 | } 41 | 42 | func iMustHazTemplate(name string) *template.Template { 43 | return template.Must(template.New("").Parse(iMustHazFile(name))) 44 | } 45 | --------------------------------------------------------------------------------