--- /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());