mINI keeps comments and key order intact when it rewrites a file
INI file reader and writer
At a glance
- What is it?
- mINI is a header-only C++ INI reader and writer whose write path reads the original file back and patches only what changed, so comments and custom formatting survive an edit. It defines a narrow format, ignores keys outside any section, and stores everything as std::string.
- Who is it for?
- mINI suits a C++ project that needs to read and update a small configuration file without pulling in a dependency, especially when the file carries comments that a regenerating writer would destroy. It does not suit a project that needs multiline values, keys outside a section, or strict encoding guarantees, since the format excludes all three and the library leans on the host environment for encoding.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A format definition narrower than most INI dialects
mINI starts by stating exactly which INI dialect it implements, and the list is short enough to check your files against. Section and key names are case insensitive by default. Whitespace around sections, keys and values is ignored. Empty section and key names are allowed, and keys that do not belong to a section are ignored, so a stray key at the top of the file is silently dropped rather than reported. Comments are lines whose first non-whitespace character is a semicolon, trailing comments are allowed on section lines but not on key and value lines, and every entry must sit on a single line because multiline is not supported. The base shape is the familiar one:
; comment
[section]
key = valueThe consequence for a user is that mINI will read most hand-written INI files without complaint and quietly drop the parts it does not model, which is convenient until the day a value that mattered disappeared on write.
Installation means copying one header into your tree
There is nothing to install in the package manager sense. It is a header-only library, so the documented procedure is to copy everything in the src directory into your own project's source folder, or to keep it in a custom location and make sure the compiler sees the extra include directory. Then one include line is all that is needed:
#include "mini/ini.h"There is a CMakeLists.txt in the repository alongside the src and tests directories and a CHANGELOG.md, so a project that wants a proper add_subdirectory or package definition can build one, but the README does not present CMake as the recommended path. Vendoring the header directly is the intended workflow, which is a genuine advantage for a small library and also means there is no version pinning mechanism unless you build one yourself.
write patches the file, generate replaces it
The central design decision shows up in the difference between two methods. Files are read on demand in one go, after which the data lives in memory and is ready to be manipulated, and files are closed after read or write operations. A write call is lazy: it reads the output file, works out which changes have been made, and updates the file accordingly, which is what preserves custom formatting and comments. A generate call instead produces the file from the structure alone, and the documentation warns it overwrites any custom formatting and comments from the original file. Both take an optional pretty-print flag, which adds spaces between key and value pairs and blank lines between sections. The practical rule is simple, use write when editing an existing file and generate when you are producing a new one.
Section and key order is part of the contract
Order is preserved on read and write, and iteration follows the same order as the original file or the order in which keys were added to the structure. That is unusual enough to call out, since most configuration parsers give you a map with no ordering guarantee, and it matters for files where a human reads the diff. The lazy write path is what makes this possible, since the file on disk is the source of truth for ordering and only the changed values are patched in place. There is one wrinkle around case. Section and key names are case insensitive and stripped of leading and trailing whitespace, so ini["section"] and ini["SECTION"] and ini[" sEcTiOn "] all refer to the same entry. Generated files always use lower case names, while writing to an existing file preserves the letter case the original file used for keys and sections that already exist.
Bracket access creates entries, get never does
Reading data has two forms and the difference is a real hazard. The bracket operator returns a reference to the real data, which you may then modify, and it creates a new item automatically if one does not exist. The get function returns a copy and does not create anything. So ini["section"]["key"] on a missing key quietly inserts it, while ini.get("section").get("key") leaves the structure untouched. The library documents has() as the way to check before using the bracket operator if you want to avoid altering the structure as a side effect of a lookup. Setting values goes through the same bracket path, and set() applies a list of key and value pairs at once. Removals are explicit and report success as a boolean, covering a single key, a whole section, all keys in a section and the entire structure.
Values are plain std::string with the encoding caveats that implies
The library operates with std::string to hold values and relies on the host environment for encoding. The documentation puts it plainly, it should play nicely with UTF-8 but your mileage may vary. There is no number or boolean type, so a value like 20 is the string 20 and conversion is on the caller. Whitespace is trimmed on the way in, since values are stripped of leading and trailing whitespace when writing to a file, and the example given is that ini["a"]["b"] = " c " reads back as just c. That normalisation is convenient for comparison but it is lossy, and it applies to values you did not touch if a lazy write rewrites the line. The topic tags on the repository describe the library as ini, ini-config, ini-file, ini-generator, ini-parser, ini-reader and ini-writer, which is a fair summary of a very small surface.
How mINI compares with a general configuration library
The realistic alternative is a full configuration framework, or the standard library facilities for reading structured text, rather than another INI parser. The difference in approach is scope. A general configuration library handles multiple formats, nested hierarchies, typed values, environment overrides and validation, and it brings a dependency and an opinionated model with it. mINI handles one flat format, keeps values as strings, and hands you nothing you did not ask for. That makes it the better answer for a small game tool, an embedded utility or a utility binary that has one configuration file, and the worse answer the moment you need a value that is an integer, a list or a nested table. The other alternative worth naming is writing a parser yourself, which is a common source of subtle bugs around whitespace, case and comment handling. mINI makes that trade explicitly.
Editorial conclusion
mINI suits a C++ project that needs to read and update a small configuration file without pulling in a dependency, especially when the file carries comments that a regenerating writer would destroy. It does not suit a project that needs multiline values, keys outside a section, or strict encoding guarantees, since the format excludes all three and the library leans on the host environment for encoding. Verify first that your existing INI files match the documented rules about comments and sectionless keys, then decide between write and generate, because generate overwrites comments and custom formatting while write patches only the changed entries.
Frequently asked questions
How do I install mINI in a C++ project?
It is a header-only library, so copy everything in the src directory into your own source folder, or use a custom location and make sure the compiler sees the additional include directory. Then include it with #include "mini/ini.h".
What is the difference between write and generate in mINI?
A write call is lazy: it reads the output file, finds which changes have been made, and updates the file, so comments and custom formatting are preserved. A generate call builds the file from the structure and overwrites any custom formatting and comments from the original file.
Are section and key names in mINI case sensitive?
No, they are case insensitive and stripped of leading and trailing whitespace, so ini["section"], ini["SECTION"] and ini[" sEcTiOn "] are the same entry. Generated files always use lower case, while writing to an existing file preserves the letter case of keys and sections that already exist.
Does mINI support multiline values or keys outside a section?
No. Every entry must exist on a single line, multiline is not supported, and keys that do not belong to a section are ignored. Comments are lines starting with a semicolon, and trailing comments are allowed on section lines but not on key and value lines.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/metayeti-mini)Was this useful?