mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-09 23:00:58 +00:00
244 lines
5.4 KiB
Go
244 lines
5.4 KiB
Go
package flags
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"unicode/utf8"
|
|
)
|
|
|
|
type parseState struct {
|
|
arg string
|
|
args []string
|
|
retargs []string
|
|
err error
|
|
|
|
command *Command
|
|
lookup lookup
|
|
}
|
|
|
|
func (p *parseState) eof() bool {
|
|
return len(p.args) == 0
|
|
}
|
|
|
|
func (p *parseState) pop() string {
|
|
if p.eof() {
|
|
return ""
|
|
}
|
|
|
|
p.arg = p.args[0]
|
|
p.args = p.args[1:]
|
|
|
|
return p.arg
|
|
}
|
|
|
|
func (p *parseState) peek() string {
|
|
if p.eof() {
|
|
return ""
|
|
}
|
|
|
|
return p.args[0]
|
|
}
|
|
|
|
func (p *parseState) checkRequired() error {
|
|
required := p.lookup.required
|
|
|
|
if len(required) == 0 {
|
|
return nil
|
|
}
|
|
|
|
names := make([]string, 0, len(required))
|
|
|
|
for k := range required {
|
|
names = append(names, "`"+k.String()+"'")
|
|
}
|
|
|
|
var msg string
|
|
|
|
if len(names) == 1 {
|
|
msg = fmt.Sprintf("the required flag %s was not specified", names[0])
|
|
} else {
|
|
msg = fmt.Sprintf("the required flags %s and %s were not specified",
|
|
strings.Join(names[:len(names)-1], ", "), names[len(names)-1])
|
|
}
|
|
|
|
p.err = newError(ErrRequired, msg)
|
|
return p.err
|
|
}
|
|
|
|
func (p *parseState) estimateCommand() error {
|
|
commands := p.command.sortedCommands()
|
|
cmdnames := make([]string, len(commands))
|
|
|
|
for i, v := range commands {
|
|
cmdnames[i] = v.Name
|
|
}
|
|
|
|
var msg string
|
|
|
|
if len(p.retargs) != 0 {
|
|
c, l := closestChoice(p.retargs[0], cmdnames)
|
|
msg = fmt.Sprintf("Unknown command `%s'", p.retargs[0])
|
|
|
|
if float32(l)/float32(len(c)) < 0.5 {
|
|
msg = fmt.Sprintf("%s, did you mean `%s'?", msg, c)
|
|
} else if len(cmdnames) == 1 {
|
|
msg = fmt.Sprintf("%s. You should use the %s command",
|
|
msg,
|
|
cmdnames[0])
|
|
} else {
|
|
msg = fmt.Sprintf("%s. Please specify one command of: %s or %s",
|
|
msg,
|
|
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
|
|
cmdnames[len(cmdnames)-1])
|
|
}
|
|
} else {
|
|
if len(cmdnames) == 1 {
|
|
msg = fmt.Sprintf("Please specify the %s command", cmdnames[0])
|
|
} else {
|
|
msg = fmt.Sprintf("Please specify one command of: %s or %s",
|
|
strings.Join(cmdnames[:len(cmdnames)-1], ", "),
|
|
cmdnames[len(cmdnames)-1])
|
|
}
|
|
}
|
|
|
|
return newError(ErrRequired, msg)
|
|
}
|
|
|
|
func (p *Parser) parseOption(s *parseState, name string, option *Option, canarg bool, argument *string) (retoption *Option, err error) {
|
|
if !option.canArgument() {
|
|
if argument != nil {
|
|
msg := fmt.Sprintf("bool flag `%s' cannot have an argument", option)
|
|
return option, newError(ErrNoArgumentForBool, msg)
|
|
}
|
|
|
|
err = option.set(nil)
|
|
} else if argument != nil {
|
|
err = option.set(argument)
|
|
} else if canarg && !s.eof() {
|
|
arg := s.pop()
|
|
err = option.set(&arg)
|
|
} else if option.OptionalArgument {
|
|
option.clear()
|
|
|
|
for _, v := range option.OptionalValue {
|
|
err = option.set(&v)
|
|
|
|
if err != nil {
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
msg := fmt.Sprintf("expected argument for flag `%s'", option)
|
|
err = newError(ErrExpectedArgument, msg)
|
|
}
|
|
|
|
if err != nil {
|
|
if _, ok := err.(*Error); !ok {
|
|
msg := fmt.Sprintf("invalid argument for flag `%s' (expected %s): %s",
|
|
option,
|
|
option.value.Type(),
|
|
err.Error())
|
|
|
|
err = newError(ErrMarshal, msg)
|
|
}
|
|
}
|
|
|
|
return option, err
|
|
}
|
|
|
|
func (p *Parser) parseLong(s *parseState, name string, argument *string) (option *Option, err error) {
|
|
if option := s.lookup.longNames[name]; option != nil {
|
|
// Only long options that are required can consume an argument
|
|
// from the argument list
|
|
canarg := !option.OptionalArgument
|
|
|
|
return p.parseOption(s, name, option, canarg, argument)
|
|
}
|
|
|
|
return nil, newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", name))
|
|
}
|
|
|
|
func (p *Parser) splitShortConcatArg(s *parseState, optname string) (string, *string) {
|
|
c, n := utf8.DecodeRuneInString(optname)
|
|
|
|
if n == len(optname) {
|
|
return optname, nil
|
|
}
|
|
|
|
first := string(c)
|
|
|
|
if option := s.lookup.shortNames[first]; option != nil && option.canArgument() {
|
|
arg := optname[n:]
|
|
return first, &arg
|
|
}
|
|
|
|
return optname, nil
|
|
}
|
|
|
|
func (p *Parser) parseShort(s *parseState, optname string, argument *string) (option *Option, err error) {
|
|
if argument == nil {
|
|
optname, argument = p.splitShortConcatArg(s, optname)
|
|
}
|
|
|
|
for i, c := range optname {
|
|
shortname := string(c)
|
|
|
|
if option = s.lookup.shortNames[shortname]; option != nil {
|
|
// Only the last short argument can consume an argument from
|
|
// the arguments list, and only if it's non optional
|
|
canarg := (i+utf8.RuneLen(c) == len(optname)) && !option.OptionalArgument
|
|
|
|
if _, err := p.parseOption(s, shortname, option, canarg, argument); err != nil {
|
|
return option, err
|
|
}
|
|
} else {
|
|
return nil, newError(ErrUnknownFlag, fmt.Sprintf("unknown flag `%s'", shortname))
|
|
}
|
|
|
|
// Only the first option can have a concatted argument, so just
|
|
// clear argument here
|
|
argument = nil
|
|
}
|
|
|
|
return option, nil
|
|
}
|
|
|
|
func (p *Parser) parseNonOption(s *parseState) error {
|
|
if cmd := s.lookup.commands[s.arg]; cmd != nil {
|
|
if err := s.checkRequired(); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.command.Active = cmd
|
|
|
|
s.command = cmd
|
|
s.lookup = cmd.makeLookup()
|
|
} else if (p.Options & PassAfterNonOption) != None {
|
|
// If PassAfterNonOption is set then all remaining arguments
|
|
// are considered positional
|
|
s.retargs = append(append(s.retargs, s.arg), s.args...)
|
|
s.args = []string{}
|
|
} else {
|
|
s.retargs = append(s.retargs, s.arg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) showBuiltinHelp() error {
|
|
var b bytes.Buffer
|
|
|
|
p.WriteHelp(&b)
|
|
return newError(ErrHelp, b.String())
|
|
}
|
|
|
|
func (p *Parser) printError(err error) error {
|
|
if err != nil && (p.Options&PrintErrors) != None {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
}
|
|
|
|
return err
|
|
}
|