0ad/binaries/data/mods/mod/gui/modmod/validatemod.js
2025-12-18 18:16:11 +01:00

153 lines
3.4 KiB
JavaScript

const g_ModProperties = {
// example: "community-mod"
"name": {
"required": true,
"type": "string",
"validate": validateName
},
// example: "0.29.0"
"version": {
"required": true,
"type": "string",
"validate": validateVersion
},
// example: ["0ad<=0.0.28", "rote"]
"dependencies": {
"required": true,
"type": "object",
"validate": validateDependencies
},
// example: "Community Mod"
"label": {
"required": true,
"type": "string",
"validate": validateLabel
},
// example: "The Community Mod is a community-led effort to improve the gameplay."
"description": {
"required": true,
"type": "string"
},
// example: "https://wildfiregames.com/"
"url": {
"required": false,
"type": "string"
}
};
/**
* Tests if the string only contains alphanumeric characters and _ -
*/
const g_RegExpName = /[a-zA-Z0-9\-_]+/;
/**
* Tests if the version string consists only of numbers and at most two periods.
*/
const g_RegExpVersion= /[0-9]+(\.[0-9]+){0,2}/;
/**
* Version checks in mod dependencies can use these operators.
*/
const g_RegExpComparisonOperator = /(<=|>=|<|>|=)/;
/**
* Tests if a dependency compares a mod version against another, for instance "0ad<=0.0.16".
*/
const g_RegExpComparison = globalRegExp(new RegExp(g_RegExpName.source + g_RegExpComparisonOperator.source + g_RegExpVersion.source));
/**
* The label may not be empty.
*/
const g_RegExpLabel = /.*\S.*/;
function globalRegExp(regexp)
{
return new RegExp("^" + regexp.source + "$");
}
/**
* Returns whether the mod defines all required properties and whether all properties are valid.
* Shows a notification if not.
*/
function validateMod(folder, modData, notify)
{
let valid = true;
for (const propertyName in g_ModProperties)
{
const property = g_ModProperties[propertyName];
if (modData[propertyName] === undefined)
{
if (!property.required)
continue;
if (notify)
warn("Mod '" + folder + "' does not define '" + propertyName + "'!");
valid = false;
}
if (typeof modData[propertyName] !== property.type)
{
if (notify)
warn(propertyName + " in mod '" + folder + "' is not of the type '" + property.type + "'!");
valid = false;
continue;
}
if (property.validate && !property.validate(folder, modData, notify))
valid = false;
}
return valid;
}
function validateName(folder, modData, notify)
{
const valid = modData.name.match(globalRegExp(g_RegExpName));
if (!valid && notify)
warn("mod name of " + folder + " may only contain alphanumeric characters, but found '" + modData.name + "'!");
return valid;
}
function validateVersion(folder, modData, notify)
{
const valid = modData.version.match(globalRegExp(g_RegExpVersion));
if (!valid && notify)
warn("mod version of " + folder + " may only contain numbers and at most 2 periods, but found '" + modData.version + "'!");
return valid;
}
function validateDependencies(folder, modData, notify)
{
let valid = true;
for (const dependency of modData.dependencies)
{
valid = valid && (
dependency.match(globalRegExp(g_RegExpName)) ||
dependency.match(globalRegExp(g_RegExpComparison)));
if (!valid && notify)
warn("mod folder " + folder + " requires an invalid dependency '" + dependency + "'!");
}
return valid;
}
function validateLabel(folder, modData, notify)
{
const valid = modData.label.match(g_RegExpLabel);
if (!valid && notify)
warn("mod label of " + folder + " may not be empty!");
return valid;
}