Skip to content

Commit

Permalink
feat: improve plugin parameter specification (helps #84)
Browse files Browse the repository at this point in the history
  • Loading branch information
hello-adam committed Apr 30, 2021
1 parent 276c9b5 commit cef1055
Show file tree
Hide file tree
Showing 49 changed files with 467 additions and 322 deletions.
80 changes: 57 additions & 23 deletions src/hobbits-core/parameterdelegate.cpp
Expand Up @@ -38,6 +38,25 @@ AbstractParameterEditor* ParameterDelegate::createEditor(QSize targetBounds)
return m_editorCreator(sharedFromThis(), targetBounds);
}

bool ParameterDelegate::jsonTypeCompatible(QJsonValue::Type jsonType, ParameterDelegate::ParameterType type) {
if (jsonType == QJsonValue::Double) {
return (type == ParameterType::Decimal) || (type == ParameterType::Integer);
}
else if (jsonType == QJsonValue::String) {
return type == ParameterType::String;
}
else if (jsonType == QJsonValue::Bool) {
return type == ParameterType::Boolean;
}
else if (jsonType == QJsonValue::Array) {
return type == ParameterType::Array;
}
else if (jsonType == QJsonValue::Object) {
return type == ParameterType::Object;
}
return false;
}

QList<ParameterDelegate::ParameterInfo> ParameterDelegate::parameterInfos() const
{
return m_parameterMap.values();
Expand All @@ -48,66 +67,81 @@ ParameterDelegate::ParameterInfo ParameterDelegate::getInfo(QString name) const
return m_parameterMap.value(name);
}

bool ParameterDelegate::validate(const QJsonObject &parameters) const
QStringList ParameterDelegate::validate(const QJsonObject &parameters) const
{
return validateAgainstInfos(parameters, parameterInfos());
}

QString ParameterDelegate::actionDescription(const QJsonObject &parameters) const
{
if (!validate(parameters)) {
if (!validate(parameters).isEmpty()) {
return QString();
}

return m_actionDescriber(parameters);
}

bool ParameterDelegate::validateAgainstInfos(const QJsonObject &parameters, QList<ParameterDelegate::ParameterInfo> infos)
QStringList ParameterDelegate::validateAgainstInfos(const QJsonObject &parameters, QList<ParameterDelegate::ParameterInfo> infos)
{
if (infos.isEmpty()) {
return true;
}
QStringList invalidations;

for (auto param : infos) {
if (!parameters.contains(param.name)) {
if (!param.optional) {
return false;
invalidations.append(QString("Missing required parameter '%1'.").arg(param.name));
continue;
}
}
else if (param.type != parameters.value(param.name).type()) {
return false;
else if (!jsonTypeCompatible(parameters.value(param.name).type(), param.type)) {
invalidations.append(QString("Value of provided parameter '%1' is wrong type.").arg(param.name));
continue;
}

if (param.type == QJsonValue::Array) {
if (param.type == ParameterType::Array) {
QJsonArray array = parameters.value(param.name).toArray();
if (array.isEmpty() && !param.optional) {
return false;
invalidations.append(QString("Required array parameter '%1' is empty.").arg(param.name));
continue;
}
for (QJsonValueRef value: array) {
if (!value.isObject()) {
return false;
invalidations.append(QString("Array parameter '%1' has invalid value '%2'.").arg(param.name).arg(value.toString()));
}
if (!validateAgainstInfos(value.toObject(), param.subInfos)) {
return false;
else {
invalidations.append(validateAgainstInfos(value.toObject(), param.subInfos));
}
}
}
else if (param.type == QJsonValue::Object) {
else if (param.type == ParameterType::Object) {
QJsonValue val = parameters.value(param.name);
if (!val.isObject()) {
return false;
invalidations.append(QString("Object parameter '%1' is not an object.").arg(param.name));
}
else {
invalidations.append(validateAgainstInfos(val.toObject(), param.subInfos));
}
if (!validateAgainstInfos(val.toObject(), param.subInfos)) {
return false;
}
else if (!param.possibleValues.isEmpty()) {
if (!param.possibleValues.contains(parameters.value(param.name))) {
invalidations.append(QString("Parameter '%1' has invalid value '%2'.").arg(param.name).arg(parameters.value(param.name).toString()));
}
}
else if (param.type == QJsonValue::Double && param.hasIntLimits) {
int value = parameters.value(param.name).toInt();
if (value > param.intMax || value < param.intMin) {
return false;
else if (param.type == ParameterType::Integer || param.type == ParameterType::Decimal) {
if (!param.ranges.isEmpty()) {
double value = parameters.value(param.name).toDouble();
bool within = false;
for (auto range : param.ranges) {
if (value >= range.first && value <= range.second) {
within = true;
break;
}
}
if (!within) {
invalidations.append(QString("Parameter '%1' value '%2' is outside valid range.").arg(param.name).arg(value));
}
}
}
}

return true;
return invalidations;
}
39 changes: 31 additions & 8 deletions src/hobbits-core/parameterdelegate.h
Expand Up @@ -27,19 +27,40 @@
class HOBBITSCORESHARED_EXPORT ParameterDelegate : public QEnableSharedFromThis<ParameterDelegate>
{
public:

/**
* @brief The ParameterType enum provides type classifications for parameters
*
* ParameterType is similar to QJsonValue, but without null or undefined, and with a
* decimal/integer distinction.
*/
enum ParameterType
{
Boolean = 0x1,
Decimal = 0x2,
String = 0x3,
Array = 0x4,
Object = 0x5,
Integer = 0x10
};

/**
* @brief The ParameterInfo struct contains information for a parameter
*
* A parameter's ParameterInfo can be used to provide editors and validation for a parameter.
*/
struct HOBBITSCORESHARED_EXPORT ParameterInfo
{
QString name;
QJsonValue::Type type;
ParameterType type;
bool optional;
QList<ParameterInfo> subInfos;

bool hasIntLimits;
int intMin;
int intMax;
QList<QPair<double, double>> ranges;
QList<QJsonValue> possibleValues;

ParameterInfo(QString name, QJsonValue::Type type, bool optional = true, QList<ParameterInfo> subInfos = {}):
name{name}, type{type}, optional{optional}, subInfos{subInfos}, hasIntLimits(false), intMin(0), intMax(INT_MAX) {}
ParameterInfo(QString name, ParameterType type, bool optional = true, QList<ParameterInfo> subInfos = {}):
name{name}, type{type}, optional{optional}, subInfos{subInfos}, ranges(QList<QPair<double, double>>()), possibleValues(QList<QJsonValue>()) {}

ParameterInfo() = default;
ParameterInfo(const ParameterInfo&) = default;
Expand All @@ -56,16 +77,18 @@ class HOBBITSCORESHARED_EXPORT ParameterDelegate : public QEnableSharedFromThis<
std::function<QString(const QJsonObject&)> actionDescriber,
std::function<AbstractParameterEditor*(QSharedPointer<ParameterDelegate>, QSize)> editorCreator);

static bool jsonTypeCompatible(QJsonValue::Type jsonType, ParameterType type);

virtual AbstractParameterEditor* createEditor(QSize targetBounds = QSize());

QList<ParameterInfo> parameterInfos() const;
ParameterInfo getInfo(QString name) const;

bool validate(const QJsonObject &parameters) const;
QStringList validate(const QJsonObject &parameters) const;
QString actionDescription(const QJsonObject &parameters) const;

protected:
static bool validateAgainstInfos(const QJsonObject &parameters, QList<ParameterInfo> infos);
static QStringList validateAgainstInfos(const QJsonObject &parameters, QList<ParameterInfo> infos);
QMap<QString, ParameterInfo> m_parameterMap;
std::function<QString(const QJsonObject&)> m_actionDescriber;
std::function<AbstractParameterEditor*(QSharedPointer<ParameterDelegate>, QSize)> m_editorCreator;
Expand Down
8 changes: 5 additions & 3 deletions src/hobbits-plugins/analyzers/Find/find.cpp
Expand Up @@ -5,7 +5,7 @@
Find::Find()
{
QList<ParameterDelegate::ParameterInfo> infos = {
{"search_string", QJsonValue::String}
{"search_string", ParameterDelegate::ParameterType::String}
};

m_delegate = ParameterDelegate::create(
Expand Down Expand Up @@ -49,9 +49,11 @@ QSharedPointer<const AnalyzerResult> Find::analyzeBits(
const QJsonObject &parameters,
QSharedPointer<PluginActionProgress> progress)
{
if (!m_delegate->validate(parameters)) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1").arg(name()));
QStringList invalidations = m_delegate->validate(parameters);
if (!invalidations.isEmpty()) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1:\n%2").arg(name()).arg(invalidations.join("\n")));
}

auto findBits = BitArray::fromString(parameters.value("search_string").toString());
auto bits = container->bits();

Expand Down
11 changes: 6 additions & 5 deletions src/hobbits-plugins/analyzers/Highlight/highlight.cpp
Expand Up @@ -5,9 +5,9 @@
Highlight::Highlight()
{
QList<ParameterDelegate::ParameterInfo> infos = {
{"start", QJsonValue::Double},
{"length", QJsonValue::Double},
{"color", QJsonValue::Double, true}
{"start", ParameterDelegate::ParameterType::Integer},
{"length", ParameterDelegate::ParameterType::Integer},
{"color", ParameterDelegate::ParameterType::Integer, true}
};

m_delegate = QSharedPointer<ParameterDelegateUi>(
Expand Down Expand Up @@ -55,8 +55,9 @@ QSharedPointer<const AnalyzerResult> Highlight::analyzeBits(
QSharedPointer<PluginActionProgress> progress)
{
progress->setProgressPercent(5);
if (!m_delegate->validate(parameters)) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1").arg(name()));
QStringList invalidations = m_delegate->validate(parameters);
if (!invalidations.isEmpty()) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1:\n%2").arg(name()).arg(invalidations.join("\n")));
}

qint64 start = parameters.value("start").toInt();
Expand Down
9 changes: 5 additions & 4 deletions src/hobbits-plugins/analyzers/KaitaiStruct/kaitaistruct.cpp
Expand Up @@ -13,8 +13,8 @@
KaitaiStruct::KaitaiStruct()
{
QList<ParameterDelegate::ParameterInfo> infos = {
{PARAM_KSY, QJsonValue::String, true},
{PARAM_PY, QJsonValue::String, true}
{PARAM_KSY, ParameterDelegate::ParameterType::String, true},
{PARAM_PY, ParameterDelegate::ParameterType::String, true}
};

m_delegate = QSharedPointer<ParameterDelegateUi>(
Expand Down Expand Up @@ -68,8 +68,9 @@ QSharedPointer<const AnalyzerResult> KaitaiStruct::analyzeBits(
const QJsonObject &parameters,
QSharedPointer<PluginActionProgress> progress)
{
if (!m_delegate->validate(parameters)) {
return AnalyzerResult::error("Invalid parameters given to plugin");
QStringList invalidations = m_delegate->validate(parameters);
if (!invalidations.isEmpty()) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1:\n%2").arg(name()).arg(invalidations.join("\n")));
}

progress->setProgressPercent(2);
Expand Down
9 changes: 5 additions & 4 deletions src/hobbits-plugins/analyzers/Metadata/metadata.cpp
Expand Up @@ -4,8 +4,8 @@
Metadata::Metadata()
{
QList<ParameterDelegate::ParameterInfo> infos = {
{"label", QJsonValue::String},
{"contents", QJsonValue::String}
{"label", ParameterDelegate::ParameterType::String},
{"contents", ParameterDelegate::ParameterType::String}
};

m_delegate = ParameterDelegate::create(
Expand Down Expand Up @@ -51,8 +51,9 @@ QSharedPointer<const AnalyzerResult> Metadata::analyzeBits(
QSharedPointer<PluginActionProgress> progress)
{
Q_UNUSED(progress)
if (!m_delegate->validate(parameters)) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1").arg(name()));
QStringList invalidations = m_delegate->validate(parameters);
if (!invalidations.isEmpty()) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1:\n%2").arg(name()).arg(invalidations.join("\n")));
}

QString label = parameters.value("label").toString();
Expand Down
7 changes: 4 additions & 3 deletions src/hobbits-plugins/analyzers/WidthFramer/widthframer.cpp
Expand Up @@ -7,7 +7,7 @@
WidthFramer::WidthFramer()
{
QList<ParameterDelegate::ParameterInfo> infos = {
{"width", QJsonValue::Double}
{"width", ParameterDelegate::ParameterType::Integer}
};

m_delegate = ParameterDelegate::create(
Expand Down Expand Up @@ -53,8 +53,9 @@ QSharedPointer<const AnalyzerResult> WidthFramer::analyzeBits(
const QJsonObject &parameters,
QSharedPointer<PluginActionProgress> progress)
{
if (!m_delegate->validate(parameters)) {
return AnalyzerResult::error("Invalid parameters passed to Width Framer");
QStringList invalidations = m_delegate->validate(parameters);
if (!invalidations.isEmpty()) {
return AnalyzerResult::error(QString("Invalid parameters passed to %1:\n%2").arg(name()).arg(invalidations.join("\n")));
}
progress->setProgressPercent(10);

Expand Down
Expand Up @@ -52,7 +52,7 @@ QString WidthFramerForm::title()

bool WidthFramerForm::setParameters(QJsonObject parameters)
{
if (!m_delegate->validate(parameters)) {
if (!m_delegate->validate(parameters).isEmpty()) {
return false;
}

Expand Down
17 changes: 9 additions & 8 deletions src/hobbits-plugins/displays/Ascii/ascii.cpp
Expand Up @@ -13,10 +13,10 @@ Ascii::Ascii() :


QList<ParameterDelegate::ParameterInfo> infos = {
{"font_size", QJsonValue::Double},
{"column_grouping", QJsonValue::Double},
{"show_headers", QJsonValue::Bool},
{"encoding", QJsonValue::String}
{"font_size", ParameterDelegate::ParameterType::Integer},
{"column_grouping", ParameterDelegate::ParameterType::Integer},
{"show_headers", ParameterDelegate::ParameterType::Boolean},
{"encoding", ParameterDelegate::ParameterType::String}
};

m_delegate = ParameterDelegate::create(
Expand Down Expand Up @@ -64,7 +64,7 @@ void Ascii::setDisplayHandle(QSharedPointer<DisplayHandle> displayHandle)
{
m_handle = displayHandle;
DisplayHelper::connectHoverUpdates(this, this, m_handle, [this](QPoint& offset, QSize &symbolSize, int &grouping, int &bitsPerSymbol) {
if (!m_delegate->validate(m_lastParams)) {
if (!m_delegate->validate(m_lastParams).isEmpty()) {
return false;
}
offset = headerOffset(m_lastParams);
Expand All @@ -86,8 +86,9 @@ QSharedPointer<DisplayResult> Ascii::renderDisplay(QSize viewportSize, const QJs
{
Q_UNUSED(progress)
m_lastParams = parameters;
if (!m_delegate->validate(parameters)) {
return DisplayResult::error("Invalid parameters");
QStringList invalidations = m_delegate->validate(parameters);
if (!invalidations.isEmpty()) {
return DisplayResult::error(QString("Invalid parameters passed to %1:\n%2").arg(name()).arg(invalidations.join("\n")));
}

QString encoding = parameters.value("encoding").toString();
Expand All @@ -105,7 +106,7 @@ QSharedPointer<DisplayResult> Ascii::renderDisplay(QSize viewportSize, const QJs

QSharedPointer<DisplayResult> Ascii::renderOverlay(QSize viewportSize, const QJsonObject &parameters)
{
if (!m_delegate->validate(m_lastParams)) {
if (!m_delegate->validate(m_lastParams).isEmpty()) {
return DisplayResult::nullResult();
}
QSize fontSize = DisplayHelper::textSize(DisplayHelper::monoFont(m_lastParams.value("font_size").toInt()), "0");
Expand Down

0 comments on commit cef1055

Please sign in to comment.