wip: codegenerator, basic widget present for showing the generated code and specifying

parameters. Some code is also generated but it is not complete yet.

minimum still required
- field assignments
- properly format and escape the query string
This commit is contained in:
eelke 2018-09-18 11:53:19 +02:00
parent daf9536bed
commit f5145f36ed
19 changed files with 380 additions and 32 deletions

View file

@ -4,6 +4,9 @@
#include <QPlainTextEdit>
#include <set>
/** This class adds some capabilities to QPlainTextEdit that are useful
* in code editor scenarios.
*/
class CodeEditor : public QPlainTextEdit
{
Q_OBJECT

51
pglab/CodeGenerator.cpp Normal file
View file

@ -0,0 +1,51 @@
#include "CodeGenerator.h"
#include "ui_CodeGenerator.h"
#include "codebuilder/CodeBuilder.h"
#include "codebuilder/DefaultConfigs.h"
#include "UserConfiguration.h"
#include <QTextStream>
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<const Pgsql::Result> 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);
}

33
pglab/CodeGenerator.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef CODEGENERATOR_H
#define CODEGENERATOR_H
#include <QWidget>
#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<const Pgsql::Result> dbres);
private slots:
void on_updateCodeButton_clicked();
private:
Ui::CodeGenerator *ui;
QString m_query;
std::shared_ptr<const Pgsql::Result> m_dbres;
void generateCode();
};
#endif // CODEGENERATOR_H

80
pglab/CodeGenerator.ui Normal file
View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CodeGenerator</class>
<widget class="QWidget" name="CodeGenerator">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1127</width>
<height>762</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSplitter" name="splitter">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="layoutWidget">
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0">
<widget class="QLabel" name="structNameLabel">
<property name="text">
<string>Struct name:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="structNameEdit"/>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="updateCodeButton">
<property name="text">
<string>Update code</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Configuration</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="comboBox"/>
</item>
</layout>
</widget>
<widget class="CodeEditor" name="generatedCodeEditor">
<property name="tabChangesFocus">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CodeEditor</class>
<extends>QPlainTextEdit</extends>
<header>CodeEditor.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -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<const Pgsql::Result> 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();
}
}

View file

@ -46,6 +46,7 @@ public:
std::shared_ptr<OpenDatabase> getDatabase() { return m_database; }
void newCrudPage(const PgClass &table);
void newCodeGenPage(QString query, std::shared_ptr<const Pgsql::Result> 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

View file

@ -45,7 +45,7 @@
<x>0</x>
<y>0</y>
<width>993</width>
<height>20</height>
<height>25</height>
</rect>
</property>
<widget class="QMenu" name="menuTest">
@ -86,6 +86,7 @@
<addaction name="actionCopy"/>
<addaction name="actionCopy_as_C_string"/>
<addaction name="actionCopy_as_raw_Cpp_string"/>
<addaction name="actionGenerate_code"/>
</widget>
<addaction name="menuTest"/>
<addaction name="menuEdit"/>
@ -233,6 +234,11 @@
<string>Copy as raw C++-string</string>
</property>
</action>
<action name="actionGenerate_code">
<property name="text">
<string>Generate code</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>

View file

@ -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<const Pgsql::Result> GetPgsqlResult() const { return result; }
protected:
// virtual Oid getType(int column) const override;
// virtual QVariant getData(const QModelIndex &index) const override;

View file

@ -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 <codebuilder/CodeBuilder.h>
#include <codebuilder/DefaultConfigs.h>
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<const Pgsql::Result> dbres = resultList[0]->GetPgsqlResult();
m_win->newCodeGenPage(command, dbres);
}
}
void QueryTab::exportData(const QString &file_name)
{
auto widget = ui->tabWidget->currentWidget();

View file

@ -55,6 +55,7 @@ public:
void copyQueryAsCString();
void copyQueryAsRawCppString();
void generateCode();
void exportData(const QString &filename);
QString fileName() const { return m_fileName; }

View file

@ -0,0 +1,25 @@
#include "UserConfiguration.h"
#include <QFont>
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;
}

24
pglab/UserConfiguration.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef USERCONFIGURATION_H
#define USERCONFIGURATION_H
#include <QSettings>
/** 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

View file

@ -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

View file

@ -18,6 +18,8 @@ public:
void setResult(std::shared_ptr<QueryResultModel> res, float ms);
void exportData(const QString &file_name) const;
std::shared_ptr<const Pgsql::Result> GetPgsqlResult() const { return resultModel->GetPgsqlResult(); }
private:
Ui::TuplesResultWidget *ui;

View file

@ -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 <QTextStream>
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<const StructureTemplate> 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<size_t>(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;
}

View file

@ -3,14 +3,19 @@
#include "Pgsql_declare.h"
#include <QString>
#include <vector>
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<const LanguageConfig> 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<ColumnData>;
std::shared_ptr<const LanguageConfig> 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

View file

@ -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<const TypeMappings> GetDefaultCppTypeMappings()
{
TypeMappings result = {
auto tm = std::make_shared<TypeMappings>();
*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<LanguageConfig> buildDefaultCppLanguageConfig()
{
return std::make_shared<LanguageConfig>();
auto config = std::make_shared<LanguageConfig>();
config->setTypeMappings(GetDefaultCppTypeMappings());
config->setNameManglingRules(std::make_shared<NameManglingRules>());
auto st_templ = std::make_shared<StructureTemplate>();
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<IndentationConfig>());
return config;
}
std::shared_ptr<const LanguageConfig> getDefaultCppLanguageConfig()

View file

@ -18,6 +18,11 @@ public:
QString columnNameToFieldName(const QString& column_name) const;
QString getTypeName(Oid dbtype) const;
void setNameManglingRules(std::shared_ptr<const NameManglingRules> name_mangling_rules)
{
m_varNaming = name_mangling_rules;
}
std::shared_ptr<const TypeMappings> typeMappings() const { return m_typeMappings; }
void setTypeMappings(std::shared_ptr<const TypeMappings> type_mappings)
{
@ -29,10 +34,20 @@ public:
return m_structureTemplate;
}
void setStructureTemplate(std::shared_ptr<const StructureTemplate> structure_template)
{
m_structureTemplate = structure_template;
}
std::shared_ptr<const IndentationConfig> indentationConfig() const
{
return m_indentationConfig;
}
void setIndentationConfig(std::shared_ptr<const IndentationConfig> indentation_config)
{
m_indentationConfig = indentation_config;
}
private:
/** Default template for declaring a variable of the correct type.
* exmaple: "{$type} {$varname};"

View file

@ -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