Skip to content

Instantly share code, notes, and snippets.

@ghostsquad
Last active January 3, 2018 00:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ghostsquad/9b5d5725cceed01fd1cf2bacfafd1f90 to your computer and use it in GitHub Desktop.
Save ghostsquad/9b5d5725cceed01fd1cf2bacfafd1f90 to your computer and use it in GitHub Desktop.
VFSGen with Custom FS
// +build ignore
package main
import (
"log"
"github.com/shurcooL/vfsgen"
"go/build"
"net/http"
"os"
"os/exec"
"strings"
"github.com/ghostsquad/ronn2docopt"
"bytes"
"fmt"
"io"
pathpkg "path"
"path/filepath"
"time"
"io/ioutil"
)
const ReadMeFile = "README.md"
const DocOptExtension = ".docopt"
const ManPageExtension = ".man"
func NewFS(source http.FileSystem, baseDir string) http.FileSystem {
return &helpDataJS{
source: source,
baseDir: baseDir,
}
}
type helpDataJS struct {
source http.FileSystem
baseDir string
}
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
}
type dir struct {
name string
modTime time.Time
entries []os.FileInfo
pos int // Position within entries for Seek and Readdir.
path string
}
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) {
//err := filterDir(d.path)
//if err != nil {
// return nil, filepath.SkipDir
//}
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 filterDir(path string) error {
if strings.HasPrefix(path, "/commands") || path == "/" {
return nil
}
return filepath.SkipDir
}
func (d *dir) Readdir(count int) ([]os.FileInfo, error) {
//err := filterDir(d.path)
//if err != nil {
// return nil, err
//}
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
}
func importPathToDir(importPath string) string {
p, err := build.Import(importPath, "", build.FindOnly)
if err != nil {
log.Fatalln(err)
}
return p.Dir
}
func hasExtension(name, ext string) bool {
return strings.HasSuffix(name, ext)
}
func (fs *helpDataJS) Open(path string) (http.File, error) {
// handle virtual files
if hasExtension(path, DocOptExtension) {
return fs.convertToDocopt(path)
}
if hasExtension(path, ManPageExtension) {
return fs.convertToMan(path)
}
var err error
_, filename := pathpkg.Split(path)
f, err := fs.source.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return nil, err
}
// handle original markdown files
if filename == ReadMeFile {
f, err := fs.source.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
data, err := ioutil.ReadAll(f)
if err != nil {
log.Fatal(err)
}
return &file{
name: fi.Name(),
size: fi.Size(),
modTime: time.Time{},
Reader: bytes.NewReader(data),
}, nil
}
// handle all other files
if !fi.IsDir() {
return nil, &os.PathError{Op: "open", Path: path, Err: os.ErrNotExist}
}
// handle skipped directories
err = filterDir(path)
if err != nil {
//return nil, &os.PathError{Op: "open", Path: path, Err: err}
return &dir{
name: fi.Name(),
entries: []os.FileInfo{},
modTime: time.Time{},
path: path,
}, nil
}
fis, err := f.Readdir(0)
if err != nil {
return nil, err
}
var entries []os.FileInfo
for _, fi := range fis {
switch {
case fi.IsDir():
entries = append(entries, fi)
case fi.Name() == ReadMeFile:
entries = append(entries, &file{
name: ReadMeFile,
size: 0,
modTime: time.Time{},
})
entries = append(entries, &file{
name: "help" + DocOptExtension,
size: 0,
modTime: time.Time{},
})
entries = append(entries, &file{
name: "help" + ManPageExtension,
size: 0,
modTime: time.Time{},
})
}
}
return &dir{
name: fi.Name(),
entries: entries,
modTime: time.Time{},
path: path,
}, nil
}
func (fs *helpDataJS) convertToDocopt (path string) (http.File, error) {
baseDir, _ := pathpkg.Split(path)
realPath := pathpkg.Join(fs.baseDir, baseDir, ReadMeFile)
content, err := ronn2docopt.ConvertRonnFile(realPath)
if err != nil {
return nil, err
}
_, filename := pathpkg.Split(path)
return &file{
name: filename,
size: int64(len(content)),
modTime: time.Time{},
Reader: bytes.NewReader([]byte(content)),
}, nil
}
func (fs *helpDataJS) convertToMan (path string) (http.File, error) {
baseDir, _ := pathpkg.Split(path)
realPath := pathpkg.Join(fs.baseDir, baseDir, ReadMeFile)
var out bytes.Buffer
cmd := exec.Command("pandoc", realPath, "-s", "-t", "man")
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
_, filename := pathpkg.Split(path)
content := out.String()
return &file{
name: filename,
size: int64(len(content)),
modTime: time.Time{},
Reader: bytes.NewReader([]byte(content)),
}, nil
}
func main() {
projectDir := importPathToDir("github.com/me/myproject")
var FS = NewFS(http.Dir(projectDir), projectDir)
err := vfsgen.Generate(FS, vfsgen.Options{
PackageName: "helpdata",
VariableName: "HelpData",
Filename: "helpdata/helpdata_vfsdata.go",
})
if err != nil {
log.Fatalln(err)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment