add options: --enable-sections --enable-subkeys --enable-comments --enable-line-numbers --enable-event-numbers v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Sun, 22 Nov 2020 00:44:00 +0100
branchv_0
changeset 2 f031a4dc7c52
parent 1 3876a9c56a66
child 3 4313e91da50b
add options: --enable-sections --enable-subkeys --enable-comments --enable-line-numbers --enable-event-numbers
bash-completion.sh
src/CLIParser.h
src/Configuration.h
src/INICommand.cpp
src/lib/INIContentHandler.h
src/lib/INIReader.cpp
src/lib/INIReader.h
--- 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
+};