ede/pekwm/CfgParser.cc

738 lines
20 KiB
C++

//
// Copyright © 2005-2009 Claes Nästén <me@pekdon.net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//
#include "CfgParser.hh"
#include "Compat.hh"
#include "Util.hh"
#include <algorithm>
#include <iostream>
#include <memory>
#include <cassert>
#include <cstring>
enum {
PARSE_BUF_SIZE = 1024
};
using std::cerr;
using std::endl;
using std::list;
using std::map;
using std::set;
using std::string;
using std::auto_ptr;
const string CfgParser::_root_source_name = string("");
const char *CP_PARSE_BLANKS = " \t\n";
//! @brief CfgParser::Entry constructor.
CfgParser::Entry::Entry(const std::string &source_name, int line,
const std::string &name, const std::string &value,
CfgParser::Entry *section)
: _section(section),
_name(name), _value(value),
_line(line), _source_name(source_name)
{
}
/**
* Copy Entry together with the content.
*/
CfgParser::Entry::Entry(const CfgParser::Entry &entry)
: _section(0),
_name(entry._name), _value(entry._value),
_line(entry._line), _source_name(_source_name)
{
list<CfgParser::Entry*>::const_iterator it(entry._entries.begin());
for (; it != entry._entries.end(); ++it) {
_entries.push_back(new Entry(*(*it)));
}
if (entry._section) {
_section = new Entry(*entry._section);
}
}
//! @brief CfgParser::Entry destructor.
CfgParser::Entry::~Entry(void)
{
for_each(_entries.begin(), _entries.end(), Util::Free<CfgParser::Entry*>());
if (_section) {
delete _section;
_section = 0;
}
}
/**
* Append Entry to the end of Entry list at current depth.
*/
CfgParser::Entry*
CfgParser::Entry::add_entry(CfgParser::Entry *entry, bool overwrite)
{
CfgParser::Entry *entry_search = 0;
if (overwrite) {
if (entry->get_section()) {
entry_search = find_entry(entry->get_name(), true, entry->get_section()->get_value().c_str());
} else {
entry_search = find_entry(entry->get_name(), false);
}
}
// This is a bit awkward but to keep compatible with old
// configuration syntax overwriting of section is only allowed
// when the value is the same.
if (entry_search
&& (! entry_search->get_section()
|| strcasecmp(entry->get_value().c_str(), entry_search->get_value().c_str()) == 0)) {
entry_search->_value = entry->get_value();
entry_search->set_section(entry->get_section(), overwrite);
// Clear resources used by entry
entry->_section = 0;
delete entry;
entry = entry_search;
} else {
_entries.push_back(entry);
}
return entry;
}
//! @brief Adds Entry to the end of Entry list at current depth.
CfgParser::Entry*
CfgParser::Entry::add_entry(const std::string &source_name, int line,
const std::string &name, const std::string &value,
CfgParser::Entry *section, bool overwrite)
{
return add_entry(new Entry(source_name, line, name, value, section), overwrite);
}
/**
* Set section, copy section entires over if overwrite.
*/
CfgParser::Entry*
CfgParser::Entry::set_section(CfgParser::Entry *section, bool overwrite)
{
if (_section) {
if (overwrite) {
_section->copy_tree_into(section);
delete section;
} else {
delete _section;
_section = section;
}
} else {
_section = section;
}
return _section;
}
//! @brief Gets next entry without subsection matching the name name.
//! @param name Name of Entry to look for.
CfgParser::Entry*
CfgParser::Entry::find_entry(const std::string &name, bool include_sections, const char *value)
{
CfgParser::Entry *value_check;
list<CfgParser::Entry*>::iterator it(_entries.begin());
for (; it != _entries.end(); ++it) {
value_check = include_sections ? (*it)->get_section() : (*it);
if (*(*it) == name.c_str()
&& (! (*it)->get_section() || include_sections)
&& (! value || (value_check && value_check->get_value() == value))) {
return *it;
}
}
return 0;
}
//! @brief Gets the next entry with subsection matchin the name name.
//! @param name Name of Entry to look for.
CfgParser::Entry*
CfgParser::Entry::find_section(const std::string &name, const char *value)
{
list<CfgParser::Entry*>::iterator it(_entries.begin());
for (; it != _entries.end(); ++it) {
if ((*it)->get_section() && *(*it) == name.c_str()
&& (! value || (*it)->get_section()->get_value() == value)) {
return (*it)->get_section();
}
}
return 0;
}
//! @brief Sets and validates data specified by key list.
void
CfgParser::Entry::parse_key_values(std::list<CfgParserKey*>::iterator begin,
std::list<CfgParserKey*>::iterator end)
{
CfgParser::Entry *value;
list<CfgParserKey*>::iterator it;
for (it = begin; it != end; ++it) {
value = find_entry((*it)->get_name());
if (value) {
try {
(*it)->parse_value(value->get_value());
} catch (string &ex) {
cerr << " *** WARNING " << ex << endl << " " << *value << endl;
}
}
}
}
/**
* Copy tree into current entry, overwrite entries if overwrite is
* true.
*/
void
CfgParser::Entry::copy_tree_into(CfgParser::Entry *from, bool overwrite)
{
// Copy section
if (from->get_section()) {
if (_section) {
_section->copy_tree_into(from->get_section(), overwrite);
} else {
_section = new Entry(*(from->get_section()));
}
}
// Copy elements
CfgParser::iterator it(from->begin());
for (; it != from->end(); ++it) {
CfgParser::Entry *entry_section = 0;
if ((*it)->get_section()) {
entry_section = new Entry(*((*it)->get_section()));
}
add_entry((*it)->get_source_name(), (*it)->get_line(), (*it)->get_name(), (*it)->get_value(),
entry_section, true);
}
}
//! @brief Operator <<, return info on source, line, name and value.
std::ostream&
operator<<(std::ostream &stream, const CfgParser::Entry &entry)
{
stream << entry.get_source_name() << "@" << entry.get_line()
<< " " << entry.get_name() << " = " << entry.get_value();
return stream;
}
//! @brief CfgParser constructor.
CfgParser::CfgParser(void)
: _source(0), _root_entry(0), _is_dynamic_content(false),
_section(_root_entry), _overwrite(false)
{
_root_entry = new CfgParser::Entry(_root_source_name, 0, "ROOT", "");
_section = _root_entry;
}
//! @brief CfgParser destructor.
CfgParser::~CfgParser(void)
{
clear(false);
}
/**
* Clear resources used by parser, end up in the same state as in
* after construction.
*
* @param realloc If realloc is false, root_entry will be cleared as well rendering the parser useless. Defaults to true.
*/
void
CfgParser::clear(bool realloc)
{
_source = 0;
delete _root_entry;
if (realloc) {
_root_entry = new CfgParser::Entry(_root_source_name, 0, "ROOT", "");
} else {
_root_entry = 0;
}
_section = _root_entry;
_overwrite = false;
// Clear lists
_source_list.clear();
_source_name_list.clear();
_source_name_set.clear();
_section_list.clear();
_var_map.clear();
// Remove sections
map<string, CfgParser::Entry*>::iterator it(_section_map.begin());
for (; it != _section_map.end(); ++it) {
delete it->second;
}
_section_map.clear();
}
/**
* Parses source and fills root section with data.
*
* @param src Source.
* @param type Type of source, defaults to file.
* @param overwrite Overwrite or append duplicate elements, defaults to false.
*/
bool
CfgParser::parse(const std::string &src, CfgParserSource::Type type, bool overwrite)
{
// Set overwrite
_overwrite = overwrite;
// Init parse buffer and reserve memory.
string buf, value;
buf.reserve(PARSE_BUF_SIZE);
// Open initial source.
parse_source_new(src, type);
if (_source_list.size() == 0) {
return false;
}
int c, next;
while (_source_list.size()) {
_source = _source_list.back();
if (_source->is_dynamic()) {
_is_dynamic_content = true;
}
while ((c = _source->getc()) != EOF) {
switch (c) {
case '\n':
// To be able to handle entry ends AND { after \n a check
// to see what comes after the newline is done. If { appears
// we continue as nothing happened else we finish the entry.
next = parse_skip_blank(_source);
if (next != '{') {
parse_entry_finish(buf, value);
}
break;
case ';':
parse_entry_finish(buf, value);
break;
case '{':
if (parse_name(buf)) {
parse_section_finish(buf, value);
} else {
cerr << "Ignoring section as name is empty." << endl;
}
buf.clear();
value.clear();
break;
case '}':
if (_section_list.size() > 0) {
if (buf.size() && parse_name(buf)) {
parse_entry_finish(buf, value);
buf.clear();
value.clear();
}
_section = _section_list.back();
_section_list.pop_back();
} else {
cerr << "Extra } character found, ignoring." << endl;
}
break;
case '=':
value.clear();
parse_value(_source, value);
break;
case '#':
parse_comment_line(_source);
break;
case '/':
next = _source->getc();
if (next == '/') {
parse_comment_line(_source);
} else if (next == '*') {
parse_comment_c(_source);
} else {
buf += c;
_source->ungetc(next);
}
break;
default:
buf += c;
break;
}
}
try {
_source->close();
} catch (string &ex) {
cerr << ex << endl;
}
delete _source;
_source_list.pop_back();
_source_name_list.pop_back();
}
if (buf.size()) {
parse_entry_finish(buf, value);
}
return true;
}
//! @brief Creates and opens new CfgParserSource.
void
CfgParser::parse_source_new(const std::string &name_orig, CfgParserSource::Type type)
{
int done = 0;
string name(name_orig);
do {
CfgParserSource *source = source_new(name, type);
assert(source);
// Open and set as active, delete if fails.
try {
source->open();
// Add source to file list if file
if (type == CfgParserSource::SOURCE_FILE) {
_file_list[name] = Util::getMtime(name);
}
_source = source;
_source_list.push_back(_source);
done = 1;
} catch (string &ex) {
delete source;
// Previously added in source_new
_source_name_list.pop_back();
// Display error message on second try
if (done) {
cerr << ex << endl;
}
// If the open fails and we are trying to open a file, try
// to open the file from the current files directory.
if (! done && (type == CfgParserSource::SOURCE_FILE)) {
if (_source_name_list.size() && (name[0] != '/')) {
name = Util::getDir(_source_name_list.back());
name += "/" + name_orig;
}
}
}
} while (! done++ && (type == CfgParserSource::SOURCE_FILE));
}
//! @brief Parses from beginning to first blank.
bool
CfgParser::parse_name(std::string &buf)
{
if (! buf.size()) {
cerr << "Unable to parse empty name." << endl;
return false;
}
// Identify name.
string::size_type begin, end;
begin = buf.find_first_not_of(CP_PARSE_BLANKS);
if (begin == string::npos) {
return false;
}
end = buf.find_first_of(CP_PARSE_BLANKS, begin);
// Check if there is any garbage after the value.
if (end != string::npos) {
if (buf.find_first_not_of(CP_PARSE_BLANKS, end) != string::npos) {
// Pass, do notihng
}
}
// Set name.
buf = buf.substr(begin, end - begin);
return true;
}
//! @brief Parses from = to end of " pair.
void
CfgParser::parse_value(CfgParserSource *source, std::string &value)
{
// We expect to get a " after the =, however we ignore anything else.
int c;
while ((c = source->getc()) != EOF && c != '"')
;
// Check if we got to a " or found EOF first.
if (c == EOF) {
cerr << "Reached EOF before opening \" in value." << endl;
return;
}
// Parse until next ", and escape characters after \.
while ((c = source->getc()) != EOF && c != '"') {
// Escape character after \, if newline drop it.
if (c == '\\') {
c = source->getc();
if (c == '\n' || c == EOF) {
continue;
}
}
value += c;
}
if (c == EOF) {
cerr << "Reached EOF before closing \" in value." << endl;
}
// If the value is empty, parse_entry_finish() might later just skip
// the complete entry. To allow empty config options we add a dummy space.
if (!value.size()) {
value = " ";
}
}
//! @brief Parses entry (name + value) and executes command accordingly.
void
CfgParser::parse_entry_finish(std::string &buf, std::string &value)
{
if (value.size()) {
parse_entry_finish_standard(buf, value);
} else {
// Template handling, expand or define template.
if (buf.size() && parse_name(buf) && buf[0] == '@') {
parse_entry_finish_template(buf);
}
buf.clear();
}
}
/**
* Finish standard entry.
*/
void
CfgParser::parse_entry_finish_standard(std::string &buf, std::string &value)
{
if (parse_name(buf)) {
if (buf[0] == '$') {
variable_define(buf, value);
} else {
variable_expand(value);
if (buf == "INCLUDE") {
parse_source_new(value, CfgParserSource::SOURCE_FILE);
} else if (buf == "COMMAND") {
parse_source_new(value, CfgParserSource::SOURCE_COMMAND);
} else {
_section->add_entry(_source->get_name(), _source->get_line(), buf, value, 0, _overwrite);
}
}
} else {
cerr << "Dropping entry with empty name." << endl;
}
value.clear();
buf.clear();
}
/**
* Finish template entry, copy data into current section.
*/
void
CfgParser::parse_entry_finish_template(std::string &name)
{
map<string, CfgParser::Entry*>::iterator it(_section_map.find(name.c_str() + 1));
if (it == _section_map.end()) {
cerr << " *** WARNING: No such template " << name << endl;
return;
}
_section->copy_tree_into(it->second);
}
//! @brief Creates new Section on {
void
CfgParser::parse_section_finish(std::string &buf, std::string &value)
{
// Create Entry representing Section
Entry *section = 0;
if (buf.size() == 6 && strcasecmp(buf.c_str(), "DEFINE") == 0) {
// Look for define section, started with Define = "Name" {
map<string, CfgParser::Entry*>::iterator it(_section_map.find(value));
if (it != _section_map.end()) {
delete it->second;
_section_map.erase(it);
}
section = new Entry(_source->get_name(), _source->get_line(), buf, value);
_section_map[value] = section;
} else {
// Create Entry for sub-section.
section = new Entry(_source->get_name(), _source->get_line(), buf, value);
// Add parent section, get section from parent section as it
// can be different from the newly created if it is not
// overwritten.
CfgParser::Entry *parent = _section->add_entry(_source->get_name(), _source->get_line(),
buf, value, section, _overwrite);
section = parent->get_section();
}
// Set current Entry to newly created Section.
_section_list.push_back(_section);
_section = section;
}
//! @brief Parses Source until end of line discarding input.
void
CfgParser::parse_comment_line(CfgParserSource *source)
{
int c;
while (((c = source->getc()) != EOF) && (c != '\n'))
;
// Give back the newline, needed for flushing value before comment
if (c == '\n') {
source->ungetc(c);
}
}
//! @brief Parses Source until */ is found.
void
CfgParser::parse_comment_c(CfgParserSource *source)
{
int c;
while ((c = source->getc()) != EOF) {
if (c == '*') {
if ((c = source->getc()) == '/') {
break;
} else if (c != EOF) {
source->ungetc(c);
}
}
}
if (c == EOF) {
cerr << "Reached EOF before closing */ in comment." << endl;
}
}
//! @brief Parses Source until next non whitespace char is found.
char
CfgParser::parse_skip_blank(CfgParserSource *source)
{
int c;
while (((c = source->getc()) != EOF) && isspace(c))
;
if (c != EOF) {
source->ungetc(c);
}
return c;
}
//! @brief Creates a CfgParserSource of type type.
CfgParserSource*
CfgParser::source_new(const std::string &name, CfgParserSource::Type type)
{
CfgParserSource *source = 0;
// Create CfgParserSource.
_source_name_list.push_back(name);
_source_name_set.insert(name);
switch (type) {
case CfgParserSource::SOURCE_FILE:
source = new CfgParserSourceFile(*_source_name_set.find(name));
break;
case CfgParserSource::SOURCE_COMMAND:
source = new CfgParserSourceCommand(*_source_name_set.find(name));
break;
default:
break;
}
return source;
}
//! @brief Defines a variable in the _var_map/setenv.
void
CfgParser::variable_define(const std::string &name, const std::string &value)
{
_var_map[name] = value;
// If the variable begins with $_ it should update the environment aswell.
if ((name.size() > 2) && (name[1] == '_')) {
setenv(name.c_str() + 2, value.c_str(), 1);
}
}
//! @brief Expands all $ variables in a string.
void
CfgParser::variable_expand(std::string &var)
{
bool did_expand;
do {
did_expand = false;
string::size_type begin = 0, end = 0;
while ((begin = var.find_first_of('$', end)) != string::npos) {
end = begin + 1;
// Skip escaped \$
if ((begin > 0) && (var[begin - 1] == '\\')) {
continue;
}
// Find end of variable
for (; end != var.size(); ++end) {
if ((isalnum(var[end]) == 0) && (var[end] != '_')) {
break;
}
}
did_expand = variable_expand_name(var, begin, end) || did_expand;
}
} while (did_expand);
}
bool
CfgParser::variable_expand_name(std::string &var,
string::size_type begin, string::size_type &end)
{
bool did_expand = false;
string var_name(var.substr(begin, end - begin));
// If the variable starts with _ it is considered an environment
// variable, use getenv to see if it is available
if (var_name.size() > 2 && var_name[1] == '_') {
char *value = getenv(var_name.c_str() + 2);
if (value) {
var.replace(begin, end - begin, value);
end = begin + strlen(value);
did_expand = true;
} else {
cerr << "Trying to use undefined environment variable: " << var_name << endl;;
}
} else {
map<string, string>::iterator it(_var_map.find(var_name));
if (it != _var_map.end()) {
var.replace(begin, end - begin, it->second);
end = begin + it->second.size();
did_expand = true;
} else {
cerr << "Trying to use undefined variable: " << var_name << endl;
}
}
return did_expand;
}