add types/; allow dec and hex for primitive int types
diff --git a/bool.go b/bool.go
deleted file mode 100644
index 50832ac..0000000
--- a/bool.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package gcfg
-
-import (
-	"fmt"
-	"strings"
-)
-
-type gbool bool
-
-var gboolValues = map[string]gbool{
-	"true": true, "yes": true, "on": true, "1": true,
-	"false": false, "no": false, "off": false, "0": false}
-
-func (b *gbool) UnmarshalText(text []byte) error {
-	s := strings.ToLower(string(text))
-	v, ok := gboolValues[s]
-	if !ok {
-		return fmt.Errorf("failed to parse %#q as bool", s)
-	}
-	*b = gbool(v)
-	return nil
-}
-
-var _ textUnmarshaler = new(gbool)
diff --git a/doc.go b/doc.go
index ed88f8c..a4356e1 100644
--- a/doc.go
+++ b/doc.go
@@ -60,25 +60,28 @@
 // type (that is, a type starting with `[]`); for these, each individual value
 // is parsed and added to the slice.
 //
-// Single-valued variables are handled based on the type as follows. For string
-// fields, the value string is assigned to the field, after unquoting and
-// unescaping as needed.
-//
-// For bool fields, the field is set to true if the value is "true", "yes", "on"
-// or "1", and set to false if the value is "false", "no", "off" or "0",
-// ignoring case. In addition, for single-valued bool fields, the equals sign
-// and the value can be omitted; in such case the value is set to true.
-//
+// Single-valued variables are handled based on the type as follows.
 // For types implementing the encoding.TextUnmarshaler interface, the
 // UnmarshalText method is used to set the value. Implementing this method is
 // the recommended way for parsing user-defined types.
 //
-// Predefined integer types [u]int(|8|16|32|64) are parsed as decimal, using
-// fmt.Sscanf with the "%d" verb. (This is to prevent unintuitively handling
-// zero-padded numbers as octal.) All other types are parsed using fmt.Sscanf
-// with the "%v" verb. (Note that this includes types with [u]int* as the
-// underlying type, such as os.FileMode. Implementing UnmarshalText is
-// recommended in case parsing as octal is undesirable.)
+// For fields of string kind, the value string is assigned to the field, after
+// unquoting and unescaping as needed.
+// For fields of bool kind, the field is set to true if the value is "true",
+// "yes", "on" or "1", and set to false if the value is "false", "no", "off" or
+// "0", ignoring case. In addition, for single-valued bool fields, the equals
+// sign and the value can be omitted; in such case the value is set to true.
+//
+// Predefined integer types [u]int(|8|16|32|64) are parsed as decimal or
+// hexadecimal (if having '0x' prefix). (This is to prevent unintuitively
+// handling zero-padded numbers as octal.) All other types are parsed using
+// fmt.Sscanf with the "%v" verb. (Note that this includes types having [u]int*
+// as the underlying type, such as os.FileMode, thus allowing octal values.
+// Implementing UnmarshalText is recommended in case parsing as octal is
+// undesirable.)
+//
+// The types subpackage for provides helpers for parsing "enum-like" and integer
+// types.
 //
 // TODO
 //
diff --git a/set.go b/set.go
index 93ed65d..14728cb 100644
--- a/set.go
+++ b/set.go
@@ -7,6 +7,8 @@
 	"strings"
 	"unicode"
 	"unicode/utf8"
+
+	"code.google.com/p/gcfg/types"
 )
 
 const (
@@ -42,51 +44,89 @@
 		t := newTag(f.Tag.Get("gcfg"))
 		if t.ident != "" {
 			return strings.EqualFold(t.ident, name)
-		} else {
-			return strings.EqualFold(n, fieldName)
 		}
+		return strings.EqualFold(n, fieldName)
 	})
 }
 
 type setter func(destp interface{}, val string) error
 
-var setterUnsupportedType = fmt.Errorf("unsupported type")
+var errUnsupportedType = fmt.Errorf("unsupported type")
 
 var setters = []setter{
-	stringSetter, boolSetter, textUnmarshalerSetter, scanSetter,
-}
-
-func stringSetter(d interface{}, val string) error {
-	dsp, ok := d.(*string)
-	if !ok {
-		return setterUnsupportedType
-	}
-	*dsp = val
-	return nil
+	textUnmarshalerSetter, typeSetter, kindSetter, scanSetter,
 }
 
 func textUnmarshalerSetter(d interface{}, val string) error {
 	dtu, ok := d.(textUnmarshaler)
 	if !ok {
-		return setterUnsupportedType
+		return errUnsupportedType
 	}
 	return dtu.UnmarshalText([]byte(val))
 }
 
 func boolSetter(d interface{}, val string) error {
-	dbp, ok := d.(*bool)
-	if !ok {
-		return setterUnsupportedType
+	b, err := types.ParseBool(val)
+	if err == nil {
+		reflect.ValueOf(d).Elem().Set(reflect.ValueOf(b))
 	}
-	return (*gbool)(dbp).UnmarshalText([]byte(val))
+	return err
+}
+
+func intSetterDecHex(d interface{}, val string) error {
+	return types.ParseInt(d, val, types.Dec+types.Hex)
+}
+
+func stringSetter(d interface{}, val string) error {
+	dsp, ok := d.(*string)
+	if !ok {
+		return errUnsupportedType
+	}
+	*dsp = val
+	return nil
+}
+
+var kindSetters = map[reflect.Kind]setter{
+	reflect.String: stringSetter,
+	reflect.Bool:   boolSetter,
+}
+
+var typeSetters = map[reflect.Type]setter{
+	reflect.TypeOf(int(0)):    intSetterDecHex,
+	reflect.TypeOf(int8(0)):   intSetterDecHex,
+	reflect.TypeOf(int16(0)):  intSetterDecHex,
+	reflect.TypeOf(int32(0)):  intSetterDecHex,
+	reflect.TypeOf(int64(0)):  intSetterDecHex,
+	reflect.TypeOf(uint(0)):   intSetterDecHex,
+	reflect.TypeOf(uint8(0)):  intSetterDecHex,
+	reflect.TypeOf(uint16(0)): intSetterDecHex,
+	reflect.TypeOf(uint32(0)): intSetterDecHex,
+	reflect.TypeOf(uint64(0)): intSetterDecHex,
+}
+
+func typeSetter(d interface{}, val string) error {
+	t := reflect.ValueOf(d).Elem().Type()
+	setter, ok := typeSetters[t]
+	if !ok {
+		return errUnsupportedType
+	}
+	return setter(d, val)
+}
+
+func kindSetter(d interface{}, val string) error {
+	k := reflect.ValueOf(d).Elem().Kind()
+	setter, ok := kindSetters[k]
+	if !ok {
+		return errUnsupportedType
+	}
+	return setter(d, val)
 }
 
 func scanSetter(d interface{}, val string) error {
 	t := reflect.ValueOf(d).Elem().Type()
-	verb := scanverb(t)
 	// attempt to read an extra rune to make sure the value is consumed
 	var r rune
-	n, err := fmt.Sscanf(val, "%"+string(verb)+"%c", d, &r)
+	n, err := fmt.Sscanf(val, "%v%c", d, &r)
 	switch {
 	case n < 1 || n == 1 && err != io.EOF:
 		return fmt.Errorf("failed to parse %q as %v: %v", val, t, err)
@@ -97,27 +137,6 @@
 	return nil
 }
 
-var typeVerbs = map[reflect.Type]rune{
-	reflect.TypeOf(int(0)):    'd',
-	reflect.TypeOf(int8(0)):   'd',
-	reflect.TypeOf(int16(0)):  'd',
-	reflect.TypeOf(int32(0)):  'd',
-	reflect.TypeOf(int64(0)):  'd',
-	reflect.TypeOf(uint(0)):   'd',
-	reflect.TypeOf(uint8(0)):  'd',
-	reflect.TypeOf(uint16(0)): 'd',
-	reflect.TypeOf(uint32(0)): 'd',
-	reflect.TypeOf(uint64(0)): 'd',
-}
-
-func scanverb(t reflect.Type) rune {
-	verb, ok := typeVerbs[t]
-	if !ok {
-		return 'v'
-	}
-	return verb
-}
-
 func set(cfg interface{}, sect, sub, name, value string) error {
 	vPCfg := reflect.ValueOf(cfg)
 	if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct {
@@ -176,12 +195,12 @@
 			ok = true
 			break
 		}
-		if err != setterUnsupportedType {
+		if err != errUnsupportedType {
 			return err
 		}
 	}
 	if !ok {
-		// in case all setters returned setterUnsupportedType
+		// in case all setters returned errUnsupportedType
 		return err
 	}
 	if isMulti {
diff --git a/types/bool.go b/types/bool.go
new file mode 100644
index 0000000..8dcae0d
--- /dev/null
+++ b/types/bool.go
@@ -0,0 +1,23 @@
+package types
+
+// BoolValues defines the name and value mappings for ParseBool.
+var BoolValues = map[string]interface{}{
+	"true": true, "yes": true, "on": true, "1": true,
+	"false": false, "no": false, "off": false, "0": false,
+}
+
+var boolParser = func() *EnumParser {
+	ep := &EnumParser{}
+	ep.AddVals(BoolValues)
+	return ep
+}()
+
+// ParseBool parses bool values according to the definitions in BoolValues.
+// Parsing is case-insensitive.
+func ParseBool(s string) (bool, error) {
+	v, err := boolParser.Parse(s)
+	if err != nil {
+		return false, err
+	}
+	return v.(bool), nil
+}
diff --git a/types/doc.go b/types/doc.go
new file mode 100644
index 0000000..9f9c345
--- /dev/null
+++ b/types/doc.go
@@ -0,0 +1,4 @@
+// Package types defines helpers for type conversions.
+//
+// The API for this package is not finalized yet.
+package types
diff --git a/types/enum.go b/types/enum.go
new file mode 100644
index 0000000..1a0c7ef
--- /dev/null
+++ b/types/enum.go
@@ -0,0 +1,44 @@
+package types
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+// EnumParser parses "enum" values; i.e. a predefined set of strings to
+// predefined values.
+type EnumParser struct {
+	Type      string // type name; if not set, use type of first value added
+	CaseMatch bool   // if true, matching of strings is case-sensitive
+	// PrefixMatch bool
+	vals map[string]interface{}
+}
+
+// AddVals adds strings and values to an EnumParser.
+func (ep *EnumParser) AddVals(vals map[string]interface{}) {
+	if ep.vals == nil {
+		ep.vals = make(map[string]interface{})
+	}
+	for k, v := range vals {
+		if ep.Type == "" {
+			ep.Type = reflect.TypeOf(v).Name()
+		}
+		if !ep.CaseMatch {
+			k = strings.ToLower(k)
+		}
+		ep.vals[k] = v
+	}
+}
+
+// Parse parses the string and returns the value or an error.
+func (ep EnumParser) Parse(s string) (interface{}, error) {
+	if !ep.CaseMatch {
+		s = strings.ToLower(s)
+	}
+	v, ok := ep.vals[s]
+	if !ok {
+		return false, fmt.Errorf("failed to parse %s %#q", ep.Type, s)
+	}
+	return v, nil
+}
diff --git a/types/enum_test.go b/types/enum_test.go
new file mode 100644
index 0000000..4bf135e
--- /dev/null
+++ b/types/enum_test.go
@@ -0,0 +1,29 @@
+package types
+
+import (
+	"testing"
+)
+
+func TestEnumParserBool(t *testing.T) {
+	for _, tt := range []struct {
+		val string
+		res bool
+		ok  bool
+	}{
+		{val: "tRuE", res: true, ok: true},
+		{val: "False", res: false, ok: true},
+		{val: "t", ok: false},
+	} {
+		b, err := ParseBool(tt.val)
+		switch {
+		case tt.ok && err != nil:
+			t.Errorf("%q: got error %v, want %v", tt.val, err, tt.res)
+		case !tt.ok && err == nil:
+			t.Errorf("%q: got %v, want error", tt.val, b)
+		case tt.ok && b != tt.res:
+			t.Errorf("%q: got %v, want %v", tt.val, b, tt.res)
+		default:
+			t.Logf("%q: got %v, %v", tt.val, b, err)
+		}
+	}
+}
diff --git a/types/int.go b/types/int.go
new file mode 100644
index 0000000..3e3a2b5
--- /dev/null
+++ b/types/int.go
@@ -0,0 +1,102 @@
+package types
+
+import (
+	"fmt"
+	"io"
+	"reflect"
+	"strings"
+)
+
+// An IntMode is a mode for parsing integer values, representing a set of
+// accepted bases.
+type IntMode uint8
+
+// IntMode values for ParseInt; can be combined using binary or.
+const (
+	Dec IntMode = 1 << iota
+	Hex
+	Oct
+)
+
+// String returns a string representation of IntMode; e.g. `IntMode(Dec|Hex)`.
+func (m IntMode) String() string {
+	var modes []string
+	if m&Dec != 0 {
+		modes = append(modes, "Dec")
+	}
+	if m&Hex != 0 {
+		modes = append(modes, "Hex")
+	}
+	if m&Oct != 0 {
+		modes = append(modes, "Oct")
+	}
+	return "IntMode(" + strings.Join(modes, "|") + ")"
+}
+
+var errIntAmbig = fmt.Errorf("ambiguous integer value; must include '0' prefix")
+
+func prefix0(val string) bool {
+	return strings.HasPrefix(val, "0") || strings.HasPrefix(val, "-0")
+}
+
+func prefix0x(val string) bool {
+	return strings.HasPrefix(val, "0x") || strings.HasPrefix(val, "-0x")
+}
+
+// ParseInt parses val using mode into intptr, which must be a pointer to an
+// integer kind type. Non-decimal value require prefix `0` or `0x` in the cases
+// when mode permits ambiguity of base; otherwise the prefix can be omitted.
+func ParseInt(intptr interface{}, val string, mode IntMode) error {
+	val = strings.TrimSpace(val)
+	verb := byte(0)
+	switch mode {
+	case Dec:
+		verb = 'd'
+	case Dec + Hex:
+		if prefix0x(val) {
+			verb = 'v'
+		} else {
+			verb = 'd'
+		}
+	case Dec + Oct:
+		if prefix0(val) && !prefix0x(val) {
+			verb = 'v'
+		} else {
+			verb = 'd'
+		}
+	case Dec + Hex + Oct:
+		verb = 'v'
+	case Hex:
+		if prefix0x(val) {
+			verb = 'v'
+		} else {
+			verb = 'x'
+		}
+	case Oct:
+		verb = 'o'
+	case Hex + Oct:
+		if prefix0(val) {
+			verb = 'v'
+		} else {
+			return errIntAmbig
+		}
+	}
+	if verb == 0 {
+		panic("unsupported mode")
+	}
+	return scanFully(intptr, val, verb)
+}
+
+func scanFully(intptr interface{}, val string, verb byte) error {
+	t := reflect.ValueOf(intptr).Elem().Type()
+	var r rune
+	n, err := fmt.Sscanf(val, "%"+string(verb)+"%c", intptr, &r)
+	switch {
+	case n < 1 || n == 1 && err != io.EOF:
+		return fmt.Errorf("failed to parse %q as %v: %v", val, t, err)
+	case n > 1:
+		return fmt.Errorf("failed to parse %q as %v: extra characters", val, t)
+	}
+	// n == 1 && err == io.EOF
+	return nil
+}
diff --git a/types/int_test.go b/types/int_test.go
new file mode 100644
index 0000000..b63dbcb
--- /dev/null
+++ b/types/int_test.go
@@ -0,0 +1,67 @@
+package types
+
+import (
+	"reflect"
+	"testing"
+)
+
+func elem(p interface{}) interface{} {
+	return reflect.ValueOf(p).Elem().Interface()
+}
+
+func TestParseInt(t *testing.T) {
+	for _, tt := range []struct {
+		val  string
+		mode IntMode
+		exp  interface{}
+		ok   bool
+	}{
+		{"0", Dec, int(0), true},
+		{"10", Dec, int(10), true},
+		{"-10", Dec, int(-10), true},
+		{"x", Dec, int(0), false},
+		{"0xa", Hex, int(0xa), true},
+		{"a", Hex, int(0xa), true},
+		{"10", Hex, int(0x10), true},
+		{"-0xa", Hex, int(-0xa), true},
+		{"0x", Hex, int(0x0), true},  // Scanf doesn't require digit behind 0x
+		{"-0x", Hex, int(0x0), true}, // Scanf doesn't require digit behind 0x
+		{"-a", Hex, int(-0xa), true},
+		{"-10", Hex, int(-0x10), true},
+		{"x", Hex, int(0), false},
+		{"10", Oct, int(010), true},
+		{"010", Oct, int(010), true},
+		{"-10", Oct, int(-010), true},
+		{"-010", Oct, int(-010), true},
+		{"10", Dec | Hex, int(10), true},
+		{"010", Dec | Hex, int(10), true},
+		{"0x10", Dec | Hex, int(0x10), true},
+		{"10", Dec | Oct, int(10), true},
+		{"010", Dec | Oct, int(010), true},
+		{"0x10", Dec | Oct, int(0), false},
+		{"10", Hex | Oct, int(0), false}, // need prefix to distinguish Hex/Oct
+		{"010", Hex | Oct, int(010), true},
+		{"0x10", Hex | Oct, int(0x10), true},
+		{"10", Dec | Hex | Oct, int(10), true},
+		{"010", Dec | Hex | Oct, int(010), true},
+		{"0x10", Dec | Hex | Oct, int(0x10), true},
+	} {
+		typ := reflect.TypeOf(tt.exp)
+		res := reflect.New(typ).Interface()
+		err := ParseInt(res, tt.val, tt.mode)
+		switch {
+		case tt.ok && err != nil:
+			t.Errorf("ParseInt(%v, %#v, %v): fail; got error %v, want ok",
+				typ, tt.val, tt.mode, err)
+		case !tt.ok && err == nil:
+			t.Errorf("ParseInt(%v, %#v, %v): fail; got %v, want error",
+				typ, tt.val, tt.mode, elem(res))
+		case tt.ok && !reflect.DeepEqual(elem(res), tt.exp):
+			t.Errorf("ParseInt(%v, %#v, %v): fail; got %v, want %v",
+				typ, tt.val, tt.mode, elem(res), tt.exp)
+		default:
+			t.Logf("ParseInt(%v, %#v, %s): pass; got %v, error %v",
+				typ, tt.val, tt.mode, elem(res), err)
+		}
+	}
+}