Browse Source

Wrote README and added examples from README to go-ini.go

Joachim M. Giæver 6 years ago
parent
commit
457cb3469f
2 changed files with 578 additions and 3 deletions
  1. 345 1
      README.md
  2. 233 2
      go-ini.go

+ 345 - 1
README.md

@@ -1,2 +1,346 @@
-# ini
+go-ini
+======
 
+Go-ini is a package that handles INI-formatted data in your golang-project. The package is written with the principles `KISS` & `DRY`, where only the most known/common data-structures/types is implemented - leaving special types to be implemented by the programmer.
+
+It also tries to adopt how other «Marshaling»-packages in go are written, so it's simple to override and add functionality for your own code. This also implies that every package that already implements [_TextMarshaling_](https://golang.org/pkg/encoding/#TextMarshaler) and/or [_TextUnmarshaling_](https://golang.org/pkg/encoding/#TextUnmarshaler) (e.g time.Time) is supported.
+# ToC
+- _coming_
+
+# Installation and update:
+```bash
+$ go get -d git.giaever.org/joachimmg/go-ini.git
+```
+_(you may have to install it to run tests)_
+or import `git.giaever.org/joachimmg/go-ini.git/ini` in your project.
+
+Update:
+```
+go get -u git.giaever.org/joachimmg/go-ini.git
+```
+
+# Usage
+At the moment INI's marshalling methods works similar to Go's JSON [Marshal](https://golang.org/pkg/encoding/json/#Marshal) and [Unmarshal](https://golang.org/pkg/encoding/json/#Unmarshal) method. 
+
+## Marshal
+```go
+func Marshal(v interface{}) ([]byte, error)
+```
+Returns the INI formatting of v. Example of usage:
+```go
+type App struct {
+    Name    string
+    Url     string
+    Scheme  string
+}
+
+func main() {
+    settings := &App{
+        Name:   "My Application",
+        Url:    "localhost",
+        Scheme: "https",
+    }
+
+    idata, err := ini.Marshal(settings)
+
+    if err != nil {
+        fmt.Println("An error occured", err)
+        return
+    }
+
+    // Save ini-data (idata) to file...
+}
+```
+
+The produced output for this will be:
+```bash
+Name = My Application
+Url = localhost
+Scheme = https
+```
+
+## Unmarshal
+```go
+func Unmarshal(b []byte, v interface{}) error
+```
+Unmarshal parses the INI-formatted data and stores the result in the value poited to by v. Example of usage:
+```go
+type App struct {
+    // same as in previous example
+}
+
+func main() {
+
+    // Same as output from previous example,
+    // but we're now going the other way around
+    idata := `
+Name = My Application
+Url = localhost
+Scheme = https`
+    
+    settings := &App{}
+
+    // Must make idata from string to []byte
+    if err := ini.Unmarshal([]byte(idata), settings); err != nil {
+        fmt.Println("An error occured", err)
+        return
+    }
+
+    fmt.Println(settings) // prints: &{My Application localhost https}
+}
+```
+
+## Sectioning?
+Yes, ini-formatted files are often divided into sections and sub-sections. Such formatting is supported and handled out of the box. 
+
+### Example:
+```
+App = Application Name
+
+[Server]
+Scheme = https
+Domain = mydomain.org
+Ip = 127.0.0.1
+
+[Server.Connections]
+Limit = 10
+```
+This should be equalient to the following structure:
+```go
+type Settings struct { // The parent struct can be named what ever
+    App String
+    
+    Server struct {
+        Scheme  string
+        Domain  string
+        Ip      string
+        
+        Connections {
+            Limit uint
+        }
+    }
+}
+```
+## Special types
+Go-ini will by default ("itself") understand the following types:
+1. Pointers to types of those listed below,
+2. Struct and it's fields of those listed below,
+3. int (8/16/32/64),
+    1. Stored as `key = value` for each entry, where `key` is the field name.
+4. uint (8/16/32/64),
+    1. Stored as `key = value` for each entry, where `key` is the field name.
+5. string,
+    1. Stored as `key = value` for each entry, where `key` is the field name.
+6. boolean,
+    1. Stored as `key` if present and set to true, if false or not present it wont be stored in the ini. Note! You can override this behavior by implementing your own Marshaller methods.
+
+### Custom types
+I addition are the following types implemented (see: [types.go](https://git.giaever.org/joachimmg/go-ini/src/master/ini/types.go)):
+
+1. `ini.Time` - wrapper around `time.Time` to get a more visual and reader/editor friendly time value stored in the ini.
+2. `ini.Date` - wrapper around `time.Time` to get a more visual and reader/editor friendly date value stored in the ini.
+3. `ini.IP` - wrapper around `net.IP` to get a flexible solution for IP-addresses, but also validation that will follow the development of the `net`-package, instead of parsing a normal string.
+
+#### Example on how to implement one on your own?
+
+In this example we will _do a little overkill_-coding. This is just to show how you can "fold" data by reusing a fields own marshalling method. 
+
+It also takes into consideration restoring of numberic values (see `AccessLevel`) below, which is stored as a numberic value in memory, but in the INI-file its stored as a readable presentation (e.g `ADMIN`).
+
+```go
+// AccessLevel type (easier to transform into text)
+type AccessLevel int
+
+// Make some access levels
+const (
+	Admin AccessLevel = 1 << iota
+	User
+	Guest
+	Unknown
+)
+
+// String returns the text representation
+func (a AccessLevel) String() string {
+	switch a {
+	case Admin:
+		return "ADMIN"
+	case User:
+		return "USER"
+	case Guest:
+		return "GUEST"
+	default:
+		return "UNKNOWN"
+	}
+}
+
+// We must be able to restore the numeric value
+func (a *AccessLevel) SetAccessLevel(al string) {
+	for i := 1; AccessLevel(i) != Unknown; i = i << 1 {
+		if AccessLevel(i).String() == al {
+			*a = AccessLevel(i)
+			return
+		}
+	}
+	*a = Unknown
+}
+
+// Person defines an user
+type Person struct {
+	Name   string
+	Birth  ini.Date
+	Access AccessLevel
+}
+
+// MarshalINI to get the ini-format of one person
+func (p *Person) MarshalINI() ([]byte, error) {
+
+	// Uses ini.Date own marshaller method (see: types.go)
+	b, err := p.Birth.MarshalINI()
+
+	if err != nil {
+		return nil, err
+	}
+
+	// Format: <name>|<ini.Date>|<access level>, e.g my name|1985-09-85-UTC-0
+	return []byte(fmt.Sprintf("%s|%s|%s", string(p.Name), string(b), p.Access.String())), nil
+}
+
+// UnmarshalINI reads the format produces by MarshalINI
+// and creates the person/user again
+func (p *Person) UnmarshalINI(b []byte) error {
+	// Split <name>|<ini.Date>|<access level>(see MarshalINI above for format)
+	spl := strings.Split(string(b), "|")
+
+	// Do some form of validation (remember: this is just an example!)
+	if len(spl) != 3 {
+		return fmt.Errorf("Invalid format. Expected <name|birth.ini.Date>|<AccessLevel>. Got %s", string(b))
+	}
+
+	// Set name
+	p.Name = spl[0]
+	// Uses trimspace here to ensure that the trailing \n (newline) is removed
+	// as this is last item in <name>|<ini.Date>|<access level>
+	p.Access.SetAccessLevel(strings.TrimSpace(spl[2]))
+	// And use ini.Date's own unmarshaller method (see: types.go)
+	return p.Birth.UnmarshalINI([]byte(spl[1]))
+}
+
+// Persons is several persons
+type Persons []Person
+
+// Personell is users with given access levels
+type Personell struct {
+	Users Persons
+}
+
+func (p *Persons) MarshalINI() ([]byte, error) {
+	// []string to store each person in
+	var persons []string
+	for i := 0; i < len(*p); i++ {
+		// Run marshaller method on each (see Person.Marshal() above)
+		person, err := (*p)[i].MarshalINI()
+
+		// Ensure there arent any errors...
+		if err != nil {
+			return nil, err
+		}
+
+		// Add the person visual representation to the list of persons
+		persons = append(persons, string(person))
+	}
+
+	// Return as a multiline ini-entry
+	return []byte(strings.Join(persons, "\\\n")), nil
+}
+
+// This is left for the reader to solve ;)
+func (p *Persons) UnmarshalINI(b []byte) error {
+	persons := bytes.NewBuffer(b)
+
+	for pbytes, err := persons.ReadBytes('\n'); err == nil; pbytes, err = persons.ReadBytes('\n') {
+		var person Person
+		person.UnmarshalINI(pbytes)
+		*p = append(*p, person)
+
+		if err == io.EOF {
+			break
+		}
+	}
+
+	return nil
+}
+
+func main() {
+
+	p := &Personell{}
+
+	// Fill some data!
+	p.Users = append(p.Users,
+		Person{
+			Name:   "Testing Johanson",
+			Birth:  ini.Date{time.Date(1985, time.Month(9), 23, 0, 0, 0, 0, time.UTC)},
+			Access: Admin, // Admin
+		},
+		Person{
+			Name:   "Testing Aniston",
+			Birth:  ini.Date{time.Date(1987, time.Month(1), 12, 0, 0, 0, 0, time.UTC)},
+			Access: User, // Unknown user
+		},
+		Person{
+			Name:   "Testing Cubain",
+			Birth:  ini.Date{time.Date(1986, time.Month(3), 3, 0, 0, 0, 0, time.UTC)},
+			Access: Guest, // Guest
+		},
+	)
+
+	idata, err := ini.Marshal(p)
+
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	fmt.Println(string(idata))
+}
+```
+Now... this produces a multi-line entry for the ini, with the format
+`<name>|<birtday>|<user access>` for each entry/user:
+
+```
+Users = Testing Johanson|1985-9-23-UTC-0|ADMIN\
+Testing Aniston|1987-1-12-UTC-0|USER\
+Testing Cubain|1986-3-3-UTC-0|GUEST
+```
+
+This is basically it to encode and decode the ini! The only thing remaing is the `UnmarshalINI()` method for the `Persons`-type.
+
+```
+// This is left for the reader to solve ;) (or.... you can read the file go-ini.go)
+func (p *Persons) UnmarshalINI(b []byte) error {
+	return fmt.Errorf("NOT IMPLEMENTED")
+}
+
+func main()
+	// Output from m3
+	idata := `
+Users = Testing Johanson|1985-9-23-UTC-0|ADMIN\
+Testing Aniston|1987-1-12-UTC-0|USER\
+Testing Cubain|1986-3-3-UTC-0|GUEST`
+
+	p := &Personell{}
+
+	if err := ini.Unmarshal([]byte(idata), p); err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	fmt.Println("Success, you did it!", p)
+}
+```
+
+
+# TO-DO 
+1. Decoder & Encoder
+    1. In memory cache of entries for the lifecycle of the `*coder`.
+    2. When memory cache is implemented we can also start on storing comments. Parsing comments and storing them at this time wont be accessible when encoded back to INI-format.
+2. Quick loader/editor/saver. Requirements: Decoder/Encoder.

+ 233 - 2
go-ini.go

@@ -1,12 +1,243 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"git.giaever.org/joachimmg/go-ini.git/ini"
+	"io"
 	"strings"
 	"time"
 )
 
+type App struct {
+	Name   string
+	Url    string
+	Scheme string
+}
+
+func m1() {
+	// Set some data into the struct
+	settings := &App{
+		Name:   "My Application",
+		Url:    "localhost",
+		Scheme: "https",
+	}
+
+	// Read struct and format to INI
+	idata, err := ini.Marshal(settings)
+
+	if err != nil {
+		fmt.Println("An error occured", err)
+		return
+	}
+
+	// Save here? No errors.
+	fmt.Println(string(idata))
+}
+
+func m2() {
+	// Same as output from m1,
+	// but we're now reading this and filling the struct
+	idata := `
+Name = My Application
+Url = localhost
+Scheme = https`
+
+	settings := &App{}
+
+	// Must make idata from string to []byte
+	if err := ini.Unmarshal([]byte(idata), settings); err != nil {
+		fmt.Println("An error occured", err)
+		return
+	}
+
+	// User setting here.
+	fmt.Println(settings, settings.Name)
+}
+
+// AccessLevel type (easier to transform into text)
+type AccessLevel int
+
+// Make some access levels
+const (
+	Admin AccessLevel = 1 << iota
+	User
+	Guest
+	Unknown
+)
+
+// String returns the text representation
+func (a AccessLevel) String() string {
+	switch a {
+	case Admin:
+		return "ADMIN"
+	case User:
+		return "USER"
+	case Guest:
+		return "GUEST"
+	default:
+		return "UNKNOWN"
+	}
+}
+
+// We must be able to restore the numeric value
+func (a *AccessLevel) SetAccessLevel(al string) {
+	for i := 1; AccessLevel(i) != Unknown; i = i << 1 {
+		if AccessLevel(i).String() == al {
+			*a = AccessLevel(i)
+			return
+		}
+	}
+	*a = Unknown
+}
+
+// Person defines an user
+type Person struct {
+	Name   string
+	Birth  ini.Date
+	Access AccessLevel
+}
+
+// MarshalINI to get the ini-format of one person
+func (p *Person) MarshalINI() ([]byte, error) {
+
+	// Uses ini.Date own marshaller method (see: types.go)
+	b, err := p.Birth.MarshalINI()
+
+	if err != nil {
+		return nil, err
+	}
+
+	// Format: <name>|<ini.Date>|<access level>, e.g my name|1985-09-85-UTC-0
+	return []byte(fmt.Sprintf("%s|%s|%s", string(p.Name), string(b), p.Access.String())), nil
+}
+
+// UnmarshalINI reads the format produces by MarshalINI
+// and creates the person/user again
+func (p *Person) UnmarshalINI(b []byte) error {
+	// Split <name>|<ini.Date>|<access level>(see MarshalINI above for format)
+	spl := strings.Split(string(b), "|")
+
+	// Do some form of validation (remember: this is just an example!)
+	if len(spl) != 3 {
+		return fmt.Errorf("Invalid format. Expected <name|birth.ini.Date>|<AccessLevel>. Got %s", string(b))
+	}
+
+	// Set name
+	p.Name = spl[0]
+	// Uses trimspace here to ensure that the trailing \n (newline) is removed
+	// as this is last item in <name>|<ini.Date>|<access level>
+	p.Access.SetAccessLevel(strings.TrimSpace(spl[2]))
+	// And use ini.Date's own unmarshaller method (see: types.go)
+	return p.Birth.UnmarshalINI([]byte(spl[1]))
+}
+
+// Persons is several persons
+type Persons []Person
+
+// Personell is users with given access levels
+type Personell struct {
+	Users Persons
+}
+
+func (p *Persons) MarshalINI() ([]byte, error) {
+	// []string to store each person in
+	var persons []string
+	for i := 0; i < len(*p); i++ {
+		// Run marshaller method on each (see Person.Marshal() above)
+		person, err := (*p)[i].MarshalINI()
+
+		// Ensure there arent any errors...
+		if err != nil {
+			return nil, err
+		}
+
+		// Add the person visual representation to the list of persons
+		persons = append(persons, string(person))
+	}
+
+	// Return as a multiline ini-entry
+	return []byte(strings.Join(persons, "\\\n")), nil
+}
+
+// This is left for the reader to solve ;)
+func (p *Persons) UnmarshalINI(b []byte) error {
+	persons := bytes.NewBuffer(b)
+
+	for pbytes, err := persons.ReadBytes('\n'); err == nil; pbytes, err = persons.ReadBytes('\n') {
+		var person Person
+		person.UnmarshalINI(pbytes)
+		*p = append(*p, person)
+
+		if err == io.EOF {
+			break
+		}
+	}
+
+	return nil
+}
+
+func m3() {
+
+	p := &Personell{}
+
+	// Fill some data!
+	p.Users = append(p.Users,
+		Person{
+			Name:   "Testing Johanson",
+			Birth:  ini.Date{time.Date(1985, time.Month(9), 23, 0, 0, 0, 0, time.UTC)},
+			Access: Admin, // Admin
+		},
+		Person{
+			Name:   "Testing Aniston",
+			Birth:  ini.Date{time.Date(1987, time.Month(1), 12, 0, 0, 0, 0, time.UTC)},
+			Access: User, // Unknown user
+		},
+		Person{
+			Name:   "Testing Cubain",
+			Birth:  ini.Date{time.Date(1986, time.Month(3), 3, 0, 0, 0, 0, time.UTC)},
+			Access: Guest, // Guest
+		},
+	)
+
+	idata, err := ini.Marshal(p)
+
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	fmt.Println(string(idata))
+}
+
+func m4() {
+	// Output from m3
+	idata := `
+Users = Testing Johanson|1985-9-23-UTC-0|ADMIN\
+Testing Aniston|1987-1-12-UTC-0|USER\
+Testing Cubain|1986-3-3-UTC-0|GUEST`
+
+	p := &Personell{}
+
+	if err := ini.Unmarshal([]byte(idata), p); err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	fmt.Println("Success, you did it!", p)
+
+}
+
+func main() {
+	m1()
+	m2()
+	m3()
+	m4()
+}
+
+/**
+ * DEBUG DATA
+ **/
 type Test struct {
 	Str            string `ini:"String"`
 	UnquotedString string
@@ -87,7 +318,8 @@ func sep(f, e string) {
 	fmt.Println(f, string(s), e)
 }
 
-func main() {
+func debug() {
+
 	text := `
 String = "This is some text"
 UnquotedString = This is some unquoted text            
@@ -149,5 +381,4 @@ Float64 = 10.00003423472342734897234872347293748`
 	sep("", "")
 	fmt.Println(string(unm))
 	sep("", "")
-
 }