Introduction
odin-ini
is a blazingly fast INI parsing and serialization library written in 100% pure Odin.
Features
- Fully supports parsing and serializing INI files.
- Can parse and generate comments, sections, nested sections, and key-value pairs.
- Highly customizable
Options
struct for both parsing and serializing. - Easy error handling with custom error modes and messages.
- Custom conversion API along with built-in support for JSON conversion.
- Full UTF-8 support.
Installation
The recommended way to install odin-ini
is to use git submodules. This way you can easily update to the latest version of the library.
First, create a new git repository for your project if you haven't already:
git init .
Next, create an external/
(or any other name) directory in the root of your project. This will hold all the external dependencies.
Then, add odin-ini
as a submodule:
git submodule add https://github.com/hrszpuk/odin-ini.git external/odin-ini
Finally, add the odin-ini
directory to your Odin project file:
package main
// Import the odin-ini package from path
import "external/odin-ini/"
main :: proc() {
// Follow the documentation to find out how to use the library
config := ini.read_from_file("config.ini");
ini.set(config, "count", "42")
ini.write_to_file(config);
ini.destroy_config(config);
}
Getting Started
To get started using odin-ini
you need to import the package into your Odin project:
import "path/to/odin-ini/"
Then you can start using the library to parse, create, and serialize INI files.
Parsing INI files
To parse an INI file, the library first reads the file and produces lexical tokens. Then it parses the tokens into a pointer to a Config struct.
You may do this yourself using the following functions: new_lexer
, lex
, new_parser
, and parse
.
However, the library provides a number of helper functions that make this process easier:
read_from_string
:read_from_file
read_from_reader
read_from_bytes
package main
import "core:fmt"
import "path/to/odin-ini/"
main :: proc() {
config := ini.read_from_file("config.ini") // Empty ini config
defer ini.destroy_config(config) // Don't forget to free the memory!
// Modifying the config
ini.set(config, "count", "42")
ini.set(config, "name", "John Doe")
// Adding a section to the config
animals := ini.add_section(config, "animals")
ini.set(animals, "dog", "Rex")
ini.set(animals, "cat", "Whiskers")
// Printing the config out
out := ini.write_to_string(config)
fmt.println(out)
}
output:
count=42
name=John Doe
[animals]
dog=Rex
cat=Whiskers
Creating INI files
Reading a pre-existing ini config is not the only way to use the library. You can also create a new config from scratch using the new_config
function.
new_config
will accept a string argument which will be the name of the config file.
This name can be overridden when we write the config to a file.
It defaults to "config.ini"
.
package main
import "core:fmt"
import "path/to/odin-ini/"
main :: proc() {
config := new_config("config.ini")
defer ini.destroy_config(config)
ini.set(config, "count", "42")
}
Modifying INI files
You've already seen some of the modification procedures in the previous examples. Here are some more examples of how you can modify an INI file:
package main
import "core:fmt"
import "path/to/odin-ini/"
main :: proc() {
config := ini.read_from_file("config.ini")
defer ini.destroy_config(config)
// Set a key-value pair
ini.set(config, "count", "42") // Internally this is handled by `set_key`
// Set a key-value pair in a section
// If a section doesn't exist it will be created
ini.set(config, "section", "key", "value") // Internally this is handled by `set_section_key`
// Add a new section
// If a section already exists it will be overwritte
// Returns a pointer to the new section that can be used in `set` directly
section := ini.add_section(config, "new_section")
// Set a key-value pair in the new section
ini.set(section, "key", "value")
// Remove a key-value pair
ini.remove(config, "count") // Internally this is handled by `remove_key`
// Remove a key-value pair from a section
ini.remove(config, "section", "key") // Internally this is handled by `remove_section_key`
// Remove a section and all its key-value pairs
ini.remove_section(config, "new_section")
// Write the config to a file
ini.write_to_file(config)
}
Extracting data
You can read data from a config using the get
procedures:
get
: alias forget_key
get_key
: get a key-value pair in the config (or section)get_section
: get a section in the configexists
: alias forexists_key
exists_key
: check if a key exists in the configexists_section
: check if a section exists in the config
All of these procedures use the optional-ok
semantics provided by Odin.
package main
import "core:fmt"
import "path/to/odin-ini/"
main :: proc() {
config := ini.read_from_file("config.ini")
defer ini.destroy_config(config)
// Get a key-value pair
count := ini.get(config, "count") or_else "0"
// Get a key-value pair from a section
section, ok := ini.get_section(config, "section")
if ok {
key, ok := ini.get(section, "key")
}
// Check if a key exists
if ini.exists(config, "count") {
fmt.println("Count exists")
}
// Check if a section exists
if ini.exists_section(config, "section") {
fmt.println("Section exists")
// Check if a key exists in a section
if ini.exists(section, "key") {
fmt.println("Key exists in section")
}
}
}
Serializing INI files
To serialize an INI file, the library first serializes the config into a string. Then it writes the string to a file. You may do this yourself using the following functions:
write_to_string
write_to_file
write_to_writer
write_to_map
write_to_array
package main
import "core:fmt"
import "path/to/odin-ini/"
main :: proc() {
config := ini.read_from_file("config.ini")
defer ini.destroy_config(config)
// Write the config to a string
out := ini.write_to_string(config)
fmt.println(out)
// Write the config to a file
ini.write_to_file(config)
// you may (optionally) override the name of the file by providing a section argument
ini.write_to_file(config, "new_config.ini")
}
Advanced Usage
Configuration Options
Error Handling
Conversion to/from JSON
Custom Conversions
API Reference
API reference for the odin-ini
package:
For examples on how to use the library, check out the Examples section.
Config
The Config
struct is the main data structure used to store the configuration data.
I
The Config
struct is defined as follows:
Config :: struct {
name: string,
keys: map[string]^Config,
}
new_config(name: string = "config.ini") -> ^Config
Creates a new pointer to a Config
struct.
config := ini.new_config()
destroy_config(config: ^Config)
Deletes the Config
struct and frees the memory.
ini.destroy_config(config)
read_from_string(s: string) -> ^Config
Reads content from a string and parses it into the Config
struct.
config := ini.read_from_string("[section]\nkey=value")
ini.destroy_config(config) // free config memory
read_from_file(filename: string) -> ^Config
Reads content from a file and parses it into the Config
struct.
config := ini.read_from_file("config.ini")
ini.destroy_config(config) // free config memory
read_from_handle(h: os.Handle) -> ^Config
Reads data from a file handle and parses it into the Config
struct.
file := os.open("config.ini", os.ModeRead)
config := ini.read_from_handle(file)
ini.destroy_config(config) // free config memory
read_from_json(s: string) -> ^Config
Reads content from a JSON string and parses it into the Config
struct.
config := ini.read_from_json("{\"name\":\"Jerry\",\"keys\":{\"section\":{\"name\":\"section\",\"keys\":{\"key\":\"value\"}}}}")
fmt.println(config) // prints &Config{name = "config.ini", keys = map[name = "Jerry", section=0x1D29928]}
ini.destroy_config(config) // free config memory
write_to_string(c: ^Config) -> string
Writes the Config
struct to a string.
config := ini.read_from_string("[section]\nkey=value")
s := ini.write_to_string(config)
delete(s) // free memory
ini.destroy_config(config) // free config memory
write_to_file(c: ^Config) -> bool
Writes the Config
struct to a file. Filename is stored in the name
field of the Config
struct.
config := ini.read_from_string("[section]\nkey=value")
config.name = "my_config.ini"
ini.write_to_file(config)
ini.destroy_config(config) // free config memory
write_to_handle(c: ^Config, h: os.Handle)
Writes the Config
struct to a file handle.
config := ini.read_from_string("[section]\nkey=value")
file := os.open("my_config.ini", os.ModeWrite)
ini.write_to_handle(config, file)
ini.destroy_config(config) // free config memory
write_to_json(c: ^Config) -> string
Writes the Config
struct to a JSON string.
config := ini.read_from_string("[section]\nkey=value")
s := ini.write_to_json(config)
delete(s) // free memory
ini.destroy_config(config) // free config memory
get{get_key}
Alias for get_key
.
get_key(c: ^Config, key: string) -> string
Gets the value of a key from the Config
struct.
This can also be used to get the key of a section because internally they are ^Config
.
config := ini.read_from_string("n=10\n[section]\nkey=value")
value := ini.get_key(config, "n")
fmt.println(value) // prints 10
ini.destroy_config(config) // free config memory
get_section(c: ^Config, section: string) -> ^Config
Gets the value of a section from a Config
struct.
config := ini.read_from_string("n=10\n[section]\nkey=value")
value := ini.get_section(config, "section")
fmt.println(value) // prints &Config{name = "config.ini", keys = map[n=0x1D27A78, section=section1=0x1D29928]}
ini.destroy_config(config) // free config memory
add_section(c: ^Config, section: string) -> ^Config
Adds a section to a Config
and returns a pointer to the new section.
You can use the pointer to add new keys, or sections, to the section.
config := ini.new_config()
section := ini.add_section(config, "section")
ini.destroy_config(config) // free config memory
set{set_key, set_section}
Alias for set_key
and set_section
.
set_key(c: ^Config, key: string, value: string)
Sets the value of a key in the Config
struct.
key=value
is the same as set(config, "key", "value")
.
config := ini.new_config()
ini.set_key(config, "key", "value")
ini.destroy_config(config)
set_section(c: ^Config, key: string, section: ^Config)
Sets the value of a key in the Config
struct to a section.
[section]
is the same as set(config, "section", section)
.
config := ini.new_config()
set_section(config, "section", ini.new_config()) // Adds an empty section called "section"
ini.destroy_config(config)
remove(c: ^Config, key: string)
Removes a key from a Config
. This will affect both key values and sections.
config := ini.read_from_string("n=10\n[section]\nkey=value")
ini.remove(config, "n") // Removes the key "n" from the config file.
ini.remove(config, "section") // Removes the section "section" from the config file (this also removes the key).
ini.destroy_config(config) // free config memory
pop_key(c: ^Config, key: string) -> string
Removes the key from the Config
and returns the value of the key.
config := ini.read_from_string("n=10\n[section]\nkey=value")
value := ini.pop_key(config, "n") // Returns "10"
ini.destroy_config(config) // free config memory
pop_section(c: ^Config, section: string) -> ^Config
Removes the key from the Config
and returns the section as a ^Config
.
This is used to remove a section from a Config
.
If this is applied to a normal key it will return a Config
with the name
field as the value but with no keys.
config := ini.read_from_string("n=10\n[section]\nkey=value")
value := ini.pop_section(config, "section") // Returns &Config{name = "section", keys = map[key=value]}
ini.destroy_config(config) // free config memory
has_key(c: ^Config, key: string) -> bool
Checks if a config has a key with the given name. This will return false if the key is a section.
config := ini.read_from_string("n=10\n[section]\nkey=value")
ini.has_key(config, "n") // Returns true
ini.has_key(config, "section") // Returns false
ini.has_key(config, "benis") // Returns false
ini.destroy_config(config) // free config memory
has_section(c: ^Config, section: string) -> bool
Checks if a config has a section with the given name.
config := ini.read_from_string("n=10\n[section]\nkey=value")
ini.has_section(config, "section") // Returns true
ini.has_section(config, "n") // Returns false
ini.has_section(config, "benis") // Returns false
ini.destroy_config(config) // free config memory
is_empty(c: ^Config) -> bool
Returns true
if the config is empty, false
otherwise.
config := ini.new_config()
ini.is_empty(config) // Returns true
ini.set_key(config, "key", "value")
ini.is_empty(config) // Returns false
ini.destroy_config(config) // free config memory
clear(c: ^Config)
Removes all keys and sections from the Config
.
config := ini.read_from_string("n=10\n[section]\nkey=value
ini.clear(config) // Removes all keys and sections from the config file.
ini.destroy_config(config) // free config memory
keys(c: ^Config) -> []string
Returns an array of all the keys in the Config
.
config := ini.read_from_string("n=10\n[section]\nkey=value")
keys := ini.keys(config) // Returns ["n"]
ini.destroy_config(config) // free config memory
values(c: ^Config) -> []string
Returns an array of all the values in the Config
.
config := ini.read_from_string("n=10\n[section]\nkey=value")
values := ini.values(config) // Returns ["10", "value"]
ini.destroy_config(config) // free config memory
sections(c: ^Config) -> []string
Returns an array of all the sections in the Config
.
config := ini.read_from_string("n=10\n[section]\nkey=value")
sections := ini.sections(config) // Returns ["section"]
ini.destroy_config(config) // free config memory