package packr import ( "bytes" "fmt" "io" "io/ioutil" "net/http" "os" "path" "path/filepath" "sort" "strings" "github.com/gobuffalo/packd" "github.com/gobuffalo/packr/v2/file" "github.com/gobuffalo/packr/v2/file/resolver" "github.com/gobuffalo/packr/v2/plog" "github.com/markbates/oncer" ) var _ packd.Box = &Box{} var _ packd.HTTPBox = &Box{} var _ packd.Addable = &Box{} var _ packd.Walkable = &Box{} var _ packd.Finder = &Box{} // Box represent a folder on a disk you want to // have access to in the built Go binary. type Box struct { Path string `json:"path"` Name string `json:"name"` ResolutionDir string `json:"resolution_dir"` DefaultResolver resolver.Resolver `json:"default_resolver"` resolvers resolversMap dirs dirsMap } // NewBox returns a Box that can be used to // retrieve files from either disk or the embedded // binary. // Deprecated: Use New instead. func NewBox(path string) *Box { oncer.Deprecate(0, "packr.NewBox", "Use packr.New instead.") return New(path, path) } // New returns a new Box with the name of the box // and the path of the box. func New(name string, path string) *Box { plog.Debug("packr", "New", "name", name, "path", path) b, _ := findBox(name) if b != nil { return b } b = construct(name, path) plog.Debug(b, "New", "Box", b, "ResolutionDir", b.ResolutionDir) b, err := placeBox(b) if err != nil { panic(err) } return b } // Folder returns a Box that will NOT be packed. // This is useful for writing tests or tools that // need to work with a folder at runtime. func Folder(path string) *Box { return New(path, path) } // SetResolver allows for the use of a custom resolver for // the specified file func (b *Box) SetResolver(file string, res resolver.Resolver) { d := filepath.Dir(file) b.dirs.Store(d, true) plog.Debug(b, "SetResolver", "file", file, "resolver", fmt.Sprintf("%T", res)) b.resolvers.Store(resolver.Key(file), res) } // AddString converts t to a byteslice and delegates to AddBytes to add to b.data func (b *Box) AddString(path string, t string) error { return b.AddBytes(path, []byte(t)) } // AddBytes sets t in b.data by the given path func (b *Box) AddBytes(path string, t []byte) error { m := map[string]file.File{} f, err := file.NewFile(path, t) if err != nil { return err } m[resolver.Key(path)] = f res := resolver.NewInMemory(m) b.SetResolver(path, res) return nil } // FindString returns either the string of the requested // file or an error if it can not be found. func (b *Box) FindString(name string) (string, error) { bb, err := b.Find(name) return string(bb), err } // Find returns either the byte slice of the requested // file or an error if it can not be found. func (b *Box) Find(name string) ([]byte, error) { f, err := b.Resolve(name) if err != nil { return []byte(""), err } bb := &bytes.Buffer{} io.Copy(bb, f) return bb.Bytes(), nil } // Has returns true if the resource exists in the box func (b *Box) Has(name string) bool { _, err := b.Find(name) return err == nil } // HasDir returns true if the directory exists in the box func (b *Box) HasDir(name string) bool { oncer.Do("packr2/box/HasDir"+b.Name, func() { for _, f := range b.List() { for d := filepath.Dir(f); d != "."; d = filepath.Dir(d) { b.dirs.Store(d, true) } } }) if name == "/" { return b.Has("index.html") } _, ok := b.dirs.Load(name) return ok } // Open returns a File using the http.File interface func (b *Box) Open(name string) (http.File, error) { plog.Debug(b, "Open", "name", name) f, err := b.Resolve(name) if err != nil { if len(filepath.Ext(name)) == 0 { return b.openWoExt(name) } return f, err } f, err = file.NewFileR(name, f) plog.Debug(b, "Open", "name", f.Name(), "file", f.Name()) return f, err } func (b *Box) openWoExt(name string) (http.File, error) { if !b.HasDir(name) { id := path.Join(name, "index.html") if b.Has(id) { return b.Open(id) } return nil, os.ErrNotExist } d, err := file.NewDir(name) plog.Debug(b, "Open", "name", name, "dir", d) return d, err } // List shows "What's in the box?" func (b *Box) List() []string { var keys []string b.Walk(func(path string, info File) error { if info == nil { return nil } finfo, _ := info.FileInfo() if !finfo.IsDir() { keys = append(keys, path) } return nil }) sort.Strings(keys) return keys } // Resolve will attempt to find the file in the box, // returning an error if the find can not be found. func (b *Box) Resolve(key string) (file.File, error) { key = strings.TrimPrefix(key, "/") var r resolver.Resolver b.resolvers.Range(func(k string, vr resolver.Resolver) bool { lk := strings.ToLower(resolver.Key(k)) lkey := strings.ToLower(resolver.Key(key)) if lk == lkey { r = vr return false } return true }) if r == nil { r = b.DefaultResolver if r == nil { r = resolver.DefaultResolver if r == nil { return nil, fmt.Errorf("resolver.DefaultResolver is nil") } } } plog.Debug(r, "Resolve", "box", b.Name, "key", key) f, err := r.Resolve(b.Name, key) if err != nil { z, err := resolver.ResolvePathInBase(resolver.OsPath(b.ResolutionDir), filepath.FromSlash(path.Clean("/"+resolver.OsPath(key)))) if err != nil { plog.Debug(r, "Resolve", "box", b.Name, "key", key, "err", err) return f, err } f, err = r.Resolve(b.Name, z) if err != nil { plog.Debug(r, "Resolve", "box", b.Name, "key", z, "err", err) return f, err } b, err := ioutil.ReadAll(f) if err != nil { return f, err } f, err = file.NewFile(key, b) if err != nil { return f, err } } plog.Debug(r, "Resolve", "box", b.Name, "key", key, "file", f.Name()) return f, nil }