Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
802 lines (680 sloc) 16.1 KB
package gluaflag
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"within.website/eclier/internal/gluaheroku"
"github.com/yuin/gopher-lua"
)
var flagSetFuncs = map[string]lua.LGFunction{
"app": app,
"number": number,
"numbers": numbers,
"int": integer,
"ints": integers,
"string": str,
"strings": strs,
"bool": boolean,
"stringArg": stringArgument,
"intArg": intArgument,
"numberArg": numberArgument,
"parse": parse,
"compgen": compgen,
"usage": usage,
}
// FlagSet is the background userdata component
type FlagSet struct {
name string
fs *flag.FlagSet
flags flgs
arguments arguments
output io.Writer
}
// New returns a new flagset userdata
func New(L *lua.LState, name string) *lua.LUserData {
f := flag.NewFlagSet(name, flag.ContinueOnError)
flags := &FlagSet{
name: name,
fs: f,
flags: make(flgs),
arguments: make(arguments, 0),
output: os.Stderr,
}
flags.fs.Usage = func() {
fmt.Fprint(flags.output, flags.Usage())
}
ud := L.NewUserData()
ud.Value = flags
L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName))
return ud
}
func new(L *lua.LState) int {
var d lua.LValue = lua.LString("")
larg := L.GetGlobal("arg")
if targ, ok := larg.(*lua.LTable); ok {
d = targ.RawGetInt(0)
}
name := L.OptString(1, d.String())
L.Push(New(L, name))
return 1
}
// Usage returns the usage message for the flag set
func (fs *FlagSet) Usage() string {
buff := &bytes.Buffer{}
buff.WriteString(fmt.Sprintf("usage: %v\n", fs.ShortUsage()))
fs.fs.SetOutput(buff)
defer fs.fs.SetOutput(os.Stderr)
fs.fs.PrintDefaults()
for _, arg := range fs.arguments {
buff.WriteString(arg.generateUsage())
}
return buff.String()
}
// ShortUsage returns the usage string for a flagset
func (fs *FlagSet) ShortUsage() string {
buff := &bytes.Buffer{}
buff.WriteString(fs.name)
if len(fs.flags) > 0 {
buff.WriteString(fmt.Sprintf(" [options]"))
}
for _, arg := range fs.arguments {
buff.WriteString(" ")
buff.WriteString(arg.shortUsage(arg.name))
}
return buff.String()
}
// FlagDefaults returns the flagsets help string for the flags
func (fs *FlagSet) FlagDefaults() string {
buff := &bytes.Buffer{}
fs.fs.SetOutput(buff)
defer fs.fs.SetOutput(os.Stderr)
fs.fs.PrintDefaults()
return buff.String()
}
// ArgDefaults returns the flagsets help string for the possitional arguments
func (fs *FlagSet) ArgDefaults() string {
buff := &bytes.Buffer{}
for _, arg := range fs.arguments {
buff.WriteString(arg.generateUsage())
}
return buff.String()
}
func (fs *FlagSet) printFlags() string {
var s []string
fs.fs.VisitAll(func(fl *flag.Flag) {
s = append(s, "-"+fl.Name)
})
return strings.Join(s, "\n")
}
func (fs *FlagSet) getFlags() []string {
var s []string
fs.fs.VisitAll(func(fl *flag.Flag) {
s = append(s, "-"+fl.Name)
})
return s
}
// Compgen returns a string with possible options for the flag
func (fs *FlagSet) Compgen(L *lua.LState, compCWords int, compWords []string) []string {
if compCWords == 1 && len(compWords) == 1 {
return fs.getArguments(compCWords, compWords, L)
}
if compCWords <= len(compWords) {
prev := compWords[compCWords-1]
if string(prev[0]) == "-" {
fl := fs.fs.Lookup(prev[1:len(prev)])
v, ok := fs.flags[fl.Name]
if !ok {
return []string{}
}
switch value := v.value.(type) {
case *bool:
if string(compWords[len(compWords)-1][0]) == "-" {
return fs.getFlags()
}
return []string{}
case *string, *float64, *int:
word := compWords[len(compWords)-1]
if compCWords == len(compWords) {
word = ""
}
table := L.NewTable()
fs.fs.Visit(func(f *flag.Flag) {
table.RawSetString(f.Name, lua.LString(f.Value.String()))
})
raw := L.NewTable()
for i, word := range compWords {
if i == 0 {
raw.RawSet(lua.LNumber(0), lua.LString(word))
continue
}
raw.Append(lua.LString(word))
}
stack := L.GetTop()
if err := L.CallByParam(lua.P{
Fn: v.compFn,
NRet: -1,
Protect: true,
}, lua.LString(word), table, raw); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
stack = L.GetTop() - stack
if stack == 1 {
res := L.Get(-1)
L.Pop(1)
switch r := res.(type) {
case *lua.LTable:
return toStringSlice(r)
case lua.LString:
s := string(r)
if strings.Index(s, "\n") > 0 {
return strings.Split(s, "\n")
}
return []string{s}
case *lua.LFunction:
return forEachStrings(L, r)
default:
L.RaiseError("unknown type: %T", r)
}
}
res := []string{}
for i := 1; i <= stack; i++ {
res = append(res, L.Get(-i).String())
}
L.Pop(stack)
return res
default:
L.RaiseError("not implemented type: %T", value)
return []string{}
}
} else if string(compWords[len(compWords)-1][0]) == "-" {
// current argument starts with "-"
return fs.getFlags()
} else { // argument
return fs.getArguments(compCWords, compWords, L)
}
}
return []string{}
}
func (fs *FlagSet) getArguments(compCWords int, compWords []string, L *lua.LState) []string {
err := fs.fs.Parse(compWords[1:len(compWords)])
if err != nil {
return []string{}
}
nargs := fs.fs.NArg()
if nargs == len(fs.arguments) {
nargs--
}
word := compWords[len(compWords)-1]
if compCWords == len(compWords) {
word = ""
}
table := L.NewTable()
fs.fs.Visit(func(f *flag.Flag) {
table.RawSetString(f.Name, lua.LString(f.Value.String()))
})
raw := L.NewTable()
for i, word := range compWords {
if i == 0 {
raw.RawSet(lua.LNumber(0), lua.LString(word))
continue
}
raw.Append(lua.LString(word))
}
if nargs >= len(fs.arguments) || nargs < 0 {
return []string{}
}
// stack is needed to know how the stack grows
stack := L.GetTop()
if err := L.CallByParam(lua.P{
Fn: fs.arguments[nargs].compFn,
NRet: -1,
Protect: true,
}, lua.LString(word), table, raw); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
stack = L.GetTop() - stack
if stack == 1 {
res := L.Get(-1)
L.Pop(1)
switch r := res.(type) {
case *lua.LTable:
return toStringSlice(r)
case *lua.LString:
s := string(*r)
if strings.Index(s, "\n") > 0 {
return strings.Split(s, "\n")
}
return []string{s}
case *lua.LFunction:
return forEachStrings(L, r)
default:
L.RaiseError("unknown type: %T", r)
}
}
res := []string{}
for i := 1; i <= stack; i++ {
res = append(res, L.Get(-i).String())
}
L.Pop(stack)
return res
}
func usage(L *lua.LState) int {
gf := checkFlagSet(L, 1)
L.Push(lua.LString(gf.Usage()))
return 1
}
func compgen(L *lua.LState) int {
ud := L.CheckUserData(1)
compCWords := L.CheckInt(2)
compWords := L.CheckTable(3)
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
comp := gf.Compgen(L, compCWords, toStringSlice(compWords))
L.Push(toTable(L, comp))
return 1
}
func number(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
value := L.CheckNumber(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
f := gf.fs.Float64(name, float64(value), usage)
gf.flags[name] = &flg{
name: name,
value: f,
usage: usage,
compFn: cf,
}
L.Push(gf.flags[name].userdata(L))
return 1
}
func numbers(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
usage := L.CheckString(3)
cf := L.OptFunction(4, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
var numbers numberslice
gf.fs.Var(&numbers, name, usage)
gf.flags[name] = &flg{
name: name,
value: &numbers,
usage: usage,
compFn: cf,
}
return 0
}
func integer(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
value := L.CheckInt(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
f := gf.fs.Int(name, int(value), usage)
gf.flags[name] = &flg{
name: name,
value: f,
usage: usage,
compFn: cf,
}
return 0
}
func integers(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
usage := L.CheckString(3)
cf := L.OptFunction(4, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
var ints intslice
gf.fs.Var(&ints, name, usage)
gf.flags[name] = &flg{
name: name,
value: &ints,
usage: usage,
compFn: cf,
}
return 0
}
func app(L *lua.LState) int {
appName, _ := gluaheroku.AppFromGitRemote("heroku")
ud := L.CheckUserData(1)
const (
name = `app`
usage = `the Heroku application name relevant to this action`
)
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
cf := L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
})
f := gf.fs.String(name, appName, usage)
gf.flags[name] = &flg{
name: name,
value: f,
usage: usage,
compFn: cf,
}
return 0
}
func str(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
value := L.CheckString(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
f := gf.fs.String(name, value, usage)
gf.flags[name] = &flg{
name: name,
value: f,
usage: usage,
compFn: cf,
}
return 0
}
func strs(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
usage := L.CheckString(3)
cf := L.OptFunction(4, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
var strs stringslice
gf.fs.Var(&strs, name, usage)
gf.flags[name] = &flg{
name: name,
value: &strs,
usage: usage,
compFn: cf,
}
return 0
}
func boolean(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
value := L.CheckBool(3)
usage := L.CheckString(4)
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
f := gf.fs.Bool(name, value, usage)
gf.flags[name] = &flg{
name: name,
value: f,
usage: usage,
compFn: nil,
}
return 0
}
func stringArgument(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
times := L.CheckAny(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
a := &argument{
name: name,
usage: usage,
compFn: cf,
typ: "string",
}
parser, err := getParser("string", times)
if err != nil {
L.RaiseError(err.Error())
}
a.parser = parser
su, err := getShortUsageFn(times)
if err != nil {
L.RaiseError(err.Error())
}
a.shortUsage = su
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
udPossitionalArgument := L.NewUserData()
udPossitionalArgument.Value = a
L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName))
gf.arguments = append(gf.arguments, a)
L.Push(udPossitionalArgument)
return 1
}
func intArgument(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
times := L.CheckAny(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
a := &argument{
name: name,
usage: usage,
compFn: cf,
typ: "int",
}
parser, err := getParser("int", times)
if err != nil {
L.RaiseError(err.Error())
}
a.parser = parser
su, err := getShortUsageFn(times)
if err != nil {
L.RaiseError(err.Error())
}
a.shortUsage = su
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
udPossitionalArgument := L.NewUserData()
udPossitionalArgument.Value = a
L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName))
gf.arguments = append(gf.arguments, a)
L.Push(udPossitionalArgument)
return 1
}
func numberArgument(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
times := L.CheckAny(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
a := &argument{
name: name,
usage: usage,
compFn: cf,
typ: "number",
}
parser, err := getParser("number", times)
if err != nil {
L.RaiseError(err.Error())
}
a.parser = parser
su, err := getShortUsageFn(times)
if err != nil {
L.RaiseError(err.Error())
}
a.shortUsage = su
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
udPossitionalArgument := L.NewUserData()
udPossitionalArgument.Value = a
L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName))
gf.arguments = append(gf.arguments, a)
L.Push(udPossitionalArgument)
return 1
}
func possitionalInt(L *lua.LState) int {
ud := L.CheckUserData(1)
name := L.CheckString(2)
times := L.CheckAny(3)
usage := L.CheckString(4)
cf := L.OptFunction(5, L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(""))
return 1
}))
a := &argument{
name: name,
usage: usage,
typ: "int",
compFn: cf,
}
switch t := times.(type) {
case lua.LString:
switch t {
case "+":
a.glob = true
case "*":
a.glob = true
a.optional = true
case "?":
a.times = 1
a.optional = true
default:
L.RaiseError("nargs should be an integer or one of '?', '*', or '+'")
}
case lua.LNumber:
if int(t) < 1 {
L.RaiseError("nargs should be an integer or one of '?', '*', or '+'")
}
a.times = int(t)
default:
L.RaiseError("nargs should be an integer or one of '?', '*', or '+'")
}
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
}
udPossitionalArgument := L.NewUserData()
udPossitionalArgument.Value = a
L.SetMetatable(ud, L.GetTypeMetatable(luaFlagSetTypeName))
gf.arguments = append(gf.arguments, a)
L.Push(udPossitionalArgument)
return 1
}
// Parse the command line parameters
func Parse(L *lua.LState, ud *lua.LUserData, args []string) (*lua.LTable, error) {
gf, ok := ud.Value.(*FlagSet)
if !ok {
L.RaiseError("Expected gluaflag userdata, got `%T`", ud.Value)
return nil, ErrUserDataType
}
gf.fs.SetOutput(ioutil.Discard)
gf.output = ioutil.Discard
err := gf.fs.Parse(args)
if err != nil {
return nil, err
}
t := L.NewTable()
for f, v := range gf.flags {
switch value := v.value.(type) {
case *float64:
t.RawSetString(f, lua.LNumber(*value))
case *string:
t.RawSetString(f, lua.LString(*value))
case *bool:
t.RawSetString(f, lua.LBool(*value))
case *int:
t.RawSetString(f, lua.LNumber(*value))
case *intslice:
t.RawSetString(f, value.Table(L))
case *numberslice:
t.RawSetString(f, value.Table(L))
case *stringslice:
t.RawSetString(f, value.Table(L))
default:
L.RaiseError("unknown type: `%T`", v)
}
}
// nothing defined for possitional arguments, just copy them
if len(gf.arguments) == 0 {
for _, v := range gf.fs.Args() {
t.Append(lua.LString(v))
}
return t, nil
}
// TODO: refactor to a function in arguments
args = gf.fs.Args()
for _, arg := range gf.arguments {
args, err = arg.parse(args, L)
if err != nil {
return nil, fmt.Errorf("argument %v: %v", arg.name, err.Error())
}
t.RawSetString(arg.name, arg.toLValue(L))
}
if len(args) > 0 {
L.RaiseError("unknown argument: %v", args)
}
return t, nil
}
func parse(L *lua.LState) int {
ud := L.CheckUserData(1)
args := L.CheckTable(2)
a := toStringSlice(args)
t, err := Parse(L, ud, a[1:len(a)])
if err != nil {
L.RaiseError("%v", err)
}
L.Push(t)
return 1
}
You can’t perform that action at this time.