Fix generated code for both compressed and uncompressed modes. We now no longer require a memcpy to read the static file data and can get it directly from the .rodata section. This addresses issue #1

This commit is contained in:
jim teeuwen 2012-06-13 14:17:25 +02:00
parent ed2cd8b81c
commit 37af2a4b27
7 changed files with 2887 additions and 3767 deletions

View File

@ -1,57 +1,37 @@
## bindata ## bindata
This tool converts any file into managable Go source code. Useful for embedding This tool converts any file into managable Go source code. Useful for embedding
binary data into a go program. The file data is gzip compressed before being binary data into a go program. The file data is optionally gzip compressed
converted to a raw byte slice. before being converted to a raw byte slice.
### Usage ### Usage
The simplest invocation is to pass it only the input file name. The simplest invocation is to pass it only the input file name.
The output file and code settings are inferred from this automatically. The output file and code settings are inferred from this automatically.
$ bindata -i testdata/gophercolor.png $ go-bindata -i testdata/gophercolor.png
[w] No output file specified. Using 'testdata/gophercolor.png.go'. [w] No output file specified. Using 'testdata/gophercolor.png.go'.
[w] No package name specified. Using 'main'. [w] No package name specified. Using 'main'.
[w] No function name specified. Using 'gophercolor_png'. [w] No function name specified. Using 'gophercolor_png'.
[i] Done. [i] Done.
This creates the "testdata/gophercolor.png.go" file which has a package This creates the `testdata/gophercolor.png.go` file which has a package
declaration with name 'main' and one function named 'gophercolor_png'. declaration with name `main` a variable holding the file data in a read-only
It looks like this: string and one function named `gophercolor_png` with the following signature:
// gophercolor_png returns the decompressed binary data. func gophercolor_png() []byte
// It panics if an error occurred.
func gophercolor_png() []byte {
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{
...
}))
if err != nil {
panic("Decompression failed: " + err.Error())
}
var b bytes.Buffer
io.Copy(&b, gz)
gz.Close()
return b.Bytes()
}
You can now simply include the new .go file in your program and call You can now simply include the new .go file in your program and call
gophercolor_png() to get the uncompressed image data. The function panics `gophercolor_png()` to get the uncompressed image data. The function panics
if something went wrong during decompression. This makes any faults appearant if something went wrong during decompression. See the testdata directory for
during initialization of your program, so it can quickly be fixed. Additionally, example input and output.
this approach allows us to assign the decompressed file data to global
variables where necessary.
See the testdata directory for example input and output.
Aternatively, you can pipe the input file data into stdin. bindata will then Aternatively, you can pipe the input file data into stdin. bindata will then
spit out the generated Go code to stdout. This does require explicitly naming 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 desired function name, as it can not be inferred from the input data.
The package name will still default to 'main'. The package name will still default to 'main'.
$ cat testdata/gophercolor.png | ./bindata -f gophercolor_png | gofmt $ cat testdata/gophercolor.png | go-bindata -f gophercolor_png | gofmt
Invoke the program with the -h flag for more options. Invoke the program with the -h flag for more options.
@ -59,12 +39,12 @@ Invoke the program with the -h flag for more options.
### Optional compression ### Optional compression
When the `-u` flag is given, the supplied resource is *not* GZIP compressed When the `-u` flag is given, the supplied resource is *not* GZIP compressed
before being turned into Go code. This also alters the generated output in that before being turned into Go code. The data should still be accessed through
we no longer need a function that decompresses the data. The resource's raw a function call, so nothing changes in the usage of the generated file.
byte data is simply assigned to a global variable of the same name as the
function would otherwise get. This feature is useful if you do not care for This feature is useful if you do not care for compression, or the supplied
compression, or the supplied resource is already compressed. Doing it again resource is already compressed. Doing it again would not add any value and may
would not add any value and may even increase the size of the data. even increase the size of the data.
The default behaviour of the program is to use compression. The default behaviour of the program is to use compression.

View File

@ -1,54 +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 (
"compress/gzip"
"fmt"
"io"
)
// Translate the input file without GZIP compression.
// input -> gowriter -> output.
func translate_uncompressed(input io.Reader, output io.Writer, pkgname, funcname string) {
fmt.Fprintf(output, "package %s\n\nvar %s []byte = []byte{", pkgname, funcname)
io.Copy(&GoWriter{Writer: output}, input)
fmt.Fprint(output, "\n}")
}
// Translate the input file with GZIP compression.
// input -> gzip -> gowriter -> output.
func translate_compressed(input io.Reader, output io.Writer, pkgname, funcname string) {
fmt.Fprintf(output, `package %s
import (
"bytes"
"compress/gzip"
"io"
)
// %s returns the decompressed binary data.
// It panics if an error occurred.
func %s() []byte {
gz, err := gzip.NewReader(bytes.NewBuffer([]byte{`, pkgname, funcname, funcname)
gz := gzip.NewWriter(&GoWriter{Writer: output})
io.Copy(gz, input)
gz.Close()
fmt.Fprint(output, `
}))
if err != nil {
panic("Decompression failed: " + err.Error())
}
var b bytes.Buffer
io.Copy(&b, gz)
gz.Close()
return b.Bytes()
}`)
}

View File

@ -9,7 +9,7 @@ import (
"io" "io"
) )
var newline = []byte{'\n'} var line = []byte("\"+\n\"")
type GoWriter struct { type GoWriter struct {
io.Writer io.Writer
@ -22,12 +22,12 @@ func (w *GoWriter) Write(p []byte) (n int, err error) {
} }
for n = range p { for n = range p {
if w.c%12 == 0 { if w.c%16 == 0 {
w.Writer.Write(newline) w.Writer.Write(line)
w.c = 0 w.c = 0
} }
fmt.Fprintf(w.Writer, "0x%02x,", p[n]) fmt.Fprintf(w.Writer, "\\x%02x", p[n])
w.c++ w.c++
} }

16
main.go
View File

@ -16,7 +16,7 @@ import (
const ( const (
AppName = "bindata" AppName = "bindata"
AppVersion = "0.7" AppVersion = "0.8"
) )
func main() { func main() {
@ -95,12 +95,7 @@ func main() {
// Read the input file, transform it into a gzip compressed data stream and // Read the input file, transform it into a gzip compressed data stream and
// write it out as a go source file. // write it out as a go source file.
if pipe { if pipe {
if *uncompressed { translate(os.Stdin, os.Stdout, *pkgname, *funcname, *uncompressed)
translate_uncompressed(os.Stdin, os.Stdout, *pkgname, *funcname)
} else {
translate_compressed(os.Stdin, os.Stdout, *pkgname, *funcname)
}
fmt.Fprintln(os.Stdout, "[i] Done.") fmt.Fprintln(os.Stdout, "[i] Done.")
return return
} }
@ -122,11 +117,6 @@ func main() {
defer fd.Close() defer fd.Close()
if *uncompressed { translate(fs, fd, *pkgname, *funcname, *uncompressed)
translate_uncompressed(fs, fd, *pkgname, *funcname)
} else {
translate_compressed(fs, fd, *pkgname, *funcname)
}
fmt.Fprintln(os.Stdout, "[i] Done.") fmt.Fprintln(os.Stdout, "[i] Done.")
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

88
translate.go Normal file
View File

@ -0,0 +1,88 @@
// 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 (
"compress/gzip"
"fmt"
"io"
)
// Translate the input file with optional GZIP compression.
// input [-> gzip] -> gowriter -> output.
func translate(input io.Reader, output io.Writer, pkgname, funcname string, uncompressed bool) {
fmt.Fprintf(output, `package %s
import (`, pkgname)
if uncompressed {
fmt.Fprint(output, `
"reflect"
"unsafe"`)
} else {
fmt.Fprint(output, `
"bytes"
"compress/gzip"
"io"
"reflect"
"unsafe"`)
}
fmt.Fprintf(output, `
)
var _%s = "`, funcname)
if uncompressed {
io.Copy(&GoWriter{Writer: output}, input)
} else {
gz := gzip.NewWriter(&GoWriter{Writer: output})
io.Copy(gz, input)
gz.Close()
}
fmt.Fprintf(output, `"
// %s returns the binary data for a given file.
func %s() []byte {`, funcname, funcname)
if uncompressed {
fmt.Fprintf(output, `
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&_%s))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(_%s)
bx.Cap = bx.Len
return b`, funcname, funcname)
} else {
fmt.Fprintf(output, `
// This bit of black magic ensures we do not get
// unneccesary memcpy's and can read directly from
// the .rodata section.
var empty [0]byte
sx := (*reflect.StringHeader)(unsafe.Pointer(&_%s))
b := empty[:]
bx := (*reflect.SliceHeader)(unsafe.Pointer(&b))
bx.Data = sx.Data
bx.Len = len(_%s)
bx.Cap = bx.Len
gz, err := gzip.NewReader(bytes.NewBuffer(b))
if err != nil {
panic("Decompression failed: " + err.Error())
}
var buf bytes.Buffer
io.Copy(&buf, gz)
gz.Close()
return buf.Bytes()`, funcname, funcname)
}
fmt.Fprintf(output, "\n}")
}