I started writing a configurations parser yesterday for my current free-time project ... I finished it a couple hours ago. It is a nested free format. It offers variables, lists and comments as well.
It has the following format:
# Comment
"\"/var/log\"" = "escape characters"
"Nest 1" {
"var" = "value"
"var" = "duplicates work too"
"list item 1" "list item 2"
"list item 3"
"list item 4"
"Nest inside a nest" {
# Another comment
}
"list item 5" "list item 6" "list item 7"
}
"Nest 1" {
"a duplicate nest"
}
"cow" = "moo"
"Colors" {
"Blue"
"Green" "Purple" "Brown"
}
As I said, it is free format, so white space has no meaning. Comments are the exception, they are delimited by # and new line.
This is the header:
#define CONF_COM '#'
#define CONF_BEG '{'
#define CONF_END '}'
#define CONF_EQU '='
#define CONF_QUO '\"'
#define CONF_IGN '\\'
#define CONF_ERR -1
#define CONF_EOF 0
#define CONF_VAR 1
#define CONF_NEST 2
#define CONF_LIST 3
/* Results */
extern char *conf_name;
extern char *conf_value;
struct conf_node {
char *nest;
unsigned int pos;
};
struct conf_file {
char *buff;
size_t size;
struct stack nests;
};
/* File operations */
int conf_init( struct conf_file *cf, FILE *file );
void conf_free( struct conf_file *cf );
int conf_flush( struct conf_file *cf, FILE *file );
/* Syntax operations */
int conf_readvar( struct conf_file *cf, const char *var, unsigned int n );
int conf_countvar( struct conf_file *cf, const char *var );
int conf_readlist( struct conf_file *cf, const char *list, unsigned int n );
int conf_countlist( struct conf_file *cf, const char *list );
int conf_opennest( struct conf_file *cf, const char *nest, unsigned int n );
void conf_closenest( struct conf_file *cf );
const char* conf_currentnest( struct conf_file *cf );
int conf_countnest( struct conf_file *cf, const char *nest );
File operationsint conf_init( struct conf_file *cf, FILE *file );
void conf_free( struct conf_file *cf );
int conf_flush( struct conf_file *cf, FILE *file );
This library doesn't directly touch files, instead it accepts a FILE* to load the file into a buffer. The assumption is that the file is relatively small (e.g. We shouldn't expect an ASCII configurations file to be megabytes in size).
int conf_init( struct conf_file *cf, FILE *file ) /* reads a file into the buffer */
void conf_free( struct conf_file *cf ) /* frees the buffer and stack of a struct conf_file */
int conf_flush( struct conf_file *cf, FILE *file ) /* flushes the buffer to file */
These functions return 0 for success, -1 for error ... The errors can be caused from anything from inability to seek, to inability to allocate memory, to bad syntax (sanity checks are done).
Syntax operationsint conf_readvar( struct conf_file *cf, const char *var, unsigned int n );
int conf_countvar( struct conf_file *cf, const char *var );
int conf_readlist( struct conf_file *cf, const char *list, unsigned int n );
int conf_countlist( struct conf_file *cf, const char *list );
int conf_opennest( struct conf_file *cf, const char *nest, unsigned int n );
void conf_closenest( struct conf_file *cf );
const char* conf_currentnest( struct conf_file *cf );
int conf_countnest( struct conf_file *cf, const char *nest );
No constraints are placed on the actual syntax usage. These functions even handle cases where variables, nests and list items are duplicates. That is, they enable you to do things like read the 2nd occurrence of a variable named "moo". To note, all results are stored in global variables
conf_name and
conf_value. These are mainly for convenience and it is assumed that you
don't have multiple threads using the library. This library is meant to load settings at the very beginning of execution. The reason I chose to use global variables to hold the resulting values is that I wrote a similar library last year in C++ where you provided it a pointer to store the results. This made the functions more complicated and it burdended the programmer to free the memory afterwards. Here, this is all taken care of automagically.
int conf_readvar( struct conf_file *cf, const char *var, unsigned int n ) /* Reads the n'th occurrence of var, if var is NULL it reads the n'th variable. n = 0 implies
first occurrence. The results are stored in conf_name and conf_value respectively */
int conf_countvar( struct conf_file *cf, const char *var ) /* Counts the occurrences of variables named var, if var is NULL, counts the number of variables */
int conf_readlist( struct conf_file *cf, const char *list, unsigned int n ) /* Reads n'th occurrence of list, if list is NULL, it reads the n'th list item. The results are stored in conf_name - This is useful for testing if a value is defined in a list, or simply to query the n'th list item */
int conf_countlist( struct conf_file *cf, const char *list ) /* This counts the occurrences of list, if list is NULL, it counts the number of list items */
int conf_opennest( struct conf_file *cf, const char *nest, unsigned int n ) /* This opens the n'th occurrence of a nest for reading, if nest is NULL it opens the n'th nest, this gives all the functions the nest's scope ... underneath these are managed with a stack. The result is stored in conf_name */
void conf_closenest( struct conf_file *cf ) /* This closes the currently opened nest */
const char* conf_currentnest( struct conf_file *cf ) /* This returns the name of the currently opened nest */
int conf_countnest( struct conf_file *cf, const char *nest ) /* This counts the occurrences of nest, if nest is NULL it counts the number of nests */
These functions return 0 for success and -1 for failure. The errors can be caused from the non-existence of the queried item or if memory couldn't be allocated for conf_name or conf_value.
There is room for adding write functions, in fact I have set it up so that adding such functions is not a terrible pain. However, the problem with writing configuration files is that this library cannot recognize a configuration file style of format ... much like there are many different styles of writing C/C++ Java and many other languages. As I said, I wrote a similar library in C++ and it introduced such write functions, but it got really ugly if many writes took place since it does not preserve the original style.
On a final note, everything is case sensative!
Here is the conf library, feel free to use it for anything (I put it under BSDL).
http://nslay.36bit.com/conf.zipIt comes with 4 files:
conf.h - The header to include
conf.c - The meat
stack.h - A header for a simple and generalized stack
stack.c - More meat
Examplestruct conf_file cf;
FILE *file = fopen( "test.conf", "r" );
if ( file == NULL ) {
perror( "fopen()" );
return -1;
}
if ( conf_init( &cf, file ) ) {
printf( "Could not read file, please check for syntax errors!\n" );
return -1;
}
fclose( file );
if ( conf_opennest( &cf, NULL, 0 ) ) {
printf( "Could not open first nest!\n" );
return -1;
}
printf( "Opened %s\n", conf_name );
if ( conf_readvar( &cf, "moo", 2 ) ) {
printf( "There is no 3rd occurrence of \"moo\"!\n" );
return -1;
}
printf( "%s = %s\n", conf_name, conf_value );
conf_free( &cf );
EDIT: I added a memory check to conf_extract (not documented here, not for external use!) ... it is a function that extracts strings from quotes and strips the escape characters ... it allocates memory to store the variably sized data. Previously this was not accounted for. As a result, the conf_count* functions return int instead of unsigned int since it must return -1 if it cannot extract a string to compare. Pedantic, but its for robustness. conf.zip has been updated.