add options: --enable-sections --enable-subkeys --enable-comments --enable-line-numbers --enable-event-numbers
--- a/bash-completion.sh Sat Nov 21 20:09:18 2020 +0100
+++ b/bash-completion.sh Sun Nov 22 00:44:00 2020 +0100
@@ -22,10 +22,25 @@
w2=${COMP_WORDS[COMP_CWORD-2]}
w3=${COMP_WORDS[COMP_CWORD-3]}
+ BOOLEAN_VALUES=(
+ "true"
+ "false"
+ )
+
if [[ "$w1" == "--relation" && "x$w0" == "x" ]]; then COMPREPLY=("''")
+ elif [[ "$w1" == "--enable-sections" ]]; then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
+ elif [[ "$w1" == "--enable-subkeys" ]]; then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
+ elif [[ "$w1" == "--enable-comments" ]]; then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
+ elif [[ "$w1" == "--enable-line-numbers" ]]; then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
+ elif [[ "$w1" == "--enable-event-numbers" ]]; then COMPREPLY=($(compgen -W "${BOOLEAN_VALUES[*]}" -- "$w0"))
else
OPTIONS=(
"--relation"
+ "--enable-sections"
+ "--enable-subkeys"
+ "--enable-comments"
+ "--enable-line-numbers"
+ "--enable-event-numbers"
)
COMPREPLY=($(compgen -W "${OPTIONS[*]}" -- "$w0"))
fi
--- a/src/CLIParser.h Sat Nov 21 20:09:18 2020 +0100
+++ b/src/CLIParser.h Sun Nov 22 00:44:00 2020 +0100
@@ -37,9 +37,23 @@
else throw relpipe::cli::RelpipeCLIException(L"Missing CLI argument" + (i > 0 ? (L" after " + arguments[i - 1]) : L""), relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
}
+ /**
+ * TODO: use a common method
+ */
+ bool parseBoolean(const relpipe::writer::string_t& value) {
+ if (value == L"true") return true;
+ else if (value == L"false") return false;
+ else throw relpipe::cli::RelpipeCLIException(L"Unable to parse boolean value: " + value + L" (expecting true or false)", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+ }
+
public:
static const relpipe::writer::string_t OPTION_RELATION;
+ static const relpipe::writer::string_t OPTION_ENABLE_SECTIONS;
+ static const relpipe::writer::string_t OPTION_ENABLE_SUBKEYS;
+ static const relpipe::writer::string_t OPTION_ENABLE_COMMENTS;
+ static const relpipe::writer::string_t OPTION_ENABLE_LINE_NUMBERS;
+ static const relpipe::writer::string_t OPTION_ENABLE_EVENT_NUMBERS;
Configuration parse(const std::vector<relpipe::writer::string_t>& arguments) {
Configuration c;
@@ -47,9 +61,13 @@
for (int i = 0; i < arguments.size();) {
relpipe::writer::string_t option = readNext(arguments, i);
- if (option == OPTION_RELATION) {
- c.relation = readNext(arguments, i);
- } else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+ if (option == OPTION_RELATION) c.relation = readNext(arguments, i);
+ else if (option == OPTION_ENABLE_SECTIONS) c.enableSections = parseBoolean(readNext(arguments, i));
+ else if (option == OPTION_ENABLE_SUBKEYS) c.enableSubkeys = parseBoolean(readNext(arguments, i));
+ else if (option == OPTION_ENABLE_COMMENTS) c.enableComments = parseBoolean(readNext(arguments, i));
+ else if (option == OPTION_ENABLE_LINE_NUMBERS) c.enableLineNumbers = parseBoolean(readNext(arguments, i));
+ else if (option == OPTION_ENABLE_EVENT_NUMBERS) c.enableEventNumbers = parseBoolean(readNext(arguments, i));
+ else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
}
return c;
@@ -60,6 +78,11 @@
};
const relpipe::writer::string_t CLIParser::OPTION_RELATION = L"--relation";
+const relpipe::writer::string_t CLIParser::OPTION_ENABLE_SECTIONS = L"--enable-sections";
+const relpipe::writer::string_t CLIParser::OPTION_ENABLE_SUBKEYS = L"--enable-subkeys";
+const relpipe::writer::string_t CLIParser::OPTION_ENABLE_COMMENTS = L"--enable-comments";
+const relpipe::writer::string_t CLIParser::OPTION_ENABLE_LINE_NUMBERS = L"--enable-line-numbers";
+const relpipe::writer::string_t CLIParser::OPTION_ENABLE_EVENT_NUMBERS = L"--enable-event-numbers";
}
}
--- a/src/Configuration.h Sat Nov 21 20:09:18 2020 +0100
+++ b/src/Configuration.h Sun Nov 22 00:44:00 2020 +0100
@@ -29,6 +29,11 @@
class Configuration {
public:
relpipe::writer::string_t relation = L"ini";
+ relpipe::writer::boolean_t enableLineNumbers = false;
+ relpipe::writer::boolean_t enableEventNumbers = false;
+ relpipe::writer::boolean_t enableSections = true;
+ relpipe::writer::boolean_t enableSubkeys = false;
+ relpipe::writer::boolean_t enableComments = false;
virtual ~Configuration() {
}
--- a/src/INICommand.cpp Sat Nov 21 20:09:18 2020 +0100
+++ b/src/INICommand.cpp Sun Nov 22 00:44:00 2020 +0100
@@ -66,10 +66,13 @@
void startDocument() override {
vector<AttributeMetadata> metadata;
- metadata.push_back({L"section", TypeId::STRING});
+ if (configuration.enableLineNumbers) metadata.push_back({L"line", TypeId::INTEGER});
+ if (configuration.enableEventNumbers) metadata.push_back({L"event", TypeId::INTEGER});
+ if (configuration.enableSections) metadata.push_back({L"section", TypeId::STRING});
metadata.push_back({L"key", TypeId::STRING});
- metadata.push_back({L"subkey", TypeId::STRING});
+ if (configuration.enableSubkeys) metadata.push_back({L"subkey", TypeId::STRING});
metadata.push_back({L"value", TypeId::STRING});
+ if (configuration.enableComments) metadata.push_back({L"comment", TypeId::STRING});
writer->startRelation(configuration.relation, metadata, true);
};
@@ -77,23 +80,31 @@
currentSection.clear();
};
- void startSection(const std::string& name) override {
- currentSection.push_back(name);
+ void startSection(const SectionStartEvent& event) override {
+ currentSection.push_back(event.name);
};
void endSection() override {
currentSection.pop_back();
};
- void entry(const std::string& key, const std::string& subkey, const std::string& value) override {
- writer->writeAttribute(convertor.from_bytes(getCurrentSectionFullName()));
+ void entry(const EntryEvent& event) override {
+ if (configuration.enableLineNumbers) writer->writeAttribute(&event.lineNumber, typeid (event.lineNumber));
+ if (configuration.enableEventNumbers) writer->writeAttribute(&event.eventNumber, typeid (event.eventNumber));
+
+ std::string section = getCurrentSectionFullName();
+ std::string key = configuration.enableSubkeys ? event.key : event.fullKey;
+
+ if (configuration.enableSections) writer->writeAttribute(convertor.from_bytes(section));
+ else if (section.size()) key = section + "/" + key;
writer->writeAttribute(convertor.from_bytes(key));
- writer->writeAttribute(convertor.from_bytes(subkey));
- writer->writeAttribute(convertor.from_bytes(value));
+ if (configuration.enableSubkeys) writer->writeAttribute(convertor.from_bytes(event.subKey));
+
+ writer->writeAttribute(convertor.from_bytes(event.value));
+ if (configuration.enableComments) writer->writeAttribute(convertor.from_bytes(event.comment));
};
// TODO: handle also comments and whitespace (to allow lossless transformation from INI and back to INI)
- // TODO: make subkeys (in [] brackets in the key) optional/configurable
};
--- a/src/lib/INIContentHandler.h Sat Nov 21 20:09:18 2020 +0100
+++ b/src/lib/INIContentHandler.h Sun Nov 22 00:44:00 2020 +0100
@@ -20,10 +20,31 @@
class INIContentHandler {
public:
+
+ class Event {
+ public:
+ int64_t eventNumber = -1;
+ int64_t lineNumber = -1;
+ std::string comment;
+ };
+
+ class SectionStartEvent : public Event {
+ public:
+ std::string name;
+ };
+
+ class EntryEvent : public Event {
+ public:
+ std::string key;
+ std::string subKey;
+ std::string fullKey;
+ std::string value;
+ };
+
virtual ~INIContentHandler() = default;
virtual void startDocument() = 0;
virtual void endDocument() = 0;
- virtual void startSection(const std::string& name) = 0;
+ virtual void startSection(const SectionStartEvent& event) = 0;
virtual void endSection() = 0;
- virtual void entry(const std::string& key, const std::string& subkey, const std::string& value) = 0;
+ virtual void entry(const EntryEvent& event) = 0;
};
\ No newline at end of file
--- a/src/lib/INIReader.cpp Sat Nov 21 20:09:18 2020 +0100
+++ b/src/lib/INIReader.cpp Sun Nov 22 00:44:00 2020 +0100
@@ -40,30 +40,43 @@
std::regex whitespacePattrern("\\s*");
std::regex commentPattrern("\\s*(;|#)\\s*(.*)");
std::regex sectionPattrern("\\s*\\[\\s*([^\\]]+)\\s*\\]\\s*");
- std::regex entryQuotesPattrern("\\s*([^=\\]]+?[^=\\s\\]]*)(\\[([^\\]]+)\\])?\\s*=\\s*\"([^']+)\"\\s*((;|#)\\s*(.*))?");
- std::regex entryApostrophesPattrern("\\s*([^=\\]]+?[^=\\s\\]]*)(\\[([^\\]]+)\\])?\\s*=\\s*'([^']+)'\\s*((;|#)\\s*(.*))?");
- std::regex entryPlainPattrern("\\s*([^=\\]]+?[^=\\s\\]]*)(\\[([^\\]]+)\\])?\\s*=\\s*(.*)");
+ std::regex entryQuotesPattrern(/***/"\\s*(([^=\\]]+?[^=\\s\\]]*)(\\[([^\\]]+)\\])?)\\s*=\\s*\"([^']+)\"\\s*((;|#)\\s*(.*))?");
+ std::regex entryApostrophesPattrern("\\s*(([^=\\]]+?[^=\\s\\]]*)(\\[([^\\]]+)\\])?)\\s*=\\s*'([^']+)'\\s*((;|#)\\s*(.*))?");
+ std::regex entryPlainPattrern("\\s*(([^=\\]]+?[^=\\s\\]]*)(\\[([^\\]]+)\\])?)\\s*=\\s*(.*)");
std::smatch match;
- std::string section;
+ bool inSection = false;
std::string line;
+ int lineNumber = 0;
+ int eventNumber = 0;
+
while (std::getline(input, line)) {
+ lineNumber++;
if (std::regex_match(line, match, whitespacePattrern)) {
// TODO: support also whitespace
} else if (std::regex_match(line, match, commentPattrern)) {
// TODO: support also comments + emit also the comment style (;/#)
} else if (std::regex_match(line, match, sectionPattrern)) {
- if (section.size()) for (INIContentHandler* handler : handlers) handler->endSection();
- section = match[1];
- for (INIContentHandler* handler : handlers) handler->startSection(section);
- } else if (std::regex_match(line, match, entryQuotesPattrern) || std::regex_match(line, match, entryApostrophesPattrern)) {
+ if (inSection) for (INIContentHandler* handler : handlers) handler->endSection();
+ INIContentHandler::SectionStartEvent event;
+ event.lineNumber = lineNumber;
+ event.eventNumber = ++eventNumber;
+ event.name = match[1];
// TODO: support also comments + emit also the comment style (;/#)
+ for (INIContentHandler* handler : handlers) handler->startSection(event);
+ } else if (std::regex_match(line, match, entryQuotesPattrern) || std::regex_match(line, match, entryApostrophesPattrern) || std::regex_match(line, match, entryPlainPattrern)) {
+ INIContentHandler::EntryEvent event;
+ event.lineNumber = lineNumber;
+ event.eventNumber = ++eventNumber;
+ event.key = match[2];
+ event.subKey = match[4];
+ event.fullKey = match[1];
+ event.value = match[5];
+ if (match.size() == 9) event.comment = match[8];
// TODO: emit also the quote style ('/"/) and surrounding whitespace
- for (INIContentHandler* handler : handlers) handler->entry(match[1], match[3], match[4]);
- } else if (std::regex_match(line, match, entryPlainPattrern)) {
- for (INIContentHandler* handler : handlers) handler->entry(match[1], match[3], match[4]);
+ for (INIContentHandler* handler : handlers) handler->entry(event);
} else {
// TODO: warning, error, or support unknown content
}
@@ -77,10 +90,9 @@
// TODO: support also nested sections – hierarchy
// TODO: support also option for alternative key-value separator (: instead of =)
// TODO: support also other encodings (currently only UTF-8 is supported)
- // TODO: emit line numbers and/or event order?
}
- if (section.size()) for (INIContentHandler* handler : handlers) handler->endSection();
+ if (inSection) for (INIContentHandler* handler : handlers) handler->endSection();
for (INIContentHandler* handler : handlers) handler->endDocument();
}
--- a/src/lib/INIReader.h Sat Nov 21 20:09:18 2020 +0100
+++ b/src/lib/INIReader.h Sun Nov 22 00:44:00 2020 +0100
@@ -30,4 +30,4 @@
virtual void addHandler(INIContentHandler* handler) = 0;
virtual void process() = 0;
static INIReader* create(std::istream& input);
-};
\ No newline at end of file
+};