Francisco J Ballesteroszxfs put fix1449f3310 years agoHistory
640 lines · 14.8 KB · Go
RawDownload
/*	command line options, in the unix style and working on any	argv[] array. 	The functions that define new flags operate in the same way:	They define a new flag and, if a pointer is given, the value pointed to	is set when the flag is present in the command line.	They call Fatal if a flag is defined twice. 	Flags should be defined by the same process, there is no mutex.*/package opt import (	"bytes"	"clive/cmd"	"errors"	"fmt"	"os"	"sort"	"strconv"	"strings"	"time"	"unicode/utf8") struct def {	name, help string	valp       face{}	argname    string} // A set of command line optionsstruct Flags {	Argv0       string // program name from the last call to Parse	usage       string // usage string w/o program name	defs        map[string]*def	plus, minus *def   // defs for +int -int	xtra        string // extra usage info} // Use Counter as the value for counting flags, which are bool flags// that can be repeated. Their value is the number of repetitions.type Counter int // Use Octal as the value for an int flag expressed in octal.type Octal int // Use Hexa as the value for an int flag expressed in hexa.type Hexa int // A range: items are counted starting from 1// and neg. values are used to count backwards from the end:// 1,1 means everything.struct Range {	P0, P1 int} // Preferred default time format for commands.const TimeFormat = "2006/0102 15:04" // time formats for option argumentsvar tfmts = []string{	"Jan 2",	"01/02",	"01/02/06",	"01/02/2006",	"Jan 2 2006",	"2006/0102",	"15:04:05",	"15:04",	"Jan 2 15:04",	"3pm",	"3:04pm",	"Jan 2 3pm",	"Jan 2 3:04pm",	"01/02 15:04",	"01/02/06 15:04",	"01/02/2006 15:04",	"2006/0102 15:04",	"2006-01-02",	"2006-01-02 3pm",	"2006-01-02 3:04pm",	"2006-01-02 15:04:05",	"2006-01-02 15:04",} // Create a new set of command line options.// Default values are to be set by the caller before processing the options.// The functions define new flags and, if a pointer is given, it is set to the// option value when the option is set in the command line.func New(usage string) *Flags {	return &Flags{		defs:  map[string]*def{},		usage: usage,	}} func (f *Flags) AddUsage(xtra string) {	f.xtra += xtra} func (f *Flags) optUsage(order []string) string {	flgs := ""	for _, kn := range order {		k := f.defs[kn]		switch k.valp.(type) {		case *bool:			if utf8.RuneCountInString(k.name) == 1 {				flgs += k.name			}		}	}	var buf bytes.Buffer	if flgs != "" {		fmt.Fprintf(&buf, "[-%s]", flgs)	}	flgs = ""	for _, kn := range order {		k := f.defs[kn]		switch k.valp.(type) {		case *Counter:			if utf8.RuneCountInString(k.name) == 1 {				flgs += k.name			}		}	}	if flgs != "" {		fmt.Fprintf(&buf, " {-%s}", flgs)	}	if f.plus != nil {		fmt.Fprintf(&buf, " [+%s]", f.plus.name)	}	if f.minus != nil {		fmt.Fprintf(&buf, " [-%s]", f.plus.name)	}	for _, kn := range order {		k := f.defs[kn]		switch k.valp.(type) {		case *bool, *Counter:			if utf8.RuneCountInString(k.name) == 1 {				continue			}		}		arg := "arg"		switch k.valp.(type) {		case *bool:			fmt.Fprintf(&buf, " [-%s]", k.name)			continue		case *Counter:			fmt.Fprintf(&buf, " {-%s}", k.name)			continue		case *Range:			arg = "range"		}		if i := strings.Index(k.help, ":"); i >= 0 {			arg = k.help[:i]		}		switch k.valp.(type) {		case *[]string:			fmt.Fprintf(&buf, " {-%s %s}", k.name, arg)		default:			fmt.Fprintf(&buf, " [-%s %s]", k.name, arg)		}	}	return buf.String()} // Print to stderr a description of the usage and exit.func (f *Flags) Usage() {	if f.Argv0 == "" {		f.Argv0 = os.Args[0]	}	ks := []string{}	for k := range f.defs {		ks = append(ks, k)	}	sort.Sort(sort.StringSlice(ks))	opts := f.optUsage(ks)	cmd.Eprintf("usage: %s %s %s\n", f.Argv0, opts, f.usage)	if f.plus != nil {		sep := ""		if !strings.Contains(f.plus.help, ":") {			sep = ":"		}		cmd.Eprintf("\t+%s%s %s\n", f.plus.name, sep, f.plus.help)	}	if f.minus != nil {		sep := ""		if !strings.Contains(f.minus.help, ":") {			sep = ":"		}		cmd.Eprintf("\t-%s %s\n", f.minus.name, sep, f.minus.help)	}	for _, k := range ks {		def := f.defs[k]		sep := ""		if !strings.Contains(def.help, ":") {			sep = ":"		}		switch def.valp.(type) {		case *Counter:			cmd.Eprintf("\t-%s%s %s\n", def.name, sep, def.help)			cmd.Eprintf("\t\tcan be repeated\n")		default:			cmd.Eprintf("\t-%s%s %s\n", def.name, sep, def.help)		}	}	if f.xtra != "" {		cmd.Eprintf("\n%s", f.xtra)	}	cmd.Exit("usage")} // Define a new flag with the given name and usage.// valuep must be a pointer to the argument type and will be set to// the command line flag value if the flag is found.// Known types are bool, int, Counter, Octal, Hexa, int64, uint64, string,// float64, time.Duration, time.Time, and []string.// []string is a string option that may be repeated.// The time formats understood are//	"01/02"//	"01/02/06"//	"01/02/2006"//	"2006/0102"//	"15:04:05"//	"15:04"//	"3pm"//	"3:04pm"//	"01/02 15:04"//	"01/02/06 15:04"//	"01/02/2006 15:04"//	"2006/0102 15:04 "//// If the name is "+..." or "-..." and vp is *int, then it is understood as// a request to accept +number or -number as an argument.//// The help string should just describe the flag for flags with no// argument, and should be something like "dir: do this with dir"// if the flag accepts a "dir" argument. This convention is used to generate// a good usage diagnostic.func (f *Flags) NewFlag(name, help string, vp face{}) {	if vp == nil {		cmd.Fatal("flag %s: nil value", name)	}	if len(name) == 0 {		cmd.Fatal("empty flag name")	}	if f.defs[name] != nil {		cmd.Fatal("flag %s redefined", name)	}	aname := ""	if i := strings.Index(help, ":"); i > 0 {		aname = help[:i]	}	switch vp.(type) {	case *bool: 	case *int:		if aname == "" {			aname = "num"		}		if name[0] == '+' {			if f.plus != nil {				cmd.Fatal("flag +number redefined")			}			f.plus = &def{name: name, help: help, valp: vp, argname: aname}			return		}		if name[0] == '-' {			if f.minus != nil {				cmd.Fatal("flag -number redefined")			}			f.minus = &def{name: name, help: help, valp: vp, argname: aname}			return		}	case *Counter:	case *Octal, *Hexa, *int64, *uint64, *float64:		if aname == "" {			aname = "num"		}	case *string, *[]string:		if aname == "" {			aname = "str"		}	case *time.Duration:		if aname == "" {			aname = "ival"		}	case *time.Time:		if aname == "" {			aname = "time"		}	case *Range:		if aname == "" {			aname = "range"		}	default:		cmd.Fatal("flag %s: unknown flag type", name)	}	if name[0] == '+' || name[0] == '-' {		cmd.Fatal("name '±...' is only for *int")	}	f.defs[name] = &def{name: name, help: help, valp: vp, argname: aname}} // Parse argv for the the flags and return the resulting argument vector w/o flags.// The first entry in argv is the program name.// A "--" argument terminates the options.// A "-?" argument fails with a "usage" error// If argv is nil, it is taken from the current cmd context.// An error in parsing calls Usage() and terminates execution.func (f *Flags) Parse(argv ...string) []string {	var err error	if len(argv) == 0 {		c := cmd.AppCtx()		argv = c.Args	}	f.Argv0 = argv[0]	argv = argv[1:]	args := make([]string, len(argv))	copy(args, argv)Loop:	for len(args) > 0 && len(args[0]) > 0 && (args[0][0] == '-' || args[0][0] == '+') {		if args[0] == "-?" {			f.Usage()		}		if f.plus != nil && args[0][0] == '+' {			args, err = f.plus.parsePlus(args)			if err != nil {				cmd.Warn("%s", err)				f.Usage()			}			continue Loop		}		isdigit := len(args[0]) > 1 && args[0][1] >= '0' && args[0][1] <= '9'		if f.minus != nil && isdigit {			args, err = f.minus.parseMinus(args)			if err != nil {				cmd.Warn("%s", err)				f.Usage()			}			continue Loop		}		args[0] = args[0][1:] // drop "-"		name := args[0]		if name == "" {			cmd.Warn("'-' supplied without option name")			f.Usage()		}		if name == "-" {			return args[1:]		}		// try full-names first		for n, def := range f.defs {			if name == n {				args, err = def.parse(args)				if err != nil {					cmd.Warn("%s", err)					f.Usage()				}				continue Loop			}		}		// try combined flags now		for len(args) > 0 && len(args[0]) > 0 {			r, nr := utf8.DecodeRuneInString(args[0])			name = args[0][:nr]			for n, def := range f.defs {				if name == n {					args, err = def.parse(args)					if err != nil {						cmd.Warn("%s", err)						f.Usage()					}					continue Loop				}			}			// no flag defined for the rune			cmd.Warn("unknown option '%c'", r)			f.Usage()		}	}	return args} func optArg(argv []string) ([]string, string, error) {	if len(argv) > 0 && len(argv[0]) == 0 {		argv = argv[1:]	}	if len(argv) == 0 {		return argv, "", errors.New("no argument")	}	return argv[1:], argv[0], nil} // argv is 1st opt. argument with the + or - present.func (d def) parsePlus(argv []string) ([]string, error) {	if d.valp == nil {		return nil, fmt.Errorf("undefined value for option '+%s'", d.name)	}	arg := argv[0][1:]	v, err := strconv.ParseInt(arg, 0, 0)	if err != nil {		return nil, fmt.Errorf("option '+%s': wrong value '%s'", d.name, arg)	}	switch vp := d.valp.(type) {	case *int:		*vp = int(v)		return argv[1:], nil	default:		return nil, fmt.Errorf("+%s:bug for type %T", d.name, vp)	}} // argv is 1st opt. argument with the + or - present.func (d def) parseMinus(argv []string) ([]string, error) {	if d.valp == nil {		return nil, fmt.Errorf("undefined value for option '-%s'", d.name)	}	arg := argv[0][1:]	v, err := strconv.ParseInt(arg, 0, 0)	if err != nil {		return nil, fmt.Errorf("option '-%s': wrong value '%s'", d.name, arg)	}	switch vp := d.valp.(type) {	case *int:		*vp = -int(v)		return argv[1:], nil	default:		return nil, fmt.Errorf("-%s: bug for type %T", d.name, vp)	}} // argv has the 1st -option argument, with the - and the option name removed.// Set the value for the option, if any, removing any argument used or return an error.// Changes argv even on errors.func (d *def) parse(argv []string) ([]string, error) {	if d.valp == nil {		return nil, fmt.Errorf("undefined value for option '%s'", d.name)	}	argv[0] = argv[0][len(d.name):]	switch vp := d.valp.(type) {	case *bool:		*vp = true		if len(argv[0]) == 0 {			argv = argv[1:]		} else { // put back the "-" for the next flag			argv[0] = "-" + argv[0]		}	case *Counter:		*vp = *vp + 1		if len(argv[0]) == 0 {			argv = argv[1:]		} else { // put back the "-" for the next flag			argv[0] = "-" + argv[0]		}	case *Octal:		nargv, arg, err := optArg(argv)		if err != nil {			return nil, fmt.Errorf("option '%s': %s", d.name, err)		}		argv = nargv		v, err := strconv.ParseInt(arg, 8, 0)		if err != nil {			return nil, fmt.Errorf("option '%s': wrong value '%s'", d.name, arg)		}		*vp = Octal(v)	case *Hexa:		nargv, arg, err := optArg(argv)		if err != nil {			return nil, fmt.Errorf("option '%s': %s", d.name, err)		}		argv = nargv		v, err := strconv.ParseInt(arg, 16, 0)		if err != nil {			return nil, fmt.Errorf("option '%s': wrong value '%s'", d.name, arg)		}		*vp = Hexa(v)	case *int:		nargv, arg, err := optArg(argv)		if err != nil {			return nil, fmt.Errorf("option '%s': %s", d.name, err)		}		argv = nargv		v, err := strconv.ParseInt(arg, 0, 0)		if err != nil {			return nil, fmt.Errorf("option '%s': wrong value '%s'", d.name, arg)		}		*vp = int(v)	case *int64:		nargv, arg, err := optArg(argv)		if err != nil {			return nil, fmt.Errorf("option '%s': %s", d.name, err)		}		argv = nargv		*vp, err = strconv.ParseInt(arg, 0, 64)		if err != nil {			return nil, fmt.Errorf("option '%s': wrong value '%s'", d.name, arg)		}

Lines 1–500 of 640.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t