mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
243 lines
5.6 KiB
Go
243 lines
5.6 KiB
Go
package gopherjs_http
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
pathpkg "path"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// NewFS returns an http.FileSystem that is exactly like source, except all Go packages are compiled to JavaScript with GopherJS.
|
|
//
|
|
// For example:
|
|
//
|
|
// /mypkg/foo.go
|
|
// /mypkg/bar.go
|
|
//
|
|
// Become replaced with:
|
|
//
|
|
// /mypkg/mypkg.js
|
|
//
|
|
// Where mypkg.js is the result of building mypkg with GopherJS.
|
|
func NewFS(source http.FileSystem) http.FileSystem {
|
|
return &gopherJSFS{source: source}
|
|
}
|
|
|
|
type gopherJSFS struct {
|
|
source http.FileSystem
|
|
}
|
|
|
|
func (fs *gopherJSFS) Open(path string) (http.File, error) {
|
|
switch dir, file := pathpkg.Split(path); {
|
|
case file == pathpkg.Base(dir)+".js":
|
|
return fs.compileGoPackage(dir)
|
|
default:
|
|
return fs.openSource(path)
|
|
}
|
|
}
|
|
|
|
func (fs *gopherJSFS) openSource(path string) (http.File, error) {
|
|
f, err := fs.source.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, err
|
|
}
|
|
|
|
switch {
|
|
// Files with .go and ".inc.js" extensions are consumed and no longer exist
|
|
// in output filesystem.
|
|
case !fi.IsDir() && pathpkg.Ext(fi.Name()) == ".go":
|
|
fallthrough
|
|
case !fi.IsDir() && strings.HasSuffix(fi.Name(), ".inc.js"):
|
|
f.Close()
|
|
return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
|
|
case !fi.IsDir():
|
|
return f, nil
|
|
}
|
|
defer f.Close()
|
|
|
|
fis, err := f.Readdir(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Include all subfolders, non-.go files.
|
|
var entries []os.FileInfo
|
|
var haveGo []os.FileInfo
|
|
for _, fi := range fis {
|
|
switch {
|
|
case !fi.IsDir() && pathpkg.Ext(fi.Name()) == ".go":
|
|
haveGo = append(haveGo, fi)
|
|
case !fi.IsDir() && strings.HasSuffix(fi.Name(), ".inc.js"):
|
|
// TODO: Handle ".inc.js" files correctly.
|
|
entries = append(entries, fi)
|
|
default:
|
|
entries = append(entries, fi)
|
|
}
|
|
}
|
|
|
|
// If it has any .go files, present the Go package compiled with GopherJS as an additional virtual file.
|
|
if len(haveGo) > 0 {
|
|
entries = append(entries, &file{
|
|
name: fi.Name() + ".js",
|
|
size: 0, // TODO.
|
|
modTime: time.Time{}, // TODO.
|
|
})
|
|
}
|
|
|
|
return &dir{
|
|
name: fi.Name(),
|
|
entries: entries,
|
|
modTime: fi.ModTime(),
|
|
}, nil
|
|
}
|
|
|
|
func (fs *gopherJSFS) compileGoPackage(dir string) (http.File, error) {
|
|
f, err := fs.source.Open(dir)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !fi.IsDir() {
|
|
return nil, fmt.Errorf("%s is not a dir", dir)
|
|
}
|
|
|
|
fis, err := f.Readdir(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var goFiles []os.FileInfo
|
|
for _, f := range fis {
|
|
// TODO: Use build.Import or equivalent to get GoFiles and CgoFiles. The below approximates it, but fails to use build tags correctly, etc.
|
|
if f.IsDir() {
|
|
continue
|
|
}
|
|
if pathpkg.Ext(f.Name()) != ".go" {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(f.Name(), ".") || strings.HasPrefix(f.Name(), "_") {
|
|
continue
|
|
}
|
|
if strings.HasSuffix(f.Name(), "_test.go") {
|
|
continue
|
|
}
|
|
goFiles = append(goFiles, f)
|
|
}
|
|
if len(goFiles) == 0 {
|
|
return nil, fmt.Errorf("%s has no matching .go files", dir)
|
|
}
|
|
|
|
// TODO: Clean this up.
|
|
{
|
|
name := pathpkg.Base(dir) + ".js"
|
|
|
|
var names []string
|
|
var goReaders []io.Reader
|
|
var goClosers []io.Closer
|
|
for _, goFile := range goFiles {
|
|
file, err := fs.source.Open(pathpkg.Join(dir, goFile.Name()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names = append(names, goFile.Name())
|
|
goReaders = append(goReaders, file)
|
|
goClosers = append(goClosers, file)
|
|
}
|
|
|
|
fmt.Printf("REBUILDING SOURCE for: %s using %+v\n", name, names)
|
|
content := []byte(handleJsError(goReadersToJS(names, goReaders)))
|
|
|
|
for _, closer := range goClosers {
|
|
closer.Close()
|
|
}
|
|
|
|
return &file{
|
|
name: name,
|
|
size: int64(len(content)),
|
|
modTime: time.Now(),
|
|
Reader: bytes.NewReader(content),
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// file is an opened file instance.
|
|
type file struct {
|
|
name string
|
|
modTime time.Time
|
|
size int64
|
|
*bytes.Reader
|
|
}
|
|
|
|
func (f *file) Readdir(count int) ([]os.FileInfo, error) {
|
|
return nil, fmt.Errorf("cannot Readdir from file %s", f.name)
|
|
}
|
|
func (f *file) Stat() (os.FileInfo, error) { return f, nil }
|
|
|
|
func (f *file) Name() string { return f.name }
|
|
func (f *file) Size() int64 { return f.size }
|
|
func (f *file) Mode() os.FileMode { return 0444 }
|
|
func (f *file) ModTime() time.Time { return f.modTime }
|
|
func (f *file) IsDir() bool { return false }
|
|
func (f *file) Sys() interface{} { return nil }
|
|
|
|
func (f *file) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// dir is an opened dir instance.
|
|
type dir struct {
|
|
name string
|
|
modTime time.Time
|
|
entries []os.FileInfo
|
|
pos int // Position within entries for Seek and Readdir.
|
|
}
|
|
|
|
func (d *dir) Read([]byte) (int, error) {
|
|
return 0, fmt.Errorf("cannot Read from directory %s", d.name)
|
|
}
|
|
func (d *dir) Close() error { return nil }
|
|
func (d *dir) Stat() (os.FileInfo, error) { return d, nil }
|
|
|
|
func (d *dir) Name() string { return d.name }
|
|
func (d *dir) Size() int64 { return 0 }
|
|
func (d *dir) Mode() os.FileMode { return 0755 | os.ModeDir }
|
|
func (d *dir) ModTime() time.Time { return d.modTime }
|
|
func (d *dir) IsDir() bool { return true }
|
|
func (d *dir) Sys() interface{} { return nil }
|
|
|
|
func (d *dir) Seek(offset int64, whence int) (int64, error) {
|
|
if offset == 0 && whence == io.SeekStart {
|
|
d.pos = 0
|
|
return 0, nil
|
|
}
|
|
return 0, fmt.Errorf("unsupported Seek in directory %s", d.name)
|
|
}
|
|
|
|
func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
|
|
if d.pos >= len(d.entries) && count > 0 {
|
|
return nil, io.EOF
|
|
}
|
|
if count <= 0 || count > len(d.entries)-d.pos {
|
|
count = len(d.entries) - d.pos
|
|
}
|
|
e := d.entries[d.pos : d.pos+count]
|
|
d.pos += count
|
|
return e, nil
|
|
}
|