Add go.rice as dependency
parent
3edb2ed2b1
commit
c0c6118582
@ -0,0 +1,4 @@
|
|||||||
|
Geert-Johan Riemer <geertjohan@geertjohan.net>
|
||||||
|
Paul Maddox <paul.maddox@gmail.com>
|
||||||
|
Vincent Petithory <vincent.petithory@gmail.com>
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2013, Geert-Johan Riemer
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,151 @@
|
|||||||
|
## go.rice
|
||||||
|
|
||||||
|
[![Wercker](https://img.shields.io/wercker/ci/54c7af4dcc09f9963725bb25.svg?style=flat-square)](https://app.wercker.com/#applications/54c7af4dcc09f9963725bb25)
|
||||||
|
[![Godoc](https://img.shields.io/badge/godoc-go.rice-blue.svg?style=flat-square)](https://godoc.org/github.com/GeertJohan/go.rice)
|
||||||
|
|
||||||
|
go.rice is a [Go](http://golang.org) package that makes working with resources such as html,js,css,images and templates very easy. During development `go.rice` will load required files directly from disk. Upon deployment it is easy to add all resource files to a executable using the `rice` tool, without changing the source code for your package. go.rice provides several methods to add resources to a binary.
|
||||||
|
|
||||||
|
### What does it do?
|
||||||
|
The first thing go.rice does is finding the correct absolute path for your resource files. Say you are executing go binary in your home directory, but your `html-files` are located in `$GOPATH/src/yourApplication/html-files`. `go.rice` will lookup the correct path for that directory (relative to the location of yourApplication). The only thing you have to do is include the resources using `rice.FindBox("html-files")`.
|
||||||
|
|
||||||
|
This only works when the source is available to the machine executing the binary. Which is always the case when the binary was installed with `go get` or `go install`. It might occur that you wish to simply provide a binary, without source. The `rice` tool analyses source code and finds call's to `rice.FindBox(..)` and adds the required directories to the executable binary. There are several methods to add these resources. You can 'embed' by generating go source code, or append the resource to the executable as zip file. In both cases `go.rice` will detect the embedded or appended resources and load those, instead of looking up files from disk.
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
Use `go get` to install the package the `rice` tool.
|
||||||
|
```
|
||||||
|
go get github.com/GeertJohan/go.rice
|
||||||
|
go get github.com/GeertJohan/go.rice/rice
|
||||||
|
```
|
||||||
|
|
||||||
|
### Package usage
|
||||||
|
|
||||||
|
Import the package: `import "github.com/GeertJohan/go.rice"`
|
||||||
|
|
||||||
|
**Serving a static content folder over HTTP with a rice Box**
|
||||||
|
```go
|
||||||
|
http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Service a static content folder over HTTP at a non-root location**
|
||||||
|
```go
|
||||||
|
box := rice.MustFindBox("cssfiles")
|
||||||
|
cssFileServer := http.StripPrefix("/css/", http.FileServer(box.HTTPBox()))
|
||||||
|
http.Handle("/css/", cssFileServer)
|
||||||
|
http.ListenAndServe(":8080", nil)
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the *trailing slash* in `/css/` in both the call to
|
||||||
|
`http.StripPrefix` and `http.Handle`.
|
||||||
|
|
||||||
|
**Loading a template**
|
||||||
|
```go
|
||||||
|
// find a rice.Box
|
||||||
|
templateBox, err := rice.FindBox("example-templates")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// get file contents as string
|
||||||
|
templateString, err := templateBox.String("message.tmpl")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// parse and execute the template
|
||||||
|
tmplMessage, err := template.New("message").Parse(templateString)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tmplMessage.Execute(os.Stdout, map[string]string{"Message": "Hello, world!"})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Never call `FindBox()` or `MustFindBox()` from an `init()` function, as the boxes might have not been loaded at that time.
|
||||||
|
|
||||||
|
### Tool usage
|
||||||
|
The `rice` tool lets you add the resources to a binary executable so the files are not loaded from the filesystem anymore. This creates a 'standalone' executable. There are several ways to add the resources to a binary, each has pro's and con's but all will work without requiring changes to the way you load the resources.
|
||||||
|
|
||||||
|
#### embed-go
|
||||||
|
**Embed resources by generating Go source code**
|
||||||
|
|
||||||
|
This method must be executed before building. It generates a single Go source file called *rice-box.go* for each package, that is compiled by the go compiler into the binary.
|
||||||
|
|
||||||
|
The downside with this option is that the generated go source files can become very large, which will slow down compilation and require lots of memory to compile.
|
||||||
|
|
||||||
|
Execute the following commands:
|
||||||
|
```
|
||||||
|
rice embed-go
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
*A Note on Symbolic Links*: `embed-go` uses the `os.Walk` function
|
||||||
|
from the standard library. The `os.Walk` function does **not** follow
|
||||||
|
symbolic links. So, when creating a box, be aware that any symbolic
|
||||||
|
links inside your box's directory will not be followed. **However**,
|
||||||
|
if the box itself is a symbolic link, its actual location will be
|
||||||
|
resolved first and then walked. In summary, if your box location is a
|
||||||
|
symbolic link, it will be followed but none of the symbolic links in
|
||||||
|
the box will be followed.
|
||||||
|
|
||||||
|
#### embed-syso
|
||||||
|
**Embed resources by generating a coff .syso file and some .go source code**
|
||||||
|
|
||||||
|
** This method is experimental and should not be used for production systems just yet **
|
||||||
|
|
||||||
|
This method must be executed before building. It generates a COFF .syso file and Go source file that are compiled by the go compiler into the binary.
|
||||||
|
|
||||||
|
Execute the following commands:
|
||||||
|
```
|
||||||
|
rice embed-syso
|
||||||
|
go build
|
||||||
|
```
|
||||||
|
|
||||||
|
#### append
|
||||||
|
**Append resources to executable as zip file**
|
||||||
|
|
||||||
|
This method changes an already built executable. It appends the resources as zip file to the binary. It makes compilation a lot faster and can be used with large resource files.
|
||||||
|
|
||||||
|
Downsides for appending are that it requires `zip` to be installed and does not provide a working Seek method.
|
||||||
|
|
||||||
|
Run the following commands to create a standalone executable.
|
||||||
|
```
|
||||||
|
go build -o example
|
||||||
|
rice append --exec example
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note: requires zip command to be installed**
|
||||||
|
|
||||||
|
On windows, install zip from http://gnuwin32.sourceforge.net/packages/zip.htm or cygwin/msys toolsets.
|
||||||
|
|
||||||
|
#### Help information
|
||||||
|
Run `rice -h` for information about all options.
|
||||||
|
|
||||||
|
You can run the -h option for each sub-command, e.g. `rice append -h`.
|
||||||
|
|
||||||
|
### Order of precedence
|
||||||
|
When opening a new box, the rice package tries to locate the resources in the following order:
|
||||||
|
|
||||||
|
- embedded in generated go source
|
||||||
|
- appended as zip
|
||||||
|
- 'live' from filesystem
|
||||||
|
|
||||||
|
|
||||||
|
### License
|
||||||
|
This project is licensed under a Simplified BSD license. Please read the [LICENSE file][license].
|
||||||
|
|
||||||
|
### TODO & Development
|
||||||
|
This package is not completed yet. Though it already provides working embedding, some important featuers are still missing.
|
||||||
|
- implement Readdir() correctly on virtualDir
|
||||||
|
- in-code TODO's
|
||||||
|
- find boxes in imported packages
|
||||||
|
|
||||||
|
Less important stuff:
|
||||||
|
- idea, os/arch dependent embeds. rice checks if embedding file has _os_arch or build flags. If box is not requested by file without buildflags, then the buildflags are applied to the embed file.
|
||||||
|
|
||||||
|
### Package documentation
|
||||||
|
|
||||||
|
You will find package documentation at [godoc.org/github.com/GeertJohan/go.rice][godoc].
|
||||||
|
|
||||||
|
|
||||||
|
[license]: https://github.com/GeertJohan/go.rice/blob/master/LICENSE
|
||||||
|
[godoc]: http://godoc.org/github.com/GeertJohan/go.rice
|
@ -0,0 +1,138 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/zip"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/daaku/go.zipexe"
|
||||||
|
"github.com/kardianos/osext"
|
||||||
|
)
|
||||||
|
|
||||||
|
// appendedBox defines an appended box
|
||||||
|
type appendedBox struct {
|
||||||
|
Name string // box name
|
||||||
|
Files map[string]*appendedFile // appended files (*zip.File) by full path
|
||||||
|
}
|
||||||
|
|
||||||
|
type appendedFile struct {
|
||||||
|
zipFile *zip.File
|
||||||
|
dir bool
|
||||||
|
dirInfo *appendedDirInfo
|
||||||
|
children []*appendedFile
|
||||||
|
content []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendedBoxes is a public register of appendes boxes
|
||||||
|
var appendedBoxes = make(map[string]*appendedBox)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// find if exec is appended
|
||||||
|
thisFile, err := osext.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return // not appended or cant find self executable
|
||||||
|
}
|
||||||
|
closer, rd, err := zipexe.OpenCloser(thisFile)
|
||||||
|
if err != nil {
|
||||||
|
return // not appended
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
for _, f := range rd.File {
|
||||||
|
// get box and file name from f.Name
|
||||||
|
fileParts := strings.SplitN(strings.TrimLeft(filepath.ToSlash(f.Name), "/"), "/", 2)
|
||||||
|
boxName := fileParts[0]
|
||||||
|
var fileName string
|
||||||
|
if len(fileParts) > 1 {
|
||||||
|
fileName = fileParts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// find box or create new one if doesn't exist
|
||||||
|
box := appendedBoxes[boxName]
|
||||||
|
if box == nil {
|
||||||
|
box = &appendedBox{
|
||||||
|
Name: boxName,
|
||||||
|
Files: make(map[string]*appendedFile),
|
||||||
|
}
|
||||||
|
appendedBoxes[boxName] = box
|
||||||
|
}
|
||||||
|
|
||||||
|
// create and add file to box
|
||||||
|
af := &appendedFile{
|
||||||
|
zipFile: f,
|
||||||
|
}
|
||||||
|
if f.Comment == "dir" {
|
||||||
|
af.dir = true
|
||||||
|
af.dirInfo = &appendedDirInfo{
|
||||||
|
name: filepath.Base(af.zipFile.Name),
|
||||||
|
//++ TODO: use zip modtime when that is set correctly: af.zipFile.ModTime()
|
||||||
|
time: time.Now(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this is a file, we need it's contents so we can create a bytes.Reader when the file is opened
|
||||||
|
// make a new byteslice
|
||||||
|
af.content = make([]byte, af.zipFile.FileInfo().Size())
|
||||||
|
// ignore reading empty files from zip (empty file still is a valid file to be read though!)
|
||||||
|
if len(af.content) > 0 {
|
||||||
|
// open io.ReadCloser
|
||||||
|
rc, err := af.zipFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
||||||
|
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
||||||
|
log.Printf("error opening appended file %s: %v", af.zipFile.Name, err)
|
||||||
|
} else {
|
||||||
|
_, err = rc.Read(af.content)
|
||||||
|
rc.Close()
|
||||||
|
if err != nil {
|
||||||
|
af.content = nil // this will cause an error when the file is being opened or seeked (which is good)
|
||||||
|
// TODO: it's quite blunt to just log this stuff. but this is in init, so rice.Debug can't be changed yet..
|
||||||
|
log.Printf("error reading data for appended file %s: %v", af.zipFile.Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add appendedFile to box file list
|
||||||
|
box.Files[fileName] = af
|
||||||
|
|
||||||
|
// add to parent dir (if any)
|
||||||
|
dirName := filepath.Dir(fileName)
|
||||||
|
if dirName == "." {
|
||||||
|
dirName = ""
|
||||||
|
}
|
||||||
|
if fileName != "" { // don't make box root dir a child of itself
|
||||||
|
if dir := box.Files[dirName]; dir != nil {
|
||||||
|
dir.children = append(dir.children, af)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements os.FileInfo.
|
||||||
|
// used for Readdir()
|
||||||
|
type appendedDirInfo struct {
|
||||||
|
name string
|
||||||
|
time time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (adi *appendedDirInfo) Name() string {
|
||||||
|
return adi.name
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) Size() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) Mode() os.FileMode {
|
||||||
|
return os.ModeDir
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) ModTime() time.Time {
|
||||||
|
return adi.time
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) IsDir() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (adi *appendedDirInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,337 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice/embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Box abstracts a directory for resources/files.
|
||||||
|
// It can either load files from disk, or from embedded code (when `rice --embed` was ran).
|
||||||
|
type Box struct {
|
||||||
|
name string
|
||||||
|
absolutePath string
|
||||||
|
embed *embedded.EmbeddedBox
|
||||||
|
appendd *appendedBox
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultLocateOrder = []LocateMethod{LocateEmbedded, LocateAppended, LocateFS}
|
||||||
|
|
||||||
|
func findBox(name string, order []LocateMethod) (*Box, error) {
|
||||||
|
b := &Box{name: name}
|
||||||
|
|
||||||
|
// no support for absolute paths since gopath can be different on different machines.
|
||||||
|
// therefore, required box must be located relative to package requiring it.
|
||||||
|
if filepath.IsAbs(name) {
|
||||||
|
return nil, errors.New("given name/path is absolute")
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for _, method := range order {
|
||||||
|
switch method {
|
||||||
|
case LocateEmbedded:
|
||||||
|
if embed := embedded.EmbeddedBoxes[name]; embed != nil {
|
||||||
|
b.embed = embed
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case LocateAppended:
|
||||||
|
appendedBoxName := strings.Replace(name, `/`, `-`, -1)
|
||||||
|
if appendd := appendedBoxes[appendedBoxName]; appendd != nil {
|
||||||
|
b.appendd = appendd
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case LocateFS:
|
||||||
|
// resolve absolute directory path
|
||||||
|
err := b.resolveAbsolutePathFromCaller()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath exists on filesystem
|
||||||
|
info, err := os.Stat(b.absolutePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath is actually a directory
|
||||||
|
if !info.IsDir() {
|
||||||
|
err = errors.New("given name/path is not a directory")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
case LocateWorkingDirectory:
|
||||||
|
// resolve absolute directory path
|
||||||
|
err := b.resolveAbsolutePathFromWorkingDirectory()
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath exists on filesystem
|
||||||
|
info, err := os.Stat(b.absolutePath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check if absolutePath is actually a directory
|
||||||
|
if !info.IsDir() {
|
||||||
|
err = errors.New("given name/path is not a directory")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
err = fmt.Errorf("could not locate box %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindBox returns a Box instance for given name.
|
||||||
|
// When the given name is a relative path, it's base path will be the calling pkg/cmd's source root.
|
||||||
|
// When the given name is absolute, it's absolute. derp.
|
||||||
|
// Make sure the path doesn't contain any sensitive information as it might be placed into generated go source (embedded).
|
||||||
|
func FindBox(name string) (*Box, error) {
|
||||||
|
return findBox(name, defaultLocateOrder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFindBox returns a Box instance for given name, like FindBox does.
|
||||||
|
// It does not return an error, instead it panics when an error occurs.
|
||||||
|
func MustFindBox(name string) *Box {
|
||||||
|
box, err := findBox(name, defaultLocateOrder)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return box
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is injected as a mutable function literal so that we can mock it out in
|
||||||
|
// tests and return a fixed test file.
|
||||||
|
var resolveAbsolutePathFromCaller = func(name string, nStackFrames int) (string, error) {
|
||||||
|
_, callingGoFile, _, ok := runtime.Caller(nStackFrames)
|
||||||
|
if !ok {
|
||||||
|
return "", errors.New("couldn't find caller on stack")
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve to proper path
|
||||||
|
pkgDir := filepath.Dir(callingGoFile)
|
||||||
|
// fix for go cover
|
||||||
|
const coverPath = "_test/_obj_test"
|
||||||
|
if !filepath.IsAbs(pkgDir) {
|
||||||
|
if i := strings.Index(pkgDir, coverPath); i >= 0 {
|
||||||
|
pkgDir = pkgDir[:i] + pkgDir[i+len(coverPath):] // remove coverPath
|
||||||
|
pkgDir = filepath.Join(os.Getenv("GOPATH"), "src", pkgDir) // make absolute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filepath.Join(pkgDir, name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Box) resolveAbsolutePathFromCaller() error {
|
||||||
|
path, err := resolveAbsolutePathFromCaller(b.name, 4)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.absolutePath = path
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Box) resolveAbsolutePathFromWorkingDirectory() error {
|
||||||
|
path, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.absolutePath = filepath.Join(path, b.name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmbedded indicates wether this box was embedded into the application
|
||||||
|
func (b *Box) IsEmbedded() bool {
|
||||||
|
return b.embed != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAppended indicates wether this box was appended to the application
|
||||||
|
func (b *Box) IsAppended() bool {
|
||||||
|
return b.appendd != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time returns how actual the box is.
|
||||||
|
// When the box is embedded, it's value is saved in the embedding code.
|
||||||
|
// When the box is live, this methods returns time.Now()
|
||||||
|
func (b *Box) Time() time.Time {
|
||||||
|
if b.IsEmbedded() {
|
||||||
|
return b.embed.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
//++ TODO: return time for appended box
|
||||||
|
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens a File from the box
|
||||||
|
// If there is an error, it will be of type *os.PathError.
|
||||||
|
func (b *Box) Open(name string) (*File, error) {
|
||||||
|
if Debug {
|
||||||
|
fmt.Printf("Open(%s)\n", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsEmbedded() {
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Box is embedded")
|
||||||
|
}
|
||||||
|
|
||||||
|
// trim prefix (paths are relative to box)
|
||||||
|
name = strings.TrimLeft(name, "/")
|
||||||
|
if Debug {
|
||||||
|
fmt.Printf("Trying %s\n", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// search for file
|
||||||
|
ef := b.embed.Files[name]
|
||||||
|
if ef == nil {
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Didn't find file in embed")
|
||||||
|
}
|
||||||
|
// file not found, try dir
|
||||||
|
ed := b.embed.Dirs[name]
|
||||||
|
if ed == nil {
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Didn't find dir in embed")
|
||||||
|
}
|
||||||
|
// dir not found, error out
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: name,
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Found dir. Returning virtual dir")
|
||||||
|
}
|
||||||
|
vd := newVirtualDir(ed)
|
||||||
|
return &File{virtualD: vd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// box is embedded
|
||||||
|
if Debug {
|
||||||
|
fmt.Println("Found file. Returning virtual file")
|
||||||
|
}
|
||||||
|
vf := newVirtualFile(ef)
|
||||||
|
return &File{virtualF: vf}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsAppended() {
|
||||||
|
// trim prefix (paths are relative to box)
|
||||||
|
name = strings.TrimLeft(name, "/")
|
||||||
|
|
||||||
|
// search for file
|
||||||
|
appendedFile := b.appendd.Files[name]
|
||||||
|
if appendedFile == nil {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: name,
|
||||||
|
Err: os.ErrNotExist,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new file
|
||||||
|
f := &File{
|
||||||
|
appendedF: appendedFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if this file is a directory, we want to be able to read and seek
|
||||||
|
if !appendedFile.dir {
|
||||||
|
// looks like malformed data in zip, error now
|
||||||
|
if appendedFile.content == nil {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "open",
|
||||||
|
Path: "name",
|
||||||
|
Err: errors.New("error reading data from zip file"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create new bytes.Reader
|
||||||
|
f.appendedFileReader = bytes.NewReader(appendedFile.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// all done
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform os open
|
||||||
|
if Debug {
|
||||||
|
fmt.Printf("Using os.Open(%s)", filepath.Join(b.absolutePath, name))
|
||||||
|
}
|
||||||
|
file, err := os.Open(filepath.Join(b.absolutePath, name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &File{realF: file}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the content of the file with given name as []byte.
|
||||||
|
func (b *Box) Bytes(name string) ([]byte, error) {
|
||||||
|
file, err := b.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
content, err := ioutil.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBytes returns the content of the file with given name as []byte.
|
||||||
|
// panic's on error.
|
||||||
|
func (b *Box) MustBytes(name string) []byte {
|
||||||
|
bts, err := b.Bytes(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return bts
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the content of the file with given name as string.
|
||||||
|
func (b *Box) String(name string) (string, error) {
|
||||||
|
// check if box is embedded, optimized fast path
|
||||||
|
if b.IsEmbedded() {
|
||||||
|
// find file in embed
|
||||||
|
ef := b.embed.Files[name]
|
||||||
|
if ef == nil {
|
||||||
|
return "", os.ErrNotExist
|
||||||
|
}
|
||||||
|
// return as string
|
||||||
|
return ef.Content, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
bts, err := b.Bytes(name)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(bts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustString returns the content of the file with given name as string.
|
||||||
|
// panic's on error.
|
||||||
|
func (b *Box) MustString(name string) string {
|
||||||
|
str, err := b.String(name)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of the box
|
||||||
|
func (b *Box) Name() string {
|
||||||
|
return b.name
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
// LocateMethod defines how a box is located.
|
||||||
|
type LocateMethod int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocateFS = LocateMethod(iota) // Locate on the filesystem according to package path.
|
||||||
|
LocateAppended // Locate boxes appended to the executable.
|
||||||
|
LocateEmbedded // Locate embedded boxes.
|
||||||
|
LocateWorkingDirectory // Locate on the binary working directory
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config allows customizing the box lookup behavior.
|
||||||
|
type Config struct {
|
||||||
|
// LocateOrder defines the priority order that boxes are searched for. By
|
||||||
|
// default, the package global FindBox searches for embedded boxes first,
|
||||||
|
// then appended boxes, and then finally boxes on the filesystem. That
|
||||||
|
// search order may be customized by provided the ordered list here. Leaving
|
||||||
|
// out a particular method will omit that from the search space. For
|
||||||
|
// example, []LocateMethod{LocateEmbedded, LocateAppended} will never search
|
||||||
|
// the filesystem for boxes.
|
||||||
|
LocateOrder []LocateMethod
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindBox searches for boxes using the LocateOrder of the config.
|
||||||
|
func (c *Config) FindBox(boxName string) (*Box, error) {
|
||||||
|
return findBox(boxName, c.LocateOrder)
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
// Debug can be set to true to enable debugging.
|
||||||
|
var Debug = false
|
@ -0,0 +1,90 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice/embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
// re-type to make exported methods invisible to user (godoc)
|
||||||
|
// they're not required for the user
|
||||||
|
// embeddedDirInfo implements os.FileInfo
|
||||||
|
type embeddedDirInfo embedded.EmbeddedDir
|
||||||
|
|
||||||
|
// Name returns the base name of the directory
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Name() string {
|
||||||
|
return ed.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size always returns 0
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Size() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the file mode bits
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Mode() os.FileMode {
|
||||||
|
return os.FileMode(0555 | os.ModeDir) // dr-xr-xr-x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModTime returns the modification time
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) ModTime() time.Time {
|
||||||
|
return ed.DirModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns the abbreviation for Mode().IsDir() (always true)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) IsDir() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sys returns the underlying data source (always nil)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ed *embeddedDirInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// re-type to make exported methods invisible to user (godoc)
|
||||||
|
// they're not required for the user
|
||||||
|
// embeddedFileInfo implements os.FileInfo
|
||||||
|
type embeddedFileInfo embedded.EmbeddedFile
|
||||||
|
|
||||||
|
// Name returns the base name of the file
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Name() string {
|
||||||
|
return ef.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the length in bytes for regular files; system-dependent for others
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Size() int64 {
|
||||||
|
return int64(len(ef.Content))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode returns the file mode bits
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Mode() os.FileMode {
|
||||||
|
return os.FileMode(0555) // r-xr-xr-x
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModTime returns the modification time
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) ModTime() time.Time {
|
||||||
|
return ef.FileModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir returns the abbreviation for Mode().IsDir() (always false)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sys returns the underlying data source (always nil)
|
||||||
|
// (implementing os.FileInfo)
|
||||||
|
func (ef *embeddedFileInfo) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
@ -0,0 +1,144 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File implements the io.Reader, io.Seeker, io.Closer and http.File interfaces
|
||||||
|
type File struct {
|
||||||
|
// File abstracts file methods so the user doesn't see the difference between rice.virtualFile, rice.virtualDir and os.File
|
||||||
|
// TODO: maybe use internal File interface and four implementations: *os.File, appendedFile, virtualFile, virtualDir
|
||||||
|
|
||||||
|
// real file on disk
|
||||||
|
realF *os.File
|
||||||
|
|
||||||
|
// when embedded (go)
|
||||||
|
virtualF *virtualFile
|
||||||
|
virtualD *virtualDir
|
||||||
|
|
||||||
|
// when appended (zip)
|
||||||
|
appendedF *appendedFile
|
||||||
|
appendedFileReader *bytes.Reader
|
||||||
|
// TODO: is appendedFileReader subject of races? Might need a lock here..
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close is like (*os.File).Close()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Close for more information
|
||||||
|
func (f *File) Close() error {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return errors.New("already closed")
|
||||||
|
}
|
||||||
|
f.appendedFileReader = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.close()
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.close()
|
||||||
|
}
|
||||||
|
return f.realF.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat is like (*os.File).Stat()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Stat for more information
|
||||||
|
func (f *File) Stat() (os.FileInfo, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedF.dir {
|
||||||
|
return f.appendedF.dirInfo, nil
|
||||||
|
}
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return nil, errors.New("file is closed")
|
||||||
|
}
|
||||||
|
return f.appendedF.zipFile.FileInfo(), nil
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.stat()
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.stat()
|
||||||
|
}
|
||||||
|
return f.realF.Stat()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir is like (*os.File).Readdir()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Readdir for more information
|
||||||
|
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedF.dir {
|
||||||
|
fi := make([]os.FileInfo, 0, len(f.appendedF.children))
|
||||||
|
for _, childAppendedFile := range f.appendedF.children {
|
||||||
|
if childAppendedFile.dir {
|
||||||
|
fi = append(fi, childAppendedFile.dirInfo)
|
||||||
|
} else {
|
||||||
|
fi = append(fi, childAppendedFile.zipFile.FileInfo())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fi, nil
|
||||||
|
}
|
||||||
|
//++ TODO: is os.ErrInvalid the correct error for Readdir on file?
|
||||||
|
return nil, os.ErrInvalid
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.readdir(count)
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.readdir(count)
|
||||||
|
}
|
||||||
|
return f.realF.Readdir(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read is like (*os.File).Read()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Read for more information
|
||||||
|
func (f *File) Read(bts []byte) (int, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: filepath.Base(f.appendedF.zipFile.Name),
|
||||||
|
Err: errors.New("file is closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if f.appendedF.dir {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: filepath.Base(f.appendedF.zipFile.Name),
|
||||||
|
Err: errors.New("is a directory"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.appendedFileReader.Read(bts)
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.read(bts)
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.read(bts)
|
||||||
|
}
|
||||||
|
return f.realF.Read(bts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek is like (*os.File).Seek()
|
||||||
|
// Visit http://golang.org/pkg/os/#File.Seek for more information
|
||||||
|
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if f.appendedF != nil {
|
||||||
|
if f.appendedFileReader == nil {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: filepath.Base(f.appendedF.zipFile.Name),
|
||||||
|
Err: errors.New("file is closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f.appendedFileReader.Seek(offset, whence)
|
||||||
|
}
|
||||||
|
if f.virtualF != nil {
|
||||||
|
return f.virtualF.seek(offset, whence)
|
||||||
|
}
|
||||||
|
if f.virtualD != nil {
|
||||||
|
return f.virtualD.seek(offset, whence)
|
||||||
|
}
|
||||||
|
return f.realF.Seek(offset, whence)
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPBox implements http.FileSystem which allows the use of Box with a http.FileServer.
|
||||||
|
// e.g.: http.Handle("/", http.FileServer(rice.MustFindBox("http-files").HTTPBox()))
|
||||||
|
type HTTPBox struct {
|
||||||
|
*Box
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPBox creates a new HTTPBox from an existing Box
|
||||||
|
func (b *Box) HTTPBox() *HTTPBox {
|
||||||
|
return &HTTPBox{b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open returns a File using the http.File interface
|
||||||
|
func (hb *HTTPBox) Open(name string) (http.File, error) {
|
||||||
|
return hb.Box.Open(name)
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// SortByName allows an array of os.FileInfo objects
|
||||||
|
// to be easily sorted by filename using sort.Sort(SortByName(array))
|
||||||
|
type SortByName []os.FileInfo
|
||||||
|
|
||||||
|
func (f SortByName) Len() int { return len(f) }
|
||||||
|
func (f SortByName) Less(i, j int) bool { return f[i].Name() < f[j].Name() }
|
||||||
|
func (f SortByName) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||||
|
|
||||||
|
// SortByModified allows an array of os.FileInfo objects
|
||||||
|
// to be easily sorted by modified date using sort.Sort(SortByModified(array))
|
||||||
|
type SortByModified []os.FileInfo
|
||||||
|
|
||||||
|
func (f SortByModified) Len() int { return len(f) }
|
||||||
|
func (f SortByModified) Less(i, j int) bool { return f[i].ModTime().Unix() > f[j].ModTime().Unix() }
|
||||||
|
func (f SortByModified) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
@ -0,0 +1,252 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/GeertJohan/go.rice/embedded"
|
||||||
|
)
|
||||||
|
|
||||||
|
//++ TODO: IDEA: merge virtualFile and virtualDir, this decreases work done by rice.File
|
||||||
|
|
||||||
|
// Error indicating some function is not implemented yet (but available to satisfy an interface)
|
||||||
|
var ErrNotImplemented = errors.New("not implemented yet")
|
||||||
|
|
||||||
|
// virtualFile is a 'stateful' virtual file.
|
||||||
|
// virtualFile wraps an *EmbeddedFile for a call to Box.Open() and virtualizes 'read cursor' (offset) and 'closing'.
|
||||||
|
// virtualFile is only internally visible and should be exposed through rice.File
|
||||||
|
type virtualFile struct {
|
||||||
|
*embedded.EmbeddedFile // the actual embedded file, embedded to obtain methods
|
||||||
|
offset int64 // read position on the virtual file
|
||||||
|
closed bool // closed when true
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new virtualFile for given EmbeddedFile
|
||||||
|
func newVirtualFile(ef *embedded.EmbeddedFile) *virtualFile {
|
||||||
|
vf := &virtualFile{
|
||||||
|
EmbeddedFile: ef,
|
||||||
|
offset: 0,
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
|
return vf
|
||||||
|
}
|
||||||
|
|
||||||
|
//++ TODO check for nil pointers in all these methods. When so: return os.PathError with Err: os.ErrInvalid
|
||||||
|
|
||||||
|
func (vf *virtualFile) close() error {
|
||||||
|
if vf.closed {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "close",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("already closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vf.EmbeddedFile = nil
|
||||||
|
vf.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) stat() (os.FileInfo, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "stat",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*embeddedFileInfo)(vf.EmbeddedFile), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) readdir(count int) ([]os.FileInfo, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "readdir",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TODO: return proper error for a readdir() call on a file
|
||||||
|
return nil, ErrNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) read(bts []byte) (int, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := vf.offset + int64(len(bts))
|
||||||
|
|
||||||
|
if end >= int64(len(vf.Content)) {
|
||||||
|
// end of file, so return what we have + EOF
|
||||||
|
n := copy(bts, vf.Content[vf.offset:])
|
||||||
|
vf.offset = 0
|
||||||
|
return n, io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
n := copy(bts, vf.Content[vf.offset:end])
|
||||||
|
vf.offset += int64(n)
|
||||||
|
return n, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vf *virtualFile) seek(offset int64, whence int) (int64, error) {
|
||||||
|
if vf.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vf.EmbeddedFile.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var e error
|
||||||
|
|
||||||
|
//++ TODO: check if this is correct implementation for seek
|
||||||
|
switch whence {
|
||||||
|
case os.SEEK_SET:
|
||||||
|
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
||||||
|
vf.offset = offset
|
||||||
|
case os.SEEK_CUR:
|
||||||
|
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
||||||
|
vf.offset += offset
|
||||||
|
case os.SEEK_END:
|
||||||
|
//++ check if new offset isn't out of bounds, set e when it is, then break out of switch
|
||||||
|
vf.offset = int64(len(vf.EmbeddedFile.Content)) - offset
|
||||||
|
}
|
||||||
|
|
||||||
|
if e != nil {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vf.Filename,
|
||||||
|
Err: e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vf.offset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtualDir is a 'stateful' virtual directory.
|
||||||
|
// virtualDir wraps an *EmbeddedDir for a call to Box.Open() and virtualizes 'closing'.
|
||||||
|
// virtualDir is only internally visible and should be exposed through rice.File
|
||||||
|
type virtualDir struct {
|
||||||
|
*embedded.EmbeddedDir
|
||||||
|
offset int // readdir position on the directory
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new virtualDir for given EmbeddedDir
|
||||||
|
func newVirtualDir(ed *embedded.EmbeddedDir) *virtualDir {
|
||||||
|
vd := &virtualDir{
|
||||||
|
EmbeddedDir: ed,
|
||||||
|
offset: 0,
|
||||||
|
closed: false,
|
||||||
|
}
|
||||||
|
return vd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) close() error {
|
||||||
|
//++ TODO: needs sync mutex?
|
||||||
|
if vd.closed {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: "close",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("already closed"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vd.closed = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) stat() (os.FileInfo, error) {
|
||||||
|
if vd.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "stat",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (*embeddedDirInfo)(vd.EmbeddedDir), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) readdir(n int) (fi []os.FileInfo, err error) {
|
||||||
|
|
||||||
|
if vd.closed {
|
||||||
|
return nil, &os.PathError{
|
||||||
|
Op: "readdir",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build up the array of our contents
|
||||||
|
var files []os.FileInfo
|
||||||
|
|
||||||
|
// Add the child directories
|
||||||
|
for _, child := range vd.ChildDirs {
|
||||||
|
child.Filename = filepath.Base(child.Filename)
|
||||||
|
files = append(files, (*embeddedDirInfo)(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the child files
|
||||||
|
for _, child := range vd.ChildFiles {
|
||||||
|
child.Filename = filepath.Base(child.Filename)
|
||||||
|
files = append(files, (*embeddedFileInfo)(child))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort it by filename (lexical order)
|
||||||
|
sort.Sort(SortByName(files))
|
||||||
|
|
||||||
|
// Return all contents if that's what is requested
|
||||||
|
if n <= 0 {
|
||||||
|
vd.offset = 0
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If user has requested past the end of our list
|
||||||
|
// return what we can and send an EOF
|
||||||
|
if vd.offset+n >= len(files) {
|
||||||
|
offset := vd.offset
|
||||||
|
vd.offset = 0
|
||||||
|
return files[offset:], io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := vd.offset
|
||||||
|
vd.offset += n
|
||||||
|
return files[offset : offset+n], nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) read(bts []byte) (int, error) {
|
||||||
|
if vd.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "read",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("is a directory"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vd *virtualDir) seek(offset int64, whence int) (int64, error) {
|
||||||
|
if vd.closed {
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vd.EmbeddedDir.Filename,
|
||||||
|
Err: errors.New("bad file descriptor"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, &os.PathError{
|
||||||
|
Op: "seek",
|
||||||
|
Path: vd.Filename,
|
||||||
|
Err: errors.New("is a directory"),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package rice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Walk is like filepath.Walk()
|
||||||
|
// Visit http://golang.org/pkg/path/filepath/#Walk for more information
|
||||||
|
func (b *Box) Walk(path string, walkFn filepath.WalkFunc) error {
|
||||||
|
|
||||||
|
pathFile, err := b.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer pathFile.Close()
|
||||||
|
|
||||||
|
pathInfo, err := pathFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.IsAppended() || b.IsEmbedded() {
|
||||||
|
return b.walk(path, pathInfo, walkFn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't have any embedded or appended box so use live filesystem mode
|
||||||
|
return filepath.Walk(b.absolutePath+string(os.PathSeparator)+path, func(path string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
// Strip out the box name from the returned paths
|
||||||
|
path = strings.TrimPrefix(path, b.absolutePath+string(os.PathSeparator))
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// walk recursively descends path.
|
||||||
|
// See walk() in $GOROOT/src/pkg/path/filepath/path.go
|
||||||
|
func (b *Box) walk(path string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
|
||||||
|
err := walkFn(path, info, nil)
|
||||||
|
if err != nil {
|
||||||
|
if info.IsDir() && err == filepath.SkipDir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := b.readDirNames(path)
|
||||||
|
if err != nil {
|
||||||
|
return walkFn(path, info, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
|
||||||
|
filename := filepath.Join(path, name)
|
||||||
|
fileObject, err := b.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fileObject.Close()
|
||||||
|
|
||||||
|
fileInfo, err := fileObject.Stat()
|
||||||
|
if err != nil {
|
||||||
|
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = b.walk(filename, fileInfo, walkFn)
|
||||||
|
if err != nil {
|
||||||
|
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDirNames reads the directory named by path and returns a sorted list of directory entries.
|
||||||
|
// See readDirNames() in $GOROOT/pkg/path/filepath/path.go
|
||||||
|
func (b *Box) readDirNames(path string) ([]string, error) {
|
||||||
|
|
||||||
|
f, err := b.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
stat, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
infos, err := f.Readdir(0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
for _, info := range infos {
|
||||||
|
names = append(names, info.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(names)
|
||||||
|
return names, nil
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
box: wercker/golang
|
||||||
|
|
||||||
|
build:
|
||||||
|
steps:
|
||||||
|
- setup-go-workspace
|
||||||
|
|
||||||
|
- script:
|
||||||
|
name: get dependencies
|
||||||
|
code: |
|
||||||
|
go get -d -t ./...
|
||||||
|
|
||||||
|
- script:
|
||||||
|
name: build
|
||||||
|
code: |
|
||||||
|
go build -x ./...
|
||||||
|
|
||||||
|
- script:
|
||||||
|
name: test
|
||||||
|
code: |
|
||||||
|
go test -cover ./...
|
||||||
|
|
||||||
|
- script:
|
||||||
|
name: vet
|
||||||
|
code: |
|
||||||
|
go vet ./...
|
||||||
|
|
||||||
|
- script:
|
||||||
|
name: lint
|
||||||
|
code: |
|
||||||
|
go get github.com/golang/lint/golint
|
||||||
|
golint .
|
Loading…
Reference in New Issue