From 7c805a5bf548259b2fc4faaefb72f915cd3f5eb2 Mon Sep 17 00:00:00 2001 From: David Schoonover Date: Mon, 18 Jun 2012 12:54:13 -0700 Subject: [PATCH] Adds generic non-Backbone base class. --- lib/base/base.co | 73 ++++++++++++ lib/base/index.co | 4 +- lib/util/event/ready-emitter.co | 5 +- lib/util/event/waiting-emitter.co | 7 +- lib/util/underscore/_functions.co | 220 +++++++++++++++++++++++++++++++++++++ lib/util/underscore/class.co | 53 +++++++++ lib/util/underscore/function.co | 27 +++++ lib/util/underscore/functions.co | 220 ------------------------------------- lib/util/underscore/index.co | 2 + 9 files changed, 383 insertions(+), 228 deletions(-) create mode 100644 lib/base/base.co create mode 100644 lib/util/underscore/_functions.co create mode 100644 lib/util/underscore/class.co create mode 100644 lib/util/underscore/function.co delete mode 100644 lib/util/underscore/functions.co diff --git a/lib/base/base.co b/lib/base/base.co new file mode 100644 index 0000000..c35ed5d --- /dev/null +++ b/lib/base/base.co @@ -0,0 +1,73 @@ +{EventEmitter} = require 'events' +EventEmitter::trigger = EventEmitter::emit + +{ _, op +} = require 'kraken/util' + + + +/** + * @class Eventful base class. + * @extends EventEmitter + */ +class Base extends EventEmitter + + /** + * After the super chain has exhausted (but not necessarily at the end + * of init -- it depends on when you super()), Base will publish a 'new' + * event on the instance's class, allowing anyone to subscribe to + * notifications about new objects. + * @constructor + */ + -> + @__class__ = @.. + @__superclass__ = @..superclass + @__apply_bind__() + super() + @__class__.emit 'new', this + + + ### Auto-Bound methods + + /** + * A list of method-names to bind on `initialize`; set this on a subclass to override. + * @type Array + */ + __bind__ : [] + + /** + * Applies the contents of `__bind__`. + */ + __apply_bind__: -> + names = _ @pluckSuperAndSelf '__bind__' .chain().flatten().compact().unique().value() + _.bindAll this, ...names if names.length + + + + getClassName: -> + "#{@..name or @..displayName}" + + toString: -> + "#{@getClassName()}()" + + + + ### Class Methods + + @extended = (Subclass) -> + # copy over all class methods, including this + for own k, v in this + Subclass[k] = v if typeof v is 'function' + Subclass.__super__ = @:: + Subclass + + + + +for k of <[ getSuperClasses pluckSuper pluckSuperAndSelf ]> + Base[k] = Base::[k] = _.methodize _[k] + +Base import EventEmitter:: + + +module.exports = Base diff --git a/lib/base/index.co b/lib/base/index.co index 9a31587..17b6726 100644 --- a/lib/base/index.co +++ b/lib/base/index.co @@ -1,7 +1,9 @@ +exports.Base = require 'kraken/base/base' mixins = require 'kraken/base/base-mixin' models = require 'kraken/base/base-model' views = require 'kraken/base/base-view' cache = require 'kraken/base/model-cache' cascading = require 'kraken/base/cascading-model' data_binding = require 'kraken/base/data-binding' -exports import mixins import models import views import cache import cascading import data_binding +exports import mixins import models import views \ + import cache import cascading import data_binding diff --git a/lib/util/event/ready-emitter.co b/lib/util/event/ready-emitter.co index a247026..049ad1c 100644 --- a/lib/util/event/ready-emitter.co +++ b/lib/util/event/ready-emitter.co @@ -1,11 +1,10 @@ -{EventEmitter} = require 'events' -EventEmitter::trigger = EventEmitter::emit +Base = require 'kraken/base/base' /** * @class An EventEmitter that auto-triggers new handlers once "ready". */ -class ReadyEmitter extends EventEmitter +class ReadyEmitter extends Base readyEventName : 'ready' ready : false diff --git a/lib/util/event/waiting-emitter.co b/lib/util/event/waiting-emitter.co index 3cc2733..3f29ddf 100644 --- a/lib/util/event/waiting-emitter.co +++ b/lib/util/event/waiting-emitter.co @@ -1,12 +1,11 @@ -{EventEmitter} = require 'events' -EventEmitter::trigger = EventEmitter::emit - +Base = require 'kraken/base/base' /** * @class An EventEmitter with a ratchet-up waiting counter. + * @extends Base */ -class WaitingEmitter extends EventEmitter +class WaitingEmitter extends Base /** * Count of outstanding tasks. diff --git a/lib/util/underscore/_functions.co b/lib/util/underscore/_functions.co new file mode 100644 index 0000000..7327961 --- /dev/null +++ b/lib/util/underscore/_functions.co @@ -0,0 +1,220 @@ +_ = require 'underscore' + + +slice = [].slice +hasOwn = {}.hasOwnProperty +objToString = {}.toString + +toArray = _.toArray + + + +decorate = (fn) -> + if not fn.__decorated__ + for name of _pet.FUNCTION_METHODS + m = _[name] + fn[name] = m.__methodized__ or methodize m + fn.__decorated__ = true + return fn + +methodize = (fn) -> + m = fn.__methodized__ + return m if m + + g = fn.__genericized__ + return g.__wraps__ if g and g.__wraps__ + + m = fn.__methodized__ = (args...) -> + args.unshift this + return fn.apply this, args + + m.__wraps__ = fn + return decorate m + + + +_pet = module.exports = \ + function pet (o, start=0, end=undefined) -> + if _.isArguments o + o = _.toArray o, start, end + + return decorate o if typeof o is 'function' + return _ o + +# function methods to be attached on call to _(fn) +_pet.FUNCTION_METHODS = [ + 'bind', 'bindAll', 'memoize', + 'delay', 'defer', 'throttle', 'debounce', 'once', 'after', + 'wrap', 'compose', + 'unwrap', 'partial', 'curry', 'flip', 'methodize', 'aritize', 'limit' +] + + +class2name = "Boolean Number String Function Array Date RegExp Object" + .split(" ") + .reduce ((class2name, name) -> + class2name[ "[object "+name+"]" ] = name + return class2name), {} + + +## Objects +_.mixin + + has: (o, v) -> + vals = if _.isArray(o) then o else _.values(o) + return vals.indexOf(v) is not -1 + + remove: (o, vs...) -> + if _.isArray(o) + _.each vs, (v) -> + idx = o.indexOf v + if idx is not -1 + o.splice idx, 1 + else + _.each o, (v, k) -> + if vs.indexOf(v) != -1 + delete o[k] + return o + + set: (o, key, value, def) -> + if o and key? and (value? or def?) + o[key] = value ? def + return o + + attr: (o, key, value, def) -> + return o if not o or key is undefined + + if _.isPlainObject key + return _.extend o, key + + if (value ? def) is not undefined + return _.set o, key, value, def + + return o[key] + + + +## Types +_.mixin + + basicTypeName: (o) -> + return if o is null then "null" else (class2name[objToString.call(o)] || "Object") + + isWindow: (o) -> + return o and typeof o is "object" and "setInterval" of o + + isPlainObject: (o) -> + # Must be an Object. + # Because of IE, we also have to check the presence of the constructor property. + # Make sure that DOM nodes and window objects don't pass through, as well + if not o or basicTypeName(o) is not "Object" or o.nodeType or _.isWindow(o) + return false + + # Not own constructor property? must be Object + C = o.constructor + if C and not hasOwn.call(o, "constructor") and not hasOwn.call(C.prototype, "isPrototypeOf") + return false + + # Own properties are enumerated firstly, so to speed up, + # if last one is own, then all properties are own. + for key in o + ; # semicolon **on new line** is required by coffeescript to denote empty statement. + + return key is void or hasOwn.call(o, key) + + +## Arrays +_.mixin + + toArray: (iterable, start=0, end=undefined) -> + _.slice toArray(iterable), start, end + + flatten: (A) -> + _.reduce do + slice.call(arguments) + (flat, v) -> + flat.concat( if _.isArray v then _.reduce(v, arguments.callee, []) else v ) + [] + + + +## Functions +_ofArity = _.memoize do + (n, limit) -> + args = ( '$'+i for i from 0 til n ).join(',') + name = ( if limit then 'limited' else 'artized' ) + apply_with = ( if limit then "[].slice.call(arguments, 0, #{n})" else 'arguments' ) + return eval " + (function #{name}(fn){ + var _fn = function(#{args}){ return fn.apply(this, #{apply_with}); }; + _fn.__wraps__ = fn; + return _(_fn); + })" + +_.mixin do + methodize + + unwrap: (fn) -> + (fn and _.isFunction(fn) and _.unwrap(fn.__wraps__)) or fn + + + partial: (fn, ...args) -> + partially = -> + fn.apply this, args.concat slice.call(arguments) + _ partially import { __wraps__:fn } + + + genericize: (fn) -> + return that if fn.__genericized__ + return that if fn.__methodized__?.__wraps__ + + fn.__genericized__ = (...args) -> + fn.apply args.shift(), args + + _ fn.__genericized__ import { __wraps__:fn } + + + curry: (fn, ...args) -> + return fn unless _.isFunction fn + return fn.apply this, args if fn.__curried__ + + L = fn.length or _.unwrap(fn).length + return fn.apply this, args if args.length >= L + + curried = -> + _args = args.concat slice.call(arguments) + return fn.apply this, _args if _args.length >= L + _args.unshift fn + _.curry.apply this, _args + + _ curried import + __wraps__ : fn + __curried__ : args + + + flip: (fn) -> + return that if fn.__flipped__ + + fn.__flipped__ = -> + [arguments[0], arguments[1]] = [arguments[1], arguments[0]] + fn ... + + _ fn.__flipped__ import { __wraps__:fn } + + + aritize: (fn, n) -> + return fn if fn.length is n + fn.__aritized__ or= {} + fn.__aritized__[n] or= _ofArity(n, false)(fn) + + + limit: (fn, n) -> + fn.__limited__ or= {} + fn.__limited__[n] or= _ofArity(n, true)(fn) + + + + + + +_.extend _pet, _ diff --git a/lib/util/underscore/class.co b/lib/util/underscore/class.co new file mode 100644 index 0000000..1381cb4 --- /dev/null +++ b/lib/util/underscore/class.co @@ -0,0 +1,53 @@ +_ = require 'underscore' + + +_cls = + + /** + * @returns {Array} The list of all superclasses for this class + * or object. Typically does not include Object or Function due to + * the prototype's constructor being set by the subclass. + */ + getSuperClasses : function getSuperClasses(Cls) + return [] unless Cls + + if Cls.__superclass__ or Cls.superclass or Cls.__super__?.constructor + superclass = that unless that is Cls + unless superclass + Cls = Cls.constructor if typeof Cls is not 'function' + if Cls.__superclass__ or Cls.superclass or Cls.__super__?.constructor + superclass = that unless that is Cls + unless superclass then [] + else [superclass].concat getSuperClasses superclass + + /** + * Looks up an attribute on the prototype of each class in the class + * hierarchy. Values from Object or Function are not typically included -- + * see the note at `getSuperClasses()`. + * + * @param {Object} obj Object on which to reflect. + * @param {String} prop Property to nab. + * @returns {Array} List of the values, from closest parent to furthest. + */ + pluckSuper : (obj, prop) -> + return [] unless obj + _ _cls.getSuperClasses(obj) .chain() + .pluck 'prototype' + .pluck prop + .value() + + /** + * As `.pluckSuper()` but includes value of `prop` on passed `obj`. Values + * from Object or Function are not typically included -- see the note + * at `getSuperClasses()`. + * + * @returns {Array} List of the values, starting with the object's own + * value, and then moving from closest parent to furthest. + */ + pluckSuperAndSelf : (obj, prop) -> + return [] unless obj + [ obj[prop] ].concat _cls.pluckSuper(obj, prop) + + + +exports import _cls diff --git a/lib/util/underscore/function.co b/lib/util/underscore/function.co new file mode 100644 index 0000000..a6e50b7 --- /dev/null +++ b/lib/util/underscore/function.co @@ -0,0 +1,27 @@ +_ = require 'underscore' + +_fn = + + /** + * Decorates a function so that its receiver (`this`) is always added as the + * first argument, followed by the call arguments. + * @returns {Function} + */ + methodize : (fn) -> + m = fn.__methodized__ + return m if m + + g = fn.__genericized__ + return that if g?.__wraps__ + + m = fn.__methodized__ = (...args) -> + args.unshift this + fn.apply this, args + + m.__wraps__ = fn + m + + + +exports import _fn + diff --git a/lib/util/underscore/functions.co b/lib/util/underscore/functions.co deleted file mode 100644 index 7327961..0000000 --- a/lib/util/underscore/functions.co +++ /dev/null @@ -1,220 +0,0 @@ -_ = require 'underscore' - - -slice = [].slice -hasOwn = {}.hasOwnProperty -objToString = {}.toString - -toArray = _.toArray - - - -decorate = (fn) -> - if not fn.__decorated__ - for name of _pet.FUNCTION_METHODS - m = _[name] - fn[name] = m.__methodized__ or methodize m - fn.__decorated__ = true - return fn - -methodize = (fn) -> - m = fn.__methodized__ - return m if m - - g = fn.__genericized__ - return g.__wraps__ if g and g.__wraps__ - - m = fn.__methodized__ = (args...) -> - args.unshift this - return fn.apply this, args - - m.__wraps__ = fn - return decorate m - - - -_pet = module.exports = \ - function pet (o, start=0, end=undefined) -> - if _.isArguments o - o = _.toArray o, start, end - - return decorate o if typeof o is 'function' - return _ o - -# function methods to be attached on call to _(fn) -_pet.FUNCTION_METHODS = [ - 'bind', 'bindAll', 'memoize', - 'delay', 'defer', 'throttle', 'debounce', 'once', 'after', - 'wrap', 'compose', - 'unwrap', 'partial', 'curry', 'flip', 'methodize', 'aritize', 'limit' -] - - -class2name = "Boolean Number String Function Array Date RegExp Object" - .split(" ") - .reduce ((class2name, name) -> - class2name[ "[object "+name+"]" ] = name - return class2name), {} - - -## Objects -_.mixin - - has: (o, v) -> - vals = if _.isArray(o) then o else _.values(o) - return vals.indexOf(v) is not -1 - - remove: (o, vs...) -> - if _.isArray(o) - _.each vs, (v) -> - idx = o.indexOf v - if idx is not -1 - o.splice idx, 1 - else - _.each o, (v, k) -> - if vs.indexOf(v) != -1 - delete o[k] - return o - - set: (o, key, value, def) -> - if o and key? and (value? or def?) - o[key] = value ? def - return o - - attr: (o, key, value, def) -> - return o if not o or key is undefined - - if _.isPlainObject key - return _.extend o, key - - if (value ? def) is not undefined - return _.set o, key, value, def - - return o[key] - - - -## Types -_.mixin - - basicTypeName: (o) -> - return if o is null then "null" else (class2name[objToString.call(o)] || "Object") - - isWindow: (o) -> - return o and typeof o is "object" and "setInterval" of o - - isPlainObject: (o) -> - # Must be an Object. - # Because of IE, we also have to check the presence of the constructor property. - # Make sure that DOM nodes and window objects don't pass through, as well - if not o or basicTypeName(o) is not "Object" or o.nodeType or _.isWindow(o) - return false - - # Not own constructor property? must be Object - C = o.constructor - if C and not hasOwn.call(o, "constructor") and not hasOwn.call(C.prototype, "isPrototypeOf") - return false - - # Own properties are enumerated firstly, so to speed up, - # if last one is own, then all properties are own. - for key in o - ; # semicolon **on new line** is required by coffeescript to denote empty statement. - - return key is void or hasOwn.call(o, key) - - -## Arrays -_.mixin - - toArray: (iterable, start=0, end=undefined) -> - _.slice toArray(iterable), start, end - - flatten: (A) -> - _.reduce do - slice.call(arguments) - (flat, v) -> - flat.concat( if _.isArray v then _.reduce(v, arguments.callee, []) else v ) - [] - - - -## Functions -_ofArity = _.memoize do - (n, limit) -> - args = ( '$'+i for i from 0 til n ).join(',') - name = ( if limit then 'limited' else 'artized' ) - apply_with = ( if limit then "[].slice.call(arguments, 0, #{n})" else 'arguments' ) - return eval " - (function #{name}(fn){ - var _fn = function(#{args}){ return fn.apply(this, #{apply_with}); }; - _fn.__wraps__ = fn; - return _(_fn); - })" - -_.mixin do - methodize - - unwrap: (fn) -> - (fn and _.isFunction(fn) and _.unwrap(fn.__wraps__)) or fn - - - partial: (fn, ...args) -> - partially = -> - fn.apply this, args.concat slice.call(arguments) - _ partially import { __wraps__:fn } - - - genericize: (fn) -> - return that if fn.__genericized__ - return that if fn.__methodized__?.__wraps__ - - fn.__genericized__ = (...args) -> - fn.apply args.shift(), args - - _ fn.__genericized__ import { __wraps__:fn } - - - curry: (fn, ...args) -> - return fn unless _.isFunction fn - return fn.apply this, args if fn.__curried__ - - L = fn.length or _.unwrap(fn).length - return fn.apply this, args if args.length >= L - - curried = -> - _args = args.concat slice.call(arguments) - return fn.apply this, _args if _args.length >= L - _args.unshift fn - _.curry.apply this, _args - - _ curried import - __wraps__ : fn - __curried__ : args - - - flip: (fn) -> - return that if fn.__flipped__ - - fn.__flipped__ = -> - [arguments[0], arguments[1]] = [arguments[1], arguments[0]] - fn ... - - _ fn.__flipped__ import { __wraps__:fn } - - - aritize: (fn, n) -> - return fn if fn.length is n - fn.__aritized__ or= {} - fn.__aritized__[n] or= _ofArity(n, false)(fn) - - - limit: (fn, n) -> - fn.__limited__ or= {} - fn.__limited__[n] or= _ofArity(n, true)(fn) - - - - - - -_.extend _pet, _ diff --git a/lib/util/underscore/index.co b/lib/util/underscore/index.co index d24ced1..f59c17f 100644 --- a/lib/util/underscore/index.co +++ b/lib/util/underscore/index.co @@ -2,8 +2,10 @@ _ = require 'underscore' _.str = require 'underscore.string' _.mixin _.str.exports() +_.mixin require 'kraken/util/underscore/function' _.mixin require 'kraken/util/underscore/array' _.mixin require 'kraken/util/underscore/object' +_.mixin require 'kraken/util/underscore/class' _.mixin require 'kraken/util/underscore/kv' _.mixin require 'kraken/util/underscore/string' -- 1.7.0.4