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 |
# NINI |
||||||
Nano INI parser |
|
||||||
|
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