diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/sqlitestudiocli | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/sqlitestudiocli')
56 files changed, 4059 insertions, 0 deletions
diff --git a/SQLiteStudio3/sqlitestudiocli/cli.cpp b/SQLiteStudio3/sqlitestudiocli/cli.cpp new file mode 100644 index 0000000..a663e84 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli.cpp @@ -0,0 +1,321 @@ +#include "cli.h" +#include "services/config.h" +#include "cli_config.h" +#include "services/dbmanager.h" +#include "commands/clicommandfactory.h" +#include "commands/clicommand.h" +#include "qio.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "climsghandler.h" +#include "clicompleter.h" +#include <QCoreApplication> +#include <QThread> +#include <QFile> +#include <QSet> +#include <QStringList> +#include <QLibrary> + +#if defined(Q_OS_WIN32) +#include "readline.h" +#elif defined(Q_OS_UNIX) +#include <readline/readline.h> +#include <readline/history.h> +#endif + +CLI* CLI::instance = nullptr; + +CLI::CLI(QObject* parent) : + QObject(parent) +{ + setCurrentDb(nullptr); + + using_history(); + +#ifdef Q_OS_UNIX + history_base = 0; // for some reason this was set to 1 under Unix, making 1st history entry to be always ommited +#endif + + + loadHistory(); + CliCompleter::getInstance()->init(this); +} + +CLI::~CLI() +{ +} + +CLI* CLI::getInstance() +{ + if (!instance) + instance = new CLI(); + + return instance; +} + +void CLI::start() +{ + thread = new QThread(this); + + CliCommandFactory::init(); + + connect(thread, &QThread::started, this, &CLI::doWork); + connect(thread, &QThread::finished, this, &CLI::done); + this->moveToThread(thread); + + if (!getCurrentDb()) // it could be set by openDbFile() from main(). + { + Db* db = DBLIST->getByName(CFG_CLI.Console.DefaultDatabase.get()); + if (db) + { + setCurrentDb(db); + } + else + { + QList<Db*> dbList = DBLIST->getDbList(); + if (dbList.size() > 0) + setCurrentDb(dbList[0]); + else + setCurrentDb(nullptr); + } + } + + qOut << QString("\n%1 (%2)\n------------------------\n\n").arg(QCoreApplication::applicationName()).arg(QCoreApplication::applicationVersion()); + qOut.flush(); + + if (getCurrentDb()) + qOut << tr("Current database: %1").arg(getCurrentDb()->getName()) << "\n"; + else + qOut << tr("No current working database is set.") << "\n"; + + qOut << tr("Type %1 for help").arg(".help") << "\n\n"; + + qOut.flush(); + + thread->start(); +} + +void CLI::setCurrentDb(Db* db) +{ + currentDb = db; + if (db && !db->isOpen()) + db->open(); +} + +Db* CLI::getCurrentDb() const +{ + return currentDb; +} + +void CLI::exit() +{ + doExit = true; +} + +QStringList CLI::getHistory() const +{ + QStringList cfgHistory; + + int length = historyLength(); + + QString line; + HIST_ENTRY* entry = nullptr; + for (int i = 0; i < length; i++) + { + entry = history_get(i); + if (!entry) + { + qWarning() << "Null history entry for i =" << i; + continue; + } + + line = QString::fromLocal8Bit(entry->line); + if (line.isEmpty()) + continue; + + cfgHistory << line; + } + return cfgHistory; +} + +void CLI::println(const QString &msg) +{ + qOut << msg << "\n"; + qOut.flush(); +} + +int CLI::historyLength() const +{ +#if defined(Q_OS_WIN) + return history_length(); +#elif defined(Q_OS_UNIX) + return history_length; +#endif +} + +void CLI::waitForExecution() +{ + executionFinished = false; + while (!executionFinished) + { + qApp->processEvents(); + QThread::usleep(20); + } +} + +bool CLI::isComplete(const QString& contents) const +{ + if (contents.startsWith(CFG_CLI.Console.CommandPrefixChar.get())) + return true; + + Dialect dialect = Dialect::Sqlite3; + if (getCurrentDb()) + dialect = getCurrentDb()->getDialect(); + + bool complete = true; + splitQueries(contents, dialect, true, &complete); + return complete; +} + +void CLI::loadHistory() +{ + foreach (const QString& line, CFG->getCliHistory()) + { + if (!line.isEmpty()) + add_history(line.toLocal8Bit().data()); + } +} + +void CLI::addHistory(const QString& text) +{ + if (text == lastHistoryEntry) + return; + + CFG->addCliHistory(text); + + add_history(text.toLocal8Bit().data()); + if (historyLength() > CFG_CORE.Console.HistorySize.get()) +#ifdef Q_OS_OSX + free(remove_history(0)); +#else + free_history_entry(remove_history(0)); +#endif + + lastHistoryEntry = text; +} + +QString CLI::getLine() const +{ + return line; +} + +void CLI::applyHistoryLimit() +{ + CFG->applyCliHistoryLimit(); + while (historyLength() > CFG_CORE.Console.HistorySize.get()) +#ifdef Q_OS_OSX + free(remove_history(0)); +#else + free_history_entry(remove_history(0)); +#endif +} + +void CLI::openDbFile(const QString& path) +{ + QString name = DBLIST->quickAddDb(path, QHash<QString,QVariant>()); + if (name.isNull()) + { + println(tr("Could not add database %1 to list.").arg(path)); + return; + } + Db* db = DBLIST->getByName(name); + setCurrentDb(db); +} + +void CLI::doWork() +{ + static const QString prompt = "%1>"; + + CliCommand* cliCommand = nullptr; + QString cmd; + QStringList cmdArgs; + QString cPrompt; + char *cline = nullptr; + while (!doExit) + { + line.clear(); + + while (!doExit && (line.isEmpty() || !isComplete(line))) + { + if (getCurrentDb()) + { + cPrompt = getCurrentDb()->getName(); + if (!getCurrentDb()->isOpen()) + cPrompt += " ["+tr("closed")+"]"; + + cPrompt = prompt.arg(cPrompt); + } + else + cPrompt = prompt.arg(""); + + if (!line.isEmpty()) + { + cPrompt = pad("->", -cPrompt.length(), ' '); + line += "\n"; + } + + cline = readline(cPrompt.toLocal8Bit().data()); + + line += cline; + free(cline); + } + addHistory(line); + + if (line.startsWith(CFG_CLI.Console.CommandPrefixChar.get())) + { + cmdArgs = tokenizeArgs(line.mid(1)); + cmd = cmdArgs.takeAt(0); + cliCommand = CliCommandFactory::getCommand(cmd); + if (!cliCommand) + { + println("No such command."); + continue; + } + } + else + { + cliCommand = CliCommandFactory::getCommand("query"); + cmdArgs.clear(); + cmdArgs << line; + } + + cliCommand->setup(this); + if (!cliCommand->parseArgs(cmdArgs)) + { + delete cliCommand; + continue; + } + + cliCommand->moveToThread(qApp->thread()); + emit execCommand(cliCommand); + waitForExecution(); + } + + thread->quit(); +} + +void CLI::done() +{ + qApp->exit(); +} + +void CLI::executionComplete() +{ + executionFinished = true; +} + +void CLI::clearHistory() +{ + clear_history(); + CFG->clearCliHistory(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/cli.h b/SQLiteStudio3/sqlitestudiocli/cli.h new file mode 100644 index 0000000..6f7c927 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli.h @@ -0,0 +1,62 @@ +#ifndef CLI_H +#define CLI_H + +#include "db/db.h" +#include <QObject> +#include <QTextStream> +#include <QStringList> +#include <QTime> + +class QThread; +class QFile; +class DbManager; +class CliCommand; + +class CLI : public QObject +{ + Q_OBJECT + + public: + ~CLI(); + + static CLI* getInstance(); + + void start(); + void setCurrentDb(Db* db); + Db* getCurrentDb() const; + void exit(); + QStringList getHistory() const; + QString getLine() const; + void applyHistoryLimit(); + + private: + explicit CLI(QObject* parent = nullptr); + + void waitForExecution(); + bool isComplete(const QString& contents) const; + void loadHistory(); + void addHistory(const QString& text); + void println(const QString& msg = QString()); + int historyLength() const; + + static CLI* instance; + + QString lastHistoryEntry; + QThread* thread = nullptr; + Db* currentDb = nullptr; + bool executionFinished = false; + bool doExit = false; + QString line; + + signals: + void execCommand(CliCommand* cmd); + + public slots: + void doWork(); + void done(); + void executionComplete(); + void clearHistory(); + void openDbFile(const QString& path); +}; + +#endif // CLI_H diff --git a/SQLiteStudio3/sqlitestudiocli/cli_config.cpp b/SQLiteStudio3/sqlitestudiocli/cli_config.cpp new file mode 100644 index 0000000..93c2b53 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli_config.cpp @@ -0,0 +1,55 @@ +#include "cli_config.h" + +CFG_DEFINE(Cli) + +CliResultsDisplay::Mode CliResultsDisplay::mode(const QString& mode) +{ + if (mode == "ROW") + return ROW; + + if (mode == "FIXED") + return FIXED; + + if (mode == "COLUMNS") + return COLUMNS; + + return CLASSIC; +} + +QString CliResultsDisplay::mode(CliResultsDisplay::Mode mode) +{ + switch (mode) + { + case ROW: + return "ROW"; + case FIXED: + return "FIXED"; + case CLASSIC: + return "CLASSIC"; + case COLUMNS: + return "COLUMNS"; + } + return "CLASSIC"; +} + + +void CliResultsDisplay::staticInit() +{ + qRegisterMetaType<CliResultsDisplay::Mode>(); + qRegisterMetaTypeStreamOperators<CliResultsDisplay::Mode>(); +} + + +QDataStream& operator<<(QDataStream& out, const CliResultsDisplay::Mode& mode) +{ + out << static_cast<int>(mode); + return out; +} + +QDataStream& operator>>(QDataStream& in, CliResultsDisplay::Mode& mode) +{ + int modeEnum; + in >> modeEnum; + mode = static_cast<CliResultsDisplay::Mode>(modeEnum); + return in; +} diff --git a/SQLiteStudio3/sqlitestudiocli/cli_config.h b/SQLiteStudio3/sqlitestudiocli/cli_config.h new file mode 100644 index 0000000..7827462 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cli_config.h @@ -0,0 +1,38 @@ +#ifndef CLI_CONFIG_H +#define CLI_CONFIG_H + +#include "config_builder.h" + +namespace CliResultsDisplay +{ + enum Mode + { + CLASSIC = 0, + FIXED = 1, + ROW = 2, + COLUMNS = 3 + }; + + Mode mode(const QString& mode); + QString mode(Mode mode); + void staticInit(); + +} + +QDataStream &operator<<(QDataStream &out, const CliResultsDisplay::Mode& mode); +QDataStream &operator>>(QDataStream &in, CliResultsDisplay::Mode& mode); + +Q_DECLARE_METATYPE(CliResultsDisplay::Mode) + +CFG_CATEGORIES(Cli, + CFG_CATEGORY(Console, + CFG_ENTRY(QString, DefaultDatabase, QString()) + CFG_ENTRY(QString, CommandPrefixChar, ".") + CFG_ENTRY(CliResultsDisplay::Mode, ResultsDisplayMode, CliResultsDisplay::CLASSIC) + CFG_ENTRY(QString, NullValue, "") + ) +) + +#define CFG_CLI CFG_INSTANCE(Cli) + +#endif // CLI_CONFIG_H diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp new file mode 100644 index 0000000..6a3072e --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.cpp @@ -0,0 +1,24 @@ +#include "clicommandexecutor.h" +#include "commands/clicommand.h" + +CliCommandExecutor::CliCommandExecutor(QObject *parent) : + QObject(parent) +{ +} + +void CliCommandExecutor::execCommand(CliCommand* cmd) +{ + connect(cmd, SIGNAL(execComplete()), this, SLOT(asyncExecutionComplete())); + cmd->execute(); + if (!cmd->isAsyncExecution()) + { + delete cmd; + emit executionComplete(); + } +} + +void CliCommandExecutor::asyncExecutionComplete() +{ + sender()->deleteLater(); + emit executionComplete(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h new file mode 100644 index 0000000..0312e51 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandexecutor.h @@ -0,0 +1,26 @@ +#ifndef CLICOMMANDEXECUTOR_H +#define CLICOMMANDEXECUTOR_H + +#include <QObject> +#include <QStringList> + +class CliCommand; + +class CliCommandExecutor : public QObject +{ + Q_OBJECT + + public: + explicit CliCommandExecutor(QObject *parent = 0); + + signals: + void executionComplete(); + + public slots: + void execCommand(CliCommand* cmd); + + private slots: + void asyncExecutionComplete(); +}; + +#endif // CLICOMMANDEXECUTOR_H diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp new file mode 100644 index 0000000..732b0f0 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.cpp @@ -0,0 +1,433 @@ +#include "clicommandsyntax.h"
+#include "commands/clicommand.h"
+#include "commands/clicommandfactory.h"
+#include "cli.h"
+#include <QDebug>
+
+CliCommandSyntax::CliCommandSyntax()
+{
+}
+
+CliCommandSyntax::~CliCommandSyntax()
+{
+ foreach (Argument* arg, arguments)
+ delete arg;
+
+ arguments.clear();
+ argumentMap.clear();
+
+ foreach (Option* opt, options)
+ delete opt;
+
+ optionMap.clear();
+ optionsLongNameMap.clear();
+ optionsShortNameMap.clear();
+ options.clear();
+}
+
+void CliCommandSyntax::addArgument(int id, const QString& name, bool mandatory)
+{
+ addArgumentInternal(id, {name}, mandatory, Argument::REGULAR);
+}
+
+void CliCommandSyntax::addStrictArgument(int id, const QStringList& values, bool mandatory)
+{
+ addArgumentInternal(id, values, mandatory, Argument::STRICT);
+}
+
+void CliCommandSyntax::addAlternatedArgument(int id, const QStringList& names, bool mandatory)
+{
+ addArgumentInternal(id, names, mandatory, Argument::ALTERNATED);
+}
+
+void CliCommandSyntax::addOptionShort(int id, const QString& shortName)
+{
+ addOptionInternal(id, shortName, QString(), QString());
+}
+
+void CliCommandSyntax::addOptionLong(int id, const QString& longName)
+{
+ addOptionInternal(id, QString(), longName, QString());
+}
+
+void CliCommandSyntax::addOption(int id, const QString& shortName, const QString& longName)
+{
+ addOptionInternal(id, shortName, longName, QString());
+}
+
+void CliCommandSyntax::addOptionWithArgShort(int id, const QString& shortName, const QString& argName)
+{
+ addOptionInternal(id, shortName, QString(), argName);
+}
+
+void CliCommandSyntax::addOptionWithArgLong(int id, const QString& longName, const QString& argName)
+{
+ addOptionInternal(id, QString(), longName, argName);
+}
+
+void CliCommandSyntax::addOptionWithArg(int id, const QString& shortName, const QString& longName, const QString& argName)
+{
+ addOptionInternal(id, shortName, longName, argName);
+}
+
+CliCommandSyntax::Argument* CliCommandSyntax::addArgumentInternal(int id, const QStringList& names, bool mandatory, Argument::Type type)
+{
+ checkNewArgument(mandatory);
+
+ Argument* arg = new Argument;
+ arg->mandatory = mandatory;
+ arg->id = id;
+ arg->type = type;
+ arg->names = names;
+ arguments << arg;
+ argumentMap[id] = arg;
+ return arg;
+}
+
+CliCommandSyntax::Option* CliCommandSyntax::addOptionInternal(int id, const QString& shortName, const QString& longName, const QString& argName)
+{
+ Option* opt = new Option;
+ opt->shortName = shortName;
+ opt->longName = longName;
+ opt->id = id;
+ opt->argName = argName;
+ optionMap[id] = opt;
+ options << opt;
+ optionsShortNameMap[shortName] = opt;
+ optionsLongNameMap[longName] = opt;
+ return opt;
+}
+
+bool CliCommandSyntax::getStrictArgumentCount() const
+{
+ return strictArgumentCount;
+}
+
+void CliCommandSyntax::setStrictArgumentCount(bool value)
+{
+ strictArgumentCount = value;
+}
+
+bool CliCommandSyntax::parse(const QStringList& args)
+{
+ pastOptions = (options.size() == 0);
+ lastParsedOption = nullptr;
+ QString arg;
+ bool res = false;
+ int argCnt = args.size();
+ for (int argIdx = 0; argIdx < argCnt; argIdx++)
+ {
+ arg = args[argIdx];
+
+ if (arg == "--help")
+ {
+ CliCommand* help = CliCommandFactory::getCommand("help");
+ help->setup(CLI::getInstance());
+ help->parseArgs({getName()});
+ help->execute();
+ return false;
+ }
+ else if (pastOptions)
+ {
+ res = parseArg(arg);
+ }
+ else if (arg == "--")
+ {
+ pastOptions = true;
+ res = true;
+ }
+ else if (arg.startsWith("-"))
+ {
+ res = parseOpt(arg, args, argIdx);
+ }
+ else
+ {
+ pastOptions = true;
+ res = parseArg(arg);
+ }
+
+ if (!res)
+ return false;
+ }
+
+ if (strictArgumentCount && argPosition < requiredArguments())
+ {
+ parsingErrorText = QObject::tr("Insufficient number of arguments.");
+ return false;
+ }
+
+ return true;
+}
+
+QString CliCommandSyntax::getErrorText() const
+{
+ return parsingErrorText;
+}
+
+QStringList CliCommandSyntax::getStrictArgumentCandidates()
+{
+ QStringList results;
+ if (!pastOptions)
+ {
+ if (lastParsedOption && !lastParsedOption->argName.isEmpty())
+ return results; // this case is covered by getRegularArgumentCandidates()
+
+ foreach (Option* opt, options)
+ {
+ if (opt->requested)
+ continue;
+
+ if (!opt->shortName.isEmpty())
+ results << "-"+opt->shortName;
+
+ if (!opt->longName.isEmpty())
+ results << "--"+opt->longName;
+ }
+ results << "--";
+ }
+
+ if (argPosition < arguments.size() && arguments[argPosition]->type == Argument::STRICT)
+ results += arguments[argPosition]->names;
+
+ return results;
+}
+
+QList<int> CliCommandSyntax::getRegularArgumentCandidates()
+{
+ QList<int> results;
+
+ if (!pastOptions && lastParsedOption && !lastParsedOption->argName.isEmpty())
+ {
+ // We're exactly at the spot where the option argument is expected
+ results << lastParsedOption->id;
+ return results;
+ }
+
+ if (argPosition < arguments.size())
+ {
+ switch (arguments[argPosition]->type)
+ {
+ case Argument::REGULAR:
+ case Argument::ALTERNATED:
+ results << arguments[argPosition]->id;
+ break;
+ case Argument::STRICT:
+ break;
+ }
+ }
+
+ return results;
+}
+
+void CliCommandSyntax::addAlias(const QString& alias)
+{
+ aliases << alias;
+}
+
+QStringList CliCommandSyntax::getAliases() const
+{
+ return aliases;
+}
+
+QString CliCommandSyntax::getSyntaxDefinition() const
+{
+ return getSyntaxDefinition(name);
+}
+
+QString CliCommandSyntax::getSyntaxDefinition(const QString& usedName) const
+{
+ static const QString mandatoryArgTempl = "<%1>";
+ static const QString optionalArgTempl = "[<%1>]";
+ static const QString mandatoryStrictTempl = "%1";
+ static const QString optionalStrictTempl = "[%1]";
+ static const QString optionTempl = "[%1]";
+ static const QString optionWithArgTempl = "[%1 <%2>]";
+
+ QStringList words;
+ words << usedName;
+
+ QString optName;
+ QStringList optNameParts;
+ foreach (Option* opt, options)
+ {
+ optNameParts.clear();;
+ if (!opt->shortName.isEmpty())
+ optNameParts << "-"+opt->shortName;
+
+ if (!opt->longName.isEmpty())
+ optNameParts += "--"+opt->longName;
+
+ optName = optNameParts.join("|");
+
+ words << (opt->argName.isEmpty() ? optionTempl.arg(optName) : optionWithArgTempl.arg(optName).arg(opt->argName));
+ }
+
+ QString templ;
+ QString argName;
+ foreach (Argument* arg, arguments)
+ {
+ templ = (arg->mandatory ? mandatoryArgTempl : optionalArgTempl);
+ switch (arg->type)
+ {
+ case CliCommandSyntax::Argument::ALTERNATED:
+ argName = arg->names.join("|");
+ break;
+ case CliCommandSyntax::Argument::REGULAR:
+ argName = arg->names.first();
+ break;
+ case CliCommandSyntax::Argument::STRICT:
+ argName = arg->names.join("|");
+ templ = (arg->mandatory ? mandatoryStrictTempl : optionalStrictTempl);
+ break;
+ }
+ words << templ.arg(argName);
+ }
+
+ return words.join(" ");
+}
+
+bool CliCommandSyntax::isArgumentSet(int id) const
+{
+ if (!argumentMap.contains(id))
+ return false;
+
+ return argumentMap[id]->defined;
+}
+
+QString CliCommandSyntax::getArgument(int id) const
+{
+ if (!argumentMap.contains(id))
+ return QString::null;
+
+ return argumentMap[id]->value;
+}
+
+bool CliCommandSyntax::isOptionSet(int id) const
+{
+ if (!optionMap.contains(id))
+ return false;
+
+ return optionMap[id]->requested;
+}
+
+QString CliCommandSyntax::getOptionValue(int id) const
+{
+ if (!optionMap.contains(id))
+ return QString::null;
+
+ return optionMap[id]->value;
+}
+
+bool CliCommandSyntax::parseArg(const QString& arg)
+{
+ if (strictArgumentCount && arguments.size() < (argPosition + 1))
+ {
+ parsingErrorText = QObject::tr("Too many arguments.");
+ return false;
+ }
+
+ switch (arguments[argPosition]->type)
+ {
+ case CliCommandSyntax::Argument::ALTERNATED:
+ {
+ arguments[argPosition]->value = arg;
+ arguments[argPosition]->defined = true;
+ break;
+ }
+ case CliCommandSyntax::Argument::REGULAR:
+ {
+ arguments[argPosition]->value = arg;
+ arguments[argPosition]->defined = true;
+ break;
+ }
+ case CliCommandSyntax::Argument::STRICT:
+ {
+ if (!arguments[argPosition]->names.contains(arg))
+ {
+ parsingErrorText = QObject::tr("Invalid argument value: %1.\nExpected one of: %2").arg(arg)
+ .arg(arguments[argPosition]->names.join(", "));
+
+ return false;
+ }
+ arguments[argPosition]->value = arg;
+ arguments[argPosition]->defined = true;
+ break;
+ }
+ default:
+ qCritical() << "Invalid argument type in CliCommandSyntax:" << arguments[argPosition]->type;
+ return false;
+ }
+
+ argPosition++;
+ return true;
+}
+
+bool CliCommandSyntax::parseOpt(const QString& arg, const QStringList& args, int& argIdx)
+{
+ Option* opt = nullptr;
+ if (arg.startsWith("--"))
+ {
+ QString longName = arg.mid(2);
+ if (optionsLongNameMap.contains(longName))
+ opt = optionsLongNameMap.value(longName);
+ }
+ else
+ {
+ QString shortName = arg.mid(1);
+ if (optionsShortNameMap.contains(shortName))
+ opt = optionsShortNameMap.value(shortName);
+ }
+
+ if (!opt)
+ {
+ parsingErrorText = QObject::tr("Unknown option: %1", "CLI command syntax").arg(arg);
+ return false;
+ }
+
+ opt->requested = true;
+ lastParsedOption = opt;
+
+ if (!opt->argName.isEmpty())
+ {
+ if (args.size() <= (argIdx + 1))
+ {
+ parsingErrorText = QObject::tr("Option %1 requires an argument.", "CLI command syntax").arg(arg);
+ return false;
+ }
+
+ argIdx++;
+ opt->value = args[argIdx];
+ }
+ return true;
+}
+
+int CliCommandSyntax::requiredArguments() const
+{
+ int cnt = 0;
+ foreach (Argument* arg, arguments)
+ {
+ if (arg->mandatory)
+ cnt++;
+ }
+ return cnt;
+}
+
+void CliCommandSyntax::checkNewArgument(bool mandatory)
+{
+ if (arguments.size() > 0 && !arguments.last()->mandatory && mandatory)
+ {
+ qWarning() << "Adding mandatory CLI command argument after optional argument. This will result in invalid syntax definition. The command is:"
+ << this->name;
+ }
+}
+
+QString CliCommandSyntax::getName() const
+{
+ return name;
+}
+
+void CliCommandSyntax::setName(const QString& value)
+{
+ name = value;
+}
+
diff --git a/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h new file mode 100644 index 0000000..adf88d2 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicommandsyntax.h @@ -0,0 +1,97 @@ +#ifndef CLICOMMANDSYNTAX_H
+#define CLICOMMANDSYNTAX_H
+
+#include <QString>
+#include <QStringList>
+#include <QHash>
+
+class CliCommandSyntax
+{
+ public:
+ CliCommandSyntax();
+ ~CliCommandSyntax();
+
+ void addArgument(int id, const QString& name, bool mandatory = true);
+ void addStrictArgument(int id, const QStringList& values, bool mandatory = true);
+ void addAlternatedArgument(int id, const QStringList& names, bool mandatory = true);
+ void addOptionShort(int id, const QString& shortName);
+ void addOptionLong(int id, const QString& longName);
+ void addOption(int id, const QString& shortName, const QString& longName);
+ void addOptionWithArgShort(int id, const QString& shortName, const QString& argName);
+ void addOptionWithArgLong(int id, const QString& longName, const QString& argName);
+ void addOptionWithArg(int id, const QString& shortName, const QString& longName, const QString& argName);
+
+ bool parse(const QStringList& args);
+ QString getErrorText() const;
+ QStringList getStrictArgumentCandidates();
+ QList<int> getRegularArgumentCandidates();
+
+ void addAlias(const QString& alias);
+ QStringList getAliases() const;
+
+ QString getSyntaxDefinition() const;
+ QString getSyntaxDefinition(const QString& usedName) const;
+
+ bool isArgumentSet(int id) const;
+ QString getArgument(int id) const;
+
+ bool isOptionSet(int id) const;
+ QString getOptionValue(int id) const;
+
+ QString getName() const;
+ void setName(const QString& value);
+
+ bool getStrictArgumentCount() const;
+ void setStrictArgumentCount(bool value);
+
+ private:
+ struct Argument
+ {
+ enum Type
+ {
+ REGULAR,
+ STRICT,
+ ALTERNATED
+ };
+
+ int id;
+ QStringList names;
+ Type type = REGULAR;
+ bool mandatory = true;
+ bool defined = false;
+ QString value;
+ };
+
+ struct Option
+ {
+ int id;
+ QString shortName;
+ QString longName;
+ QString argName;
+ bool requested = false;
+ QString value;
+ };
+
+ bool parseArg(const QString& arg);
+ bool parseOpt(const QString& arg, const QStringList& args, int& argIdx);
+ int requiredArguments() const;
+ void checkNewArgument(bool mandatory);
+ Argument* addArgumentInternal(int id, const QStringList& names, bool mandatory, Argument::Type type);
+ Option* addOptionInternal(int id, const QString& shortName, const QString& longName, const QString& argName);
+
+ int argPosition = 0;
+ QString parsingErrorText;
+ bool strictArgumentCount = true;
+ bool pastOptions = false;
+ QString name;
+ QStringList aliases;
+ QList<Argument*> arguments;
+ QHash<int,Argument*> argumentMap;
+ QList<Option*> options;
+ Option* lastParsedOption = nullptr;
+ QHash<int,Option*> optionMap;
+ QHash<QString,Option*> optionsShortNameMap;
+ QHash<QString,Option*> optionsLongNameMap;
+};
+
+#endif // CLICOMMANDSYNTAX_H
diff --git a/SQLiteStudio3/sqlitestudiocli/clicompleter.cpp b/SQLiteStudio3/sqlitestudiocli/clicompleter.cpp new file mode 100644 index 0000000..1c3c99c --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicompleter.cpp @@ -0,0 +1,173 @@ +#include "clicompleter.h"
+#include "completionhelper.h"
+#include "cli.h"
+#include "cli_config.h"
+#include "common/unused.h"
+#include "commands/clicommand.h"
+#include "parser/lexer.h"
+#include "commands/clicommandfactory.h"
+#include <QString>
+#include <QDebug>
+
+#if defined(Q_OS_WIN32)
+#include "readline.h"
+#elif defined(Q_OS_UNIX)
+#include <readline/readline.h>
+#endif
+
+CliCompleter* CliCompleter::instance = nullptr;
+
+CliCompleter::CliCompleter()
+{
+}
+
+void CliCompleter::init(CLI* value)
+{
+ rl_attempted_completion_function = CliCompleter::complete;
+ cli = value;
+}
+
+CliCompleter* CliCompleter::getInstance()
+{
+ if (!instance)
+ instance = new CliCompleter();
+
+ return instance;
+}
+
+char** CliCompleter::complete(const char* text, int start, int end)
+{
+ UNUSED(start);
+
+#ifdef Q_OS_UNIX
+ // Unix readline needs this to disable the completion using rl_completion_entry_function
+ rl_attempted_completion_over = 1;
+#endif
+
+ return toCharArray(getInstance()->completeInternal(QString::fromLocal8Bit(text), QString::fromLocal8Bit(rl_line_buffer), end));
+}
+
+QStringList CliCompleter::completeInternal(const QString& toBeReplaced, const QString& text, int curPos)
+{
+ QString str;
+ if (!cli->getLine().isEmpty())
+ {
+ str += cli->getLine();
+ curPos += str.length();
+ }
+
+ str += text;
+
+ QStringList list;
+ if (str.startsWith(CFG_CLI.Console.CommandPrefixChar.get()))
+ list = completeCommand(str, curPos);
+ else
+ list = completeQuery(toBeReplaced, str, curPos);
+
+ list.removeDuplicates();
+
+#ifdef Q_OS_WIN
+ if (list.size() == 1)
+ list[0] += " ";
+#endif
+
+#ifdef Q_OS_UNIX
+ // Unix readline treats first element in the list as a common value of all elements
+ if (list.size() > 0)
+ list.prepend(longestCommonPart(list));
+#endif
+
+ return list;
+}
+
+QStringList CliCompleter::completeCommand(const QString& str, int curPos)
+{
+ QStringList results;
+ QString text = str.mid(0, curPos);
+
+ QStringList cmdWords = tokenizeArgs(text);
+ if (cmdWords.size() == 0)
+ return results;
+
+ if (text[text.length()-1].isSpace())
+ cmdWords << "";
+
+ QString cmdStr = cmdWords[0].mid(1);
+ if (cmdWords.size() > 1)
+ {
+ CliCommand* command = CliCommandFactory::getCommand(cmdStr);
+ if (!command)
+ return results;
+
+ command->setup(cli);
+ results = command->complete(cmdWords.mid(1)).filter(QRegExp("^"+cmdWords.last()+".*"));
+ }
+ else
+ {
+ QStringList cmdNames = CliCommandFactory::getCommandNames().filter(QRegExp("^"+cmdStr+".*"));
+ cmdNames.sort(Qt::CaseInsensitive);
+ foreach (const QString& cmdName, cmdNames)
+ results << CFG_CLI.Console.CommandPrefixChar.get() + cmdName;
+ }
+
+ return results;
+}
+
+QStringList CliCompleter::completeQuery(const QString& toBeReplaced, const QString& str, int curPos)
+{
+ QStringList list;
+ if (!cli->getCurrentDb())
+ return list;
+
+ bool keepOriginalStr = doKeepOriginalStr(str, curPos);
+
+ CompletionHelper completer(str, curPos, cli->getCurrentDb());
+ QList<ExpectedTokenPtr> expectedTokens = completer.getExpectedTokens().filtered();
+
+ foreach (const ExpectedTokenPtr& token, expectedTokens)
+ list << token->value;
+
+ list.removeAll("");
+ if (list.size() > 1)
+ list.removeOne(";"); // we don't want it together with other proposals, cause it introduces problems when proposed by completer
+
+ if (keepOriginalStr)
+ {
+ QMutableStringListIterator it(list);
+ while (it.hasNext())
+ it.next().prepend(toBeReplaced);
+ }
+ return list;
+}
+
+bool CliCompleter::doKeepOriginalStr(const QString& str, int curPos)
+{
+ Dialect dialect = Dialect::Sqlite3;
+ if (cli->getCurrentDb())
+ dialect = cli->getCurrentDb()->getDialect();
+
+ TokenList tokens = Lexer::tokenize(str.mid(0, curPos), dialect);
+ if (tokens.size() == 0)
+ return false;
+
+ return tokens.last()->isSeparating();
+}
+
+char** CliCompleter::toCharArray(const QStringList& list)
+{
+ if (list.size() == 0)
+ return nullptr;
+
+ char** array = (char**)malloc((list.size() + 1) * sizeof(char*));
+ array[list.size()] = nullptr;
+
+ int i = 0;
+ foreach (const QString& str, list)
+#if defined(Q_OS_WIN)
+ array[i++] = _strdup(str.toLocal8Bit().data());
+#elif defined(Q_OS_UNIX)
+ array[i++] = strdup(str.toLocal8Bit().data());
+#endif
+
+ return array;
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/clicompleter.h b/SQLiteStudio3/sqlitestudiocli/clicompleter.h new file mode 100644 index 0000000..a43a98d --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/clicompleter.h @@ -0,0 +1,30 @@ +#ifndef CLICOMPLETER_H
+#define CLICOMPLETER_H
+
+#include <QStringList>
+
+class CLI;
+
+class CliCompleter
+{
+ public:
+ static CliCompleter* getInstance();
+ static char** complete(const char* text, int start, int end);
+
+ void init(CLI* value);
+
+ private:
+ CliCompleter();
+ QStringList completeInternal(const QString& toBeReplaced, const QString& text, int curPos);
+ QStringList completeCommand(const QString& str, int curPos);
+ QStringList completeQuery(const QString& toBeReplaced, const QString& str, int curPos);
+ bool doKeepOriginalStr(const QString& str, int curPos);
+
+ static char** toCharArray(const QStringList& list);
+
+ static CliCompleter* instance;
+
+ CLI* cli = nullptr;
+};
+
+#endif // CLICOMPLETER_H
diff --git a/SQLiteStudio3/sqlitestudiocli/climsghandler.cpp b/SQLiteStudio3/sqlitestudiocli/climsghandler.cpp new file mode 100644 index 0000000..d36f9d0 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/climsghandler.cpp @@ -0,0 +1,38 @@ +#include "climsghandler.h"
+#include "qio.h"
+#include "cli_config.h"
+#include "common/unused.h"
+
+bool cliDebug = false;
+
+void cliMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
+{
+ if (!cliDebug)
+ return;
+
+ UNUSED(context);
+
+ QString txt;
+ switch (type) {
+ case QtDebugMsg:
+ txt = QString("Debug: %1").arg(msg);
+ break;
+ case QtWarningMsg:
+ txt = QString("Warning: %1").arg(msg);
+ break;
+ case QtCriticalMsg:
+ txt = QString("Critical: %1").arg(msg);
+ break;
+ case QtFatalMsg:
+ txt = QString("Fatal: %1").arg(msg);
+ abort();
+ }
+
+ qOut << txt << "\n";
+ qOut.flush();
+}
+
+void setCliDebug(bool enabled)
+{
+ cliDebug = enabled;
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/climsghandler.h b/SQLiteStudio3/sqlitestudiocli/climsghandler.h new file mode 100644 index 0000000..e2f184e --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/climsghandler.h @@ -0,0 +1,9 @@ +#ifndef CLIMSGHANDLER_H
+#define CLIMSGHANDLER_H
+
+#include <QtDebug>
+
+void cliMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);
+void setCliDebug(bool enabled);
+
+#endif // CLIMSGHANDLER_H
diff --git a/SQLiteStudio3/sqlitestudiocli/cliutils.cpp b/SQLiteStudio3/sqlitestudiocli/cliutils.cpp new file mode 100644 index 0000000..2b5c44d --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cliutils.cpp @@ -0,0 +1,99 @@ +#include "cliutils.h"
+#include <QtGlobal>
+
+#if defined(Q_OS_WIN32)
+#include <windows.h>
+#elif defined(Q_OS_UNIX)
+#include <sys/ioctl.h>
+#include <unistd.h>
+#endif
+
+#if defined(Q_OS_WIN32)
+
+int getCliColumns()
+{
+ CONSOLE_SCREEN_BUFFER_INFO data;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &data);
+ return data.dwSize.X;
+}
+
+int getCliRows()
+{
+ CONSOLE_SCREEN_BUFFER_INFO data;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &data);
+ return data.dwSize.Y;
+}
+
+#elif defined(Q_OS_UNIX)
+
+int getCliColumns()
+{
+ struct winsize w;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
+ return w.ws_col;
+}
+
+int getCliRows()
+{
+ struct winsize w;
+ ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
+ return w.ws_row;
+}
+
+#endif
+
+QStringList toAsciiTree(const AsciiTree& tree, const QList<bool>& indents, bool topLevel, bool lastNode)
+{
+ static const QString indentStr = " | ";
+ static const QString indentStrEmpty = " ";
+ static const QString branchStr = " +-";
+ static const QString branchStrLast = " `-";
+
+ QStringList lines;
+ QString line;
+
+ if (!topLevel)
+ {
+ // Draw indent before this node
+ foreach (bool indent, indents)
+ line += (indent ? indentStr : indentStrEmpty);
+
+ // Draw node prefix
+ line += (lastNode ? branchStrLast : branchStr);
+ }
+
+ // Draw label
+ line += tree.label;
+ lines << line;
+
+ if (tree.childs.size() == 0)
+ return lines;
+
+ // Draw childs
+ int i = 0;
+ int lastIdx = tree.childs.size() - 1;
+ QList<bool> subIndents = indents;
+
+ if (!topLevel)
+ subIndents << (lastNode ? false : true);
+
+ foreach (const AsciiTree& subTree, tree.childs)
+ {
+ lines += toAsciiTree(subTree, subIndents, false, i == lastIdx);
+ i++;
+ }
+
+ return lines;
+}
+
+QString toAsciiTree(const AsciiTree& tree)
+{
+ QList<bool> subIndents;
+ QStringList lines = toAsciiTree(tree, subIndents, true, true);
+ return lines.join("\n");
+}
+
+void initCliUtils()
+{
+ qRegisterMetaType<AsciiTree>();
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/cliutils.h b/SQLiteStudio3/sqlitestudiocli/cliutils.h new file mode 100644 index 0000000..2130a65 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/cliutils.h @@ -0,0 +1,22 @@ +#ifndef CLIUTILS_H
+#define CLIUTILS_H
+
+#include <QString>
+#include <QVariant>
+
+void initCliUtils();
+
+int getCliColumns();
+int getCliRows();
+
+struct AsciiTree
+{
+ QList<AsciiTree> childs;
+ QString label;
+};
+
+Q_DECLARE_METATYPE(AsciiTree)
+
+QString toAsciiTree(const AsciiTree& tree);
+
+#endif // CLIUTILS_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp new file mode 100644 index 0000000..3002ee5 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp @@ -0,0 +1,334 @@ +#include "clicommand.h" +#include "qio.h" +#include "cli_config.h" +#include "cliutils.h" +#include "common/utils.h" +#include "common/utils_sql.h" +#include "clicommandfactory.h" +#include "services/dbmanager.h" +#include "schemaresolver.h" +#include "cli.h" + +#include <QDir> + +CliCommand::CliCommand() +{ +} + +CliCommand::~CliCommand() +{ +} + +void CliCommand::setup(CLI *cli) +{ + this->cli = cli; + defineSyntax(); +} + +bool CliCommand::isAsyncExecution() const +{ + return false; +} + +bool CliCommand::parseArgs(const QStringList& args) +{ + bool res = syntax.parse(args); + + if (!res && !syntax.getErrorText().isEmpty()) + { + println(syntax.getErrorText()); + println(); + } + + return res; +} + +QString CliCommand::usage() const +{ + return syntax.getSyntaxDefinition(); +} + +QString CliCommand::usage(const QString& alias) const +{ + return syntax.getSyntaxDefinition(alias); +} + +QString CliCommand::getName() const +{ + return syntax.getName(); +} + +QStringList CliCommand::complete(const QStringList& args) +{ + syntax.parse(args.mid(0, args.size() - 1)); + + QStringList results; + results += syntax.getStrictArgumentCandidates(); + foreach (int id, syntax.getRegularArgumentCandidates()) + results += getCompletionValuesFor(id, args.last()); + + return results; +} + +QStringList CliCommand::aliases() const +{ + return syntax.getAliases(); +} + +void CliCommand::println(const QString &str) +{ + qOut << str << "\n"; + qOut.flush(); +} + +void CliCommand::print(const QString& str) +{ + qOut << str; + qOut.flush(); +} + +void CliCommand::printBox(const QString& str) +{ + int cols = getCliColumns(); + + QStringList lines = str.split("\n"); + println(".---------------------------"); + foreach (const QString& line, lines) + { + foreach (const QString& lineWithMargin, applyMargin(line, cols - 3)) // 2 for "| " and 1 for final new line character + println("| " + lineWithMargin); + } + + println("`---------------------------"); +} + +void CliCommand::printUsage() +{ + println(tr("Usage: %1%2").arg(CFG_CLI.Console.CommandPrefixChar.get()).arg(usage())); + println(""); +} + +QString CliCommand::getFilterAndFixDir(QDir& dir, const QString& path) +{ + if (path.isEmpty()) + return "*"; + + QString filter; + QDir tempDir; + tempDir.setPath(path); + if (tempDir.exists() && path.endsWith("/")) + { + dir.cd(path); + filter = "*"; + } + else if (tempDir.cdUp()) + { + dir.setPath(path); + dir.cdUp(); + filter = QFileInfo(path).fileName() + "*"; + } + else + { + filter = path; + } + return filter; +} + +QStringList CliCommand::getCompletionDbNames() +{ + QStringList results = DBLIST->getDbNames(); + results.sort(Qt::CaseInsensitive); + return results; +} + +QStringList CliCommand::getCompletionTables() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getTables(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getTables("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getTables(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionIndexes() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getIndexes(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getIndexes("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getIndexes(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionTriggers() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getTriggers(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getTriggers("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getTriggers(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionViews() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + Dialect dialect = Dialect::Sqlite3; + + SchemaResolver resolver(cli->getCurrentDb()); + resolver.setIgnoreSystemObjects(true); + results += wrapObjNamesIfNeeded(resolver.getViews(), dialect); + results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getViews("temp"), dialect)); + foreach (const QString& database, resolver.getDatabases()) + results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getViews(database), dialect)); + + return results; +} + +QStringList CliCommand::getCompletionDbNamesOrFiles(const QString& partialValue) +{ + QStringList results = getCompletionDbNames(); + results += getCompletionFiles(partialValue); + return results; +} + +QStringList CliCommand::getCompletionFiles(const QString& partialValue) +{ + QDir dir; + QString filter = getFilterAndFixDir(dir, partialValue); + QFileInfoList entries = dir.entryInfoList({filter}, QDir::Files, QDir::LocaleAware|QDir::Name); + + QStringList results; + QString name; + foreach (const QFileInfo& entry, entries) + { + name = entry.fileName(); + if (dir != QDir::current()) + name.prepend(dir.path() + "/"); + + results << name; + } + + return results; +} + +QStringList CliCommand::getCompletionDirs(const QString& partialValue) +{ + QDir dir; + QString filter = getFilterAndFixDir(dir, partialValue); + QFileInfoList entries = dir.entryInfoList({filter}, QDir::Dirs|QDir::NoDotAndDotDot, QDir::LocaleAware|QDir::Name); + + QStringList results; + QString name; + foreach (const QFileInfo& entry, entries) + { + name = entry.fileName(); + if (dir != QDir::current()) + name.prepend(dir.path() + "/"); + + results << name; + } + + return results; +} + +QStringList CliCommand::getCompletionDirsOrFiles(const QString& partialValue) +{ + QStringList results = getCompletionDirs(partialValue); + results += getCompletionFiles(partialValue); + return results; +} + +QStringList CliCommand::getCompletionInternalDbs() +{ + QStringList results; + if (!cli->getCurrentDb()) + return results; + + SchemaResolver resolver(cli->getCurrentDb()); + results += resolver.getDatabases().toList(); + results << "main" << "temp"; + results.sort(Qt::CaseInsensitive); + return results; +} + +QStringList CliCommand::getCompletionValuesFor(int id, const QString& partialValue) +{ + QStringList results; + if (id < 1000) // this base implementation is only for local enum values (>= 1000). + return results; + + switch (static_cast<ArgIds>(id)) + { + case CliCommand::DB_NAME: + results += getCompletionDbNames(); + break; + case CliCommand::DB_NAME_OR_FILE: + results += getCompletionDbNamesOrFiles(partialValue); + break; + case CliCommand::FILE_PATH: + results += getCompletionFiles(partialValue); + break; + case CliCommand::DIR_PATH: + results += getCompletionDirs(partialValue); + break; + case CliCommand::CMD_NAME: + results += CliCommandFactory::getCommandNames(); + break; + case CliCommand::DIR_OR_FILE: + results += getCompletionDirsOrFiles(partialValue); + break; + case CliCommand::INTRNAL_DB: + results += getCompletionInternalDbs(); + break; + case CliCommand::TABLE: + results += getCompletionTables(); + break; + case CliCommand::INDEX: + results += getCompletionIndexes(); + break; + case CliCommand::TRIGGER: + results += getCompletionTriggers(); + break; + case CliCommand::VIEW: + results += getCompletionViews(); + break; + case CliCommand::STRING: + break; + } + + return results; +} + +QString CliCommand::cmdName(const QString& cmd) +{ + return CFG_CLI.Console.CommandPrefixChar.get()+cmd; +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h new file mode 100644 index 0000000..4fc8618 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h @@ -0,0 +1,97 @@ +#ifndef CLICOMMAND_H +#define CLICOMMAND_H + +#include "clicommandsyntax.h" +#include <QStringList> +#include <QTextStream> +#include <QObject> +#include <QDir> + +class QFile; +class SQLiteStudio; +class DbManager; +class CLI; +class Config; + +class CliCommand : public QObject +{ + Q_OBJECT + + public: + CliCommand(); + virtual ~CliCommand(); + + void setup(CLI* cli); + + /** + * @brief execute + */ + virtual void execute() = 0; + + /** + * @brief Short help displayed in commands index. + * @return Single line of command description. + */ + virtual QString shortHelp() const = 0; + + /** + * @brief Full help is displayed when help for specific command was requested. + * @return Multi line, detailed description, including syntax. + */ + virtual QString fullHelp() const = 0; + + virtual bool isAsyncExecution() const; + + virtual void defineSyntax() = 0; + + QStringList aliases() const; + bool parseArgs(const QStringList& args); + QString usage() const; + QString usage(const QString& alias) const; + QString getName() const; + QStringList complete(const QStringList& args); + + protected: + enum ArgIds + { + DB_NAME = 1000, + DB_NAME_OR_FILE = 1001, + FILE_PATH = 1002, + DIR_OR_FILE = 1003, + DIR_PATH = 1004, + CMD_NAME = 1005, + INTRNAL_DB = 1006, + STRING = 1007, + TABLE = 1008, + INDEX = 1009, + TRIGGER = 1010, + VIEW = 1011 + }; + + static void println(const QString& str = ""); + static void print(const QString& str = ""); + static void printBox(const QString& str); + static QString cmdName(const QString& cmd); + + void printUsage(); + QString getFilterAndFixDir(QDir& dir, const QString& path); + QStringList getCompletionDbNames(); + QStringList getCompletionTables(); + QStringList getCompletionIndexes(); + QStringList getCompletionTriggers(); + QStringList getCompletionViews(); + QStringList getCompletionDbNamesOrFiles(const QString& partialValue); + QStringList getCompletionFiles(const QString& partialValue); + QStringList getCompletionDirs(const QString& partialValue); + QStringList getCompletionDirsOrFiles(const QString& partialValue); + QStringList getCompletionInternalDbs(); + virtual QStringList getCompletionValuesFor(int id, const QString& partialValue); + + CLI* cli = nullptr; + CliCommandSyntax syntax; + + signals: + void execComplete(); +}; + +#endif // CLICOMMAND_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp new file mode 100644 index 0000000..e3c14bb --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp @@ -0,0 +1,36 @@ +#include "clicommandadd.h" +#include "cli.h" +#include "services/dbmanager.h" + +void CliCommandAdd::execute() +{ + if (!DBLIST->addDb(syntax.getArgument(DB_NAME), syntax.getArgument(FILE_PATH))) + { + println(tr("Could not add database %1 to list.").arg(syntax.getArgument(FILE_PATH))); + return; + } + + cli->setCurrentDb(DBLIST->getByName(syntax.getArgument(DB_NAME))); + println(tr("Database added: %1").arg(cli->getCurrentDb()->getName())); +} + +QString CliCommandAdd::shortHelp() const +{ + return tr("adds new database to the list"); +} + +QString CliCommandAdd::fullHelp() const +{ + return tr( + "Adds given database pointed by <path> with given <name> to list the databases list. " + "The <name> is just a symbolic name that you can later refer to. Just pick any unique name. " + "For list of databases already on the list use %1 command." + ).arg(cmdName("dblist")); +} + +void CliCommandAdd::defineSyntax() +{ + syntax.setName("add"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax")); + syntax.addArgument(FILE_PATH, tr("path", "CLI command syntax")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h new file mode 100644 index 0000000..ae09ee1 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDADD_H +#define CLICOMMANDADD_H + +#include "clicommand.h" + +class CliCommandAdd : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDADD_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp new file mode 100644 index 0000000..0ca6cb6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp @@ -0,0 +1,34 @@ +#include "clicommandcd.h"
+#include <QCoreApplication>
+#include <QDir>
+
+void CliCommandCd::execute()
+{
+ QDir dir;
+ dir.cd(syntax.getArgument(DIR_PATH));
+ if (QDir::setCurrent(dir.absolutePath()))
+ println(tr("Changed directory to: %1").arg(QDir::currentPath()));
+ else
+ println(tr("Could not change directory to: %1").arg(QDir::currentPath()));
+}
+
+QString CliCommandCd::shortHelp() const
+{
+ return tr("changes current workind directory");
+}
+
+QString CliCommandCd::fullHelp() const
+{
+ return tr(
+ "Very similar command to 'cd' known from Unix systems and Windows. "
+ "It requires a <path> argument to be passed, therefore calling %1 will always cause a change of the directory. "
+ "To learn what's the current working directory use %2 command and to list contents of the current working directory "
+ "use %3 command."
+ );
+}
+
+void CliCommandCd::defineSyntax()
+{
+ syntax.setName("cd");
+ syntax.addArgument(DIR_PATH, tr("path", "CLI command syntax"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h new file mode 100644 index 0000000..4cfabc9 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDCD_H
+#define CLICOMMANDCD_H
+
+#include "clicommand.h"
+
+class CliCommandCd : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDCD_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp new file mode 100644 index 0000000..44fc72c --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp @@ -0,0 +1,51 @@ +#include "clicommandclose.h" +#include "cli.h" +#include "db/db.h" +#include "services/dbmanager.h" + +void CliCommandClose::execute() +{ + if (!syntax.isArgumentSet(DB_NAME) && !cli->getCurrentDb()) + { + println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.") + .arg(cmdName("close")).arg(cmdName("use")).arg(cmdName("close"))); + return; + } + + if (syntax.isArgumentSet(DB_NAME)) + { + Db* db = DBLIST->getByName(syntax.getArgument(DB_NAME)); + if (db) + { + db->close(); + println(tr("Connection to database %1 closed.").arg(db->getName())); + } + else + println(tr("No such database: %1. Use %2 to see list of known databases.").arg(syntax.getArgument(DB_NAME)).arg(cmdName("dblist"))); + } + else if (cli->getCurrentDb()) + { + cli->getCurrentDb()->close(); + println(tr("Connection to database %1 closed.").arg(cli->getCurrentDb()->getName())); + } +} + +QString CliCommandClose::shortHelp() const +{ + return tr("closes given (or current) database"); +} + +QString CliCommandClose::fullHelp() const +{ + return tr( + "Closes database connection. If the database was already closed, nothing happens. " + "If <name> is provided, it should be name of the database to close (as printed by %1 command). " + "The the <name> is not provided, then current working database is closed (see help for %2 for details)." + ).arg(cmdName("dblist")).arg(cmdName("use")); +} + +void CliCommandClose::defineSyntax() +{ + syntax.setName("close"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"), false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h new file mode 100644 index 0000000..04fbbeb --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDCLOSE_H +#define CLICOMMANDCLOSE_H + +#include "clicommand.h" + +class CliCommandClose : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDCLOSE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp new file mode 100644 index 0000000..cbc16b6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp @@ -0,0 +1,86 @@ +#include "clicommanddblist.h" +#include "cli.h" +#include "services/dbmanager.h" +#include "common/unused.h" +#include "common/utils.h" +#include <QList> + +void CliCommandDbList::execute() +{ + if (!cli->getCurrentDb()) + { + println(tr("No current working database defined.")); + return; + } + + QString currentName = cli->getCurrentDb()->getName(); + + println(tr("Databases:")); + QList<Db*> dbList = DBLIST->getDbList(); + QString path; + QString msg; + + int maxNameLength = tr("Name", "CLI db name column").length(); + int lgt = 0; + foreach (Db* db, dbList) + { + lgt = db->getName().length() + 1; + maxNameLength = qMax(maxNameLength, lgt); + } + + int connStateLength = qMax(tr("Open", "CLI connection state column").length(), tr("Closed", "CLI connection state column").length()); + connStateLength = qMax(connStateLength, tr("Connection", "CLI connection state column").length()); + + msg = pad(tr("Name", "CLI db name column"), maxNameLength, ' '); + msg += "|"; + msg += pad(tr("Connection", "CLI connection state column"), connStateLength, ' '); + msg += "|"; + msg += tr("Database file path"); + println(msg); + + msg = QString("-").repeated(maxNameLength); + msg += "+"; + msg += QString("-").repeated(connStateLength); + msg += "+"; + msg += QString("-").repeated(tr("Database file path").length() + 1); + println(msg); + + QString name; + foreach (Db* db, dbList) + { + bool open = db->isOpen(); + path = db->getPath(); + name = db->getName(); + if (name == currentName) + name.prepend("*"); + else + name.prepend(" "); + + msg = pad(name, maxNameLength, ' '); + msg += "|"; + msg += pad((open ? tr("Open") : tr("Closed")), connStateLength, ' '); + msg += "|"; + msg += path; + println(msg); + } +} + +QString CliCommandDbList::shortHelp() const +{ + return tr("prints list of registered databases"); +} + +QString CliCommandDbList::fullHelp() const +{ + return tr( + "Prints list of databases registered in the SQLiteStudio. Each database on the list can be in open or closed state " + "and .dblist tells you that. The current working database (aka default database) is also marked on the list with '*' at the start of its name. " + "See help for %1 command to learn about the default database." + ).arg(cmdName("use")); +} + +void CliCommandDbList::defineSyntax() +{ + syntax.setName("dblist"); + syntax.addAlias("databases"); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h new file mode 100644 index 0000000..ecbaa6f --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDDBLIST_H +#define CLICOMMANDDBLIST_H + +#include "clicommand.h" + +class CliCommandDbList : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDDBLIST_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp new file mode 100644 index 0000000..4865e23 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp @@ -0,0 +1,26 @@ +#include "clicommanddesc.h"
+
+CliCommandDesc::CliCommandDesc()
+{
+}
+
+void CliCommandDesc::execute()
+{
+
+}
+
+QString CliCommandDesc::shortHelp() const
+{
+ return tr("shows details about the table");
+}
+
+QString CliCommandDesc::fullHelp() const
+{
+ return QString();
+}
+
+void CliCommandDesc::defineSyntax()
+{
+ syntax.setName("desc");
+ syntax.addArgument(TABLE, tr("table"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h new file mode 100644 index 0000000..ee52c30 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h @@ -0,0 +1,16 @@ +#ifndef CLICOMMANDDESC_H
+#define CLICOMMANDDESC_H
+
+#include "clicommand.h"
+
+class CliCommandDesc : public CliCommand
+{
+ public:
+ CliCommandDesc();
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDDESC_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp new file mode 100644 index 0000000..c61f503 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp @@ -0,0 +1,50 @@ +#include "clicommanddir.h"
+#include <QDir>
+
+void CliCommandDir::execute()
+{
+ QDir dir;
+ QFileInfoList entries;
+
+ if (syntax.isArgumentSet(DIR_OR_FILE))
+ {
+ QString filter = getFilterAndFixDir(dir, syntax.getArgument(DIR_OR_FILE));
+ entries = dir.entryInfoList({filter}, QDir::AllEntries|QDir::NoDotAndDotDot, QDir::DirsFirst|QDir::LocaleAware|QDir::Name);
+ }
+ else
+ entries = dir.entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot, QDir::DirsFirst|QDir::LocaleAware|QDir::Name);
+
+ QString name;
+ foreach (const QFileInfo& entry, entries)
+ {
+ name = entry.fileName();
+ if (entry.isDir())
+ name += "/";
+
+ if (dir != QDir::current())
+ name.prepend(dir.path()+"/");
+
+ println(name);
+ }
+}
+
+QString CliCommandDir::shortHelp() const
+{
+ return tr("lists directories and files in current working directory");
+}
+
+QString CliCommandDir::fullHelp() const
+{
+ return tr(
+ "This is very similar to 'dir' command known from Windows and 'ls' command from Unix systems.\n"
+ "\n"
+ "You can pass <pattern> with wildcard characters to filter output."
+ );
+}
+
+void CliCommandDir::defineSyntax()
+{
+ syntax.setName("dir");
+ syntax.addAlias("ls");
+ syntax.addArgument(DIR_OR_FILE, tr("pattern"), false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h new file mode 100644 index 0000000..8aa5ccc --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDDIR_H
+#define CLICOMMANDDIR_H
+
+#include "clicommand.h"
+
+class CliCommandDir : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDDIR_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp new file mode 100644 index 0000000..2c45e1d --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp @@ -0,0 +1,26 @@ +#include "clicommandexit.h" +#include "cli.h" +#include "common/unused.h" + +void CliCommandExit::execute() +{ + cli->exit(); +} + +QString CliCommandExit::shortHelp() const +{ + return tr("quits the application"); +} + +QString CliCommandExit::fullHelp() const +{ + return tr( + "Quits the application. Settings are stored in configuration file and will be restored on next startup." + ); +} + +void CliCommandExit::defineSyntax() +{ + syntax.setName("exit"); + syntax.addAlias("quit"); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h new file mode 100644 index 0000000..dc061bb --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDEXIT_H +#define CLICOMMANDEXIT_H + +#include "clicommand.h" + +class CliCommandExit : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDEXIT_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp new file mode 100644 index 0000000..742709b --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp @@ -0,0 +1,85 @@ +#include "clicommandfactory.h" +#include "cli.h" +#include "clicommandadd.h" +#include "clicommandremove.h" +#include "clicommandexit.h" +#include "clicommanddblist.h" +#include "clicommanduse.h" +#include "clicommandopen.h" +#include "clicommandclose.h" +#include "clicommandsql.h" +#include "clicommandhelp.h" +#include "clicommandtables.h" +#include "clicommandmode.h" +#include "clicommandnullvalue.h" +#include "clicommandhistory.h" +#include "clicommanddir.h" +#include "clicommandpwd.h" +#include "clicommandcd.h" +#include "clicommandtree.h" +#include "clicommanddesc.h" +#include <QDebug> + +QHash<QString,CliCommandFactory::CliCommandCreatorFunc> CliCommandFactory::mapping; + +#define REGISTER_CMD(Cmd) registerCommand([]() -> CliCommand* {return new Cmd();}) + +void CliCommandFactory::init() +{ + REGISTER_CMD(CliCommandAdd); + REGISTER_CMD(CliCommandRemove); + REGISTER_CMD(CliCommandExit); + REGISTER_CMD(CliCommandDbList); + REGISTER_CMD(CliCommandUse); + REGISTER_CMD(CliCommandOpen); + REGISTER_CMD(CliCommandClose); + REGISTER_CMD(CliCommandSql); + REGISTER_CMD(CliCommandHelp); + REGISTER_CMD(CliCommandTables); + REGISTER_CMD(CliCommandMode); + REGISTER_CMD(CliCommandNullValue); + REGISTER_CMD(CliCommandHistory); + REGISTER_CMD(CliCommandDir); + REGISTER_CMD(CliCommandPwd); + REGISTER_CMD(CliCommandCd); + REGISTER_CMD(CliCommandTree); + REGISTER_CMD(CliCommandDesc); +} + +CliCommand *CliCommandFactory::getCommand(const QString &cmdName) +{ + if (!mapping.contains(cmdName)) + return nullptr; + + return mapping[cmdName](); +} + +QHash<QString,CliCommand*> CliCommandFactory::getAllCommands() +{ + QHash<QString,CliCommand*> results; + QHashIterator<QString,CliCommandFactory::CliCommandCreatorFunc> it(mapping); + while (it.hasNext()) + { + it.next(); + results[it.key()] = it.value()(); + } + + return results; +} + +QStringList CliCommandFactory::getCommandNames() +{ + return mapping.keys(); +} + +void CliCommandFactory::registerCommand(CliCommandCreatorFunc func) +{ + CliCommand* cmd = func(); + cmd->defineSyntax(); + + mapping[cmd->getName()] = func; + foreach (const QString& alias, cmd->aliases()) + mapping[alias] = func; + + delete cmd; +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h new file mode 100644 index 0000000..4329463 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h @@ -0,0 +1,25 @@ +#ifndef CLICOMMANDFACTORY_H +#define CLICOMMANDFACTORY_H + +#include <QString> +#include <QHash> + +class CliCommand; + +class CliCommandFactory +{ + public: + static void init(); + static CliCommand* getCommand(const QString& cmdName); + static QHash<QString,CliCommand*> getAllCommands(); + static QStringList getCommandNames(); + + private: + typedef CliCommand* (*CliCommandCreatorFunc)(); + + static void registerCommand(CliCommandCreatorFunc func); + + static QHash<QString,CliCommandCreatorFunc> mapping; +}; + +#endif // CLICOMMANDFACTORY_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp new file mode 100644 index 0000000..7a957c3 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp @@ -0,0 +1,86 @@ +#include "clicommandhelp.h"
+#include "clicommandfactory.h"
+#include "common/utils.h"
+#include "cli_config.h"
+
+void CliCommandHelp::execute()
+{
+ if (syntax.isArgumentSet(CMD_NAME))
+ printHelp(syntax.getArgument(CMD_NAME));
+ else
+ printHelp();
+}
+
+QString CliCommandHelp::shortHelp() const
+{
+ return tr("shows this help message");
+}
+
+QString CliCommandHelp::fullHelp() const
+{
+ return tr(
+ "Use %1 to learn about certain commands supported by the command line interface (CLI) of the SQLiteStudio.\n"
+ "To see list of supported commands, type %2 without any arguments.\n\n"
+ "When passing <command> name, you can skip special prefix character ('%3').\n\n"
+ "You can always execute any command with exactly single '--help' option to see help for that command. "
+ "It's an alternative for typing: %1 <command>."
+ ).arg(cmdName("help")).arg(cmdName("help")).arg(CFG_CLI.Console.CommandPrefixChar.get()).arg(cmdName("help"));
+}
+
+void CliCommandHelp::defineSyntax()
+{
+ syntax.setName("help");
+ syntax.addArgument(CMD_NAME, tr("command", "CLI command syntax"), false);
+}
+
+void CliCommandHelp::printHelp(const QString& cmd)
+{
+ QString cmdStr = cmd.startsWith(".") ? cmd.mid(1) : cmd;
+ CliCommand* command = CliCommandFactory::getCommand(cmdStr);
+ if (!command)
+ {
+ println(tr("No such command: %1").arg(cmd));
+ println(tr("Type '%1' for list of available commands.").arg(cmdName("help")));
+ println("");
+ return;
+ }
+ command->defineSyntax();
+ QStringList aliases = command->aliases();
+ QString prefix = CFG_CLI.Console.CommandPrefixChar.get();
+
+ QString msg;
+ msg += tr("Usage: %1%2").arg(prefix).arg(command->usage(cmdStr));
+ msg += "\n";
+ if (aliases.size() > 0)
+ {
+ if (aliases.contains(cmdStr))
+ {
+ aliases.removeOne(cmdStr);
+ aliases << command->getName();
+ }
+
+ msg += tr("Aliases: %1").arg(prefix + aliases.join(", " + prefix));
+ msg += "\n";
+ }
+ msg += "\n";
+ msg += command->fullHelp();
+ delete command;
+
+ printBox(msg);
+}
+
+void CliCommandHelp::printHelp()
+{
+ QHash<QString, CliCommand*> allCommands = CliCommandFactory::getAllCommands();
+ QStringList names = allCommands.keys();
+ int width = longest(names).size();
+
+ names.sort();
+ QStringList msgList;
+ foreach (const QString& cmd, names)
+ {
+ msgList << (CFG_CLI.Console.CommandPrefixChar.get() + pad(cmd, width, ' ') + " - " + allCommands[cmd]->shortHelp());
+ delete allCommands[cmd];
+ }
+ printBox(msgList.join("\n"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h new file mode 100644 index 0000000..c12c588 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h @@ -0,0 +1,19 @@ +#ifndef CLICOMMANDHELP_H
+#define CLICOMMANDHELP_H
+
+#include "clicommand.h"
+
+class CliCommandHelp : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ void printHelp(const QString& cmd);
+ void printHelp();
+};
+
+#endif // CLICOMMANDHELP_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp new file mode 100644 index 0000000..fd2a494 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp @@ -0,0 +1,81 @@ +#include "clicommandhistory.h"
+#include "cli.h"
+#include "common/utils.h"
+#include "cliutils.h"
+#include "services/config.h"
+
+void CliCommandHistory::execute()
+{
+ if (syntax.isOptionSet(OPER_TYPE))
+ {
+ clear();
+ return;
+ }
+
+ if (syntax.isOptionSet(HIST_LIMIT))
+ {
+ setMax(syntax.getOptionValue(HIST_LIMIT));
+ return;
+ }
+
+ if (syntax.isOptionSet(SHOW_LIMIT))
+ {
+ println(tr("Current history limit is set to: %1").arg(CFG_CORE.Console.HistorySize.get()));
+ return;
+ }
+
+ int cols = getCliColumns();
+ QString hline = pad("", cols, '-');
+ foreach (const QString& line, cli->getHistory())
+ {
+ print(hline);
+ println(line);
+ }
+ println(hline);
+}
+
+QString CliCommandHistory::shortHelp() const
+{
+ return tr("prints history or erases it");
+}
+
+QString CliCommandHistory::fullHelp() const
+{
+ return tr(
+ "When no argument was passed, this command prints command line history. "
+ "Every history entry is separated with a horizontal line, so multiline entries are easier to read.\n"
+ "\n"
+ "When the -c or --clear option is passed, then the history gets erased.\n"
+ "When the -l or --limit option is passed, it sets the new history entries limit. It requires an additional argument"
+ "saying how many entries do you want the history to be limited to.\n"
+ "Use -ql or --querylimit option to see the current limit value."
+ );
+}
+
+void CliCommandHistory::defineSyntax()
+{
+ syntax.setName("history");
+ syntax.addOption(OPER_TYPE, "c", "clear");
+ syntax.addOptionWithArg(HIST_LIMIT, "l", "limit", tr("number"));
+ syntax.addOption(SHOW_LIMIT, "ql", "querylimit");
+}
+
+void CliCommandHistory::clear()
+{
+ cli->clearHistory();
+ println(tr("Console history erased."));
+}
+
+void CliCommandHistory::setMax(const QString& arg)
+{
+ bool ok;
+ int max = arg.toInt(&ok);
+ if (!ok)
+ {
+ println(tr("Invalid number: %1").arg(arg));
+ return;
+ }
+ CFG_CORE.Console.HistorySize.set(max);
+ cli->applyHistoryLimit();
+ println(tr("History limit set to %1").arg(max));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h new file mode 100644 index 0000000..f2e07d0 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h @@ -0,0 +1,26 @@ +#ifndef CLICOMMANDHISTORY_H
+#define CLICOMMANDHISTORY_H
+
+#include "clicommand.h"
+
+class CliCommandHistory : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum ArgIds
+ {
+ OPER_TYPE,
+ HIST_LIMIT,
+ SHOW_LIMIT
+ };
+
+ void clear();
+ void setMax(const QString& arg);
+};
+
+#endif // CLICOMMANDHISTORY_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp new file mode 100644 index 0000000..b258045 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp @@ -0,0 +1,65 @@ +#include "clicommandmode.h"
+#include "common/unused.h"
+#include "cli_config.h"
+
+void CliCommandMode::execute()
+{
+ if (!syntax.isArgumentSet(MODE))
+ {
+ println(tr("Current results printing mode: %1").arg(CliResultsDisplay::mode(CFG_CLI.Console.ResultsDisplayMode.get())));
+ return;
+ }
+
+ CliResultsDisplay::Mode mode = CliResultsDisplay::mode(syntax.getArgument(MODE).toUpper());
+ if (syntax.getArgument(MODE).toUpper() != CliResultsDisplay::mode(mode))
+ {
+ println(tr("Invalid results printing mode: %1").arg(syntax.getArgument(MODE).toUpper()));
+ return;
+ }
+
+ CFG_CLI.Console.ResultsDisplayMode.set(mode);
+ println(tr("New results printing mode: %1").arg(CliResultsDisplay::mode(mode)));
+}
+
+QString CliCommandMode::shortHelp() const
+{
+ return tr("tells or changes the query results format");
+}
+
+QString CliCommandMode::fullHelp() const
+{
+ return tr(
+ "When called without argument, tells the current output format for a query results. "
+ "When the <mode> is passed, the mode is changed to the given one. "
+ "Supported modes are:\n"
+ "- CLASSIC - columns are separated by a comma, not aligned,\n"
+ "- FIXED - columns have equal and fixed width, they always fit into terminal window width, but the data in columns can be cut off,\n"
+ "- COLUMNS - like FIXED, but smarter (do not use with huge result sets, see details below),\n"
+ "- ROW - each column from the row is displayed in new line, so the full data is displayed.\n"
+ "\n"
+ "The CLASSIC mode is recommended if you want to see all the data, but you don't want to waste lines for each column. "
+ "Each row will display full data for every column, but this also means, that columns will not be aligned to each other in next rows. "
+ "The CLASSIC mode also doesn't respect the width of your terminal (console) window, so if values in columns are wider than the window, "
+ "the row will be continued in next lines.\n"
+ "\n"
+ "The FIXED mode is recommended if you want a readable output and you don't care about long data values. "
+ "Columns will be aligned, making the output a nice table. The width of columns is calculated from width of the console window "
+ "and a number of columns.\n"
+ "\n"
+ "The COLUMNS mode is similar to FIXED mode, except it tries to be smart and make columns with shorter values more thin, "
+ "while columns with longer values get more space. First to shrink are columns with longest headers (so the header names are to be "
+ "cut off as first), then columns with the longest values are shrinked, up to the moment when all columns fit into terminal window.\n"
+ "ATTENTION! The COLUMNS mode reads all the results from the query at once in order to evaluate column widhts, therefore it is dangerous "
+ "to use this mode when working with huge result sets. Keep in mind that this mode will load entire result set into memory.\n"
+ "\n"
+ "The ROW mode is recommended if you need to see whole values and you don't expect many rows to be displayed, because this mode "
+ "displays a line of output per each column, so you'll get 10 lines for single row with 10 columns, then if you have 10 of such rows, "
+ "you will get 100 lines of output (+1 extra line per each row, to separate rows from each other)."
+ );
+}
+
+void CliCommandMode::defineSyntax()
+{
+ syntax.setName("mode");
+ syntax.addStrictArgument(MODE, {"classic", "fixed", "columns", "row"}, false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h new file mode 100644 index 0000000..2b087d3 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h @@ -0,0 +1,21 @@ +#ifndef CLICOMMANDMODE_H
+#define CLICOMMANDMODE_H
+
+#include "clicommand.h"
+
+class CliCommandMode : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum ArgIgs
+ {
+ MODE
+ };
+};
+
+#endif // CLICOMMANDMODE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp new file mode 100644 index 0000000..9616d9e --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp @@ -0,0 +1,32 @@ +#include "clicommandnullvalue.h"
+#include "cli_config.h"
+
+void CliCommandNullValue::execute()
+{
+ if (syntax.isArgumentSet(STRING))
+ CFG_CLI.Console.NullValue.set(syntax.getArgument(STRING));
+
+ println(tr("Current NULL representation string: %1").arg(CFG_CLI.Console.NullValue.get()));
+ return;
+}
+
+QString CliCommandNullValue::shortHelp() const
+{
+ return tr("tells or changes the NULL representation string");
+}
+
+QString CliCommandNullValue::fullHelp() const
+{
+ return tr(
+ "If no argument was passed, it tells what's the current NULL value representation "
+ "(that is - what is printed in place of NULL values in query results). "
+ "If the argument is given, then it's used as a new string to be used for NULL representation."
+ );
+}
+
+void CliCommandNullValue::defineSyntax()
+{
+ syntax.setName("null");
+ syntax.addAlias("nullvalue");
+ syntax.addArgument(STRING, QObject::tr("string", "CLI command syntax"), false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h new file mode 100644 index 0000000..4ac4699 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDNULLVALUE_H
+#define CLICOMMANDNULLVALUE_H
+
+#include "clicommand.h"
+
+class CliCommandNullValue : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDNULLVALUE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp new file mode 100644 index 0000000..fef1737 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp @@ -0,0 +1,84 @@ +#include "clicommandopen.h" +#include "cli.h" +#include "services/dbmanager.h" +#include <QFile> +#include <QDir> +#include <QDebug> + +void CliCommandOpen::execute() +{ + if (!syntax.isArgumentSet(DB_NAME_OR_FILE) && !cli->getCurrentDb()) + { + println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.") + .arg(cmdName("open")).arg(cmdName("use")).arg(cmdName("open"))); + return; + } + + Db* db = nullptr; + if (syntax.isArgumentSet(DB_NAME_OR_FILE)) + { + QString arg = syntax.getArgument(DB_NAME_OR_FILE); + db = DBLIST->getByName(arg); + if (!db) + { + if (QFile::exists(arg)) + { + QString newName = DbManager::generateDbName(arg); + if (!DBLIST->addDb(newName, arg, false)) + { + println(tr("Could not add database %1 to list.").arg(arg)); + return; + } + db = DBLIST->getByName(arg); + Q_ASSERT(db != nullptr); + } + else + { + println(tr("File %1 doesn't exist in %2. Cannot open inexisting database with %3 command. " + "To create a new database, use %4 command.").arg(arg).arg(QDir::currentPath()) + .arg(cmdName("open")).arg(cmdName("add"))); + return; + } + } + } + else + { + db = cli->getCurrentDb(); + if (!db) + { + qCritical() << "Default database is not in the list!"; + return ; + } + } + + if (!db->open()) + { + println(db->getErrorText()); + return; + } + + cli->setCurrentDb(db); + println(tr("Database %1 has been open and set as the current working database.").arg(db->getName())); +} + +QString CliCommandOpen::shortHelp() const +{ + return tr("opens database connection"); +} + +QString CliCommandOpen::fullHelp() const +{ + return tr( + "Opens connection to the database. If no additional argument was passed, then the connection is open to the " + "current default database (see help for %1 for details). However if an argument was passed, it can be either " + "<name> of the registered database to open, or it can be <path> to the database file to open. " + "In the second case, the <path> gets registered on the list with a generated name, but only for the period " + "of current application session. After restarting application such database is not restored on the list." + ).arg(cmdName("use")); +} + +void CliCommandOpen::defineSyntax() +{ + syntax.setName("open"); + syntax.addAlternatedArgument(DB_NAME_OR_FILE, {tr("name", "CLI command syntax"), tr("path", "CLI command syntax")}, false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h new file mode 100644 index 0000000..e66f574 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDOPEN_H +#define CLICOMMANDOPEN_H + +#include "clicommand.h" + +class CliCommandOpen : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDOPEN_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp new file mode 100644 index 0000000..f96cae4 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp @@ -0,0 +1,28 @@ +#include "clicommandpwd.h"
+#include "common/unused.h"
+#include <QDir>
+
+void CliCommandPwd::execute()
+{
+ QDir dir;
+ println(dir.absolutePath());
+}
+
+QString CliCommandPwd::shortHelp() const
+{
+ return tr("prints the current working directory");
+}
+
+QString CliCommandPwd::fullHelp() const
+{
+ return tr(
+ "This is the same as 'pwd' command on Unix systems and 'cd' command without arguments on Windows. "
+ "It prints current working directory. You can change the current working directory with %1 command "
+ "and you can also list contents of the current working directory with %2 command."
+ ).arg(cmdName("cd")).arg(cmdName("dir"));
+}
+
+void CliCommandPwd::defineSyntax()
+{
+ syntax.setName("pwd");
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h new file mode 100644 index 0000000..3568ce1 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDPWD_H
+#define CLICOMMANDPWD_H
+
+#include "clicommand.h"
+
+class CliCommandPwd : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDPWD_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp new file mode 100644 index 0000000..6474a78 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp @@ -0,0 +1,51 @@ +#include "clicommandremove.h" +#include "cli.h" +#include "services/dbmanager.h" +#include "db/db.h" + +void CliCommandRemove::execute() +{ + QString dbName = syntax.getArgument(DB_NAME); + Db* db = DBLIST->getByName(dbName); + if (!db) + { + println(tr("No such database: %1").arg(dbName)); + return; + } + + bool isCurrent = cli->getCurrentDb() == db; + QString name = db->getName(); + + DBLIST->removeDb(db); + println(tr("Database removed: %1").arg(name)); + + QList<Db*> dblist = DBLIST->getDbList(); + if (isCurrent && dblist.size() > 0) + { + cli->setCurrentDb(dblist[0]); + println(tr("New current database set:")); + println(cli->getCurrentDb()->getName()); + } + else + cli->setCurrentDb(nullptr); +} + +QString CliCommandRemove::shortHelp() const +{ + return tr("removes database from the list"); +} + +QString CliCommandRemove::fullHelp() const +{ + return tr( + "Removes <name> database from the list of registered databases. " + "If the database was not on the list (see %1 command), then error message is printed " + "and nothing more happens." + ).arg(cmdName("dblist")); +} + +void CliCommandRemove::defineSyntax() +{ + syntax.setName("remove"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax")); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h new file mode 100644 index 0000000..f4b5402 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDREMOVE_H +#define CLICOMMANDREMOVE_H + +#include "clicommand.h" + +class CliCommandRemove : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDREMOVE_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp new file mode 100644 index 0000000..3f2c4b3 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp @@ -0,0 +1,506 @@ +#include "clicommandsql.h" +#include "cli.h" +#include "parser/ast/sqliteselect.h" +#include "parser/parser.h" +#include "parser/parsererror.h" +#include "db/queryexecutor.h" +#include "qio.h" +#include "common/unused.h" +#include "cli_config.h" +#include "cliutils.h" +#include <QList> +#include <QDebug> + +void CliCommandSql::execute() +{ + if (!cli->getCurrentDb()) + { + println(tr("No working database is set.\n" + "Call %1 command to set working database.\n" + "Call %2 to see list of all databases.") + .arg(cmdName("use")).arg(cmdName("dblist"))); + + return; + } + + Db* db = cli->getCurrentDb(); + if (!db || !db->isOpen()) + { + println(tr("Database is not open.")); + return; + } + + // Executor deletes itself later when called with lambda. + QueryExecutor *executor = new QueryExecutor(db, syntax.getArgument(STRING)); + connect(executor, SIGNAL(executionFinished(SqlQueryPtr)), this, SIGNAL(execComplete())); + connect(executor, SIGNAL(executionFailed(int,QString)), this, SLOT(executionFailed(int,QString))); + connect(executor, SIGNAL(executionFailed(int,QString)), this, SIGNAL(execComplete())); + + executor->exec([=](SqlQueryPtr results) + { + if (results->isError()) + return; // should not happen, since results handler function is called only for successful executions + + switch (CFG_CLI.Console.ResultsDisplayMode.get()) + { + case CliResultsDisplay::FIXED: + printResultsFixed(executor, results); + break; + case CliResultsDisplay::COLUMNS: + printResultsColumns(executor, results); + break; + case CliResultsDisplay::ROW: + printResultsRowByRow(executor, results); + break; + default: + printResultsClassic(executor, results); + break; + } + }); +} + +QString CliCommandSql::shortHelp() const +{ + return tr("executes SQL query"); +} + +QString CliCommandSql::fullHelp() const +{ + return tr( + "This command is executed every time you enter SQL query in command prompt. " + "It executes the query on the current working database (see help for %1 for details). " + "There's no sense in executing this command explicitly. Instead just type the SQL query " + "in the command prompt, without any command prefixed." + ).arg(cmdName("use")); +} + +bool CliCommandSql::isAsyncExecution() const +{ + return true; +} + +void CliCommandSql::defineSyntax() +{ + syntax.setName("query"); + syntax.addArgument(STRING, tr("sql", "CLI command syntax")); + syntax.setStrictArgumentCount(false); +} + +void CliCommandSql::printResultsClassic(QueryExecutor* executor, SqlQueryPtr results) +{ + int metaColumns = executor->getMetaColumnCount(); + int resultColumnCount = executor->getResultColumns().size(); + + // Columns + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + qOut << resCol->displayName << "|"; + + qOut << "\n"; + + // Data + SqlResultsRowPtr row; + QList<QVariant> values; + int i; + while (results->hasNext()) + { + row = results->next(); + i = 0; + values = row->valueList().mid(metaColumns); + foreach (QVariant value, values) + { + qOut << getValueString(value); + if ((i + 1) < resultColumnCount) + qOut << "|"; + + i++; + } + + qOut << "\n"; + } + qOut.flush(); +} + +void CliCommandSql::printResultsFixed(QueryExecutor* executor, SqlQueryPtr results) +{ + QList<QueryExecutor::ResultColumnPtr> resultColumns = executor->getResultColumns(); + int resultColumnsCount = resultColumns.size(); + int metaColumns = executor->getMetaColumnCount(); + int termCols = getCliColumns(); + int baseColWidth = termCols / resultColumns.size() - 1; + + if (resultColumnsCount == 0) + return; + + if ((resultColumnsCount * 2 - 1) > termCols) + { + println(tr("Too many columns to display in %1 mode.").arg("FIXED")); + return; + } + + int width; + QList<int> widths; + for (int i = 0; i < resultColumnsCount; i++) + { + width = baseColWidth; + if (i+1 == resultColumnsCount) + width += (termCols - resultColumnsCount * (baseColWidth + 1) + 1); + + widths << width; + } + + // Columns + QStringList columns; + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + columns << resCol->displayName; + + printColumnHeader(widths, columns); + + // Data + while (results->hasNext()) + printColumnDataRow(widths, results->next(), metaColumns); + + qOut.flush(); +} + +void CliCommandSql::printResultsColumns(QueryExecutor* executor, SqlQueryPtr results) +{ + // Check if we don't have more columns than we can display + QList<QueryExecutor::ResultColumnPtr> resultColumns = executor->getResultColumns(); + int termCols = getCliColumns(); + int resultColumnsCount = resultColumns.size(); + QStringList headerNames; + if (resultColumnsCount == 0) + return; + + // Every column requires at least 1 character width + column separators between them + if ((resultColumnsCount * 2 - 1) > termCols) + { + println(tr("Too many columns to display in %1 mode.").arg("COLUMNS")); + return; + } + + // Preload data (we will calculate column widths basing on real values) + QList<SqlResultsRowPtr> allRows = results->getAll(); + int metaColumns = executor->getMetaColumnCount(); + + // Get widths of each column in every data row, remember the longest ones + QList<SortedColumnWidth*> columnWidths; + SortedColumnWidth* colWidth = nullptr; + foreach (const QueryExecutor::ResultColumnPtr& resCol, resultColumns) + { + colWidth = new SortedColumnWidth(); + colWidth->setHeaderWidth(resCol->displayName.length()); + columnWidths << colWidth; + headerNames << resCol->displayName; + } + + int dataLength; + foreach (const SqlResultsRowPtr& row, allRows) + { + for (int i = 0; i < resultColumnsCount; i++) + { + dataLength = row->value(metaColumns + i).toString().length(); + columnWidths[i]->setMinDataWidth(dataLength); + } + } + + // Calculate width as it would be required to display entire rows + int totalWidth = 0; + foreach (colWidth, columnWidths) + totalWidth += colWidth->getWidth(); + + totalWidth += (resultColumnsCount - 1); // column separators + + // Adjust column sizes to fit into terminal window + if (totalWidth < termCols) + { + // Expanding last column + int diff = termCols - totalWidth; + columnWidths.last()->incrWidth(diff); + } + else if (totalWidth > termCols) + { + // Shrinking columns + shrinkColumns(columnWidths, termCols, resultColumnsCount, totalWidth); + } + + // Printing + QList<int> finalWidths; + foreach (colWidth, columnWidths) + finalWidths << colWidth->getWidth(); + + printColumnHeader(finalWidths, headerNames); + + foreach (SqlResultsRowPtr row, allRows) + printColumnDataRow(finalWidths, row, metaColumns); + + qOut.flush(); +} + +void CliCommandSql::printResultsRowByRow(QueryExecutor* executor, SqlQueryPtr results) +{ + // Columns + int metaColumns = executor->getMetaColumnCount(); + int colWidth = 0; + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + { + if (resCol->displayName.length() > colWidth) + colWidth = resCol->displayName.length(); + } + + QStringList columns; + foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns()) + columns << pad(resCol->displayName, -colWidth, ' '); + + // Data + static const QString rowCntTemplate = tr("Row %1"); + int termWidth = getCliColumns(); + QString rowCntString; + int i; + int rowCnt = 1; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + i = 0; + rowCntString = " " + rowCntTemplate.arg(rowCnt) + " "; + qOut << center(rowCntString, termWidth - 1, '-') << "\n"; + foreach (QVariant value, row->valueList().mid(metaColumns)) + { + qOut << columns[i] + ": " + getValueString(value) << "\n"; + i++; + } + rowCnt++; + } + qOut.flush(); +} + +void CliCommandSql::shrinkColumns(QList<CliCommandSql::SortedColumnWidth*>& columnWidths, int termCols, int resultColumnsCount, int totalWidth) +{ + // This implements quite a smart shrinking algorithm: + // All columns are sorted by their current total width (data and header width) + // and then longest headers are shrinked first, then if headers are no longer a problem, + // but the data is - then longest data values are shrinked. + // If either the hader or the data value is huge (way more than fits into terminal), + // then such column is shrinked in one step to a reasonable width, so it can be later + // shrinked more precisely. + int maxSingleColumnWidth = (termCols - (resultColumnsCount - 1) * 2 ); + bool shrinkData; + int previousTotalWidth = -1; + while (totalWidth > termCols && totalWidth != previousTotalWidth) + { + shrinkData = true; + previousTotalWidth = totalWidth; + + // Sort columns by current widths + qSort(columnWidths); + + // See if we can shrink headers only, or we already need to shrink the data + foreach (SortedColumnWidth* colWidth, columnWidths) + { + if (colWidth->isHeaderLonger()) + { + shrinkData = false; + break; + } + } + + // Do the shrinking + if (shrinkData) + { + for (int i = resultColumnsCount - 1; i >= 0; i--) + { + // If the data is way larger then the terminal, shrink it to reasonable length in one step. + // We also make sure that after data shrinking, the header didn't become longer than the data, + // cause at this moment, we were finished with headers and we enforce shrinking data + // and so do with headers. + if (columnWidths[i]->getDataWidth() > maxSingleColumnWidth) + { + totalWidth -= (columnWidths[i]->getDataWidth() - maxSingleColumnWidth); + columnWidths[i]->setDataWidth(maxSingleColumnWidth); + columnWidths[i]->setMaxHeaderWidth(maxSingleColumnWidth); + break; + } + else if (columnWidths[i]->getDataWidth() > 1) // just shrink it by 1 + { + totalWidth -= 1; + columnWidths[i]->decrDataWidth(); + columnWidths[i]->setMaxHeaderWidth(columnWidths[i]->getDataWidth()); + break; + } + } + } + else // shrinking headers + { + for (int i = resultColumnsCount - 1; i >= 0; i--) + { + // We will shrink only the header that + if (!columnWidths[i]->isHeaderLonger()) + continue; + + // If the header is way larger then the terminal, shrink it to reasonable length in one step + if (columnWidths[i]->getHeaderWidth() > maxSingleColumnWidth) + { + totalWidth -= (columnWidths[i]->getHeaderWidth() - maxSingleColumnWidth); + columnWidths[i]->setHeaderWidth(maxSingleColumnWidth); + break; + } + else if (columnWidths[i]->getHeaderWidth() > 1) // otherwise just shrink it by 1 + { + totalWidth -= 1; + columnWidths[i]->decrHeaderWidth(); + break; + } + } + } + } + + if (totalWidth == previousTotalWidth && totalWidth > termCols) + qWarning() << "The shrinking algorithm in printResultsColumns() failed, it could not shrink columns enough."; +} + +void CliCommandSql::printColumnHeader(const QList<int>& widths, const QStringList& columns) +{ + QStringList line; + int i = 0; + foreach (const QString& col, columns) + { + line << pad(col.left(widths[i]), widths[i], ' '); + i++; + } + + qOut << line.join("|"); + + line.clear(); + QString hline("-"); + for (i = 0; i < columns.count(); i++) + line << hline.repeated(widths[i]); + + qOut << line.join("+"); +} + +void CliCommandSql::printColumnDataRow(const QList<int>& widths, const SqlResultsRowPtr& row, int rowIdOffset) +{ + int i = 0; + QStringList line; + foreach (const QVariant& value, row->valueList().mid(rowIdOffset)) + { + line << pad(getValueString(value).left(widths[i]), widths[i], ' '); + i++; + } + + qOut << line.join("|"); +} + +QString CliCommandSql::getValueString(const QVariant& value) +{ + if (value.isValid() && !value.isNull()) + return value.toString(); + + return CFG_CLI.Console.NullValue.get(); +} + +void CliCommandSql::executionFailed(int code, const QString& msg) +{ + UNUSED(code); + qOut << tr("Query execution error: %1").arg(msg) << "\n\n"; + qOut.flush(); +} + +CliCommandSql::SortedColumnWidth::SortedColumnWidth() +{ + dataWidth = 0; + headerWidth = 0; + width = 0; +} + +bool CliCommandSql::SortedColumnWidth::operator<(const CliCommandSql::SortedColumnWidth& other) +{ + return width < other.width; +} + +int CliCommandSql::SortedColumnWidth::getHeaderWidth() const +{ + return headerWidth; +} + +void CliCommandSql::SortedColumnWidth::setHeaderWidth(int value) +{ + headerWidth = value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::setMaxHeaderWidth(int value) +{ + if (headerWidth > value) + { + headerWidth = value; + updateWidth(); + } +} + +void CliCommandSql::SortedColumnWidth::incrHeaderWidth(int value) +{ + headerWidth += value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::decrHeaderWidth(int value) +{ + headerWidth -= value; + updateWidth(); +} + +int CliCommandSql::SortedColumnWidth::getDataWidth() const +{ + return dataWidth; +} + +void CliCommandSql::SortedColumnWidth::setDataWidth(int value) +{ + dataWidth = value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::setMinDataWidth(int value) +{ + if (dataWidth < value) + { + dataWidth = value; + updateWidth(); + } +} + +void CliCommandSql::SortedColumnWidth::incrDataWidth(int value) +{ + dataWidth += value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::decrDataWidth(int value) +{ + dataWidth -= value; + updateWidth(); +} + +void CliCommandSql::SortedColumnWidth::incrWidth(int value) +{ + width += value; + dataWidth = width; + headerWidth = width; +} + +int CliCommandSql::SortedColumnWidth::getWidth() const +{ + return width; +} + +bool CliCommandSql::SortedColumnWidth::isHeaderLonger() const +{ + return headerWidth > dataWidth; +} + +void CliCommandSql::SortedColumnWidth::updateWidth() +{ + width = qMax(headerWidth, dataWidth); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h new file mode 100644 index 0000000..5423a85 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h @@ -0,0 +1,66 @@ +#ifndef CLICOMMANDSQL_H +#define CLICOMMANDSQL_H + +#include "clicommand.h" +#include "db/sqlquery.h" + +class QueryExecutor; + +class CliCommandSql : public CliCommand +{ + Q_OBJECT + + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + bool isAsyncExecution() const; + void defineSyntax(); + + private: + class SortedColumnWidth + { + public: + SortedColumnWidth(); + + bool operator<(const SortedColumnWidth& other); + + int getHeaderWidth() const; + void setHeaderWidth(int value); + void setMaxHeaderWidth(int value); + void incrHeaderWidth(int value = 1); + void decrHeaderWidth(int value = 1); + + int getDataWidth() const; + void setDataWidth(int value); + void setMinDataWidth(int value); + void incrDataWidth(int value = 1); + void decrDataWidth(int value = 1); + + void incrWidth(int value = 0); + int getWidth() const; + bool isHeaderLonger() const; + + private: + void updateWidth(); + + int width; + int headerWidth; + int dataWidth; + }; + + void printResultsClassic(QueryExecutor *executor, SqlQueryPtr results); + void printResultsFixed(QueryExecutor *executor, SqlQueryPtr results); + void printResultsColumns(QueryExecutor *executor, SqlQueryPtr results); + void printResultsRowByRow(QueryExecutor *executor, SqlQueryPtr results); + void shrinkColumns(QList<SortedColumnWidth*>& columnWidths, int termCols, int resultColumnsCount, int totalWidth); + void printColumnHeader(const QList<int>& widths, const QStringList& columns); + void printColumnDataRow(const QList<int>& widths, const SqlResultsRowPtr& row, int rowIdOffset); + + QString getValueString(const QVariant& value); + + private slots: + void executionFailed(int code, const QString& msg); +}; + +#endif // CLICOMMANDSQL_H diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp new file mode 100644 index 0000000..9a6b325 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp @@ -0,0 +1,79 @@ +#include "clicommandtables.h"
+#include "cli.h"
+#include "schemaresolver.h"
+#include "services/dbmanager.h"
+#include "common/utils.h"
+
+void CliCommandTables::execute()
+{
+ Db* db = nullptr;
+ if (syntax.isArgumentSet(DB_NAME))
+ {
+ db = DBLIST->getByName(syntax.getArgument(DB_NAME));
+ if (!db)
+ {
+ println(tr("No such database: %1. Use .dblist to see list of known databases.").arg(syntax.getArgument(DB_NAME)));
+ return;
+ }
+ }
+ else if (cli->getCurrentDb())
+ {
+ db = cli->getCurrentDb();
+ }
+ else
+ {
+ println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.")
+ .arg(cmdName("tables")).arg(cmdName("use")).arg(cmdName("tables")));
+ return;
+ }
+
+ if (!db->isOpen())
+ {
+ println(tr("Database %1 is closed.").arg(db->getName()));
+ return;
+ }
+
+ println();
+
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(!syntax.isOptionSet(SHOW_SYSTEM_TABLES));
+ QSet<QString> dbList;
+ dbList << "main" << "temp";
+ dbList += resolver.getDatabases();
+
+ int width = longest(dbList.toList()).length();
+ width = qMax(width, tr("Database").length());
+
+ println(pad(tr("Database"), width, ' ') + " " + tr("Table"));
+ println(pad("", width, '-') + "-------------------");
+
+ foreach (const QString& dbName, dbList)
+ {
+ foreach (const QString& table, resolver.getTables(dbName))
+ println(pad(dbName, width, ' ') + " " + table);
+ }
+
+ println();
+}
+
+QString CliCommandTables::shortHelp() const
+{
+ return tr("prints list of tables in the database");
+}
+
+QString CliCommandTables::fullHelp() const
+{
+ return tr(
+ "Prints list of tables in given <database> or in the current working database. "
+ "Note, that the <database> should be the name of the registered database (see %1). "
+ "The output list includes all tables from any other databases attached to the queried database.\n"
+ "When the -s option is given, then system tables are also listed."
+ ).arg(cmdName("use"));
+}
+
+void CliCommandTables::defineSyntax()
+{
+ syntax.setName("tables");
+ syntax.addArgument(DB_NAME, tr("database", "CLI command syntax"), false);
+ syntax.addOptionShort(SHOW_SYSTEM_TABLES, "s");
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h new file mode 100644 index 0000000..68ae314 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h @@ -0,0 +1,21 @@ +#ifndef CLICOMMANDTABLES_H
+#define CLICOMMANDTABLES_H
+
+#include "clicommand.h"
+
+class CliCommandTables : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum ArgIds
+ {
+ SHOW_SYSTEM_TABLES
+ };
+};
+
+#endif // CLICOMMANDTABLES_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp new file mode 100644 index 0000000..cbd57c6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp @@ -0,0 +1,154 @@ +#include "clicommandtree.h"
+#include "cli.h"
+#include "common/sortedhash.h"
+#include "common/unused.h"
+
+const QString CliCommandTree::metaNodeNameTemplate = "<%1>";
+
+void CliCommandTree::execute()
+{
+ if (!cli->getCurrentDb())
+ {
+ println(tr("No current working database is selected. Use %1 to define one and then run %2.").arg(cmdName("use")).arg(cmdName("tree")));
+ return;
+ }
+
+ bool printColumns = syntax.isOptionSet(COLUMNS);
+ bool printSystemObjects = syntax.isOptionSet(SYSTEM_OBJECTS);
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ resolver.setIgnoreSystemObjects(!printSystemObjects);
+
+ QStringList databases;
+ if (syntax.isArgumentSet(INTRNAL_DB))
+ {
+ databases << syntax.getArgument(INTRNAL_DB);
+ }
+ else
+ {
+ databases << "main" << "temp";
+ databases += resolver.getDatabases().toList();
+ }
+
+ AsciiTree tree;
+ tree.label = cli->getCurrentDb()->getName();
+ foreach (const QString& database, databases)
+ {
+ tree.childs << getDatabaseTree(database, resolver, printColumns);
+ }
+
+ println();
+ println(toAsciiTree(tree));
+ println();
+}
+
+AsciiTree CliCommandTree::getDatabaseTree(const QString& database, SchemaResolver& resolver, bool printColumns)
+{
+ QStringList tables = resolver.getTables(database);
+ QStringList views = resolver.getViews(database);
+
+ AsciiTree tree;
+ AsciiTree tablesTree;
+ AsciiTree viewsTree;
+
+ tablesTree.label = metaNodeNameTemplate.arg(tr("Tables"));
+ foreach (const QString& table, tables)
+ tablesTree.childs << getTableTree(database, table, resolver, printColumns);
+
+ viewsTree.label = metaNodeNameTemplate.arg(tr("Views"));
+ foreach (const QString& view, views)
+ viewsTree.childs << getViewTree(database, view, resolver);
+
+ tree.label = database;
+ tree.childs << tablesTree << viewsTree;
+ return tree;
+}
+
+AsciiTree CliCommandTree::getTableTree(const QString& database, const QString& table, SchemaResolver& resolver, bool printColumns)
+{
+ QStringList columns;
+ if (printColumns)
+ columns = resolver.getTableColumns(database, table);
+
+ QStringList indexes = resolver.getIndexesForTable(database, table);
+ QStringList triggers = resolver.getTriggersForTable(database, table);
+
+ AsciiTree tree;
+ AsciiTree columnsTree;
+ AsciiTree indexesTree;
+ AsciiTree triggersTree;
+
+ if (printColumns)
+ {
+ columnsTree.label = metaNodeNameTemplate.arg(tr("Columns"));
+ foreach (const QString& column, columns)
+ columnsTree.childs << getTreeLeaf(column);
+ }
+
+ indexesTree.label = metaNodeNameTemplate.arg(tr("Indexes"));
+ foreach (const QString& index, indexes)
+ indexesTree.childs << getTreeLeaf(index);
+
+ triggersTree.label = metaNodeNameTemplate.arg(tr("Triggers"));
+ foreach (const QString& trig, triggers)
+ triggersTree.childs << getTreeLeaf(trig);
+
+ if (printColumns)
+ tree.childs << columnsTree;
+
+ tree.label = table;
+ tree.childs << indexesTree;
+ tree.childs << triggersTree;
+
+ return tree;
+}
+
+AsciiTree CliCommandTree::getViewTree(const QString& database, const QString& view, SchemaResolver& resolver)
+{
+ QStringList triggers = resolver.getTriggersForView(database, view);
+
+ AsciiTree tree;
+ AsciiTree triggersTree;
+
+ triggersTree.label = metaNodeNameTemplate.arg(tr("Triggers"));
+ foreach (const QString& trig, triggers)
+ triggersTree.childs << getTreeLeaf(trig);
+
+ tree.label = view;
+ tree.childs << triggersTree;
+
+ return tree;
+}
+
+AsciiTree CliCommandTree::getTreeLeaf(const QString& column)
+{
+ AsciiTree tree;
+ tree.label = column;
+ return tree;
+}
+
+QString CliCommandTree::shortHelp() const
+{
+ return tr("prints all objects in the database as a tree");
+}
+
+QString CliCommandTree::fullHelp() const
+{
+ return tr(
+ "Prints all objects (tables, indexes, triggers and views) that are in the database as a tree. "
+ "The tree is very similar to the one that you can see in GUI client of the SQLiteStudio.\n"
+ "When -c option is given, then also columns will be listed under each table.\n"
+ "When -s option is given, then also system objects will be printed (sqlite_* tables, autoincrement indexes, etc).\n"
+ "The database argument is optional and if provided, then only given database will be printed. This is not a registered database name, "
+ "but instead it's an internal SQLite database name, like 'main', 'temp', or any attached database name. To print tree for other "
+ "registered database, call %1 first to switch the working database, and then use %2 command."
+ ).arg(cmdName("use")).arg(cmdName("tree"));
+}
+
+void CliCommandTree::defineSyntax()
+{
+ syntax.setName("tree");
+ syntax.addOptionShort(COLUMNS, "c");
+ syntax.addOptionShort(SYSTEM_OBJECTS, "s");
+ syntax.addArgument(INTRNAL_DB, "database", false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h new file mode 100644 index 0000000..815d781 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h @@ -0,0 +1,31 @@ +#ifndef CLICOMMANDTREE_H
+#define CLICOMMANDTREE_H
+
+#include "schemaresolver.h"
+#include "cliutils.h"
+#include "clicommand.h"
+
+class CliCommandTree : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum Opts
+ {
+ COLUMNS,
+ SYSTEM_OBJECTS
+ };
+
+ AsciiTree getDatabaseTree(const QString& database, SchemaResolver& resolver, bool printColumns);
+ AsciiTree getTableTree(const QString& database, const QString& table, SchemaResolver& resolver, bool printColumns);
+ AsciiTree getViewTree(const QString& database, const QString& view, SchemaResolver& resolver);
+ AsciiTree getTreeLeaf(const QString& column);
+
+ static const QString metaNodeNameTemplate;
+};
+
+#endif // CLICOMMANDTREE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp new file mode 100644 index 0000000..ef0f641 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp @@ -0,0 +1,64 @@ +#include "clicommanduse.h" +#include "cli.h" +#include "services/config.h" +#include "../cli_config.h" +#include "services/dbmanager.h" + +void CliCommandUse::execute() +{ + if (syntax.isArgumentSet(DB_NAME)) + { + if (!cli->getCurrentDb()) + { + println(tr("No current database selected.")); + return; + } + println(tr("Current database: %1").arg(cli->getCurrentDb()->getName())); + return; + } + + Db* db = DBLIST->getByName(syntax.getArgument(DB_NAME)); + if (!db) + { + println(tr("No such database: %1").arg(syntax.getArgument(DB_NAME))); + return; + } + + cli->setCurrentDb(db); + CFG_CLI.Console.DefaultDatabase.set(db->getName()); + + println(tr("Current database: %1").arg(db->getName())); +} + +QString CliCommandUse::shortHelp() const +{ + return tr("changes default working database"); +} + +QString CliCommandUse::fullHelp() const +{ + return tr( + "Changes current working database to <name>. If the <name> database is not registered in the application, " + "then the error message is printed and no change is made.\n" + "\n" + "What is current working database?\n" + "When you type a SQL query to be executed, it is executed on the default database, which is also known as " + "the current working database. Most of database-related commands can also work using default database, if no database was " + "provided in their arguments. The current database is always identified by command line prompt. " + "The default database is always defined (unless there is no database on the list at all).\n" + "\n" + "The default database can be selected in various ways:\n" + "- using %1 command,\n" + "- by passing database file name to the application startup parameters,\n" + "- by passing registered database name to the application startup parameters,\n" + "- by restoring previously selected default database from saved configuration,\n" + "- or when default database was not selected by any of the above, then first database from the registered databases list " + "becomes the default one." + ).arg(cmdName("use")); +} + +void CliCommandUse::defineSyntax() +{ + syntax.setName("use"); + syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"), false); +} diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h new file mode 100644 index 0000000..9a8f280 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h @@ -0,0 +1,15 @@ +#ifndef CLICOMMANDUSE_H +#define CLICOMMANDUSE_H + +#include "clicommand.h" + +class CliCommandUse : public CliCommand +{ + public: + void execute(); + QString shortHelp() const; + QString fullHelp() const; + void defineSyntax(); +}; + +#endif // CLICOMMANDUSE_H diff --git a/SQLiteStudio3/sqlitestudiocli/main.cpp b/SQLiteStudio3/sqlitestudiocli/main.cpp new file mode 100644 index 0000000..118e3f6 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/main.cpp @@ -0,0 +1,92 @@ +#include "cli.h" +#include "clicommandexecutor.h" +#include "sqlitestudio.h" +#include "commands/clicommand.h" +#include "cli_config.h" +#include "cliutils.h" +#include "qio.h" +#include "climsghandler.h" +#include "completionhelper.h" +#include "services/updatemanager.h" +#include "services/pluginmanager.h" +#include <QCoreApplication> +#include <QtGlobal> +#include <QCommandLineParser> +#include <QCommandLineOption> + +bool listPlugins = false; + +QString cliHandleCmdLineArgs() +{ + QCommandLineParser parser; + parser.setApplicationDescription(QObject::tr("Command line interface to SQLiteStudio, a SQLite manager.")); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption debugOption({"d", "debug"}, QObject::tr("Enables debug messages on standard error output.")); + QCommandLineOption lemonDebugOption("debug-lemon", QObject::tr("Enables Lemon parser debug messages for SQL code assistant.")); + QCommandLineOption listPluginsOption("list-plugins", QObject::tr("Lists plugins installed in the SQLiteStudio end exists.")); + parser.addOption(debugOption); + parser.addOption(lemonDebugOption); + parser.addOption(listPluginsOption); + + parser.addPositionalArgument(QObject::tr("file"), QObject::tr("Database file to open")); + + parser.process(qApp->arguments()); + + if (parser.isSet(debugOption)) + setCliDebug(true); + + if (parser.isSet(listPluginsOption)) + listPlugins = true; + + CompletionHelper::enableLemonDebug = parser.isSet(lemonDebugOption); + + QStringList args = parser.positionalArguments(); + if (args.size() > 0) + return args[0]; + + return QString::null; +} + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + int retCode = 1; + if (UpdateManager::handleUpdateOptions(a.arguments(), retCode)) + return retCode; + + QCoreApplication::setApplicationName("SQLiteStudio"); + QCoreApplication::setApplicationVersion(SQLITESTUDIO->getVersionString()); + + qInstallMessageHandler(cliMessageHandler); + + QString dbToOpen = cliHandleCmdLineArgs(); + + CliResultsDisplay::staticInit(); + initCliUtils(); + + SQLITESTUDIO->init(a.arguments(), false); + SQLITESTUDIO->initPlugins(); + + if (listPlugins) + { + for (const PluginManager::PluginDetails& details : PLUGINS->getAllPluginDetails()) + qOut << details.name << " " << details.versionString << "\n"; + + return 0; + } + + CliCommandExecutor executor; + + QObject::connect(CLI::getInstance(), &CLI::execCommand, &executor, &CliCommandExecutor::execCommand); + QObject::connect(&executor, &CliCommandExecutor::executionComplete, CLI::getInstance(), &CLI::executionComplete); + + if (!dbToOpen.isEmpty()) + CLI::getInstance()->openDbFile(dbToOpen); + + CLI::getInstance()->start(); + + return a.exec(); +} diff --git a/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro b/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro new file mode 100644 index 0000000..1b11aa2 --- /dev/null +++ b/SQLiteStudio3/sqlitestudiocli/sqlitestudiocli.pro @@ -0,0 +1,95 @@ +#-------------------------------------------------
+#
+# Project created by QtCreator 2013-02-28T23:21:43
+#
+#-------------------------------------------------
+
+include($$PWD/../dirs.pri)
+
+OBJECTS_DIR = $$OBJECTS_DIR/sqlitestudiocli
+MOC_DIR = $$MOC_DIR/sqlitestudiocli
+UI_DIR = $$UI_DIR/sqlitestudiocli
+
+QT += core
+QT -= gui
+
+TARGET = sqlitestudiocli
+CONFIG += console
+CONFIG -= app_bundle
+
+TEMPLATE = app
+
+CONFIG += c++11
+QMAKE_CXXFLAGS += -pedantic
+linux|portable {
+ QMAKE_LFLAGS += -Wl,-rpath,./lib
+}
+
+SOURCES += main.cpp \
+ cli.cpp \
+ commands/clicommand.cpp \
+ commands/clicommandfactory.cpp \
+ commands/clicommandadd.cpp \
+ commands/clicommandremove.cpp \
+ commands/clicommandexit.cpp \
+ commands/clicommanddblist.cpp \
+ commands/clicommanduse.cpp \
+ commands/clicommandopen.cpp \
+ commands/clicommandclose.cpp \
+ commands/clicommandsql.cpp \
+ clicommandexecutor.cpp \
+ cli_config.cpp \
+ commands/clicommandhelp.cpp \
+ cliutils.cpp \
+ commands/clicommandtables.cpp \
+ climsghandler.cpp \
+ commands/clicommandmode.cpp \
+ commands/clicommandnullvalue.cpp \
+ commands/clicommandhistory.cpp \
+ commands/clicommanddir.cpp \
+ commands/clicommandpwd.cpp \
+ commands/clicommandcd.cpp \
+ clicommandsyntax.cpp \
+ commands/clicommandtree.cpp \
+ clicompleter.cpp \
+ commands/clicommanddesc.cpp
+
+LIBS += -lcoreSQLiteStudio
+
+win32: {
+ INCLUDEPATH += $$PWD/../../../include
+ LIBS += -L$$PWD/../../../lib -ledit_static
+}
+
+unix: {
+ LIBS += -lreadline -ltermcap
+}
+
+HEADERS += \
+ cli.h \
+ commands/clicommand.h \
+ commands/clicommandfactory.h \
+ commands/clicommandadd.h \
+ commands/clicommandremove.h \
+ commands/clicommandexit.h \
+ commands/clicommanddblist.h \
+ commands/clicommanduse.h \
+ commands/clicommandopen.h \
+ commands/clicommandclose.h \
+ commands/clicommandsql.h \
+ cli_config.h \
+ clicommandexecutor.h \
+ commands/clicommandhelp.h \
+ cliutils.h \
+ commands/clicommandtables.h \
+ climsghandler.h \
+ commands/clicommandmode.h \
+ commands/clicommandnullvalue.h \
+ commands/clicommandhistory.h \
+ commands/clicommanddir.h \
+ commands/clicommandpwd.h \
+ commands/clicommandcd.h \
+ clicommandsyntax.h \
+ commands/clicommandtree.h \
+ clicompleter.h \
+ commands/clicommanddesc.h
|
