route/vendor/github.com/Xe/eclier/router.go

207 lines
4.7 KiB
Go

package eclier
import (
"context"
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/olekukonko/tablewriter"
lua "github.com/yuin/gopher-lua"
"layeh.com/asar"
)
// Router is the main subcommand router for eclier. At a high level what this is
// doing is similar to http.ServeMux, but for CLI commands instead of HTTP handlers.
type Router struct {
lock sync.Mutex
cmds map[string]Command
// configured data
gluaCreationHook func(*lua.LState)
scriptHomes []string
cartridge map[string]string
}
// NewRouter creates a new instance of Router and sets it up for use.
func NewRouter(opts ...RouterOption) (*Router, error) {
r := &Router{
cmds: map[string]Command{},
cartridge: map[string]string{},
}
for _, opt := range opts {
opt(r)
}
// scan r.scriptHome for lua scripts, load them into their own lua states and
// make a wrapper around them for the Command type.
for _, home := range r.scriptHomes {
err := filepath.Walk(home, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Printf("error in arg: %v", err)
return err
}
if strings.HasSuffix(info.Name(), ".lua") {
fname := filepath.Join(home, info.Name())
fin, err := os.Open(fname)
if err != nil {
return err
}
defer fin.Close()
c := newGluaCommand(r.gluaCreationHook, fname, fin)
r.cmds[c.Verb()] = c
}
return nil
})
if err != nil {
return nil, err
}
}
var helpCommand Command = NewBuiltinCommand("help", "shows help for subcommands", "[subcommand]", func(ctx context.Context, arg []string) error {
if len(arg) == 0 {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Verb", "Author", "Version", "Help"})
for _, cmd := range r.cmds {
table.Append([]string{cmd.Verb(), cmd.Author(), cmd.Version(), cmd.Help()})
}
table.Render()
return nil
}
cmd, ok := r.cmds[arg[0]]
if !ok {
fmt.Printf("can't find help for %s", arg[0])
os.Exit(2)
}
fmt.Printf("Verb: %s\nAuthor: %s\nVersion: %s\nHelp: %s\nUsage: %s %s\n", cmd.Verb(), cmd.Author(), cmd.Version(), cmd.Help(), cmd.Verb(), cmd.Usage())
return nil
})
r.cmds["plugins"] = &pluginCommand{r: r}
r.cmds["help"] = helpCommand
return r, nil
}
// Run executes a single command given in slot 0 of the argument array.
func (r *Router) Run(ctx context.Context, arg []string) error {
r.lock.Lock()
defer r.lock.Unlock()
if len(arg) == 0 {
fmt.Printf("please specify a subcommand, such as `%s help`\n", filepath.Base(os.Args[0]))
os.Exit(2)
}
cmd := arg[0]
arg = arg[1:]
ci, ok := r.cmds[cmd]
if !ok {
fmt.Printf("No such command %s could be run.\n", cmd)
os.Exit(2)
}
ci.Init()
return ci.Run(ctx, arg)
}
// RouterOption is a functional option for Router.
type RouterOption func(*Router)
// WithScriptHome sets the router's script home to the given directory. This is
// where lua files will be walked and parsed.
func WithScriptHome(dir string) RouterOption {
return func(r *Router) {
r.scriptHomes = append(r.scriptHomes, dir)
}
}
// WithGluaCreationHook adds a custom bit of code that runs every time a new
// gopher-lua LState is created. This allows users of this library to register
// custom libraries to the pile of states.
func WithGluaCreationHook(hook func(*lua.LState)) RouterOption {
return func(r *Router) {
r.gluaCreationHook = hook
}
}
// WithFilesystem loads a http.FileSystem full of lua scripts into this eclier
// router.
func WithFilesystem(shortName string, fs http.FileSystem) RouterOption {
return func(r *Router) {
fin, err := fs.Open("/")
if err != nil {
log.Fatal(err)
}
defer fin.Close()
childs, err := fin.Readdir(-1)
if err != nil {
log.Fatal(err)
}
for _, chl := range childs {
if strings.HasSuffix(chl.Name(), ".lua") {
fname := filepath.Join(shortName, chl.Name())
sFin, err := fs.Open(chl.Name())
if err != nil {
log.Fatal(err)
}
defer sFin.Close()
c := newGluaCommand(r.gluaCreationHook, fname, sFin)
r.cmds[c.Verb()] = c
}
}
}
}
// WithAsarFile loads an asar file full of lua scripts into this eclier router.
func WithAsarFile(shortName, fname string) RouterOption {
return func(r *Router) {
fin, err := os.Open(fname)
if err != nil {
log.Fatal(err)
}
defer fin.Close()
e, err := asar.Decode(fin)
if err != nil {
log.Fatal(err)
}
err = e.Walk(func(path string, info os.FileInfo, err error) error {
if strings.HasSuffix(info.Name(), ".lua") {
fname := filepath.Join(shortName, "::", path)
fin := e.Find(path)
if fin == nil {
return nil
}
c := newGluaCommand(r.gluaCreationHook, fname, fin.Open())
r.cmds[c.Verb()] = c
}
return nil
})
if err != nil {
log.Fatal(err)
}
}
}