| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package structarg
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "reflect"
- "strconv"
- "strings"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/util/reflectutils"
- "yunion.io/x/pkg/utils"
- )
- type BaseOptions struct {
- PidFile string `help:"Pid file path"`
- Config string `help:"Configuration file"`
- Help bool `help:"Show help and exit"`
- Version bool `help:"Show version and exit"`
- }
- type Argument interface {
- NeedData() bool
- Token() string
- AliasToken() string
- ShortToken() string
- NegativeToken() string
- MetaVar() string
- IsPositional() bool
- IsRequired() bool
- IsMulti() bool
- IsSubcommand() bool
- HelpString(indent string) string
- String() string
- SetValue(val string) error
- Reset()
- DoAction(nega bool) error
- Validate() error
- SetDefault()
- IsSet() bool
- }
- type SingleArgument struct {
- token string
- aliasToken string
- shortToken string
- negaToken string
- metavar string
- positional bool
- required bool
- help string
- choices []string
- useDefault bool
- defValue reflect.Value
- value reflect.Value
- ovalue reflect.Value
- isSet bool
- parser *ArgumentParser
- }
- type MultiArgument struct {
- SingleArgument
- minCount int64
- maxCount int64
- }
- type SubcommandArgumentData struct {
- parser *ArgumentParser
- callback reflect.Value
- }
- type SubcommandArgument struct {
- SingleArgument
- subcommands map[string]SubcommandArgumentData
- }
- type ArgumentParser struct {
- target interface{}
- prog string
- description string
- epilog string
- help bool
- optArgs []Argument
- posArgs []Argument
- }
- type sHelpArg struct {
- }
- func (self *sHelpArg) AliasToken() string {
- return ""
- }
- func (self *sHelpArg) DoAction(nega bool) error {
- return nil
- }
- func (self *sHelpArg) HelpString(indent string) string {
- return indent + "Print usage and this help message and exit."
- }
- func (self *sHelpArg) NeedData() bool {
- return false
- }
- func (self *sHelpArg) Token() string {
- return "help"
- }
- func (self *sHelpArg) ShortToken() string {
- return ""
- }
- func (self *sHelpArg) NegativeToken() string {
- return ""
- }
- func (self *sHelpArg) MetaVar() string {
- return ""
- }
- func (self *sHelpArg) IsPositional() bool {
- return false
- }
- func (self *sHelpArg) IsRequired() bool {
- return false
- }
- func (self *sHelpArg) IsMulti() bool {
- return false
- }
- func (self *sHelpArg) IsSubcommand() bool {
- return false
- }
- func (self *sHelpArg) String() string {
- return "[--help]"
- }
- func (self *sHelpArg) SetValue(val string) error {
- return nil
- }
- func (self *sHelpArg) Reset() {
- return
- }
- func (self *sHelpArg) Validate() error {
- return nil
- }
- func (self *sHelpArg) SetDefault() {
- }
- func (self *sHelpArg) IsSet() bool {
- return false
- }
- func newArgumentParser(target interface{}, prog, desc, epilog string) (*ArgumentParser, error) {
- parser := ArgumentParser{prog: prog, description: desc,
- epilog: epilog, target: target}
- targetValue := reflect.ValueOf(target)
- if targetValue.Kind() != reflect.Ptr {
- return nil, fmt.Errorf("target must be a pointer")
- }
- targetValue = targetValue.Elem()
- e := parser.addStructArgument("", targetValue)
- if e != nil {
- return nil, e
- }
- // always add a help argument --help
- helpArg := &sHelpArg{}
- parser.AddArgument(helpArg)
- return &parser, nil
- }
- func NewArgumentParser(target interface{}, prog, desc, epilog string) (*ArgumentParser, error) {
- return newArgumentParser(target, prog, desc, epilog)
- }
- func NewArgumentParserWithHelp(target interface{}, prog, desc, epilog string) (*ArgumentParser, error) {
- return newArgumentParser(target, prog, desc, epilog)
- }
- const (
- /*
- help text of the argument
- the argument is optional.
- */
- TAG_HELP = "help"
- /*
- command-line token for the optional argument, e.g. token:"url"
- the command-line argument will be "--url http://127.0.0.1:3306"
- the tag is optional.
- if the tag is missing, the variable name will be used as token.
- If the variable name is CamelCase, the token will be transformed
- into kebab-case, e.g. if the variable is "AuthURL", the token will
- be "--auth-url"
- */
- TAG_TOKEN = "token"
- /*
- short form of command-line token, e.g. short-token:"u"
- the command-line argument will be "-u http://127.0.0.1:3306"
- the tag is optional
- */
- TAG_SHORT_TOKEN = "short-token"
- /*
- Metavar of the argument
- the tag is optional
- */
- TAG_METAVAR = "metavar"
- /*
- The default value of the argument.
- the tag is optional
- */
- TAG_DEFAULT = "default"
- /*
- The possible values of an arguments. All choices are are concatenatd by "|".
- e.g. `choices:"1|2|3"`
- the tag is optional
- */
- TAG_CHOICES = "choices"
- /*
- A boolean value explicitly declare whether the argument is optional,
- the tag is optional
- */
- TAG_POSITIONAL = "positional"
- /*
- A boolean value explicitly declare whether the argument is required.
- The tag is optional. This is for optional arguments. Positional
- arguments must be "required"
- */
- TAG_REQUIRED = "required"
- /*
- A boolean value explicitly decalre whther the argument is an subcommand
- A subcommand argument must be the last positional argument.
- the tag is optional, the default value is false
- */
- TAG_SUBCOMMAND = "subcommand"
- /*
- The attribute defines the possible number of argument. Possible values
- are:
- * positive integers, e.g. "1", "2"
- * "*" any number of arguments
- * "+" at lease one argument
- * "?" at most one argument
- the tag is optional, the default value is "1"
- */
- TAG_NARGS = "nargs"
- /*
- Alias name of argument
- */
- TAG_ALIAS = "alias"
- /*
- Token for negative value, applicable to boolean values
- */
- TAG_NEGATIVE_TOKEN = "negative"
- /*
- Token for ignore
- */
- TAG_IGNORE = "ignore"
- )
- func (this *ArgumentParser) addStructArgument(prefix string, tpVal reflect.Value) error {
- sets := reflectutils.FetchAllStructFieldValueSetForWrite(tpVal)
- for i := range sets {
- if sets[i].Value.Kind() == reflect.Struct && sets[i].Value.Type() != gotypes.TimeType {
- tagMap := sets[i].Info.Tags
- if _, ok := tagMap[reflectutils.TAG_DEPRECATED_BY]; ok {
- // deprecated field, ignore
- return nil
- }
- token, ok := tagMap[TAG_TOKEN]
- if !ok {
- token = sets[i].Info.MarshalName()
- }
- token = prefix + token + "-"
- err := this.addStructArgument(token, sets[i].Value)
- if err != nil {
- return errors.Wrap(err, "addStructArgument")
- }
- } else {
- err := this.addArgument(prefix, sets[i].Value, sets[i].Info)
- if err != nil {
- return errors.Wrap(err, "addArgument")
- }
- }
- }
- return nil
- }
- func (this *ArgumentParser) addArgument(prefix string, fv reflect.Value, info *reflectutils.SStructFieldInfo) error {
- tagMap := info.Tags
- if _, ok := tagMap[reflectutils.TAG_DEPRECATED_BY]; ok {
- // deprecated field, ignore
- return nil
- }
- if val, ok := tagMap[TAG_IGNORE]; ok && val == "true" {
- // ignore field
- return nil
- }
- help := tagMap[TAG_HELP]
- token, ok := tagMap[TAG_TOKEN]
- if !ok {
- token = info.MarshalName()
- }
- token = prefix + token
- shorttoken := tagMap[TAG_SHORT_TOKEN]
- alias := tagMap[TAG_ALIAS]
- negative := tagMap[TAG_NEGATIVE_TOKEN]
- metavar := tagMap[TAG_METAVAR]
- defval := tagMap[TAG_DEFAULT]
- if len(defval) > 0 {
- for _, dv := range strings.Split(defval, "|") {
- if dv[0] == '$' {
- dv = os.Getenv(strings.TrimLeft(dv, "$"))
- }
- defval = dv
- if len(defval) > 0 {
- break
- }
- }
- }
- if len(negative) > 0 && !valueIsBool(fv) {
- return fmt.Errorf("negative token is applicable to boolean option ONLY")
- }
- use_default := true
- if len(defval) == 0 {
- use_default = false
- }
- var choices []string
- if choices_str, ok := tagMap[TAG_CHOICES]; ok {
- choices = strings.Split(choices_str, "|")
- }
- // heuristic guessing "positional"
- var positional bool
- if info.FieldName == strings.ToUpper(info.FieldName) {
- positional = true
- } else {
- positional = false
- }
- if positionalTag := tagMap[TAG_POSITIONAL]; len(positionalTag) > 0 {
- switch positionalTag {
- case "true":
- positional = true
- case "false":
- positional = false
- default:
- return fmt.Errorf("Invalid positional tag %q, neither true nor false", positionalTag)
- }
- }
- required := positional
- if requiredTag := tagMap[TAG_REQUIRED]; len(requiredTag) > 0 {
- switch requiredTag {
- case "true":
- required = true
- case "false":
- required = false
- default:
- return fmt.Errorf("Invalid required tag %q, neither true nor false", requiredTag)
- }
- }
- if positional {
- if !required {
- return fmt.Errorf("positional %s must not have required:false", token)
- }
- if use_default {
- return fmt.Errorf("positional %s must not have default value", token)
- }
- }
- if !positional && use_default && required {
- return fmt.Errorf("non-positional argument with default value should not have required:true set")
- }
- subcommand, err := strconv.ParseBool(tagMap[TAG_SUBCOMMAND])
- if err != nil {
- subcommand = false
- }
- var defval_t reflect.Value
- if use_default {
- defval_t, err = gotypes.ParseValue(defval, fv.Type())
- if err != nil {
- return err
- }
- }
- if subcommand {
- positional = true
- }
- var arg Argument = nil
- ovalue := reflect.New(fv.Type()).Elem()
- ovalue.Set(fv)
- sarg := SingleArgument{
- token: token,
- shortToken: shorttoken,
- aliasToken: alias,
- negaToken: negative,
- positional: positional,
- required: required,
- metavar: metavar,
- help: help,
- choices: choices,
- useDefault: use_default,
- defValue: defval_t,
- value: fv,
- ovalue: ovalue,
- parser: this,
- }
- // fmt.Println(token, f.Type, f.Type.Kind())
- if subcommand {
- arg = &SubcommandArgument{SingleArgument: sarg,
- subcommands: make(map[string]SubcommandArgumentData)}
- } else if fv.Kind() == reflect.Array || fv.Kind() == reflect.Slice || fv.Kind() == reflect.Map {
- var min, max int64
- var err error
- nargs := tagMap[TAG_NARGS]
- if nargs == "*" {
- min = 0
- max = -1
- } else if nargs == "?" {
- min = 0
- max = 1
- } else if nargs == "+" {
- min = 1
- max = -1
- } else {
- min, err = strconv.ParseInt(nargs, 10, 64)
- if err == nil {
- max = min
- } else if positional {
- min = 1
- max = -1
- } else if !required {
- min = 0
- max = -1
- }
- }
- arg = &MultiArgument{SingleArgument: sarg,
- minCount: min, maxCount: max}
- } else {
- arg = &sarg
- }
- err = this.AddArgument(arg)
- if err != nil {
- return fmt.Errorf("AddArgument %s: %v", arg, err)
- }
- return nil
- }
- func (this *ArgumentParser) AddArgument(arg Argument) error {
- if arg.IsPositional() {
- if len(this.posArgs) > 0 {
- last_arg := this.posArgs[len(this.posArgs)-1]
- switch {
- case last_arg.IsMulti():
- return fmt.Errorf("Cannot append positional argument after an array positional argument")
- case last_arg.IsSubcommand():
- return fmt.Errorf("Cannot append positional argument after a subcommand argument")
- }
- }
- this.posArgs = append(this.posArgs, arg)
- } else {
- for _, argOld := range this.optArgs {
- if argOld.Token() == arg.Token() {
- if arg.Token() == "help" {
- // silently ignore help arguments
- return nil
- }
- rt := reflect.TypeOf(this.target)
- if rt.Kind() == reflect.Ptr || rt.Kind() == reflect.Interface {
- rt = rt.Elem()
- }
- return fmt.Errorf("%s: Duplicate argument %s", rt.Name(), argOld.Token())
- }
- }
- // Put required at the end and try to be stable
- if arg.IsRequired() {
- this.optArgs = append(this.optArgs, arg)
- } else {
- var i int
- var opt Argument
- for i, opt = range this.optArgs {
- if opt.IsRequired() {
- break
- }
- }
- this.optArgs = append(this.optArgs, nil)
- copy(this.optArgs[i+1:], this.optArgs[i:])
- this.optArgs[i] = arg
- }
- }
- return nil
- }
- func (this *ArgumentParser) SetDefault() {
- for _, arg := range this.posArgs {
- arg.SetDefault()
- }
- for _, arg := range this.optArgs {
- arg.SetDefault()
- }
- }
- func (this *ArgumentParser) Options() interface{} {
- return this.target
- }
- func valueIsBool(rv reflect.Value) bool {
- if rv.Kind() == reflect.Bool {
- return true
- }
- if rv.Kind() == reflect.Ptr && rv.Type().Elem().Kind() == reflect.Bool {
- return true
- }
- return false
- }
- func valueIsMap(rv reflect.Value) bool {
- if rv.Kind() == reflect.Map {
- return true
- }
- return false
- }
- func (this *SingleArgument) defaultBoolValue() bool {
- rv := this.defValue
- if rv.Kind() == reflect.Bool {
- return rv.Bool()
- }
- if rv.Kind() == reflect.Ptr && rv.Type().Elem().Kind() == reflect.Bool {
- return rv.Elem().Bool()
- }
- panic("expecting bool or *bool type: got " + rv.Type().String())
- }
- func (this *SingleArgument) NeedData() bool {
- if valueIsBool(this.value) {
- return false
- } else {
- return true
- }
- }
- func (this *SingleArgument) MetaVar() string {
- if len(this.metavar) > 0 {
- return this.metavar
- } else if len(this.choices) > 0 {
- return fmt.Sprintf("{%s}", strings.Join(this.choices, ","))
- } else {
- return strings.ToUpper(strings.Replace(this.Token(), "-", "_", -1))
- }
- }
- func splitCamelString(str string) string {
- return utils.CamelSplit(str, "-")
- }
- func (this *SingleArgument) AllToken() string {
- ret := this.Token()
- if len(this.AliasToken()) != 0 {
- ret = fmt.Sprintf("%s|--%s", ret, this.AliasToken())
- }
- if len(this.ShortToken()) != 0 {
- ret = fmt.Sprintf("%s|-%s", ret, this.ShortToken())
- }
- if len(this.NegativeToken()) != 0 {
- ret = fmt.Sprintf("%s/--%s", ret, this.NegativeToken())
- }
- return ret
- }
- func (this *SingleArgument) Token() string {
- return splitCamelString(this.token)
- }
- func (this *SingleArgument) AliasToken() string {
- return splitCamelString(this.aliasToken)
- }
- func (this *SingleArgument) ShortToken() string {
- return this.shortToken
- }
- func (this *SingleArgument) NegativeToken() string {
- return strings.ReplaceAll(this.negaToken, "_", "-")
- }
- func (this *SingleArgument) String() string {
- var start, end byte
- if this.IsRequired() {
- start = '<'
- end = '>'
- } else {
- start = '['
- end = ']'
- }
- if this.IsPositional() {
- return fmt.Sprintf("%c%s%c", start, this.MetaVar(), end)
- } else {
- if this.NeedData() {
- return fmt.Sprintf("%c--%s %s%c", start, this.AllToken(), this.MetaVar(), end)
- } else {
- return fmt.Sprintf("%c--%s%c", start, this.AllToken(), end)
- }
- }
- }
- func (this *SingleArgument) IsRequired() bool {
- return this.required
- }
- func (this *SingleArgument) IsPositional() bool {
- return this.positional
- }
- func (this *SingleArgument) IsMulti() bool {
- return false
- }
- func (this *SingleArgument) IsSubcommand() bool {
- return false
- }
- func (this *SingleArgument) HelpString(indent string) string {
- return indent + strings.Join(strings.Split(this.help, "\n"), "\n"+indent)
- }
- func (this *SingleArgument) InChoices(val string) bool {
- if len(this.choices) > 0 {
- for _, s := range this.choices {
- if s == val {
- return true
- }
- }
- return false
- } else {
- return true
- }
- }
- func (this *SingleArgument) Choices() []string {
- return this.choices
- }
- func (this *SingleArgument) SetValue(val string) error {
- if !this.InChoices(val) {
- return this.choicesErr(val)
- }
- e := gotypes.SetValue(this.value, val)
- if e != nil {
- return e
- }
- this.isSet = true
- return nil
- }
- func (this *SingleArgument) choicesErr(val string) error {
- cands := FindSimilar(val, this.choices, -1, 0.5)
- if len(cands) > 3 {
- cands = cands[:3]
- }
- msg := fmt.Sprintf("Unknown argument '%s' for %s", val, this.Token())
- if len(cands) > 0 {
- msg += fmt.Sprintf(", did you mean %s?", quotedChoicesString(cands))
- } else if len(this.choices) > 0 {
- msg += fmt.Sprintf(", accepts %s", quotedChoicesString(this.choices))
- }
- return fmt.Errorf("%s", msg)
- }
- func (this *SingleArgument) Reset() {
- this.value.Set(this.ovalue)
- this.isSet = false
- }
- func (this *SingleArgument) DoAction(nega bool) error {
- if valueIsBool(this.value) {
- var v bool
- if this.useDefault {
- v = !this.defaultBoolValue()
- } else if nega {
- v = false
- } else {
- v = true
- }
- gotypes.SetValue(this.value, fmt.Sprintf("%t", v))
- this.isSet = true
- }
- return nil
- }
- func (this *SingleArgument) SetDefault() {
- if !this.isSet && this.useDefault {
- this.value.Set(this.defValue)
- }
- }
- func (this *SingleArgument) Validate() error {
- if this.required && !this.isSet && !this.useDefault {
- return fmt.Errorf("Non-optional argument %s not set", this.token)
- }
- return nil
- }
- func (this *SingleArgument) IsSet() bool {
- return this.isSet
- }
- func (this *MultiArgument) IsMulti() bool {
- return true
- }
- func (this *MultiArgument) setKeyValue(val string) error {
- pos := strings.IndexByte(val, '=')
- var key, value string
- if pos >= 0 {
- key = val[:pos]
- value = val[pos+1:]
- } else {
- key = val
- }
- keyType := this.value.Type().Key()
- keyValue, err := gotypes.ParseValue(key, keyType)
- if err != nil {
- return errors.Wrapf(err, "ParseValue for key %s", key)
- }
- valType := this.value.Type().Elem()
- valValue, err := gotypes.ParseValue(value, valType)
- if err != nil {
- return errors.Wrapf(err, "ParseValue for value %s", value)
- }
- if this.value.Len() == 0 {
- this.value.Set(reflect.MakeMap(this.value.Type()))
- }
- this.value.SetMapIndex(keyValue, valValue)
- this.isSet = true
- return nil
- }
- func (this *MultiArgument) SetValue(val string) error {
- if valueIsMap(this.value) {
- return this.setKeyValue(val)
- }
- if !this.InChoices(val) {
- return this.choicesErr(val)
- }
- var e error = nil
- e = gotypes.AppendValue(this.value, val)
- if e != nil {
- return e
- }
- this.isSet = true
- return nil
- }
- func (this *MultiArgument) Validate() error {
- var e = this.SingleArgument.Validate()
- if e != nil {
- return e
- }
- var vallen int64 = int64(this.value.Len())
- if this.minCount >= 0 && vallen < this.minCount {
- return fmt.Errorf("Argument count requires at least %d", this.minCount)
- }
- if this.maxCount >= 0 && vallen > this.maxCount {
- return fmt.Errorf("Argument count requires at most %d", this.maxCount)
- }
- return nil
- }
- func (this *SubcommandArgument) IsSubcommand() bool {
- return true
- }
- func (this *SubcommandArgument) String() string {
- return fmt.Sprintf("<%s>", strings.ToUpper(this.token))
- }
- func (this *SubcommandArgument) AddSubParser(target interface{}, command string, desc string, callback interface{}) (*ArgumentParser, error) {
- return this.addSubParser(target, command, desc, callback)
- }
- func (this *SubcommandArgument) AddSubParserWithHelp(target interface{}, command string, desc string, callback interface{}) (*ArgumentParser, error) {
- return this.addSubParser(target, command, desc, callback)
- }
- func (this *SubcommandArgument) addSubParser(target interface{}, command string, desc string, callback interface{}) (*ArgumentParser, error) {
- prog := fmt.Sprintf("%s %s", this.parser.prog, command)
- parser, e := newArgumentParser(target, prog, desc, "")
- if e != nil {
- return nil, e
- }
- cbfunc := reflect.ValueOf(callback)
- this.subcommands[command] = SubcommandArgumentData{parser: parser,
- callback: cbfunc}
- this.choices = append(this.choices, command)
- return parser, nil
- }
- func (this *SubcommandArgument) HelpString(indent string) string {
- var buf bytes.Buffer
- for k, data := range this.subcommands {
- buf.WriteString(indent)
- buf.WriteString(k)
- buf.WriteByte('\n')
- buf.WriteString(indent)
- buf.WriteString(" ")
- buf.WriteString(data.parser.ShortDescription())
- buf.WriteByte('\n')
- }
- return buf.String()
- }
- func (this *SubcommandArgument) SubHelpString(cmd string) (string, error) {
- val, ok := this.subcommands[cmd]
- if ok {
- return val.parser.HelpString(), nil
- } else {
- return "", fmt.Errorf("No such command %s", cmd)
- }
- }
- func (this *SubcommandArgument) GetSubParser() *ArgumentParser {
- var cmd = this.value.String()
- val, ok := this.subcommands[cmd]
- if ok {
- return val.parser
- } else {
- return nil
- }
- }
- func (this *SubcommandArgument) Invoke(args ...interface{}) error {
- var inargs = make([]reflect.Value, 0)
- for _, arg := range args {
- inargs = append(inargs, reflect.ValueOf(arg))
- }
- var cmd = this.value.String()
- val, ok := this.subcommands[cmd]
- if !ok {
- return fmt.Errorf("Unknown subcommand %s", cmd)
- }
- out := val.callback.Call(inargs)
- if len(out) == 1 {
- if out[0].IsNil() {
- return nil
- } else {
- return out[0].Interface().(error)
- }
- } else {
- return fmt.Errorf("Callback return %d unknown outputs", len(out))
- }
- }
- func (this *ArgumentParser) ShortDescription() string {
- return strings.Split(this.description, "\n")[0]
- }
- func (this *ArgumentParser) Usage() string {
- var buf bytes.Buffer
- buf.WriteString("Usage: ")
- buf.WriteString(this.prog)
- for _, arg := range this.optArgs {
- buf.WriteByte(' ')
- buf.WriteString(arg.String())
- }
- for _, arg := range this.posArgs {
- buf.WriteByte(' ')
- buf.WriteString(arg.String())
- if arg.IsSubcommand() || arg.IsMulti() {
- buf.WriteString(" ...")
- }
- }
- buf.WriteByte('\n')
- buf.WriteByte('\n')
- return buf.String()
- }
- func (this *ArgumentParser) HelpString() string {
- var buf bytes.Buffer
- buf.WriteString(this.Usage())
- buf.WriteString(this.description)
- buf.WriteByte('\n')
- buf.WriteByte('\n')
- if len(this.posArgs) > 0 {
- buf.WriteString("Positional arguments:\n")
- for _, arg := range this.posArgs {
- buf.WriteString(" ")
- buf.WriteString(arg.String())
- buf.WriteByte('\n')
- buf.WriteString(arg.HelpString(" "))
- buf.WriteByte('\n')
- }
- buf.WriteByte('\n')
- }
- if len(this.optArgs) > 0 {
- buf.WriteString("Optional arguments:\n")
- for _, arg := range this.optArgs {
- buf.WriteString(" ")
- buf.WriteString(arg.String())
- buf.WriteByte('\n')
- buf.WriteString(arg.HelpString(" "))
- buf.WriteByte('\n')
- }
- buf.WriteByte('\n')
- }
- if len(this.epilog) > 0 {
- buf.WriteString(this.epilog)
- buf.WriteByte('\n')
- buf.WriteByte('\n')
- }
- return buf.String()
- }
- func tokenMatch(argToken, input string, exactMatch bool) bool {
- if exactMatch {
- return argToken == input
- } else {
- return strings.HasPrefix(argToken, input)
- }
- }
- func (this *ArgumentParser) findOptionalArgument(token string, exactMatch bool) (Argument, bool) {
- var match_arg Argument = nil
- match_len := -1
- negative := false
- for _, arg := range this.optArgs {
- if tokenMatch(arg.Token(), token, exactMatch) {
- if match_len < 0 || match_len > len(arg.Token()) {
- match_len = len(arg.Token())
- match_arg = arg
- negative = false
- }
- } else if tokenMatch(arg.ShortToken(), token, exactMatch) {
- if match_len < 0 || match_len > len(arg.ShortToken()) {
- match_len = len(arg.ShortToken())
- match_arg = arg
- negative = false
- }
- } else if tokenMatch(arg.AliasToken(), token, exactMatch) {
- if match_len < 0 || match_len > len(arg.AliasToken()) {
- match_len = len(arg.AliasToken())
- match_arg = arg
- negative = false
- }
- } else if tokenMatch(arg.NegativeToken(), token, exactMatch) {
- if match_len < 0 || match_len > len(arg.AliasToken()) {
- match_len = len(arg.AliasToken())
- match_arg = arg
- negative = true
- }
- }
- }
- return match_arg, negative
- }
- func validateArgs(args []Argument) error {
- for _, arg := range args {
- e := arg.Validate()
- if e != nil {
- return fmt.Errorf("%s error: %s", arg.Token(), e)
- }
- }
- return nil
- }
- func (this *ArgumentParser) Validate() error {
- var e error = nil
- e = validateArgs(this.posArgs)
- if e != nil {
- return e
- }
- e = validateArgs(this.optArgs)
- if e != nil {
- return e
- }
- return nil
- }
- func (this *ArgumentParser) reset() {
- for _, arg := range this.posArgs {
- arg.Reset()
- }
- for _, arg := range this.optArgs {
- arg.Reset()
- }
- this.help = false
- }
- func (this *ArgumentParser) ParseArgs(args []string, ignore_unknown bool) error {
- return this.ParseArgs2(args, ignore_unknown, true)
- }
- func (this *ArgumentParser) ParseArgs2(args []string, ignore_unknown bool, setDefaults bool) error {
- var pos_idx int
- var err error
- var argStr string
- this.reset()
- for i := 0; i < len(args) && err == nil; i++ {
- argStr = args[i]
- if argStr == "--help" {
- // shortcut to show help
- fmt.Println(this.HelpString())
- this.help = true
- continue
- }
- if strings.HasPrefix(argStr, "-") {
- arg, nega := this.findOptionalArgument(strings.TrimLeft(argStr, "-"), false)
- if arg != nil {
- if arg.NeedData() {
- if i+1 < len(args) {
- err = arg.SetValue(args[i+1])
- if err != nil {
- break
- }
- i++
- } else {
- err = fmt.Errorf("Missing arguments for %s", argStr)
- break
- }
- } else {
- err = arg.DoAction(nega)
- if err != nil {
- break
- }
- }
- } else if !ignore_unknown {
- err = fmt.Errorf("Unknown optional argument %s", argStr)
- break
- }
- } else {
- if pos_idx >= len(this.posArgs) {
- if len(this.posArgs) > 0 {
- last_arg := this.posArgs[len(this.posArgs)-1]
- if last_arg.IsMulti() {
- last_arg.SetValue(argStr)
- } else if !ignore_unknown {
- err = fmt.Errorf("Unknown positional argument %s", argStr)
- break
- }
- } else if !ignore_unknown {
- err = fmt.Errorf("Unknown positional argument %s", argStr)
- break
- }
- } else {
- arg := this.posArgs[pos_idx]
- pos_idx += 1
- err = arg.SetValue(argStr)
- if err != nil {
- break
- }
- if arg.IsSubcommand() {
- subarg := arg.(*SubcommandArgument)
- var subparser = subarg.GetSubParser()
- err = subparser.ParseArgs(args[i+1:], ignore_unknown)
- break
- }
- }
- }
- }
- if err == nil && pos_idx < len(this.posArgs) {
- err = &NotEnoughArgumentsError{argument: this.posArgs[pos_idx]}
- }
- if err == nil {
- err = this.Validate()
- }
- if setDefaults {
- this.SetDefault()
- }
- return err
- }
- func isQuotedByChar(str string, quoteChar byte) bool {
- return len(str) >= 2 && str[0] == quoteChar && str[len(str)-1] == quoteChar
- }
- func isQuoted(str string) bool {
- return isQuotedByChar(str, '"') || isQuotedByChar(str, '\'')
- }
- func (this *ArgumentParser) parseKeyValue(key, value string) error {
- arg, nega := this.findOptionalArgument(key, true)
- if arg != nil {
- if nega {
- log.Warningf("Ignore negative token when parse %s=%v", key, value)
- return nil
- }
- if arg.IsSet() {
- return nil
- }
- if arg.IsMulti() {
- if value[0] == '(' {
- value = strings.Trim(value, "()")
- } else {
- value = strings.Trim(value, "[]")
- }
- values := utils.FindWords([]byte(value), 0)
- for _, v := range values {
- e := arg.SetValue(v)
- if e != nil {
- return e
- }
- }
- } else {
- if !isQuoted(value) {
- value = fmt.Sprintf("\"%s\"", value)
- }
- values := utils.FindWords([]byte(value), 0)
- if len(values) == 1 {
- return arg.SetValue(values[0])
- } else {
- log.Warningf("too many arguments %#v for %s", values, key)
- }
- }
- } else {
- log.Warningf("Cannot find argument %s", key)
- }
- return nil
- }
- func removeComments(line string) string {
- pos := strings.IndexByte(line, '#')
- if pos >= 0 {
- return line[:pos]
- } else {
- return line
- }
- }
- func line2KeyValue(line string) (string, string, error) {
- // first remove comments
- pos := strings.IndexByte(line, '=')
- if pos > 0 && pos < len(line) {
- key := keyToToken(line[:pos])
- val := strings.Trim(line[pos+1:], " ")
- return key, val, nil
- } else {
- return "", "", fmt.Errorf("Misformated line: %s", line)
- }
- }
- func removeCharacters(input, charSet string) string {
- filter := func(r rune) rune {
- if strings.IndexRune(charSet, r) < 0 {
- return r
- }
- return -1
- }
- return strings.Map(filter, input)
- }
- func (this *ArgumentParser) ParseYAMLFile(filepath string) error {
- content, err := ioutil.ReadFile(filepath)
- if err != nil {
- return fmt.Errorf("read file %s: %v", filepath, err)
- }
- obj, err := jsonutils.ParseYAML(string(content))
- if err != nil {
- return fmt.Errorf("parse yaml to json object: %v", err)
- }
- dict, ok := obj.(*jsonutils.JSONDict)
- if !ok {
- return fmt.Errorf("object %s is not JSONDict", obj.String())
- }
- return this.parseJSONDict(dict)
- }
- func (this *ArgumentParser) parseJSONDict(dict *jsonutils.JSONDict) error {
- mapJson, err := dict.GetMap()
- if err != nil {
- return errors.Wrap(err, "GetMap")
- }
- for key, obj := range mapJson {
- if err := this.parseJSONKeyValue(key, obj); err != nil {
- return fmt.Errorf("parse json %s: %s: %v", key, obj.String(), err)
- }
- }
- return nil
- }
- func keyToToken(key string) string {
- return strings.Replace(strings.Trim(key, " "), "_", "-", -1)
- }
- func (this *ArgumentParser) parseJSONKeyValue(key string, obj jsonutils.JSONObject) error {
- token := keyToToken(key)
- arg, nega := this.findOptionalArgument(token, true)
- if arg == nil {
- log.Warningf("Cannot find argument %s", token)
- return nil
- }
- if nega {
- log.Warningf("Ignore negative token when parse JSONKeyValue %s", token)
- return nil
- }
- if arg.IsSet() {
- return nil
- }
- // process multi argument
- if arg.IsMulti() {
- array, err := obj.GetArray()
- if err != nil {
- return errors.Wrap(err, "GetArray")
- }
- for _, item := range array {
- str, err := item.GetString()
- if err != nil {
- return err
- }
- if err := arg.SetValue(str); err != nil {
- return err
- }
- }
- return nil
- }
- // process single argument
- str, err := obj.GetString()
- if err != nil {
- return err
- }
- return arg.SetValue(str)
- }
- func (this *ArgumentParser) ParseFile(filepath string) error {
- if err := this.ParseYAMLFile(filepath); err == nil {
- return nil
- }
- return this.ParseTornadoFile(filepath)
- }
- func (this *ArgumentParser) parseReader(r io.Reader) error {
- scanner := bufio.NewScanner(r)
- for scanner.Scan() {
- line := scanner.Text()
- line = strings.TrimSpace(removeComments(line))
- // line = removeCharacters(line, `"'`)
- if len(line) > 0 {
- if line[0] == '[' {
- continue
- }
- key, val, e := line2KeyValue(line)
- if e == nil {
- this.parseKeyValue(key, val)
- } else {
- return e
- }
- }
- }
- if err := scanner.Err(); err != nil {
- return err
- }
- return nil
- }
- func (this *ArgumentParser) ParseTornadoFile(filepath string) error {
- file, e := os.Open(filepath)
- if e != nil {
- return e
- }
- defer file.Close()
- return this.parseReader(file)
- }
- func (this *ArgumentParser) GetSubcommand() *SubcommandArgument {
- if len(this.posArgs) > 0 {
- last_arg := this.posArgs[len(this.posArgs)-1]
- if last_arg.IsSubcommand() {
- return last_arg.(*SubcommandArgument)
- }
- }
- return nil
- }
- func (this *ArgumentParser) ParseKnownArgs(args []string) error {
- return this.ParseArgs(args, true)
- }
- func (this *ArgumentParser) GetOptArgs() []Argument {
- return this.optArgs
- }
- func (this *ArgumentParser) GetPosArgs() []Argument {
- return this.posArgs
- }
- func (this *ArgumentParser) IsHelpSet() bool {
- return this.help
- }
|