141 lines
3.3 KiB
Go
141 lines
3.3 KiB
Go
|
package eclier
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"os"
|
||
|
"path/filepath"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/olekukonko/tablewriter"
|
||
|
lua "github.com/yuin/gopher-lua"
|
||
|
)
|
||
|
|
||
|
// 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
|
||
|
}
|
||
|
}
|