mirror of
https://github.com/Hopiu/postal.js.git
synced 2026-03-16 22:20:23 +00:00
Code updated to v 0.7.0, prepping to tag
This commit is contained in:
parent
4c0aea72b9
commit
83c0464429
1467 changed files with 357548 additions and 1559 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
25
README.md
25
README.md
|
|
@ -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 #
|
||||
|
||||
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?
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
{
|
||||
"source" : "src/main",
|
||||
"output" : "lib/standard",
|
||||
"lint" : {},
|
||||
"uglify" : {},
|
||||
"extensions" : {
|
||||
"uglify" : "min"
|
||||
},
|
||||
"hosts" : {
|
||||
"/" : "./"
|
||||
},
|
||||
"port" : 8080
|
||||
}
|
||||
27
build.json
Normal file
27
build.json
Normal 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
|
||||
}
|
||||
Binary file not shown.
|
|
@ -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;
|
||||
} );
|
||||
Binary file not shown.
2
example/amd/js/libs/postal/postal.min.js
vendored
2
example/amd/js/libs/postal/postal.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
} );
|
||||
1
example/node/client/js/lib/postal.min.js
vendored
Normal file
1
example/node/client/js/lib/postal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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" } );
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
} )
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
2
example/standard/js/postal.min.js
vendored
2
example/standard/js/postal.min.js
vendored
File diff suppressed because one or more lines are too long
31
lib/amd/classic-resolver.js
Normal file
31
lib/amd/classic-resolver.js
Normal 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
1
lib/amd/classic-resolver.min.js
vendored
Normal 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})
|
||||
|
|
@ -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;
|
||||
} );
|
||||
2
lib/amd/postal.min.js
vendored
2
lib/amd/postal.min.js
vendored
File diff suppressed because one or more lines are too long
1
lib/classic-resolver.node.min.js
vendored
Normal file
1
lib/classic-resolver.node.min.js
vendored
Normal 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}}
|
||||
33
lib/node/classic-resolver.js
Normal file
33
lib/node/classic-resolver.js
Normal 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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
29
lib/standard/classic-resolver.js
Normal file
29
lib/standard/classic-resolver.js
Normal 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
1
lib/standard/classic-resolver.min.js
vendored
Normal 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)
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
2
lib/standard/postal.min.js
vendored
2
lib/standard/postal.min.js
vendored
File diff suppressed because one or more lines are too long
1
node_modules/.bin/anvil
generated
vendored
Symbolic link
1
node_modules/.bin/anvil
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../anvil.js/bin/anvil
|
||||
6
node_modules/anvil.js/.npmignore
generated
vendored
Normal file
6
node_modules/anvil.js/.npmignore
generated
vendored
Normal 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
239
node_modules/anvil.js/README.md
generated
vendored
Normal 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
15
node_modules/anvil.js/build.json
generated
vendored
Normal 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
64
node_modules/anvil.js/changelog.md
generated
vendored
Normal 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
11
node_modules/anvil.js/contributors.md
generated
vendored
Normal 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
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
186
node_modules/anvil.js/docs/stylesheets/docco.css
generated
vendored
Normal 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
26
node_modules/anvil.js/ext/anvilHook.js
generated
vendored
Normal 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
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
576
node_modules/anvil.js/ext/pavlov.js
generated
vendored
Normal 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
226
node_modules/anvil.js/ext/qunit.css
generated
vendored
Normal 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
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
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
22
node_modules/anvil.js/license.txt
generated
vendored
Normal 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
16
node_modules/anvil.js/next.json
generated
vendored
Normal 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
199
node_modules/anvil.js/next/ext/machina.js
generated
vendored
Normal 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
45
node_modules/anvil.js/next/ext/machina.postal.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/anvil.min.js
generated
vendored
Normal 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
25
node_modules/anvil.js/next/lib/cli.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/cli.min.js
generated
vendored
Normal 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
141
node_modules/anvil.js/next/lib/combiner.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/combiner.min.js
generated
vendored
Normal 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
43
node_modules/anvil.js/next/lib/compiler.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/compiler.min.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/configuration.min.js
generated
vendored
Normal 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
91
node_modules/anvil.js/next/lib/crawler.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/crawler.min.js
generated
vendored
Normal 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
32
node_modules/anvil.js/next/lib/log.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/log.min.js
generated
vendored
Normal 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
1
node_modules/anvil.js/next/lib/scheduler.min.js
generated
vendored
Normal 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
16
node_modules/anvil.js/next/next.json
generated
vendored
Normal 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
7
node_modules/anvil.js/next/spec/anvil.specs.coffee
generated
vendored
Normal 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
373
node_modules/anvil.js/next/spec/combiner.specs.coffee
generated
vendored
Normal 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
680
node_modules/anvil.js/next/spec/compiler.specs.coffee
generated
vendored
Normal 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
396
node_modules/anvil.js/next/spec/config.specs.coffee
generated
vendored
Normal 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()
|
||||
33
node_modules/anvil.js/next/spec/fileMachine.spec.coffee
generated
vendored
Normal file
33
node_modules/anvil.js/next/spec/fileMachine.spec.coffee
generated
vendored
Normal 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 ) ->
|
||||
17
node_modules/anvil.js/next/spec/fileProvider.int.coffee
generated
vendored
Normal file
17
node_modules/anvil.js/next/spec/fileProvider.int.coffee
generated
vendored
Normal 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
120
node_modules/anvil.js/next/spec/fsMock.coffee
generated
vendored
Normal 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
46
node_modules/anvil.js/next/spec/logMock.coffee
generated
vendored
Normal 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
82
node_modules/anvil.js/next/spec/scheduler.specs.coffee
generated
vendored
Normal 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
113
node_modules/anvil.js/next/src/anvil.js
generated
vendored
Normal 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
44
node_modules/anvil.js/next/src/build.js
generated
vendored
Normal 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
25
node_modules/anvil.js/next/src/cli.js
generated
vendored
Normal 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
115
node_modules/anvil.js/next/src/combiner.js
generated
vendored
Normal 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
43
node_modules/anvil.js/next/src/compiler.js
generated
vendored
Normal 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
14
node_modules/anvil.js/next/src/configuration.js
generated
vendored
Normal 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
91
node_modules/anvil.js/next/src/crawler.js
generated
vendored
Normal 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
130
node_modules/anvil.js/next/src/file.js
generated
vendored
Normal 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
125
node_modules/anvil.js/next/src/fileMachine.js
generated
vendored
Normal 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
32
node_modules/anvil.js/next/src/log.js
generated
vendored
Normal 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
80
node_modules/anvil.js/next/src/scheduler.js
generated
vendored
Normal 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
1
node_modules/anvil.js/node_modules/.bin/_mocha
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../mocha/bin/_mocha
|
||||
1
node_modules/anvil.js/node_modules/.bin/ape
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/ape
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../ape/bin/ape
|
||||
1
node_modules/anvil.js/node_modules/.bin/cake
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/cake
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../coffee-script/bin/cake
|
||||
1
node_modules/anvil.js/node_modules/.bin/coffee
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/coffee
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../coffee-script/bin/coffee
|
||||
1
node_modules/anvil.js/node_modules/.bin/coffeekup
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/coffeekup
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../coffeekup/bin/coffeekup
|
||||
1
node_modules/anvil.js/node_modules/.bin/cssmin
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/cssmin
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../cssmin/bin/cssmin
|
||||
1
node_modules/anvil.js/node_modules/.bin/haml-js
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/haml-js
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../haml/lib/cli.js
|
||||
1
node_modules/anvil.js/node_modules/.bin/jslint
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/jslint
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../readyjslint/bin/jslint.js
|
||||
1
node_modules/anvil.js/node_modules/.bin/lessc
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/lessc
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../less/bin/lessc
|
||||
1
node_modules/anvil.js/node_modules/.bin/marked
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/marked
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../marked/bin/marked
|
||||
1
node_modules/anvil.js/node_modules/.bin/mocha
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/mocha
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../mocha/bin/mocha
|
||||
1
node_modules/anvil.js/node_modules/.bin/stylus
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/stylus
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../stylus/bin/stylus
|
||||
1
node_modules/anvil.js/node_modules/.bin/uglifyjs
generated
vendored
Symbolic link
1
node_modules/anvil.js/node_modules/.bin/uglifyjs
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../uglify-js/bin/uglifyjs
|
||||
2
node_modules/anvil.js/node_modules/ape/.npmignore
generated
vendored
Normal file
2
node_modules/anvil.js/node_modules/ape/.npmignore
generated
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
.DS_Store
|
||||
19
node_modules/anvil.js/node_modules/ape/LICENSE
generated
vendored
Normal file
19
node_modules/anvil.js/node_modules/ape/LICENSE
generated
vendored
Normal 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
26
node_modules/anvil.js/node_modules/ape/README.md
generated
vendored
Normal 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
161
node_modules/anvil.js/node_modules/ape/lib/ape.html
generated
vendored
Normal 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 && <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 < l; i++) {
|
||||
this_line = code[i];
|
||||
<span class="keyword">if</span> (this_line.match(lang.comment) && !in_comment && !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) && !in_comment) {
|
||||
<span class="keyword">if</span> (lang.name === <span class="string">'python'</span> && 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 < 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) && 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> && !in_comment && !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 && 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 < 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
Loading…
Reference in a new issue