A lightweight C library for reading, writing, and modifying IniNG-format .ini files entirely in memory. All mutations are implemented as string search-and-replace operations on a single heap buffer, so comments, indentation, and whitespace are preserved on every round-trip.
IniNG extends the classic .ini format with an explicit two-level hierarchy: groups contain sites, and sites contain key/value pairs. Comments (;) are supported on any line.
# IniNg - Ini Next Gen
{GroupOne} ; Group One
[kelly] ; Site kelly
address = 192.168.128.23
port = 8554
type = RTSP TCP
user = root
password = agilemesh1
isCell = false
[End]
[Wiles] ; Site Wiles
address = 192.168.128.24
port = 554
type = RTSP UDP
user = root
password = agilemesh1
isCell = false
[End]
{End}
{GroupTwo} ; Group Two
[MegaPixel]
address = 192.168.129.33
port = 8554
type = RTSP TCP
user = root
password = agilemesh1
isCell = false
[End]
{End}Rules:
- Groups are delimited by
{GroupName}…{End} - Sites are delimited by
[SiteName]…[End] - Key/value pairs use
key = value(whitespace around=is ignored) - Inline comments start with
; - All names are case-insensitive
- Leading whitespace (tabs/spaces) before
[and{is allowed and preserved
make # builds libining.a
make test # builds and runs the test suite (45 tests)
make clean # removes build artifactsRequires a C11-compatible compiler (gcc or clang). No external dependencies.
To link against your own project:
gcc -std=c11 myapp.c ining.c -o myapp
# or with the static library:
gcc -std=c11 myapp.c -L. -lining -o myappAll functions are declared in ining.h. The opaque handle IniNG * owns the in-memory buffer. Call iningSave() to persist changes and iningFree() when done.
// Load a file from disk into memory. Returns NULL on error.
IniNG *iningLoad(const char *filename);
// Parse an in-memory string instead of a file.
IniNG *iningParse(const char *text);
// Write the (possibly modified) document back to disk.
// Pass NULL to write back to the original file.
// Returns 0 on success, -1 on error.
int iningSave(IniNG *ini, const char *filename);
// Return the current document as a heap-allocated string. Caller must free().
char *iningToString(const IniNG *ini);
// Free all memory.
void iningFree(IniNG *ini);// Get a value. Returns NULL if group, site, or key is not found.
// The returned pointer is valid until the next mutating call.
const char *iningGet(const IniNG *ini,
const char *group,
const char *site,
const char *key);
// Return non-zero if the group exists.
int iningHasGroup(const IniNG *ini, const char *group);
// Return non-zero if the site exists within the group.
int iningHasSite(const IniNG *ini, const char *group, const char *site);All write operations modify the in-memory buffer only. Call iningSave() to persist.
// Set a key to a new value, or add the key if it does not exist.
// Returns 0 on success, -1 if the group or site is not found.
int iningSet(IniNG *ini,
const char *group,
const char *site,
const char *key,
const char *value);
// Add a new group. Returns 0 on success, -1 if it already exists.
int iningAddGroup(IniNG *ini, const char *group, const char *comment);
// Add a new site inside an existing group.
// Returns 0 on success, -1 if the group is not found or site already exists.
int iningAddSite(IniNG *ini,
const char *group,
const char *site,
const char *comment);// Delete a single key from a site. Returns 0 on success, -1 if not found.
int iningDeleteKey(IniNG *ini,
const char *group,
const char *site,
const char *key);
// Delete an entire site and all its keys. Returns 0 on success, -1 if not found.
int iningDeleteSite(IniNG *ini, const char *group, const char *site);
// Delete an entire group and all its sites/keys. Returns 0 on success, -1 if not found.
int iningDeleteGroup(IniNG *ini, const char *group);// Fill *names with a NULL-terminated array of group name strings.
// *count receives the number of groups found.
// Caller must free(*names) but NOT the individual strings.
int iningListGroups(const IniNG *ini, const char ***names, int *count);
// Same, for sites within a group.
int iningListSites(const IniNG *ini, const char *group,
const char ***names, int *count);#include "ining.h"
#include <stdio.h>
int main(void) {
IniNG *ini = iningLoad("cameras.ini");
if (!ini) { perror("load failed"); return 1; }
// Read a value
const char *addr = iningGet(ini, "GroupOne", "kelly", "address");
printf("kelly address: %s\n", addr); // 192.168.128.23
// Update an existing key
iningSet(ini, "GroupOne", "kelly", "port", "9000");
// Add a new key to an existing site
iningSet(ini, "GroupOne", "kelly", "timeout", "30");
// Add a brand-new group and site
iningAddGroup(ini, "GroupThree", "Offsite cameras");
iningAddSite (ini, "GroupThree", "Roof", "Rooftop camera");
iningSet(ini, "GroupThree", "Roof", "address", "10.0.0.50");
iningSet(ini, "GroupThree", "Roof", "port", "554");
iningSet(ini, "GroupThree", "Roof", "type", "RTSP TCP");
// Delete a site and a key
iningDeleteSite(ini, "GroupTwo", "MegaPixel");
iningDeleteKey (ini, "GroupOne", "Wiles", "password");
// Save (overwrites the original file)
iningSave(ini, NULL);
iningFree(ini);
return 0;
}const char **groups;
int gcount;
iningListGroups(ini, &groups, &gcount);
for (int i = 0; i < gcount; i++) {
printf("Group: %s\n", groups[i]);
const char **sites;
int scount;
iningListSites(ini, groups[i], &sites, &scount);
for (int j = 0; j < scount; j++) {
const char *addr = iningGet(ini, groups[i], sites[j], "address");
printf(" [%s] address=%s\n", sites[j], addr ? addr : "(none)");
free((char *)sites[j]);
}
free(sites);
free((char *)groups[i]);
}
free(groups);const char *text =
"{Cameras}\n"
" [Lobby]\n"
" address = 192.168.1.10\n"
" port = 554\n"
" [End]\n"
"{End}\n";
IniNG *ini = iningParse(text);
printf("%s\n", iningGet(ini, "Cameras", "Lobby", "address"));
iningFree(ini);- Single buffer, no tree. The document is stored as one
char *string. Mutations splice new text in and out viabuf_replace(). This keeps the implementation simple and guarantees the output is always human-readable. - Zero external dependencies. Only the C standard library and POSIX
strncasecmp/strdup. - Case-insensitive name matching. Group, site, and key lookups all use
strncasecmp, soGroupOne,groupone, andGROUPONEall refer to the same group. - Comments and whitespace preserved. Because edits are positional string splices rather than full serialisations, untouched lines come out exactly as they went in.
iningGet()return value lifetime. The returned pointer is into a small internal scratch buffer that is overwritten on the next call toiningGet(). Copy the value withstrdup()if you need to keep it across calls.
| File | Description |
|---|---|
ining.h |
Public API header |
ining.c |
Library implementation |
test_ining.c |
Test suite (45 tests) |
Makefile |
Build rules |