diff --git a/asset.go b/asset.go new file mode 100644 index 0000000..6f9e50d --- /dev/null +++ b/asset.go @@ -0,0 +1,12 @@ +// 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 + +// File is an asset entry for the table of contents. +type Asset struct { + Path string // Full file path. + Name string // Key used in TOC -- name by which asset is referenced. + Func string // Function name for the procedure returning the asset contents. +} diff --git a/bytewriter.go b/bytewriter.go new file mode 100644 index 0000000..c9b602c --- /dev/null +++ b/bytewriter.go @@ -0,0 +1,37 @@ +// 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" + "io" +) + +var newline = []byte{'\n'} + +type ByteWriter struct { + io.Writer + c int +} + +func (w *ByteWriter) Write(p []byte) (n int, err error) { + if len(p) == 0 { + return + } + + for n = range p { + if w.c%12 == 0 { + w.Writer.Write(newline) + w.c = 0 + } + + fmt.Fprintf(w.Writer, "0x%02x,", p[n]) + w.c++ + } + + n++ + + return +} diff --git a/config.go b/config.go index c9d7f96..e27a860 100644 --- a/config.go +++ b/config.go @@ -6,11 +6,14 @@ package bindata // Config defines a set of options for the asset conversion. type Config struct { + // Name of the package to use. Defaults to 'main'. + Package string + // 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 + Tags string // Input defines the directory path, containing all asset files. // This may contain sub directories, which will be included in the @@ -86,7 +89,16 @@ type Config struct { /* Compress means the assets are GZIP compressed before being turned into Go code. The generated function will automatically unzip - the file data when called. + the file data when called. Defaults to true. */ Compress bool } + +// NewConfig returns a default configuration struct. +func NewConfig() *Config { + c := new(Config) + c.Package = "main" + c.NoMemCopy = false + c.Compress = true + return c +} diff --git a/convert.go b/convert.go index 757add6..7f77ddc 100644 --- a/convert.go +++ b/convert.go @@ -27,32 +27,67 @@ type ProgressFunc func(file string, current, total int) bool // 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) + var toc []Asset + err := findFiles(c.Input, c.Prefix, &toc) if err != nil { return err } + // Open output files. + debug, release, err := openOutput(c.Output) + if err != nil { + return err + } + + defer func() { + debug.Close() + release.Close() + }() + + // Prepare files -- write package header and build tags. + writeDebugHeader(debug, c) + writeReleaseHeader(release, c) + + // Convert assets and write them to the output files. var current int - for key, value := range toc { + for i := range toc { if pf != nil { current++ - if pf(key, current, len(toc)) { + if pf(toc[i].Path, current, len(toc)) { return nil } } - _ = value + writeDebug(debug, c, &toc[i]) + writeRelease(release, c, &toc[i]) } + // Generate TOC file. return nil } +// openOutput opens two output files. One for debug code and +// one for release code. +func openOutput(dir string) (*os.File, *os.File, error) { + debug, err := os.Create(filepath.Join(dir, "bindata_debug.go")) + if err != nil { + return nil, nil, err + } + + release, err := os.Create(filepath.Join(dir, "bindata_release.go")) + if err != nil { + debug.Close() + return nil, nil, err + } + + return debug, release, 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 { +func findFiles(dir, prefix string, toc *[]Asset) error { if len(prefix) > 0 { dir, _ = filepath.Abs(dir) prefix, _ = filepath.Abs(prefix) @@ -71,28 +106,32 @@ func findFiles(dir, prefix string, toc map[string]string) error { } for _, file := range list { - key := filepath.Join(dir, file.Name()) + var asset Asset + asset.Path = filepath.Join(dir, file.Name()) + asset.Name = asset.Path 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 + findFiles(asset.Path, prefix, toc) + continue } + + if strings.HasPrefix(asset.Name, prefix) { + asset.Name = asset.Name[len(prefix):] + } + + // If we have a leading slash, get rid of it. + if len(asset.Name) > 0 && asset.Name[0] == '/' { + asset.Name = asset.Name[1:] + } + + // This shouldn't happen. + if len(asset.Name) == 0 { + return fmt.Errorf("Invalid file: %v", asset.Path) + } + + asset.Func = safeFunctionName(asset.Name) + asset.Path, _ = filepath.Abs(asset.Path) + *toc = append(*toc, asset) } return nil @@ -106,15 +145,20 @@ 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) } + // Leading underscores are silly (unless they prefix a digit (see below)). + for len(name) > 1 && name[0] == '_' { + name = name[1:] + } + + // Identifier can't start with a digit. + if unicode.IsDigit(rune(name[0])) { + name = "_" + name + } + return name } diff --git a/debug.go b/debug.go new file mode 100644 index 0000000..0b70c8a --- /dev/null +++ b/debug.go @@ -0,0 +1,69 @@ +// 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" + "io" +) + +// writeDebugHeader writes output file headers with the given build tags. +// This targets debug builds. +func writeDebugHeader(w io.Writer, c *Config) { + // Write build tags, if applicable. + if len(c.Tags) > 0 { + fmt.Fprintf(w, "// +build !release %s\n\n", c.Tags) + } else { + fmt.Fprintf(w, "// +build !release\n\n") + } + + // Write package declaration + fmt.Fprintf(w, "package %s\n\n", c.Package) + + // Define packages we need to import. + // And add the asset_read function. This is called + // from asset-specific functions. + fmt.Fprintf(w, `import ( + "bytes" + "io" + "log" + "os" +) + +// bindata_read reads the given file from disk. +// It panics if anything went wrong. +func bindata_read(path, name string) []byte { + fd, err := os.Open(path) + if err != nil { + log.Fatalf("Read %%s: %%v", name, err) + } + + defer fd.Close() + + var buf bytes.Buffer + _, err = io.Copy(&buf, fd) + if err != nil { + log.Fatalf("Read %%s: %%v", name, err) + } + + return buf.Bytes() +} + +`) +} + +// writeDebug write a debug entry for the given asset. +// A debug entry is simply a function which reads the asset from +// the original file (e.g.: from disk). +func writeDebug(w io.Writer, c *Config, asset *Asset) { + fmt.Fprintf(w, `func %s() []byte { + return bindata_read( + %q, + %q, + ) +} + +`, asset.Func, asset.Path, asset.Name) +} diff --git a/go-bindata/main.go b/go-bindata/main.go index bd07afe..df3f006 100644 --- a/go-bindata/main.go +++ b/go-bindata/main.go @@ -10,7 +10,6 @@ import ( "github.com/jteeuwen/go-bindata" "os" "path/filepath" - "strings" ) func main() { @@ -29,19 +28,19 @@ func main() { // 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) + c := bindata.NewConfig() flag.Usage = func() { fmt.Printf("Usage: %s [options] []\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.StringVar(&c.Tags, "tags", c.Tags, "Comma-separated list of build tags to include.") + flag.StringVar(&c.Prefix, "prefix", c.Prefix, "Optional path prefix to strip off map keys and function names.") + flag.StringVar(&c.Package, "pkg", c.Package, "Package name to use in the generated code.") + flag.BoolVar(&c.NoMemCopy, "nomemcopy", c.NoMemCopy, "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", c.Compress, "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() @@ -102,13 +101,6 @@ func parseArgs() (*bindata.Config, bindata.ProgressFunc) { } } - // Process build tags. - if len(tagstr) > 0 { - c.Tags = strings.Split(tagstr, ",") - } else { - c.Tags = append(c.Tags, "debug") - } - if quiet { return c, nil } diff --git a/go-bindata/version.go b/go-bindata/version.go index 25e75cd..a12508e 100644 --- a/go-bindata/version.go +++ b/go-bindata/version.go @@ -10,7 +10,7 @@ import ( ) const ( - AppName = "bindata" + AppName = "go-bindata" AppVersionMajor = 3 AppVersionMinor = 1 ) diff --git a/release.go b/release.go new file mode 100644 index 0000000..a893001 --- /dev/null +++ b/release.go @@ -0,0 +1,215 @@ +// 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 ( + "compress/gzip" + "fmt" + "io" + "os" +) + +// writeReleaseHeader writes output file headers with the given build tags. +// This targets release builds. +func writeReleaseHeader(w io.Writer, c *Config) { + // Write build tags, if applicable. + if len(c.Tags) > 0 { + fmt.Fprintf(w, "// +build release %s\n\n", c.Tags) + } else { + fmt.Fprintf(w, "// +build release\n\n") + } + + // Write package declaration + fmt.Fprintf(w, "package %s\n\n", c.Package) + + if c.Compress { + if c.NoMemCopy { + header_compressed_nomemcopy(w) + } else { + header_compressed_memcopy(w) + } + } else { + if c.NoMemCopy { + header_uncompressed_nomemcopy(w) + } else { + header_uncompressed_memcopy(w) + } + } +} + +// writeRelease write a release entry for the given asset. +// A release entry is a function which embeds and returns +// the file's byte content. +func writeRelease(w io.Writer, c *Config, asset *Asset) error { + fd, err := os.Open(asset.Path) + if err != nil { + return err + } + + defer fd.Close() + + if c.Compress { + if c.NoMemCopy { + compressed_nomemcopy(w, asset, fd) + } else { + compressed_memcopy(w, asset, fd) + } + } else { + if c.NoMemCopy { + uncompressed_nomemcopy(w, asset, fd) + } else { + uncompressed_memcopy(w, asset, fd) + } + } + + return nil +} + +func header_compressed_nomemcopy(w io.Writer) { + fmt.Fprintf(w, ` +import ( + "bytes" + "compress/gzip" + "io" + "log" + "reflect" + "unsafe" +) + +func bindata_read(data, name string) []byte { + var empty [0]byte + sx := (*reflect.StringHeader)(unsafe.Pointer(&data)) + b := empty[:] + bx := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bx.Data = sx.Data + bx.Len = len(data) + bx.Cap = bx.Len + + gz, err := gzip.NewReader(bytes.NewBuffer(b)) + if err != nil { + log.Fatalf("Read %%q: %%v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + gz.Close() + + if err != nil { + log.Fatalf("Read %%q: %%v", name, err) + } + + return buf.Bytes() +} + +`) +} + +func header_compressed_memcopy(w io.Writer) { + fmt.Fprintf(w, ` +import ( + "bytes" + "compress/gzip" + "io" + "log" +) + +func bindata_read(data []byte, name string) []byte { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + log.Fatalf("Read %%q: %%v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + gz.Close() + + if err != nil { + log.Fatalf("Read %%q: %%v", name, err) + } + + return buf.Bytes() +} + +`) +} + +func header_uncompressed_nomemcopy(w io.Writer) { + fmt.Fprintf(w, ` +import ( + "log" + "reflect" + "unsafe" +) + +func bindata_read(data, name string) []byte { + var empty [0]byte + sx := (*reflect.StringHeader)(unsafe.Pointer(&data)) + b := empty[:] + bx := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + bx.Data = sx.Data + bx.Len = len(data) + bx.Cap = bx.Len + return b +} + +`) +} + +func header_uncompressed_memcopy(w io.Writer) { + // nop -- We require no imports or helper functions. +} + +func compressed_nomemcopy(w io.Writer, asset *Asset, r io.Reader) { + fmt.Fprintf(w, `var _%s = "`, asset.Func) + + gz := gzip.NewWriter(&StringWriter{Writer: w}) + io.Copy(gz, r) + gz.Close() + + fmt.Fprintf(w, `" + +func %s() []byte { + return bindata_read( + _%s, + %q, + ) +} + +`, asset.Func, asset.Func, asset.Name) +} + +func compressed_memcopy(w io.Writer, asset *Asset, r io.Reader) { + fmt.Fprintf(w, `func %s() []byte { + return bindata_read([]byte{`, asset.Func) + + gz := gzip.NewWriter(&ByteWriter{Writer: w}) + io.Copy(gz, r) + gz.Close() + + fmt.Fprintf(w, ` + }, + %q, + ) +} + +`, asset.Name) +} + +func uncompressed_nomemcopy(w io.Writer, asset *Asset, r io.Reader) { + +} + +func uncompressed_memcopy(w io.Writer, asset *Asset, r io.Reader) { + fmt.Fprintf(w, `func %s() []byte { + return []byte{`, asset.Func) + + io.Copy(&ByteWriter{Writer: w}, r) + + fmt.Fprintf(w, ` + } +} + +`) +} diff --git a/stringwriter.go b/stringwriter.go new file mode 100644 index 0000000..278f35b --- /dev/null +++ b/stringwriter.go @@ -0,0 +1,30 @@ +// 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" + "io" +) + +type StringWriter struct { + io.Writer + c int +} + +func (w *StringWriter) Write(p []byte) (n int, err error) { + if len(p) == 0 { + return + } + + for n = range p { + fmt.Fprintf(w.Writer, "\\x%02x", p[n]) + w.c++ + } + + n++ + + return +} diff --git a/testdata/gophercolor.png b/testdata/gophercolor.png deleted file mode 100644 index c9697c7..0000000 Binary files a/testdata/gophercolor.png and /dev/null differ diff --git a/testdata/a/test.asset b/testdata/in/a/test.asset similarity index 100% rename from testdata/a/test.asset rename to testdata/in/a/test.asset diff --git a/testdata/b/test.asset b/testdata/in/b/test.asset similarity index 100% rename from testdata/b/test.asset rename to testdata/in/b/test.asset diff --git a/testdata/in/c/test.asset b/testdata/in/c/test.asset new file mode 100644 index 0000000..ab2a2d9 --- /dev/null +++ b/testdata/in/c/test.asset @@ -0,0 +1 @@ +// sample file diff --git a/testdata/out/bindata_debug.go b/testdata/out/bindata_debug.go new file mode 100644 index 0000000..c72c02b --- /dev/null +++ b/testdata/out/bindata_debug.go @@ -0,0 +1,51 @@ +// +build !release + +package main + +import ( + "bytes" + "io" + "log" + "os" +) + +// bindata_read reads the given file from disk. +// It panics if anything went wrong. +func bindata_read(path, name string) []byte { + fd, err := os.Open(path) + if err != nil { + log.Fatalf("Read %s: %v", name, err) + } + + defer fd.Close() + + var buf bytes.Buffer + _, err = io.Copy(&buf, fd) + if err != nil { + log.Fatalf("Read %s: %v", name, err) + } + + return buf.Bytes() +} + +func testdata_in_b_test_asset() []byte { + return bindata_read( + "/a/code/go/src/github.com/jteeuwen/go-bindata/testdata/in/b/test.asset", + "../testdata/in/b/test.asset", + ) +} + +func testdata_in_a_test_asset() []byte { + return bindata_read( + "/a/code/go/src/github.com/jteeuwen/go-bindata/testdata/in/a/test.asset", + "../testdata/in/a/test.asset", + ) +} + +func testdata_in_c_test_asset() []byte { + return bindata_read( + "/a/code/go/src/github.com/jteeuwen/go-bindata/testdata/in/c/test.asset", + "../testdata/in/c/test.asset", + ) +} + diff --git a/testdata/out/bindata_release.go b/testdata/out/bindata_release.go new file mode 100644 index 0000000..61aa67f --- /dev/null +++ b/testdata/out/bindata_release.go @@ -0,0 +1,24 @@ +// +build release + +package main + +func testdata_in_b_test_asset() []byte { + return []byte{ +0x2f,0x2f,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x66,0x69, +0x6c,0x65,0x0a, + } +} + +func testdata_in_a_test_asset() []byte { + return []byte{ +0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x66,0x69,0x6c,0x65,0x0a, + } +} + +func testdata_in_c_test_asset() []byte { + return []byte{ +0x2f,0x2f,0x20,0x73,0x61,0x6d,0x70,0x6c,0x65,0x20,0x66,0x69, +0x6c,0x65,0x0a, + } +} +