Skip to content

Instantly share code, notes, and snippets.

@raix
Last active December 27, 2015 18:59
Show Gist options
  • Save raix/7374295 to your computer and use it in GitHub Desktop.
Save raix/7374295 to your computer and use it in GitHub Desktop.
An idea for parsing arguments in Meteor.js With this code declaring interfaces and validating input just got a bit easier..
typeNames = function(type) {
if (Match.test(type, [Match.Any])) return 'array';
if (type === Object) return 'object';
if (type === String) return 'string';
if (type === Number) return 'number';
if (type === Boolean) return 'boolean';
if (type === Function) return 'function';
return typeof type;
};
// If arguments are correctly parsed then return the object
// otherwice we return the new Error() object - the user can then throw this
// if relevant
parseArguments = function(args, names, types) {
// Names are array of strings or string in array
check(names, [Match.OneOf(String, [String])]);
check(types, [Match.Any]);
check(args, [Match.Any]);
// Check lengths, we throw this since this function needs this
if (names.length !== types.length) {
throw new RangeError('names and types dont match');
}
// Make sure we dont have too many arguments to begin with, return parse error
if (args.length > names.length) {
return new Error('too many arguments');
}
var requiredLength = 0;
// Count required arguments
for (var i = 0; i < names.length; i++) {
if (names[i] === ''+names[i]) {
requiredLength++;
}
}
// Make sure we have enough arguments
if (args.length < requiredLength) {
return new Error('not enough arguments');
}
// The returning result object
var result = {};
// Argument index
var a = 0;
// Init number of allowed optionals left
var optionalsLeft = args.length - requiredLength;
// Go find a match
for (var i = 0; i < types.length; i++) {
// If name is a string then argument is required
var isRequired = names[i] === ''+names[i];
var name = (isRequired)? names[i] : names[i][0];
// Check to see if we have a type match
if (Match.test(args[a], types[i])) {
if (isRequired || optionalsLeft > 0) {
// If not required then decrease number of optionals left
isRequired || optionalsLeft--;
// Check if we are about to overwrite an existing key
if (typeof result[name] !== 'undefined') {
throw new Error('duplicate argument names "' + name + '"');
}
// Set key and value on result
result[name] = args[a]; // could use args[a++]
// Goto next argument
a++;
}
} else {
// We are not allowed to skip over isRequired arguments
if (isRequired) {
return new TypeError('type (' + typeNames(args[a]) +
') did not match (' + typeNames(types[i]) +
') for required argument "' + names[i] + '"');
} // If not isRequired we skip to the next
// If more arguments left than interface allows then we are
// out of bounds...
if (args.length - a > types.length - i) {
return new TypeError('type (' + typeNames(args[a]) +
') did not match remaining arguments.' +
' eg.: "' + names[i-1] + '" (' + typeNames(types[i-1]) + ')');
}
}
}
return result;
};
Test = function(/* name, [options], [nr], nr2, [on], callback */) {
var self = this;
var result = parseArguments(arguments,
['name', ['options'], ['nr'], 'nr2', ['on'], 'callback' ],
[String, Object, Number, Number, Boolean, Function]
);
var self.options = {
test: 'default'
};
// If the parseArgument returned an instance of Error we can
// throw it or test with another parseArguments if we also
// accept a complete different input pattern
if (result instanceof Error) {
throw result;
} else {
// Finally the user got it right, we can now trust the
// input
console.log('GOT:');
console.log(result);
// Extend and overwrite options - we could use a more
// specific match than using Object
_.extend(self.options, result.options || {});
// Maybe use the callback
result.callback('We do have a callback since its required');
}
};
@aldeed
Copy link

aldeed commented Nov 13, 2013

Here are some tests I did with it:

var at = function(/* name, [options], [nr], nr2, [on], callback */) {
  var result = parseArguments(arguments,
            ['name', ['options'], ['nr'], 'nr2', ['on'], 'callback'],
            [String, Object, Number, Number, Boolean, Function]
            );

  // If the parseArgument returned an instance of Error we can
  // throw it or test with another parseArguments if we also
  // accept a complete different input pattern
  if (result instanceof Error) {
    console.log(result.message);
  } else {
    console.log('GOT:', result);
  }
};

at();
at(function() {});
at("myName", 200);
at("myName", {});
at("myName", 200, function() {});
at("myName", {}, 200, function() {});
at("myName", {}, 100, 200, function() {});
at("myName", 100, 200, function() {});
at("myName", 100, 200, true, function() {});
at("myName", 100, 200, "true", function() {});
at("myName", {}, 100, 200, true, function() {});
at("myName", null, null, 200, null, function() {});
at("myName", void 0, void 0, 200, void 0, function() {});
at("myName", 200, true, function() {});

@raix
Copy link
Author

raix commented Nov 19, 2013

Thanks, nice :) yeah null is a bitch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment