ncfg is a general-purpose configuration file parser/saving library. It is written in plain C99 and should compile warnings and errors free pretty much anywhere, even under Windows (if you use MinGW). At the moment, MSVC cannot compile this, because it lacks C99 support.
ncfg is a low-level library. It isn't the most easiest library to use, but it does its job. It currently implements only the bare minimum things needed and not much else.
ncfg IS NOT FINISHED YET.
It works and it does its job, but it will very likely get more features in the future. I'll add things when they're needed. I've used it already in many projects and it has worked well.
This documentation is not yet finished either! ncfg IS NOT BUG-FREE. The parser has not been fully tested and fuzzed yet! Test it thoroughly before using it! I have no unit tests nor practical test cases for this thing yet.
There may be discrepancies between how things are supposed to work (this documentation) and how they actually work (the code). I've tried to verify everything, but small things always slip through.
ncfg has no specific makefile yet. At the moment, it is compiled on the command line like this:
gcc -Wall -s -O2 -o ncfg.o -c ncfg.c -std=c99 ar rs libncfg.a ncfg.o
Install the library by simply copying the files libncfg.a
and ncfg.h
into appropriate directories.
The following table lists the current compiler support. The code should compile cleanly with maximum warnings.
Compiler | Status |
---|---|
GCC 4.6.x (Linux) | Should work |
GCC 4.6.2 (MinGW, Windows) | Should work |
GCC 4.7.2 (Linux) | Works |
GCC 4.7.2 (MinGW, Windows) | Works |
GCC 4.8.1 (Linux) | Should work |
Clang 3.x (Linux) | Should work |
MSVC, any version (Windows) | Not supported, no C99 compatibility |
MSVC users either have to use MinGW GCC or modify the library themselves to make it compile. I won't accept "patches" that makes ncfg compile under MSVC, that would be a downgrade.
Everything in ncfg is stored in items. Every item has a name (that is not necessarily unique), plus zero or more attached values. Yes, the item does not necessarily need any values, just the name is enough. There is no limit on how many values item can have. The values also have no length limitation. You could create an item named "book" and give it one value that contains a megabyte's worth of text and ncfg would not flinch at it.
You can group items into sections. Every section (except the automatically managed root section) has a parent item, which also gives the section a name. The sections can be nested freely and they can be empty.
Any item can have a child section, even items that have values. The values belong to the item, not to the child section. The next example creates an item match
that has one value, *.txt
, but it also has a child section. The item action
also has a child section, that has one item that has no values:
match "*.txt" { action move { warn-if-exists; } directory $(TARGET)/txt; }
Note that while item names cannot contain the slash, its values can:
path /;
The reason you can't use forward slashes in item names is that it becomes impossible to know which slash is an actual path separator and which slash belongs in the name. You can't use slashes in file names either.
Every item (and section) is referred through what is called "path name". It is identical to a file name that includes the directory names. The path name contains the names of the parent items (or sections) separated by slashes (/) and at the end of it is the name of the actual item.
If you have an item test
, its path name is just that, test
, and nothing else. If you create another item, say parent
, add a child section under it and then attach the item test
to that section, the item's path name would be parent/test
. You just keep adding the parent sections and slashes.
Unlike its predecessor, ncfg has a very simple file format. The format is still divided into two main categories: sections and items.
The parser is a free-form, meaning you can use newlines, tabs and other whitespace as much as you want. Only whitespace breaks things into separate tokens. This means that ab;cd;
means an item whose name is ab;cd
and not two items (ab
and cd
). This reduces the amount of escaping you must do, but it might lead to gotchas if you aren't aware of this.
Every item declaration begins with the item's name, followed by the (optional) values and it ends in a semicolon:
message "Hello world"; coordinates 14 39; optimize;
The above example creates three items. The first, named message
has one value: Hello world
. The quotes are needed only if he value has embedded whitespace, and they're automatically removed and inserted again when the file is saved, if needed. The second item, coordinates
has two values, 14
and 39
. Note that both of those numbers are actually text strings, ncfg treats everything as a string. The last one, optimize
has no values. It just exists.
Because the semicolon ends an item declaration, you can put them all on the same line:
message "Hello world"; coordinates 14 39; optimize;
If the item has a child section, then the { and } (denoting the section's start and end) come before the semicolon. In fact, the semicolon is is not needed at all if you have a child section:
file { name data/archive.zip; mount_point /; }
This creates an item file
which has a child section. The child section has no name, but its name indirectly is file
, since that item is its parent item. The items name
and mount_point
are both in that section and can be accessed by first getting a pointer to the file
section, then by iterating the linked list in the file
's child_section
structure.
You must put a whitespace between the item's name and the {, otherwise ncfg does not pick it up.
As said, semicolons are not needed if you have a child section. It is not an error to use them, though. ncfg ignores unnecessary semicolons:
vfs { file { name data/archive.zip; mount_point /; } file { name dev/new_data.zip; mount_point /new; }; };;;;;
However, after you end an item with a semicolon, you cannot "go back" to it and add a child section. Either you declare the child section right away, or you don't declare it at all. So the following will result in a syntax error, because the item has already ended and the section has nothing where it could be assigned to:
file; { name data/archive.zip; mount_point /; }
In item names and values, certain characters must be escaped to prevent ncfg from interpreting them. These characters are:
Escape sequence | Result |
---|---|
\n | Newline (0x0A) |
\r | Newline (0x0D, use \r\n to make a "Windows"-style newline) |
\t | Tab (ncfg converts this to 0x09 and back to \t when saving) |
\' | ' |
\" | " (if you want typographic quotes, just write them out - ncfg is fully Unicode-aware. This escape sequence is needed only if you need the ASCII quotation mark.) |
\; | ; |
\\ | \ |
\{ | { |
\} | } |
ncfg automatically escapes these when saving files, so you don't have to do it. Any other character can be passed as-is.
Not that in some cases, if the escaped character happens to be a part of a string or identifier, then you don't have to escape them. Consider this example:
item\a;
In this case, the \ does not need to be escaped, because it is not standalone. If it was the first or the last character, then it needs to be escaped.
Note that no matter what you do, you cannot use forward slashes (/) in item names! They work fine in the values.
ncfg's file format is text-only and you should refrain from abusing it to store binary data. But if you really need to store binary data, encode it first.
As you can see from the previous chapter, sections begin with { and they end in }. Items between those belong in that section. Sections can be nested freely. But you MUST end every section you start. Otherwise you'll get an unbalanced nesting level error if you try to load the file.
An implicit section called "root section" exists. Every item that is not inside { and } is placed in this section. The root section is automatically created and deleted by ncfg. You can delete it, but you can't do anything unless you recreate it first.
Sections have no names in ncfg, but the parent item's name can be thought as a name for the section. The root section is an exception, it never has a name.
ncfg supports C and C++ -style comments. A C-style comment begins with /*
and continues until an ending */
is found. C++ -style comment begins with a //
and continues until the line end. C-style comments can be nested, ncfg keeps track of the nesting level and only the last */
finally terminates the whole comment.
LibCFG did not handle /*
, */
and //
inside strings correctly. But ncfg does. This means, for example, that you can write URLs just fine and they are not interpreted as C++-style comments (while in LibCFG you had to escape the slashes).
So this works just as expected:
url http://www.google.com;
ncfg has several important structures.
cfg_t
The cfg_t
structure is the main handle in ncfg. You can't do much with the library if you don't allocate an instance of cfg_t
structure. Actually, an "instance" is a bit of a misnomer, since you don't actually initialize the library anywhere. But cfg_t
stores the actual tree data, contains error number and error row/column numbers, I/O data and various other important things. So, in essence, one cfg_t
instance represents ONE configuration file. You'll probably have multiple instances allocated simultaneously and that's fine: they don't interfere with each other. You can even reuse existing instances if you want.
Strictly speaking, you don't need this. You can create the tree all by yourself. You just can't save it easily, plus some future tasks might be difficult or impossible. So you should still create and use this.
Member | Type | Usage |
---|---|---|
io | struct cfg_io_functions_t * | A structure containing the function pointers for the I/O customization. By default these functions are the internal ones that use normal stdio functions for the I/O, but you can change these at will. |
user_io_data | void * | Opaque pointer where you can store arbitrary data for your own custom I/O functions. It is up to you to allocate/free/manage this pointer, ncfg only sets it to NULL when you create a new instance. This pointer is passed to all I/O functions. |
root | struct cfg_section_t * | The tree root section. As long as you use ncfg's own functions to manage this structure, this pointer should never be NULL (even if the tree is empty). If this is NULL, something has probably gone horribly wrong and you should watch out. |
error_code | int | The last error code. Use cfg_error_code_to_string() to turn this into a textual explanation. |
error_row | uint32_t | Row and column where the error occurred. Valid only if you load a file and even then under certain circumstances only. Saving a file does not use/set these. |
error_col |
cfg_section_t
A section stores zero or more child items. They have an optional "owner" item. Sections can be nested, but they always require separate owner items, since an item can only have one child section.
Member | Type | Usage |
---|---|---|
owner | struct cfg_item_t * | The item of which this section is a child of. The parent item's key is, in a way, the "name" of this section. This can be NULL if the section is a root-level section. The root level section always exists. Non-root sections should always have a valid owner item, otherwise most functions in ncfg will probably fail when they try to access the tree structure. |
head | struct cfg_item_t * | Head and tail pointers of the doubly-linked list of the items stored in this section. Both can be NULL if the section has no items. |
tail |
cfg_item_t
Items are where the actual configuration data is stored. They have a key that identifies them (not necessarily a unique name) plus zero or more associated values (which are always strings). They optionally have a child section. The items are stored in doubly-linked lists in their owner sections.
Member | Type | Usage |
---|---|---|
row | uint32_t | Row and column in the source file where this item was loaded from. Valid only if you loaded a file. |
col | ||
static_key | char [] | A fixed-size array that stores the key name if its less than 15 bytes long. Never access this by yourself, always use the key member below! Used to reduce the number of small memory allocations and memory fragmentation. |
key | char * | The item's name (or key) which identifies it. Not necessarily unique within the section, or even in the file. This is a dynamically allocated string if the key is longer than 15 bytes. Always use this when referring to the item's key! Never attempt to rename an item by yourself, use the function designed to do that! |
size | size_t | How many values this item has. Zero, if the item has no values. |
capacity | size_t | Capacity of the dynamic 2D array that stores the items. Do not touch. |
value | char ** | An array of the associated values. Empty if the item has no values. |
owner | struct cfg_section_t * | The item's owner section. Can be NULL. If the item is stored on the root level, then this points to the actual tree root section (the root member in cfg_t structure). |
child | struct cfg_section_t * | This item's child section. NULL if nothing. |
prev | struct cfg_item_t * | Pointers to the previous and next item in the doubly-linked list of items in the owner section. |
next |
cfg_io_functions_t
A structure containing pointers to the I/O functions. You can use it to override ncfg's internal stdio I/O functions. To see how exactly these functions work, you'll have to check the source code. Most likely you'll only have to use this structure if you want to create a memory buffer reader/writer.
Member | Type | Usage |
---|---|---|
open | int | Pointer to the function that opens a file in read or write mode. Must return 1 on success, 0 on failure. Must be always defined. |
close | int | Pointer to the function that closes a file. Must return 1 on success, 0 on failure. Must be always defined. |
read | size_t | Pointer to the function that reads from a file. Must return the number of bytes actually read. Not needed if you only need to write. |
write | size_t | Pointer to the function that writes to a file. Must return the number of bytes actually written. Not needed if you only need to read. |
cfg_io_data_t
This structure contains the file handle and a pointer to your own custom I/O data you might need. It is passed to all I/O functions and they can do whatever they want with it.
Member | Type | Usage |
---|---|---|
handle | void * | Opaque pointer to the actual file handle. Can be NULL, if you use your custom I/O functions and store your handle data in your own structure. Generally though, you should use this. |
user_io_data | void * | Opaque pointer for your own custom I/O data structures. This is the same pointer you set in the cfg_t structure. You can allocate it, free it and otherwise mess with it by yourself as much as you want. |
int cfg_new(struct cfg_t **cfg)
Allocates a new instance of the main cfg_t
structure. After this, you can save and load files. The tree root section exists, but it is completely empty.
void cfg_delete(struct cfg_t **cfg)
Deletes the main structure, freeing the tree data and everything.
int cfg_section_new(struct cfg_section_t **sect)
Creates a new empty section. It has no parent item (so it doesn't even have a name), or any child items.
int cfg_section_delete(struct cfg_section_t **sect)
Deletes a section. The section is unlinked from the tree structure first. All child items and their child sections will be automatically deleted.
struct cfg_section_t *s = NULL; cfg_section_new(&s); // oops, didn't want to create it anyway... cfg_section_delete(&s);
int cfg_section_add_item(struct cfg_section_t *sect, struct cfg_item_t *item)
Adds the item into an existing section.
int cfg_section_remove_item(struct cfg_item_t *item)
Unlinks the item from its parent section. The item is not deleted, so if you don't save its pointer first, you'll leak memory.
struct cfg_section_t *s = NULL; struct cfg_item_t *i = NULL; cfg_section_new(&s); cfg_item_new(&i, "some_item", 0, NULL); // add the item into the section cfg_section_add_item(s, i); // or never mind cfg_section_remove_item(s, i);
char *cfg_section_get_full_path(const struct cfg_section_t *section)
Constructs a full path for a section. Appends the parent section item's names, separated by slashes, followed by the owner's name. Returns NULL if something failed. The returned string must be free()
'd after you're done with it.
This works in the same way as cfg_item_get_full_path()
, but this works on sections not items.
int cfg_item_new(struct cfg_item_t **item, const char *key, const size_t initial_size, const char **initial_values)
Creates a new item and sets its key. Optionally takes an array of the initial values.
struct cfg_item_t *itemA = NULL, *itemB = NULL; char *values[] = { "Hello", "world!" }; cfg_item_new(&itemA, "item A", 0, NULL); cfg_item_new(&itemB, "item B", 2, &values); ... cfg_item_delete(&itemA); cfg_item_delete(&itemB);
int cfg_item_delete(struct cfg_item_t **item)
Deletes an item. The item is unlinked from its parent section. Child section is deleted along with its items and other subsections.
struct cfg_item_t *item = NULL; cfg_item_new(&item, "example", 0, NULL); cfg_item_delete(&item); // 'item' is now NULL
int cfg_item_set_child_section(struct cfg_item_t *item, struct cfg_section_t *sect)
Assigns a child section for this item. The item will be then the section's parent item. If the item already has a child section, it will be unlinked first and, if you don't save the old pointer first, will be lost (meaning you can't access it anymore and you'll leak memory).
int cfg_item_append_value(struct cfg_item_t *item, const char *value)
Appends a string at the end of the item's value list.
int cfg_item_set_key(struct cfg_item_t *item, const char *new_key)
Renames the item.
int cfg_item_delete_value(struct cfg_item_t *item, const size_t index)
Removes an existing value from an item. The index is a zero-based number of the value to be removed.
int cfg_item_delete_all_values(struct cfg_item_t *item)
Deletes all of the item's values, but leaves the item otherwise intact.
int cfg_item_set_value(struct cfg_item_t *item, const size_t index, const char *new_value)
Changes an existing value in an item. The index is a zero-based number of the value to be changed. The value must already exist, this cannot create new values!
int cfg_item_from_int(struct cfg_item_t **item, const char *key, const int value)
int cfg_item_from_float(struct cfg_item_t **item, const char *key, const float value)
Creates a new item and sets its first value to an integer or a floating point number.
int cfg_item_add_int(struct cfg_item_t *item, const int value)
int cfg_item_add_float(struct cfg_item_t *item, const float value)
Appends an integer or a floating point value to an item.
char *cfg_item_get_full_path(const struct cfg_item_t *item)
Constructs a full path for an item. Appends the parent section's names, separated with slashes and finally adds the item's name. Returns NULL if something failed. The returned string must be free()
'd after you're done with it.
Suppose you load the following file, test.cfg
:
vfs { file { name data/archive.zip; mount_point /; } file { name dev/new_data.zip; mount_point /new; } }
You can then get the item mount_point
inside the first file
section and print out its full path like this:
struct cfg_t *conf = NULL; cfg_new(&conf); cfg_load("test.cfg"); char *path = cfg_item_get_full_path(cfg_get_item(conf->root, "vfs/file/mount_point", 0, NULL)); if (path) { printf("%s\n", path); free(path); } cfg_delete(&conf);
Assuming the file could be loaded and the item exists, the above code will print:
vfs/file/mount_point
const char *cfg_item_get_value(const struct cfg_item_t *item, const size_t value_index, const char *default_value)
Gets the Nth value of an item, or the default value, if the Nth value does not exist (or an error occurred).
Suppose you have the following item declared in a file that you load:
some_item a b c "Hello world" d;
Now you can access its values:
struct cfg_item_t *item = ...; printf("%s\n", cfg_item_get_value(item, 0, "invalid index")); printf("%s\n", cfg_item_get_value(item, 3, "invalid index")); printf("%s\n", cfg_item_get_value(item, 5, "invalid index"));
You'll see this:
a Hello World invalid index
const struct cfg_item_t *cfg_section_find_item(const struct cfg_section_t *where, const char *key)
Returns a pointer to the specified item in a section. Does not check the child sections, so names like "a/b" are not possible. If there are multiple items with the same name, this returns the first match. Returns NULL if nothing found.
const char *cfg_find_and_get_value(const struct cfg_section_t *where, const char *key, const size_t value_index, const char *default_value)
Combines cfg_section_find_item
and cfg_item_get_value
into one single handy function. First this finds the item, then it returns the Nth value of it. If the item or the value does not exist, returns default_value
.
struct cfg_item_t *cfg_get_item(struct cfg_section_t *where, const char *path, int allow_create, int *error_code)
Returns a pointer to the specified item. If it does not exist, it will be created (if allow_create
is non-zero) and a pointer to it will be returned. Can create items recursively. The error code is stored in error_code
if it's not NULL. The section "where" can be ANY valid section, not just the tree root section.
Strictly speaking, if you want to be absolutely minimalistic, this function is the only one you need, besides save/load and a few other utility functions. It accepts constructs like "a/b/c" as a path. If sections "a" or "b" don't exist, they will be created before item "c" is created.
int cfg_load(struct cfg_t *cfg, const char *name)
int cfg_load(struct cfg_t *cfg, const wchar_t *name)
Loads the configuration data from a file. Does not delete the existing tree data, but appends the new data at the end. This allows you to "stack" multiple files on top of each other, simulating a simple file inclusion.
Two versions of this exist. The wchar_t
version exists only if ncfg is compiled under Windows. Under Linux, normal UTF-8 filenames work, but under Windows you need to use UCS-2 names.
struct cfg_t *cfg = NULL; cfg_new(&cfg); if (!cfg_load(cfg, "filename.cfg")) { // something went wrong, check cfg->error_code } else { // you can now access the data via cfg->root } cfg_delete(&cfg);
int cfg_save(struct cfg_t *cfg, const char *name, const size_t num_headers, const char **headers)
int cfg_save(struct cfg_t *cfg, const wchar_t *name, const size_t num_headers, const char **headers)
Saves the current tree structure into a file. Allows you to specify optional headers for the file. These headers are written as comments at the top of the file, before any other data is written. Do not put newlines in the headers, because the function already writes every item on its own line. The headers must be UTF-8 encoded already.
Two versions of this exist. The wchar_t
version exists only if ncfg is compiled under Windows. Under Linux, normal UTF-8 filenames work, but under Windows you need to use UCS-2 names.
To just save a tree data, without any headers:
struct cfg_t *cfg = NULL; cfg_new(&cfg); // create the items and sections here if (!cfg_save(cfg, "filename.cfg", 0, NULL)) { // something went wrong, check cfg->error_code } cfg_delete(&cfg);
Using the headers:
struct cfg_t *cfg = NULL; cfg_new(&cfg); // create the items and sections here const char *headers[] = { "This is just an example on how to use the comment headers.", "This is the second line." }; if (!cfg_save(cfg, "filename.cfg", 2, &headers)) { // something went wrong, check cfg->error_code } cfg_delete(&cfg);
The above example produces this file:
// This is just an example on how to use the comment headers. // This is the second line. (actual data here)
int cfg_reset_root(struct cfg_t *cfg)
Deletes the tree structure, then recreates it. This effectively allows you to reuse an existing instances of this structure.
struct cfg_t *cfg = NULL; cfg_new(&cfg); cfg_load(cfg, "file1.cfg"); cfg_load(cfg, "file2.cfg"); cfg_load(cfg, "file3.cfg"); // now we have three files' worth of data loaded in memory ... // start from scratch cfg_reset_root(cfg); cfg_load(cfg, "file4.cfg"); // now only file4.cfg's data exists in memory
int cfg_for_each_item(struct cfg_section_t *where, const int is_recursive, int (*callback)(struct cfg_item_t *item, void *params), void *params)
Iterates over every item in a section, optionally recursively, and calls the specified callback function on each of them. The callback function receives a pointer to the item, plus one opaque void pointer you supply to this function. This pointer can contain any data you want, but you have to manage the pointer by yourself.
The callback function can alter the tree structure, but be very careful: you can make cfg_for_each_item()
fail, or get stuck in an infinite loop, or crash. So change things only if you really know what you're doing!
The return value of the callback is important. Return zero to cancel the iteration, non-zero to keep it going.
Returns CFG_OK
if every item was iterated, or CFG_CALLBACK_ABORTED
if the callback function aborted the iteration.
const char *cfg_error_code_to_string(const int code)
Converts an error code into a UTF-8 encoded string. Combined with the error row and column, you can display good error messages.
struct cfg_t *cfg = NULL; cfg_new(&cfg); if (!cfg_load(cfg, "file.cfg")) { printf("%s (%d)\nLine: %u\nColumn: %u\n", cfg_error_code_to_string(cfg->error_code), cfg->error_code, cfg->error_row, cfg->error_col); cfg_delete(&cfg); return 1; } // no errors
void cfg_io_use_stdio(struct cfg_t *cfg)
Resets the internal I/O pointers to the normal stdio functions. This is done automatically when you allocate a new cfg_t
structure, but you can call this manually, if you want to reuse an existing instance after you've modified the I/O pointers by yourself.
const char *cfg_get_version_number(void)
Returns a UTF-8 string containing the library version number. This is never NULL.
const char *cfg_get_copyright(void)
Returns a UTF-8 encoded library copyright string. You can display this anywhere you want. This never returns NULL.
The following table lists all error codes ncfg currently uses. The "structure" column contains an X if the error code updates the row/column numbers in the cfg_t
structure.
Symbolic value | Explanation | Structure |
---|---|---|
CFG_OK | No errors | |
CFG_NORMAL_END CFG_UNEXPECTED_END | Internally used to signal various end-of-file states. You should never get any of these; if you do, it's probably a bug in ncfg. | |
CFG_INVALID_UTF8 | The file you were parsing contained an invalid UTF-8 codepoint. You must fix the file before loading it. | |
CFG_IO_ERROR | File read or write operation failed for some reasons. Check the file names and permissions. | |
CFG_UNKNOWN_ERROR | Something went wrong, but ncfg does not know what. Most likely a bug. | |
CFG_SYNTAX_ERROR | The file you were loading had a syntax error somewhere. | X |
CFG_ARGUMENT_ERROR | You passed invalid arguments to some of the ncfg's functions. | |
CFG_INVALID_DATA | The tree structure contains invalid data. That is, pointers are not NULL when they were supposed to be NULL and vice versa. Or something else. Either way, something is wrong. | |
CFG_TOO_MANY_CLOSING_BRACES | The parser encountered too many closing }'s. | X |
CFG_UNBALANCED_NESTING_LEVEL | The file you were reading had more starting {'s than ending }'s. | X |
CFG_STRAY_COMMENT_END | The parser encountered a stray C-style comment end (*/ ), but no matching comment start (/* ). | X |
CFG_OUT_OF_MEMORY | ncfg ran out of memory. You might not get this on modern systems. | |
CFG_CANT_OPEN_FILE | ncfg can't open the file you asked it to open. | |
CFG_INTERNAL_PARSER_ERROR | The parser encountered an internal error and it could not recover. Very likely a bug in the parser. See if you can create a minimal testcase, then send it to me and I'll see what I can do. | Maybe |
CFG_INVALID_SECTION_DEFINITION | The section definition was botched. Most likely you tried to add multiple sections under one item, or you tried to create a section without an associated item. Check the file syntax. | X |
CFG_KEY_CANNOT_BE_EMPTY | For some reason, ncfg's parser could not determine the item's key, leaving it empty. Keys cannot be empty, so you probably have a syntax error in your file, or you've discovered a bug in ncfg. | X |
CFG_PATH_TOO_LONG | Each of the path segments (segment1/segment2/.../segmentN ) cannot be longer than 255 bytes. Note that they're bytes, not UTF-8 codepoints. I'll probably remove this limitation in the future. | |
CFG_NOT_FOUND | The item, section or value was not found. Check the names and/or indexes. | |
CFG_NO_IO_FUNCTIONS | The required I/O functions ncfg needs are not defined. You need to define only the read functions if you don't need to write anything (and vice versa). | |
CFG_VALUE_SIZE_ERROR | You passed in a too large value to the item creation/appending functions. | |
CFG_CALLBACK_ABORTED | The callback function passed to cfg_for_each_item() aborted the iteration. |
Version 0.9.2 (released on August 4 2013) is the first publicly released version, so everything is new.
cfg_get_item
does not handle trailing slashes correctly.I am unable to say when new versions will be released. I am following YAGNI principle with ncfg (You Aint Gonna Need It), so features are added only when they're needed. Having said that, I do have a list of things I'll probably add at some point:
cfg_get_item
sucks. It's unnecessarily complex and limited and it doesn't support every possible path variation.If you come up with useful ideas for new features, don't hesitate to contact me.
Like its predecessor, ncfg is licensed under the zlib/libpng license:
ncfg v0.9.2 Copyright (c) 2012-2013 Jarmo Pietiläinen
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
This documentation is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
You can contact me via e-email. My address and other projects I've done can be found on my home page at http://z0b.kapsi.fi/. I am open to new ideas, suggestions and other criticism.