Code updated to v 0.7.0, prepping to tag

This commit is contained in:
Jim Cowart 2012-08-28 12:06:19 -04:00
parent 4c0aea72b9
commit 83c0464429
1467 changed files with 357548 additions and 1559 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -1,6 +1,6 @@
# Postal.js
## Version 0.6.3 (Dual Licensed [MIT](http://www.opensource.org/licenses/mit-license) & [GPL](http://www.opensource.org/licenses/gpl-license))
## Version 0.7.0 (Dual Licensed [MIT](http://www.opensource.org/licenses/mit-license) & [GPL](http://www.opensource.org/licenses/gpl-license))
## What is it?
Postal.js is an in-memory message bus - very loosely inspired by [AMQP](http://www.amqp.org/) - written in JavaScript. Postal.js runs in the browser, or on the server-side using Node.js. It takes a familiar "eventing-style" paradigm most JavaScript developers are already used to and extends it by providing "broker" and subscriber implementations which are more sophisticated than what you typically find in simple event delegation.
@ -20,11 +20,15 @@ Postal.js is in good company - there are many options for <airquotes>pub/s
* topcis should be hierarchical and allow plain string or wildcard bindings
* messages should include envelope metadata
## Recent Updates (IMPORTANT)
Version 0.7.0 of postal has implemented a bindings resolver that aligns with how AMQP handles wildcards in topical bindings. ***Please note that this effectively inverts how postal has handled wildcards up to now***. You can still use the old version of the bindings resolve by including the `classic-resolver.js` file in your project. If you want to use the new resolver, just use postal as-is and know that "#" matches 0 or more "words" (words are period-delimited segments of topics) and "*" matches exactly one word.
### Channels? WAT?
A channel is a logical partition of topics. Conceptually, it's like a dedicated highway for a specific set of communication. At first glance it might seem like that's overkill for an environment that runs in an event loop, but it actually proves to be quite useful. Every library has architectural opinions that it either imposes or nudges you toward. Channel-oriented messaging nudges you to separate your communication by bounded context, and enables the kind of fine-tuned visibility you need into the interactions between components as your application grows.
### Hierarchical Topics
In my experience, seeing publish and subscribe calls all over application logic is usually a strong code smell. Ideally, the majority of message-bus integration should be concealed within application infrastructure. Having a hierarchical-wildcard-bindable topic system makes it very easy to keep things concise (especially subscribe calls!). For example, if you have a module that needs to listen to every message published on the ShoppingCart channel, you'd simply subscribe to "\*", and never have to worry about additional subscribes on that channel again - even if you add new messages in the future. If you need to capture all messages with ".validation" at the end of the topic, you'd simply subscribe to "\*.validation". If you needed to target all messages with topics that started with "Customer.", ended with ".validation" and had only one period-delimited segment in between, you'd subscribe to "Customer.#.validation" (thus your subscription would capture Customer.address.validation and Customer.email.validation").
In my experience, seeing publish and subscribe calls all over application logic is usually a strong code smell. Ideally, the majority of message-bus integration should be concealed within application infrastructure. Having a hierarchical-wildcard-bindable topic system makes it very easy to keep things concise (especially subscribe calls!). For example, if you have a module that needs to listen to every message published on the ShoppingCart channel, you'd simply subscribe to "\#", and never have to worry about additional subscribes on that channel again - even if you add new messages in the future. If you need to capture all messages with ".validation" at the end of the topic, you'd simply subscribe to "\#.validation". If you needed to target all messages with topics that started with "Customer.", ended with ".validation" and had only one period-delimited segment in between, you'd subscribe to "Customer.*.validation" (thus your subscription would capture Customer.address.validation and Customer.email.validation").
## How do I use it?
@ -55,12 +59,12 @@ channel.publish( { name: "Dr. Who" } );
subscription.unsubscribe();
```
### Subscribing to a wildcard topic using #
### Subscribing to a wildcard topic using *
The `#` symbol represents "one word" in a topic (i.e - the text between two periods of a topic). By subscribing to `"#.Changed"`, the binding will match `Name.Changed` & `Location.Changed` but *not* `Changed.Companion`.
The `*` symbol represents "one word" in a topic (i.e - the text between two periods of a topic). By subscribing to `"*.Changed"`, the binding will match `Name.Changed` & `Location.Changed` but *not* `Changed.Companion`.
```javascript
var hashChannel = postal.channel( { topic: "#.Changed" } ),
var hashChannel = postal.channel( { topic: "*.Changed" } ),
chgSubscription = hashChannel.subscribe( function( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example2" );
});
@ -71,12 +75,12 @@ postal.channel( "Location.Changed" )
chgSubscription.unsubscribe();
```
### Subscribing to a wildcard topic using *
### Subscribing to a wildcard topic using &#35;
The `*` symbol represents any number of characters/words in a topic string. By subscribing to ``"DrWho.*.Changed"``, the binding will match `DrWho.NinthDoctor.Companion.Changed` & `DrWho.Location.Changed` but *not* `Changed`.
The `#` symbol represents any number of characters/words in a topic string. By subscribing to ``"DrWho.#.Changed"``, the binding will match `DrWho.NinthDoctor.Companion.Changed` & `DrWho.Location.Changed` but *not* `Changed`.
```javascript
var starChannel = postal.channel( { channel: "Doctor.Who", topic: "DrWho.*.Changed" } ),
var starChannel = postal.channel( { channel: "Doctor.Who", topic: "DrWho.#.Changed" } ),
starSubscription = starChannel.subscribe( function( data ) {
$( '<li>' + data.type + " Changed: " + data.value + '</li>' ).appendTo( "#example3" );
});
@ -100,10 +104,12 @@ starChannel.publish( { topic: "Changed", data: { typ
starSubscription.unsubscribe();
```
### Applying distinctUntilChanged to a subscription
```javascript
var dupChannel = postal.channel( { topic: "WeepingAngel.*" } ),
var dupChannel = postal.channel( { topic: "WeepingAngel.#" } ),
dupSubscription = dupChannel.subscribe( function( data ) {
$( '<li>' + data.value + '</li>' ).appendTo( "#example4" );
}).distinctUntilChanged();
@ -143,7 +149,6 @@ Please - by all means! While I hope the API is relatively stable, I'm open to p
## Roadmap for the Future
Here's where Postal is headed:
* The default binding resolver will be changed in v0.7.0 to match AMQP binding conventions exactly.
* Add-ons to enable message capture and replay are in the works and should be ready soon.
* The `SubscriptionDefinition` object will be given the ability to pause (skip) responding to subscriptions
* What else would you like to see?

View file

@ -1,13 +0,0 @@
{
"source" : "src/main",
"output" : "lib/standard",
"lint" : {},
"uglify" : {},
"extensions" : {
"uglify" : "min"
},
"hosts" : {
"/" : "./"
},
"port" : 8080
}

27
build.json Normal file
View file

@ -0,0 +1,27 @@
{
"source" : "src",
"output" : "lib",
"name" : {
"postal.node.js" : "node/postal.js",
"postal.standard.js" : "standard/postal.js",
"postal.standard.min.js" : "standard/postal.min.js",
"postal.amd.js" : "amd/postal.js",
"postal.amd.min.js" : "amd/postal.min.js",
"classic-resolver.node.js" : "node/classic-resolver.js",
"classic-resolver.standard.js" : "standard/classic-resolver.js",
"classic-resolver.standard.min.js" : "standard/classic-resolver.min.js",
"classic-resolver.amd.js" : "amd/classic-resolver.js",
"classic-resolver.amd.min.js" : "amd/classic-resolver.min.js"
},
"lint" : {},
"uglify" : {
"exclude": ["postal.node.js", "classic-resolver.node.min.js"]
},
"extensions" : {
"uglify" : "min"
},
"hosts": {
"/": "./"
},
"port" : 8080
}

View file

@ -2,509 +2,509 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the amd-module version of postal.js
// If you need the standard lib style version, go to http://github.com/ifandelse/postal.js
define( ["underscore"], function ( _, undefined ) {
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this.topic
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( predicate );
return this;
},
withConstraints : function ( predicates ) {
var self = this;
if ( _.isArray( predicates ) ) {
_.each( predicates, function ( predicate ) {
self.withConstraint( predicate );
} );
}
return self;
},
withContext : function ( context ) {
this.context = context;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
return function () {
var idx = self.wireTaps.indexOf( callback );
if ( idx !== -1 ) {
self.wireTaps.splice( idx, 1 );
}
};
},
changePriority: function ( subDef ) {
var idx, found;
if(this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic]) {
this.subscriptions[subDef.channel][subDef.topic] = _.without(this.subscriptions[subDef.channel][subDef.topic], subDef);
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone(topic), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint( envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.apply( subDef.context, [envelope.data, envelope] );
subDef.onHandled();
}
}
}
} );
} );
},
reset : function () {
if ( this.subscriptions ) {
_.each( this.subscriptions, function ( channel ) {
_.each( channel, function ( topic ) {
while ( topic.length ) {
topic.pop().unsubscribe();
}
} );
} );
this.subscriptions = {};
}
},
subscribe : function ( subDef ) {
var idx, found, fn, channel = this.subscriptions[subDef.channel], subs;
if ( !channel ) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if ( !subs ) {
subs = this.subscriptions[subDef.channel][subDef.topic] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish( newEnv );
}
} )
);
} );
} );
return result;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
topic : this.topic
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
var fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( predicate );
return this;
},
withConstraints : function ( predicates ) {
var self = this;
if ( _.isArray( predicates ) ) {
_.each( predicates, function ( predicate ) {
self.withConstraint( predicate );
} );
}
return self;
},
withContext : function ( context ) {
this.context = context;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
this.cache = {};
}
}
};
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
return function () {
var idx = self.wireTaps.indexOf( callback );
if ( idx !== -1 ) {
self.wireTaps.splice( idx, 1 );
}
};
},
changePriority: function ( subDef ) {
var idx, found;
if(this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic]) {
this.subscriptions[subDef.channel][subDef.topic] = _.without(this.subscriptions[subDef.channel][subDef.topic], subDef);
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone(topic), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint( envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.apply( subDef.context, [envelope.data, envelope] );
subDef.onHandled();
}
}
}
} );
} );
},
reset : function () {
if ( this.subscriptions ) {
_.each( this.subscriptions, function ( channel ) {
_.each( channel, function ( topic ) {
while ( topic.length ) {
topic.pop().unsubscribe();
}
} );
} );
this.subscriptions = {};
}
},
subscribe : function ( subDef ) {
var idx, found, fn, channel = this.subscriptions[subDef.channel], subs;
if ( !channel ) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if ( !subs ) {
subs = this.subscriptions[subDef.channel][subDef.topic] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish( newEnv );
}
} )
);
} );
} );
return result;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
return postal;
} );

File diff suppressed because one or more lines are too long

View file

@ -4,6 +4,10 @@
As much as I want to convince you that this sample app was written with you in mind, I'd be lying if I said so. The browser examples in this repo are currently "bare bones", with no real usage examples beyond calling the API in browser js and then calling it a day. I wanted to demonstrate node.js usage of postal as well, and that gave birth to this example project.
## How to run
Open a terminal/console to the example/node directory and run `node index.js`. Then open a browser to http://localhost:8002. Once you enter a search term in the client UI, it will trigger the server side twitter search module to start searching, etc.
### The Good, the Bad & the Caveats
The Good:

View file

@ -2,509 +2,509 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the amd-module version of postal.js
// If you need the standard lib style version, go to http://github.com/ifandelse/postal.js
define( ["underscore"], function ( _, undefined ) {
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this.topic
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( predicate );
return this;
},
withConstraints : function ( predicates ) {
var self = this;
if ( _.isArray( predicates ) ) {
_.each( predicates, function ( predicate ) {
self.withConstraint( predicate );
} );
}
return self;
},
withContext : function ( context ) {
this.context = context;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
return function () {
var idx = self.wireTaps.indexOf( callback );
if ( idx !== -1 ) {
self.wireTaps.splice( idx, 1 );
}
};
},
changePriority: function ( subDef ) {
var idx, found;
if(this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic]) {
this.subscriptions[subDef.channel][subDef.topic] = _.without(this.subscriptions[subDef.channel][subDef.topic], subDef);
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone(topic), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint( envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.apply( subDef.context, [envelope.data, envelope] );
subDef.onHandled();
}
}
}
} );
} );
},
reset : function () {
if ( this.subscriptions ) {
_.each( this.subscriptions, function ( channel ) {
_.each( channel, function ( topic ) {
while ( topic.length ) {
topic.pop().unsubscribe();
}
} );
} );
this.subscriptions = {};
}
},
subscribe : function ( subDef ) {
var idx, found, fn, channel = this.subscriptions[subDef.channel], subs;
if ( !channel ) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if ( !subs ) {
subs = this.subscriptions[subDef.channel][subDef.topic] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish( newEnv );
}
} )
);
} );
} );
return result;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
topic : this.topic
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
var fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( predicate );
return this;
},
withConstraints : function ( predicates ) {
var self = this;
if ( _.isArray( predicates ) ) {
_.each( predicates, function ( predicate ) {
self.withConstraint( predicate );
} );
}
return self;
},
withContext : function ( context ) {
this.context = context;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
this.cache = {};
}
}
};
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
return function () {
var idx = self.wireTaps.indexOf( callback );
if ( idx !== -1 ) {
self.wireTaps.splice( idx, 1 );
}
};
},
changePriority: function ( subDef ) {
var idx, found;
if(this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic]) {
this.subscriptions[subDef.channel][subDef.topic] = _.without(this.subscriptions[subDef.channel][subDef.topic], subDef);
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone(topic), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint( envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.apply( subDef.context, [envelope.data, envelope] );
subDef.onHandled();
}
}
}
} );
} );
},
reset : function () {
if ( this.subscriptions ) {
_.each( this.subscriptions, function ( channel ) {
_.each( channel, function ( topic ) {
while ( topic.length ) {
topic.pop().unsubscribe();
}
} );
} );
this.subscriptions = {};
}
},
subscribe : function ( subDef ) {
var idx, found, fn, channel = this.subscriptions[subDef.channel], subs;
if ( !channel ) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if ( !subs ) {
subs = this.subscriptions[subDef.channel][subDef.topic] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish( newEnv );
}
} )
);
} );
} );
return result;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
return postal;
} );

File diff suppressed because one or more lines are too long

View file

@ -16,6 +16,38 @@ require.config( {
require( [ 'backbone', 'jquery', 'underscore', 'amplify', 'machina', 'postal', 'lib/postal.diagnostics', 'infrastructure/postal.socket-client' ],
function ( Backbone, $, _, amplify, machina, postal ) {
// Customizing Postal with some experimental functionality....
var sessionInfo = {};
postal.configuration.getSessionIdAction = function ( callback ) {
callback( sessionInfo );
};
postal.configuration.setSessionIdAction = function ( info, callback ) {
sessionInfo = info;
callback( sessionInfo );
};
postal.utils.getSessionId = function ( callback ) {
postal.configuration.getSessionIdAction.call( this, callback );
};
postal.utils.setSessionId = function ( value, callback ) {
postal.utils.getSessionId( function ( info ) {
// get the session info to move id to last id
info.lastId = info.id;
info.id = value;
// invoke the callback the user provided to handle storing session
postal.configuration.setSessionIdAction( info, function ( session ) {
callback( session );
// publish postal event msg about the change
postal.publish( {
channel : postal.configuration.SYSTEM_CHANNEL,
topic : "sessionId.changed",
data : session
} );
} );
} );
};
// for debugging purposes ONLY for now:
window.postal = postal;

View file

@ -27,9 +27,9 @@ var TwitterSocketStats = function ( port, refreshinterval ) {
// Stand up our express app
app.use( "/", express.static( __dirname + '/client' ) );
app.listen( port );
var searchChannel = postal.channel( "twittersearch", "*" ),
statsChannel = postal.channel( "stats", "*" ),
appChannel = postal.channel( "statsApp", "*" ),
var searchChannel = postal.channel( "twittersearch", "#" ),
statsChannel = postal.channel( "stats", "#" ),
appChannel = postal.channel( "statsApp", "#" ),
fsm;
postal.linkChannels( { channel : "postal.socket", topic : "client.migrated"}, { channel : "statsApp", topic : "client.migrated" } );

View file

@ -14,7 +14,7 @@ module.exports = function ( postal, machina ) {
},
wireHandlersToBus : function ( fsm, handlerChannel ) {
bus.channels[handlerChannel]._subscriptions.push(
bus.channels[handlerChannel].subscribe( "*", function ( data, envelope ) {
bus.channels[handlerChannel].subscribe( "#", function ( data, envelope ) {
fsm.handle.call( fsm, envelope.topic, data, envelope );
} )
);

View file

@ -2,7 +2,7 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the node.js version of postal.js
@ -238,11 +238,13 @@ var bindingsResolver = {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
@ -456,13 +458,13 @@ var postal = {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;

View file

@ -171,7 +171,7 @@ var RemoteClientProxy = function ( postal, socketClient, bridge ) {
fsm.subscriptions._direct = {
_private : postal.subscribe( {
channel : socketClient.id,
topic : "*",
topic : "#",
callback : function ( d, e ) {
fsm.handle( "socketTransmit", d, e );
}

View file

@ -2,7 +2,7 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the standard lib version of postal.js
@ -238,11 +238,13 @@ var bindingsResolver = {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
@ -456,13 +458,13 @@ var postal = {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,31 @@
define( [
'postal'
], function ( postal ) {
var classicBindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
postal.configuration.resolver = classicBindingsResolver;
} );

1
lib/amd/classic-resolver.min.js vendored Normal file
View file

@ -0,0 +1 @@
define(["postal"],function(e){var t={cache:{},compare:function(e,t){if(this.cache[t]&&this.cache[t][e])return!0;var n=new RegExp("^"+e.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/#/g,"[A-Z,a-z,0-9]*")+"$"),r=n.test(t);return r&&(this.cache[t]||(this.cache[t]={}),this.cache[t][e]=!0),r},reset:function(){this.cache={}}};e.configuration.resolver=t})

View file

@ -2,509 +2,509 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the amd-module version of postal.js
// If you need the standard lib style version, go to http://github.com/ifandelse/postal.js
define( ["underscore"], function ( _, undefined ) {
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this._topic,
data : _obj
var DEFAULT_CHANNEL = "/",
DEFAULT_PRIORITY = 50,
DEFAULT_DISPOSEAFTER = 0,
SYSTEM_CHANNEL = "postal",
NO_OP = function () {
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
} );
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
var DistinctPredicate = function () {
var previous = [];
return function (data) {
var isDistinct = !_.any(previous, function (p) {
if (_.isObject(data) || _.isArray(data)) {
return _.isEqual(data, p);
}
return data === p;
});
if (isDistinct) {
previous.push(data);
}
return isDistinct;
};
};
var ConsecutiveDistinctPredicate = function () {
var previous;
return function ( data ) {
var eq = false;
if ( _.isString( data ) ) {
eq = data === previous;
previous = data;
}
else {
eq = _.isEqual( data, previous );
previous = _.clone( data );
}
return !eq;
};
};
var ChannelDefinition = function ( channelName, defaultTopic ) {
this.channel = channelName || DEFAULT_CHANNEL;
this._topic = defaultTopic || "";
};
ChannelDefinition.prototype = {
subscribe : function () {
var len = arguments.length;
if ( len === 1 ) {
return new SubscriptionDefinition( this.channel, this._topic, arguments[0] );
}
else if ( len === 2 ) {
return new SubscriptionDefinition( this.channel, arguments[0], arguments[1] );
}
},
publish : function ( obj ) {
var _obj = obj || {};
var envelope = {
channel : this.channel,
topic : this.topic
topic : this._topic,
data : _obj
};
// If this is an envelope....
if ( _obj.topic && _obj.data ) {
envelope = _obj;
envelope.channel = envelope.channel || this.channel;
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
var fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( predicate );
return this;
},
withConstraints : function ( predicates ) {
var self = this;
if ( _.isArray( predicates ) ) {
_.each( predicates, function ( predicate ) {
self.withConstraint( predicate );
} );
}
return self;
},
withContext : function ( context ) {
this.context = context;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
return function () {
var idx = self.wireTaps.indexOf( callback );
if ( idx !== -1 ) {
self.wireTaps.splice( idx, 1 );
}
};
},
changePriority: function ( subDef ) {
var idx, found;
if(this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic]) {
this.subscriptions[subDef.channel][subDef.topic] = _.without(this.subscriptions[subDef.channel][subDef.topic], subDef);
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone(topic), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint( envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.apply( subDef.context, [envelope.data, envelope] );
subDef.onHandled();
}
}
}
} );
} );
},
reset : function () {
if ( this.subscriptions ) {
_.each( this.subscriptions, function ( channel ) {
_.each( channel, function ( topic ) {
while ( topic.length ) {
topic.pop().unsubscribe();
}
} );
} );
this.subscriptions = {};
}
},
subscribe : function ( subDef ) {
var idx, found, fn, channel = this.subscriptions[subDef.channel], subs;
if ( !channel ) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if ( !subs ) {
subs = this.subscriptions[subDef.channel][subDef.topic] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
topic : function ( topic ) {
if ( topic === this._topic ) {
return this;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
return new ChannelDefinition( this.channel, topic );
}
};
var SubscriptionDefinition = function ( channel, topic, callback ) {
this.channel = channel;
this.topic = topic;
this.callback = callback;
this.priority = DEFAULT_PRIORITY;
this.constraints = new Array( 0 );
this.maxCalls = DEFAULT_DISPOSEAFTER;
this.onHandled = NO_OP;
this.context = null;
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.created",
timeStamp : new Date(),
data : {
event : "subscription.created",
channel : channel,
topic : topic
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish( newEnv );
}
} )
);
} );
} );
return result;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
postal.configuration.bus.subscribe( this );
};
SubscriptionDefinition.prototype = {
unsubscribe : function () {
postal.configuration.bus.unsubscribe( this );
postal.configuration.bus.publish( {
channel : SYSTEM_CHANNEL,
topic : "subscription.removed",
timeStamp : new Date(),
data : {
event : "subscription.removed",
channel : this.channel,
topic : this.topic
}
} );
},
defer : function () {
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( fn, 0, data );
};
return this;
},
disposeAfter : function ( maxCalls ) {
if ( _.isNaN( maxCalls ) || maxCalls <= 0 ) {
throw "The value provided to disposeAfter (maxCalls) must be a number greater than zero.";
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
var fn = this.onHandled;
var dispose = _.after( maxCalls, _.bind( function () {
this.unsubscribe( this );
}, this ) );
this.onHandled = function () {
fn.apply( this.context, arguments );
dispose();
};
return this;
},
distinctUntilChanged : function () {
this.withConstraint( new ConsecutiveDistinctPredicate() );
return this;
},
distinct : function () {
this.withConstraint( new DistinctPredicate() );
return this;
},
withConstraint : function ( predicate ) {
if ( !_.isFunction( predicate ) ) {
throw "Predicate constraint must be a function";
}
this.constraints.push( predicate );
return this;
},
withConstraints : function ( predicates ) {
var self = this;
if ( _.isArray( predicates ) ) {
_.each( predicates, function ( predicate ) {
self.withConstraint( predicate );
} );
}
return self;
},
withContext : function ( context ) {
this.context = context;
return this;
},
withDebounce : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.debounce( fn, milliseconds );
return this;
},
withDelay : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = function ( data ) {
setTimeout( function () {
fn( data );
}, milliseconds );
};
return this;
},
withPriority : function ( priority ) {
if ( _.isNaN( priority ) ) {
throw "Priority must be a number";
}
this.priority = priority;
postal.configuration.bus.changePriority( this );
return this;
},
withThrottle : function ( milliseconds ) {
if ( _.isNaN( milliseconds ) ) {
throw "Milliseconds must be a number";
}
var fn = this.callback;
this.callback = _.throttle( fn, milliseconds );
return this;
},
subscribe : function ( callback ) {
this.callback = callback;
return this;
}
};
var bindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
this.cache = {};
}
}
};
};
var localBus = {
addWireTap : function ( callback ) {
var self = this;
self.wireTaps.push( callback );
return function () {
var idx = self.wireTaps.indexOf( callback );
if ( idx !== -1 ) {
self.wireTaps.splice( idx, 1 );
}
};
},
changePriority: function ( subDef ) {
var idx, found;
if(this.subscriptions[subDef.channel] && this.subscriptions[subDef.channel][subDef.topic]) {
this.subscriptions[subDef.channel][subDef.topic] = _.without(this.subscriptions[subDef.channel][subDef.topic], subDef);
idx = this.subscriptions[subDef.channel][subDef.topic].length - 1;
for ( ; idx >= 0; idx-- ) {
if ( this.subscriptions[subDef.channel][subDef.topic][idx].priority <= subDef.priority ) {
this.subscriptions[subDef.channel][subDef.topic].splice( idx + 1, 0, subDef );
found = true;
break;
}
}
if ( !found ) {
this.subscriptions[subDef.channel][subDef.topic].unshift( subDef );
}
}
},
publish : function ( envelope ) {
_.each( this.wireTaps, function ( tap ) {
tap( envelope.data, envelope );
} );
_.each( this.subscriptions[envelope.channel], function ( topic ) {
// TODO: research faster ways to handle this than _.clone
_.each( _.clone(topic), function ( subDef ) {
if ( postal.configuration.resolver.compare( subDef.topic, envelope.topic ) ) {
if ( _.all( subDef.constraints, function ( constraint ) {
return constraint( envelope.data, envelope );
} ) ) {
if ( typeof subDef.callback === 'function' ) {
subDef.callback.apply( subDef.context, [envelope.data, envelope] );
subDef.onHandled();
}
}
}
} );
} );
},
reset : function () {
if ( this.subscriptions ) {
_.each( this.subscriptions, function ( channel ) {
_.each( channel, function ( topic ) {
while ( topic.length ) {
topic.pop().unsubscribe();
}
} );
} );
this.subscriptions = {};
}
},
subscribe : function ( subDef ) {
var idx, found, fn, channel = this.subscriptions[subDef.channel], subs;
if ( !channel ) {
channel = this.subscriptions[subDef.channel] = {};
}
subs = this.subscriptions[subDef.channel][subDef.topic];
if ( !subs ) {
subs = this.subscriptions[subDef.channel][subDef.topic] = new Array( 0 );
}
subs.push( subDef );
return subDef;
},
subscriptions : {},
wireTaps : new Array( 0 ),
unsubscribe : function ( config ) {
if ( this.subscriptions[config.channel][config.topic] ) {
var len = this.subscriptions[config.channel][config.topic].length,
idx = 0;
for ( ; idx < len; idx++ ) {
if ( this.subscriptions[config.channel][config.topic][idx] === config ) {
this.subscriptions[config.channel][config.topic].splice( idx, 1 );
break;
}
}
}
}
};
var publishPicker = {
"1" : function ( envelope ) {
if ( !envelope ) {
throw new Error( "publishing from the 'global' postal.publish call requires a valid envelope." );
}
envelope.channel = envelope.channel || DEFAULT_CHANNEL;
envelope.timeStamp = new Date();
postal.configuration.bus.publish( envelope );
return envelope;
},
"2" : function ( topic, data ) {
var envelope = { channel : DEFAULT_CHANNEL, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
},
"3" : function ( channel, topic, data ) {
var envelope = { channel : channel, topic : topic, timeStamp : new Date(), data : data };
postal.configuration.bus.publish( envelope );
return envelope;
}
},
channelPicker = {
"1" : function ( chn ) {
var channel = chn, topic, options = {};
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
}
else {
channel = chn.channel || DEFAULT_CHANNEL;
topic = chn.topic;
options = chn.options || options;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"2" : function ( chn, tpc ) {
var channel = chn, topic = tpc, options = {};
if ( Object.prototype.toString.call( tpc ) === "[object Object]" ) {
channel = DEFAULT_CHANNEL;
topic = chn;
options = tpc;
}
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
},
"3" : function ( channel, topic, options ) {
return new postal.channelTypes[ options.type || "local" ]( channel, topic );
}
},
sessionInfo = {};
// save some setup time, albeit tiny
localBus.subscriptions[SYSTEM_CHANNEL] = {};
var postal = {
configuration : {
bus : localBus,
resolver : bindingsResolver,
DEFAULT_CHANNEL : DEFAULT_CHANNEL,
DEFAULT_PRIORITY : DEFAULT_PRIORITY,
DEFAULT_DISPOSEAFTER : DEFAULT_DISPOSEAFTER,
SYSTEM_CHANNEL : SYSTEM_CHANNEL
},
channelTypes : {
local : ChannelDefinition
},
channel : function () {
var len = arguments.length;
if ( channelPicker[len] ) {
return channelPicker[len].apply( this, arguments );
}
},
subscribe : function ( options ) {
var callback = options.callback,
topic = options.topic,
channel = options.channel || DEFAULT_CHANNEL;
return new SubscriptionDefinition( channel, topic, callback );
},
publish : function () {
var len = arguments.length;
if ( publishPicker[len] ) {
return publishPicker[len].apply( this, arguments );
}
},
addWireTap : function ( callback ) {
return this.configuration.bus.addWireTap( callback );
},
linkChannels : function ( sources, destinations ) {
var result = [];
if ( !_.isArray( sources ) ) {
sources = [sources];
}
if ( !_.isArray( destinations ) ) {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;
newEnv.channel = destChannel;
newEnv.data = data;
postal.publish( newEnv );
}
} )
);
} );
} );
return result;
},
utils : {
getSubscribersFor : function () {
var channel = arguments[ 0 ],
tpc = arguments[ 1 ],
result = [];
if ( arguments.length === 1 ) {
if ( Object.prototype.toString.call( channel ) === "[object String]" ) {
channel = postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ];
}
else {
channel = arguments[ 0 ].channel || postal.configuration.DEFAULT_CHANNEL;
tpc = arguments[ 0 ].topic;
}
}
if ( postal.configuration.bus.subscriptions[ channel ] &&
postal.configuration.bus.subscriptions[ channel ].hasOwnProperty( tpc ) ) {
result = postal.configuration.bus.subscriptions[ channel ][ tpc ];
}
return result;
},
reset : function () {
postal.configuration.bus.reset();
postal.configuration.resolver.reset();
}
}
};
return postal;
} );

File diff suppressed because one or more lines are too long

1
lib/classic-resolver.node.min.js vendored Normal file
View file

@ -0,0 +1 @@
var classicBindingsResolver={cache:{},compare:function(e,t){if(this.cache[t]&&this.cache[t][e])return!0;var n=new RegExp("^"+e.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/#/g,"[A-Z,a-z,0-9]*")+"$"),r=n.test(t);return r&&(this.cache[t]||(this.cache[t]={}),this.cache[t][e]=!0),r},reset:function(){this.cache={}}};module.exports={configure:function(e){return e.configuration.resolver=classicBindingsResolver,e}}

View file

@ -0,0 +1,33 @@
var classicBindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
module.exports = {
configure: function(postal) {
postal.configuration.resolver = classicBindingsResolver;
return postal;
}
};

View file

@ -1,7 +1,7 @@
{
"name" : "postal",
"description" : "Pub/Sub library providing wildcard subscriptions, complex message handling, etc. Works server and client-side.",
"version" : "0.6.4",
"version" : "0.7.0",
"homepage" : "http://github.com/ifandelse/postal.js",
"repository" : {
"type" : "git",
@ -22,6 +22,11 @@
"name" : "Alex Robson",
"email" : "WhyNotJustComment@OnMyBlog.com",
"url" : "http://freshbrewedcode.com/alexrobson"
},
{
"name" : "Nicholas Cloud",
"email" : "WhyNotJustComment@OnMyBlog.com",
"url" : "http://nicholascloud.com"
}
],
"keywords": [
@ -31,7 +36,10 @@
"messaging",
"message",
"bus",
"event"
"event",
"mediator",
"broker",
"envelope"
],
"bugs" : {
"email" : "PleaseJustUseTheIssuesPage@github.com",

View file

@ -2,7 +2,7 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the node.js version of postal.js
@ -238,11 +238,13 @@ var bindingsResolver = {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
@ -456,13 +458,13 @@ var postal = {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;

View file

@ -0,0 +1,29 @@
(function(postal, undefined){
var classicBindingsResolver = {
cache : { },
compare : function ( binding, topic ) {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
}
this.cache[topic][binding] = true;
}
return result;
},
reset : function () {
this.cache = {};
}
};
postal.configuration.resolver = classicBindingsResolver;
})(window.postal);

1
lib/standard/classic-resolver.min.js vendored Normal file
View file

@ -0,0 +1 @@
(function(e,t){var n={cache:{},compare:function(e,t){if(this.cache[t]&&this.cache[t][e])return!0;var n=new RegExp("^"+e.replace(/\./g,"\\.").replace(/\*/g,".*").replace(/#/g,"[A-Z,a-z,0-9]*")+"$"),r=n.test(t);return r&&(this.cache[t]||(this.cache[t]={}),this.cache[t][e]=!0),r},reset:function(){this.cache={}}};e.configuration.resolver=n})(window.postal)

View file

@ -2,7 +2,7 @@
postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.6.3
Version 0.7.0
*/
// This is the standard lib version of postal.js
@ -238,11 +238,13 @@ var bindingsResolver = {
if ( this.cache[topic] && this.cache[topic][binding] ) {
return true;
}
// binding.replace(/\./g,"\\.") // escape actual periods
// .replace(/\*/g, ".*") // asterisks match any value
// .replace(/#/g, "[A-Z,a-z,0-9]*"); // hash matches any alpha-numeric 'word'
var rgx = new RegExp( "^" + binding.replace( /\./g, "\\." ).replace( /\*/g, ".*" ).replace( /#/g, "[A-Z,a-z,0-9]*" ) + "$" ),
result = rgx.test( topic );
var pattern = ("^" + binding.replace( /\./g, "\\." ) // escape actual periods
.replace( /\*/g, "[A-Z,a-z,0-9]*" ) // asterisks match any alpha-numeric 'word'
.replace( /#/g, ".*" ) + "$") // hash matches 'n' # of words (+ optional on start/end of topic)
.replace( "\\..*$", "(\\..*)*$" ) // fix end of topic matching on hash wildcards
.replace( "^.*\\.", "^(.*\\.)*" ); // fix beginning of topic matching on hash wildcards
var rgx = new RegExp( pattern );
var result = rgx.test( topic );
if ( result ) {
if ( !this.cache[topic] ) {
this.cache[topic] = {};
@ -456,13 +458,13 @@ var postal = {
destinations = [destinations];
}
_.each( sources, function ( source ) {
var sourceTopic = source.topic || "*";
var sourceTopic = source.topic || "#";
_.each( destinations, function ( destination ) {
var destChannel = destination.channel || DEFAULT_CHANNEL;
result.push(
postal.subscribe( {
channel : source.channel || DEFAULT_CHANNEL,
topic : source.topic || "*",
topic : source.topic || "#",
callback : function ( data, env ) {
var newEnv = env;
newEnv.topic = _.isFunction( destination.topic ) ? destination.topic( env.topic ) : destination.topic || env.topic;

File diff suppressed because one or more lines are too long

1
node_modules/.bin/anvil generated vendored Symbolic link
View file

@ -0,0 +1 @@
../anvil.js/bin/anvil

6
node_modules/anvil.js/.npmignore generated vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules/
test/node_modules/
.idea/
index.html
.DS_Store
tmp

239
node_modules/anvil.js/README.md generated vendored Normal file
View file

@ -0,0 +1,239 @@
# Anvil
Anvil started as a way to build a single javascript module from several source files. Build tools that require a lot of explicit/declarative instructions distract from getting work on the project done.
Anvil is currently being rewritten as CI tool for JS, CSS and HTML.
## What Does It Do?
Here are the main features:
* Create simple directory structure / scaffolding for new projects
* Combine resource files through a comment-based import syntax
* 'Compile' CoffeeScript, Stylus, LESS, HAML, and Markdown
* Minify JS and CSS resources
* Generate annotated JS or CoffeeScript source with docco or ape
* Continously perform these steps in the background as files change
* Mocha test runner
* Host static content
* Compiles CoffeeScript, Stylus, LESS, Markdown and HAML on the fly
* Useful for hosting browser test suites
* Simple hook script to cause page refreshes after every build
## Installation
npm install anvil.js -g
## By Convention
Without a build file, Anvil will use its default conventions to attempt to build your project.
## The Build File ( large example showing lots of options )
{
"source": "src",
"style": "style",
"markup": "markup",
"output": {
"source": [ "lib", "site/js" ],
"style": [ "css", "site/css" ],
"markup": "site/"
},
"lint": {},
"uglify": {},
"cssmin": {},
"extensions": { "uglify": "min" },
"finalize": {
"header|header-file": "this is some unprocessed text or a file name",
"footer|footer-file": "this is some unprocessed text or a file name"
},
"wrap": {
"prefix|prefix-file": "this is some unprocessed text or a file name",
"suffix|suffix-file": "this is some unprocessed text or a file name"
},
"hosts": {
"/": "site",
"/docs": "docs"
},
"name": "custom-name.js",
"mocha": { "reporter": "spec" },
"docs": { "generator": "ape", "output": "docs" }
}
* source is your JS and CS code.
* output is where build outputs go.
* lint will run output files run through JSLint before Uglify occurs (JS only).
* uglify specifies that you want your JS output uglified.
* cssmin minifies CSS output.
* wrap
* happens before minification
* provides a means to wrap all output files of a type with a prefix and suffix before minification
* if prefix-file or suffix-file is provided, the file will be read and the contents used
* this feature is primarily a convenience method included for legacy reasons
* finalize
* header prepends the following string to the final output ONLY.
* footer appends the following string to the final output ONLY.
* if header-file or footer-file is provided, the file will be read and the contents used
* this section was added to support adding boiler plate text headers to minified/gzipped output
* name
* for projects with a single file output, this will replace the name of the output file
* for projects with multiple file outputs, you can provide a lookup hash to over-write
each specific file name
* mocha
* allows you to provide customizations to how the mocha tests will run
* docs
* generate annotated source documents for your project
## Jumpstart New Projects
There are two ways to do this now - one for lib projects and one for sites.
Anvil will build a set of standard project directories for you and even spit out a build.json file based on the conventional use.
### Lib Projects
anvil --lib <projectName>
Will produce a directory structure that looks like this:
-projectName
|-ext
|-src
|-lib
|-spec
build.json
### Site Projects
anvil --site <projectName>
Will produce a directory structure that looks like this:
-projectName
|-ext
|-src
|-site
|-js
|-css
|-style
|-markup
|-lib
|-css
|-spec
build.json
## Building By Convention
If you don't specify your own build file, anvil assumes you intend to use a build.json file. If one isn't present, it will use its own conventions to build your project. If that's all you need, great! Chances are you'll want a build.json that's configured for your specific project.
Now that there are two types of projects, Anvil infers the project type based on the folders you have.
## Combining source files
Anvil allows you to combine source files by using a commented command
**Javascript**
// import("dependency.{ext}");
**Coffeescript**
### import "dependency.{ext}" ###
**Stylus, LESS, CSS**
CSS: /* import "dependency.{ext}" */
LESS, Stylus: // import "dependency.{ext}
When you use Anvil to compile your project, it will traverse all the files in your source directory and combine them so that your top level files are what get output. **Warning** Currently, Anvil is not clever enough to detect circular dependencies created via import statements and it will _shatter your world_ if you do this.
## Building With Specific Build Files
To build with a specific build file
anvil -b <buildfile>
## Creating New / Additional Build Files
To create a build file for lib projects, you can just type the following:
anvil --libfile <buildfile>
or for a site project
anvil --sitefile <buildfile>
and it will create the build file for you. If you don't include the file name, anvil will create a build.json (possibly overwriting your existing one, be careful!)
## Custom Naming
For projects with a single file output, you can provide a name property which will override the default name of the file:
"name": "my-custom-name.js"
For projects where there are multiple files in the output, you must provide a hash object that will tell anvil how to rename each specific file. For example, if you have a build producing 'one.js' and 'two.js' you would need to provide a hash object that would tell anvil how to name each:
"name": {
"one.js" : "main.js",
"two.js" : "plugin.js"
}
## Continuous Integration
Anvil will watch your source directory for changes and rebuild the project in the event any changes are saved to the files in the directory.
anvil --ci
Remember, if you intend to always run in this mode, you can put a "continuous": true in your build.json file.
## Hosting
Anvil provides local hosting based on the "hosts" config block. Adding -h, --host argument or a "host": true block to your build.json file will cause Anvil to host your project's directories (according to configuration) at port 3080 via express.
anvil -h
or
anvil --host
Coffee, Stylus, LESS, Mardown, and HAML are all converted at request time if they are referenced directly.
The hosts key in the build.json file is where you can control what each folder will be hosted at in the relative url.
"hosts": {
"/example1" : "./examples/example1",
"/example2" : "./examples/example2"
}
The block above would host the folder ./example/example1 at http://localhost:3080/example1 and folder ./example/example2 at http://localhost:3080/example2
### External Dependencies
External dependencies get included in all hosting scenarios.
### Testing With Mocha
Mocha might be the best thing ever. You can tell Anvil to run your spec files with mocha from the command line
anvil --mocha
or by adding a "mocha" configuration block to your build.json file.
## Too chatty?
You can tell anvil to run in quiet mode (it will still print errors (red) and step completions (green) )
anvil -q
# Contributors
Special thanks to the following individuals who have contributed source code or ideas to help make Anvil.js less buggy and more useful:
* Jim Cowart
* Aaron McCall
* Mike Stenhouse
* Robert Messerle
* Mike Hostetler
* Doug Neiner
* Derick Bailey

15
node_modules/anvil.js/build.json generated vendored Normal file
View file

@ -0,0 +1,15 @@
{
"source": "src",
"spec": "spec",
"ext": "ext",
"finalize": {
"header-file": "license.txt"
},
"docs": {
"generator": "docco",
"output": "docs"
},
"hosts": {
"/": "docs"
}
}

64
node_modules/anvil.js/changelog.md generated vendored Normal file
View file

@ -0,0 +1,64 @@
# Anvil.js Change Log
## 0.7.9
* #38 - Improved support for Mocha and fixed a continuous integration related bug by using its new JS API and simplifying the clean up approach used between builds. (thanks @madcapnmckay)
* #39 - Fixed a capitalization issue with the require statement for commander. (thanks @tutukin)
## 0.7.8
Added support for relative import statements. (thanks @robertmesserle for helping with ideas and pushing me to get this done). Don't prefix the import path with a ./ or /, Anvil won't recognize that as a match.
### Examples of (now valid) import statements:
__To import a file in the same directory:__
// import( "child.js" );
__To import a file from a subdirectory:__
// import( "subdir/child.js" );
__To import a file from a parent directory:__
// import( "../parent.js" );
__To import a file from a sibling directory:__
// import( "../sibling/file.js" );
## 0.7.7
Removed support for docco since the flocco repository disappeared from NPM and GitHub. The original docco package requires python and a python package to be installed. This feels too heavy a requirement to install Anvil (which already has a lot of dependencies).
## 0.7.6
Addressed issues: #28, #29 and #30 (thanks @mikesten)
* Fixed issues with the combiner's regular expressions that caused issues with jQuery's $
* Fixed a bug in how the minification was mangling the creation of minified file names
## 0.7.5
###Issues
* #18 - ENOENT error on creating project scaffold (thanks @mikehostetler and @ifandelse)
* #22 - No longer copying files to user's ext folder. Anvil's browser dependencies (for QUnit support) are now available via relative url. (thanks @barclayadam)
* jquery -> /anvil/jquery.js
* qunit -> /anvil/qunit.js, /anvil/qunit.css
* pavlov -> /anvil/pavlov.js
* anvilHook -> /anvil/anvilHook.js
* #23 - ENOENT was occurring and not caught in CI mode because of symbolic links (thanks @yesimon)
### Experimental Uglify Exclusions
### Parallel Development for Anvil 0.8.*
## 0.7.4
* Added --help option to command line
* Bug fix for Mocha in CI mode to force re-load of source files
* Bug fix for CI mode that caused multiple builds to kick off on file change
* CLI went from big ugly function to a proper module
## 0.7.3
* Bug fix to prevent Anvil from trying to "BUILD ALL THE THINGS" when it had no idea how to process every file it found.

11
node_modules/anvil.js/contributors.md generated vendored Normal file
View file

@ -0,0 +1,11 @@
# Contributors
Special thanks to the following individuals who have contributed source code or ideas to help make Anvil.js less buggy and more useful:
* Jim Cowart
* Aaron McCall
* Mike Stenhouse
* Robert Messerle
* Mike Hostetler
* Doug Neiner
* Derick Bailey

1649
node_modules/anvil.js/docs/anvil.html generated vendored Normal file

File diff suppressed because it is too large Load diff

186
node_modules/anvil.js/docs/stylesheets/docco.css generated vendored Normal file
View file

@ -0,0 +1,186 @@
/*--------------------- Layout and Typography ----------------------------*/
body {
font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif;
font-size: 15px;
line-height: 22px;
color: #252519;
margin: 0; padding: 0;
}
a {
color: #261a3b;
}
a:visited {
color: #261a3b;
}
p {
margin: 0 0 15px 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 0px 0 15px 0;
}
h1 {
margin-top: 40px;
}
#container {
position: relative;
}
#background {
position: fixed;
top: 0; left: 525px; right: 0; bottom: 0;
background: #f5f5ff;
border-left: 1px solid #e5e5ee;
z-index: -1;
}
#jump_to, #jump_page {
background: white;
-webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777;
-webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px;
font: 10px Arial;
text-transform: uppercase;
cursor: pointer;
text-align: right;
}
#jump_to, #jump_wrapper {
position: fixed;
right: 0; top: 0;
padding: 5px 10px;
}
#jump_wrapper {
padding: 0;
display: none;
}
#jump_to:hover #jump_wrapper {
display: block;
}
#jump_page {
padding: 5px 0 3px;
margin: 0 0 25px 25px;
}
#jump_page .source {
display: block;
padding: 5px 10px;
text-decoration: none;
border-top: 1px solid #eee;
}
#jump_page .source:hover {
background: #f5f5ff;
}
#jump_page .source:first-child {
}
table td {
border: 0;
outline: 0;
}
td.docs, th.docs {
max-width: 450px;
min-width: 450px;
min-height: 5px;
padding: 10px 25px 1px 50px;
overflow-x: hidden;
vertical-align: top;
text-align: left;
}
.docs pre {
margin: 15px 0 15px;
padding-left: 15px;
}
.docs p tt, .docs p code {
background: #f8f8ff;
border: 1px solid #dedede;
font-size: 12px;
padding: 0 0.2em;
}
.pilwrap {
position: relative;
}
.pilcrow {
font: 12px Arial;
text-decoration: none;
color: #454545;
position: absolute;
top: 3px; left: -20px;
padding: 1px 2px;
opacity: 0;
-webkit-transition: opacity 0.2s linear;
}
td.docs:hover .pilcrow {
opacity: 1;
}
td.code, th.code {
padding: 14px 15px 16px 25px;
width: 100%;
vertical-align: top;
background: #f5f5ff;
border-left: 1px solid #e5e5ee;
}
pre, tt, code {
font-size: 12px; line-height: 18px;
font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace;
margin: 0; padding: 0;
}
/*---------------------- Syntax Highlighting -----------------------------*/
td.linenos { background-color: #f0f0f0; padding-right: 10px; }
span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; }
body .hll { background-color: #ffffcc }
body .c { color: #408080; font-style: italic } /* Comment */
body .err { border: 1px solid #FF0000 } /* Error */
body .k { color: #954121 } /* Keyword */
body .o { color: #666666 } /* Operator */
body .cm { color: #408080; font-style: italic } /* Comment.Multiline */
body .cp { color: #BC7A00 } /* Comment.Preproc */
body .c1 { color: #408080; font-style: italic } /* Comment.Single */
body .cs { color: #408080; font-style: italic } /* Comment.Special */
body .gd { color: #A00000 } /* Generic.Deleted */
body .ge { font-style: italic } /* Generic.Emph */
body .gr { color: #FF0000 } /* Generic.Error */
body .gh { color: #000080; font-weight: bold } /* Generic.Heading */
body .gi { color: #00A000 } /* Generic.Inserted */
body .go { color: #808080 } /* Generic.Output */
body .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
body .gs { font-weight: bold } /* Generic.Strong */
body .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
body .gt { color: #0040D0 } /* Generic.Traceback */
body .kc { color: #954121 } /* Keyword.Constant */
body .kd { color: #954121; font-weight: bold } /* Keyword.Declaration */
body .kn { color: #954121; font-weight: bold } /* Keyword.Namespace */
body .kp { color: #954121 } /* Keyword.Pseudo */
body .kr { color: #954121; font-weight: bold } /* Keyword.Reserved */
body .kt { color: #B00040 } /* Keyword.Type */
body .m { color: #666666 } /* Literal.Number */
body .s { color: #219161 } /* Literal.String */
body .na { color: #7D9029 } /* Name.Attribute */
body .nb { color: #954121 } /* Name.Builtin */
body .nc { color: #0000FF; font-weight: bold } /* Name.Class */
body .no { color: #880000 } /* Name.Constant */
body .nd { color: #AA22FF } /* Name.Decorator */
body .ni { color: #999999; font-weight: bold } /* Name.Entity */
body .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
body .nf { color: #0000FF } /* Name.Function */
body .nl { color: #A0A000 } /* Name.Label */
body .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
body .nt { color: #954121; font-weight: bold } /* Name.Tag */
body .nv { color: #19469D } /* Name.Variable */
body .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
body .w { color: #bbbbbb } /* Text.Whitespace */
body .mf { color: #666666 } /* Literal.Number.Float */
body .mh { color: #666666 } /* Literal.Number.Hex */
body .mi { color: #666666 } /* Literal.Number.Integer */
body .mo { color: #666666 } /* Literal.Number.Oct */
body .sb { color: #219161 } /* Literal.String.Backtick */
body .sc { color: #219161 } /* Literal.String.Char */
body .sd { color: #219161; font-style: italic } /* Literal.String.Doc */
body .s2 { color: #219161 } /* Literal.String.Double */
body .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
body .sh { color: #219161 } /* Literal.String.Heredoc */
body .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
body .sx { color: #954121 } /* Literal.String.Other */
body .sr { color: #BB6688 } /* Literal.String.Regex */
body .s1 { color: #219161 } /* Literal.String.Single */
body .ss { color: #19469D } /* Literal.String.Symbol */
body .bp { color: #954121 } /* Name.Builtin.Pseudo */
body .vc { color: #19469D } /* Name.Variable.Class */
body .vg { color: #19469D } /* Name.Variable.Global */
body .vi { color: #19469D } /* Name.Variable.Instance */
body .il { color: #666666 } /* Literal.Number.Integer.Long */

26
node_modules/anvil.js/ext/anvilHook.js generated vendored Normal file
View file

@ -0,0 +1,26 @@
var socket, port;
$(function() {
port = window.location.port
socket = io.connect( "http://" + document.domain + ':' + port + '/' );
socket.on('connect', function () {
socket.on( 'refresh', function () {
window.location.reload();
} );
socket.on( 'reconnecting', function() {
console.log( 'Lost connection to anvil, attempting to reconnect', 'warning' );
} );
socket.on( 'reconnect', function() {
alert( 'Reconnection to anvil succeeded' );
} );
socket.on( 'reconnect_failed', function() {
console.log( 'Reconnected to anvil failed', 'error' );
} );
socket.on( 'connect_failed', function() {
console.log( 'Could not connect to anvil', 'error' );
} );
socket.on( 'disconnect', function() {
alert( 'Anvil server has disconnected', 'error' );
} );
} );
} );

4
node_modules/anvil.js/ext/jquery.js generated vendored Normal file

File diff suppressed because one or more lines are too long

576
node_modules/anvil.js/ext/pavlov.js generated vendored Normal file
View file

@ -0,0 +1,576 @@
/**
* Pavlov - Behavioral API over QUnit
*
* version 0.2.3
*
* http://michaelmonteleone.net/projects/pavlov
* http://github.com/mmonteleone/pavlov
*
* Copyright (c) 2009 Michael Monteleone
* Licensed under terms of the MIT License (README.markdown)
*/
(function(){
// capture reference to global scope
var globalScope = this;
// ===========
// = Helpers =
// ===========
// Trimmed versions of jQuery helpers for use only within pavlov
/**
* Iterates over an object or array
* @param {Object|Array} object object or array to iterate
* @param {Function} callback callback for each iterated item
*/
var each = function(object, callback) {
var name;
var i = 0;
var length = object.length;
if ( length === undefined ) {
for ( name in object ) {
if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
break;
}
}
} else {
for ( var value = object[0];
i < length && callback.call( value, i, value ) !== false;
value = object[++i] ) {}
}
return object;
};
/**
* converts an array-like object to an array
* @param {Object} array array-like object
* @returns array
*/
var makeArray = function(array) {
var ret = [];
var i = array.length;
while( i ) { ret[--i] = array[i]; }
return ret;
};
/**
* returns whether or not an object is an array
* @param {Object} obj object to test
* @returns whether or not object is array
*/
var isArray = function(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
/**
* merges properties form one object to another
* @param {Object} dest object to receive merged properties
* @param {Object} src object containing properies to merge
*/
var extend = function(dest, src) {
for(var prop in src) {
dest[prop] = src[prop];
}
};
/**
* minimalist (and yes, non-optimal/leaky) event binder
* not meant for wide use. only for jquery-less internal use in pavlov
* @param {Element} elem Event-triggering Element
* @param {String} type name of event
* @param {Function} fn callback
*/
var addEvent = function(elem, type, fn){
if ( elem.addEventListener ) {
elem.addEventListener( type, fn, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, fn );
}
};
// ====================
// = Example Building =
// ====================
var examples = [];
var currentExample;
/**
* Example Class
* Represents an instance of an example (a describe)
* contains references to parent and nested examples
* exposes methods for returning combined lists of before, after, and names
* @constructor
* @param {example} parent example to append self as child to (optional)
*/
function example(parent) {
// private
if(parent) {
// if there's a parent, append self as nested example
parent.children.push(this);
} else {
// otherwise, add this as a new root example
examples.push(this);
}
var thisExample = this;
/**
* Rolls up list of current and ancestors values for given prop name
* @param {String} prop Name of property to roll up
* @returns array of values corresponding to prop name
*/
var rollup = function(prop) {
var items = [];
var node = thisExample;
while(node !== null) {
items.push(node[prop]);
node = node.parent;
}
return items;
};
// public
// parent example
this.parent = parent ? parent : null;
// nested examples
this.children = [];
// name of this description
this.name = '';
// function to happen before all contained specs
this.before = function() {};
// function to happen after all contained specs
this.after = function() {};
// array of it() tests
this.specs = [];
/**
* rolls up this and ancestor's before functions
* @returns arrayt of functions
*/
this.befores = function(){
return rollup('before').reverse();
};
/**
* Rolls up this and ancestor's after functions
* @returns array of functions
*/
this.afters = function(){
return rollup('after');
};
/**
* Rolls up this and ancestor's description names, joined
* @returns string of joined description names
*/
this.names = function(){
return rollup('name').reverse().join(', ');
};
}
// ==============
// = Assertions =
// ==============
/**
* Collection of default-bundled assertion implementations
*/
var assertions = {
equals: function(actual, expected, message) {
equals(actual, expected, message);
},
isEqualTo: function(actual, expected, message) {
equals(actual, expected, message);
},
isNotEqualTo: function(actual, expected, message) {
ok(actual !== expected, message);
},
isSameAs: function(actual, expected, message) {
same(actual, expected, message);
},
isNotSameAs: function(actual, expected, message) {
ok(!QUnit.equiv(actual, expected), message);
},
isTrue: function(actual, message) {
ok(actual, message);
},
isFalse: function(actual, message) {
ok(!actual, message);
},
isNull: function(actual, message) {
ok(actual === null, message);
},
isNotNull: function(actual, message) {
ok(actual !== null, message);
},
isDefined: function(actual, message) {
ok(typeof(actual) !== 'undefined', message);
},
isUndefined: function(actual, message) {
ok(typeof(actual) === 'undefined', message);
},
pass: function(actual, message) {
ok(true, message);
},
fail: function(actual, message) {
ok(!true, message);
},
throwsException: function(actual, expectedErrorDescription, message) {
/* can optionally accept expected error message */
try{
actual();
ok(!true, message);
} catch(e) {
if(arguments.length > 1) {
ok(e === expectedErrorDescription, message);
} else {
ok(true, message);
}
}
}
};
/**
* AssertionHandler
* represents instance of an assertion regarding a particular
* actual value, and provides an api around asserting that value
* against any of the bundled assertion handlers and custom ones.
* @constructor
* @param {Object} value A test-produced value to assert against
*/
var assertHandler = function(value) {
this.value = value;
};
/**
* Appends assertion methods to the assertHandler prototype
* For each provided assertion implementation, adds an identically named
* assertion function to assertionHandler prototype which can run impl
* @param {Object} asserts Object containing assertion implementations
*/
var addAssertions = function(asserts) {
each(asserts, function(name, fn){
assertHandler.prototype[name] = function() {
// implement this handler against backend
// by pre-pending assertHandler's current value to args
var args = makeArray(arguments);
args.unshift(this.value);
fn.apply(this, args);
};
});
};
// pre-add all the default bundled assertions
addAssertions(assertions);
// =====================
// = Pavlov Public API =
// =====================
/**
* Object containing methods to be made available as public API
*/
var api = {
/**
* Initiates a new Example context
* @param {String} description Name of what's being "described"
* @param {Function} fn Function containing description (before, after, specs, nested examples)
*/
describe: function(description, fn) {
if(arguments.length < 2) {
throw("both 'description' and 'fn' arguments are required");
}
// capture reference to current example before construction
var originalExample = currentExample;
try{
// create new current example for construction
currentExample = new example(currentExample);
currentExample.name = description;
fn();
} finally {
// restore original reference after construction
currentExample = originalExample;
}
},
/**
* Sets a function to occur before all contained specs and nested examples' specs
* @param {Function} fn Function to be executed
*/
before: function(fn) {
if(arguments.length === 0) {
throw("'fn' argument is required");
}
currentExample.before = fn;
},
/**
* Sets a function to occur after all contained tests and nested examples' tests
* @param {Function} fn Function to be executed
*/
after: function(fn) {
if(arguments.length === 0) {
throw("'fn' argument is required");
}
currentExample.after = fn;
},
/**
* Creates a spec (test) to occur within an example
* When not passed fn, creates a spec-stubbing fn which asserts fail "Not Implemented"
* @param {String} specification Description of what "it" "should do"
* @param {Function} fn Function containing a test to assert that it does indeed do it (optional)
*/
it: function(specification, fn) {
if(arguments.length === 0) {
throw("'specification' argument is required");
}
thisApi = this;
if(fn) {
currentExample.specs.push([specification, fn]);
} else {
// if not passed an implementation, create an implementation that simply asserts fail
thisApi.it(specification, function(){thisApi.assert.fail('Not Implemented');});
}
},
/**
* Generates a row spec for each argument passed, applying
* each argument to a new call against the spec
* @returns an object with an it() function for defining
* function to be called for each of given's arguments
* @param {Array} arguments either list of values or list of arrays of values
*/
given: function() {
if(arguments.length === 0) {
throw("at least one argument is required");
}
var args = makeArray(arguments);
var thisIt = this.it;
return {
/**
* Defines a row spec (test) which is applied against each
* of the given's arguments.
*/
it: function(specification, fn) {
each(args, function(){
var arg = this;
thisIt("given " + arg + ", " + specification, function(){
fn.apply(this, isArray(arg) ? arg : [arg]);
});
});
}
};
},
/**
* Assert a value against any of the bundled or custom assertions
* @param {Object} value A value to be asserted
* @returns an assertHandler instance to fluently perform an assertion with
*/
assert: function(value) {
return new assertHandler(value);
},
/**
* specifies test runner to synchronously wait
* @param {Number} ms Milliseconds to wait
* @param {Function} fn Function to execute after ms has
* passed before resuming
*/
wait: function(ms, fn) {
if(arguments.length < 2) {
throw("both 'ms' and 'fn' arguments are required");
}
stop();
QUnit.specify.globalObject.setTimeout(function(){
fn();
start();
}, ms);
}
};
// extend api's assert function for easier syntax for blank pass and fail
extend(api.assert, {
/**
* Shortcuts assert().pass() with assert.pass()
* @param {String} message Assertion message (optional)
*/
pass: function(message){
(new assertHandler()).pass(message);
},
/**
* Shortcuts assert().fail() with assert.fail()
* @param {String} message Assertion message (optional)
*/
fail: function(message){
(new assertHandler()).fail(message);
}
});
/**
* Extends a function's scope
* applies the extra scope to the function returns un-run new version of fn
* inspired by Yehuda Katz's metaprogramming Screw.Unit
* different in that new function can still accept all parameters original function could
* @param {Function} fn Target function for extending
* @param {Object} thisArg Object for the function's "this" to refer
* @param {Object} extraScope object whose members will be added to fn's scope
* @returns Modified version of original function with extra scope. Can still
* accept parameters of original function
*/
var extendScope = function(fn, thisArg, extraScope) {
// get a string of the fn's parameters
var params = fn.toString().match(/\(([^\)]*)\)/)[1];
// get a string of fn's body
var source = fn.toString().match(/^[^\{]*\{((.*\n*)*)\}/m)[1];
// create a new function with same parameters and
// body wrapped in a with(extraScope){ }
fn = new Function(
"extraScope" + (params ? ", " + params : ""),
"with(extraScope){" + source + "}");
// returns a fn wrapper which takes passed args,
// pre-pends extraScope arg, and applies to modified fn
return function(){
var args = [extraScope];
each(arguments,function(){
args.push(this);
});
fn.apply(thisArg, args);
};
};
/**
* Top-level Specify method. Declares a new QUnit.specify context
* @param {String} name Name of what's being specified
* @param {Function} fn Function containing exmaples and specs
*/
var specify = function(name, fn) {
if(arguments.length < 2) {
throw("both 'name' and 'fn' arguments are required")
}
examples = [];
currentExample = null;
// set the test suite title
document.title = name + " Specifications";
addEvent(window,'load',function(){
// document.getElementsByTag('h1').innerHTML = name;
var h1s = document.getElementsByTagName('h1');
if(h1s.length > 0)
h1s[0].innerHTML = document.title;
});
if(QUnit.specify.globalApi) {
// if set to extend global api,
// extend global api and run example builder
extend(globalScope, api);
fn();
} else {
// otherwise, extend example builder's scope with api
// and run example builder
extendScope(fn, this, api)();
}
// compile examples into flat qunit statements
var qunitStatements = compile(examples);
// run qunit tests
each(qunitStatements, function(){ this(); });
};
// ==========================================
// = Example-to-QUnit Statement Compilation =
// ==========================================
/**
* Compiles nested set of examples into flat array of QUnit statements
* @param {Array} examples Array of possibly nested Example instances
* @returns array of QUnit statements each wrapped in an anonymous fn
*/
var compile = function(examples) {
var statements = [];
/**
* Comples a single example and its children into QUnit statements
* @param {Example} example Single example instance
* possibly with nested instances
*/
var compileDescription = function(example) {
// get before and after rollups
var befores = example.befores();
var afters = example.afters();
// create a module with setup and teardown
// that executes all current befores/afters
statements.push(function(){
module(example.names(), {
setup: function(){
each(befores, function(){ this(); });
},
teardown: function(){
each(afters, function(){ this(); });
}
});
});
// create a test for each spec/"it" in the example
each(example.specs, function(){
var spec = this;
statements.push(function(){
test(spec[0],spec[1]);
});
});
// recurse through example's nested examples
each(example.children, function() {
compileDescription(this);
});
};
// compile all root examples
each(examples, function() {
compileDescription(this, statements);
});
return statements;
};
// =====================
// = Expose Public API =
// =====================
// extend QUnit
QUnit.specify = specify;
// add global settings onto QUnit.specify
extend(specify, {
version: '0.2.3',
globalApi: false, // when true, adds api to global scope
extendAssertions: addAssertions, // function for adding custom assertions
globalObject: window // injectable global containing setTimeout and pals
});
})();

226
node_modules/anvil.js/ext/qunit.css generated vendored Normal file
View file

@ -0,0 +1,226 @@
/**
* QUnit - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2011 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
/** Font Family and Sizes */
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
}
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
#qunit-tests td {
vertical-align: top;
}
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
white-space: pre;
}
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}

1552
node_modules/anvil.js/ext/qunit.js generated vendored Normal file

File diff suppressed because it is too large Load diff

1840
node_modules/anvil.js/lib/anvil.js generated vendored Normal file

File diff suppressed because it is too large Load diff

22
node_modules/anvil.js/license.txt generated vendored Normal file
View file

@ -0,0 +1,22 @@
/*-----------------------------------------------------------------------------
* Anvil.JS v0.7.8
* Copyright (c) 2011-2012 Alex Robson
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*---------------------------------------------------------------------------*/

16
node_modules/anvil.js/next.json generated vendored Normal file
View file

@ -0,0 +1,16 @@
{
"source": "src",
"spec": "spec",
"ext": "ext",
"output": "lib",
"finalize": {
"header-file": "../license.txt"
},
"docs": {
"generator": "docco",
"output": "docs"
},
"hosts": {
"/": "docs"
}
}

199
node_modules/anvil.js/next/ext/machina.js generated vendored Normal file
View file

@ -0,0 +1,199 @@
var _ = require('underscore');
/*
machina.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.1.0
*/
var slice = [].slice,
NEXT_TRANSITION = "transition",
NEXT_HANDLER = "handler",
transformEventListToObject = function(eventList){
var obj = {};
_.each(eventList, function(evntName) {
obj[evntName] = [];
});
return obj;
},
parseEventListeners = function(evnts) {
var obj = evnts;
if(_.isArray(evnts)) {
obj = transformEventListToObject(evnts);
}
return obj;
},
utils = {
makeFsmNamespace: (function(){
var machinaCount = 0;
return function() {
return "fsm." + machinaCount++;
};
})(),
getDefaultOptions: function() {
return {
initialState: "uninitialized",
eventListeners: {
"*" : []
},
states: {},
eventQueue: [],
namespace: utils.makeFsmNamespace()
};
}
},
Fsm = function(options) {
var opt, initialState, defaults = utils.getDefaultOptions();
if(options) {
if(options.eventListeners) {
options.eventListeners = parseEventListeners(options.eventListeners);
}
if(options.messaging) {
options.messaging = _.extend({}, defaults.messaging, options.messaging);
}
}
opt = _.extend(defaults , options || {});
initialState = opt.initialState;
delete opt.initialState;
_.extend(this,opt);
this.state = undefined;
this._priorAction = "";
this._currentAction = "";
if(initialState) {
this.transition(initialState);
}
machina.fireEvent("newFsm", this);
};
Fsm.prototype.fireEvent = function(eventName) {
var args = arguments;
_.each(this.eventListeners["*"], function(callback) {
try {
callback.apply(this,slice.call(args, 0));
} catch(exception) {
if(console && typeof console.log !== "undefined") {
console.log(exception.toString());
}
}
});
if(this.eventListeners[eventName]) {
_.each(this.eventListeners[eventName], function(callback) {
try {
callback.apply(this,slice.call(args, 1));
} catch(exception) {
if(console && typeof console.log !== "undefined") {
console.log(exception.toString());
}
}
});
}
};
Fsm.prototype.handle = function(msgType) {
// vars to avoid a "this." fest
var states = this.states, current = this.state, args = slice.call(arguments,0), handlerName;
this.currentActionArgs = args;
if(states[current] && (states[current][msgType] || states[current]["*"])) {
handlerName = states[current][msgType] ? msgType : "*";
this._currentAction = current + "." + handlerName;
this.fireEvent.apply(this, ["Handling"].concat(args));
states[current][handlerName].apply(this, args.slice(1));
this.fireEvent.apply(this, ["Handled"].concat(args));
this._priorAction = this._currentAction;
this._currentAction = "";
this.processQueue(NEXT_HANDLER);
}
else {
this.fireEvent.apply(this, ["NoHandler"].concat(args));
}
this.currentActionArgs = undefined;
};
Fsm.prototype.transition = function(newState) {
if(this.states[newState]){
var oldState = this.state;
this.state = newState;
if(this.states[newState]._onEnter) {
this.states[newState]._onEnter.call( this );
}
this.fireEvent.apply(this, ["Transitioned", oldState, this.state ]);
this.processQueue(NEXT_TRANSITION);
return;
}
this.fireEvent.apply(this, ["InvalidState", this.state, newState ]);
};
Fsm.prototype.processQueue = function(type) {
var filterFn = type === NEXT_TRANSITION ?
function(item){
return item.type === NEXT_TRANSITION && ((!item.untilState) || (item.untilState === this.state));
} :
function(item) {
return item.type === NEXT_HANDLER;
},
toProcess = _.filter(this.eventQueue, filterFn, this);
this.eventQueue = _.difference(this.eventQueue, toProcess);
_.each(toProcess, function(item, index){
this.handle.apply(this, item.args);
}, this);
};
Fsm.prototype.deferUntilTransition = function(stateName) {
if(this.currentActionArgs) {
var queued = { type: NEXT_TRANSITION, untilState: stateName, args: this.currentActionArgs };
this.eventQueue.push(queued);
this.fireEvent.apply(this, [ "Deferred", this.state, queued ]);
}
};
Fsm.prototype.deferUntilNextHandler = function() {
if(this.currentActionArgs) {
var queued = { type: NEXT_TRANSITION, args: this.currentActionArgs };
this.eventQueue.push(queued);
this.fireEvent.apply(this, [ "Deferred", this.state, queued ]);
}
};
Fsm.prototype.on = function(eventName, callback) {
if(!this.eventListeners[eventName]) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push(callback);
};
Fsm.prototype.off = function(eventName, callback) {
if(this.eventListeners[eventName]){
this.eventListeners[eventName] = _.without(this.eventListeners[eventName], callback);
}
};
var machina = {
Fsm: Fsm,
bus: undefined,
utils: utils,
on: function(eventName, callback) {
if(!this.eventListeners[eventName]) {
this.eventListeners[eventName] = [];
}
this.eventListeners[eventName].push(callback);
},
off: function(eventName, callback) {
if(this.eventListeners[eventName]){
this.eventListeners[eventName] = _.without(this.eventListeners[eventName], callback);
}
},
fireEvent: function(eventName) {
var i = 0, len, args = arguments, listeners = this.eventListeners[eventName];
if(listeners && listeners.length) {
_.each(listeners, function(callback) {
callback.apply(null,slice.call(args, 1));
});
}
},
eventListeners: {
newFsm : []
}
};
module.exports = machina;

45
node_modules/anvil.js/next/ext/machina.postal.js generated vendored Normal file
View file

@ -0,0 +1,45 @@
/*
machina.postal.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.1.0
*/
module.exports = function(postal, machina) {
var bus = machina.bus = {
channels: {},
config: {
handlerChannelSuffix: "",
eventChannelSuffix: ".events"
},
wireHandlersToBus: function(fsm, handlerChannel) {
bus.channels[handlerChannel]._subscriptions.push(
bus.channels[handlerChannel].subscribe("*", function(data, envelope){
fsm.handle.call(fsm, envelope.topic, data, envelope);
})
);
},
wireEventsToBus: function(fsm, eventChannel) {
var publisher = bus.channels[eventChannel].eventPublisher = function(){
try {
bus.channels[eventChannel].publish({ topic: arguments[0], data: arguments[1] || {} });
} catch(exception) {
if(console && typeof console.log !== "undefined") {
console.log(exception.toString());
}
}
};
fsm.on("*", publisher);
},
wireUp: function(fsm) {
var handlerChannel = fsm.namespace + bus.config.handlerChannelSuffix,
eventChannel = fsm.namespace + bus.config.eventChannelSuffix;
bus.channels[handlerChannel] = postal.channel({ channel: handlerChannel });
bus.channels[eventChannel] = postal.channel({ channel: eventChannel });
bus.channels[handlerChannel]._subscriptions = [];
bus.wireHandlersToBus(fsm, handlerChannel);
bus.wireEventsToBus(fsm, eventChannel);
}
};
machina.on("newFsm", bus.wireUp);
};

1
node_modules/anvil.js/next/lib/anvil.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var anvilFactory=function(a,b,c,d,e,f){var g=function(){this.conventions={defaultSiteBlock:{source:"src",style:"style",markup:"markup",output:{source:["lib","site/js"],style:["css","site/css"],markup:"site/"},spec:"spec",ext:"ext",lint:{},uglify:{},cssmin:{},hosts:{"/":"site"}},defaultLibBlock:{source:"src",output:"lib",spec:"spec",ext:"ext",lint:{},uglify:{},hosts:{"/":"site"}}},this.services={},this.combiner=f,this.compiler=e,this.preprocessors={},this.postprocessors={},this.buildState={},this.events={},this.inProcess=!1,a.bindAll(this)};return g.prototype.load=function(){var e;a.each(this.extensions,function(f){e=f.file||f.module,require(e)(a,b,c,d,this)})},g.prototype.raise=function(b,c){var d=this.events[b];a.each(d,function(a){try{a.apply(arguments)}catch(b){}})},g.prototype.onConfiguration=function(a,b){this.configuration=a,!!b},g.prototype.on=function(a,b){var c=this.events[a]||[];c.push(b)},g};module.exports=anvilFactory

25
node_modules/anvil.js/next/lib/cli.js generated vendored Normal file
View file

@ -0,0 +1,25 @@
// # Cli
// Provides the command line interface for interacting with Anvil and related modules
var cliFactory = function( _, Anvil, Configuration ) {
// ## constructor
// Create the initial instance where all we really have is the
// configuration module to get us started.
var Cli = function() {
this.configuration = new Configuration();
_.bindAll( this );
};
// ## start
// Kicks off the configuration process
Cli.prototype.start = function() {
this.configuration( process.argv, function( config, done ) {
if( !done ) {
Anvil.configure( config );
}
} );
};
return Cli;
};
module.exports = cliFactory;

1
node_modules/anvil.js/next/lib/cli.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var cliFactory=function(a,b,c){var d=function(){this.configuration=new c,a.bindAll(this)};return d.prototype.start=function(){this.configuration(process.argv,function(a,c){c||b.configure(a)})},d};module.exports=cliFactory

141
node_modules/anvil.js/next/lib/combiner.js generated vendored Normal file
View file

@ -0,0 +1,141 @@
var combinerFactory = function( _, fp, scheduler ) {
var Combiner = function( findPatterns, replacePatterns ) {
this.findPatterns = findPatterns;
this.replacePatterns = replacePatterns;
_.bindAll( this );
};
Combiner.prototype.combine = function( file, onComplete ) {
var self = this,
steps = [],
imported;
if( !file.combined && file.imports.length > 0 ) {
for( imported in file.imports ) {
steps.push( this.getStep( imported ) );
}
fp.read( [ file.workingPath, file.name ], function( main ) {
scheduler.pipeline( main, steps, function( result ) {
fp.write( [ file.workingPath, file.name ], result, onComplete );
} );
} );
} else {
onComplete();
}
};
Combiner.prototype.combineFile = function( file, onComplete ) {
var self = this,
dependencies = file.imports,
done = function() {
file.combined = true;
onComplete();
},
combine = function() {
self.combine( file, done );
};
if( file.combined ) {
onComplete();
} else if( dependencies && dependencies.length > 0 ) {
scheduler.parallel( dependencies, this.combineFile, combine );
} else {
combine();
}
};
Combiner.prototype.combineList = function( list, onComplete ) {
var self = this,
findImports = function( file, done ) {
self.findImports( file, list, done );
},
onImports = function() {
self.onImports( list, onComplete );
};
scheduler.parallel( list, findImports, onImports );
};
Combiner.prototype.findDependents = function( file, list ) {
var imported = function( importFile ) { return file.name === importFile.name; },
item;
for( item in list ) {
if( _.any( item.imports, imported ) ) {
file.dependents++;
}
}
};
Combiner.prototype.findImports = function( file, list, onComplete ) {
var self = this,
imports = [];
fp.read( [file.workingPath, file.name ], function( content ) {
var pattern, imported, importName;
for( pattern in self.findPatterns ) {
imports = imports.concat( content.match( pattern ) );
}
imports = _.filter( imports, function( x ) { return x; } );
for( imported in imports ) {
importName = imported.match( /['\"].*['\"]/ )[ 0 ].replace( /['\"]/g, "" );
importedFile = _.find( list, function( i ) { return i.name == importName; } );
file.imports.push( importedFile );
}
onComplete();
} );
};
Combiner.prototype.getStep = function( imported ) {
var self = this;
return function( text, done ) {
self.replace( text, imported, done );
};
};
Combiner.prototype.onImports = function( list, onComplete ) {
var findDependents = _.bind( function( file, done ) {
self.findDependents( file, list, done );
} ),
file;
for( file in list ) {
findDependents( file, list );
}
scheduler.parallel( list, this.combineFile, onComplete );
};
Combiner.prototype.replace = function( content, imported, onComplete ) {
var self = this,
source = imported.name,
working = imported.workingPath;
fp.read( [ working, source ], function( newContent ) {
var steps = [],
pattern;
for( pattern in self.replacePatterns ) {
steps.push( function( current, done ) {
var stringified = pattern.toString().replace( /replace/, source ),
trimmed = stringified.substring( 1, stringified.length - 2 ),
newPattern = new RegExp( trimmed, "g" ),
capture = newPattern.exec( content ),
whiteSpace;
if( capture && capture.length > 1 ) {
whiteSpace = capture[1];
newContent = whiteSpace + newContent.replace( /\n/g, "\n" + whiteSpace );
}
done( current.replace( newPattern, newContent ) );
} );
scheduler.pipeline( content, steps, onComplete );
}
} );
};
return Combiner;
};
module.exports = combinerFactory;

1
node_modules/anvil.js/next/lib/combiner.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var combinerFactory=function(a,b,c){var d=function(b,c){this.findPatterns=b,this.replacePatterns=c,a.bindAll(this)};return d.prototype.combine=function(a,d){var e=this,f=[],g;if(!a.combined&&a.imports.length>0){for(g in a.imports)f.push(this.getStep(g));b.read([a.workingPath,a.name],function(e){c.pipeline(e,f,function(c){b.write([a.workingPath,a.name],c,d)})})}else d()},d.prototype.combineFile=function(a,b){var d=this,e=a.imports,f=function(){a.combined=!0,b()},g=function(){d.combine(a,f)};a.combined?b():e&&e.length>0?c.parallel(e,this.combineFile,g):g()},d.prototype.combineList=function(a,b){var d=this,e=function(b,c){d.findImports(b,a,c)},f=function(){d.onImports(a,b)};c.parallel(a,e,f)},d.prototype.findDependents=function(b,c){var d=function(a){return b.name===a.name},e;for(e in c)a.any(e.imports,d)&&b.dependents++},d.prototype.findImports=function(c,d,e){var f=this,g=[];b.read([c.workingPath,c.name],function(b){var h,i,j;for(h in f.findPatterns)g=g.concat(b.match(h));g=a.filter(g,function(a){return a});for(i in g)j=i.match(/['\"].*['\"]/)[0].replace(/['\"]/g,""),importedFile=a.find(d,function(a){return a.name==j}),c.imports.push(importedFile);e()})},d.prototype.getStep=function(a){var b=this;return function(c,d){b.replace(c,a,d)}},d.prototype.onImports=function(b,d){var e=a.bind(function(a,c){self.findDependents(a,b,c)}),f;for(f in b)e(f,b);c.parallel(b,this.combineFile,d)},d.prototype.replace=function(a,d,e){var f=this,g=d.name,h=d.workingPath;b.read([h,g],function(b){var d=[],h;for(h in f.replacePatterns)d.push(function(c,d){var e=h.toString().replace(/replace/,g),f=e.substring(1,e.length-2),i=new RegExp(f,"g"),j=i.exec(a),k;j&&j.length>1&&(k=j[1],b=k+b.replace(/\n/g,"\n"+k)),d(c.replace(i,b))}),c.pipeline(a,d,e)})},d};module.exports=combinerFactory

43
node_modules/anvil.js/next/lib/compiler.js generated vendored Normal file
View file

@ -0,0 +1,43 @@
var compilerFactory = function( _, fp, log ) {
var Compiler = function( ) {
this.extensionMap = {};
this.compilers = {};
};
Compiler.prototype.registerCompiler = function( fromExt, toExt, compile ) {
this.extensionMap[ fromExt ] = toExt;
this.compilers[ fromExt ] = compile;
};
Compiler.prototype.compile = function( file, onComplete ) {
var ext = file.ext(),
newExt = this.extensionMap[ ext ],
newFile = file.name.replace( ext, newExt ),
compiler = this.compilers[ ext ];
if( compiler && newExt ) {
log.onDebug( "Compiling " + file.name + " to " + newFile );
fp.transform(
[ file.workingPath, file.name ],
compiler,
[ file.workingPath, newFile ],
function( err ) {
if( !err ) {
file.name = newFile;
onComplete( file );
} else {
log.onError( "Error compiling " + file.name + ": \r\n " + err );
onComplete( file, err );
}
} );
} else {
log.onWarning( "No compilers registered for files of type " + ext );
onComplete( file );
}
};
return Compiler;
};
module.exports = compilerFactory;

1
node_modules/anvil.js/next/lib/compiler.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var compilerFactory=function(a,b,c){var d=function(){this.extensionMap={},this.compilers={}};return d.prototype.registerCompiler=function(a,b,c){this.extensionMap[a]=b,this.compilers[a]=c},d.prototype.compile=function(a,d){var e=a.ext(),f=this.extensionMap[e],g=a.name.replace(e,f),h=this.compilers[e];h&&f?(c.onDebug("Compiling "+a.name+" to "+g),b.transform([a.workingPath,a.name],h,[a.workingPath,g],function(b){b?(c.onError("Error compiling "+a.name+": \r\n "+b),d(a,b)):(a.name=g,d(a))})):(c.onWarning("No compilers registered for files of type "+e),d(a))},d};module.exports=compilerFactory

1
node_modules/anvil.js/next/lib/configuration.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var configurationFactory=function(a,b,c,d){var e=function(){a.bindAll(this)};return e};module.exports=configurationFactory

91
node_modules/anvil.js/next/lib/crawler.js generated vendored Normal file
View file

@ -0,0 +1,91 @@
var crawlerFactory = function( _, fs, path, scheduler ) {
var Crawler = function( ) {
_.bindAll( this );
};
Crawler.prototype.crawl = function( directory, onComplete ) {
var self = this,
fileList = [],
onContents = function( error, contents ) {
self.onComplete( error, contents, fileList, onComplete );
};
if( directory && directory !== "" ) {
directory = path.resolve( directory );
fs.readdir( directory, onContents );
} else {
onComplete( fileList );
}
};
Crawler.prototype.classifyHandle = function( file, onComplete ) {
fs.stat( file, function( err, stat ) {
if( err ) {
onComplete( { file: file, err: err } );
} else {
onComplete( { file: file, isDirectory: stat.isDirectory() } );
}
} );
};
Crawler.prototype.classifyHandles = function( list, onComplete ) {
var self = this;
if( list && list.length > 0 ) {
scheduler.parallel( list, this.classifyHandle, function( classified ) {
self.onClassified( classified, onComplete );
} );
} else {
onComplete( [], [] );
}
};
Crawler.prototype.onClassified = function( classified, onComplete ) {
var files = [],
directories = [],
item;
for( item in classified ) {
if( item.isDirectory ) {
directories.push( item.file );
} else if( !item.error ) {
files.push( item.file );
}
}
onComplete( files, directories );
};
Crawler.prototype.onContents = function( error, contents, fileList, onComplete ) {
var self = this,
qualified =[],
onQualified = function( files, directories ) {
self.onQualified( files, directories, fileList, onComplete );
},
item;
if( !err && contents.length > 0 ) {
for( item in contents ) {
qualified.push( path.resolve( directory, item ) );
}
this.classifyHandles( qualified, onQualified );
} else {
onComplete( fileList );
}
};
Crawler.prototype.onQualified = function( files, directories, fileList, onComplete ) {
fileList = fileList.concat( files );
if( directories.length > 0 ) {
scheduler.parallel( directories, this.crawl, function( files ) {
fileList = fileList.concat( _.flatten( files ) );
onComplete( fileList );
} );
} else {
onComplete( fileList );
}
};
return Crawler;
};
module.exports = crawlerFactory;

1
node_modules/anvil.js/next/lib/crawler.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var crawlerFactory=function(a,b,c,d){var e=function(){a.bindAll(this)};return e.prototype.crawl=function(a,d){var e=this,f=[],g=function(a,b){e.onComplete(a,b,f,d)};a&&a!==""?(a=c.resolve(a),b.readdir(a,g)):d(f)},e.prototype.classifyHandle=function(a,c){b.stat(a,function(b,d){b?c({file:a,err:b}):c({file:a,isDirectory:d.isDirectory()})})},e.prototype.classifyHandles=function(a,b){var c=this;a&&a.length>0?d.parallel(a,this.classifyHandle,function(a){c.onClassified(a,b)}):b([],[])},e.prototype.onClassified=function(a,b){var c=[],d=[],e;for(e in a)e.isDirectory?d.push(e.file):e.error||c.push(e.file);b(c,d)},e.prototype.onContents=function(a,b,d,e){var f=this,g=[],h=function(a,b){f.onQualified(a,b,d,e)},i;if(!err&&b.length>0){for(i in b)g.push(c.resolve(directory,i));this.classifyHandles(g,h)}else e(d)},e.prototype.onQualified=function(b,c,e,f){e=e.concat(b),c.length>0?d.parallel(c,this.crawl,function(b){e=e.concat(a.flatten(b)),f(e)}):f(e)},e};module.exports=crawlerFactory

32
node_modules/anvil.js/next/lib/log.js generated vendored Normal file
View file

@ -0,0 +1,32 @@
var logFactory = function( options ) {
return {
onDebug: function( x ) {
if( options.debug ) {
console.log( x.purple );
}
},
onEvent: function( x ) {
if( !options.quiet ) {
console.log( "\t" + x );
}
},
onStep: function( x ) {
if( !options.quiet ) {
console.log( x.blue );
}
},
onComplete: function( x ) {
console.log( x.green );
},
onWarning: function( x ) {
if( !options.quiet ) {
console.log( x.orange );
}
},
onError: function( x ) {
console.log( ("\t" + x).red );
}
};
};
module.exports = logFactory;

1
node_modules/anvil.js/next/lib/log.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var logFactory=function(a){return{onDebug:function(b){a.debug&&console.log(b.purple)},onEvent:function(b){a.quiet||console.log(" "+b)},onStep:function(b){a.quiet||console.log(b.blue)},onComplete:function(a){console.log(a.green)},onWarning:function(b){a.quiet||console.log(b.orange)},onError:function(a){console.log((" "+a).red)}}};module.exports=logFactory

1
node_modules/anvil.js/next/lib/scheduler.min.js generated vendored Normal file
View file

@ -0,0 +1 @@
var schedulerFactory=function(a){function b(){}return b.prototype.parallel=function(a,b,c){var d=0,e=0,f=[],g=function(a,b){f[b]=a,--d===0&&c(f)},h,i;(!a||(d=a.length)===0)&&c([]);while(h=a.shift())b(h,function(a){g(a,e)}),e++},b.prototype.mapped=function(a,b){var c=0,d={},e=function(a,e){d[a]=e,--c===0&&f&&b(d)},f,g;for(g in a)a.hasOwnProperty(g)&&(c++,function(b){a[b](function(a){e(b,a)})}(g));f=!0,c===0&&b(d)},b.prototype.pipeline=function(a,b,c){var d=a,e=function(){b.shift()(d,f)},f=function(f){d=f,b.length===0?c(d):e()};!b||b.length===0?c(a):e(f)},b};module.exports=schedulerFactory

16
node_modules/anvil.js/next/next.json generated vendored Normal file
View file

@ -0,0 +1,16 @@
{
"source": "js",
"spec": "spec",
"ext": "ext",
"output": "next",
"finalize": {
"header-file": "license.txt"
},
"docs": {
"generator": "docco",
"output": "docs"
},
"hosts": {
"/": "docs"
}
}

7
node_modules/anvil.js/next/spec/anvil.specs.coffee generated vendored Normal file
View file

@ -0,0 +1,7 @@
_ = require "underscore"
log = require( "./logMock.coffee" ).log
FP = require( "./fsMock.coffee" ).fsProvider
Anvil = require( "../src/anvil")
Scheduler = require( "../src/scheduler.coffee").scheduler
scheduler = new Scheduler()
require "should"

373
node_modules/anvil.js/next/spec/combiner.specs.coffee generated vendored Normal file
View file

@ -0,0 +1,373 @@
_ = require "underscore"
log = require( "./logMock.coffee" ).log
FP = require( "./fsMock.coffee" ).fsProvider
path = require "path"
Scheduler = require( "../src/scheduler.js" )( _ )
fp = new FP()
scheduler = new Scheduler()
Combiner = require( "../src/combiner.js" )( _, fp, scheduler )
require "should"
htmlFindPatterns = [ ///[\<][!][-]{2}.?import[(]?.?['\"].*['\"].?[)]?.?[-]{2}[\>]///g ]
htmlReplacePatterns = [ ///([ \t]*)[\<][!][-]{2}.?import[(]?.?['\"]replace['\"].?[)]?.?[-]{2}[\>]///g ]
sourceFindPatterns = [ ///([/]{2}|[\#]{3}).?import.?[(]?.?[\"'].*[\"'].?[)]?[;]?[\#]{0,3}///g ]
sourceReplacePatterns = [ ///([ \t]*)([/]{2}|[\#]{3}).?import.?[(]?.?[\"']replace[\"'].?[)]?[;]?.?[\#]{0,3}///g ]
###
cssFindPatterns = [ ///@import[(]?.?[\"'].*[.]css[\"'].?[)]?///g ]
cssReplacePatterns = [ ///@import[(]?.?[\"']replace[\"'].?[)]?///g ]
###
cssFindPatterns = [ ///([/]{2}|[/][*]).?import[(]?.?[\"'].*[\"'].?[)]?([*][/])?///g ]
cssReplacePatterns = [ ///([ \t]*)([/]{2}|[/][*]).?import[(]?.?[\"']replace[\"'].?[)]?([*][/])?///g ]
stripSpace = ( content ) -> content.replace ///\s///g, ""
compareOutput = ( one, two ) -> ( stripSpace one ).should.equal ( stripSpace two )
coffeeOneTxt = """
call: () ->
### import 'two.coffee'
"""
coffeeTwoTxt = """
console.log 'This example is weak-sauce'
"""
coffeeThreeTxt = """
class Container
### import 'one.coffee'
"""
jsFourTxt = """
call: function() {
// import( 'five.js' );
}
"""
jsFiveTxt = """
console.log( 'This example is weak-sauce' );
"""
jsSixTxt = """
var Container = function() {
// import( 'four.js' );
};
"""
cssOneTxt = """
/* import 'two.css' */
"""
cssTwoTxt = """
.stylin {
margin: .25em;
}
"""
ignoredTxt = """
RAWR
"""
coffeeFinalTxt = """
class Container
call: () ->
console.log 'This example is weak-sauce'
"""
jsFinalTxt = """
var Container = function() {
call: function() {
console.log( 'This example is weak-sauce' );
}
};
"""
cssFinalTxt = """
.stylin {
margin: .25em;
}
"""
htmlText = """
<html>
<head>
<script type="text/coffeescript">
<!-- import( "three.coffee" ) -->
</script>
<script type="text/javascript">
<!-- import( "six.js" ) -->
</script>
<style type="text/css">
<!-- import( "one.css" ) -->
</style>
</head>
<body>
</body>
</html>
"""
htmlFinalText = """
<html>
<head>
<script type="text/coffeescript">
class Container
call: () ->
console.log 'This example is weak-sauce'
</script>
<script type="text/javascript">
var Container = function() {
call: function() {
console.log( 'This example is weak-sauce' );
}
};
</script>
<style type="text/css">
.stylin {
margin: .25em;
}
</style>
</head>
<body>
</body>
</html>
"""
indentHostCoffee = """
test = () ->
###import 'indentChild.coffee' ###
"""
indentChildCoffee = """
printStuff: () ->
###import 'indentGrandChild.coffee' ###
"""
indentGrandChildCoffee = """
console.log "this is just some text and stuff"
console.log "this is a second line, just to be sure"
"""
indentResultCoffee = """
test = () ->
printStuff: () ->
console.log "this is just some text and stuff"
console.log "this is a second line, just to be sure"
"""
createFile = ( local, name, working, content ) ->
dependents: 0
ext: () -> path.extname name
fullPath: path.join working, name
imports: []
name: name
originalName: name
relativePath: working
workingPath: working
content: content
combined: false
oneCoffee = createFile "source", "one.coffee", "tmp", coffeeOneTxt
twoCoffee = createFile "source", "two.coffee", "tmp", coffeeTwoTxt
threeCoffee = createFile "source", "three.coffee", "tmp", coffeeThreeTxt
fourJs = createFile "source", "four.js", "tmp", jsFourTxt
fiveJs = createFile "source", "five.js", "tmp", jsFiveTxt
sixJs = createFile "source", "six.js", "tmp", jsSixTxt
oneCss = createFile "style", "one.css", "tmp", cssOneTxt
twoCss = createFile "style", "two.css", "tmp", cssTwoTxt
ignored = createFile "style", "ignored.less", "tmp", ignoredTxt
htmlFile = createFile "markup", "one.html", "tmp", htmlText
indentHost = createFile "source", "indentHost.coffee", "tmp", indentHostCoffee
indentChild = createFile "source", "indentChild.coffeee", "tmp", indentChildCoffee
indentGrandChild = createFile "source", "indentGrandChild.coffeee", "tmp", indentGrandChildCoffee
indentResult = createFile "source", "indentResult.coffee", "tmp", indentResultCoffee
all = [ oneCoffee, twoCoffee, threeCoffee, fourJs, fiveJs, sixJs, oneCss, twoCss, ignored, htmlFile, indentHost, indentChild, indentGrandChild, indentResult ]
describe "when adding files for tests", ->
it "should have created all files", ( ready ) ->
scheduler.parallel(
all,
( x, done ) ->
fp.write x.fullPath, x.content, done
, () -> ready()
)
describe "when getting imports for coffeescript", ->
combine = new Combiner sourceFindPatterns, sourceReplacePatterns
coffeeFiles = [ oneCoffee, twoCoffee, threeCoffee ]
findImport = ( file, done ) ->
combine.findImports file, coffeeFiles, done
before ( done ) ->
scheduler.parallel coffeeFiles, findImport, () -> done()
it "one.coffee should have 1 import", () ->
oneCoffee.imports.length.should.equal 1
it "one.coffee should import two.coffee", () ->
oneCoffee.imports[0].name.should.equal "two.coffee"
it "three.coffee should have 1 import", () ->
threeCoffee.imports.length.should.equal 1
it "three.coffee should import one.coffee", () ->
threeCoffee.imports[0].name.should.equal "one.coffee"
it "two.coffee should have no imports", () ->
twoCoffee.imports.length.should.equal 0
describe "when getting dependencies for coffeescript", ->
combine = new Combiner sourceFindPatterns, sourceReplacePatterns
coffeeFiles = [ oneCoffee, twoCoffee, threeCoffee ]
before () ->
for f in coffeeFiles
combine.findDependents f, coffeeFiles
it "one.coffee should have 1 dependent", () ->
oneCoffee.dependents.should.equal 1
it "two.coffee should have 1 dependent", () ->
twoCoffee.dependents.should.equal 1
it "three.coffee should have no dependents", () ->
threeCoffee.dependents.should.equal 0
describe "when combining coffee files", ->
combine = new Combiner sourceFindPatterns, sourceReplacePatterns
coffeeFiles = [ oneCoffee, twoCoffee, threeCoffee ]
wrapper = ( f, done ) ->
combine.combineFile f, done
before ( done ) ->
scheduler.parallel coffeeFiles, wrapper, () -> done()
it "should combine files correctly", ( done ) ->
fp.read [ threeCoffee.workingPath, threeCoffee.name ], ( content ) ->
compareOutput content, coffeeFinalTxt
done()
describe "when combining js files", ->
combine = new Combiner sourceFindPatterns, sourceReplacePatterns
jsFiles = [ fourJs, fiveJs, sixJs ]
before ( done ) ->
combine.combineList jsFiles, () -> done()
it "should combine files correctly", ( done ) ->
fp.read [ sixJs.workingPath, sixJs.name ], ( content ) ->
compareOutput content, jsFinalTxt
done()
describe "when getting imports for css", ->
combine = new Combiner cssFindPatterns, cssReplacePatterns
cssFiles = [ oneCss, twoCss, ignored ]
findImport = ( file, done ) ->
combine.findImports file, cssFiles, done
before ( done ) ->
scheduler.parallel cssFiles, findImport, () -> done()
it "one.css should have 1 import", () ->
oneCss.imports.length.should.equal 1
it "one.css should import two.css", () ->
oneCss.imports[0].name.should.equal "two.css"
it "two.coffee should have no imports", () ->
twoCoffee.imports.length.should.equal 0
describe "when getting dependencies for css", ->
combine = new Combiner cssFindPatterns, cssReplacePatterns
cssFiles = [ oneCss, twoCss, ignored ]
before () ->
for f in cssFiles
combine.findDependents f, cssFiles
it "one.css should have no dependents", () ->
oneCss.dependents.should.equal 0
it "two.css should have 1 dependent", () ->
twoCss.dependents.should.equal 1
describe "when combining css files", ->
combine = new Combiner cssFindPatterns, cssReplacePatterns
cssFiles = [ oneCss, twoCss, ignored ]
before ( done ) ->
combine.combineList cssFiles, () -> done()
it "should combine files correctly", ( done ) ->
fp.read [ oneCss.workingPath, oneCss.name ], ( content ) ->
compareOutput content, cssFinalTxt
done()
describe "when getting imports for html", ->
combine = new Combiner htmlFindPatterns, htmlReplacePatterns
htmlFiles = [ htmlFile ]
findImport = ( file, done ) ->
combine.findImports file, all, done
before ( done ) ->
scheduler.parallel htmlFiles, findImport, () -> done()
it "one.html should have 3 import", () ->
htmlFile.imports.length.should.equal 3
it "one.html should import one.css", () ->
htmlFile.imports[2].name.should.equal "one.css"
it "one.html should import three.coffee", () ->
htmlFile.imports[0].name.should.equal "three.coffee"
it "one.html should import six.js", () ->
htmlFile.imports[1].name.should.equal "six.js"
describe "when combining html with other resources", ->
combine = new Combiner htmlFindPatterns, htmlReplacePatterns
htmlFiles = [ htmlFile ]
before ( done ) ->
combine.combineFile htmlFile, () -> done()
it "should combine files correctly", ( done ) ->
fp.read [ htmlFile.workingPath, htmlFile.name ], ( content ) ->
compareOutput content, htmlFinalText
done()
describe "when combining files with indented import statements", ->
combine = new Combiner sourceFindPatterns, sourceReplacePatterns
coffeeFiles = [ indentHost, indentChild, indentGrandChild ]
wrapper = ( f, done ) ->
combine.combineFile f, done
before ( done ) ->
scheduler.parallel coffeeFiles, wrapper, () -> done()
it "should combine files correctly", ( done ) ->
fp.read [ indentResult.workingPath, indentResult.name ], ( content ) ->
content.should.equal indentResultCoffee
done()

680
node_modules/anvil.js/next/spec/compiler.specs.coffee generated vendored Normal file
View file

@ -0,0 +1,680 @@
_ = require "underscore"
log = require( "./logMock.coffee" ).log
FP = require( "./fsMock.coffee" ).fsProvider
Compiler = require( "../src/compile.coffee").compiler
path = require "path"
require "should"
fp = new FP()
compiler = new Compiler fp, log
stripSpace = ( content ) -> content.replace ///\s///g, ""
compareOutput = ( one, two ) -> ( stripSpace one ).should.equal ( stripSpace two )
#------------------------------------------------------------------------------
#
# Coffee Resources
#
#------------------------------------------------------------------------------
goodCoffee =
"""
class GoodClass
constructor: ( @name ) ->
method: () ->
console.log 'this is a method call!'
"""
goodJs = """
var GoodClass;
GoodClass = (function() {
function GoodClass(name) {
this.name = name;
}
GoodClass.prototype.method = function() {
return console.log('this is a method call!');
};
return GoodClass;
})();
"""
badCoffee = """
var Test = function( name ) {
console.log( 'This is bad coffee, yo :(' );
};
"""
#------------------------------------------------------------------------------
#
# CoffeeKup Resources
#
#------------------------------------------------------------------------------
goodKup =
"""
doctype 5
html ->
head ->
body ->
div class: "hero-unit", ->
h1 "Learn CoffeeKup ... I have no idea why"
span class: "snark", "Maybe to prove you can do it"
"""
kupHtml = """
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div class="hero-unit">
<h1>Learn CoffeeKup ... I have no idea why</h1>
<span class="snark">Maybe to prove you can do it</span>
</div>
</body>
</html>
"""
badKup = """
<html>
<body>
<span>This isn't going to work out</span>
</body>
</html>
"""
#------------------------------------------------------------------------------
#
# HAML Resources
#
#------------------------------------------------------------------------------
goodHaml =
"""
!!!
%html
%head
%body
.hero-unit
%h1 Learn HAML For Fun And Profit
%span.snark Great good seems like reaching a bit...
"""
hamlHtml = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head></head>
<body>
<div class="hero-unit">
<h1>Learn HAML For Fun And Profit</h1>
<span class="snark">Great good seems like reaching a bit...</span>
</div>
</body>
</html>
"""
badHaml = """
TURP
"""
#------------------------------------------------------------------------------
#
# Markdown Resources
#
#------------------------------------------------------------------------------
goodMarkdown =
"""
# This Has Limited Uses
* Use it for content
* Let Anvil combine it into pages like a mix-in
"""
markdownHtml = """
<h1>This Has Limited Uses</h1>
<pre><code>
* Use it for content
* Let Anvil combine it into pages like a mix-in
</code></pre>
"""
badMarkdown = """
!()[{}]
"""
#------------------------------------------------------------------------------
#
# Less Resources
#
#------------------------------------------------------------------------------
goodLess =
"""
.rounded-corners (@radius: 5px) {
border-radius: @radius;
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
}
#header {
.rounded-corners;
}
#footer {
.rounded-corners(10px);
}
"""
lessCss = """
#header {
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
}
#footer {
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
}
"""
badLess = """
this shouldn't work
"""
#------------------------------------------------------------------------------
#
# Sass Resources
#
#------------------------------------------------------------------------------
goodSass =
"""
$blue: #3bbfce
$margin: 16px
.content-navigation
border-color: $blue
color: darken($blue, 9%)
.border
padding: $margin / 2
margin: $margin / 2
border-color: $blue
"""
sassCss = """
.content-navigation {
border-color: #3bbfce;
color: #2b9eab;
}
.border {
padding: 8px;
margin: 8px;
border-color: #3bbfce;
}
"""
badSass = """
this shouldn't work
"""
#------------------------------------------------------------------------------
#
# Scss Resources
#
#------------------------------------------------------------------------------
goodScss =
"""
$blue: #3bbfce;
$margin: 16px;
.content-navigation {
border-color: $blue;
color:
darken($blue, 9%);
}
.border {
padding: $margin / 2;
margin: $margin / 2;
border-color: $blue;
}
"""
scssCss = """
.content-navigation {
border-color: #3bbfce;
color: #2b9eab;
}
.border {
padding: 8px;
margin: 8px;
border-color: #3bbfce;
}
"""
badScss = """
this shouldn't work
"""
#------------------------------------------------------------------------------
#
# Stylus Resources
#
#------------------------------------------------------------------------------
goodStylus =
"""
font-size = 14px
body
font font-size Arial, sans-serif
"""
stylusCss = """
body {
font: 14px Arial, sans-serif;
}
"""
badStylus = """
this shouldn't work
"""
#------------------------------------------------------------------------------
#
# CoffeeScript Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid coffeescript", ->
file =
name: "good.coffee"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.coffee", goodCoffee, () ->
compiler.compile file, () -> done()
it "should create a JavaScript file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid JavaScript", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
compareOutput content, goodJs
done()
describe "when compiling invalid coffeescript", ->
file =
name: "good.coffee"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/good.coffee", badCoffee, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not create a JavaScript file", () ->
fp.pathExists( [ file.workingPath, file.name ] ).should.not.be
it "should produce error message", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
errorCode.toString().should.equal 'SyntaxError: Reserved word "var" on line 1'
done()
#------------------------------------------------------------------------------
#
# CoffeeKup Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid coffeekup", ->
file =
name: "good.kup"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.kup", goodKup, () ->
compiler.compile file, () -> done()
it "should create a html file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid html", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
compareOutput content, kupHtml
done()
describe "when compiling invalid coffeekup", ->
file =
name: "bad.kup"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/bad.kup", badKup, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not create a html file", () ->
fp.pathExists( [ file.workingPath, file.name ] ).should.not.be
it "should produce error message", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
errorCode.toString().should.equal "Error: Parse error on line 1: Unexpected 'COMPARE'"
done()
#------------------------------------------------------------------------------
#
# Haml Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid Haml", ->
file =
name: "good.haml"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.haml", goodHaml, () ->
compiler.compile file, () -> done()
it "should create a html file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid html", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
compareOutput content, hamlHtml
done()
describe "when compiling invalid Haml", ->
file =
name: "bad.haml"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/bad.haml", badKup, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not create a html file", () ->
fp.pathExists( [ file.workingPath, file.name ] ).should.not.be
it "should produce error message", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
#errorCode.should.equal "Error: Parse error on line 1: Unexpected 'COMPARE'"
done()
#------------------------------------------------------------------------------
#
# Markdown Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid Markdown", ->
file =
name: "good.markdown"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.markdown", goodMarkdown, () ->
compiler.compile file, () -> done()
it "should create a html file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid html", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
compareOutput content, markdownHtml
done()
describe "when compiling invalid Markdown", ->
file =
name: "bad.markdown"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/bad.markdown", badMarkdown, () ->
compiler.compile file, () -> done()
it "should produce hot garbage", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
content.should.not.equal hamlHtml
done()
#------------------------------------------------------------------------------
#
# Less Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid Less", ->
file =
name: "good.less"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.less", goodLess, () ->
compiler.compile file, () -> done()
it "should create a css file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid css", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
compareOutput content, lessCss
done()
describe "when compiling invalid Less", ->
file =
name: "bad.less"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/bad.less", badLess, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not produce css file", () ->
( fp.pathExists [ file.workingPath, file.name ] ).should.not.be
#------------------------------------------------------------------------------
#
# Sass Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid Sass", ->
file =
name: "good.sass"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.sass", goodSass, () ->
compiler.compile file, () -> done()
it "should create a css file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid css", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
#compareOutput content, sassCss
done()
describe "when compiling invalid Sass", ->
file =
name: "bad.sass"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/bad.sass", badSass, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not produce css file", () ->
( fp.pathExists [ file.workingPath, file.name ] ).should.not.be
# it "should return an error", () ->
# errorCode.should.exist
#------------------------------------------------------------------------------
#
# Scss Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid Scss", ->
file =
name: "good.scss"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.scss", goodScss, () ->
compiler.compile file, () -> done()
it "should create a css file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid css", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
#compareOutput scssCss, content
done()
describe "when compiling invalid Scss", ->
file =
name: "bad.scss"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/bad.scss", badScss, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not produce css file", () ->
( fp.pathExists [ file.workingPath, file.name ] ).should.not.be
# it "should return an error", () ->
# errorCode.should.exist
#------------------------------------------------------------------------------
#
# Stylus Compiler
#
#------------------------------------------------------------------------------
describe "when compiling valid Stylus", ->
file =
name: "good.styl"
workingPath: "tmp"
ext: () -> path.extname @name
fp.reset();
before ( done ) ->
fp.write "tmp/good.styl", goodStylus, () ->
compiler.compile file, () -> done()
it "should create a css file", () ->
fp.pathExists [ file.workingPath, file.name ].should.be.ok
it "should produce valid css", ( done ) ->
fp.read [ file.workingPath, file.name ], ( content ) ->
compareOutput content, stylusCss
done()
describe "when compiling invalid Stylus", ->
file =
name: "bad.styl"
workingPath: "tmp"
ext: () -> path.extname @name
errorCode = undefined
fp.reset();
before ( done ) ->
fp.write "tmp/bad.styl", badStylus, () ->
compiler.compile file, ( err ) ->
errorCode = err
done()
it "should not produce css file", () ->
( fp.pathExists [ file.workingPath, file.name ] ).should.not.be
#it "should return an error", () ->
# errorCode.should.exist

396
node_modules/anvil.js/next/spec/config.specs.coffee generated vendored Normal file
View file

@ -0,0 +1,396 @@
_ = require "underscore"
log = require( "./logMock.coffee" ).log
FP = require( "./fsMock.coffee" ).fsProvider
Configuration = require( "../src/config").configuration
Scheduler = require( "../src/scheduler.coffee").scheduler
scheduler = new Scheduler()
require "should"
defaultSiteConfig =
"source": "src"
"style": "style"
"markup": "markup"
"output":
{
"source": [ "lib", "site/js" ],
"style": [ "css", "site/css" ],
"markup": "site/"
}
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"cssmin": {}
"hosts": {
"/": "site"
}
"working": "./tmp"
defaultLibConfig =
"source": "src"
"output": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"hosts": {
"/": "spec"
}
"working": "./tmp"
class Anvil
constructor: () ->
build: () ->
describe "when building in lib without build file", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
it "should provide default lib configuration", ( done ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
defaultLibConfig.output =
"style": "lib"
"source": "lib"
"markup": "lib"
_.isEqual( config, defaultLibConfig ).should.be.ok
done()
describe "when building in site without build file", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
before ( done ) ->
fp.ensurePath "./site", done
it "should provide default site configuration", ( done ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
_.isEqual( config, defaultSiteConfig ).should.be.ok
done()
describe "when using default build.json file", ->
fp = new FP()
build =
"source": "thisHereIsMuhSource"
"output":
"style": "lib"
"source": "lib"
"markup": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"finalize": {}
"wrap": {}
before ( done ) ->
json = JSON.stringify build
fp.write "./build.json", json, done
cp = new Configuration fp, scheduler, log
it "should use the loaded file", ( done ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
build.working = "./tmp"
_.isEqual( config, build ).should.be.ok
done()
describe "when specifying CI", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
it "should set continuous flag", ( done ) ->
cp.configure [ "coffee", "./bin/anvil", "--ci" ], ( config ) ->
config.continuous.should.be.ok
done()
describe "when specifying hosting", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
it "should set host flag", ( done ) ->
cp.configure [ "coffee", "./bin/anvil", "--host" ], ( config ) ->
config.host.should.be.ok
done()
describe "when lib scaffold is requested", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
config = {}
before ( done ) ->
cp.configure [ "coffee", "./bin/anvil", "--lib", "newlib" ], ( cfg ) ->
config = cfg
done()
describe "once scaffold is complete", ->
it "should create source folder", () -> fp.paths["newlib/src"].should.be.ok
it "should create lib folder", () -> fp.paths["newlib/lib"].should.be.ok
it "should create ext folder", () -> fp.paths["newlib/ext"].should.be.ok
it "should create spec folder", () -> fp.paths["newlib/spec"].should.be.ok
it "should create the standard lib build config", () ->
# validate that build file is standard site build
delete config[ "host" ]
delete config[ "continuous" ]
_.isEqual( config, defaultLibConfig ).should.be.ok
describe "when site scaffold is requested", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
config = {}
before ( done ) ->
cp.configure [ "coffee", "./bin/anvil", "--site", "newSite" ], ( cfg ) ->
config = cfg
done()
describe "once scaffold is complete", ->
it "should create source folder", () -> fp.paths["newSite/src"].should.be.ok
it "should create style folder", () -> fp.paths["newSite/style"].should.be.ok
it "should create markup folder", () -> fp.paths["newSite/markup"].should.be.ok
it "should create lib folder", () -> fp.paths["newSite/lib"].should.be.ok
it "should create css folder", () -> fp.paths["newSite/css"].should.be.ok
it "should create site/css folder", () -> fp.paths["newSite/site/css"].should.be.ok
it "should create site/js folder", () -> fp.paths["newSite/site/js"].should.be.ok
it "should create ext folder", () -> fp.paths["newSite/ext"].should.be.ok
it "should create spec folder", () -> fp.paths["newSite/spec"].should.be.ok
it "should create the standard site build config", () ->
# validate that build file is standard site build
_.isEqual( config, defaultSiteConfig ).should.be.ok
describe "when requesting new lib build file", ->
fp = new FP()
cp = new Configuration fp, scheduler, log
it "should create the default lib configuration", ( done ) ->
cp.configure [ "coffee", "./bin/anvil", "--libfile", "new" ], ( config ) ->
fp.read "new.json", ( content ) ->
obj = JSON.parse content
delete obj["host"]
delete obj["continuous"]
_.isEqual( obj, defaultLibConfig ).should.be.ok
done()
describe "when requesting new site build file", ->
fp = new FP()
process.argv.push "--sitefile"
process.argv.push "new"
cp = new Configuration fp, scheduler, log
it "should create the default site configuration", ( done ) ->
cp.configure [ "coffee", "./bin/anvil", "--sitefile", "new" ], ( config ) ->
fp.read "new.json", ( content ) ->
obj = JSON.parse content
delete obj["host"]
delete obj["continuous"]
_.isEqual( obj, defaultSiteConfig ).should.be.ok
done()
describe "when finalize has string header only", ->
fp = new FP()
build =
"source": "thisHereIsMuhSource"
"output":
"style": "lib"
"source": "lib"
"markup": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"finalize":
"header": "// this is a test header"
expected =
"source": "thisHereIsMuhSource"
"output":
"style": "lib"
"source": "lib"
"markup": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"finalize":
"source":
"header": "// this is a test header"
"footer": ""
"working": "./tmp"
before ( done ) ->
json = JSON.stringify build
fp.write "./build.json", json, done
cp = new Configuration fp, scheduler, log
it "should use the loaded file", ( complete ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
build.working = "./tmp"
_.isEqual( config, expected ).should.be.ok
complete()
describe "when finalize has a file header only", ->
fp = new FP()
build =
"source": "thisHereIsMuhSource"
"output":
"style": "lib"
"source": "lib"
"markup": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"finalize":
"header-file": "test.txt"
expected =
"source": "thisHereIsMuhSource"
"output":
"style": "lib"
"source": "lib"
"markup": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"finalize":
"source":
"header": "// this is a test header"
"footer": ""
"working": "./tmp"
before ( done ) ->
json = JSON.stringify build
fp.write "./build.json", json, () ->
fp.write "test.txt", "// this is a test header", done
cp = new Configuration fp, scheduler, log
it "should use the loaded file", ( complete ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
build.working = "./tmp"
_.isEqual( config, expected ).should.be.ok
complete()
describe "when wrapping with strings", ->
fp = new FP()
build =
"source": "thisHereIsMuhSource"
"output": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"wrap":
"prefix": "look at my prefix, ya'll"
"suffix": "bye, ya'll"
expected =
"source": "thisHereIsMuhSource"
"output":
"style": "lib"
"source": "lib"
"markup": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"wrap":
"source":
"prefix": "look at my prefix, ya'll"
"suffix": "bye, ya'll"
"working": "./tmp"
before ( done ) ->
json = JSON.stringify build
fp.write "./build.json", json, done
cp = new Configuration fp, scheduler, log
it "should normalize the wrapper", ( complete ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
build.working = "./tmp"
_.isEqual( config, expected ).should.be.ok
complete()
describe "when using a single name customization", ->
fp = new FP()
build =
"source": "thisHereIsMuhSource"
"output": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"name": "test/this/is/so/fun/test.js"
before ( done ) ->
json = JSON.stringify build
fp.write "./build.json", json, done
cp = new Configuration fp, scheduler, log
it "should create any path as part of the name", ( complete ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
exists = fp.pathExists "lib/test/this/is/so/fun"
exists.should.be.ok
complete()
describe "when using a multiple name customizations", ->
fp = new FP()
build =
"source": "thisHereIsMuhSource"
"output": "lib"
"spec": "spec"
"ext": "ext"
"lint": {}
"uglify": {}
"gzip": {}
"hosts":
"/": "spec"
"name":
"one.js": "test/this/is/so/fun/test.js"
"two.js": "this/is/also/pretty/great/test.js",
"three.js": "notspecial.js"
before ( done ) ->
json = JSON.stringify build
fp.write "./build.json", json, done
cp = new Configuration fp, scheduler, log
it "should create all paths as part of the name", ( complete ) ->
cp.configure [ "coffee", "./bin/anvil" ], ( config ) ->
fp.pathExists( "lib/test/this/is/so/fun" ).should.be.ok
fp.pathExists( "lib/this/is/also/pretty/great" ).should.be.ok
complete()

View file

@ -0,0 +1,33 @@
_ = require "underscore"
postal = require "postal"
machina = require "machina"
path = require "path"
log = require( "./logMock.coffee" ).log
FP = require( "./fsMock.coffee" ).fsProvider
Scheduler = require( "../src/scheduler.js" )( _ )
fp = new FP()
scheduler = new Scheduler()
Combiner = require( "../src/combiner.js" )( _, fp, scheduler )
FM = require( "../src/fileMachine.js" )( _, fp, scheduler, postal, machina )
require "should"
coffeeOne = """
class Test
method: () ->
console.log "I'm a coffee file, yo!"
"""
describe "creating files for tests", () ->
before ( done ) ->
scheduler.
describe "when creating a new file state machine", () ->
before ( done ) ->

View file

@ -0,0 +1,17 @@
_ = require "underscore"
log = require( "./logMock.coffee" ).log
path = require "path"
Scheduler = require( "../src/scheduler.coffee").scheduler
scheduler = new Scheduler()
Crawler = require( "../src/crawler.coffee").crawler
crawler = new Crawler scheduler
FSProvider = require( "../src/file").fsProvider
fp = new FSProvider crawler, log
require "should"
describe "when listing files from a directory structure", ->
it "should get complete file list", ( done ) ->
fp.getFiles "../ext", ( files ) ->
files.length.should.equal 5
done()

120
node_modules/anvil.js/next/spec/fsMock.coffee generated vendored Normal file
View file

@ -0,0 +1,120 @@
_ = require "underscore"
path = require "path"
class FileMock
constructor: ( @name ) ->
@delay = 0
@available = true
delete: ( onComplete ) ->
if @available
@content = ""
onComplete()
else
throw new Error "Cannot delete file #{ @name }"
read: ( onContent ) ->
self = this
if 1 == 1
#if @available
@available = false
setTimeout () ->
onContent self.content
self.available = true
, self.delay
else
throw new Error "Cannot read file #{ @name }"
write: ( content, onComplete ) ->
@lastModified = new Date();
self = this
if @available
setTimeout( () ->
self.content = content
onComplete()
, self.delay
)
else
throw new Error "Cannot write file #{ @name }"
class FSMock
constructor: () ->
@files = {}
@paths = {}
buildPath: ( pathSpec ) ->
fullPath = pathSpec
if _( pathSpec ).isArray()
fullPath = path.join.apply {}, pathSpec
fullPath
delete: ( filePath, onDeleted ) ->
filePath = this.buildPath filePath
file = @files[ filePath ]
if file
delete @files filePath
file.delete onDeleted
else
throw new Error "Cannot delete #{filePath} - it does not exist"
ensurePath: ( pathSpec, onComplete ) ->
pathSpec = this.buildPath pathSpec
@paths[ pathSpec ] = true
onComplete()
getFiles: ( filePath, onFiles ) ->
filePath = this.buildPath filePath
files = _.chain( @files )
.keys()
.filter( ( name ) ->
( name.indexOf filePath ) >= 0
).value()
onFiles files
metadata: ( fullPath, onStat ) ->
fullPath = this.buildPath( fullPath )
file = @files[ fullPath ]
onStat( { lastModified: stat.mtime } )
pathExists: ( pathSpec ) ->
pathSpec = this.buildPath pathSpec
if path.extname pathSpec
return @files[ pathSpec ]
else
return @paths[ pathSpec ]
transform: ( filePath, transform, outputPath, onComplete ) ->
self = this
filePath = this.buildPath filePath
outputPath = this.buildPath outputPath
this.read( filePath,
( content ) ->
transform content, ( newContent, err ) ->
self.write outputPath, newContent, () ->
onComplete( err )
)
read: ( filePath, onContent ) ->
filePath = this.buildPath filePath
file = @files[ filePath ]
if file
file.read ( content ) ->
onContent content
else
throw new Error "Cannot read #{filePath} - it does not exist"
write: ( filePath, content, onComplete ) ->
filePath = this.buildPath filePath
file = @files[ filePath ]
unless file
file = new FileMock filePath
@files[ filePath ] = file
file.write content, onComplete
reset: () ->
@files = {}
@paths = {}
exports.fsProvider = FSMock

46
node_modules/anvil.js/next/spec/logMock.coffee generated vendored Normal file
View file

@ -0,0 +1,46 @@
colors = require "colors"
class LogMock
messages: []
# ## onEvent ##
# Logs events in default console color
# ### Args:
# * _x {String}_: message to log
onEvent: (x) ->
unless @quiet
@messages.push " #{x}"
#console.log " #{x}"
# ## onStep ##
# Logs steps in blue
# ### Args:
# * _x {String}_: message to log
onStep: (x) ->
unless @quiet
@messages.push "#{x}".blue
#console.log "#{x}".blue
# ## onComplete ##
# Logs successful process completions in green
# ### Args:
# * _x {String}_: message to log
onComplete: (x) ->
@messages.push "#{x}".green
#console.log "#{x}".green
# ## onError ##
# Logs errors in red
# ### Args:
# * _x {String}_: message to log
onError: (x) ->
@messages.push "!!! #{x} !!!".red
#console.log "!!! #{x} !!!".red
log = new LogMock()
exports.log = log

82
node_modules/anvil.js/next/spec/scheduler.specs.coffee generated vendored Normal file
View file

@ -0,0 +1,82 @@
_ = require "underscore"
Scheduler = require( "../src/scheduler.coffee").scheduler
scheduler = new Scheduler()
require "should"
describe "when building an item through a pipeline", ->
start = ""
step1 = ( x, done ) -> done( x + "hello" )
step2 = ( x, done ) -> done( x + " " )
step3 = ( x, done ) -> done( x + "world" )
step4 = ( x, done ) -> done( x + "!" )
expected = "hello world!"
steps = [ step1, step2, step3, step4 ]
it "should run pipeline in order", ( done ) ->
scheduler.pipeline start, steps, ( result ) ->
result.should.equal expected
done()
describe "when manipulating a single item through a pipeline", ->
start = 100
step1 = ( x, done ) -> done( x / 2 )
step2 = ( x, done ) -> done( x - 25 )
step3 = ( x, done ) -> done( x / 5 )
step4 = ( x, done ) -> done( x + 5 )
expected = 10
steps = [ step1, step2, step3, step4 ]
it "should run pipeline in order", ( done ) ->
scheduler.pipeline start, steps, ( result ) ->
result.should.equal expected
done()
describe "when mutating a single item through a pipeline", ->
start = "<1> [2] {3}"
step1 = ( x, done ) -> done( x.replace ///[<]1[>]///, "one" )
step2 = ( x, done ) -> done( x.replace ///[\[]2[\]]///, "two" )
step3 = ( x, done ) -> done( x.replace ///[\{]3[\}]///, "three" )
expected = "one two three"
steps = [ step1, step2, step3 ]
it "should run pipeline in order", ( done ) ->
scheduler.pipeline start, steps, ( result ) ->
result.should.equal expected
done()
describe "when running calls in parallel", ->
start = [ 2, 3, 4 ]
call = ( x, done ) -> done x * 2
expected = [ 4, 6, 8 ]
it "should return collection (in any order)", ( done ) ->
scheduler.parallel start, call, ( result ) ->
_.difference( result, expected ).length.should.equal 0
done()
describe "when aggregating multiple calls", ->
calls =
one: ( done ) -> setTimeout () ->
done 1
, 10
two: ( done ) -> setTimeout () ->
done 2
, 5
three: ( done ) -> setTimeout () ->
done 3
, 1
it "should complete with correctly constructed object", ( done ) ->
scheduler.aggregate calls, ( result ) ->
result.one.should.equal 1
result.two.should.equal 2
result.three.should.equal 3
done()

113
node_modules/anvil.js/next/src/anvil.js generated vendored Normal file
View file

@ -0,0 +1,113 @@
var anvilFactory = function( _, scheduler, fs, log, compiler, combiner ) {
var Anvil = function( ) {
this.conventions = {
defaultImportPatterns: {
"**/*.(js|coffee)": {
find: [ /([\/]{2}|[\#]{3}).?import.?[(]?.?['"].*["'].?[)]?[;]?.?([\#]{0,3})/g ],
replace: [ /([\/]{2}|[\#]{3}).?import.?[(]?.?['"]replace["'].?[)]?[;]?.?([\#]{0,3})/g ]
},
"**/*.(css|styl|less|stylus)": {
find: [ /([\/]{2}|[\/][*]).?import[(]?.?['"].*["'].?[)]?([*][\/])?/g ],
replace: [ ]
},
"**/*.(md|html)": {
find: [ /[<][!][-]{2}.?import[(]?.?['"].*["'].?[)]?.?[-]{2}[>]/g ],
replace: [ /[<][!][-]{2}.?import[(]?.?['"]replace["'].?[)]?.?[-]{2}[>]/g ]
}
},
defaultSiteBlock: {
source: "src",
style: "style",
markup: "markup",
output: {
source: [ "lib", "site/js" ],
style: [ "css", "site/css" ],
markup: "site/"
},
spec: "spec",
ext: "ext",
lint: {},
uglify: {},
cssmin: {},
hosts: {
"/": "site"
}
},
defaultLibBlock: {
source: "src",
output: "lib",
spec: "spec",
ext: "ext",
lint: {},
uglify: {},
hosts: {
"/": "site"
}
}
};
this.services = {};
this.combiner = combiner;
this.compiler = compiler;
this.preprocessors = {};
this.postprocessors = {};
this.buildState = {};
this.events = {};
this.inProcess = false;
_.bindAll( this );
};
Anvil.prototype.load = function() {
var self = this,
moduleSpecification;
_.each( this.extensions, function( extension ) {
moduleSpecification = extension.file || extension.module;
require( moduleSpecification )( _, scheduler, fs, log, self );
} );
};
Anvil.prototype.raise = function( eventName, data ) {
var handlers = this.events[ eventName ];
_.each( handlers, function( handler ) {
try {
handler.apply( arguments );
} catch( error ) {
// we don't need to do anything,
// but there's no reason to blow up
// just because a subscriber did
}
} );
};
Anvil.prototype.onConfiguration = function( config, stop ) {
this.configuration = config;
if( !stop ) {
// create load pipeline
// wire up services
}
};
Anvil.prototype.on = function( eventName, onEvent ) {
var handlers = this.events[ eventName ] || [];
handlers.push( onEvent );
};
return Anvil;
};
module.exports = anvilFactory;

44
node_modules/anvil.js/next/src/build.js generated vendored Normal file
View file

@ -0,0 +1,44 @@
var fileBootstrapFactory = function( _, fp, scheduler, minimatch ) {
var Bootstrapper = function( paths, inclusions, exclusions, callback ) {
var map = {};
this.inclusions = inclusions;
this.exclusions = exclusions;
this.callback = callback;
_.bindAll( this );
paths = _.isArray( paths ) ? paths : [ paths ];
_.each( paths, function( p ) {
map[ p ] = fp.getFiles;
} );
scheduler.mapped( map, this.onFiles );
};
Build.prototype.onFiles = function( fileLists ) {
var included = [],
exlcluded = [],
list;
_.each( this.inclusions, function( inclusion ) {
included.push( fileLists.filter( minimatch.filter( inclusion ) ) );
} );
_.each( this.exclusions, function( exclusion ) {
excluded.push( fileLists.filter( minimatch.filter( exclusion ) ) );
} );
list = _( included )
.chain()
.flatten()
.uniq()
.difference( excluded )
.value();
fileMap = {};
_.each( list, function( path ) {
fileMap[ path ] = this.createFileMachine;
}, this );
scheduler.mapped( fileMap, this.callback );
};
};
module.exports = fileBootStrapFactory;

25
node_modules/anvil.js/next/src/cli.js generated vendored Normal file
View file

@ -0,0 +1,25 @@
// # Cli
// Provides the command line interface for interacting with Anvil and related modules
var cliFactory = function( _, Anvil, Configuration ) {
// ## constructor
// Create the initial instance where all we really have is the
// configuration module to get us started.
var Cli = function() {
this.configuration = new Configuration();
_.bindAll( this );
};
// ## start
// Kicks off the configuration process
Cli.prototype.start = function() {
this.configuration( process.argv, function( config, done ) {
if( !done ) {
Anvil.configure( config );
}
} );
};
return Cli;
};
module.exports = cliFactory;

115
node_modules/anvil.js/next/src/combiner.js generated vendored Normal file
View file

@ -0,0 +1,115 @@
var combinerFactory = function( _, fp, scheduler ) {
var Combiner = function( findPatterns, replacePatterns ) {
this.findPatterns = findPatterns;
this.replacePatterns = replacePatterns;
_.bindAll( this );
};
Combiner.prototype.combine = function( file, onComplete ) {
var self = this,
steps = [],
imported;
if( !file.combined && file.imports.length > 0 ) {
_.each( file.imports, function( imported ) {
steps.push( this.getStep( imported ) );
}, this );
fp.read( [ file.workingPath, file.name ], function( main ) {
scheduler.pipeline( main, steps, function( result ) {
fp.write( [ file.workingPath, file.name ], result, onComplete );
} );
} );
} else {
onComplete();
}
};
Combiner.prototype.combineFile = function( file, onComplete ) {
var self = this,
dependencies = file.imports,
done = function() {
file.combined = true;
onComplete();
},
combine = function() {
self.combine( file, done );
};
if( file.combined ) {
onComplete();
} else if( dependencies && dependencies.length > 0 ) {
scheduler.parallel( dependencies, this.combineFile, combine );
} else {
combine();
}
};
Combiner.prototype.combineList = function( list, onComplete ) {
var self = this,
findImports = function( file, done ) {
self.findImports( file, list, done );
},
onImports = function() {
self.onImports( list, onComplete );
};
scheduler.parallel( list, findImports, onImports );
};
Combiner.prototype.findDependents = function( file, list ) {
var imported = function( importFile ) { return file.name === importFile.name; },
item;
_.each( list, function( item ) {
if( _.any( item.imports, imported ) ) {
file.dependents++;
}
} );
};
Combiner.prototype.getStep = function( imported ) {
var self = this;
return function( text, done ) {
self.replace( text, imported, done );
};
};
Combiner.prototype.onImports = function( list, onComplete ) {
_.each( list, function( file ) {
this.findDependents( file, list );
}, this );
scheduler.parallel( list, this.combineFile, onComplete );
};
Combiner.prototype.replace = function( content, imported, onComplete ) {
var self = this,
source = imported.name,
working = imported.workingPath;
fp.read( [ working, source ], function( newContent ) {
var steps = [],
pattern;
_.each( self.replacePatterns, function( pattern ) {
steps.push( function( current, done ) {
var stringified = pattern.toString().replace( /replace/, source ),
trimmed = stringified.substring( 1, stringified.length - 2 ),
newPattern = new RegExp( trimmed, "g" ),
capture = newPattern.exec( content ),
whiteSpace;
newContent = newContent.replace( "\$", "dollahr" )
if( capture && capture.length > 1 ) {
whiteSpace = capture[ 1 ];
newContent = whiteSpace + newContent.replace( /\n/g, "\n" + whiteSpace );
}
done( current.replace( newPattern, newContent ).replace( "dollahr", "$" ) );
} );
scheduler.pipeline( content, steps, onComplete );
} );
} );
};
return Combiner;
};
module.exports = combinerFactory;

43
node_modules/anvil.js/next/src/compiler.js generated vendored Normal file
View file

@ -0,0 +1,43 @@
var compilerFactory = function( _, fp, log ) {
var Compiler = function( ) {
this.extensionMap = {};
this.compilers = {};
};
Compiler.prototype.registerCompiler = function( fromExt, toExt, compile ) {
this.extensionMap[ fromExt ] = toExt;
this.compilers[ fromExt ] = compile;
};
Compiler.prototype.compile = function( file, onComplete ) {
var ext = file.ext(),
newExt = this.extensionMap[ ext ],
newFile = file.name.replace( ext, newExt ),
compiler = this.compilers[ ext ];
if( compiler && newExt ) {
log.onDebug( "Compiling " + file.name + " to " + newFile );
fp.transform(
[ file.workingPath, file.name ],
compiler,
[ file.workingPath, newFile ],
function( err ) {
if( !err ) {
file.name = newFile;
onComplete( file );
} else {
log.onError( "Error compiling " + file.name + ": \r\n " + err );
onComplete( file, err );
}
} );
} else {
log.onWarning( "No compilers registered for files of type " + ext );
onComplete( file );
}
};
return Compiler;
};
module.exports = compilerFactory;

14
node_modules/anvil.js/next/src/configuration.js generated vendored Normal file
View file

@ -0,0 +1,14 @@
var configurationFactory = function( _, FSProvider, Scheduler, Log ) {
var Configuration = function() {
_.bindAll( this );
};
return Configuration;
};
module.exports = configurationFactory;

91
node_modules/anvil.js/next/src/crawler.js generated vendored Normal file
View file

@ -0,0 +1,91 @@
var crawlerFactory = function( _, fs, path, scheduler ) {
var Crawler = function( ) {
_.bindAll( this );
};
Crawler.prototype.crawl = function( directory, onComplete ) {
var self = this,
fileList = [],
onContents = function( error, contents ) {
self.onContents( error, contents, fileList, onComplete );
};
if( directory && directory !== "" ) {
directory = path.resolve( directory );
fs.readdir( directory, onContents );
} else {
onComplete( fileList );
}
};
Crawler.prototype.classifyHandle = function( file, onComplete ) {
fs.stat( file, function( err, stat ) {
if( err ) {
onComplete( { file: file, err: err } );
} else {
onComplete( { file: file, isDirectory: stat.isDirectory() } );
}
} );
};
Crawler.prototype.classifyHandles = function( list, onComplete ) {
var self = this;
if( list && list.length > 0 ) {
scheduler.parallel( list, this.classifyHandle, function( classified ) {
self.onClassified( classified, onComplete );
} );
} else {
onComplete( [], [] );
}
};
Crawler.prototype.onClassified = function( classified, onComplete ) {
var files = [],
directories = [],
item;
for( item in classified ) {
if( item.isDirectory ) {
directories.push( item.file );
} else if( !item.error ) {
files.push( item.file );
}
}
onComplete( files, directories );
};
Crawler.prototype.onContents = function( error, contents, fileList, onComplete ) {
var self = this,
qualified =[],
onQualified = function( files, directories ) {
self.onQualified( files, directories, fileList, onComplete );
},
item;
if( !err && contents.length > 0 ) {
for( item in contents ) {
qualified.push( path.resolve( directory, item ) );
}
this.classifyHandles( qualified, onQualified );
} else {
onComplete( fileList );
}
};
Crawler.prototype.onQualified = function( files, directories, fileList, onComplete ) {
fileList = fileList.concat( files );
if( directories.length > 0 ) {
scheduler.parallel( directories, this.crawl, function( files ) {
fileList = fileList.concat( _.flatten( files ) );
onComplete( fileList );
} );
} else {
onComplete( fileList );
}
};
return Crawler;
};
module.exports = crawlerFactory;

130
node_modules/anvil.js/next/src/file.js generated vendored Normal file
View file

@ -0,0 +1,130 @@
var fileFactory = function( _, fs, path, mkdir, crawler ) {
var FileProvider = function() {
_.bindAll( this );
};
FileProvider.prototype.buildPath = function( pathSpec ) {
var fullPath = pathSpec || "";
if( _.isArray( pathSpec ) ) {
fullPath = path.join.apply( {}, pathSpec );
}
return fullPath;
};
FileProvider.prototype.copy = function( from, to, onComplete ) {
from = this.buildPath( from );
to = this.buildPath( to );
var toDir = path.dirname( to ),
readStream, writeStream;
this.ensurePath( to, function() {
writeStream = fs.createWriterStream( to );
( readStream = fs.createReadStream( from ) ).pipe( writeStream );
readStream.on( "end", function() {
if( writeStream ) {
writeStream.destroySoon();
}
onComplete( to );
} );
} );
};
FileProvider.prototype["delete"] = function( filePath, onDeleted ) {
filePath = this.buildPath( filePath );
if( this.pathExists( filePath ) ) {
fs.unlink( filePath, function( error ) {
onDeleted( error );
} );
}
};
FileProvider.prototype.ensurePath = function( fullPath, onComplete ) {
fullPath = this.buildPath( fullPath );
path.exists( fullPath, function( exists ) {
if( !exists ) {
mkdir( fullPath, "0755", function( error ) {
if( error ) {
onComplete( error );
} else {
onComplete();
}
} );
} else {
onComplete();
}
} );
};
FileProvider.prototype.getFiles = function( fullPath, onFiles ) {
var fullPath = this.buildPath( fullPath );
crawler.crawl( fullPath, onFiles );
};
FileProvider.prototype.metadata = function( fullPath, onStat ) {
fullPath = this.buildPath( fullPath );
try {
return fs.stat( fullPath, function( stat ) {
onStat( { lastModified: stat.mtime } );
} );
} catch ( err ) {
onStat( { error: err } );
}
}
FileProvider.prototype.pathExists = function( fullPath ) {
fullPath = this.buildPath( fullPath );
path.existsSync( fullPath );
};
FileProvider.prototype.read = function( fullPath, onContent ) {
fullPath = this.buildPath( fullPath );
fs.readFile( fullPath, "utf8", function( error, content ) {
if( error ) {
onContent( "", error );
} else {
onContent( content );
}
} );
};
FileProvider.prototype.readSync = function( fullPath ) {
fullPath = this.buildPath( fullPath );
try {
return fs.readFileSync( fullPath, "utf8" );
} catch( error ) {
return error;
}
};
FileProvider.prototype.transform = function( from, transform, to, onComplete ) {
from = this.buildPath( from );
to = this.buildPath( to );
var self = this;
this.read( from, function( content ) {
transform( content, function( modified, error ) {
if( !error ) {
self.write( to, modified, onComplete );
} else {
onComplete( error );
}
} );
} );
};
FileProvider.prototype.write = function( fullPath, content, onComplete ) {
fullPath = this.buildPath( fullPath );
fs.writeFile( fullPath, content, "utf8", function( error ) {
if( !error ) {
onComplete();
} else {
onComplete( error );
}
} );
};
return FileProvider;
};
module.exports = fileFactory;

125
node_modules/anvil.js/next/src/fileMachine.js generated vendored Normal file
View file

@ -0,0 +1,125 @@
var fileMachineFactory = function( _, fp, scheduler, postal, machina ) {
// ## FileMachine
// This state machine manages the pipeline and interactions
// for this file during the build.
// The filePath is the fully qualified or relative path to
// the file and the onComplete callback will receive a handle
// to the created file state machine.
var FileMachine = function( filePath, searchPatterns, replacePatterns, onCreated ) {
fp.stat( filePath, function( metadata ) {
var machine = new machina.Fsm( {
broadcast: postal.channel( "fileEvents" ),
dependents: 0,
ext: function() { return path.extname( this.name ); },
fullPath: path.resolve( filePath ),
imports: [],
initialState: "initilization",
lastModified: metadata.lastModified,
name: path.basename( filePath ),
relativePath: path.resolve( "./", filePath ),
replacePatterns: replacePatterns,
searchPatterns: searchPatterns,
watcher: fs.watch( filePath, this.onFileChange ),
workingPath: "",
currentFullPath: function() {
return fp.buildPath( [ this.workingPath, this.name ] );
},
onFileChange: function() {},
broadcastStep: function( step ) {
this.broadcast.publish( {
topic: this.currentFullPath(),
body: {
file: this,
event: step
}
} );
},
copy: function( to, onComplete ) {
var self = this;
fp.copy( [ this.workingPath, this.name ], to, function( newFull ) {
self.workingPath = path.dirname( newFull );
self.name = path.basename( newFull );
onComplete();
} );
},
findImports: function( onComplete ) {
var self = this,
sourcePath = this.currentFullPath(),
imports = [],
match;
fp.read( this.currentFullPath(), function( content ) {
_.each( self.searchPatterns, function( pattern ) {
if( ( match = content.match( pattern ) ) != undefined ) {
imports = imports.concat( match );
}
} );
_.each( imports, function ( imported ) {
var importName = imported.match( /['\"].*['\"]/ )[ 0 ].replace( /['\"]/g, "" );
var importPath = path.resolve( currentFullPath, importName );
if( path.existsSync( importPath ) ) {
self.imports.push( importPath );
}
} );
onComplete();
} );
},
transform: function( type, newName, transform, onComplete ) {
var self = this;
fp.transform(
[ this.workingPath, this.name ],
transform,
[ this.workingPath, newName ],
function( err ) {
if( !err ) {
file.name = newFile;
onComplete( file );
} else {
self.raiseEvent( {
type: type,
message: type + " failed for " + self.fullPath,
error: err
} );
onComplete( file, err );
}
} );
},
states: {
"initialization": {
_onEnter: function() {
_.bindAll( this );
this.broadcastStep("ready");
},
"copy": function( command ) {
this.copy( command.path, function() {
this.transition( "scanning" );
} );
}
},
"scanning": {
_onEnter: function() {
},
"imported": function() {
this.dependents++;
},
"*": function() {
}
}
}
} ) );
} );
};
modules.exports = fileMachineFactory;

32
node_modules/anvil.js/next/src/log.js generated vendored Normal file
View file

@ -0,0 +1,32 @@
var logFactory = function( options ) {
return {
onDebug: function( x ) {
if( options.debug ) {
console.log( x.purple );
}
},
onEvent: function( x ) {
if( !options.quiet ) {
console.log( "\t" + x );
}
},
onStep: function( x ) {
if( !options.quiet ) {
console.log( x.blue );
}
},
onComplete: function( x ) {
console.log( x.green );
},
onWarning: function( x ) {
if( !options.quiet ) {
console.log( x.orange );
}
},
onError: function( x ) {
console.log( ("\t" + x).red );
}
};
};
module.exports = logFactory;

80
node_modules/anvil.js/next/src/scheduler.js generated vendored Normal file
View file

@ -0,0 +1,80 @@
// # Scheduler
// Asynchronous abstractions
var schedulerFactory = function( _ ) {
function Scheduler() {
}
Scheduler.prototype.parallel = function( list, task, onComplete ) {
var length = 0,
index = 0,
results = [],
callback = function( result, resultIndex ){
results[ resultIndex ] = result;
if( --length === 0 ) {
onComplete( results );
}
},
input,
args;
// if the list of inputs is empty, then return an empty list.
if( !list || ( length = list.length ) === 0 ) {
onComplete( [] );
}
_.each( list, function( input ) {
task( input, function( result ) { callback( result, index ); } );
index++;
} );
};
Scheduler.prototype.mapped = function( map, onComplete ) {
var keys = _.keys( map ),
remaining = keys.length,
results = {},
callback = function( name, result ){
results[ name ] = result;
if( --remaining === 0 && firstPassComplete ) {
onComplete( results );
}
},
firstPassComplete;
_.each( keys, function( key ) {
map[ key ]( function( value ){ callback( key, value ); } );
} );
firstPassComplete = true;
// if the remaining count is 0, we're done
if( remaining === 0 ) {
onComplete( results );
}
};
Scheduler.prototype.pipeline = function( initial, transforms, onComplete ) {
var current = initial,
iterate = function iterate() {
transforms.shift()( current, done );
},
done = function done( result ) {
current = result;
if( transforms.length === 0 ) {
onComplete( current );
} else {
iterate();
}
};
if( !transforms || transforms.length === 0 ) {
onComplete( initial );
} else {
iterate( done );
}
};
return Scheduler;
};
module.exports = schedulerFactory;

1
node_modules/anvil.js/node_modules/.bin/_mocha generated vendored Symbolic link
View file

@ -0,0 +1 @@
../mocha/bin/_mocha

1
node_modules/anvil.js/node_modules/.bin/ape generated vendored Symbolic link
View file

@ -0,0 +1 @@
../ape/bin/ape

1
node_modules/anvil.js/node_modules/.bin/cake generated vendored Symbolic link
View file

@ -0,0 +1 @@
../coffee-script/bin/cake

1
node_modules/anvil.js/node_modules/.bin/coffee generated vendored Symbolic link
View file

@ -0,0 +1 @@
../coffee-script/bin/coffee

1
node_modules/anvil.js/node_modules/.bin/coffeekup generated vendored Symbolic link
View file

@ -0,0 +1 @@
../coffeekup/bin/coffeekup

1
node_modules/anvil.js/node_modules/.bin/cssmin generated vendored Symbolic link
View file

@ -0,0 +1 @@
../cssmin/bin/cssmin

1
node_modules/anvil.js/node_modules/.bin/haml-js generated vendored Symbolic link
View file

@ -0,0 +1 @@
../haml/lib/cli.js

1
node_modules/anvil.js/node_modules/.bin/jslint generated vendored Symbolic link
View file

@ -0,0 +1 @@
../readyjslint/bin/jslint.js

1
node_modules/anvil.js/node_modules/.bin/lessc generated vendored Symbolic link
View file

@ -0,0 +1 @@
../less/bin/lessc

1
node_modules/anvil.js/node_modules/.bin/marked generated vendored Symbolic link
View file

@ -0,0 +1 @@
../marked/bin/marked

1
node_modules/anvil.js/node_modules/.bin/mocha generated vendored Symbolic link
View file

@ -0,0 +1 @@
../mocha/bin/mocha

1
node_modules/anvil.js/node_modules/.bin/stylus generated vendored Symbolic link
View file

@ -0,0 +1 @@
../stylus/bin/stylus

1
node_modules/anvil.js/node_modules/.bin/uglifyjs generated vendored Symbolic link
View file

@ -0,0 +1 @@
../uglify-js/bin/uglifyjs

2
node_modules/anvil.js/node_modules/ape/.npmignore generated vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules
.DS_Store

19
node_modules/anvil.js/node_modules/ape/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
Programmed by Nathan LaFreniere, Copyright (c) 2012 &Yet
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

26
node_modules/anvil.js/node_modules/ape/README.md generated vendored Normal file
View file

@ -0,0 +1,26 @@
ape
===
API docs? Nope, ape docs!
ape generates API documentation in github-flavored-markup from comments in your code, and places them in line with the actual code. This allows for very easy integration with github.
Optionally, ape can also output to html with a built-in jade template, or one you specify.
See lib/template.jade for an example template, lib/ape.html and lib/ape.md are example output.
To install:
sudo npm install -g ape
And how to use:
Usage: node ./bin/ape [input file|directory list]
Options:
--md Output as markdown [boolean] [default: true]
--html Output as HTML [boolean] [default: false]
--template, -t Template for HTML output [string]
--output, -o Output directory [string]
Currently python, javascript, ruby, lua, coffeescript, C, C++, Perl, PHP, C#, ObjC, SQL, Bash, CSS, and ActionScript are supported. Feel free to submit a pull request for additional languages!

161
node_modules/anvil.js/node_modules/ape/lib/ape.html generated vendored Normal file
View file

@ -0,0 +1,161 @@
<!DOCTYPE html><head><style type="text/css">pre code{display:block;padding:.5em;color:black;background:#f8f8ff}pre .comment,pre .template_comment,pre .diff .header,pre .javadoc{color:#998;font-style:italic}pre .keyword,pre .css .rule .keyword,pre .winutils,pre .javascript .title,pre .lisp .title,pre .subst{color:black;font-weight:bold}pre .number,pre .hexcolor{color:#40a070}pre .string,pre .tag .value,pre .phpdoc,pre .tex .formula{color:#d14}pre .title,pre .id{color:#900;font-weight:bold}pre .javascript .title,pre .lisp .title,pre .subst{font-weight:normal}pre .class .title,pre .haskell .label,pre .tex .command{color:#458;font-weight:bold}pre .tag,pre .tag .title,pre .rules .property,pre .django .tag .keyword{color:navy;font-weight:normal}pre .attribute,pre .variable,pre .instancevar,pre .lisp .body{color:teal}pre .regexp{color:#009926}pre .class{color:#458;font-weight:bold}pre .symbol,pre .ruby .symbol .string,pre .ruby .symbol .keyword,pre .ruby .symbol .keymethods,pre .lisp .keyword,pre .tex .special,pre .input_number{color:#990073}pre .builtin,pre .built_in,pre .lisp .title{color:#0086b3}pre .preprocessor,pre .pi,pre .doctype,pre .shebang,pre .cdata{color:#999;font-weight:bold}pre .deletion{background:#fdd}pre .addition{background:#dfd}pre .diff .change{background:#0086b3}pre .chunk{color:#aaa}pre .tex .formula{opacity:.5}#docs {margin:auto}.block {clear:both}.comment {padding: 0 10px;width:50%;background:snow}.code {width:49%;background:#f8f8ff}td {vertical-align:top}table {width:90%; border-collapse:collapse}
</style></head><body><table id="docs"><tr class="block"><td class="comment"><h2>API docs? Nope, ape docs!</h2>
<hr />
<p>ape is a command line tool to generate documentation from your comments.<br />It parses your source code files, and strips markdown formatted comments out,<br />it then puts your code in github-flavored-markdown code blocks, and displays the comments in line.</p>
<p>It wasn't written to be fancy, but rather to have a simple, automated way of keeping docs on github up to date.</p>
<p>To use:</p>
<pre><code>sudo npm install -g ape
ape [list of files or directories]
</code></pre></td><td class="code"></td></tr><tr class="block"><td class="comment"><p>Require dependencies</p></td><td class="code"><pre><code><span class="keyword">var</span> fs = require(<span class="string">'fs'</span>),
path = require(<span class="string">'path'</span>),
gfm = require(<span class="string">'ghm'</span>),
hljs = require(<span class="string">'hljs'</span>),
jade = require(<span class="string">'jade'</span>);
</code></pre></td></tr><tr class="block"><td class="comment"><p>This function is a helper for frontends, to make it simpler to determine if a file can be processed by ape.<br />It returns a callback with a single boolean parameter indicating if the file is supported</p></td><td class="code"><pre><code>exports.supported = <span class="function"><span class="keyword">function</span> <span class="params">(filename, callback)</span> {</span>
<span class="keyword">var</span> lang = languages[path.extname(filename)];
<span class="keyword">if</span> (<span class="keyword">typeof</span> lang === <span class="string">'undefined'</span> ) {
callback(<span class="literal">false</span>);
} <span class="keyword">else</span> {
callback(<span class="literal">true</span>);
}
};
</code></pre></td></tr><tr class="block"><td class="comment"><p>A simple helper function to return the dictionary of comment regexs, determined by the file extension</p></td><td class="code"><pre><code>exports.get_language = <span class="function"><span class="keyword">function</span> <span class="params">(filename)</span> {</span>
<span class="keyword">var</span> lang = languages[path.extname(filename)];
<span class="keyword">return</span> lang;
}
</code></pre></td></tr><tr class="block"><td class="comment"><p>This is the main function to parse the line array of source code, and return a new line array<br />containing the formatted text</p></td><td class="code"><pre><code><span class="function"><span class="keyword">function</span> <span class="title">parse_code</span><span class="params">(code, lang, outputFormat, template, callback)</span> {</span>
<span class="keyword">var</span> parsed_code = [],
this_line,
in_comment,
in_code,
spaces,
commentblock = [],
codeblock = [],
tempblock = [];
<span class="keyword">if</span> (code &amp;&amp; <span class="keyword">typeof</span> code !== <span class="string">'string'</span>) code = code.toString().split(<span class="string">"\n"</span>);
<span class="keyword">if</span> (<span class="keyword">typeof</span> lang === <span class="string">'undefined'</span> || !code) <span class="keyword">return</span>;
<span class="function"><span class="keyword">function</span> <span class="title">pushblock</span><span class="params">()</span> {</span>
parsed_code.push({ code: codeblock.join(<span class="string">'\n'</span>), comment: commentblock.join(<span class="string">'\n'</span>) });
codeblock = [];
commentblock = [];
in_code = <span class="literal">false</span>;
}
<span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = code.length; i &lt; l; i++) {
this_line = code[i];
<span class="keyword">if</span> (this_line.match(lang.comment) &amp;&amp; !in_comment &amp;&amp; !this_line.match(<span class="regexp">/^#\!/</span>)) {
<span class="keyword">if</span> (in_code) pushblock();
commentblock.push(this_line.replace(lang.comment, <span class="string">''</span>))
} <span class="keyword">else</span> <span class="keyword">if</span> (this_line.match(lang.start) &amp;&amp; !in_comment) {
<span class="keyword">if</span> (lang.name === <span class="string">'python'</span> &amp;&amp; in_code) {
<span class="keyword">while</span> (codeblock[codeblock.length - <span class="number">1</span>].trim() !== <span class="string">''</span>) {
tempblock.push(codeblock.pop());
}
}
<span class="keyword">if</span> (in_code) pushblock();
<span class="keyword">if</span> (lang.name === <span class="string">'python'</span>) {
<span class="keyword">for</span> (<span class="keyword">var</span> ti = <span class="number">0</span>, tl = tempblock.length; ti &lt; tl; ti++) {
codeblock.push(tempblock.pop());
}
}
in_comment = <span class="literal">true</span>;
spaces = this_line.match(<span class="regexp">/^\s+/</span>);
<span class="keyword">if</span> (spaces) spaces = spaces[<span class="number">0</span>].length;
this_line = this_line.replace(lang.start, <span class="string">''</span>);
<span class="keyword">if</span> (this_line.match(lang.end)) {
this_line = this_line.replace(lang.end, <span class="string">''</span>);
in_comment = <span class="literal">false</span>;
}
<span class="keyword">if</span> (this_line.trim() !== <span class="string">''</span>) commentblock.push(this_line);
} <span class="keyword">else</span> <span class="keyword">if</span> (this_line.match(lang.end) &amp;&amp; in_comment) {
this_line = this_line.replace(lang.end, <span class="string">''</span>);
<span class="keyword">if</span> (this_line.trim() !== <span class="string">''</span>) commentblock.push(this_line);
in_comment = <span class="literal">false</span>;
} <span class="keyword">else</span> <span class="keyword">if</span> (this_line.trim() === <span class="string">''</span> &amp;&amp; !in_comment &amp;&amp; !in_code) {
pushblock();
} <span class="keyword">else</span> {
<span class="keyword">if</span> (in_comment) {
<span class="keyword">if</span> (lang.name === <span class="string">'python'</span>) this_line = this_line.substring(spaces);
commentblock.push(this_line);
} <span class="keyword">else</span> {
<span class="keyword">if</span> (!in_code &amp;&amp; this_line.trim() !== <span class="string">''</span>) in_code = <span class="literal">true</span>;
codeblock.push(this_line);
}
}
}
pushblock();
<span class="keyword">if</span> (outputFormat === <span class="string">'md'</span>) {
generate_md(parsed_code, lang, callback);
} <span class="keyword">else</span> <span class="keyword">if</span> (outputFormat === <span class="string">'html'</span>) {
generate_html(parsed_code, lang, template, callback);
}
}
</code></pre></td></tr><tr class="block"><td class="comment"><p>This is the exported method</p></td><td class="code"><pre><code>exports.generate_doc = parse_code;
</code></pre></td></tr><tr class="block"><td class="comment"><p>This function writes the parsed output to a markdown file, matching the original source's filename but changing the extension to .md</p></td><td class="code"><pre><code><span class="function"><span class="keyword">function</span> <span class="title">generate_md</span><span class="params">(parsed_code, language, callback)</span> {</span>
<span class="keyword">var</span> outfile,
outcode = <span class="string">''</span>;
<span class="keyword">for</span> (<span class="keyword">var</span> i = <span class="number">0</span>, l = parsed_code.length; i &lt; l; i++) {
<span class="keyword">if</span> (parsed_code[i].comment !== <span class="string">''</span>) outcode += parsed_code[i].comment + <span class="string">'\n\n'</span>;
<span class="keyword">if</span> (parsed_code[i].code !== <span class="string">''</span>) outcode += <span class="string">'```'</span> + language.name + <span class="string">'\n'</span> + parsed_code[i].code + <span class="string">'\n```\n\n'</span>;
}
callback(<span class="literal">null</span>, outcode);
}
</code></pre></td></tr><tr class="block"><td class="comment"><p>This function writes parsed output to html</p></td><td class="code"><pre><code><span class="function"><span class="keyword">function</span> <span class="title">generate_html</span><span class="params">(parsed_code, language, template, callback)</span> {</span>
<span class="keyword">var</span> outfile,
templatePath,
template;
<span class="keyword">if</span> (<span class="keyword">typeof</span> template === <span class="string">'undefined'</span>) {
templatePath = path.join(__dirname, <span class="string">'template.jade'</span>);
} <span class="keyword">else</span> {
templatePath = template;
}
template = fs.readFileSync(__dirname + <span class="string">'/template.jade'</span>, <span class="string">'utf-8'</span>);
<span class="keyword">var</span> fn = jade.compile(template);
callback(<span class="literal">null</span>, fn({ gfm: gfm, data: parsed_code, hljs: hljs, lang: language.name }));
}
</code></pre></td></tr><tr class="block"><td class="comment"><p>Here we define our supported languages. Each language is a dictionary, keyed on the file extension. Inside the dictionary<br />we have the the following items:</p>
<ul>
<li>'name': the identifier that we output to the markdown for code blocks</li>
<li>'comment': is a regex that will match a single line comment for the specific language, but does NOT include the text on the line, only the comment</li>
<li>'start': a regular expression to match the beginning of a multi-line commment block. 'start' should only match if it's on the beginning
of a line</li>
<li>'end': the partner regex to 'start' matching the end of a multi-line comment only if the match is at the end of a line.</li>
</ul></td><td class="code"><pre><code><span class="keyword">var</span> C_LINE_COMMENT = <span class="regexp">/^\s*\/\/\s?/</span>,
C_BLOCK_COMMENT_START = <span class="regexp">/^\s*\/\*\s?/</span>,
C_BLOCK_COMMENT_END = <span class="regexp">/\*\/\s*$/</span>,
HASH_LINE_COMMENT = <span class="regexp">/^\s*#\s?/</span>,
NEVER_MATCH = <span class="regexp">/a\bc/</span>;
<span class="keyword">var</span> languages = {
<span class="string">'.js'</span>: { name: <span class="string">'javascript'</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.py'</span>: { name: <span class="string">'python'</span>, comment: HASH_LINE_COMMENT, start: <span class="regexp">/^\s*\"\"\"\s?/</span>, end: <span class="regexp">/\"\"\"\s*$/</span> },
<span class="string">'.rb'</span>: { name: <span class="string">'ruby'</span>, comment: HASH_LINE_COMMENT, start: <span class="regexp">/^\s*\=begin\s?/</span>, end: <span class="regexp">/\=end\s*$/</span> },
<span class="string">'.lua'</span>: { name: <span class="string">'lua'</span>, comment: <span class="regexp">/^\s*--\s?/</span>, start: <span class="regexp">/^\s*--\[\[\s?/</span>, end: <span class="regexp">/--\]\]\s*$/</span> },
<span class="string">'.coffee'</span>: { name: <span class="string">'coffeescript'</span>, comment: <span class="regexp">/^\s*#(?!##)\s?/</span>, start: <span class="regexp">/^\s*###\s?/</span>, end: <span class="regexp">/###\s*$/</span> },
<span class="string">'.php'</span>: { name: <span class="string">'php'</span>, comment: <span class="regexp">/^\s*(?:#|\/\/\s?)/</span>, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.c'</span>: { name: <span class="literal">null</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.h'</span>: { name: <span class="literal">null</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.pl'</span>: { name: <span class="string">'perl'</span>, comment: HASH_LINE_COMMENT, start: NEVER_MATCH, end: NEVER_MATCH },
<span class="string">'.cpp'</span>: { name: <span class="string">'cpp'</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.cs'</span>: { name: <span class="string">'cs'</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.m'</span>: { name: <span class="string">'objectivec'</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.sql'</span>: { name: <span class="string">'sql'</span>, comment: <span class="regexp">/^\s*--\s?/</span>, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.sh'</span>: { name: <span class="string">'bash'</span>, comment: HASH_LINE_COMMENT, start: NEVER_MATCH, end: NEVER_MATCH },
<span class="string">'.css'</span>: { name: <span class="string">'css'</span>, comment: NEVER_MATCH, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END },
<span class="string">'.as'</span>: { name: <span class="string">'actionscript'</span>, comment: C_LINE_COMMENT, start: C_BLOCK_COMMENT_START, end: C_BLOCK_COMMENT_END }
};
</code></pre></td></tr></table></body>

Some files were not shown because too many files have changed in this diff Show more