parent
80e17ad329
commit
0390122728
@ -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