job jargon

This commit is contained in:
Mark Bates 2019-08-31 17:00:24 -04:00
parent 68f3a2a457
commit b42e0b7d71
31 changed files with 1902 additions and 3 deletions

1
.gitignore vendored
View File

@ -28,3 +28,4 @@ bin/*
gin-bin
.idea/
pkged.go
cover.out

View File

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

View File

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func Testg_Create(t *testing.T) {
func Test_Create(t *testing.T) {
r := require.New(t)
f, err := Create("/hello.txt")
@ -28,7 +28,7 @@ func Testg_Create(t *testing.T) {
r.Equal("github.com/markbates/pkger", her.ImportPath)
}
func Testg_Create_Write(t *testing.T) {
func Test_Create_Write(t *testing.T) {
r := require.New(t)
f, err := Create("/hello.txt")

22
fs/file.go Normal file
View File

@ -0,0 +1,22 @@
package fs
import (
"net/http"
"os"
"github.com/markbates/pkger/here"
)
type File interface {
Close() error
FilePath() string
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)
}

20
fs/fs.go Normal file
View File

@ -0,0 +1,20 @@
package fs
import (
"os"
"path/filepath"
"github.com/markbates/pkger/here"
)
type FileSystem interface {
Create(name string) (File, error)
Current() (here.Info, error)
Info(p string) (here.Info, error)
MkdirAll(p string, perm os.FileMode) error
Open(name string) (File, error)
Parse(p string) (Path, error)
ReadFile(s string) ([]byte, error)
Stat(name string) (os.FileInfo, error)
Walk(p string, wf filepath.WalkFunc) error
}

37
fs/fstest/file.go Normal file
View File

@ -0,0 +1,37 @@
package fstest
import (
"bytes"
"io"
"github.com/markbates/pkger/fs"
)
type TestFile struct {
Name string
Data []byte
}
func (t TestFile) Create(fx fs.FileSystem) error {
f, err := fx.Create(t.Name)
if err != nil {
return err
}
_, err = io.Copy(f, bytes.NewReader(t.Data))
if err != nil {
return err
}
return f.Close()
}
type TestFiles map[string]TestFile
func (t TestFiles) Create(fx fs.FileSystem) error {
for k, f := range t {
f.Name = k
if err := f.Create(fx); err != nil {
return err
}
}
return nil
}

82
fs/hdfs/file.go Normal file
View File

@ -0,0 +1,82 @@
package hdfs
import (
"net/http"
"os"
"github.com/markbates/pkger/fs"
"github.com/markbates/pkger/here"
)
var _ fs.File = &File{}
type File struct {
*os.File
filePath string
info *fs.FileInfo
her here.Info
path fs.Path
fs fs.FileSystem
}
func NewFile(fx fs.FileSystem, osf *os.File) (*File, error) {
info, err := osf.Stat()
if err != nil {
return nil, err
}
pt, err := fx.Parse(info.Name())
if err != nil {
return nil, err
}
f := &File{
File: osf,
filePath: info.Name(),
path: pt,
fs: fx,
}
f.info = fs.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) FilePath() string {
return f.filePath
}
func (f *File) Info() here.Info {
return f.her
}
func (f *File) Name() string {
return f.info.Name()
}
func (f *File) Open(name string) (http.File, error) {
return f.File, nil
}
func (f *File) Path() fs.Path {
return f.path
}
func (f *File) Stat() (os.FileInfo, error) {
if f.info == nil {
info, err := os.Stat(f.filePath)
if err != nil {
return nil, err
}
f.info = fs.NewFileInfo(info)
}
return f.info, nil
}

153
fs/hdfs/hdfs.go Normal file
View File

@ -0,0 +1,153 @@
package hdfs
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/markbates/pkger/fs"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/internal/maps"
)
var _ fs.FileSystem = &FS{}
type FS struct {
infos *maps.Infos
paths *maps.Paths
current here.Info
}
func New() (*FS, error) {
f := &FS{
infos: &maps.Infos{},
paths: &maps.Paths{},
}
var err error
f.current, err = here.Current()
return f, err
}
func (fx *FS) Create(name string) (fs.File, error) {
name, err := fx.locate(name)
if err != nil {
return nil, err
}
f, err := os.Create(name)
if err != nil {
return nil, err
}
return NewFile(fx, f)
}
func (f *FS) Current() (here.Info, error) {
return f.current, nil
}
func (f *FS) 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 *FS) MkdirAll(p string, perm os.FileMode) error {
p, err := f.locate(p)
if err != nil {
return err
}
return os.MkdirAll(p, perm)
}
func (fx *FS) Open(name string) (fs.File, error) {
name, err := fx.locate(name)
if err != nil {
return nil, err
}
f, err := os.Open(name)
if err != nil {
return nil, err
}
return NewFile(fx, f)
}
func (f *FS) Parse(p string) (fs.Path, error) {
return f.paths.Parse(p)
}
func (f *FS) ReadFile(s string) ([]byte, error) {
s, err := f.locate(s)
if err != nil {
return nil, err
}
return ioutil.ReadFile(s)
}
func (f *FS) Stat(name string) (os.FileInfo, error) {
name, err := f.locate(name)
if err != nil {
return nil, err
}
return os.Stat(name)
}
func (f *FS) Walk(p string, wf filepath.WalkFunc) error {
fp, err := f.locate(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(), fs.WithName(path, fs.NewFileInfo(fi)), nil)
})
return err
}
func (f *FS) locate(p string) (string, error) {
pt, err := f.Parse(p)
if err != nil {
return "", err
}
var info here.Info
if pt.Pkg == "." {
info, err = f.Current()
if err != nil {
return "", err
}
pt.Pkg = info.ImportPath
}
if info.IsZero() {
info, err = f.Info(pt.Pkg)
if err != nil {
return "", fmt.Errorf("%s: %s", pt, err)
}
}
fp := filepath.Join(info.Dir, pt.Name)
return fp, nil
}

104
fs/info.go Normal file
View File

@ -0,0 +1,104 @@
package fs
import (
"encoding/json"
"os"
"strings"
"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
}
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 {
if ft, ok := info.(*FileInfo); ok {
ft.Details.Name = cleanName(name)
return ft
}
fo := NewFileInfo(info)
fo.Details.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
}

40
fs/memfs/create.go Normal file
View File

@ -0,0 +1,40 @@
package memfs
import (
"path/filepath"
"time"
"github.com/markbates/pkger/fs"
)
func (fx *FS) Create(name string) (fs.File, error) {
pt, err := fx.Parse(name)
if err != nil {
return nil, err
}
her, err := fx.Info(pt.Pkg)
if err != nil {
return nil, err
}
f := &File{
path: pt,
her: her,
info: &fs.FileInfo{
Details: fs.Details{
Name: pt.Name,
Mode: 0666,
ModTime: fs.ModTime(time.Now()),
},
},
fs: fx,
}
fx.files.Store(pt, f)
dir := filepath.Dir(pt.Name)
if err := fx.MkdirAll(dir, 0644); err != nil {
return nil, err
}
return f, nil
}

90
fs/memfs/create_test.go Normal file
View File

@ -0,0 +1,90 @@
package memfs
import (
"io"
"os"
"strings"
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_Create(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
f, err := fs.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())
}
func Test_Create_Write(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
f, err := fs.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())
}
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`

203
fs/memfs/file.go Normal file
View File

@ -0,0 +1,203 @@
package memfs
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/markbates/pkger/fs"
"github.com/markbates/pkger/here"
)
const timeFmt = time.RFC3339Nano
var _ fs.File = &File{}
type File struct {
info *fs.FileInfo
her here.Info
path fs.Path
data []byte
parent fs.Path
writer *bytes.Buffer
reader io.Reader
fs fs.FileSystem
}
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.Details.Size = int64(len(f.data))
fi.Details.ModTime = fs.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) FilePath() string {
return f.her.FilePath(f.Name())
}
func (f File) Path() fs.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.fs.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.fs.Parse(path)
if err != nil {
return err
}
if pt.Name == f.parent.Name {
return nil
}
// if f.parent.Name != "/" {
info = fs.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
}
func (f *File) Open(name string) (http.File, error) {
pt, err := f.fs.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.fs.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: fs.NewFileInfo(fi),
her: di.Info(),
path: pt,
parent: f.path,
fs: f.fs,
}
di = d2
}
return di, nil
}

70
fs/memfs/file_test.go Normal file
View File

@ -0,0 +1,70 @@
package memfs
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"time"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_File_Read_Memory(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
r.NoError(err)
f, err := fs.Create("/file_test.go")
r.NoError(err)
_, err = io.Copy(f, bytes.NewReader([]byte("hi!")))
r.NoError(err)
r.NoError(f.Close())
f, err = fs.Open("/file_test.go")
r.NoError(err)
fi, err := f.Stat()
r.NoError(err)
r.Equal("/file_test.go", fi.Name())
b, err := ioutil.ReadAll(f)
r.NoError(err)
r.Equal(string(b), "hi!")
}
func Test_File_Write(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
r.NoError(err)
f, err := fs.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())
}

1
fs/memfs/http.go Normal file
View File

@ -0,0 +1 @@
package memfs

84
fs/memfs/http_test.go Normal file
View File

@ -0,0 +1,84 @@
package memfs
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func Test_HTTP_Dir(t *testing.T) {
r := require.New(t)
fs := NewFS()
r.NoError(Folder.Create(fs))
dir, err := fs.Open("/")
r.NoError(err)
ts := httptest.NewServer(http.FileServer(dir))
defer ts.Close()
res, err := http.Get(ts.URL + "/")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
r.Contains(string(b), `<a href="/public/images/mark.png">/public/images/mark.png</a>`)
}
func Test_HTTP_File_Memory(t *testing.T) {
r := require.New(t)
fs := NewFS()
r.NoError(Folder.Create(fs))
dir, err := fs.Open("/")
r.NoError(err)
ts := httptest.NewServer(http.FileServer(dir))
defer ts.Close()
res, err := http.Get(ts.URL + "/public/images/mark.png")
r.NoError(err)
r.Equal(200, res.StatusCode)
b, err := ioutil.ReadAll(res.Body)
r.NoError(err)
r.Contains(string(b), `!/public/images/mark.png`)
}
func Test_HTTP_Dir_Memory_StripPrefix(t *testing.T) {
r := require.New(t)
fs := NewFS()
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`)
}

66
fs/memfs/json.go Normal file
View File

@ -0,0 +1,66 @@
package memfs
import (
"encoding/json"
"fmt"
"github.com/markbates/pkger/fs"
)
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 = &fs.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
}

43
fs/memfs/json_test.go Normal file
View File

@ -0,0 +1,43 @@
package memfs
import (
"encoding/json"
"io"
"strings"
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_File_JSON(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
r.NoError(err)
f, err := fs.Create("/radio.radio")
r.NoError(err)
_, err = io.Copy(f, strings.NewReader(radio))
r.NoError(err)
r.NoError(f.Close())
f, err = fs.Open("/radio.radio")
r.NoError(err)
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(radio, string(f2.data))
}

58
fs/memfs/memfs.go Normal file
View File

@ -0,0 +1,58 @@
package memfs
import (
"io/ioutil"
"github.com/markbates/pkger/fs"
"github.com/markbates/pkger/here"
"github.com/markbates/pkger/internal/maps"
)
var _ fs.FileSystem = &FS{}
func New(info here.Info) (*FS, error) {
f := &FS{
infos: &maps.Infos{},
paths: &maps.Paths{},
files: &maps.Files{},
}
return f, nil
}
type FS struct {
infos *maps.Infos
paths *maps.Paths
files *maps.Files
current here.Info
}
func (f *FS) Current() (here.Info, error) {
return f.current, nil
}
func (f *FS) 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 *FS) Parse(p string) (fs.Path, error) {
return f.paths.Parse(p)
}
func (fx *FS) ReadFile(s string) ([]byte, error) {
f, err := fx.Open(s)
if err != nil {
return nil, err
}
defer f.Close()
return ioutil.ReadAll(f)
}

26
fs/memfs/memfs_test.go Normal file
View File

@ -0,0 +1,26 @@
package memfs
import (
"log"
"github.com/markbates/pkger/fs/fstest"
"github.com/markbates/pkger/here"
)
func NewFS() *FS {
fs, err := New(here.Info{})
if err != nil {
log.Fatal(err)
}
return fs
}
var Folder = fstest.TestFiles{
"/main.go": {Data: []byte("!/main.go")},
"/go.mod": {Data: []byte("!/go.mod")},
"/go.sum": {Data: []byte("!/go.sum")},
"/public/index.html": {Data: []byte("!/public/index.html")},
"/public/images/mark.png": {Data: []byte("!/public/images/mark.png")},
"/templates/a.txt": {Data: []byte("!/templates/a.txt")},
"/templates/b/b.txt": {Data: []byte("!/templates/b/b.txt")},
}

61
fs/memfs/mkdirall.go Normal file
View File

@ -0,0 +1,61 @@
package memfs
import (
"os"
"path/filepath"
"time"
"github.com/markbates/pkger/fs"
)
func (fx *FS) 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 := fs.Path{
Pkg: path.Pkg,
Name: root,
}
if _, ok := fx.files.Load(pt); ok {
root = filepath.Dir(root)
if root == "/" || root == "\\" {
break
}
continue
}
f := &File{
fs: fx,
path: pt,
her: cur,
info: &fs.FileInfo{
Details: fs.Details{
Name: pt.Name,
Mode: perm,
ModTime: fs.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
}

25
fs/memfs/mkdirall_test.go Normal file
View File

@ -0,0 +1,25 @@
package memfs
import (
"os"
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_MkdirAll(t *testing.T) {
r := require.New(t)
fs, _ := New(here.Info{})
err := fs.MkdirAll("/foo/bar/baz", 0755)
r.NoError(err)
fi, err := fs.Stat("/foo/bar/baz")
r.NoError(err)
r.Equal("/foo/bar/baz", fi.Name())
r.Equal(os.FileMode(0755), fi.Mode())
r.True(fi.IsDir())
}

32
fs/memfs/open.go Normal file
View File

@ -0,0 +1,32 @@
package memfs
import (
"fmt"
"github.com/markbates/pkger/fs"
)
func (fx *FS) Open(name string) (fs.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{
fs: fx,
info: fs.WithName(f.info.Name(), f.info),
path: f.path,
data: f.data,
her: f.her,
}
return nf, nil
}

34
fs/memfs/open_test.go Normal file
View File

@ -0,0 +1,34 @@
package memfs
import (
"io"
"io/ioutil"
"strings"
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_Open(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
r.NoError(err)
_, err = fs.Open("/i.dont.exist")
r.Error(err)
f, err := fs.Create("/i.exist")
r.NoError(err)
_, err = io.Copy(f, strings.NewReader(radio))
r.NoError(err)
r.NoError(f.Close())
f, err = fs.Open("/i.exist")
r.NoError(err)
b, err := ioutil.ReadAll(f)
r.NoError(err)
r.NoError(f.Close())
r.Equal([]byte(radio), b)
}

18
fs/memfs/stat.go Normal file
View File

@ -0,0 +1,18 @@
package memfs
import (
"fmt"
"os"
)
func (fx *FS) 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", name)
}

25
fs/memfs/stat_test.go Normal file
View File

@ -0,0 +1,25 @@
package memfs
import (
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_Stat(t *testing.T) {
r := require.New(t)
fs, err := New(here.Info{})
r.NoError(err)
_, err = fs.Stat("/i.dont.exist")
r.Error(err)
f, err := fs.Create("/i.exist")
r.NoError(err)
r.NoError(f.Close())
fi, err := fs.Stat("/i.exist")
r.NoError(err)
r.Equal("/i.exist", fi.Name())
}

38
fs/memfs/walk.go Normal file
View File

@ -0,0 +1,38 @@
package memfs
import (
"fmt"
"path/filepath"
"strings"
"github.com/markbates/pkger/fs"
)
func (f *FS) Walk(p string, wf filepath.WalkFunc) error {
keys := f.files.Keys()
pt, err := f.Parse(p)
if err != nil {
return err
}
for _, k := range keys {
if !strings.HasPrefix(k.Name, pt.Name) {
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 = fs.WithName(strings.TrimPrefix(k.Name, pt.Name), fi)
err = wf(k.String(), fi, nil)
if err != nil {
return err
}
}
return nil
}

69
fs/memfs/walk_test.go Normal file
View File

@ -0,0 +1,69 @@
package memfs
import (
"io"
"os"
"sort"
"strings"
"testing"
"github.com/markbates/pkger/here"
"github.com/stretchr/testify/require"
)
func Test_Walk(t *testing.T) {
r := require.New(t)
files := []struct {
name string
body string
}{
{name: "/a/a.txt", body: "A"},
{name: "/a/a.md", body: "Amd"},
{name: "/b/c/d.txt", body: "B"},
{name: "/f.txt", body: "F"},
}
sort.Slice(files, func(a, b int) bool {
return files[a].name < files[b].name
})
fs, err := New(here.Info{})
r.NoError(err)
for _, file := range files {
f, err := fs.Create(file.name)
r.NoError(err)
_, err = io.Copy(f, strings.NewReader(file.body))
r.NoError(err)
r.NoError(f.Close())
}
var found []string
err = fs.Walk("/", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
found = append(found, path)
return nil
})
r.NoError(err)
expected := []string{":/", ":/a", ":/a/a.md", ":/a/a.txt", ":/b", ":/b/c", ":/b/c/d.txt", ":/f.txt"}
r.Equal(expected, found)
found = []string{}
err = fs.Walk("/a/", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
found = append(found, path)
return nil
})
r.NoError(err)
expected = []string{":/a/a.md", ":/a/a.txt"}
r.Equal(expected, found)
}

39
fs/path.go Normal file
View File

@ -0,0 +1,39 @@
package fs
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())
}
}

146
internal/maps/files.go Normal file
View File

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

126
internal/maps/infos.go Normal file
View File

@ -0,0 +1,126 @@
// Code generated by github.com/markbates/pkger/mapgen. DO NOT EDIT.
package maps
import (
"encoding/json"
"fmt"
"sort"
"sync"
"github.com/markbates/pkger/here"
)
// Infos wraps sync.Map and uses the following types:
// key: string
// value: here.Info
type Infos struct {
data *sync.Map
once *sync.Once
}
func (m *Infos) 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 *Infos) 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 *Infos) UnmarshalJSON(b []byte) error {
mm := map[string]here.Info{}
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 *Infos) Delete(key string) {
m.Data().Delete(key)
}
// Load the key from the map.
// Returns here.Info or bool.
// A false return indicates either the key was not found
// or the value is not of type here.Info
func (m *Infos) Load(key string) (here.Info, bool) {
m.Data()
i, ok := m.data.Load(key)
if !ok {
return here.Info{}, false
}
s, ok := i.(here.Info)
return s, ok
}
// LoadOrStore will return an existing key or
// store the value if not already in the map
func (m *Infos) 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 *Infos) LoadOr(key string, fn func(*Infos) (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
func (m *Infos) Range(f func(key string, value here.Info) bool) {
m.Data().Range(func(k, v interface{}) bool {
key, ok := k.(string)
if !ok {
return false
}
value, ok := v.(here.Info)
if !ok {
return false
}
return f(key, value)
})
}
// Store a here.Info in the map
func (m *Infos) Store(key string, value here.Info) {
m.Data().Store(key, value)
}
// Keys returns a list of keys in the map
func (m *Infos) Keys() []string {
var keys []string
m.Range(func(key string, value here.Info) bool {
keys = append(keys, key)
return true
})
sort.Strings(keys)
return keys
}

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

@ -0,0 +1,182 @@
// Code generated by github.com/gobuffalo/mapgen. DO NOT EDIT.
package maps
import (
"encoding/json"
"fmt"
"regexp"
"sort"
"strings"
"sync"
"github.com/markbates/pkger/fs"
"github.com/markbates/pkger/here"
)
// 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]fs.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) (fs.Path, bool) {
i, ok := m.Data().Load(key)
if !ok {
return fs.Path{}, false
}
s, ok := i.(fs.Path)
return s, ok
}
// LoadOrStore will return an existing key or
// store the value if not already in the map
func (m *Paths) LoadOrStore(key string, value fs.Path) (fs.Path, bool) {
i, _ := m.Data().LoadOrStore(key, value)
s, ok := i.(fs.Path)
return s, ok
}
// LoadOr will return an existing key or
// run the function and store the results
func (m *Paths) LoadOr(key string, fn func(*Paths) (fs.Path, bool)) (fs.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 *Paths) Range(f func(key string, value fs.Path) bool) {
m.Data().Range(func(k, v interface{}) bool {
key, ok := k.(string)
if !ok {
return false
}
value, ok := v.(fs.Path)
if !ok {
return false
}
return f(key, value)
})
}
// Store a Path in the map
func (m *Paths) Store(key string, value fs.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 fs.Path) bool {
keys = append(keys, key)
return true
})
sort.Strings(keys)
return keys
}
func (m *Paths) Parse(p string) (fs.Path, error) {
p = strings.Replace(p, "\\", "/", -1)
pt, ok := m.Load(p)
if ok {
return pt, nil
}
if len(p) == 0 {
return m.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 m.build(p, matches[1], matches[3])
}
var pathrx = regexp.MustCompile("([^:]+)(:(/.+))?")
func (m *Paths) build(p, pkg, name string) (fs.Path, error) {
pt := fs.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
}
m.Store(p, pt)
return pt, nil
}