A short introduction to scconf as an API and a file format ========================================================== written by Jamie Honan The scconf system is a small system library for handling scconf files. Why should anyone care about scconf format? It is a handy format for short pieces of structured data. Handy because: - it is readable, which makes support easy - it is easy to parse and write - it is extensible, you can add fields without breaking things It isn't - XML, so it doesn't need xml parsing - suitable for large amounts of data, like a database or text files It doesn't have - anything else but data. No locking, no threads etc. It has hierarchical data blocks, it has lists. Similar, but different: - .ini files. scconf is block structured, has lists and arrays - xml. xml is more complete, but requires a lot of overhead - sexp. sexp resembles lisp with it's use of parenthesis. sexp has modes for binary. scconf really doesn't have binary - yaml. yaml is larger What does it look like? ======================= Like this: transport_stream { id = 0x0009; original_network_id = 0x1000; sat_tuning_info { frequency = 12278000; symbol_rate = 30000000; polarization = 0; } service { id = 0x0064; pmt_pid = 0x0101; type = 144; name = "aGuide"; provider_name = "A"; } service { id = 0x238D; pmt_pid = 0x0623; type = 144; name = "aCar"; provider_name = "A"; } } Why doesn't it have X, why don't you use XML? ============================================= Maybe it should. Maybe XML is the answer. Maybe a database is more appropriate. It's all a trade-off. You choose. API === There are four useful structures. scconf_block, scconf_list, scconf_item, and a scconf_context. A context is similar to a file, except in memory. Within a context there is a root block. Within each block there are one or more items. Items can be sub-blocks, lists, or comments. Every item can have a name, or key. A list can have one or more values; boolean, integer or string. A context contains a root block, which contains one or more blocks. A block is : key [[,] name [[,] name ... ] ] { block_contents } block_contents is one or more block_items block_items is one of # comment string \n or key [[,] name [[,] name ... ] ] = value [[,] value ... ]]; or block Initialising and file handling ============================== Allocate scconf_context The filename can be NULL. The file is not read at this point, but in the function scconf_parse. scconf_context *scconf_new(const char *filename); Free scconf_context void scconf_free(scconf_context * config); Parse configuration Returns 1 = ok, 0 = error, -1 = error opening config file int scconf_parse(scconf_context * config); Write config to a file If the filename is NULL, use the config->filename Returns 0 = ok, else = errno int scconf_write(scconf_context * config, const char *filename); Finding items and blocks ======================== Find a block by key If the block is NULL, the root block is used const scconf_block *scconf_find_block(const scconf_context * config, const scconf_block * block, const char *item_name); This finds a block in the given context. This function doesn't descend the hierarchy, it only finds blocks in the top level of either the context (the root block) or of the block given in the block parameter (if not NULL). The block pointer returned points to data held by the context, hence the const qualifier. Find blocks by key and possibly name If the block is NULL, the root block is used The key can be used to specify what the blocks first name should be scconf_block **scconf_find_blocks(const scconf_context * config, const scconf_block * block, const char *item_name, const char *key); This function is similar to scconf_find_block above, except that an array of pointers to matched blocks is returned. Each pointer points to data held by the context. The last entry in the returned table is the null pointer. The table should be freed after use, but the individual pointers to blocks point to data held by the context. The key values for blocks is matched. If name is not NULL, the block name must also match. Get a list of values for option const scconf_list *scconf_find_list(const scconf_block * block, const char *option); Find an item that has a value (i.e. is not a block nor a comment), and return the values for that item as a list. The list is held in memory owned by the context. Return the first string of the option If no option found, return def const char *scconf_get_str(const scconf_block * block, const char *option, const char *def); This is similar to scconf_find_list, but instead of returning the whole list, just return the first value, as a string. If this is not possible, return the default value. Again the value returned is either a pointer the default value or to memory held by the context. Return the first value of the option as integer If no option found, return def int scconf_get_int(const scconf_block * block, const char *option, int def); This is similar to scconf_get_str, but an integer value is returned. Return the first value of the option as boolean If no option found, return def int scconf_get_bool(const scconf_block * block, const char *option, int def); This completes the types that can be returned by a find. For parsing blocks and items ============================ A table of scconf_entry values is used, terminated by a NULL name value. This table is passed to the routine scconf_parse_entries. This function walks the current context or block, and adds the data to the scconf_entry table entries. Sub-blocks can be walked, using SCCONF_BLOCK, and callbacks can be issued using SCCONF_CALLBACK. This is a handy method for accessing scconf data from within a program. typedef struct _scconf_entry { const char *name; * Look for blocks with this key, or check if this * block has an item with this key. Run the block * or blocks found against the rest of this entry * Stop after the first one, unless * SCCONF_ALL_BLOCKS is set in flags unsigned int type; * SCCONF_CALLBACK * parm contains a function ptr of type * int (*callback)(scconf_context* context, * scconf_block* block, * scconf_entry* entry, * int depth); * run the callback with the block found * * SCCONF_BLOCK * param contains a pointer to another entry table * use the found block against every entry * in the pointed entry table * * SCCONF_LIST * SCCONF_BOOLEAN * SCCONF_INTEGER * SCCONF_STRING * find the entry with the key given in name in * the found block. Return the value found * to parm as follows: * SCCONF_INTEGER: * if parm not NULL, then * points to integer location to put * the value * SCCONF_BOOLEAN: * if parm not NULL, then * points to integer location to put * the value * SCCONF_STRING: * if parm not NULL, then * if flag bit SCCONF_ALLOC not set * then parm points to a buffer * else * parm points to a pointer where * the pointer to an allocated * buffer should be stored. * if arg is not NULL, points * to a location where the buffer * length (size_t) is to be stored * SCCONF_LIST: * if parm not NULL, then * if flag bit SCCONF_ALLOC not set * then parm points to a location * where a pointer to the list * can be stored * else * then parm points to a location * where a pointer to a copy of list * can be stored * * unsigned int flags; * SCCONF_PRESENT * This bit is or'ed in when found * SCCONF_MANDATORY * If not found, this is a fault * SCCONF_ALLOC * C.f. type above * SCCONF_ALL_BLOCKS * C.f. name above * SCCONF_VERBOSE * For debugging void *parm; void *arg; } scconf_entry; For adding blocks and items =========================== A table of scconf_entry values is used, terminated by a NULL name value. This table is passed to the routine scconf_write_entries. This function adds the scconf_entry table entries to the current block. Sub-blocks can be added, and callbacks can be issued. This is a handy method for adding scconf data from within a program. typedef struct _scconf_entry { const char *name; * key value for blocks and items * unsigned int type; * SCCONF_CALLBACK * parm contains a function ptr of type * int (*callback)(scconf_context* context, * scconf_block* block, * scconf_entry* entry, * int depth); * * SCCONF_BLOCK * param contains a pointer to another entry table * the entry table is added as a block to the * current block, with name as the key, and * arg is a list of names * * SCCONF_LIST * SCCONF_BOOLEAN * SCCONF_INTEGER * SCCONF_STRING * these add key=value pairs to the current * block. The value is in parm. * unsigned int flags; * SCCONF_PRESENT * This bit is or'ed in when item added void *parm; void *arg; } scconf_entry;