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
 | |
| }
 | 
