6#include "application.h"
9#include "controller_p.h"
10#include "dispatcher.h"
14#include <QMetaClassInfo>
15#include <QRegularExpression>
18using namespace Qt::Literals::StringLiterals;
245 , d_ptr(new ControllerPrivate(this))
249Controller::~Controller()
252 qDeleteAll(d->actionList);
259 return d->pathPrefix;
265 auto it = d->actions.constFind(name);
266 if (it != d->actions.constEnd()) {
269 return d->dispatcher->getAction(name.toString(), d->pathPrefix);
275 return d->actionList;
280 return !qstrcmp(metaObject()->className(), className);
295ControllerPrivate::ControllerPrivate(
Controller *parent)
304 q->setObjectName(QString::fromLatin1(q->metaObject()->className()));
306 dispatcher = _dispatcher;
312 const QMetaObject *meta = q->metaObject();
313 const QString className = QString::fromLatin1(meta->className());
314 q->setObjectName(className);
316 bool namespaceFound =
false;
317 for (
int i = meta->classInfoCount() - 1; i >= 0; --i) {
318 if (qstrcmp(meta->classInfo(i).name(),
"Namespace") == 0) {
319 pathPrefix = QString::fromLatin1(meta->classInfo(i).value());
320 while (pathPrefix.startsWith(u
'/')) {
321 pathPrefix.remove(0, 1);
323 namespaceFound =
true;
328 if (!namespaceFound) {
330 bool lastWasUpper =
true;
332 for (
int i = 0; i < className.length(); ++i) {
333 const QChar c = className.at(i);
334 if (c.isLower() || c.isDigit()) {
335 controlerNS.append(c);
336 lastWasUpper =
false;
337 }
else if (c == u
'_') {
338 controlerNS.append(c);
342 controlerNS.append(u
'/');
345 controlerNS.append(c.toLower());
350 pathPrefix = controlerNS;
353 registerActionMethods(meta, q, app);
356void ControllerPrivate::setupFinished()
360 const ActionList beginList = dispatcher->getActions(u
"Begin"_s, pathPrefix);
361 if (!beginList.isEmpty()) {
362 beginAutoList.append(beginList.last());
365 beginAutoList.append(dispatcher->getActions(u
"Auto"_s, pathPrefix));
367 const ActionList endList = dispatcher->getActions(u
"End"_s, pathPrefix);
368 if (!endList.isEmpty()) {
369 end = endList.last();
372 for (
Action *action : std::as_const(actionList)) {
373 action->dispatcherReady(dispatcher, q);
376 q->preFork(qobject_cast<Application *>(q->parent()));
385 const int &actionRefCount = c->d_ptr->actionRefCount;
388 const auto beginAutoList = d->beginAutoList;
389 for (
Action *action : beginAutoList) {
390 if (actionRefCount) {
391 c->d_ptr->pendingAsync.enqueue(action);
392 }
else if (!action->dispatch(c)) {
400 if (actionRefCount) {
401 c->d_ptr->pendingAsync.enqueue(c->
action());
409 if (actionRefCount) {
410 c->d_ptr->pendingAsync.enqueue(d->end);
411 }
else if (!d->end->dispatch(c)) {
416 if (actionRefCount) {
417 c->d_ptr->engineRequest->status |= EngineRequest::Async;
423Action *ControllerPrivate::actionClass(
const QVariantHash &args)
425 const auto attributes = args.value(u
"attributes"_s).value<
ParamsMultiMap>();
426 const QString actionClass = attributes.value(u
"ActionClass"_s);
428 QObject *
object = instantiateClass(actionClass,
"Cutelyst::Action");
430 if (
auto action = qobject_cast<Cutelyst::Action *>(
object); action) {
433 qCWarning(CUTELYST_CONTROLLER) <<
"ActionClass" << actionClass <<
"is not an ActionClass"
434 <<
object->metaObject()->superClass()->className();
441Action *ControllerPrivate::createAction(
const QVariantHash &args,
442 const QMetaMethod &method,
446 Action *action = actionClass(args);
451 QStack<Component *> roles = gatherActionRoles(args);
452 for (
int i = 0; i < roles.size(); ++i) {
454 code->
init(app, args);
455 code->setParent(action);
460 action->
setName(args.value(u
"name"_s).toString());
461 action->
setReverse(args.value(u
"reverse"_s).toString());
467void ControllerPrivate::registerActionMethods(
const QMetaObject *meta,
472 for (
int i = 0; i < meta->methodCount(); ++i) {
473 const QMetaMethod method = meta->method(i);
474 const QByteArray name = method.name();
479 if (method.isValid() &&
480 (method.methodType() == QMetaMethod::Method ||
481 method.methodType() == QMetaMethod::Slot) &&
482 (method.parameterCount() &&
483 method.parameterType(0) == qMetaTypeId<Cutelyst::Context *>())) {
486 QByteArray attributeArray;
487 for (
int i2 = meta->classInfoCount() - 1; i2 >= 0; --i2) {
488 QMetaClassInfo classInfo = meta->classInfo(i2);
489 if (name == classInfo.name()) {
490 attributeArray.append(classInfo.value());
493 ParamsMultiMap attrs = parseAttributes(method, attributeArray, name);
496 if (controller->
ns().isEmpty()) {
497 reverse = QString::fromLatin1(name);
499 reverse = controller->
ns() + u
'/' + QString::fromLatin1(name);
502 Action *action = createAction({{u
"name"_s, QVariant::fromValue(name)},
503 {u
"reverse"_s, QVariant::fromValue(reverse)},
504 {u
"namespace"_s, QVariant::fromValue(controller->
ns())},
505 {u
"attributes"_s, QVariant::fromValue(attrs)}},
510 actions.insert(action->
reverse(), {action->reverse(), action});
511 actionList.append(action);
516ParamsMultiMap ControllerPrivate::parseAttributes(
const QMetaMethod &method,
517 const QByteArray &str,
518 const QByteArray &name)
521 std::vector<std::pair<QString, QString>> attributes;
528 int size = str.size();
532 if (str.at(pos) ==
':') {
535 int keyStart = ++pos;
538 if (str.at(pos) ==
'(') {
540 int valueStart = ++pos;
542 if (str.at(pos) ==
')') {
546 if (pos < size && str.at(pos) ==
':') {
550 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
552 }
else if (pos >= size) {
556 QString::fromLatin1(str.mid(valueStart, valueEnd - valueStart));
565 }
else if (str.at(pos) ==
':') {
574 key = QString::fromLatin1(str.mid(keyStart, keyLength));
577 if (!value.isEmpty()) {
578 if ((value.startsWith(u
'\'') && value.endsWith(u
'\'')) ||
579 (value.startsWith(u
'"') && value.endsWith(u
'"'))) {
581 value.remove(value.size() - 1, 1);
586 attributes.emplace_back(key, value);
592 const static auto digitRE = QRegularExpression(u
"\\D"_s);
595 std::ranges::for_each(attributes | std::views::reverse, [&](
const auto &pair) {
596 QString key = pair.first;
597 QString value = pair.second;
598 if (key.compare(u
"Global") == 0) {
600 value = parsePathAttr(u
'/' + QString::fromLatin1(name));
601 }
else if (key.compare(u
"Local") == 0) {
603 value = parsePathAttr(QString::fromLatin1(name));
604 }
else if (key.compare(u
"Path") == 0) {
605 value = parsePathAttr(value);
606 }
else if (key.compare(u
"Args") == 0) {
607 QString args = value;
608 if (!args.isEmpty()) {
609 value = args.remove(digitRE);
611 }
else if (key.compare(u
"CaptureArgs") == 0) {
612 QString captureArgs = value;
613 value = captureArgs.remove(digitRE);
614 }
else if (key.compare(u
"Chained") == 0) {
615 value = parseChainedAttr(value);
618 ret.insert(key, value);
622 if (!ret.contains(u
"Args"_s) && !ret.contains(u
"CaptureArgs"_s) &&
623 (ret.contains(u
"AutoArgs"_s) || ret.contains(u
"AutoCaptureArgs"_s))) {
624 if (ret.contains(u
"AutoArgs"_s) && ret.contains(u
"AutoCaptureArgs"_s)) {
625 qFatal(
"Action '%s' has both AutoArgs and AutoCaptureArgs, which is not allowed",
628 QString parameterName;
629 if (ret.contains(u
"AutoArgs"_s)) {
630 ret.remove(u
"AutoArgs"_s);
631 parameterName = u
"Args"_s;
633 ret.remove(u
"AutoCaptureArgs"_s);
634 parameterName = u
"CaptureArgs"_s;
638 if (!(method.parameterCount() == 2 &&
639 method.parameterType(1) == QMetaType::QStringList)) {
640 int parameterCount = 0;
641 for (
int i2 = 1; i2 < method.parameterCount(); ++i2) {
642 int typeId = method.parameterType(i2);
643 if (typeId == QMetaType::QString) {
647 ret.replace(parameterName, QString::number(parameterCount));
653 if (!ret.contains(u
"Private"_s) && method.access() == QMetaMethod::Private) {
654 ret.insert(u
"Private"_s, {});
660QStack<Component *> ControllerPrivate::gatherActionRoles(
const QVariantHash &args)
662 QStack<Component *> roles;
663 const auto attributes = args.value(u
"attributes"_s).value<
ParamsMultiMap>();
664 auto doesIt = attributes.constFind(u
"Does"_s);
665 while (doesIt != attributes.constEnd() && doesIt.key().compare(u
"Does") == 0) {
667 instantiateClass(doesIt.value(), QByteArrayLiteral(
"Cutelyst::Component"));
669 roles.push(qobject_cast<Component *>(
object));
676QString ControllerPrivate::parsePathAttr(
const QString &value)
678 QString ret = pathPrefix;
679 if (value.startsWith(u
'/')) {
681 }
else if (!value.isEmpty()) {
682 ret = pathPrefix + u
'/' + value;
687QString ControllerPrivate::parseChainedAttr(
const QString &attr)
689 QString ret = u
"/"_s;
690 if (attr.isEmpty()) {
694 if (attr.compare(u
".") == 0) {
695 ret.append(pathPrefix);
696 }
else if (!attr.startsWith(u
'/')) {
697 if (!pathPrefix.isEmpty()) {
698 ret.append(pathPrefix + u
'/' + attr);
710QObject *ControllerPrivate::instantiateClass(
const QString &name,
const QByteArray &super)
712 QString instanceName = name;
713 if (!instanceName.isEmpty()) {
714 const static QRegularExpression nonWordsRE(u
"\\W"_s);
715 instanceName.remove(nonWordsRE);
717 QMetaType
id = QMetaType::fromName(instanceName.toLatin1().data());
719 if (!instanceName.endsWith(u
'*')) {
720 instanceName.append(u
'*');
723 id = QMetaType::fromName(instanceName.toLatin1().data());
724 if (!
id.isValid() && !instanceName.startsWith(u
"Cutelyst::")) {
725 instanceName = u
"Cutelyst::" + instanceName;
726 id = QMetaType::fromName(instanceName.toLatin1().data());
731 const QMetaObject *metaObj =
id.metaObject();
733 if (!superIsClassName(metaObj->superClass(), super)) {
734 qCWarning(CUTELYST_CONTROLLER)
735 <<
"Class name" << instanceName <<
"is not a derived class of" << super;
738 QObject *
object = metaObj->newInstance();
740 qCWarning(CUTELYST_CONTROLLER)
741 <<
"Could create a new instance of" << instanceName
742 <<
"make sure it's default constructor is "
743 "marked with the Q_INVOKABLE macro";
749 Component *component = application->createComponentPlugin(name);
754 component = application->createComponentPlugin(instanceName);
761 qCCritical(CUTELYST_CONTROLLER,
762 "Could not create component '%s', you can register it with "
763 "qRegisterMetaType<%s>(); or set a proper CUTELYST_PLUGINS_DIR",
764 qPrintable(instanceName),
765 qPrintable(instanceName));
772bool ControllerPrivate::superIsClassName(
const QMetaObject *super,
const QByteArray &className)
775 if (super->className() == className) {
778 return superIsClassName(super->superClass(), className);
783#include "moc_controller.cpp"
This class represents a Cutelyst Action.
void setupAction(const QVariantHash &args, Application *app)
bool dispatch(Context *c)
void setMethod(const QMetaMethod &method)
void setController(Controller *controller)
The Cutelyst application.
The Cutelyst Component base class.
void setReverse(const QString &reverse)
virtual bool init(Application *application, const QVariantHash &args)
void applyRoles(const QStack< Component * > &roles)
QString reverse() const noexcept
void setName(const QString &name)
Cutelyst Controller base class.
virtual bool preFork(Application *app)
virtual bool postFork(Application *app)
bool operator==(const char *className)
QString ns() const noexcept
ActionList actions() const noexcept
bool _DISPATCH(Context *c)
Controller(QObject *parent=nullptr)
Action * actionFor(QStringView name) const
QMultiMap< QString, QString > ParamsMultiMap
The Cutelyst namespace holds all public Cutelyst API.
QVector< Action * > ActionList