diff --git a/src/mami.js/args.js b/src/mami.js/args.js index a7f518c..2cc6e95 100644 --- a/src/mami.js/args.js +++ b/src/mami.js/args.js @@ -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; +}; diff --git a/src/mami.js/colpick/picker.jsx b/src/mami.js/colpick/picker.jsx index 70497c4..7d4176f 100644 --- a/src/mami.js/colpick/picker.jsx +++ b/src/mami.js/colpick/picker.jsx @@ -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); diff --git a/src/mami.js/controls/msgbox.jsx b/src/mami.js/controls/msgbox.jsx index ff06545..7865492 100644 --- a/src/mami.js/controls/msgbox.jsx +++ b/src/mami.js/controls/msgbox.jsx @@ -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; diff --git a/src/mami.js/controls/tabs.js b/src/mami.js/controls/tabs.js index 0546a15..05a9ed1 100644 --- a/src/mami.js/controls/tabs.js +++ b/src/mami.js/controls/tabs.js @@ -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, diff --git a/src/mami.js/controls/views.js b/src/mami.js/controls/views.js index 7061592..c22b664 100644 --- a/src/mami.js/controls/views.js +++ b/src/mami.js/controls/views.js @@ -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); diff --git a/src/mami.js/settings/settings.js b/src/mami.js/settings/settings.js index 3092f81..bab9385 100644 --- a/src/mami.js/settings/settings.js +++ b/src/mami.js/settings/settings.js @@ -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(); diff --git a/src/mami.js/title.js b/src/mami.js/title.js index c1b7378..bf7a8f1 100644 --- a/src/mami.js/title.js +++ b/src/mami.js/title.js @@ -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;