change CLI interface: options: --relation --attribute v_0 v0.17
authorFrantišek Kučera <franta-hg@frantovo.cz>
Wed, 23 Sep 2020 16:58:48 +0200
branchv_0
changeset 16 15ee963675af
parent 15 157bb1d5e08a
child 17 f2dfd6dc1817
change CLI interface: options: --relation --attribute
bash-completion.sh
src/CLIParser.h
src/CSVCommand.cpp
src/CSVCommand.h
src/Configuration.h
src/relpipe-in-csv.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bash-completion.sh	Wed Sep 23 16:58:48 2020 +0200
@@ -0,0 +1,43 @@
+# 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_csv_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"
+		)
+		COMPREPLY=($(compgen -W "${OPTIONS[*]}" -- "$w0"))
+	fi
+}
+
+complete -F _relpipe_in_csv_completion relpipe-in-csv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/CLIParser.h	Wed Sep 23 16:58:48 2020 +0200
@@ -0,0 +1,93 @@
+/**
+ * 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 csv {
+
+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);
+	}
+
+	/**
+	 * 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;
+
+	Configuration parse(const std::vector<relpipe::writer::string_t>& arguments) {
+		Configuration c;
+
+		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 if (option == OPTION_ATTRIBUTE) {
+				AttributeRecipe attribute;
+				attribute.name = readNext(arguments, i);
+				attribute.type = parseTypeId(readNext(arguments, i));
+				c.attributes.push_back(attribute);
+			} else throw relpipe::cli::RelpipeCLIException(L"Unsupported CLI option: " + option, relpipe::cli::CLI::EXIT_CODE_BAD_CLI_ARGUMENTS);
+		}
+
+		return c;
+	}
+
+	virtual ~CLIParser() {
+	}
+};
+
+const relpipe::writer::string_t CLIParser::OPTION_RELATION = L"--relation";
+const relpipe::writer::string_t CLIParser::OPTION_ATTRIBUTE = L"--attribute";
+
+}
+}
+}
--- a/src/CSVCommand.cpp	Wed Sep 23 11:26:33 2020 +0200
+++ b/src/CSVCommand.cpp	Wed Sep 23 16:58:48 2020 +0200
@@ -87,7 +87,7 @@
 	return false;
 }
 
-void CSVCommand::process(std::istream& input, const vector<relpipe::writer::string_t>& args, std::shared_ptr<writer::RelationalWriter> writer) {
+void CSVCommand::process(std::istream& input, std::shared_ptr<writer::RelationalWriter> writer, Configuration& configuration) {
 	wstring_convert < codecvt_utf8<wchar_t>> convertor; // UTF-8 is required for CSV
 	vector<AttributeMetadata> metadata;
 	bool headerDone = false;
@@ -105,30 +105,28 @@
 			metadata.push_back(am);
 			if (lastInRecord) {
 
-				/*
-				 * Usage (simple syntax):
-				 * relpipe-in-csv → default relation name, attribute names on the first line, all types are string
-				 * relpipe-in-csv my_relation → custom relation name
-				 * relpipe-in-csv my_relation a b c → custom relation name, custom attribute names (a,b,c), first line contains data
-				 * relpipe-in-csv my_relation a integer b string c boolean → custom relation name, custom attribute names (a,b,c), custom types (integer,string,boolean), first line contains data
-				 */
+				// TODO: allow types on CLI and names from CSV?
+				// TODO: allow types on the second line of the CSV?
+				// TODO: allow regex pattern+replacement for extracting name and type from the first line of the CSV?
+				// TODO: allow attribute filtering, subset, like relpipe-tr-cur?
+				// TODO: allow skipping lines, like tail -n +2 ?
+				
+				vector<string_t> firstLine;
 
-				vector<string_t> firstLine;
-				if (args.size() == (1 + metadata.size())) {
+				if (metadata.size() == configuration.attributes.size()) {
 					for (int i = 0; i < metadata.size(); i++) {
 						firstLine.push_back(metadata[i].attributeName);
-						metadata[i].attributeName = args[1 + i];
+						metadata[i].attributeName = configuration.attributes[i].name;
+						metadata[i].typeId = configuration.attributes[i].type;
 					}
-				} else if (args.size() == (1 + 2 * metadata.size())) {
-					for (int i = 0; i < metadata.size(); i++) {
-						firstLine.push_back(metadata[i].attributeName);
-						metadata[i].attributeName = args[1 + i * 2];
-						metadata[i].typeId = writer->toTypeId(args[1 + i * 2 + 1]);
-					}
+				} else if (configuration.attributes.size() == 0) {
+					// first line contains attribute names and type is always string
+				} else {
+					throw RelpipeWriterException(L"Declared attribute count (" + std::to_wstring(configuration.attributes.size()) + L") does not match with number of columns of the first line (" + std::to_wstring(metadata.size()) + L")");
 				}
 
 				headerDone = true;
-				writer->startRelation(args.size() > 0 ? args[0] : L"csv", metadata, true);
+				writer->startRelation(configuration.relation, metadata, true);
 				if (firstLine.size()) {
 					for (string_t value : firstLine) writer->writeAttribute(value);
 				}
--- a/src/CSVCommand.h	Wed Sep 23 11:26:33 2020 +0200
+++ b/src/CSVCommand.h	Wed Sep 23 16:58:48 2020 +0200
@@ -23,6 +23,8 @@
 
 #include <relpipe/writer/TypeId.h>
 
+#include "Configuration.h"
+
 namespace relpipe {
 namespace in {
 namespace csv {
@@ -31,7 +33,7 @@
 private:
 	bool readValue(std::istream& input, std::stringstream& currentValue, bool& lastInRecord);
 public:
-	void process(std::istream& input, const vector<relpipe::writer::string_t>& args, std::shared_ptr<writer::RelationalWriter> writer);
+	void process(std::istream& input, std::shared_ptr<writer::RelationalWriter> writer, Configuration& configuration);
 
 };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/Configuration.h	Wed Sep 23 16:58:48 2020 +0200
@@ -0,0 +1,51 @@
+/**
+ * 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 csv {
+
+class AttributeRecipe {
+public:
+
+	virtual ~AttributeRecipe() {
+	}
+
+	relpipe::writer::string_t name;
+	relpipe::writer::TypeId type;
+
+};
+
+class Configuration {
+public:
+	relpipe::writer::string_t relation = L"csv";
+	std::vector<AttributeRecipe> attributes;
+
+	virtual ~Configuration() {
+	}
+};
+
+}
+}
+}
\ No newline at end of file
--- a/src/relpipe-in-csv.cpp	Wed Sep 23 11:26:33 2020 +0200
+++ b/src/relpipe-in-csv.cpp	Wed Sep 23 16:58:48 2020 +0200
@@ -30,6 +30,8 @@
 #include <relpipe/cli/CLI.h>
 
 #include "CSVCommand.h"
+#include "CLIParser.h"
+#include "Configuration.h"
 
 using namespace std;
 using namespace relpipe::cli;
@@ -44,9 +46,11 @@
 	int resultCode = CLI::EXIT_CODE_UNEXPECTED_ERROR;
 
 	try {
+		CLIParser cliParser;
+		Configuration configuration = cliParser.parse(cli.arguments());
 		CSVCommand command;
 		std::shared_ptr<RelationalWriter> writer(Factory::create(std::cout));
-		command.process(cin, cli.arguments(), writer);
+		command.process(cin, writer, configuration);
 		resultCode = CLI::EXIT_CODE_SUCCESS;
 	} catch (RelpipeWriterException e) {
 		fwprintf(stderr, L"Caught Writer exception: %ls\n", e.getMessge().c_str());