Optional default values from environment variables

Tags `env:"VARIABLE_NAME"` and `env-delim:"DELIM_STRING"` can
be optionally specified to get the option default value from
the corresponding environment variable.

The delimiter, if one is defined, is used to split the value
to multiple strings, making it possible to define default values
for both slices and maps thru the environment.
diff --git a/flags.go b/flags.go
index 68173e7..eb2012f 100644
--- a/flags.go
+++ b/flags.go
@@ -17,6 +17,7 @@
     Options with long names (--verbose)
     Options with and without arguments (bool v.s. other type)
     Options with optional arguments and default values
+    Option default values from ENVIRONMENT_VARIABLES, including slice and map values
     Multiple option groups each containing a set of options
     Generate and print well-formatted help message
     Passing remaining command line arguments after -- (optional)
@@ -95,6 +96,12 @@
                     showing up in the help. If default-mask takes the special
                     value "-", then no default value will be shown at all
                     (optional)
+    env:            the default value of the option is overridden from the
+                    specified environment variable, if one has been defined.
+                    (optional)
+    env-delim:      the 'env' default value from environment is split into
+                    multiple values with the given delimiter string, use with
+                    slices and maps (optional)
     value-name:     the name of the argument value (to be shown in the help,
                     (optional)
 
diff --git a/group_private.go b/group_private.go
index 26ee92a..15251ce 100644
--- a/group_private.go
+++ b/group_private.go
@@ -124,6 +124,7 @@
 
 		description := mtag.Get("description")
 		def := mtag.GetMany("default")
+
 		optionalValue := mtag.GetMany("optional-value")
 		valueName := mtag.Get("value-name")
 		defaultMask := mtag.Get("default-mask")
@@ -136,6 +137,8 @@
 			ShortName:        short,
 			LongName:         longname,
 			Default:          def,
+			EnvDefaultKey:    mtag.Get("env"),
+			EnvDefaultDelim:  mtag.Get("env-delim"),
 			OptionalArgument: optional,
 			OptionalValue:    optionalValue,
 			Required:         required,
diff --git a/help.go b/help.go
index 0139215..d16f3b1 100644
--- a/help.go
+++ b/help.go
@@ -10,6 +10,7 @@
 	"fmt"
 	"io"
 	"reflect"
+	"runtime"
 	"strings"
 	"unicode/utf8"
 )
@@ -173,12 +174,24 @@
 			def = strings.Join(defs, ", ")
 		}
 
+		var envDef string
+		if option.EnvDefaultKey != "" {
+			var envPrintable string
+			if runtime.GOOS == "windows" {
+				envPrintable = "%" + option.EnvDefaultKey + "%"
+			} else {
+				envPrintable = "$" + option.EnvDefaultKey
+			}
+			envDef = fmt.Sprintf(" [%s]", envPrintable)
+		}
+
 		var desc string
 
 		if def != "" {
-			desc = fmt.Sprintf("%s (%v)", option.Description, def)
+			desc = fmt.Sprintf("%s (%v)%s", option.Description, def,
+				envDef)
 		} else {
-			desc = option.Description
+			desc = option.Description + envDef
 		}
 
 		writer.WriteString(wrapText(desc,
diff --git a/help_test.go b/help_test.go
index 72b3e81..49ee5b4 100644
--- a/help_test.go
+++ b/help_test.go
@@ -54,6 +54,8 @@
 	Default      string            `long:"default" default:"Some value" description:"Test default value"`
 	DefaultArray []string          `long:"default-array" default:"Some value" default:"Another value" description:"Test default array value"`
 	DefaultMap   map[string]string `long:"default-map" default:"some:value" default:"another:value" description:"Testdefault map value"`
+	EnvDefault1  string            `long:"env-default1" default:"Some value" env:"ENV_DEFAULT" description:"Test env-default1 value"`
+	EnvDefault2  string            `long:"env-default2" env:"ENV_DEFAULT" description:"Test env-default2 value"`
 
 	OnlyIni string `ini-name:"only-ini" description:"Option only available in ini"`
 
@@ -81,8 +83,11 @@
 }
 
 func TestHelp(t *testing.T) {
-	var opts helpOptions
+	oldEnv := EnvSnapshot()
+	defer oldEnv.Restore()
+	os.Setenv("ENV_DEFAULT", "env-def")
 
+	var opts helpOptions
 	p := NewNamedParser("TestHelp", HelpFlag)
 	p.AddGroup("Application Options", "The application options", &opts)
 
@@ -113,6 +118,8 @@
       /default:            Test default value (Some value)
       /default-array:      Test default array value (Some value, Another value)
       /default-map:        Testdefault map value (some:value, another:value)
+      /env-default1:       Test env-default1 value (Some value) [%ENV_DEFAULT%]
+      /env-default2:       Test env-default2 value [%ENV_DEFAULT%]
 
 Other Options:
   /s:                      A slice of strings (some, value)
@@ -147,6 +154,8 @@
       --default=           Test default value (Some value)
       --default-array=     Test default array value (Some value, Another value)
       --default-map=       Testdefault map value (some:value, another:value)
+      --env-default1=      Test env-default1 value (Some value) [$ENV_DEFAULT]
+      --env-default2=      Test env-default2 value [$ENV_DEFAULT]
 
 Other Options:
   -s=                      A slice of strings (some, value)
@@ -184,8 +193,11 @@
 }
 
 func TestMan(t *testing.T) {
-	var opts helpOptions
+	oldEnv := EnvSnapshot()
+	defer oldEnv.Restore()
+	os.Setenv("ENV_DEFAULT", "env-def")
 
+	var opts helpOptions
 	p := NewNamedParser("TestMan", HelpFlag)
 	p.ShortDescription = "Test manpage generation"
 	p.LongDescription = "This is a somewhat `longer' description of what this does"
@@ -229,6 +241,12 @@
 \fB--default-map\fP
 Testdefault map value
 .TP
+\fB--env-default1\fP
+Test env-default1 value
+.TP
+\fB--env-default2\fP
+Test env-default2 value
+.TP
 \fB-s\fP
 A slice of strings
 .TP
@@ -273,8 +291,11 @@
 }
 
 func TestHelpCommand(t *testing.T) {
-	var opts helpCommandNoOptions
+	oldEnv := EnvSnapshot()
+	defer oldEnv.Restore()
+	os.Setenv("ENV_DEFAULT", "env-def")
 
+	var opts helpCommandNoOptions
 	p := NewNamedParser("TestHelpCommand", HelpFlag)
 	p.AddGroup("Application Options", "The application options", &opts)
 
diff --git a/ini_test.go b/ini_test.go
index eb752e9..cb88b64 100644
--- a/ini_test.go
+++ b/ini_test.go
@@ -9,6 +9,10 @@
 )
 
 func TestWriteIni(t *testing.T) {
+	oldEnv := EnvSnapshot()
+	defer oldEnv.Restore()
+	os.Setenv("ENV_DEFAULT", "env-def")
+
 	var tests = []struct {
 		args     []string
 		options  IniOptions
@@ -22,6 +26,12 @@
 verbose = true
 verbose = true
 
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
 [Other Options]
 ; A map from string to int
 int-map = a:2
@@ -53,6 +63,12 @@
 DefaultMap = another:value
 DefaultMap = some:value
 
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
 ; Option only available in ini
 only-ini =
 
@@ -102,6 +118,12 @@
 ; DefaultMap = another:value
 ; DefaultMap = some:value
 
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
 ; Option only available in ini
 ; only-ini =
 
@@ -148,6 +170,12 @@
 ; Testdefault map value
 DefaultMap = new:value
 
+; Test env-default1 value
+EnvDefault1 = env-def
+
+; Test env-default2 value
+EnvDefault2 = env-def
+
 ; Option only available in ini
 ; only-ini =
 
diff --git a/option.go b/option.go
index 325d9c8..26f2441 100644
--- a/option.go
+++ b/option.go
@@ -27,6 +27,12 @@
 	// The default value of the option.
 	Default []string
 
+	// The optional environment default value key name.
+	EnvDefaultKey string
+
+	// The optional delimiter string for EnvDefaultKey values.
+	EnvDefaultDelim string
+
 	// If true, specifies that the argument to an option flag is optional.
 	// When no argument to the flag is specified on the command line, the
 	// value of Default will be set in the field this option represents.
diff --git a/option_private.go b/option_private.go
index c6b7c0d..d36c841 100644
--- a/option_private.go
+++ b/option_private.go
@@ -2,6 +2,8 @@
 
 import (
 	"reflect"
+	"strings"
+	"syscall"
 )
 
 // Set the value of an option to the specified value. An error will be returned
@@ -48,10 +50,24 @@
 }
 
 func (option *Option) clearDefault() {
-	if len(option.Default) > 0 {
+	usedDefault := option.Default
+	if envKey := option.EnvDefaultKey; envKey != "" {
+		// os.Getenv() makes no distinction between undefined and
+		// empty values, so we use syscall.Getenv()
+		if value, ok := syscall.Getenv(envKey); ok {
+			if option.EnvDefaultDelim != "" {
+				usedDefault = strings.Split(value,
+					option.EnvDefaultDelim)
+			} else {
+				usedDefault = []string{value}
+			}
+		}
+	}
+
+	if len(usedDefault) > 0 {
 		option.empty()
 
-		for _, d := range option.Default {
+		for _, d := range usedDefault {
 			option.set(&d)
 		}
 	} else {
diff --git a/parser_test.go b/parser_test.go
index b227244..add87d8 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -1,7 +1,9 @@
 package flags
 
 import (
+	"os"
 	"reflect"
+	"strings"
 	"testing"
 	"time"
 )
@@ -96,3 +98,126 @@
 		}
 	}
 }
+
+// envRestorer keeps a copy of a set of env variables and can restore the env from them
+type envRestorer struct {
+	env map[string]string
+}
+
+func (r *envRestorer) Restore() {
+	os.Clearenv()
+	for k, v := range r.env {
+		os.Setenv(k, v)
+	}
+}
+
+// EnvSnapshot returns a snapshot of the currently set env variables
+func EnvSnapshot() *envRestorer {
+	r := envRestorer{make(map[string]string)}
+	for _, kv := range os.Environ() {
+		parts := strings.SplitN(kv, "=", 2)
+		if len(parts) != 2 {
+			panic("got a weird env variable: " + kv)
+		}
+		r.env[parts[0]] = parts[1]
+	}
+	return &r
+}
+
+type envDefaultOptions struct {
+	Int   int            `long:"i" default:"1" env:"TEST_I"`
+	Time  time.Duration  `long:"t" default:"1m" env:"TEST_T"`
+	Map   map[string]int `long:"m" default:"a:1" env:"TEST_M" env-delim:";"`
+	Slice []int          `long:"s" default:"1" default:"2" env:"TEST_S"  env-delim:","`
+}
+
+func TestEnvDefaults(t *testing.T) {
+	var tests = []struct {
+		msg      string
+		args     []string
+		expected envDefaultOptions
+		env      map[string]string
+	}{
+		{
+			msg:  "no arguments, no env, expecting default values",
+			args: []string{},
+			expected: envDefaultOptions{
+				Int:   1,
+				Time:  time.Minute,
+				Map:   map[string]int{"a": 1},
+				Slice: []int{1, 2},
+			},
+		},
+		{
+			msg:  "no arguments, env defaults, expecting env default values",
+			args: []string{},
+			expected: envDefaultOptions{
+				Int:   2,
+				Time:  2 * time.Minute,
+				Map:   map[string]int{"a": 2, "b": 3},
+				Slice: []int{4, 5, 6},
+			},
+			env: map[string]string{
+				"TEST_I": "2",
+				"TEST_T": "2m",
+				"TEST_M": "a:2;b:3",
+				"TEST_S": "4,5,6",
+			},
+		},
+		{
+			msg:  "non-zero value arguments, expecting overwritten arguments",
+			args: []string{"--i=3", "--t=3ms", "--m=c:3", "--s=3"},
+			expected: envDefaultOptions{
+				Int:   3,
+				Time:  3 * time.Millisecond,
+				Map:   map[string]int{"c": 3},
+				Slice: []int{3},
+			},
+			env: map[string]string{
+				"TEST_I": "2",
+				"TEST_T": "2m",
+				"TEST_M": "a:2;b:3",
+				"TEST_S": "4,5,6",
+			},
+		},
+		{
+			msg:  "zero value arguments, expecting overwritten arguments",
+			args: []string{"--i=0", "--t=0ms", "--m=:0", "--s=0"},
+			expected: envDefaultOptions{
+				Int:   0,
+				Time:  0,
+				Map:   map[string]int{"": 0},
+				Slice: []int{0},
+			},
+			env: map[string]string{
+				"TEST_I": "2",
+				"TEST_T": "2m",
+				"TEST_M": "a:2;b:3",
+				"TEST_S": "4,5,6",
+			},
+		},
+	}
+
+	oldEnv := EnvSnapshot()
+	defer oldEnv.Restore()
+
+	for _, test := range tests {
+		var opts envDefaultOptions
+		oldEnv.Restore()
+		for envKey, envValue := range test.env {
+			os.Setenv(envKey, envValue)
+		}
+		_, err := ParseArgs(&opts, test.args)
+		if err != nil {
+			t.Fatalf("%s:\nUnexpected error: %v", test.msg, err)
+		}
+
+		if opts.Slice == nil {
+			opts.Slice = []int{}
+		}
+
+		if !reflect.DeepEqual(opts, test.expected) {
+			t.Errorf("%s:\nUnexpected options with arguments %+v\nexpected\n%+v\nbut got\n%+v\n", test.msg, test.args, test.expected, opts)
+		}
+	}
+}