From ab4e2275ce70ed482da212bda50e78cba23e5371 Mon Sep 17 00:00:00 2001 From: "Alex A. Naanou" Date: Sat, 20 Aug 2016 23:46:34 +0300 Subject: [PATCH] added base code... Signed-off-by: Alex A. Naanou --- actions.js | 1407 ++++++++++++++++++++++++++++++++++++++++++++++++++ features.js | 698 +++++++++++++++++++++++++ package.json | 4 +- 3 files changed, 2107 insertions(+), 2 deletions(-) create mode 100755 actions.js create mode 100755 features.js diff --git a/actions.js b/actions.js new file mode 100755 index 0000000..bed7ed5 --- /dev/null +++ b/actions.js @@ -0,0 +1,1407 @@ +/********************************************************************** +* +* +* +**********************************************************************/ +(typeof(define)[0]=='u'?function(f){module.exports=f(require)}:define)( +function(require){ var module={} // makes module AMD/node compatible... +/*********************************************************************/ + +var args2array = require('lib/util').args2array +var toggler = require('lib/toggler') +var object = require('lib/object') + + + +/*********************************************************************/ +// Actions +// +// Actions are an extension to the JavaScript object model tailored for +// a set of specific tasks. +// +// Goals: +// - provide a unified mechanism to define and manage user API's for +// use in UI-hooks, keyboard mappings, scripting, ... etc. +// - a means to generate configuration UI's +// - a means to generate documentation +// +// +// The main entities: +// +// Action set +// - an object containing a number of actions, +// - optionally, directly or indirectly inherited from MetaActions +// and/or other action sets, +// - the action handlers are bound relative to it (._action_handlers) +// +// Action +// +// + pre + pre + + post + post + +// Action event handler: o-------x o-------x +// v ^ +// Actions o-------x o-------x +// v ^ +// Root Action o---|---x +// +// - a method, created by Action(..), +// - calls all the shadowed/overloaded actions in the inheritance +// chain in sequence implicitly, +// NOTE: there is no way to prevent an action in the chain from +// running, this is by design, i.e. no way to fully shadow. +// - actions that do not shadow anything are called root actions. +// - returns the action set by default (for call chaining), +// - the base/root action can return any value. +// NOTE: if undefined is returned, it will be replaced by the +// action context/action set. +// NOTE: there is no distinction between root and other actions +// other than that root action's return values are not +// ignored. +// - can consist of two parts: the first is called before the +// shadowed action (pre-callback) and the second after (post-callback). +// - post-callback has access to the return value and can modify it +// but not replace it. +// - can be bound to, a-la an event, calling the handlers when it is +// called, +// +// Action (event) handler +// - a function, +// - can be bound to run before and/or after the action itself, +// - is local to an action set it was bound via, +// - when an action is triggered from an action set, all the pre +// handlers in its inheritance chain will be called before the +// respective actions they are bound to and all the post handlers +// are called directly after. +// - pre handlers are passed the same arguments the original actions +// got when it was called. +// - post action handlers will get the root action result as first +// argument succeeded by the action arguments. +// +// +// +// The action system main protocols: +// +// 1) Documentation generation and introspection (MetaActions) +// +// .toString() +// -> code of original action function +// +// .getDoc() +// .getDoc([, ..]) +// -> dict of action-name, doc +// +// .a.getHandlerDocStr() +// -> formated string of action handlers +// +// .actions +// -> list of action names +// +// .length +// -> number of actions +// +// +// 2) Event-like callbacks for actions (MetaActions, Action) +// +// .on('action', function(){ ... }) +// .on('action.post', function(){ ... }) +// +// .on('action.pre', function(){ ... }) +// +// +// 3) A mechanism to define and extend already defined actions +// This replaces / complements the standard JavaScript overloading +// mechanisms (Action, Actions) +// +// // Actions... +// var X = Actions({ +// m: [function(){ console.log('m') }] +// }) +// var O = Actions(X, { +// m: [function(){ +// console.log('pre') +// return function(res){ +// console.log('post') +// } +// }] +// }) +// +// NOTE: what is done here is similar to calling O.__proto__.m.call(..) +// but is implicit, and not dependant on the original containing +// object name/reference ('O'), thus enabling an action to be +// referenced and called from any object and still chain correctly. +// +// +// +// Secondary action protocols: +// +// 1) A mechanism to manually call the pre/post stages of an action +// +// Pre phase... +// .pre() +// .pre(, [, ..]) +// -> +// +// Post phase... +// .post(, ) +// -> +// +// This is internally used to implement the action call as well as the +// chaining callbacks (see below). +// +// All action protocol details apply. +// +// NOTE: there is not reliable way to call the post phase without first +// calling the pre phase due to how the pre phase is defined (i.e. +// pre phase functions can return post phase functions). +// +// +// 2) A mechanism to chain/wrap actions or an action and a function. +// This enables us to call a callback or another action (inner) between +// the root action's (outer) pre and post stages. +// +// Outer action o-------x o-------x +// v ^ +// Inner action/callback o---|---x +// +// A trivial example: +// +// actionSet.someAction.chainApply(actionsSet, +// function(){ +// // this gets run between someAction's pre and post +// // stages... +// }, +// args) +// +// This is intended to implement protocols where a single action is +// intended to act as a hook point (outer) and multiple different +// implementations (inner) within a single action set can be used as +// entry points. +// +// // Protocol root action (outer) definition... +// protocolAction: [function(){}], +// +// // Implementation actions (inner)... +// implementationAction1: [function(){ +// return this.protocolAction.chainApply(this, function(){ +// // ... +// }, ..) +// }] +// +// implementationAction2: [function(){ +// return this.protocolAction.chainApply(this, function(){ +// // ... +// }, ..) +// }] +// +// Now calling any of the 'implementation' actions will execute code +// in the following order: +// 1) pre phase of protocol action (outer) +// 2) implementation action (inner) +// 3) post phase of protocol action (outer) +// +// NOTE: this will not affect to protocol/signature of the outer action +// in any way. +// NOTE: both the inner and outer actions will get passed the same +// arguments. +// NOTE: another use-case is testing/debugging actions. +// NOTE: this is effectively the inside-out of normal action overloading. +// NOTE: there is intentionally no shorthand for this feature, to avoid +// confusion and to discourage the use of this feature unless +// really necessary. +// +// +// 3) .__call__ action / handler +// This action if defined is called for every action called. It behaves +// like any other action but with a fixed signature, it always receives +// the action name as first argument and a list of action arguments as +// the second arguments, and as normal a result on the post phase. +// +// NOTE: it is not necessary to define the actual action, binding to a +// handler will also work. +// NOTE: one should not call actions directly from within a __call__ +// handler as that will result in infinite recursion. +// XXX need a way to prevent this... +// NOTE: one should use this with extreme care as this will introduce +// an overhead on all the actions if not done carefully. +// +// +// +/*********************************************************************/ +// helpers... + +var normalizeTabs = function(str){ + str = str.split(/\n/g) + + // get min number of leading tabs... + var i = str.length == 2 && /^\t/.test(str[1]) ? + str[1].split(/^(\t+)/)[1].length - 1 + : Math.min.apply(null, str + // skip first line... + .slice(1) + // skip empty strings... + .filter(function(l){ return l.trim() != '' }) + // count leading tabs... + .map(function(l){ + return /^\t+/.test(l) ? + l.split(/^(\t+)/)[1].length + : 0})) + + return (str[0] +'\n' + + str + .slice(1) + // trim leading tabs... + .map(function(l){ return l.slice(i) }).join('\n') + // replace tabs... + .replace(/\t/g, ' ')) + // remove leading and trailing whitespace... + .trim() +} + + +var doWithRootAction = +module.doWithRootAction = +function(func){ + return function(name){ + var handlers = (this.getHandlerList + || MetaActions.getHandlerList) + .call(this, name) + + return func.call(this, handlers.pop(), name) + } +} + + + +/*********************************************************************/ + +// Construct an action object... +// +// Action function format: +// +// // pre event code... +// function(..){ +// ... // pre code +// } +// +// // pre/post event code... +// function(..){ +// ... // pre code +// return function(, ..){ +// ... // post code +// } +// } +// +// // same as above but using a deferred instead of a callback... +// function(..){ +// ... // pre code +// return $.Deferred() +// .done(function(, ..){ +// ... // post code +// }) +// } +// +// +// An action is essentially a method with several additional features: +// +// - actions are split into two stages: +// pre: the code of the method is executed before the action +// event is fired +// post: if the action returns a callback function or a deferred +// object it will be executed after the event is fired +// NOTE: the signature if the post stage is the same as the +// action's with the added return value as first argument +// (the rest og the arguments are shifted by 1). +// +// - actions automatically call the shadowed action, the pre stage is +// executed down-up while the post stage is run in reverse order, +// i.e. the pre is going down and the post is going up. +// +// - actions provide an event-like mechanism to register handlers or +// callbacks. These callbacks are local to a specific object and will +// be fired on action event/call starting from the current action +// caller and down the inheritance chain, i.e. all event handlers +// registered from the current object and up to the base action set +// will be fired. +// +// - an action will return the deepest (root) action's return, if that +// return is undefined, then the action set is returned instead. +// +// - action arguments are "threaded" through the action chain down and +// root action return value and arguments are threaded back up the +// action chain. +// +// NOTE: if the root handler is instance of Toggler (jli) and the action +// is called with '?'/'??' as argument, then the toggler will be +// called with the argument and return the result bypassing the +// handlers. +// NOTE: actions once defined do not depend on the inheritance hierarchy, +// other than the .getHandlerList(..) method. If this method is not +// found in the inheritance chain (i.e. the link to MetaActions) +// was severed, then the default will be used: +// MetaActions.getHandlerList(..) +// This makes it possible to redefine the method if needed but +// prevents the system from breaking when an action set gets +// disconnected from MetaActions. This can be useful, for example, +// to remove .on(..) / .off(..) handler functionality. +// XXX is this correct?? +// NOTE: by default an action will return 'this', i.e. the action set +// object the action was called from. +// +// XXX add more metadata/docs: +// .section +// .category +// ... +// XXX might be a good idea to add an option to return the full results... +var Action = +module.Action = +function Action(name, doc, ldoc, func){ + // we got called without a 'new'... + if(this == null || this.constructor !== Action){ + // XXX using something like .apply(.., arguemnts) would be more + // generel but have no time to figure out how to pass it + // to new without the later complaining... + return new Action(name, doc, ldoc, func) + } + + // prevent action overloading... + if(this[name] != null){ + throw 'action "'+name+'" already exists.' + } + + // create the actual instance we will be returning... + //var meth = function(){ + // return meth.chainApply(this, null, arguments) } + var meth = function(){ + return meth.chainApply(this, null, arguments) } + meth.__proto__ = this.__proto__ + + // populate the action attributes... + //meth.name = name + Object.defineProperty(meth, 'name', { + value: name, + }) + meth.doc = doc + meth.long_doc = ldoc + + meth.func = func + + // make introspection be a bit better... + meth.toString = func.toString.bind(func) + + return meth +} +// this will make action instances behave like real functions... +Action.prototype.__proto__ = Function + +// The pre/post stage runners... +// +// .pre(context, args) +// -> data +// +// .post(context, data) +// -> result +// +// +// NOTE: All the defaults should be handled by the pre stage, post will +// process data assuming that it is correct. +// +// XXX revise the structure.... +// ...is it a better idea to define action methods in an object +// and assign that??? +Action.prototype.pre = function(context, args){ + args = args || [] + + var res = context + var outer = this.name + + var getHandlers = context.getHandlers || MetaActions.getHandlers + var isToggler = context.isToggler || MetaActions.isToggler + + // get the handler list... + var handlers = getHandlers.call(context, outer) + + // special case: toggler -- do not handle special args... + // XXX should this be here??? + if(isToggler.call(context, outer) + && args.length == 1 + && (args[0] == '?' || args[0] == '??')){ + return { + result: handlers.slice(-1)[0].pre.apply(context, args), + } + } + + var call_wrapper = outer != '__call__' ? + getHandlers.call(context, '__call__') + : [] + + // wrapper handlers: pre phase... + call_wrapper = call_wrapper + .map(function(a){ + if(a.pre){ + res = a.pre.call(context, outer, args) + + // if a handler returns a function or a deferred, + // register is as a post handler... + if(res + && res !== context + && (res instanceof Function + || res.resolve instanceof Function)){ + a.post = res + } + } + return a + }) + + // handlers: pre phase... + handlers + // NOTE: current action will get included and called by the code + // above and below, so no need to explicitly call func... + // NOTE: pre handlers are called FIFO, i.e. the last defined first... + .map(function(a){ + if(a.pre){ + res = a.pre.apply(context, args) + + // if a handler returns a function or a deferred, + // register is as a post handler... + if(res + && res !== context + && (res instanceof Function + || res.resolve instanceof Function)){ + a.post = res + + // reset the result... + res = context + } + } + return a + }) + + // return context if nothing specific is returned... + res = res === undefined ? context : res + + return { + arguments: args, + + wrapper: call_wrapper, + handlers: handlers, + + result: res, + } +} +Action.prototype.post = function(context, data){ + var res = data.result + + var args = data.arguments || [] + // the post handlers get the result as the first argument... + args.splice(0, 0, res) + + var outer = this.name + + // handlers: post phase... + data.handlers && data.handlers + // NOTE: post handlers are called LIFO -- last defined last... + .reverse() + .forEach(function(a){ + a.post + && (a.post.resolve ? + a.post.resolve.apply(a.post, args) + : a.post.apply(context, args)) + }) + + // wrapper handlers: post phase... + data.wrapper && data.wrapper + // NOTE: post handlers are called LIFO -- last defined last... + .reverse() + .forEach(function(a){ + a.post + && (a.post.resolve ? + a.post.resolve.apply(a.post, res, outer, args.slice(1)) + : a.post.call(context, res, outer, args.slice(1))) + }) + + return res +} + +// Chaining... +Action.prototype.chainApply = function(context, inner, args){ + args = [].slice.call(args || []) + var res = context + var outer = this.name + + var data = this.pre(context, args) + + // call the inner action/function if preset.... + if(inner){ + //res = inner instanceof Function ? + inner instanceof Function ? + inner.call(context, args) + : inner instanceof Array && inner.length > 0 ? + context[inner.pop()].chainCall(context, inner, args) + : typeof(inner) == typeof('str') ? + context[inner].chainCall(context, null, args) + : null + } + + return this.post(context, data) +} +Action.prototype.chainCall = function(context, inner){ + return this.chainApply(context, inner, args2array(arguments).slice(2)) +} + + + +//--------------------------------------------------------------------- + +// A base action-set object... +// +// This will define a set of action-set specific methods and helpers. +// +// XXX .off(...) needs more work... +// XXX need a mechanism to publish specific attrs... +var MetaActions = +module.MetaActions = { + // List actions... + // + get actions(){ + var res = [] + for(var k in this){ + // avoid recursion, skip props... + var cur = this + var prop = Object.getOwnPropertyDescriptor(cur, k) + while(!prop && cur.__proto__ != null){ + var cur = cur.__proto__ + var prop = Object.getOwnPropertyDescriptor(cur, k) + } + if(prop.get != null){ + continue + } + //if(k == 'actions' || k == 'length'){ + // continue + //} + // get only actions... + if(this[k] instanceof Action){ + res.push(k) + } + } + return res + }, + + // Number of defined actions... + // + get length(){ + return this.actions.length + }, + + // Get action documentation... + // + getDoc: function(actions){ + var res = {} + var that = this + actions = actions == null ? this.actions + : arguments.length > 1 ? args2array(arguments) + : typeof(actions) == typeof('str') ? [actions] + : actions + + // get the first defined set of docs in the inheritance chain... + actions.forEach(function(n){ + var cur = that + res[n] = [] + // go up the proto chain... + while(cur.__proto__ != null){ + if(cur[n] != null && cur[n].doc != null){ + res[n] = [ cur[n].doc, cur[n].long_doc, cur[n].name ] + break + } + cur = cur.__proto__ + } + }) + return res + }, + + getPath: function(actions){ + var res = {} + var that = this + actions = actions == null ? this.actions + : arguments.length > 1 ? args2array(arguments) + : typeof(actions) == typeof('str') ? [actions] + : actions + + // get the first defined set of docs in the inheritance chain... + actions.forEach(function(n){ + var cur = that + // go up the proto chain... + while(cur.__proto__ != null){ + if(cur[n] != null && cur[n].doc != null){ + var doc = cur[n].doc + var long_doc = cur[n].long_doc + break + } + cur = cur.__proto__ + } + + res[(doc && doc.replace(/[\\\/]$/, '/'+n)) || n] = [n, doc, long_doc] + }) + return res + }, + + + // Get action handlers from the inheritance chain... + // + // NOTE: this collects both the event handlers (in order of hierarchy, + // then order of definition) and actions (in order of hierarchy) + // NOTE: this is the correct order for 'pre' calling, but is the + // reverse of how the 'post' handlers must be called. + // + // For more docs on handler sequencing and definition see: .on(..) + getHandlerList: function(name){ + var handlers = [] + var cur = this + while(cur.__proto__ != null){ + // get action "event" handlers... + if(cur.hasOwnProperty('_action_handlers') + && name in cur._action_handlers){ + handlers.splice.apply(handlers, + [handlers.length, 0].concat(cur._action_handlers[name])) + } + + // get the overloading action... + // NOTE: this will get all the handlers including the root + // and the current handlers... + // NOTE: this will ignore "shadows" that are not actions... + if(cur.hasOwnProperty(name) && cur[name] instanceof Action){ + handlers.push(cur[name].func) + } + + cur = cur.__proto__ + } + return handlers + }, + + // Get structured action handler definitions... + // + // Format: + // [ + // { + // // NOTE: only one handler per level can be present, either + // // .pre or .post, this does not mean that one can + // // not define both, just that they are stored separately + // pre|post: , + // }, + // ... + // ] + // + // XXX need to get parent action or definition context for each level... + // XXX is this simpler to use than the original .getHandlerList(..) + // XXX rename this.... + getHandlers: function(name){ + return (this.getHandlerList || MetaActions.getHandlerList).call(this, name) + .map(function(a){ + var res = { + // action doc... + // XXX + } + + if(!a.post_handler){ + res.pre = a + + } else { + res.post = a.post_handler + } + + return res + }) + }, + + // Test if the action is a Toggler... + // + // NOTE: an action is considered a toggler only if it's base action + // is a toggler (instance of Toggler), thus, the same "top" + // action can be or not be a toggler in different contexts. + // + // For more info on togglers see: lib/toggler.js + isToggler: doWithRootAction(function(action){ + return action instanceof toggler.Toggler }), + + // Register an action callback... + // + // Register a post action callback + // .on('action', [, ]) + // .on('action.post', [, ]) + // -> + // + // Register a pre action callback + // .on('action.pre', [, ]) + // -> + // + // Modes: + // 'pre' - the handler is fired before the action is triggered, + // and if the handler returns a deferred or a function + // then that will get resolved, called resp. after + // the action is done. + // 'post' - the handler is fired after the action is finished. + // this is the default. + // + // Handler Arguments: + // 'pre' - the handler will get the same arguments as the main + // action when called. + // 'post' - the handler will get the action return value followed + // by action arguments. + // + // The optional tag marks the handler to enable group removal via + // .off(..) + // + // NOTE: 'post' mode is the default. + // + // XXX should we have multiple tags per handler??? + on: function(actions, b, c){ + var handler = typeof(c) == 'function' ? c : b + var tag = typeof(c) == 'function' ? b : c + + // XXX make this split by whitespace... + actions = typeof(actions) == 'string' ? actions.split(/ +/) : actions + + var that = this + actions.forEach(function(action){ + // prepare the handler... + var mode = action.split('.') + action = mode[0] + mode = mode[1] + + // keep the original handler for future use... + var a_handler = handler + + // a post handler (default)... + if(mode == null || mode == 'post'){ + var old_handler = a_handler + a_handler = function(){ return old_handler } + a_handler.post_handler = old_handler + // NOTE: this is set so as to identify the handler + // for removal via. .off(..) + a_handler.orig_handler = old_handler.orig_handler || old_handler + + // not pre mode... + } else if(mode != 'pre') { + // XXX + throw 'Unknown action mode: '+action+'.'+mode + } + + a_handler.tag = tag + + // register handlers locally only... + if(!that.hasOwnProperty('_action_handlers')){ + that._action_handlers = {} + } + if(!(action in that._action_handlers)){ + that._action_handlers[action] = [] + } + // register a handler only once... + if(that._action_handlers[action].indexOf(a_handler) < 0){ + // NOTE: last registered is first... + that._action_handlers[action].splice(0, 0, a_handler) + } + }) + + return this + }, + + // Remove an action callback... + // + // Remove all handlers from action: + // .off('action') + // .off('action', '*') + // .off('action', 'all') + // -> + // + // Remove specific handler from action: + // .off('action', ) + // -> + // + // Remove handlers from action by tag: + // .off('action', ) + // -> + // + // NOTE: the handler passed to .off(..) for removal must be the same + // as the handler passed to .on(..) / .one(..) + off: function(actions, handler){ + if(this.hasOwnProperty('_action_handlers')){ + + actions = actions == '*' ? Object.keys(this._action_handlers) + : typeof(actions) == 'string' ? actions.split(' ') + : actions + + var that = this + actions.forEach(function(action){ + var mode = action.split('.') + action = mode[0] + mode = mode[1] + + // get the handlers... + var h = that._action_handlers[action] || [] + + // remove explicit handler... + if(typeof(handler) == 'function'){ + var i = -1 + if(mode == null || mode == 'post'){ + // XXX find via e.orig_handler == handler && e.mode == 'post' + h.forEach(function(e, j){ + // NOTE: we will only get the first match... + if(e.orig_handler === handler && i == -1){ + i = j + } + }) + + } else if(mode == 'pre'){ + i = h.indexOf(handler) + } + + // NOTE: unknown modes are skipped... + if(i >= 0){ + h.splice(i, 1) + } + + // remove all handlers... + } else if(handler == null || handler == 'all' || handler == '*'){ + h.splice(0, h.length) + + // remove handlers by tag... + } else { + // filter out everything that mathches a tag in-place... + h.splice.apply(h, + [0, h.length] + .concat(h.filter(function(e){ + return e.tag != handler }))) + } + }) + } + + return this + }, + + // Register an action callback that will only fire once per event... + // + // This is signature compatible with .on(..) + one: function(actions, b, c){ + var _handler = typeof(c) == 'function' ? c : b + var tag = typeof(c) == 'function' ? b : c + + actions = typeof(actions) == 'string' ? actions.split(' *') : actions + + var that = this + actions.forEach(function(action){ + // NOTE: we are using both 'that' below and 'this', so as + // to separate the call context and the bind context, + // .off(..) must be called at the bind context while + // the actual action is called from the call context + // NOTE: we are not using the closure _handler below to + // keep the code introspectable, and direct the user + // to the original function. + var handler = function(){ + // remove handler... + that.off(action, handler.orig_handler) + + // call the actual supplied handler function... + return handler.orig_handler.apply(this, arguments) + } + handler.orig_handler = _handler + that.on(action, tag, handler) + }) + + return this + }, + + // Apply/call a function/action "inside" an action... + // + // .chainApply(outer, inner) + // .chainApply(outer, inner, arguments) + // -> result + // + // .chainCall(outer, inner) + // .chainCall(outer, inner, ..) + // -> result + // + // + // The inner action call is completely nested as base of the outer + // action. + // + // Outer action o-------x o-------x + // v ^ + // Inner action o---|---x + // + // The given arguments are passed as-is to both the outer and inner + // actions. + // The base inner action return value is passed to the outer action + // .post handlers. + // + // NOTE: these call the action's .chainApply(..) and .chainCall(..) + // methods, thus is not compatible with non-action methods... + // NOTE: .chianCall('action', ..) is equivalent to .action.chianCall(..) + chainApply: function(outer, inner, args){ + return this[outer].chainApply(this, inner, args) }, + chainCall: function(outer, inner){ + return this[outer].chainApply(this, inner, args2array(arguments).slice(2)) }, + + // Get mixin object in inheritance chain... + // + // NOTE: if pre is true this will return the chain item before the + // mixin, this is useful, for example, to remove mixins, see + // .mixout(..) for an example... + getMixin: function(from, pre){ + var cur = this + var proto = this.__proto__ + while(proto != null){ + // we have a hit... + if(proto.hasOwnProperty('__mixin_source') + && proto.__mixin_source === from){ + return pre ? cur : proto + } + // go to next item in chain... + cur = proto + proto = cur.__proto__ + } + return null + }, + + // Mixin a set of actions into this... + // + // NOTE: if 'all' is set them mixin all the actions available, + // otherwise only mixin local actions... + // NOTE: this will override existing own attributes. + inlineMmixin: function(from, all, descriptors, all_attr_types){ + // defaults... + descriptors = descriptors || true + all_attr_types = all_attr_types || false + + if(all){ + var keys = [] + for(var k in from){ + keys.push(k) + } + } else { + var keys = Object.keys(from) + } + + var that = this + keys.forEach(function(k){ + /* + // XXX is this the right way to go??? + // check if we are not overwriting anything... + if(that.hasOwnProperty(k)){ + console.warn('WARNING:', that,'already has attribute', k, '- skipping...') + return + } + */ + + // properties.... + var prop = Object.getOwnPropertyDescriptor(from, k) + if(descriptors && prop.get != null){ + // NOTE: so as to be able to delete this on mixout... + prop.configurable = true + Object.defineProperty(that, k, prop) + + + // actions and other attributes... + } else { + var attr = from[k] + if(all_attr_types || attr instanceof Action){ + that[k] = attr + } + } + }) + + return this + }, + + // Same as .inlineMmixin(..) but isolates a mixin in a seporate object + // in the inheritance chain... + // + mixin: function(from, all, descriptors, all_attr_types){ + var proto = Object.create(this.__proto__) + + // mixinto an empty object + proto.inlineMmixin(from, all, descriptors, all_attr_types) + + // mark the mixin for simpler removal... + proto.__mixin_source = from + + this.__proto__ = proto + + return this + }, + + // Mixin a set of local actions into an object... + // + // XXX this will not work on non-actions... + mixinTo: function(to, all, descriptors, all_attr_types){ + return this.mixin.call(to, this, all, descriptors, all_attr_types) + }, + + + // Remove mixed in actions from this... + // + // NOTE: this will only remove local actions, inherited actions will + // not be affected... + // NOTE: this will not affect event handlers, they should be removed + // manually if needed... + inlineMixout: function(from, all, descriptors, all_attr_types){ + // defaults... + descriptors = descriptors || true + all_attr_types = all_attr_types || false + + if(all){ + var keys = [] + for(var k in from){ + keys.push(k) + } + } else { + var keys = Object.keys(from) + } + + var locals = Object.keys(this) + var that = this + keys.forEach(function(k){ + var prop = Object.getOwnPropertyDescriptor(from, k) + + // descriptor... + if(descriptors && prop.get != null){ + if(prop.get === Object.getOwnPropertyDescriptor(that, k).get){ + delete that[k] + } + + // actions and other attrs... + } else { + var attr = from[k] + if((all_attr_types || attr instanceof Action) + // remove only local attrs... + && locals.indexOf(k) >= 0){ + delete that[k] + } + } + }) + + return this + }, + + // This is similare in effect but different in mechanics to .inlineMixout(..) + // + // This will find and remove a mixin object from the inheritance chian. + // + // NOTE: this will remove only the first occurance of a mixin. + mixout: function(from){ + var o = this.getMixin(from, true) + + // pop the mixin off the chain... + if(o != null){ + o.__proto__ = o.__proto__.__proto__ + } + + return this + }, + + // Remove a set of local mixed in actions from object... + // + mixoutFrom: function(to, all, descriptors, all_attr_types){ + return this.mixout.call(to, this, all, descriptors, all_attr_types) + }, + + // Create a child object... + // + // NOTE: this will create a .config in the instance that inherits from + // this.config... + // NOTE: this will not copy/clone any data. + // + // XXX is this correct??? + // XXX should this be an action??? + clone: function(full){ + var o = Object.create(this) + if(this.config){ + if(full){ + o.config = JSON.parse(JSON.stringify(this.config)) + } else { + o.config = Object.create(this.config) + } + } + return o + }, + + // doc generators... + // + // XXX would be nice to make these prop of the action itself but I + // do not see a way to do this properly yet -- we can't get to + // the action context from the action dynamically... + getHandlerDocStr: function(name){ + var lst = this.getHandlers(name) + var str = '' + + var handler = function(p){ + if(lst.length == 0){ + //str += p + '---' + return + } + + // indicate root action... + p = lst.length == 1 ? p+'| ' : p+' ' + + var cur = lst.shift() + + if(cur.pre){ + str += p + + normalizeTabs(cur.pre.toString()).replace(/\n/g, p) + + p + } + + handler(p + ' |') + + str += p + + if(cur.post){ + str += p + p + + normalizeTabs(cur.post.toString()).replace(/\n/g, p) + } + } + + handler('\n|') + + return str + }, + getHandlerDocHTML: function(name){ + var lst = this.getHandlers(name) + var res = $('
') + + var handler = function(p){ + if(lst.length == 0){ + return + } + + var cur = lst.shift() + p = $('
') + .appendTo(p) + + if(cur.pre){ + p.append($('
').html(
+					normalizeTabs(cur.pre.toString())
+						.replace(/return/g, 'return')))
+			}
+
+			handler(p)
+
+			if(cur.post){
+				p.append($('
').html(
+					normalizeTabs(cur.post.toString())))
+			}
+		}
+
+		handler(res)
+
+		return res
+	},
+
+
+	// This will create a .config in instances...
+	// NOTE: this makes Actions compatible with lib/object.js...
+	__init__: function(){
+		if(this.__proto__.config && !Object.hasOwnProperty(this, 'config')){
+			this.config = Object.create(this.__proto__.config)
+		}
+	},
+}
+
+
+var ActionSet =
+module.ActionSet =
+object.makeConstructor('ActionSet', MetaActions)
+
+
+
+// An action set...
+//
+//	Actions()
+//	Actions(, )
+//		-> actions
+//
+// This will pre-process an object to setup the action mechanics.
+//
+// If the 'this and prototype both contain a .config attribute then this
+// will make set .config.__proto__ = .config 
+//
+//
+// The action format:
+// 	{
+// 		// full format...
+// 		 : [
+// 			,
+// 			,
+// 			
+// 		],
+//
+// 		// short doc only...
+// 		 : [
+// 			,
+// 			
+// 		],
+//
+// 		// only the code...
+// 		 : [
+// 			
+// 		],
+// 		...
+// 	}
+//
+//
+// NOTE: the action function is always last.
+// NOTE: if  is not given, MetaActions will be used as default.
+//
+// For more documentation see: Action(..).
+//
+// XXX add doc, ldoc, tags and save them to each action...
+// XXX is .config processing correct here???
+var Actions =
+module.Actions =
+function Actions(a, b){
+	var obj = b == null ? a : b
+	var proto = b == null ? b : a
+	obj = obj || new ActionSet()
+
+	// NOTE: this is intentionally done only for own attributes...
+	Object.keys(obj).forEach(function(k){
+		// NOTE: we are not getting the attrs directly (vars = obj[k])
+		// 		as that will trigger the getters on an object that is
+		// 		not in a consistent state...
+		// NOTE: this will skip all the getters and setters, they will 
+		// 		be included as-is...
+		var arg = Object.getOwnPropertyDescriptor(obj, k).value
+
+		// skip non-arrays...
+		if(arg == null 
+				|| arg.constructor !== Array 
+				// and arrays the last element of which is not a function...
+				|| !(arg[arg.length-1] instanceof Function)){
+			return
+		}
+
+		var func = arg.pop()
+
+		// create a new action...
+		obj[k] = new Action(k, arg[0], arg[1], func)
+	})
+
+	if(proto != null){
+		obj.__proto__ = proto
+
+		// XXX is this the right way to go???
+		if(obj.config != null && proto.config != null){
+			obj.config.__proto__ = proto.config
+		}
+	}
+
+	return obj
+}
+
+
+
+/*********************************************************************/
+
+var test =
+module.test =
+function test(){
+	// NOTE: this is needed only to add action methods to TestActions...
+	var BaseActions = new ActionSet()
+
+	var TestActions = 
+	module.TestActions = 
+	Actions(BaseActions, {
+		testActionGen1: ['baisc test action...',
+			'some extra info',
+			function(){
+				console.log('  test 1!')
+				return function(){
+					console.log('  test 2!')
+				}
+			}],
+
+		testActionGen2: ['baisc 2\'nd gen test action...',
+			// no extra info...
+			function(){
+				console.log('  test gen 2!')
+				this.testActionGen1()
+			}],
+	})
+
+	var TestActions2 = 
+	module.TestActions2 = 
+	Actions(TestActions, {
+		// NOTE: this looks like an action and feels like an action but 
+		// 		actually this is a callback as an action with this name 
+		// 		already exists...
+		testActionGen1: [
+			function(){
+				console.log('  pre callback!')
+				return function(){
+					console.log('  post callback!')
+				}
+			}],
+
+		testAction2: ['this is an action',
+			function(){
+				console.log('testAction2 args:', arguments)
+			}],
+
+	})
+
+	// XXX the main question here is that there is no way to know if a 
+	// 		particular action is going to be a root action or an action
+	// 		callback because we do not know if the action in the parent 
+	// 		will be available at mix time or not, and the two models 
+	// 		are different...
+	// 		XXX one way to do this is to make all code a callback and 
+	// 			just use the root as an event trigger...
+	//
+	// 			...but this effectively means we are implementing 
+	// 			inheritance ourselves as the traditional name resolution
+	// 			will no longer be used, and as in the case we implement
+	// 			MRO why not go the whole way and implement multiple 
+	// 			inheritance in the first place...
+	//
+	// 			...let's try and avoid this...
+	/*
+	var TestActionMixin =
+	module.TestActionMixin = 
+	ActionMixin({
+		// XXX
+	})
+	*/
+
+
+	console.log('TestActions.testActionGen1()')
+	TestActions.testActionGen1()
+	console.log('TestActions.testActionGen2()')
+	TestActions.testActionGen2()
+
+		
+	// both of these should cet a callback...
+	console.log('TestActions2.testActionGen1()')
+	TestActions2.testActionGen1()
+	console.log('TestActions2.testActionGen2()')
+	TestActions2.testActionGen2()
+
+	// and an event-like handler...
+	TestActions2.on('testActionGen1.post', 
+			function(){ console.log('  post handler! (first defined)') })
+	TestActions2.on('testActionGen1', 
+			function(){ console.log('  post handler! (last defined)') })
+
+	console.log('TestActions2.testActionGen1()')
+	TestActions2.testActionGen1()
+
+	TestActions2.on('testActionGen2.pre', 
+			function(){ console.log('  pre handler! (first defined)') })
+	TestActions2.on('testActionGen2.pre', 
+			function(){ console.log('  pre handler! (last defined)') })
+
+	console.log('TestActions2.testActionGen2()')
+	TestActions2.testActionGen2()
+}
+
+
+
+/**********************************************************************
+* vim:set ts=4 sw=4 :                                                */
+return module })
diff --git a/features.js b/features.js
new file mode 100755
index 0000000..bd4b46c
--- /dev/null
+++ b/features.js
@@ -0,0 +1,698 @@
+/**********************************************************************
+* 
+*
+*
+**********************************************************************/
+(typeof(define)[0]=='u'?function(f){module.exports=f(require)}:define)(
+function(require){ var module={} // makes module AMD/node compatible...
+/*********************************************************************/
+
+var args2array = require('lib/util').args2array
+
+var actions = require('lib/actions')
+var object = require('lib/object')
+
+
+
+/*********************************************************************/
+//
+// Feature attributes:
+// 	.tag			- feature tag (string)
+// 					  this is used to identify the feature, its event handlers
+// 					  and DOM elements.
+//
+// 	.title			- feature name (string | null)
+// 	.doc			- feature description (string | null)
+//
+// 	.priority		- feature priority
+// 					  can be:
+// 					  	- 'high' (99) | 'medium' (0) | 'low' (-99)
+// 					  	- number
+// 					  	- null (0, default)
+// 					  features with higher priority will be setup first,
+// 					  features with the same priority will be run in order of
+// 					  occurrence.
+// 	.suggested		- list of optional suggested features, these are not 
+// 					  required but setup if available.
+// 					  This is useful for defining meta features but without
+// 					  making each sub-feature a strict dependency.
+// 	.depends		- feature dependencies -- tags of features that must setup
+// 					  before the feature (list | null)
+// 	.exclusive		- feature exclusivity tags (list | null)
+// 					  an exclusivity group enforces that only one feature in
+// 					  it will be run, i.e. the first / highest priority.
+//
+// 	.actions		- action object containing feature actions (ActionSet | null)
+// 					  this will be mixed into the base object on .setup()
+// 					  and mixed out on .remove()
+// 	.config			- feature configuration, will be merged with base 
+// 					  object's .config
+// 					  NOTE: the final .config is an empty object with
+// 					  		.__proto__ set to the merged configuration
+// 					  		data...
+// 	.handlers		- feature event handlers (list | null)
+// 
+//
+//
+// .handlers format:
+// 	[
+// 		[ ,  ],
+// 		...
+// 	]
+//
+// NOTE: both  and  must be compatible with
+// 		Action.on(..)
+//
+//
+// Feature applicability:
+// 	If feature.isApplicable(..) returns false then the feature will not be
+// 	considered on setup...
+//
+//
+// XXX this could install the handlers in two locations:
+// 		- mixin if available...
+// 		- base object (currently implemented)
+// 		should the first be done?
+var FeatureProto =
+module.FeatureProto = {
+	tag: null,
+
+	isApplicable: function(actions){
+		return true
+	},
+
+	getPriority: function(){
+		var res = this.priority || 0
+		return res == 'high' ? 99
+			: res == 'low' ? -99
+			: res == 'medium' ? 0
+			: res
+	},
+
+	setup: function(actions){
+		var that = this
+
+		// mixin actions...
+		if(this.actions != null){
+			actions.mixin(this.actions)
+		}
+
+		// install handlers...
+		if(this.handlers != null){
+			this.handlers.forEach(function(h){
+				actions.on(h[0], that.tag, h[1])
+			})
+		}
+
+		// merge config...
+		// NOTE: this will merge the actual config in .config.__proto__
+		// 		keeping the .config clean for the user to lay with...
+		if(this.config != null 
+				|| (this.actions != null 
+					&& this.actions.config != null)){
+			var config = this.config = this.config || this.actions.config
+
+			if(actions.config == null){
+				actions.config = Object.create({})
+			}
+			Object.keys(config).forEach(function(n){
+				// NOTE: this will overwrite existing values...
+				actions.config.__proto__[n] = config[n]
+			})
+		}
+
+		// custom setup...
+		// XXX is this the correct way???
+		if(this.hasOwnProperty('setup') && this.setup !== FeatureProto.setup){
+			this.setup(actions)
+		}
+
+		return this
+	},
+	remove: function(actions){
+		if(this.actions != null){
+			actions.mixout(this.actions)
+		}
+
+		if(this.handlers != null){
+			actions.off('*', this.tag)
+		}
+
+		if(this.hasOwnProperty('remove') && this.setup !== FeatureProto.remove){
+			this.remove(actions)
+		}
+
+		// remove feature DOM elements...
+		actions.ribbons.viewer.find('.' + this.tag).remove()
+
+		return this
+	},
+}
+
+
+// XXX is hard-coded default feature-set a good way to go???
+//
+// 	Feature(obj)
+// 		-> feature
+//
+// 	Feature(feature-set, obj)
+// 		-> feature
+//
+// 	Feature(tag, obj)
+// 		-> feature
+//
+//
+// 	Feature(tag, actions)
+// 		-> feature
+//
+// 	Feature(feature-set, tag, actions)
+// 		-> feature
+//
+var Feature =
+module.Feature =
+function Feature(feature_set, tag, obj){
+	if(arguments.length == 2){
+		// Feature(, )
+		if(typeof(feature_set) == typeof('str')){
+			obj = tag
+			tag = feature_set
+			feature_set = Features
+
+		// Feature(, )
+		} else {
+			obj = tag
+			tag = null
+		}
+
+	// Feature()
+	} else if(arguments.length == 1){
+		obj = feature_set
+		feature_set = Features
+	}
+
+	if(tag != null && obj.tag != null && obj.tag != tag){
+		throw 'Error: tag and obj.tag mismatch, either use one or both must match.'
+	}
+
+	// action...
+	if(obj instanceof actions.Action){
+		if(tag == null){
+			throw 'Error: need a tag to make a feature out of an action'
+		}
+		var f = {
+			tag: tag,
+			actions: obj,
+		}
+		obj = f
+
+	// meta-feature...
+	} else if(obj.constructor === Array){
+		if(tag == null){
+			throw 'Error: need a tag to make a meta-feature'
+		}
+		var f = {
+			tag: tag,
+			suggested: obj,
+		}
+		obj = f
+
+	// feature...
+	} else {
+		obj.__proto__ = FeatureProto
+	}
+
+	if(feature_set){
+		feature_set[obj.tag] = obj
+	}
+
+	return obj
+}
+Feature.prototype = FeatureProto
+Feature.prototype.constructor = Feature
+
+
+var FeatureSetProto = {
+	__feature__: Feature,
+	__actions__: actions.Actions,
+
+	// if true, .setup(..) will report things it's doing... 
+	__verbose__: null,
+
+	// List of registered features...
+	get features(){
+		var that = this
+		return Object.keys(this)
+			.filter(function(e){ 
+				return e != 'features' 
+					&& that[e] instanceof Feature }) 
+	},
+
+	// Build list of features...
+	//
+	//	Build list of all features for an empty object...
+	//	.buildFeatureList()
+	//	.buildFeatureList({})
+	//	.buildFeatureList({}, '*')
+	//		-> data
+	//
+	//	Build a list of features for a specific root feature and object...
+	//	.buildFeatureList(object, feature)
+	//		-> data
+	//
+	//	Build a list of features for a specific set of root features and object...
+	//	.buildFeatureList(object, [feature, ..])
+	//		-> data
+	//		NOTE: to disable a feature and all of it's dependants prefix
+	//			it's tag with '-' in the list.
+	//			e.g. including 'some-feature' will include the feature
+	//			and its dependants while '-some-feature' will remove
+	//			it and it's dependants.
+	//
+	//
+	// This will build from user input a loadable list of features taking 
+	// into account feature dependencies, priorities and suggestions.
+	//
+	// Roughly this is done in this order starting with the given features:
+	// 	- include all dependencies (recursively)
+	// 	- include all suggested features (recursively)
+	// 	- sort features by priority
+	// 	- sort features by dependency
+	// 	- check for feature applicability
+	// 	- remove non-applicable features and all dependants (recursively)
+	// 	- remove disabled features and all dependants (recursively)
+	// 	- check and resolve exclusivity conflicts (XXX needs revision)
+	// 	- check for missing features and dependencies
+	//
+	//
+	// Return format:
+	// 	{
+	// 		// list of input features...
+	// 		input: [ .. ],
+	//
+	//		// features in correct load order...
+	//		features: [ .. ],
+	//
+	//		// features disabled explicitly and their dependants...
+	//		disabled: [ .. ],
+	//		// unapplicable features and their dependants...
+	//		unapplicable: [ .. ],
+	//
+	//		// features removed due to exclusivity conflict...
+	//		excluded: [ .. ],
+	//
+	//		missing: {
+	//			// features explicitly given by user but missing...
+	//			USER: [ .. ],
+	//			// missing  dependencies...
+	//			: [ .. ],
+	//			...
+	//		},
+	//		conflicts: {
+	//			XXX
+	//		},
+	// 	}
+	//
+	//
+	// NOTE: obj (action set) here is used only for applicability testing...
+	// NOTE: some feature applicability checks (.isApplicable(..)) may 
+	// 		require a real action set, thus for correct operation one 
+	// 		should be provided.
+	// NOTE: all feature sorting is done maintaining relative feature order
+	// 		when possible...
+	// NOTE: meta-features are not included in the list as they do not 
+	// 		need to be setup.
+	// 		...this is because they are not Feature objects.
+	//
+	// XXX should meta-features be MetaFeature objects???
+	// XXX not sure about handling excluded features (see inside)...
+	// XXX add dependency loops to .conflicts...
+	// XXX might be a good idea to check dependency loops on feature 
+	// 		construction, too... (???)
+	buildFeatureList: function(obj, lst){
+		var that = this
+		obj = obj || {}
+
+		lst = (lst == null || lst == '*') ? this.features : lst
+		lst = lst.constructor !== Array ? [lst] : lst
+
+		var input = lst.slice()
+		var disabled = [] 
+		var excluded = []
+		var unapplicable = []
+		var missing = {}
+		var conflicts = {}
+
+
+		// reverse dependency cache... 
+		var dependants = {}
+
+		// build dependency list...
+		var _buildDepList = function(n, seen){
+			seen = seen || []
+			return seen.indexOf(n) >= 0 ? []
+				: seen.push(n) && dependants[n] ? []
+					.concat.apply(
+						dependants[n], 
+						dependants[n]
+							.map(function(n){ return _buildDepList(n, seen) }))
+				: []
+		}
+
+
+		// missing stage 1: check if all user included features exist...
+		// NOTE: we'll ignore missing disabled features too...
+		lst.forEach(function(n){
+			if(!that[n] && n[0] != '-'){
+				var m = missing['USER'] = missing['USER'] || []
+				m.push(n)
+			}
+		})
+
+		// include all dependencies...
+		//
+		// NOTE: this should never fall into an infinite loop as we do 
+		// 		not include feature already seen...
+		// 		...unless there is an infinite number of features, but 
+		// 		I'll try to avoid that big a feature creep.
+		// XXX should we check for dependency loops here???
+		// 		...this would have been simple if this was a recursion
+		// 		(just check if cur is in path), but here it is not 
+		// 		trivial...
+		for(var i=0; i < lst.length; i++){
+			var k = lst[i]
+
+			// skip disabled or missing features....
+			if(k[0] == '-' || !that[k]){
+				continue
+			}
+
+			var deps = that[k].depends || []
+			var refs = that[k].suggested || []
+
+			deps.forEach(function(n){
+				// expand lst with dependencies....
+				lst.indexOf(n) < 0 && lst.push(n)
+
+				// build reverse dependency index...
+				var d = dependants[n] = dependants[n] || []
+				d.indexOf(k) < 0 && d.push(k)
+			})
+
+			// expand lst with suggenstions....
+			refs.forEach(function(n){
+				lst.indexOf(n) < 0 && lst.push(n)
+			})
+		}
+
+		// sort features by priority or position...
+		lst = lst
+			// remove undefined and non-features...
+			.filter(function(n){ 
+				// feature disabled -> record and skip...
+				if(n[0] == '-'){
+					disabled.push(n.slice(1))
+					return false
+				}
+				var f = that[n]
+				// feature not defined or is not a feature...
+				if(f == null || !(f instanceof Feature)){
+					return false
+				}
+				// check applicability...
+				if(f.isApplicable && !f.isApplicable.call(that, obj)){
+					unapplicable.push(n)
+					return false
+				}
+				return true
+			})
+			// remove disabled...
+			.filter(function(e){ return disabled.indexOf(e) < 0 })
+			// build the sort table: [ , ,  ]
+			.map(function(e, i){ return [ that[e].getPriority(), i, e ] })
+			// do the sort...
+			// NOTE: JS compares lists as strings so we have to compare 
+			// 		the list manually...
+			.sort(function(a, b){ return a[0] - b[0] || a[1] - b[1] })
+			// cleanup -- drop the sort table...
+			.map(function(e){ return e[2] })
+
+		// remove dependants on not applicable and on disabled...
+		var _unapplicable = unapplicable.slice()
+		var _disabled = disabled.slice()
+		// build the full lists of features to remove...
+		_unapplicable
+			.forEach(function(n){ _unapplicable = _unapplicable.concat(_buildDepList(n)) })
+		_disabled
+			.forEach(function(n){ _disabled = _disabled.concat(_buildDepList(n)) })
+		// clear...
+		// NOTE: in case of intersection disabled has priority...
+		lst = lst
+			.filter(function(n){
+				return _disabled.indexOf(n) >= 0 ?
+						disabled.push(n) && false
+					: _unapplicable.indexOf(n) >= 0 ?
+						unapplicable.push(n) && false
+					: true })
+
+		// missing stage 2: dependencies...
+		lst.forEach(function(k){
+			(that[k].depends || []).forEach(function(d){
+				// NOTE: we do not need to check disabled or unapplicable
+				// 		here as if the feature depended on dropped feature
+				// 		it would have been already dropped too...
+				if(!that[k]){
+					var m = missing[k] = missing[k] || []
+					m.push(d)
+				}
+			})
+		})
+
+		// check exclusive -> excluded...
+		//
+		// NOTE: this is the right spot for this, just after priority 
+		// 		sorting and clearing but before dependency sorting.
+		//
+		// XXX do we need to clear dependencies pulled by excluded features???
+		// 		ways to go:
+		// 			- drop excluded and continue (current state)
+		// 			- disable excluded, add to original input and rebuild
+		// 			- err and let the user decide
+		var _exclusive = []
+		lst = lst.filter(function(n){
+			var e = that[n]
+
+			// keep non-exclusive stuff...
+			if(!e || e.exclusive == null){
+				return true
+			}
+
+			// count the number of exclusive features already present...
+			var res = e.exclusive
+				.filter(function(n){
+					if(_exclusive.indexOf(n) < 0){
+						_exclusive.push(n)
+						return false
+					}
+					return true
+				})
+				.length == 0
+
+			!res 
+				&& excluded.push(n)
+				// warn the user...
+				// XXX not sure if this is the right place for this...
+				&& console.warn(
+					'Excluding unaplicable:', n, '(reccomended to exclude manually)')
+
+			return res
+		})
+
+		// sort by dependency...
+		var l = lst.length
+		// get maximum possible length...
+		// ...the worst case length appears to be (for full reversal):
+		// 		S(2*(n-1) + 1)
+		// 			S = n => n > 0 ? 2*(n-1)+1 + S(n-1) : 0
+		// 			S = n => n > 0 ? 2*n-1 + S(n-1) : 0
+		//
+		// 		2 * S(n) - n
+		// 			S = n => n > 0 ? n + S(n-1) : 0
+		// 			f = n => 2 * S(n) - n
+		//
+		//		N^2 + C
+		//			S = n => n * n
+		//
+		// NOTE: this is the brute force way to check if we have a 
+		// 		dependency loop, need something faster...
+		//
+		// XXX is O(n^2) good enough worst case here?
+		// 		...at this point I think it is acceptable as we'll not 
+		// 		expect dependency graphs too saturated, and the average 
+		// 		complexity is far better...
+		var max = l * l
+
+		for(var i=0; i < lst.length; i++){
+			var k = lst[i]
+			var depends = that[k].depends || []
+
+			// list of dependencies to move...
+			var move = []
+
+			lst
+				.slice(0, i)
+				.forEach(function(n, j){
+					// if n is a dependency of k, prepare to move...
+					if(depends.indexOf(n) >= 0){
+						delete lst[j] 
+						move.push(n)
+					}
+				})
+
+			// move the dependencies after k...
+			// NOTE: this will keep the order within the dependencies...
+			move.length > 0
+				&& lst.splice.apply(lst, [i+1, 0].concat(move))
+
+			// check for cyclic dependencies...
+			// XXX loop signs:
+			// 		- the tail length stops changing -- we stop progressing to list end
+			// 		- the loop is packed
+			// 			- each element includes a set of dependencies
+			// 			- this set is of the same length when at a specific element
+			// 		- we only shift the same set of N elements over N iterations
+			// 		- ...
+			if(lst.length >= max){
+				// XXX get the actual cycle...
+				console.error('Feature cyclic dependency...')
+				break
+			}
+		}
+
+		// cleanup after sort...
+		lst = lst
+			// remove undefined and non-features...
+			.filter(function(e){ 
+				return that[e] != null && that[e] instanceof Feature })
+			.reverse()
+
+
+		return {
+			input: input,
+
+			features: lst,
+
+			disabled: disabled,
+			unapplicable: unapplicable,
+			excluded: excluded,
+
+			missing: missing,
+			conflicts: conflicts,
+		}
+	},
+
+
+	//
+	//	.setup(, [, ...])
+	//		-> 
+	//
+	//	.setup([, ...])
+	//		-> 
+	//
+	setup: function(obj, lst){
+		// if no explicit object is given, just the list...
+		if(lst == null){
+			lst = obj
+			obj = null
+		}
+
+		obj = obj || (this.__actions__ || actions.Actions)()
+
+		lst = lst.constructor !== Array ? [lst] : lst
+		var features = this.buildFeatureList(obj, lst)
+		lst = features.features
+
+		// check for conflicts...
+		if(Object.keys(features.conflicts).length != 0
+				|| Object.keys(features.missing).length != 0){
+			var m = features.missing
+			var c = features.conflicts
+
+			// build a report...
+			var report = []
+
+			// missing deps...
+			Object.keys(m).forEach(function(k){
+				report.push(k + ': missing but required by:\n          ' + m[k].join(', '))
+			})
+			report.push('\n')
+
+			// conflicts...
+			Object.keys(c).forEach(function(k){
+				report.push(k + ': must setup after:\n          ' + c[k].join(', '))
+			})
+
+			// break...
+			throw 'Feature dependency error:\n    ' + report.join('\n    ') 
+		}
+
+		// report excluded features...
+		if(this.__verbose__ && features.excluded.length > 0){
+			console.warn('Excluded features due to exclusivity conflict:', 
+					features.excluded.join(', '))
+		}
+
+		// report unapplicable features...
+		if(this.__verbose__ && features.unapplicable.length > 0){
+			console.log('Features not applicable in current context:', 
+					features.unapplicable.join(', '))
+		}
+
+		// do the setup...
+		var that = this
+		var setup = FeatureProto.setup
+		lst.forEach(function(n){
+			// setup...
+			if(that[n] != null){
+				this.__verbose__ && console.log('Setting up feature:', n)
+				setup.call(that[n], obj)
+			}
+		})
+
+		// XXX should we extend this if it already was in the object???
+		obj.features = features
+
+		return obj
+	},
+	remove: function(obj, lst){
+		lst = lst.constructor !== Array ? [lst] : lst
+		var that = this
+		lst.forEach(function(n){
+			if(that[n] != null){
+				console.log('Removing feature:', n)
+				that[n].remove(obj)
+			}
+		})
+	},
+
+	// shorthand for: Feature(, ...)
+	// XXX should this return this?
+	Feature: function(){
+		return this.__feature__.apply(null, [this].concat(args2array(arguments)))
+	},
+}
+
+
+var FeatureSet =
+module.FeatureSet = object.makeConstructor('FeatureSet', FeatureSetProto)
+
+
+//---------------------------------------------------------------------
+
+var Features =
+module.Features = new FeatureSet()
+
+
+
+
+/**********************************************************************
+* vim:set ts=4 sw=4 :                               */ return module })
diff --git a/package.json b/package.json
index 2da191b..4ae290b 100755
--- a/package.json
+++ b/package.json
@@ -2,12 +2,12 @@
   "name": "features",
   "version": "1.0.0",
   "description": "",
-  "main": "index.js",
+  "main": "features.js",
   "scripts": {
   },
   "repository": {
     "type": "git",
-    "url": "git+https://github.com/flynx/features.git"
+    "url": "git+https://github.com/flynx/features.js.git"
   },
   "keywords": [
     "js",