Merge pull request #116 from mpobrien/unknown_handler

Add field for callback to handle unknown/unmapped options during parse
diff --git a/parser.go b/parser.go
index 5a8ec92..a417ba9 100644
--- a/parser.go
+++ b/parser.go
@@ -24,6 +24,13 @@
 	// NamespaceDelimiter separates group namespaces and option long names
 	NamespaceDelimiter string
 
+	// UnknownOptionsHandler is a function which gets called when the parser
+	// encounters an unknown option. The function receives the unknown option
+	// name and the remaining command line arguments.
+	// It should return a new list of remaining arguments to continue parsing,
+	// or an error to indicate a parse failure.
+	UnknownOptionHandler func(option string, args []string) ([]string, error)
+
 	internalError error
 }
 
@@ -204,13 +211,22 @@
 			ignoreUnknown := (p.Options & IgnoreUnknown) != None
 			parseErr := wrapError(err)
 
-			if !(parseErr.Type == ErrUnknownFlag && ignoreUnknown) {
+			if parseErr.Type != ErrUnknownFlag || (!ignoreUnknown && p.UnknownOptionHandler == nil) {
 				s.err = parseErr
 				break
 			}
 
 			if ignoreUnknown {
 				s.addArgs(arg)
+			} else if p.UnknownOptionHandler != nil {
+				modifiedArgs, err := p.UnknownOptionHandler(optname, s.args)
+
+				if err != nil {
+					s.err = err
+					break
+				}
+
+				s.args = modifiedArgs
 			}
 		}
 	}
diff --git a/parser_test.go b/parser_test.go
index 3026b33..bba7576 100644
--- a/parser_test.go
+++ b/parser_test.go
@@ -1,6 +1,7 @@
 package flags
 
 import (
+	"fmt"
 	"os"
 	"reflect"
 	"strconv"
@@ -356,3 +357,54 @@
 		}
 	}
 }
+
+func TestUnknownFlagHandler(t *testing.T) {
+
+	var opts struct {
+		Flag1 string `long:"flag1"`
+		Flag2 string `long:"flag2"`
+	}
+
+	p := NewParser(&opts, None)
+
+	var unknownFlag1 string
+	var unknownFlag2 bool
+
+	// Set up a callback to intercept unknown options during parsing
+	p.UnknownOptionHandler = func(option string, args []string) ([]string, error) {
+		if option == "unknownFlag1" {
+			// consume a value from remaining args list
+			unknownFlag1 = args[0]
+			return args[1:], nil
+		} else if option == "unknownFlag2" {
+			// treat this one as a bool switch, don't consume any args
+			unknownFlag2 = true
+			return args, nil
+		}
+
+		return args, fmt.Errorf("Unknown flag: %v", option)
+	}
+
+	// Parse args containing some unknown flags, verify that
+	// our callback can handle all of them
+	_, err := p.ParseArgs([]string{"--flag1=stuff", "--unknownFlag1", "blah", "--unknownFlag2", "--flag2=foo"})
+
+	if err != nil {
+		assertErrorf(t, "Parser returned unexpected error %v", err)
+	}
+
+	assertString(t, opts.Flag1, "stuff")
+	assertString(t, opts.Flag2, "foo")
+	assertString(t, unknownFlag1, "blah")
+
+	if !unknownFlag2 {
+		assertErrorf(t, "Flag should have been set by unknown handler, but had value: %v", unknownFlag2)
+	}
+
+	// Parse args with unknown flags that callback doesn't handle, verify it returns error
+	_, err = p.ParseArgs([]string{"--flag1=stuff", "--unknownFlagX", "blah", "--flag2=foo"})
+
+	if err == nil {
+		assertErrorf(t, "Parser should have returned error, but returned nil")
+	}
+}