Add some more test files and directories and implement locating

of asset files.
This commit is contained in:
Jim Teeuwen 2013-10-29 18:23:52 +01:00
parent 560fbff5df
commit c4d457542a
7 changed files with 324 additions and 178 deletions

View File

@ -4,45 +4,10 @@ This tool converts any file into managable Go source code. Useful for embedding
binary data into a go program. The file data is optionally gzip compressed
before being converted to a raw byte slice.
### Usage
The simplest invocation is to pass it only the input file name.
The output file and code settings are inferred from this automatically.
$ go-bindata testdata/gophercolor.png
[w] No output file specified. Using 'testdata/gophercolor.png.go'.
[w] No package name specified. Using 'main'.
[w] No function name specified. Using 'testdata_gophercolor_png'.
This creates the `testdata/gophercolor.png.go` file which has a package
declaration with name `main` and one function named `testdata_gophercolor_png` with
the following signature:
```go
func testdata_gophercolor_png() []byte
```
You can now simply include the new .go file in your program and call
`testdata_gophercolor_png()` to get the (uncompressed) image data. The function panics
if something went wrong during decompression. See the testdata directory for
example input and output files for various modes.
Aternatively, you can pipe the input file data into stdin. `go-bindata` will
then spit out the generated Go code to stdout. This does require explicitly
naming the desired function name, as it can not be inferred from the
input data. The package name will still default to 'main'.
$ cat testdata/gophercolor.png | go-bindata -f gophercolor_png | gofmt
Invoke the program with the `-h` flag for more options.
In order to strip off a part of the generated function name, we can use the `-prefix` flag.
In the above example, the input file `testdata/gophercolor.png` yields a function named
`testdata_gophercolor_png`. If we want the `testdata` component to be left out, we invoke
the program as follows:
$ go-bindata -prefix "testdata/" testdata/gophercolor.png
TODO
### Lower memory footprint
@ -108,25 +73,6 @@ even increase the size of the data.
The default behaviour of the program is to use compression.
### Table of Contents
With the `-toc` flag, we can have `go-bindata` create a table of contents for all the files
which have been generated by the tool. It does this by first generating a new file named
`bindata-toc.go`. This contains a global map of type `map[string] func() []byte`. It uses the
input filename as the key and the data function as the value. We can use this
to fetch all data for our files, matching a given pattern.
It then appands an `init` function to each generated file, which simply makes the data
function append itself to the global `bindata` map.
Once you have compiled your program with all these new files and run it, the map will
be populated by all generated data files.
**Note**: The `bindata-toc.go` file will not be created when we run in `pipe` mode.
The reason being, that the tool does not write any files at all, as it has no idea
where to save them. The data file is written to `stdout` instead after all.
#### Table of Contents keys
The keys used in the `go_bindata` map, are the same as the input file name passed to `go-bindata`.
@ -148,20 +94,6 @@ Running with the `-prefix` flag, we get:
go_bindata["templates/foo.html"] = templates_foo_html
#### bindata-toc.go
The `bindata-toc.go` file is very simple and looks as follows:
```go
package $PACKAGENAME
// Global Table of Contents map. Generated by go-bindata.
// After startup of the program, all generated data files will
// put themselves in this map. The key is the full filename, as
// supplied to go-bindata.
var go_bindata = make(map[string] func() []byte)
```
#### Build tags
With the optional -tags flag, you can specify any go build tags that

92
config.go Normal file
View File

@ -0,0 +1,92 @@
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
// license. Its contents can be found at:
// http://creativecommons.org/publicdomain/zero/1.0/
package bindata
// Config defines a set of options for the asset conversion.
type Config struct {
// Tags specify a set of optional build tags, which should be
// included in the generated output. The tags are appended to a
// `// +build` line in the beginning of the output file
// and must follow the build tags syntax specified by the go tool.
Tags []string
// Input defines the directory path, containing all asset files.
// This may contain sub directories, which will be included in the
// conversion.
Input string
// Output defines the output directory for the generated code.
Output string
/*
Prefix defines a path prefix which should be stripped from all
file names when generating the keys in the table of contents.
For example, running without the `-prefix` flag, we get:
$ go-bindata /path/to/templates
go_bindata["/path/to/templates/foo.html"] = _path_to_templates_foo_html
Running with the `-prefix` flag, we get:
$ go-bindata -prefix "/path/to/" /path/to/templates/foo.html
go_bindata["templates/foo.html"] = templates_foo_html
*/
Prefix string
/*
NoMemCopy will alter the way the output file is generated.
It will employ a hack that allows us to read the file data directly from
the compiled program's `.rodata` section. This ensures that when we call
call our generated function, we omit unnecessary mem copies.
The downside of this, is that it requires dependencies on the `reflect` and
`unsafe` packages. These may be restricted on platforms like AppEngine and
thus prevent you from using this mode.
Another disadvantage is that the byte slice we create, is strictly read-only.
For most use-cases this is not a problem, but if you ever try to alter the
returned byte slice, a runtime panic is thrown. Use this mode only on target
platforms where memory constraints are an issue.
The default behaviour is to use the old code generation method. This
prevents the two previously mentioned issues, but will employ at least one
extra memcopy and thus increase memory requirements.
For instance, consider the following two examples:
This would be the default mode, using an extra memcopy but gives a safe
implementation without dependencies on `reflect` and `unsafe`:
func myfile() []byte {
return []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a}
}
Here is the same functionality, but uses the `.rodata` hack.
The byte slice returned from this example can not be written to without
generating a runtime error.
var _myfile = "\x89\x50\x4e\x47\x0d\x0a\x1a"
func myfile() []byte {
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&_myfile))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(_myfile)
bx.Cap = bx.Len
return b
}
*/
NoMemCopy bool
/*
Compress means the assets are GZIP compressed before being turned
into Go code. The generated function will automatically unzip
the file data when called.
*/
Compress bool
}

120
convert.go Normal file
View File

@ -0,0 +1,120 @@
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
// license. Its contents can be found at:
// http://creativecommons.org/publicdomain/zero/1.0/
package bindata
import (
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"unicode"
)
// ProgressFunc is a callback handler which is fired whenever
// bindata begins translating a new file.
//
// It takes the file path and the current and total file counts.
// This can be used to indicate progress of the conversion.
//
// If this handler returns true, the processing is stopped and
// Translate() returns immediately.
type ProgressFunc func(file string, current, total int) bool
// Translate reads assets from an input directory, converts them
// to Go code and writes new files to the output directory specified
// in the given configuration.
func Translate(c *Config, pf ProgressFunc) error {
toc := make(map[string]string)
err := findFiles(c.Input, c.Prefix, toc)
if err != nil {
return err
}
var current int
for key, value := range toc {
if pf != nil {
current++
if pf(key, current, len(toc)) {
return nil
}
}
_ = value
}
return nil
}
// fillTOC recursively finds all the file paths in the given directory tree.
// They are added to the given map as keys. Values will be safe function names
// for each file, which will be used when generating the output code.
func findFiles(dir, prefix string, toc map[string]string) error {
if len(prefix) > 0 {
dir, _ = filepath.Abs(dir)
prefix, _ = filepath.Abs(prefix)
}
fd, err := os.Open(dir)
if err != nil {
return err
}
defer fd.Close()
list, err := fd.Readdir(0)
if err != nil {
return err
}
for _, file := range list {
key := filepath.Join(dir, file.Name())
if file.IsDir() {
findFiles(key, prefix, toc)
} else {
if strings.HasPrefix(key, prefix) {
key = key[len(prefix):]
}
// If we have a leading slash, get rid of it.
if len(key) > 0 && key[0] == '/' {
key = key[1:]
}
// This shouldn't happen.
if len(key) == 0 {
return fmt.Errorf("Invalid file: %v", filepath.Join(dir, file.Name()))
}
value := safeFunctionName(key)
toc[key] = value
}
}
return nil
}
var regFuncName = regexp.MustCompile(`[^a-zA-Z0-9_]`)
// safeFunctionName converts the given name into a name
// which qualifies as a valid function identifier.
func safeFunctionName(name string) string {
name = strings.ToLower(name)
name = regFuncName.ReplaceAllString(name, "_")
// Identifier can't start with a digit.
if unicode.IsDigit(rune(name[0])) {
name = "_" + name
}
// Get rid of "__" instances for niceness.
for strings.Index(name, "__") > -1 {
name = strings.Replace(name, "__", "_", -1)
}
return name
}

View File

@ -1,106 +0,0 @@
// This work is subject to the CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
// license. Its contents can be found at:
// http://creativecommons.org/publicdomain/zero/1.0/
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
)
// Config defines command line options.
type Config struct {
Input string // Input directory with assets.
Output string // Output directory for generated code.
Tags []string // Build tags to include in output files.
}
// NewConfig create s anew, filled configuration instance
// by reading and parsing command line options.
//
// This function exits the program with an error, if
// any of the command line options are incorrect.
func NewConfig() *Config {
var version bool
var tagstr string
c := new(Config)
flag.Usage = func() {
fmt.Printf("Usage: %s [options] <input> [<output>]\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.StringVar(&tagstr, "tags", "", "Comma-separated list of build tags to include.")
flag.BoolVar(&version, "version", false, "Displays version information.")
flag.Parse()
if version {
fmt.Printf("%s\n", Version())
os.Exit(0)
}
// Make sure we have in/output paths.
if flag.NArg() == 0 {
fmt.Fprintf(os.Stderr, "Missing asset directory.\n")
os.Exit(1)
}
// Test validity of input path.
c.Input = filepath.Clean(flag.Arg(0))
stat, err := os.Lstat(c.Input)
if err != nil {
fmt.Fprintf(os.Stderr, "Input path: %v.\n", err)
os.Exit(1)
}
if !stat.IsDir() {
fmt.Fprintf(os.Stderr, "Input path is not a directory.\n")
os.Exit(1)
}
// Find and test validity of output path.
if flag.NArg() > 1 {
c.Output = filepath.Clean(flag.Arg(1))
stat, err := os.Lstat(c.Output)
if err != nil {
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Output path: %v.\n", err)
os.Exit(1)
}
// Create output directory
err = os.MkdirAll(c.Output, 0744)
if err != nil {
fmt.Fprintf(os.Stderr, "Create Output directory: %v.\n", err)
os.Exit(1)
}
} else if !stat.IsDir() {
fmt.Fprintf(os.Stderr, "Output path is not a directory.\n")
os.Exit(1)
}
} else {
// If no output path is specified, use the current directory.
c.Output, err = os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to determine current working directory: %v\n", err)
os.Exit(1)
}
}
// Process build tags.
if len(tagstr) > 0 {
c.Tags = strings.Split(tagstr, ",")
} else {
c.Tags = append(c.Tags, "debug")
}
return c
}

View File

@ -5,10 +5,116 @@
package main
import (
"flag"
"fmt"
"github.com/jteeuwen/go-bindata"
"os"
"path/filepath"
"strings"
)
func main() {
cfg := NewConfig()
fmt.Println(cfg)
cfg, status := parseArgs()
err := bindata.Translate(cfg, status)
if err != nil {
fmt.Fprintf(os.Stderr, "bindata: %v", err)
}
}
// parseArgs create s a new, filled configuration instance
// by reading and parsing command line options.
//
// This function exits the program with an error, if
// any of the command line options are incorrect.
func parseArgs() (*bindata.Config, bindata.ProgressFunc) {
var version, quiet bool
var tagstr string
c := new(bindata.Config)
flag.Usage = func() {
fmt.Printf("Usage: %s [options] <input> [<output>]\n\n", os.Args[0])
flag.PrintDefaults()
}
flag.StringVar(&tagstr, "tags", "", "Comma-separated list of build tags to include.")
flag.StringVar(&c.Prefix, "prefix", "", "Optional path prefix to strip off map keys and function names.")
flag.BoolVar(&c.NoMemCopy, "nomemcopy", false, "Use a .rodata hack to get rid of unnecessary memcopies. Refer to the documentation to see what implications this carries.")
flag.BoolVar(&c.Compress, "compress", false, "Assets will be GZIP compressed when this flag is specified.")
flag.BoolVar(&version, "version", false, "Displays version information.")
flag.BoolVar(&quiet, "quiet", false, "Do not print conversion status.")
flag.Parse()
if version {
fmt.Printf("%s\n", Version())
os.Exit(0)
}
// Make sure we have in/output paths.
if flag.NArg() == 0 {
fmt.Fprintf(os.Stderr, "Missing asset directory.\n")
os.Exit(1)
}
// Test validity of input path.
c.Input = filepath.Clean(flag.Arg(0))
stat, err := os.Lstat(c.Input)
if err != nil {
fmt.Fprintf(os.Stderr, "Input path: %v.\n", err)
os.Exit(1)
}
if !stat.IsDir() {
fmt.Fprintf(os.Stderr, "Input path is not a directory.\n")
os.Exit(1)
}
// Find and test validity of output path.
if flag.NArg() > 1 {
c.Output = filepath.Clean(flag.Arg(1))
stat, err := os.Lstat(c.Output)
if err != nil {
if !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "Output path: %v.\n", err)
os.Exit(1)
}
// Create output directory
err = os.MkdirAll(c.Output, 0744)
if err != nil {
fmt.Fprintf(os.Stderr, "Create Output directory: %v.\n", err)
os.Exit(1)
}
} else if !stat.IsDir() {
fmt.Fprintf(os.Stderr, "Output path is not a directory.\n")
os.Exit(1)
}
} else {
// If no output path is specified, use the current directory.
c.Output, err = os.Getwd()
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to determine current working directory: %v\n", err)
os.Exit(1)
}
}
// Process build tags.
if len(tagstr) > 0 {
c.Tags = strings.Split(tagstr, ",")
} else {
c.Tags = append(c.Tags, "debug")
}
if quiet {
return c, nil
}
return c, func(file string, current, total int) bool {
fmt.Printf("[%d/%d] %s\n", current, total, file)
return false
}
}

1
testdata/a/test.asset vendored Normal file
View File

@ -0,0 +1 @@
sample file

1
testdata/b/test.asset vendored Normal file
View File

@ -0,0 +1 @@
// sample file