A simple and small ini file parsing library. Licensed under MIT.
I tried to make this easily usable for anyone, no matter how inexperienced or stubborn. If you are new to C, I recommend you use the source inclusion method. I plan on adding a header-only "mirror" of this library in the future, so check back if that's your kinda thing.
Just paste ini.c and ini.h into your project and compile
ini.c along with your own project files.
Just drop this repo into your project and add it as a subdirectory to your target. Something like:
add_subdirectory(ini)
target_link_libraries(project_name PRIVATE ini)If you the modularity of CMake but are allergic to modern build
systems for some reason, you can slap this repo into your root
and call the Makefile from your own. You can grab the object file
from ini/build/ afterwards and do what you please with it.
$(MAKE) -C ini/ release # release build
$(MAKE) -C ini/ debug # debug build
INI_OBJ = ini/build/ini.o # grab the object file. you're on
# your own from here
For documentation on each function, macro, struct, and more, you
can and should consult the header. For all examples that
you see in this section, you can find compiler-ready code in the
example/ directory. Some things, like parsing raw text rather
than files, are not covered in this document.
Consider a basic .ini file.
[Text]
greeting = Hello
subject = World
enthusiastic = true
[Execution]
iterations = 5
[Pi]
string = A proper number of digits for pi are:
pi = 3.1415The easiest way to parse this file is to create our database object and pass the filepath to the parsing function.
INIData_t *ini = ini_create_data();
ini_read_file("example/example.ini", ini, NULL, 0);
// do what you need
ini_free_data(ini);On success, the ini_read_file function returns the very same
ini parser that you passed. On failure, it returns NULL.
This library doesn't necessarily force you to check for this, but
you would probably still like to know if that happens.
if (!ini_read_file("example/example.ini", ini, NULL, 0)) {
// Handle accordingly.
}You'll notice that we have two additional arguments. Above, we
pass NULL where we could pass a pointer to an INIError_t
object. If we passed a valid pointer instead, the object would
populate the appropriate struct members with details of any
error encountered while parsing.
INIData_t *ini = ini_create_data();
INIError_t error;
ini_read_file("example/example.ini", ini, &error, 0);After parsing our file, we could print out the details of any error that might occur.
ini_read_file("example/example.ini", &ini, &error, 0);
if (error.encountered)
{
printf("%s\n%s\n%*s^\n", error.msg, error.line, error.offset, "");
// do anything else you need to do
}That fourth parameter is a 64-bit integer that accepts flags. As of right now, there are only two flags implemented:
INI_CONTINUE_PAST_ERRORwill allow a call toini_read_file()to continue parsing after encountering an error.INI_ALLOW_DUPLICATE_SECTIONSallows duplicate sections to be parsed, and will place pairs under the duplicate into the original section.
So, we could have done:
INIData_t *ini = ini_create_data();
INIError_t error;
ini_read_file("example/example.ini", ini, &error, INI_CONTINUE_PAST_ERROR);
if (error.encountered)
print_error(&error); Now, the parser will only fail if it can't continue, and you can see what exactly what went wrong if that happens.
After we construct our INIData_t object, we can query for the
data. The most basic way to do this is to just get the value string
by providing the key and section strings.
const char *greeting = ini_get_value(&ini, "Text", "greeting");On success, greeting will be a null-terminated string with the
value "Hello" from our .ini file. If this failed, then greeting
will be a NULL pointer. This works fine and the most basic way
to make a query.
There are numerous other functions available to make queries for specific types and provide a default value. Note, queries for numeric types use the largest of the appropriate primitives, so to avoid aggressive compiler warnings you can simply cast the return value.
// Query for strings
const char *subject = ini_get_string(&ini, "Text", "subject", "nobody...");
// Query for bools
bool enthusiastic = ini_get_bool(&ini, "Text", "enthusiastic", false);
const char enthusiasm = enthusiastic ? '!' : '.';
// Query for signed integers
int iterations = (int)ini_get_signed(&ini, "Execution", "iterations", 1);
for (int i = 0; i < iterations; i++)
printf("%s, %s%c\n", greeting, subject, enthusiasm);
const char *pi_string = ini_get_string(&ini, "Pi", "string", "pi =");
// Query for floats
float pi = (float)ini_get_float(&ini, "Pi", "pi", 3);
printf("%s %f\n", pi_string, pi);This library gives you the option to use the stack rather than the
heap. All this changes about our code is how we initialize the
INIData_t object.
const int max_sections = 32;
const int max_pairs = 32;
// Create our data on the stack
INISection_t sections[max_sections];
INIPair_t pairs[max_sections][max_pairs];
INIPair_t *section_pairs[max_sections];
for (int i = 0; i < max_sections; i++)
section_pairs[i] = pairs[i];
INIData_t ini;
ini_init_data(&ini, sections, section_pairs, max_sections, max_pairs);This block of code shows us create stack-allocated arrays of objects
that we will need, then uses the ini_init_data() helper function
to initialize our object. Everything else stays the same, except if
we parse a file with more sections/pairs than we have allocated, the
parsing will fail. However, if we pass the INI_CONTINUE_PAST_ERROR
flag, then the excess sections and pairs will simply be ignored.
; this style of comment
# ... and this one too
[Sections]
[Duplicate Sections]
# [Duplicate Sections]
; ... can only be duplicated with
; `INI_CONTINUE_PAST_ERROR` or
; `INI_ALLOW_DUPLICATE_SECTIONS` passed to the parsing functions
[Sections with spaces]
key=value
key=value with spaces
this: style of pair is legal too
[_Section_with_underscores_]
key1: this is allowed
_key_: this is also allowed
; special characters are allowed in values (aside from comments and [])
path=C:\path\on\windows\gross\
unix=/much/better.txtThe library is open to contributions. I don't have any strict guidelines besides please don't make any stylistic contributions. I'm open to anything else, please don't make me change my mind on that :)
Make sure all test cases pass under both debug and release. Ensure no
memory is leaked. I will be running these tests if you open a PR. If
they fail or a memory profiler screams about your code, I'm going
to immediately close your pull request. I've made this easy for you
with a build.sh build script. Here is your short usage guide:
bash build.sh -rt # build tests under release
bash build.sh -r # build library under release
bash build.sh -t # build tests under debug
bash build.sh # build library under debugIf you make a low-effort contribution (issue, PR, anything) it will be ignored and you will be blocked. This includes obvious use of AI code generation.
- rktest by Rasmus Källqvist
- Yousha's .gitignore for C/C++ developers