A Go library to manage INI-files in your project. Adopts the idea with Marshalling, typically used when parsing JSON and other text-formatted files in Go.

Joachim M. Giæver e53f896285 Fixed formatting 6 years ago
data dfa396f8b1 Init commit. Started on scanner, for decoding. 6 years ago
ini 1d401d7478 Encoding + decoding 6 years ago
.gitignore 9b7e74e74a Initial commit 6 years ago
LICENSE 9b7e74e74a Initial commit 6 years ago
README.md e53f896285 Fixed formatting 6 years ago
go-ini.go 457cb3469f Wrote README and added examples from README to go-ini.go 6 years ago

README.md

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 and/or TextUnmarshaling (e.g time.Time) is supported.

ToC

  1. Installation and update
  2. Usage
    1. Marshal
    2. Unmarshal
    3. Sectioning
    4. Data-types
  3. TO-DO

Installation and update:

$ 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 and Unmarshal method.

Marshal

func Marshal(v interface{}) ([]byte, error)

Returns the INI formatting of v. Example of usage:

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:

Name = My Application
Url = localhost
Scheme = https

Unmarshal

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:

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:

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):

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

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