forked from lug/matterbridge
		
	
		
			
				
	
	
		
			338 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			338 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						|
}
 |