Merge lp:~rogpeppe/gnuflag/support-equals into lp:~gophers/gnuflag/trunk

Proposed by Roger Peppe
Status: Merged
Merged at revision: 12
Proposed branch: lp:~rogpeppe/gnuflag/support-equals
Merge into: lp:~gophers/gnuflag/trunk
Diff against target: 547 lines (+258/-172)
2 files modified
flag.go (+36/-12)
flag_test.go (+222/-160)
To merge this branch: bzr merge lp:~rogpeppe/gnuflag/support-equals
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+127469@code.launchpad.net

Description of the change

gnuflag: implement = in flags.

We should be pretty close to compatible with
all GNU flag usage now.

https://codereview.appspot.com/6598052/

To post a comment you must log in.
Revision history for this message
Roger Peppe (rogpeppe) wrote :

Reviewers: mp+127469_code.launchpad.net,

Message:
Please take a look.

Description:
gnuflag: implement = in flags.

https://code.launchpad.net/~rogpeppe/gnuflag/support-equals/+merge/127469

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/6598052/

Affected files:
   A [revision details]
   M flag.go
   M flag_test.go

Revision history for this message
Roger Peppe (rogpeppe) wrote :
lp:~rogpeppe/gnuflag/support-equals updated
14. By Roger Peppe

refine behaviour of -x=foo

Revision history for this message
Roger Peppe (rogpeppe) wrote :

i've changed it now, based on your remarks from your original CL.

-x=foo is different from how i imagined it, but
i think it's better to go with gnu-faithfullness in this case.

PTAL.

On 2 October 2012 14:16, <email address hidden> wrote:
> On 2012/10/02 12:27:23, rog wrote:
>>
>> Please take a look.
>
>
> LGTM, nice tests. Thanks.
>
> https://codereview.appspot.com/6598052/

Revision history for this message
Roger Peppe (rogpeppe) wrote :

*** Submitted:

gnuflag: implement = in flags.

We should be pretty close to compatible with
all GNU flag usage now.

R=dfc
CC=
https://codereview.appspot.com/6598052

https://codereview.appspot.com/6598052/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'flag.go'
2--- flag.go 2012-02-02 11:25:56 +0000
3+++ flag.go 2012-10-02 14:10:24 +0000
4@@ -35,6 +35,7 @@
5 "os"
6 "sort"
7 "strconv"
8+ "strings"
9 "time"
10 "unicode/utf8"
11 )
12@@ -688,16 +689,18 @@
13 }
14 }
15
16-func (f *FlagSet) parseOneGnu() (flagName string, finished bool, err error) {
17+func (f *FlagSet) parseOneGnu() (flagName string, long, finished bool, err error) {
18 if len(f.procArgs) == 0 {
19- return "", true, nil
20+ finished = true
21+ return
22 }
23
24 // processing previously encountered single-rune flag
25 if flag := f.procFlag; len(flag) > 0 {
26 _, n := utf8.DecodeRuneInString(flag)
27 f.procFlag = flag[n:]
28- return flag[0:n], false, nil
29+ flagName = flag[0:n]
30+ return
31 }
32
33 a := f.procArgs[0]
34@@ -707,25 +710,38 @@
35 if f.allowIntersperse {
36 f.args = append(f.args, a)
37 f.procArgs = f.procArgs[1:]
38- return "", false, nil
39+ return
40 }
41 f.args = append(f.args, f.procArgs...)
42 f.procArgs = nil
43- return "", true, nil
44+ finished = true
45+ return
46 }
47
48 // end of flags
49 if f.procArgs[0] == "--" {
50 f.args = append(f.args, f.procArgs[1:]...)
51 f.procArgs = nil
52- return "", true, nil
53+ finished = true
54+ return
55 }
56
57 // long flag signified with "--" prefix
58 if a[1] == '-' {
59- // TODO allow '=' so we can set bools to false?
60- flagName = a[2:]
61+ long = true
62+ i := strings.Index(a, "=")
63+ if i < 0 {
64+ f.procArgs = f.procArgs[1:]
65+ flagName = a[2:]
66+ return
67+ }
68+ flagName = a[2:i]
69+ if flagName == "" {
70+ err = fmt.Errorf("empty flag in argument %q", a)
71+ return
72+ }
73 f.procArgs = f.procArgs[1:]
74+ f.procFlag = a[i:]
75 return
76 }
77
78@@ -745,7 +761,7 @@
79 return "-" + name
80 }
81
82-func (f *FlagSet) parseGnuFlagArg(name string) (finished bool, err error) {
83+func (f *FlagSet) parseGnuFlagArg(name string, long bool) (finished bool, err error) {
84 m := f.formal
85 flag, alreadythere := m[name] // BUG
86 if !alreadythere {
87@@ -756,7 +772,9 @@
88 // TODO print --xxx when flag is more than one rune.
89 return false, f.failf("flag provided but not defined: %s", flagWithMinus(name))
90 }
91- if fv, ok := flag.Value.(*boolValue); ok { // special case: doesn't need an arg
92+ if fv, ok := flag.Value.(*boolValue); ok && !strings.HasPrefix(f.procFlag, "=") {
93+ // special case: doesn't need an arg, and an arg hasn't
94+ // been provided explicitly.
95 fv.Set("true")
96 } else {
97 // It must have a value, which might be the next argument.
98@@ -765,6 +783,12 @@
99 if f.procFlag != "" {
100 // value directly follows flag
101 value = f.procFlag
102+ if long {
103+ if value[0] != '=' {
104+ panic("no leading '=' in long flag")
105+ }
106+ value = value[1:]
107+ }
108 hasValue = true
109 f.procFlag = ""
110 }
111@@ -800,10 +824,10 @@
112 f.args = nil
113 f.allowIntersperse = allowIntersperse
114 for {
115- name, finished, err := f.parseOneGnu()
116+ name, long, finished, err := f.parseOneGnu()
117 if !finished {
118 if name != "" {
119- finished, err = f.parseGnuFlagArg(name)
120+ finished, err = f.parseGnuFlagArg(name, long)
121 }
122 }
123 if err != nil {
124
125=== modified file 'flag_test.go'
126--- flag_test.go 2012-02-02 11:13:43 +0000
127+++ flag_test.go 2012-10-02 14:10:24 +0000
128@@ -108,168 +108,228 @@
129 }
130
131 var parseTests = []struct {
132+ about string
133 intersperse bool
134 args []string
135 vals map[string]interface{}
136 remaining []string
137 error string
138-}{
139- {
140- true,
141- []string{
142- "--bool2",
143- "--int", "22",
144- "--int64", "0x23",
145- "--uint", "24",
146- "--uint64", "25",
147- "--string", "hello",
148- "--float64", "2718e28",
149- "--duration", "2m",
150- "one - extra - argument",
151- },
152- map[string]interface{}{
153- "bool": false,
154- "bool2": true,
155- "int": 22,
156- "int64": int64(0x23),
157- "uint": uint(24),
158- "uint64": uint64(25),
159- "string": "hello",
160- "float64": 2718e28,
161- "duration": 2 * 60 * time.Second,
162- },
163- []string{
164- "one - extra - argument",
165- },
166- "",
167- },
168- {
169- true,
170- []string{
171- "-a",
172- "-",
173- "-bc",
174- "2",
175- "-de1s",
176- "-f2s",
177- "-g", "3s",
178- "--h",
179- "--long",
180- "--long2", "-4s",
181- "3",
182- "4",
183- "--", "-5",
184- },
185- map[string]interface{}{
186- "a": true,
187- "b": true,
188- "c": true,
189- "d": true,
190- "e": "1s",
191- "f": "2s",
192- "g": "3s",
193- "h": true,
194- "long": true,
195- "long2": "-4s",
196- "z": "default",
197- "www": 99,
198- },
199- []string{
200- "-",
201- "2",
202- "3",
203- "4",
204- "-5",
205- },
206- "",
207- }, {
208- true,
209- []string{
210- "-a",
211- "--",
212- "-b",
213- },
214- map[string]interface{}{
215- "a": true,
216- "b": false,
217- },
218- []string{
219- "-b",
220- },
221- "",
222- }, {
223- false,
224- []string{
225- "-a",
226- "foo",
227- "-b",
228- },
229- map[string]interface{}{
230- "a": true,
231- "b": false,
232- },
233- []string{
234- "foo",
235- "-b",
236- },
237- "",
238- },
239- {
240- false,
241- []string{
242- "-a",
243- "--",
244- "foo",
245- "-b",
246- },
247- map[string]interface{}{
248- "a": true,
249- "b": false,
250- },
251- []string{
252- "foo",
253- "-b",
254- },
255- "",
256- },
257- {
258- true,
259- []string{
260- "-a",
261- "-b",
262- },
263- map[string]interface{}{
264- "a": true,
265- },
266- nil,
267- "flag provided but not defined: -b",
268- },
269- {
270- true,
271- []string{
272- "-a",
273- },
274- map[string]interface{}{
275- "a": "default",
276- },
277- nil,
278- "flag needs an argument: -a",
279- },
280- {
281- true,
282- []string{
283- "-a", "b",
284- },
285- map[string]interface{}{
286- "a": 0,
287- },
288- nil,
289- `invalid value "b" for flag -a: strconv.ParseInt: parsing "b": invalid syntax`,
290- },
291+}{{
292+ about: "regular args",
293+ intersperse: true,
294+ args: []string{
295+ "--bool2",
296+ "--int", "22",
297+ "--int64", "0x23",
298+ "--uint", "24",
299+ "--uint64", "25",
300+ "--string", "hello",
301+ "--float64", "2718e28",
302+ "--duration", "2m",
303+ "one - extra - argument",
304+ },
305+ vals: map[string]interface{}{
306+ "bool": false,
307+ "bool2": true,
308+ "int": 22,
309+ "int64": int64(0x23),
310+ "uint": uint(24),
311+ "uint64": uint64(25),
312+ "string": "hello",
313+ "float64": 2718e28,
314+ "duration": 2 * 60 * time.Second,
315+ },
316+ remaining: []string{
317+ "one - extra - argument",
318+ },
319+}, {
320+ about: "playing with -",
321+ intersperse: true,
322+ args: []string{
323+ "-a",
324+ "-",
325+ "-bc",
326+ "2",
327+ "-de1s",
328+ "-f2s",
329+ "-g", "3s",
330+ "--h",
331+ "--long",
332+ "--long2", "-4s",
333+ "3",
334+ "4",
335+ "--", "-5",
336+ },
337+ vals: map[string]interface{}{
338+ "a": true,
339+ "b": true,
340+ "c": true,
341+ "d": true,
342+ "e": "1s",
343+ "f": "2s",
344+ "g": "3s",
345+ "h": true,
346+ "long": true,
347+ "long2": "-4s",
348+ "z": "default",
349+ "www": 99,
350+ },
351+ remaining: []string{
352+ "-",
353+ "2",
354+ "3",
355+ "4",
356+ "-5",
357+ },
358+}, {
359+ about: "flag after explicit --",
360+ intersperse: true,
361+ args: []string{
362+ "-a",
363+ "--",
364+ "-b",
365+ },
366+ vals: map[string]interface{}{
367+ "a": true,
368+ "b": false,
369+ },
370+ remaining: []string{
371+ "-b",
372+ },
373+}, {
374+ about: "flag after end",
375+ args: []string{
376+ "-a",
377+ "foo",
378+ "-b",
379+ },
380+ vals: map[string]interface{}{
381+ "a": true,
382+ "b": false,
383+ },
384+ remaining: []string{
385+ "foo",
386+ "-b",
387+ },
388+}, {
389+ about: "arg and flag after explicit end",
390+ args: []string{
391+ "-a",
392+ "--",
393+ "foo",
394+ "-b",
395+ },
396+ vals: map[string]interface{}{
397+ "a": true,
398+ "b": false,
399+ },
400+ remaining: []string{
401+ "foo",
402+ "-b",
403+ },
404+}, {
405+ about: "boolean args, explicitly and non-explicitly given",
406+ args: []string{
407+ "--a=false",
408+ "--b=true",
409+ "--c",
410+ },
411+ vals: map[string]interface{}{
412+ "a": false,
413+ "b": true,
414+ "c": true,
415+ },
416+}, {
417+ about: "using =",
418+ args: []string{
419+ "--arble=bar",
420+ "--bletch=",
421+ "--a=something",
422+ "-b=other",
423+ "-cdand more",
424+ "--curdle=--milk",
425+ "--sandwich", "=",
426+ "--darn=",
427+ "=arg",
428+ },
429+ vals: map[string]interface{}{
430+ "arble": "bar",
431+ "bletch": "",
432+ "a": "something",
433+ "b": "=other",
434+ "c": true,
435+ "d": "and more",
436+ "curdle": "--milk",
437+ "sandwich": "=",
438+ "darn": "",
439+ },
440+ remaining: []string{"=arg"},
441+}, {
442+ about: "empty flag #1",
443+ args: []string{
444+ "--=bar",
445+ },
446+ error: `empty flag in argument "--=bar"`,
447+}, {
448+ about: "single-letter equals",
449+ args: []string{
450+ "-=bar",
451+ },
452+ error: `flag provided but not defined: -=`,
453+}, {
454+ about: "empty flag #2",
455+ args: []string{
456+ "--=",
457+ },
458+ error: `empty flag in argument "--="`,
459+}, {
460+ about: "no equals",
461+ args: []string{
462+ "-=",
463+ },
464+ error: `flag provided but not defined: -=`,
465+}, {
466+ args: []string{
467+ "-a=true",
468+ },
469+ vals: map[string]interface{}{
470+ "a": true,
471+ },
472+ error: `invalid value "=true" for flag -a: strconv.ParseBool: parsing "=true": invalid syntax`,
473+}, {
474+ intersperse: true,
475+ args: []string{
476+ "-a",
477+ "-b",
478+ },
479+ vals: map[string]interface{}{
480+ "a": true,
481+ },
482+ error: "flag provided but not defined: -b",
483+}, {
484+ intersperse: true,
485+ args: []string{
486+ "-a",
487+ },
488+ vals: map[string]interface{}{
489+ "a": "default",
490+ },
491+ error: "flag needs an argument: -a",
492+}, {
493+ intersperse: true,
494+ args: []string{
495+ "-a", "b",
496+ },
497+ vals: map[string]interface{}{
498+ "a": 0,
499+ },
500+ error: `invalid value "b" for flag -a: strconv.ParseInt: parsing "b": invalid syntax`,
501+},
502 }
503
504 func testParse(newFlagSet func() *FlagSet, t *testing.T) {
505 for i, g := range parseTests {
506+ t.Logf("test %d. %s", i, g.about)
507 f := newFlagSet()
508 flags := make(map[string]interface{})
509 for name, val := range g.vals {
510@@ -291,28 +351,30 @@
511 case time.Duration:
512 flags[name] = f.Duration(name, 5*time.Second, "duration value")
513 default:
514- panic(fmt.Errorf("unhandled type %T", val))
515+ t.Fatalf("unhandled type %T", val)
516 }
517 }
518 err := f.Parse(g.intersperse, g.args)
519- if err != nil {
520- if err.Error() != g.error {
521- t.Errorf("test %d; expected error %q got %q", i, g.error, err.Error())
522+ if g.error != "" {
523+ if err == nil {
524+ t.Errorf("expected error %q got nil", g.error)
525+ } else if err.Error() != g.error {
526+ t.Errorf("expected error %q got %q", g.error, err.Error())
527 }
528 continue
529 }
530 for name, val := range g.vals {
531 actual := reflect.ValueOf(flags[name]).Elem().Interface()
532 if val != actual {
533- t.Errorf("test %d: flag %q, expected %v got %v", i, name, val, actual)
534+ t.Errorf("flag %q, expected %v got %v", name, val, actual)
535 }
536 }
537 if len(f.Args()) != len(g.remaining) {
538- t.Fatalf("test %d: remaining args, expected %q got %q", i, g.remaining, f.Args())
539+ t.Fatalf("remaining args, expected %q got %q", g.remaining, f.Args())
540 }
541 for j, a := range f.Args() {
542 if a != g.remaining[j] {
543- t.Errorf("test %d: arg %d, expected %q got %q", i, j, g.remaining[i], a)
544+ t.Errorf("arg %d, expected %q got %q", j, g.remaining[i], a)
545 }
546 }
547 }

Subscribers

People subscribed via source and target branches