README ¶
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
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:
- Pointers to types of those listed below,
- Struct and it's fields of those listed below,
- int (8/16/32/64),
- Stored as
key = value
for each entry, wherekey
is the field name.
- Stored as
- uint (8/16/32/64),
- Stored as
key = value
for each entry, wherekey
is the field name.
- Stored as
- string,
- Stored as
key = value
for each entry, wherekey
is the field name.
- Stored as
- boolean,
- 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.
- Stored as
Custom types
I addition are the following types implemented (see: types.go):
ini.Time
- wrapper aroundtime.Time
to get a more visual and reader/editor friendly time value stored in the ini.ini.Date
- wrapper aroundtime.Time
to get a more visual and reader/editor friendly date value stored in the ini.ini.IP
- wrapper aroundnet.IP
to get a flexible solution for IP-addresses, but also validation that will follow the development of thenet
-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
- Decoder & Encoder
- In memory cache of entries for the lifecycle of the
*coder
. - 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.
- In memory cache of entries for the lifecycle of the
- Quick loader/editor/saver. Requirements: Decoder/Encoder.
Documentation ¶
There is no documentation for this package.