mirror of https://github.com/markbates/pkger.git
laugh with you
This commit is contained in:
parent
1d23c6b87b
commit
d4e044edbd
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
This package is WIP. Please use/test/try and report issues, but be careful with production. OK?
|
||||
|
||||
Pkger is powered by the dark magic of Go Modules, so they're like, totally required.
|
||||
|
||||
With Go Modules pkger can resolve packages with accuracy. No more guessing and trying to
|
||||
figure out build paths, GOPATHS, etc... for this tired old lad.
|
||||
|
||||
With the module's path correctly resolved, it can serve as the "root" directory for that
|
||||
module, and all files in that module's directory are available.
|
||||
|
||||
Paths:
|
||||
* Paths should use UNIX style paths:
|
||||
/cmd/pkger/main.go
|
||||
* If unspecified the path's package is assumed to be the current module.
|
||||
* Packages can specified in at the beginning of a path with a `:` seperator.
|
||||
github.com/markbates/pkger:/cmd/pkger/main.go
|
||||
|
||||
"github.com/gobuffalo/buffalo:/go.mod" => $GOPATH/pkg/mod/github.com/gobuffalo/buffalo@v0.14.7/go.mod
|
||||
*/
|
||||
package pkger
|
96
file.go
96
file.go
|
@ -21,9 +21,74 @@ type File struct {
|
|||
path Path
|
||||
data []byte
|
||||
index *index
|
||||
writer io.ReadWriter
|
||||
Source io.ReadCloser
|
||||
}
|
||||
|
||||
func (f *File) Close() error {
|
||||
defer func() {
|
||||
f.Source = nil
|
||||
f.writer = nil
|
||||
}()
|
||||
if f.Source != nil {
|
||||
if c, ok := f.Source.(io.Closer); ok {
|
||||
if err := c.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if f.writer == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(f.writer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.data = b
|
||||
|
||||
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 && len(f.data) <= len(p) {
|
||||
return copy(p, f.data), io.EOF
|
||||
}
|
||||
|
||||
if len(f.data) > 0 {
|
||||
f.Source = ioutil.NopCloser(bytes.NewReader(f.data))
|
||||
}
|
||||
|
||||
if f.Source != nil {
|
||||
return f.Source.Read(p)
|
||||
}
|
||||
|
||||
of, err := f.her.Open(f.Path())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.Source = of
|
||||
return f.Source.Read(p)
|
||||
}
|
||||
|
||||
func (f *File) Write(b []byte) (int, error) {
|
||||
if f.writer == nil {
|
||||
f.writer = &bytes.Buffer{}
|
||||
}
|
||||
i, err := f.writer.Write(b)
|
||||
fmt.Println(f.Name(), i, err)
|
||||
return i, err
|
||||
}
|
||||
|
||||
func (f File) HereInfo() here.Info {
|
||||
return f.her
|
||||
}
|
||||
|
||||
func (f File) MarshalJSON() ([]byte, error) {
|
||||
m := map[string]interface{}{}
|
||||
m["info"] = f.info
|
||||
|
@ -76,9 +141,7 @@ func (f *File) UnmarshalJSON(b []byte) error {
|
|||
if !ok {
|
||||
return fmt.Errorf("missing index")
|
||||
}
|
||||
f.index = &index{
|
||||
Files: map[Path]*File{},
|
||||
}
|
||||
f.index = newIndex()
|
||||
if err := json.Unmarshal(ind, f.index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -87,9 +150,7 @@ func (f *File) UnmarshalJSON(b []byte) error {
|
|||
|
||||
func (f *File) Open(name string) (http.File, error) {
|
||||
if f.index == nil {
|
||||
f.index = &index{
|
||||
Files: map[Path]*File{},
|
||||
}
|
||||
f.index = newIndex()
|
||||
}
|
||||
pt, err := Parse(name)
|
||||
if err != nil {
|
||||
|
@ -148,16 +209,6 @@ func (f File) Stat() (os.FileInfo, error) {
|
|||
return f.info, nil
|
||||
}
|
||||
|
||||
func (f *File) Close() error {
|
||||
if f.Source == nil {
|
||||
return nil
|
||||
}
|
||||
if c, ok := f.Source.(io.Closer); ok {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f File) Name() string {
|
||||
return f.info.Name()
|
||||
}
|
||||
|
@ -174,19 +225,6 @@ func (f File) String() string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
func (f *File) Read(p []byte) (int, error) {
|
||||
if f.Source != nil {
|
||||
return f.Source.Read(p)
|
||||
}
|
||||
|
||||
of, err := f.her.Open(f.Path())
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
f.Source = of
|
||||
return f.Source.Read(p)
|
||||
}
|
||||
|
||||
// Readdir reads the contents of the directory associated with file and returns a slice of up to n FileInfo values, as would be returned by Lstat, in directory order. Subsequent calls on the same file will yield further FileInfos.
|
||||
//
|
||||
// If n > 0, Readdir returns at most n FileInfo structures. In this case, if Readdir returns an empty slice, it will return a non-nil error explaining why. At the end of a directory, the error is io.EOF.
|
||||
|
|
47
file_test.go
47
file_test.go
|
@ -1,7 +1,9 @@
|
|||
package pkger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -31,3 +33,48 @@ func Test_File_Open_Dir(t *testing.T) {
|
|||
|
||||
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)
|
||||
|
||||
i := newIndex()
|
||||
|
||||
f, err := i.Create(Path{
|
||||
Name: "/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())
|
||||
}
|
||||
|
|
22
http_test.go
22
http_test.go
|
@ -48,3 +48,25 @@ func Test_HTTP_Dir(t *testing.T) {
|
|||
|
||||
r.NoError(f.Close())
|
||||
}
|
||||
|
||||
func Test_HTTP_File_Memory(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
i := newIndex()
|
||||
f, err := createFile(i, "/cmd/pkger/main.go")
|
||||
|
||||
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), "I wanna bite the hand that feeds me")
|
||||
|
||||
r.NoError(f.Close())
|
||||
}
|
||||
|
|
66
index.go
66
index.go
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gobuffalo/here"
|
||||
"github.com/markbates/pkger/pkgs"
|
||||
|
@ -16,6 +17,26 @@ type index struct {
|
|||
Files map[Path]*File
|
||||
}
|
||||
|
||||
func (i index) Create(pt Path) (*File, error) {
|
||||
her, err := pkgs.Pkg(pt.Pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &File{
|
||||
path: pt,
|
||||
index: newIndex(),
|
||||
her: her,
|
||||
info: &FileInfo{
|
||||
name: pt.Name,
|
||||
mode: 0666,
|
||||
modTime: time.Now(),
|
||||
},
|
||||
}
|
||||
|
||||
i.Files[pt] = f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (i index) MarshalJSON() ([]byte, error) {
|
||||
m := map[string]interface{}{
|
||||
"pkg": i.Pkg,
|
||||
|
@ -90,13 +111,11 @@ func (i index) Open(pt Path) (*File, error) {
|
|||
return i.openDisk(pt)
|
||||
}
|
||||
return &File{
|
||||
info: f.info,
|
||||
path: f.path,
|
||||
data: f.data,
|
||||
her: f.her,
|
||||
index: &index{
|
||||
Files: map[Path]*File{},
|
||||
},
|
||||
info: f.info,
|
||||
path: f.path,
|
||||
data: f.data,
|
||||
her: f.her,
|
||||
index: newIndex(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -128,6 +147,35 @@ func (i index) openDisk(pt Path) (*File, error) {
|
|||
return f, nil
|
||||
}
|
||||
|
||||
var rootIndex = &index{
|
||||
Files: map[Path]*File{},
|
||||
func (i index) Parse(p string) (Path, error) {
|
||||
var pt Path
|
||||
res := strings.Split(p, ":")
|
||||
|
||||
if len(res) < 1 {
|
||||
return pt, fmt.Errorf("could not parse %q (%d)", res, len(res))
|
||||
}
|
||||
if len(res) == 1 {
|
||||
if strings.HasPrefix(res[0], "/") {
|
||||
pt.Name = res[0]
|
||||
} else {
|
||||
pt.Pkg = res[0]
|
||||
}
|
||||
} else {
|
||||
pt.Pkg = res[0]
|
||||
pt.Name = res[1]
|
||||
}
|
||||
pt.Name = strings.TrimPrefix(pt.Name, "/")
|
||||
pt.Pkg = strings.TrimPrefix(pt.Pkg, "/")
|
||||
if len(pt.Pkg) == 0 {
|
||||
pt.Pkg = i.Pkg
|
||||
}
|
||||
return pt, nil
|
||||
}
|
||||
|
||||
func newIndex() *index {
|
||||
return &index{
|
||||
Files: map[Path]*File{},
|
||||
}
|
||||
}
|
||||
|
||||
var rootIndex = newIndex()
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package pkger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_index_Create(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
i := newIndex()
|
||||
|
||||
f, err := i.Create(Path{
|
||||
Name: "/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 Test_index_Create_Write(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
i := newIndex()
|
||||
|
||||
f, err := i.Create(Path{
|
||||
Name: "/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())
|
||||
}
|
|
@ -10,7 +10,7 @@ import (
|
|||
)
|
||||
|
||||
func main() {
|
||||
f, err := pkger.Open("github.com/gobuffalo/buffalo:/server.go")
|
||||
f, err := pkger.Open("github.com/gobuffalo/buffalo:/go.mod")
|
||||
if err != nil {
|
||||
log.Fatal("1", err)
|
||||
}
|
||||
|
|
21
path.go
21
path.go
|
@ -2,7 +2,6 @@ package pkger
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Path struct {
|
||||
|
@ -21,23 +20,5 @@ func (p Path) String() string {
|
|||
}
|
||||
|
||||
func Parse(p string) (Path, error) {
|
||||
var pt Path
|
||||
res := strings.Split(p, ":")
|
||||
|
||||
if len(res) < 1 {
|
||||
return pt, fmt.Errorf("could not parse %q (%d)", res, len(res))
|
||||
}
|
||||
if len(res) == 1 {
|
||||
if strings.HasPrefix(res[0], "/") {
|
||||
pt.Name = res[0]
|
||||
} else {
|
||||
pt.Pkg = res[0]
|
||||
}
|
||||
} else {
|
||||
pt.Pkg = res[0]
|
||||
pt.Name = res[1]
|
||||
}
|
||||
pt.Name = strings.TrimPrefix(pt.Name, "/")
|
||||
pt.Pkg = strings.TrimPrefix(pt.Pkg, "/")
|
||||
return pt, nil
|
||||
return rootIndex.Parse(p)
|
||||
}
|
||||
|
|
36
pkger.go
36
pkger.go
|
@ -1,31 +1,6 @@
|
|||
package pkger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func modRoot() (string, error) {
|
||||
c := exec.Command("go", "env", "GOMOD")
|
||||
b, err := c.CombinedOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b = bytes.TrimSpace(b)
|
||||
if len(b) == 0 {
|
||||
return "", fmt.Errorf("the `go env GOMOD` was empty/modules are required")
|
||||
}
|
||||
|
||||
return filepath.Dir(string(b)), nil
|
||||
}
|
||||
|
||||
func Getwd() (string, error) {
|
||||
return modRoot()
|
||||
}
|
||||
|
||||
// Open opens the named file for reading.
|
||||
func Open(p string) (*File, error) {
|
||||
pt, err := Parse(p)
|
||||
if err != nil {
|
||||
|
@ -33,3 +8,12 @@ func Open(p string) (*File, error) {
|
|||
}
|
||||
return rootIndex.Open(pt)
|
||||
}
|
||||
|
||||
// Create creates the named file with mode 0666 (before umask), 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. If there is an error, it will be of type *PathError.
|
||||
func Create(p string) (*File, error) {
|
||||
pt, err := Parse(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rootIndex.Create(pt)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
package pkger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func createFile(i *index, p string, body ...string) (*File, error) {
|
||||
pt, err := i.Parse(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(body) == 0 {
|
||||
body = append(body, radio)
|
||||
}
|
||||
|
||||
f, err := i.Create(pt)
|
||||
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`
|
Loading…
Reference in New Issue