6 changed files with 281 additions and 2 deletions
@ -0,0 +1,6 @@ |
|||
*.obj |
|||
*.so |
|||
*.out |
|||
*.a |
|||
CMakeLists.txt |
|||
cmake-build-*/ |
@ -0,0 +1,5 @@ |
|||
a.out: test.c nini.c |
|||
cc -Og test.c nini.c |
|||
|
|||
run: a.out |
|||
./a.out |
@ -1,2 +1,36 @@ |
|||
# nini |
|||
Nano INI parser |
|||
# NINI |
|||
|
|||
This parser aims to be the smallest possible while supporting a sufficient subset of the INI format. |
|||
The parser is meant for use in embedded applications. |
|||
|
|||
When compiled for a bare metal Cortex-M0, **the whole parser fits in 336 bytes** of Flash and 30 (+buffers) bytes of RAM. |
|||
|
|||
## Features |
|||
|
|||
- Basic INI format support |
|||
- Accepts both Unix and DOS newlines |
|||
- Minimal memory footprint |
|||
- The input file can be loaded in multiple pieces of any size. |
|||
- Buffer sizes (section, key, value) can be configured in the header file. |
|||
- Custom data `void *` for maintaining user context, passed to the callback |
|||
|
|||
## Sypported syntax |
|||
|
|||
Any whitespace, except inside a value, is discarded. |
|||
|
|||
- **Sections** - `[section.name]` |
|||
- **Key-value pairs** - `key.foo-bar_baz123 = value lorem ipsum 123` |
|||
- Value can contain whitespace, leading and trailing whitespace is removed. |
|||
- Ends with either `\r` or `\n` |
|||
- **Comment** `# comment ...` |
|||
- Ends with either `\r` or `\n` |
|||
|
|||
## Limitations |
|||
|
|||
- Not re-entrant, uses a static state variable |
|||
- No checks for invalid syntax |
|||
- Whitespace inside keys and section names is removed |
|||
- Quoted strings and escape sequences are not supported, will be collected as plain text |
|||
- Value can't be followed by an inline comment |
|||
|
|||
See the file `test.c` for an example of the most basic usage; see the header file for more details on the API. |
|||
|
@ -0,0 +1,143 @@ |
|||
///
|
|||
/// nini - Nano INI parser
|
|||
///
|
|||
/// Written by MightyPork, 2018
|
|||
/// MIT license
|
|||
///
|
|||
|
|||
|
|||
#include "nini.h" |
|||
|
|||
enum nini_state { |
|||
NINI_IDLE, |
|||
NINI_SECTION, |
|||
NINI_KEY, |
|||
NINI_VALUE, |
|||
NINI_COMMENT, |
|||
}; |
|||
|
|||
static struct { |
|||
uint8_t section_i; |
|||
char section[INI_KEY_MAX]; |
|||
|
|||
uint8_t key_i; |
|||
char key[INI_KEY_MAX]; |
|||
|
|||
uint8_t value_i; |
|||
char value[INI_VALUE_MAX]; |
|||
bool val_last_space; |
|||
|
|||
IniParserCallback cb; |
|||
void *userdata; |
|||
enum nini_state state; |
|||
} nini; |
|||
|
|||
void ini_parse_begin(IniParserCallback callback, void *userData) |
|||
{ |
|||
ini_parse_reset(); |
|||
nini.cb = callback; |
|||
nini.userdata = userData; |
|||
} |
|||
|
|||
void ini_parse(const char *data, size_t len) |
|||
{ |
|||
for (; len > 0; len--) { |
|||
char c = *data++; |
|||
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { |
|||
if (nini.state != NINI_VALUE && nini.state != NINI_COMMENT) |
|||
continue; |
|||
} |
|||
|
|||
switch (nini.state) { |
|||
case NINI_IDLE: |
|||
if (c == '[') { |
|||
nini.state = NINI_SECTION; |
|||
nini.section_i = 0; |
|||
} |
|||
else if (c == '#') { |
|||
nini.state = NINI_COMMENT; |
|||
} |
|||
else { |
|||
nini.state = NINI_KEY; |
|||
nini.key_i = 0; |
|||
nini.value_i = 0; |
|||
nini.val_last_space = false; |
|||
nini.key[nini.key_i++] = c; |
|||
} |
|||
break; |
|||
|
|||
case NINI_COMMENT: |
|||
if (c == '\n' || c == '\r') { |
|||
nini.state = NINI_IDLE; |
|||
} |
|||
break; |
|||
|
|||
case NINI_SECTION: |
|||
if (c == ']') { |
|||
nini.section[nini.section_i] = 0; |
|||
nini.state = NINI_COMMENT; // discard to EOL
|
|||
break; |
|||
} |
|||
else if (nini.section_i < INI_KEY_MAX - 1) { |
|||
nini.section[nini.section_i++] = c; |
|||
} |
|||
break; |
|||
|
|||
case NINI_KEY: |
|||
if (c == '=') { |
|||
nini.key[nini.key_i] = 0; |
|||
nini.state = NINI_VALUE; |
|||
} |
|||
else if (nini.key_i < INI_KEY_MAX - 1) { |
|||
nini.key[nini.key_i++] = c; |
|||
} |
|||
break; |
|||
|
|||
case NINI_VALUE: |
|||
switch (c) { |
|||
case ' ': |
|||
case '\t': |
|||
if (nini.value_i) nini.val_last_space = true; |
|||
break; |
|||
case '\r': |
|||
case '\n': |
|||
nini.value[nini.value_i] = 0; |
|||
nini.state = NINI_IDLE; |
|||
nini.cb(nini.section, nini.key, nini.value, nini.userdata); |
|||
break; |
|||
default: |
|||
if (nini.val_last_space && nini.value_i < INI_VALUE_MAX - 1) { |
|||
nini.value[nini.value_i++] = ' '; |
|||
} |
|||
|
|||
if (nini.value_i < INI_VALUE_MAX - 1) { |
|||
nini.value[nini.value_i++] = c; |
|||
} |
|||
nini.val_last_space = false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
void *ini_parse_end(void) |
|||
{ |
|||
if (nini.state == NINI_VALUE) { |
|||
nini.value[nini.value_i] = 0; |
|||
nini.state = NINI_IDLE; |
|||
nini.cb(nini.section, nini.key, nini.value, nini.userdata); |
|||
} |
|||
|
|||
return nini.userdata; |
|||
} |
|||
|
|||
void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData) |
|||
{ |
|||
ini_parse_begin(callback, userData); |
|||
ini_parse(text, len); |
|||
ini_parse_end(); |
|||
} |
|||
|
|||
void ini_parse_reset(void) |
|||
{ |
|||
nini.state = NINI_IDLE; |
|||
} |
@ -0,0 +1,69 @@ |
|||
///
|
|||
/// nini - Nano INI parser
|
|||
///
|
|||
/// Written by MightyPork, 2018
|
|||
/// MIT license
|
|||
///
|
|||
|
|||
#ifndef INIPARSE_H |
|||
#define INIPARSE_H |
|||
|
|||
#include <stdint.h> |
|||
#include <stdbool.h> |
|||
#include <stdlib.h> |
|||
#include <stdio.h> |
|||
|
|||
// buffer sizes
|
|||
#define INI_KEY_MAX 20 |
|||
#define INI_VALUE_MAX 30 |
|||
|
|||
/**
|
|||
* INI parser callback, called for each found key-value pair. |
|||
* |
|||
* @param section - current section, empty string for global keys |
|||
* @param key - found key (trimmed of whitespace) |
|||
* @param value - value, trimmed of quotes or whitespace |
|||
* @param userData - opaque user data pointer, general purpose |
|||
*/ |
|||
typedef void (*IniParserCallback)(const char *section, const char *key, const char *value, void *userData); |
|||
|
|||
/**
|
|||
* Begin parsing a stream |
|||
* |
|||
* @param callback - key callback to assign |
|||
* @param userData - optional user data that will be passed to the callback |
|||
*/ |
|||
void ini_parse_begin(IniParserCallback callback, void *userData); |
|||
|
|||
/**
|
|||
* End parse stream. |
|||
* Flushes what remains in the buffer and removes callback. |
|||
* |
|||
* @returns userData or NULL if none |
|||
*/ |
|||
void* ini_parse_end(void); |
|||
|
|||
/**
|
|||
* Parse a string (needn't be complete line or file) |
|||
* |
|||
* @param data - string to parse |
|||
* @param len - string length (0 = use strlen) |
|||
*/ |
|||
void ini_parse(const char *data, size_t len); |
|||
|
|||
/**
|
|||
* Parse a complete file loaded to string |
|||
* |
|||
* @param text - entire file as string |
|||
* @param len - file length (0 = use strlen) |
|||
* @param callback - key callback |
|||
* @param userData - optional user data for key callback |
|||
*/ |
|||
void ini_parse_file(const char *text, size_t len, IniParserCallback callback, void *userData); |
|||
|
|||
/**
|
|||
* Explicitly reset the parser |
|||
*/ |
|||
void ini_parse_reset(void); |
|||
|
|||
#endif // INIPARSE_H
|
@ -0,0 +1,22 @@ |
|||
#include "nini.h" |
|||
#include <string.h> |
|||
|
|||
const char *testfile = |
|||
"# Comment ...........\n" |
|||
"rootkey = Value\r\n" |
|||
"\r\n" |
|||
" [ Section@name!@+12346 ]\r\n" |
|||
"sec.key-a_b-1 = 444\r\n" |
|||
"sec.key2 = Test 123456 with spaces\r\n" |
|||
"[ this is a section with spaces nthat will be removed ]\n" |
|||
"this_one_has_no_eol = 123456"; |
|||
|
|||
|
|||
void test_cb(const char *section, const char *key, const char *value, void *userData) |
|||
{ |
|||
printf("[%s] >%s< = >%s<\r\n", section, key, value); |
|||
} |
|||
|
|||
int main (void) { |
|||
ini_parse_file(testfile, strlen(testfile), test_cb, NULL); |
|||
} |
Loading…
Reference in new issue