change CLI interface: options: --relation --attribute --record --records --records-from-stdin v_0
authorFrantišek Kučera <franta-hg@frantovo.cz>
Tue, 22 Sep 2020 21:00:30 +0200
branchv_0
changeset 43 3c8ea5dcf793
parent 42 09cd32a65709
child 44 dd7094457e44
change CLI interface: options: --relation --attribute --record --records --records-from-stdin
bash-completion.sh
nbproject/configurations.xml
src/ArgumentsCommand.h
src/CLICommand.h
src/CLIParser.h
src/Command.h
src/Configuration.h
src/StdInCommand.h
src/relpipe-in-cli.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bash-completion.sh	Tue Sep 22 21:00:30 2020 +0200
@@ -0,0 +1,46 @@
+# Relational pipes
+# Copyright © 2019 František Kučera (Frantovo.cz, GlobalCode.info)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+_relpipe_in_cli_completion() {
+	local w0 w1 w2 w3
+
+	COMPREPLY=()
+	w0=${COMP_WORDS[COMP_CWORD]}
+	w1=${COMP_WORDS[COMP_CWORD-1]}
+	w2=${COMP_WORDS[COMP_CWORD-2]}
+	w3=${COMP_WORDS[COMP_CWORD-3]}
+
+	DATA_TYPE=(
+		"string"
+		"integer"
+		"boolean"
+	)
+
+	if   [[ "$w1" == "--relation"                      && "x$w0" == "x" ]];    then COMPREPLY=("''")
+	elif [[ "$w1" == "--attribute"                     && "x$w0" == "x" ]];    then COMPREPLY=("''")
+	elif [[ "$w2" == "--attribute"                                      ]];    then COMPREPLY=($(compgen -W "${DATA_TYPE[*]}" -- "$w0"))
+	else
+		OPTIONS=(
+			"--relation"
+			"--attribute"
+			"--record"
+			"--records"
+			"--records-from-stdin"
+		)
+		COMPREPLY=($(compgen -W "${OPTIONS[*]}" -- "$w0"))
+	fi
+}
+
+complete -F _relpipe_in_cli_completion relpipe-in-cli
--- a/nbproject/configurations.xml	Tue Sep 22 17:42:19 2020 +0200
+++ b/nbproject/configurations.xml	Tue Sep 22 21:00:30 2020 +0200
@@ -42,6 +42,7 @@
   <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
     <df root="." name="0">
       <df name="src">
+        <in>CLICommand.h</in>
         <in>relpipe-in-cli.cpp</in>
       </df>
     </df>
@@ -90,6 +91,8 @@
           <preBuildFirst>true</preBuildFirst>
         </preBuild>
       </makefileType>
+      <item path="src/CLICommand.h" ex="false" tool="3" flavor2="0">
+      </item>
       <item path="src/relpipe-in-cli.cpp" ex="false" tool="1" flavor2="0">
         <ccTool flags="0">
         </ccTool>
@@ -124,6 +127,8 @@
           <preBuildFirst>true</preBuildFirst>
         </preBuild>
       </makefileType>
+      <item path="src/CLICommand.h" ex="false" tool="3" flavor2="0">
+      </item>
     </conf>
   </confs>
 </configurationDescriptor>
--- a/src/ArgumentsCommand.h	Tue Sep 22 17:42:19 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-/**
- * Relational pipes
- * Copyright © 2018 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include <cstdlib>
-#include <iostream>
-#include <string>
-#include <vector>
-#include <algorithm>
-
-#include <relpipe/writer/typedefs.h>
-#include <relpipe/writer/AttributeMetadata.h>
-
-#include "Command.h"
-
-namespace relpipe {
-namespace in {
-namespace cli {
-
-class ArgumentsCommand : public Command {
-public:
-
-	void process(std::istream& input, std::ostream& output, const relpipe::writer::string_t& command, const std::vector<relpipe::writer::string_t>& arguments) override {
-		using namespace relpipe::writer;
-
-		size_t i = 0;
-		string_t relationName = arguments[i++];
-		integer_t attributeCount = std::stol(arguments[i++]); // TODO: use integer data type's method? + unsigned type
-		boolean_t writeHeader = true; // TODO: add option for header omitting
-
-		// TODO: check argument count
-
-		std::shared_ptr<RelationalWriter> writer(Factory::create(output));
-
-		std::vector<AttributeMetadata> attributes(attributeCount);
-
-		for (size_t j = 0; j < attributeCount; j++) {
-			string_t attributeName = arguments[i++];
-			TypeId attributeType = writer->toTypeId(arguments[i++]);
-			attributes[j] = {attributeName, attributeType};
-		}
-
-		writer->startRelation(relationName, attributes, writeHeader);
-
-		for (; i < arguments.size(); i++) {
-			writer->writeAttribute(arguments[i]);
-		}
-	}
-};
-
-}
-}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CLICommand.h	Tue Sep 22 21:00:30 2020 +0200
@@ -0,0 +1,74 @@
+/**
+ * Relational pipes
+ * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <codecvt>
+#include <memory>
+#include <iostream>
+
+#include "Configuration.h"
+
+namespace relpipe {
+namespace in {
+namespace cli {
+
+class CLICommand {
+private:
+
+	std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings and use platform encoding as default
+
+	void processRelation(RelationConfiguration& configuration, std::shared_ptr<writer::RelationalWriter> writer) {
+		// Write header / metadata:
+		std::vector<relpipe::writer::AttributeMetadata> attributesMetadata;
+		for (AttributeRecipe ar : configuration.attributes) attributesMetadata.push_back({ar.name, ar.type});
+		writer->startRelation(configuration.relation, attributesMetadata, true);
+
+		// Write records from CLI:
+		for (auto value : configuration.values) writer->writeAttribute(value);
+
+		// Write records from STDIN:
+		if (configuration.valueStream) {
+			std::stringstream rawValue;
+			while (true) {
+				char ch = 0;
+				configuration.valueStream->get(ch);
+				if (ch == 0 && configuration.valueStream->good()) {
+					writer->writeAttribute(convertor.from_bytes(rawValue.str()));
+					rawValue.str("");
+					rawValue.clear();
+				} else if (configuration.valueStream->good()) {
+					rawValue << ch;
+				} else if (configuration.valueStream->eof()) {
+					return;
+				} else {
+					throw relpipe::cli::RelpipeCLIException(L"Error while reading values from the input stream.", relpipe::cli::CLI::EXIT_CODE_UNEXPECTED_ERROR); // TODO: better exception
+				}
+			}
+		}
+
+	}
+
+public:
+
+	void process(Configuration& configuration, std::shared_ptr<writer::RelationalWriter> writer) {
+		for (RelationConfiguration& relationConfiguration : configuration.relationConfigurations) processRelation(relationConfiguration, writer);
+	}
+};
+
+}
+}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CLIParser.h	Tue Sep 22 21:00:30 2020 +0200
@@ -0,0 +1,120 @@
+/**
+ * Relational pipes
+ * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <vector>
+#include <iostream>
+
+#include <relpipe/writer/typedefs.h>
+#include <relpipe/cli/CLI.h>
+#include <relpipe/cli/RelpipeCLIException.h>
+
+#include "Configuration.h"
+
+namespace relpipe {
+namespace in {
+namespace cli {
+
+class CLIParser {
+private:
+
+	relpipe::writer::string_t readNext(const std::vector<relpipe::writer::string_t>& arguments, int& i) {
+		if (i < arguments.size()) return arguments[i++];
+		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);
+	}
+
+	void readNextRecord(const std::vector<relpipe::writer::string_t>& arguments, int& i, RelationConfiguration& currentRelation) {
+		for (const auto& a : currentRelation.attributes) currentRelation.values.emplace_back(readNext(arguments, i));
+	}
+
+	void addRelation(Configuration& c, RelationConfiguration& currentRelation) {
+		if (currentRelation.relation.size()) {
+			c.relationConfigurations.push_back(currentRelation);
+			currentRelation = RelationConfiguration();
+		}
+	}
+
+	/**
+	 * 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);
+	}
+
+	/**
+	 * TODO: use a common method
+	 */
+	relpipe::writer::TypeId parseTypeId(const relpipe::writer::string_t& value) {
+		using t = relpipe::writer::TypeId;
+		if (value == L"string") return t::STRING;
+		else if (value == L"integer") return t::INTEGER;
+		else if (value == L"boolean") return t::BOOLEAN;
+		else throw relpipe::cli::RelpipeCLIException(L"Unable to parse TypeId: " + value, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+	}
+
+public:
+
+	static const relpipe::writer::string_t OPTION_RELATION;
+	static const relpipe::writer::string_t OPTION_ATTRIBUTE;
+	static const relpipe::writer::string_t OPTION_RECORD;
+	static const relpipe::writer::string_t OPTION_RECORDS;
+	static const relpipe::writer::string_t OPTION_RECORDS_STDIN;
+
+	Configuration parse(const std::vector<relpipe::writer::string_t>& arguments) {
+		Configuration c;
+		RelationConfiguration currentRelation;
+
+		for (int i = 0; i < arguments.size();) {
+			relpipe::writer::string_t option = readNext(arguments, i);
+
+			if (option == OPTION_RELATION) {
+				addRelation(c, currentRelation); // previous relation
+				currentRelation.relation = readNext(arguments, i);
+			} else if (option == OPTION_ATTRIBUTE) {
+				AttributeRecipe attribute;
+				attribute.name = readNext(arguments, i);
+				attribute.type = parseTypeId(readNext(arguments, i));
+				currentRelation.attributes.push_back(attribute);
+			} else if (option == OPTION_RECORD) {
+				readNextRecord(arguments, i, currentRelation);
+			} else if (option == OPTION_RECORDS) {
+				while (i < arguments.size()) readNextRecord(arguments, i, currentRelation);
+			} else if (option == OPTION_RECORDS_STDIN) {
+				for (RelationConfiguration r : c.relationConfigurations) if (r.valueStream) throw relpipe::cli::RelpipeCLIException(L"Only one relation can read data from STDIN.", relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+				currentRelation.valueStream = &std::cin;
+			} else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+		}
+		addRelation(c, currentRelation); // last relation
+
+		return c;
+	}
+
+	virtual ~CLIParser() {
+	}
+};
+
+const relpipe::writer::string_t CLIParser::OPTION_RELATION = L"--relation";
+const relpipe::writer::string_t CLIParser::OPTION_ATTRIBUTE = L"--attribute";
+const relpipe::writer::string_t CLIParser::OPTION_RECORD = L"--record";
+const relpipe::writer::string_t CLIParser::OPTION_RECORDS = L"--records";
+const relpipe::writer::string_t CLIParser::OPTION_RECORDS_STDIN = L"--records-from-stdin";
+
+}
+}
+}
--- a/src/Command.h	Tue Sep 22 17:42:19 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-/**
- * Relational pipes
- * Copyright © 2018 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include <cstdlib>
-#include <iostream>
-#include <string>
-#include <vector>
-#include <algorithm>
-
-
-namespace relpipe {
-namespace in {
-namespace cli {
-
-class Command {
-public:
-	virtual ~Command() = default;
-
-	/**
-	 * Processes the inputs (stream + arguments) and generates output in the relational pipes format.
-	 * 
-	 * @param input usually STDIN, may not be used if all data are passed as CLI arguments
-	 * @param output usually STDOUT, relational data is passed here
-	 * @param command command name, usualy not needed, may be significant if the same Command instance is used for several commands
-	 * @param arguments CLI arguments containing data or parameters (format is specific to each command)
-	 */
-	virtual void process(std::istream& input, std::ostream& output, const relpipe::writer::string_t& command, const std::vector<relpipe::writer::string_t> &arguments) = 0;
-
-};
-
-}
-}
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Configuration.h	Tue Sep 22 21:00:30 2020 +0200
@@ -0,0 +1,62 @@
+/**
+ * Relational pipes
+ * Copyright © 2020 František Kučera (Frantovo.cz, GlobalCode.info)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+
+#include <vector>
+#include <iostream>
+
+#include <relpipe/writer/typedefs.h>
+
+
+namespace relpipe {
+namespace in {
+namespace cli {
+
+class AttributeRecipe {
+public:
+
+	virtual ~AttributeRecipe() {
+	}
+
+	relpipe::writer::string_t name;
+	relpipe::writer::TypeId type;
+
+};
+
+class RelationConfiguration {
+public:
+
+	virtual ~RelationConfiguration() {
+	}
+
+	relpipe::writer::string_t relation;
+	std::vector<AttributeRecipe> attributes;
+	std::vector<relpipe::writer::string_t> values;
+	std::istream* valueStream = nullptr;
+};
+
+class Configuration {
+public:
+	std::vector<RelationConfiguration> relationConfigurations;
+
+	virtual ~Configuration() {
+	}
+};
+
+}
+}
+}
\ No newline at end of file
--- a/src/StdInCommand.h	Tue Sep 22 17:42:19 2020 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,117 +0,0 @@
-/**
- * Relational pipes
- * Copyright © 2018 František Kučera (Frantovo.cz, GlobalCode.info)
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, version 3 of the License.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include <cstdlib>
-#include <iostream>
-#include <string>
-#include <vector>
-#include <algorithm>
-#include <string>
-#include <locale>
-
-#include <relpipe/writer/typedefs.h>
-#include <relpipe/writer/AttributeMetadata.h>
-#include <relpipe/cli/CLI.h>
-#include <relpipe/cli/RelpipeCLIException.h>
-
-#include "Command.h"
-
-namespace relpipe {
-namespace in {
-namespace cli {
-
-/**
- * TODO: consider code merge with ArgumentsCommand
- */
-class StdInCommand : public Command {
-private:
-	relpipe::writer::boolean_t readStdIn;
-	std::wstring_convert<codecvt_utf8<wchar_t>> convertor; // TODO: support also other encodings.
-
-	/**
-	 * Reads next value from arguments and (if no arguments left) from input
-	 * @param input
-	 * @param arguments
-	 * @param i current index in the arguments vector
-	 * @param required if true, exception is thrown when no data found
-	 * @return 
-	 */
-	relpipe::writer::string_t readNext(std::istream& input, const std::vector<relpipe::writer::string_t>& arguments, size_t& i, relpipe::writer::boolean_t required) {
-		using namespace relpipe::writer;
-		using namespace relpipe::cli;
-
-		if (i < arguments.size()) {
-			return arguments[i++];
-		} else if (readStdIn) {
-			std::stringstream value;
-
-			while (true) {
-				char ch;
-				input.get(ch);
-				if (ch == 0 || input.eof() || input.fail()) break;
-				else value << ch;
-			}
-
-			if (required && value.str().empty()) throw RelpipeCLIException(L"Missing value on STDIN.", CLI::EXIT_CODE_BAD_SYNTAX);
-
-			return convertor.from_bytes(value.str());
-		} else {
-			if (required) throw RelpipeCLIException(L"Missing value on CLI.", CLI::EXIT_CODE_BAD_SYNTAX);
-			return L"";
-		}
-	}
-
-
-public:
-
-	StdInCommand(relpipe::writer::boolean_t readStdIn) :
-	readStdIn(readStdIn) {
-	}
-
-	void process(std::istream& input, std::ostream& output, const relpipe::writer::string_t& command, const std::vector<relpipe::writer::string_t>& arguments) override {
-		using namespace relpipe::writer;
-
-		size_t i = 0;
-		string_t relationName = readNext(input, arguments, i, true);
-		integer_t attributeCount = std::stol(readNext(input, arguments, i, true)); // TODO: use integer data type's method? + unsigned type
-		boolean_t writeHeader = true; // TODO: add option for header omitting
-
-		std::shared_ptr<RelationalWriter> writer(Factory::create(output));
-
-		std::vector<AttributeMetadata> attributes(attributeCount);
-
-		for (size_t j = 0; j < attributeCount; j++) {
-			string_t attributeName = readNext(input, arguments, i, true);
-			TypeId attributeType = writer->toTypeId(readNext(input, arguments, i, true));
-			attributes[j] = {attributeName, attributeType};
-		}
-
-		writer->startRelation(relationName, attributes, writeHeader);
-
-		while (true) {
-			string_t value = readNext(input, arguments, i, false);
-			if (value.empty() && (input.eof() || input.fail() || (!readStdIn && i >= arguments.size()))) break;
-			else writer->writeAttribute(value);
-			// TODO: check attribute count (avoid unfinished records)
-		}
-	}
-};
-
-}
-}
-}
--- a/src/relpipe-in-cli.cpp	Tue Sep 22 17:42:19 2020 +0200
+++ b/src/relpipe-in-cli.cpp	Tue Sep 22 21:00:30 2020 +0200
@@ -25,23 +25,15 @@
 #include <relpipe/cli/CLI.h>
 #include <relpipe/cli/RelpipeCLIException.h>
 
-#include "Command.h"
-#include "ArgumentsCommand.h"
-#include "StdInCommand.h"
+#include "CLIParser.h"
+#include "Configuration.h"
+
+#include "CLICommand.h"
 
 using namespace relpipe::cli;
 using namespace relpipe::in::cli;
 using namespace relpipe::writer;
 
-Command* findCommand(string_t commandName) {
-	// TODO: better command names
-	// TODO: help command
-	if (commandName == L"generate") return new ArgumentsCommand();
-	else if (commandName == L"generate-without-stdin") return new StdInCommand(false); // TODO: just for testing, StdInCommand(false) should work same as ArgumentsCommand()
-	else if (commandName == L"generate-from-stdin") return new StdInCommand(true);
-	else throw RelpipeCLIException(L"Unknown command: " + commandName, CLI::EXIT_CODE_UNKNOWN_COMMAND);
-}
-
 int main(int argc, char** argv) {
 	setlocale(LC_ALL, "");
 	CLI::untieStdIO();
@@ -50,29 +42,20 @@
 	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
 
 	try {
-		if (cli.arguments().size() > 0) {
-
-			const wstring commandName = cli.arguments()[0];
-			vector<wstring> arguments(cli.arguments().size() - 1);
-			for (int i = 1; i < cli.arguments().size(); i++) {
-				arguments[i - 1] = cli.arguments()[i];
-			}
-
-			std::shared_ptr<Command> command(findCommand(commandName));
-			command->process(cin, cout, commandName, arguments);
-			resultCode = CLI::EXIT_CODE_SUCCESS;
-
-		} else {
-			throw RelpipeCLIException(L"Missing command…", CLI::EXIT_CODE_BAD_SYNTAX);
-		}
-	} catch (RelpipeCLIException e) {
+		CLIParser cliParser;
+		Configuration configuration = cliParser.parse(cli.arguments());
+		CLICommand command;
+		std::shared_ptr<RelationalWriter> writer(Factory::create(std::cout));
+		command.process(configuration, writer);
+		resultCode = CLI::EXIT_CODE_SUCCESS;
+	} catch (RelpipeWriterException& e) {
+		fwprintf(stderr, L"Caught Writer exception: %ls\n", e.getMessge().c_str());
+		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
+		resultCode = CLI::EXIT_CODE_DATA_ERROR;
+	} catch (RelpipeCLIException& e) {
 		fwprintf(stderr, L"Caught CLI exception: %ls\n", e.getMessge().c_str());
 		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
 		resultCode = e.getExitCode();
-	} catch (RelpipeWriterException e) {
-		fwprintf(stderr, L"Caught Writer exception: %ls\n", e.getMessge().c_str());
-		fwprintf(stderr, L"Debug: Input stream: eof=%ls, lastRead=%d\n", (cin.eof() ? L"true" : L"false"), cin.gcount());
-		resultCode = CLI::EXIT_CODE_DATA_ERROR;
 	}
 
 	return resultCode;