One path reads a flag's value: short flags attach and bundle, = after one is rejected naming the spelling
Tests / vet + fmt + tests (pull_request) Successful in 52s
Tests / vet + fmt + tests (pull_request) Successful in 52s
This commit is contained in:
+116
-65
@@ -21,7 +21,7 @@ import (
|
||||
|
||||
const usage = `Usage: fejkdata [flags] <path>
|
||||
|
||||
<path> a category, or a dotted path into one (person, person.last)
|
||||
<path> a category, or a dotted path into one (person, person.last)
|
||||
|
||||
-d, --data-path D a data directory to layer over the shipped data (repeatable; last wins on a clash)
|
||||
-h, --help print this help, then exit
|
||||
@@ -32,7 +32,8 @@ const usage = `Usage: fejkdata [flags] <path>
|
||||
--separator S string between repeated values (default newline)
|
||||
--version print the version, then exit
|
||||
|
||||
Flags may come before or after <path>; -- ends the flags.
|
||||
Flags may come before or after <path>; -- ends the flags. A short flag's value
|
||||
attaches or follows (-n3, -n 3); short flags bundle (-hn 3).
|
||||
`
|
||||
|
||||
type invocation struct {
|
||||
@@ -98,62 +99,131 @@ func flagByShort(name string) *flagDef {
|
||||
return nil
|
||||
}
|
||||
|
||||
// flagArg is one flag as written: its definition and the value attached to it,
|
||||
// if any.
|
||||
type flagArg struct {
|
||||
def *flagDef
|
||||
value string
|
||||
inline bool
|
||||
}
|
||||
|
||||
// splitFlags reads one argv element as flags: --name or --name=value, or -abc
|
||||
// where each letter is a short flag and the first that takes a value takes the
|
||||
// rest of the element.
|
||||
func splitFlags(arg string) ([]flagArg, error) {
|
||||
if strings.HasPrefix(arg, "--") {
|
||||
name, value, inline := strings.Cut(arg[2:], "=")
|
||||
def := flagByLong(name)
|
||||
if def == nil {
|
||||
return nil, fmt.Errorf("unknown flag --%s", name)
|
||||
}
|
||||
return []flagArg{{def, value, inline}}, nil
|
||||
}
|
||||
if name, _, _ := strings.Cut(arg[1:], "="); len(name) > 1 && flagByLong(name) != nil {
|
||||
return nil, fmt.Errorf("unknown flag %s; use --%s", arg, name)
|
||||
}
|
||||
var flags []flagArg
|
||||
letters := arg[1:]
|
||||
for i := 0; i < len(letters); i++ {
|
||||
letter := letters[i : i+1]
|
||||
def := flagByShort(letter)
|
||||
if def == nil {
|
||||
return nil, fmt.Errorf("unknown flag -%s", letter)
|
||||
}
|
||||
if !def.value {
|
||||
flags = append(flags, flagArg{def: def})
|
||||
continue
|
||||
}
|
||||
rest := letters[i+1:]
|
||||
if strings.HasPrefix(rest, "=") {
|
||||
return nil, fmt.Errorf("-%s takes its value attached (-%s%s) or next (-%s %s); = belongs to --%s=%s",
|
||||
letter, letter, rest[1:], letter, rest[1:], def.long, rest[1:])
|
||||
}
|
||||
return append(flags, flagArg{def, rest, rest != ""}), nil
|
||||
}
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
func parseArgs(argv []string) (invocation, error) {
|
||||
in := invocation{repeat: 1, separator: "\n"}
|
||||
for i := 0; i < len(argv); i++ {
|
||||
arg := argv[i]
|
||||
switch {
|
||||
case arg == "--":
|
||||
if arg == "--" {
|
||||
in.paths = append(in.paths, argv[i+1:]...)
|
||||
return in, nil
|
||||
case strings.HasPrefix(arg, "--"):
|
||||
name, value, hasValue := strings.Cut(arg[2:], "=")
|
||||
def := flagByLong(name)
|
||||
if def == nil {
|
||||
return in, fmt.Errorf("unknown flag --%s", name)
|
||||
}
|
||||
if !def.value {
|
||||
if hasValue {
|
||||
return in, fmt.Errorf("--%s takes no value", name)
|
||||
}
|
||||
if len(arg) < 2 || arg[0] != '-' {
|
||||
in.paths = append(in.paths, arg)
|
||||
continue
|
||||
}
|
||||
flags, err := splitFlags(arg)
|
||||
if err != nil {
|
||||
return in, err
|
||||
}
|
||||
for _, fl := range flags {
|
||||
if !fl.def.value {
|
||||
if fl.inline {
|
||||
return in, fmt.Errorf("--%s takes no value", fl.def.long)
|
||||
}
|
||||
_ = def.set(&in, "")
|
||||
_ = fl.def.set(&in, "")
|
||||
continue
|
||||
}
|
||||
if !hasValue {
|
||||
value := fl.value
|
||||
if !fl.inline {
|
||||
if i++; i >= len(argv) {
|
||||
return in, fmt.Errorf("--%s needs a value", name)
|
||||
return in, fmt.Errorf("--%s needs a value", fl.def.long)
|
||||
}
|
||||
value = argv[i]
|
||||
}
|
||||
if err := def.set(&in, value); err != nil {
|
||||
if err := fl.def.set(&in, value); err != nil {
|
||||
return in, err
|
||||
}
|
||||
case len(arg) > 1 && arg[0] == '-':
|
||||
name, _, _ := strings.Cut(arg[1:], "=")
|
||||
def := flagByShort(name)
|
||||
if def == nil {
|
||||
if flagByLong(name) != nil {
|
||||
return in, fmt.Errorf("unknown flag %s; use --%s", arg, name)
|
||||
}
|
||||
return in, fmt.Errorf("unknown flag %s", arg)
|
||||
}
|
||||
if !def.value {
|
||||
_ = def.set(&in, "")
|
||||
continue
|
||||
}
|
||||
if i++; i >= len(argv) {
|
||||
return in, fmt.Errorf("--%s needs a value", def.long)
|
||||
}
|
||||
if err := def.set(&in, argv[i]); err != nil {
|
||||
return in, err
|
||||
}
|
||||
default:
|
||||
in.paths = append(in.paths, arg)
|
||||
}
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
// check rejects a flag combination that cannot run.
|
||||
func (in invocation) check() error {
|
||||
if in.noShipped && len(in.dirs) == 0 {
|
||||
return errors.New("--no-shipped-data needs at least one --data-path")
|
||||
}
|
||||
if in.list && len(in.paths) > 0 {
|
||||
return errors.New("--list takes no path")
|
||||
}
|
||||
if !in.list && len(in.paths) != 1 {
|
||||
return fmt.Errorf("expected one path, got %d", len(in.paths))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (in invocation) options() []fejkdata.Option {
|
||||
var opts []fejkdata.Option
|
||||
if in.noShipped {
|
||||
opts = append(opts, fejkdata.WithoutShippedData())
|
||||
}
|
||||
for _, dir := range in.dirs {
|
||||
opts = append(opts, fejkdata.WithDataPath(dir))
|
||||
}
|
||||
if in.seeded {
|
||||
opts = append(opts, fejkdata.WithSeed(in.seed))
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
// values renders the path repeat times, joined by the separator.
|
||||
func (in invocation) values(f *fejkdata.Generator) (string, error) {
|
||||
vals := make([]string, in.repeat)
|
||||
for i := range vals {
|
||||
v, err := f.Fake(in.paths[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
vals[i] = v
|
||||
}
|
||||
return strings.Join(vals, in.separator), nil
|
||||
}
|
||||
|
||||
func main() { os.Exit(run(os.Args[1:], os.Stdout, os.Stderr)) }
|
||||
|
||||
// run returns the exit code: 0 ok, 1 runtime error, 2 misuse.
|
||||
@@ -170,27 +240,10 @@ func run(args []string, stdout, stderr io.Writer) int {
|
||||
fmt.Fprintln(stdout, "fejkdata "+buildVersion())
|
||||
return 0
|
||||
}
|
||||
if in.noShipped && len(in.dirs) == 0 {
|
||||
return misuse(stderr, errors.New("--no-shipped-data needs at least one --data-path"))
|
||||
if err := in.check(); err != nil {
|
||||
return misuse(stderr, err)
|
||||
}
|
||||
if in.list && len(in.paths) > 0 {
|
||||
return misuse(stderr, errors.New("--list takes no path"))
|
||||
}
|
||||
if !in.list && len(in.paths) != 1 {
|
||||
return misuse(stderr, fmt.Errorf("expected one path, got %d", len(in.paths)))
|
||||
}
|
||||
|
||||
var opts []fejkdata.Option
|
||||
if in.noShipped {
|
||||
opts = append(opts, fejkdata.WithoutShippedData())
|
||||
}
|
||||
for _, dir := range in.dirs {
|
||||
opts = append(opts, fejkdata.WithDataPath(dir))
|
||||
}
|
||||
if in.seeded {
|
||||
opts = append(opts, fejkdata.WithSeed(in.seed))
|
||||
}
|
||||
f, err := fejkdata.New(opts...)
|
||||
f, err := fejkdata.New(in.options()...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
@@ -201,14 +254,12 @@ func run(args []string, stdout, stderr io.Writer) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
vals := make([]string, in.repeat)
|
||||
for i := range vals {
|
||||
if vals[i], err = f.Fake(in.paths[0]); err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
out, err := in.values(f)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, err)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintln(stdout, strings.Join(vals, in.separator))
|
||||
fmt.Fprintln(stdout, out)
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user