|
@@ -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.
|