code import
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
*.obj
|
||||
*.so
|
||||
*.out
|
||||
*.a
|
||||
CMakeLists.txt
|
||||
cmake-build-*/
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user