Browse Source

Encoding + decoding

Joachim M. Giæver 6 years ago
parent
commit
1d401d7478
6 changed files with 750 additions and 352 deletions
  1. 96 81
      go-ini.go
  2. 46 146
      ini/decode.go
  3. 62 124
      ini/encode.go
  4. 414 0
      ini/field.go
  5. 10 1
      ini/scanner.go
  6. 122 0
      ini/types.go

+ 96 - 81
go-ini.go

@@ -2,68 +2,20 @@ package main
 
 import (
 	"fmt"
-	"strconv"
+	"git.giaever.org/joachimmg/go-ini.git/ini"
 	"strings"
 	"time"
-
-	"git.giaever.org/joachimmg/go-ini.git/ini"
 )
 
-type IniDate time.Time
-
-func (i IniDate) UnmarshalINI(b []byte) error {
-	return nil
-}
-
-type TestUnm struct {
-	myname  string
-	mydate  int
-	mymonth int
-	myyear  int
-}
-
-func (t *TestUnm) UnmarshalINI(b []byte) error {
-	str := strings.Split(string(b), ",")
-
-	if len(str) != 4 {
-		return fmt.Errorf("must contain 4 entries, got %d", len(str))
-	}
-
-	d, err := strconv.Atoi(str[1])
-
-	if err != nil {
-		return err
-	}
-
-	m, err := strconv.Atoi(str[2])
-
-	if err != nil {
-		return err
-	}
-
-	y, err := strconv.Atoi(str[3])
-
-	if err != nil {
-		return err
-	}
-
-	t.myname = str[0]
-	t.mydate = d
-	t.mymonth = m
-	t.myyear = y
-
-	fmt.Println(str[0], d, m, y, t)
-
-	return nil
-}
-
 type Test struct {
-	String         string
+	Str            string `ini:"String"`
 	UnquotedString string
 	MultipleLines  string
 	WithoutValue   bool
-	AboutMe        *TestUnm
-	Date           IniDate `ini:"date"`
+	Date           ini.Date
+	Time           ini.Time
+	TestTime       time.Time
+	IP             ini.IP
 
 	Int struct {
 		Int   int
@@ -80,19 +32,72 @@ type Test struct {
 			Uint64 uint64
 		}
 	}
+
+	Float struct {
+		Float32 float32
+		Float64 float64
+	}
 }
 
-func main() {
+func (t *Test) String() string {
+	return fmt.Sprintf(`
+STRUCT DATA:
+	Str:			%v
+	UnquotedString:		%v
+	MultipleLines:		%v
+	WithoutValue:		%v
+	Date:			%v
+	Time:			%v
+	TestTime:		%v
+	IP:			%v
+
+	Int {
+		Int:		%v
+		Int8:		%v
+		Int16:		%v
+		Int32:		%v
+		Int64:		%v
+
+		Uint {
+			Uint:		%v
+			Uint8:		%v
+			Uint16:		%v
+			Uint32:		%v
+			Uint64:		%v
+		}
+	}
 
-	text := `
+	Float {
+		Float32:	%v
+		Float64:	%v
+	}`,
+		t.Str, t.UnquotedString, strings.Replace(t.MultipleLines, "\n", "\n\t\t\t\t", -1), t.WithoutValue, t.Date, t.Time, t.TestTime, t.IP,
+		t.Int.Int, t.Int.Int8, t.Int.Int16, t.Int.Int32, t.Int.Int64,
+		t.Int.Uint.Uint, t.Int.Uint.Uint8, t.Int.Uint.Uint16, t.Int.Uint.Uint32, t.Int.Uint.Uint64,
+		t.Float.Float32, t.Float.Float64,
+	)
+}
+
+func sep(f, e string) {
+	var s []byte
+	for i := 0; i < 80; i++ {
+		s = append(s, '/')
+	}
+
+	fmt.Println(f, string(s), e)
+}
 
+func main() {
+	text := `
 String = "This is some text"
 UnquotedString = This is some unquoted text            
 MultipleLines = This is \
 multiple lines \
 going on
 WithoutValue
-AboutMe = joachim,23,09,85
+Date = 2017-9-28-Local-0
+Time = 01 Jan 01 00:00 UTC
+IP = ::
 
 [Int]
 Int = -1
@@ -110,29 +115,39 @@ Uint64 = 64
 
 Uint64 = 64
 
-`
+[Float]
+Float32 = 3.14722354762387648273648726348
+Float64 = 10.00003423472342734897234872347293748`
+
+	first := &Test{}
+
+	sep("", "")
+	fmt.Println(" ### INI TO UNMARSHAL:")
+	sep("", "")
+	fmt.Println(text)
+
+	sep("\n", "")
+	fmt.Println(" ### INITIAL STRUCT VALUES (empty/unset values):")
+	sep("", "")
+	fmt.Println(first)
+
+	sep("\n", "")
+	fmt.Println(" --- NOW MARSHALING: INI -> STRUCT ---")
+	err := ini.Unmarshal([]byte(text), first)
+	fmt.Println(" !!! ERROR ON UMARSHALING:", err)
+	sep("", "")
+	fmt.Println(" ### NEW STRUCT VALUES (unmarshaled from INI):")
+	sep("", "\n")
+	fmt.Println(first)
+
+	sep("\n", "")
+	fmt.Println(" --- NOW UNMARSHALING: STRUCT -> INI ---")
+	unm, err := ini.Marshal(first)
+	fmt.Println(" !!! ERROR ON MARCHALING:", err)
+	sep("", "")
+	fmt.Println(" ### PRODUCED INI (marshaled from STRUCT)")
+	sep("", "")
+	fmt.Println(string(unm))
+	sep("", "")
 
-	fmt.Println("INI", text)
-
-	test1 := &Test{}
-	test2 := Test{
-		String:         "This is some text",
-		UnquotedString: "Helloooo... Not possible to not quote this",
-		MultipleLines:  "This is \nmultiple lines \ngoing on",
-		WithoutValue:   true,
-		Date:           IniDate(time.Now()),
-	}
-	test2.Int.Int = -1
-	test2.Int.Int8 = -8
-	test2.Int.Int16 = -16
-	test2.Int.Int32 = -32
-	test2.Int.Int64 = -64
-	test2.Int.Uint.Uint = 1
-	test2.Int.Uint.Uint8 = 8
-	test2.Int.Uint.Uint16 = 16
-	test2.Int.Uint.Uint32 = 32
-	test2.Int.Uint.Uint64 = 64
-	fmt.Println("Test1", test1)
-	fmt.Println("Result", ini.Unmarshal([]byte(text), test1), test1, test1.AboutMe)
-	//fmt.Println(ini.Marshal(test2))
 }

+ 46 - 146
ini/decode.go

@@ -1,40 +1,37 @@
 package ini
 
+// Decode INI-fomatted data into an interface (typically a struct)
+// Known types:
+// - Any kind of ints
+// - Any kind of unsigned ints
+// - Structs + pointers
+// - Boolean
+// - Strings
+// - Types with the method `UnmarshalText`
+//
+// Unknown types (also known types!) can impement the method UnmarshalINI
+// to be to override standard unmarshaling method, or make marshalable if it's not.
+//
+// e.g: see types.go and the Time example. Time does implement MarshalText, but it doesnt
+// store the time-value in a very readable/editable manner, so the ini.Time is just a
+// wrapper-struct with the Marshaler-interface to read/write the time-value differently.
+
 import (
-	"encoding"
-	"fmt"
 	"reflect"
-	"strconv"
-)
-
-var (
-	unmarshalerType     = reflect.TypeOf(new(Unmarshaler)).Elem()
-	textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
 )
 
+// Unmarshaler is an interface to implement for unknown types
 type Unmarshaler interface {
 	UnmarshalINI([]byte) error
 }
 
-type valueDecoder func([]byte) error
-
-type field struct {
-	ns string
-
-	t reflect.Type
-	v reflect.Value
-
-	p interface{}
-
-	f valueDecoder
-}
-
 type decoder struct {
 	scan *scanner
 	ns   map[string]*field
 	nss  string
 }
 
+// Unmarshal decodes byte-array/slice containing INI into interface v
 func Unmarshal(d []byte, v interface{}) error {
 
 	dec := &decoder{
@@ -42,21 +39,23 @@ func Unmarshal(d []byte, v interface{}) error {
 		ns:   make(map[string]*field),
 	}
 
+	// Ensure data is valid before reflecting
 	if err := dec.scan.valid(); err != nil {
-		fmt.Println("ERROR, invalid", err)
 		return err
 	}
 
+	// Map struct -> INI
 	return dec.unmarshal(v)
 }
 
+// umarshal does the actual mapping by first collecting struct data
+// then scanning ini-file and mapping data to the struct.
 func (d *decoder) unmarshal(v interface{}) error {
 	rv := reflect.ValueOf(v)
 	d.imap("", rv, nil, 0)
-	fmt.Println(d.ns)
 
 	ns := d.nss
-	for elem, err := d.scan.next(); err == EOL; elem, err = d.scan.next() {
+	for elem, err := d.scan.next(); err == EOL || err == EOF; elem, err = d.scan.next() {
 
 		switch elem.t {
 		case section:
@@ -65,68 +64,54 @@ func (d *decoder) unmarshal(v interface{}) error {
 			key := ns + "." + string(elem.key)
 
 			if f, ok := d.ns[key]; ok {
-				if err := f.f(elem.val); err != nil {
-					fmt.Println(err)
+				if err := f.decode(elem.val); err != nil {
+					return err
 				}
 			}
 		}
+		if err == EOF {
+			break
+		}
 	}
 
 	return nil
 }
 
-func (d *decoder) ns_append(ns string, f *field) string {
-	if f.p == nil {
-		f.ns = ns + "." + f.t.Name()
-		return f.ns
-	}
-
-	if tag := f.p.(reflect.StructField).Tag.Get("ini"); len(tag) != 0 {
-		f.ns = ns + "." + tag
-		return f.ns
-	}
-
-	f.ns = ns + "." + f.p.(reflect.StructField).Name
-	return f.ns
+// nsmap is a mapper function, storing each entry on a given namespace
+func (d *decoder) nsmap(ns string, f *field, fn decodeFn) {
+	f.setFn(fn)
+	d.ns[f.setNS(ns)] = f
 }
 
+// imap maps interface data into namespaces
 func (d *decoder) imap(ns string, v reflect.Value, p interface{}, i int) {
 
-	f := &field{
-		t: v.Type(),
-		v: v,
-		p: nil,
-	}
-
-	if p != nil {
-		f.p = p.(reflect.Type).Field(i)
-	}
+	f := newField(v, p, i)
 
-	if f.t.Implements(unmarshalerType) {
-		f.f = f.unmDecoder
-		d.ns[d.ns_append(ns, f)] = f
+	if f.implements(unmarshalerType) || f.implements(textUnmarshalerType) {
+		d.nsmap(ns, f, f.unmarshalerDecoder)
 		return
 	}
 
 	switch f.v.Kind() {
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		f.f = f.uintDecoder
-		d.ns[d.ns_append(ns, f)] = f
+		d.nsmap(ns, f, f.uintDecoder)
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		f.f = f.intDecoder
-		d.ns[d.ns_append(ns, f)] = f
+		d.nsmap(ns, f, f.intDecoder)
 	case reflect.String:
-		f.f = f.strDecoder
-		d.ns[d.ns_append(ns, f)] = f
+		d.nsmap(ns, f, f.strDecoder)
 	case reflect.Bool:
-		f.f = f.boolDecoder
-		d.ns[d.ns_append(ns, f)] = f
+		d.nsmap(ns, f, f.boolDecoder)
+	case reflect.Float32:
+		d.nsmap(ns, f, f.float32Decoder)
+	case reflect.Float64:
+		d.nsmap(ns, f, f.float64Decoder)
 	case reflect.Struct:
 		if len(d.nss) == 0 {
-			d.nss = d.ns_append(ns, f)
+			d.nss = f.setNS(ns)
 		}
 		for i := 0; i < f.v.NumField(); i++ {
-			d.imap(d.ns_append(ns, f), f.v.Field(i), f.t, i)
+			d.imap(f.setNS(ns), f.v.Field(i), f.t, i)
 		}
 	case reflect.Ptr:
 		if f.v.IsNil() {
@@ -134,90 +119,5 @@ func (d *decoder) imap(ns string, v reflect.Value, p interface{}, i int) {
 		}
 		d.imap(ns, f.v.Elem(), nil, i)
 	default:
-		fmt.Printf("OTHER TYPE: %v", f.t.Name())
-	}
-}
-
-func (f *field) valid() error {
-
-	if f.v.CanAddr() {
-		return nil
-	}
-
-	return fmt.Errorf("Field '%s' not addressable", f.ns)
-}
-
-func (f *field) uintDecoder(b []byte) error {
-
-	if err := f.valid(); err != nil {
-		return err
 	}
-
-	i, err := strconv.ParseUint(string(b), 10, 64)
-
-	if err != nil {
-		return err
-	}
-
-	f.v.SetUint(i)
-
-	return nil
-}
-
-func (f *field) intDecoder(b []byte) error {
-
-	if err := f.valid(); err != nil {
-		return err
-	}
-
-	i, err := strconv.ParseInt(string(b), 10, 64)
-
-	if err != nil {
-		return err
-	}
-
-	f.v.SetInt(i)
-
-	return nil
-}
-
-func (f *field) strDecoder(b []byte) error {
-	if err := f.valid(); err != nil {
-		return err
-	}
-	f.v.SetString(string(b))
-	return nil
-}
-
-func (f *field) boolDecoder(b []byte) error {
-
-	if err := f.valid(); err != nil {
-		return err
-	}
-	f.v.SetBool(true)
-	return nil
-}
-
-func (f *field) unmDecoder(b []byte) error {
-
-	if err := f.valid(); err != nil {
-		return err
-	}
-
-	v := f.v
-
-	if v.Kind() != reflect.Ptr && v.Type().Name() != "" {
-		v = v.Addr()
-	}
-
-	if v.IsNil() {
-		v.Set(reflect.New(v.Type().Elem()))
-	}
-
-	if i, ok := v.Interface().(Unmarshaler); ok {
-		err := i.UnmarshalINI(b)
-		return err
-	}
-
-	return fmt.Errorf("Cannot load Unmarshaler")
 }

+ 62 - 124
ini/encode.go

@@ -1,36 +1,41 @@
 package ini
 
+// Encode an interface (typically a struct) into INI-formatted data
+// Known types:
+// - Any kind of ints
+// - Any kind of unsigned ints
+// - Structs + pointers
+// - Boolean
+// - Strings
+// -  Types with the method `MarshalText`
+//
+// Unknown types (also known types!) can impement the method MarshalINI
+// to be to override standard marshaling method, or make it marshalable if it's not.
+//
+// e.g: see types.go and the Time example. Time does implement MarshalText, but it doesnt
+// store the time-value in a very readable/editable manner, so the ini.Time is just a
+// wrapper-struct with the Marshaler-interface to read/write the time-value differently.
+
 import (
 	"bytes"
-	"encoding"
-	"fmt"
 	"reflect"
 )
 
-var (
-	marshalerType     = reflect.TypeOf(new(Marshaler)).Elem()
-	textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
-)
-
+// Marshaler is an interface to implement for unknown types
 type Marshaler interface {
 	MarshalINI() ([]byte, error)
 }
 
-type encoder interface {
-	encode()
-}
-
-type encode struct {
+type encoder struct {
 	bytes.Buffer
-	scratch [64]byte
 
 	/*cache sync.Map TODO */
 }
 
+// Marshal encode interface v into INI-format
 func Marshal(v interface{}) ([]byte, error) {
 
-	fmt.Println("Interface", v)
-	enc := &encode{}
+	enc := &encoder{}
 
 	if err := enc.marshal(v); err != nil {
 		return nil, err
@@ -39,130 +44,63 @@ func Marshal(v interface{}) ([]byte, error) {
 	return enc.Bytes(), nil
 }
 
-func (e *encode) marshal(v interface{}) error {
-
-	s := "Text"
-
-	e.reflectValue(reflect.ValueOf(v))
-	e.reflectValue(reflect.ValueOf(1))
-	e.reflectValue(reflect.ValueOf(s))
-	return nil
-}
-
-func (e *encode) reflectValue(v reflect.Value) {
-	fmt.Println("ReflectValue", v)
-	valueEncoder(e, v).encode()
+// marshal maps struct to ini
+func (e *encoder) marshal(v interface{}) error {
+	return e.imap("", reflect.ValueOf(v), nil, 0)
 }
 
-func valueEncoder(e *encode, v reflect.Value) encoder {
-	if !v.IsValid() {
-		return invalidValueEncoder{e, v, v.Type()}
+// write to byte buffer whenever a successfull encoding occurs
+func (e *encoder) write(ns string, f *field, fn encodeFn) error {
+	f.fn = fn
+	b, err := f.encode()
+	if b != nil && err == nil {
+		if _, err := e.Write(append(b, '\n')); err != nil {
+			return err
+		}
 	}
-
-	return e.encoder(v)
+	return err
 }
 
-func (e *encode) encoder(v reflect.Value) encoder {
-	t := v.Type()
+// imap does the actual mapping on a specific namespace
+func (e *encoder) imap(ns string, v reflect.Value, p interface{}, i int) error {
+	f := newField(v, p, i)
 
-	if t.Implements(marshalerType) {
-		fmt.Println("t.Implements(marshalerType)", t.Implements(marshalerType))
-		return marshalerEncoder{e, v, t}
-	}
-
-	if t.Kind() != reflect.Ptr {
-		if reflect.PtrTo(t).Implements(marshalerType) {
-			fmt.Println("Allow marshalType")
-		}
-	}
-
-	if t.Implements(textMarshalerType) {
-		fmt.Println("t.Implements(textMarshalerType)", t.Implements(textMarshalerType))
-		return &textMarshalerEncoder{e, v, t}
-	}
+	f.setNS(ns)
 
-	if t.Kind() != reflect.Ptr {
-		if reflect.PtrTo(t).Implements(textMarshalerType) {
-			fmt.Println("Allow TextmarshalType")
-		}
+	if f.implements(marshalerType) || f.implements(textMarshalerType) {
+		return e.write(ns, f, f.marshalerDecoder)
 	}
 
-	switch t.Kind() {
+	switch v.Kind() {
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
-		fmt.Println("INT")
+		return e.write(ns, f, f.intEncoder)
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
-		fmt.Println("UINT")
-	case reflect.Float32, reflect.Float64:
-		fmt.Println("Float32/64")
+		return e.write(ns, f, f.uintEncoder)
+	case reflect.Float32:
+		return e.write(ns, f, f.float32Encoder)
+	case reflect.Float64:
+		return e.write(ns, f, f.float64Encoder)
 	case reflect.String:
-		fmt.Println("STRING")
-		return &stringEncoder{e, v, t}
+		return e.write(ns, f, f.strEncoder)
+	case reflect.Bool:
+		return e.write(ns, f, f.boolEncoder)
 	case reflect.Struct:
-		fmt.Println("STRUCT")
-		return &structEncoder{e, v, t}
-	case reflect.Interface:
-		fmt.Println("Interface")
-	case reflect.Slice, reflect.Array:
-		fmt.Println("Array or slice")
-	default:
-		fmt.Printf("Other kind: %s", t.Kind())
-	}
-
-	return &invalidValueEncoder{e, v, t}
-}
-
-type marshalerEncoder struct {
-	e *encode
-	v reflect.Value
-	t reflect.Type
-}
 
-func (m marshalerEncoder) encode() {
-
-}
-
-type textMarshalerEncoder struct {
-	e *encode
-	v reflect.Value
-	t reflect.Type
-}
-
-func (m textMarshalerEncoder) encode() {
-
-}
-
-type structEncoder struct {
-	e *encode
-	v reflect.Value
-	t reflect.Type
-}
-
-func (s structEncoder) encode() {
-	//fmt.Println("STRUCT", s.t)
-
-	fmt.Printf("NAME: %s", s.t)
+		if err := e.write(f.ns, f, f.structEncoder); err != nil {
+			return err
+		}
 
-	for i := 0; i < s.v.NumField(); i++ {
-		s.e.encoder(s.v.Field(i)).encode()
+		for i := 0; i < f.v.NumField(); i++ {
+			if err := e.imap(f.ns, f.v.Field(i), f.t, i); err != nil {
+				return err
+			}
+		}
+	case reflect.Ptr:
+		if v.IsNil() {
+			return nil
+		}
+		return e.imap(ns, v.Elem(), p, i)
 	}
-}
-
-type stringEncoder struct {
-	e *encode
-	v reflect.Value
-	t reflect.Type
-}
-
-func (t *stringEncoder) encode() {
-	fmt.Println("STRING ENCODER", t.v, t.t)
-}
-
-type invalidValueEncoder struct {
-	e *encode
-	v reflect.Value
-	t reflect.Type
-}
-
-func (i invalidValueEncoder) encode() {
 
+	return nil
 }

+ 414 - 0
ini/field.go

@@ -0,0 +1,414 @@
+package ini
+
+import (
+	"encoding"
+	"fmt"
+	"reflect"
+	"strconv"
+	"strings"
+)
+
+// Types to look for ("Marshaler"/"Unmarshaler" interface)
+var (
+	marshalerType       = reflect.TypeOf(new(Marshaler)).Elem()
+	unmarshalerType     = reflect.TypeOf(new(Unmarshaler)).Elem()
+	textMarshalerType   = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
+	textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
+)
+
+// INIDecoderError is returned on decoding error
+type INIDecoderError struct {
+	msg string
+	err error
+	f   *field
+}
+
+// Error returns an formatted string of the INIDecoderError
+func (i *INIDecoderError) Error() string {
+	return fmt.Sprintf(
+		"INI Decoder Error: %s. %s. Field %s, namespace: %s",
+		i.msg, i.err.Error(), i.f.t.Name(), i.f.ns,
+	)
+}
+
+// INIEncoderError is returned on encoding error
+type INIEncoderError struct {
+	msg string
+	err error
+	f   *field
+}
+
+// Error returns an formatted string of the INIEncoderError
+func (i *INIEncoderError) Error() string {
+	return fmt.Sprintf(
+		"INI Encoder Error: %s. Field: %s, namespace: %s",
+		i.msg, i.err.Error(), i.f.t.Name(), i.f.ns,
+	)
+}
+
+// decoder and encoder methods
+type decodeFn func([]byte) error
+type encodeFn func() ([]byte, error)
+
+type field struct {
+	ns string
+
+	t reflect.Type
+	v reflect.Value
+
+	p interface{}
+
+	// decoder or encoder method (will be changed when a cache for "elements" is implemented)
+	fn interface{} // Use setFn to set this!
+}
+
+func newField(v reflect.Value, p interface{}, i int) *field {
+	f := &field{
+		t: v.Type(),
+		v: v,
+		p: nil,
+	}
+
+	if p != nil {
+		switch p.(type) {
+		case reflect.Type:
+			f.p = p.(reflect.Type).Field(i)
+		case reflect.Value:
+			f.p = p.(reflect.Value).Type().Field(i)
+		}
+	}
+
+	return f
+}
+
+// implements checks if the field implements the type t, or a pointer method to type t
+func (f *field) implements(t reflect.Type) bool {
+	return (f.t.Implements(t) || (f.t.Kind() != reflect.Ptr && reflect.PtrTo(f.t).Implements(t)))
+}
+
+// setNS determines the namespace for the field
+func (f *field) setNS(ns string) string {
+	if f.p == nil {
+		f.ns = ns + "." + f.t.Name()
+		return f.ns
+	}
+
+	if tag := f.p.(reflect.StructField).Tag.Get("ini"); len(tag) != 0 {
+		f.ns = ns + "." + tag
+		return f.ns
+	}
+
+	f.ns = ns + "." + f.p.(reflect.StructField).Name
+	return f.ns
+}
+
+// setFN sets the function
+func (f *field) setFn(fn interface{}) {
+	f.fn = fn
+}
+
+// decode returns the decoder-method (do not call f.fn, for future changes!)
+func (f *field) decode(b []byte) error {
+	return f.fn.(decodeFn)(b)
+}
+
+// encode returns the decoder-method (do not call f.fn directly, for future changes!)
+func (f *field) encode() ([]byte, error) {
+	return f.fn.(encodeFn)()
+}
+
+/**
+ DECODER METHODS
+**/
+
+func (f *field) valid() error {
+
+	if f.v.CanAddr() {
+		return nil
+	}
+
+	return fmt.Errorf("Field '%s' not addressable", f.ns)
+}
+
+func (f *field) uintDecoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "Unsigned integer decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	i, err := strconv.ParseUint(string(b), 10, 64)
+
+	if err != nil {
+		return &INIDecoderError{
+			msg: "Unsigned integer decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	f.v.SetUint(i)
+
+	return nil
+}
+
+func (f *field) intDecoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "Integer decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	i, err := strconv.ParseInt(string(b), 10, 64)
+
+	if err != nil {
+		return &INIDecoderError{
+			msg: "Integer decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	f.v.SetInt(i)
+
+	return nil
+}
+
+func (f *field) float32Decoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "Float (32) decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	i, err := strconv.ParseFloat(string(b), 32)
+
+	if err != nil {
+		return &INIDecoderError{
+			msg: "Float (32) decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	f.v.SetFloat(i)
+
+	return nil
+}
+
+func (f *field) float64Decoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "Float (64) decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	i, err := strconv.ParseFloat(string(b), 64)
+
+	if err != nil {
+		return &INIDecoderError{
+			msg: "Float (64) decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	f.v.SetFloat(i)
+
+	return nil
+}
+
+func (f *field) strDecoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "String decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	f.v.SetString(string(b))
+	return nil
+}
+
+func (f *field) boolDecoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "Boolean decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	f.v.SetBool(true)
+	return nil
+}
+
+func (f *field) unmarshalerDecoder(b []byte) error {
+
+	if err := f.valid(); err != nil {
+		return &INIDecoderError{
+			msg: "Unmarshaler decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	v := f.v
+
+	if v.Kind() != reflect.Ptr && v.Type().Name() != "" {
+		v = v.Addr()
+	}
+
+	if v.IsNil() {
+		v.Set(reflect.New(v.Type().Elem()))
+	}
+
+	if i, ok := v.Interface().(Unmarshaler); ok {
+		if err := i.UnmarshalINI(b); err != nil {
+			return &INIDecoderError{
+				msg: "UnmarshalINI decoder",
+				err: err,
+				f:   f,
+			}
+		}
+		return nil
+	}
+
+	if i, ok := v.Interface().(encoding.TextUnmarshaler); ok {
+		if err := i.UnmarshalText(b); err != nil {
+			return &INIDecoderError{
+				msg: "UnmarshalText decoder",
+				err: err,
+				f:   f,
+			}
+		}
+		return nil
+	}
+
+	return fmt.Errorf("Cannot load Unmarshaler")
+}
+
+/**
+ ENCODER METHODS
+**/
+
+func (f *field) nskey() string {
+	return f.ns[strings.LastIndexByte(f.ns, '.')+1:]
+}
+
+func (f *field) uintEncoder() ([]byte, error) {
+	i := strconv.FormatUint(f.v.Uint(), 10)
+	return []byte(f.nskey() + " = " + i), nil
+}
+
+func (f *field) intEncoder() ([]byte, error) {
+	i := strconv.FormatInt(f.v.Int(), 10)
+	return []byte(f.nskey() + " = " + i), nil
+}
+
+func (f *field) float32Encoder() ([]byte, error) {
+	i := strconv.FormatFloat(f.v.Float(), 'G', -1, 32)
+	return []byte(f.nskey() + " = " + i), nil
+}
+
+func (f *field) float64Encoder() ([]byte, error) {
+	i := strconv.FormatFloat(f.v.Float(), 'G', -1, 64)
+	return []byte(f.nskey() + " = " + i), nil
+}
+
+func (f *field) strEncoder() ([]byte, error) {
+	str := strings.Replace(f.v.String(), "\n", "\\\n", -1)
+	return []byte(f.nskey() + " = " + str), nil
+}
+
+func (f *field) boolEncoder() ([]byte, error) {
+	if f.v.Bool() {
+		return []byte(f.nskey()), nil
+	}
+	return nil, nil
+}
+
+func (f *field) structEncoder() ([]byte, error) {
+
+	split := strings.SplitN(f.ns, ".", 3)
+
+	if len(split) == 2 {
+		return nil, nil
+	}
+
+	if len(split) != 3 {
+		return nil, &INIEncoderError{
+			msg: "Struct encoder",
+			err: fmt.Errorf("Invalid namespace"),
+			f:   f,
+		}
+	}
+
+	return []byte("\n[" + split[2] + "]"), nil
+}
+
+func (f *field) marshalerDecoder() ([]byte, error) {
+
+	if err := f.valid(); err != nil {
+		return nil, &INIEncoderError{
+			msg: "Unmarshaler decoder",
+			err: err,
+			f:   f,
+		}
+	}
+
+	v := f.v
+
+	if v.Kind() != reflect.Ptr && v.Type().Name() != "" {
+		v = v.Addr()
+	}
+
+	if v.IsNil() {
+		v.Set(reflect.New(v.Type().Elem()))
+	}
+
+	if i, ok := v.Interface().(Marshaler); ok {
+		b, err := i.MarshalINI()
+
+		if err != nil {
+			return nil, err
+		}
+
+		if b == nil {
+			return nil, nil
+		}
+
+		return append([]byte(f.nskey()+" = "), b...), nil
+	}
+
+	if i, ok := v.Interface().(encoding.TextMarshaler); ok {
+		b, err := i.MarshalText()
+
+		if err != nil {
+			return nil, err
+		}
+
+		if b == nil {
+			return nil, nil
+		}
+
+		return append([]byte(f.nskey()+" = "), b...), nil
+	}
+
+	return nil, fmt.Errorf("Cannot load Marshaler")
+}

+ 10 - 1
ini/scanner.go

@@ -1,5 +1,7 @@
 package ini
 
+// Scan a databuffer of INI-formatted data.
+
 import (
 	"fmt"
 )
@@ -24,6 +26,7 @@ var (
 	EOL = fmt.Errorf("END OF LINE")
 )
 
+// SyntaxError is returned when the formatting is wrong.
 type SyntaxError struct {
 	got byte
 	exp byte
@@ -31,6 +34,7 @@ type SyntaxError struct {
 	off int
 }
 
+// Error returns the error as a string
 func (s SyntaxError) Error() string {
 	if s.got == 0 {
 		return fmt.Sprintf("Error on position '%d: %s'", s.off, s.msg)
@@ -73,6 +77,7 @@ func (s *scanner) reset() {
 	s.err = nil
 }
 
+// loop assists next-method
 func (s *scanner) loop() (*element, error) {
 	e := newElement()
 	for _, c := range s.data[s.read:] {
@@ -96,6 +101,7 @@ func (s *scanner) loop() (*element, error) {
 	return e, nil
 }
 
+// Just quickly loops through the INI and returns an error on error
 func (s *scanner) valid() error {
 	defer s.reset()
 
@@ -115,6 +121,7 @@ func (s *scanner) valid() error {
 	return nil
 }
 
+// next returns one element and most likely an error (typically EOF/EOL) on every iteration
 func (s *scanner) next() (*element, error) {
 	e, err := s.loop()
 
@@ -125,6 +132,7 @@ func (s *scanner) next() (*element, error) {
 	return e, err
 }
 
+// ret ensures that we return the proper error
 func (s *scanner) ret(state scanState) scanState {
 	if state != scanEOF && s.read == s.len {
 		state = scanEOF
@@ -200,7 +208,8 @@ func (s *scanner) variableValue(c byte, e *element) scanState {
 	switch c {
 	case '\n', '\r':
 		if e.val.lastByteMatching('\\') >= 0 {
-			e.val.add('n')
+			e.val.removeLastByteMatching('\\')
+			e.val.add('\n')
 			return s.ret(scanContinue)
 		}
 

+ 122 - 0
ini/types.go

@@ -0,0 +1,122 @@
+package ini
+
+// Types of with values more readable for humans
+
+import (
+	"fmt"
+	"net"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// IP is equalient to net.IP but with custom Marshaler-interface
+type IP struct {
+	net.IP
+}
+
+// MarshalINI will marshal the IP into IPv4 or IPv6 readable format, e.g
+// 127.0.0.1 or ::
+func (i *IP) MarshalINI() ([]byte, error) {
+	if ip := i.To4(); ip != nil {
+		return []byte(ip.String()), nil
+	}
+
+	if ip := i.To16(); ip != nil {
+		return []byte(ip.String()), nil
+	}
+
+	return nil, nil
+}
+
+// UnmarshalINI will unmarshal the value produced by MarshalINI, e.g
+// 127.0.0.1 or :: into a net.IP
+func (i *IP) UnmarshalINI(b []byte) error {
+	i.IP = net.ParseIP(string(b))
+	return nil
+}
+
+// Time is equalient to time.Time but with custom Marshaler-interface
+type Time struct {
+	time.Time
+}
+
+// MarshalINI will marshal the time into RFC822
+func (i *Time) MarshalINI() ([]byte, error) {
+	if i.IsZero() {
+		return nil, nil
+	}
+
+	return []byte(i.Format(time.RFC822)), nil
+}
+
+// UnmarshalINI will unmarshal the time from RFC822
+func (i *Time) UnmarshalINI(b []byte) error {
+	var err error
+
+	(*i).Time, err = time.Parse(time.RFC822, string(b))
+
+	return err
+}
+
+// Date is equalient to time.Time but with custom Marshaler-interface to store only the date
+type Date struct {
+	time.Time
+}
+
+// MarshalINI will marshal the time into a string of format y-d-m-timezone-offset
+func (i *Date) MarshalINI() ([]byte, error) {
+	if i.IsZero() {
+		return nil, nil
+	}
+	y, m, d := i.Date()
+	tz, off := i.Zone()
+	return []byte(fmt.Sprintf("%d-%d-%d-%s-%d", y, m, d, tz, off)), nil
+}
+
+// UnmarshalINI will unmarshal a string formatted as y-m-d-timezone-offset
+func (i *Date) UnmarshalINI(b []byte) error {
+
+	(*i).Time = time.Now()
+
+	spl := strings.Split(string(b), "-")
+
+	if len(spl) != 5 {
+		return fmt.Errorf("Invalid format %s, need d-m-y-tz-offset", string(b))
+	}
+
+	y, err := strconv.ParseInt(spl[0], 10, 32)
+
+	if err != nil {
+		return err
+	}
+
+	m, err := strconv.ParseInt(spl[1], 10, 32)
+
+	if err != nil {
+		return err
+	}
+
+	d, err := strconv.ParseInt(spl[2], 10, 32)
+
+	if err != nil {
+		return err
+	}
+
+	off, err := strconv.ParseInt(spl[4], 10, 32)
+
+	if err != nil {
+		return err
+	}
+
+	l := time.FixedZone(spl[3], int(off))
+
+	if err != nil {
+		return err
+	}
+
+	diff := time.Date(int(y), time.Month(m), int(d), 0, 0, 0, 0, l).Sub(i.Time)
+	i.Time.Add(diff)
+
+	return nil
+}