Merge pull request #88 from bogem/fixes

Some minor fixes
diff --git a/.travis.yml b/.travis.yml
index 580ad22..0a7c136 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,8 +5,12 @@
 go:
         - 1.5.4
         - 1.6.3
+        - 1.7
         - tip
 
+matrix:
+        allow_failures:
+                  - go: tip
 install:
         - go get github.com/golang/lint/golint
         - export PATH=$GOPATH/bin:$PATH
diff --git a/flag.go b/flag.go
index 965df13..eb143d7 100644
--- a/flag.go
+++ b/flag.go
@@ -419,20 +419,26 @@
 	fmt.Fprintf(f.out(), "%s", usages)
 }
 
-// isZeroValue guesses whether the string represents the zero
-// value for a flag. It is not accurate but in practice works OK.
-func isZeroValue(value string) bool {
-	switch value {
-	case "false":
-		return true
-	case "<nil>":
-		return true
-	case "":
-		return true
-	case "0":
+// defaultIsZeroValue returns true if the default value for this flag represents
+// a zero value.
+func (f *Flag) defaultIsZeroValue() bool {
+	switch f.Value.(type) {
+	case boolFlag:
+		return f.DefValue == "false"
+	case *durationValue:
+		// Beginning in Go 1.7, duration zero values are "0s"
+		return f.DefValue == "0" || f.DefValue == "0s"
+	case *intValue, *int8Value, *int32Value, *int64Value, *uintValue, *uint8Value, *uint16Value, *uint32Value, *uint64Value, *countValue, *float32Value, *float64Value:
+		return f.DefValue == "0"
+	case *stringValue:
+		return f.DefValue == ""
+	case *ipValue, *ipMaskValue, *ipNetValue:
+		return f.DefValue == "<nil>"
+	case *intSliceValue, *stringSliceValue:
+		return f.DefValue == "[]"
+	default:
 		return true
 	}
-	return false
 }
 
 // UnquoteUsage extracts a back-quoted name from the usage
@@ -455,22 +461,19 @@
 			break // Only one back quote; use type name.
 		}
 	}
-	// No explicit name, so use type if we can find one.
-	name = "value"
-	switch flag.Value.(type) {
-	case boolFlag:
+
+	name = flag.Value.Type()
+	switch name {
+	case "bool":
 		name = ""
-	case *durationValue:
-		name = "duration"
-	case *float64Value:
+	case "float64":
 		name = "float"
-	case *intValue, *int64Value:
+	case "int64":
 		name = "int"
-	case *stringValue:
-		name = "string"
-	case *uintValue, *uint64Value:
+	case "uint64":
 		name = "uint"
 	}
+
 	return
 }
 
@@ -519,7 +522,7 @@
 		}
 
 		line += usage
-		if !isZeroValue(flag.DefValue) {
+		if !flag.defaultIsZeroValue() {
 			if flag.Value.Type() == "string" {
 				line += fmt.Sprintf(" (default %q)", flag.DefValue)
 			} else {
diff --git a/flag_test.go b/flag_test.go
index 0ae2e4f..86b37e5 100644
--- a/flag_test.go
+++ b/flag_test.go
@@ -13,6 +13,7 @@
 	"os"
 	"reflect"
 	"sort"
+	"strconv"
 	"strings"
 	"testing"
 	"time"
@@ -873,19 +874,38 @@
 	}
 }
 
-const defaultOutput = `      --A                    for bootstrapping, allow 'any' type
-      --Alongflagname        disable bounds checking
-  -C, --CCC                  a boolean defaulting to true (default true)
-      --D path               set relative path for local imports
-      --F number             a non-zero number (default 2.7)
-      --G float              a float that defaults to zero
-      --N int                a non-zero int (default 27)
-      --ND1 string[="bar"]   a string with NoOptDefVal (default "foo")
-      --ND2 num[=4321]       a num with NoOptDefVal (default 1234)
-      --Z int                an int that defaults to zero
-      --maxT timeout         set timeout for dial
+const defaultOutput = `      --A                     for bootstrapping, allow 'any' type
+      --Alongflagname         disable bounds checking
+  -C, --CCC                   a boolean defaulting to true (default true)
+      --D path                set relative path for local imports
+      --F number              a non-zero number (default 2.7)
+      --G float               a float that defaults to zero
+      --IP ip                 IP address with no default
+      --IPMask ipMask         Netmask address with no default
+      --IPNet ipNet           IP network with no default
+      --Ints intSlice         int slice with zero default
+      --N int                 a non-zero int (default 27)
+      --ND1 string[="bar"]    a string with NoOptDefVal (default "foo")
+      --ND2 num[=4321]        a num with NoOptDefVal (default 1234)
+      --Strings stringSlice   string slice with zero default
+      --Z int                 an int that defaults to zero
+      --custom custom         custom Value implementation
+      --maxT timeout          set timeout for dial
 `
 
+// Custom value that satisfies the Value interface.
+type customValue int
+
+func (cv *customValue) String() string { return fmt.Sprintf("%v", *cv) }
+
+func (cv *customValue) Set(s string) error {
+	v, err := strconv.ParseInt(s, 0, 64)
+	*cv = customValue(v)
+	return err
+}
+
+func (cv *customValue) Type() string { return "custom" }
+
 func TestPrintDefaults(t *testing.T) {
 	fs := NewFlagSet("print defaults test", ContinueOnError)
 	var buf bytes.Buffer
@@ -897,12 +917,21 @@
 	fs.Float64("F", 2.7, "a non-zero `number`")
 	fs.Float64("G", 0, "a float that defaults to zero")
 	fs.Int("N", 27, "a non-zero int")
+	fs.IntSlice("Ints", []int{}, "int slice with zero default")
+	fs.IP("IP", nil, "IP address with no default")
+	fs.IPMask("IPMask", nil, "Netmask address with no default")
+	fs.IPNet("IPNet", net.IPNet{}, "IP network with no default")
 	fs.Int("Z", 0, "an int that defaults to zero")
 	fs.Duration("maxT", 0, "set `timeout` for dial")
 	fs.String("ND1", "foo", "a string with NoOptDefVal")
 	fs.Lookup("ND1").NoOptDefVal = "bar"
 	fs.Int("ND2", 1234, "a `num` with NoOptDefVal")
 	fs.Lookup("ND2").NoOptDefVal = "4321"
+	fs.StringSlice("Strings", []string{}, "string slice with zero default")
+
+	var cv customValue
+	fs.Var(&cv, "custom", "custom Value implementation")
+
 	fs.PrintDefaults()
 	got := buf.String()
 	if got != defaultOutput {
diff --git a/string_slice.go b/string_slice.go
index f2a49d9..927a440 100644
--- a/string_slice.go
+++ b/string_slice.go
@@ -1,6 +1,7 @@
 package pflag
 
 import (
+	"bytes"
 	"encoding/csv"
 	"fmt"
 	"strings"
@@ -48,7 +49,13 @@
 	return "stringSlice"
 }
 
-func (s *stringSliceValue) String() string { return "[" + strings.Join(*s.value, ",") + "]" }
+func (s *stringSliceValue) String() string {
+	b := &bytes.Buffer{}
+	w := csv.NewWriter(b)
+	w.Write(*s.value)
+	w.Flush()
+	return "[" + strings.TrimSuffix(b.String(), fmt.Sprintln()) + "]"
+}
 
 func stringSliceConv(sval string) (interface{}, error) {
 	sval = strings.Trim(sval, "[]")
@@ -56,8 +63,7 @@
 	if len(sval) == 0 {
 		return []string{}, nil
 	}
-	v := strings.Split(sval, ",")
-	return v, nil
+	return readAsCSV(sval)
 }
 
 // GetStringSlice return the []string value of a flag with the given name
diff --git a/string_slice_test.go b/string_slice_test.go
index ed7cbfc..26118c7 100644
--- a/string_slice_test.go
+++ b/string_slice_test.go
@@ -150,29 +150,66 @@
 	if err != nil {
 		t.Fatal("expected no error; got", err)
 	}
+
+	if len(expected) != len(ss) {
+		t.Fatalf("expected number of ss to be %d but got: %d", len(expected), len(ss))
+	}
 	for i, v := range ss {
 		if expected[i] != v {
 			t.Fatalf("expected ss[%d] to be %s but got: %s", i, expected[i], v)
 		}
 	}
+
+	values, err := f.GetStringSlice("ss")
+	if err != nil {
+		t.Fatal("expected no error; got", err)
+	}
+
+	if len(expected) != len(values) {
+		t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(ss))
+	}
+	for i, v := range values {
+		if expected[i] != v {
+			t.Fatalf("expected got ss[%d] to be %s but got: %s", i, expected[i], v)
+		}
+	}
 }
 
 func TestSSWithComma(t *testing.T) {
 	var ss []string
 	f := setUpSSFlagSet(&ss)
 
-	in := []string{`"one,two"`, `"three"`}
-	expected := []string{"one,two", "three"}
+	in := []string{`"one,two"`, `"three"`, `"four,five",six`}
+	expected := []string{"one,two", "three", "four,five", "six"}
 	argfmt := "--ss=%s"
 	arg1 := fmt.Sprintf(argfmt, in[0])
 	arg2 := fmt.Sprintf(argfmt, in[1])
-	err := f.Parse([]string{arg1, arg2})
+	arg3 := fmt.Sprintf(argfmt, in[2])
+	err := f.Parse([]string{arg1, arg2, arg3})
 	if err != nil {
 		t.Fatal("expected no error; got", err)
 	}
+
+	if len(expected) != len(ss) {
+		t.Fatalf("expected number of ss to be %d but got: %d", len(expected), len(ss))
+	}
 	for i, v := range ss {
 		if expected[i] != v {
 			t.Fatalf("expected ss[%d] to be %s but got: %s", i, expected[i], v)
 		}
 	}
+
+	values, err := f.GetStringSlice("ss")
+	if err != nil {
+		t.Fatal("expected no error; got", err)
+	}
+
+	if len(expected) != len(values) {
+		t.Fatalf("expected number of values to be %d but got: %d", len(expected), len(values))
+	}
+	for i, v := range values {
+		if expected[i] != v {
+			t.Fatalf("expected got ss[%d] to be %s but got: %s", i, expected[i], v)
+		}
+	}
 }