package gluaflag import ( "bytes" "flag" "fmt" "io" "io/ioutil" "os" "strings" "github.com/yuin/gopher-lua" ) var flagSetFuncs = map[string]lua.LGFunction{ "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 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 }