laugh with you

This commit is contained in:
Mark Bates 2019-07-31 16:29:49 -04:00
parent 1d23c6b87b
commit d4e044edbd
10 changed files with 357 additions and 85 deletions

21
doc.go Normal file
View File

@ -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
View File

@ -21,9 +21,74 @@ type File struct {
path Path path Path
data []byte data []byte
index *index index *index
writer io.ReadWriter
Source io.ReadCloser 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) { func (f File) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{} m := map[string]interface{}{}
m["info"] = f.info m["info"] = f.info
@ -76,9 +141,7 @@ func (f *File) UnmarshalJSON(b []byte) error {
if !ok { if !ok {
return fmt.Errorf("missing index") return fmt.Errorf("missing index")
} }
f.index = &index{ f.index = newIndex()
Files: map[Path]*File{},
}
if err := json.Unmarshal(ind, f.index); err != nil { if err := json.Unmarshal(ind, f.index); err != nil {
return err return err
} }
@ -87,9 +150,7 @@ func (f *File) UnmarshalJSON(b []byte) error {
func (f *File) Open(name string) (http.File, error) { func (f *File) Open(name string) (http.File, error) {
if f.index == nil { if f.index == nil {
f.index = &index{ f.index = newIndex()
Files: map[Path]*File{},
}
} }
pt, err := Parse(name) pt, err := Parse(name)
if err != nil { if err != nil {
@ -148,16 +209,6 @@ func (f File) Stat() (os.FileInfo, error) {
return f.info, nil 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 { func (f File) Name() string {
return f.info.Name() return f.info.Name()
} }
@ -174,19 +225,6 @@ func (f File) String() string {
return string(b) 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. // 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. // 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.

View File

@ -1,7 +1,9 @@
package pkger package pkger
import ( import (
"io"
"io/ioutil" "io/ioutil"
"strings"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -31,3 +33,48 @@ func Test_File_Open_Dir(t *testing.T) {
r.NoError(f.Close()) 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())
}

View File

@ -48,3 +48,25 @@ func Test_HTTP_Dir(t *testing.T) {
r.NoError(f.Close()) 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())
}

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/gobuffalo/here" "github.com/gobuffalo/here"
"github.com/markbates/pkger/pkgs" "github.com/markbates/pkger/pkgs"
@ -16,6 +17,26 @@ type index struct {
Files map[Path]*File 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) { func (i index) MarshalJSON() ([]byte, error) {
m := map[string]interface{}{ m := map[string]interface{}{
"pkg": i.Pkg, "pkg": i.Pkg,
@ -94,9 +115,7 @@ func (i index) Open(pt Path) (*File, error) {
path: f.path, path: f.path,
data: f.data, data: f.data,
her: f.her, her: f.her,
index: &index{ index: newIndex(),
Files: map[Path]*File{},
},
}, nil }, nil
} }
@ -128,6 +147,35 @@ func (i index) openDisk(pt Path) (*File, error) {
return f, nil return f, nil
} }
var rootIndex = &index{ func (i index) Parse(p string) (Path, error) {
Files: map[Path]*File{}, 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()

63
index_test.go Normal file
View File

@ -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())
}

View File

@ -10,7 +10,7 @@ import (
) )
func main() { 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 { if err != nil {
log.Fatal("1", err) log.Fatal("1", err)
} }

21
path.go
View File

@ -2,7 +2,6 @@ package pkger
import ( import (
"fmt" "fmt"
"strings"
) )
type Path struct { type Path struct {
@ -21,23 +20,5 @@ func (p Path) String() string {
} }
func Parse(p string) (Path, error) { func Parse(p string) (Path, error) {
var pt Path return rootIndex.Parse(p)
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
} }

View File

@ -1,31 +1,6 @@
package pkger package pkger
import ( // Open opens the named file for reading.
"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()
}
func Open(p string) (*File, error) { func Open(p string) (*File, error) {
pt, err := Parse(p) pt, err := Parse(p)
if err != nil { if err != nil {
@ -33,3 +8,12 @@ func Open(p string) (*File, error) {
} }
return rootIndex.Open(pt) 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)
}

68
pkger_test.go Normal file
View File

@ -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`