diff --git a/lib/interface.js b/lib/interface.js index 8cba127..6e41e8d 100644 --- a/lib/interface.js +++ b/lib/interface.js @@ -35,28 +35,46 @@ var util = require( './util' ), * extended. To extend an existing interface, call its extend() method, or use * the extend() method of this module. * - * @param {Object} def interface definition + * @param {string=} name optional name + * @param {Object} def interface definition * * @return {Interface} new interface */ -module.exports = function( def ) +module.exports = function() { - // if the first argument is an object, then we are declaring an interface - if ( typeof def !== 'object' ) - { - throw TypeError( - "Must provide interface definition when declaring interface" - ); - } + var def = {}, + name = ''; - // ensure we have the proper number of arguments (if they passed in too - // many, it may signify that they don't know what they're doing, and likely - // they're not getting the result they're looking for) - if ( arguments.length > 1 ) + // anonymous interface + if ( typeof arguments[ 0 ] === 'object' ) { - throw Error( - "Expecting one argument for Interface definition; " + - arguments.length + " given." + def = arguments[ 0 ]; + + // ensure we have the proper number of arguments (if they passed in too + // many, it may signify that they don't know what they're doing, and likely + // they're not getting the result they're looking for) + if ( arguments.length > 1 ) + { + throw Error( + "Expecting one argument for Interface definition; " + + arguments.length + " given." + ); + } + } + // named class + else if ( typeof arguments[ 0 ] === 'string' ) + { + name = arguments[ 0 ]; + def = arguments[ 1 ]; + + // add the name to the definition + def.__name = name; + } + else + { + // we don't know what to do! + throw TypeError( + "Expecting anonymous interface definition or named interface definition" ); } @@ -114,12 +132,20 @@ var extend = ( function( extending ) props = args.pop() || {}, base = args.pop() || Interface, prototype = new base(), + iname = '', members = member_builder.initMembers( prototype, prototype, prototype ) ; + // grab the name, if one was provided + if ( iname = props.__name ) + { + // we no longer need it + delete props.__name; + } + // sanity check inheritCheck( prototype ); @@ -165,6 +191,8 @@ var extend = ( function( extending ) } ); attachExtend( new_interface ); + attachStringMethod( new_interface, iname ); + new_interface.prototype = prototype; new_interface.constructor = new_interface; @@ -258,3 +286,20 @@ function attachExtend( func ) }); } + +/** + * Provides more sane/useful output when interface is converted to a string + * + * @param {Object} func interface + * @param {string=} iname interface name + * + * @return {undefined} + */ +function attachStringMethod( func, iname ) +{ + func.toString = ( iname ) + ? function() { return '[object Interface <' + iname + '>]'; } + : function() { return '[object Interface]'; } + ; +} + diff --git a/test/test-interface-name.js b/test/test-interface-name.js new file mode 100644 index 0000000..cfe5689 --- /dev/null +++ b/test/test-interface-name.js @@ -0,0 +1,85 @@ +/** + * Tests interface naming + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package test + */ + +var common = require( './common' ), + assert = require( 'assert' ), + Interface = common.require( 'interface' ) +; + + +/** + * Interfaces may be named by passing the name as the first argument to the + * module + */ +( function testInterfaceAcceptsName() +{ + assert.doesNotThrow( function() + { + var iface = Interface( 'Foo', {} ); + + assert.equal( + Interface.isInterface( iface ), + true, + "Interface defined with name is returned as a valid interface" + ); + }, Error, "Interface accepts name" ); + + // the second argument must be an object + assert.throws( function() + { + Interface( 'Foo', 'Bar' ); + }, TypeError, "Second argument to named interface must be the definition" ); +} )(); + + +/** + * By default, anonymous interfacees should simply state that they are a + * interface when they are converted to a string + */ +( function testConvertingAnonymousInterfaceToStringYieldsInterfaceString() +{ + assert.equal( + Interface( {} ).toString(), + '[object Interface]', + "Converting anonymous interface to string yields interface string" + ); +} )(); + + +/** + * If the interface is named, then the name should be presented when it is + * converted to a string + */ +( function testConvertingNamedInterfaceToStringYieldsInterfaceStringContainingName() +{ + var name = 'Foo'; + + assert.equal( + Interface( name, {} ).toString(), + '[object Interface <' + name + '>]', + "Converting named interface to string yields string with name of " + + "interface" + ); +} )(); +