Imported project in git, removed bindata dependency

This commit is contained in:
Christopher Ramey 2019-12-26 07:21:48 -09:00 committed by Christopher Ramey
parent b4cb7a823e
commit 948be25267
18 changed files with 230 additions and 491 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.swp
qurl

View File

@ -1,14 +1,15 @@
qurl.org
========
[qurl.org](https://qurl.org) is a simple url shortening service, in the same vein as [bit.ly](https://bit.ly) and [tinyurl.com](https://tinyurl.com).
[qurl.org](https://qurl.org) is a simple url shortening service, in the same
vein as [bit.ly](https://bit.ly) and [tinyurl.com](https://tinyurl.com).
[qurl.org](https://qurl.org) is written in [Go](https://golang.org) and uses [BoltDB](https://github.com/etcd-io/bbolt) as storage.
[qurl.org](https://qurl.org) is written in [Go](https://golang.org) and uses
[BoltDB](https://github.com/etcd-io/bbolt) as storage.
building from source
--------------------
Assuming the go/bin directory is in your path:
go install qurl/vendor/github.com/simleb/bindata
go generate qurl
go build qurl
go get git.binarythought.com/cdramey/qurl
go generate git.binarythought.com/cdramey/qurl
go build git.binarythought.com/cdramey/qurl

View File

@ -1,7 +1,7 @@
package main
import (
"qurl/storage"
"git.binarythought.com/cdramey/qurl/storage"
"time"
"fmt"
"path"

View File

@ -5,8 +5,8 @@ import (
"fmt"
"net"
"os"
"qurl/qurl"
"qurl/storage"
"git.binarythought.com/cdramey/qurl/storage"
"git.binarythought.com/cdramey/qurl/obj"
"time"
)
@ -42,7 +42,7 @@ func loadjson(stor storage.Storage, filename string) error {
max = e.ID
}
q := &qurl.QURL{
q := &obj.QURL{
ID: e.ID,
URL: e.URL,
}

10
main.go
View File

@ -6,11 +6,15 @@ import (
"net"
"net/http"
"os"
"qurl/pages"
"qurl/storage"
"git.binarythought.com/cdramey/qurl/pages"
"git.binarythought.com/cdramey/qurl/storage"
)
//go:generate bindata -m Assets -r assets -p static -o static/assets.go assets
//go:generate go run ./togo -n Favicon_ico -i assets/favicon.ico -p static -o static/favicon_ico.go
//go:generate go run ./togo -n Index_html -i assets/index.html -p static -o static/index_html.go
//go:generate go run ./togo -n Qurl_css -i assets/qurl.css -p static -o static/qurl_css.go
//go:generate go run ./togo -n Submit_html -i assets/submit.html -p static -o static/submit_html.go
//go:generate go run ./togo -n Usage_html -i assets/usage.html -p static -o static/usage_html.go
func main() {
dburl := flag.String("u", "bolt:./qurl.db", "url to database")

View File

@ -1,4 +1,4 @@
package qurl
package obj
import (
"fmt"

View File

@ -4,9 +4,9 @@ import (
"bytes"
"encoding/json"
"fmt"
"git.binarythought.com/cdramey/qurl/obj"
"net"
"net/http"
"qurl/qurl"
"time"
)
@ -18,8 +18,8 @@ type apijson struct {
func (ctx *RootHandler) ServeAPI(w http.ResponseWriter, r *http.Request) {
var (
j apijson
q *qurl.QURL
j apijson
q *obj.QURL
err error
)
@ -39,11 +39,11 @@ func (ctx *RootHandler) ServeAPI(w http.ResponseWriter, r *http.Request) {
// Deal with URLs that already exist in the database
if q != nil {
j.Exists = true
j.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID))
j.URL = fmt.Sprintf("https://qurl.org/%s", obj.ToString(q.ID))
goto complete
}
q = &qurl.QURL{
q = &obj.QURL{
URL: u,
Created: time.Now(),
}
@ -68,7 +68,7 @@ func (ctx *RootHandler) ServeAPI(w http.ResponseWriter, r *http.Request) {
goto complete
}
j.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID))
j.URL = fmt.Sprintf("https://qurl.org/%s", obj.ToString(q.ID))
complete:

View File

@ -4,18 +4,18 @@ import (
"fmt"
"html/template"
"net/http"
"qurl/static"
"qurl/storage"
"qurl/qurl"
"git.binarythought.com/cdramey/qurl/static"
"git.binarythought.com/cdramey/qurl/storage"
"git.binarythought.com/cdramey/qurl/obj"
)
type RootHandler struct {
Storage storage.Storage
index *static.StaticContent
css *static.StaticContent
favi *static.StaticContent
usage *static.StaticContent
index *StaticContent
css *StaticContent
favi *StaticContent
usage *StaticContent
submit *template.Template
}
@ -44,7 +44,7 @@ func (ctx *RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx.ServeAPI(w, r)
default:
id, err := qurl.FromString(fname[1:len(fname)])
id, err := obj.FromString(fname[1:len(fname)])
if err != nil {
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
return
@ -68,40 +68,24 @@ func (ctx *RootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
func (ctx *RootHandler) Init() error {
// Initialize the static content object for the index page
index := &static.StaticContent{Content: "index.html"}
err := index.Init()
if err != nil {
return err
}
ctx.index = index
ctx.index = &StaticContent{Type: "text/html", Content: static.Index_html}
ctx.index.Init()
// Initialize the static content object for the css
css := &static.StaticContent{Content: "qurl.css"}
err = css.Init()
if err != nil {
return err
}
ctx.css = css
ctx.css = &StaticContent{Type: "text/css", Content: static.Qurl_css}
ctx.css.Init()
// Initialize the static content object favicon
favi := &static.StaticContent{Content: "favicon.ico"}
err = favi.Init()
if err != nil {
return err
}
ctx.favi = favi
ctx.favi = &StaticContent{Type: "image/x-icon", Content: static.Favicon_ico}
ctx.favi.Init()
// Initialize the api usage instructions
usage := &static.StaticContent{Content: "usage.html"}
err = usage.Init()
if err != nil {
return err
}
ctx.usage = usage
ctx.usage = &StaticContent{Type: "text/html", Content: static.Usage_html}
ctx.usage.Init()
// Initialize submit page template
ctx.submit = template.New("submit.html")
_, err = ctx.submit.Parse(string(static.Assets["submit.html"]))
_, err := ctx.submit.Parse(string(static.Submit_html))
if err != nil {
return err
}

79
pages/static.go Normal file
View File

@ -0,0 +1,79 @@
package pages
import (
"bytes"
"compress/gzip"
"crypto/md5"
"fmt"
"net/http"
"strings"
)
type StaticContent struct {
Type string
Content []byte
ETag string
GZIPContent []byte
GZIPETag string
}
func (sc *StaticContent) Init() {
// Populate ETag
sc.ETag = fmt.Sprintf("%x", md5.Sum(sc.Content))
// Set a default Content-Type, if needed
if sc.Type == "" {
sc.Type = "application/octet-stream"
}
var buf bytes.Buffer
gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
defer gz.Close()
if _, err := gz.Write(sc.Content); err != nil {
return
}
if err := gz.Flush(); err != nil {
return
}
// Using gzip encoding adds a minimum of 24 characters to the HTTP
// header, so only accept gzip encoding if we save that much or more
if (buf.Len()+24) < len(sc.Content) {
sc.GZIPContent = buf.Bytes()
sc.GZIPETag = fmt.Sprintf("%x", md5.Sum(sc.GZIPContent))
}
}
func (sc *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
gzok := strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
gzlen := len(sc.GZIPContent)
// Use the correct etag
var localETag string
if gzok && gzlen > 0 {
localETag = sc.GZIPETag
} else {
localETag = sc.ETag
}
// Check the etag, maybe we don't need to send content
remoteETag := r.Header.Get("If-None-Match")
if localETag == remoteETag {
w.WriteHeader(http.StatusNotModified)
return
}
w.Header().Set("ETag", localETag)
w.Header().Set("Content-Type", sc.Type)
// Finally, write our content
if gzok && gzlen > 0 {
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Content-Length", fmt.Sprintf("%d", gzlen))
w.Write(sc.GZIPContent)
} else {
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(sc.Content)))
w.Write(sc.Content)
}
}

View File

@ -3,9 +3,9 @@ package pages
import (
"bytes"
"fmt"
"git.binarythought.com/cdramey/qurl/obj"
"net"
"net/http"
"qurl/qurl"
"time"
)
@ -30,9 +30,9 @@ func (ctx *RootHandler) ServeSubmit(w http.ResponseWriter, r *http.Request) {
if q != nil {
pg.Message = "URL already exists."
pg.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID))
pg.URL = fmt.Sprintf("https://qurl.org/%s", obj.ToString(q.ID))
} else {
q = &qurl.QURL{
q = &obj.QURL{
URL: u,
Created: time.Now(),
}
@ -55,7 +55,7 @@ func (ctx *RootHandler) ServeSubmit(w http.ResponseWriter, r *http.Request) {
}
pg.Message = "URL Added."
pg.URL = fmt.Sprintf("https://qurl.org/%s", qurl.ToString(q.ID))
pg.URL = fmt.Sprintf("https://qurl.org/%s", obj.ToString(q.ID))
}
}
}

4
static/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

View File

@ -1,103 +0,0 @@
package static
import (
"bytes"
"compress/gzip"
"crypto/md5"
"fmt"
"mime"
"net/http"
"path"
"strings"
)
type StaticContent struct {
Content string
ContentType string
ETag string
GZIPContent []byte
GZIPETag string
DisableGZIP bool
}
func (ctx *StaticContent) Init() error {
// Do we have a content type? If not, initialize it
if ctx.ContentType == "" {
ext := path.Ext(ctx.Content)
ctx.ContentType = mime.TypeByExtension(ext)
// Fallback to default mime type
if ctx.ContentType == "" {
ctx.ContentType = "application/octet-stream"
}
}
// Do we have an etag? If not, generate one
if ctx.ETag == "" {
ctx.ETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
}
// If gzip is allowed and we have no gzip etag, generate
// gzip content and etag
if !ctx.DisableGZIP && ctx.GZIPETag == "" {
var buf bytes.Buffer
gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
defer gz.Close()
if _, err := gz.Write(Assets[ctx.Content]); err != nil {
return err
}
if err := gz.Flush(); err != nil {
return err
}
// Check if GZIP actually resulted in a smaller file
if buf.Len() < len(Assets[ctx.Content]) {
ctx.GZIPContent = buf.Bytes()
ctx.GZIPETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
} else {
// If gzip turns out to be ineffective, disable it
ctx.DisableGZIP = true
}
}
return nil
}
func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// By default, don't use gzip
useGZIP := false
// If gzip isn't explicitly disabled, test for it
if !ctx.DisableGZIP {
useGZIP = strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
}
// Use the correct etag
var localETag string
if useGZIP {
localETag = ctx.GZIPETag
} else {
localETag = ctx.ETag
}
// Check the etag, maybe we don't need to send content
remoteETag := r.Header.Get("If-None-Match")
if localETag == remoteETag {
w.WriteHeader(http.StatusNotModified)
return
}
w.Header().Set("ETag", localETag)
w.Header().Set("Content-Type", ctx.ContentType)
// Finally, write our content
if useGZIP {
w.Header().Set("Content-Encoding", "gzip")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(ctx.GZIPContent)))
w.Write(ctx.GZIPContent)
} else {
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(Assets[ctx.Content])))
w.Write(Assets[ctx.Content])
}
}

View File

@ -2,7 +2,7 @@ package bolt
import (
"encoding/binary"
"qurl/qurl"
"git.binarythought.com/cdramey/qurl/obj"
)
var (
@ -15,7 +15,7 @@ var (
BrowserField = []byte{0x03}
)
func (stor *BoltStorage) AddQURL(q *qurl.QURL) error {
func (stor *BoltStorage) AddQURL(q *obj.QURL) error {
tx, err := stor.DB.Begin(true)
if err != nil {
return err
@ -122,7 +122,7 @@ func (stor *BoltStorage) SetQURLSequence(seq uint64) error {
return nil
}
func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
func (stor *BoltStorage) GetQURLByURL(u string) (*obj.QURL, error) {
tx, err := stor.DB.Begin(false)
if err != nil {
return nil, err
@ -149,7 +149,7 @@ func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
return nil, nil
}
q := &qurl.QURL{ID: binary.BigEndian.Uint64(bid)}
q := &obj.QURL{ID: binary.BigEndian.Uint64(bid)}
qu := qb.Get(URLField)
if qu != nil {
@ -159,7 +159,7 @@ func (stor *BoltStorage) GetQURLByURL(u string) (*qurl.QURL, error) {
return q, nil
}
func (stor *BoltStorage) GetQURLByID(id uint64) (*qurl.QURL, error) {
func (stor *BoltStorage) GetQURLByID(id uint64) (*obj.QURL, error) {
tx, err := stor.DB.Begin(false)
if err != nil {
return nil, err
@ -180,7 +180,7 @@ func (stor *BoltStorage) GetQURLByID(id uint64) (*qurl.QURL, error) {
return nil, nil
}
q := &qurl.QURL{ID: id}
q := &obj.QURL{ID: id}
qu := qb.Get(URLField)
if qu != nil {

View File

@ -3,15 +3,15 @@ package storage
import (
"fmt"
"net/url"
"qurl/qurl"
"qurl/storage/bolt"
"git.binarythought.com/cdramey/qurl/obj"
"git.binarythought.com/cdramey/qurl/storage/bolt"
"strings"
)
type Storage interface {
AddQURL(*qurl.QURL) error
GetQURLByURL(string) (*qurl.QURL, error)
GetQURLByID(uint64) (*qurl.QURL, error)
AddQURL(*obj.QURL) error
GetQURLByURL(string) (*obj.QURL, error)
GetQURLByID(uint64) (*obj.QURL, error)
SetQURLSequence(uint64) error
Backup(string) error
Shutdown()

88
togo/main.go Normal file
View File

@ -0,0 +1,88 @@
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"time"
)
func main() {
pkg := flag.String("p", "", "package")
name := flag.String("n", "", "const name")
inputfn := flag.String("i", "", "input file")
outputfn := flag.String("o", "", "output file")
flag.Parse()
if *pkg == "" {
log.Fatal("pkg required")
}
if *name == "" {
log.Fatal("name required")
}
if *inputfn == "" {
log.Fatal("input file required")
}
if *outputfn == "" {
*outputfn = *inputfn + ".go"
}
omod := fmod(*outputfn)
imod := fmod(*inputfn)
if omod.After(imod) {
log.Printf("Refusing to update %s\n", *outputfn)
return
}
ifile, err := os.Open(*inputfn)
if err != nil {
log.Fatal(err)
}
defer ifile.Close()
ofile, err := os.OpenFile(*outputfn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
log.Fatal(err)
}
defer ofile.Close()
fmt.Fprintf(ofile, "package %s\n\nvar %s = []byte{", *pkg, *name)
buf := make([]byte, 4096)
for c := 0; ; {
i, err := ifile.Read(buf)
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
for j := 0; j < i; j++ {
if (c % 13) == 0 {
fmt.Fprintf(ofile, "\n\t")
} else {
fmt.Fprintf(ofile, " ")
}
fmt.Fprintf(ofile, "0x%02x,", buf[j])
c++
}
}
fmt.Fprintf(ofile, "\n}\n")
}
func fmod(fn string) time.Time {
fi, err := os.Stat(fn)
if err != nil {
if os.IsNotExist(err) {
return time.Time{}
}
log.Fatal(err)
}
return fi.ModTime()
}

View File

@ -1,19 +0,0 @@
Copyright (c) 2014 Simon Leblanc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,73 +0,0 @@
# bindata
[![GoDoc](https://godoc.org/github.com/simleb/bindata?status.svg)](http://godoc.org/github.com/simleb/bindata)
[![Coverage Status](https://coveralls.io/repos/github/simleb/bindata/badge.svg?branch=master)](https://coveralls.io/github/simleb/bindata?branch=master)
[![Build Status](https://drone.io/github.com/simleb/bindata/status.png)](https://drone.io/github.com/simleb/bindata/latest)
[![Go Report Card](https://goreportcard.com/badge/github.com/simleb/bindata)](https://goreportcard.com/report/github.com/simleb/bindata)
The `bindata` command embeds binary files as byte arrays into a Go source file.
It is designed with go generate in mind, but can be used on its own as well.
The data is stored as a map of byte slices or strings indexed by the file paths as specified on the command line. The default name of the map is `bindata` but a custom name can be specified on the command line (`-m`).
Multiple files and directories can be provided on the command line. Directories are treated recursively. The keys of the map are the paths of the files relative to the current directory. A different root for the paths can be specified on the command line (`-r`).
By default, the data are saved as byte slices. It is also possible to save them a strings (`-s`).
By default, the package name of the file containing the generate directive is used as the package name of the generated file, or `main` otherwise. A custom package name can also be specified on the command line (`-p`).
The output file can be specified on the command line (`-o`). If a file already exists at this location, it will be overwritten. The file produced is properly formatted and commented. If no output file is specified, the contents are printed on the standard output.
To see the full list of flags, run:
bindata -h
## Example
Given a file `hello.go` containing:
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Running `bindata hello.go` will produce:
package main
// This file is generated. Do not edit directly.
// bindata stores binary files as byte slices indexed by filepaths.
var bindata = map[string][]byte{
"hello.go": []byte{
0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x61, 0x69, 0x6e,
0x0a, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x22, 0x66, 0x6d,
0x74, 0x22, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x20, 0x6d, 0x61, 0x69,
0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x66, 0x6d, 0x74, 0x2e, 0x50,
0x72, 0x69, 0x6e, 0x74, 0x6c, 0x6e, 0x28, 0x22, 0x48, 0x65, 0x6c, 0x6c,
0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x22, 0x29, 0x0a,
0x7d, 0x0a,
},
}
## Example using Go generate
Add a command like this one anywhere in a source file:
//go:generate bindata -o jpegs.go pic1.jpg pic2.jpg pic3.jpg
Then simply run `go generate` and the file `jpegs.go` will be created.
## Todo (maybe)
- [ ] add option to compress data (but then need accessor)
## License
The MIT License (MIT). See [LICENSE.txt](LICENSE.txt).

View File

@ -1,228 +0,0 @@
// The bindata command embeds binary files as byte arrays into a Go source file.
//
// It is designed with go generate in mind, but can be used on its own as well.
//
// The data is stored as a map of byte slices or strings indexed by the
// file paths as specified on the command line. The default name of the
// map is "bindata" but a custom name can be specified on the command line (-m).
//
// Multiple files and directories can be provided on the command line.
// Directories are treated recursively. The keys of the map are the paths
// of the files relative to the current directory. A different root for
// the paths can be specified on the command line (-r).
//
// By default, the data are saved as byte slices.
// It is also possible to save them a strings (-s).
//
// By default, the package name of the file containing the generate directive
// is used as the package name of the generated file, or "main" otherwise.
// A custom package name can also be specified on the command line (-p).
//
// The output file can be specified on the command line (-o).
// If a file already exists at this location, it will be overwritten.
// The file produced is properly formatted and commented.
// If no output file is specified, the contents are printed on the standard output.
//
// To see the full list of flags, run:
// bindata -h
//
// Example
//
// Given a file hello.go containing:
//
// package main
//
// import "fmt"
//
// func main() {
// fmt.Println("Hello, 世界")
// }
//
// Running `bindata hello.go` will produce:
//
// package main
//
// // This file is generated. Do not edit directly.
//
// // bindata stores binary files as byte slices indexed by filepaths.
// var bindata = map[string][]byte{
// "hello.go": []byte{
// 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x61, 0x69, 0x6e,
// 0x0a, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x22, 0x66, 0x6d,
// 0x74, 0x22, 0x0a, 0x0a, 0x66, 0x75, 0x6e, 0x63, 0x20, 0x6d, 0x61, 0x69,
// 0x6e, 0x28, 0x29, 0x20, 0x7b, 0x0a, 0x09, 0x66, 0x6d, 0x74, 0x2e, 0x50,
// 0x72, 0x69, 0x6e, 0x74, 0x6c, 0x6e, 0x28, 0x22, 0x48, 0x65, 0x6c, 0x6c,
// 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, 0x95, 0x8c, 0x22, 0x29, 0x0a,
// 0x7d, 0x0a,
// },
// }
//
// Example using go generate
//
// Add a command like this one anywhere in a source file:
// //go:generate bindata -o jpegs.go pic1.jpg pic2.jpg pic3.jpg
// Then simply run
// go generate
// and the file jpegs.go will be created.
package main
import (
"bufio"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"text/template"
)
// tmpl is the template of the generated Go source file.
var tmpl = template.Must(template.New("bindata").Parse(`package {{.Pkg}}
// This file is generated. Do not edit directly.
// {{.Map}} stores binary files as {{if .AsString}}strings{{else}}byte slices{{end}} indexed by file paths.
var {{.Map}} = map[string]{{if .AsString}}string{{else}}[]byte{{end}}{{"{"}}{{range $name, $data := .Files}}
{{printf "%#v" $name}}: {{printf "%#v" $data}},{{end}}
}
`))
// vars contains the variables required by the template.
var vars struct {
Pkg string
Map string
AsString bool
Files map[string]fmt.Formatter
}
func main() {
if err := run(); err != nil {
fmt.Println("bindata:", err)
os.Exit(1)
}
}
// run executes the program.
func run() error {
// use GOPACKAGE (set by go generate) as default package name if available
pkg := os.Getenv("GOPACKAGE")
if pkg == "" {
pkg = "main"
}
var out, prefix string
fs := flag.NewFlagSet("bindata", flag.ExitOnError)
fs.StringVar(&out, "o", "", "output file (default: stdout)")
fs.StringVar(&vars.Pkg, "p", pkg, "name of the package")
fs.StringVar(&vars.Map, "m", "bindata", "name of the map variable")
fs.StringVar(&prefix, "r", "", "root path for map keys")
fs.BoolVar(&vars.AsString, "s", false, "save data as strings")
if err := fs.Parse(os.Args[1:]); err != nil {
return err
}
vars.Files = make(map[string]fmt.Formatter)
for _, path := range fs.Args() {
if err := AddPath(path, prefix); err != nil {
return err
}
}
var file *os.File
if out != "" {
var err error
if file, err = os.Create(out); err != nil {
return err
}
} else {
file = os.Stdout
}
return tmpl.Execute(file, vars)
}
// AddPath add files to the slice in vars recursively.
func AddPath(path, prefix string) error {
fi, err := os.Stat(path)
if err != nil {
return err
}
if fi.IsDir() {
dir, err := os.Open(path)
if err != nil {
return err
}
files, err := dir.Readdirnames(0)
if err != nil {
return err
}
for _, file := range files {
if err := AddPath(filepath.Join(path, file), prefix); err != nil {
return err
}
}
} else {
file, err := os.Open(path)
if err != nil {
return err
}
path, err := filepath.Rel(prefix, path)
if err != nil {
return err
}
if vars.AsString {
vars.Files[path] = StringFormatter{file}
} else {
vars.Files[path] = ByteSliceFormatter{file}
}
}
return nil
}
// A ByteSliceFormatter is a byte slice pretty printing io.Reader.
type ByteSliceFormatter struct {
io.Reader
}
// Format pretty prints the bytes read from the ByteSliceFormatter.
func (f ByteSliceFormatter) Format(s fmt.State, c rune) {
buf := bufio.NewReader(f)
const cols = 12 // number of columns in the formatted byte slice.
fmt.Fprintf(s, "[]byte{")
b, err := buf.ReadByte()
for i := 0; err == nil; i++ {
if i%cols == 0 {
fmt.Fprintf(s, "\n\t\t")
} else {
fmt.Fprintf(s, " ")
}
fmt.Fprintf(s, "%#02x,", b)
b, err = buf.ReadByte()
}
fmt.Fprintf(s, "\n\t}")
}
// A StringFormatter is a string pretty printing io.Reader.
type StringFormatter struct {
io.Reader
}
// Format pretty prints the bytes read from the StringFormatter.
func (f StringFormatter) Format(s fmt.State, c rune) {
buf := bufio.NewReader(f)
const cols = 16 // number of bytes per line in the formatted string.
fmt.Fprintf(s, `"`)
b, err := buf.ReadByte()
for i := 0; err == nil; i++ {
if i%cols == 0 {
fmt.Fprintf(s, "\" +\n\t\t\"")
}
fmt.Fprintf(s, "\\x%02x", b)
b, err = buf.ReadByte()
}
fmt.Fprintf(s, `"`)
}