Merge pull request #2 from markbates/disolve

Disolve
This commit is contained in:
Mark Bates 2019-09-20 15:46:00 -04:00 committed by GitHub
commit f788ee9125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2652 additions and 1646 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
.fstest
*.log *.log
.DS_Store .DS_Store
doc doc
@ -28,3 +29,5 @@ bin/*
gin-bin gin-bin
.idea/ .idea/
pkged.go pkged.go
cover.out
.pkgtest

View File

@ -16,6 +16,11 @@ test: tidy
$(GO_BIN) test -cover -tags ${TAGS} -timeout 5s ./... $(GO_BIN) test -cover -tags ${TAGS} -timeout 5s ./...
make tidy make tidy
cov:
$(GO_BIN) test -coverprofile cover.out -tags ${TAGS} ./...
go tool cover -html cover.out
make tidy
ci-test: ci-test:
$(GO_BIN) test -tags ${TAGS} -race ./... $(GO_BIN) test -tags ${TAGS} -race ./...
@ -43,4 +48,3 @@ release:
make tidy make tidy

View File

@ -1,4 +1,5 @@
variables: variables:
GOPROXY: "https://proxy.golang.org"
GOBIN: "$(GOPATH)/bin" # Go binaries path GOBIN: "$(GOPATH)/bin" # Go binaries path
GOPATH: "$(system.defaultWorkingDirectory)/gopath" # Go workspace path GOPATH: "$(system.defaultWorkingDirectory)/gopath" # Go workspace path
modulePath: "$(GOPATH)/src/github.com/$(build.repository.name)" # Path to the module"s code modulePath: "$(GOPATH)/src/github.com/$(build.repository.name)" # Path to the module"s code
@ -9,12 +10,12 @@ jobs:
vmImage: "vs2017-win2016" vmImage: "vs2017-win2016"
strategy: strategy:
matrix: matrix:
go 1.11 (on):
go_version: "1.11.13"
GO111MODULE: "on"
go 1.12 (on): go 1.12 (on):
go_version: "1.12.9" go_version: "1.12.9"
GO111MODULE: "on" GO111MODULE: "on"
go 1.13 (on):
go_version: "1.13"
GO111MODULE: "on"
steps: steps:
- template: azure-tests.yml - template: azure-tests.yml
@ -23,12 +24,12 @@ jobs:
vmImage: "macOS-10.13" vmImage: "macOS-10.13"
strategy: strategy:
matrix: matrix:
go 1.11 (on):
go_version: "1.11.13"
GO111MODULE: "on"
go 1.12 (on): go 1.12 (on):
go_version: "1.12.9" go_version: "1.12.9"
GO111MODULE: "on" GO111MODULE: "on"
go 1.13 (on):
go_version: "1.13"
GO111MODULE: "on"
steps: steps:
- template: azure-tests.yml - template: azure-tests.yml
@ -37,11 +38,11 @@ jobs:
vmImage: "ubuntu-16.04" vmImage: "ubuntu-16.04"
strategy: strategy:
matrix: matrix:
go 1.11 (on):
go_version: "1.11.13"
GO111MODULE: "on"
go 1.12 (on): go 1.12 (on):
go_version: "1.12.9" go_version: "1.12.9"
GO111MODULE: "on" GO111MODULE: "on"
go 1.13 (on):
go_version: "1.13"
GO111MODULE: "on"
steps: steps:
- template: azure-tests.yml - template: azure-tests.yml

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/markbates/pkger" "github.com/markbates/pkger"
"github.com/markbates/pkger/pkging"
) )
type infoCmd struct { type infoCmd struct {
@ -44,12 +45,12 @@ func (f *infoCmd) Exec(args []string) error {
return err return err
} }
for _, ff := range files { for _, ff := range files {
fmt.Println(pkger.NewFileInfo(ff)) fmt.Println(pkging.NewFileInfo(ff))
} }
continue continue
} }
fmt.Println(pkger.NewFileInfo(fi)) fmt.Println(pkging.NewFileInfo(fi))
} }
return nil return nil

View File

@ -8,6 +8,7 @@ import (
"github.com/markbates/pkger" "github.com/markbates/pkger"
"github.com/markbates/pkger/parser" "github.com/markbates/pkger/parser"
"github.com/markbates/pkger/pkging"
) )
const outName = "pkged.go" const outName = "pkged.go"
@ -24,12 +25,12 @@ func (e *packCmd) Name() string {
} }
func (e *packCmd) Exec(args []string) error { func (e *packCmd) Exec(args []string) error {
info, err := pkger.Stat() info, err := pkger.Current()
if err != nil { if err != nil {
return err return err
} }
res, err := parser.Parse(info.Dir) res, err := parser.Parse(info)
if err != nil { if err != nil {
return err return err
} }
@ -116,7 +117,7 @@ func (e *packCmd) Flags() *flag.FlagSet {
return e.FlagSet return e.FlagSet
} }
func Package(out string, paths []pkger.Path) error { func Package(out string, paths []pkging.Path) error {
os.RemoveAll(out) os.RemoveAll(out)
f, err := os.Create(out) f, err := os.Create(out)
@ -124,7 +125,7 @@ func Package(out string, paths []pkger.Path) error {
return err return err
} }
c, err := pkger.Stat() c, err := pkger.Current()
if err != nil { if err != nil {
return err return err
} }
@ -132,9 +133,10 @@ func Package(out string, paths []pkger.Path) error {
fmt.Fprintf(f, "import \"github.com/markbates/pkger\"\n\n") fmt.Fprintf(f, "import \"github.com/markbates/pkger\"\n\n")
fmt.Fprintf(f, "var _ = pkger.Unpack(`") fmt.Fprintf(f, "var _ = pkger.Unpack(`")
if err := pkger.Pack(f, paths); err != nil { // TODO
return err // if err := pkger.Pack(f, paths); err != nil {
} // return err
// }
fmt.Fprintf(f, "`)\n") fmt.Fprintf(f, "`)\n")

View File

@ -48,10 +48,11 @@ func (s *serveCmd) Exec(args []string) error {
return err return err
} }
ex := append(defaultExcludes, s.excludes...) // TODO
if err := pkger.Exclude(f, ex...); err != nil { // ex := append(defaultExcludes, s.excludes...)
return err // if err := pkger.Exclude(f, ex...); err != nil {
} // return err
// }
defer f.Close() defer f.Close()
fmt.Println(f.Path()) fmt.Println(f.Path())

View File

@ -8,7 +8,10 @@ import (
) )
func walk(args []string) error { func walk(args []string) error {
err := pkger.Walk(".", func(path pkger.Path, info os.FileInfo) error { err := pkger.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Println(path) fmt.Println(path)
return nil return nil
}) })

View File

@ -1,36 +0,0 @@
package pkger
import (
"path/filepath"
"time"
)
func Create(name string) (*File, error) {
pt, err := Parse(name)
if err != nil {
return nil, err
}
her, err := Info(pt.Pkg)
if err != nil {
return nil, err
}
f := &File{
path: pt,
her: her,
info: &FileInfo{
name: pt.Name,
mode: 0666,
modTime: time.Now(),
virtual: true,
},
}
filesCache.Store(pt, f)
dir := filepath.Dir(pt.Name)
if err := MkdirAll(dir, 0644); err != nil {
return nil, err
}
return f, nil
}

View File

@ -1,55 +0,0 @@
package pkger
import (
"io"
"os"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
func Testg_Create(t *testing.T) {
r := require.New(t)
f, err := Create("/hello.txt")
r.NoError(err)
r.NotNil(f)
fi, err := f.Stat()
r.NoError(err)
r.Equal("/hello.txt", fi.Name())
r.Equal(os.FileMode(0666), fi.Mode())
r.NotZero(fi.ModTime())
her := f.her
r.NotZero(her)
r.Equal("github.com/markbates/pkger", her.ImportPath)
}
func Testg_Create_Write(t *testing.T) {
r := require.New(t)
f, err := Create("/hello.txt")
r.NoError(err)
r.NotNil(f)
fi, err := f.Stat()
r.NoError(err)
r.Zero(fi.Size())
r.Equal("/hello.txt", fi.Name())
mt := fi.ModTime()
r.NotZero(mt)
sz, err := io.Copy(f, strings.NewReader(radio))
r.NoError(err)
r.Equal(int64(1381), sz)
r.NoError(f.Close())
r.Equal(int64(1381), fi.Size())
r.NotZero(fi.ModTime())
r.NotEqual(mt, fi.ModTime())
}

55
dir.go
View File

@ -1,55 +0,0 @@
package pkger
import (
"os"
"path/filepath"
"time"
)
func MkdirAll(p string, perm os.FileMode) error {
path, err := Parse(p)
if err != nil {
return err
}
root := path.Name
cur, err := Stat()
if err != nil {
return err
}
for root != "" && root != "/" && root != "\\" {
pt := Path{
Pkg: path.Pkg,
Name: root,
}
if _, ok := filesCache.Load(pt); ok {
root = filepath.Dir(root)
continue
}
f := &File{
path: pt,
her: cur,
info: &FileInfo{
name: pt.Name,
mode: 0666,
modTime: time.Now(),
virtual: true,
},
}
if err != nil {
return err
}
f.info.isDir = true
f.info.mode = perm
f.info.virtual = true
if err := f.Close(); err != nil {
return err
}
filesCache.Store(pt, f)
root = filepath.Dir(root)
}
return nil
}

View File

@ -1,5 +1,7 @@
module github.com/markbates/pkger/examples/app module github.com/markbates/pkger/examples/app
go 1.12 go 1.13
require github.com/markbates/pkger v0.0.0-20190830024022-c5e3a7de4d41 require github.com/markbates/pkger v0.0.0
replace github.com/markbates/pkger => ../../

View File

@ -8,8 +8,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc=
github.com/markbates/pkger v0.0.0-20190830024022-c5e3a7de4d41 h1:bFEHpLBby7Qdvq92qw0TudeOfUKIZLcrweE/MMlNueI=
github.com/markbates/pkger v0.0.0-20190830024022-c5e3a7de4d41/go.mod h1:M9VeozwduQUCr6z54kJrK9JegpbOv4wiePSbgSbFOew=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=

View File

@ -18,16 +18,19 @@ func main() {
} }
defer pub.Close() defer pub.Close()
fi, err := pub.Stat() fmt.Println(pub.Path())
if err != nil {
log.Fatal(err)
}
fmt.Println(fi)
mux.Handle("/t", http.StripPrefix("/t", tmplHandler())) mux.Handle("/t", http.StripPrefix("/t", tmplHandler()))
mux.Handle("/", http.FileServer(pub)) mux.Handle("/", http.FileServer(pub))
log.Fatal(http.ListenAndServe(":3000", mux)) log.Fatal(http.ListenAndServe(":3000", logger(mux)))
}
func logger(h http.Handler) http.HandlerFunc {
return func(res http.ResponseWriter, req *http.Request) {
log.Println(req.Method, req.URL.String())
h.ServeHTTP(res, req)
}
} }
func tmplHandler() http.HandlerFunc { func tmplHandler() http.HandlerFunc {

View File

@ -2,4 +2,7 @@ module github.com/markbates/pkger/examples/extfile
go 1.12 go 1.12
require github.com/markbates/pkger v0.0.0-20190830024022-c5e3a7de4d41 require github.com/markbates/pkger v0.0.0
replace github.com/markbates/pkger => ../../

View File

@ -2,4 +2,7 @@ module github.com/markbates/pkger/examples/httpserver
go 1.12 go 1.12
require github.com/markbates/pkger v0.0.0-20190830024022-c5e3a7de4d41 require github.com/markbates/pkger v0.0.0
replace github.com/markbates/pkger => ../../

View File

@ -2,4 +2,7 @@ module github.com/markbates/pkger/examples/walk
go 1.12 go 1.12
require github.com/markbates/pkger v0.0.0-20190830024022-c5e3a7de4d41 require github.com/markbates/pkger v0.0.0
replace github.com/markbates/pkger => ../../

179
file.go
View File

@ -1,179 +0,0 @@
package pkger
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/markbates/pkger/here"
)
const timeFmt = time.RFC3339Nano
var _ http.File = &File{}
type File struct {
info *FileInfo
her here.Info
path Path
data []byte
parent Path
writer *bytes.Buffer
reader io.Reader
excludes []string
}
func (f *File) Seek(offset int64, whence int) (int64, error) {
if sk, ok := f.reader.(io.Seeker); ok {
return sk.Seek(offset, whence)
}
return 0, nil
}
func (f *File) Close() error {
defer func() {
f.reader = nil
f.writer = nil
}()
if f.reader != nil {
if c, ok := f.reader.(io.Closer); ok {
if err := c.Close(); err != nil {
return err
}
}
}
if f.writer == nil {
return nil
}
f.data = f.writer.Bytes()
fi := f.info
fi.size = int64(len(f.data))
fi.modTime = time.Now()
f.info = fi
return nil
}
func (f *File) Read(p []byte) (int, error) {
if len(f.data) > 0 && f.reader == nil {
f.reader = bytes.NewReader(f.data)
}
if f.reader != nil {
return f.reader.Read(p)
}
of, err := f.her.Open(f.FilePath())
if err != nil {
return 0, err
}
f.reader = of
return f.reader.Read(p)
}
func (f *File) Write(b []byte) (int, error) {
if f.writer == nil {
f.writer = &bytes.Buffer{}
}
i, err := f.writer.Write(b)
return i, err
}
func (f File) Info() here.Info {
return f.her
}
func (f File) Stat() (os.FileInfo, error) {
if f.info == nil {
return nil, os.ErrNotExist
}
return f.info, nil
}
func (f File) Name() string {
return f.info.Name()
}
func (f File) FilePath() string {
return f.her.FilePath(f.Name())
}
func (f File) Path() Path {
return f.path
}
func (f File) String() string {
return f.Path().String()
}
func (f File) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
if st.Flag('+') {
b, err := json.MarshalIndent(f, "", " ")
if err != nil {
fmt.Fprint(os.Stderr, err)
return
}
fmt.Fprint(st, string(b))
return
}
fmt.Fprint(st, f.String())
case 'q':
fmt.Fprintf(st, "%q", f.String())
default:
fmt.Fprint(st, f.String())
}
}
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
var infos []os.FileInfo
err := Walk(f.Name(), func(pt Path, info os.FileInfo) error {
if count > 0 && len(infos) == count {
return io.EOF
}
for _, x := range f.excludes {
rx, err := regexp.Compile(x)
if err != nil {
return err
}
if rx.MatchString(pt.Name) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
}
if pt.Name == f.parent.Name {
return nil
}
if f.parent.Name != "/" {
info = WithName(strings.TrimPrefix(info.Name(), f.parent.Name), info)
}
infos = append(infos, info)
return nil
})
if err != nil {
if _, ok := err.(*os.PathError); ok {
return infos, nil
}
if err != io.EOF {
return nil, err
}
}
return infos, nil
}

View File

@ -1,139 +0,0 @@
package pkger
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
)
type FileInfo struct {
name string
size int64
mode os.FileMode
modTime time.Time
isDir bool
virtual bool
sys interface{}
}
func (f *FileInfo) String() string {
b, _ := json.MarshalIndent(f, "", " ")
return string(b)
}
func (f *FileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.name,
"size": f.size,
"mode": f.mode,
"modTime": f.modTime.Format(timeFmt),
"isDir": f.isDir,
"virtual": true,
"sys": f.sys,
})
}
func (f *FileInfo) UnmarshalJSON(b []byte) error {
m := map[string]interface{}{}
if err := json.Unmarshal(b, &m); err != nil {
return err
}
var ok bool
f.name, ok = m["name"].(string)
if !ok {
return fmt.Errorf("could not determine name %q", m["name"])
}
size, ok := m["size"].(float64)
if !ok {
return fmt.Errorf("could not determine size %q", m["size"])
}
f.size = int64(size)
mode, ok := m["mode"].(float64)
if !ok {
return fmt.Errorf("could not determine mode %q", m["mode"])
}
f.mode = os.FileMode(mode)
modTime, ok := m["modTime"].(string)
if !ok {
return fmt.Errorf("could not determine modTime %q", m["modTime"])
}
t, err := time.Parse(timeFmt, modTime)
if err != nil {
return err
}
f.modTime = t
f.isDir, ok = m["isDir"].(bool)
if !ok {
return fmt.Errorf("could not determine isDir %q", m["isDir"])
}
f.sys = m["sys"]
f.virtual = true
return nil
}
func (f *FileInfo) Name() string {
return f.name
}
func (f *FileInfo) Size() int64 {
return f.size
}
func (f *FileInfo) Mode() os.FileMode {
return f.mode
}
func (f *FileInfo) ModTime() time.Time {
return f.modTime
}
func (f *FileInfo) IsDir() bool {
return f.isDir
}
func (f *FileInfo) Sys() interface{} {
return f.sys
}
var _ os.FileInfo = &FileInfo{}
func NewFileInfo(info os.FileInfo) *FileInfo {
fi := &FileInfo{
name: cleanName(info.Name()),
size: info.Size(),
mode: info.Mode(),
modTime: info.ModTime(),
isDir: info.IsDir(),
sys: info.Sys(),
}
return fi
}
func WithName(name string, info os.FileInfo) *FileInfo {
if ft, ok := info.(*FileInfo); ok {
ft.name = cleanName(name)
return ft
}
fo := NewFileInfo(info)
fo.name = cleanName(name)
return fo
}
func cleanName(s string) string {
if strings.Contains(s, "\\") {
s = strings.Replace(s, "\\", "/", -1)
}
if !strings.HasPrefix(s, "/") {
s = "/" + s
}
return s
}

View File

@ -1,108 +0,0 @@
package pkger
import (
"encoding/json"
"io"
"io/ioutil"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func Test_File_Open(t *testing.T) {
r := require.New(t)
f, err := Open("/file_test.go")
r.NoError(err)
r.Equal("/file_test.go", f.Name())
b, err := ioutil.ReadAll(f)
r.NoError(err)
r.Contains(string(b), "Test_File_Open")
r.NoError(f.Close())
}
func Test_File_Open_Dir(t *testing.T) {
r := require.New(t)
f, err := Open("/cmd")
r.NoError(err)
r.Equal("/cmd", f.Name())
r.NoError(f.Close())
}
func Test_File_Read_Memory(t *testing.T) {
r := require.New(t)
f, err := Open("/file_test.go")
r.NoError(err)
f.data = []byte("hi!")
r.Equal("/file_test.go", f.Name())
b, err := ioutil.ReadAll(f)
r.NoError(err)
r.Equal(string(b), "hi!")
r.NoError(f.Close())
}
func Test_File_Write(t *testing.T) {
r := require.New(t)
f, err := Create("/hello.txt")
r.NoError(err)
r.NotNil(f)
fi, err := f.Stat()
r.NoError(err)
r.Zero(fi.Size())
r.Equal("/hello.txt", fi.Name())
mt := fi.ModTime()
r.NotZero(mt)
sz, err := io.Copy(f, strings.NewReader(radio))
r.NoError(err)
r.Equal(int64(1381), sz)
// because windows can't handle the time precisely
// enough, we have to *force* just a smidge of time
// to ensure the two ModTime's are different.
// i know, i hate it too.
time.Sleep(time.Millisecond)
r.NoError(f.Close())
r.Equal(int64(1381), fi.Size())
r.NotZero(fi.ModTime())
r.NotEqual(mt, fi.ModTime())
}
func Test_File_JSON(t *testing.T) {
r := require.New(t)
f, err := createFile("/radio.radio")
r.NoError(err)
r.NotNil(f)
bi, err := f.Stat()
r.NoError(err)
mj, err := json.Marshal(f)
r.NoError(err)
f2 := &File{}
r.NoError(json.Unmarshal(mj, f2))
ai, err := f2.Stat()
r.NoError(err)
r.Equal(bi.Size(), ai.Size())
r.Equal(string(f.data), string(f2.data))
}

View File

@ -1,6 +0,0 @@
package pkger
func Exclude(fl *File, excludes ...string) error {
fl.excludes = append(fl.excludes, excludes...)
return nil
}

2
go.mod
View File

@ -1,6 +1,6 @@
module github.com/markbates/pkger module github.com/markbates/pkger
go 1.12 go 1.13
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect

View File

@ -8,34 +8,36 @@ import (
// Dir attempts to gather info for the requested directory. // Dir attempts to gather info for the requested directory.
func Dir(p string) (Info, error) { func Dir(p string) (Info, error) {
var i Info return Cache(p, func(p string) (Info, error) {
var i Info
fi, err := os.Stat(p) fi, err := os.Stat(p)
if err != nil { if err != nil {
return i, err return i, err
} }
if !fi.IsDir() { if !fi.IsDir() {
p = filepath.Dir(p) p = filepath.Dir(p)
} }
pwd, err := os.Getwd() pwd, err := os.Getwd()
if err != nil { if err != nil {
return i, err return i, err
} }
defer os.Chdir(pwd) defer os.Chdir(pwd)
os.Chdir(p) os.Chdir(p)
b, err := run("go", "list", "-json") b, err := run("go", "list", "-json")
if err != nil { if err != nil {
return i, err return i, err
} }
if err := json.Unmarshal(b, &i); err != nil { if err := json.Unmarshal(b, &i); err != nil {
return i, err return i, err
} }
return i, nil return i, nil
})
} }

View File

@ -4,8 +4,13 @@ import (
"bytes" "bytes"
"os" "os"
"os/exec" "os/exec"
"sync"
) )
var cache = &infoMap{
data: &sync.Map{},
}
func run(n string, args ...string) ([]byte, error) { func run(n string, args ...string) ([]byte, error) {
c := exec.Command(n, args...) c := exec.Command(n, args...)
@ -19,3 +24,16 @@ func run(n string, args ...string) ([]byte, error) {
return bb.Bytes(), nil return bb.Bytes(), nil
} }
func Cache(p string, fn func(string) (Info, error)) (Info, error) {
i, ok := cache.Load(p)
if ok {
return i, nil
}
i, err := fn(p)
if err != nil {
return i, err
}
cache.Store(p, i)
return i, nil
}

87
here/info_map.go Normal file
View File

@ -0,0 +1,87 @@
// Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.
package here
import (
"sort"
"sync"
)
// infoMap wraps sync.Map and uses the following types:
// key: string
// value: Info
type infoMap struct {
data *sync.Map
}
// Delete the key from the map
func (m *infoMap) Delete(key string) {
m.data.Delete(key)
}
// Load the key from the map.
// Returns Info or bool.
// A false return indicates either the key was not found
// or the value is not of type Info
func (m *infoMap) Load(key string) (Info, bool) {
i, ok := m.data.Load(key)
if !ok {
return Info{}, false
}
s, ok := i.(Info)
return s, ok
}
// LoadOrStore will return an existing key or
// store the value if not already in the map
func (m *infoMap) LoadOrStore(key string, value Info) (Info, bool) {
i, _ := m.data.LoadOrStore(key, value)
s, ok := i.(Info)
return s, ok
}
// LoadOr will return an existing key or
// run the function and store the results
func (m *infoMap) LoadOr(key string, fn func(*infoMap) (Info, bool)) (Info, bool) {
i, ok := m.Load(key)
if ok {
return i, ok
}
i, ok = fn(m)
if ok {
m.Store(key, i)
return i, ok
}
return i, false
}
// Range over the Info values in the map
func (m *infoMap) Range(f func(key string, value Info) bool) {
m.data.Range(func(k, v interface{}) bool {
key, ok := k.(string)
if !ok {
return false
}
value, ok := v.(Info)
if !ok {
return false
}
return f(key, value)
})
}
// Store a Info in the map
func (m *infoMap) Store(key string, value Info) {
m.data.Store(key, value)
}
// Keys returns a list of keys in the map
func (m *infoMap) Keys() []string {
var keys []string
m.Range(func(key string, value Info) bool {
keys = append(keys, key)
return true
})
sort.Strings(keys)
return keys
}

View File

@ -1,6 +1,8 @@
package here package here
import "encoding/json" import (
"encoding/json"
)
// Package attempts to gather info for the requested package. // Package attempts to gather info for the requested package.
// //
@ -12,14 +14,16 @@ import "encoding/json"
// returned `Info` value and pass it to the `Dir(string) (Info, error)` // returned `Info` value and pass it to the `Dir(string) (Info, error)`
// function to return the complete data. // function to return the complete data.
func Package(p string) (Info, error) { func Package(p string) (Info, error) {
var i Info return Cache(p, func(p string) (Info, error) {
b, err := run("go", "list", "-json", "-find", p) var i Info
if err != nil { b, err := run("go", "list", "-json", "-find", p)
return i, err if err != nil {
} return i, err
if err := json.Unmarshal(b, &i); err != nil { }
return i, err if err := json.Unmarshal(b, &i); err != nil {
} return i, err
}
return i, nil return i, nil
})
} }

17
info.go
View File

@ -1,17 +0,0 @@
package pkger
import "github.com/markbates/pkger/here"
func Info(p string) (here.Info, error) {
info, ok := infosCache.Load(p)
if ok {
return info, nil
}
info, err := here.Package(p)
if err != nil {
return info, err
}
infosCache.Store(p, info)
return info, nil
}

View File

@ -1,23 +1,25 @@
// Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT. // Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.
package pkger package maps
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort" "sort"
"sync" "sync"
"github.com/markbates/pkger/pkging"
) )
// filesMap wraps sync.Map and uses the following types: // Files wraps sync.Map and uses the following types:
// key: Path // key: pkging.Path
// value: *File // value: pkging.File
type filesMap struct { type Files struct {
data *sync.Map data *sync.Map
once *sync.Once once *sync.Once
} }
func (m *filesMap) Data() *sync.Map { func (m *Files) Data() *sync.Map {
if m.once == nil { if m.once == nil {
m.once = &sync.Once{} m.once = &sync.Once{}
} }
@ -29,7 +31,7 @@ func (m *filesMap) Data() *sync.Map {
return m.data return m.data
} }
func (m *filesMap) MarshalJSON() ([]byte, error) { func (m *Files) MarshalJSON() ([]byte, error) {
var err error var err error
mm := map[string]interface{}{} mm := map[string]interface{}{}
m.Data().Range(func(key, value interface{}) bool { m.Data().Range(func(key, value interface{}) bool {
@ -49,14 +51,14 @@ func (m *filesMap) MarshalJSON() ([]byte, error) {
return json.Marshal(mm) return json.Marshal(mm)
} }
func (m *filesMap) UnmarshalJSON(b []byte) error { func (m *Files) UnmarshalJSON(b []byte) error {
mm := map[string]*File{} mm := map[string]pkging.File{}
if err := json.Unmarshal(b, &mm); err != nil { if err := json.Unmarshal(b, &mm); err != nil {
return err return err
} }
for k, v := range mm { for k, v := range mm {
var pt Path var pt pkging.Path
if err := json.Unmarshal([]byte(k), &pt); err != nil { if err := json.Unmarshal([]byte(k), &pt); err != nil {
return err return err
} }
@ -66,54 +68,31 @@ func (m *filesMap) UnmarshalJSON(b []byte) error {
} }
// Delete the key from the map // Delete the key from the map
func (m *filesMap) Delete(key Path) { func (m *Files) Delete(key pkging.Path) {
m.Data().Delete(key) m.Data().Delete(key)
} }
// Load the key from the map. // Load the key from the map.
// Returns *File or bool. // Returns pkging.File or bool.
// A false return indicates either the key was not found // A false return indicates either the key was not found
// or the value is not of type *File // or the value is not of type pkging.File
func (m *filesMap) Load(key Path) (*File, bool) { func (m *Files) Load(key pkging.Path) (pkging.File, bool) {
i, ok := m.Data().Load(key) i, ok := m.Data().Load(key)
if !ok { if !ok {
return nil, false return nil, false
} }
s, ok := i.(*File) s, ok := i.(pkging.File)
return s, ok return s, ok
} }
// LoadOrStore will return an existing key or // Range over the pkging.File values in the map
// store the value if not already in the map func (m *Files) Range(f func(key pkging.Path, value pkging.File) bool) {
func (m *filesMap) LoadOrStore(key Path, value *File) (*File, bool) {
i, _ := m.Data().LoadOrStore(key, value)
s, ok := i.(*File)
return s, ok
}
// LoadOr will return an existing key or
// run the function and store the results
func (m *filesMap) LoadOr(key Path, fn func(*filesMap) (*File, bool)) (*File, bool) {
i, ok := m.Load(key)
if ok {
return i, ok
}
i, ok = fn(m)
if ok {
m.Store(key, i)
return i, ok
}
return i, false
}
// Range over the *File values in the map
func (m *filesMap) Range(f func(key Path, value *File) bool) {
m.Data().Range(func(k, v interface{}) bool { m.Data().Range(func(k, v interface{}) bool {
key, ok := k.(Path) key, ok := k.(pkging.Path)
if !ok { if !ok {
return false return false
} }
value, ok := v.(*File) value, ok := v.(pkging.File)
if !ok { if !ok {
return false return false
} }
@ -121,15 +100,15 @@ func (m *filesMap) Range(f func(key Path, value *File) bool) {
}) })
} }
// Store a *File in the map // Store a pkging.File in the map
func (m *filesMap) Store(key Path, value *File) { func (m *Files) Store(key pkging.Path, value pkging.File) {
m.Data().Store(key, value) m.Data().Store(key, value)
} }
// Keys returns a list of keys in the map // Keys returns a list of keys in the map
func (m *filesMap) Keys() []Path { func (m *Files) Keys() []pkging.Path {
var keys []Path var keys []pkging.Path
m.Range(func(key Path, value *File) bool { m.Range(func(key pkging.Path, value pkging.File) bool {
keys = append(keys, key) keys = append(keys, key)
return true return true
}) })
@ -139,6 +118,6 @@ func (m *filesMap) Keys() []Path {
return keys return keys
} }
func (m *filesMap) String() string { func (m *Files) String() string {
return fmt.Sprintf("%v", m.Keys()) return fmt.Sprintf("%v", m.Keys())
} }

View File

@ -1,6 +1,6 @@
// Code generated by github.com/markbates/pkger/mapgen. DO NOT EDIT. // Code generated by github.com/markbates/pkger/mapgen. DO NOT EDIT.
package pkger package maps
import ( import (
"encoding/json" "encoding/json"
@ -11,15 +11,15 @@ import (
"github.com/markbates/pkger/here" "github.com/markbates/pkger/here"
) )
// infosMap wraps sync.Map and uses the following types: // Infos wraps sync.Map and uses the following types:
// key: string // key: string
// value: here.Info // value: here.Info
type infosMap struct { type Infos struct {
data *sync.Map data *sync.Map
once *sync.Once once *sync.Once
} }
func (m *infosMap) Data() *sync.Map { func (m *Infos) Data() *sync.Map {
if m.once == nil { if m.once == nil {
m.once = &sync.Once{} m.once = &sync.Once{}
} }
@ -31,7 +31,7 @@ func (m *infosMap) Data() *sync.Map {
return m.data return m.data
} }
func (m *infosMap) MarshalJSON() ([]byte, error) { func (m *Infos) MarshalJSON() ([]byte, error) {
mm := map[string]interface{}{} mm := map[string]interface{}{}
m.data.Range(func(key, value interface{}) bool { m.data.Range(func(key, value interface{}) bool {
mm[fmt.Sprintf("%s", key)] = value mm[fmt.Sprintf("%s", key)] = value
@ -40,7 +40,7 @@ func (m *infosMap) MarshalJSON() ([]byte, error) {
return json.Marshal(mm) return json.Marshal(mm)
} }
func (m *infosMap) UnmarshalJSON(b []byte) error { func (m *Infos) UnmarshalJSON(b []byte) error {
mm := map[string]here.Info{} mm := map[string]here.Info{}
if err := json.Unmarshal(b, &mm); err != nil { if err := json.Unmarshal(b, &mm); err != nil {
@ -53,7 +53,7 @@ func (m *infosMap) UnmarshalJSON(b []byte) error {
} }
// Delete the key from the map // Delete the key from the map
func (m *infosMap) Delete(key string) { func (m *Infos) Delete(key string) {
m.Data().Delete(key) m.Data().Delete(key)
} }
@ -61,7 +61,7 @@ func (m *infosMap) Delete(key string) {
// Returns here.Info or bool. // Returns here.Info or bool.
// A false return indicates either the key was not found // A false return indicates either the key was not found
// or the value is not of type here.Info // or the value is not of type here.Info
func (m *infosMap) Load(key string) (here.Info, bool) { func (m *Infos) Load(key string) (here.Info, bool) {
m.Data() m.Data()
i, ok := m.data.Load(key) i, ok := m.data.Load(key)
if !ok { if !ok {
@ -71,31 +71,8 @@ func (m *infosMap) Load(key string) (here.Info, bool) {
return s, ok return s, ok
} }
// LoadOrStore will return an existing key or
// store the value if not already in the map
func (m *infosMap) LoadOrStore(key string, value here.Info) (here.Info, bool) {
i, _ := m.Data().LoadOrStore(key, value)
s, ok := i.(here.Info)
return s, ok
}
// LoadOr will return an existing key or
// run the function and store the results
func (m *infosMap) LoadOr(key string, fn func(*infosMap) (here.Info, bool)) (here.Info, bool) {
i, ok := m.Load(key)
if ok {
return i, ok
}
i, ok = fn(m)
if ok {
m.Store(key, i)
return i, ok
}
return i, false
}
// Range over the here.Info values in the map // Range over the here.Info values in the map
func (m *infosMap) Range(f func(key string, value here.Info) bool) { func (m *Infos) Range(f func(key string, value here.Info) bool) {
m.Data().Range(func(k, v interface{}) bool { m.Data().Range(func(k, v interface{}) bool {
key, ok := k.(string) key, ok := k.(string)
if !ok { if !ok {
@ -110,12 +87,12 @@ func (m *infosMap) Range(f func(key string, value here.Info) bool) {
} }
// Store a here.Info in the map // Store a here.Info in the map
func (m *infosMap) Store(key string, value here.Info) { func (m *Infos) Store(key string, value here.Info) {
m.Data().Store(key, value) m.Data().Store(key, value)
} }
// Keys returns a list of keys in the map // Keys returns a list of keys in the map
func (m *infosMap) Keys() []string { func (m *Infos) Keys() []string {
var keys []string var keys []string
m.Range(func(key string, value here.Info) bool { m.Range(func(key string, value here.Info) bool {
keys = append(keys, key) keys = append(keys, key)

165
internal/maps/paths.go Normal file
View File

@ -0,0 +1,165 @@
package maps
import (
"encoding/json"
"fmt"
"path/filepath"
"regexp"
"sort"
"strings"
"sync"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/pkging"
)
// Paths wraps sync.Map and uses the following types:
// key: string
// value: Path
type Paths struct {
Current here.Info
data *sync.Map
once *sync.Once
}
func (m *Paths) Data() *sync.Map {
if m.once == nil {
m.once = &sync.Once{}
}
m.once.Do(func() {
if m.data == nil {
m.data = &sync.Map{}
}
})
return m.data
}
func (m *Paths) MarshalJSON() ([]byte, error) {
mm := map[string]interface{}{}
m.Data().Range(func(key, value interface{}) bool {
mm[fmt.Sprintf("%s", key)] = value
return true
})
return json.Marshal(mm)
}
func (m *Paths) UnmarshalJSON(b []byte) error {
mm := map[string]pkging.Path{}
if err := json.Unmarshal(b, &mm); err != nil {
return err
}
for k, v := range mm {
m.Store(k, v)
}
return nil
}
// Delete the key from the map
func (m *Paths) Delete(key string) {
m.Data().Delete(key)
}
// Load the key from the map.
// Returns Path or bool.
// A false return indicates either the key was not found
// or the value is not of type Path
func (m *Paths) Load(key string) (pkging.Path, bool) {
i, ok := m.Data().Load(key)
if !ok {
return pkging.Path{}, false
}
s, ok := i.(pkging.Path)
return s, ok
}
// Range over the Path values in the map
func (m *Paths) Range(f func(key string, value pkging.Path) bool) {
m.Data().Range(func(k, v interface{}) bool {
key, ok := k.(string)
if !ok {
return false
}
value, ok := v.(pkging.Path)
if !ok {
return false
}
return f(key, value)
})
}
// Store a Path in the map
func (m *Paths) Store(key string, value pkging.Path) {
m.Data().Store(key, value)
}
// Keys returns a list of keys in the map
func (m *Paths) Keys() []string {
var keys []string
m.Range(func(key string, value pkging.Path) bool {
keys = append(keys, key)
return true
})
sort.Strings(keys)
return keys
}
func (m *Paths) Parse(p string) (pkging.Path, error) {
p = strings.TrimSpace(p)
p = filepath.Clean(p)
p = strings.TrimPrefix(p, m.Current.Dir)
p = strings.Replace(p, "\\", "/", -1)
p = strings.TrimSpace(p)
pt, ok := m.Load(p)
if ok {
return pt, nil
}
if len(p) == 0 || p == ":" {
return m.build("", "", "")
}
res := pathrx.FindAllStringSubmatch(p, -1)
if len(res) == 0 {
return pt, fmt.Errorf("could not parse %q", p)
}
matches := res[0]
if len(matches) != 4 {
return pt, fmt.Errorf("could not parse %q", p)
}
return m.build(p, matches[1], matches[3])
}
var pathrx = regexp.MustCompile("([^:]+)(:(/.+))?")
func (m *Paths) build(p, pkg, name string) (pkging.Path, error) {
pt := pkging.Path{
Pkg: pkg,
Name: name,
}
if strings.HasPrefix(pt.Pkg, "/") || len(pt.Pkg) == 0 {
pt.Name = pt.Pkg
pt.Pkg = m.Current.ImportPath
}
if len(pt.Name) == 0 {
pt.Name = "/"
}
if pt.Pkg == pt.Name {
pt.Pkg = m.Current.ImportPath
pt.Name = "/"
}
if !strings.HasPrefix(pt.Name, "/") {
pt.Name = "/" + pt.Name
}
pt.Name = strings.TrimPrefix(pt.Name, m.Current.Dir)
m.Store(p, pt)
return pt, nil
}

83
json.go
View File

@ -1,83 +0,0 @@
package pkger
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/markbates/pkger/here"
)
type jason struct {
Files *filesMap `json:"files"`
Infos *infosMap `json:"infos"`
Paths *pathsMap `json:"paths"`
CurrentInfo here.Info `json:"current_info"`
}
func (f File) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{}
m["info"] = f.info
m["her"] = f.her
m["path"] = f.path
m["data"] = f.data
m["parent"] = f.parent
if !f.info.virtual {
if len(f.data) == 0 && !f.info.IsDir() {
b, err := ioutil.ReadAll(&f)
if err != nil {
return nil, err
}
m["data"] = b
}
}
return json.Marshal(m)
}
func (f *File) UnmarshalJSON(b []byte) error {
m := map[string]json.RawMessage{}
if err := json.Unmarshal(b, &m); err != nil {
return err
}
info, ok := m["info"]
if !ok {
return fmt.Errorf("missing info")
}
f.info = &FileInfo{}
if err := json.Unmarshal(info, f.info); err != nil {
return err
}
her, ok := m["her"]
if !ok {
return fmt.Errorf("missing her")
}
if err := json.Unmarshal(her, &f.her); err != nil {
return err
}
path, ok := m["path"]
if !ok {
return fmt.Errorf("missing path")
}
if err := json.Unmarshal(path, &f.path); err != nil {
return err
}
parent, ok := m["parent"]
if !ok {
return fmt.Errorf("missing parent")
}
if err := json.Unmarshal(parent, &f.parent); err != nil {
return err
}
if err := json.Unmarshal(m["data"], &f.data); err != nil {
return err
}
return nil
}

79
open.go
View File

@ -1,79 +0,0 @@
package pkger
import (
"net/http"
"os"
"path"
"path/filepath"
"strings"
)
func (f *File) Open(name string) (http.File, error) {
pt, err := Parse(name)
if err != nil {
return nil, err
}
if pt == f.path {
return f, nil
}
pt.Name = path.Join(f.Path().Name, pt.Name)
di, err := Open(pt.String())
if err != nil {
return nil, err
}
fi, err := di.Stat()
if err != nil {
return nil, err
}
if fi.IsDir() {
di.parent = f.path
di.excludes = f.excludes
}
return di, nil
}
func Open(name string) (*File, error) {
pt, err := Parse(name)
if err != nil {
return nil, err
}
f, ok := filesCache.Load(pt)
if !ok {
return openDisk(pt)
}
nf := &File{
info: WithName(f.info.Name(), f.info),
path: f.path,
data: f.data,
her: f.her,
}
return nf, nil
}
func openDisk(pt Path) (*File, error) {
info, err := Info(pt.Pkg)
if err != nil {
return nil, err
}
fp := info.Dir
if len(pt.Name) > 0 {
fp = filepath.Join(fp, pt.Name)
}
fi, err := os.Stat(fp)
if err != nil {
return nil, err
}
f := &File{
info: WithName(strings.TrimPrefix(pt.Name, "/"), NewFileInfo(fi)),
her: info,
path: pt,
}
return f, nil
}

View File

@ -1,133 +0,0 @@
package pkger
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func Test_Open_File(t *testing.T) {
r := require.New(t)
f, err := Open(".")
r.NoError(err)
ts := httptest.NewServer(http.FileServer(f))
defer ts.Close()
res, err := http.Get(ts.URL + "/cmd/pkger/main.go")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
r.Contains(string(b), "does not compute")
r.NoError(f.Close())
}
func Test_Open_Dir(t *testing.T) {
r := require.New(t)
f, err := Open("/")
r.NoError(err)
ts := httptest.NewServer(http.FileServer(f))
defer ts.Close()
res, err := http.Get(ts.URL + "/cmd/pkger")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
r.Contains(string(b), `<a href="/cmd/pkger/main.go">/cmd/pkger/main.go</a>`)
r.NoError(f.Close())
}
func Test_Open_File_Memory(t *testing.T) {
r := require.New(t)
f, err := Create("/suit/case.txt")
r.NoError(err)
f.Write([]byte(radio))
r.NoError(f.Close())
r.Equal([]byte(radio), f.data)
r.Contains(string(f.data), "I wanna bite the hand that feeds me")
dir, err := Open("/")
r.NoError(err)
defer dir.Close()
ts := httptest.NewServer(http.FileServer(dir))
defer ts.Close()
res, err := http.Get(ts.URL + "/suit/case.txt")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, _ := ioutil.ReadAll(res.Body)
// r.NoError(err)
r.Contains(string(b), "I wanna bite the hand that feeds me")
}
func Test_Open_Dir_StripPrefix(t *testing.T) {
r := require.New(t)
ts := httptest.NewServer(http.StripPrefix("/assets/", http.FileServer(http.Dir("./testdata/public"))))
defer ts.Close()
res, err := http.Get(ts.URL + "/assets/radio.radio")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, _ := ioutil.ReadAll(res.Body)
// r.NoError(err)
r.Contains(string(b), "I wanna bite the hand that feeds me")
res, err = http.Get(ts.URL + "/assets/")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, _ = ioutil.ReadAll(res.Body)
// r.NoError(err)
r.Contains(string(b), `<a href="radio.radio">radio.radio</a>`)
}
func Test_Open_Dir_Memory_StripPrefix(t *testing.T) {
r := require.New(t)
err := MkdirAll("/testdata/public", 0755)
r.NoError(err)
dir, err := Open("/testdata/public")
r.NoError(err)
defer dir.Close()
ts := httptest.NewServer(http.StripPrefix("/assets/", http.FileServer(dir)))
defer ts.Close()
res, err := http.Get(ts.URL + "/assets/radio.radio")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, _ := ioutil.ReadAll(res.Body)
// r.NoError(err)
r.Contains(string(b), "I wanna bite the hand that feeds me")
res, err = http.Get(ts.URL + "/assets/")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, _ = ioutil.ReadAll(res.Body)
// r.NoError(err)
r.Contains(string(b), `<a href="/radio.radio">/radio.radio</a>`)
r.NotContains(string(b), `/public`)
r.NotContains(string(b), `//`)
}

View File

@ -9,20 +9,15 @@ import (
"github.com/markbates/pkger" "github.com/markbates/pkger"
"github.com/markbates/pkger/here" "github.com/markbates/pkger/here"
"github.com/markbates/pkger/pkging"
) )
var DefaultIgnoredFolders = []string{".", "_", "vendor", "node_modules", "_fixtures", "testdata"} var DefaultIgnoredFolders = []string{".", "_", "vendor", "node_modules", "_fixtures", "testdata"}
func Parse(name string) (Results, error) { func Parse(cur here.Info) (Results, error) {
var r Results var r Results
c, err := pkger.Stat()
if err != nil {
return r, err
}
if name == "" { name := cur.ImportPath
name = c.ImportPath
}
pt, err := pkger.Parse(name) pt, err := pkger.Parse(name)
if err != nil { if err != nil {
@ -36,7 +31,7 @@ func Parse(name string) (Results, error) {
return r, err return r, err
} }
m := map[pkger.Path]bool{} m := map[pkging.Path]bool{}
root := r.Path.Name root := r.Path.Name
if !strings.HasPrefix(root, string(filepath.Separator)) { if !strings.HasPrefix(root, string(filepath.Separator)) {
@ -117,7 +112,7 @@ func Parse(name string) (Results, error) {
return nil return nil
}) })
var found []pkger.Path var found []pkging.Path
for k := range m { for k := range m {
if len(k.String()) == 0 { if len(k.String()) == 0 {
@ -133,8 +128,8 @@ func Parse(name string) (Results, error) {
return r, err return r, err
} }
func sourceFiles(pt pkger.Path) ([]pkger.Path, error) { func sourceFiles(pt pkging.Path) ([]pkging.Path, error) {
var res []pkger.Path var res []pkging.Path
her, err := pkger.Info(pt.Pkg) her, err := pkger.Info(pt.Pkg)
@ -177,7 +172,7 @@ func sourceFiles(pt pkger.Path) ([]pkger.Path, error) {
n := strings.TrimPrefix(p, her.Dir) n := strings.TrimPrefix(p, her.Dir)
n = strings.Replace(n, "\\", "/", -1) n = strings.Replace(n, "\\", "/", -1)
pt := pkger.Path{ pt := pkging.Path{
Name: n, Name: n,
} }
res = append(res, pt) res = append(res, pt)
@ -188,6 +183,6 @@ func sourceFiles(pt pkger.Path) ([]pkger.Path, error) {
} }
type Results struct { type Results struct {
Paths []pkger.Path Paths []pkging.Path
Path pkger.Path Path pkging.Path
} }

View File

@ -1,22 +1,23 @@
package parser package parser
import ( import (
"os"
"path/filepath" "path/filepath"
"sort" "sort"
"testing" "testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func Test_Parser(t *testing.T) { func Test_Parser(t *testing.T) {
r := require.New(t) r := require.New(t)
pwd, err := os.Getwd()
r.NoError(err)
r.NoError(os.Chdir(filepath.Join("..", "examples", "app")))
defer os.Chdir(pwd)
res, err := Parse("") ch := filepath.Join("..", "examples", "app")
info := here.Info{
Dir: ch,
ImportPath: "github.com/markbates/pkger/examples/app",
}
res, err := Parse(info)
r.NoError(err) r.NoError(err)

View File

@ -8,11 +8,12 @@ import (
"github.com/markbates/pkger" "github.com/markbates/pkger"
"github.com/markbates/pkger/here" "github.com/markbates/pkger/here"
"github.com/markbates/pkger/pkging"
) )
type visitor struct { type visitor struct {
File string File string
Found map[pkger.Path]bool Found map[pkging.Path]bool
info here.Info info here.Info
errors []error errors []error
} }
@ -20,12 +21,12 @@ type visitor struct {
func newVisitor(p string, info here.Info) (*visitor, error) { func newVisitor(p string, info here.Info) (*visitor, error) {
return &visitor{ return &visitor{
File: p, File: p,
Found: map[pkger.Path]bool{}, Found: map[pkging.Path]bool{},
info: info, info: info,
}, nil }, nil
} }
func (v *visitor) Run() ([]pkger.Path, error) { func (v *visitor) Run() ([]pkging.Path, error) {
pf, err := parseFile(v.File) pf, err := parseFile(v.File)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s: %v", v.File, err) return nil, fmt.Errorf("%s: %v", v.File, err)
@ -33,7 +34,7 @@ func (v *visitor) Run() ([]pkger.Path, error) {
ast.Walk(v, pf.Ast) ast.Walk(v, pf.Ast)
var found []pkger.Path var found []pkging.Path
for k := range v.Found { for k := range v.Found {
found = append(found, k) found = append(found, k)

99
path.go
View File

@ -1,99 +0,0 @@
package pkger
import (
"encoding/json"
"fmt"
"os"
"regexp"
"strings"
)
type Path struct {
Pkg string `json:"pkg"`
Name string `json:"name"`
}
func Parse(p string) (Path, error) {
p = strings.Replace(p, "\\", "/", -1)
pt, ok := pathsCache.Load(p)
if ok {
return pt, nil
}
if len(p) == 0 {
return build(p, "", "")
}
res := pathrx.FindAllStringSubmatch(p, -1)
if len(res) == 0 {
return pt, fmt.Errorf("could not parse %q", p)
}
matches := res[0]
if len(matches) != 4 {
return pt, fmt.Errorf("could not parse %q", p)
}
return build(p, matches[1], matches[3])
}
func (p Path) String() string {
if p.Name == "" {
p.Name = "/"
}
return fmt.Sprintf("%s:%s", p.Pkg, p.Name)
}
func (p Path) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
if st.Flag('+') {
b, err := json.MarshalIndent(p, "", " ")
if err != nil {
fmt.Fprint(os.Stderr, err)
return
}
fmt.Fprint(st, string(b))
return
}
fmt.Fprint(st, p.String())
case 'q':
fmt.Fprintf(st, "%q", p.String())
default:
fmt.Fprint(st, p.String())
}
}
var pathrx = regexp.MustCompile("([^:]+)(:(/.+))?")
func build(p, pkg, name string) (Path, error) {
pt := Path{
Pkg: pkg,
Name: name,
}
current, err := Stat()
if err != nil {
return pt, err
}
if strings.HasPrefix(pt.Pkg, "/") || len(pt.Pkg) == 0 {
pt.Name = pt.Pkg
pt.Pkg = current.ImportPath
}
if len(pt.Name) == 0 {
pt.Name = "/"
}
if pt.Pkg == pt.Name {
pt.Pkg = current.ImportPath
pt.Name = "/"
}
if !strings.HasPrefix(pt.Name, "/") {
pt.Name = "/" + pt.Name
}
pathsCache.Store(p, pt)
return pt, nil
}

View File

@ -1,52 +0,0 @@
package pkger
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_Path_String(t *testing.T) {
table := []struct {
in Path
out string
}{
{in: Path{}, out: ":/"},
{in: Path{Pkg: curPkg}, out: curPkg + ":/"},
{in: Path{Pkg: curPkg, Name: "/foo.go"}, out: curPkg + ":/foo.go"},
{in: Path{Name: "/foo.go"}, out: ":/foo.go"},
{in: Path{Pkg: "github.com/markbates/pkger/internal/examples/app"}, out: "github.com/markbates/pkger/internal/examples/app:/"},
}
for _, tt := range table {
t.Run(tt.in.String(), func(st *testing.T) {
r := require.New(st)
r.Equal(tt.out, tt.in.String())
})
}
}
func Test_Parse(t *testing.T) {
table := []struct {
in string
out Path
}{
{in: curPkg, out: Path{Pkg: curPkg, Name: "/"}},
{in: curPkg + ":/foo.go", out: Path{Pkg: curPkg, Name: "/foo.go"}},
{in: "/paths/parse_test.go", out: Path{Pkg: curPkg, Name: "/paths/parse_test.go"}},
{in: `\windows\path.go`, out: Path{Pkg: curPkg, Name: "/windows/path.go"}},
{in: "", out: Path{Pkg: curPkg, Name: "/"}},
{in: "github.com/markbates/pkger/internal/examples/app", out: Path{Pkg: "github.com/markbates/pkger/internal/examples/app", Name: "/"}},
}
for _, tt := range table {
t.Run(tt.in, func(st *testing.T) {
r := require.New(st)
pt, err := Parse(tt.in)
r.NoError(err)
r.Equal(tt.out.Pkg, pt.Pkg)
r.Equal(tt.out.Name, pt.Name)
})
}
}

View File

@ -1,123 +0,0 @@
// Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.
package pkger
import (
"encoding/json"
"fmt"
"sort"
"sync"
)
// pathsMap wraps sync.Map and uses the following types:
// key: string
// value: Path
type pathsMap struct {
data *sync.Map
once *sync.Once
}
func (m *pathsMap) Data() *sync.Map {
if m.once == nil {
m.once = &sync.Once{}
}
m.once.Do(func() {
if m.data == nil {
m.data = &sync.Map{}
}
})
return m.data
}
func (m *pathsMap) MarshalJSON() ([]byte, error) {
mm := map[string]interface{}{}
m.Data().Range(func(key, value interface{}) bool {
mm[fmt.Sprintf("%s", key)] = value
return true
})
return json.Marshal(mm)
}
func (m *pathsMap) UnmarshalJSON(b []byte) error {
mm := map[string]Path{}
if err := json.Unmarshal(b, &mm); err != nil {
return err
}
for k, v := range mm {
m.Store(k, v)
}
return nil
}
// Delete the key from the map
func (m *pathsMap) Delete(key string) {
m.Data().Delete(key)
}
// Load the key from the map.
// Returns Path or bool.
// A false return indicates either the key was not found
// or the value is not of type Path
func (m *pathsMap) Load(key string) (Path, bool) {
i, ok := m.Data().Load(key)
if !ok {
return Path{}, false
}
s, ok := i.(Path)
return s, ok
}
// LoadOrStore will return an existing key or
// store the value if not already in the map
func (m *pathsMap) LoadOrStore(key string, value Path) (Path, bool) {
i, _ := m.Data().LoadOrStore(key, value)
s, ok := i.(Path)
return s, ok
}
// LoadOr will return an existing key or
// run the function and store the results
func (m *pathsMap) LoadOr(key string, fn func(*pathsMap) (Path, bool)) (Path, bool) {
i, ok := m.Load(key)
if ok {
return i, ok
}
i, ok = fn(m)
if ok {
m.Store(key, i)
return i, ok
}
return i, false
}
// Range over the Path values in the map
func (m *pathsMap) Range(f func(key string, value Path) bool) {
m.Data().Range(func(k, v interface{}) bool {
key, ok := k.(string)
if !ok {
return false
}
value, ok := v.(Path)
if !ok {
return false
}
return f(key, value)
})
}
// Store a Path in the map
func (m *pathsMap) Store(key string, value Path) {
m.Data().Store(key, value)
}
// Keys returns a list of keys in the map
func (m *pathsMap) Keys() []string {
var keys []string
m.Range(func(key string, value Path) bool {
keys = append(keys, key)
return true
})
sort.Strings(keys)
return keys
}

126
pkger.go
View File

@ -1,117 +1,29 @@
package pkger package pkger
import ( import (
"bytes"
"compress/gzip"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log" "log"
"sync"
"github.com/markbates/pkger/here" "github.com/markbates/pkger/pkging"
"github.com/markbates/pkger/pkging/stdos"
) )
var filesCache = &filesMap{} var current = func() pkging.Pkger {
var infosCache = &infosMap{} n, err := stdos.New()
var pathsCache = &pathsMap{}
var curOnce = &sync.Once{}
var currentInfo here.Info
var packed bool
var packMU = &sync.RWMutex{}
func ReadFile(s string) ([]byte, error) {
f, err := Open(s)
if err != nil { if err != nil {
return nil, err log.Fatal(err)
} }
defer f.Close() return n
return ioutil.ReadAll(f) }()
}
func Unpack(ind string) error { var Abs = current.Abs
packed = true var AbsPath = current.AbsPath
packMU.Lock() var Create = current.Create
defer packMU.Unlock() var Current = current.Current
b, err := hex.DecodeString(ind) var Info = current.Info
if err != nil { var MkdirAll = current.MkdirAll
log.Fatal("hex.DecodeString", err) var Open = current.Open
return err var Parse = current.Parse
} var Remove = current.Remove
var RemoveAll = current.RemoveAll
gz, err := gzip.NewReader(bytes.NewReader(b)) var Stat = current.Stat
if err != nil { var Walk = current.Walk
log.Fatal("gzip.NewReader", err)
return err
}
defer gz.Close()
var jay jason
if err := json.NewDecoder(gz).Decode(&jay); err != nil {
return err
}
jay.Files.Range(func(key Path, value *File) bool {
filesCache.Store(key, value)
return true
})
jay.Infos.Range(func(key string, value here.Info) bool {
infosCache.Store(key, value)
return true
})
jay.Paths.Range(func(key string, value Path) bool {
pathsCache.Store(key, value)
return true
})
currentInfo = jay.CurrentInfo
return nil
}
func Pack(out io.Writer, paths []Path) error {
packMU.RLock()
defer packMU.RUnlock()
bb := &bytes.Buffer{}
gz := gzip.NewWriter(bb)
defer gz.Close()
for _, p := range paths {
f, err := Open(p.String())
if err != nil {
return err
}
fi, err := f.Stat()
if err != nil {
return err
}
if fi.IsDir() {
filesCache.Store(p, f)
f.Close()
continue
}
filesCache.Store(p, f)
f.Close()
}
jay := jason{
Files: filesCache,
Infos: infosCache,
Paths: pathsCache,
CurrentInfo: currentInfo,
}
if err := json.NewEncoder(gz).Encode(jay); err != nil {
return err
}
if err := gz.Close(); err != nil {
return err
}
s := hex.EncodeToString(bb.Bytes())
_, err := fmt.Fprint(out, s)
return err
}

View File

@ -1,75 +0,0 @@
package pkger
import (
"io"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
const curPkg = "github.com/markbates/pkger"
func Test_ReadFile(t *testing.T) {
r := require.New(t)
b, err := ReadFile("/LICENSE")
r.NoError(err)
r.Contains(string(b), "MIT")
}
func createFile(p string, body ...string) (*File, error) {
if len(body) == 0 {
body = append(body, radio)
}
f, err := Create(p)
if err != nil {
return nil, err
}
_, err = io.Copy(f, strings.NewReader(strings.Join(body, "\n\n")))
if err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, err
}
return f, nil
}
const radio = `I was tuning in the shine on the late night dial
Doing anything my radio advised
With every one of those late night stations
Playing songs bringing tears to my eyes
I was seriously thinking about hiding the receiver
When the switch broke 'cause it's old
They're saying things that I can hardly believe
They really think we're getting out of control
Radio is a sound salvation
Radio is cleaning up the nation
They say you better listen to the voice of reason
But they don't give you any choice 'cause they think that it's treason
So you had better do as you are told
You better listen to the radio
I wanna bite the hand that feeds me
I wanna bite that hand so badly
I want to make them wish they'd never seen me
Some of my friends sit around every evening
And they worry about the times ahead
But everybody else is overwhelmed by indifference
And the promise of an early bed
You either shut up or get cut up; they don't wanna hear about it
It's only inches on the reel-to-reel
And the radio is in the hands of such a lot of fools
Tryin' to anesthetize the way that you feel
Radio is a sound salvation
Radio is cleaning up the nation
They say you better listen to the voice of reason
But they don't give you any choice 'cause they think that it's treason
So you had better do as you are told
You better listen to the radio
Wonderful radio
Marvelous radio
Wonderful radio
Radio, radio`

22
pkging/file.go Normal file
View File

@ -0,0 +1,22 @@
package pkging
import (
"net/http"
"os"
"github.com/markbates/pkger/here"
)
type File interface {
Close() error
Abs() (string, error)
Info() here.Info
Name() string
Open(name string) (http.File, error)
Path() Path
Read(p []byte) (int, error)
Readdir(count int) ([]os.FileInfo, error)
Seek(offset int64, whence int) (int64, error)
Stat() (os.FileInfo, error)
Write(b []byte) (int, error)
}

96
pkging/info.go Normal file
View File

@ -0,0 +1,96 @@
package pkging
import (
"encoding/json"
"os"
"strings"
"time"
)
type Details struct {
Name string `json:"name"`
Size int64 `json:"size"`
Mode os.FileMode `json:"mode"`
ModTime ModTime `json:"mod_time"`
IsDir bool `json:"is_dir"`
Sys interface{} `json:"sys"`
}
type FileInfo struct {
Details `json:"details"`
}
func (f *FileInfo) String() string {
b, _ := json.MarshalIndent(f, "", " ")
return string(b)
}
func (f *FileInfo) Name() string {
return f.Details.Name
}
func (f *FileInfo) Size() int64 {
return f.Details.Size
}
func (f *FileInfo) Mode() os.FileMode {
return f.Details.Mode
}
func (f *FileInfo) ModTime() time.Time {
return time.Time(f.Details.ModTime)
}
func (f *FileInfo) IsDir() bool {
return f.Details.IsDir
}
func (f *FileInfo) Sys() interface{} {
return f.Details.Sys
}
var _ os.FileInfo = &FileInfo{}
func NewFileInfo(info os.FileInfo) *FileInfo {
fi := &FileInfo{
Details: Details{
Name: cleanName(info.Name()),
Size: info.Size(),
Mode: info.Mode(),
ModTime: ModTime(info.ModTime()),
IsDir: info.IsDir(),
Sys: info.Sys(),
},
}
return fi
}
func WithName(name string, info os.FileInfo) *FileInfo {
s := cleanName(name)
if !strings.HasPrefix(s, "/") {
s = "/" + s
}
fo := NewFileInfo(info)
fo.Details.Name = cleanName(name)
return fo
}
func WithRelName(name string, info os.FileInfo) *FileInfo {
s := cleanName(name)
if !strings.HasPrefix(s, "/") {
s = "/" + s
}
fo := NewFileInfo(info)
fo.Details.Name = cleanName(name)
return fo
}
func cleanName(s string) string {
if strings.Contains(s, "\\") {
s = strings.Replace(s, "\\", "/", -1)
}
return s
}

264
pkging/mem/file.go Normal file
View File

@ -0,0 +1,264 @@
package mem
import (
"bytes"
"fmt"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/pkging"
)
const timeFmt = time.RFC3339Nano
var _ pkging.File = &File{}
type File struct {
info *pkging.FileInfo
her here.Info
path pkging.Path
data []byte
parent pkging.Path
writer *bytes.Buffer
reader io.Reader
pkging pkging.Pkger
}
func (f *File) Seek(ofpkginget int64, whence int) (int64, error) {
if sk, ok := f.reader.(io.Seeker); ok {
return sk.Seek(ofpkginget, whence)
}
return 0, nil
}
func (f *File) Close() error {
defer func() {
f.reader = nil
f.writer = nil
}()
if f.reader != nil {
if c, ok := f.reader.(io.Closer); ok {
if err := c.Close(); err != nil {
return err
}
}
}
if f.writer == nil {
return nil
}
f.data = f.writer.Bytes()
fi := f.info
fi.Details.Size = int64(len(f.data))
fi.Details.ModTime = pkging.ModTime(time.Now())
f.info = fi
return nil
}
func (f *File) Read(p []byte) (int, error) {
if len(f.data) > 0 && f.reader == nil {
f.reader = bytes.NewReader(f.data)
}
if f.reader != nil {
return f.reader.Read(p)
}
return 0, fmt.Errorf("unable to read %s", f.Name())
}
func (f *File) Write(b []byte) (int, error) {
if f.writer == nil {
f.writer = &bytes.Buffer{}
}
i, err := f.writer.Write(b)
return i, err
}
func (f File) Info() here.Info {
return f.her
}
func (f File) Stat() (os.FileInfo, error) {
if f.info == nil {
return nil, os.ErrNotExist
}
return f.info, nil
}
func (f File) Name() string {
return f.info.Name()
}
func (f File) Abs() (string, error) {
return f.pkging.AbsPath(f.Path())
}
func (f File) Path() pkging.Path {
return f.path
}
func (f File) String() string {
return f.Path().String()
}
// func (f File) Format(st fmt.State, verb rune) {
// switch verb {
// case 'v':
// if st.Flag('+') {
// b, err := json.MarshalIndent(f, "", " ")
// if err != nil {
// fmt.Fprint(os.Stderr, err)
// return
// }
// fmt.Fprint(st, string(b))
// return
// }
// fmt.Fprint(st, f.String())
// case 'q':
// fmt.Fprintf(st, "%q", f.String())
// default:
// fmt.Fprint(st, f.String())
// }
// }
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
var infos []os.FileInfo
root := f.Path().String()
err := f.pkging.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if count > 0 && len(infos) == count {
return io.EOF
}
pt, err := f.pkging.Parse(path)
if err != nil {
return err
}
if pt.Name == f.parent.Name {
return nil
}
info = pkging.WithRelName(strings.TrimPrefix(info.Name(), f.parent.Name), info)
infos = append(infos, info)
if info.IsDir() && path != root {
return filepath.SkipDir
}
return nil
})
if err != nil {
if _, ok := err.(*os.PathError); ok {
return infos, nil
}
if err != io.EOF {
return nil, err
}
}
return infos, nil
}
func (f *File) Open(name string) (http.File, error) {
pt, err := f.pkging.Parse(name)
if err != nil {
return nil, err
}
if pt == f.path {
return f, nil
}
pt.Name = path.Join(f.Path().Name, pt.Name)
di, err := f.pkging.Open(pt.String())
if err != nil {
return nil, err
}
fi, err := di.Stat()
if err != nil {
return nil, err
}
if fi.IsDir() {
d2 := &File{
info: pkging.NewFileInfo(fi),
her: di.Info(),
path: pt,
parent: f.path,
pkging: f.pkging,
}
di = d2
}
return di, nil
}
// func (f File) MarshalJSON() ([]byte, error) {
// m := map[string]interface{}{
// "info": f.info,
// "her": f.her,
// "path": f.path,
// "data": f.data,
// "parent": f.parent,
// }
// return json.Marshal(m)
// }
// func (f *File) UnmarshalJSON(b []byte) error {
// m := map[string]json.RawMessage{}
// if err := json.Unmarshal(b, &m); err != nil {
// return err
// }
//
// info, ok := m["info"]
// if !ok {
// return fmt.Errorf("missing info")
// }
//
// f.info = &pkging.FileInfo{}
// if err := json.Unmarshal(info, f.info); err != nil {
// return err
// }
//
// her, ok := m["her"]
// if !ok {
// return fmt.Errorf("missing her")
// }
// if err := json.Unmarshal(her, &f.her); err != nil {
// return err
// }
//
// path, ok := m["path"]
// if !ok {
// return fmt.Errorf("missing path")
// }
// if err := json.Unmarshal(path, &f.path); err != nil {
// return err
// }
//
// parent, ok := m["parent"]
// if !ok {
// return fmt.Errorf("missing parent")
// }
// if err := json.Unmarshal(parent, &f.parent); err != nil {
// return err
// }
//
// if err := json.Unmarshal(m["data"], &f.data); err != nil {
// return err
// }
//
// return nil
// }

52
pkging/mem/file_test.go Normal file
View File

@ -0,0 +1,52 @@
package mem
import (
"io/ioutil"
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_File_Seek(t *testing.T) {
r := require.New(t)
info, err := here.Current()
r.NoError(err)
pkg, err := New(info)
r.NoError(err)
f, err := pkg.Create(":/wilco.band")
r.NoError(err)
data := []byte("a shot in the arm")
f.Write(data)
r.NoError(f.Close())
f, err = pkg.Open(":/wilco.band")
r.NoError(err)
b, err := ioutil.ReadAll(f)
r.NoError(err)
r.Equal(data, b)
_, err = f.Seek(0, 0)
r.NoError(err)
b, err = ioutil.ReadAll(f)
r.NoError(err)
r.Equal(data, b)
b, err = ioutil.ReadAll(f)
r.NoError(err)
r.NotEqual(data, b)
_, err = f.Seek(10, 0)
r.NoError(err)
b, err = ioutil.ReadAll(f)
r.NoError(err)
r.NotEqual(data, b)
r.Equal([]byte("the arm"), b)
}

272
pkging/mem/mem.go Normal file
View File

@ -0,0 +1,272 @@
package mem
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/internal/maps"
"github.com/markbates/pkger/pkging"
)
var _ pkging.Pkger = &Pkger{}
func WithInfo(fx *Pkger, infos ...here.Info) {
for _, info := range infos {
fx.infos.Store(info.ImportPath, info)
}
}
func New(info here.Info) (*Pkger, error) {
f := &Pkger{
infos: &maps.Infos{},
paths: &maps.Paths{
Current: info,
},
files: &maps.Files{},
current: info,
}
f.infos.Store(info.ImportPath, info)
f.MkdirAll("/", 0755)
return f, nil
}
type Pkger struct {
infos *maps.Infos
paths *maps.Paths
files *maps.Files
current here.Info
}
func (f *Pkger) Abs(p string) (string, error) {
pt, err := f.Parse(p)
if err != nil {
return "", err
}
return f.AbsPath(pt)
}
func (f *Pkger) AbsPath(pt pkging.Path) (string, error) {
return pt.String(), nil
}
func (f *Pkger) Current() (here.Info, error) {
return f.current, nil
}
func (f *Pkger) Info(p string) (here.Info, error) {
info, ok := f.infos.Load(p)
if !ok {
return info, fmt.Errorf("no such package %q", p)
}
return info, nil
}
func (f *Pkger) Parse(p string) (pkging.Path, error) {
return f.paths.Parse(p)
}
func (fx *Pkger) Remove(name string) error {
pt, err := fx.Parse(name)
if err != nil {
return err
}
if _, ok := fx.files.Load(pt); !ok {
return &os.PathError{"remove", pt.String(), fmt.Errorf("no such file or directory")}
}
fx.files.Delete(pt)
return nil
}
func (fx *Pkger) RemoveAll(name string) error {
pt, err := fx.Parse(name)
if err != nil {
return err
}
fx.files.Range(func(key pkging.Path, file pkging.File) bool {
if strings.HasPrefix(key.Name, pt.Name) {
fx.files.Delete(key)
}
return true
})
fx.paths.Range(func(key string, value pkging.Path) bool {
if strings.HasPrefix(key, pt.Name) {
fx.paths.Delete(key)
}
return true
})
return nil
}
func (fx *Pkger) Create(name string) (pkging.File, error) {
fx.MkdirAll("/", 0755)
pt, err := fx.Parse(name)
if err != nil {
return nil, err
}
her, err := fx.Info(pt.Pkg)
if err != nil {
return nil, err
}
dir := filepath.Dir(pt.Name)
if _, err := fx.Stat(dir); err != nil {
return nil, err
}
f := &File{
path: pt,
her: her,
info: &pkging.FileInfo{
Details: pkging.Details{
Name: pt.Name,
Mode: 0644,
ModTime: pkging.ModTime(time.Now()),
},
},
pkging: fx,
}
fx.files.Store(pt, f)
return f, nil
}
func (fx *Pkger) MkdirAll(p string, perm os.FileMode) error {
path, err := fx.Parse(p)
if err != nil {
return err
}
root := path.Name
cur, err := fx.Current()
if err != nil {
return err
}
for root != "" {
pt := pkging.Path{
Pkg: path.Pkg,
Name: root,
}
if _, ok := fx.files.Load(pt); ok {
root = filepath.Dir(root)
if root == "/" || root == "\\" {
break
}
continue
}
f := &File{
pkging: fx,
path: pt,
her: cur,
info: &pkging.FileInfo{
Details: pkging.Details{
Name: pt.Name,
Mode: perm,
ModTime: pkging.ModTime(time.Now()),
},
},
}
if err != nil {
return err
}
f.info.Details.IsDir = true
f.info.Details.Mode = perm
if err := f.Close(); err != nil {
return err
}
fx.files.Store(pt, f)
root = filepath.Dir(root)
}
return nil
}
func (fx *Pkger) Open(name string) (pkging.File, error) {
pt, err := fx.Parse(name)
if err != nil {
return nil, err
}
fl, ok := fx.files.Load(pt)
if !ok {
return nil, fmt.Errorf("could not open %s", name)
}
f, ok := fl.(*File)
if !ok {
return nil, fmt.Errorf("could not open %s", name)
}
nf := &File{
pkging: fx,
info: pkging.WithName(f.info.Name(), f.info),
path: f.path,
data: f.data,
her: f.her,
}
return nf, nil
}
func (fx *Pkger) Stat(name string) (os.FileInfo, error) {
pt, err := fx.Parse(name)
if err != nil {
return nil, err
}
f, ok := fx.files.Load(pt)
if ok {
return f.Stat()
}
return nil, fmt.Errorf("could not stat %s", pt)
}
func (f *Pkger) Walk(p string, wf filepath.WalkFunc) error {
keys := f.files.Keys()
pt, err := f.Parse(p)
if err != nil {
return err
}
skip := "!"
for _, k := range keys {
if !strings.HasPrefix(k.Name, pt.Name) {
continue
}
if strings.HasPrefix(k.Name, skip) {
continue
}
fl, ok := f.files.Load(k)
if !ok {
return fmt.Errorf("could not find %s", k)
}
fi, err := fl.Stat()
if err != nil {
return err
}
fi = pkging.WithName(strings.TrimPrefix(k.Name, pt.Name), fi)
err = wf(k.String(), fi, nil)
if err == filepath.SkipDir {
skip = k.Name
continue
}
if err != nil {
return err
}
}
return nil
}

30
pkging/mem/mem_test.go Normal file
View File

@ -0,0 +1,30 @@
package mem
import (
"testing"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/pkging"
"github.com/markbates/pkger/pkging/pkgtest"
)
func Test_Pkger(t *testing.T) {
suite, err := pkgtest.NewSuite("memos", func() (pkging.Pkger, error) {
info, err := here.Current()
if err != nil {
return nil, err
}
pkg, err := New(info)
if err != nil {
return nil, err
}
return pkg, nil
})
if err != nil {
t.Fatal(err)
}
suite.Test(t)
}

24
pkging/mod_time.go Normal file
View File

@ -0,0 +1,24 @@
package pkging
import (
"encoding/json"
"time"
)
const timeFmt = time.RFC3339Nano
type ModTime time.Time
func (m ModTime) MarshalJSON() ([]byte, error) {
t := time.Time(m)
return json.Marshal(t.Format(timeFmt))
}
func (m *ModTime) UnmarshalJSON(b []byte) error {
t := time.Time{}
if err := json.Unmarshal(b, &t); err != nil {
return err
}
(*m) = ModTime(t)
return nil
}

39
pkging/path.go Normal file
View File

@ -0,0 +1,39 @@
package pkging
import (
"encoding/json"
"fmt"
"os"
)
type Path struct {
Pkg string `json:"pkg"`
Name string `json:"name"`
}
func (p Path) String() string {
if p.Name == "" {
p.Name = "/"
}
return fmt.Sprintf("%s:%s", p.Pkg, p.Name)
}
func (p Path) Format(st fmt.State, verb rune) {
switch verb {
case 'v':
if st.Flag('+') {
b, err := json.MarshalIndent(p, "", " ")
if err != nil {
fmt.Fprint(os.Stderr, err)
return
}
fmt.Fprint(st, string(b))
return
}
fmt.Fprint(st, p.String())
case 'q':
fmt.Fprintf(st, "%q", p.String())
default:
fmt.Fprint(st, p.String())
}
}

38
pkging/pkger.go Normal file
View File

@ -0,0 +1,38 @@
package pkging
import (
"os"
"path/filepath"
"github.com/markbates/pkger/here"
)
type Pkger interface {
Parse(p string) (Path, error)
Abs(string) (string, error)
AbsPath(Path) (string, error)
Current() (here.Info, error)
Info(p string) (here.Info, error)
// Create creates the named file with mode 0666 (before umask) - It's actually 0644, truncating it if it already exists. If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR.
Create(name string) (File, error)
// MkdirAll creates a directory named path, along with any necessary parents, and returns nil, or else returns an error. The permission bits perm (before umask) are used for all directories that MkdirAll creates. If path is already a directory, MkdirAll does nothing and returns nil.
MkdirAll(p string, perm os.FileMode) error
// Open opens the named file for reading. If successful, methods on the returned file can be used for reading; the associated file descriptor has mode O_RDONLY.
Open(name string) (File, error)
// Stat returns a FileInfo describing the named file.
Stat(name string) (os.FileInfo, error)
// Walk walks the file tree rooted at root, calling walkFn for each file or directory in the tree, including root. All errors that arise visiting files and directories are filtered by walkFn. The files are walked in lexical order, which makes the output deterministic but means that for very large directories Walk can be inefficient. Walk does not follow symbolic links. - That is from the standard library. I know. Their grammar teachers can not be happy with them right now.
Walk(p string, wf filepath.WalkFunc) error
// Remove removes the named file or (empty) directory.
Remove(name string) error
// RemoveAll removes path and any children it contains. It removes everything it can but returns the first error it encounters. If the path does not exist, RemoveAll returns nil (no error).
RemoveAll(path string) error
}

65
pkging/pkgtest/file.go Normal file
View File

@ -0,0 +1,65 @@
package pkgtest
import (
"path/filepath"
"testing"
"github.com/markbates/pkger/pkging/pkgutil"
"github.com/stretchr/testify/require"
)
func (s Suite) Test_File_Info(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
r.NoError(pkg.RemoveAll(mould))
r.NoError(pkg.MkdirAll(filepath.Dir(tt.in), 0755))
err := pkgutil.WriteFile(pkg, tt.in, []byte(mould), 0644)
r.NoError(err)
f, err := pkg.Open(tt.in)
r.NoError(err)
r.Equal(mould, f.Name())
r.Equal(cur.ImportPath, f.Info().ImportPath)
r.NoError(f.Close())
})
}
}
// func (s Suite) Test_File_Read(t *testing.T) {
// panic("not implemented")
// }
//
// func (s Suite) Test_File_Readdir(t *testing.T) {
// panic("not implemented")
// }
//
// func (s Suite) Test_File_Seek(t *testing.T) {
// panic("not implemented")
// }
//
// func (s Suite) Test_File_Stat(t *testing.T) {
// panic("not implemented")
// }
//
// func (s Suite) Test_File_Write(t *testing.T) {
// panic("not implemented")
// }

221
pkging/pkgtest/http.go Normal file
View File

@ -0,0 +1,221 @@
package pkgtest
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"path/filepath"
"testing"
"github.com/markbates/pkger/pkging"
"github.com/markbates/pkger/pkging/pkgutil"
"github.com/stretchr/testify/require"
)
// ├── main.go
// ├── public
// │   ├── images
// │   │   ├── mark.png
// │   └── index.html
// └── templates
// ├── a.txt
// └── b
// └── b.txt
func (s Suite) LoadFolder(pkg pkging.Pkger) error {
files := []string{
"/main.go",
"/public/images/mark.png",
"/public/index.html",
"/templates/a.txt",
"/templates/b/b.txt",
}
for _, f := range files {
if err := pkg.MkdirAll(filepath.Dir(f), 0755); err != nil {
return err
}
if err := pkgutil.WriteFile(pkg, f, []byte("!"+f), 0644); err != nil {
return err
}
}
return nil
}
func (s Suite) Test_HTTP_Dir(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
req string
exp string
}{
{in: "/", req: "/", exp: `>public/</a`},
{in: ":" + "/", req: "/", exp: `>public/</a`},
{in: ip + ":" + "/", req: "/", exp: `>public/</a`},
}
for _, tt := range table {
s.Run(t, tt.in+tt.req, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
r.NoError(s.LoadFolder(pkg))
dir, err := pkg.Open(tt.in)
r.NoError(err)
defer dir.Close()
ts := httptest.NewServer(http.FileServer(dir))
defer ts.Close()
res, err := http.Get(ts.URL + tt.req)
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
r.Contains(string(b), tt.exp)
r.NotContains(string(b), "mark.png")
})
}
}
func (s Suite) Test_HTTP_Dir_IndexHTML(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
req string
}{
{in: "/public", req: "/"},
{in: ":" + "/public", req: "/"},
{in: ip + ":" + "/public", req: "/"},
}
exp := "index.html"
for _, tt := range table {
s.Run(t, tt.in+exp, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
r.NoError(s.LoadFolder(pkg))
dir, err := pkg.Open(tt.in)
r.NoError(err)
defer dir.Close()
ts := httptest.NewServer(http.FileServer(dir))
defer ts.Close()
res, err := http.Get(ts.URL + tt.req)
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
body := string(b)
r.Contains(body, exp)
r.NotContains(body, "mark.png")
})
}
}
func (s Suite) Test_HTTP_File(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: "/public"},
{in: ":" + "/public"},
{in: ip + ":" + "/public"},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
r.NoError(s.LoadFolder(pkg))
dir, err := pkg.Open(tt.in)
r.NoError(err)
defer dir.Close()
ts := httptest.NewServer(http.FileServer(dir))
defer ts.Close()
res, err := http.Get(ts.URL + "/images/mark.png")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
body := string(b)
r.Contains(body, `!/public/images/mark.png`)
})
}
}
//
// func (s Suite) Test_HTTP_Dir_Memory_StripPrefix(t *testing.T) {
// r := require.New(t)
//
// fs := NewPkger()
// r.NoError(Folder.Create(fs))
//
// dir, err := fs.Open("/public")
// r.NoError(err)
// defer dir.Close()
//
// ts := httptest.NewServer(http.StripPrefix("/assets/", http.FileServer(dir)))
// defer ts.Close()
//
// res, err := http.Get(ts.URL + "/assets/images/mark.png")
// r.NoError(err)
// r.Equal(200, res.StatusCode)
//
// b, _ := ioutil.ReadAll(res.Body)
// // r.NoError(err)
// r.Contains(string(b), "!/public/images/mark.png")
//
// res, err = http.Get(ts.URL + "/assets/images/")
// r.NoError(err)
// r.Equal(200, res.StatusCode)
//
// b, _ = ioutil.ReadAll(res.Body)
// // r.NoError(err)
// r.Contains(string(b), `<a href="/mark.png">/mark.png</a>`)
// r.NotContains(string(b), `/public`)
// r.NotContains(string(b), `/images`)
// r.NotContains(string(b), `/go.mod`)
// }

603
pkging/pkgtest/suite.go Normal file
View File

@ -0,0 +1,603 @@
package pkgtest
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/markbates/pkger/pkging"
"github.com/markbates/pkger/pkging/pkgutil"
"github.com/stretchr/testify/require"
)
const mould = "/easy/listening/sugar.file"
const hart = "/easy/listening/grant.hart"
const husker = "github.com/husker/du"
type Suite struct {
Name string
gen func() (pkging.Pkger, error)
}
func (s Suite) Make() (pkging.Pkger, error) {
if s.gen == nil {
return nil, fmt.Errorf("missing generator function")
}
return s.gen()
}
func NewSuite(name string, fn func() (pkging.Pkger, error)) (Suite, error) {
suite := Suite{
Name: name,
gen: fn,
}
return suite, nil
}
func (s Suite) Test(t *testing.T) {
rv := reflect.ValueOf(s)
rt := rv.Type()
if rt.NumMethod() == 0 {
t.Fatalf("something went wrong wrong with %s", s.Name)
}
for i := 0; i < rt.NumMethod(); i++ {
m := rt.Method(i)
if !strings.HasPrefix(m.Name, "Test_") {
continue
}
s.sub(t, m)
}
}
// func (s Suite) clone() (Suite, error) {
// if ns, ok := s.Pkger.(Newable); ok {
// pkg, err := ns.New()
// if err != nil {
// return s, err
// }
// s, err = NewSuite(pkg)
// if err != nil {
// return s, err
// }
// }
// if ns, ok := s.Pkger.(WithRootable); ok {
// dir, err := ioutil.TempDir("")
// if err != nil {
// return s, err
// }
// // defer opkg.RemoveAll(dir)
//
// pkg, err := ns.WithRoot(dir)
// if err != nil {
// return s, err
// }
// s, err = NewSuite(pkg)
// if err != nil {
// return s, err
// }
// }
// return s, nil
// }
func (s Suite) Run(t *testing.T, name string, fn func(t *testing.T)) {
t.Run(name, func(st *testing.T) {
fn(st)
})
}
func (s Suite) sub(t *testing.T, m reflect.Method) {
name := fmt.Sprintf("%s/%s", s.Name, m.Name)
// s, err := s.clone()
// if err != nil {
// t.Fatal(err)
// }
s.Run(t, name, func(st *testing.T) {
m.Func.Call([]reflect.Value{
reflect.ValueOf(s),
reflect.ValueOf(st),
})
})
}
func (s Suite) Test_Create(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.NoError(pkg.MkdirAll(filepath.Dir(pt.Name), 0755))
f, err := pkg.Create(pt.Name)
r.NoError(err)
r.Equal(pt.Name, f.Name())
fi, err := f.Stat()
r.NoError(err)
r.NoError(f.Close())
r.Equal(pt.Name, fi.Name())
r.NotZero(fi.ModTime())
r.NoError(pkg.RemoveAll(pt.String()))
})
}
}
func (s Suite) Test_Create_No_MkdirAll(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
{in: filepath.Dir(mould)},
{in: ":" + filepath.Dir(mould)},
{in: ip + ":" + filepath.Dir(mould)},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
_, err = pkg.Create(pt.Name)
r.Error(err)
})
}
}
func (s Suite) Test_Current(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
info, err := pkg.Current()
r.NoError(err)
r.NotZero(info)
}
func (s Suite) Test_Info(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
info, err := pkg.Info(cur.ImportPath)
r.NoError(err)
r.NotZero(info)
}
func (s Suite) Test_MkdirAll(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
{in: filepath.Dir(mould)},
{in: ":" + filepath.Dir(mould)},
{in: ip + ":" + filepath.Dir(mould)},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
dir := filepath.Dir(pt.Name)
r.NoError(pkg.MkdirAll(dir, 0755))
fi, err := pkg.Stat(dir)
r.NoError(err)
if runtime.GOOS == "windows" {
dir = strings.Replace(dir, "\\", "/", -1)
}
r.Equal(dir, fi.Name())
r.NotZero(fi.ModTime())
r.NoError(pkg.RemoveAll(pt.String()))
})
}
}
func (s Suite) Test_Open_File(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
{in: hart},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.NoError(pkg.RemoveAll(pt.String()))
r.NoError(pkg.MkdirAll(filepath.Dir(pt.Name), 0755))
body := "!" + pt.String()
pkgutil.WriteFile(pkg, tt.in, []byte(body), 0644)
f, err := pkg.Open(tt.in)
r.NoError(err)
r.Equal(pt.Name, f.Path().Name)
b, err := ioutil.ReadAll(f)
r.NoError(err)
r.Equal(body, string(b))
b, err = pkgutil.ReadFile(pkg, tt.in)
r.NoError(err)
r.Equal(body, string(b))
r.NoError(f.Close())
})
}
}
func (s Suite) Test_Parse(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
exp pkging.Path
}{
{in: mould, exp: pkging.Path{Pkg: ip, Name: mould}},
{in: filepath.Join(cur.Dir, mould), exp: pkging.Path{Pkg: ip, Name: mould}},
{in: ":" + mould, exp: pkging.Path{Pkg: ip, Name: mould}},
{in: ip + ":" + mould, exp: pkging.Path{Pkg: ip, Name: mould}},
{in: ip, exp: pkging.Path{Pkg: ip, Name: "/"}},
{in: ":", exp: pkging.Path{Pkg: ip, Name: "/"}},
{in: husker + ":" + mould, exp: pkging.Path{Pkg: husker, Name: mould}},
{in: husker, exp: pkging.Path{Pkg: husker, Name: "/"}},
{in: husker + ":", exp: pkging.Path{Pkg: husker, Name: "/"}},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.Equal(tt.exp, pt)
})
}
}
func (s Suite) Test_Stat_Error(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: hart},
{in: ":" + hart},
{in: ip},
{in: ip + ":"},
{in: ip + ":" + hart},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.NoError(pkg.RemoveAll(pt.String()))
_, err = pkg.Stat(tt.in)
r.Error(err)
})
}
}
func (s Suite) Test_Stat_Dir(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
dir := filepath.Dir(mould)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: ip},
{in: dir},
{in: ":" + dir},
{in: ip + ":" + dir},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.NoError(pkg.RemoveAll(pt.String()))
r.NoError(pkg.MkdirAll(pt.Name, 0755))
info, err := pkg.Stat(tt.in)
r.NoError(err)
r.Equal(pt.Name, info.Name())
})
}
}
func (s Suite) Test_Stat_File(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
{in: hart},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.NoError(pkg.RemoveAll(pt.String()))
r.NoError(pkg.MkdirAll(filepath.Dir(pt.Name), 0755))
f, err := pkg.Create(tt.in)
r.NoError(err)
_, err = io.Copy(f, strings.NewReader("!"+pt.String()))
r.NoError(err)
r.NoError(f.Close())
info, err := pkg.Stat(tt.in)
r.NoError(err)
r.Equal(pt.Name, info.Name())
})
}
}
func (s Suite) Test_Walk(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
r.NoError(s.LoadFolder(pkg))
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: ip},
{in: "/"},
{in: ":/"},
{in: ip + ":/"},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
var act []string
err := pkg.Walk(tt.in, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
act = append(act, path)
return nil
})
r.NoError(err)
exp := []string{
"github.com/markbates/pkger:/",
"github.com/markbates/pkger:/main.go",
"github.com/markbates/pkger:/public",
"github.com/markbates/pkger:/public/images",
"github.com/markbates/pkger:/public/images/mark.png",
"github.com/markbates/pkger:/public/index.html",
"github.com/markbates/pkger:/templates",
"github.com/markbates/pkger:/templates/a.txt",
"github.com/markbates/pkger:/templates/b",
"github.com/markbates/pkger:/templates/b/b.txt",
}
r.Equal(exp, act)
})
}
}
func (s Suite) Test_Remove(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: "/public/images/mark.png"},
{in: ":/public/images/mark.png"},
{in: ip + ":/public/images/mark.png"},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
r.NoError(s.LoadFolder(pkg))
_, err = pkg.Stat(tt.in)
r.NoError(err)
r.NoError(pkg.Remove(tt.in))
_, err = pkg.Stat(tt.in)
r.Error(err)
r.Error(pkg.Remove("unknown"))
})
}
}
func (s Suite) Test_RemoveAll(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: "/public"},
{in: ":/public"},
{in: ip + ":/public"},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
r.NoError(s.LoadFolder(pkg))
_, err = pkg.Stat(tt.in)
r.NoError(err)
r.NoError(pkg.RemoveAll(tt.in))
_, err = pkg.Stat(tt.in)
r.Error(err)
})
}
}

58
pkging/pkgtest/util.go Normal file
View File

@ -0,0 +1,58 @@
package pkgtest
import (
"io"
"path/filepath"
"strings"
"testing"
"github.com/markbates/pkger/pkging/pkgutil"
"github.com/stretchr/testify/require"
)
func (s Suite) Test_Util_ReadFile(t *testing.T) {
r := require.New(t)
pkg, err := s.Make()
r.NoError(err)
cur, err := pkg.Current()
r.NoError(err)
ip := cur.ImportPath
table := []struct {
in string
}{
{in: mould},
{in: ":" + mould},
{in: ip + ":" + mould},
{in: hart},
}
for _, tt := range table {
s.Run(t, tt.in, func(st *testing.T) {
r := require.New(st)
pkg, err := s.Make()
r.NoError(err)
pt, err := pkg.Parse(tt.in)
r.NoError(err)
r.NoError(pkg.RemoveAll(pt.String()))
r.NoError(pkg.MkdirAll(filepath.Dir(pt.Name), 0755))
f, err := pkg.Create(tt.in)
r.NoError(err)
body := "!" + pt.String()
_, err = io.Copy(f, strings.NewReader(body))
r.NoError(err)
r.NoError(f.Close())
b, err := pkgutil.ReadFile(pkg, tt.in)
r.NoError(err)
r.Equal(body, string(b))
})
}
}

76
pkging/pkgutil/file.go Normal file
View File

@ -0,0 +1,76 @@
package pkgutil
import (
"io"
"io/ioutil"
"os"
"sort"
"github.com/markbates/pkger/pkging"
)
type Opener interface {
Open(name string) (pkging.File, error)
}
type Creator interface {
Create(name string) (pkging.File, error)
}
type OpenFiler interface {
OpenFile(name string, flag int, perm os.FileMode) (pkging.File, error)
}
// ReadDir reads the directory named by dirname and returns a list of directory entries sorted by filename.
func ReadDir(pkg Opener, dirname string) ([]os.FileInfo, error) {
f, err := pkg.Open(dirname)
if err != nil {
return nil, err
}
list, err := f.Readdir(-1)
f.Close()
if err != nil {
return nil, err
}
sort.Slice(list, func(i, j int) bool { return list[i].Name() < list[j].Name() })
return list, nil
}
// ReadFile reads the file named by filename and returns the contents. A successful call returns err == nil, not err == EOF. Because ReadFile reads the whole file, it does not treat an EOF from Read as an error to be reported.
func ReadFile(pkg Opener, s string) ([]byte, error) {
f, err := pkg.Open(s)
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}
// WriteFile writes data to a file named by filename. If the file does not exist, WriteFile creates it with permissions perm; otherwise WriteFile truncates it before writing.
func WriteFile(pkg Creator, filename string, data []byte, perm os.FileMode) error {
var f pkging.File
var err error
if of, ok := pkg.(OpenFiler); ok {
f, err = of.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
return err
}
}
if f == nil {
f, err = pkg.Create(filename)
if err != nil {
return err
}
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}

106
pkging/stdos/file.go Normal file
View File

@ -0,0 +1,106 @@
package stdos
import (
"net/http"
"os"
"path"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/pkging"
)
var _ pkging.File = &File{}
type File struct {
*os.File
info *pkging.FileInfo
her here.Info
path pkging.Path
pkging pkging.Pkger
}
func NewFile(fx pkging.Pkger, osf *os.File) (*File, error) {
name := osf.Name()
pt, err := fx.Parse(name)
if err != nil {
return nil, err
}
info, err := osf.Stat()
if err != nil {
return nil, err
}
f := &File{
File: osf,
path: pt,
pkging: fx,
}
f.info = pkging.WithName(pt.Name, info)
her, err := here.Package(pt.Pkg)
if err != nil {
return nil, err
}
f.her = her
return f, nil
}
func (f *File) Close() error {
return f.File.Close()
}
func (f *File) Abs() (string, error) {
return f.pkging.AbsPath(f.path)
}
func (f *File) Info() here.Info {
return f.her
}
func (f *File) Name() string {
return f.info.Name()
}
type HTTP struct {
pkging.File
osf *os.File
}
func (f *HTTP) Readdir(count int) ([]os.FileInfo, error) {
osinfos, err := f.osf.Readdir(count)
if err != nil {
return nil, err
}
infos := make([]os.FileInfo, len(osinfos))
for i, info := range osinfos {
infos[i] = pkging.WithName(info.Name(), info)
}
return infos, err
}
func (f *File) Open(name string) (http.File, error) {
fp := path.Join(f.Path().Name, name)
f2, err := f.pkging.Open(fp)
if err != nil {
return nil, err
}
return &HTTP{File: f2, osf: f.File}, nil
}
func (f *File) Path() pkging.Path {
return f.path
}
func (f *File) Stat() (os.FileInfo, error) {
if f.info != nil {
return f.info, nil
}
nf, err := NewFile(f.pkging, f.File)
if err != nil {
return nil, err
}
(*f) = *nf
return f.info, nil
}

29
pkging/stdos/file_test.go Normal file
View File

@ -0,0 +1,29 @@
package stdos
import (
"testing"
"github.com/stretchr/testify/require"
)
func Test_File_Stat_No_Info(t *testing.T) {
r := require.New(t)
pkg, err := New()
r.NoError(err)
f, err := pkg.Open(":/pkging/stdos/file_test.go")
r.NoError(err)
defer f.Close()
sf, ok := f.(*File)
r.True(ok)
oi := sf.info
sf.info = nil
info, err := sf.Stat()
r.NoError(err)
r.Equal(oi.Name(), info.Name())
// r.Equal("", f.Name())
}

172
pkging/stdos/stdos.go Normal file
View File

@ -0,0 +1,172 @@
package stdos
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/internal/maps"
"github.com/markbates/pkger/pkging"
)
var _ pkging.Pkger = &Pkger{}
type Pkger struct {
infos *maps.Infos
paths *maps.Paths
current here.Info
}
func (f *Pkger) Abs(p string) (string, error) {
pt, err := f.Parse(p)
if err != nil {
return "", err
}
return f.AbsPath(pt)
}
func (f *Pkger) AbsPath(pt pkging.Path) (string, error) {
if pt.Pkg == f.current.ImportPath {
return filepath.Join(f.current.Dir, pt.Name), nil
}
info, err := f.Info(pt.Pkg)
if err != nil {
return "", err
}
return filepath.Join(info.Dir, pt.Name), nil
}
func New() (*Pkger, error) {
info, err := here.Current()
if err != nil {
return nil, err
}
p := &Pkger{
infos: &maps.Infos{},
paths: &maps.Paths{
Current: info,
},
current: info,
}
p.infos.Store(info.ImportPath, info)
return p, nil
}
func (fx *Pkger) Create(name string) (pkging.File, error) {
name, err := fx.Abs(name)
if err != nil {
return nil, err
}
f, err := os.Create(name)
if err != nil {
return nil, err
}
return NewFile(fx, f)
}
func (f *Pkger) Current() (here.Info, error) {
return f.current, nil
}
func (f *Pkger) Info(p string) (here.Info, error) {
info, ok := f.infos.Load(p)
if ok {
return info, nil
}
info, err := here.Package(p)
if err != nil {
return info, err
}
f.infos.Store(p, info)
return info, nil
}
func (f *Pkger) MkdirAll(p string, perm os.FileMode) error {
p, err := f.Abs(p)
if err != nil {
return err
}
return os.MkdirAll(p, perm)
}
func (fx *Pkger) Open(name string) (pkging.File, error) {
name, err := fx.Abs(name)
if err != nil {
return nil, err
}
f, err := os.Open(name)
if err != nil {
return nil, err
}
return NewFile(fx, f)
}
func (f *Pkger) Parse(p string) (pkging.Path, error) {
return f.paths.Parse(p)
}
func (f *Pkger) Stat(name string) (os.FileInfo, error) {
pt, err := f.Parse(name)
if err != nil {
return nil, err
}
abs, err := f.AbsPath(pt)
if err != nil {
return nil, err
}
info, err := os.Stat(abs)
if err != nil {
return nil, err
}
info = pkging.WithName(pt.Name, pkging.NewFileInfo(info))
return info, nil
}
func (f *Pkger) Walk(p string, wf filepath.WalkFunc) error {
fp, err := f.Abs(p)
if err != nil {
return err
}
pt, err := f.Parse(p)
if err != nil {
return err
}
err = filepath.Walk(fp, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
path = strings.TrimPrefix(path, fp)
pt, err := f.Parse(fmt.Sprintf("%s:%s", pt.Pkg, path))
if err != nil {
return err
}
return wf(pt.String(), pkging.WithName(path, pkging.NewFileInfo(fi)), nil)
})
return err
}
func (fx *Pkger) Remove(name string) error {
name, err := fx.Abs(name)
if err != nil {
return err
}
return os.Remove(name)
}
func (fx *Pkger) RemoveAll(name string) error {
name, err := fx.Abs(name)
if err != nil {
return err
}
return os.RemoveAll(name)
}

View File

@ -0,0 +1,33 @@
package stdos
import (
"io/ioutil"
"testing"
"github.com/markbates/pkger/pkging"
"github.com/markbates/pkger/pkging/pkgtest"
)
func Test_Pkger(t *testing.T) {
suite, err := pkgtest.NewSuite("stdos", func() (pkging.Pkger, error) {
mypkging, err := New()
if err != nil {
return nil, err
}
dir, err := ioutil.TempDir("", "stdos")
if err != nil {
return nil, err
}
mypkging.current.Dir = dir
mypkging.paths.Current = mypkging.current
return mypkging, nil
})
if err != nil {
t.Fatal(err)
}
suite.Test(t)
}

14
stat.go
View File

@ -1,14 +0,0 @@
package pkger
import "github.com/markbates/pkger/here"
func Stat() (here.Info, error) {
var err error
curOnce.Do(func() {
if currentInfo.IsZero() {
currentInfo, err = here.Current()
}
})
return currentInfo, err
}

View File

@ -1,35 +0,0 @@
I was tuning in the shine on the late night dial
Doing anything my radio advised
With every one of those late night stations
Playing songs bringing tears to my eyes
I was seriously thinking about hiding the receiver
When the switch broke 'cause it's old
They're saying things that I can hardly believe
They really think we're getting out of control
Radio is a sound salvation
Radio is cleaning up the nation
They say you better listen to the voice of reason
But they don't give you any choice 'cause they think that it's treason
So you had better do as you are told
You better listen to the radio
I wanna bite the hand that feeds me
I wanna bite that hand so badly
I want to make them wish they'd never seen me
Some of my friends sit around every evening
And they worry about the times ahead
But everybody else is overwhelmed by indifference
And the promise of an early bed
You either shut up or get cut up; they don't wanna hear about it
It's only inches on the reel-to-reel
And the radio is in the hands of such a lot of fools
Tryin' to anesthetize the way that you feel
Radio is a sound salvation
Radio is cleaning up the nation
They say you better listen to the voice of reason
But they don't give you any choice 'cause they think that it's treason
So you had better do as you are told
You better listen to the radio
Wonderful radio
Marvelous radio
Wonderful radio
Radio, radio

View File

@ -1,4 +0,0 @@
package pkger
// Version of pkger
const Version = "v0.0.1"

70
walk.go
View File

@ -1,70 +0,0 @@
package pkger
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/markbates/pkger/here"
)
type WalkFunc func(Path, os.FileInfo) error
func Walk(p string, wf WalkFunc) error {
pt, err := Parse(p)
if err != nil {
return err
}
filesCache.Range(func(k Path, v *File) bool {
if k.Pkg != pt.Pkg {
return true
}
if !strings.HasPrefix(k.Name, pt.Name) {
return true
}
if err = wf(k, v.info); err != nil {
if err == filepath.SkipDir {
return true
}
return false
}
return true
})
if packed {
return nil
}
var info here.Info
if pt.Pkg == "." {
info, err = Stat()
if err != nil {
return err
}
pt.Pkg = info.ImportPath
}
if info.IsZero() {
info, err = Info(pt.Pkg)
if err != nil {
return fmt.Errorf("%s: %s", pt, err)
}
}
fp := filepath.Join(info.Dir, pt.Name)
err = filepath.Walk(fp, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
path = strings.TrimPrefix(path, info.Dir)
pt, err := Parse(fmt.Sprintf("%s:%s", pt.Pkg, path))
if err != nil {
return err
}
return wf(pt, WithName(path, NewFileInfo(fi)))
})
return err
}