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 1. [Installation and update](https://git.giaever.org/joachimmg/go-ini#installation-and-update) 2. [Usage](https://git.giaever.org/joachimmg/go-ini#usage) 1. [Marshal](https://git.giaever.org/joachimmg/go-ini#marshal) 2. [Unmarshal](https://git.giaever.org/joachimmg/go-ini#unmarshal) 3. [Sectioning](https://git.giaever.org/joachimmg/go-ini#sectioning) * [Example](https://git.giaever.org/joachimmg/go-ini#example) 4. [Data-types](https://git.giaever.org/joachimmg/go-ini#data-types) * [Custom types](https://git.giaever.org/joachimmg/go-ini#custom-types) * [Example on how to implement one on your own?](https://git.giaever.org/joachimmg/go-ini#example-on-how-to-implement-one-on-your-own) 3. [TO-DO](https://git.giaever.org/joachimmg/go-ini#to-do) # 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 } } } ``` ## Data 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: ||, 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 ||(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 |. 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 || 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 `||` 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. ```go // 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.