diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml index 64cc295e4..1f88a4c02 100644 --- a/.github/workflows/actions.yml +++ b/.github/workflows/actions.yml @@ -364,7 +364,7 @@ jobs: vs-version: '[17,18)' # 17.xx msbuild-architecture: x64 - name: Install Qt - uses: jurplel/install-qt-action@v4 + uses: jurplel/install-qt-action@v4.1.1 with: version: 6.8.0 host: windows @@ -414,7 +414,7 @@ jobs: steps: - uses: actions/checkout@main - name: Install Qt - uses: jurplel/install-qt-action@v4 + uses: jurplel/install-qt-action@v4.1.1 with: version: 6.8.0 host: windows @@ -466,7 +466,7 @@ jobs: steps: - uses: actions/checkout@main - name: Install Qt - uses: jurplel/install-qt-action@v4 + uses: jurplel/install-qt-action@v4.1.1 with: version: 6.5.2 host: windows diff --git a/configure.bat b/configure.bat index 61503ad6c..f8abd05d2 100644 --- a/configure.bat +++ b/configure.bat @@ -1,7 +1,7 @@ @echo off @setlocal -set VERSION=2.10.1 +set VERSION=2.11.0 set TFDIR=C:\TreeFrog\%VERSION% set MONBOC_VERSION=1.26.2 set LZ4_VERSION=1.9.4 diff --git a/defaults/development.ini b/defaults/development.ini index ddb5f0939..a90d60322 100644 --- a/defaults/development.ini +++ b/defaults/development.ini @@ -7,8 +7,8 @@ ## Template system section ## -# Specify the template system of view, ERB or Otama. -TemplateSystem=ERB +# Template system of view. +TemplateSystem=$TemplateSystem$ ## diff --git a/installer/create_installer.bat b/installer/create_installer.bat index 801f1fe20..a06592ebd 100644 --- a/installer/create_installer.bat +++ b/installer/create_installer.bat @@ -7,7 +7,7 @@ :: 10行目、28行目、39行目を編集 -set VERSION=2.10.1 +set VERSION=2.11.0 set QTBASE=C:\Qt set TFDIR=C:\TreeFrog\%VERSION% diff --git a/installer/treefrog-setup/treefrog-setup/AssemblyInfo.cpp b/installer/treefrog-setup/treefrog-setup/AssemblyInfo.cpp index 5e9021252..1010c389c 100644 --- a/installer/treefrog-setup/treefrog-setup/AssemblyInfo.cpp +++ b/installer/treefrog-setup/treefrog-setup/AssemblyInfo.cpp @@ -31,7 +31,7 @@ using namespace System::Security::Permissions; // すべての値を指定するか、下のように '*' を使ってリビジョンおよびビルド番号を // 既定値にすることができます: -[assembly:AssemblyVersionAttribute("2.10.1")]; +[assembly:AssemblyVersionAttribute("2.11.0")]; [assembly:ComVisible(false)]; diff --git a/src/corelib.pro b/src/corelib.pro index 076b4dc32..a0b93c2fc 100644 --- a/src/corelib.pro +++ b/src/corelib.pro @@ -412,6 +412,7 @@ linux-* { SOURCES += tthreadapplicationserver_linux.cpp SOURCES += tredisdriver_linux.cpp SOURCES += tmemcacheddriver_linux.cpp + SOURCES += tsharedmemory_linux.cpp } # For Mac @@ -420,6 +421,7 @@ macx { SOURCES += tthreadapplicationserver_qt.cpp SOURCES += tredisdriver_qt.cpp SOURCES += tmemcacheddriver_qt.cpp + SOURCES += tsharedmemory_macx.cpp } # For UNIX diff --git a/src/tabstractcontroller.h b/src/tabstractcontroller.h index 1107df6ef..19749bc88 100644 --- a/src/tabstractcontroller.h +++ b/src/tabstractcontroller.h @@ -23,6 +23,10 @@ class T_CORE_EXPORT TAbstractController : public QObject { virtual const TSession &session() const; virtual QString getRenderingData(const QString &templateName, const QVariantMap &vars = QVariantMap()); virtual QByteArray authenticityToken() const { return QByteArray(); } + virtual QVariantMap flashVariants() const { return QVariantMap(); } + virtual QVariant flashVariant(const QString &) const { return QVariant(); } + virtual QJsonObject flashVariantsJson() const { return QJsonObject(); } + virtual QJsonObject flashVariantJson(const QString &) const { return QJsonObject(); } virtual void setFlash(const QString &name, const QVariant &value); void exportVariant(const QString &name, const QVariant &value, bool overwrite = true); virtual bool isUserLoggedIn() const; diff --git a/src/tactioncontext.cpp b/src/tactioncontext.cpp index 642c13299..6d2268b4c 100644 --- a/src/tactioncontext.cpp +++ b/src/tactioncontext.cpp @@ -155,10 +155,8 @@ void TActionContext::execute(THttpRequest &request) tSystemDebug("Re-generate session ID: {}", (const char*)_currController->session().sessionId.data()); } - if (EnableCsrfProtectionModuleFlag && _currController->csrfProtectionEnabled()) { - // Sets CSRF protection information - TActionController::setCsrfProtectionInto(_currController->session()); - } + // Sets CSRF protection information + TActionController::setCsrfProtectionInto(_currController->session()); } // Database Transaction diff --git a/src/tactioncontroller.cpp b/src/tactioncontroller.cpp index bb963273f..a837c07d2 100644 --- a/src/tactioncontroller.cpp +++ b/src/tactioncontroller.cpp @@ -31,7 +31,8 @@ #include #include -const QString FLASH_VARS_SESSION_KEY("_flashVariants"); +const QString QUEUED_FLASH_SESSION_KEY("_flashVariants"); +const QString FLASH_VARS_SESSION_KEY("_activeFlash"); const QString LOGIN_USER_NAME_KEY("_loginUserName"); const QByteArray DEFAULT_CONTENT_TYPE("text/html"); @@ -207,7 +208,11 @@ void TActionController::setCsrfProtectionInto(TSession &session) { if (TSessionManager::instance().storeType() == QLatin1String("cookie")) { QString key = TSessionManager::instance().csrfProtectionKey(); - session.insert(key, TSessionManager::instance().generateId()); // it's just a random value + QByteArray val = session.value(key).toByteArray(); + + if (val.isEmpty()) { + session.insert(key, TSessionManager::instance().generateId()); // it's just a random value + } } } @@ -248,6 +253,7 @@ QString TActionController::loginUserNameKey() bool TActionController::verifyRequest(const THttpRequest &request) const { if (!csrfProtectionEnabled()) { + tSystemWarn("Skipped verifying authenticity token : {}", request.header().path().data()); return true; } @@ -263,7 +269,11 @@ bool TActionController::verifyRequest(const THttpRequest &request) const } tSystemDebug("postAuthToken: {}", (const char*)postAuthToken.data()); - return Tf::strcmp(postAuthToken, authenticityToken()); + bool res = Tf::strcmp(postAuthToken, authenticityToken()); + if (res) { + tSystemDebug("Verified authenticity token : {}", request.header().path().data()); + } + return res; } /*! @@ -595,7 +605,7 @@ void TActionController::redirect(const QUrl &url, int statusCode) // Enable flash-variants QVariant var; var.setValue(_flashVars); - _sessionStore.insert(FLASH_VARS_SESSION_KEY, var); + _sessionStore.insert(QUEUED_FLASH_SESSION_KEY, var); } /*! @@ -655,12 +665,39 @@ bool TActionController::sendData(const QByteArray &data, const QByteArray &conte */ void TActionController::exportAllFlashVariants() { - QVariant var = _sessionStore.take(FLASH_VARS_SESSION_KEY); + _sessionStore.remove(FLASH_VARS_SESSION_KEY); + + QVariant var = _sessionStore.take(QUEUED_FLASH_SESSION_KEY); if (!var.isNull()) { exportVariants(var.toMap()); + _sessionStore.insert(FLASH_VARS_SESSION_KEY, var); } } + +QVariantMap TActionController::flashVariants() const +{ + return _sessionStore.value(FLASH_VARS_SESSION_KEY).toMap(); +} + + +QVariant TActionController::flashVariant(const QString &key) const +{ + return _sessionStore.value(FLASH_VARS_SESSION_KEY).toMap().value(key); +} + + +QJsonObject TActionController::flashVariantsJson() const +{ + return QJsonObject::fromVariantMap(flashVariants()); +} + + +QJsonObject TActionController::flashVariantJson(const QString &key) const +{ + return QJsonObject::fromVariantMap(flashVariant(key).toMap()); +} + /*! Validates the access of the user \a user. Returns true if the user access is allowed by rule; otherwise returns false. diff --git a/src/tactioncontroller.h b/src/tactioncontroller.h index 90c096cda..226409cf2 100644 --- a/src/tactioncontroller.h +++ b/src/tactioncontroller.h @@ -37,7 +37,11 @@ class T_CORE_EXPORT TActionController : public TAbstractController, public TActi virtual QStringList exceptionActionsOfCsrfProtection() const { return QStringList(); } virtual bool transactionEnabled() const { return true; } QByteArray authenticityToken() const override; - QString flash(const QString &name) const; + QVariantMap flashVariants() const override; + QVariant flashVariant(const QString &key) const override; + QJsonObject flashVariantsJson() const override; + QJsonObject flashVariantJson(const QString &key) const override; + //QString flash(const QString &name) const; QHostAddress clientAddress() const; virtual bool isUserLoggedIn() const override; virtual QString identityKeyOfLoginUser() const; @@ -199,10 +203,10 @@ inline void TActionController::setStatusCode(int code) _statCode = code; } -inline QString TActionController::flash(const QString &name) const -{ - return _flashVars.value(name).toString(); -} +// inline QString TActionController::flash(const QString &name) const +// { +// return _flashVars.value(name).toString(); +// } inline QByteArray TActionController::contentType() const { diff --git a/src/tactionview.cpp b/src/tactionview.cpp index 8430b1d5a..f20e155df 100644 --- a/src/tactionview.cpp +++ b/src/tactionview.cpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2019, AOYAMA Kazuharu +/* Copyright (c) 2010-2025, AOYAMA Kazuharu * All rights reserved. * * This software may be used and distributed according to the terms of @@ -72,68 +72,122 @@ QString TActionView::authenticityToken() const } /*! - Outputs the string of the HTML attribute \a attr to a view - template. + Returns flash variants; */ -QString TActionView::echo(const THtmlAttribute &attr) +QVariantMap TActionView::flashVariants() const { - responsebody += attr.toString().trimmed(); - return QString(); + static QVariantMap dummy; + return (actionController) ? actionController->flashVariants() : dummy; } +/*! + \fn QString TActionView::echo(const THtmlAttribute &attr) + Outputs the string of the HTML attribute \a attr to a view + template. +*/ + /*! \fn QString TActionView::echo(const QVariant &var) Outputs the variant variable \a var to a view template. */ -QString TActionView::echo(const QVariant &var) -{ - if (var.userType() == QMetaType::QUrl) { - responsebody += var.toUrl().toString(QUrl::FullyEncoded); - } else { - responsebody += var.toString(); - } - return QString(); -} /*! + \fn QString TActionView::echo(const QVariantMap &map) Outputs the variantmap variable \a map to a view template. */ -QString TActionView::echo(const QVariantMap &map) -{ - responsebody += QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact); - return QString(); -} /*! + \fn QString TActionView::eh(const THtmlAttribute &attr) Outputs a escaped string of the HTML attribute \a attr to a view template. */ -QString TActionView::eh(const THtmlAttribute &attr) -{ - return echo(THttpUtility::htmlEscape(attr.toString().trimmed())); -} /*! \fn QString TActionView::eh(const QVariant &var) Outputs a escaped string of the variant variable \a var to a view template. */ -QString TActionView::eh(const QVariant &var) + +/*! + \fn QString TActionView::eh(const QVariantMap &map) + Outputs a escaped string of the variantmap variable \a map + to a view template. +*/ + + +QString TActionView::fromValue(int n, int base) +{ + return QString::number(n, base); +} + + +QString TActionView::fromValue(long n, int base) +{ + return QString::number(n, base); +} + + +QString TActionView::fromValue(ulong n, int base) +{ + return QString::number(n, base); +} + + +QString TActionView::fromValue(qlonglong n, int base) +{ + return QString::number(n, base); +} + + +QString TActionView::fromValue(qulonglong n, int base) +{ + return QString::number(n, base); +} + + +QString TActionView::fromValue(double d, char format, int precision) +{ + return QString::number(d, format, precision); +} + + +QString TActionView::fromValue(const QJsonObject &object) +{ + return QJsonDocument(object).toJson(QJsonDocument::Compact); +} + + +QString TActionView::fromValue(const QJsonArray &array) +{ + return QJsonDocument(array).toJson(QJsonDocument::Compact); +} + + +QString TActionView::fromValue(const QJsonDocument &doc) +{ + return doc.toJson(QJsonDocument::Compact); +} + + +QString TActionView::fromValue(const THtmlAttribute &attr) +{ + return attr.toString().trimmed(); +} + + +QString TActionView::fromValue(const QVariant &var) { if (var.userType() == QMetaType::QUrl) { - return echo(var.toUrl().toString(QUrl::FullyEncoded)); + return var.toUrl().toString(QUrl::FullyEncoded); } else { - return echo(THttpUtility::htmlEscape(var.toString())); + return var.toString(); } } -/*! - Outputs a escaped string of the variantmap variable \a map - to a view template. -*/ -QString TActionView::eh(const QVariantMap &map) + +QString TActionView::fromValue(const QVariantMap &map) { - return echo(THttpUtility::htmlEscape(QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact))); + return QJsonDocument::fromVariant(map).toJson(QJsonDocument::Compact); } /*! diff --git a/src/tactionview.h b/src/tactionview.h index 7886f53b5..0703b8453 100644 --- a/src/tactionview.h +++ b/src/tactionview.h @@ -23,6 +23,7 @@ class T_CORE_EXPORT TActionView : public QObject, public TActionHelper, public T QVariant variant(const QString &name) const; bool hasVariant(const QString &name) const; const QVariantMap &allVariants() const; + QVariantMap flashVariants() const; const TAbstractController *controller() const override; const THttpRequest &httpRequest() const; void reset(); @@ -62,6 +63,22 @@ class T_CORE_EXPORT TActionView : public QObject, public TActionHelper, public T QString responsebody; + static inline QString fromValue(const QString &str) { return str; } + static inline QString fromValue(const char *str) { return QString(str); } // using codecForCStrings() + static inline QString fromValue(const QByteArray &str) { return QString(str); } // using codecForCStrings() + static QString fromValue(int n, int base = 10); + static QString fromValue(long n, int base = 10); + static QString fromValue(ulong n, int base = 10); + static QString fromValue(qlonglong n, int base = 10); + static QString fromValue(qulonglong n, int base = 10); + static QString fromValue(double d, char format = 'g', int precision = 6); + static QString fromValue(const QJsonObject &object); + static QString fromValue(const QJsonArray &array); + static QString fromValue(const QJsonDocument &doc); + static QString fromValue(const THtmlAttribute &attr); + static QString fromValue(const QVariant &var); + static QString fromValue(const QVariantMap &map); + private: T_DISABLE_COPY(TActionView) T_DISABLE_MOVE(TActionView) @@ -113,66 +130,68 @@ inline const QVariantMap &TActionView::allVariants() const inline QString TActionView::echo(const QString &str) { - responsebody += str; + responsebody += fromValue(str); return QString(); } inline QString TActionView::echo(const char *str) { - responsebody += QString(str); // using codecForCStrings() + responsebody += fromValue(str); return QString(); } inline QString TActionView::echo(const QByteArray &str) { - responsebody += QString(str); // using codecForCStrings() + responsebody += fromValue(str); return QString(); } inline QString TActionView::echo(int n, int base) { - responsebody += QString::number(n, base); + responsebody += fromValue(n, base); return QString(); } inline QString TActionView::echo(long n, int base) { - responsebody += QString::number(n, base); + responsebody += fromValue(n, base); return QString(); } inline QString TActionView::echo(ulong n, int base) { - responsebody += QString::number(n, base); + responsebody += fromValue(n, base); return QString(); } inline QString TActionView::echo(qlonglong n, int base) { - responsebody += QString::number(n, base); + responsebody += fromValue(n, base); return QString(); } inline QString TActionView::echo(qulonglong n, int base) { - responsebody += QString::number(n, base); + responsebody += fromValue(n, base); return QString(); } inline QString TActionView::echo(double d, char format, int precision) { - responsebody += QString::number(d, format, precision); + responsebody += fromValue(d, format, precision); return QString(); } inline QString TActionView::echo(const QJsonObject &object) { - return echo(QJsonDocument(object)); + responsebody += fromValue(object); + return QString(); } inline QString TActionView::echo(const QJsonArray &array) { - return echo(QJsonDocument(array)); + responsebody += fromValue(array); + return QString(); } inline QString TActionView::echo(const QJsonDocument &doc) @@ -181,64 +200,97 @@ inline QString TActionView::echo(const QJsonDocument &doc) return QString(); } +inline QString TActionView::echo(const THtmlAttribute &attr) +{ + responsebody += fromValue(attr); + return QString(); +} + +inline QString TActionView::echo(const QVariant &var) +{ + responsebody += fromValue(var); + return QString(); +} + +inline QString TActionView::echo(const QVariantMap &map) +{ + responsebody += fromValue(map); + return QString(); +} + inline QString TActionView::eh(const QString &str) { - return echo(THttpUtility::htmlEscape(str)); + return echo(THttpUtility::htmlEscape(fromValue(str))); } inline QString TActionView::eh(const char *str) { - return echo(THttpUtility::htmlEscape(str)); + return echo(THttpUtility::htmlEscape(fromValue(str))); } inline QString TActionView::eh(const QByteArray &str) { - return echo(THttpUtility::htmlEscape(str)); + return echo(THttpUtility::htmlEscape(fromValue(str))); } inline QString TActionView::eh(int n, int base) { - return echo(THttpUtility::htmlEscape(QString::number(n, base))); + return echo(THttpUtility::htmlEscape(fromValue(n, base))); } inline QString TActionView::eh(long n, int base) { - return echo(THttpUtility::htmlEscape(QString::number(n, base))); + return echo(THttpUtility::htmlEscape(fromValue(n, base))); } inline QString TActionView::eh(ulong n, int base) { - return echo(THttpUtility::htmlEscape(QString::number(n, base))); + return echo(THttpUtility::htmlEscape(fromValue(n, base))); } inline QString TActionView::eh(qlonglong n, int base) { - return echo(THttpUtility::htmlEscape(QString::number(n, base))); + return echo(THttpUtility::htmlEscape(fromValue(n, base))); } inline QString TActionView::eh(qulonglong n, int base) { - return echo(THttpUtility::htmlEscape(QString::number(n, base))); + return echo(THttpUtility::htmlEscape(fromValue(n, base))); } inline QString TActionView::eh(double d, char format, int precision) { - return echo(THttpUtility::htmlEscape(QString::number(d, format, precision))); + return echo(THttpUtility::htmlEscape(fromValue(d, format, precision))); } inline QString TActionView::eh(const QJsonObject &object) { - return eh(QJsonDocument(object)); + return echo(THttpUtility::htmlEscape(fromValue(object))); } inline QString TActionView::eh(const QJsonArray &array) { - return eh(QJsonDocument(array)); + return echo(THttpUtility::htmlEscape(fromValue(array))); } inline QString TActionView::eh(const QJsonDocument &doc) { - return echo(THttpUtility::htmlEscape(doc.toJson(QJsonDocument::Compact))); + return echo(THttpUtility::htmlEscape(fromValue(doc))); +} + +inline QString TActionView::eh(const THtmlAttribute &attr) +{ + return echo(THttpUtility::htmlEscape(fromValue(attr))); +} + +inline QString TActionView::eh(const QVariant &var) +{ + return echo(THttpUtility::htmlEscape(fromValue(var))); +} + +inline QString TActionView::eh(const QVariantMap &map) +{ + return echo(THttpUtility::htmlEscape(fromValue(map))); } inline void TActionView::setController(TAbstractController *controller) diff --git a/src/test/mailmessage/main.cpp b/src/test/mailmessage/main.cpp index 200de6b18..742727c66 100644 --- a/src/test/mailmessage/main.cpp +++ b/src/test/mailmessage/main.cpp @@ -154,18 +154,31 @@ void TestMailMessage::dateTime_data() QTest::addColumn("result"); // Timezone +#if QT_VERSION >= 0x060700 + uint utc = QDateTime(QDate(2000,1,1), QTime(0,0,0), QTimeZone::UTC).toSecsSinceEpoch(); + uint local = QDateTime(QDate(2000,1,1), QTime(0,0,0), QTimeZone::LocalTime).toSecsSinceEpoch(); +#else uint utc = QDateTime(QDate(2000,1,1), QTime(0,0,0), Qt::UTC).toSecsSinceEpoch(); uint local = QDateTime(QDate(2000,1,1), QTime(0,0,0), Qt::LocalTime).toSecsSinceEpoch(); +#endif int offset = (utc - local) / 60; QString offsetStr = QString("%1%2%3") .arg(offset > 0 ? '+' : '-') .arg(qAbs(offset) / 60, 2, 10, QLatin1Char('0')) .arg(qAbs(offset) % 60, 2, 10, QLatin1Char('0')); +#if QT_VERSION >= 0x060700 + QTest::newRow("1") << QDateTime(QDate(2011,3,28), QTime(12,11,04), QTimeZone::LocalTime) << "Mon, 28 Mar 2011 12:11:04 " + offsetStr; + QTest::newRow("2") << QDateTime(QDate(2014,3,31), QTime( 1, 0, 0), QTimeZone::LocalTime) << "Mon, 31 Mar 2014 01:00:00 " + offsetStr; + QTest::newRow("3") << QDateTime(QDate(2011,3,28), QTime(12,11,04), QTimeZone::UTC) << "Mon, 28 Mar 2011 12:11:04 +0000"; + QTest::newRow("4") << QDateTime(QDate(2014,3,31), QTime( 1, 0, 0), QTimeZone::UTC) << "Mon, 31 Mar 2014 01:00:00 +0000"; +#else QTest::newRow("1") << QDateTime(QDate(2011,3,28), QTime(12,11,04), Qt::LocalTime) << "Mon, 28 Mar 2011 12:11:04 " + offsetStr; QTest::newRow("2") << QDateTime(QDate(2014,3,31), QTime( 1, 0, 0), Qt::LocalTime) << "Mon, 31 Mar 2014 01:00:00 " + offsetStr; - QTest::newRow("3") << QDateTime(QDate(2011,3,28), QTime(12,11,04), Qt::UTC) << "Mon, 28 Mar 2011 12:11:04 +0000"; - QTest::newRow("4") << QDateTime(QDate(2014,3,31), QTime( 1, 0, 0), Qt::UTC) << "Mon, 31 Mar 2014 01:00:00 +0000"; + QTest::newRow("3") << QDateTime(QDate(2011,3,28), QTime(12,11,04), Qt::UTC) << "Mon, 28 Mar 2011 12:11:04 +0000"; + QTest::newRow("4") << QDateTime(QDate(2014,3,31), QTime( 1, 0, 0), Qt::UTC) << "Mon, 31 Mar 2014 01:00:00 +0000"; + +#endif } void TestMailMessage::dateTime() diff --git a/src/test/sharedmemory/sharedmemory b/src/test/sharedmemory/sharedmemory new file mode 100755 index 000000000..a1bc2e4fc Binary files /dev/null and b/src/test/sharedmemory/sharedmemory differ diff --git a/src/test/sharedmemory/sharedmemory.cpp b/src/test/sharedmemory/sharedmemory.cpp new file mode 100644 index 000000000..d8ef80391 --- /dev/null +++ b/src/test/sharedmemory/sharedmemory.cpp @@ -0,0 +1,119 @@ +#include +#include "tsharedmemory.h" +#include "tglobal.h" +#include + + +namespace { +TSharedMemory sharedMomory("testshared.shm"); +} + + +class TestSharedMemory : public QObject +{ + Q_OBJECT +private slots: + void initTestCase(); + void cleanupTestCase(); + + void attach(); + void test1(); + void test2_data(); + void test2(); +}; + + +static QByteArray randomString(int length) +{ + constexpr auto ch = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-+/^=_[]@:;!#$%()~? \t\n"; + QByteArray ret; + int max = (int)strlen(ch) - 1; + + for (int i = 0; i < length; ++i) { + ret += ch[Tf::random(max)]; + } + return ret; +} + +void TestSharedMemory::initTestCase() +{ + sharedMomory.unlink(); + sharedMomory.create(100 * 1024 * 1024); +} + +void TestSharedMemory::cleanupTestCase() +{ + sharedMomory.unlink(); +} + +void TestSharedMemory::attach() +{ + bool res = sharedMomory.attach(); + Q_ASSERT(res); + Tf::msleep(1000); + res = sharedMomory.detach(); + Q_ASSERT(res); + Tf::msleep(1000); + res = sharedMomory.attach(); + Q_ASSERT(res); + res = sharedMomory.detach(); + Q_ASSERT(res); +} + +void TestSharedMemory::test1() +{ + sharedMomory.attach(); + QVERIFY(sharedMomory.size() == 100 * 1024 * 1024); + + // Lock and unlock + auto ptr = sharedMomory.data(); + QVERIFY(ptr != nullptr); + bool res = sharedMomory.lockForRead(); + Q_ASSERT(res); + res = sharedMomory.lockForWrite(); + Q_ASSERT(!res); + res = sharedMomory.unlock(); + Q_ASSERT(res); + res = sharedMomory.lockForWrite(); + Q_ASSERT(res); + res = sharedMomory.unlock(); + Q_ASSERT(res); + QVERIFY(ptr == sharedMomory.data()); +} + +void TestSharedMemory::test2_data() +{ + QTest::addColumn("string"); + + QTest::newRow("1") << QUuid::createUuid().toByteArray(); + QTest::newRow("2") << QUuid::createUuid().toByteArray(); + QTest::newRow("3") << randomString(128); + QTest::newRow("4") << randomString(256); + QTest::newRow("5") << randomString(1024); +} + +void TestSharedMemory::test2() +{ + QFETCH(QByteArray, string); + + bool res = sharedMomory.attach(); + Q_ASSERT(res); + auto ptr = sharedMomory.data(); + res = sharedMomory.lockForWrite(); + Q_ASSERT(res); + std::memcpy(ptr, string.data(), string.length()); + sharedMomory.unlock(); + res = sharedMomory.detach(); + Q_ASSERT(res); + + res = sharedMomory.attach(); + Q_ASSERT(res); + res = sharedMomory.lockForRead(); + Q_ASSERT(res); + int cmp = strncmp((char *)sharedMomory.data(), string.data(), string.length()); + Q_ASSERT(cmp == 0); + sharedMomory.unlock(); +} + +TF_TEST_MAIN(TestSharedMemory) +#include "sharedmemory.moc" diff --git a/src/test/sharedmemory/sharedmemory.pro b/src/test/sharedmemory/sharedmemory.pro new file mode 100644 index 000000000..b644dfd48 --- /dev/null +++ b/src/test/sharedmemory/sharedmemory.pro @@ -0,0 +1,3 @@ +include(../test.pri) +TARGET = sharedmemory +SOURCES = sharedmemory.cpp diff --git a/src/test/sharedmemoryhash/sharedmemoryhash.cpp b/src/test/sharedmemoryhash/sharedmemoryhash.cpp index 1fc146df3..c8b5837cd 100644 --- a/src/test/sharedmemoryhash/sharedmemoryhash.cpp +++ b/src/test/sharedmemoryhash/sharedmemoryhash.cpp @@ -196,7 +196,7 @@ void TestSharedMemoryHash::testAlloc4() smhash.set(key, "hoge", seconds); QVERIFY(!smhash.get(key).isEmpty()); // not empty qDebug() << "smhash.get(" << key << ") =" << smhash.get(key); - Tf::msleep(seconds * 1000 + 1); + Tf::msleep(seconds * 1000 + 20); auto val = smhash.get(key); QCOMPARE(val, QByteArray()); // timeout, empty } diff --git a/src/test/test.pro b/src/test/test.pro index 992efc95a..a481a0f37 100644 --- a/src/test/test.pro +++ b/src/test/test.pro @@ -5,7 +5,7 @@ SUBDIRS += mailmessage multipartformdata smtpmailer viewhelper paginator SUBDIRS += fieldnametovariablename rand urlrouter urlrouter2 SUBDIRS += buildtest stack queue forlist SUBDIRS += jscontext compression sqlitedb url malloc -SUBDIRS += sharedmemoryhash sharedmemorymutex +SUBDIRS += sharedmemory sharedmemoryhash sharedmemorymutex unix { SUBDIRS += redis memcached } diff --git a/src/test/testall.sh b/src/test/testall.sh index 778e42d70..fa2fce840 100755 --- a/src/test/testall.sh +++ b/src/test/testall.sh @@ -8,11 +8,12 @@ cd $WORKDIR for e in `ls -d *`; do if [ -f "$e/Makefile" ]; then - make -C $e clean + make -k -C $e clean fi done -[ -f Makefile ] && make distclean +[ -f Makefile ] && make -k distclean +rm -f Makefile qmake -r make -j8 diff --git a/src/test/viewhelper/viewhelper.cpp b/src/test/viewhelper/viewhelper.cpp index 9c034a0e9..e615a37f7 100644 --- a/src/test/viewhelper/viewhelper.cpp +++ b/src/test/viewhelper/viewhelper.cpp @@ -153,7 +153,7 @@ void TestViewHelper::stylesheetTag() attr.append("onclick", "return 0;"); attr.append("style", "none"); QString actual = view.styleSheetTag("hoge.png", attr); - QString result = ""; + QString result = ""; QCOMPARE(actual, result); } diff --git a/src/tglobal.h b/src/tglobal.h index ddbea826f..1dc8c010d 100644 --- a/src/tglobal.h +++ b/src/tglobal.h @@ -1,7 +1,7 @@ #pragma once -constexpr auto TF_VERSION_STR = "2.10.1"; -constexpr auto TF_VERSION_NUMBER = 0x020a01; -constexpr auto TF_SRC_REVISION = 3001; +constexpr auto TF_VERSION_STR = "2.11.0"; +constexpr auto TF_VERSION_NUMBER = 0x020b00; +constexpr auto TF_SRC_REVISION = 3013; #include #include diff --git a/src/tsharedmemory.h b/src/tsharedmemory.h index c5545dab4..2cb4002f4 100644 --- a/src/tsharedmemory.h +++ b/src/tsharedmemory.h @@ -3,6 +3,10 @@ #ifdef Q_OS_WIN #include #endif +#ifdef Q_OS_LINUX +#include +#endif + class T_CORE_EXPORT TSharedMemory #ifdef Q_OS_WIN @@ -27,6 +31,17 @@ class T_CORE_EXPORT TSharedMemory bool unlock(); private: + struct header_t { +#ifdef Q_OS_LINUX + pthread_rwlock_t rwlock; +#endif + uint lockcounter {0}; + }; + + bool initRwlock(header_t *header) const; + void releaseRwlock(header_t *header) const; + + #ifndef Q_OS_WIN QString _name; size_t _size {0}; diff --git a/src/tsharedmemory_linux.cpp b/src/tsharedmemory_linux.cpp new file mode 100644 index 000000000..43f8d2229 --- /dev/null +++ b/src/tsharedmemory_linux.cpp @@ -0,0 +1,98 @@ +/* Copyright (c) 2022, AOYAMA Kazuharu + * All rights reserved. + * + * This software may be used and distributed according to the terms of + * the New BSD License, which is incorporated herein by reference. + */ + +#include "tsharedmemory.h" +#include +#include +#include + + +bool TSharedMemory::initRwlock(header_t *header) const +{ + int res = -1; + pthread_rwlockattr_t attr; + + if (header) { + res = pthread_rwlockattr_init(&attr); + Q_ASSERT(!res); + res = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // Linux only + Q_ASSERT(!res); + if ((res = pthread_rwlock_init(&header->rwlock, &attr)) < 0) { + tSystemError("pthread_rwlock_init failed: {}", (const char*)strerror(errno)); + } + } + return !res; +} + + +void TSharedMemory::releaseRwlock(header_t *header) const +{ + if (header) { + pthread_rwlock_destroy(&header->rwlock); + } +} + + +bool TSharedMemory::lockForRead() +{ + struct timespec timeout; + header_t *header = (header_t *)_ptr; + + while (pthread_rwlock_tryrdlock(&header->rwlock) == EBUSY) { + uint cnt = header->lockcounter; + timespec_get(&timeout, TIME_UTC); + timeout.tv_sec += 1; // 1sec + + int res = pthread_rwlock_timedrdlock(&header->rwlock, &timeout); + if (!res) { + // success + break; + } else { + if (res == ETIMEDOUT && header->lockcounter == cnt) { + // resets rwlock object + initRwlock(header); + } + } + } + header->lockcounter++; + return true; +} + + +bool TSharedMemory::lockForWrite() +{ + struct timespec timeout; + header_t *header = (header_t *)_ptr; + + while (pthread_rwlock_trywrlock(&header->rwlock) == EBUSY) { + uint cnt = header->lockcounter; + timespec_get(&timeout, TIME_UTC); + timeout.tv_sec += 1; // 1sec + + int res = pthread_rwlock_timedwrlock(&header->rwlock, &timeout); + if (!res) { + // success + break; + } else { + if (res == ETIMEDOUT && header->lockcounter == cnt) { + // resets rwlock object + releaseRwlock(header); + initRwlock(header); + } + } + } + header->lockcounter++; + return true; +} + + +bool TSharedMemory::unlock() +{ + header_t *header = (header_t *)_ptr; + pthread_rwlock_unlock(&header->rwlock); + return true; +} diff --git a/src/tsharedmemory_macx.cpp b/src/tsharedmemory_macx.cpp new file mode 100644 index 000000000..1564b8b12 --- /dev/null +++ b/src/tsharedmemory_macx.cpp @@ -0,0 +1,120 @@ +/* Copyright (c) 2025, AOYAMA Kazuharu + * All rights reserved. + * + * This software may be used and distributed according to the terms of + * the New BSD License, which is incorporated herein by reference. + */ + +#include "tsharedmemory.h" +#include +#include +#include // O_CREAT, O_EXCL +#include +#include +#include + + +static inline QByteArray semaphoreName(const QString &name) +{ + return (name.startsWith("/") ? "" : "/") + name.toLatin1() + "_global_lock"; +} + + +bool TSharedMemory::initRwlock(header_t *) const +{ + sem_t *sem = sem_open(semaphoreName(_name).data(), O_CREAT | O_EXCL, 0644, 1); + if (sem == SEM_FAILED) { + if (errno == EEXIST) { + tSystemError("sem_open semaphore already exists: {}", semaphoreName(_name)); + return true; + } else { + tSystemError("sem_open (init) failed: {}", (const char*)strerror(errno)); + return false; + } + } + + tSystemDebug("Semaphore initialized"); + sem_close(sem); + return true; +} + + +void TSharedMemory::releaseRwlock(header_t *) const +{ + sem_unlink(semaphoreName(_name).data()); +} + + +bool TSharedMemory::lockForRead() +{ + // Use semaphore as a lock mechanism between processes. + // PTHREAD_PROCESS_SHARED attribute is not supported on macos. + + sem_t *sem = SEM_FAILED; + header_t *header = (header_t *)_ptr; + uint cnt = header->lockcounter; + + auto sem_timedwait = [&](int msecs) { + sem = sem_open(semaphoreName(_name).data(), 0); + if (sem == SEM_FAILED) { + tSystemError("sem_open (lock) failed: {}", (const char*)strerror(errno)); + return -1; + } + + auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(msecs); + while (std::chrono::steady_clock::now() < deadline) { + if (sem_trywait(sem) < 0) { + if (errno == EAGAIN) { + std::this_thread::sleep_for(std::chrono::milliseconds(20)); + continue; + } else { + return -1; // error + } + } else { + header->lockcounter++; + return 0; // lock success + } + } + tSystemError("sem_wait (lock) timed out: {}", semaphoreName(_name)); + return 1; // timeout + }; + + int res; + while ((res = sem_timedwait(1000)) == 1) { + if (header->lockcounter == cnt) { // timeout and same counter + releaseRwlock(header); + initRwlock(header); + } + } + + if (sem != SEM_FAILED) { + sem_close(sem); + } + return !res; +} + + +bool TSharedMemory::lockForWrite() +{ + // Same as lockForRead() + return lockForRead(); +} + + +bool TSharedMemory::unlock() +{ + sem_t *sem = sem_open(semaphoreName(_name).data(), 0); + if (sem == SEM_FAILED) { + tSystemError("sem_open (unlock) failed: {}", (const char*)strerror(errno)); + return false; + } + + if (sem_post(sem) < 0) { + tSystemError("sem_post failed: {}", (const char*)strerror(errno)); + sem_close(sem); + return false; + } + + sem_close(sem); + return true; +} diff --git a/src/tsharedmemory_qt.cpp b/src/tsharedmemory_qt.cpp index 81f21bbaa..ca990f55c 100644 --- a/src/tsharedmemory_qt.cpp +++ b/src/tsharedmemory_qt.cpp @@ -103,3 +103,13 @@ bool TSharedMemory::unlock() { return QSharedMemory::unlock(); } + + +bool TSharedMemory::initRwlock(header_t *) const +{ + return true; +} + + +void TSharedMemory::releaseRwlock(header_t *) const +{} diff --git a/src/tsharedmemory_unix.cpp b/src/tsharedmemory_unix.cpp index 55c0a4a54..4d43e92be 100644 --- a/src/tsharedmemory_unix.cpp +++ b/src/tsharedmemory_unix.cpp @@ -12,27 +12,8 @@ #include #include #include -#include #include -struct header_t { - pthread_rwlock_t rwlock; - uint lockcounter {0}; -}; - - -static void rwlock_init(pthread_rwlock_t *rwlock) -{ - pthread_rwlockattr_t attr; - - int res = pthread_rwlockattr_init(&attr); - Q_ASSERT(!res); - res = pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); - Q_ASSERT(!res); - res = pthread_rwlock_init(rwlock, &attr); - Q_ASSERT(!res); -} - TSharedMemory::TSharedMemory(const QString &name) : _name(name) @@ -52,17 +33,12 @@ TSharedMemory::~TSharedMemory() bool TSharedMemory::create(size_t size) { - static const header_t INIT_HEADER = []() { - static header_t header; - rwlock_init(&header.rwlock); - return header; - }(); - if (_ptr || size == 0 || _name.isEmpty()) { return false; } struct stat st; + header_t *header = nullptr; // Creates shared memory _fd = shm_open(qUtf8Printable(_name), O_CREAT | O_RDWR | O_CLOEXEC, S_IRUSR | S_IWUSR); @@ -90,7 +66,9 @@ bool TSharedMemory::create(size_t size) goto error; } - std::memcpy(_ptr, &INIT_HEADER, sizeof(INIT_HEADER)); + header = new (_ptr) header_t{}; + initRwlock(header); + _size = size; tSystemDebug("SharedMemory created. name:{} size:{}", qUtf8Printable(_name), (qulonglong)_size); return true; @@ -111,6 +89,7 @@ bool TSharedMemory::create(size_t size) void TSharedMemory::unlink() { + releaseRwlock((header_t *)_ptr); shm_unlink(qUtf8Printable(_name)); tSystemDebug("SharedMemory unlinked. name:{}", qUtf8Printable(_name)); } @@ -176,6 +155,7 @@ bool TSharedMemory::detach() _ptr = nullptr; _size = 0; + tSystemDebug("SharedMemory detached. name:{}", qUtf8Printable(_name)); return true; } @@ -202,73 +182,3 @@ size_t TSharedMemory::size() const { return _size; } - - -bool TSharedMemory::lockForRead() -{ -#ifdef Q_OS_LINUX - struct timespec timeout; - header_t *header = (header_t *)_ptr; - - while (pthread_rwlock_tryrdlock(&header->rwlock) == EBUSY) { - uint cnt = header->lockcounter; - timespec_get(&timeout, TIME_UTC); - timeout.tv_sec += 1; // 1sec - - int res = pthread_rwlock_timedrdlock(&header->rwlock, &timeout); - if (!res) { - // success - break; - } else { - if (res == ETIMEDOUT && header->lockcounter == cnt) { - // resets rwlock object - rwlock_init(&header->rwlock); - } - } - } - header->lockcounter++; - return true; -#else - header_t *header = (header_t *)_ptr; - return pthread_rwlock_rdlock(&header->rwlock) == 0; -#endif -} - - -bool TSharedMemory::lockForWrite() -{ -#ifdef Q_OS_LINUX - struct timespec timeout; - header_t *header = (header_t *)_ptr; - - while (pthread_rwlock_trywrlock(&header->rwlock) == EBUSY) { - uint cnt = header->lockcounter; - timespec_get(&timeout, TIME_UTC); - timeout.tv_sec += 1; // 1sec - - int res = pthread_rwlock_timedwrlock(&header->rwlock, &timeout); - if (!res) { - // success - break; - } else { - if (res == ETIMEDOUT && header->lockcounter == cnt) { - // resets rwlock object - rwlock_init(&header->rwlock); - } - } - } - header->lockcounter++; - return true; -#else - header_t *header = (header_t *)_ptr; - return pthread_rwlock_wrlock(&header->rwlock) == 0; -#endif -} - - -bool TSharedMemory::unlock() -{ - header_t *header = (header_t *)_ptr; - pthread_rwlock_unlock(&header->rwlock); - return true; -} diff --git a/src/tsharedmemorykvs.cpp b/src/tsharedmemorykvs.cpp index f7106e637..c9d22249b 100644 --- a/src/tsharedmemorykvs.cpp +++ b/src/tsharedmemorykvs.cpp @@ -83,16 +83,13 @@ TSharedMemoryKvs::~TSharedMemoryKvs() */ bool TSharedMemoryKvs::initialize(const QString &name, const QString &options) { - hash_header_t hashheader; - TSharedMemoryKvsDriver::initialize(name, options); TSharedMemoryKvsDriver driver; - driver.open(name, QString(), QString(), QString(), 0, options); - hash_header_t *header = (hash_header_t *)driver.origin(); - void *ptr = driver.malloc(sizeof(hashheader)); - Q_ASSERT(ptr == header); - std::memcpy(header, &hashheader, sizeof(hashheader)); + driver.open(name, QString(), QString(), QString(), 0, options); + void *ptr = driver.malloc(sizeof(hash_header_t)); + hash_header_t *header = new (ptr) hash_header_t{}; // Initialize with the default constructor + Q_ASSERT(header == (hash_header_t *)driver.origin()); ptr = driver.calloc(header->tableSize, sizeof(uintptr_t)); header->setHashg(ptr); Q_ASSERT(ptr); diff --git a/src/ttextview.h b/src/ttextview.h index 57b163b9a..e188f35f8 100644 --- a/src/ttextview.h +++ b/src/ttextview.h @@ -33,4 +33,3 @@ inline QString TTextView::toString() { return viewText; } - diff --git a/src/tviewhelper.cpp b/src/tviewhelper.cpp index 647311f2f..50136fb04 100644 --- a/src/tviewhelper.cpp +++ b/src/tviewhelper.cpp @@ -12,6 +12,7 @@ #include #include #include +#include /*! @@ -539,9 +540,7 @@ QString TViewHelper::styleSheetTag(const QString &src, const THtmlAttribute &att QString TViewHelper::styleSheetTag(const QString &src, bool withTimestamp, const THtmlAttribute &attributes) const { THtmlAttribute attr = attributes; - if (!attr.contains("type")) { - attr.prepend("type", "text/css"); - } + if (!attr.contains("rel")) { attr.prepend("rel", "stylesheet"); } @@ -549,6 +548,46 @@ QString TViewHelper::styleSheetTag(const QString &src, bool withTimestamp, const return selfClosingTag("link", attr); } + +static QJsonObject readManifest(const QString &path) +{ + QJsonObject json; + QFile manifest(path); + + if (manifest.open(QIODevice::ReadOnly | QIODevice::Text)) { + QJsonParseError error; + QJsonDocument jsonDoc = QJsonDocument::fromJson(manifest.readAll(), &error); + manifest.close(); + + if (error.error == QJsonParseError::NoError) { + json = jsonDoc.object(); + } else { + tSystemWarn("Manifest parse error [{}]", qUtf8Printable(path)); + } + } else { + tSystemWarn("Manifest file not found [{}]", qUtf8Printable(path)); + } + return json; +} + + +QString TViewHelper::viteStyleSheetTag(const QString &src, const THtmlAttribute &attributes) const +{ + constexpr auto MANIFEST_PATH = ".vite/manifest.json"; + static QJsonObject manifestJson; + QString tag; + + if (manifestJson.isEmpty()) { + manifestJson = readManifest(Tf::app()->publicPath() + MANIFEST_PATH); + } + + auto array = manifestJson.value(src).toObject().value("css").toArray(); + for (auto item : array) { + tag += styleSheetTag("/" + item.toString(), false, attributes); + } + return tag; +} + /*! Creates a \ script tag with src=\a "src". The \a src must be one of URL, a absolute path or a relative path. If \a src is a @@ -574,6 +613,27 @@ QString TViewHelper::scriptTag(const QString &src, bool withTimestamp, const THt return tag("script", attr, QString()); } + +QString TViewHelper::viteScriptTag(const QString &name, const THtmlAttribute &attributes) const +{ + constexpr auto MANIFEST_PATH = ".vite/manifest.json"; + static QJsonObject manifestJson; + + if (manifestJson.isEmpty()) { + manifestJson = readManifest(Tf::app()->publicPath() + MANIFEST_PATH); + } + + QString src = manifestJson.value(name).toObject().value("file").toString(); + + if (src.isEmpty()) { + tSystemWarn("'{}' not found in manifest [{}]", qUtf8Printable(name), MANIFEST_PATH); + src = name; + } else { + src = "/" + src; + } + return scriptTag(src, false, attributes); +} + /*! Creates and returns a THtmlAttribute object with \a key =\a "value". */ @@ -687,6 +747,12 @@ QString TViewHelper::srcPath(const QString &src, const QString &dir, bool withTi } +QString TViewHelper::databaseEnvironment() const +{ + return Tf::app()->databaseEnvironment(); +} + + void TViewHelper::clear() { endTags.clear(); diff --git a/src/tviewhelper.h b/src/tviewhelper.h index de5dc0743..5f6f0e264 100644 --- a/src/tviewhelper.h +++ b/src/tviewhelper.h @@ -168,10 +168,14 @@ class T_CORE_EXPORT TViewHelper { QString styleSheetTag(const QString &src, bool withTimestamp = true, const THtmlAttribute &attributes = THtmlAttribute()) const; + QString viteStyleSheetTag(const QString &src, const THtmlAttribute &attributes = THtmlAttribute()) const; + QString scriptTag(const QString &src, const THtmlAttribute &attributes) const; QString scriptTag(const QString &src, bool withTimestamp = true, const THtmlAttribute &attributes = THtmlAttribute()) const; + QString viteScriptTag(const QString &name, const THtmlAttribute &attributes) const; + QString tag(const QString &name, const THtmlAttribute &attributes); QString tag(const QString &name, const THtmlAttribute &attributes, bool selfClose); @@ -197,6 +201,8 @@ class T_CORE_EXPORT TViewHelper { THtmlAttribute a(const QString &key, const QString &value) const; THtmlAttribute a() const { return THtmlAttribute(); } + QString databaseEnvironment() const; + void clear(); protected: diff --git a/tfbase.pri b/tfbase.pri index 67384b496..52b55c8fb 100644 --- a/tfbase.pri +++ b/tfbase.pri @@ -1,4 +1,4 @@ TF_VER_MAJ=2 -TF_VER_MIN=10 -TF_VER_PAT=1 +TF_VER_MIN=11 +TF_VER_PAT=0 TF_VERSION=$${TF_VER_MAJ}.$${TF_VER_MIN}.$${TF_VER_PAT} diff --git a/tools/tfmanager/tfmanager.pro b/tools/tfmanager/tfmanager.pro index 431f8d0d1..8b806b447 100644 --- a/tools/tfmanager/tfmanager.pro +++ b/tools/tfmanager/tfmanager.pro @@ -5,6 +5,8 @@ CONFIG += console CONFIG -= app_bundle QT += network QT -= gui +MOC_DIR = .obj/ +OBJECTS_DIR = .obj/ # C++ Standards Support CONFIG += c++20 diff --git a/tools/tfserver/tfserver.pro b/tools/tfserver/tfserver.pro index 696af8b91..2121ac22b 100644 --- a/tools/tfserver/tfserver.pro +++ b/tools/tfserver/tfserver.pro @@ -5,6 +5,8 @@ CONFIG += console CONFIG -= app_bundle QT += network sql xml qml QT -= gui +MOC_DIR = .obj/ +OBJECTS_DIR = .obj/ # C++ Standards Support CONFIG += c++20 diff --git a/tools/tmake/erbparser.cpp b/tools/tmake/erbparser.cpp index 00e0b142f..5126ec352 100644 --- a/tools/tmake/erbparser.cpp +++ b/tools/tmake/erbparser.cpp @@ -161,15 +161,17 @@ void ErbParser::parsePercentTag() QPair p = parseEndPercentTag(); if (!p.first.isEmpty()) { if (p.second.isEmpty()) { - srcCode += QLatin1String("responsebody += QVariant("); + srcCode += QLatin1String("echo("); srcCode += semicolonTrim(p.first); - srcCode += QLatin1String(").toString();\n"); + srcCode += QLatin1String(");\n"); } else { - srcCode += QLatin1String("{ QString ___s = QVariant("); + srcCode += QLatin1String("{ QString ___s(fromValue("); srcCode += semicolonTrim(p.first); - srcCode += QLatin1String(").toString(); responsebody += (___s.isEmpty()) ? QVariant("); + srcCode += QLatin1String(")); if (___s.isEmpty()) { echo("); srcCode += semicolonTrim(p.second); - srcCode += QLatin1String(").toString() : ___s; }\n"); + srcCode += QLatin1String("); } else { echo("); + srcCode += semicolonTrim(p.first); + srcCode += QLatin1String("); }}\n"); } } @@ -178,15 +180,17 @@ void ErbParser::parsePercentTag() QPair p = parseEndPercentTag(); if (!p.first.isEmpty()) { if (p.second.isEmpty()) { - srcCode += QLatin1String("responsebody += THttpUtility::htmlEscape("); + srcCode += QLatin1String("eh("); srcCode += semicolonTrim(p.first); srcCode += QLatin1String(");\n"); } else { - srcCode += QLatin1String("{ QString ___s = QVariant("); + srcCode += QLatin1String("{ QString ___s(fromValue("); srcCode += semicolonTrim(p.first); - srcCode += QLatin1String(").toString(); responsebody += (___s.isEmpty()) ? THttpUtility::htmlEscape("); + srcCode += QLatin1String(")); if (___s.isEmpty()) { eh("); srcCode += semicolonTrim(p.second); - srcCode += QLatin1String(") : THttpUtility::htmlEscape(___s); }\n"); + srcCode += QLatin1String("); } else { eh("); + srcCode += semicolonTrim(p.first); + srcCode += QLatin1String("); }}\n"); } } } diff --git a/tools/tmake/main.cpp b/tools/tmake/main.cpp index 3e00bd68a..b60bc73ef 100644 --- a/tools/tmake/main.cpp +++ b/tools/tmake/main.cpp @@ -99,7 +99,7 @@ int main(int argc, char *argv[]) ViewConverter conv(viewDir, outputDir, createProFile); QString templateSystem = devSetting.value("TemplateSystem").toString(); if (templateSystem.isEmpty()) { - templateSystem = appSetting.value("TemplateSystem", "Erb").toString(); + templateSystem = appSetting.value("TemplateSystem", "erb").toString(); } res = conv.convertView(templateSystem); diff --git a/tools/tmake/test/tmaketest.cpp b/tools/tmake/test/tmaketest.cpp index 2a6668c24..fb2918528 100644 --- a/tools/tmake/test/tmaketest.cpp +++ b/tools/tmake/test/tmaketest.cpp @@ -210,11 +210,11 @@ void TestTfpconverter::erbparse_data() QTest::newRow("7") << "Hello <% QString s(\"%>\"); %>" << " responsebody += QStringLiteral(\"Hello \");\n QString s(\"%>\");\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("8") << "Hello <%== vvv %>" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += QVariant(vvv).toString();\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n echo(vvv);\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("9") << "Hello <%= vvv %> \n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\" \\n\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\" \\n\");\n"; QTest::newRow("10") << "Hello <%= vvv; -%> \n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("11") << "Hello <% int i; -%> \r\n " << " responsebody += QStringLiteral(\"Hello \");\n int i;\n responsebody += QStringLiteral(\" \");\n"; QTest::newRow("12") << "Hello <% int i; %> \r\n" @@ -222,9 +222,9 @@ void TestTfpconverter::erbparse_data() QTest::newRow("13") << "Hello ... \r\n" << " responsebody += QStringLiteral(\"Hello ... \\r\\n\");\n"; QTest::newRow("14") << "Hello <%= vvv; +%> \n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\" \\n\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\" \\n\");\n"; QTest::newRow("15") << "Hello <%= vvv; +%>\r\n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\"\\r\\n\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\"\\r\\n\");\n"; QTest::newRow("16") << "Hello <% int i; +%> \r\n " << " responsebody += QStringLiteral(\"Hello \");\n int i;\n responsebody += QStringLiteral(\" \\r\\n \");\n"; @@ -238,16 +238,16 @@ void TestTfpconverter::erbparse_data() QTest::newRow("19") << "<%# comment. %|% 33 %>" << " responsebody += QStringLiteral(\"\");\n /* comment. */\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("20") << "<%= number %|% 33 %>" - << " responsebody += QStringLiteral(\"\");\n { QString ___s = QVariant(number).toString(); responsebody += (___s.isEmpty()) ? THttpUtility::htmlEscape(33) : THttpUtility::htmlEscape(___s); }\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"\");\n { QString ___s(fromValue(number)); if (___s.isEmpty()) { eh(33); } else { eh(number); }}\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("21") << "<%== number %|% 33 %>" - << " responsebody += QStringLiteral(\"\");\n { QString ___s = QVariant(number).toString(); responsebody += (___s.isEmpty()) ? QVariant(33).toString() : ___s; }\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"\");\n { QString ___s(fromValue(number)); if (___s.isEmpty()) { echo(33); } else { echo(number); }}\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("22") << "<%=$number %|% 33 %>" << " responsebody += QStringLiteral(\"\");\n tehex2(number, (33));\n responsebody += QStringLiteral(\"\");\n"; // Irregular pattern QTest::newRow("23") << "<%==$number %|% 33 -%>\t\n" << " responsebody += QStringLiteral(\"\");\n techoex2(number, (33));\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("24") << "<%== \" %|%\" %|% \"%|%\" -%> \t \n" - << " responsebody += QStringLiteral(\"\");\n { QString ___s = QVariant(\" %|%\").toString(); responsebody += (___s.isEmpty()) ? QVariant(\"%|%\").toString() : ___s; }\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"\");\n { QString ___s(fromValue(\" %|%\")); if (___s.isEmpty()) { echo(\"%|%\"); } else { echo(\" %|%\"); }}\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("25") << "" << " responsebody += QStringLiteral(\"\");\n"; @@ -292,13 +292,13 @@ void TestTfpconverter::erbparseStrong_data() QTest::newRow("7") << "Hello <% QString s(\"%>\"); %>" << " responsebody += QStringLiteral(\"Hello \");\n QString s(\"%>\");\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("8") << "Hello <%== vvv %>" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += QVariant(vvv).toString();\n responsebody += QStringLiteral(\"\");\n"; - QTest::newRow("9") << "Hello <%= vvv %> \n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\"\\n\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n echo(vvv);\n responsebody += QStringLiteral(\"\");\n"; + QTest::newRow("9-1") << "Hello <%= vvv %> \n" + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\"\\n\");\n"; QTest::newRow("9-2") << "Hello <%= vvv %>縲\n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += tr(\"縲\\n\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += tr(\"縲\\n\");\n"; QTest::newRow("10") << "Hello <%= vvv; -%> \n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("11") << " Hello <% int i; -%> \r\n " << " responsebody += QStringLiteral(\"Hello \");\n int i;\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("12") << "Hello <% int i; %> \r\n" @@ -306,9 +306,9 @@ void TestTfpconverter::erbparseStrong_data() QTest::newRow("13") << "Hello ... \t\r\n\t" << " responsebody += QStringLiteral(\"Hello ...\\n\");\n"; QTest::newRow("14") << "Hello <%= vvv; +%> \n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\"\\n\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\"\\n\");\n"; QTest::newRow("15") << "Hello <%= vvv; +%>\t\r\n" - << " responsebody += QStringLiteral(\"Hello \");\n responsebody += THttpUtility::htmlEscape(vvv);\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"Hello \");\n eh(vvv);\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("16") << " \tHello <% int i; +%> \r\n " << " responsebody += QStringLiteral(\"Hello \");\n int i;\n responsebody += QStringLiteral(\"\\n\");\n"; @@ -322,16 +322,16 @@ void TestTfpconverter::erbparseStrong_data() QTest::newRow("19") << "<%# comment. %|% 33 %>" << " responsebody += QStringLiteral(\"\");\n /* comment. */\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("20") << "<%= number %|% 33 %>" - << " responsebody += QStringLiteral(\"\");\n { QString ___s = QVariant(number).toString(); responsebody += (___s.isEmpty()) ? THttpUtility::htmlEscape(33) : THttpUtility::htmlEscape(___s); }\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"\");\n { QString ___s(fromValue(number)); if (___s.isEmpty()) { eh(33); } else { eh(number); }}\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("21") << "<%== number %|% 33 %>" - << " responsebody += QStringLiteral(\"\");\n { QString ___s = QVariant(number).toString(); responsebody += (___s.isEmpty()) ? QVariant(33).toString() : ___s; }\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"\");\n { QString ___s(fromValue(number)); if (___s.isEmpty()) { echo(33); } else { echo(number); }}\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("22") << "<%=$number %|% 33 %>" << " responsebody += QStringLiteral(\"\");\n tehex2(number, (33));\n responsebody += QStringLiteral(\"\");\n"; // Irregular pattern QTest::newRow("23") << "<%==$number %|% 33 -%>\t\n" << " responsebody += QStringLiteral(\"\");\n techoex2(number, (33));\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("24") << "<%== \" %|%\" %|% \"%|%\" -%> \t \n" - << " responsebody += QStringLiteral(\"\");\n { QString ___s = QVariant(\" %|%\").toString(); responsebody += (___s.isEmpty()) ? QVariant(\"%|%\").toString() : ___s; }\n responsebody += QStringLiteral(\"\");\n"; + << " responsebody += QStringLiteral(\"\");\n { QString ___s(fromValue(\" %|%\")); if (___s.isEmpty()) { echo(\"%|%\"); } else { echo(\" %|%\"); }}\n responsebody += QStringLiteral(\"\");\n"; QTest::newRow("25") << "" << " responsebody += QStringLiteral(\"\");\n"; diff --git a/tools/tmake/tmake.pro b/tools/tmake/tmake.pro index f7a2a8984..2da9cce0b 100644 --- a/tools/tmake/tmake.pro +++ b/tools/tmake/tmake.pro @@ -4,6 +4,8 @@ VERSION = 2.0.0 CONFIG += console CONFIG -= app_bundle QT -= gui +MOC_DIR = .obj/ +OBJECTS_DIR = .obj/ # C++ Standards Support CONFIG += c++20 diff --git a/tools/tspawn/erbgenerator.h b/tools/tspawn/erbgenerator.h index 168cf8a4c..ad05e3385 100644 --- a/tools/tspawn/erbgenerator.h +++ b/tools/tspawn/erbgenerator.h @@ -1,16 +1,17 @@ #pragma once #include "global.h" +#include "generator.h" #include #include #include #include -class ErbGenerator { +class ErbGenerator : public Generator { public: ErbGenerator(const QString &view, const QList> &fields, int pkIdx, int autoValIdx); virtual ~ErbGenerator() {} - bool generate(const QString &dstDir) const; + bool generate(const QString &dstDir) const override; protected: virtual QString indexTemplate() const; diff --git a/tools/tspawn/generator.h b/tools/tspawn/generator.h new file mode 100644 index 000000000..c18f9aac3 --- /dev/null +++ b/tools/tspawn/generator.h @@ -0,0 +1,9 @@ +#pragma once +#include + + +class Generator { +public: + virtual ~Generator() = default; + virtual bool generate(const QString &dstDir) const = 0; +}; diff --git a/tools/tspawn/main.cpp b/tools/tspawn/main.cpp index a03bac667..74908b3a3 100644 --- a/tools/tspawn/main.cpp +++ b/tools/tspawn/main.cpp @@ -17,6 +17,8 @@ #include "otamagenerator.h" #include "vueservicegenerator.h" #include "vueerbgenerator.h" +#include "vitevuegenerator.h" +#include "vitevueservicegenerator.h" #include "projectfilegenerator.h" #include "sqlobjgenerator.h" #include "tableschema.h" @@ -26,6 +28,7 @@ #include "apicontrollergenerator.h" #include "apiservicegenerator.h" #include +#include #include #ifndef Q_CC_MSVC #include @@ -38,8 +41,8 @@ #define D_VIEWS QLatin1String("views/") #define D_HELPERS QLatin1String("helpers/") -enum SubCommand { - Invalid = 0, +enum class SubCommand { + Invalid, Help, New, Controller, @@ -62,45 +65,60 @@ enum SubCommand { ShowCollections, }; -const QMap subCommands = { - {"-h", Help}, - {"--help", Help}, - {"new", New}, - {"n", New}, - {"controller", Controller}, - {"c", Controller}, - {"model", Model}, - {"m", Model}, - {"helper", Helper}, - {"h", Helper}, - {"usermodel", UserModel}, - {"u", UserModel}, - {"sqlobject", SqlObject}, - {"o", SqlObject}, - {"mongoscaffold", MongoScaffold}, - {"ms", MongoScaffold}, - //{"updatemodel", UpdateModel}, - //{"um", UpdateModel}, - {"mongomodel", MongoModel}, - {"mm", MongoModel}, - {"websocket", WebSocketEndpoint}, - {"w", WebSocketEndpoint}, - {"api", Api}, - {"a", Api}, - {"validator", Validator}, - {"v", Validator}, - {"mailer", Mailer}, - {"l", Mailer}, - {"scaffold", Scaffold}, - {"s", Scaffold}, - {"delete", Delete}, - {"d", Delete}, - {"remove", Delete}, - {"r", Delete}, - {"--show-drivers", ShowDrivers}, - {"--show-driver-path", ShowDriverPath}, - {"--show-tables", ShowTables}, - {"--show-collections", ShowCollections}, +enum class TemplateSystem { + Invalid, + Erb, + Otama, + Vue, + Vite_Vue, +}; + +const QMap subCommands = { + {"-h", SubCommand::Help}, + {"--help", SubCommand::Help}, + {"new", SubCommand::New}, + {"n", SubCommand::New}, + {"controller", SubCommand::Controller}, + {"c", SubCommand::Controller}, + {"model", SubCommand::Model}, + {"m", SubCommand::Model}, + {"helper", SubCommand::Helper}, + {"h", SubCommand::Helper}, + {"usermodel", SubCommand::UserModel}, + {"u", SubCommand::UserModel}, + {"sqlobject", SubCommand::SqlObject}, + {"o", SubCommand::SqlObject}, + {"mongoscaffold", SubCommand::MongoScaffold}, + {"ms", SubCommand::MongoScaffold}, + //{"updatemodel", SubCommand::UpdateModel}, + //{"um", SubCommand::UpdateModel}, + {"mongomodel", SubCommand::MongoModel}, + {"mm", SubCommand::MongoModel}, + {"websocket", SubCommand::WebSocketEndpoint}, + {"w", SubCommand::WebSocketEndpoint}, + {"api", SubCommand::Api}, + {"a", SubCommand::Api}, + {"validator", SubCommand::Validator}, + {"v", SubCommand::Validator}, + {"mailer", SubCommand::Mailer}, + {"l", SubCommand::Mailer}, + {"scaffold", SubCommand::Scaffold}, + {"s", SubCommand::Scaffold}, + {"delete", SubCommand::Delete}, + {"d", SubCommand::Delete}, + {"remove", SubCommand::Delete}, + {"r", SubCommand::Delete}, + {"--show-drivers", SubCommand::ShowDrivers}, + {"--show-driver-path", SubCommand::ShowDriverPath}, + {"--show-tables", SubCommand::ShowTables}, + {"--show-collections", SubCommand::ShowCollections}, +}; + +const QMap templateSystemMap = { + {"erb", TemplateSystem::Erb}, + {"otama", TemplateSystem::Otama}, + {"vue", TemplateSystem::Vue}, + {"vite+vue", TemplateSystem::Vite_Vue}, }; const QStringList subDirs = { @@ -178,14 +196,16 @@ const QStringList filePaths = { L("helpers/CMakeLists.txt"), }; +namespace { const QString appIni = QLatin1String("config/application.ini"); const QString devIni = QLatin1String("config/development.ini"); -static QSettings appSettings(appIni, QSettings::IniFormat); -static QSettings devSettings(devIni, QSettings::IniFormat); -static QString templateSystem; +QSettings appSettings(appIni, QSettings::IniFormat); +QSettings devSettings(devIni, QSettings::IniFormat); +TemplateSystem templateSystem = TemplateSystem::Invalid; + -static void usage() +void usage() { std::printf("usage: tspawn [args]\n\n" "Type 'tspawn --show-drivers' to show all the available database drivers for Qt.\n" @@ -193,7 +213,7 @@ static void usage() "Type 'tspawn --show-tables' to show all tables to user in the setting of 'dev'.\n" "Type 'tspawn --show-collections' to show all collections in the MongoDB.\n\n" "Available subcommands:\n" - " new (n) \n" + " new (n) [--template [erb | otama | vue | vite+vue]]\n" " scaffold (s) [model-name]\n" " controller (c) action [action ...]\n" " model (m) [model-name]\n" @@ -206,11 +226,12 @@ static void usage() " api (a) \n" " validator (v) \n" " mailer (l) action [action ...]\n" - " delete (d) \n"); + " delete (d) \n" + ); } -static QStringList rmfiles(const QStringList &files, bool &allRemove, bool &quit, const QString &baseDir, const QString &proj = QString()) +QStringList rmfiles(const QStringList &files, bool &allRemove, bool &quit, const QString &baseDir, const QString &proj = QString()) { QStringList rmd; @@ -282,7 +303,7 @@ static QStringList rmfiles(const QStringList &files, bool &allRemove, bool &quit } -static QStringList rmfiles(const QStringList &files, const QString &baseDir, const QString &proj) +QStringList rmfiles(const QStringList &files, const QString &baseDir, const QString &proj) { bool allRemove = false; bool quit = false; @@ -290,7 +311,7 @@ static QStringList rmfiles(const QStringList &files, const QString &baseDir, con } -static uint random(uint max) +uint random(uint max) { static std::random_device randev; static std::default_random_engine eng(randev()); @@ -299,7 +320,7 @@ static uint random(uint max) } -static QByteArray randomString(int length) +QByteArray randomString(int length) { constexpr auto ch = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; QByteArray ret; @@ -312,7 +333,7 @@ static QByteArray randomString(int length) } -static bool createNewApplication(const QString &name) +bool createNewApplication(const QString &name, const QByteArray &templateSystem) { if (name.isEmpty()) { qCritical("invalid argument"); @@ -355,6 +376,11 @@ static bool createNewApplication(const QString &name) if (filename == "application.ini") { replaceString(dst, "$SessionSecret$", randomString(30)); } + + // Replaces a string in development.ini file + if (filename == "development.ini") { + replaceString(dst, "$TemplateSystem$", templateSystem); + } } #ifdef Q_OS_WIN @@ -368,7 +394,7 @@ static bool createNewApplication(const QString &name) } -static int deleteScaffold(const QString &name) +int deleteScaffold(const QString &name) { // Removes files QString str = name; @@ -405,7 +431,7 @@ static int deleteScaffold(const QString &name) << "api" + str + "service.cpp"; // Template system - if (templateSystem == "otama") { + if (templateSystem == TemplateSystem::Otama) { views << str + "/index.html" << str + "/index.otm" << str + "/show.html" @@ -414,13 +440,14 @@ static int deleteScaffold(const QString &name) << str + "/create.otm" << str + "/save.html" << str + "/save.otm"; - } else if (templateSystem == "erb") { + } else if (templateSystem == TemplateSystem::Erb || templateSystem == TemplateSystem::Vue + || templateSystem == TemplateSystem::Vite_Vue) { views << str + "/index.erb" << str + "/show.erb" << str + "/create.erb" << str + "/save.erb"; } else { - qCritical("Invalid template system specified: %s", qUtf8Printable(templateSystem)); + qCritical("Invalid template system specified"); return 2; } @@ -458,7 +485,7 @@ static int deleteScaffold(const QString &name) } -static bool checkIniFile() +bool checkIniFile() { // Checking INI file if (!QFile::exists(appIni)) { @@ -470,7 +497,7 @@ static bool checkIniFile() } -static void printSuccessMessage(const QString &model) +void printSuccessMessage(const QString &model) { QString msg; @@ -498,28 +525,39 @@ static void printSuccessMessage(const QString &model) } -static bool isVueEnabled() +std::unique_ptr createServiceGenerator(TemplateSystem templateSystem, const QString &service, const QList> &fields, int pkIdx, int lockRevIdx) { - static int vueEnable = -1; + if (templateSystem == TemplateSystem::Erb) { + return std::make_unique(service, fields, pkIdx, lockRevIdx); + } else if (templateSystem == TemplateSystem::Vue) { + return std::make_unique(service, fields, pkIdx, lockRevIdx); + } else if (templateSystem == TemplateSystem::Vite_Vue) { + return std::make_unique(service, fields, pkIdx, lockRevIdx); + } else if (templateSystem == TemplateSystem::Otama) { + return std::make_unique(service, fields, pkIdx, lockRevIdx); + } else { + qCritical("Invalid template system specified"); + return nullptr; + } +} - if (vueEnable < 0) { - std::printf("\n"); - QTextStream stream(stdin); - for (;;) { - std::printf(" Create sources for vue.js? [y/n] "); - QString line = stream.readLine().trimmed(); - const QChar c = line[0]; - if (c == 'Y' || c == 'y') { - vueEnable = 1; - break; - } else if (c == 'N' || c == 'n') { - vueEnable = 0; - break; - } - } +std::unique_ptr createViewGenerator(TemplateSystem templateSystem, const QString &view, const QList> &fields, int pkIdx, int autoValIdx) +{ + if (templateSystem == TemplateSystem::Erb) { + return std::make_unique(view, fields, pkIdx, autoValIdx); + } else if (templateSystem == TemplateSystem::Vue) { + return std::make_unique(view, fields, pkIdx, autoValIdx); + } else if (templateSystem == TemplateSystem::Vite_Vue) { + return std::make_unique(view, fields, pkIdx, autoValIdx); + } else if (templateSystem == TemplateSystem::Otama) { + return std::make_unique(view, fields, pkIdx, autoValIdx); + } else { + qCritical("Invalid template system specified"); + return nullptr; } - return (bool)vueEnable; +} + } @@ -527,33 +565,42 @@ int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QStringList args = QCoreApplication::arguments(); - int subcmd = subCommands.value(args.value(1), Invalid); + SubCommand subcmd = subCommands.value(args.value(1), SubCommand::Invalid); switch (subcmd) { - case Invalid: + case SubCommand::Invalid: qCritical("invalid argument"); return 1; break; - case Help: + case SubCommand::Help: usage(); break; - case New: + case SubCommand::New: { // Creates new project - if (!createNewApplication(args.value(2))) { + QByteArray ts = "erb"; + if (args.count() > 4 && args.value(3) == "--template") { + const auto name = args.value(4).toLower(); + if (templateSystemMap.contains(name)) { + ts = name.toLatin1(); + } + } + + if (!createNewApplication(args.value(2), ts)) { return 1; } break; + } - case ShowDrivers: + case SubCommand::ShowDrivers: std::printf("Available database drivers for Qt:\n"); for (QStringListIterator i(TableSchema::databaseDrivers()); i.hasNext();) { std::printf(" %s\n", qUtf8Printable(i.next())); } break; - case ShowDriverPath: { + case SubCommand::ShowDriverPath: { QString path = QLibraryInfo::path(QLibraryInfo::PluginsPath) + "/sqldrivers"; QFileInfo fi(path); if (!fi.exists() || !fi.isDir()) { @@ -564,7 +611,7 @@ int main(int argc, char *argv[]) break; } - case ShowTables: + case SubCommand::ShowTables: if (checkIniFile()) { QStringList tables = TableSchema::tables(); if (!tables.isEmpty()) { @@ -579,7 +626,7 @@ int main(int argc, char *argv[]) } break; - case ShowCollections: + case SubCommand::ShowCollections: if (checkIniFile()) { // MongoDB settings QString mongoini = appSettings.value("MongoDbSettingsFile").toString().trimmed(); @@ -614,14 +661,21 @@ int main(int argc, char *argv[]) return 2; } - // ERB or Otama - templateSystem = devSettings.value("TemplateSystem").toString().toLower(); - if (templateSystem.isEmpty()) { - templateSystem = appSettings.value("TemplateSystem", "Erb").toString().toLower(); + // Template system + QString ts = devSettings.value("TemplateSystem").toString().toLower(); + if (ts.isEmpty()) { + ts = appSettings.value("TemplateSystem", "Erb").toString().toLower(); + } + + if (!templateSystemMap.contains(ts)) { + qCritical("Invalid template system specified: %s", qUtf8Printable(ts)); + return 2; + } else { + templateSystem = templateSystemMap.value(ts); } switch (subcmd) { - case Controller: { + case SubCommand::Controller: { QString ctrl = args.value(2); ControllerGenerator crtlgen(ctrl, args.mid(3)); crtlgen.generate(D_CTRLS); @@ -633,7 +687,7 @@ int main(int argc, char *argv[]) break; } - case Model: { + case SubCommand::Model: { ModelGenerator modelgen(ModelGenerator::Sql, args.value(3), args.value(2)); modelgen.generate(D_MODELS); @@ -643,23 +697,21 @@ int main(int argc, char *argv[]) return 2; } - if (isVueEnabled()) { - VueServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); - } else { - ServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); + // Generates servie file of the specified template system + std::unique_ptr svrgen = createServiceGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); + if (svrgen) { + svrgen->generate(D_MODELS); } break; } - case Helper: { + case SubCommand::Helper: { HelperGenerator helpergen(args.value(2)); helpergen.generate(D_HELPERS); break; } - case UserModel: { + case SubCommand::UserModel: { ModelGenerator modelgen(ModelGenerator::Sql, args.value(5), args.value(2), args.mid(3, 2)); modelgen.generate(D_MODELS, true); @@ -669,17 +721,15 @@ int main(int argc, char *argv[]) return 2; } - if (isVueEnabled()) { - VueServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); - } else { - ServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); + // Generates servie file of the specified template system + std::unique_ptr svrgen = createServiceGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); + if (svrgen) { + svrgen->generate(D_MODELS); } break; } - case SqlObject: { + case SubCommand::SqlObject: { SqlObjGenerator sqlgen(args.value(3), args.value(2)); QString path = sqlgen.generate(D_MODELS); @@ -689,7 +739,7 @@ int main(int argc, char *argv[]) break; } - case MongoScaffold: { + case SubCommand::MongoScaffold: { ModelGenerator modelgen(ModelGenerator::Mongo, args.value(2)); bool success = modelgen.generate(D_MODELS); @@ -699,26 +749,23 @@ int main(int argc, char *argv[]) return 2; } - if (isVueEnabled()) { - VueServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - success &= svrgen.generate(D_MODELS); + // Generates view files of the specified template system + std::unique_ptr svrgen = createServiceGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); + if (svrgen) { + svrgen->generate(D_MODELS); } else { - ServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - success &= svrgen.generate(D_MODELS); + qCritical("Invalid template system specified"); + return 2; } ControllerGenerator crtlgen(modelgen.model(), modelgen.fieldList(), modelgen.primaryKeyIndex(), modelgen.lockRevisionIndex()); success &= crtlgen.generate(D_CTRLS); // Generates view files of the specified template system - if (templateSystem == "otama") { - OtamaGenerator viewgen(modelgen.model(), modelgen.fieldList(), modelgen.primaryKeyIndex(), modelgen.autoValueIndex()); - viewgen.generate(D_VIEWS); - } else if (templateSystem == "erb") { - ErbGenerator viewgen(modelgen.model(), modelgen.fieldList(), modelgen.primaryKeyIndex(), modelgen.autoValueIndex()); - viewgen.generate(D_VIEWS); + std::unique_ptr viewgen = createViewGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), modelgen.primaryKeyIndex(), modelgen.autoValueIndex()); + if (viewgen) { + viewgen->generate(D_VIEWS); } else { - qCritical("Invalid template system specified: %s", qUtf8Printable(templateSystem)); return 2; } @@ -728,7 +775,7 @@ int main(int argc, char *argv[]) break; } - case MongoModel: { + case SubCommand::MongoModel: { ModelGenerator modelgen(ModelGenerator::Mongo, args.value(2)); modelgen.generate(D_MODELS); @@ -738,17 +785,15 @@ int main(int argc, char *argv[]) return 2; } - if (isVueEnabled()) { - VueServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); - } else { - ServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); + // Generates servie file of the specified template system + std::unique_ptr svrgen = createServiceGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); + if (svrgen) { + svrgen->generate(D_MODELS); } break; } - case WebSocketEndpoint: { + case SubCommand::WebSocketEndpoint: { const QString appendpointfiles[] = {L("controllers/applicationendpoint.h"), L("controllers/applicationendpoint.cpp")}; @@ -767,7 +812,7 @@ int main(int argc, char *argv[]) break; } - case Api: { + case SubCommand::Api: { ModelGenerator modelgen(ModelGenerator::Sql, args.value(3), args.value(2)); modelgen.generate(D_MODELS); @@ -785,20 +830,20 @@ int main(int argc, char *argv[]) break; } - case Validator: { + case SubCommand::Validator: { ValidatorGenerator validgen(args.value(2)); validgen.generate(D_HELPERS); break; } - case Mailer: { + case SubCommand::Mailer: { MailerGenerator mailgen(args.value(2), args.mid(3)); mailgen.generate(D_CTRLS); copy(dataDirPath + "mail.erb", D_VIEWS + "mailer/mail.erb"); break; } - case Scaffold: { + case SubCommand::Scaffold: { ModelGenerator modelgen(ModelGenerator::Sql, args.value(3), args.value(2)); bool success = modelgen.generate(D_MODELS); @@ -815,33 +860,22 @@ int main(int argc, char *argv[]) ControllerGenerator crtlgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); success &= crtlgen.generate(D_CTRLS); - if (isVueEnabled()) { - VueServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); - - // Generates view files of the specified template system - if (templateSystem == "erb") { - VueErbGenerator viewgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.autoValueIndex()); - viewgen.generate(D_VIEWS); - } else { - qCritical("Invalid template system specified: %s", qUtf8Printable(templateSystem)); - return 2; - } + // Generates service file of the specified template system + std::unique_ptr svrgen = createServiceGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); + if (svrgen) { + svrgen->generate(D_MODELS); } else { - ServiceGenerator svrgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.lockRevisionIndex()); - svrgen.generate(D_MODELS); - - // Generates view files of the specified template system - if (templateSystem == "otama") { - OtamaGenerator viewgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.autoValueIndex()); - viewgen.generate(D_VIEWS); - } else if (templateSystem == "erb") { - ErbGenerator viewgen(modelgen.model(), modelgen.fieldList(), pkidx, modelgen.autoValueIndex()); - viewgen.generate(D_VIEWS); - } else { - qCritical("Invalid template system specified: %s", qUtf8Printable(templateSystem)); - return 2; - } + qCritical("Invalid template system specified"); + return 2; + } + + // Generates view files of the specified template system + std::unique_ptr viewgen = createViewGenerator(templateSystem, modelgen.model(), modelgen.fieldList(), modelgen.primaryKeyIndex(), modelgen.autoValueIndex()); + if (viewgen) { + viewgen->generate(D_VIEWS); + } else { + qCritical("Invalid template system specified"); + return 2; } if (success) { @@ -850,7 +884,7 @@ int main(int argc, char *argv[]) break; } - case Delete: { + case SubCommand::Delete: { // Removes files int ret = deleteScaffold(args.value(2)); if (ret) { diff --git a/tools/tspawn/otamagenerator.h b/tools/tspawn/otamagenerator.h index d23ee3974..473c03d64 100644 --- a/tools/tspawn/otamagenerator.h +++ b/tools/tspawn/otamagenerator.h @@ -1,14 +1,16 @@ #pragma once +#include "generator.h" #include #include #include #include -class OtamaGenerator { +class OtamaGenerator : public Generator { public: OtamaGenerator(const QString &view, const QList> &fields, int pkIdx, int autoValIdx); - bool generate(const QString &dstDir) const; + virtual ~OtamaGenerator() {} + bool generate(const QString &dstDir) const override; protected: QStringList generateViews(const QString &dstDir) const; diff --git a/tools/tspawn/servicegenerator.h b/tools/tspawn/servicegenerator.h index fab926329..1d4269319 100644 --- a/tools/tspawn/servicegenerator.h +++ b/tools/tspawn/servicegenerator.h @@ -1,15 +1,16 @@ #pragma once +#include "generator.h" #include #include #include #include -class ServiceGenerator { +class ServiceGenerator : public Generator { public: ServiceGenerator(const QString &service, const QList> &fields, int pkIdx, int lockRevIdx); ~ServiceGenerator() { } - bool generate(const QString &dstDir) const; + bool generate(const QString &dstDir) const override; private: virtual QString headerFileTemplate() const; diff --git a/tools/tspawn/tspawn.pro b/tools/tspawn/tspawn.pro index 3cded0403..ea1e86107 100644 --- a/tools/tspawn/tspawn.pro +++ b/tools/tspawn/tspawn.pro @@ -5,6 +5,8 @@ CONFIG += console CONFIG -= app_bundle QT += sql QT -= gui +MOC_DIR = .obj/ +OBJECTS_DIR = .obj/ # C++ Standards Support CONFIG += c++20 @@ -184,6 +186,10 @@ HEADERS += vueservicegenerator.h SOURCES += vueservicegenerator.cpp HEADERS += vueerbgenerator.h SOURCES += vueerbgenerator.cpp +HEADERS += vitevuegenerator.h +SOURCES += vitevuegenerator.cpp +HEADERS += vitevueservicegenerator.h +SOURCES += vitevueservicegenerator.cpp HEADERS += abstractobjgenerator.h SOURCES += abstractobjgenerator.cpp HEADERS += sqlobjgenerator.h @@ -210,3 +216,4 @@ HEADERS += apiservicegenerator.h SOURCES += apiservicegenerator.cpp HEADERS += util.h SOURCES += util.cpp +HEADERS += generator.h diff --git a/tools/tspawn/validatorgenerator.cpp b/tools/tspawn/validatorgenerator.cpp index 3e447948e..bd4ea0e96 100644 --- a/tools/tspawn/validatorgenerator.cpp +++ b/tools/tspawn/validatorgenerator.cpp @@ -10,26 +10,28 @@ #include "global.h" #include "projectfilegenerator.h" -constexpr auto VALIDATOR_HEADER_TEMPLATE = "#pragma once\n" - "#include \n" - "#include \n" - "\n\n" - "class T_HELPER_EXPORT %1Validator : public TFormValidator {\n" - "public:\n" - " %1Validator();\n" - "};\n" - "\n" - "Q_DECLARE_METATYPE(%1Validator)\n" - "\n"; - -constexpr auto VALIDATOR_IMPL_TEMPLATE = "#include \"%1validator.h\"\n" - "\n\n" - "%2Validator::%2Validator() : TFormValidator()\n" - "{\n" - " //Set the rules below\n" - " //setRule(\"xxxx\", Tf::MaxLength, 20);\n" - " // :\n" - "}\n"; +constexpr auto VALIDATOR_HEADER_TEMPLATE = + "#pragma once\n" + "#include \n" + "#include \n" + "\n\n" + "class T_HELPER_EXPORT %1Validator : public TFormValidator {\n" + "public:\n" + " %1Validator();\n" + "};\n" + "\n" + "Q_DECLARE_METATYPE(%1Validator)\n" + "\n"; + +constexpr auto VALIDATOR_IMPL_TEMPLATE = + "#include \"%1validator.h\"\n" + "\n\n" + "%2Validator::%2Validator() : TFormValidator()\n" + "{\n" + " //Set the rules below\n" + " //setRule(\"xxxx\", Tf::MaxLength, 20);\n" + " // :\n" + "}\n"; ValidatorGenerator::ValidatorGenerator(const QString &validator) diff --git a/tools/tspawn/vitevuegenerator.cpp b/tools/tspawn/vitevuegenerator.cpp new file mode 100644 index 000000000..a12cd09c4 --- /dev/null +++ b/tools/tspawn/vitevuegenerator.cpp @@ -0,0 +1,394 @@ +/* Copyright (c) 2025, AOYAMA Kazuharu + * All rights reserved. + * + * This software may be used and distributed according to the terms of + * the New BSD License, which is incorporated herein by reference. + */ + +#include "vitevuegenerator.h" +#include "filewriter.h" +#include "util.h" +#include + + +constexpr auto INDEX_TEMPLATE = + "\n" + "\n" + "\n" + " \n" + " %ClassName%: index\n" + "<% if (databaseEnvironment() == \"dev\") { %>\n" + " \n" + "<% } else { %>\n" + " <%== viteScriptTag(\"src/main.js\", a(\"type\", \"module\")) %>\n" + "<% } %>\n" + " \">\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n"; + +constexpr auto CREATE_TEMPLATE = + "\n" + "\n" + "\n" + " \n" + " %ClassName%: create\n" + "<% if (databaseEnvironment() == \"dev\") { %>\n" + " \n" + "<% } else { %>\n" + " <%== viteScriptTag(\"src/main.js\", a(\"type\", \"module\")) %>\n" + "<% } %>\n" + " \">\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n"; + +constexpr auto SHOW_TEMPLATE = + "\n" + "\n" + "\n" + " \n" + " %ClassName%: show\n" + "<% if (databaseEnvironment() == \"dev\") { %>\n" + " \n" + "<% } else { %>\n" + " <%== viteScriptTag(\"src/main.js\", a(\"type\", \"module\")) %>\n" + "<% } %>\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n"; + +constexpr auto SAVE_TEMPLATE = + "\n" + "\n" + "\n" + " \n" + " %ClassName%: save\n" + "<% if (databaseEnvironment() == \"dev\") { %>\n" + " \n" + "<% } else { %>\n" + " <%== viteScriptTag(\"src/main.js\", a(\"type\", \"module\")) %>\n" + "<% } %>\n" + " \">\n" + "\n" + "\n" + "
\n" + "\n" + "\n" + "\n"; + +constexpr auto INDEX_VUE_TEMPLATE = + "\n" + "\n" + "\n" + "\n" + "\n"; + +constexpr auto SHOW_VUE_TEMPLATE = + "\n" + "\n" + "\n" + "\n" + "\n"; + +constexpr auto CREATE_VUE_TEMPLATE = + "\n" + "\n" + "\n" + "\n" + "\n"; + +constexpr auto SAVE_VUE_TEMPLATE = + "\n" + "\n" + "\n" + "\n" + "\n"; + + +const QStringList excludedColumn = { + "created_at", + "updated_at", + "modified_at", + "lock_revision", + "createdAt", + "updatedAt", + "modifiedAt", + "lockRevision", +}; + +namespace { + +const QStringList excludedDirName = { + "layouts", + "partial", + "direct", + "_src", + "mailer", +}; + +} + + + ViteVueGenerator::ViteVueGenerator(const QString &view, const QList> &fields, int pkIdx, int autoValIdx) : + _viewName(view), _fieldList(fields), _primaryKeyIndex(pkIdx), _autoValueIndex(autoValIdx) +{ +} + + +bool ViteVueGenerator::generate(const QString &dstDir) const +{ + QDir dir(dstDir + _viewName.toLower()); + QString varName = enumNameToVariableName(_viewName); + const QPair &pkFld = _fieldList[_primaryKeyIndex]; + QString pkVarName = fieldNameToVariableName(pkFld.first); + QString th, td, showitems, entryitems, edititems; + + // Generates view files + for (int i = 0; i < _fieldList.count(); ++i) { + const QPair &p = _fieldList[i]; + + QString icap = fieldNameToCaption(p.first); + QString ivar = fieldNameToVariableName(p.first); + + showitems += "
"; + showitems += icap; + showitems += "
{{ item."; + showitems += ivar; + showitems += " }}
\n"; + + if (!excludedColumn.contains(ivar, Qt::CaseInsensitive)) { + th += " "; + th += icap; + th += "\n"; + td += " {{ item."; + td += ivar; + td += " }}\n"; + + if (i != _autoValueIndex) { // case of not auto-value field + entryitems += "

\n \n

\n"; + } + edititems += "

\n