diff --git a/pglab/CodeEditor.h b/pglab/CodeEditor.h index 62b97bb..ec6910a 100644 --- a/pglab/CodeEditor.h +++ b/pglab/CodeEditor.h @@ -4,6 +4,9 @@ #include #include +/** This class adds some capabilities to QPlainTextEdit that are useful + * in code editor scenarios. + */ class CodeEditor : public QPlainTextEdit { Q_OBJECT diff --git a/pglab/CodeGenerator.cpp b/pglab/CodeGenerator.cpp new file mode 100644 index 0000000..92c4c2f --- /dev/null +++ b/pglab/CodeGenerator.cpp @@ -0,0 +1,51 @@ +#include "CodeGenerator.h" +#include "ui_CodeGenerator.h" +#include "codebuilder/CodeBuilder.h" +#include "codebuilder/DefaultConfigs.h" +#include "UserConfiguration.h" +#include + +CodeGenerator::CodeGenerator(QWidget *parent) : + PlgPage(parent), + ui(new Ui::CodeGenerator) +{ + ui->setupUi(this); + + ui->generatedCodeEditor->setFont(UserConfiguration::instance()->codeFont()); + ui->generatedCodeEditor->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); +} + +CodeGenerator::~CodeGenerator() +{ + delete ui; +} + +void CodeGenerator::Init(QString query, std::shared_ptr dbres) +{ + m_query = query; + m_dbres = dbres; + generateCode(); + + +} + + +void CodeGenerator::on_updateCodeButton_clicked() +{ + generateCode(); +} + +void CodeGenerator::generateCode() +{ + QString struct_name = ui->structNameEdit->text(); + + QString buffer; + QTextStream stream(&buffer); + CodeBuilder builder; + builder.setLanguageConfig(getDefaultCppLanguageConfig()); + builder.GenCodeForExecutingQuery(stream, m_query, *m_dbres, struct_name); + stream.flush(); + ui->generatedCodeEditor->setPlainText(buffer); +// QApplication::clipboard()->setText(buffer); + +} diff --git a/pglab/CodeGenerator.h b/pglab/CodeGenerator.h new file mode 100644 index 0000000..c2dfac9 --- /dev/null +++ b/pglab/CodeGenerator.h @@ -0,0 +1,33 @@ +#ifndef CODEGENERATOR_H +#define CODEGENERATOR_H + +#include +#include "PlgPage.h" +#include "Pgsql_declare.h" + +namespace Ui { +class CodeGenerator; +} + +class CodeGenerator : public PlgPage +{ + Q_OBJECT + +public: + explicit CodeGenerator(QWidget *parent = nullptr); + ~CodeGenerator(); + + void Init(QString query, std::shared_ptr dbres); +private slots: + void on_updateCodeButton_clicked(); + +private: + Ui::CodeGenerator *ui; + + QString m_query; + std::shared_ptr m_dbres; + + void generateCode(); +}; + +#endif // CODEGENERATOR_H diff --git a/pglab/CodeGenerator.ui b/pglab/CodeGenerator.ui new file mode 100644 index 0000000..8d91f1b --- /dev/null +++ b/pglab/CodeGenerator.ui @@ -0,0 +1,80 @@ + + + CodeGenerator + + + + 0 + 0 + 1127 + 762 + + + + Form + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + Struct name: + + + + + + + + + + Update code + + + + + + + Configuration + + + + + + + + + + + true + + + false + + + + + + + + + CodeEditor + QPlainTextEdit +
CodeEditor.h
+
+
+ + +
diff --git a/pglab/MainWindow.cpp b/pglab/MainWindow.cpp index 256ab72..064649f 100644 --- a/pglab/MainWindow.cpp +++ b/pglab/MainWindow.cpp @@ -14,6 +14,7 @@ #include "QueryTab.h" #include "util.h" #include "PlgPage.h" +#include "CodeGenerator.h" #include "MasterController.h" #include "CrudTab.h" #include "WorkManager.h" @@ -58,6 +59,14 @@ void MainWindow::newCrudPage(const PgClass &table) addPage(ct, table.name); } +void MainWindow::newCodeGenPage(QString query, std::shared_ptr dbres) +{ + auto cgtab = new CodeGenerator(this); + cgtab->Init(query, dbres); + addPage(cgtab, "Codegen"); +} + + QueryTab *MainWindow::GetActiveQueryTab() { QWidget *widget = ui->tabWidget->currentWidget(); @@ -344,3 +353,12 @@ void MainWindow::on_tabWidget_currentChanged(int index) } m_previousPage = page; } + +void MainWindow::on_actionGenerate_code_triggered() +{ + QueryTab *tab = GetActiveQueryTab(); + if (tab) { + tab->generateCode(); + } + +} diff --git a/pglab/MainWindow.h b/pglab/MainWindow.h index 8fd7cc6..4fcfa60 100644 --- a/pglab/MainWindow.h +++ b/pglab/MainWindow.h @@ -46,6 +46,7 @@ public: std::shared_ptr getDatabase() { return m_database; } void newCrudPage(const PgClass &table); + void newCodeGenPage(QString query, std::shared_ptr dbres); private: Ui::MainWindow *ui; @@ -116,6 +117,7 @@ private slots: void on_actionCopy_as_C_string_triggered(); void on_actionCopy_as_raw_Cpp_string_triggered(); void on_tabWidget_currentChanged(int index); + void on_actionGenerate_code_triggered(); }; #endif // MAINWINDOW_H diff --git a/pglab/MainWindow.ui b/pglab/MainWindow.ui index 7599ceb..98fe391 100644 --- a/pglab/MainWindow.ui +++ b/pglab/MainWindow.ui @@ -45,7 +45,7 @@ 0 0 993 - 20 + 25 @@ -86,6 +86,7 @@ + @@ -233,6 +234,11 @@ Copy as raw C++-string + + + Generate code + + diff --git a/pglab/QueryResultModel.h b/pglab/QueryResultModel.h index 111ab7a..68169c8 100644 --- a/pglab/QueryResultModel.h +++ b/pglab/QueryResultModel.h @@ -19,6 +19,7 @@ public: virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; // virtual Qt::ItemFlags flags(const QModelIndex &index) const override; + std::shared_ptr GetPgsqlResult() const { return result; } protected: // virtual Oid getType(int column) const override; // virtual QVariant getData(const QModelIndex &index) const override; diff --git a/pglab/QueryTab.cpp b/pglab/QueryTab.cpp index f55fb35..69f98d1 100644 --- a/pglab/QueryTab.cpp +++ b/pglab/QueryTab.cpp @@ -22,6 +22,7 @@ #include "QueryParamListController.h" #include "util.h" #include "GlobalIoService.h" +#include "UserConfiguration.h" QueryTab::QueryTab(MainWindow *win, QWidget *parent) : PlgPage(parent), @@ -34,11 +35,7 @@ QueryTab::QueryTab(MainWindow *win, QWidget *parent) : connect(&m_dbConnection, &ASyncDBConnection::onStateChanged, this, &QueryTab::connectionStateChanged); connect(&m_dbConnection, &ASyncDBConnection::onNotice, this, &QueryTab::receiveNotice); - QFont font; - font.setFamily("Source Code Pro"); - font.setFixedPitch(true); - font.setPointSize(10); - ui->queryEdit->setFont(font); + ui->queryEdit->setFont(UserConfiguration::instance()->codeFont()); highlighter = new SqlSyntaxHighlighter(ui->queryEdit->document()); auto open_database = m_win->getDatabase(); @@ -583,14 +580,30 @@ void QueryTab::copyQueryAsCString() QApplication::clipboard()->setText(cs); } +#include +#include + void QueryTab::copyQueryAsRawCppString() { - //auto sql = getAllOrSelectedSql(); QString command = getCommand(); + //auto sql = getAllOrSelectedSql(); QString cs = ConvertToMultiLineRawCppString(command); QApplication::clipboard()->setText(cs); } +void QueryTab::generateCode() +{ + QString command = getCommand(); + + if (resultList.empty()) { + QMessageBox::question(this, "pglab", tr("Please execute the query first"), QMessageBox::Ok); + } + if (resultList.size() == 1) { + std::shared_ptr dbres = resultList[0]->GetPgsqlResult(); + m_win->newCodeGenPage(command, dbres); + } +} + void QueryTab::exportData(const QString &file_name) { auto widget = ui->tabWidget->currentWidget(); diff --git a/pglab/QueryTab.h b/pglab/QueryTab.h index 0d86c0b..d31faed 100644 --- a/pglab/QueryTab.h +++ b/pglab/QueryTab.h @@ -55,6 +55,7 @@ public: void copyQueryAsCString(); void copyQueryAsRawCppString(); + void generateCode(); void exportData(const QString &filename); QString fileName() const { return m_fileName; } diff --git a/pglab/UserConfiguration.cpp b/pglab/UserConfiguration.cpp new file mode 100644 index 0000000..3b0ef95 --- /dev/null +++ b/pglab/UserConfiguration.cpp @@ -0,0 +1,25 @@ +#include "UserConfiguration.h" +#include + +UserConfiguration* UserConfiguration::instance() +{ + static UserConfiguration config; // in C++ 11 static ensures thread safe initialization at first call of function + return &config; +} + +UserConfiguration::UserConfiguration() + : m_settings(nullptr) +{ + m_settings.setIniCodec("UTF-8"); +} + +QFont UserConfiguration::codeFont() const +{ + QString family = m_settings.value("fonts/code/family", QString("Source Code Pro")).toString(); + int size = m_settings.value("fonts/code/size", 10).toInt(); + QFont font; + font.setFamily(family); + font.setFixedPitch(true); + font.setPointSize(size); + return font; +} diff --git a/pglab/UserConfiguration.h b/pglab/UserConfiguration.h new file mode 100644 index 0000000..897f1ce --- /dev/null +++ b/pglab/UserConfiguration.h @@ -0,0 +1,24 @@ +#ifndef USERCONFIGURATION_H +#define USERCONFIGURATION_H + +#include +/** Class for most of the program configuration + * + * The settings represented by this class are stored in a settings file + * that is user specific. Depending on OS setup it might be shared with + * other machines in the network because for instance on windows it is + * part of the roaming profile. + */ +class UserConfiguration +{ +public: + static UserConfiguration* instance(); + + UserConfiguration(); + + QFont codeFont() const; +private: + QSettings m_settings; +}; + +#endif // USERCONFIGURATION_H diff --git a/pglab/pglab.pro b/pglab/pglab.pro index 8aaaa8a..18b7a69 100644 --- a/pglab/pglab.pro +++ b/pglab/pglab.pro @@ -73,7 +73,9 @@ SOURCES += main.cpp\ EditorGutter.cpp \ CodeEditor.cpp \ PlgPage.cpp \ -PropertyProxyModel.cpp +PropertyProxyModel.cpp \ + CodeGenerator.cpp \ + UserConfiguration.cpp HEADERS += \ QueryResultModel.h \ @@ -121,7 +123,9 @@ HEADERS += \ PlgPage.h \ AbstractCommand.h \ PropertyProxyModel.h \ -CustomDataRole.h +CustomDataRole.h \ + CodeGenerator.h \ + UserConfiguration.h FORMS += mainwindow.ui \ ConnectionManagerWindow.ui \ @@ -134,7 +138,8 @@ FORMS += mainwindow.ui \ TablesPage.ui \ NamespaceFilterWidget.ui \ ApplicationWindow.ui \ - CrudTab.ui + CrudTab.ui \ + CodeGenerator.ui RESOURCES += \ resources.qrc diff --git a/pglab/tuplesresultwidget.h b/pglab/tuplesresultwidget.h index 7511404..b2605ba 100644 --- a/pglab/tuplesresultwidget.h +++ b/pglab/tuplesresultwidget.h @@ -18,6 +18,8 @@ public: void setResult(std::shared_ptr res, float ms); void exportData(const QString &file_name) const; + + std::shared_ptr GetPgsqlResult() const { return resultModel->GetPgsqlResult(); } private: Ui::TuplesResultWidget *ui; diff --git a/pglablib/codebuilder/CodeBuilder.cpp b/pglablib/codebuilder/CodeBuilder.cpp index 9671311..dd782c0 100644 --- a/pglablib/codebuilder/CodeBuilder.cpp +++ b/pglablib/codebuilder/CodeBuilder.cpp @@ -1,4 +1,5 @@ #include "CodeBuilder.h" +#include "FormatToStream.h" #include "Pgsql_Result.h" #include "IndentationConfig.h" #include "LanguageConfig.h" @@ -6,31 +7,41 @@ #include "util.h" #include -void CodeBuilder::GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result) +void CodeBuilder::GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result, QString structname) { // %1 query string // %2 struct_name // %3 list of field assignments QString exec_query_template = "Pgsql::Result result = conn.query(%1);"; - QString loop_start_template = - "Pgsql::Result result = conn.query(/%query_literal%/);" - "for (auto row: result) {\n" - " Pgsql::Col col(row);\n" - " /%struct_type%/ v;\n" - "/%assign_fields%/" - "}"; + QString loop_start_template = R"__(Pgsql::Result result = conn.query(/%queryliteral%/); +for (auto row: result) { + Pgsql::Col col(row); + /%structname%/ v; + /%assignfields%/ +})__"; // %field_name% // %column_name% // %column_index% - QString assign_result_field_template = "col >> v./%field_name%/"; + QString assign_result_field_template = "col >> v./%fieldname%/"; QString query_string_literal = ConvertToMultiLineCString(query); + ColumnDataList columnData = createColumnDataList(result); + + auto get_var_func = [&] (QTextStream &out, QString var) { + if (var == "structname") out << structname; + //else if (var == "assign_fields") GenFieldAssignments(out); + else if (var == "queryliteral") out << query; + + }; + + // [optional] Gen declaration of result structure - GenReturnStructDefinition(q, result); + GenReturnStructDefinition(q, result, structname); // assume we have connection? What name??? + FormatToStream(q, loop_start_template, get_var_func); // gen code for creating and executing statement // - bind parameters @@ -40,16 +51,20 @@ void CodeBuilder::GenCodeForExecutingQuery(QTextStream &q, const QString &query, } -void CodeBuilder::GenReturnStructDefinition(QTextStream &q, const Pgsql::Result &result) const +void CodeBuilder::GenReturnStructDefinition(QTextStream &q, const Pgsql::Result &result, QString structname) const { std::shared_ptr templ = m_configuration->structureTemplate(); - QString struct_name = "TODO"; QString field_format = templ->m_fieldTemplate; int field_indent_levels = templ->m_fieldIndentation; QString field_indent_string = m_configuration->indentationConfig()->getIndentString(field_indent_levels); + + auto struct_callback = [&] (QTextStream &out, QString var) { + if (var == "structname") out << structname; + }; // Make struct start - q << QString(templ->m_startTemplate).arg(struct_name); + //q << QString(templ->m_startTemplate).arg(struct_name); + FormatToStream(q, templ->m_startTemplate, struct_callback); // Process fields for (int column = 0; column < result.cols(); ++column) { QString res_col_name = result.getColName(column); @@ -71,7 +86,9 @@ void CodeBuilder::GenReturnStructDefinition(QTextStream &q, const Pgsql::Result // - null pointer (useful for languages where this has no cost, other cases boolean flags will be more performant) } // Finish struct - q << QString(templ->m_endTemplate).arg(struct_name); + + //q << QString(templ->m_endTemplate).arg(struct_name); + FormatToStream(q, templ->m_endTemplate, struct_callback); } QString CodeBuilder::columnNameToVariableName(QString column_name) const @@ -88,6 +105,22 @@ void CodeBuilder::genFieldDeclaration(QTextStream &q, const QString &format, con { QString field_name = columnNameToVariableName(column_name); QString type_name = getTypeName(column_type); - q << QString(format).arg(field_name).arg(type_name); + //q << QString(format).arg(field_name).arg(type_name); + FormatToStream(q, format, [&] (QTextStream &out, QString var) { + if (var == "typename") out << type_name; + else if (var == "varname") out << field_name; + }); } +CodeBuilder::ColumnDataList CodeBuilder::createColumnDataList(const Pgsql::Result &result) +{ + ColumnDataList list; + list.reserve(static_cast(result.cols())); + for (int column = 0; column < result.cols(); ++column) { + Oid type_oid = result.type(column); + QString column_name = result.getColName(column); + QString field_name = columnNameToVariableName(column_name); + list.push_back({type_oid, column_name, field_name}); + } + return list; +} diff --git a/pglablib/codebuilder/CodeBuilder.h b/pglablib/codebuilder/CodeBuilder.h index c52e070..aad070d 100644 --- a/pglablib/codebuilder/CodeBuilder.h +++ b/pglablib/codebuilder/CodeBuilder.h @@ -3,14 +3,19 @@ #include "Pgsql_declare.h" #include +#include class LanguageConfig; class QTextStream; class CodeBuilder { public: - void GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result); - void GenReturnStructDefinition(QTextStream &q, const Pgsql::Result &result) const; + void setLanguageConfig(std::shared_ptr config){ + m_configuration = config; + } + + void GenCodeForExecutingQuery(QTextStream &q, const QString &query, const Pgsql::Result &result, QString structname); + void GenReturnStructDefinition(QTextStream &q, const Pgsql::Result &result, QString structname) const; // Generating code for performing query and going through the result // - Code for executing the query @@ -20,9 +25,21 @@ public: QString columnNameToVariableName(QString column_name) const; QString getTypeName(Oid dbtype) const; private: + class ColumnData { + public: + Oid oid; + QString columnName; + QString varName; ///< either field of the struct or in loop local var name + }; + using ColumnDataList = std::vector; + std::shared_ptr m_configuration; + + void GenFieldAssignments(QTextStream &q, const Pgsql::Result &result); void genFieldDeclaration(QTextStream &q, const QString &format, const QString &column_name, Oid column_type) const; + + ColumnDataList createColumnDataList(const Pgsql::Result &result); }; #endif // CODEBUILDER_H diff --git a/pglablib/codebuilder/DefaultConfigs.cpp b/pglablib/codebuilder/DefaultConfigs.cpp index ca3554d..d9f31c3 100644 --- a/pglablib/codebuilder/DefaultConfigs.cpp +++ b/pglablib/codebuilder/DefaultConfigs.cpp @@ -1,13 +1,17 @@ #include "DefaultConfigs.h" +#include "IndentationConfig.h" #include "LanguageConfig.h" +#include "NameManglingRules.h" +#include "StructureTemplate.h" #include "TypeMappings.h" #include "Pgsql_oids.h" using namespace Pgsql; -TypeMappings GetDefaultCppTypeMappings() +std::shared_ptr GetDefaultCppTypeMappings() { - TypeMappings result = { + auto tm = std::make_shared(); + *tm = { { bool_oid, "bool" }, { char_oid, "char" }, { name_oid, "std::string" }, @@ -19,13 +23,27 @@ TypeMappings GetDefaultCppTypeMappings() { float4_oid, "float" }, { float8_oid, "double" } }; - return result; + tm->setDefaultStringType("std::string"); + tm->setDefaultContainerType("std::vector<%1>"); + return tm; } std::shared_ptr buildDefaultCppLanguageConfig() { - return std::make_shared(); + auto config = std::make_shared(); + config->setTypeMappings(GetDefaultCppTypeMappings()); + config->setNameManglingRules(std::make_shared()); + + auto st_templ = std::make_shared(); + st_templ->m_startTemplate = "class /%structname%/ {\npublic:\n"; + st_templ->m_endTemplate = "};\n"; // }; + st_templ->m_fieldTemplate = "/%typename%/ /%varname%/;"; + //st_templ->m_fieldSeparator; + config->setStructureTemplate(st_templ); + config->setIndentationConfig(std::make_shared()); + + return config; } std::shared_ptr getDefaultCppLanguageConfig() diff --git a/pglablib/codebuilder/LanguageConfig.h b/pglablib/codebuilder/LanguageConfig.h index a850b5c..e583a28 100644 --- a/pglablib/codebuilder/LanguageConfig.h +++ b/pglablib/codebuilder/LanguageConfig.h @@ -18,6 +18,11 @@ public: QString columnNameToFieldName(const QString& column_name) const; QString getTypeName(Oid dbtype) const; + void setNameManglingRules(std::shared_ptr name_mangling_rules) + { + m_varNaming = name_mangling_rules; + } + std::shared_ptr typeMappings() const { return m_typeMappings; } void setTypeMappings(std::shared_ptr type_mappings) { @@ -29,10 +34,20 @@ public: return m_structureTemplate; } + void setStructureTemplate(std::shared_ptr structure_template) + { + m_structureTemplate = structure_template; + } + std::shared_ptr indentationConfig() const { return m_indentationConfig; } + + void setIndentationConfig(std::shared_ptr indentation_config) + { + m_indentationConfig = indentation_config; + } private: /** Default template for declaring a variable of the correct type. * exmaple: "{$type} {$varname};" diff --git a/tests/pglabtests/pglabtests.pro b/tests/pglabtests/pglabtests.pro index 0d2005e..e2da178 100644 --- a/tests/pglabtests/pglabtests.pro +++ b/tests/pglabtests/pglabtests.pro @@ -22,7 +22,8 @@ SOURCES += main.cpp \ tst_PasswordManager.cpp \ tst_ParamJson.cpp \ tst_CodeBuilder.cpp \ - tst_NameManglingRules.cpp + tst_NameManglingRules.cpp \ + tst_TypeMappings.cpp win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../core/release/ -lcore else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../core/debug/ -lcore