Rewrote argument dict checker to actually make it human readable.

This commit is contained in:
flash 2024-04-23 21:43:08 +00:00
parent 24d9fef94c
commit d4440e03cc
7 changed files with 200 additions and 90 deletions

View file

@ -1,64 +1,180 @@
const MamiArguments = (function() {
return {
type: (name, type, fallback, throwOnFail) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof type !== 'string')
throw 'type must be a string';
const MamiArgs = (argName, input, builder) => {
if(input !== undefined && typeof input !== 'object')
throw `${argName} must be an object or undefined`;
if(typeof builder !== 'function')
throw 'builder must be a function';
return { name: name, type: type, fallback: fallback, throwOnFail: !!throwOnFail };
},
const args = new Map;
check: (name, fallback, check, throwOnFail) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof check !== 'function')
throw 'check must be a function';
builder(name => {
if(typeof name !== 'string')
throw 'name must be a string';
return { name: name, fallback: fallback, check: check, throwOnFail: !!throwOnFail };
},
const checkDefined = () => {
if(args.has(name))
throw `${name} has already been defined`;
};
checkDefined();
filter: (name, type, fallback, filter, throwOnFail) => {
if(typeof name !== 'string')
throw 'name must be a string';
if(typeof type !== 'string')
throw 'type must be a string';
if(typeof filter !== 'function')
throw 'filter must be a function';
let created = false;
const checkCreated = () => {
if(created)
throw 'argument has already been defined';
};
return { name: name, type: type, fallback: fallback, filter: filter, throwOnFail: !!throwOnFail };
},
const info = {
name: name,
type: undefined,
filter: undefined,
constraint: undefined,
fallback: undefined,
throw: true,
required: false,
min: undefined,
max: undefined,
};
verify: (args, options) => {
if(!Array.isArray(options))
throw 'options must be an array';
if(typeof args !== 'object')
args = {};
const blueprint = {
type: value => {
if(typeof value !== 'string' && !Array.isArray(value))
throw 'type must be a javascript type or array of valid string values';
for(const option of options) {
if(typeof option !== 'object')
throw 'entries of options must be objects';
if(!('name' in option) || typeof option.name !== 'string')
throw 'option.name must be a string';
const name = option.name;
const type = 'type' in option && typeof option.type === 'string' ? option.type : undefined;
const fallback = 'fallback' in option ? option.fallback : undefined;
const check = 'check' in option && typeof option.check === 'function' ? option.check : undefined;
const filter = 'filter' in option && typeof option.filter === 'function' ? option.filter : undefined;
const throwOnFail = 'throwOnFail' in option && option.throwOnFail;
checkCreated();
if(!(name in args)
|| (type !== undefined && typeof args[name] !== type)
|| (check !== undefined && !check(args[name]))) {
if(throwOnFail)
throw `${name} is invalid`;
info.type = value;
return blueprint;
},
default: value => {
checkCreated();
info.fallback = value;
args[name] = fallback;
} else if(filter !== undefined)
args[name] = filter(args[name]);
if(info.type === undefined && info.constraint === undefined)
info.type = typeof info.fallback;
return blueprint;
},
filter: value => {
if(typeof value !== 'function')
throw 'filter must be a function';
checkCreated();
info.filter = value;
return blueprint;
},
constraint: value => {
if(typeof value !== 'function')
throw 'constraint must be a function';
checkCreated();
info.constraint = value;
return blueprint;
},
throw: value => {
checkCreated();
info.throw = value === undefined || value === true;
return blueprint;
},
required: value => {
checkCreated();
info.required = value === undefined || value === true;
return blueprint;
},
min: value => {
checkCreated();
if(typeof value !== 'number')
throw 'value must be a number';
info.min = value;
return blueprint;
},
max: value => {
checkCreated();
if(typeof value !== 'number')
throw 'value must be a number';
info.max = value;
return blueprint;
},
done: () => {
checkCreated();
checkDefined();
args.set(name, info);
},
};
return blueprint;
});
const inputIsNull = input === null;
if(inputIsNull)
input = {};
const output = {};
for(const [name, info] of args) {
let value = info.fallback;
if(info.name in input) {
const defaultOrThrow = ex => {
if(info.throw)
throw ex;
value = info.fallback;
};
value = input[info.name];
if(info.type !== undefined) {
if(Array.isArray(info.type)) {
if(!info.type.includes(value))
defaultOrThrow(`${info.name} must match an enum value`);
} else {
const type = typeof value;
let resolved = false;
if(type !== info.type) {
if(type === 'string') {
if(info.type === 'number') {
value = parseFloat(value);
resolved = true;
if(info.min !== undefined && value < info.min)
value = info.min;
else if(info.max !== undefined && value > info.max)
value = info.max;
} else if(info.type === 'boolean') {
value = !!value;
resolved = true;
}
} else if(info.type === 'string') {
value = value.toString();
resolved = true;
}
} else resolved = true;
if(!resolved)
defaultOrThrow(`${info.name} must be of type ${info.type}`);
}
}
return args;
},
};
})();
if(info.constraint !== undefined && !info.constraint(value))
defaultOrThrow(`${info.name} did not fit within constraints`);
// you could have a devious streak with this one cuz the checks aren't rerun
// but surely you wouldn't fuck yourself over like that
if(info.filter !== undefined)
value = info.filter(value);
} else if(info.required) {
if(inputIsNull)
throw `${argName} must be a non-null object`;
throw `${argName} is missing required key ${info.name}`;
}
output[info.name] = value;
}
return output;
};

View file

@ -9,18 +9,18 @@
#include colpick/vraw.jsx
const MamiColourPicker = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.filter('colour', 'number', 0, num => Math.min(0xFFFFFF, Math.max(0, num))),
MamiArguments.type('posX', 'number', -1),
MamiArguments.type('posY', 'number', -1),
MamiArguments.type('presets', 'object', []),
MamiArguments.type('showPresetsTab', 'boolean', options.presets.length > 0),
MamiArguments.type('showGridTab', 'boolean', true),
MamiArguments.type('showSlidersTab', 'boolean', true),
MamiArguments.type('showHexValue', 'boolean', true),
MamiArguments.type('showRawValue', 'boolean', true),
MamiArguments.type('showDialogButtons', 'boolean', true),
]);
options = MamiArgs('options', options, define => {
define('colour').default(0).filter(value => Math.min(0xFFFFFF, Math.max(0, value))).done();
define('posX').default(-1).done();
define('posY').default(-1).done();
define('presets').default([]).constraint(value => Array.isArray(value)).done();
define('showPresetsTab').default(true).done();
define('showGridTab').default(true).done();
define('showSlidersTab').default(true).done();
define('showHexValue').default(true).done();
define('showRawValue').default(true).done();
define('showDialogButtons').default(true).done();
});
let colour;
const needsColour = [];
@ -115,7 +115,7 @@ const MamiColourPicker = function(options) {
},
});
if(options.showPresetsTab)
if(options.showPresetsTab && options.presets.length > 0)
tabs.add(new MamiColourPickerPresetsTab(options.presets));
if(options.showGridTab)
tabs.add(new MamiColourPickerGridTab);

View file

@ -238,9 +238,9 @@ const MamiMessageBoxDialog = function(info) {
};
const MamiMessageBoxControl = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.check('parent', undefined, value => value instanceof Element, true),
]);
options = MamiArgs('options', options, define => {
define('parent').required().constraint(value => value instanceof Element).done();
});
const parent = options.parent;
const container = new MamiMessageBoxContainer;

View file

@ -2,11 +2,11 @@
#include uniqstr.js
const MamiTabsControl = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.type('onAdd', 'function', undefined, true),
MamiArguments.type('onRemove', 'function', undefined, true),
MamiArguments.type('onSwitch', 'function', undefined, true),
]);
options = MamiArgs('options', options, define => {
define('onAdd').required().type('function').done();
define('onRemove').required().type('function').done();
define('onSwitch').required().type('function').done();
});
const onAdd = options.onAdd,
onRemove = options.onRemove,

View file

@ -2,9 +2,9 @@
#include utility.js
const MamiViewsControl = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.check('body', undefined, value => value instanceof Element, true),
]);
options = MamiArgs('options', options, define => {
define('body').required().constraint(value => value instanceof Element).done();
});
const targetBody = options.body;
targetBody.classList.toggle('views', true);

View file

@ -146,7 +146,7 @@ const MamiSettings = function(storageOrPrefix, eventTarget) {
const pub = {
type: value => {
if(typeof value !== 'string' && !Array.isArray(value))
throw 'type must be a javascript type or array of valid string values.';
throw 'type must be a javascript type or array of valid string values';
checkCreated();

View file

@ -1,18 +1,12 @@
#include args.js
const MamiWindowTitle = function(options) {
options = MamiArguments.verify(options, [
MamiArguments.type('getName', 'function', () => ''),
MamiArguments.type('setTitle', 'function', text => document.title = text),
MamiArguments.check('strobeInterval', 500, val => {
const valType = typeof val;
return val === 'number' || val === 'function';
}),
MamiArguments.check('strobeRepeat', 5, val => {
const valType = typeof val;
return val === 'number' || val === 'function';
}),
]);
options = MamiArgs('options', options, define => {
define('getName').default(() => {}).done();
define('setTitle').default(text => { document.title = text; }).done();
define('strobeInterval').constraint(value => typeof value === 'number' || typeof value === 'function').default(500).done();
define('strobeRepeat').constraint(value => typeof value === 'number' || typeof value === 'function').default(5).done();
});
const getName = options.getName;